1 2 Simple and compound s The if- Simple s: E.g.: expression; Various jumps : break, goto, continue, return. k = a * p + 3; printf("k = %d\n", k); 1 + 2; ; The if- comes in two forms: or E.g.: if (expression) if (expression) else Compound s: declarations s if (n > 0) n--; else printf("!\n"); n = 100; 3 4 The if- (cont). The switch- if (c == a ) else if (c == b ) else if (c == c ) else What happens below? Assume i = 0 and consider both the case decrement = 0 and decrement = 1. if (decrement) if (i > 0) i--; else i++; The switch- is similar to case in Pascal: switch (expression) case label 1 : s case label 2 : s default: s Note: expression of some integer type. Each label must be a constant. Fall through! Explicit breaks needed!
5 6 The switch- (cont.) The while and do s Example: Syntax: switch (c) case a : i++; break; case : case \t : case \n : blank = 1; break; default: j++; break; while (expression) do while (expression); Example: int fac(int n) int f, i; f = i = 1; while (++i <= n) f *= i; 7 8 The for- Function definition using prototypes Very common. Syntax: for (exp 1 ; exp 2 ; exp 3 ) Equivalent to: exp 1 ; while (exp 2 ) exp 3 ; Examples. for (;;) printf("hello!\n"); s = 0; for (i = 1; i <= 10; i++) s += i; for (s=0, i=1; i<=10; s+=(i++)); The general form of a function definition: type name(formal args) local vars code where formal args is a list of the form: type 1 arg 1,, type n arg n An example: double max(double x, double y) if (x > y) return x; return y;
9 10 Function definition (cont.) Two special prototypes: No parameters: type name(void) /*body*/ Variable number of parameters: type name(args, ) /*body*/ As an example, the printf-prototype: int printf(const char *format, ) Functions without a result ( procedures ): void foo(formal args) /*body*/ Only return s of the form return; are allowed in the body. Declaring functions using prototypes A function declaration looks like: type name(formal args); where formal args is a list of the form: type 1 arg 1,, type n arg n, or type 1,, type n What is the difference? A function declaration makes the function known to the compiler. A function may be declared more than once. A function definition gives the code for the function. A function must be defined exactly once, later in the same file or externally. 11 12 Generally: Definitions vs. declarations A declaration specifies what an identifier stands for. A definition is a construction that, in addition to declaring an identifier, also reserves space for it and gives its value. Old style (K&R) function definitions and declarations Old style function definition: type name(arg. names) arg. decls Typical example: As a rule, always at least declare before use. Include files (e.g. stdio.h etc.) contain declarations only. This is vital. int foo(x, y) double x; double y; Old style function declaration: type name(); E.g. int foo(); No info about number of args or their type!
13 14 A function call: Calling functions name(actual args.) Calling a function without arguments: name() Note: the parentheses are essential. Argument passing is always call-by-value: No reference arguments. The same effect is obtained by passing pointers by value. Examples: int foo(float, int); i = foo(1.2, 17); printf("%d\n", foo(7.77, 42); (void) printf("%d\n", 42); Storage classes Basic idea: Storage classes determine how and when storage is allocated, e.g.: Suggest that the compiler try to put a variable in a CPU register: register int i; Tell the compiler that something is defined elsewhere: external double sin(double); Unfortunately, a little complex in practice: The exact meaning of a storage class is context dependent. Somewhat complicated default assumptions. Other uses, such as controlling visibility of top level symbols. 15 16 For local variables. The storage class auto Default for local variables (and formal arguments). Usually not stated explicitly. Storage is automatically allocated and deallocated (on system stack) when a block is entered/left. Any initialization performed each time. A declaration of an auto object is also a definition. An auto-object must never be accessed outside of the block in which it is defined. Example: int foo(float x, int i) int n = 10, m = i; The storage class extern Separate compilation makes it necessary to tell the compiler: which symbols that are defined in another file, i.e. defined externally, which top-level symbols should be visible outside the file, i.e. visible externally. The storage class extern serves both these purposes.
17 18 The storage class extern (cont.) Extern function or variable declaration: Assumed by default for top-level declarations. States the type of the object. Indicates that the object is either - defined later in the same file, or - defined in another file (externally). Storage allocated for top-level variable declaration (i.e the declaration counts as a definition, implicit initialization to 0) if: - extern is not stated explicitly, - no definition is encountered later in the file. Otherwise, no storage is allocated. The storage class extern (cont.) Extern function or variable definition: Only for top-level (global) symbols. Global storage allocated, object lives throughout the entire execution. Initialization performed only once. (Actually, initialized global data comprises a section of the executable file.) Also indicates to the compiler that the symbol should be externally visible. Assumed by default for top-level definitions; usually not stated explicitly. 19 20 The storage class extern (cont.) The easy option: Use extern explicitly only for importing things from other modules. Be aware of that global symbols are visible outside the file, unless you say otherwise. File foo.c Example on extern usage extern int a; int foo(int i) return a * i; File main.c #include <stdio.h> extern int foo(int); int a = 10; main() printf("%d\n", foo(7)); a = 20; printf("%d\n", foo(7));
21 22 The storage class static For functions and variables: Like external in that a static object is initialized once and lives during the entire execution. Unlike external in that static objects are not visible externally. Also, can be applied to local variable definitions (and declarations, which then count as definitions). Example: static int fie() static int count = 0; return (++count); Constants Variables and parameters may be declared as constants: But: const int max = 7; const char end = \0 ; Not the same thing as const in Pascal. Really variables that cannot be assigned to. Use #define if you need compile-time constants (for sizes of arrays etc.). To see the difference: int foo(int a, int b) const int m = max(a,b); /* body of function */ 23 24 Strings in C No real strings in C. Instead: Pointer to a sequence of char. Explicit termination by the null character, \0. printf("hello!\n"); is represented as printf( ); H e l l o! \n \0 Declaration: T a[n]; Basic arrays The variable a is an array of N elements of the type T. Subscripting: a[0], a[1],, a[n-1] No range checks! Arrays may be initialized when declared: float f[3] = 0.0, 1.0, 2.0; int a[100] = 0; char s[] = a, b, c, \0 ; char s[] = "abc"; Default initialization to 0 of static and extern arrays.
25 26 #include <stdio.h> #define N 20 Bubblesort main() int a[n], i, j, temp; for (i = 0; i < N; i++) scanf("%d", &a[i]); for (i = 0; i < N-1; i++) for (j = N-1; j > i; j--) if (a[j-1] > a[j]) temp = a[j-1]; a[j-1] = a[j]; a[j] = temp; for (i = 0; i < N; i++) printf("%d ", a[i]); Declaration: T *ap; Basic pointers ap can hold a pointer to an object of type T. Taking the address of a variable of type T by using & creates a pointer to an object of type T: int a, b, *ap = &a, *bp = &b; char *s = "abc"; a: b: ap: bp: s: a b c \0 27 28 Basic pointers (cont.) Dereferencing pointers: a) *ap = 1; b = *ap + 1; b1) ap = bp b2) *ap = *bp; a: b: ap: a b1 b2 1 2 a: b: ap: 1 2 a: b: ap: 2 2 Call-by-value vs. call-by-reference Call-by-value (C or C++): int y = 1; void foo(int x) x++ foo(y); y is still 1 after the call (contents of y copied to x which is just a local variable). Call-by-reference (C++): int y = 1; bp: bp: bp: void foo(int &x) x++ foo(y); y is 2 after the call since the formal argument x refers to the actual argument y during the call.
29 30 Call-by-reference in C Arguments are always passed by value in C. No concept of reference parameters. Instead, pointers are passed explicitly. #include <stdio.h> void foo(int *ap, int *bp) scanf("%d", ap); *bp = *ap * 2; Exercise 1 Write a loop to reverse an array int a[n], i.e. swap a[0] and a[n-1], a[1] and a[n-2], etc. Write a function that may be used to swap the contents of two integer variables. Then change your code for reversing the array so that it uses your swap function. main() int a, b, c; foo(&a, &b); scanf("%d", &c); printf("a*b=%d,c=%d\n",a*b,c); 31 32 Pointer arithmetic Bubblesort revisited In C, + and - also work on pointers: An integer may be added to/subtracted from a pointer. One pointer may be subtracted from another. The basic assumption is that the pointers refer to elements in some array: T *a: T *b: T x[n] a-2 a+3 Pointer arithmetic is often used to access arrays by pointer stepping. #include <stdio.h> #define N 20 main() int a[n], *ip, *jp, temp; for (ip = &a[0]; ip < &a[n]; ip++) scanf("%d", ip); for (ip = &a[0]; ip < &a[n-1]; ip++) for (jp = &a[n-1]; jp > ip; jp--) if (*(jp-1) > *jp) temp = *(jp-1); *(jp-1) = *jp; *jp = temp; for (ip = &a[0]; ip < &a[n]; ip++) printf("%d ", *ip);
33 34 The relationship between arrays and pointers The relationship between arrays and pointers (cont.) Arrays and pointers are not identical! char s1[] = "hello"; char *s2 = "world"; s1: h e l l o \0 In C, an equivalence between arrays and pointers in the following sense holds: An array is, with three exceptions, converted to a pointer to its first element when used. s2: w o r l d \0 The exceptions occur when the array is: an argument to sizeof, Assume a typical 32-bit computer sizeof(s1) =? sizeof(s2) =? Is the following legal C? Why? s1 = s2; s2 = s1; an argument to &, a literal string used for initialization of a char array. In all other cases we have a = &a[0]. Thus, the for-loops earlier could have been written: for (ip = a; ip < &a[n]; ip++) 35 36 Given The relationship between arrays and pointers (cont.) char s1[] = "hello"; char *s2 = "world"; the equivalence explains why: s1 = s2 is as invalid as 1 = a, s2 = s1 is a legal assignment, sizeof(s1) = 6. Moreover, by definition we have: s1[i] *(s1 + i) s2[i] *(s2 + i) The equivalence (together with pointer arithmetic) explains why this works. Arrays as arguments to functions It is always a pointer to the first element that is passed! void foo(t *a) main() T a[n]; foo(a); Alternative, equivalent ways of declaring foo: void foo(t a[]) void foo(t a[n])
37 38 Arrays as arguments to functions (cont.) What gets printed when foo is called? void foo(int a[10]) int b[10]; printf("%d, %d\n", sizeof(a), sizeof(b)); Reading a string: char s[20]; scanf("%19s", s); s on its own is the address of the array: no & in this case! There must be room for the terminating \0. Exercise 2 Assume a typical 32-bit architecture, and: char s[5]="abcd", (*cap)[5]; Answer the following: sizeof(s) =? sizeof(cap) =? Is cap = s OK? Is cap = &s OK? sizeof(*cap) =? sizeof(&s) =? sizeof(*s) =? Is the following legal C? If so, what gets printed? printf("%c\n", 5["abcdef"]); 39 Solution exercise 1 Loop to reverse an array int a[n]: int i, temp; for (i = 0; i < N/2; i++) temp = a[i]; a[i] = a[(n-1)-i]; a[(n-1)-i] = temp; Function to swap two integer variables. void swap(int *a, int *b) int temp; temp = *a; *a = *b; *b = temp; Rewriting the loop body: swap(&a[i], &a[(n-1)-i]);