Chapter 6

User-Defined Types

Subsections of User-Defined Types

Enums

enum provides a way to create integer constants. For instance, in Java we might declare:

public static final int GOLD = 1;
public static final int SILVER = 2;
public static final int BRONZE = 3;

..but really what we want is to create a “medal” type that can take on the values GOLD, SILVER, and BRONZE. Moreover, we want GOLD to mean “1”, SILVER to mean “2”, and BRONZE to mean “3”.

(Note: Java does have enums available in its latest version, which are handled very similarly to how they are in C.)

Defining Enums

Here’s the format for defining an enum in C:

enum typeName {
    value1,
    value2,
    ...
    valueN
} objectList;

Here, typeName is the name of the new data type we’re creating. value1, value2, ..., valueN are the different values this type can have. Finally, objectList is a list of variables we want to create of type typeName. In this definition, typeName and objectList are both optional.

Here’s how we would represent medals using an enum:

enum medal {gold, silver, bronze};

Enum Variables

Now, the type of this enum is enum medal. All variables of this type can have a value of either gold, silver, or bronze. Here’s how to declare a variable:

enum medal medal100m;

And here’s how to give this variable the value gold:

medal100m = gold;

We can also use the possible values gold, silver, and bronze in comparisons:

if (medal100m == gold) {
    printf(You won!\n);
}

What’s Going On?

When you declare an enum type, what you’re really doing is making the enum values into integer constants. For example, in the medal enum, we’re really creating the integer constants:

gold = 0;
silver = 1;
bronze = 2;

So, for instance, to set medal100m to gold, we could also say:

medal100m = 0;

Then the test:

if (medal100m == gold) {...}

would still evaluate to true, since gold holds the value 0.

Changing Enum Values

Our medal example isn’t quite right. It has gold with the value 0, silver with 1, and bronze with 2. But we would like gold to have the value 1, silver 2, and bronze 3. Fortunately, there’s a way to do that:

enum medal {gold = 1, silver, bronze};

Setting gold to 1 resets the numbering in an enum, so that silver gets set to 2 and bronze to 3. We also could have explicitly set each value:

enum medal {gold = 1, silver = 2, bronze = 3};

Here’s another example:

enum example {val1, val2 = 9, val3, val4 = 12, val5};

Here’s how the integer values were assigned:

val1 = 0;
val2 = 9;
val3 = 10;
val4 = 12;
val5 = 13;

An enum tries to keep numbering in order, unless we explicitly set a value. Then, it uses that value to reset the numbering.

Examples

There is no bool type in C. However, we can create our own bool type using an enum:

enum bool {false, true};

(Notice that false has the value 0 and true has the value 1). Now we can use “bool variables” like we do in C#:

enum bool flag;
flag = true;
if (flag == true) {...}

We can also use an enum to represent grade levels in high school. We’ll start freshman out at 9, and continue the numbering from there:

enum grade {freshman = 9, sophomore, junior, senior};

Now we can use this type:

enum grade class;
class = freshman;
class = 10;         //same as sophomore

Enums inside Structs

Since an enum is a valid type, we can put an enum field inside a struct. For example, suppose we want to create a student struct that has a name and a grade level. Here’s how:

//define the enum type
enum grade {freshman = 9, sophomore, junior, senior};

//define the struct
struct student {
    char name[20];
    enum grade class;
};

This code works, but it’s overkill. Instead of defining the enum separately, we want to be able to define it inside the struct. In fact, ALL we want is a variable of that enum type – we don’t even care about naming the type. The enum name is optional, so we’ll leave it off. Here’s our revised definition:

struct student {
    char name[20];
    enum {freshman = 9, sophomore, junior, senior} class;
};

Now, suppose we want to create a student named Mary who is a junior. Here’s how:

struct student mary;
strcpy(mary.name, "Mary");
mary.class = junior;

Unions

A union is a construct in C that can hold one of several types. A union variable can only hold one value at a time, unlike a struct, but that value is not restricted to a single type.

Here’s the format for declaring a union:

union modelName {
    type1 name1;
    type2 name2;
    ...
    typeN nameN;
} objectList;

