#include.
This lecture we'll look into more detail about how that's
done, the organization benefits of doing it, and a few of the
pitfalls. We considered a program like this:
| point.h | point.cpp | main0.cpp |
~/$ g++ -c point.cpp compiles point.cpp to produce point.o ~/$ g++ -c main0.cpp compiles main0.cpp to produce main0.o ~/$ g++ -o ex0 main0.o point.o links point.o and main0.co to produce executable ex0... or you can compile and link it all at one go, like this:
~/$ g++ -o ex0 main0.cpp point.cppLarge programs can take minutes or even hours to compile from scratch. When you compile separately, a change in one .cpp means only that .cpp needs to be recompiled - for the rest, the old .o files can be reused. So separate compilation is the way to go once projects start to grow.
bool f(int); in one
place and string f(int); in another..h file
and use #include to put them in other files. This is
wisdom, pay attention to it!
Now, there's one trick that can come up, which is illustrated by the following example:
| point.h | point.cpp | triangle.h | triangle.cpp | main1.cpp |
$ g++ -c point.cpp
$ g++ -c triangle.cpp
$ g++ -c main1.cpp
In file included from triangle.h:1:0,
from main1.cpp:2:
point.h:1:8: error: redefinition of 'struct point'
point.h:1:8: error: previous definition of 'struct point'
The trick is that this causes the struct point to be
defined twice in main.cpp. Why? Well,
point.h includes the definition, and
point.h is #included in
main.cpp, so that's the first definition of point
in main. However, main also #includes
triangle.h, which in turn #includes
point.h, which has the definition of the struct
point, so that's the second definition of point in main.
Admittedly, if the programmer was on top of things he could've
avoided this, but it's not always avoidable, and it's expecting
alot of the guy who uses structs point and
triangle to go back and worry about this stuff.
The way around this problem is to guard your
.h files with #defines.
The #include that we've been using and the
#define that we're about to use are examples
of "preprocessor directives". The #define lets
you give a string x and say "every time you see x
in the source code, replace it with string y." This can
be used and abused in all sorts of ways, but for the moment
we'll simply use #define to determine
whether or not this is the first time the compiler has seen our
.h while compiling a give
.cpp file. We can test whether or not a
certain string has been #defined already
using the #ifndef preprocessor directive,
which we read as "if not defined".
For example
cout << "This is "; #ifndef SILLYSTYRING cout << "not "; #endif cout << "much fun!" << endl;If
SILLYSTRING has been #defined
previously in our program, the word not won't get
printed out --- the #ifndef SILLYSTRING asks
if it's true that SILLYSTRING hasn't been defined. If
it is, then all the text until the #endif is seen by
the compiler. Otherwise, as in our case, it is as if the code in
between was never there.
How does this help us with our multiple-definition problem?
point.h | triangle.h | main.cpp |
#ifndef POINTHEADER
#define POINTHEADER
struct point
{
double x, y;
};
void read(point&);
#endif |
#include "point.h"
struct triangle
{
point vertex[3];
};
void read(triangle&); |
#include "point.h"
#include "triangle.h"
int main()
{
triangle T;
read(T);
point P = T.vertex[0];
} |
main.cpp we first
#include the file point.h. Since this
is the first time the compiler has seen point.h
it'll find that POINTHEADER has not been defined.
Therefore all the code until the #endif, which is
essentially the whole file, will be seen and evaluated by the
compiler. This will include the line that #defines
POINTHEADER. Now, the next time we come across
point.h, which is when main.cpp does the
#include "triangle.h", the compiler will hit that
#ifndef line and will find that
POINTHEADER has been defined, and therefore
everything up to the #endif, which is essentially
the whole file, will be ignored by the compiler. This way we
never get any multiple definitions.
Golden rule number 2: All .h files should
be "protected" by #ifndefs this way!
load
source, where source is
either keyboard or a filename, depending on
whether the user will enter the committee info by hand or
read it from a file. From a file or from the user, the
format should be committee name on the first line,
committee chair name on the second line, and the other 4
committee member names on the following lines.
makechair first last, where
first last is the name of the new chair.
writeminutes destination
date minutes
endminutes
The minutes should be written to the file
destination, and should begin with the date, the
comittee name, the chair, and the rest of the committee in
alphabetical order. Dates should be input in mm/dd/yyyy
format, but should appear in the minuts as
<day> <monthname>, <year>
save filename
The file saved should be in the proper format to read in
the next time.
comm.txt it makes.
| Program: | load comm.txt makechair Chris Brown writeminutes CCM110802.txt 11/08/14 New Chairman Brown wrested control of the Committee from the senior faculty! endminutes save comm.txt quit |
| CCM110814.txt | 8 November, 2019 Minutes of the Committee on Confusing Midshipmen Chris Brown, Committee Chair Committee Members: Ric Crabbe Dan Roche Gavin Taylor Don Needham Minutes: New Chairman Brown wrested control of the Committee from the senior faculty! |
| comm.txt | Committee on Confusing Midshipmen Chris Brown Ric Crabbe Dan Roche Gavin Taylor Don Needham |
Committee that
stored all the info for a committee, and functions
read and write for reading and writing
committees, both to-and-from files and to-and-from the screen".
If I had such things, perhaps in a file Committee.h,
I could write my main like this:
#include "Committee.h"
int main()
{
Committee C;
string command;
while(cin >> command && command != quit)
{
////// LOAD COMMAND //////
if (command == "load")
{
string source;
cin >> source;
if (source == "keyboard")
read(C,cin);
else
{
ifstream fin(source.c_str());
read(C,fin);
}
}
///// SAVE COMMAND /////
if (command == "save")
{
string name;
cin >> name;
ofstream fout(name.c_str());
write(C,fout);
}
}
return 0;
}
Clearly what we need to do is go off and give the struct definition and
function prototypes in a file Commitee.h, and later
we can get around to actually implementing the functions in a file
Committee.cpp. Let's focus on defining the struct
Committee first, since this is needed for the two
prototypes. Well ... a committee has a name, which we'll store in
a string, and it has five comittee members, one of whom is the Chair.
A committee member has a first and a last name, so there's no
built-in type to represent that for me. So, I say "I wish there
was a type Member that could store a first and a last
name for me ... and I'll probably want read and write functions
for this type, as well." Well, let's assume there is! (It's
.h file might look like
Member.h and, in bottom-up fahion, whose
.cpp file might look like
Member.cpp) We'll define
Committee as:
struct Commitee
{
string name;
Member chair, mems[4];
};
So that a Committee consists of a name, a chair and 4 members.
Additionally we want functions for reading and writing, whose
prototypes would be:
void read(Member&,istream&);
void write(Member,ostream&);
Now, the struct Member comes from the
Member.h header file, so our Committee.h
header file looks like
/***************************************
** Header file for struct Commitee and
** associated functions
***************************************/
#include "Member.h"
#ifndef COMMITTEEHEADER
#define COMMITTEEHEADER
struct Committee
{
string name;
Member chair, mems[4];
};
void read(Committee&,istream&);
void write(Committee,ostream&);
#endif
Putting the whole program together, we have:
Member.h ----------------------> Member.cpp
| |
| |
V |
Committee.h ---> Committee.cpp |
| | /
| | /
V | /
main.cpp | /
\ | /
\ | |
\ V V
------------> THE PROGRAM
if (command == "makechair")
{
Member M;
read(M,cin);
makechair(C,M);
}
In other words, you read in a member and say "I wish there were a
function makechair that could modifiy the committee
to make a new member the chair" ... and then assume there is!
The prototype for makechair would naturally be added
to Committee.h and the definition of the function
would naturally reside in Committee.cpp. The
structure of the program in terms of files looks just like it did before.
.h
file and have its own .cpp file as well.
In main.cpp we'll add the code for our next command,
which will "assume" we have a function "writeminutes",
because gee wouldn't it be easier if we did?
if (command == "writeminutes")
{
string name;
cin >> name;
ofstream fout(name.c_str());
writeminutes(C,fout);
}
Of course, now we have to go and implement writeminutes
and create Date.h and Date.cpp. When we
do, we end up with a program that, on the file level, is
structured like this:
Member.h ---------------------------------> Member.cpp
| |
| Date.h ----> Date.cpp |
| | | |
V V | |
Committee.h ---> Committee.cpp | |
| | | /
| | | /
V | | /
main.cpp | / /
\ | / /
\ | / /
\ V \/ /
------------> THE PROGRAM <--------
Point defined in dbpoint.h
and dbpoint.cpp. You
can use this point struct to track position.
Your program will read from the user info on a single boat. Specifically, you will be given its name, position (as an (x,y) pair giving its position in miles from some fixed point), its heading (in degrees from north) and its speed in miles per hour. It will then read a number of hours from the user and update the boat's position after that number hours. It will keep reading number of hours and printing over and over for as long as the user wants.
This function might also help you:
Point move(Point position, double heading, double speed, double t)
{
double PI = 3.14159;
Point d;
d.x = t*speed*sin(heading*PI/180);
d.y = t*speed*cos(heading*PI/180);
return position + d;
}
A run of the program should look like this:
Enter boat info: Slowpoke (140.3,33.7) 37.0 degrees 10 mph Currently: Slowpoke (140.3,33.7) 37 degrees 10 mph Time ellapsed in hours: 0.5 Currently: Slowpoke (140.601,34.0993) 37 degrees 10 mph Time ellapsed in hours: 2.5 Currently: Slowpoke (142.105,36.0959) 37 degrees 10 mph Time ellapsed in hours: quitRequirements: You must make a struct
Boat
that stores all the necessary information about a boat, and
make all the work (like reading and writing and moving the
boat) into functions. There should be a file
Boat.h and Boat.cpp that implements
all of this stuff. Your main function should be
very simple, and should just include Boat.h.
If you do this right, it would be trivial to transform this
program so that it would track many boats simultaneously!