Previous Section Table of Contents Next Section

Brief Summary of C

Statements in C end with a semicolon (;). C treats all whitespace as equivalent, so line breaks and indents are for readability only (with a couple of exceptions that won't matter here). Blocks of code are surrounded with braces, { and }.

Comment lines begin with the characters // and everything after that marker on a line is ignored. (This is one of the rare cases in C where a line break has a different meaning from other whitespace, because only a line break will end a // comment.) Comments can also be delimited by a starting /* and an ending */. Within those comments, a line break is like any other whitespace, and has no effect on the comment.

C code is run through a pre-processor before it is compiled. The main way in which programmers are aware of the pre-processor is that it can substitute constant definitions throughout the code; so for example, the following pre-processor statement


#define  ARRAY_SIZE  20


causes the pre-processor to substitute 20 every place it sees ARRAY_SIZE. #define can be used to define macros with arguments that are replaced, such as the following


#define  NEGATIVE(x)  (-(x))


but that won't be used in the examples in this chapter.

Data Types and Variables

The basic data types in C are int and char. An int holds an integer value, whose length can vary depending on the platform; 4 bytes is typical. (None of the code here depends on the exact length of an int.) A char is an integer that can hold a single byte. Single characters are surrounded by single quotes, such as 'a' and 'x'; the value of a character constant is the numeric value of the character in the machine's character set (for example, 'A' is 65 in ASCII). There are other data types for floating-point numbers and integers of different sizes, which are not used in this book.

Variable and function names are case-sensitive. Variables are declared with the type followed by the name, for example:


int counter;


Multiple variables can be declared together, separated by a comma:


char letter, lastbyte, direction;


Arrays in C are denoted with square brackets and indexed from 0, so


int scores[20];


allocates room for 20 ints, of which scores[0] would be the first and scores[19] the last.

Assignment is done with the = sign:


counter = 0;


Variables can be declared and initialized in one step:


int bytecount = 0;


Arithmetic is as expected, with expressions grouped using parentheses:


counter = counter + 1;

lastbyte = ((direction - 5) * 6) / 2;


The statement


++counter;


is shorthand for


counter = counter + 1;


Bitwise and, or, and xor can be done with the &, |, and ^ operators:


mask = mask & 0x07;

finished = finished | 1;

checksum = checksum ^ array[i];


Casting between types is done by preceding the expression with the type name in parentheses:


int k;

char y;

k = (int)y;


Strings

Strings are simply arrays of type char, with the last element containing a 0 value, written as a single character '\0'. Thus, the length of a string can be less than the size of the char array where it is stored. Declaring


char name[10];


allocates room for a string that can be up to 9 bytes long, because one byte must be left for the terminating '\0'. (You could put a different character in the tenth byte, but it would not be a properly terminated string, according to C conventions.) The code


name[0] = 't';

name[1] = 'e';

name[2] = 'd';

name[3] = '\0';


sets the name to be "ted", with the 6 extra bytes unused at that point. A string in double quotes, such as "hello", is converted by the compiler into a char array including the final '\0', so "hello" occupies 6 bytes.

Pointers

Pointers are declared with *. For example:


char * city;


This only allocates storage for the pointer itself. Pointers can be declared together with variables of the type, so


char * city, name;


declares a pointer to a char called city, and a char (not a pointer) called name. char pointers are often assigned from string literals. For example


city = "Boston";


automatically allocates the 7 bytes needed to store the string "Boston" and sets city to point to it.

The value NULL can be assigned to pointers to indicate that they point to nothing.

Pointers are also dereferenced with *, so *city is the first byte pointed to by city. In fact, pointers and arrays are often used interchangeably, and the first char in the city array could be referenced as city[0] or *city. Note that C does not check the validity of pointers, so *city likely causes a crash if city is uninitialized, and name[20] gives an undefined result if name is allocated as previously mentioned, with room for only 10 chars.

Pointer arithmetic is allowed and automatically compensates for the size of the element pointed to. Thus, city+2 points to 2 bytes after city because a char occupies 1 byte. But for an int array declared as the following


int distances[5];


and assuming an int occupies 4 bytes, distances+2 will be 8 bytes after distances. Thus, array[n] is equivalent to *(array + n) and is defined as such.

Structures are defined as in the following example:


typedef struct _record {

    int element1;

    char element2;

    struct _record * next;

} record, * record_ptr;


This code combines two things (which could be separated if desired, but won't be in this book): the definition of the structure _record, and the creation of a new type record, which is equivalent to the more cumbersome struct _record. (It also defines a new type, record_ptr, which is a pointer to a record.) Within the structure definition itself, struct _record is used because the typedef is not finished, but from then on, record can be used instead.

Variables can then be declared such as the following:


record current_record;

record_ptr first_record;


The & operator returns the address of a variable, so with the previous declarations, you could write the following:


first_record = &current_record;


or


int j = 7;

int * jp = &j;


For clarity, in this book, programs use record * as opposed to record_ptr to indicate a pointer to a record structure. record_ptr * means a pointer to a pointer to a record.

Structures

Elements in a struct are referenced with . as


current_record.element1


For pointers, -> combines dereferencing a pointer to a structure and accessing an entry in the structure, as in the following:


first_record->element2;


This is equivalent to


(*first_record).element2;


or even


first_record[0].element2;


Conditionals

Conditional statements are defined as follows:


if (test-expression)

    true-code-block

else

    false-code-block


else and false-code-block are optional. The code blocks can be either a single statement, or multiple statements surrounded by braces. The if() is true if test-expression evaluates to a non-zero value (if() is false if it is zero). Comparisons are done with ==, !=, <, >, <=, and >=.

In C, an assignment is also an expression having the value of the left hand of the assignment. Therefore, the following assignment expression


c = 5


evaluates to 5, and you could write


d = (c = 5);


The ++ operator, which was previously shown, can be written before or after the variable. When it's written before the variable, it returns the new value, but when written after, the old value is returned. In the following example, both k and m will be set to 6:


j = 5;

k = ++j;

m = k++;


There is also a -- operator that works the same way for subtracting 1.

In C, it is a common mistake to write


if (c = 5)


because the assignment always evaluates to 5; it's therefore always non-zero and always true, instead of the following:


if (c == 5)


This evaluates as expected-true if c is equal to 5, false otherwise.

There is no specific boolean type. Any non-zero value is considered true and zero is considered false. Therefore


if (c)


is the same as


if (c != 0)


Conditionals can be grouped together with && (logical and) and || (logical or):


if ((j > 5) && (j < 10))

if ((*byte == 0) || (endoffile))


These are different from the & and | bitwise operators: An && expression is true if the expression on both sides is non-zero; || is true if the expression on either side is non-zero. Furthermore, the expression on the right of && is evaluated only if the expression on the left is true (otherwise there is no point in doing so, since the overall result will be false no matter what), and the expression on the right of || is evaluated only if the expression on the left is false (for similar reasons). So, you can write code like this:


if ( (openfile(a) != INVALID) && (readfile(a)) )


Loops

Loops can be done with a for statement:


for (init-statement ;

    test-expression ;

    iteration-statement )

    for-code-block


Typically, init-statement initializes a loop counter, test-expression involves the loop counter, and iteration-statement modifies the loop counter (but that is not always true):


int array[20];

for (i = 0; i < 20; i++) {

    { /* code involving array[i]; */ }

}


This walks through the elements of array. Note that test-expression is i < 20, not i <= 20, because entries in an array of size 20 are accessed as i[0] through i[19].

test-expression is evaluated at the beginning of each iteration through the loop, and if it is true (non-zero), for-code-block is executed. At the end of the loop, iteration-statement is executed. From anywhere within a loop, the statement continue jumps to the end of the loop (causing iteration-statement to execute and then beginning another check of test-expression and possible iteration of the loop); the statement break immediately leaves the loop without executing iteration-statement.

There is also a while loop:


while (test-expression)

    while-code-block


This loop evaluates text-expression each time, and executes while-code-block if it is true. continue and break can also be used within while loops.

Functions

Functions are defined as follows:


return-type

function-name(type1 argument1, type2 argument2)

{

    local-variable-declarations;

    function-code;

}


If argument1 is followed with [], it is an array, as shown in the following example:


int find_largest ( int array[], int array_length )


local-variable-declarations consists of variables declarations that are local to the function.

The return statement exits a function. return should be followed by a variable of the proper return-type for the function. A special return-type of void in the function declaration means the function does not return a value and the return statement needs no arguments. Functions that return type void can end without a return statement.

    Previous Section Table of Contents Next Section