All at C -------- Episode 2 By Andy P++ This article is number two in a series that will probably peter out when one of us gets fed up with it! In the previous installment (what? you missed it!!) I introduced some basic concepts of C programs, the simple types of data and a couple of types of statement. In this article I promised you complex data types and pointers... well, I lied. Well, not so much a lie, more a change of heart. In fact I WILL talk about pointers, but complex data types can wait, instead I will introduce the full range of statements. This will allow a more significant example program to be written. POINTERS -------- This is a real doddle for the assembler folks... a pointer is just the address of a variable. If you understand that please skim the next few paragraphs. BASIC has no real equivalent for a pointer... although they are used extensively within any BASIC interpreter. A pointer is a reference to another piece of data. Perhaps an analogy would help: In a library I could give you the title of a book, that would be similar to the name of a variable. Alternatively I could simply show you where it was on the shelf, that would be a pointer. Both things allow you to get to the contents on the book (in slightly different ways). Note that the pointer method is slightly more hazardous, someone might move the book before you get there, or I might point to the wrong book. These sorts of problems, and more, afflict C pointers and make programs difficult to read and write. More on this later! To use pointers may seem odd, why get at something indirectly when a more direct approach can be used? The usefulness of pointers becomes more obvious when we talk of function arguments. To be a little more concrete about what a pointer is, it is a variable that contains the address (machine code address) of another variable. Lets introduce some notation that C uses for pointers. Remember from the last article that all data must be declared before use... for instance to create an integer variable called 'fred' we might have the line: int fred; To create a pointer called 'barnie' that is used to refer to an integer we need the line: int *barnie; The asterisk ('*') says that this is a pointer and in this case should be read as 'pointed to by'. Note that in C, unlike some languages, we must not only declare the variable to be a pointer, but also say what sort of thing it points to. In a similar way to the integer 'fred' not having a defined values yet, 'barnie' doesn't point anywhere in particular, THIS IS A VERY COMMON SOURCE OF ERROR, beware of uninitialized variables, they are a great way of causing 'gurus', trashed disks, and other such dire behavior. YOU HAVE BEEN WARNED. I will revisit this problem later. OK, we have a pointer, how can it be used? Assume we have 'fred' and 'barnie' defined as above. To initialize 'barnie' with a pointer to 'fred' we write: barnie = &fred; The ampersand ('&') should be pronounced 'address of'. The '&' is an operator that returns the address (or location) of the variable (or thing) it precedes. Now 'barnie' points to 'fred' we have two ways of getting at 'fred'. If we want to put the value 69 into 'fred' we could use the boring approach of: fred = 69; But that is much to simple, we can now be much more obscure and write: *barnie = 69; Note that the asterisk is used again here but in a different way. Now it means, 'the thing pointed to by'. Equally, if we want to read the value of 'fred' into another integer variable called 'wilma' we could use the boring: wilma = fred; Or the much more C-like: wilma = *barnie; The asterisk here can still be pronounced 'the thing pointed to by'. HOW TO CRASH YOUR AMIGA... I said I would come back to this. If you compile then run a program with the following lines in: int *oops; *oops = 69; Various odd and perhaps terminal thing may occur. What happens is that the variable 'oops' points to an integer somewhere, maybe anywhere, knowing our luck, somewhere critically important, in the AMIGA's memory. We then splat this location with the value 69, obliterating its previous contents. This can be BAD NEWS. Not only can this be serious but it can be difficult to trace since it may not be the same location every time. I hope I have put the fear of God into you, 90% of the most serious and difficult to trace C problems are down to variations on the uninitialized pointers theme. Enough of pointers for now... And now for something completely different: C STATEMENTS C programs are broken down into statements, these correspond to a line of BASIC. Statements DO something. Statements can usually be broken down into expressions. Expressions create a VALUE. Expressions are broken down into operators and operands, these are the basic building blocks of C. Informally the types of statement you can use are Assignment Compound statements Conditionals: If Switch Looping: While Do For Control Flow: Break Continue Return Goto Assignment ---------- We've already met this. This allows values to be moved around. wilma = 462; Take note here, the single equals sign should be pronounced 'has its value changed to', not 'equals'. To test for equality in C we use the double equals '=='. Compound Statements ------------------- Wherever a single statement can be used, so can a compound statement. A compound statement is a list of statements surrounded by curly brackets: { wilma = 462; fred = barnie * 16; *stegosaurus = bedrock / 43; } if -- Used when the programmer wants to do one of two things dependant upon some previous thing: if ( fred > barnie) wilma = 42; else wilma = 16; Note that the 'if' can have an 'else' associated with it to show what the alternative is. The 'else' may be omitted in which case the alternative is to do nothing. If you want to do more than one thing as an alternative then you must use compound statements as in: if ( fred > barnie) { roger = 44; henry = 8; } else { roger = 0; henry = 0; } This trick applies in all cases where normally only a single statement is allowed. switch ------ The 'if' statement was used to control one of two options. The 'switch' statement allows branching amongst many possibilities. If for instance we want to do different things for each day of the week, and we have a variable 'day' of value 1..7 then we may write: switch( day) { case 1: printf( "I hate mondays"); break; case 6: case 7: printf( "It's the weekend!!"); break; case 5: printf( "POETS day"); break; default: printf("Just another boring day..."); break; } This illustrates several things about the 'switch' statement. The switch statement contains an expression (in this case a simple variable) that is evaluated. The result on this is then matched against the values named by the 'case' statements. If a match is found then all statements after that are executed until the 'break' statement is reached. The same group of statements can match to several values by using more than one 'case' one after another. If no match is found then the 'default' is taken. Note that it is bad programming practice to omit the 'default' although the language allows you to do so. while ----- The most basic type of loop in C is the while loop. This allows repetition of a statement (simple, or more commonly compound) while some condition is true. donuts = 10; while( donuts > 0) { printf( "Yum-yum!\n"); donuts = donuts - 1; } Will print 'Yum-yum!' ten times (once to a line). Note that had zero been assigned to 'donuts' initially, nothing would have been printed. do ... while ------------ This less commonly used form of loop is used when the test is wanted AFTER the loop. Thus: raw_fish = 10; do { printf("Barf-barf!\n"); raw_fish = raw_fish - 1; } while ( raw_fish > 0); Will print 'Barf-barf!' ten times. This time, had zero been assigned, then 'Barf-barf!' would have been printed once. for --- This is the most complex built-in form of loop. If you understand 'while' loops, it is easiest to explain 'for' loops in 'while' loop form, hence: for( expr1; expr2; expr3) statement; Can also be expressed as: expr1; while( expr2) { statement; expr3; } To explain in words. The first expression is evaluated once at the start of the loop and is usually some form of initialization of the loop counter. The second expression is the test to be performed before each loop iteration, and must evaluate to true to continue. The third expression is executed at the end of each loop and usually increments the loop counter. Two quite common usages of the 'for' loop are: for( counter = 0; counter < loopcount; counter++) { /* This bit gets done 'loopcount' times with */ /* 'counter' running from 0..loopcount-1 */ } for(;;) { /* This is the traditional C forever loop */ /* it will spin until we do something special */ /* to get out of it */ } Loops, breaks and continues --------------------------- As an added flexibility in loops, two types of statement (apart from return, exit, and goto) can affect the flow of control. We have already met 'break' in the switch statement. It may also be used to prematurely get out of a loop. It immediately causes the nearest enclosing loop to terminate as if one had simply jumped out of it (it does not affect any loop counters). An example for our forever loop. for(;;) { x = x + 1; if ( x > 200) break; } Will run until 'x' is greater than 200. The 'continue' statement is more rarely used. When encountered inside a loop it causes a jump to the next iteration of the loop. It is usually used when we want to selectively process certain values. An example: for( x = 0; x < 200; x++) { if ( (x % 2) == 0) continue; /* Process all odd values of x here */ /* (% is modulo function) */ printf( "x is odd"); } return ------ This statement is used to return from functions... I shall deal with it later. goto ---- Bad for your programming health, I refuse to discuss it further. --------------------------------------------- Well, that's all the new stuff for this lesson. Lets use what we know to produce a slightly larger example program. The problem: To produce a program to play 'whizz, buzz, fizz'. In this version of the game the competitor must call out ascending numbers, but whenever the number contains a 3 or is divisible by 3 the number is replaced with the word 'whizz'. Similarly 'buzz' for 4, and 'fizz' for 7. If more than one condition applies then all the relevant words must be used. A solution: This solution is undoubtedly not 'optimal' in any sense, other than an educational one, we are really playing with the constructs we have learned. There are much better ways of programming this in C but that will have to wait until we have introduced some more concepts. /* Lets have some variables to play with, in C we must declare variables before use */ int number; /* The number to consider */ int whizz; /* Set this if 3 in it */ int buzz; /* Set this if 5 in it */ int fizz; /* Set this if 7 in it */ main() /* Start of program */ { int check; /* Use for local tests */ int remainder; /* Used to check digits */ /* Set up a loop for 1 to 100 */ for( number = 1; number <= 100; number++) { /* Clear our whizz, buzz, fizz variables */ whizz = 0; buzz = 0; fizz = 0; /* Consider all the digits by dividing by 10 and checking remainder */ check = number; /* Keep on dividing until we reach 0 */ while ( check > 0) { remainder = check % 10; check = check / 10; /* Look for 3, 5, or 7 */ switch( remainder) { case 3: whizz = 1; break; case 5: buzz = 1; break; case 7: fizz = 1; break; default: /* Don't care otherwise */ break; } } /* Right, we have checked individual digits, lets check for divisibility */ if ( (number % 3) == 0) whizz = 1; if ( (number % 5) == 0) buzz = 1; if ( (number % 7) == 0) fizz = 1; /* OK, we have done all the checks and whizz, buzz and fizz reflect the results */ if ( (whizz + buzz + fizz) == 0) { /* All zero so just show number */ printf( "%d\n", number); /* then go onto next one */ continue; } /* If we get here then its a special case */ if ( whizz == 1) printf( "whizz "); if ( buzz == 1) printf( "buzz "); if ( fizz == 1) printf( "fizz "); /* Tidy up by adding a newline */ printf( "\n"); } } Epilogue -------- If you thought all that a bit lengthy, two interesting bits of C code to puzzle over, you will have to know something about pointers. Answers to what they do in the next article!! a) int *head, *p; for( p = head; p; p = *p) { /* Some code using p here! */ } b) char *d, *s; while( *d++ = *s++); In Our Next Thrilling Installment --------------------------------- At last! Complex data types. Also starring functions and procedures (with special guest star, function prototypes!). Bet you can't wait eh!!