#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:
Note that we now have a structure (in blue):
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:
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:
- Many pairs of header file/implementation file whose names only differ in their suffix. There should be one file for every class. Tightly related classes (i.e. friends) can (and should) share the same file. Changes in the implementation files that do not require header file changes should not affect any users of the classes defined there.
- One file containing the main() function. The processing in this file should be restricted to using classes defined elsewhere. This file should contain no class definitions.
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.hThe 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
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:
In C++, this is accomplished quite easily:
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:
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:
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.
- Public members are listed first. Usually these are functions. It is not a good idea to make data elements public, because you are then committing to a certain representation. It is better to write a function that will return the data member, because functions are more flexible. If you change your representation, you can change your member function to convert back to the old representation, keeping compatibility with old applications.
- Private members are listed last. They shouldn't be listed at all, really, but users need to know the size of the structure when allocating memory for it. There are techniques to effectively hide private data members, but they are too advanced to present now.