260 likes | 429 Views
Further C. Multiple source code file projects Structs The preprocessor Pointers. Multiple source code files. Real programs are normally written in the form of several source code files - several .c files Just one file will contain main() In an IDE, the files are listed in a ‘ project ’
E N D
Further C • Multiple source code file projects • Structs • The preprocessor • Pointers
Multiple source code files • Real programs are normally written in the form of several source code files - several .c files • Just one file will contain main() • In an IDE, the files are listed in a ‘project’ • so eg your project might contain files called base.c, graphics.c, data.c, with main() being in base.c • The options are normally - • Compile just one file • Build - compiles any changed files, then links • Build all - compiles and links all • In a command line environment, you use a ‘make’ utility to do the equivalent
Scope across multiple source files • An external variable has scope across all files • It can only be defined in 1 file egint x; • In other files where it needs to be accessed, it must be declared as extern egextern int x; • Same applies to functions • An external defined as static has scope restricted to the one file egstatic int x;
Scope across files - example #include <stdio.h> void foo( void); int x; int y; int main() { foo(); return 0; } file1.c • extern int x in file2.c stops the compiler complaining that x is an undeclared identifier - it is declared in file1.c extern int x; extern int y; void foo() { x++; y++; return; } file2.c
Multiple file project exercise Start a new project Add two new files to it - called prog1.c and prog2.c prog1 should contain a global int called x, and a main function prog2 should contain a function called setX, which makes x equal to 4 The main in prog1 should call setX, then display the value of x
Structures • A data structure is a way of arranging and organising sets of data items • In C the struct keyword is used to help set up such structures • A struct is like a record or a row in a database - it consists of a set of named fields • The syntax for declaring a struct is likestruct structurename { type fieldname1; type fieldname2; .. } • A variable of this structure is then declared likestruct structurename variablename;
struct stockItemStruct { int barCode; double price; int stockLevel; }; int main() { struct stockItemStructbeans; struct stockItemStructcornflakes; beans.barCode = 100; beans.price = 1.49; beans.stockLevel = 50; cornflakes.barCode = 101; cornflakes.price=2.49; cornflakes.stockLevel = 35; printf("%i \n", beans.barCode); return 0; } struct example type variables structure member reference
struct exercise Design and write a struct suitable for an employee (no strings so no names yet) Declare 3 employee variables Give the fields suitable values Display the 3 employees Keep this program for future use
The preprocessor • This is a software tool which acts on the source code carrying out various textual processes - before the compiler operates • Preprocessor directives start with a # • The main ones are • #include - for including header files • #define - to define constants and macros • #if - conditional
#include • the purpose is to include header files. These define constants and prototype standard library functions • #include “myheader.h” will make the preprocessor look for myheader.h in the current directory - used for headers you write • #include <standard.h> means the preprocessor will look in an implementation-defined directory - used for standard headers like <stdio.h>
#define • used to define constants eg#define PI 3.1415962no equals, no ; • used to define macros - similar to functions • eg macro to find the larger of 2 values: #include <stdio.h> #define bigger( a, b ) a > b ? a : b int main() { int x; int y = 2, z = 3; x= bigger( y-3, z-1); printf(" x = %i\n",x); return 0; }
macro errors • macros are prone to bugs which are hard to see eg • a macro to square a number : #include <stdio.h> #define square( a ) a * a int main() { int y, x = 2; y=square( x ); printf(" y = %i\n",y); // get 4 - correct y=square( x+1 ); printf(" y = %i\n",y); // get 5 - wrong should be 9 return 0; } • because square(x) becomes x * x • but square ( x +1 ) becomes x + 1 * x + 1which is 2x+1 not x2 • should be square ( a ) ( a ) * ( a )
macro exercise Following the example #define bigger( a, b ) a > b ? a : b write a macro called cube which works out the cube of its argument test it works
#if - first technique • Writing code which is easily switched between platforms • eg a code fragment.. #if defined UNIX ... blah - UNIX-specific code ... #elif defined MSDOS .. DOS-specific code #else #error platform not specified #endif • then put a line in code at start#define UNIXand compile UNIX version, then change that to#define MSDOSand compile an MSDOS version
#if - second technique • problem with multiple includes eg each source code file in project contains#include <stdio.h>so the compiler will see lines like#define SEEK_END 2more than once, giving a macro re-definition warning • can solve problem with only doing #include <stdio.h> in one file - but very difficult to track.. • solution is like this .. stdio.h starts #ifndef _INC_STDIO #define _INC_STDIO ... .. rest of it #endif • on the first include, _INC_STDIO is not defined, so it defines it, and all the rest • on second and subsequent includes, it is defined, so rest of file ignored
pointers • pointers are actually addresses where values are stored in memory • but they are better thought of as being things which ‘point to’ data stored • symbol & means ‘the address of’so &x is the address of x - where it is stored • symbol * means ‘the value stored at’so *p is the value stored at where p points to
pointers - first example #include <stdio.h> int main() { int x; int * pointer_variable; x = 3; pointer_variable = &x; *pointer_variable = 4; printf("x = %i\n",x); return 0; } • x=3 does the following.. • x is stored at address 1003 (maybe), so.. • address value there1003 3 • pointer_variable = &x does this.. • &x is the address of x, so pointer_variable becomes 1003 • or, now pointer_variable ‘points to’ x • *pointer_variable means the value stored at the address pointer_variable • *pointer_variable = 4; makes the value stored at pointer_variable to be 4 • but that is where x is stored • so it changes x to 4 ..Address value1003 4
dynamic memory allocation • pointers are often used for dynamic storage - the program requests, uses and releases memory as it runs • functions for this are in <malloc.h> • int * block; declares block to be a pointer to integer • calloc(20, sizeof(int)) requests the use of some memory - enough for 20 items, each the size of an integer. calloc returns a pointer to the start of it, or NULL if there is not enough memory available • block = calloc(20, sizeof(int)); makes this request, and sets block to point to the start of it • *block is the first integer in this part of memory • *(block+1) is the second • *(block+19) is the last • *(block+20) is the usual error • free(block) releases the memory - so the system can re-use it
A program to - • get memory to store 20 integers in • store 0, 2, 4, 6, 8.. in it • print them out • release the memory dynamic memory example Output.. Offset 0 Value 0 Offset 1 Value 2 Offset 2 Value 4 Offset 3 Value 6 Offset 4 Value 8 Offset 5 Value 10 Offset 6 Value 12 Offset 7 Value 14 Offset 8 Value 16 Offset 9 Value 18 Offset 10 Value 20 Offset 11 Value 22 Offset 12 Value 24 Offset 13 Value 26 Offset 14 Value 28 Offset 15 Value 30 Offset 16 Value 32 Offset 17 Value 34 Offset 18 Value 36 Offset 19 Value 38 #include <stdio.h> #include <malloc.h> int main() { int i; int * block; block = calloc(20, sizeof(int)); if (block) { for (i=0; i<20; i++) *(block+i)=2*i; for (i=0; i<20; i++) printf("Offset %2i Value %i\n",i,*(block+i) ); free(block); } return 0; }
Pointer exercise Write a program which - obtains a block of memory to hold 10 integers fills the block with random values prints then out adds them up and displays the total
pointers and arrays #include <stdio.h> #include <stdlib.h> int main() { int numbers[ 10 ]; int i; for ( i = 0; i < 10; i++ ) *( numbers + i ) = rand(); for ( i = 0; i < 10; i++) printf("%i\n", numbers[ i ] ); return 0; } • arrays are implemented in C so that an array with an index is the same as a pointer to the start of a memory block with an offset • the program shown fills an array with 10 random numbers, then outputs them • numbers is declared as an array • When the array is filled, *( numbers + i )treats numbers as a memory block pointer • When it is output, numbers[ i ]treats numbers as an array again
functions with pointer arguments #include <stdio.h> void swap(int * a, int * b) { // exchange values at a and b int temp; // classic 3-cornered swap.. temp = *a; *a = *b; *b = temp; return; } int main() { int x=1, y=2; swap(&x, &y); printf("x = %i, y = %i\n", x, y); return 0; } • arguments are passed to functions by value ie copies • so foo(x,y) cannot change the values of x and y • functions can accept pointers as arguments • like foo( &x, &y ) • this cannot change the addresses of x and y • but it can alter the values stored at those addresses • which are the values of x and y • such as this example
the example coming up uses a function which returns a pointer to the largest value in an array • the main() uses this to find where the biggest number in an array is, then • print that value, • store INT_MIN there • and do this for however many numbers there are there functions returning pointers #include <stdio.h> #include <limits.h> #include <stdlib.h> int * biggest(int * numbers) { int * where; int offset; int biggestsofar = INT_MIN; for (offset=0; offset<10;offset++) if ( *(numbers+offset) > biggestsofar ) { biggestsofar = *(numbers+offset); where = numbers+offset; } return where; } Output.. 29358 26962 26500 24464 19169 18467 15724 11478 6334 41 int main() { int data[10]; int i; int * place; for (i=0; i<10; i++) data[i]=rand(); for (i=0; i<10; i++) { place = biggest(data); printf("%i \n", *place); *place = INT_MIN; } return 0; }
function and pointer exercise Write a program with no global variables but with functions which - create a block of 10 integers filled with random values a function to display the values in a memory block a function to reverse the values in a memory block main() should call the 'create' block, then display, then reverse, then display again
suppose we have a struct likestruct productStruct{int barCode;int stockLevel;} we can create one of these and get a pointer to it bystruct productStruct * prodPtr;prodPtr = calloc(1, sizeof(struct productStruct); we can access a field in this struct by –(*prodPtr).barCode = 99; but more convenient is the arrow notation – prodPtr -> barCode = 99; pointers to structs
pointers to structures exercise Re-use the employee struct Create a memory block of 100 employee structs Give them suitable random values Display them