override
: declare that you are overriding a method, like
@override in Java
virtual
: allows redefining method in subclass
draw
operation to each would require making
the draw
method be virtual (in C++)
Time *submissions[20] = { new Time(10, 22), new Time(13, 45), new MilTime(13, 18), new MilTime(23, 0), new MilTime(5, 10), new Time(1, 15) }; ... for(int i = 0; submissions[i] != nullptr; ++i) { submissions[i]->print(cout); cout << endl; }
: public
" in the class header: means derived from
: private
", then base class public methods are
private in subclass
public class Firefly extends Insect
(in Java)
class Insect { public: Insect(std::string species); }; class Firefly : public Insect { public: Firefly(std::string species) : Insect(species) { // note: no super in C++ // more code } };
class A { public: int f(); private: int num; };
class A;
new
, it
stays on the heap until the program exits
delete
to "undo" allocating the space:
Task *todo = new Task("eat", 0.75); // code to process the item delete todo;
new
operation
todo
:
delete todo; cout << todo->description(); // dangling pointer reference! // (random result)
todo
to point to nothing:
delete todo; todo = nullptr;so later references to
todo
will cause run-time errors.
delete
for every new
TaskList *items = new TaskList(); items->add(new Task("sleep", 8.0)); items->add(new Task("eat breakfast", 0.25)); items->add(new Task("learn something", 6.0)); ... delete items;
TaskList
:
class TaskList { ... public: ~TaskList() { for(int i = 0; i < count; ++i) delete todo[i]; }
TaskList
also deletes any tasks
currently on the list
TaskList *items = new TaskList(); Task *x = new Task("do something", 1.0); items->add(x); delete items; delete x; // breaks the heap!
class Student { string name; string address; double total_grade_points, total_credits; Schedule *schedule; public: Student() : schedule{new Schedule}, total_grade_points{0.0}, total_credits{0.0}, name(""), address("") { } virtual ~Student() { delete schedule; } double gpa() const { return total_grade_points / total_credits; } virtual void add(Course *new_course); }; class SpecialStudent : public Student { Certificate *cert; public: ~SpecialStudent() { delete cert; } void add(Course *new_course); ... };
Student
object, also allocate
a Schedule
object
delete student;
- works, but doesn't reclaim the space for
the schedule
~Student() { delete schedule; }
add
is virtual; can be redefined in subclasses
add
is virtual, then ~Student
must be:
virtual ~Student() { delete schedule; }
void doSomethingNice() { Student someStudent; ... // destructor called here }
class AbstractContainer { public: virtual double& operator[](int index) = 0; virtual int size() = 0; virtual ~AbstractContainer() { } };
= 0
syntax means the method is abstract or pure
virtual
AbstractContainer
double&
in operator[]
means we can
assign as well as use the value
void swapFirstLast(AbstractContainer &nums) {
int last_index = nums.size() - 1;
assert(last_index > 0); // last_index can be 0!
double save = nums[0];
nums[0] = nums[last_index];
nums[last_index] = save;
}
... declare xs to be some class derived from AbstractContainer
... and read data into the container
// first shall be last, last shall be first:
swapFirstLast(xs);
nums
passed by reference
double* p = &nums[0]; *p = nums[last_index];
x.do_something()Question: how do we find the code to execute for do_something?
long fact(int n) { return n <= 0 ? 1 : n * fact(n - 1); } long pow2(int n) { return n <= 0 ? 1 : 2 * pow2(n - 1); } long (number_cruncher*)(int); if ( x > 0 ) number_cruncher = pow2; else number_cruncher = fact; cout << (*number_cruncher)(x); // either pow2(x) or fact(x)
class X { public: void a(); virtual void b(); virtual void c(); }; class Y : public X { public: void b() override; void d(); virtual void e(); };
X *something = new X(), *other = new Y();
+-------------------+ | &X::b | &X::c | +-------------------+
+-----------------------------+ | &Y::b | &X::c | &Y::e | +-----------------------------+
Y *confused = new X();isn't just a "little" illegal!!
train.s
, starting at
line 6912
simple_vector.h
double&
{ int n; cin >> n; FixedVector xs(n); ... xs[1] = 23.4; // this works because [] returns double& xs[0] = xs[1] / 2.0; ... { FixedVector ys(n * 2); ... } // ys destroyed here ... } // xs destroyed here
new
, delete
follows
FixedVector(std::initializer_list<double>)
initializer_list
is an STL type known to the compiler
FixedVector v1 = {1, 2, 3, 4, 5};
megavector.h
void initializeUpSequence(AbstractContainer *container, double start, double step) { double value = start; for(int ix = 0; ix < container->size(); ++ix) { (*container)[ix] = value; value += step; } } ... AbstractContainer *some_numbers = new MegaVector(); initializeUpSequence(some_numbers, 1.0, 0.1); double sum = 0.0; for(int i = 0; i < some_numbers->size(); ++i) sum += (*some_numbers)[i];
void initializeUpSequence(AbstractContainer container, double start, double step);(with no *)
void initializeUpSequence(AbstractContainer &container, double start, double step);however, the pointer version makes it more obvious that the object is on the heap
double first_and_last(MegaVector nums) { double sum = nums[0] + nums[nums.size() - 1]; return sum; }
MegaVector xs; ... double s = first_and_last(xs);
double first_and_last(MegaVector &nums) { double sum = nums[0] + nums[nums.size() - 1]; return sum; }
double sum = (nums[0] = 0.0) + nums[nums.size() - 1];
double first_and_last(const MegaVector &nums) { double sum = nums[0] + nums[nums.size() - 1]; return sum; }
const
methods:
revised_vector.h
const&
<
on students:
bool operator<(const Student &a, const Student &b) { return a.name() < b.name(); }
Point
with
different purposes
namespace myNames { void getData(int&); }; namespace yourNames { void getData(int&); };
iostream
namespace std { class istream { ... }; istream cin; class ostream { ... }; ostream cout; };and file
string
:
namespace std { class string { ... }; };hence the need for
using namespace std
or std::cin
, std::string
namespace myNames { void getData(int &x) { ... } }; void yourNames::getData(int &y) {...}
int i; myNames::getData(i); yourNames::getData(i);
using myNames::getData;All calls to
getData
now refer to myNames
implicitly
using namespace myNames;for easy access to everything
string
, cin
, cout
are all names
in namespace std
a::f
or b::f
and
correctness depends on which one you use, reconsider your naming scheme!
xyz.h
and xyz.cpp
xyz.h
can be included multiple times
#ifndef
/#define
/#endif
#ifdef
magic"
xyz.h
must be complete: if it refers to something
in another header, include that header
X*
data,
can simply write
class X;to declare that
X
is a class.
std::string
whenever refer to standard classes
xyz.cpp
: #include "xyz.h"
xyz.h
is complete
const&
parameters
#include
s