Next Lecture | Exercises | Top Level

Functions, Modules, Simple Classes

Contents


Next Section | Contents

Functions

Let's write a C program. My program will calculate the square root of a real number. Here it is:

#include <stdio.h>

static double epsilon = 0.001;

main()
{
  double x;
  double low, high;
  double root;

  scanf("%E", &x);

  low = 0; high = x;

  while (high - low > epsilon) {
    root = (high + low) / 2.0;
    if (root * root > x)
      high = root;
    else
      low  = root;
    printf("%E %E %E\n", high, low, root);
  }

  printf("The square root of %E is %E\n", x, root);
}
Exercise: This program has a bug. Find it. (Hint: for some numbers, it will not find the (real) square root even though it exists.)

Now let's suppose we wanted to calculate the square root of many numbers. We could, of course, run the whole program again and again, but let's suppose we wanted to use this code inside another program. We could simply insert copies of this code at all places where we would want to calculate a square root (inlining). This is ugly, because:

The solution is to use a function. Let's rewrite the program using a function:

prog1.c

Note that we now have a structure (in blue):

Note the different scope of the names used (in green).


Previous Section | Next Section | Contents

Modules

Our Monopoly project is a big program. We will use many functions. It is important that we keep our code organized.

C and C++ uses the file system as its primary organizer. The code is broken down in several files. In the example above, the red indicates how we break down our square root program.

The resulting files look like this:

sqrt.h
sqrt.h
sqrt.c
sqrt.c
main.c
main.c
Let's review those three files and their meaning: The advantage of dividing up the code this way is that we have a division of responsibilities. We can assign one programmer to maintain a module, and a different programmer to use the module. These two programmers communicate via the header file, in this case sqrt.h.

The maintainer of the sqrt-module can change implementation details without affecting the users of his module. Only when the interface changes (as documented by changing the header file) is the user of the module required to pay attention.


Next Idiom | About Idioms
Any program consisting of several files should have the following structure:



Previous Section | Next Section | Contents

Separate Compilation

Remember how compilation works in general:

  1. The compiler (gcc) translates your source code (extension .c) into object code (extension .o).
  2. The linker (ld) links different object files into one big executable.
By using our module structure, we can automatically keep track of which object files need to be redone. The tool of choice to do the tracking is make.

To use make, we simply need to write down the dependencies between modules. In our case, the dependency looks like this:

sqrt: main.o sqrt.o

main.o: main.c sqrt.h
sqrt.o: sqrt.c sqrt.h
The first line tells us that our executable program (sqrt) is made out of the main part that uses the square root module and the module itself.

Now, the main part depends on the source code for the main part and on the header file of the square root module. This means that if the header file is changed, we need to recompile main.c. If, on the other hand, the implementation of the square root module is changed, main.c does not need to be recompiled.

The square root module depends on itself and its header file - nothing special here.

All we need to do is to tell make that we are using the gcc compiler by adding a line at the beginning, and ready is our Makefile:

# This is the Makefile
CC=gcc

sqrt: main.o sqrt.o

main.o: main.c sqrt.h
sqrt.o: sqrt.c sqrt.h
# End of Makefile

Previous Section | Next Section | Contents

Structures

Let's write a new program. This time, we will make a module to handle complex numbers. Remember complex numbers are of the form x + yi, where i*i = -1, x and y are real.

To store those numbers, we will use a structure. It looks like this:

struct complex {
  double x, y;
};
We will need functions to read and write complex numbers:

void read(struct complex *z);
void write(struct complex *z);
We might want to do some operations on complex numbers, for example calculating the conjugate:

void conjugate(struct complex *z);
Note that we pass pointers to the structures, so that we don't have to move the structures themselves.

Using our module conventions from above, this is what the program will look like:

/* This is complex.h */

struct complex {
  double x, y;
};

void read(struct complex *z);
void write(struct complex *z);

void conjugate(struct complex *z);
/* end of complex.h */


/* This is complex.c */
#include <stdio.h>
#include "complex.h"

void read(struct complex *z)
{
  scanf("%E%E", &z->x, &z->y);
}

void write(struct complex *z)
{
  printf("%E %E\n", z->x, z->y);
}

void conjugate(struct complex *z)
{
  z->y = -z->y;
}
/* end of complex.c */


/* This is main.c */
#include <stdio.h>
#include "complex.h"

main()
{
  struct complex a;

  read(&a);
  conjugate(&a);
  write(&a);
}
/* end of main.c */
Again, we have the interface to our module in complex.h, the implementation in complex.c, and we are using our masterpiece in main.c.

The situation with our complex number module is a very typical one. Almost every module that accomplishes non-trivial stuff has:

The idea of object-oriented languages is that you can group the data and the functions into one unit, an object or a class.

In C++, this is accomplished quite easily:

complex.h
Complex.h

Note that the argument disappears. Since the function is now part of the structure, the function can access any data members of that structure. In fact, the functions are themselves considered to be data members, which is why they are called member functions.

Watch how the implementation changes:

complex.c Complex.C

Note that we have to specify to which structure the member function belongs. Just as you can reuse the same data element name in different structures, you can use the same member function name in different structures. The complex:: construct tells the compiler we are defining complex's member functions.

Watch how the usage changes:

main.c Main.C

Instead of applying a function to a variable, we simply call the variable's member function.


Previous Section | Contents

Classes

One of our major motivations for using modules is distributing responsibilities. We wanted to have the module maintainer keep control over how his module is implemented. This means that he should be able to hide some parts of his structures.

In the example above, anyone can access and modify the data members x and y of our complex number. If, for example, the maintainer of the complex class decided to represent complex numbers via polar coordinates, he would break every application that accesses the data members. Not good.

It is therefore essential to be able to protect and hide parts of your structure.

For this purpose, C++ has the class keyword. A class is almost like a struct, except that every data member is hidden by default. You can use public: to declare certain parts of your class to be accessible by the user.

This is how our complex class would look like using the new keywords:

class complex {
public:
  void read();
  void write();
  void congugate();
private:
  double x_, y_;
};

Previous Idiom | Next Idiom | About Idioms
Conventions for defining classes:

Private members should have names ending with an underscore (_). This makes reading the code of the member functions easier, as one can distinguish immediately between class components and other variables.

Next Lecture | Exercises | Contents
Christian Goetze