Here, modelName is the name of the type you’re creating, each type name is a type you want to be able to store in this union plus a name for it, and objectList is a list of variables you want to create with this union type. Both modelName and objectList are optional.

Defining Unions

Suppose I want a type that will allow me to store money as dollars (in a double field) or as yen (in an int field). Here’s how I’d do that:

union money {
    double dollars;
    int yen;
} price;

This creates a variable called price that has type union money.

I could also create a variable called price2 like this:

union money price2;

Union Fields

To access a field in a union, use the . notation – just like with a struct:

variableName.fieldName

(variableName is the name of the union variable, and fieldName is the name of the field that we want to access.) If we had a pointer to a union variable, we would use the -> notation to access a field.

For example, I can make the price variable hold 17 yen:

price.yen = 17;

Or I can make the price variable hold $4.20:

price.dollars = 4.20;

Note that when I change the value of price from 17 yen to $4.20, I lose the information about 17 yen. A union variable can only hold one value at a time, but that value can have different types.

Unions and Enums

Suppose we have a variable of type union money, and we want to print out its value (in either dollars or yen, depending on what is stored). With what we’ve done so far, there is no way to tell WHICH type is stored in a union variable – it might be yen and it might be dollars. If we do:

union money cost;
cost.yen = 17;
printf("Dollars: %lf\n", cost.dollars);

which sets the yen field but then prints the dollars field, the behavior is undefined. This code is likely to crash or at least have unpredictable results.

To solve this problem, we need to keep track of which type we’re storing in a union variable. The most common way to do this is with an enum. For example, we could declare:

enum costK {dollarsK, yenK};

Then if we did:

union money cost;
cost.yen = 17;

We could also do:

enum costK costEnum;
costEnum = yenK;

Then we could tell which type was stored in our union variable by checking the value of our enum variable:

if (costEnum == yenK) }
    printf("Yen: %d\n", cost.yen);
}
else if (costEnum == dollarsK) {
    printf("Dollars: %lf\n", cost.dollars);
}

We will print the value of the field that we’re currently using in the union.

Structs inside Unions

A struct is a valid type, so we can have a struct be a field in a union. For example, suppose we wanted to create an animal union that could be either a dog (struct type) or a cat (struct type). Furthermore, we want to keep track of the name and breed of the dog, and the name and color of the cat.

Here’s how:

union animal {
    struct {
        char name[20];
        char breed[20];
    } dog;
    struct {
        char name[20];
        char color[20];
    } cat;
};

Notice that we don’t name the dog struct or the cat struct because we only want a single variable of that type. Instead, we create a single dog variable and a single cat variable. Here’s how we create a union variable that holds an orange cat called Tiger:

union animal kitty;
strcpy(kitty.cat.name, "Tiger");
strcpy(kitty.cat.color, "orange");

Unions inside Structs

Similarly, we can put a union inside a struct. For example, suppose we wanted to keep track of someone’s name, what country they’re from, and how much their bill is (in the appropriate currency). We could store the country with an enum, and the bill (with different currency) as a union. Here’s how:

struct guest {
    char name[20];
    enum {USA, France, Japan} country;
    union {
        double dollars;
        double euro;
        int yen;
    } cost;
};

Now if we want the guest Maria from France, who paid 100 euro, here’s what we’d do:

struct guest maria;
strcpy(maria.name, "Maria");
maria.country = France;
maria.cost.euro = 100.0;

Unions as Inheritance

The union construct allows us to simulate inheritance from other languages. For example, suppose we defined the following abstract class in C#:

public abstract class Person {
    public string name;
    public int age;
}

Suppose as well that we have two C# classes, Student and Employee, which extend Person:

public class Student : Person {
    public string major;
    public double gpa;
}

public class Employee extends Person {
    public string division;
    public int yearsWorked;
}

The equivalent in C would be a person structure that holds a name, age, and a union field. The union field could hold either a student struct (which has a major and gpa) or an employee struct (which has a division and a yearsWorked). We would also need an enum field that keeps track of which type in the union is active.

Here’s what it would look like:

