Department of Engineering

IT Services

Review

CUED's Tutorial Guide to C++ Programming. What follows is a brief summary

Keywords

You haven't been introduced to all of these keywords before. Many you'll never need. They're included here for completeness because you can't use them for variable names, etc.

and and_eq asm auto bitand bitor
bool break case catch char class
compl const const_cast continue default delete
do double dynamic_cast else enum explicit
export extern false float for friend
goto if inline int long mutable
namespace new not not_eq operator or
or_eq private protected public register reinterpret_cast
return short signed sizeof static static_cast
struct switch template this throw true
try typedef typeid typename union unsigned
using virtual void volatile wchar_t while
xor xor_eq        

Built-in Types and Enumerations

There are integral types (char, short, int, long) which can be intended to hold signed (the default) or unsigned values. So for example ``unsigned char c'' creates a one-byte variable big enough to hold any integer from 0 to 255. There are also floating point types (float, double, long double). If you want an integer variable to be restricted to containing just a few specific values you can create an enumerated type

  enum user_type {ugrad, pgrad, staff, visitor};
  user_type u;

creates an enumerated type called user_type. u can only be set to the values 0, 1, 2 or 3 (or equivalently - and preferably - ugrad, pgrad, staff or visitor). The values can be set explicitly; for example

  enum user_type {ugrad=2, pgrad=7, staff=9, visitor=3};
  user_type u;

Enumerated types can be used in most places where integer types can be, though u++ for example isn't allowed.

Operators

The lines of the table are in order of precedence, so `a * b + 6' is interpreted as `(a * b) + 6'. When in doubt put brackets in!

The Associativity column shows how the operators group. E.g. `<' groups left to right, meaning that a<b<c is equivalent to (a < b) < c rather than a < (b < c). Both are pretty useless expressions - (a < b) evaluates to 1 or 0 depending on whether it's true or not.

Associativity Operator
left to right ::
left to right () [], ->, ., typeid, casts,
right to left ! (negation), ~ (bit-not)
  new, delete ++, --, - (unary) , * (unary), & (unary), sizeof
right to left (type)
left to right *, /, % (modulus)
left to right - +
left to right <<, >>
left to right <, <=, >, >=
left to right ==, !=
left to right & (bit-and), | (bit-or)
left to right ^ (bit-xor)
left to right && (logical and)
left to right || (logical or)
right to left ?:
right to left =, +=, -=, /=, %=, >>=, &=
left to right throw
left to right ,

Selection

if

...
if (i==3) // checking for equality; `!=' tests for inequality
   // no braces needed for a single statement
   j=4;
else{ 
  //  the braces are necessary if the 
  //  clause has more than one statement
   j=5;
   k=6;
}
...

switch

...
// switch is like a multiple 'if'. 
// The values that the switching variable is compared with
// have to be constants, or `default'.

switch(i){
case 1: printf("i is one\n");
        break; // if break wasn't here, this case will
               // fall through into the next.
case 2: printf("i is two\n");
        break;
default: printf("i is neither  one nor two\n");
        break;
}
...

Loops

Note that if you use the Standard Library's containers and vectors, explicit loops might not be needed very much.

while, do

...
while(i<30){    // test at top of loop
  something();
...
}

...
do {
   something();
} while (i<30); // test at bottom of loop
...

for

The `for' construction in C++ is very general. In its most common form it's much like for in other languages. The following loop starts with i set to 0 and carries on while i<5 is true, adding 1 to i each time round.

...
for(int i=0; i<5; i=i+1){ 
   something();
}
...

The general form of `for' is

for ([expression1]; [expression2]; [expression3])
     something();

where all the expressions are optional. The default value for expression2 (the while condition) is 1 (true). Essentially, the for loop is a while loop. The above for loop is equivalent to

...
expression1;         // initialisation
while (expression2){ // condition
   something();
   expression3;      // code done each iteration
};
...

E.g. the 2 fragments below are equivalent. `i' is set to 3, the loop is run once for i=3 and once for i=4, then iteration finishes when i=5.

for (int i = 3; i < 5; i=i+1)
   total = total + i;
int i = 3;
while(i < 5){
  total = total + i;
  i=i+1;
}

Within any of the above loop constructions, continue stops the current iteration and goes to the next and break stops the iterations altogether. E.g. in the following fragment 0 and 2 will be printed out.

...
int i=0;
while (i<5){
   if (i==1){
     i = i+1;
     continue;
   }
   if (i==3)
     break;
   printf("i = %d\n", i);
   i=i+1;
}
...

If you want a loop which only ends when break is done, you can use `while(true)' or `for(;;)'.

Aggregation

Variables of the same or different types can be bundled together into a single object.

arrays

Variables of the same type can be put into arrays.

char letter[50];

defines an array of 50 characters, letter[0] being the 1st and letter[49] being the last character. C++ arrays have no subscript checking; if you go off the end of an array C++ won't warn you.

Multidimensional arrays can be defined too. E.g.

char values[50][30][10];

defines a 3D array. Note that you can't access an element using values[3,6,1]; you have to type values[3][6][1].

The Standard Library offers various alternatives (vector, for example) which are often preferable to the basic arrays described above because

  • They can be made to grow on demand
  • Many routines for copying and modifying them exist.

structures and classes

Variables of different types can be grouped into a structure or a class.

