lecture 2 constructors:composition
DESCRIPTION
noneTRANSCRIPT
Wednesday, Jan 11th
• Collect Academic Integrity Forms• Constructors• Destructors• Class Composition• Composition with Initializer Lists• A few final topics required for
Project #1• Learning how to use the VC
debugger (time permitting)
class CSNerd{public:void Init(int PCs, bool usesMac){
m_numPCs = PCs; // # of PCs owned m_macUser = usesMac;}
int getNerdScore(void){
if(m_macUser == true) return(0); //not nerdy; “artistic” return(10 * m_numPCs);}
private: int m_numPCs; bool m_macUser;};
main(){ CSNerd david; david.Init(2,false); // geeky cout << david.getNerdScore();}
Constructors: Class InitializationEvery class should
have an initialization function that can be used to reset new variables before
they’re used.void Init(int PCs, bool usesMac){ m_numPCs = PCs; m_macUser = usesMac; }
int getNerdScore(void){ if(m_macUser == true) return(0); return(10 * m_numPCs);}
m_numPCs m_macUser
david
2 false 2 false
10*2 = 20
But there’s one problem with such an Init function… What is
it?
Here’s a hint!
Right! Our programmer might forget to call the Init function before using the
variable… What’ll happen?
Well, remember, all simple variables (e.g., ints, bools, etc.) in C++ start out with random values unless they’re explicitly initialized!
So if you forget to call the Init function, your CSNerd’s member
variables will have random values. Not what you’d want.
-32 false
Ack! How can someone have -32 computers?
class CSNerd{public:void Init(int PCs, bool usesMac){
m_numPCs = PCs; m_macUser = usesMac;}
int getNerdScore(void){
if(m_macUser == true) return(0); return(10 * m_numPCs);}
private: int m_numPCs; bool m_macUser;};
ConstructorsWouldn’t it be great if C++ would guarantee
that every time we create a new class
variable, it’ll be auto-initialized?
Well, as it turns out, that’sexactly what the C++
constructor does!
main(){ CSNerd david; cout << david.getNerdScore();}
class CSNerd{public:void Init(int PCs, bool usesMac){
m_numPCs = PCs; m_macUser = usesMac;}
int getNerdScore(void){
if(m_macUser == true) return(0); return(10 * m_numPCs);}
private: int m_numPCs; bool m_macUser;};
Constructorsmain(){ CSNerd chen;
chen.Init(3,true); cout << chen.getNerdScore();}
CSNerd
A constructor is a special member function that automatically initializes every new variable you create of that class.
The constructor is called automatically
every time you create a new
instance of your class.
(3,true);
(int PCs, bool usesMac)
CSNerd(int PCs, bool usesMac){ m_numPCs = PCs; m_macUser = usesMac; }
int getNerdScore(void){ if(m_macUser == true) return(0); return(10 * m_numPCs);}
m_numPCs m_macUser
chen
3 true
true3Instead of being called Init, the constructor
function has the same name as the class! (Confusing, huh?)
Since the constructor is called automatically any time you define a new
variable…
there’s no chance of a new variable being
uninitialized accidentally.
Constructors
You can define the constructor in the class
declaration (see above)…
class CSNerd{public:CSNerd(int PCs, bool usesMac){
m_numPCs = PCs; m_macUser = usesMac;}
...
private: int m_numPCs; bool m_macUser;};
class CSNerd{public:
CSNerd(int PCs, bool usesMac){ m_numPCs = PCs; m_macUser = usesMac;}
CSNerd(int PCs, bool usesMac){ m_numPCs = PCs; m_macUser = usesMac;}
CSNerd::
;
Or, outside the class declaration, like this…
...
private: int m_numPCs; bool m_macUser;};
To summarize… A constructor is a special function in your class that initializes a new variable when its first created.
The constructor function MUST have the SAME NAME AS THE CLASS!
The constructor has no return type! It’s not allowed! Notice it’s not void, int, or bool.
return(true); //illegal!}
And thus it’s not allowed to return a value.
Like all other member functions, the constructor’s logic can be defined inside or outside the class declaration.
Boy, isn’t that syntax ugly?
Just remember, to define a constructor
outside the class declaration:
use the class name, followed by ::, followed
by the class name.
And remember to also include just the function
header, followed by a semicolon, in the class
declaration itself.
class CSNerd{public:CSNerd(int PCs, bool usesMac {
m_numPCs = PCs; m_macUser = usesMac;}
int getNerdScore(void){
if(m_macUser == true) return(0); return(10 * m_numPCs);}
private: int m_numPCs; bool m_macUser;};
Constructors If a constructor requires parameters:
You must to provide values for those
parameters when you create a new
variable:
main(){
CSNerd ed(1,true); // OK CSNerd alan; // invalid!
}
)
main(){
CSNerd ed(1,true);//invalid! CSNerd alan; // OK!
}
= 0; = false;
class CSNerd{public:CSNerd(int PCs, bool usesMac ) {
m_numPCs = PCs; m_macUser = usesMac;}
int getNerdScore(void){
if(m_macUser == true) return(0); return(10 * m_numPCs);}
private: int m_numPCs; bool m_macUser;};
ConstructorsJust like any C++
function, a constructor can have one or more default
parameters…main(){
CSNerd lyn(1,false);
CSNerd ned(5); // OK!
CSNerd dave; //invalid!
cout << lyn.getNerdScore();
}
= true
1 false
5
true
class CSNerd{public:CSNerd(int PCs, bool usesMac = true) {
m_numPCs = PCs; m_macUser = usesMac;}
private: int m_numPCs; bool m_macUser;};
ConstructorsYour class can have
many different constructors. (this is called overloading
constructors)main(){
CSNerd lyn(1,false);
CSNerd ned(5); // OK!
CSNerd dave; //invalid!
cout << lyn.getNerdScore();
}
int getNerdScore(void){ if(m_macUser == true) return(0); return(10 * m_numPCs);}
CSNerd() { m_numPCs = 1; m_macUser = false;}
// OK!!!
One more thing: If you have 2 or more constructors,
they cannot have the exact same parameters/types.
CSNerd(int PCs=1, bool usesMac=true)
C++: “I’m confused! Should I call this constructor with default
parameters…”
C++: “…or should I call this constructor with no parameters?”
ConstructorsIf you don’t define any constructors at all…
main(){
CSNerd carey; // OK
}
In this case, your member variables are
never initialized.
cout << carey.getNerdScore(); //??
class CSNerd{public: int getNerdScore(void) { if(m_macUser == true) return(0); return(10 * m_numPCs); }
private: int m_numPCs; bool m_macUser;};
CSNerd() // generated by compiler{ // I do nothing at all. // I’m not worthy!!!!!}
then C++ will provide an implicit, default
constructor for you that basically does nothing!
Constructors & Arraysclass CSNerd{public:CSNerd(int PCs, bool usesMac = true) {
m_numPCs = PCs; m_macUser = usesMac;}
private: int m_numPCs; bool m_macUser;};
CSNerd() { m_numPCs = 1; m_macUser = false;}
...main(){ CSNerd lec1[4]; cout << lec1[0].getNerdScore();}
1false
1false
1false
1false
If you want to have an array of your class, your class must have a constuctor
that requires no arguments!
When you define an array, the constructor is run on
every element in the array!
Constructors
class CSNerd{public:
CSNerd(int PCs, bool usesMac) { m_numPCs = PCs; m_macUser = usesMac; }...
There are several different ways to initialize your member variables in a constructor. Here’s the easiest way:
Here’s a more complex way that does the same
thing…Class CSNerd{public:
CSNerd(int PCs, bool usesMac) { // I don’t need to do anything! }...
Or, if you’re really
masochistic, you can do a little of
both.
:m_numPCs(PCs),m_macUser(usesMac)
m_macUser = usesMac;
This is called an
“initializer list”
Constructor Challengeclass CSProf{public:
...private: string m_name; int m_age;};
Define a constructor that initializes a
CSProf.The user can specify the name of the prof
and his/her age.
If the user omits the age, then the prof’s default age is 39.
int main(void){ CSProf a(“David”,52); CSProf b(“Carey”);}
// version #1CSProf(const string &name, int age = 39){ m_name = name; m_age = age;}
// version #2CSProf(const string &name, int age = 39);
// version #2CSProf::CSProf(const string &name, int age){ m_name = name; m_age = age;}
// version #3CSProf(const string &name, int age = 39) : m_name(name), m_age(age){ // do nothing else}
Notice you don’t put the “= 39” here
too!
// version #4CSProf::CSProf(const string &name, int age) : m_name(name), m_age(age){ // do nothing else}
// version #4CSProf(const string &name, int age = 39);
When Constructors are CalledA constructor is called any time you
create a new variable for a class.
main(){
}
CSNerd carey(3,false), bill; // called once for each var
A constructor is called N times when you create an array of size N.
CSNerd arr[52]; // constructor’s called 52 times
A constructor is called when you use new to dynamically allocate a new variable or an array. (we’ll learn new
later)
CSNerd *ptr = new CSNerd(1,3); // c’tor called onceCSNerd *dyn = new CSNerd[6]; // c’tor called 6 times
The constructor is not called when you just define a pointer variable.
CSNerd *justAPtr; // c’tor is NOT called
Class ChallengeName all the times the CSNerd constructor is
called in this example…
class CSNerd{public: CSNerd() { m_numPCs = 1; m_macUser = true; } ...private: int m_numPCs; bool m_macUser;};
void foo(){ CSNerd *herbert = new CSNerd;}
void main(void){ int j; CSNerd *ptrToNerd; CSNerd xavier;
for (j=0;j<3;j++) { CSNerd a[5]; foo(); }}
DestructorsJust as every class has a constructor, every class also has
a destructor function (one and only one of these).
The job of the destructor is to de-initialize or destruct each variable of a class when the variable goes away.
Destructors return nothing and take NO parameters. class SomeClass{public:SomeClass(); // constructor~SomeClass(); // destructor!
private:... // whatever
};
SomeClass::~SomeClass(){ // destructor code}
To define a d’tor function, you use the
tilde ~ character in front of the class’s name:
Or define the destructor function outside your
class…
and just add a function header inside the class
declaration.
class SomeClass{public:SomeClass(); // constructor~SomeClass() // destructor!
{ // destructor code } private:... // whatever
};
Why do we need a Destructor?
class InternetBanking {public: void startBanking(string nm,string pw) { m_ic.connect(“payperusebank.com”); m_ic.send(nm); // send name m_ic.send(pw); // send password }
void stopBanking(){ m_ic.disconnect(); }
void deposit(float amount) { … }
private: InternetConnection m_ic;
};
Here we have a class that allows us to do internet
banking.
~InternetBanking(){ m_ic.disconnect();}
int main(void){ InternetBanking ib; string name, pw; cin >> name >> pw; ib.startBanking(name,pw); ib.deposit(300);}
Let’s see what happens when we use this class without a destructor.
PayPerUseBank.com(we charge by the minute!)
Ben DoverCSRules
Ben DoverCSRules
ib
Hmmm… ib disappears when main exits, but the
user forgot to call stopBanking, so our PC is
still connected to (and being charged by) the
bank!
$300
Of course, had we added a destructor… None of this would have happened…
Now, when our ib variable goes away,
C++ will automatically call it’s destructor!!!
$1000
$990$1290$1280$1270$1260Ben’s
Balance:
Wow – we just logged on and they’re already charging fees!
When must you have a destructor?
Any time a class…
Allocates data using the new commandOpens a disk file
Opens a network connection
Your class must have a destructor that…
Don’t forget or you’ll FAIL!
Frees the allocated memoryCloses the disk file
Disconnects the network connection
DestructorsSo when is a destructor called anyway?
Local function variables are destructed any time a function exits.
void Dingleberry(void){ SomeClass c;
} c’s destructor is called.
Local variables defined in a block are destructed when the block finishes.
for (int j=0;j<10;j++){ SomeClass v; // do something} v’s destructor is called each time we hit the end of the block
InternetBanking *ib = new InternetBanking(“Carey”,”MyPassword”);// do somethingdelete ib; The d’tor is called for ib by C++ here
Dynamically allocated variables are destructed when delete is called before the memory is actually freed by the operating system.
Oh, and what about arrays?
When an array goes away, the destructor is
called for every item in the array (just like construction).
Class Challenge #2Name all the times the CSNerd destructor is called
in this example…
void foo(){ CSNerd a, *b;}
void main(void){ int j; CSNerd *ptr, x; ptr = new CSNerd; for (j=0;j<3;j++) { CSNerd a[5]; foo(); } delete ptr;}
class CSNerd{public: CSNerd() { m_numPCs = 1; m_macUser = true; }
~CSNerd() { cout <<“Argh! I’m dying. Where’s my
iphone?”; } ...private: int m_numPCs; bool m_macUser;};
Question: How could we make our program more
efficient?(Hint: Look where this is
pointing)
Can you guess?
Programming Language Inventor
Or
Serial Killer
See if you can guess who uses a keyboard and who uses a chainsaw!
Class CompositionClass composition: If a class contains one or more classes as member variables, then you are using class composition:
class GasTank{ …};
class Car{ ...private: GasTank myTank;};
Class Composition
class Valve{ …};
class Heart{… private: Valve m_valves[4];};
class TinMan{ ...private: Heart m_myHeart;};
Class Composition
Class Composition
Compositionclass Stomach{public:
Stomach() { gas = 0; } ~Stomach() { cout << “boom!”; } void eat() { cout << “yummy”; gas ++; } void fart() { gas--; cout << “pffft!”; }private:
int gas;};
In this example, our Nerd class holds a Stomach
variable (every nerd has a stomach)
When using composition, the outer class (Nerd) can use all of the public functions of the contained variable (belly) in:
class Nerd{
public: Nerd() { thought=“Waaa”; }
void meetCuteGirl(string &herName) {
thought = “H.O.T. HOT!”;
}
~Nerd() { thought=“Argh”; }
private: string thought; Stomach belly;};
• The outer class’s constructor(s)belly.eat();
• The outer class’s functions
belly.fart(); • The outer class’s destructor
belly.eat( ); Ok, that was easy… But now let’s look into how
construction and destruction work with
composition…
Composition and Construction
When we create a Stomach variable, its constructor is
called.
int main(void){ Stomach s; ... // use s}
sgas 0
And when the variable goes away, its destructor is
called…
boom!
class Stomach{public:
Stomach() { gas = 0; } ~Stomach() { cout << “boom!”; } void eat() { cout << “yummy”; gas ++; } void fart() { gas--; cout << “pffft!”; }private:
int gas;};
When we create a Nerd variable, Nerd’s constructor
must be called.
int main(void){ Nerd david; ...}
But our Nerd contains a Stomach, so its constructor
must be called too!
So which constructor is called first? Nerd’s or
Stomach’s?
class Stomach{public:
Stomach() { gas = 0; } ~Stomach() { cout << “boom!”; } void eat() { cout << “yummy”; gas ++; } void fart() { gas--; cout << “pffft!”; }private:
int gas;};
Composition and
Construction
class Nerd{
public: Nerd() { thought=“Waaa”; belly.eat( ); }
void meetCuteGirl(string &herName) {
thought = “H.O.T. HOT!”; belly.fart(); }
~Nerd() { thought=“Argh”; belly.eat(); }
private: string thought; Stomach belly;};
Hint: How can Nerd’s constructor use the
belly variable if belly hasn’t been
constructed?
class Stomach{public:
Stomach() { gas = 0; } ~Stomach() { cout << “boom!”; } void eat() { cout << “yummy”; gas ++; } void fart() { gas--; cout << “pffft!”; }private:
int gas;};
class Nerd{
public: Nerd() { thought=“Waaa”; belly.eat( ); }
void meetCuteGirl(string &herName) {
thought = “H.O.T. HOT!”; belly.fart(); }
~Nerd() { thought=“Argh”; belly.eat(); }
private: string thought; Stomach belly;};
david thought
belly gas
int main(void){ Nerd david; ...}
C++ constructs the contained class first,
and the outer class second.
So our Stomach is constructed first and then Nerd is constructed after.
0
“Waaa”
yummy!
Composition and
Construction
1
Since the Stomach was already constructed, our Nerd class can use it in
its constructor!
class Stomach{public:
Stomach() { gas = 0; } ~Stomach() { cout << “boom!”; } void eat() { cout << “yummy”; gas ++; } void fart() { gas--; cout << “pffft!”; }private:
int gas;};
class Nerd{
public: Nerd() { thought=“Waaa”; belly.eat( ); }
void meetCuteGirl(string &herName) {
thought = “H.O.T. HOT!”; belly.fart(); }
~Nerd() { thought=“Argh”; belly.eat(); }
private: string thought; Stomach belly;};
david thought
int main(void){ Nerd david; ...}
Now what happens when our Nerd is destructed?
Destruction happens in the reverse order. The outside
destructor runs first, then the contained
destructor runs second.
“Waaa”
yummy
“Argh”
Composition and Destruction
Since the Stomach has not been destructed yet,
it can still be used by Nerd!
boom!
belly gas 01
Class Composition & Construction/Destruction
class Stomach{public: Stomach(void) { cout<<“Mmm food\n”; } ~Stomach() { cout << “Poof!\n”; }};
class Nerd{public: Nerd(void) { cout<<“I like integrals\n”; } ~Nerd() { cout << “Oh derivatives!\n”; } private: Stomach belly;};
int main(void){ Nerd herbert; ...}
What does it print?
Mmm foodI like integralsOh derivativesPoof!
Class Composition
Let’s examine a slightly different Stomach class.
Here’s the Stomach’s specification:
• When a Stomach is constructed, you must specify how many farts it starts with. It prints out the starting number of farts
• You can add farts to a Stomach with an eat method
• When a Stomach is destructed, it farts N times
class Stomach{public: Stomach(int startFarts) { farts = startFarts; cout << “Start farts: “ << farts; }
void eat() { farts ++; }
~Stomach() { while (farts-- > 0) cout << “pffft!”; }
private: int farts;};
int main(void){ Stomach a(5); // 5 farts
a.eat(); // 6 farts ...}
Stomach b; // ???
Now, what happens if we want to use our new
Stomach class in a Nerd?
Class Composition
class Nerd{public: Nerd(void) { thought = "CS"; }
~Nerd() { cout << “Argh “ << thought; }
private: Stomach belly; string thought;};
Will this work?
NO!
class Stomach{public: Stomach(int startFarts) { farts = startFarts; cout << “Start farts: “ << farts; }
void eat() { farts ++; }
~Stomach() { while (farts-- > 0) cout << “pffft!”; }
private: int farts;};
Class Composition
Class Compositionclass Nerd{public: Nerd(void) { thought = "CS"; }
~Nerd() { cout << “Argh “ << thought; }
private: Stomach belly; string thought;};
class Stomach{public: Stomach(int startFarts) { farts = startFarts; cout << “Start farts: “ << farts; }
void eat() { farts ++; }
~Stomach() { while (farts-- > 0) cout << “pffft!”; }
private: int farts;};
This won’t work because you must pass in a # of
farts any time you construct a Stomach. But our Nerd
doesn’t do that!
But doesn’t specify the
required starting # of farts!!!
Your Nerd has a Stomach variable…
class Nerd{public: Nerd(void) { thought = "CS"; }
~Nerd() { cout << “Argh “ << thought; }
private: Stomach belly; string thought;};
Q: How can we fix our Nerd?
A: By properly specifying the # of farts when we construct a Stomach??
(10); // Good?
class Stomach{public: Stomach(int startFarts) { farts = startFarts; cout << “Start farts: “ << farts; }
void eat() { farts ++; }
~Stomach() { while (farts-- > 0) cout << “pffft!”; }
private: int farts;};
Class Composition
class Nerd{public: Nerd(void) { thought = "CS"; }
~Nerd() { cout << “Argh “ << thought; }
private: Stomach belly; string thought;};
A: You MUST explicitly construct a contained class variable using an initializer
list in your constructor.
: belly(10)
class Stomach{public: Stomach(int startFarts) { farts = startFarts; cout << “Start farts: “ << farts; }
void eat() { farts ++; }
~Stomach() { while (farts-- > 0) cout << “pffft!”; }
private: int farts;};
Class CompositionThis means:
“Before you construct the Nerd…
first construct its Stomach by passing in a value of 10.”
class Nerd{public: Nerd(void) { thought = "CS"; }
~Nerd() { cout << “Argh “ << thought; }
private: Stomach belly; string thought;};
class Stomach{public: Stomach(int startFarts) { farts = startFarts; cout << “Start farts: “ << farts; }
void eat() { farts ++; }
~Stomach() { while (farts-- > 0) cout << “pffft!”; }
private: int farts;};
Class Composition
: belly(10)
int main(void){ Nerd carey;
...}
Start farts: 10
thought
carey bellyfarts 10
"CS"10
class Nerd{public: Nerd( void ) { thought = “CS”; }
~Nerd() { cout << “Argh “<< thought; }
private: Stomach belly; string thought;};
Class Composition
Challenge: Modify the Nerd class so you can pass in the number of farts of gas that the Nerd starts with.
In our current Nerd, the Stomach always starts out with 10 farts of gas…
class Nerd{public: Nerd(void) { thought = “CS”; }
~Nerd() { cout << “Argh “<< thought; }
private: Stomach belly; string thought;};
: belly(10) : belly( 10 )int farts farts
class Nerd{public: Nerd(int farts) : belly(farts) { thought = "CS"; }
~Nerd() { cout << “Argh “ << thought; }
private: Stomach belly; string thought;};
class Stomach{public: Stomach(int startFarts) { farts = startFarts; cout << “Start farts: “ << farts; }
void eat() { farts ++; }
~Stomach() { while (farts-- > 0) cout << “pffft!”; }
private: int farts;};
Class Composition
int main(void){ Nerd Carey(3);
...}
Start farts: 3
thought
Carey bellyfarts 3
“CS”
3 33
Class Composition
When you construct the NerdyCow, you should be able to specify how many farts start out in each stomach and its
thought.
Challenge:Our current Nerd only has one Stomach.
Create a NerdyCow class that has two Stomachs and a thought.
class NerdyCow{public: NerdyCow(int f1, int f2, string &idea)
{ thought = idea; }
~NerdyCow() { cout << “Moooo!”; }
private: Stomach belly1; Stomach belly2; string thought;};
: belly1(f1), belly2(f2)
class Nerd{public: Nerd(int farts) : belly(farts) { thought = "CS"; }
~Nerd() { cout << “Argh “ << thought; }
private: Stomach belly; string thought;};
class NerdyCow{public: NerdyCow(int f1, int f2, string &idea)
{ thought = idea; }
~NerdyCow() { cout << “Moooo!”; }
private: Stomach belly1; Stomach belly2; string thought;};
: belly1(f1), belly2(f2)
class Stomach{public: Stomach(int startFarts) { farts = startFarts; cout << “Start farts: “ << farts; }
void eat() { farts ++; }
~Stomach() { while (farts-- > 0) cout << “pffft!”; }
private: int farts;};
Class Composition
int main(void){ NerdyCow earl(7,8, “milk”);
...}
7 8 “milk”
thought
earl belly1farts 7
Start farts: 7Start farts: 8
belly2farts 8
“milk”78
A Few Final TopicsLet’s talk about three final topics that you’ll need in
order to solve your Project #1…
Topic #1: Include Etiquette
A. Never include a CPP file in another .CPP or .H file.
void someFunc(void){ cout << “I’m cool!”}
file1.cpp
#include “file1.cpp”
void otherFunc(void){ cout << “So am I!” someFunc();}
file2.cpp
You’ll get linker errors.
Only include .H files within a .CPP file.
Topic #1: Include Etiquette
B. Never put a “using namespace” command in a header file.
#include <iostream>
someHeader.h
#include “someHeader.h”
int main(){ cout << “Hello world!”;}
hello.cpp
So this is bad…
This is called “Namespace Pollution”using namespace std;
Why? The .H file is forcing CPP files that include it to use its
namespace.
And that’s just selfish.
Instead, just move the “using” commands into your
C++ files.
// BUT I DON’T WANT THAT// NAMESPACE! YOU BASTARD!
Topic #1: Include Etiquette
C. Never assume that a header file will include some
other header file for you.
class Student { ... private: };
student.h
#include “student.h”
int main(){
Student larry; Alcohol vodka;
cout << larry << “ is drinking “ << vodka;}
main.cpp
class Alcohol{ ... };
alcohol.h
main.cpp defines an Alcohol variable but it doesn’t #include"alcohol.h".
Why? It assumes that student.h will just include alcohol.h for it.
And in fact, this works right now, because student.h does include alcohol.h.
But what happens if the author of student.h decides
to change it’s implementation?
#include "alcohol.h"
Alcohol bottles[5];
#include “redbull.h”
RedBull bottles[5];
Utoh! Now main.cpp no longer compiles!
Why? Because it doesn’t include alcohol.h but it defines an Alcohol
variable!
What can you do?
Always DIRECTLY include the header files you need RIGHT
where you need them.
In this case, main.cpp has a Student variable and an
Alcohol variable. So it should include both of their header
files.
#include "alcohol.h"
Now your main.cpp file will compile correctly regardless of what’s
contained in the other header files it uses!
Topic #2: Preprocessor Directives
C++ has several special commands which you sometimesneed in your programs.
Command 1: #define
You can use the #define command to define new constants:
#define PI 3.14159
void someFunc(void){ cout << PI;}
file.cpp
You can also use #define to define a new constant without specifying a value! Why you ask? We’ll see!
#define FOOBAR
Preprocessor Directives
Command 2: #ifdef and #endif
You can use the #ifdef command to check if a constant hasbeen defined already…
#define FOOBAR
#ifdef FOOBARvoid someFunc(void){ cout << PI;}#endif
file.cppThe compiler only compiles the code
between the #ifdef and the #endif
if the constant was defined.
Since FOOBAR was defined I’ll
compile the code until
#endif.
Since FOOBAR was NOT defined
I’ll ignore the code until #endif.
/*
*/
Preprocessor Directives
Command 2: #ifndef and #endif
You can use the #ifndef command to check if a constant hasNOT been defined already…
#define FOOBAR
#ifndef FOOBARvoid someFunc(void){ cout << PI;}#endif
file.cpp
Since FOOBAR was defined I’ll ignore the code
until #endif.
/*
*/
Since FOOBAR was NOT defined I’ll compile the
code until #endif.The compiler
only compiles the code between the
#ifndef and the #endif if the constant was NOT
defined.
Separate Compilation
When using class composition, it helps to define each class in a separate pair of .cpp/.h files.
class Calculator{public: void compute(); ...};
calc.h
#include “calc.h”
int Calculator::compute(){ ...}
calc.cpp
#include “calc.h”
class Student{public: void study() ...private: Calculator myCalc;};
student.h
#include “student.h”
int Student::study(){ cout << myCalc.compute();}
student.cpp
#include “student.h”
int main(){ Student grace; ... grace.study();}
main.cpp
Then you can use them like this in your main program…
Now – something interesting (and bad) happens if I use both classes in my main program. Let’s see!
#include “student.h”#include “calc.h”
int main(){ Student grace; Calculator hp; ... grace.study(); hp.compute();}
main.cpp
Can anyone see what the problem is?
Separate Compilation
class Calculator{public: void compute(); ...};
calc.h
#include “calc.h”
int Calc::compute(){ ...}
calc.cpp
#include “calc.h”
class Student{public: void study() ...private: Calculator myCalc;};
student.h
#include “student.h”
int Student::study(){ cout << myCalc.compute();}
student.cpp
#include “student.h”
int main(){ Student grace; ... grace.study();}
main.cpp
Your main program first includes student.h…
Then student.h then includes calc.h!
#include “student.h”#include “calc.h”
int main(){ Student grace; Calculator hp; ... grace.study(); hp.compute();}
main.cpp
main.cpp#include “student.h”#include “calc.h”int main(){ Student grace; Calculator hp; ... grace.study(); hp.compute();}#include “calc.h”
class Student{public: void study() ...private: Calculator myCalc;};
class Calculator{public: void compute(); ...};
…and finally, main.cpp includes calc.h… again!
class Calculator{public: void compute(); ...};
So what’s the problem?
Well, since main.cpp and student.h both included calc.h, we ended up with
two definitions of Calc! That’s bad!
This can result in a compiler error!
So how do we fix it, you ask?
Here’s how…
Separate Compilation
class Calculator{public: void compute(); ...};
calc.h
#include “calc.h”
class Student{public: void study() ...private: Calculator myCalc;};
student.h
Add “include guards” to each
header file.
An include guard is a special check that prevents duplicate header inclusions.
#ifndef CALC_H
#endif // for CALC_H
#define CALC_H
#ifndef STUDENT_H
#define STUDENT_H
#endif // for STUDENT_H
So what would our fully-compiled
program look like now?
#ifndef STUDENT_H#define STUDENT_H
#ifndef CALC_H#define CALC_Hclass Calculator{public: void compute();};#endif // for CALC_H
class Student{public: void study()private: Calculator myCalc;};#endif // for STUDENT_H
#ifndef CALC_H#define CALC_Hclass Calculator{public: void compute();};#endif // for CALC_H
int main(){ Student grace; Calculator hp; ... grace.study(); hp.compute();}
main.cpp
Now, when the compiler compiles
this code, it will ignore the redefined
definitions!
Compiler:STUDENT_H has been defined!
CALC_H has been defined!
Once the compilation is done,
the compiler discards all of the
# commands...
Make sure you do this any time you define header files
from now on…
It makes your code safer!
Compiler: Since no one’s defined
STUDENT_H, I’ll keep compiling.
Compiler: The user has defined STUDENT_H. I’ll remember that!
Compiler: Since no one’s defined
CALC_H, I’ll keep compiling.
Compiler: The user has defined
CALC_H. I’ll remember that!Compiler: Ok, I
just finished with the #ifndef
CALC_H block of code…
Compiler: Ok, I just finished with the
#ifndef STUDENT_H block of code…
Compiler: The CALC_H constant
was already defined. I’ll ignore the code until the
#endif!
Compiler: Ok, I just finished with the #ifndef CALC_H block of code…
class Calculator{public: void compute();};
class Student{public: void study()private: Calculator myCalc;};
And you can see that only one copy of each
definition is now included!
Well, not so fast!
Last Topic: Knowing WHEN to Include .H
Files
You might think that any time you refer to a
class, you should include it’s .h file first…
Right?
class Student {
public: void beIrresponsible(); ... private: Alcohol *myBottle;};
student.h
class Alcohol{
public: void drink() { cout << “glug!”; }};
alcohol.h
#include “alcohol.h”
I use the Alcohol class (which is defined in
alcohol.h)to define a member
variable
So I need to include alcohol.h, right?
You must include the header file (containing the full definition of a class)
Last Topic: Knowing WHEN to Include .H
Files
Here are the rules…
class SecondClass{
public: void otherFunc() {
}
private:
};
B.h
class FirstClass{
public: void someFunc() { ... }};
A.h
FirstClass x;
1. You define a regular variable of that class’s type, OR
FirstClass a[10];
2. You use the variable in any way (call a method on it, return it, etc).
y.someFunc();
#include “A.h”
Any time…
FirstClass y;FirstClass b[10];
On the other hand…
return(y);
Why? Because C++ needs to know the class’s details in
order to define actual variables with it or to let you
call methods from it!
Last Topic: Knowing WHEN to Include .H
Files
If you do NONE of the previous items, but
you…
class SecondClass{
public:
private: };
B.h
class FirstClass{
public: void someFunc() { ... }};
A.h
void goober(FirstClass p1);
1. Use the class to define a parameter to a function, OR
3. Use the class to define a pointer or reference variable
Then you DON’T needto include the class’s .H file.
(You may do so, but you don’t need to)
FirstClass *ptr1, *z[10];
class FirstClass; // enough!
2. Use the class as the return type for a func, OR
FirstClass hoober(int a); void joober(FirstClass &p1); void koober(FirstClass *p1);
void loober(){ FirstClass *ptr;} Instead, all you need to do is
give C++ a hint that your classexists (and is defined
elsewhere).Here’s how you do that:
This line tells C++ that your class exists, but doesn’t
actually define all the gory details. Since none of this code actually
uses the “guts” of the class (as the code did on the previous
slide), this is all C++ needs to know!
Last Topic: Knowing WHEN to Include .H
Files
Wow – so confusing!
class SecondClass{
public:
private: };
B.h
A.h
Why not just always use #include to avoid the
confusion?
1. If a .h file is large (thousands of lines), #including it when you don’t strictly need to slows down your compilation!
#include “A.h”
There are two reasons:
2. If two classes refer to each other, and both classes try to #include the other’s .H file, you’ll get a compile error.
class FirstClass{
public: void someFunc() { ... }};
class FirstClass{
… // thousands of lines … // thousands of lines };
// really slow!
Students and ClassroomsEvery student knows what class he/she is in…
CS 32
Math 31b
Every class has a roster of its students…
Kerry
Cyril
Lydia
Hal
KerryCyril
LydiaHal
These type of cyclical relationships cause #include problems!
And here we have the Student class. Note that each Student knows which classroom he or she is in…
Since our ClassRoom class refers to the Student class it includes Student.h
Since our Student class refers to the classroom class, it includes ClassRoom.h…
Here we have a ClassRoom class that holds a bunch of Students…
Last Topic: Self-referential Classes
#include “ClassRoom.h”
class Student{public: ... private: ClassRoom *m_myRoom;};
Student.h#include “Student.h”
class ClassRoom{public: ... private: Student m_studs[100];};
ClassRoom.h
#include “Student.h”
void Student::printMyClassRoom(){ cout <<“I’m in Boelter #” << m_myRoom->getRmNum(); }
Student.cpp#include “ClassRoom.h”
void ClassRoom::printRoster(){ for (int i=0;i<100;i++) cout << m_studs[i].getName();}
ClassRoom.cpp
Do you see the problem?
Last Topic: Self-referential Classes
#include “ClassRoom.h”
class Student{public: ... private: ClassRoom *m_myRoom;};
Student.h
#include “Student.h”
class ClassRoom{public: ... private: Student m_studs[100];};
ClassRoom.h
int main(){ Student david; …}
main.cpp#include “Student.h”
class Student{public: ... private: ClassRoom *m_myRoom;};
#include “ClassRoom.h”
#include “Student.h”
class ClassRoom{public: ... private: Student m_studs[100];};
class Student{public: ... private: ClassRoom *m_myRoom;};
#include “ClassRoom.h”
The compiler will keep going forever!!!!!
So how do we solve this cyclical nightmare?Step #1:
Look at the two class definitions in your .h files. At least one of them should NOT need the full #include.
Question: Which of our two classes doesn’t need the full #include? Why?
#include “ClassRoom.h”
class Student{public: ... private: ClassRoom *m_myRoom;};
Student.h#include “Student.h”
class ClassRoom{public: ... private: Student m_studs[100];};
ClassRoom.h
This class defines actual Student variables… It
therefore requires the full class definition to work!
This class JUST defines a pointer to a ClassRoom. It does
NOT hold a full ClassRoom variable and therefore doesn’t require the full class definition
here!!!
#include “ClassRoom.h”
class Student{public: ... private: ClassRoom *m_myRoom;};
Student.h
So how do we solve this cyclical nightmare?Step #2:
Take the class that does NOT require the full #include (forget the other one) and update its header file:
Replace the #include “XXXX.h” statement with the following: class YYY;
Where YYY is the other class name (e.g., ClassRoom).
class ClassRoom;
This line tells the compiler:
“Hey Compiler, there’s another class called
‘ClassRoom’ but we’re not going to tell you
exactly what it looks like just yet.”
#include “Student.h”
class ClassRoom{public: ... private: Student m_studs[100];};
ClassRoom.hThe Compiler says:
“Alright smartypants, I’ll trust you.
But if won’t tell me about ClassRoom’s functions and
variables
…then you can’t define any actual variables with it
or call any member functions on it.”
, haha;
The Programmer says:
“I think I’ll have a little fun! Let’s see what the compiler
says if I define a full variable!”
The Compiler replies:
“Wannt play nasty? Ok. Here are 9000 SYNTAX ERRORS
for you to deal with!”X
Step #3: Almost done. Now update
the CPP file for this class by #including the other header
file normally.
So how do we solve this cyclical nightmare?
#include “ClassRoom.h”
class Student{public: ... private: ClassRoom *m_myRoom;};
Student.hclass ClassRoom;
#include “Student.h”
void Student::printMyClassRoom(){ cout <<“I’m in Boelter #” << m_myRoom->getRmNum(); }
Student.cpp
#include “ClassRoom.h”
This line tells the compiler:
“Hey Compiler, since my CPP file is going to
actually use class’s functionality, I’ll now tell you all the
details about the class.”
The Compiler says:
Ah, ok, now I can see the full definition of what a ClassRoom
variable really is…
Feel free to call its getRmNum() method if you like!
The compiler is happy as long as you #include the
class definition before you actually use the class
in your functions…
You’re calling a member function of ClassRoom here…
So you must #include its header file here!
Since this .H file JUST has a pointer to a ClassRoom (but no
code in this file that uses or defines a full ClassRoom
variable)…
All you need is this class declaration;
line to give the compiler a heads-up.
Step #4: Finally, make sure that your class doesn’t have any other
member functions that violate our #include vs class
rules…
So how do we solve this cyclical nightmare?
#include “Student.h”
void Student::printMyClassRoom(){ cout <<“I’m in Boelter #” << m_myRoom->getRmNum(); }
Student.cpp
#include “ClassRoom.h”
#include “ClassRoom.h”
class Student{public: … private: ClassRoom *m_myRoom;};
Student.hclass ClassRoom;
If you have defined one or more functions DIRECTLY in
your class definition AND they use your other class in a significant way, then you
must MOVE them to the CPP file.
void beObnoxious() { cout << m_myRoom->getRmNum() << “ sucks!”; }
The Compiler says:
Hey! Wait a second, you’re calling the getRmNum( ) function but you haven’t defined the full class. No
WAY!Fix it or DIE!
void beObnoxious();
void Student::beObnoxious() { cout << m_myRoom->getRmNum() << “ sucks!”; }
The Compiler says:
Alright. That’s better.
Don’t let it happen again.
So how do we solve this cyclical nightmare?
#include “Student.h”
void Student::printMyClassRoom(){ cout <<“I’m in Boelter #” << m_myRoom->getRmNum(); }
Student.cpp
#include “ClassRoom.h”
#include “ClassRoom.h”
class Student{public: ... private: ClassRoom *m_myRoom;};
Student.hclass ClassRoom;
Now let’s look at both of our classes… Notice, we no longer have a cyclical reference! Woohoo!
#include “Student.h”
class ClassRoom{public: ... private: Student m_studs[100];};
ClassRoom.h
#include “ClassRoom.h”
void ClassRoom::printRoster(){ for (int i=0;i<100;i++) cout << m_studs[i].getName();}
ClassRoom.cpp
Class Challenge
• The class will split into left and right teams
• One student from each team will come up to the board
• Each student can either – write one new line of code to
solve the problem OR– fix a single error in the code
their teammates have already written
• Then the next two people come up, etc.
• The team that completes their program first wins!
RULESTeam #1 Team #2
VOID FUNC() int func(int v)int int
*float
{ while(v > 0)
Challenge #1
Write a class called quadratic which represents a second-order quadratic equation, e.g.: 4x2+3x+5
When you construct a quadratic class, you pass in thethree coefficients (e.g., 4, 3, and 5) for the equation.
The class also has an evaluate method which allows you pass in a value for x. The function will return the value of f(x).
The class has a destructor which prints out “goodbye”
Challenge #2
Write a class called MathNerd which represents a math nerd. Every MathNerd has his own special quadratic equation!
When you construct a MathNerd, he always wants youto specify the first two coefficients (for the x2 and x)for his equation. The MathNerd always selects a value of PI for his final coefficient.
The MathNerd had a getMyValue function which acceptsa value for x and should return f(x) for the nerd’s QE.