Types

If we consider computer memory to be a sequence of bits then we can understand/interpret the bits if we were to say: “consider these 8 bits as a coded representation of a character” or “consider these 32 bits as a coded representation of an integer”. So how do we interpret the bits? We do this in C by using types. So we say “a type helps a program interpret a sequence of (memory) bits”. A person can change the type associated with a sequence of bits using the “cast” operation.

int x;
double y = 3.45;
x = (int) y; //interpret the bits in y as an integer
printf(“x is %d”, x); //will print x is 3

‘Built-in’ types aren’t fixed and may change from compiler to compiler. In the standard of the language there are limits set that give a minimum range of values that a basic type must support. For instance, the standard language rules that an integer should at least be 16 bits, that a short integer should have a smaller range than a long, etc.

Function/Variable Declaration and Definition

We can declare functions and variables. A declaration introduces an identifier to the compiler. The identifier is the name of a function or variable. If you just want to declare without allocating any storage within your code you add the keyword extern:

extern double sqrt(double); //Note no curly braces

extern int a;

Optionally, you can define a function or variable. Note that we effectively declare it when we define it! When we define we are allocating storage within our code. We can define functions and variables.

double get(int) {……}; //Code would go inside curly braces

int age;

int count =200; //Here we (declare) define and assign a value (a.k.a. initialization)

Note:

We can only initialize with values the compiler can understand, or compile time constants. The following won’t work because the compiler can’t ‘perform’ the sqrt function:

int a = sqrt(9);

However, we can we can initialise a variable with an unknown value within a function!

double fn(double f) {

double d = sqrt(f); //**** Compiler won’t complain

// more statements

}

The syntax of C declarations has been criticized for being quite obscure. Peter van der Linden uses a simple algorithm to read them. He proposes :

The Precedence Rule for Understanding C Declarations.

· Rule 1: Start with the name and then read in precedence order as follows.

· Rule 2: The precedence, from high to low, is:

o 2.A: Parentheses grouping together parts of a declaration

o 2.B: The postfix operators:

§ 2.B.1: Parentheses ( ) indicating a function prototype, and

§ 2.B.2: Square brackets [ ] indicating an array.

§ 2.B.3: The prefix operator: the asterisk denoting "pointer to".

· Rule 3: If a const and/or volatile keyword is next to a type specifier (e.g. int, long, etc.) it applies to the type specifier. Otherwise the const and/or volatile keyword applies to the pointer asterisk on its immediate left.

Using those rules, we can even understand a thing like:

char * const *(*next) (int a, int b);

R1: We start with the variable name, in this case “next”. This is the name of the thing being declared. We see it is in a parenthesized expression with an asterisk, so we conclude that “next is a pointer to…” well, something. We go outside the parentheses and we see an asterisk at the left, and a function prototype at the right. Using rule 2B1 we continue with the prototype. “next is a pointer to a function with two arguments”. We then process the asterisk: “next is a pointer to a function with two arguments returning a pointer to…” Finally we add the char * const, to get “next” is a pointer to a function with two arguments returning a pointer to a constant pointer to char.

Now let’s see this:

int *abc[10];

char (*j)[20]; //the declaration of j

j = malloc ( sizeof(j) ); //the definition of j

The first example is straight forward: abc is an array ..of 10 elements .. each of which contains an integer pointer.

Next, we start with “j is a pointer to”. At the right is an expression in brackets, so we apply 2B2 to get “j is a pointer to an array of 20”. Yes what? We continue at the left and see ”char”. Done. “j” is a pointer to an array of 20 chars. The second line defines j and allocates memory space for it.

Note that we could use the following declaration without an identifier when making a cast:

x = ( char (*) [20] ) malloc ( sizeof(d) );

We see, in bold and enclosed in parentheses, a cast. It’s the same as in the declaration but without an identifier.

enum

enum <optional-identifier>{ list-of-unique-names} <optional-variable(s)-of this type> ;

Enumerations provide a convenient way to associate constant values with names, an alternative to ‘#define’ with the advantage that the values can be generated for you. An enumeration is a list/or series of constant integer values identified by unique names. The optional-identifier is an optional name for the enumeration.

enum {CAT, DOG} myPet; //new enum type and a variable called ‘myPet’ of this type

myPet = DOG;

printf("myPet == DOG == %i\n", myPet); //prints myPet = 1

enum boolean { NO, YES }; //NO == 0 and YES == 1 by default

The first tag in the list has value 0, the next 1, and so on, unless explicit values are specified. If not all values are specified then unspecified values continue the progression from the last specified value. Tag names in different enumerations must be distinct. Values need not be distinct in the same enumeration.

enum escapes {BELL = '\a', BS= '\b', TAB = '\t', NL = '\n', VTAB = '\v', RETURN = '\r' };

enum months {JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC }; /* FEB is 2, MAR is 3, etc. */

We can also declare variables of the enum type if you have given a name/optional-identifier to your enumeration:

enum boolean { NO, YES }; //have an identifier

typedef enum boolean ans, reply; //ans is an ‘enum boolean’ type and so is reply

ans x = NO;

ans y = YES;

reply a = YES;

reply b = NO;

Examples where identifiers are used;

//CREATE VARIABLE OF THIS TYPE

enum fruits {APPLE, ORANGE, BANANA} var1, var2, var3; //create variables var1 var2, var3 here

