Skip to Content

C++: Non-OO Features

C++ added some language features to C that are not directly related to Object Oriented ideas. The main ones are discussed below.

Non-OO Improvements in Function Calling

True Call-by-Reference: Using the & on a parameter type declaration (e.g., int&) declares the parameter as a reference to a variable. You no longer need to use cumbersome pointer syntax to do call-by-reference. Note in the function below, the parameters can be refered to as simple integer variables, not as pointers.

// call-by-reference swap function
void swap(int& a, int& b)
{
   int tmp;
   tmp = b;
   b = a;
   a = tmp;
   return;
}

C++ reference variables can be used elsewhere, too.

Default Arguments: starting at last parameter, default values can be specified in function prototype, and then if no arguments are given for those parameters in any call of the function, default values will be supplied. The function below could have calls with 1, 2, or 3 arguments given.

int add(int x, int y=12, int z=0);

int add(int x, int y, int z)
{
   return x+y+z;
}

Function Overloading: You can create multiple different functions of the same name, but only if they have different parameters. The compiler will figure out which one to call each time you write a function call in your program using that function name. E.g., you could create both of the functions that have the prototypes below:

int add(int x, int y);
double add(double x, double y);

Improvements in Memory Allocation and Deallocation

In plain C, memory allocation and deallocation is done using the library functions malloc() and free()

In C++, memory allocation and deallocation is done using the built-in operators new and delete.

C++ memory allocation is type-safe – that is, C++ knows what type you are creating and you can’t accidentally create the wrong-sized space for the type that you want.

Simple single-allocation form: p = new T; where p is a pointer of type T and T is either a built-in type or a user-defined type.

Simple single-deallocation form: delete p; where p is a pointer that points to a single allocated instance of its type T.

Array allocation form: p = new T[size]; where size is an integer expression (constant, variable, or more complex expression). This allocates size elements of type T.

Array deallocation form: delete[] p; where p is a pointer that points to an array of its type T.

Warning: delete will sometimes seem to correctly delete an array of elements, but you should not use it to do this. Always use delete[] if you have an array of elements to delete.

Warning: Do not mix C and C++-style allocation. Most especially do not use free() to deallocate memory that was allocated with new, and do not use delete to deallocated memory allocated by malloc().

Safe Programming: Using const in Parameter and Variable Declarations

The new keyword const allows you to tell the compiler that you are not allowed to modify a particular parameter or variable. (Note: newer C standards have also introduced the const keyword.)

Why? To protect you against yourself (or your teammates). Using const promotes safer interactions between functions and modules.

How to read declarations: As we have been doing with all type declarations, read the declaration backwards, starting with the variable name.

Forms:

const T v or T const v – this declares a call-by-value parameter v that is constant (i.e. you are not allowed to change it). This form is rarely used, since call-by-value parameters can be changed without affecting the rest of the program anyways.

T const *p – p is a pointer to a constant variable of type T. As above, this form can be reversed: const T *p == T const *p. The pointer itself can change, but you cannot modify the values that it points to. This is the most common form of constant parameter declaration. If you want to pass an array, or linked structure, or object, and not allow the function to modify the thing, use this form.

T * const p; – p is a constant pointer to T. This means that you cannot change the pointer itself, but you are allowed to modify what it points to. This is useful if you don’t want the function to make the pointer point to anything it wants, but you still want the function to be able to modify the value it points to.

T const * const p; – p is a constant pointer to a constant T. Both the pointer itself and the data it points to are not allowed to be changed.

Const can be used in variable declarations too. For example, you cannot do this:

void func(const char *s)
{
   char *tmp;
   tmp = s;
   ...

because s points to constant data, and you will lose the “const-ness” of it if you assign s to tmp. So you need to do:

void func(const char *s)
{
   const char *tmp;
   tmp = s;
   ...

so that the compiler can continue checking that you will not modify the data.

Also, you can use const to create real constants. The old C and new C++ methods for declaring constants are shown below:

// old C way 
#define MAX_SIZE 48

// new C++ way
const int MAX_SIZE=48;

The new C++ form allows the compiler to do more type checking, and it is more integrated into the programming language, rather than relying on the C preprocessor (which is what the macro definition #define does).

Const methods: save this information for later; it will be understandable when we get to OO (object-oriented) features.

  • Placing const after a method head and before the body means that it does not modify the object that it is called on.
  • Placing const in front of the return type means that the value returned cannot be modified.

C++ Input and Output

Rather than printf/scanf style functions, C++ has a new style of input and output, called iostreams.

To use iostreams, you have to do

#include <iostream>

Iostreams give you three standard channels, the standard input, called std::cin, the standard output, called std::cout, and the standard error output, called std::cerr.

Many people will do using namespace std; after the include line above, and then directly use the symbols cin, cout, and cerr without the std:: qualification. This is bad practice, because the namespace std has many many symbols that get imported in the using statement, and some may conflict with your own names. I strongly recommend just using std::cout, etc., rather than importing. If you really want to use the short versions, then do using namespace std::cout; and only import that single symbol (and then do the same for cin and cerr if you need to.

Output is done using the << operator, with std::cout or std::cerr (or other file stream handle) on the left, and then multiple right-hand arguments, each separated by <<.

The constant named std::endl represents a newline character. You can use it to end the current output line and start a new line, but newer practice actually prefers just using a \n character in a string.

The two lines below do the same thing:

std::cout << "my int var is " << i << " and my double var is " << d << std::endl;
std::cout << "my int var is " << i << " and my double var is " << d << "\n";

Input is done using the >> operator with std::cin on the left (or other input file stream handle), and multiple variables on the right. For example,

std::cin >> i >> d;

will input an integer value and then a double value, and assign them to the variables i and d, respectively.

Although it looks wierd, it is actually safer than the printf/scanf form, because the compiler can check and verify that the values being assigned to the variables are of the same type. A plain C compiler cannot do the same types of checks with printf/scanf.

Probably need lots more here….