Review
CUED's Tutorial Guide to C++ Programming. What follows is a brief summaryKeywords
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
{
<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'.