CPSC 427: Object-Oriented Programming
Michael J. Fischer
Carryover from Lecture 9
Finish analysis of 09-BarGraph demo
graph.hpp
Points to note:
graph.cpp
Points to note:
shows the use of a subscript and a pointer dereferencing in the same statement.
row.hpp
Points to note:
row.cpp
Points to note:
rowNest.hpp
This is an alternative definition of class Row with the same public
interface and behavior but different internal structure.
Points to note:
item.hpp
This is a data class. In C, one would use a struct, but C++ permits
tighter semantic control.
Points to note:
Introduction to the
C++ Standarad Library
A bit of history C++ standardization.
The standard library was derived from several different sources.
STL (Standard Template Library) portion of the C++ standard was derived from an earlier STL produced by Silicon Graphics (SGI).
Some useful classes
Here are some useful classes that you have already seen:
Class stringstream
A stringstream object (in the default case) acts like an ostream
object.
It can be used just like you would use cout.
The characters go into an internal buffer rather than to a file or
device.
The buffer can be retrieved as a string using the str() member function.
stringstream example
Example: Creating a label from an integer.
vector
vector<T> myvec is something like the C array T myvec[].
The element type T can be any primitive, object, or pointer type.
One big difference is that a vector starts empty (in the default case)
and it grows as elements are appended to the end.
Useful functions:
Other operations on vectors
Other operations include creating an empty vector, inserting,
deleting, and copying elements, scanning through the vector, and so
forth.
Liberal use is made of operator definitions to make vectors behave as
much like other C++ objects as possible.
Vectors implement value semantics, meaning type T objects are copied
freely within the vectors.
If copying is a problem, store pointers instead.
vector examples
You must #include <vector>.
Elements can be accessed using standard subscript notion.
Inserting at the beginning or middle of a vector takes time O(n).
Example:
Handling Circularly Dependent Classes
Tightly
coupled
classes
Class B depends on class A if B refers to elements declared within class A
or to A itself.
The class B definition must be read by the compiler after reading
A.
This is often ensured by putting #include "A.hpp" at the top of file
B.hpp.
A pair of classes A and B are tightly coupled if each depends on the
other.
It is not possible to have each read after the other.
Whichever the compiler reads first will cause the compiler to complain about undefined symbols from the other class.
Example: List and Cell
Suppose we want to extend a cell to have a pointer to a sublist.
This won’t compile, because List is used (in class Cell) before it is defined. But putting the two class definitions in the opposite order also doesn’t work since then Cell would be used (in class List) before it is defined.
Circularity with #include
Circularity is less apparent when definitions are in separate files.
File list.hpp:
File cell.hpp:
File main.cpp:
What happens?
In this example, it appears that class List will get read before class
Cell since main.cpp includes list.hpp before cell.hpp.
Actually, the opposite occurs. The compiler starts reading list.hpp but
then jumps to cell.hpp when it sees the #include "cell.hpp"
line.
It jumps again to list.hpp when it sees the #include "list.hpp"
line in cell.hpp, but this is the second attempt to load list.hpp, so it
only gets as far as #pragma once. It then resumes reading cell.hpp
and processes class Cell.
When done with cell.hpp, it resumes reading list.hpp and processes class List.
Resolving circular dependencies
Several tricks can be used to allow tightly coupled classes to compile. Assume A.hpp is to be read first.
References
Reference types Recall: Given int x, two types are associated with x: an L-value (the reference to x) and an R-value (the type of its values).
C++ exposes this distinction through reference types and declarators.
A reference type is any type T followed by &, i.e., T&.
A reference type is the internal type of an L-value.
Example: Given int x, the name x is bound to an L-value of type int&, whereas the values stored in x have type int
This generalizes to arbitrary types T: If an L-value stores values of type T, then the type of the L-value is T&.
Reference declarators
The syntax T& can be used to declare names, but its meaning is not what one might expect.
int x = 3; | // Ordinary int variable |
int& y = x; | // y is an alias for x |
y = 4; | // Now x == 4. |
The declaration must include an initializer.
The meaning of int& y = x; is that y becomes a name for the L-value x.
Since x is simply the name of an L-value, the effect is to make y an alias for x.
For this to work, the L-value type (int&) of x must match the type declarator (int&) for y, as above.