The notes below are based on the slides created by the lecturer (Dan Pomerantz), which can be found on the course website.
1Quiz 1¶
The first quiz will take place on Tuesday, February 26, during class (at the usual location). It will be 50 minutes long and will cover everything up to and including lecture 7, except for inheritance.
Other resources:
- Practice exercises, with solutions
- Quiz from Winter 2012, with solutions
1.1Lecture 1: Introduction¶
- C++ = C with classes
- Compile-time type-checking, mostly compatible with C
- In C++ but not in C:
- Classes
- Overloading
- Templates
- Exceptions
- Namespaces
- In C++ but not in Java:
- Compiles to machine code, not VM code
- Multiple inheritance is allowed
- Pointers and references (done very differently from Java)
- No garbage collection - memory management must be done manually
- The hello world program:
#include <iostream>
to have access tostd
int main()
as usual, should return 0 for successstd::cout << "lol";
to print something (printf works too, though you have to includestdio.h
in that case)std::cout
is an object of the classostream
; similar to the stdout global from C<<
is overloaded to act as both a bitshift operator and a stream-appending operator- It returns a stream, so you can chain multiple
<<
s together - To compile, you can do
g++ -Wall helloworld.cpp -o outputfile
where-Wall
means that you get all the warnings
- Types
- bool
- signed, unsigned, regular char
- int: short, long, unsigned or signed, etc
- float, double
- long double
- Operators and statements
- Basically the same as C. Don't worry about it.
- Scope
- global scope, local scope, block scope (C++ has this because it's not Javascript)
- Preprocessor
#define
,#include
,#ifdef
, etc
1.2Lecture 2: Basics of C++¶
- Function orders
- You can't call a function before its defined
- Defining the function header works (so if you put everything in a header file, you can call anything in whatever order you like)
- Preprocessor commands
#define
, for defining macros- Textual substition (basically find & replace), uses capital letters by convention
- Can even use it for functions, like: `#define MAX(a, b) ((a < b) ? b : a)
#include
- Copies & pastes the content of the file into the output file
- Usually header filenames end with .h, but only by convention
"header.h"
-> searches local dir, then standard directories<standard.h>
-> only searches standard dirs
#if
,#ifdef
,#ifndef
,#if defined
- You can also use standard boolean operators in preprocessor statements (
&&
,||
), and you can nest statements undef
deletes a macro
- Some operators
sizeof something
- return the number of bytes occupied bysomething
(could be a type, too)- comma operator:
exp1, exp2
evaluates both, returnsexp2
- Assignment returns the assigned value, so you can put an assignment in another assignment
- Some keywords, used when declaring things
auto
: implicit in any variable declaration (for temp storage); don't know why we need this thenvolatile
: warns compiler that the value may change unexpectedly (used for e.g., semaphores)register
: tells compiler that this variable will be used frequently (may be ignored by compiler)const
: can't modify after initialisation (compiler will complain), and must be initialised at declarationextern
: declares something that isn't initialised until later on (or in another file that is being included); can be used in conjunction with const to prevent the compiler from complainingstatic
: several different meanings, depending on the context:- outside a function, this makes the variable local so that it can't be used by other files
- inside a function, this allocates the memory for it on the heapi think so that it sticks around until the end of the program and not just when it goes out of scope
- also, used inside a loop, it can prevent a variable from being re-initialised (only set the first time)
- it can also be used for OOP, but we'll probably cover that later
- Arrays
- Don't need to specify the length of the array if initialising explicitly (string, or
{1, 2, 3}
, etc)
- Don't need to specify the length of the array if initialising explicitly (string, or
- Function definitions
- No nesting
- If it doesn't return anything, should return
void
in the header - Inline functions: used for small functions; asks the compiler to use macro substitutions to avoid actually calling it (the compiler may or may not accede)
- Parameters of "simple" types (int, bool, char, etc) are passed by value
- User-defined types
- enums
- you can make a named type, or omit the type (then they can be used anywhere)
enum season { WINTER, SPRING, SUMMER, FALL}
andenum season this_season = WINTER
- each enum value is implicitly assigned an int, starting from 0, though you can set explicit values with equals or just set the first one
enum grade { A=4, B=3, C=2, D=1 }
orenum grade { D=1, C, B, A }
(equivalent)
- structs: same as in C.
struct something { // define properties };
thenstruct something var;
, or make an array of these, or whatever - union: sort of like a struct, but all fields share the same place in memory (legitimate examples are hard to come by, but it's usually when you want to save space I guess)
- the
typedef
operator:typedef unsigned short word
makesword
a shorthand forunsigned short
(can also be used for structs -typedef struct _Etc {} etc
makesetc
a shorthand forstruct _Etc
)
- enums
- Namespaces
- like
std::cout
(std is the namespace) - Pretty simple concept
- To stay within the
std
scope (to avoid having to usestd::etc
every time), useusing namespace std;
- Or,
using std::cout;
to get onlycout
(likefrom x import y
in python as opposed tofrom x import *
, although in the case of C++ we can still usestd::cout
, so we can consider#include <iostream>
as doingimport x
) - To define a namespace:
namespace A { // define functions here }
- like
1.3Lecture 3: Pointers and references¶
- Pointers
int* p
orint * p
orint *p
all mean thatp
points to something of typeint
*
: unary dereference operator (gets whatever is stored at that address)&
: gets the address of a variable- Pointer arithmetic:
integer = pointer1 - pointer2
gets the number of elements between the two pointers (which MUST be of the same base type, in the same array?)- (I guess the pointers are by element and not by byte. Weird)
- Useful for modifying function params (the function must take a pointer, and must be passed a pointer)
- Pointers to structs/unions:
- Probably faster than passing by value or whatever happens normally
- Declare an instance of a struct, get its address with
&
, save as a pointer - To access a field on a struct instance, do
(*p).field
orp->field
(-> only in C++)
- Things to avoid
- Failing to initialise
*p
and then attempting to do*p = 1
- Returning pointers to local variables (well duh)
- Comparing pointers when we really want to compare the data the pointers are pointing to
- Failing to initialise
- Memory allocation
- Use the
new
keyword to prevent local vars from going out of scope - Must delete using
delete
to prevent memory leaks
- Use the
- References
int x; int & y = x;
means thaty
is an alias forx
- So, if we change
y
, we changex
as well (they are pointing to the same cell in memory, etc) - Must be initialised upon declaration
- Must have the same type as the thing it's referencing
- Cannot be reassigned after initialisation
- Can't have references to references
- Useful for function parameters (callee can pass in regular variables, and the function can change the values of them without having to use pointers at all)
- Taking a reference whose type is some large data structure is faster than being passed it by value, as we can avoid having to copy it
- Using
const
in a function def for a ref parameter means that we can't modify the contents of the ref - If you try to pass something that is not a variable of some sort (an
lvalue
) to a function expecting a reference, it will fail (compiler error) - Same if you pass something of the wrong type
- More on
lvalues
:- These are:
x
,something->x
,something.x
,*p
,array[1]
- These are not:
x+1
,p
(wherep
is a pointer),lol()
(unless it returns a reference)
- These are:
- References as return values:
- Can be used to return a large object without copying it (should actually the pointer)
- Or, to return an lvalue
- To do this, just return the thing you want a reference to, and put
&
in the function header directly after the return type
1.4Lecture 4: Memory management¶
static
vsautomatic
(permanent storage vs ephemeral, in-scope storage)- In C, to dynamically allocate memory on the heap, use
malloc
andfree
- In C++, we also have
new
anddelete
(unary operators, not functions) new
returns a pointer;delete
takes in a pointer (if the pointer is NULL, it does nothing)- We only need to delete things created with new, so delete must be passed a pointer that was returned by using new
- new implicitly calls the constructor; we can specifiy arguments for it if we like, in the usual way
- if
new
fails, it throws an exception, which terminates the program - the exception can be surpressed by doing
new(std::nothrow)
(gotta#include <new>
; in that case, if it fails, it will just return NULL
- new and delete on arrays:
- If we need to determine an array length at runtime, we have to use the new operator (
int array = new int[x]
) - Of course, that then has to be deleted, using
delete [] array
(note the[]
) - Doing it this way initialises the elements to 0 or something (if we just did
int array[100]
, the elements could be anything)
- If we need to determine an array length at runtime, we have to use the new operator (
- Initialising multidimensional arrays
- Can't just do
new int[5][5]
; have to initialise each element in the first outer to anew int[5]
- Can't just do
- Advantages of new/delete over malloc/free
- No need to cast pointers - new always returns the right type
- Don't need to calculate the size of the object
new
implicitly calls constructor- Exceptions
delete
implicitly calls destructornew
can be overridden (is this a good thing???)
- Possible issues with new/delete
- Trying to delete a pointer that was not returned by
new
will result in a segfault - Trying to delete the same pointer twice will result in this cryptic error:
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x08618008 ***
- Not deleting things created with new (memory leaks)
- In the slides there is an example on page 14 titled "Assuming the memory is initialised"
- However, this is NOT an example of a common memory management error, at least not with my compiler (gcc 4.6)
pv1[0]
is indeed defined- In fact, the elements are all initialised to 0
- Even
sum
has been implicitly initialised to 0.0, which makessum += pv1[0]
work - Not sure if it's a mistake in the slides or something compiler-specific
- Trying to delete a pointer that was not returned by
1.5Lecture 5: The standard library¶
- I/O
- cin, cout, and cerr are stream objects which supersede stdin, stdout and stderr respectively (they're easier to type I guess)
[somestream] << [some string]
is basically a replacement offprintf([some stream], [some string])
though without the formatting options maybe- Remember that
<<
is LTR, when chaining (as it should be) - To read from cin or something, use
>>
(returns the stream, again) - To do the equivalent of string formatting with streams, use output stream manipulators
- Examples:
hex
,oct
,dec
to control the base;setprecision(2)
which is sort of like%.2f
;fixed
to set fixed precision in some shape or form - To use these, have to
#include <iomanip>
- Some manipulators are temporary, in which case, they only apply to the next item printed
- Examples:
- For I/O with normal files,
#include <fstream>
which givesofstream
(files we can write to) andifstream
(files we can read from)- To write to a file:
ofstream file; file.open('file'); file << "ll"; file.close()
- Going to skip the reading example, but it's basically
ifstream
instead ofofstream
and with some other stuff that we probably don't need to know - Remember that you can use strings to hold input in C++, and don't need to rely on buffers
- Other functions:
file.get(ch)
reads a character (byte) intoch
;file.unget
puts it back into the stream somehow (?);getline(file, line)
is self-explanatory; stream.get()
can take a second parameter, which is the number of bytes we want to read- We can implement random access if we want, using
seekp
,tellg
, etc
- To write to a file:
- Container classes in the standard lib
- They're all templated classes (i.e. generic, can operate on any type)
- Although when you instantiate a particular class, you need to fix the type
vector<int> x
creates a vector which holds ints (need to#include<vector>
andusing namespace std
- Then, you can call methods on
x
, etc - Iterators
for (vector<int>::iterator current = x.begin(); current != x.end(); current++)
- note that
x.end()
is actually one past the end, to ensure that we reach the last element - Also, the size of the container (number of elements) is
x.end() - x.begin()
- Then, to get the data, just do
*current
since current basically acts like a pointer - Note that if the container has been
const
ed, you need to usevector<int>::const_iterator
(and the container ends up being read-only) - Beyond read-only, we also have write-only, forward-only, bidirectional, random-access iterators
begin()
returns a random-access iterator (we could jump ahead any number of elements)
- Generic iterators
- have to put
template class<Iterator>
somewhere - then, instead of doing
vector<int>::iterator
, can just doIterator
(the type determined and checked at compile time)
- have to put
1.6Lecture 6: Introduction to C++ classes¶
- Header declarations for classes
class whatever
public: // public declarations here
(including the constructor usually)private: // etc
- if the return type is something declared in the class, have to use
whatever::thetype
in the method signature
- Constructors
- If you do
whatever x(0)
you get awhatever
object; if you want a pointer, dowhatever* x = new whatever(0)
(both call the constructor with the parameter 0) - If you omit the () part, you call the default constructor which doesn't really do much
- If you do
- Access modifiers
- public, private (only members of the class), protected (this class and classes that inherit from it)
this
- Pointer to the current object (rarely necessary to use)
const
methods- put a const right before the { to declare that the method will not change anything
- not sure if this is reinforced by the compiler
static
- for functions: no
this
- for data: shared among all instances of the class (usage example: a counter of how many objects of this class exist)
- for functions: no
- Default parameters
- like kwargs in python except you have to specify the type
- Destructors
- Called when an object is
delete
d OR when it goes out of scope - define it by
~whatever()
- Called when an object is
- Copy constructors
- When we try to copy an object, we make a shallow clone
- To prevent this, we can create our own copy constructor which does a deep clone
whatever(const whatever & source)
- friend functions lol
- To allow other classes to access private members
friend returntype somefunction(...)
should be added to thewhatever
class def- then somefunction has access to the private members of
whatever
- Also applicable to member functions, and even classes
- Scoping issues
- use
this->
if you need to refer to a private member that is shadowed by a local variable - Use
::x
to refer to a global object and not thex
defined in the class - Nested classes are not visible outside of that scope
- use
2Quiz 2¶
The second quiz will take place on Tuesday, April 2, during class (at the usual location). It will be 50 minutes long and will cover everything up to and including lecture 11, though the last few lectures will be the focus.
The notes below are based on the slides, which can be found on the course website
Other resources:
Since official solutions are not provided, you can find student-generated solutions below
2.1Lecture 8: OOP and inheritance¶
Purpose of a class:
- Abstract data type (functionality, data)
- Hide implementation detail
- Inherit functionality
2.1.1Recap of classes¶
To recap, here's how you declare a class (usually in a header file):
class Person { public: Person(); // Default constructor Person(string name, float money); // Full constructor Person* clone_self(); Person* reproduce(const Person &other_parent); void donate_money(float amount, Person &beneficiary); // This works even if money is private string get_name(); private: string name; float money; }
You can define the functions in the non-header file:
Person::Person() { this->name = "John Doe"; } Person::Person(string name, float money) { this->name = name; this->money = money; } Person* Person::clone_self() { return new Person(this->name, this->money); }
Then you can instantiate people and call methods:
Person* alice = new Person("Alice Doe", 100); Person* bob = new Person("Bob Doe", 100); Person* alice_bob = alice->reproduce(*bob); // ^ doesn't really make sense but whatever cout << alice_bob->get_name(); alice->donate_money(50, *alice_bob); bob->donate_money(50, *alice_bob);
2.1.2Inheritance¶
- Derived classes inherit all data and methods, and can add new ones or override inherited
- Privacy of methods and data:
- Private: only visible to that class
- Protected: visible to derived classes as well
- Public: visible to everybody
- Privacy of inheritance:
- Usually you do
class B: public A
- If the
public
is omitted, it is equivalent toclass B: private A
- Pretty much the same as above except in this case the privacy refers to the fact that B is inheriting from A
- If public, everyone can know that B inherits from A
- If protected, only children can know that B inherits from A. Thus only children will be able to access the public methods/data that B inherits from A
- If private, only B knows that B inherits from A ... thus only B can access the public or protected methods/data that B inherits from A
- You might use private inheritance when B relies on A in terms of implementation, but outsiders don't need to know that B uses A (e.g., a graph implemented using an array for an adjacency list)
- In other words, public inheritance gives the default access modifier behaviour
- Protected inheritance makes public things protected
- Private inheritance makes public and protected things private
- Usually you do
- No super, due to multiple inheritance
- Constructors and destructors:
- Automatically inherited
- Base class constructors are always invoked, before derived class constructors
- Base class destructors are always invoked, after derived class destructors (and the derived class destructors are only invoked if the base destructor is virtual)
- The default constructors for base classes are invoked by default, but we can invoke a different constructor by inheriting from it:
B(int x) : A(x) { // ... }
, p. cool - (Obviously this is not true of other methods)
- Assignment compatibility:
- Assigning something of type B to something of type A is legal, but it makes you lose anything defined only in B
A a; B b = a
is illegal since B-only data would be undefined- Using pointers, though, we can do
A *pa = new B;
- Polymorphism
- Doesn't happen by default - calling
*pa->something()
whensomething
is defined for A and overridden for B would still call the method in the base class - To avoid this, define the base class method to be
virtual
(before the return type) - Note that a derived class method will only override a virtual function if the type sigs match
virtual
is legal in derived classes as well, of course- You can choose which particular method to use using the scope operator:
pa->A::something
to call the base class (when it's virtual) - Constructors cannot be virtual; destructors can (and probably should be)
- Doesn't happen by default - calling
- Abstract classes:
- At least one function is virtual and unimplemented/pure (
virtual void something() = 0;
) - Abstract classes can not be instantiated directly - you can only instantiate an inheriting class that is not abstract
- At least one function is virtual and unimplemented/pure (
2.2Lecture 9: Inheritance continued¶
2.2.1Static and dynamic dispatch¶
- If a member function is virtual, choosing which function to run is done at runtime (dynamic dispatch)
- Implemented using a vtable (contains addresses of virtual functions)
- At runtime, the call to the function is executed by "indirecting" through the vtable pointer
- I don't really know how this works
- But a function in the base class can call methods that are pure as long as they're defined in a derived class (and obviously the base class can't be instantiated)
- If it's not virtual, then the function called depends on the type of object (static dispatch)
- Calls within a constructor or destructor are always static (so, always referring to the method accessible within the class)
2.2.2Multiple inheritance¶
- Akin to the interface construct in Java, except messier
- Syntax:
class C : public A, private B
- Then an object of type C can be assigned to an object of type A or B, though you might lose stuff
- Assignment compatibility with pointers is the same as with single inheritance
- However, pointers of different types to the same object will probably have different values, due to the way objects are stored (A|B|C) - the compiler returns a pointer to the beginning of the part corresponding to the type of the object
- Ambiguities
- Let's say
f
is defined in both A and B, and something in C calls f- This is somewhat ambiguous - is it A::f or B::f which is called?
- You'd think that it would be resolved by the order in which the base classes are inherited from, but no, you just get a compiler error
- Same thing happens for data
- Diamond inheritance: A, B1 and B2 inherit from A, C inherits from B1 and B2
- Then something of type C basically has two copies of type A stuff
- Assignment compatibility is frail when you try to assign to something of type A, because which A do you use? (could avoid this by casting)
- Scope resolution can be used to avoid the "which method do you use" problem
- Try to avoid this situation
- Use virtual inheritance if necessary:
class B1 : virtual public A
,class B2 : virtual public A
; ensures that only one copy of the common base class is used - Avoid function name conflicts (data name conflicts are fine I guess)
- Use virtual inheritance if necessary:
- Let's say
2.2.3Advanced type-casting¶
Dynamic casting: takes runtime type into account. Say C inherits from A and B, A *pa1 = new A
, A *pa2 = new C
. Then:
dynamic_cast<C*>(pa1)
: returns null - you can't safely convert this to a C (missing B parts)dynamic_cast<C*>(pa2)
: works, returning something of type Cdynamic_cast<B*>(pa2)
: works, returning something of type B (trims off the A parts I suppose)
Static casting: only the compile-time type is considered.
static_cast<C*>(pa1)
: works, but is probably a bad idea since the B parts are missingstatic_cast<C*>(pa2)
: works, returning something of type C (same as dynamic casting)static_cast<B*>(new C)
: works, returning something of type Bstatic_cast<B*>(pa2)
: compiler error, becausepa2
is technically (statically) of type A, which is not related to B as far as the compiler knows
reinterpret_cast<desired type>(expression)
: no checks at all. Pretty dangerous.
2.2.4Remarks on algorithms.h¶
If you want to use, say, sort
from algorithms.h, you'll have to pass in a function that defines the comparison. Sometimes, you'll want the function to be able to take other arguments. In that case, you'll need to define the function as part of a class; you can then pass other arguments to the constructor. Then you'll have to define a method called operator()(arguments)
(the return type should be whatever you want your function to return - in this case, bool
). To pass the function as an argument to sort
, do this: MyClass f(arguments)
then sort(..., ..., f)
.
2.3Lecture 10: Overloading operators and exceptions¶
2.3.1Operator overloading¶
Syntax: [type] operator [sign] ([arguments])
where operator
is the word operator, [type]
is the return type as usual, and [sign]
is the operator we want to overload (e.g., +
, or []
for array-like operations, or =
for assignment, or ==
for comparison)
2.3.2Exceptions¶
try
,catch
,throw
(nofinally
)- catch can take in parameters of any type, or it can just be
catch (...)
including the ellipsis for the default handler- it must always specify the type, though it doesn't have to specify a name (in which case we can't examine the exception more closely of course - anonymous)
- the default handler always behaves anonymously (even the type is unknown)
- methods don't have to specify the exceptions that they can throw, unlike in Java, though they may if they like
- if a method does specify
throw
, it's assumed that it can only throw the types given bool f() throw()
means it throws no exceptions,bool f() throw(int)
means it can only throw int- Throwing types beyond those specified is legal (compiler-wise), but those exceptions can't be caught; the program will just terminate
- if a method does specify
- Some obvious things: try/catch blocks can be nested; exceptions can cascade through multiple function calls
- When an exception is thrown:
- the call stack is unwound
- all fully-constructed objects (NOT created with new) are destroyed
- Those allocated with new are not destroyed
- You can pass execptions upward after catching them, simply by using
throw
within a catch block - Exceptions can be class types (inheritance can be used, too)
- Only one catch block is executed, and the first compatible one is chosen
- To avoid problems with assignment involving derived/base classes, we can use references:
catch (SomeException &e)
- References are recommended over pointers, as you don't have to worry about deleting them then
- Don't throw exceptions in a destructor. Why? Probably because you don't know when destructors will be executed (since they are called whenever an object goes out of scope)
- Exceptions that can be thrown by the std lib are defined in
stdexcept
2.3.3Resource acquisition is initialisation¶
This is a design pattern used in C++ to guarantee that resources are freed when a function either returns normally or throws an exception. This is done by putting resource-freeing code in the destructor, which will be called whenever the object goes out of scope. Of course, for this to occur, relevant objects must be local, not global. Acquiring resouces should occur in the constructor.
Incidentally, it is possible to enclose entire functions within a try/catch block:
void f() try { // something } catch (...) { // something else } { // presumably the actual function body goes here }
2.4Lecture 11: Templates and custom iterators¶
2.4.1Custom iterators¶
Example:
class FibonacciIterator { private: int next_position; int current_position; vector<int>* data; public: FibonacciIterator(vector<int>* data) { this->data = data; this->current_position = 0; this->next_position = 1; } FibonacciIterator& operator=(const FibonacciIterator& other) { this->data = other.data; this->current_position = other.current_position; this->next_position = other.next_position; } // Prefix ++ operator (++i) FibonacciIterator& operator++() { int temp = next_position; this->next_position += current_position; this->current_position = temp; return *this; } // Postfix ++ operator (i++) FibonacciIterator& operator++(int) { int temp = next_position; this->next_position += current_position; this->current_position = temp; return *this; } // Dereference operator int& operator*() { return (*data)[current_position]; } }
haven't tested it
2.4.2Templates¶
#include<iostream> #include<string> using namespace std; /* This is a fairly useless class for a container that holds exactly one object at a time. Although it does keep track of how many objects it has held so far, which could possibly come in handy one day. */ template <class T> class Container { private: T* item; int num; public: Container() { this->num = 0; } void fill_with(T item) { this->num++; this->item = &item; } T retrieve_item() { return *item; } }; int main() { Container<string> container; string item = "Lol"; container.fill_with(item); cout << container.retrieve_item(); }
If we wanted to define the member functions outside the class, we would have to do something like
template <class T> T Container<T>::retrieve_item() { return *item; }
Multiple template types can be used as well:
template <class T, class U, class V> class TripleContainer { // ... }
We can also use integer constants (<int N>
) and even other templates (<template <class> class A>
though I'm not sure why you would do that).
If two Containers are instantiated with different template types, they are usually not assignment compatible, though they can be if the types are essentially equivalent.
If you try to instantiate a class with a template type that is not supported (say, if the class tries to perform arithmetic with things of that type, but you give it something that does not support arithmetic), you'll get a runtime error (usually not compile-time).
Functions can also be defined using templates:
template <class T> void some_function(int x) { // ... }
We can mark templates for export by putting the export
keyword in front of a template def, but this isn't supported by many compilers so why does it even matter?
Template specialisation allows us to write code that only works for a particular template type.why does this mean
The typename
keyword can be used instead of class
in a template def to reduce confusion (recently introduced).
Note that inheritance is not preserved across templates if we use a class as a template type. Speaking of inheritance, did you know that templates can inherit from templated classes? Same deal as regular inheritance. For example:
template <class T> class B : public A<T> { // ... };
Templates can also be members of a class.
2.5Practice question solutions¶
2.5.1Practice questions¶
1: Is a: inherits from. Has a: should be a class member.
2: Encapsulation, preventing access to attributes, hiding implementation details, allows for inheritance, etc
3: Defines relationships (more semantic), saves typing, allows for polymorphism, etc
4:
// Let data be the vector<float> int n = 0; for (vector<float>::const_iterator current = data.begin(); current != data.end(); current++) { n += *current > 0; }
5:
// Just going to define the bare minimum for now class IntLinkedListIterator { private: Node* current; public: IntLinkedListIterator(Node* node) { current = node; } // Postfix operator (i++) IntLinkedListIterator& operator++(int) { current = current->next; // Not really necessary in this case but it's convention return *this; } // Dereference operator int operator*() { return current->value; } }
6: I haven't looked at the assignment but I'm assuming it would be misleading because we don't actually have O(1) random access. Not a good idea with a linked list iterator. To implement, just use a loop (while or for)
7: Unexpected input or result, want to allow something else to handle it
8: Too long
9: ^
2.5.2Winter 2010 quiz¶
- 1 then 2 on the next line. Why: since
A::f
is virtual and theB::f
is defined, thenA::f
is never called when callingpa->f()
. Similarly, when callingpb->f()
, onlyC::f
is called sinceA::f
is virtual (B::f
doesn't even need to be virtual) - 1 then 2 on the next line. Why: 1 is thrown as an exception; it is caught and printed out. Then, it is incremented by 1, and is thrown again, after which is it caught and printed.
- 1 then 0 because both destructors are called, since the A destructor is virtual (thus
~B
is called first, then~A
). - 0 only because B the A destructor is not virtual.
- Not 1, not 2, yes 3, yes 4, yes 5, no 6.
2.5.3Winter 2012 quiz¶
- Normal, virtual (can be overriden), pure (must be overriden).
- a) totally fine, though you will lose any apple-only things. b) fine, and you won't lose any apple-only things.
- Private: only members of that class can access/modify it. Protected: inheriting classes can too. Public: anyone can. Bad practice because sometimes you don't want others to be able to access/modify it (and it hides the implementation, which is less likely to cause issues with backwards compatibility if you ever modify the implementation, etc).
- The vector of ints
vector<int>
, the current position (int
) current += x;
?- Define an exception called
NoRecordFound
or something, then throw it if you can't find a record