• No results found

Suppose that you want to represent character strings as

struct string {

int length;

char *data; /* malloc'd block */

};

int stringLength(const struct string *s);

If you later change the representation to, say, traditional null-terminatedchar * strings or some even more complicated type (union string **some_string[2];), you will need to go back and replace ever occurrence of struct string * in every program that uses it with the new type. Even if you don’t expect to change the type, you may still get tired of typingstruct string *all the time, especially if your fingers slip and give youstruct stringsometimes.

The solution is to use atypedef, which defines a new type name:

typedef struct string *String;

int stringLength(const String s);

The syntax fortypedeflooks like a variable declaration preceded bytypedef, except that the variable is replaced by the new type name that acts like whatever type the defined variable would have had. You can use a name defined with typedefanywhere you could use a normal type name, as long as it is later in the source file than thetypedefdefinition. Typicallytypedefs are placed in a header file (.hfile) that is then included anywhere that needs them.

You are not limited to usingtypedefs only for complex types. For example, if you were writing numerical code and wanted to declare overtly that a certain

quantity was not just anydouble but actually a length in meters, you could write

typedef double LengthInMeters;

typedef double AreaInSquareMeters;

AreaInSquareMeters rectangleArea(LengthInMeters height, LengthInMeters width); Unfortunately, C does not do type enforcement ontypedef’d types: it is perfectly

acceptable to the compiler if you pass a value of type AreaInSquareMeters as the first argument to rectangleArea, since by the time it checks it has replaced by AreaInSquareMeters and LengthInMeters by double. So this feature is not as useful as it might be, although it does mean that you can write rectangleArea(2.0, 3.0)without having to do anything to convert2.0and 3.0to typeLengthInMeters.

4.12.1 Opaque structs

There are certain cases where the compiler needs to know the definition of a struct:

1. When the program accesses its components.

2. When the compiler needs to know its size. This may be because you are building an array of thesestructs, because they appear in a larger struct, when you are passing thestructas an argument or assigning it to a variable, or just because you appliedsizeofto thestruct.

But the compile doesnot need to know the definition of astructto know how create a pointer to it. This is because allstructpointers have the same size and structure.

This allows a trick called anopaque struct, which can be used forinformation hiding, where one part of your program is allowed to see the definition of a structbut other parts are not.

The idea is to create a header file that defines all the functions that might be used to access thestruct, but does not define thestructitself. For example, suppose we want to create a counter, where the user can call a functionincrementthat acts like ++in the sense that it increments the counter and returns the new value, but we don’t want to allow the user to change the value of the counter in any other way. This header file defines theinterfaceto the counter.

Here is the header file:

/* Create a new counter, initialized to 0. Call counterDestroy to get rid of it. */ struct counter * counterCreate(void);

/* Free space used by a counter. */

/* Increment a counter and return new value. */

int counterIncrement(struct counter *); examples/structs/opaqueStructs/counter.h

We can now write code that uses thestruct counter *type without knowing what it is actually pointing to:

#include <stdio.h> #include <stdlib.h> #include <assert.h> #include "counter.h"

int

main(int argc, char **argv) {

struct counter *c;

int value;

c = counterCreate();

while((value = counterIncrement(c)) < 10) { printf("%d\n", value); } counterDestroy(c); return 0; } examples/structs/opaqueStructs/testCounter.c

To make this work, we do have to provide an implementation. The obvious way to do it is have astruct counterstore the counter value in an int, but one could imagine other (probably bad) implementations that did other things, as long as from the outside they acted like we expect.

We only put the definition of astruct counterin this file. This means that only functions in this file can access a counter’s components, compute the size of a counter, and so forth. While we can’t absolutely prevent some other function from extracting or modifying the contents of a counter (C doesn’t provide that kind of memory protection), we can at least hint very strongly that the programmer shouldn’t be doing this.

#include <stdlib.h> #include <assert.h>

#include "counter.h" struct counter { int value; }; struct counter * counterCreate(void) { struct counter *c;

c = malloc(sizeof(struct counter)); assert(c);

c->value = 0;

return c; }

void

counterDestroy(struct counter *c) {

free(c); }

int

counterIncrement(struct counter *c) {

return ++(c->value); }

examples/structs/opaqueStructs/counter.c

We will see this trick used over and over again when we buildabstract data types.