class person {
public:
     int age;
     int height;
     string surname;
};

person fred, jane;

defines 2 objects of type person each of 3 fields. Fields are accessed using the `.' operator. For example, fred.age is an integer which can be used in assignments just as a simple variable can.

Structure and Class objects may be assigned, passed to functions and returned, but they cannot (by default) be compared, so continuing from the above fragment fred = jane; is possible (the fields of jane being copied into fred) but you can't then go on to do

if (fred == jane)
  cout << "The copying worked ok\n";

- you have to compare field by field.

Classes can contain member functions as well as data. The data can be made private so that other objects can't access it directly.

Pointers

Even if you don't use pointers yourself, the code you'll learn from is likely to have them. Suppose i is an integer. To find the address of i the & operator is used (&i). Setting a pointer to this value lets you refer indirectly to the variable i. If you have the address of a variable and want to find the variable's value, then the dereferencing operator * is used.

...
int i=0;
cout << i << " is at memory location " << &i << endl;

// The next statement declares i_ptr to be a pointer pointing  
// to an integer. The declaration says that if i_ptr is 
// dereferenced, one gets an int.
 
int *i_ptr;
i_ptr = &i; // initialise i_ptr to point to i

// The following 2 lines each set i to 5
i = 5;
*iptr = 5; // i.e. set to 5 the int that iptr points to

Pointers aren't just memory addresses; they have types. A pointer-to-an-int is of type int*, a different type to a pointer-to-a-char (which is char*). The difference matters especially when the pointer is being incremented; the value of the pointer is increased by the size of the object it points to. So if we added

     iptr=iptr+1;

in the above example, then iptr wouldn't be incremented by 1 (which would make it point somewhere in the middle of i) but by the length of an int, so that it would point to the memory location just beyond i. This is useful if i is part of an array. In the following fragment, the pointer steps through an array.

...
int numbers[10];

numbers[0] = 1;
numbers[1] = 2;
numbers[2] = 3;

int *iptr = &numbers[0]; // Point iptr to the first element in numbers[].
                    // A more common shorthand is   iptr = numbers; 
// now increment iptr to point to successive elements
for (int i=0; i<3; i++){
   cout << "*iptr is " << *iptr << endl;
   iptr= iptr+1;
}
...

Pointers are especially useful when functions operate on structures. Using a pointer avoids copies of potentially big structures being made.

class {
public:
     int age;
     int height;
     string surname;
} person;

person fred, jane;

int sum_of_ages(person *person1, person *person2){
int sum;  // a variable local to this function  

    // Dereference the pointers, then use the `.' operator to get the 
    // fields
    sum = (*person1).age + (*person2).age;
    return sum;
}

Operations like (*person1).age are so common that there's a special, more natural notation for it: person1->age.

Functions

The form of a function definition is

<function return type> <function name> ( <formal argument list> )
{
<local variables>

<body>

}

E.g.

int mean(int x, int y)
{
int tmp;

tmp = (x + y)/2;
return tmp;
}

or, if the function is a member of class stats, ::, the scope resolution operator can be used to specify which particular function of that name to access.

int stats::mean(int x, int y)
{
int tmp;

tmp = (x + y)/2;
return tmp;
}

You can provide default values for the trailing arguments. E.g.

int mean(int x, int y=5)
{
int tmp;

tmp = (x + y)/2;
return tmp;
}

...
int foo=mean(7)

is legal, setting foo to 6.

You can have more than function of a particular name provided that the compiler knows unambiguously which one is required for a function call, so the functions with identical names have to take different arguments or must only be visible one at a time. It's not enough for functions to differ only in the return type. By default, the variables given to a function won't have their values changed when the function returns. In the following fragment number won't be increased to 6.

void add (int x)
{
  x = x+1;
}

int number = 5;
add(number);

When add(number) is called, the x variable is set to the current value of number, then incremented, but number is unchanged. The following slight variation that uses ``call by reference'' will increase number

void add(int& x)
{
  x = x+1;
}

int number = 5;
add(number);

Declarations

First, a note on terminology. A variable is defined when it is created, and space is made for it. A variable is declared when it already exists but needs to be re-described to the compiler (perhaps because it was defined in another source file). Think of declaring in C++ like declaring at customs - admitting to the existence of something. In C++ you can only define a variable once but you can declare it many times.

C++ declarations are not easy to read. You shouldn't need to use complicated declarations so don't worry too much if you can't `decode' them. Keep a cribsheet of useful ones The following examples show common declarations.

int *p pointer to an int
int x[10] an array of 10 ints
const int *p pointer, p, to int; int can't be modified via p
int * const p a constant pointer to an int
int (*x)[10] a pointer to an array of 10 ints
int *x[10] array of 10 pointers to ints
int (*f)(int) pointer to a function taking and returning an int
void (*f)(void) pointer to a function taking no args and returning nothing
int (*f[])(int) An array of pointers to a functions taking and returning an int

Note the importance of the brackets in these declarations. On the Teaching System the c++decl program can help. E.g.

   c++decl explain "char *&x"

prints

   declare x as reference to pointer to char

typedef can be used to create a shorthand notation for a verbose type.

typedef int (*PFI)(int) // declare PFI as pointer to function that 
                        //   takes and returns an int.
PFI f[];  // declare f as an array of pointers of type 'PFI'.