430 likes | 599 Views
CSE 332 Course Review. Goals for today ’ s review Review and summary of material this semester A chance to clarify and review key concepts/examples Discuss details about the final exams Please see course web site for exam times and locations
E N D
CSE 332 Course Review • Goals for today’s review • Review and summary of material this semester • A chance to clarify and review key concepts/examples • Discuss details about the final exams • Please see course web site for exam times and locations • One 8.5”x11” page (with notes on 1 or 2 sides) allowed • All electronics must be off, including cell phones, etc. • Recommendations for exam preparation • Catch up on any studios/readings you’ve not done • Write up your notes page as you study • Ask questions here as you have them today
What Goes Into a C++ Program? • Declarations: data types, function signatures, classes • Allows the compiler to check for type safety, correct syntax • Usually kept in “header” (.h) files • Included as needed by other files (to keep compiler happy) class Simple { typedef unsigned int UINT32; public: Simple (int i); int usage (char * program_name); void print_i (); private: struct Point2D { int i_; double x_; }; double y_; }; • Definitions: static variable initialization, function implementation • The part that turns into an executable program • Usually kept in “source” (.cpp) files void Simple::print_i () { cout << “i_ is ” << i_ << endl; } • Directives: tell precompiler or compiler to do something • E.g., #include <vector> or using namespace std;
Lifecycle of a C++ Program xterm An “IDE” window console/terminal/window Makefile WebCAT make turnin/checkin E-mail “make” utility Runtime/utility libraries (binary) .lib .a .dll .so Eclipse compile link Visual Studio C++ source code Programmer (you) debugger precompiler gcc, etc. link compiler linker executable program compiler object code (binary, one per compilation unit) .o
Also a variable holding an address Of what it “refers to” in memory But with a nicer interface A more direct alias for the object Hides indirection from programmers Must be typed Checked by compiler Again can only refer to the type with which it was declared E.g., int & r =i; // refers to int i Always refers to (same) something Must initialize to refer to a variable Can’t change what it aliases What’s a Reference? int i 7 0x7fffdad0 int & r
R-Values, L-Values, and References to Either • A variable is an l-value (has a location) • E.g., int i = 7; • Can take a regular (l-value) reference to it • E.g., int & lvri = i; • An expression is an r-value • E.g., i * 42 • Can only take an r-value reference to it (note syntax) • E.g., int && rvriexp = i * 42; • Can only get r-value reference to l-value via move • E.g., int && rvri = std::move(i); • Promises that i won’t be used for anything afterward • Also, must be safe to destroy i (could be stack/heap/global)
A variable holding an address Of what it “points to” in memory Can be untyped E.g., void * v; // points to anything However, usually they’re typed Checked by compiler Can only be assigned addresses of variables of type to which it can point E.g., int * p; // only points to int Can point to nothing E.g., p = 0; // or p = nullptr; (C++11) Can change where it points As long as pointer itself isn’t const E.g., p = &i; // now points to i What’s a Pointer? int i 7 0x7fffdad0 int *p
int main (int argc, char **argv) { int arr [3] = {0, 1, 2}; int * p = & arr[0]; int * q = p + 1; return 0; } You can subtract (but not add, multiply, etc.) pointers Gives an integer with the distance between them You can add/subtract an integer to/from a pointer E.g., p+(q-p)/2 is allowed but (p+q)/2 gives an error Note relationship between array and pointer arithmetic Given pointer p and integer n, the expressions p[n] and *(p+n) are both allowed and mean the same thing Rules for Pointer Arithmetic int arr [3] 0 1 2 0xefffdad0 0xefffdad0 int *p int *q
#include <iostream> using namespace std; int main (int, char*[]) { int i; // cout == std ostream cout << “how many?” << endl; // cin == std istream cin >> i; cout << “You said ” << i << “.” << endl; return 0; } E.g., <iostream> etc. Use istream for std input Use ostream for std output Use ifstream and ofstream for file input and output Use istringstream and ostringstream for strings Overloaded operators << ostream insertion operator >> istream extraction operator Other methods ostream: write, put istream: get, eof, good, clear Stream manipulators ostream: flush, endl, setwidth, setprecision, hex, boolalpha C++ Input/Output Stream Classes
Expressions: Operators and Operands • Operators obey arity, associativity, and precedence int result = 2 * 3 + 5; // assigns 11 • Operators are often overloaded for different types string name = first + last; // concatenation • An lvalue gives a location; an rvalue gives a value • Left hand side of an assignment must be an lvalue • Prefix increment and decrement take and produce lvalues • Posfix versions (and &) take lvalues, produce rvalues • Beware accidentally using the “future equivalence” operator, e.g., if (i = j) instead of if (i == j) • Avoid type conversions if you can, and only use named casts (if you must explicitly convert types)
In C++ statements are basic units of execution Each ends with ; (can use expressions to compute values) Statements introduce scopes, e.g., of (temporary) variables A (useful) statement usually has a side effect Stores a value for future use: j = i + 5; Performs input or output: cout << j << endl; Directs control flow: if (j > 0) {…} else {…} Interrupts control flow: throw out_of_range; Resumes control flow: catch (RangeError &re) {…} goto considered too low-level Usually better to use break or continue If you have to use goto, you must comment why C++ Statements
C++ Exceptions Interrupt Control Flow • Normal program control flow is halted • At the point where an exception is thrown • The program call stack “unwinds” • Stack frame of each function in call chain “pops” • Variables in each popped frame are destroyed • This goes on until a matching try/catch scope is reached • Control passes to first matching catch block • Can handle the exception and continue from there • Can free some resources and re-throw exception • Let’s look at the call stack and how it behaves • Good way to explain how exceptions work (in some detail) • Also a good way to understand normal function behavior
try { // can throw exceptions } catch (Derived &d) { // Do something } catch (Base &d) { // Do something else } catch (...) { // Catch everything else } Control jumps to first matching catch block Order matters if multiple possible matches Especially with inheritance-related exception classes Put more specific catch blocks before more general ones Put catch blocks for more derived exception classes before catch blocks for their respective base classes Details on Catching C++ Exceptions
Pass by Value void foo () { int i = 7; baz (i); } void baz (int j) { j = 3; } 7 local variable i (stays 7) Think of this as declaration with initialization, along the lines of: int j = what baz was passed; parameter variable j (initialized with the value passed to baz, and then is assigned the value 3) 7 → 3
Pass by Reference void foo () { int i = 7; baz (i); } void baz (int & j) { j = 3; } again declaration with initialization int & j = what baz was passed; 7 → 3 local variable i j is initialized to refer to the variable that was passed to baz: when j is assigned 3, the passed variable is assigned 3. 7 → 3 argument variable j
C++ Memory Overview • 4 major memory segments • Global: variables outside stack, heap • Code (a.k.a. text): the compiled program • Heap: dynamically allocated variables • Stack: parameters, automatic and temporary variables • Key differences from Java • Destructors of automatic variables called when stack frame where declared pops • No garbage collection: program must explicitly free dynamic memory • Heap and stack use varies dynamically • Code and global use is fixed • Code segment is “read-only” stack heap code global
string *sp = new string ("a value"); string *arr = new string[10]; Three steps occur when new is called A library function named operator new (or operator new[]) is called, which allocates raw, untyped memory from the heap “Placement” version of new is passed pointer to existing memory, and skips this step The appropriate constructor(s) is(are) called to construct the object(s) from the initializers A pointer to the newly allocated and constructed object is returned as the expression’s value delete sp; delete [] arr; Three steps occur when delete is called (inverse of for new) A pointer to the memory to be de-allocated is passed as a parameter A destructor is run at the position (version without []) or destructors are run at all positions (version with []) to the parameter points Memory is returned to the heap by calling library function operator delete or operator delete[] Operators new and delete are Inverses
C++11 Smart Pointers C++11 provides two key dynamic allocation features shared_ptr : a reference counted pointer template to alias and manage objects allocated in dynamic memory (we’ll mostly use the shared_ptr smart pointer in this course) make_shared : a function template that dynamically allocates and value initializes an object and then returns a shared pointer to it (hiding the object’s address, for safety) C++11 provides 2 other smart pointers as well unique_ptr : a more complex but potentially very efficient way to transfer ownership of dynamic memory safely (implements C++11 “move semantics”) weak_ptr: gives access to a resource that is guarded by a shared_ptr without increasing reference count (can be used to prevent memory leaks due to circular references)
C++ Class Structure class Date { public: // member functions, visible outside the class Date (); // default constructor Date (const Date &); // copy constructor Date (int d, int m, int y); // another constructor virtual ~Date (); // (virtual) destructor Date & operator= (const Date &); // assignment operator int d () const; int m () const; int y () const; // accessors void d (int); void m (int); void y (int); // mutators string yyyymmdd () const; // generate a formatted string private: // member variables, visible only within functions above int d_; int m_; int y_; }; The compiler defines these 4 if you don’t* operators can be member functions as well *Compiler omits default constructor if any constructor is declared
Access Control • Declaring access control scopes within a class private: visible only within the class protected: also visible within derived classes (more later) public: visible everywhere • Access control in a class is private by default • but, it’s better style to label access control explicitly • A struct is the same as a class, except • Access control for a struct is public by default • Usually used for things that are “mostly data” • E.g., if initialization and deep copy only, may suggest using a struct • Versus classes, which are expected to have both data and some form of non-trivial behavior • E.g., if reference counting, etc. probably want to use a class
Initialization and Destruction with Inheritance • Initialization follows a well defined order • Base class constructor is called • That constructor recursively follows this order, too • Member constructors are called • In order members were declared • Good style to list in that order (a good compiler may warn if not) • Constructor body is run • Destruction occurs in the reverse order • Destructor body is run, then member destructors, then base class destructor (which recursively follows reverse order) • Make destructor virtual if members are virtual • Or if class is part of an inheritance hierarchy • Avoids “slicing”: ensures destruction starts at the most derived class destructor (not at some higher base class)
class A { public: void x() {cout<<"A::x";}; virtual void y() {cout<<"A::y";}; }; class B : public A { public: void x() {cout<<"B::x";}; virtual void y() {cout<<"B::y";}; }; int main () { B b; A *ap = &b; B *bp = &b; b.x (); // prints "B::x" b.y (); // prints "B::y" bp->x (); // prints "B::x" bp->y (); // prints "B::y" ap->x (); // prints "A::x" ap->y (); // prints "B::y" return 0; }; Only matter with pointer or reference Calls on object itself resolved statically E.g., b.y(); Look first at pointer/reference type If non-virtual there, resolve statically E.g., ap->x(); If virtual there, resolve dynamically E.g., ap->y(); Note that virtual keyword need not be repeated in derived classes But it’s good style to do so Caller can force static resolution of a virtual function via scope operator E.g., ap->A::y(); prints “A::y” Virtual Functions
class MI: public C, public Z { public: MI(int numa, int numb, string s, char xC, char yC, int numZ ) : C(numa, numb, s), Z(xC, yC, numZ) {cout << "MI::constructor"<<endl;} ~MI() {cout << "MI::destructor"<<endl;} protected: string cStr; }; int main (int, char * []) { MI mi(2,4,"eve", 'r', 's', 26); return 0; } Output: A::constructor B::constructor C::constructor X::constructor Y::constructor Z::constructor MI::constructor MI::destructor Z::destructor Y::destructor X::destructor C::destructor B::destructor A::destructor Multiple Inheritance A Y X B Z C MI
// X and Y as declared previously class R : public X, public Y { public: R(char xC, char yC, int numx): Y(yC), X(xC), rInt(numx) {cout << "R::constructor" << endl;} ~R() { cout << "R::destructor" <<endl;} protected: int rInt; }; class S : public Y, public X { public: S(char yC, int numx): Y(yC), sInt(numx) {cout << "S::constructor"<<endl;} ~S() {cout << "S::destructor"<<endl;} protected: int sInt; }; int main (int, char * []) { Rr ('x', 'y', 8); return 0; } Output: X::constructor Y::constructor R::constructor R::destructor Y::destructor X::destructor int main (int, char * []) { S s('y', 10); return 0; } Output: Y::constructor X::default constructor S::constructor S::destructor X::destructor Y::destructor All Base Class Constructors are Called
// Based on LLM Ch. 18.3 and // today’s studio exercises Bear * bear_ptr = new Panda ("bao_bao"); bear_ptr ->print(); // ok bear_ptr ->toes(); // ok bear_ptr ->cuddle(); // not ok bear_ptr ->growl(); // not ok delete bear_ptr; // ok Endangered * endangered_ptr = new Grizzly; endangered_ptr->print(); // ok endangered_ptr->toes(); // not ok endangered_ptr ->cuddle();// not ok endangered_ptr ->growl(); // not ok delete endangered_ptr; // ok Base Pointer/Reference Type Restricts Interface
The dynamic_cast operator returns a pointer or reference to a more specific (derived) type if it can Only can be used with pointers to structs or classes with virtual methods The dynamic_cast operator returns nullptr (or throws an exception) if it cannot downcast to the specified type dynamic_cast<type*> (base) // returns a pointer dynamic_cast<type&> (base) // returns an l-value reference dynamic_cast<type&&> (base) // returns an r-value reference The typeid operator returns a type_info for a given expression Works with built-in types as well as user-declared types Cannot declare or construct type_info objects explicitly Can only use implicitly via expressions involving the typeid operator and operators and methods available for type_info objects, e.g., cout << "e is of type " << typeid(e).name() << endl; cout << " f is of type " << typeid(f).name() << endl; cout << " the types are " << (typeid(e) == typeid(f) ? " " : " not ") << "the same" << endl; Run-Time Type Identification
struct Truck { Truck (unsigned int w): weight_(w) { cout << "weight: " << weight_ << endl;} unsigned int weight_; }; int main (int, char * []) { Truck trucks [] = {902, 900}; auto weight_ptr = &Truck::weight_; // data cout << "Truck weights are: " << endl; for (Truck* truck_ptr = trucks; truck_ptr - trucks < sizeof(trucks)/sizeof(Truck); ++truck_ptr) { cout << "Truck " << truck_ptr - trucks << " weight is " << truck_ptr->*weight_ptr << endl; } return 0; } Output: weight: 902 weight: 900 Truck weights are: Truck 0 weight is 902 Truck 1 weight is 900 Pointer to Data Member
class Truck { public: Truck (unsigned int w): weight_(w) { cout << "weight: " << weight_ << endl;} unsigned int weight() {return weight_;} private: unsigned int weight_; }; int main (int, char * []) { Truck trucks [] = {902, 900}; // declaration almost identical to previous auto weight_ptr = &Truck::weight; // fxn cout << "Truck weights are: " << endl; for (Truck* truck_ptr = trucks; truck_ptr - trucks < sizeof(trucks)/sizeof(Truck); ++truck_ptr) { cout << "Truck " << truck_ptr - trucks << " weight is " << (truck_ptr->*weight_ptr)() << endl; // note how parens are used } return 0; } Output: weight: 902 weight: 900 Truck weights are: Truck 0 weight is 902 Truck 1 weight is 900 Pointer to Member Function
Copy Control • Copy control consists of 5 distinct operations • A copy constructor initializes an object by duplicating the const l-value that was passed to it by reference • A copy-assignment operator (re)sets an object’s value by duplicating the const l-value passed to it by reference • A destructor manages the destruction of an object • A move constructor initializes an object by transferring the implementation from the r-value reference passed to it • A move-assignment operator (re)sets an object’s value by transferring the implementation from the r-value reference passed to it • The first two leave the original object unmodified, while the last two modify it (i.e., steal its implementation)
class A { friend ostream &operator<< (ostream &, const A &); private: int m_; }; ostream &operator<< (ostream &out, const A &a) { out << "A::m_ = " << a.m_; return out; } int main () { A a; cout << a << endl; return 0; } Similar to function overloading Resolved by signature Best match is used But the list of operators and the “arity” of each is fixed Can’t invent operators (e.g., like ** for exponentiation ) Must use same number of arguments as for built-in types (except for operator()) Some operators are off limits :: (scope) ?: (conditional) .* (member dereference) . (member) sizeof typeid (RTTI) Operator Overloading
Sequence Containers (e.g. vector) • Implements a dynamically sized array of elements • Elements are (almost certainly) contiguous in memory • Random access and can iterate forward or backward • Can only grow at back of vector (reallocates as needed) • Additional Expressions Complexity • Beginning of reverse range a.rbegin() constant • End of reverse range a.rend() constant • Element access a[n] constant
Associative Containers unordered_multimap • Associative containers support efficient key lookup • Find, equal_range, etc. implemented as container methods • vs. sequence containers, which lookup by position • Associative containers differ in 3 design dimensions • Ordered vs. unordered (tree vs. hash structured) • Set vs. map (just the key or the key and a mapped type) • Unique vs. multiple instances of a key • Can extend a container’s type with callable object types, then pass comparison/hashing objects into a container’s constructor map C 2 set C C 3 B 2 A 0 B 7 C 2 D 7 B D A 3 multimap A C 2 multiset C unordered_map B 2 D 7 B D A 0 B 7 C 2 A 3 C 5 A C unordered_multiset C A B C unordered_set A B C
Iterators • Iterators generalize different uses of pointers • Most importantly, define left-inclusive intervals over the ranges of elements in a container [begin, end) • Interface between algorithms and data structures • Algorithm manipulates iterators, not containers • An iterator’s value can represent 3 kinds of states: • dereferencable (points to a valid location in a range) • past the end (points just past last valid location in a range) • singular (points to nothing) • What’s an example of a pointer in each of these states? • Can construct, compare, copy, and assign iterators so that native and library types can inter-operate
Key Ideas: Concepts and Models • A concept gives a set of type requirements • Classify/categorize types (e.g., random access iterators) • Tells whether or not a type can or cannot be used with a particular algorithm (get a compiler error if it cannot) • E.g., in the examples from last time, we could not use a linked list iterator in find1 or even find2, but we can use one in find • Any specific type that meets the requirements is a model of that concept • E.g., list<char>::iterator vs. char *in find • Different abstractions (bi-linked list iterator vs. char array iterator) • No inheritance-based relationship between them • But both model iterator concept necessary for find
Iterator Concept Hierarchy “transient” write to stream (ostream) “destructive” read at head of stream (istream) • read or write a value (one-shot) Input Iterator Output Iterator Singly-linked-list style access (forward_list) • value persists after read/write • (values have locations) • can express distancebetween two iterators Forward Iterator Bi-linked-list style access (list) Bidirectional Iterator is-a (refines) Array/buffer style access (vector, deque) Random Access Iterator
Can Extend STL Algorithms with Callable Objects • Make the algorithms even more general • Can be used parameterize policy • E.g., the order produced by a sorting algorithm • E.g., the order maintained by an associative containe • Each callable object does a single, specific operation • E.g., returns true if first value is less than second value • Algorithms often have overloaded versions • E.g., sort that takes two iterators (uses operator<) • E.g., sort that takes two iterators and a binary predicate, uses the binary predicate to compare elements in range
Callable Objects and Adapters • Callable objectssupportfunction call syntax • A function or function pointer bool (*PF) (const string &, const string &); // function pointer bool string_func (const string &, const string &); // function • A struct or class providing an overloaded operator() struct strings_ok { bool operator() (const string &s, const string &t) { return (s != “quit”) && (t != “quit”); } }; • A lambda expression (unnamed inline function) [quit_string] (const string &s, const string &t) -> bool {return (s != quit_string) && (t != quit_string);} • Adapters further extend callable objects • E.g., bind any argument using bind and _1 _2 _3 etc. • E.g., wrap a member function using mem_fn • E.g., wrap callable object with function (associates types)
template <typename T> void swap(T & lhs, T & rhs) { T temp = lhs; lhs = rhs; rhs = temp; } int main () { int i = 3; int j = 7; long r = 12; long s = 30; swap (i, j); // i is now 7, j is now 3 swap (r, s); // r is now 30, s is now 12 return 0; } The swap function template takes a single type parameter, T interchanges values of two passed arguments of the parameterized type Compiler instantiates the function template twice in main with type int for the first call with type long for the second call Note that the compiler infers the type each time swap is called Based on the types of the arguments Function Templates
template <typename T> class Array { public: Array(const int size); ~Array(); const int size () const; private: T * m_values; const int m_size; }; int main (int, char**) { Array <int> a(10); return 0; } Start with a simple declaration Which we’ll expand as we go Notice that parameterized type T is used within the class template Type of pointer to array When an instance is declared, must also explicitly specify the concrete type parameter E.g., int in function main (int, char**) Class Templates
Concept Refinement Motivates Overriding • A concept C is a refinement of concept D if C imposes all of the requirements of D • Modeling and refinement satisfy three formal properties • Reflexivity: A concept refines itself • Containment: if T models C and C refines D then T models D • Transitivity: If C refines D then C refines any concept D refines • How can we override function, class, and struct templates for specific parameterized types? C0 transitivity C1 T1 T2 C2 containment T3 T4 can substitute, e.g., T3 for T1
typedef char * charptr; typedef int * intptr; template <> void print (ostream & os, const char * message, const bool & b) { os << message << std::boolalpha << b << endl; } template <> void print (ostream & os, const char * message, const charptr & s) { os << message << reinterpret_cast<void *> (s); if (s != 0) { os << " (points to \"" << s << "\")"; } os << endl; } template <> void print (ostream & os, const char * message, const intptr & ip) { os << message << ip; if (ip != 0) { os << " (points to " << *ip << ")"; } os << endl; } Specialize on individual types bool char * int * Notice the use of typedef With specialization, we get i is 7 b is false ip is 0xfeebf064 (points to 7) cp is 0x8048c30 (points to "hello, world!") vp is 0x8048c30 And, we can reuse the solution! Fully Specialize to Override Function Templates template <typename T> void print (ostream & os, const char * message, const T & t) { os << message << t << endl; }
Overview of C++ Specialized Library Facilities • A (C++11) tuple is a template type that can have any number of members, possibly of varying types • A (C++11) bitset is creates a fixed sized container for bitwise data manipulation • The (C++11) Regular Expression Library allows for pattern matching, replacement, and search • (C++11) Random Number Generation now supports different kinds of engines and distributions
CSE 332 Final Exams • Please find exam room ahead of time • Each will start promptly, please arrive early and it’s probably a good idea to find the room before hand • Only one (2 sided) 8.5”x11” page of notes allowed (if you bring >1 we can help you staple 2 together) • All electronics must be off, including any cell phones, music players, tablets, laptops, etc. • Recommendations for exam preparation • Catch up on any studio exercises you’ve not done • Catch up on any of the readings you’ve not done • Write up your notes page as you study
CSE 332 Final Exams: Time & Locations • Date Time: • April 30th, 2015. 10:30 a.m. – 12:30 p.m. • Locations: • Section 1: Lopata Hall 101 • Section 2: Lab Sciences 250