Review for midterm
Classes
class name {
public:
// data members
// methods
private:
// private methods...
};
A class creates a template for new objects. E.g.,
class dog {
public:
string name;
string color;
float weight;
};
dog fido{"Fido", "brown", 37.6};
fido.name // evaluates to "Fido"
fido.color // evaluates to "brown"
fido.weight // 37.6
If we create more than one dog, they are all independent:
dog fido{"Fido", "brown", 37.6};
dog grimm{"Grimm", "white spots", 30};
grimm.color // white spots
fido.color // brown
class owner {
public:
string name;
vector<dog> dogs;
};
owner jsmith;
jsmith.name = "John Smith";
jsmith.dogs.push_back(fido);
jsmith.dogs.push_back(grimm);
jsmith.dogs.at(0).name // "Fido"
We can create an “anonymous” dog that doesn’t live in a box:
fido = dog{"Not Fido", "Green", 1000};
Besides having data attached to them, we can add behaviors, by adding methods (sometimes methods are called “member functions”, to correspond to “data members”).
class dog {
public:
void speak() {
cout << "Woof!" << endl;
}
};
dog fido;
fido.speak();
A member function can refer to the data members of the current object. E.g.,
speak()
can use name
, color
, etc. and they will implicitly refer to
whatever dog is speaking.
class dog {
...
void speak() {
cout << "Woof! says " << name << endl;
}
}
fido.speak(); // Prints "Woof! says Fido"
grimm.speak(); // Prints "Woof! says Grimm"
Methods can be defined inside the class, as above, or we can declare the method inside the class, and then put the method implementation elsewhere:
class dog {
void speak(); // Declaration
}
// Method implementation
void dog::speak() {
...
}
Constructors
An object can control how it is constructed. By default, every class gets a constructor that takes all values for all the members, as we did above by giving the name/color/weight for each dog.
class dog {
public:
dog(string n) {
name = n;
color = "Unknown";
weight = -1;
}
};
Now we can construct dogs from just a name:
dog fido{"Fido"};
This fills in the other members for us. Note that we lose the ability to construct a dog from all three members. If we want it back, we have to write a constructor to do it:
class dog {
public:
dog(string n) {
name = n;
color = "Unknown";
weight = -1;
}
dog(string n, string c, float w) {
name = n;
color = c;
weight = w;
}
};
A constructor like the first one that takes a single argument also defines
an implicit conversion from string
to dogs:
dog fluffy = "Fluffy";
If you don’t want this behavior, you can label the constructor explicit
class dog {
public:
explicit dog(string n) {
name = n;
color = "Unknown";
weight = -1;
}
...
};
Access specifiers
The public
at the top is an access specifier. It determines whether things
outside the class can access parts of the class public
says “anyone can
access this”. We can also say private:
which means, “only the class itself
can access this”.
Usually, we try to make the data members of a class private
, and the methods
public, allowing the public methods to control and moderate access to the
private members.
class thing {
public:
int x;
float y;
void z() { cout << "Hello!"; }
private:
int a;
float b;
void c() { cout << "Secret!"; }
};
thing q;
q.x // OK
q.y // OK
q.z() // OK
q.a // ERROR
q.b // ERROR
q.c() // ERROR
A class can have friend
functions, which are functions outside the class,
but which are still granted access to the class’s private elements:
class thing {
...
friend f();
};
void f() {
cout << q.a << q.b << q.c() << endl; // All OK
}
Why would we sometimes write a class operation as a function (friend or not) rather than a method?
Multi-file projects
Source files end with .cpp
, header files end with .h
. Header files contain
function declarations, class definitions. Source files contain method
definitions, class definitions (but they will only be visible in that file) and
function definitions (function declarations, but again, only visible within
the file). Basically, you put things in a .h
to make them visible to other
parts of the program that want to use them.
If other parts of the program want to use something, they #include
the
appropriate .h
file (containing the declarations/definitions for whatever
we want to use).
When we compile a project consisting of multiple source files, we have to tell the compiler about all the files:
compile file1.cc file2.cc ...
Header files normally contain include guards to prevent them from being included more than once:
file.h
#pragma once
#ifndef FILE_H_
#define FILE_H_
// Stuff goes here
#endif
Note that you only need to put stuff into an include file that you want to be visible to other parts of your program. If a file has functions, or even classes, that are not needed in the rest of the program, those can stay in the source file.
You can put a class definition in a header file, with just the method declarations in the class, and then put the method definitions in the corresponding source file.
Header files can (and often should) include other header files. Source files should almost never include other source files, only header files.
Exceptions and assertions
Exceptions are how C++ signals that something has gone wrong, but you might be able to fix it.
We have to tell C++ that we are interested in exceptions:
try {
// do stuff
}
catch( ) {
// Handle exception
}
The catch()
part has to specify what kind of exception we are interested
in catching. E.g., to catch any of the standard exceptions:
try {
}
catch(exception& e) {
}
(We always catch exceptions by reference.)
Standard exception hierarchy
exception
|
+---- logic_error
| +-------------- invalid_argument
| +-------------- domain_error
| +-------------- invalid_argument
| +-------------- length_error
| +-------------- out_of_range
|
+---- runtime_error
| +-------------- range_error
| +-------------- overflow_error
| +-------------- underflow_error
| +-------------- system_error
| +--------------- ios_base::failure
+---- bad_alloc
+-------------- bad_array_new_length
We can catch more than one type of exception, but we have to list them in order from most to least specific:
try {
}
catch(range_error& e) {
}
catch(runtime_error& e) {
}
catch(exception& e) {
}
If you want to catch any kind of exception, even those not part of the standard hierarchy, you can write
try {
}
catch(...) {
}
Example:
int main() {
try {
cout << "Start" << endl;
f(); g();
}
catch(runtime_error& e) {
cout << "RE" << endl;
}
catch(out_of_range& e) {
cout << "OOR" << endl;
}
return 0;
}
void f() {
try {
g();
throw system_error{};
}
catch(logic_error& e) {
cout << "LE" << endl;
}
}
void g() {
cout << "In g" << endl;
throw out_of_range{};
}
Assertions – use these for when things go wrong internally, so that the only thing to do is crash the program as soon as possible.
#include <cassert>
assert(x > 0); // Crash if x <= 0
File access
fstream f{"my-file.txt"};
f << "Hello"; // Write to file
int x;
f >> x; // Read from file
Overloading <<
:
class thing {
public:
float x,y;
};
ostream& operator<< (ostream& out, thing t) {
out << x << " " << y;
return out;
}
istream& operator>> (istream& in, thing& t) {
float x, y;
if(in >> x)
if(in.peek() == ' ') {
in.ignore(1);
if(in >> y) {
t.x = x;
t.y = y;
}
}
}