struct person {
    char name[20];
    int age;
    union {
        struct {
            char major[20];
            double gpa;
        } student;
        struct {
            char division[20];
            int yearsWorked;
        } employee;
    } type;
    enum {employeeK, studentK} typeK;
};

Suppose a student has the following information:

  • Name: Bob Jones
  • Age: 18
  • Major: CMPEN
  • GPA: 3.2

Here’s how we would create a variable to represent that student:

struct person bob;
strcpy(bob.name, "Bob Jones");
bob.age = 18;
strcpy(bob.type.student.major, "CMPEN");
bob.type.student.gpa = 3.2;
bob.typeK = studentK;

typedef

It gets a bit cumbersome to use types called struct person and enum grade. It would be much nicer to be able to call them just person or grade. With C’s typedef construct, we can do just that.

The format of typedef is:

typedef oldType newType;

Here, we rename type oldType to the new name newType. We can now create variables using the name newType. For example:

typedef char letterGrade;
letterGrade g;
g = 'A';

Notice that we treat our new letterGrade type just like it was a char. The only difference is that when we declare the variable, we can use letterGrade as the type instead of char.

Typedef with Structs, Unions, and Enums

We can do a similar thing to rename structs, unions, and enums. For example, consider the following struct:

struct person {
    char name[20];
    int age;
};

The formal type of the struct is struct person. Now, we want to rename the type to be just person:

//typedef oldType newType
typedef struct person person;  

Alternatively, we can declare the struct and rename it with typedef all on the same line:

//typedef oldType newType
typedef struct person {
    char name[20];
    int age;
} person;

However, we don’t need to name the struct now, since we’re always going to be using the new type name when creating variables of this type:

typedef struct {
    char name[20];
    int age;
} person;

Now we can declare and use a person variable:

person p;
strcpy(p.name, "Bill");
p.age = 22;

We can do a similar thing to rename unions and enums. Consider the following union:

union money {
    double dollars;
    int yen;
};

We can rename the union type to money:

typedef union {
    double dollars;
    int yen;
} money;

Now we can use money as the type name instead of union money. Similarly, consider the following enum:

enum grade{freshman = 9, sophomore, junior, senior};

We can rename the enum type to grade:

typedef enum {freshman = 9, sophomore, junior, senior} grade;

Now we can use grade as the type name instead of enum grade.

Typedef with Linked Lists

Consider the following structure for a node in a linked list:

struct node {
    int data;
    struct node *next;
};

We can try to rename the struct node type to node using typedef:

typedef struct {
    int data;
    node *next;
} node;

However, this will give us a compiler error. The reason is that this struct is self-referential. When we declare the field node *next in the struct, the compiler hasn’t yet seen that we’re renaming the type to node. If instead we list the field as:

struct node *next;

we will also get a complaint, as we left off the name of the struct. If you’re using typedef on a self-referential struct, you need to include BOTH the name of the struct and the name of the renamed type. The fixed node struct looks like:

typedef struct node {
    int data;
    struct node *next;
} node;

Here’s how we’d use it:

node *head = malloc(sizeof(node));
head->data = 4;
head->next = malloc(sizeof(node));
head->next->data = 7;
head->next->next = NULL;

This creates the linked list 4->7.

User-Defined Types with Pointers

Consider the following struct:

typedef struct {
    char name[20];
    int age;
    union {
        struct {
            char major[20];
            double gpa;
        } student;
        struct {
            char division[20];
            int yearsWorked;
        } employee;
    } type;
    enum {employeeK, studentK} typeK;
} person;

Suppose we want to create a pointer to a struct variable with the following information:

  • Name: Bob Jones
  • Student
  • Age: 18
  • Major: EECE
  • GPA: 3.2

First, we’d declare a pointer of type person:

person *p;

Then we’d allocate memory:

p = malloc(sizeof(person));

And then we would initialize the fields:

strcpy(p->name, "Bob Jones");
p->age = 18;
strcpy(p->type.student.major, "EECE");
p->type.student.gpa = 3.2;
p->typeK = studentK;

Notice that we use a -> to access any fields in the struct (since our variable is a pointer). After we are inside an internal field like a union, we switch to . notation (since the union is not a pointer).

Preprocessor Directives

