#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 problem that can come up, which is illustrated by the following example:
point.h | point.cpp | main1.cpp |
|
|
|
triangle.h | triangle.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 problem 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 #include
d in
main.cpp
, so that's the first definition of point in main.
However, main also #include
s triangle.h
, which in
turn #include
s 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 #define
s. 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
#define
d 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 #define
d 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 |
|
|
|
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 #define
s 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 #ifndef
s 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
committee 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 minutes 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, 2014 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
committee 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
fashion, 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 modify 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 elapsed in hours: 0.5 Currently: Slowpoke (140.601,34.0993) 37 degrees 10 mph Time elapsed in hours: 2.5 Currently: Slowpoke (142.105,36.0959) 37 degrees 10 mph Time elapsed 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!