0% found this document useful (0 votes)
76 views

Structure of This Course: 1. Types - Variables - Expressions & Statements

This document outlines the structure and content of a course on programming in C and C++. It covers types, variables, expressions, statements, functions, pointers, structures, references, namespaces, inheritance, templates and the standard template library. It provides examples of basic C programs and discusses basic C concepts like data types, constants, variables, operators, expressions, control flow, arrays and strings. It also briefly discusses more advanced C topics like scope, declarations vs definitions, functions, and the preprocessor.

Uploaded by

Byung-Sub Kim
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
76 views

Structure of This Course: 1. Types - Variables - Expressions & Statements

This document outlines the structure and content of a course on programming in C and C++. It covers types, variables, expressions, statements, functions, pointers, structures, references, namespaces, inheritance, templates and the standard template library. It provides examples of basic C programs and discusses basic C concepts like data types, constants, variables, operators, expressions, control flow, arrays and strings. It also briefly discusses more advanced C topics like scope, declarations vs definitions, functions, and the preprocessor.

Uploaded by

Byung-Sub Kim
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 45

Structure of this course

Programming in C:

C and C++
1. Types Variables Expressions & Statements Alastair R. Beresford
University of Cambridge

types, variables, expressions & statements functions, compilation, pre-processor pointers, structures extended examples, tick hints n tips

Programming in C++:

references, overloading, namespaces,C/C++ interaction operator overloading, streams, inheritence exceptions and templates standard template library

Lent Term 2008

1 / 24

2 / 24

Text books
There are literally hundreds of books written about C and C++; ve you might nd useful include:

Past Exam Questions


1993 Paper 5 Question 5 1994 Paper 5 Question 5 1995 Paper 5 Question 5 1996 Paper 6 Question 5 1997 Paper 5 Question 5 1998 Paper 6 Question 6 *

1993 Paper 6 Question 5 1994 Paper 6 Question 5 1995 Paper 6 Question 5