The C pre-processor is a special program that runs before the C compiler. It processes every line that begins with a #, such as a #include statement. The pre-processor may add, remove, or change your code when handling the # statements.

#include

We’ve been using the #include statement since our first program in order to get access to C’s library functions (like printf in stdio.h). When the pre-processor sees a #include statement, it replaces the #include line with the contents of the included file. For example, when the pre-processor sees:

#include <stdio.h>

then it replaces that line with the contents of the stdio.h file (which includes definitions for printf, scanf, fprintf, etc.). Then, when the compiler sees a call to printf, it knows what you mean because printf is defined in your file.

Including a file like:

#include <stdio.h>

tells the pre-processor to look for stdio.h in the standard include directory (which is where all the library files live). If instead you say:

#include "stdio.h"

the pre-processor will first look for stdio.h in the current directory. If it can’t find the file, it will hen look in the standard include directory.

#define

The #define statement tells the pre-processor to find all occurrences of one value in your code, and to replace them with another value. This can be used in two ways: to define constants and to define macros (simplified functions).

Constants

A constant in C is defined like this:

#define name value

Here, name is the name we are giving the constant, and value is the value we would like it to have. Here is an example:

#define PI 3.14159

Now, we can use PI in our code when we want the value 3.14159. For example:

int radius = 10;
double area = PI * radius * radius;

When the pre-processor sees a #define constant, it replaces all occurrences of the constant’s name with it’s specified value. So by the time the pre-processor gets done with it, the above code looks like:

int radius = 10;
double area = 3.14159 * radius * radius;

Macros

A macro is simplified function that includes a name and a list of arguments. They are defined using the #define statement, just like constants. For example:

#define SUM(a, b) a+b

When the preprocessor sees a call to the macro, it will replace the macro call with the macro formula. For example, if we do:

int x = 3;
int y = 4;
int result = SUM(x, y);

Then the pre-processor will change the last line to be:

//Uses x as a and y as b in the macro formula
int result = x + y; 

Just like other pre-processor directives, the compiler never sees the macro itself. It only sees the final result, after the pre-processor has replaced the macro call with the macro formula.

As another example, let’s try to write a macro that computes the difference of two squares:

//This is not right!
#define DIFF_SQUARE(a, b) a*a – b*b 

This macro may look right (and this is how we would write a diffSquare function), but it has some problems. For example:

int x = 4;
int y = 3;
int c = 2;
int d = 1;
int result = DIFF_SQUARE(x-c, y-d);

The pre-processor will replace the call to DIFF_SQUARE with the macro value. It will use x-c as the value for a, and y-d as the value for b. Here’s what the last line will become:

int result = x-c*x-c  y-d*y-d;

However, what we WANT is:

int result = (x-c)*(x-c)  (y-d)*(y-d);

To get this substitution, we need to add parentheses to the original macro:

#define DIFF_SQUARE(a, b) (a)*(a) – (b)*(b)

Here is a macro that returns the minimum of two numbers:

#define MIN(a, b) a < b ? a : b

This uses the ternary conditional operator. It means: if a is less then b, then a is the minimum. Otherwise, b is the minimum.

Here is a macro that eats all input left in the input buffer. This can be VERY useful as any unexpected input will stay in the input buffer. It reads one character at a time until it runs out of input.

#define FLUSH() while (getchar() != '\n')

#ifdef, #ifndef

There is also a pre-processor if-statement that checks to see whether or not a constant has been defined. Here’s the format:

#ifdef (pre-processor constant)
code
#endif

The code inside this #ifdef/#endif block will only be compiled if the pre-processor constant was defined. For example:

#define SIZE 10

//(elsewhere)
#ifdef SIZE
int nums[SIZE];
#endif

In this example, we only include the int nums[SIZE]; line in our program if the SIZE constant has already been defined.

There is a similar construct called #ifndef, which evaluates to true if a constant has NOT been defined. For example:

#ifndef SIZE
printf("Define SIZE!\n");
#endif

The printf statement will only be part of the program if the SIZE constant has not been defined. The #ifndef statement will be very useful when we start to break our programs into several files – it will help ensure that we don’t end up including the same information twice when we link our files together.