enum fruits myfruit; //and create a variable myfruit of type ‘enum fruits’

myfruit = ORANGE;

var1 = myfruit;

printf("%i", myfruit);

//CREATE NEW TYPE

enum fruit{APPLE, ORANGE, BANANA};

typedef enum fruit F_TYPE; //create a new type called F_TYPE of type ‘enum fruit’

F_TYPE fruit1 = BANANA;

printf("%i", fruit1);

enum size {SMALL, MEDIUM, LARGE};

typedef enum size packet1, packet2; //create new types packet1 and packet2

packet1 apkt= LARGE;

packet2 bpkt = SMALL;

enum boolean { NO, YES };

enum boolean ans;

ans = YES; //Assigned value must be one defined in the enum declaration

ans = 1; //!!!! Compile time error, 1 is not is list

Although variables of enum types may be declared, compilers need not check that what you store in such a variable is a valid value for the enumeration. Nevertheless, enumeration variables offer the chance of checking and so are often better than #defines. In addition, a debugger may be able to print values of enumeration variables in their symbolic form. Note: The actual value of an enum variable is an int however you cannot use it to index an array element without an explicit cast.

structure

struct <struct-shorthand-name> {

<field-type n1;>

<field-type n2;>

} <variable-name>;

Consider a point on a graph the x, y components can be placed in a structure declared like this:

struct point {

int x;

int y;

} point1, point2 ;

The keyword struct introduces a structure declaration, which is a list of declarations enclosed in braces. An optional name called a structure-shorthand--name-tag may follow the word struct. The structure-shorthand-name-tag names this kind of structure, and can be used subsequently as a shorthand for the part of the declaration in braces. The variables named in a structure are called members. A structure member or structure-shortcut-name-tag and an ordinary variable can have the same name without conflict, since they can always be distinguished by context. Furthermore, the same member names may occur in different structures, although as a matter of style one would normally use the same -'names only for closely related objects.

If we want to define a bin to hold printer cables. The structure definition is:

struct bin {

char name [30]; /* name of the part * /

int quantity; /* how many are in the bin */

int cost; /* The cost of a single part (in cents) * /

} printer_cable_bin; /* where we put the print cables */

This definition actually tells C two things. The first is what a struct bin looks like. This statement defines a new data type, struct bin, that can be used in declaring other variables. The variable printer_cable_bin is also declared by this statement. Since the structure of a bin has been defined (i.e. inside the braces) and we have also gone to the bother of giving the contents of the braces a shorthand name (i.e. bin) we can use it to declare additional variables :

struct bin terminal_cable_box; /* Place to put terminal cables */

The structure-name part of the definition may be omitted but it means that the contents of the braces must be written each time a variable of this type is defined:

struct {

char name[30];

int quantity;

int cost;

} printer_cable_bin;

Structure can also contain other structures

#include <stdio.h>

struct chain{

int value;

struct chain * next; };

void printchain(struct chain *);

void printchainAuto(struct chain * achain);

int main(){

struct chain first, second;

first.value=10;

first.next = &second;

second.value = 20;

second.next = NULL;

//printchain(&first);

printchainAuto(&first);

}

void printchain(struct chain * achain){

printf("first value %i ", achain -> value);

printf("second value %i\n", (achain -> next) -> value);

}

void printchainAuto(struct chain * achain){

printf("Auto print ...\n");

do{ printf("value %i \n", achain -> value);

achain = achain -> next;

}while( achain != NULL);

}

//~ pdunne@pd-pc ~/code

//~ $ ./run

//~ Auto print ...

//~ value 10

//~ value 20

typedef

typedef existing-type new-name for-this-type

C provides a facility called typedef for creating useful, more informative names for data types including new data types such as enum and structure. Remember it does not create a new type it simply adds a new name for an existing type. It is interpreted by the compiler, unlike #define which is interpreted by the pre-processor, and is more powerful than a #define. Examples of exiting type names would include: int, double, char[30], enum days, structure etc. Typically capitalized names are used for typedefs to make them stand out.

Besides purely aesthetic issues, there are two main reasons for using typedefs. The first is to parameterize a program against portability problems. If typedefs are used for data types that may be machine-dependent, only the typedefs need change when the program is moved. One common situation is to use typedef names for various integer quantities, then make an appropriate set of choices of short, int, and long for each host machine. Types like size_t and ptrdiff_t from the standard library are examples.

The second purpose of typedefs is to provide better documentation for a program - a type called Treeptr may be easier to understand than one declared only as a pointer to a complicated structure.

typedef int Length_t;

typedef char[ ] String_t

typedef enum colours {BLUE, RED, GREEN} Colour_t;

Length_t width, height = 23;

String_t name;

Colour_t sun, moon = BLUE;

if (moon == RED) puts(“Ahhh”);

/* Compute the next day. '/

enum day {sun, mon, tue, wed, thu, fri, sat}; // a new type called ‘enum day’

typedef enum day dow; //a nicer name for this new type

day find_next_day(dow d) //using the nicer name

{

dow next_day;

switch (d) {

case sun: next_day = mon ; break;

case man: next_day = tue; break;

….

case sat: next_day = sun; break;

}

return next_day;

}

structure point{

int x;

int y; };

typedef struct point point_t;

point_t p1, p2

C:\gmitweb\cprog\C-Notes-PaulDunne.doc