Eckel, B. (2000). Thinking in C++, Volume 1: Introduction to Standard C++ (2nd edition). Prentice-Hall. (http://www.mindview.net/Books/TICPP/ThinkingInCPP2e.html) Kernighan, B.W. & Ritchie, D.M. (1988). The C programming language (2nd edition). Prentice-Hall. Stroustrup, B. (1997). The C++ Programming Language (3rd edition). Addison Wesley Longman Stroustrup, B. (1994). The design and evolution of C++. Addison-Wesley. Lippman, S.B. (1996). Inside the C++ object model. Addison-Wesley.
3 / 24

1996 Paper 5 Question 5 (except part (f) setjmp) 1997 Paper 6 Question 5

1999 Paper 5 Question 5 * (rst two sections only) 2000 Paper 5 Question 5 * 2006 Paper 3 Question 4 * 2007 Paper 3 Question 4 2007 Paper 11 Question 3
* denotes CPL questions relevant to this course.
4 / 24

Context: from BCPL to Java

C is a low-level language
C uses low-level features: characters, numbers & addresses Operators work on these fundamental types No C operators work on composite types e.g. strings, arrays, sets Only static denition and stack-based local variables heap-based storage is implemented as a library There are no read and write primitives instead, these are implemented by library routines There is only a single control-ow no threads, synchronisation or coroutines

1966 Martin Richards developed BCPL 1969 Ken Thompson designed B 1972 Dennis Ritchies C 1979 Bjarne Stroustrup created C with Classes 1983 C with Classes becomes C++ 1989 Original C90 ANSI C standard (ISO adoption 1990) 1990 James Gosling started Java (initially called Oak) 1998 ISO C++ standard 1999 C99 standard (ISO adoption 1999, ANSI, 2000)

5 / 24

6 / 24

Classic rst example

Basic types

Compile with:
1 2 3 4 5 6 7

#include <stdio.h> int main(void) { printf("Hello, world\n"); return 0; }

$ cc example1.c

Execute program with:


$ ./a.out Hello, world $

C has a small and limited set of basic types: description (size) type char characters ( 8 bits) int integer values ( 16 bits, commonly one word) float single-precision oating point number double double-precision oating point number Precise size of types is architecture dependent Various type operators for altering type meaning, including: unsigned, long, short, const, static This means we can have types such as long int and unsigned char

7 / 24

8 / 24

Constants

Dening constant values

Numeric constants can be written in a number of ways: style example type char none none int number, character or es- 12 A \n \007 cape seq. long int number w/sux l or L 1234L float number with ., e or E 1.234e3F or 1234.0f and sux f or F double number with ., e or E 1.234e3 1234.0 long double number ., e or E and 1.234E3l or 1234.0L sux l or L Numbers can be expressed in octal by prexing with a 0 and hexadecimal with 0x; for example: 52=064=0x34

An enumeration can be used to specify a set of constants; e.g.: enum boolean {FALSE, TRUE}; By default enumerations allocate successive integer values from zero It is possible to assign values to constants; for example:
enum months {JAN=1,FEB,MAR} enum boolean {F,T,FALSE=0,TRUE,N=0,Y}

Names for constants in dierent enums must be distinct; values in the same enum need not The preprocessor can also be used (more on this later)

9 / 24

10 / 24

Variables

Operators

Variables must be dened (i.e. storage set aside) exactly once A variable name can be composed of letters, digits and underscore (_); a name must begin with a letter or underscore Variables are dened by prexing a name with a type, and can optionally be initialised; for example: long int i = 28L; Multiple variables of the same basic type can be dened together; for example: char c,d,e;

All operators (including assignment) return a result Most operators are similar to those found in Java: type arithmetic logic bitwise assignment other operators
+ - * / ++ -- % == != > >= < <= || && ! | & << >> ^ ~ = += -= *= /= %= <<= >>= &= |= ^= sizeof

11 / 24

12 / 24

Type conversion
Automatic type conversion may occur when two operands to a binary operator are of a dierent type Generally, conversion widens a variable (e.g. short int) However narrowing is possible and may not generate a compiler warning; for example:
1 2 3

Expressions and statements

An expression is created when one or more operators are combined; for example x *= y % z Every expression (even assignment) has a type and a result Operator precedence provides an unambiguous interpretation for every expression An expression (e.g. x=0) becomes a statement when followed by a semicolon (i.e. x=0;) Several expressions can be separated using a comma ,; expressions are then evaluated left to right; for example: x=0,y=1.0 The type and value of a comma-separated expression is the type and value of the result of the right-most expression

int i = 1234; char c; c = i+1; /* i overflows c */

Type conversion can be forced by using a cast, which is written as: (type) exp; for example: c = (char) 1234L;

13 / 24

14 / 24

Blocks or compound statements

Variable scope

A block or compound statement is formed when multiple statements are surrounded with braces ({ }) A block of statements is then equivalent to a single statement In ANSI/ISO C90, variables can only be declared or dened at the start of a block (this restriction was lifted in ANSI/ISO C99) Blocks are typically associated with a function denition or a control ow statement, but can be used anywhere

Variables can be dened outside any function, in which case they:


are often called global or static variables have global scope and can be used anywhere in the program consume storage for the entire run-time of the program are initialised to zero by default are often called local or automatic variables can only be accessed from denition until the end of the block are only allocated storage for the duration of block execution are only initialised if given a value; otherwise their value is undened

Variables dened within a block (e.g. function):


15 / 24

16 / 24

Variable denition versus declaration

Scope and type example


1 2 3

#include <stdio.h> int a; unsigned char b = A; extern int alpha; /*what value does a have? */ /* safe to use this? */

A variable can be declared but not dened using the extern keyword; for example extern int a; The declaration tells the compiler that storage has been allocated elsewhere (usually in another source le) If a variable is declared and used in a program, but not dened, this will result in a link error (more on this later)

4 5 6 7 8 9 10 11 12 13 14 15 16

int main(void) { extern unsigned char b; /* is this needed? double a = 3.4; { extern a; /*why is this sloppy? printf("%d %d\n",b,a+1); /*what will this print? } return 0; }

*/

*/ */

17 / 24

18 / 24

Arrays and strings

Control ow

One or more items of the same type can be grouped into an array; for example: long int i[10]; The compiler will allocate a contiguous block of memory for the relevant number of values Array items are indexed from zero, and there is no bounds checking Strings in C are usually represented as an array of chars, terminated with a special character \0 There is compiler support for string constants using the " character; for example:
char str[]="two strs mer" "ged and terminated"

Control ow is similar to Java:


String support is available in the string.h library

exp ? exp : exp if (exp) stmt 1 else stmt 2 switch(exp) { case exp 1 : stmt 1 ... default: stmt n+1 } while (exp) stmt for ( exp 1 ; exp 2 ; exp 3 ) stmt do stmt while (exp);

The jump statements break and continue also exist

19 / 24

20 / 24

Control ow and string example


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Goto (considered harmful)


#include <stdio.h> #include <string.h> char s[]="University of Cambridge Computer Laboratory"; int main(void) { char c; int i, j; for (i=0,j=strlen(s)-1;i<j;i++,j--) /* strlen(s)-1 ? */ c=s[i], s[i]=s[j], s[j]=c; printf("%s\n",s); return 0; }

The goto statement is never required It often results in code which is hard to understand and maintain Exception handling (where you wish to exit or break from two or more loops) may be one case where a goto is justied:
1 2 3 4 5 6 7 8 9

for (...) { for (...) { ... if (critical_problem) goto error; } } ... error:

x problem, or abort
21 / 24 22 / 24

Exercises

1. What is the dierence between a and "a"? 2. Will char i,j; for(i=0;i<10,j<5;i++,j++) ; terminate? If so, under what circumstances? 3. Write an implementation of bubble sort for a xed array of integers. (An array of integers can be dened as int i[] = {1,2,3,4}; the 2nd integer in an array can be printed using printf("%d\n",i[1]);.) 4. Modify your answer to (3) to sort characters into lexicographical order. (The 2nd character in a character array i can be printed using printf("%c\n",i[1]);.)

23 / 24

Functions

C and C++
2. Functions Preprocessor Alastair R. Beresford
University of Cambridge

C does not have objects, but does have function support A function denition has a return type, parameter specication, and a body or statement; for example: int power(int base, int n) { stmt } A function declaration has a return type and parameter specication followed by a semicolon; for example:
int power(int base, int n); The use of the extern keyword for function declarations is optional

Lent Term 2008

1 / 18

All arguments to a function are copied, i.e. passed-by-value; modication of the local value does not aect the original Just as for variables, a function must have exactly one denition and can have multiple declarations A function which is used but only has a declaration, and no denition, results in a link error (more on this later) Functions cannot be nested
2 / 18

Function type-system nasties

Recursion
Functions can call themselves recursively On each call, a new set of local variables are created Therefore, a function recursion of depth n has n sets of variables Recursion can be useful when dealing with recursively dened data structures, like trees (more on such data structures later) Recursion can also be used as you would in ML:
1 2 3 4

A function denition with no values (e.g. power()) is not an empty parameter specication, rather it means that its arguments should not be type-checked! (this is not the case in C++) Instead, a function with no arguments is declared using void An ellipsis (...) can be used for partial parameter specication, for example: int printf(char* fmt,...) { stmt } The ellipsis is useful for dening functions with variable length arguments, but leaves a hole in the type system (stdarg.h) In comparison, C++ uses operator overloading to provide better I/O type safety (more on this later)

unsigned int fact(unsigned int n) { return n ? n*fact(n-1) : 1; }

3 / 18

4 / 18

Compilation

Handling code in multiple les in C

A compiler transforms a C source le or execution unit into an object le An object le consists of machine code, and a list of:

C separates declaration from denition for both variables and functions This allows portions of code to be split across multiple les Code in dierent les can then be compiled at dierent times

dened or exported symbols representing dened function names and global variables undened or imported symbols for functions and global variables which are declared but not dened combining all object code into a single le adjusting the absolute addresses from each object le resolving all undened symbols

This allows libraries to be compiled once, but used many times It also allows companies to sell binary-only libraries

A linker combines several object les into an executable by:


In order to use code written in another le we still need a declaration A header le can be used to:

The Part 1b Compiler Course describes how to build a compiler and linker in more detail

supply the declarations of function and variable denitions in another le provide preprocessor macros (more on this later) avoid duplication (and errors) that would otherwise occur

You might nd the Unix tool nm useful for inspecting symbol tables

5 / 18

6 / 18

Multi-source le example
Header File example4.h 1 /*reverse a string in place */ 2 void reverse(char str[]);

Variable and function scope with static

Source File example4a.c 1 #include <string.h> 2 #include "example4.h"


3 4 5 6 7 8 9 10

Source File example4b.c 1 #include <stdio.h> 2 #include "example4.h"


3 4 5 6 7 8 9 10

The static keyword limits the scope of a variable or function In the global scope, static does not export the function or variable symbol

This prevents the variable or function from being called externally

/*reverse a string in place */ void reverse(char s[]) { int c, i, j; for (i=0,j=strlen(s)-1; i<j;i++,j--) c=s[i], s[i]=s[j], s[j]=c; }

int main(void) { char s[] = "Reverse me"; reverse(s); printf("%s\n",s); return 0; }

In the local scope, a static variable retains its value between function calls

A single static variable exists even if a function call is recursive

7 / 18

8 / 18

C Preprocessor

Controlling the preprocessor programmatically

The preprocessor is executed before any compilation takes place It manipulates the textual content of the source le in a single pass Amongst other things, the preprocessor:

The preprocessor can be used by the programmer to rewrite source code This is a powerful (and, at times, useful) feature, but can be hard to debug (more on this later) The preprocessor interprets lines starting with # with a special meaning Two text substitution directives: #include and #define Conditional directives: #if, #elif, #else and #endif

deletes each occurrence of a backslash followed by a newline; replaces comments by a single space; replaces denitions, obeys conditional preprocessing directives and expands macros; and it replaces escaped sequences in character constants and string literals and concatenates adjacent string literals

9 / 18

10 / 18

The #include directive


The #dene directive

The #include directive performs text substitution It is written in one of two forms:
#include "lename" #include <lename>

The #define directive has the form: #define name replacement text The directive performs a direct text substitution of all future examples of name with the replacement text for the remainder of the source le The name has the same constraints as a standard C variable name Replacement does not take place if name is found inside a quoted string By convention, name tends to be written in upper case to distinguish it from a normal variable name

Both forms replace the #include ... line in the source le with the contents of lename The quote (") form searches for the le in the same location as the source le, then searches a predened set of directories The angle (<) form searches a predened set of directories When a #included le is changed, all source les which depend on it should be recompiled

11 / 18

12 / 18

Dening macros

Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

The #define directive can be used to dene macros as well; for example: #define MAX(A,B) ((A)>(B)?(A):(B)) In the body of the macro:

#include <stdio.h> #define #define #define #define #define PI 3.141592654 MAX(A,B) ((A)>(B)?(A):(B)) PERCENT(D) (100*D) /* Wrong? */ DPRINT(D) printf(#D " = %g\n",D) JOIN(A,B) (A ## B)

prexing a parameter in the replacement text with # places the parameter value inside string quotes (") placing ## between two parameters in the replacement text removes any whitespace between the variables in generated output This means that syntax analysis and type checking doesnt occur until the compilation stage This can, initially at least, generate some confusing compiler warnings on line numbers where the macro is used, rather than when it is dened; for example: #define JOIN(A,B) (A ## B))

Remember: the preprocessor only performs text substitution

int main(void) { const unsigned int a1=3; const unsigned int i = JOIN(a,1); printf("%u %g\n",i, MAX(PI,3.14)); DPRINT(MAX(PERCENT(0.32+0.16),PERCENT(0.15+0.48))); return 0; }
14 / 18

13 / 18

Conditional preprocessor directives


Conditional directives: #if, #ifdef, #ifndef, #elif and #endif

Example
Conditional directives have several uses, including preventing double denitions in header les and enabling code to function on several dierent architectures; for example:
1 2 3 4 5 6 7 8

The preprocessor can use conditional statements to include or exclude code in later phases of compilation
#if accepts a (somewhat limited) integer expression as an argument and only retains the code between #if and #endif (or #elif) if the

expression evaluates to a non-zero value; for example:


#if SOME_DEF > 8 && OTHER_DEF != THIRD_DEF

The built-in preprocessor function defined accepts a name as its sole argument and returns 1L if the name has been #defined; 0L otherwise
#ifdef N and #ifndef N are equivalent to #if defined(N) and #if !defined(N) respectively #undef can be used to remove a #defined name from the

#if SYSTEM_SYSV #define HDR "sysv.h" #elif SYSTEM_BSD #define HDR "bsd.h" #else #define HDR "default.h" #endif #include HDR

1 2 3 4 5 6

#ifndef MYHEADER_H #define MYHEADER_H 1 ... /* declarations & defns */ ... #endif /* !MYHEADER_H */

preprocessor macro and variable namespace.


15 / 18 16 / 18

Error control

Exercises

To help other compilers which generate C code (rather than machine code) as output, compiler line and lename warnings can be overridden with: #line constant "lename" The compiler then adjusts its internal value for the next line in the source le as constant and the current name of the le being processed as lename ("lename" may be omitted) The statement #error some text causes the preprocessor to write a diagnostic message containing some text There are several predened identiers that produce special information: __LINE__, __FILE__, __DATE__, and __TIME__.

1. Write a function denition which matches the declaration int cntlower(char str[]);. The implementation should return the number of lower-case letters in a string 2. Use function recursion to write an implementation of merge sort for a xed array of integers; how much memory does your program use for a list of length n? 3. Dene a macro SWAP(t,x,y) that exchanges two arguments of type t
(K&R, Exercise 4-14)

4. Dene a macro SWAP(x,y) that exchanges two arguments of the same type (e.g. int or char) without using a temporary

17 / 18

18 / 18

Pointers

Computer memory is often abstracted as a sequence of bytes, grouped into words Each byte has a unique address or index into this sequence The size of a word (and byte!) determines the size of addressable memory in the machine A pointer in C is a variable which contains the memory address of another variable (this can, itself, be a pointer) Pointers are declared or dened using an asterisk(*); for example: char *pc; or int **ppi; The asterisk binds to the variable name, not the type denition; for example char *pc,c; A pointer does not necessarily take the same amount of storage space as the type it points to
2 / 25

C and C++
3. Pointers Structures Alastair R. Beresford
University of Cambridge

Lent Term 2008

1 / 25

Example
int **ppi char *pc int *pi

Manipulating pointers

char c

int i

The value pointed to by a pointer can be retrieved or dereferenced by using the unary * operator; for example:
int *p = ... int x = *p;

00 ... 00 00 38 0x2c 0x30 0x34

00 00 00 4c 0x38 ...

52 1c 42 05 0x4c

00 00 00 62 0x50 0x60 41 41 Little ... Big

The memory address of a variable is returned with the unary ampersand (&) operator; for example
int *p = &x;

Dereferenced pointer values can be used in normal expressions; for example: *pi += 5; or (*pi)++

3 / 25

4 / 25

Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14

Pointers and arrays

#include <stdio.h>

int main(void) { int x=1,y=2; int *pi; int **ppi; pi = &x; ppi = &pi; printf("%p, %p, %d=%d=%d\n",ppi,pi,x,*pi,**ppi); pi = &y; printf("%p, %p, %d=%d=%d\n",ppi,pi,y,*pi,**ppi); return 0; }

A C array uses consecutive memory addresses without padding to store data An array name (without an index) represents the memory address of the beginning of the array; for example:
char c[10]; char *pc = c;

Pointers can be used to index into any element of an array; for example:
int i[10]; int *pi = &i[5];

5 / 25

6 / 25

Pointer arithmetic

Example

Pointer arithmetic can be used to adjust where a pointer points; for example, if pc points to the rst element of an array, after executing pc+=3; then pc points to the fourth element A pointer can even be dereferenced using array notation; for example pc[2] represents the value of the array element which is two elements beyond the array element currently pointed to by pc In summary, for an array c, *(c+i)c[i] and c+i&c[i] A pointer is a variable, but an array name is not; therefore pc=c and pc++ are valid, but c=pc and c++ are not

1 2 3 4 5 6 7 8 9 10 11 12

#include <stdio.h> int main(void) { char str[] = "A string."; char *pc = str; printf("%c %c %c\n",str[0],*pc,pc[3]); pc += 2; printf("%c %c %c\n",*pc, pc[2], pc[5]); return 0; }

7 / 25

8 / 25

Pointers as function arguments

Example

Recall that all arguments to a function are copied, i.e. passed-by-value; modication of the local value does not aect the original In the second lecture we dened functions which took an array as an argument; for example void reverse(char s[]) Why, then, does reverse aect the values of the array after the function returns (i.e. the array values havent been copied)?

Compare swp1(a,b) with swp2(&a,&b):


void swp2(int *px,int *py) { int temp = *px; *px = *py; *py = temp; }

1 2 3 4 5 6

because s is a pointer to the start of the array

Pointers of any type can be passed as parameters and return types of functions Pointers allow a function to alter parameters passed to it

void swp1(int x,int y) { int temp = x; x = y; y = temp; }

1 2 3 4 5 6

9 / 25

10 / 25

Arrays of pointers

Example

C allows the creation of arrays of pointers; for example


int *a[5];

argv: argc: 3

argv[0] argv[1] argv[2] argv[3] NULL

progname\0 firstarg\0 secondarg\0

Arrays of pointers are particularly useful with strings An example is C support of command line arguments:
int main(int argc, char *argv[]) { ... }

In this case argv is an array of character pointers, and argc tells the programmer the length of the array

11 / 25

12 / 25

Multi-dimensional arrays

Pointers to functions

Multi-dimensional arrays can be declared in C; for example:


int i[5][10];

Values of the array can be accessed using square brackets; for example: i[3][2] When passing a two dimensional array to a function, the rst dimension is not needed; for example, the following are equivalent:
void f(int i[5][10]) { ... } void f(int i[][10]) { ... } void f(int (*i)[10]) { ... }

C allows the programmer to use pointers to functions This allows functions to be passed as arguments to functions For example, we may wish to parameterise a sort algorithm on dierent comparison operators (e.g. lexicographically or numerically) If the sort routine accepts a pointer to a function, the sort routine can call this function when deciding how to order values

In arrays with higher dimensionality, all but the rst dimension must be specied

13 / 25

14 / 25

Example
1 2 3 4 5 6 7 8 9 10 11 12 13

Example
1 2 3 4 5 6 7 8 9 10 11

void sort(int a[], const int len, int (*compare)(int, int)) { int i,j,tmp; for(i=0;i<len-1;i++) for(j=0;j<len-1-i;j++) if ((*compare)(a[j],a[j+1])) tmp=a[j], a[j]=a[j+1], a[j+1]=tmp; } int inc(int a, int b) { return a > b ? 1 : 0; }

#include <stdio.h> #include "example8.h" int main(void) { int a[] = {1,4,3,2,5}; unsigned int len = 5; sort(a,len,inc); //or sort(a,len,&inc); int *pa = a; //C99 printf("["); while (len--) printf("%d%s",*pa++,len?" ":""); printf("]\n"); return 0; }
16 / 25

12 13 14 15 16

15 / 25

The void * pointer

Structure declaration

C has a typeless or generic pointer: void *p This can be a pointer to anything This can be useful when dealing with dynamic memory Enables polymorphic code; for example:
1 2

A structure is a collection of one or more variables It provides a simple method of abstraction and grouping A structure may itself contain structures A structure can be assigned to, as well as passed to, and returned from functions We declare a structure using the keyword struct For example, to declare a structure circle we write
struct circle {int x; int y; unsigned int r;};

sort(void *p, const unsigned int len, int (*comp)(void *,void *));

However this is also a big hole in the type system Therefore void * pointers should only be used where necessary

Once declared, a structure creates a new type

17 / 25

18 / 25

Structure denition

Member access

To dene an instance of the structure circle we write


struct circle c;

A structure member can be accessed using . notation: structname.member; for example: pt.x Comparison (e.g. pt1 > pt2) is undened Pointers to structures may be dened; for example:
struct circle *pc

A structure can also be initialised with values:


struct circle c = {12, 23, 5};

An automatic, or local, structure variable can be initialised by function call:


struct circle c = circle_init();

A structure can declared, and several instances dened in one go:


struct circle {int x; int y; unsigned int r;} a, b;

When using a pointer to a struct, member access can be achieved with the . operator, but can look clumsy; for example: (*pc).x Alternatively, the -> operator can be used; for example: pc->x

19 / 25

20 / 25

Self-referential structures
A structure declaration can contain a member which is a pointer whose type is the structure declaration itself This means we can build recursive data structures; for example:

Unions

A union variable is a single variable which can hold one of a number of dierent types A union variable is declared using a notation similar to structures; for example: union u { int i; float f; char c;}; The size of a union variable is the size of its largest member The type held can change during program execution The type retrieved must be the type most recently stored Member access to unions is the same as for structures (. and ->) Unions can be nested inside structures, and vice versa

1 2 3 4 5

struct tree { int val; struct tree *left; struct tree *right; }

1 2 3 4

struct link { int val; struct link *next; }

21 / 25

22 / 25

Bit elds

Example
1

(adapted from K&R)

Bit elds allow low-level access to individual bits of a word Useful when memory is limited, or to interact with hardware A bit eld is specied inside a struct by appending a declaration with a colon (:) and number of bits; for example:
struct fields { int f1 : 2; int f2 : 3;};

2 3 4 5 6 7 8 9 10 11 12 13 14 15

Members are accessed in the same way as for structs and unions A bit eld member does not have an address (no & operator) Lots of details about bit elds are implementation specic:

word boundary overlap & alignment, assignment direction, etc.

struct { /* a compiler symbol table */ char *name; struct { unsigned int is_keyword : 1; unsigned int is_extern : 1; unsigned int is_static : 1; ... } flags; int utype; union { int ival; /* accessed as symtab[i].u.ival */ float fval; char *sval; } u; } symtab[NSYM];

23 / 25

24 / 25

Exercises

1. If p is a pointer, what does p[-2] mean? When is this legal? 2. Write a string search function with a declaration of char *strfind(const char *s, const char *f); which returns a pointer to rst occurrence of s in f (and NULL otherwise) 3. If p is a pointer to a structure, write some C code which uses all the following code snippets: ++p->i, p++->i, *p->i, *p->i++, (*p->i)++ and *p++->i; describe the action of each code snippet 4. Write a program calc which evaluates a reverse Polish expression given on the command line; for example $ calc 2 3 4 + * should print 14 (K&R Exercise 5-10)

25 / 25

Uses of const and volatile

Any declaration can be prexed with const or volatile A const variable can only be assigned a value when it is dened The const declaration can also be used for parameters in a function denition The volatile keyword can be used to state that a variable may be changed by hardware, the kernel, another thread etc.

C and C++
4. Misc. Libary Features Gotchas Hints n Tips Alastair R. Beresford
University of Cambridge

Lent Term 2008

For example, the volatile keyword may prevent unsafe compiler optimisations for memory-mapped input/output The use of pointers and the const keyword is quite subtle: const int *p is a pointer to a const int int const *p is also a pointer to a const int int *const p is a const pointer to an int const int *const p is a const pointer to a const int

1 / 22

2 / 22

Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

Typedefs

int main(void) { int i = 42; int j = 28; const int *pc = &i; *pc = 41; pc = &j; int *const cp = &i; *cp = 41; cp = &j; //Also: "int const *pc" //Wrong

The typedef operator, creates new data type names; for example, typedef unsigned int Radius; Once a new data type has been created, it can be used in place of the usual type name in declarations and casts; for example, Radius r = 5; ...; r = (Radius) rshort; A typedef declaration does not create a new type

It just creates a synonym for an existing type

A typedef is particularly useful with structures and unions:


1

//Wrong
2 3 4 5

const int *const cpc = &i; *cpc = 41; //Wrong cpc = &j; //Wrong return 0; }
3 / 22

typedef struct llist *llptr; typedef struct llist { int val; llptr next; } linklist;

4 / 22

In-line functions

Thats it!

A function in C can be declared inline; for example:


1 2 3

inline fact(unsigned int n) { return n ? n*fact(n-1) : 1; }

We have now explored most of the C language The language is quite subtle in places; in particular watch out for:

The compiler will then try to in-line the function

operator precedence pointer assignment (particularly function pointers) implicit casts between ints of dierent sizes and chars shell and le I/O (stdio.h) dynamic memory allocation (stdlib.h) string manipulation (string.h) character class tests (ctype.h) ...
(Read, for example, K&R Appendix B for a quick introduction) (Or type man function at a Unix shell for details)

A clever compiler might generate 120 for fact(5)

There is also extensive standard library support, including:


A compiler might not always be able to in-line a function An inline function must be dened in the same execution unit as it is used The inline operator does not change function semantics

the in-line function itself still has a unique address static variables of an in-line function still have a unique address

5 / 22

6 / 22

Library support: I/O


I/O is not managed directly by the compiler; support in stdio.h:

1 2 3 4 5 6 7 8

#include<stdio.h> #define BUFSIZE 1024 int main(void) { FILE *fp; char buffer[BUFSIZE]; if ((fp=fopen("somefile.txt","rb")) == 0) { perror("fopen error:"); return 1; } while(!feof(fp)) { int r = fread(buffer,sizeof(char),BUFSIZE,fp); fwrite(buffer,sizeof(char),r,stdout); } fclose(fp); return 0; }
8 / 22

int printf(const char *format, ...); int sprintf(char *str, const char *format, ...); int scanf(const char *format, ...); FILE *fopen(const char *path, const char *mode); int fclose(FILE *fp); size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); int fprintf(FILE *stream, const char *format, ...); int fscanf(FILE *stream, const char *format, ...);
7 / 22

9 10 11 12 13 14 15 16 17 18 19 20

Library support: dynamic memory allocation


Dynamic memory allocation is not managed directly by the C compiler Support is available in stdlib.h:

Gotchas: operator precedence


1 2

#include<stdio.h> struct test {int i;}; typedef struct test test_t; int main(void) { test_t a,b; test_t *p[] = {&a,&b}; p[0]->i=0; p[1]->i=0; test_t *q = p[0]; printf("%d\n",++q->i); //What does this do? return 0; }
10 / 22

3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

void void void void

*malloc(size_t size) *calloc(size_t nobj, size_t size) *realloc(void *p, size_t size) free(void *p)

The C sizeof unary operator is handy when using malloc:


p = (char *) malloc(sizeof(char)*1000)

Any successfully allocated memory must be deallocated manually

Note: free() needs the pointer to the allocated memory

Failure to deallocate will result in a memory leak

9 / 22

Gotchas:

i++

Gotchas: local stack


1 2

#include <stdio.h> char *unary(unsigned short s) { char local[s+1]; int i; for (i=0;i<s;i++) local[i]=1; local[s]=\0; return local; } int main(void) { printf("%s\n",unary(6)); //What does this print? return 0; }
12 / 22

1 2 3 4 5 6 7 8 9 10

#include <stdio.h> int main(void) { int i=2; int j=i++ + ++i; printf("%d %d\n",i,j); //What does this print? return 0; }

3 4 5 6 7 8 9 10 11 12 13 14 15 16

11 / 22

Gotchas: local stack (contd.)


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

Gotchas: careful with pointers


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

#include <stdio.h> char global[10]; char *unary(unsigned short s) { char local[s+1]; char *p = s%2 ? global : local; int i; for (i=0;i<s;i++) p[i]=1; p[s]=\0; return p; } int main(void) { printf("%s\n",unary(6)); //What does this print? return 0; }
13 / 22

#include <stdio.h> struct values { int a; int b; }; int main(void) { struct values test2 = {2,3}; struct values test1 = {0,1}; int *pi = &(test1.a); pi += 1; //Is this sensible? printf("%d\n",*pi); pi += 2; //What could this point at? printf("%d\n",*pi); return 0; }
14 / 22

Tricks: Dus device


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Assessed exercise

send(int *to, int *from, int count) { int n=(count+7)/8; switch(count%8){ case 0: do{ *to = *from++; case 7: *to = *from++; case 6: *to = *from++; case 5: *to = *from++; case 4: *to = *from++; case 3: *to = *from++; case 2: *to = *from++; case 1: *to = *from++; } while(--n>0); } }

To be completed by midday on 25th April 2008 Sign-up sheet removed midday on 25th April 2008 Viva examinations 1300-1600 on 8th May 2008 Viva examinations 1300-1600 on 9th May 2008 Download the starter pack from: http://www.cl.cam.ac.uk/Teaching/current/CandC++/ This should contain eight les: server.c rfc0791.txt message1 client.c rfc0793.txt message2 message3 message4

15 / 22

16 / 22

Exercise aims
Demonstrate an ability to:

Exercise submission

Understand (simple) networking code Use control ow, functions, structures and pointers Use libraries, including reading and writing les Understand a specication Compile and test code

Assessment is in the form of a tick There will be a short viva; remember to sign up! Submission is via email to [email protected] Your submission should include seven les, packed in to a ZIP le called crsid.zip and attached to your submission email: answers.txt client1.c server1.c summary.c extract.c message1.txt message2.jpg

Task is split into three parts:


Comprehension and debugging Preliminary analysis Completed code and testing

17 / 22

18 / 22

Hints: IP header
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Version| IHL |Type of Service| Total Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Identification |Flags| Fragment Offset | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Time to Live | Protocol | Header Checksum | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Options | Padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Hints: IP header (in C)


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

#include <stdint.h> struct ip { uint8_t hlenver; uint8_t tos; uint16_t len; uint16_t id; uint16_t off; uint8_t ttl; uint8_t p; uint16_t sum; uint32_t src; uint32_t dst; }; #define IP_HLEN(lenver) (lenver & 0x0f) #define IP_VER(lenver) (lenver >> 4)
20 / 22

19 / 22

Hints: network byte order

Exercises
1. What is the value of i after executing each of the following:
i = sizeof(char); i = sizeof(int); int a; i = sizeof a; char b[5]; i = sizeof(b); char *c=b; i = sizeof(c); struct {int d;char e;} s; i = sizeof s; void f(int j[5]) { i = sizeof j;} void f(int j[][10]) { i = sizeof j;} 2. Use struct to dene a data structure suitable for representing a binary tree of integers. Write a function heapify(), which takes a 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8

The IP network is big-endian; x86 is little-endian Reading multi-byte values requires conversion The BSD API species:

uint16_t uint32_t uint16_t uint32_t

ntohs(uint16_t ntohl(uint32_t htons(uint16_t htonl(uint32_t

netshort) netlong) hostshort) hostlong)

pointer to an integer array of values and a pointer to the head of an (empty) tree and builds a binary heap of the integer array values. (Hint: youll need to use malloc()) 3. What other C data structure can be used to represent a heap? Would using this structure lead to a more ecient implementation of heapify()?
21 / 22 22 / 22

C++

C and C++
5. Overloading Namespaces Classes Alastair R. Beresford
University of Cambridge

To quote Bjarne Stroustrup: C++ is a general-purpose programming language with a bias towards systems programming that:

is a better C supports data abstraction supports object-oriented programming supports generic programming.

Lent Term 2008

1 / 22

2 / 22

C++ fundamental types

C++ enumeration

Unlike C, C++ enumerations dene a new type; for example


enum flag {is_keyword=1, is_static=2, is_extern=4, ... }

C++ has all the fundamental types C has

character literals (e.g. a) are now of type char

When dening storage for an instance of an enumeration, you use its name; for example: flag f = is_keyword Implicit type conversion is not allowed:
f = 5; //wrong f = flag(5); //right

In addition, C++ denes a new fundamental type, bool A bool has two values: true and false When cast to an integer, true1 and false0 When casting from an integer, non-zero values become true and false otherwise

The maximum valid value of an enumeration is the enumerations largest value rounded up to the nearest larger binary power minus one The minimum valid value of an enumeration with no negative values is zero The minimum valid value of an enumeration with negative values is the nearest least negative binary power

3 / 22

4 / 22

References

References in function arguments

C++ supports references, which provide an alternative name for a variable Generally used for specifying parameters to functions and return values as well as overloaded operators (more later) A reference is declared with the & operator; for example:
int i[] = {1,2}; int &refi = i[0];

When used as a function parameter, a referenced value is not copied; for example:
void inc(int& i) { i++;} //bad style?

Declare a reference as const when no modication takes place It can be noticeably more ecient to pass a large struct by reference Implicit type conversion into a temporary takes place for a const reference but results in an error otherwise; for example:
1 2 3 4 5 6 7

A reference must be initialised when it is dened A variable referred to by a reference cannot be changed after it is initialised; for example:
refi++; //increments value referenced

float fun1(float&); float fun2(const float&); void test() { double v=3.141592654; fun1(v); //Wrong fun2(v); }

5 / 22

6 / 22

Overloaded functions

Scoping and overloading

Functions doing dierent things should have dierent names It is possible (and sometimes sensible!) to dene two functions with the same name Functions sharing a name must dier in argument types Type conversion is used to nd the best match A best match may not always be possible:
1 2 3 4 5 6 1 2 3 4

Functions in dierent scopes are not overloaded; for example:


void f(int); void example() { void f(double); f(1); //calls f(double); }

void f(double); void f(long); void test() { f(1L); //f(long) f(1.0); //f(double) f(1); //Wrong: f(long(1)) or f(double(1)) ?

5 6

7 / 22

8 / 22

Default function arguments

Namespaces
Related data can be grouped together in a namespace:
namespace Stack { //header file void push(char); char pop(); } namespace Stack { //implementation const int max_size = 100; char s[max_size]; int top = 0; void push(char c) { ... } char pop() { ... } }
9 / 22 10 / 22

A function can have default arguments; for example:


double log(double v, double base=10.0);

A non-default argument cannot come after a default; for example:


double log(double base=10.0, double v); //wrong

void f() { //usage ... Stack::push(c); ... }

A declaration does not need to name the variable; for example:


double log(double v, double=10.0);

Be careful of the interaction between * and =; for example:


void f(char*=0); //Wrong *= is assignment

Using namespaces

Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14

A namespace is a scope and expresses logical program structure It provides a way of collecting together related pieces of code A namespace without a name limits the scope of variables, functions and classes within it to the local execution unit The same namespace can be declared in several source les The global function main() cannot be inside a namespace The use of a variable or function name from a dierent namespace must be qualied with the appropriate namespace(s)

namespace Module1 {int x;} namespace Module2 { inline int sqr(const int& i) {return i*i;} inline int halve(const int& i) {return i/2;} } using namespace Module1; //"import" everything int main() { using Module2::halve; x = halve(x); sqr(x); }

The keyword using allows this qualication to be stated once, thereby shortening names Can also be used to generate a hybrid namespace typedef can be used: typedef Some::Thing thing; Allows, for example, internal and external library denitions
11 / 22

//"import" the halve function //Wrong

A namespace can be dened more than once

12 / 22

Linking C and C++ code

Linking C and C++ code

The directive extern "C" species that the following declaration or denition should be linked as C, not C++ code:
extern "C" int f();

Care must be taken with pointers to functions and linkage:


1 2 3

Multiple declarations and denitions can be grouped in curly brackets:


1 2 3 4 5

extern "C" void qsort(void* p, \ size_t nmemb, size_t size, \ int (*compar)(const void*, const void*)); int compare(const void*,const void*); char s[] = "some chars"; qsort(s,9,1,compare); //Wrong

extern "C" { int globalvar; //definition int f(); void g(int); }

4 5 6 7 8

13 / 22

14 / 22

User-dened types

Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14

C++ provides a means of dening classes and instantiating objects Classes contain both data storage and functions which operate on storage Classes have access control: private, protected and public Classes are created with class or struct keywords

class Complex { double re,im; public: Complex(double r=0.0L, double i=0.0L); }; Complex::Complex(double r,double i) { re=r,im=i; } int main() { Complex c(2.0), d(), e(1,5.0L); return 0; }

struct members default to public access; class to private

A member function with the same name as a class is called a constructor A member function with the same name as the class, prexed with a tilde (~), is called a destructor A constructor can be overloaded to provide multiple instantiation methods Can create static (i.e. per class) member variables
15 / 22

16 / 22

Constructors and destructors

Copy constructor
A new class instance can dened by assignment; for example;
Complex c(1,2); Complex d = c;

A default constructor is a function with no arguments (or only default arguments) If no constructor is specied, the compiler will generate one The programmer can specify one or more constructors Only one constructor is called when an object is created There can only be one destructor

In this case, the new class is initialised with copies of all the existing class non-static member variables; no constructor is called This behaviour may not always be desirable (e.g. consider a class with a pointer as a member variable)

This is called when a stack allocated object goes out of scope or when a heap allocated object is deallocated with delete; this also occurs for stack allocated objects during exception handling (more later)

In which case, dene an alternative copy constructor: Complex::Complex(const Complex&) { ... }

If a copy constructor is not appropriate, make the copy constructor a private member function

17 / 22

18 / 22

Assignment operator

Constant member functions

By default a class is copied on assignment by over-writing all non-static member variables; for example:
1 2

Complex c(), d(1.0,2.3); c = d; //assignment

Member functions can be declared const Prevents object members being modied by the function:
1 2 3

This behaviour may also not be desirable The assignment operator (operator=) can be dened explicitly:
1 2 3

double Complex::real() const { return re; }

Complex& Complex::operator=(const Complex& c) { ... }

19 / 22

20 / 22

Arrays and the free store

Exercises
1. Write an implementation of a class LinkList which stores zero or more positive integers internally as a linked list on the heap. The class should provide appropriate constructors and destructors and a method pop() to remove items from the head of the list. The method pop() should return -1 if there are no remaining items. Your implementation should override the copy constructor and assignment operator to copy the linked-list structure between class instances. You might like to test your implementation with the following:
1 2 3 4 5 6

An array of class objects can be dened if a class has a default constructor C++ has a new operator to place items on the heap:
Complex* c = new Complex(3.4);

Items on the heap exist until they are explicity deleted:


delete c;

Since C++ (like C) doesnt distinguish between a pointer to an object and a pointer to an array of objects, array deletion is dierent:
1 2 3

Complex* c = new Complex[5]; ... delete[] c; //Cannot use "delete" here

When an object is deleted, the object destructor is invoked

7 8

int main() { int test[] = {1,2,3,4,5}; LinkList l1(test+1,4), l2(test,5); LinkList l3=l2, l4; l4=l1; printf("%d %d %d\n",l1.pop(),l3.pop(),l4.pop()); return 0; }
22 / 22

Hint: heap allocation & deallocation should occur exactly once!


21 / 22

Operators

C++ allows the programmer to overload the built-in operators For example, a new test for equality:
1 2 3

C and C++
6. Operators Inheritance Virtual Alastair R. Beresford

bool operator==(Complex a, Complex b) { return a.real()==b.real() && a.imag()==b.imag(); }

University of Cambridge

An operator can be dened or declared within the body of a class, and in this case one fewer argument is required; for example:
1 2 3

Lent Term 2008

bool Complex::operator==(Complex b) { return re==b.real() && im==b.imag(); }

Almost all operators can be overloaded

1 / 19

2 / 19

Streams

The this pointer

Overloaded operators also work with built-in types Overloading is used to dene a C++ printf; for example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

#include <iostream> int main() { const char* s = "char array";

If an operator is dened in the body of a class, it may need to return a reference to the current object

The keyword this can be used

For example:
1 2 3 4 5

std::cout << s << std::endl; //Unexpected output; prints &s[0] std::cout.operator<<(s).operator<<(std::endl); //Expected output; prints s std::operator<<(std::cout,s); std::cout.operator<<(std::endl); return 0; }
3 / 19

Complex& Complex::operator+=(Complex b) { re += b.real(); this->im += b.imag(); return *this; }

4 / 19

Class instances as member variables


Temporary objects

A class can have an instance of another class as a member variable How can we pass arguments to the class constructor? New notation for a constructor:
1 2 3 4 5 6 7

Temporary objects are often created during execution A temporary which is not bound to a reference or named object exists only during evaluation of a full expression Example: the string class has a function c_str() which returns a pointer to a C representation of a string:
1 2 3 4 5

class X { Complex c; Complex d; X(double a, double b): c(a,b), d(b) { ... } };

This notation must be used to initialise const and reference members It can also be more ecient

string a("A "), b("string"); const char *s = (a+b).c_str(); //Wrong ... //s still in scope here, but the temporary holding //"a+b" has been deallocated ...

5 / 19

6 / 19

Friends
A (non-member) friend function can access the private members of a class instance it befriends This can be done by placing the function declaration inside the class denition and prexing it with the keyword friend; for example:
1 2 3 4 5 6 7

Inheritance

C++ allows a class to inherit features of another:


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

class vehicle { int wheels; public: vehicle(int w=4):wheels(w) {} }; class bicycle : public vehicle { bool panniers; public: bicycle(bool p):vehicle(2),panniers(p) {} }; int main() { bicycle(false); }
8 / 19

class Matrix { ... friend Vector operator*(const Matrix&, \ const Vector&); ... }; }

7 / 19

Derived member function call

Example

Default derived member function call semantics dier from Java:


1 2 3 4 5 6 7 8 9 10 11 12 13

class vehicle { int wheels; public: vehicle(int w=4):wheels(w) {} int maxSpeed() {return 60;} }; class bicycle : public vehicle { int panniers; public: bicycle(bool p=true):vehicle(2),panniers(p) {} int maxSpeed() {return panniers ? 12 : 15;} };

1 2 3 4 5 6 7 8 9 10 11 12

#include <iostream> #include "example13.hh" void print_speed(vehicle &v, bicycle &b) { std::cout << v.maxSpeed() << " "; std::cout << b.maxSpeed() << std::endl; } int main() { bicycle b = bicycle(true); print_speed(b,b); //prints "60 12" }

9 / 19

10 / 19

Virtual functions

Virtual functions

Non-virtual member functions are called depending on the static type of the variable, pointer or reference Since a derived class can be cast to a base class, this prevents a derived class from overloading a function To get polymorphic behaviour, declare the function virtual in the superclass:
1 2 3 4 5 6

In general, for a virtual function, selecting the right function has to be run-time decision; for example:
1 2 3 4

bicycle b; vehicle v; vehicle* pv; user_input() ? pv = &b : pv = &v; std::cout << pv->maxSpeed() << std::endl; }

class vehicle { int wheels; public: vehicle(int w=4):wheels(w) {} virtual int maxSpeed() {return 60;} };

5 6 7 8

11 / 19

12 / 19

Enabling virtual functions

Abstract classes

Sometimes a base class is an un-implementable concept In this case we can create an abstract class:
1 2 3 4

To enable virtual functions, the compiler generates a virtual function table or vtable A vtable contains a pointer to the correct function for each object instance The vtable is an example of indirection The vtable introduces run-time overhead

class shape { public: virtual void draw() = 0; }

It is not possible to instantiate an abstract class:


shape s; //Wrong

A derived class can provide an implementation for some (or all) the abstract functions A derived class with no abstract functions can be instantiated

13 / 19

14 / 19

Example

Multiple inheritance

1 2 3 4 5 6 7 8 9 10

class shape { public: virtual void draw() = 0; }; class circle : public shape { public: //... void draw() { /* impl */ } };

It is possible to inherit from multiple base classes; for example:


1 2 3

class ShapelyVehicle: public vehicle, public shape { ... }

Members from both base classes exist in the derived class If there is a name clash, explicit naming is required This is done by specifying the class name; for example:
ShapelyVehicle sv; sv.vehicle::maxSpeed();

15 / 19

16 / 19

Multiple instances of a base class

Virtual base classes

With multiple inheritance, we can build:


1 2 3 4

class class class class

A B C D

{}; : public A {}; : public A {}; : public B, C {};

Alternatively, we can have a single instance of the base class Such a virtual base class is shared amongst all those deriving from it
1 2 3 4

This means we have two instances of A even though we only have a single instance of D This is legal C++, but means all references to A must be stated explicitly:
1 2 3

class class class class

Vehicle {int VIN;}; Boat : public virtual Vehicle { ... }; Car : public virtual Vehicle { ... }; JamesBondCar : public Boat, public Car { ... };

D d; d.B::A::var=3; d.C::A::var=4;

17 / 19

18 / 19

Exercises

1. If a function f has a static instance of a class as a local variable, when might the class constructor be called? 2. Write a class Matrix which allows a programmer to dene two dimensional matrices. Overload the common operators (e.g. +, -, *, and /) 3. Write a class Vector which allows a programmer to dene a vector of length two. Modify your Matrix and Vector classes so that they interoperate correctly (e.g. v2 = m*v1 should work as expected) 4. Why should destructors in an abstract class almost always be declared virtual?

19 / 19

Exceptions

C and C++
7. Exceptions Templates

Some code (e.g. a library module) may detect an error but not know what to do about it; other code (e.g. a user module) may know how to handle it C++ provides exceptions to allow an error to be communicated In C++ terminology, one portion of code throws an exception; another portion catches it. If an exception is thrown, the call stack is unwound until a function is found which catches the exception If an exception is not caught, the program terminates

Alastair R. Beresford
University of Cambridge

Lent Term 2008

1 / 20

2 / 20

Throwing exceptions

Conveying information

Exceptions in C++ are just normal values, matched by type A class is often used to dene a particular error type:
class MyError {};

The thrown type can carry information:


1 2 3 4 5 6 7 8 9 10 11 12 13 14

An instance of this can then be thrown, caught and possibly re-thrown:


1 2 3 4 5 6 7 8 9

struct MyError { int errorcode; MyError(i):errorcode(i) {} }; void f() { ... throw MyError(5); ... } try { f(); } catch (MyError x) { //handle error (x.errorcode has the value 5) ... }

void f() { ... throw MyError(); ... } ... try { f(); } catch (MyError) { //handle error throw; //re-throw error }

3 / 20

4 / 20

Handling multiple errors

1 2 3

#include <iostream> struct SomeError {virtual void print() = 0;}; struct ThisError : public SomeError { virtual void print() { std::cout << "This Error" << std::endl; } }; struct ThatError : public SomeError { virtual void print() { std::cout << "That Error" << std::endl; } }; int main() { try { throw ThisError(); } catch (SomeError& e) { //reference, not value e.print(); } return 0; }
6 / 20

Multiple catch blocks can be used to catch dierent errors:


1 2 3 4 5 6 7 8 9

4 5 6 7 8 9 10 11 12 13 14 15

try { ... } catch (MyError x) { //handle MyError } catch (YourError x) { //handle YourError }

Every exception will be caught with catch(...) Class hierarchies can be used to express exceptions:

16 17 18 19 20 5 / 20

Exceptions and local variables

Templates

When an exception is thrown, the stack is unwound The destructors of any local variables are called as this process continues Therefore it is good C++ design practise to wrap any locks, open le handles, heap memory etc., inside a stack-allocated class to ensure that the resources are released correctly

Templates support meta-programming, where code can be evaluated at compile-time rather than run-time Templates support generic programming by allowing types to be parameters in a program Generic programming means we can write one set of algorithms and one set of data structures to work with objects of any type We can achieve some of this exibility in C, by casting everything to void * (e.g. sort routine presented earlier) The C++ Standard Template Library (STL) makes extensive use of templates

7 / 20

8 / 20

An example: a stack

Creating a stack template

The stack data structure is a useful data abstraction concept for objects of many dierent types In one program, we might like to store a stack of ints In another, a stack of NetworkHeader objects Templates allow us to write a single generic stack implementation for an unspecied type T What functionality would we like a stack to have?

A class template is dened as:


1 2 3

template<class T> class Stack { ... }

Where class T can be any C++ type (e.g. int) When we wish to create an instance of a Stack (say to store ints) then we must specify the type of T in the declaration and denition of the object: Stack<int> intstack; We can then use the object as normal: intstack.push(3); So, how do we implement Stack?

bool isEmpty(); void push(T item); T pop(); ...

Many of these operations depend on the type T

Write T whenever you would normally use a concrete type

9 / 20

10 / 20

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

template<class T> class Stack {


1

#include "example16.hh" template<class T> void Stack<T>::append(T val) { Item **pp = &head; while(*pp) {pp = &((*pp)->next);} *pp = new Item(val); } //Complete these as an exercise template<class T> void Stack<T>::push(T) {/* ... */} template<class T> T Stack<T>::pop() {/* ... */} template<class T> Stack<T>::~Stack() {/* ... */} int main() { Stack<char> s; s.push(a), s.append(b), s.pop(); }

struct Item { //class with all public members T val; Item* next; Item(T v) : val(v), next(0) {} }; Item* head; Stack(const Stack& s) {} //private Stack& operator=(const Stack& s) {} // public: Stack() : head(0) {} ~Stack(); T pop(); void push(T val); void append(T val); };
11 / 20

2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

12 / 20

Template details

Default parameters

A template parameter can take an integer value instead of a type: template<int i> class Buf { int b[i]; ... }; A template can take several parameters:
template<class T,int i> class Buf { T b[i]; ... };

Template parameters may be given default values


1 2 3 4 5 6 7 8

A template can even use one template parameter in the denition of a subsequent parameter:
template<class T, T val> class A { ... };

template <class T,int i=128> struct Buffer{ T buf[i]; }; int main() { Buffer<int> B; //i=128 Buffer<int,256> C; }

A templated class is not type checked until the template is instantiated: template<class T> class B {const static T a=3;};

B<int> b; is ne, but what about B<B<int> > bi;?

Template denitions often need to go in a header le, since the compiler needs the source to instantiate an object
13 / 20 14 / 20

Specialization

Templated functions

The class T template parameter will accept any type T We can dene a specialization for a particular type as well:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

A function denition can also be specied as a template; for example:


1 2

#include <iostream> class A {}; template<class T> struct B { void print() { std::cout << "General" << std::endl;} }; template<> struct B<A> { void print() { std::cout << "Special" << std::endl;} }; int main() { B<A> b1; B<int> b2; b1.print(); //Special b2.print(); //General }
15 / 20

template<class T> void sort(T a[], const unsigned int& len);

The type of the template is inferred from the argument types: int a[] = {2,1,3}; sort(a,3); = T is an int The type can also be expressed explicitly:
sort<int>(a)

There is no such type inference for templated classes Using templates in this way enables:

better type checking than using void * potentially faster code (no function pointers) larger binaries if sort() is used with data of many dierent types

16 / 20

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

#include <iostream> template<class T> void sort(T a[], const unsigned int& len) { T tmp; for(unsigned int i=0;i<len-1;i++) for(unsigned int j=0;j<len-1-i;j++) if (a[j] > a[j+1]) //type T must support "operator>" tmp = a[j], a[j] = a[j+1], a[j+1] = tmp; } int main() { const unsigned int len = 5; int a[len] = {1,4,3,2,5}; float f[len] = {3.14,2.72,2.54,1.62,1.41}; sort(a,len), sort(f,len); for(unsigned int i=0; i<len; i++) std::cout << a[i] << "\t" << f[i] << std::endl; }
17 / 20

Overloading templated functions

Templated functions can be overloaded with templated and non-templated functions Resolving an overloaded function call uses the most specialised function call If this is ambiguous, then an error is given, and the programmer must x by:

being explicit with template parameters (e.g. sort<int>(...)) re-writing denitions of overloaded functions

Overloading templated functions enables meta-programming:

18 / 20

Meta-programming example
1 2 3 4 5 6 7 8 9 10 11 12 13

Exercises
1. Provide an implementation for:
template<class T> T Stack<T>::pop(); and template<class T> Stack<T>::~Stack();

#include <iostream> template<unsigned int N> inline long long int fact() { return N*fact<N-1>(); } template<> inline long long int fact<0>() { return 1; } int main() { std::cout << fact<20>() << std::endl; }

2. Provide an implementation for: Stack(const Stack& s); and


Stack& operator=(const Stack& s);

3. Using meta programming, write a templated class prime, which evaluates whether a literal integer constant (e.g. 7) is prime or not at compile time. 4. How can you be sure that your implementation of class prime has been evaluated at compile time?

19 / 20

20 / 20

The STL
Alexander Stepanov, designer of the Standard Template Library says:

C and C++
8. The Standard Template Library

STL was designed with four fundamental ideas in mind:

Abstractness Eciency Von Neumann computational model Value semantics

Alastair R. Beresford
University of Cambridge

Lent Term 2008 Its an example of generic programming; in other words reusable or widely adaptable, but still ecient code

1 / 20

2 / 20

Additional references

Advantages of generic programming

Traditional container libraries place algorithms as member functions of classes

Consider, for example, "test".substring(1,2); in Java

Musser et al (2001). STL Tutorial and Reference Guide (Second Edition). Addison-Wesley.

So if you have m container types and n algorithms, thats nm pieces of code to write, test and document Also, a programmer may have to copy values between container types to execute an algorithm The STL does not make algorithms member functions of classes, but uses meta programming to allow programmers to link containers and algorithms in a more exible way This means the library writer only has to produce n + m pieces of code The STL, unsurprisingly, uses templates to do this

http://gcc.gnu.org/onlinedocs/libstdc++/documentation.html

3 / 20

4 / 20

Plugging together storage and algorithms


Basic idea:

A simple example
1 2 3

#include <iostream> #include <vector> //vector<T> template #include <numeric> //required for accumulate int main() { int i[] = {1,2,3,4,5}; std::vector<int> vi(&i[0],&i[5]); std::vector<int>::iterator viter; for(viter=vi.begin(); viter < vi.end(); ++viter) std::cout << *viter << std::endl; std::cout << accumulate(vi.begin(),vi.end(),0) << std::endl; }

dene useful data storage components, called containers, to store a set of objects dene a generic set of access methods, called iterators, to manipulate the values stored in containers of any type dene a set of algorithms which use containers for storage, but only access data held in them through iterators

4 5 6 7 8 9 10 11 12

The time and space complexity of containers and algorithms is specied in the STL standard

13 14 15

5 / 20

6 / 20

Containers

Containers

The STL uses containers to store collections of objects Each container allows the programmer to store multiple objects of the same type Containers dier in a variety of ways:

Container examples for storing sequences:


vector<T> deque<T> list<T> set<Key> multiset<Key> map<Key,T> multimap<Key, T>

memory eciency access time to arbitrary elements arbitrary insertion cost append and prepend cost deletion cost ...

Container examples for storing associations:


7 / 20

8 / 20

Using containers
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

std::string

#include <string> #include <map> #include <iostream> int main() {

Built-in arrays and the std::string hold elements and can be considered as containers in most cases You cant call .begin() on an array however! Strings are designed to interact well with C char arrays String assignments, like containers, have value semantics:
1 2

std::map<std::string,std::pair<int,int> > born_award; born_award["Perlis"] = std::pair<int,int>(1922,1966); born_award["Wilkes"] = std::pair<int,int>(1913,1967); born_award["Hamming"] = std::pair<int,int>(1915,1968); //Turing Award winners (from Wikipedia) std::cout << born_award["Wilkes"].first << std::endl; return 0; }
9 / 20

#include <iostream> #include <string> int main() { char s[] = "A string "; std::string str1 = s, str2 = str1; str1[0]=a, str2[0]=B; std::cout << s << str1 << str2 << std::endl; return 0; }
10 / 20

3 4 5 6 7 8 9 10 11

Iterators

Iterator types
Iterator type Supported operators
== == == == == != != != != != ++ ++ ++ ++ ++ *(read only) *(write only) * * -* -- + - += -= < > <= >=

Containers support iterators, which allow access to values stored in a container Iterators have similar semantics to pointers

A compiler may represent an iterator as a pointer at run-time

There are a number of dierent types of iterator Each container supports a subset of possible iterator operations Containers have a concept of a beginning and end

Input Output Forward Bidirectional Random Access

Notice that, with the exception of input and output iterators, the relationship is hierarchical Whilst iterators are organised logically in a hierarchy, they do not do so formally through inheritence! There are also const iterators which prohibit writing to refd objects
12 / 20

11 / 20

Adaptors

Generic algorithms

An adaptor modies the interface of another component For example the reverse_iterator modies the behaviour of an
iterator
1 2 3 4 5 6 7 8 9 10 11 12 13

Generic algorithms make use of iterators to access data in a container This means an algorithm need only be written once, yet it can function on containers of many dierent types When implementing an algorithm, the library writer tries to use the most restrictive form of iterator, where practical Some algorithms (e.g. sort) cannot be written eciently using anything other than random access iterators Other algorithms (e.g. find) can be written eciently using only input iterators Lesson: use common sense when deciding what types of iterator to support Lesson: if a container type doesnt support the algorithm you want, you are probably using the wrong container type!
14 / 20

#include <vector> #include <iostream> int main() { int i[] = {1,3,2,2,3,5}; std::vector<int> v(&i[0],&i[6]); for (std::vector<int>::reverse_iterator i = v.rbegin(); i != v.rend(); ++i) std::cout << *i << std::endl; return 0; }
13 / 20

Algorithm example

Algorithm example
1

#include "example23.hh" #include "example23a.cc" int main() { char s[] = "The quick brown fox jumps over the lazy dog"; std::cout << search(&s[0],&s[strlen(s)],d) << std::endl; int i[] = {1,2,3,4,5}; std::vector<int> v(&i[0],&i[5]); std::cout << search(v.begin(),v.end(),3)-v.begin() << std::endl; std::list<int> l(&i[0],&i[5]); std::cout << (search(l.begin(),l.end(),4)!=l.end()) << std::endl; return 0; }
16 / 20

Algorithms usually take a start and finish iterator and assume the valid range is start to finish-1; if this isnt true the result is undened

2 3 4 5 6 7

Here is an example routine search to nd the rst element of a storage container which contains the value element:
1 2 3 4 5 6

8 9 10

//search: similar to std::find template<class I,class T> I search(I start, I finish, T element) { while (*start != element && start != finish) ++start; return start; }

11 12 13 14 15 16 17 18 19

15 / 20

Heterogeneity of iterators
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

Function objects

#include "example24.hh" int main() { char one[] = {1,2,3,4,5}; int two[] = {0,2,4,6,8}; std::list<int> l (&two[0],&two[5]); std::deque<long> d(10); std::merge(&one[0],&one[5],l.begin(),l.end(),d.begin()); for(std::deque<long>::iterator i=d.begin(); i!=d.end(); ++i) std::cout << *i << " "; std::cout << std::endl; return 0; }
17 / 20 18 / 20

C++ allows the function call () to be overloaded This is useful if we want to pass functions as parameters in the STL More exible than function pointers, since we can store per-instance object state inside the function Example:
1 2 3

struct binaccum { int operator()(int x, int y) const {return 2*x + y;} };

Higher-order functions in C++



1 2 3 4 5 6 7 8 9 10 11 12 13 14

Higher-order functions in C++

1 2 3 4 5 6

In ML we can write: foldl (fn (y,x) => 2*x+y) 0 [1,1,0]; Or in Python: reduce(lambda x,y: 2*x+y, [1,1,0]) Or in C++:

By using reverse iterators, we can also get foldr:

#include<iostream> #include<numeric> #include<vector> #include "example27a.cc" int main() { //equivalent to foldl bool binary[] = {true,true,false}; std::cout<< std::accumulate(&binary[0],&binary[3],0,binaccum()) << std::endl; //output: 6 return 0; }
19 / 20

#include<iostream> #include<numeric> #include<vector> #include "example27a.cc" int main() { //equivalent to foldr bool binary[] = {true,true,false}; std::vector<bool> v(&binary[0],&binary[3]); std::cout << std::accumulate(v.rbegin(),v.rend(),0,binaccum()); std::cout << std::endl; //output: 3 return 0; }
20 / 20

7 8 9 10 11 12 13 14 15 16

You might also like