upgrade structures and migrate to nextra v4

This commit is contained in:
Zheyuan Wu
2025-07-06 12:40:25 -05:00
parent 76e50de44d
commit 717520624d
317 changed files with 18143 additions and 22777 deletions

View File

@@ -0,0 +1,148 @@
# CSE332S Lecture 1
## Today:
1. A bit about me
2. A bit about the course
3. A bit about C++
4. How we will learn
5. Canvas tour and course policies
6. Piazza tour
7. Studio: Setup our work environment
## A bit about me:
This is my 14th year at WashU.
- 5 as a graduate student advised by Dr. Cytron
- Research focused on optimizing the memory system for garbage collected languages
- My 9th as an instructor
- Courses taught: 131, 247, 332S, 361S, 422S, 433S, 454A, 523S
## CSE 332S Overview:
This course has 3 high level goals:
1. Gain proficiency with a 2nd programming language
2. Introduce important lower-level constructs that many high-level languages abstract away (pointers, explicit dynamic memory management, code compilation, stack management, static programming languages, etc.)
3. Teach fundamental object-oriented programming principles and design
C++ allows us to accomplish all three goals above!
### An introduction to C++
C++ is a multi-paradigm language
- Procedural programming - functions
- Object-oriented programming - classes and structs
- Generic programming - templates
C++ is built upon C, keeping lower-level features of C while adding higher-level features
#### Evolution of C++
1. C is a procedural programming language primarily used to develop low-level systems software, such as operating systems.
- designed to map efficiently to typical machine instructions, making compilation fairly straightforward and giving low-level access to memory
- However, type safe code reuse is hard without high-level programming constructs such as objects and generics.
2. Stroustrup first designed C++ with classes/objects, but kept procedural parts similar to C
3. Templates (generics) were later added and the STL was developed
4. C++ is now standardized, with the latest revision of the standard being C++23
### So, why C++? And an overview of the semester timeline...
1. C++ allows us to explore programming constructs such as low-level memory access (pointers and references), function calls (stack management), and explicit memory management (1st 1/3rd of the semester)
2. We can then learn how those lower-level constructs are used to enable more abstract higher-level constructs, such as objects and the development of the C++ Standard Template Library (the STL) (middle 1/3rd of the semester)
3. Finally, we will use C++ to study the fundamentals of object-oriented design (final 3rd of the semester)
### How we will learn (flipped classroom):
#### Prior to class
Lectures are pre-recorded and posted for you to view asynchronously before class
- Posted 72 hours before class on Canvas
Post-lecture tasks are posted alongside the lectures and should be completed before class
- Canvas discussion to ask questions, “like” already asked questions
- A short quiz over the lecture content
#### During class
- Work on a studio within a group to build an understanding of the topic via hands-on exercises and discussion (10:30 - 11:20 AM, 1:30 - 2:20 PM)
- Treat studio as a time to explore and test your understanding of a concept. Place emphasis on exploration.
- TAs and I will be there to help guide you through and discuss the exercises.
- Optional recitation for the first 30 minutes of each class - content generally based on questions posed in the discussion board. Recitations will be recorded.
#### Outside of class
- Readings provide further details on the topics covered in class
- Lab assignments ask you to apply the concepts you have learned
### In-class studio policy
You should be in-class by 35 minutes after the official class start time (10 am -> 10:35 AM, 1 PM -> 1:35 PM) to receive credit
- Credit awarded for being in-class and working on studio. If you do not finish the studio, you will still get credit IF you are working on studio
- All studio content is fair game on an exam. The exam is hard, the best way to prep is to spend class time efficiently working through studio
- If instructors (myself or TAs) feel you are not working on studio, credit will be taken away. Old studios may be reviewed if this is a consistent problem
- You should always commit and push the work you completed at the end of class. You should always accept the assignment link and join the team you are working with so you have access to the studio repository.
### Other options for studio
If studio must be missed for some reason:
- Complete the studio exercises in full (must complete all non-optional exercises) within 4 days of the assigned date to receive credit
- Friday at 11:59 PM for Monday studios
- Sunday at 11:59 PM for Wednesday studios
- Ok to work asynchronously in a group
### Topics we will cover:
- C++ program basics
- Variables, types, control statements, development environments
- C++ functions
- Parameters, the call stack, exception handling
- C++ memory
- Addressing, layout, management
- C++ classes and structs
- Encapsulation, abstraction, inheritance
- C++ STL
- Containers, iterators, algorithms, functors
- OO design
- Principles and Fundamentals, reusable design patterns
### Other details:
We will use Canvas to distribute lecture slides, studios, assignments, and announcements. Piazza will be used for discussion
### Lab details:
CSE 332 focuses on correctness, but also code readability and maintainability
- Labs graded on correctness as well as programming style
- Each lab lists the programming guidelines that should be followed
- Please review the CSE 332 programming guidelines before turning in each lab
Labs 1, 2, and 3 are individual assignments. You may work in groups of up to three on labs 4 and 5
### Academic Integrity
Cheating is the misrepresentation of someone elses work as your own, or assisting someone else in cheating
- Providing or receiving answers on exams
- Accessing unapproved sources of information on an exam
- Submitting code written outside of this course in this semester, written by someone else not on your team (or taken from the internet)
- Allowing another student to copy your solution
- Do not host your projects in public repos
Please also refer to the McKelvey Academic Integrity Policy
Online resources may be used to lookup general purpose C++ information (libraries, etc.). They should not be used to lookup questions specific to a course assignment. Any online resources used, including generative AIs such as chatGPT must be cited, with a description of the prompt/question asked. A comment in your code works fine for this. You may use code from the textbook or from [cppreference.com](https://en.cppreference.com/w/) or [cplusplus.com](https://cplusplus.com/) without citations.
If you have any doubt at all, ask me!
### Studio: Setting up our working environment
Visit the course canvas page, sign up for the course piazza page, and get started on studio 1

View File

@@ -0,0 +1,274 @@
# CSE332S Lecture 10
## Associative Containers
| Container | Sorted | Unique Key | Allow duplicates |
| -------------------- | ------ | ---------- | ---------------- |
| `set` | Yes | Yes | No |
| `multiset` | Yes | Yes | Yes |
| `unordered_set` | No | Yes | No |
| `unordered_multiset` | No | Yes | Yes |
| `map` | Yes | Yes | No |
| `multimap` | Yes | Yes | Yes |
| `unordered_map` | No | Yes | No |
| `unordered_multimap` | No | Yes | Yes |
Associative containers support efficient key lookup
vs. sequence containers, which lookup by position
Associative containers differ in 3 design dimensions
- Ordered vs. unordered (tree vs. hash structured)
- Well look at ordered containers today, unordered next time
- Set vs. map (just the key or the key and a mapped type)
- Unique vs. multiple instances of a key
### Ordered Associative Containers
Example: `set`, `multiset`, `map`, `multimap`
Ordered associative containers are tree structured
- Insert/delete maintain sorted order, e.g. `operator<`
- Dont use sequence algorithms like `sort` or `find` with them
- Already sorted, so sorting unnecessary (or harmful)
- `find` is more efficient (logarithmic time) as a container method
Ordered associative containers are bidirectional
- Can iterate through them in either direction, find sub-ranges
- Can use as source or destination for algorithms like `copy`
### Set vs. Map
A set/multiset stores keys (the key is the entire value)
- Used to collect single-level information (e.g., a set of words to ignore)
- Avoid in-place modification of keys (especially in a set or multiset)
A map/multimap associates keys with mapped types
- That style of data structure is sometimes called an associative array
- Map subscripting operator takes key, returns reference to mapped type
- E.g., `string s = employees[id]; // returns employee name`
- If key does not exist, `[]` creates new entry with the key, value-initialized (0 if numeric, default initialized if class) instance of the mapped type
### Unique vs. Multiple Instances of a Key
In set and map containers, keys are unique
- In set, keys are the entire value, so every element is unique
- In map, multiple keys may map to same value, but cant duplicate keys
- Attempt to insert a duplicate key is ignored by the container (returns false)
In multiset and multimap containers, duplicate keys ok
- Since containers are ordered, duplicates are kept next to each other
- Insertion will always succeed, at appropriate place in the order
### Key Types, Comparators, Strict Weak Ordering
Like `sort` algorithm, can modify containers order ...
... with any callable object that can be used correctly for sort
Must establish a **strict weak ordering** over elements
- Two keys cannot both be less than each other (inequality), so comparison operator must return `false` if they are equal
- If `a < b` and `b < c` then `a < c` (transitivity of inequality)
- If `!(a < b)` and `! (b < a)` then `a == b` (equivalence)
- If `a == b` and `b == c` then `a == c` (transitivity of eqivalence)
_Sounds like definition of order in math_
Type of the callable object is used in container type
- Cool example in LLM pp. 426 using `decltype` for a function
- Could do this by declaring your own pointer to function type
- But much easier to let compilers type inference figure it out for you
### Pairs
Maps use `pair` template to hold key, mapped type
- A `pair` can be used hold any two types
- Maps use the key type as the 1st element of the pair (`p.first`)
- Maps use the mapped type as the 2nd element of the pair (`p.second`)
Can compare `pair` variables using operators
- Equivalence, less than, other relational operators
Can declare `pair` variables several different ways
- Easiest uses initialization list (curly braces around values) (e.g. `pair<string, int> p = {"hello", 1};`)
- Can also default construct (value initialization) (e.g. `pair<string, int> p;`)
- Can also construct with two values (e.g. `pair<string, int> p("hello", 1);`)
- Can also use special `make_pair` function (e.g. `pair<string, int> p = make_pair("hello", 1);`)
### Unordered Containers (UCs)
Example: `unordered_set`, `unordered_multiset`, `unordered_map`, `unordered_multimap`
UCs use `==` to compare elements instead of `<` to order them
- Types in unordered containers must be equality comparable
- When you write your own structs, overload `==` as well as `<`
UCs store elements in indexed buckets instead of in a tree
- Useful for types that dont have an obvious ordering relation over their values
UCs use hash functions to put and find elements in buckets
- May improve performance in some cases (if performance profiling suggests so)
- Declare UCs with pluggable hash functions via callable objects, decltype, etc.
- Or specialize the `std::hash()` template for your type, used by default
### Summary
Use associative containers for key based lookup
- Ordering of elements is maintained over the keys
- Think ranges and ordering rather than position indexes
- A sorted vector may be a better alternative (depends on which operations you will use most often, and their costs)
Ordered associative containers use strict weak order
- Operator `<` or any callable object that acts like `<` over `int` can be used
Maps allow two-level (dictionary-like) lookup
- Vs. sets which are used for “there or not there” lookup
- Map uses a `pair` to associate key with mapped type
Can enforce uniqueness or allow duplicates
- Duplicates are still stored in order, creating “equal ranges”
## IO Libraries
### `std::copy()`
`std::copy()`
http://www.cplusplus.com/reference/algorithm/copy/
Takes 3 parameters:
- `copy(InputIterator first, InputIterator last, OutputIterator result);`
- `[first, last)` specifies the range of elements to copy.
- `result` specifies where we are copying to.
Example:
```cpp
vector<int> v = {1, 2, 3, 4, 5};
// copy v to cout
std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " "));
```
Some useful destination iterator types:
1. `ostream_iterator` - iterator over an output stream (like `cout`)
2. `insert_iterator` - inserts elements directly into an STL container (will be practiced in studio)
```cpp
#include <iostream>
#include <string>
#include <fstream>
#include <iterator>
#include <algorithm>
using namespace std;
int main(int argc, char *argv[]) {
if (argc != 3) {
cerr << "Usage: " << argv[0] << " <input file> <output file>" << endl;
return 1;
}
string input_file = argv[1];
string output_file = argv[2];
ifstream input_file(input_file.c_str());
ofstream output_file(output_file.c_str());
// don't skip whitespace
input_file >> noskipws;
istream_iterator<char> i (input_file);
ostream_iterator<char> o (output_file);
// copy the input file to the output file: copy(InputIterator first, InputIterator last, OutputIterator result);
copy(i, istream_iterator<char>(), o);
cout << "Copied input file" << input_file << " to " << output_file << endl;
return 0;
}
```
### IO reviews
How to move data into and out of a program:
- Using `argc` and `argv` to pass command line args
- Using `cout` to print data out to the terminal
- Using `cin` to obtain data from the user at run-time
- Using an `ifstream` to read data in from a file
- Using an `ofstream` to write data out to a file
How to move data between strings, basic types
- Using an `istringstream` to extract formatted int values
- Using an `ostringstream` to assemble a string
### Streams
Simply a buffer of data (array of bytes).
Insertion operator (`<<`) specifies how to move data from a variable into an output stream
Extraction operator (`>>`) specifies how to pull data off of an input stream and store it into a variable
Both operators defined for built-in types:
- Numeric types
- Pointers
- Pointers to char (char *)
Cannot copy or assign stream objects
- Copy construction or assignment syntax using them results in a compile-time error
Extraction operator consumes data from input stream
- "Destructive read" that reads a different element each time
- Use a variable if you want to read same value repeatedly
Need to test streams condition states
- E.g., calling the `is_open` method on a file stream
- E.g., use the stream object in a while or if test
- Insertion and extraction operators return a reference to a stream object, so can test them too
File stream destructor calls close automatically
### Flushing and stream manipulators
An output stream may hold onto data for a while, internally
- E.g., writing chunks of text rather than a character at a time is efficient
- When it writes data out (e.g., to a file, the terminal window, etc.) is entirely up to the stream, **unless you tell it to flush out its buffers**
- If a program crashes, any un-flushed stream data is lost
- So, flushing streams reasonably often is an excellent debugging trick
Can tie an input stream directly to an output stream
- Output stream is then flushed by call to input stream extraction operator
- E.g., `my_istream.tie(&my_ostream);`
- `cout` is already tied to `cin` (useful for prompting the user, getting input)
Also can flush streams directly using stream manipulators
- E.g., `cout << flush;` or `cout << endl;` or `cout << unitbuf;`
Other stream manipulators are useful for formatting streams
- Field layout: `setwidth`, `setprecision`, etc.
- Display notation: `oct`, `hex`, `dec`, `boolalpha`, `nobooleanalpha`, `scientific`, etc.

View File

@@ -0,0 +1,171 @@
# CSE332S Lecture 11
## Operator overloading intro
> Insertion operator (`<<`) - pushes data from an object into an ostream
>
> Extraction operator (`>>`) - pulls data off of an istream and stores it into an object
>
> Defined for built-in types, but what about **user-defined types**?
**Operator overloading** - we can provide overloaded versions of operators to work with objects of our classes and structs
Example:
```cpp
// declaration in point2d.h
struct Point2D {
Point2d(int x, int y);
int x_;
int y_;
}
// definition in point2d.cpp
Point2D::Point2D(int x, int y): x_(x), y_(y) {}
// main function
int main() {
Point2D p1(5,5);
cout << p1 << endl; // this is equivalent to calling `operator<<(ostream &, const Point2d &);` Not declared yet.
cout << "enter 2 coordinates, separated by a space" << endl;
cin >> p1; // this is equivalent to calling `operator>>(istream &, const Point2d &);` Not declared yet.
cout << p1 << endl;
return 0;
}
```
Example of declaration of operator:
```cpp
// declaration in point2d.h
struct Point2D {
Point2D(int x, int y);
int x_;
int y_;
}
istream & operator>> (istream
&, Point2D &);
ostream & operator<< (ostream
&, const Point2D &);
// definition in point2d.cpp
Point2D::Point2D(int x, int y): x_(x), y_(y) {}
istream & operator>> (istream &i, Point2d &p) {
// we will change p so don't put const on it
i >> p.x_ >> p.y_;
return i;
}
ostream & operator<< (ostream &o, const Point2D &p) {
// we will not change p, so put const
o << p.x_ << << p.y_;
return o;
}
```
## Operator overloading: Containers
Require element type they hold to implement a certain interface:
- Containers take ownership of the elements they contain - a copy of the element is made and the copy is inserted into the container (implies element needs a **copy constructor**)
- Ordered associative containers maintain order with elements `<` operator
- Unordered containers compare elements for equivalence with `==` operator
```cpp
// declaration in point2d.h
struct Point2D {
Point2D(int x, int y);
bool operator< (const Point2D &) const;
bool operator== (const Point2D &) const;
int x_;
int y_;
}
// must be a non-member
operator istream & operator>> (istream &, Point2D &);
// must be a non-member
operator ostream & operator<< (ostrea &, const Point2D &);
// definition in point2d.cpp
// order by x_ value, then y_
bool Point2D::operator<(const Point2D & p) const {
if(x_ < p,x_) {return true;}
if(x_ == p.x_) {
return y_ < p.y_;
}
return false;
}
```
## Operator overloading: Algorithms
Require elements to implement a specific **interface** - can find what this interface is via the cpp reference pages
Example: `std::sort()` requires elements implement `operator<`, `std::accumulate()`
requires `operator+`
Suppose we want to calculate the centroid of all Point2D objects in a `vector<Point2D>`
We can use `accumulate()` to sum all x coordinates, and all y coordinates. Then divide each by the size of the vector.
By default, accumulate uses the elements `+` operator.
```cpp
// declaration, within the struct Point2D declaration in point2d.h, used by accumulate algorithm
Point2D operator+(const Point2D &) const;
// definition, in point2d.cpp
Point2D Point2D::operator+ (const Point2D &p) const {
return Point2D(x_ + p.x_, y_ + p.y_);
}
// in main()
// assume v is populated with points
Point2D accumulated = accumulate(v.begin(), v.end(), Point2D(0,0));
Point2D centroid (accumulated.x_/v.size(), accumulated.y_/v.size());
```
## 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 containers
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
Callable objects support function call syntax
- A function or function pointer
```cpp
// function pointer
bool (*PF) (const string &, const string &);
// function
bool string_func (const string &, const string &);
```
- A struct or class providing an overloaded `operator()`
```cpp
// an example of self-defined operator
struct strings_ok {
bool operator() (const string &s, const string &t) {
return (s != "quit") && (t != "quit");
}
};
```

View File

@@ -0,0 +1,427 @@
# CSE332S Lecture 12
## Object-Oriented Programming (OOP) in C++
Today:
1. Type vs. Class
2. Subtypes and Substitution
3. Polymorphism
a. Parametric polymorphism (generic programming)
b. Subtyping polymorphism (OOP)
4. Inheritance and Polymorphism in C++
a. construction/destruction order
b. Static vs. dynamic type
c. Dynamic binding via virtual functions
d. Declaring interfaces via pure virtual functions
## Type vs. Class, substitution
### Type (interface) vs. Class
Each function/operator declared by an object has a signature: name, parameter list, and return value
The set of all public signatures defined by an object makes up the interface to the object, or its type
- An objects type is known (what can we request of an object?)
- Its implementation is not - different objects may implement an interface very differently
- An object may have many types (think interfaces in Java)
An objects class defines its implementation:
- Specifies its state (internal data and its representation)
- Implements the functions/operators it declares
### Subtyping: Liskov Substitution Principle
An interface may contain other interfaces!
A type is a **subtype** if it contains the full interface of another type (its **supertype**) as a subset of its own interface. (subtype has more methods than supertype)
**Substitutability**: if S is a subtype of T, then objects of type T may be replaced with objects of type S
Substitutability leads to **polymorphism**: a single interface may have many different implementations
## Polymorphism
Parametric (interface) polymorphism (substitution applied to generic programming)
- Design algorithms or classes using **parameterized types** rather than specific concrete data types.
- Any class that defines the full interface required of the parameterized type (is a **subtype** of the parameterized type) can be substituted in place of the type parameter **at compile-time**.
- Allows substitution of **unrelated types**.
### Polymorphism in OOP
Subtyping (inheritance) polymorphism: (substitution applied to OOP)
- A derived class can inherit an interface from its parent (base) class
- Creates a subtype/supertype relationship. (subclass/superclass)
- All subclasses of a superclass inherit the superclasss interface and its implementation of that interface.
- Function overriding - subclasses may override the superclasss implementation of an interface
- Allows the implementation of an interface to be substituted at run-time via dynamic binding
## Inheritance in C++ - syntax
### Forms of Inheritance in C++
A derived class can inherit from a base class in one of 3 ways:
- Public Inheritance ("is a", creates a subtype)
- Public part of base class remains public
- Protected part of base class remains protected
- Protected Inheritance ("contains a", **derived class is not a subtype**)
- Public part of base class becomes protected
- Protected part of base class remains protected
- Private Inheritance ("contains a", **derived class is not a subtype**)
- Public part of base class becomes private
- Protected part of base class becomes private
So public inheritance is the only way to create a **subtype**.
```cpp
class A {
public:
int i;
protected:
int j;
private:
int k;
};
class B : public A {
// ...
};
class C : protected A {
// ...
};
class D : private A {
// ...
};
```
Class B uses public inheritance from A
- `i` remains public to all users of class B
- `j` remains protected. It can be used by methods in class B or its derived classes
Class C uses protected inheritance from A
- `i` becomes protected in C, so the only users of class C that can access `i` are the methods of class C
- `j` remains protected. It can be used by methods in class C or its derived classes
Class D uses private inheritance from A
- `i` and `j` become private in D, so only methods of class D can access them.
## Construction and Destruction Order of derived class objects
### Class and Member Construction Order
```cpp
class A {
public:
A(int i) : m_i(i) {
cout << "A" << endl;}
~A() {cout<<"~A"<<endl;}
private:
int m_i;
};
class B : public A {
public:
B(int i, int j)
: A(i), m_j(j) {
cout << "B" << endl;}
~B() {cout << "~B" << endl;}
private:
int m_j;
};
int main (int, char *[]) {
B b(2,3);
return 0;
};
```
In the main function, the B constructor is called on object b
- Passes in integer values 2 and 3
B constructor calls A constructor
- passes value 2 to A constructor via base/member initialization list
A constructor initializes `m_i` with the passed value 2
- Body of A constructor runs
- Outputs "A"
B constructor initializes `m_j` with passed value 3
- Body of B constructor runs
- outputs "B"
### Class and Member Destruction Order
```cpp
class A {
public:
A(int i) : m_i(i) {
cout << "A" << endl;}
~A() {cout<<"~A"<<endl;}
private:
int m_i;
};
class B : public A {
public:
B(int i, int j)
: A(i), m_j(j) {
cout << "B" << endl;}
~B() {cout << "~B" << endl;}
private:
int m_j;
};
int main (int, char *[]) {
B b(2,3);
return 0;
};
```
B destructor called on object b in main
- Body of B destructor runs
- outputs "~B"
B destructor calls “destructor” of m_j
- int is a built-in type, so its a no-op
B destructor calls A destructor
- Body of A destructor runs
- outputs "~A"
A destructor calls “destructor” of m_i
- again a no-op
At the level of each class, order of steps is reversed in constructor vs. destructor
- ctor: base class, members, body
- dtor: body, members, base class
In short, cascading order is called when constructor is called, and reverse cascading order is called when destructor is called.
## Polymorphic function calls - function overriding
### Static vs. Dynamic type
The type of a variable is known statically (at compile time), based on its declaration
```cpp
int i; int * p;
Fish f; Mammal m;
Fish * fp = &f;
```
However, actual types of objects aliased by references & pointers to base classes vary dynamically (at run-time)
```cpp
Fish f; Mammal m;
Animal * ap = &f; // dynamic type is Fish
ap = &m; // dynamic type is Mammal
Animal & ar = get_animal(); // dynamic type is the type of the object returned by get_animal()
```
A base class and its derived classes form a set of types
`type(*ap)` $\in$ `{Animal, Fish, Mammal}`
`typeset(*fp)` $\subset$ `typeset(*ap)`
Each type set is **open**
- More subclasses can be added
### Supporting Function Overriding in C++: Virtual Functions
Static binding: A function/operator call is bound to an implementation at compile-time
Dynamic binding: A function/operator call is bound to an implementation at run-time. When dynamic binding is used:
1. Lookup the dynamic type of the object the function/operator is called on
2. Bind the call to the implementation defined in that class
Function overriding requires dynamic binding!
In C++, virtual functions facilitate dynamic binding.
```cpp
class A {
public:
A () {cout<<" A";}
virtual ~A () {cout<<" ~A";} // tells compiler that this destructor might be overridden in a derived class (the destructor of the parent class is usually virtual)
virtual void f(int); // tells compiler that this function might be overridden in a derived class
};
class B : public A {
public:
B () :A() {cout<<" B";}
virtual ~B() {cout<<" ~B";}
virtual void f(int) override; // tells compiler that this function might be overridden in a derived class, the parent function is virtual otherwise it will be an error
//C++11
};
int main (int, char *[]) {
// prints "A B"
A *ap = new B;
// prints "~B ~A" : would only
// print "~A" if non-virtual
delete ap;
return 0;
};
```
Virtual functions:
- Declared virtual in a base class
- Can override in derived classes
- Overriding only happens when signatures are the same
- Otherwise it just overloads the function or operator name
When called through a pointer or reference to a base class:
- function/operator calls are resolved dynamically
Use `final` (C++11) to prevent overriding of a virtual method
Use `override` (C++11) in derived class to ensure that the signatures match (error if not)
```cpp
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": static binding always calls the x() function of the class of the object
b.y (); // prints "B::y": static binding always calls the y() function of the class of the object
bp->x (); // prints "B::x": lookup the type of bp, which is B, and x() is non-virtual so it is statically bound
bp->y (); // prints "B::y": lookup the dynamic type of bp, which is B (at run-time), and call the overridden y() function
ap->x (); // prints "A::x": lookup the type of ap, which is A, and x() is non-virtual so it is statically bound
ap->y (); // prints "B::y": lookup the dynamic type of ap, which is B (at run-time), and call the overridden y() function of class B
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 its 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”
Potential Problem: Class Slicing
When a derived type may be caught by a catch block, passed into a function, or returned out of a function that expects a base type:
- Be sure to catch by reference
- Pass by reference
- Return by reference
Otherwise, a copy is made:
- Loses original object's "dynamic type"
- Only the base parts of the object are copied, resulting in the class slicing problem
## Class (implementation) Inheritance VS. Interface
Inheritance
Class is the implementation of a type.
- Class inheritance involves inheriting interface and implementation
- Internal state and representation of an object
Interface is the set of operations that can be called on an object.
- Interface inheritance involves inheriting only a common interface
- What operations can be called on an object of the type?
- Subclasses are related by a common interface
- But may have very different implementations
In C++, pure virtual functions make interface inheritance possible.
```cpp
class A { // the abstract base class
public:
virtual void x() = 0; // pure virtual function, no default implementation
virtual void y() = 0; // pure virtual function, no default implementation
};
class B : public A { // B is still an abstract class because it still has a pure virtual function y() that is not defined
public:
virtual void x();
};
class C : public B { // C is a concrete derived class because it has all the pure virtual functions defined
public:
virtual void y();
};
int main () {
A * ap = new C; // ap is a pointer to an abstract class type, but it can point to a concrete derived class object, cannot create an object of an abstract class, for example, new A() will be an error.
ap->x ();
ap->y ();
delete ap;
return 0;
};
```
Pure Virtual Functions and Abstract Base Classes:
A is an **abstract (base) class**
- Similar to an interface in Java
- Declares pure virtual functions (=0)
- May also have non-virtual methods, as well as virtual methods that are not pure virtual
Derived classes override pure virtual methods
- B overrides `x()`, C overrides `y()`
Can't instantiate an abstract class
- class that declares pure virtual functions
- or inherits ones that are not overridden
A and B are abstract, can create a C
Can still have a pointer or reference to an abstract class type
- Useful for polymorphism
## Review of Inheritance and Subtyping Polymorphism in C++
Create related subclasses via public inheritance from a common superclass
- All subclasses inherit the interface and its implementation from the superclass
Override superclass implementation via function overriding
- Relies on virtual functions to support dynamic binding of function/operator calls
Use pure virtual functions to declare a common interface that related subclasses can implement
- Client code uses the common interface, does not care how the interface is defined. Reduces complexity and dependencies between objects in a system.

View File

@@ -0,0 +1,309 @@
# CSE332S Lecture 13
## Memory layout of a C++ program, variables and their lifetimes
### 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 (all the variables that are declared inside a function, managed by the compiler, so must be fixed size)
- _For the dynamically allocated variables, they will be allocated in the heap segment, but the pointer (fixed size) to them will be stored in the stack segment._
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"
```cpp
int g_default_value = 1;
int main (int argc, char **argv) {
Foo *f = new Foo;
f->setValue(g_default_value);
delete f; // programmer must explicitly free dynamic memory
return 0;
}
void Foo::setValue(int v) {
this->m_value = v;
}
```
![Image of memory layout](https://notenextra.trance-0.com/images/CSE332S/CPP_Function_Memory.png)
### Memory, Lifetimes, and Scopes
Temporary variables
- Are scoped to an expression, e.g., `a = b + 3 * c;`
Automatic (stack) variables
- Are scoped to the duration of the function in which they are declared
Dynamically allocated variables
- Are scoped from explicit creation (new) to explicit destruction (delete)
Global variables
- Are scoped to the entire lifetime of the program
- Includes static class and namespace members
- May still have initialization ordering issues
Member variables
- Are scoped to the lifetime of the object within which they reside
- Depends on whether object is temporary, automatic, dynamic, or global
**Lifetime of a pointer/reference can differ from the lifetime of the location to which it points/refers**
## Direct Dynamic Memory Allocation and Deallocation
```cpp
#include <iostream>
using namespace std;
int main (int, char *[]) {
int * i = new int; // any of these can throw bad_alloc
int * j = new int(3);
int * k = new int[*j];
int * l = new int[*j];
for (int m = 0; m < *j; ++m) { // fill the array with loop
l[m] = m;
}
delete i; // call int destructor
delete j; // single destructor call
delete [] k; // call int destructor for each element
delete [] l;
return 0;
}
```
## Issues with direct memory management
### A Basic Issue: Multiple Aliasing
```cpp
int main (int argc, char **argv) {
Foo f;
Foo *p = &f;
Foo &r = f;
delete p;
return 0;
}
```
Multiple aliases for same object
- `f` is a simple alias, the object itself
- `p` is a variable holding a pointer
- `r` is a variable holding a reference
What happens when we call delete on p?
- Destroy a stack variable (may get a bus error there if were lucky)
- If not, we may crash in destructor of f at function exit
- Or worse, a local stack corruption that may lead to problems later
Problem: object destroyed but another alias to it was then used (**dangling pointer issue**)
### Memory Lifetime Errors
```cpp
Foo *bad() {
Foo f;
return &f; // return address of local variable, f is destroyed after function returns
}
Foo &alsoBad() {
Foo f;
return f; // return reference to local variable, f is destroyed after function returns
}
Foo mediocre() {
Foo f;
return f; // return copy of local variable, f is destroyed after function returns, danger when f is a large object
}
Foo * good() {
Foo *f = new Foo;
return f; // return pointer to local variable, with new we can return a pointer to a dynamically allocated object, but we must remember to delete it later
}
int main() {
Foo *f = &mediocre(); // f is a pointer to a temporary object, which is destroyed after function returns, f is invalid after function returns
cout << good()->value() << endl; // good() returns a pointer to a dynamically allocated object, but we did not store the pointer, so it will be lost after function returns, making it impossible to delete it later.
return 0;
}
```
Automatic variables
- Are destroyed on function return
- But in bad, we return a pointer to a variable that no longer exists
- Reference from also_bad similar
- Like an un-initialized pointer
What if we returned a copy?
- Ok, we avoid the bad pointer, and end up with an actual object
- But we do twice the work (why?)
- And, its a temporary variable (more on this next)
We really want dynamic allocation here
Dynamically allocated variables
- Are not garbage collected
- But are lost if no one refers to them: called a "**memory leak**"
Temporary variables
- Are destroyed at end of statement
- Similar to problems w/ automatics
Can you spot 2 problems?
- One with a temporary variable
- One with dynamic allocation
### Double Deletion Errors
```cpp
int main (int argc, char **argv) {
Foo *f = new Foo;
delete f;
// ... do other stuff
delete f; // will throw an error because f is already deleted
return 0;
}
```
What could be at this location?
- Another heap variable
- Could corrupt heap
## Shared pointers and the RAII idiom
### A safer approach using 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 (well 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 objects 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)
### Resource Acquisition Is Initialization (RAII)
Also referred to as the "Guard Idiom"
- However, the term "RAII" is more widely used for C++
Relies on the fact that in C++ a stack objects destructor is called when stack frame pops
Idea: we can use a stack object (usually a smart pointer) to hold the ownership of a heap object, or any other resource that requires explicit clean up
- Immediately initialize stack object with the allocated resource
- De-allocate resource in the stack objects destructor
### Example: Resource Acquisition Is Initialization (RAII)
```cpp
shared_ptr<Foo> createAndInit() {
shared_ptr<Foo> p =
make_shared<Foo> ();
init(p);// may throw exception
return p;
}
int run () {
try {
shared_ptr<Foo> spf =
createAndInit();
cout << *spf is << *spf;
} catch (...) {
return -1
}
return 0;
}
```
RAII idiom example using shared_ptr
```cpp
#include <memory>
using namespace std;
```
- `shared_ptr<X>` assumes and maintains ownership of aliased X
- Can access the aliased X through it (*spf)
- `shared_ptr<X>` destructor calls delete on address of owned X when its safe to do so (per reference counting idiom discussed next)
- Combines well with other memory idioms
### Reference Counting
Basic Problem
- Resource sharing is often more efficient than copying
- But its hard to tell when all are done using a resource
- Must avoid early deletion
- Must avoid leaks (non-deletion)
Solution Approach
- Share both the resource and a counter for references to it
- Each new reference increments the counter
- When a reference is done, it decrements the counter
- If count drops to zero, also deletes resource and counter
- "last one out shuts off the lights"
### Reference Counting Example
```cpp
shared_ptr<Foo> createAndInit() {
shared_ptr<Foo> p =
make_shared<Foo> ();
init(p);// may throw exception
return p;
}
int run () {
try {
shared_ptr<Foo> spf =
createAndInit();
shared_ptr<Foo> spf2 = spf;
// object destroyed after
// both spf and spf2 go away
} catch (...) {
return -1
}
return 0;
}
```
Again starts with RAII idiom via shared_ptr
- `spf` initially has sole ownership of aliased X
- `spf.unique()` would return true
- `spf.use_count` would return 1
`shared_ptr<X>` copy constructor increases count, and its destructor decreases count
`shared_ptr<X>` destructor calls delete on the pointer to the owned X when count drops to 0

View File

@@ -0,0 +1,224 @@
# CSE332S Lecture 14
## 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 (next lecture)
- A `move-assignment operator` (re)sets an object's value by transferring the implementation from the r-value reference passed to it (next lecture)
Today we'll focus on the first 3 operations and will defer the others (introduced in C++11) until next time
- The others depend on the new C++11 `move semantics`
### Basic copy control operations
A copy constructor or copy-assignment operator takes a reference to a (usually const) instance of the class
- Copy constructor initializes a new object from it
- Copy-assignment operator sets object's value from it
- In either case, original the object is left unchanged (which differs from the move versions of these operations)
- Destructor takes no arguments `~A()` (except implicit `this`)
Copy control operations for built-in types
- Copy construction and copy-assignment copy values
- Destructor of built-in types does nothing (is a "no-op")
Compiler-synthesized copy control operations
- Just call that same operation on each member of the object
- Uses defined/synthesized definition of that operation for user-defined types (see above for built-in types)
### Preventing or Allowing Basic Copy Control
Old (C++03) way to prevent compiler from generating a default constructor, copy constructor, destructor, or assignment operator was somewhat awkward
- Declare private, don't define, don't use within class
- This works, but gives cryptic linker error if operation is used
New (C++11) way to prevent calls to any method
- End the declaration with `= delete` (and don't define)
- Compiler will then give an intelligible error if a call is made
C++11 allows a constructor to call peer constructors
- Allows re-use of implementation (through delegation)
- Object is fully constructed once any constructor finishes
C++11 lets you ask compiler to synthesize operations
- Explicitly, but only for basic copy control, default constructor
- End the declaration with `= default` (and don't define) The compiler will then generate the operation or throw an error if it can't.
## Shallow vs Deep Copy
### Shallow Copy Construction
```cpp
// just uses the array that's already in the other object
IntArray::IntArray(const IntArray &a)
:size_(a.size_),
values_(a.values_) {
// only memory address is copied, not the memory it points to
}
int main(int argc, char * argv[]){
IntArray arr = {0,1,2};
IntArray arr2 = arr;
return 0;
}
```
There are two ways to "copy"
- Shallow: re-aliases existing resources
- E.g., by copying the address value from a pointer member variable
- Deep: makes a complete and separate copy
- I.e., by following pointers and deep copying what they alias
Version above shows shallow copy
- Efficient but may be risky (why?) The destructor will delete the memory that the other object is pointing to.
- Usually want no-op destructor, aliasing via `shared_ptr` or a boolean value to check if the object is the original memory allocator for the resource.
### Deep Copy Construction
```cpp
IntArray::IntArray(const IntArray &a)
:size_(0), values_(nullptr) {
if (a.size_ > 0) {
// new may throw bad_alloc,
// set size_ after it succeeds
values_ = new int[a.size_];
size_ = a.size_;
// could use memcpy instead
for (size_t i = 0;
i < size_; ++i) {
values_[i] = a.values_[i];
}
}
}
int main(int argc, char * argv[]){
IntArray arr = {0,1,2};
IntArray arr2 = arr;
return 0;
}
```
This code shows deep copy
- Safe: no shared aliasing, exception aware initialization
- But may not be as efficient as shallow copy in many cases
Note trade-offs with arrays
- Allocate memory once
- More efficient than multiple calls to new (heap search)
- Constructor and assignment called on each array element
- Less efficient than block copy
- E.g., using `memcpy()`
- But sometimes necessary
- i.e., constructors, destructors establish needed invariants
Each object is responsible for its own resources.
## Swap Trick for Copy-Assignment
The swap trick is a way to implement the copy-assignment operator, given that the `size_` and `values_` members are already defined in constructor.
```cpp
class Array {
public:
Array(unsigned int) ; // assume constructor allocates memory
Array(const Array &); // assume copy constructor makes a deep copy
~Array(); // assume destructor calls delete on values_
Array & operator=(const Array &a);
private:
size_t size_;
int * values_;
};
Array & Array::operator=(const Array &a) { // return ref lets us chain
if (&a != this) { // note test for self-assignment (safe, efficient)
Array temp(a); // copy constructor makes deep copy of a
swap(temp.size_, size_); // note unqualified calls to swap
swap(temp.values_, values_); // (do user-defined or std::swap)
}
return *this; // previous *values_ cleaned up by temp's destructor, which is the member variable of the current object
}
int main(int argc, char * argv[]){
IntArray arr = {0,1,2};
IntArray arr2 = {3,4,5};
arr2 = arr;
return 0;
}
```
## Review: Construction/destruction order with inheritance, copy control with inheritance
### Constructor and Destructor are Inverses
```cpp
IntArray::IntArray(unsigned int u)
: size_(0), values_(nullptr) {
// exception safe semantics
values_ = new int [u];
size_ = u;
}
IntArray::~IntArray() {
// deallocates heap memory
// that values_ points to,
// so it's not leaked:
// with deep copy, object
// owns the memory
delete [] values_;
// the size_ and values_
// member variables are
// themselves destroyed
// after destructor body
}
```
Constructors initialize
- At the start of each object's lifetime
- Implicitly called when object is created
Destructors clean up
- Implicitly called when an object is destroyed
- E.g., when stack frame where it was declared goes out of scope
- E.g., when its address is passed to delete
- E.g., when another object of which it is a member is being destroyed
### More on Initialization and Destruction
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)

View File

@@ -0,0 +1,148 @@
# CSE332S Lecture 15
## Move semantics introduction and motivation
Review: 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 objects 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 objects value by transferring the implementation from the r-value reference passed to it
Today we'll focus on the last 2 operations and other features (introduced in C++11) like r-value references
I.e., features that support the new C++11 `move semantics`
### Motivation for move semantics
Copy construction and copy-assignment may be expensive due to time/memory for copying
It would be more efficient to simply "take" the implementation from the passed object, if that's ok
It's ok if the passed object won't be used afterward
- E.g., if it was passed by value and so is a temporary object
- E.g., if a special r-value reference says it's ok to take from (as long as object remains in a state that's safe to destruct)
Note that some objects require move semantics
- I.e., types that don't allow copy construction/assignment
- E.g., `unique_ptr`, `ifstream`, `thread`, etc.
New for C++11: r-value references and move function
- E.g., `int i; int &&rvri = std::move(i);`
### Synthesized move operations
Compiler will only synthesize a move operation if
- Class does not declare any copy control operations, and
- Every non-static data member of the class can be moved
Members of built-in types can be moved
- E.g., by `std::move` etc.
User-defined types that have synthesized/defined version of the specific move operation can be moved
L-values are always copied, r-values can be moved
- If there is no move constructor, r-values only can be copied
Can ask for a move operation to be synthesized
- I.e., by using `= default`
- But if cannot move all members, synthesized as `= delete`
## Move constructor and assignment operator examples, more details on inheritance
### R-values, L-values, and Reference 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 wont be used for anything afterward
- Also, must be safe to destroy i (could be stack/heap/global)
### Move Constructors
```cpp
// takes implementation from a
IntArray::IntArray(IntArray &&a)
: size_(a.size_),
values_(a.values_) {
// make a safe to destroy
a.values_ = nullptr;
a.size_ = 0;
}
```
Note r-value reference
- Says it's safe to take a's implementation from it
- Promises only subsequent operation will be destruction
Note constructor design
- A lot like shallow copy constructor's implementation
- Except, zeroes out state of `a`
- No sharing, current object owns the implementation
- Object `a` is now safe to destroy (but is not safe to do anything else with afterward)
### Move Assignment Operator
No allocation, so no exceptions to worry about
- Simply free existing implementation (delete `values_`)
- Then copy over size and pointer values from `a`
- Then zero out size and pointer in `a`
This leaves assignment complete, `a` safe to destroy
- Implementation is transferred from `a` to current object
```cpp
Array & Array::operator=(Array &&a) { // Note r-value reference
if (&a != this) { // still test for self-assignment
delete [] values_; // safe to free first (if not self-assigning)
size_ = a. size_; // take as size value
values_ = a.values_; // take as pointer value
a.size_ = 0; // zero out as size
a.values_ = nullptr; // zero out as pointer (now safe to destroy)
}
return *this;
}
```
### Move Semantics and Inheritance
Base classes should declare/define move operations
- If it makes sense to do so at all
- Derived classes then can focus on moving their members
- E.g., calling `Base::operator=` from `Derived::operator=`
Containers further complicate these issues
- Containers hold their elements by value
- Risks slicing, other inheritance and copy control problems
So, put (smart) pointers, not objects, into containers
- Access is polymorphic if destructors, other methods virtual
- Smart pointers may help reduce need for copy control operations, or at least simplify cases where needed

View File

@@ -0,0 +1,200 @@
# CSE332S Lecture 16
## Intro to OOP design and principles
### Review: Class Design
Designing a class to work well with the STL:
- What operators are required of our class?
- `operator<` for ordered associative containers, `operator==` for unordered associative containers
- `operator<<` and `operator>>` for interacting with iostreams
- Algorithms require particular operators as well
Designing a class that manages dynamic resources:
- Must think about copy control
- **Shallow copy** or **deep copy**?
- When should the dynamic resources be cleaned up?
- Move semantics for efficiency
### OOP Design: How do we combine objects to create complex software?
Goals - Software should be:
- Flexible
- Extensible
- Reusable
Today: 4 Principles of object-oriented programming
1. Encapsulation
2. Abstraction
3. Inheritance
4. Polymorphism
#### Review: Client Code, interface vs. implementation
Today we will focus on a single class or family of classes related via a common
base class and client code that interacts with it.
Next time: Combining objects to create more powerful and complex objects
**Client code**: code that has access to an object (via the object directly, a reference to the object, or a pointer/smart pointer to the object).
- Knows an objects public interface only, not its implementation.
**Interface**: The set of all functions/operators (public member variables in C++ as well) a client can request of an object
**Implementation**: The definition of an objects interface. State (member variables) and definitions of member functions/operators
#### Principle 1: Encapsulation
Data and behaviors are encapsulated together behind an interface
1. Member functions have direct access to the member variables of the object via “this”
1. Benefit: Simplifies function calls (much smaller argument lists)
Proper encapsulation:
1. Data of a class remains internal (not enforced in C++)
2. Client can only interact with the data of an object via its interface
**Benefit**:
(Flexible) Reduces impact of change - Easy to change how an object is stored
without needing to modify client code that uses the object.
#### Principle 2: Abstraction
An object presents only the necessary interface to client code
1. Hides unnecessary implementation details from the client
a. Member functions that client code does not need should be private or protected
We see abstraction everyday:
- TV
- Cell phone
- Coffee machine
Benefits:
1. Reduces code complexity, makes an object easier to use
2. (Flexible) Reduces impact of change - internal implementation details can be
modified without modification to client code
#### Principle 3: Inheritance (public inheritance in C++)
**"Implementation" inheritance - class inherits interface and implementation of
its base class**
Benefits:
- Remove redundant code by placing it in a common base class.
- (Reusable) Easily extend a class to add new functionality.
**"Interface" inheritance - inherit the interface of the base class only (abstract base class in C++, pure virtual functions)**
Benefits:
- Reduce dependencies between base/derived class
- (Flexible, Extensible, Reusable) Program a client to depend on an interface rather than a specific implementation (more on this later)
#### One More Useful C++ Construct: Multiple Inheritance
C++ allows a class to inherit from more than one base class
```cpp
class Bear: public ZooAnimal {/*...*/};
class Panda: public Bear, public Endangered {/*...*/};
```
Construction order - all base classes are constructed first:
- all base classes -> derived classes member variables -> constructor body
Destruction order - opposite of construction order:
- Destructor body -> derived classes member variables -> all base class
destructors
**Rule of thumb**: When using multiple inheritance, a class should inherit
implementation from a single base class only. Any number of interfaces may be
inherited (this is enforced in Java)
#### Principle 4: Polymorphism
A single interface may have many different implementations (virtual functions and
function overriding in C++)
Benefits:
1. Avoid nasty switch statements (function calls resolved dynamically)
2. (Flexible) Allows the implementation of an interface to change at run-time
#### Program to an interface
Client should restrict variables to an interface only, not a specific implementation
- **Extensible, reusable**: New subclasses that define the interface can be created and used without modification to the client. Easy to add new functionality. Easy to reuse client.
- **Reduce impact of change**: Decouples client from concrete classes it uses.
- **Flexible**: The implementation of an interface used by the client can change at run-time.
In C++:
- Abstract base class using pure virtual functions to declare the interface
- Implement the interface in subclasses via public inheritance
- Client maintains reference or pointer to the base class
- Calls through the reference or pointer are polymorphic
```cpp
// declare printable interface
class printable {
public:
virtual void print(ostream &o) = 0;
};
// derived classes defines printable
// interface
class smiley : public printable {
public:
virtual void print(ostream &o) {
o << ":)" ;
};
};
class frown : public printable {
public:
virtual void print(ostream &o) {
o << ":(";
};
};
int main(int argc, char * argv[]) {
smiley s; // s restricted to
// a smiley object
s.print();
// p may point to an object
// of any class that defines
// the printable interface
printable * p =
generateOutput();
// Client unaware of the
// implementation of print()
p->print();
return 0;
}
```
Program to an interface
Allows easily extensible designs: anything that defines the printable interface can
be used with our client
```cpp
class Book : public printable {
vector<string> pages;
public:
virtual void print(ostream &o) {
for(unsigned int page = 0; page < pages.size(); ++page){
o << "page: " << page << endl;
o << pages[i] << endl;
};
};

View File

@@ -0,0 +1,147 @@
# CSE332S Lecture 17
## Object Oriented Programming Building Blocks
OOP Building Blocks for Extensible, Flexible, and Reusable Code
Today: Techniques Commonly Used in Design Patterns
- **Program to an interface** (last time)
- **Object composition and request forwarding** (today)
- Composition vs. inheritance
- **Run-time relationships between objects** (today)
- Aggregate vs. acquaintance
- **Delegation** (later...)
Next Time: Design Patterns
Describe the core of a repeatable solution to common design problems.
### Code Reuse: Two Ways to Reuse a Class
#### Inheritance
Code reuse by inheriting the implementation of a base class.
- **Pros:**
- Inheritance relationships defined at compile-time - simple to understand.
- **Cons:**
- Subclass often inherits some implementation from superclass - derived class now depends on its base class implementation, leading to less flexible code.
#### Composition
Assemble multiple objects together to create new complex functionality, forward requests to the responsible assembled object.
- **Pros:**
- Allows flexibility at run-time, composite objects often constructed dynamically by obtaining references/pointers to other objects (dependency injection).
- Objects known only through their interface - increased flexibility, reduced impact of change.
- **Cons:**
- Code can be more difficult to understand, how objects interact may change dynamically.
### Example: Our First Design Pattern (Adapter Pattern)
**Problem:** We are given a class that we cannot modify for some reason - it provides functionality we need, but defines an interface that does not match our program (client code).
**Solution:** Create an adapter class, adapter declares the interface needed by our program, defines it by forwarding requests to the unmodifiable object.
Two ways to do this:
```cpp
class unmodifiable {
public:
int func(); // does something useful, but doesnt match the interface required by the client code
};
```
1. **Inheritance**
```cpp
// Using inheritance:
class adapter : protected unmodifiable {
// open the access to the protected member func() for derived class
public:
int myFunc() {
return func(); // forward request to encapsulated object
}
};
```
2. **Composition**
```cpp
class adapterComp {
unmodifiable var;
public:
int myFunc() {
return var.func();
}
};
```
### Thinking About and Describing Run-time Relationships
Typically, composition is favored over inheritance! Object composition with programming to an interface allows relationships/interactions between objects to vary at run-time.
- **Aggregate:** Object is part of another. Its lifetime is the same as the object it is contained in. (similar to base class and derived class relationship)
- **Acquaintance:** Objects know of each other, but are not responsible for each other. Lifetimes may be different.
```cpp
// declare Printable Interface
// declare printable interface
class printable {
public:
virtual void print(ostream &o) = 0;
};
// derived classes defines printable
// interface
class smiley : public printable {
public:
virtual void print(ostream &o) {
o << ":)";
};
};
// second derived class defines
// printable interface
class frown : public printable {
public:
virtual void print(ostream &o) {o << ":(";
};
};
```
1. **Aggregate**
```cpp
// implementation 1:
// Aggregate relationship
class emojis {
printable * happy;
printable * sad;
public:
emojis() {
happy = new smiley();
sad = new frown();
};
~emojis() {
delete happy;
delete sad;
};
};
```
2. **Acquaintance**
```cpp
// implementation 2:
// Acquaintances only
class emojis {
printable * happy;
printable * sad;
public:
emojis();
~emojis();
// dependency injection
void setHappy(printable *);
void setSad(printable *);
};
```

View File

@@ -0,0 +1,256 @@
# CSE332S Lecture 2
Today we'll talk generally about C++ development (plus a few platform specifics):
- We'll develop, submit, and grade code in Windows
- It's also helpful to become familiar with Linux
- E.g., on shell.cec.wustl.edu
- For example, running code through two different compilers can catch a lot more "easy to make" errors
Extra credit on Lab 1: compile the cpp program in Linux.
## Writing C++
Makefile ASCII text
C++ course files, ASCII text, end it with .cpp
C++ header files, ASCII text, end it with .h
readme, ASCII text (show what program does)
## Parts of a C++ Program
### Declarations
data types, function signatures, class declarations
- This allows the compiler to check for type safety, correct syntax, and other errors
- Usually kept in header files (e.g., .h)
- Included as needed by other files (to make compiler happy)
```cpp
// my_class.h
class Simple {
public:
Simple (int i);
void print_i();
private:
int i_;
}
typedef unsigned int UNIT32;
int usage (char * program_name);
struct Point2D {
double x_;
double y_;
};
```
### Definitions
Static variables initialization, function implementation
- The part that turns into an executable program
- Usually kept in source files (e.g., .cpp)
```cpp
// my_class.cpp
#include "my_class.h"
Simple::Simple (int i) : i_(i) {}
void Simple::print_i() {
std::cout << i_ << std::endl;
}
```
### Directives
tell complier or preprocessor what to do
more on this later
## A Very Simple C++ Program
```cpp
#include <iostream> // precompiler directive
using namespace std; // compiler directive
// definition of main function
int main(int, char *[]) {
cout << "Hello, World!" << endl;
return 0;
}
```
### What is `#include <iostream>`?
- `#include` tells the precompiler to include a file
- Usually, we include header files that:
- Contain declarations of structs, classes, functions
- Sometimes contain template _definitions_ (template not included in this course)
- Implementation varies from compiler to compiler (advanced topic covered later)
- `<iostream>` is the C++ standard header file for input/output streams
### What is `using namespace std;`?
- The `using` directive tells the compiler to include code from libraries that have separate namespaces
- Similar idea to "packages" in other languages
- C++ provides a namespace for its standard library
- Called the "standard namespace" (written as `std`)
- Contains `cout`, `cin`, `cerr` standard iostreams, and much more
- Namespaces reduce collisions between symbols
- Rely on the `::` scoping operator to match symbols to them
- If another library with namespace `mylib` defined `cout` we could say `std::cout` vs. `mylib::cout`
- Can also apply `using` more selectively:
- E.g., just `using std::cout`
### What is `int main(int, char *[]) { ... }`?
- Defines the main function of any C++ program, it is the entry point of the program
- Who calls main?
- The runtime environment, specifically a function often called something like `crt0` or `crtexe`
- What about the stuff in parentheses?
- A list of types of the input arguments to function `main`
- With the function name, makes up its signature
- Since this version of `main` ignores any inputs, we leave off names of the input variables, and only give their types
- What about the stuff in braces?
- It's the body of function `main`, its definition
### What is `cout << "Hello, World!" << endl;`?
- Uses the standard output iostream, named `cout`
- For standard input, use `cin`
- For standard error, use `cerr`
- `<<` is an operator for inserting into the stream
- A member operator of the `ostream` class
- Returns a reference to stream on which it's called
- Can be applied repeatedly to references left-to-right
- `"hello, world!"` is a C-style string
- A 14-position character array terminated by `'\0'`
- `endl` is an iostream manipulator
- Ends the line by inserting end-of-line character(s)
- Also flushes the stream
### What about `return 0;`?
- The `main` function must return an integer value
- By convention:
- Return `0` to indicate successful execution
- Return non-zero value to indicate failure
- The program should exit gracefully through `main`'s return
- Other ways the program can terminate abnormally:
- Uncaught exceptions propagating out of `main`
- Division by zero
- Dereferencing null pointers
- Accessing memory not owned by the program
- Array index out of bounds
- Dereferencing invalid/"stray" pointers
## A slightly more complex program
```cpp
#include <iostream>
using namespace std;
int main(int argc, char *argv[]) {
for (int i = 0; i < argc; i++) {
cout << argv[i] << endl;
}
return 0;
}
```
### `int argc, char *argv[]`
- A way to affect the program's behavior
- Carry parameters with which program was called
- Passed as parameters to main from crt0
- Passed by value (we'll discuss what that means)
`argc`:
- An integer with the number of parameters (>=1)
`argv`:
- An array of pointers to C-style character strings
- **Its array-length is the value stored in `argc`**
- The name of the program is kept in `argv[0]`
### What is `for (int i = 0; i < argc; i++) { ... }`?
Standard C++ for loop syntax:
- Consists of 3 parts:
1. Initialization statement (executed once at start)
2. Test expression (checked before each iteration)
3. Increment expression (executed after each iteration)
Let's break down each part:
`int i = 0`:
- Declares integer variable `i` (scoped to the loop)
- Initializes `i` to 0 (initialization, not assignment)
`i < argc`:
- Tests if we're within array bounds
- Critical for memory safety - accessing outside array can crash program
`++i`:
- Increments array position counter
- Uses prefix increment operator
## Lifecycle of a C++ Program
Start from the makefile
- The makefile is a text file that tells the compiler how to build the program, it activates the 'make' utility to build the program
- The make file turnin/checkin to the WebCAT E-mail
- The makefile complies the gcc compiler to compile the cpp file
- The makefile links the object files to create the executable file
The cpp file
- The cpp file is a text file that contains the source code of the program
- The cpp file is compiled into an object file by the gcc compiler to combined with the link produced by the makefile with the runtime/util library
Finally, the object file is linked with the runtime/util library to create the executable program and ready to debug with Eclipse or Visual Studio.
## Development Environment Studio
### Course Format
- We'll follow a similar format most days in the course:
- Around 30 minutes of lecture and discussion
- Then about 60 minutes of studio time
- Except for:
- Open studio/lab days
- Reviews before the midterm and final
- The day of the midterm itself
### Studio Guidelines
- Work in groups of 2 or 3
- Exercises are posted on the course web page
- Record your answers and email them to the course account
- Instructors will circulate to answer questions
### Purpose of Studios
- Develop skills and understanding
- Explore ideas you can use for labs
- Prepare for exams which test studio material
- Encouraged to try variations beyond exercises

View File

@@ -0,0 +1,308 @@
# CSE332S Lecture 3
## C++ basic data types
- int, long, short, char (signed, integer arithmetic)
- char is only 1 byte for all platforms
- other types are platform dependent
- can determine the size of the type by using `sizeof()`, `<climits> INT_MAX`
- float, double (floating point arithmetic)
- more expensive in space and time
- useful when you need to describe continuous quantities
- bool (boolean logic)
### User-defined types
- (unscoped or scoped) enum
- maps a sequence of integer values to named constants
- function and operators
- function is a named sequence of statements, for example `int main()`
- struct and class
- similar to abstractions in cpp, extend C struct
### struct and class
- struct is public by default
- class is private by default
- both can have
- member variables
- member functions
- constructors
- destructors
- common practice:
- use struct for simple data structures
- use class for more complex data structures with non-trivial functionality
```cpp
struct My_Data{
My_Data(int x, int y): x_(x), y_(y) {}
int x_;
int y_;
};
```
```cpp
class My_Data{
public:
My_Object(int x, int y): x_(x), y_(y) {}
~My_Object(){}
private:
int x_;
int y_;
};
```
### More about native and user-defined types
- Pointer
- raw memory address of an object
- its type constrains what types it can point to
- can take on a value of 0 (null pointer)
- Reference
- alias for an existing object
- its type constrains what types it can refer to
- cannot take on a value of 0 (**always** refer to a valid object)
- Mutable (default) vs. const types (read right to left)
- `const int x;` is a read-only variable
- `int j` is a read-write declaration
## Scopes
Each variable is associated with a scope, which is a region of the program where the variable is valid
- the entire program is a global scope
- a namespace is a scope
- member of a class is a scope
- a function is a scope
- a block is a scope
```cpp
int g_x; // global scope
namespace my_namespace{
int n_x; // namespace scope
}
class My_Class{
int c_x; // class scope
int my_function(){
int f_x; // function scope
{
int b_x; // block scope
}
return 0;
}
}
```
A symbol is only visible within its scope
- helps hide unneeded details (abstraction)
- helps avoid name collisions (encapsulation)
## Motivation for pointer and reference
We often need to _refer_ to an object, but don't want to copy it
There are two common ways to do this:
- Indirectly, via a pointer
- This gives the address of the object
- Requires the code to do extra work. eg, dereferencing
- Like going to the address of the object
- Directly, via a reference
- Acts as an alias for the object
- Code interacts with reference as if it were the object itself
## Pointer and reference syntax
### Pointer
A pointer is a variable that holds the address of an object
can be untyped. eg, `void *p;`
usually typed. eg, `int *p;` so that it can be checked by the compiler
If typed, the type constrains what it can point to, a int pointer can only point to an int. `int *p;`
A pointer can be null, eg, `int *p = nullptr;`
We can change to what it points to, eg, `p = &x;`
### Reference
A reference is an alias for an existing object, also holds the address of the object, but is only created on compile time.
Usually with nicer interface than pointers.
Must be typed, and its type constrains what types it can refer to. `int &r;`
Always refers to a valid object, so cannot be null. `int &r = nullptr;` is invalid.
Note: **reference cannot be reassigned to refer to a different object.**
|symbol|used in declaration|used in definition|
|---|---|---|
|unary `&`|reference, eg, `int &r;`|address-of, eg, `int &r = &x;`|
|unary `*`|pointer, eg, `int *p;`|dereference, eg, `int *p = *q;`|
|`->`|member access, eg, `p->x;`|member access via pointer, eg, `p->second;`|
|`.`|member access, eg, `p.x;`|member access via reference, eg, `p.second;`|
## Aliasing via pointers and references
### Aliasing via reference
Example:
```cpp
int main(int argc, char *argv[]){
int i=0;
int j=1;
int &r = i;
int &s = i;
r = 8; // do not need to dereference r, just use it as an alias for i
cout << "i: " << i << ", j: " << j << ", r: " << r << ", s: " << s << endl;
// should print: i: 8, j: 1, r: 8, s: 8
return 0;
}
```
### Aliasing via pointer
Example:
```cpp
int main(int argc, char *argv[]){
int i=0;
int j=1;
int *p = &i;
int *q = &i;
*q = 6; // need to dereference q to access the value of j
cout << "i: " << i << ", j: " << j << ", p: " << *p << ", q: " << *q << endl;
// should print: i: 6, j: 1, p: 6, q: 6
return 0;
}
```
### Reference to Pointer
Example:
```cpp
int main(int argc, char *argv[]){
int j = 1;
int &r = j; // r is a **reference** to j
int *p = &r; // p is a **pointer** to the address of r, here & is the address-of operator, which returns the address of the object
int * &t = p; // t is a **reference** to pointer p, here & is the reference operator, which returns the reference of the object.
cout << "j: " << j << ", r: " << r << ", p: " << *p << ", t: " << *t << endl;
// should print: j: 1, r: 1, p: 1, t: [address of p]
return 0;
}
```
Notice that we cannot have a pointer to a reference. But we can have a reference to a pointer.
### Reference to Constant
Example:
```cpp
int main(int argc, char *argv[]){
const int i = 0;
int j = 1;
int &r = j; // r cannot refer to i, because i is a constant (if true, alter i through r should be valid)
const int &s=i; // s can refer to i, because s is a constant reference (we don't reassign s)
const int &t=j; // t can refer to j, because t is a constant reference (we don't reassign t)
cout << "i: " << i << ", j: " << j << ", r: " << r << ", s: " << s << ", t: " << t << endl;
// should print: i: 0, j: 1, r: 1, s: 0
return 0;
}
```
Notice that we cannot have a non-constant reference to a constant object. But we can have a constant reference to a non-constant object.
### Pointer to Constant
Example:
```cpp
int main(int argc, char *argv[]){
const int i = 0;
int j = 1;
int k = 2;
// pointer to int
int *w = &j;
// const pointer to int
int *const x = &j;
// pointer to const int
const int *y = &i;
// const pointer to const int, notice that we cannot change the value of the int that z is pointing to, in this case j **via pointer z**, nor the address that z is pointing to. But we can change the value of j via pointer w or j itself.
const int *const z = &j;
}
```
- Read declaration from right to left, eg, `int *w = &j;` means `w` is a pointer to an `int` that is the address of `j`.
- Make promises via the `const` keyword, two options:
- `const int *p;` means `p` is a pointer to a constant `int`, so we cannot change the value of the `int` that `p` is pointing to, but we can change the address that `p` is pointing to.
- `int *const p;` means `p` is a constant pointer to an `int`, so we cannot change the address that `p` is pointing to, but we can change the value of the `int` that `p` is pointing to.
- A pointer to non-constant cannot point to a const variable.
- neither `w = &i;` nor `x = &i;` is valid.
- any of the pointer can points to `j`.
## Pass by value, pass by reference, and type inference
Example:
```cpp
int main(int argc, char *argv[]){
int h = -1;
int i = 0;
int j = 1;
int k = 2;
return func(h, i, j, &k);
}
int func(int a, const int &b, int &c, int *d){
++a; // [int] pass by value, a is a copy of h, so a is not the same as h
c = b; // [int &] pass by reference, c is an alias for j, the value of c is the same as the value of b (or i), but we cannot change the value of b (or i) through c (const int &b)
*d = a; // [int *] pass by value, d is a pointer to k, so *d is the value of k, a is assigned to value of k.
++d; // d is a pointer to k, but pass by value, so ++d doesn't change the value of k.
return 0;
}
```
### More type declaration keywords
`typedef` keyword introduces a "type alias" for a type.
```cpp
typedef Foo * Foo_ptr; // Foo_ptr is a type alias for Foo *
// the following two variables are of the same type
Foo_ptr p1 = 0;
Foo *p2 = 0;
```
`auto` keyword allows the compiler to deduce the type of a variable from the initializer.
```cpp
int x = 0; // x is of type int
float y = 1.0; // y is of type float
auto z = x + y; // z is of type float, with initialized value 1.0
```
`decltype` keyword allows the compiler to deduce the type of a variable from the type of an expression.
```cpp
int x = 0;
double y = 0.0;
float z = 0.0f;
decltype(x) a; // a is of type int, value is not initialized
decltype(y) b; // b is of type double, value is not initialized
decltype(z) c; // c is of type float, value is not initialized
```

View File

@@ -0,0 +1,478 @@
# CSE332S Lecture 4
## Namespace details
### Motivation
Classes encapsulate behavior (methods) and state (member data) behind an interface.
Structs are similar, but with state accessible.
Classes and structs are used to specify self contained, cohesive abstractions.
- Can say what class/struct does in one sentence.
What if we want to describe more loosely related collections of state and behavior?
Could use a class or struct
- But that dilutes their design intent.
### Namespace
Cpp offers an appropriate scoping mechanism for **loosely related** aggregates: Namespaces.
- Good for large function collections.
- E.g. a set of related algorithms and function objects
- Good for general purpose collections
- E.g. program utilities, performance statistics, etc.
Declarative region
- Where a variable/function can be used
- From where declared to end of declarative region
### Namespace Properties
Declared/(re)opend with `namespace` keyword.
- `namespace name { ... }`
- `namespace name = namespace existing_name { ... };`
Access members using scoping `operator::`
- `std::cout << "Hello, World!" << std::endl;`
Everything not declared in another namespace is in the global namespace.
Can nest namespace declarations
- `namespace outer { namespace inner { ... } }`
### Using Namespaces
The `using` keyword make elements visible.
- Only apples to the current scope.
Can add entire name space to the current scope
- `using namespace std;`
- `cout << "Hello, World!" << endl;`
Can also declare unnamed namespaces
- Elements are visible after the declaration
- `namespace { int i = 42; }` will make `i` visible in the current file.
## C-style vs. C++ strings
### C++ string class
```cpp
#include <iostream>
#include <string>
using namespace std;
int main (int argc, char *argv[]) {
string s = "Hello,";
s += " World!";
cout << s << endl; // prints "Hello, World!"
return 0;
}
```
- Using `<string>` header
- Various constructions
- Assignment operator
- Overloaded operators
- Indexing operator, we can index cpp strings like arrays, `s[i]`
### C-style strings
```cpp
#include <iostream>
#include <cstring>
using namespace std;
int main (int argc, char *argv[]) {
char *h = "Hello, ";
string sh = "Hello, ";
char *w = "World!";
string sw = "World!";
cout << (h < w) << endl; // this returns 0 because we are comparing pointers
cout << (sh < sw) << endl; // this returns 1 because we are comparing values of strings in alphabetical order
h += w; // this operation is illegal because we are trying to add a pointer to a pointer
sh += sw; // concatenates the strings
cout << h << endl; // this prints char repeatedly till the termination char
cout << sh << endl; // this prints the string
return 0;
}
```
- C-style strings continguous arrays of char
- Often accessed as `char *` by pointer.
- Cpp string class provides a rich set of operations.
- Cpp strings do "what you expected" as a programmer.
- C-style strings do "what you expected" as a machine designer.
Use cpp strings for most string operations.
## Cpp native array
### Storing Other Data Types Besides `char`
There are many options to store non-char data in an array.
Native C-style arrays
- Cannot add or remove positions
- Can index positions directly (constant time)
- Not necessary zero-terminated (no null terminator as ending)
STL list container (bi-linked list)
- Add/remove position on either end
- Cannot index positions directly
STL vector container ("back stack")
- Can add/remove position at the back
- Can index positions directly
### Pointer and Arrays
```cpp
#include <iostream>
using namespace std;
int main (int argc, char *argv[]) {
int a[10];
int *p = &a[0];
int *q = a;
// p and q are pointing to the same location
++q; // q is now pointing to the second element of the array
}
```
An array holds a contiguous sequence of memory locations
- Can refer to locations using either array index or pointer location
- `int a[0]` vs `int *p`
- `a[i]` vs `*(a + i)`
Array variable essentially behaves like a const pointer
- Like `int * const arr;`
- Cannot change where it points
- Can change locations unless declared as const, eg `const int arr[10];`
Can initalize other pointers to the start of the array
- Using array name
- `int *p = a;`
- `int *p = &a[0];`
Adding or subtracting int pointer n moves a pointer by n of the type it points to
- `int *p = a;`
- `p += 1;` moves pointer by 1 `sizeof(int)`
- `p -= 1;` moves pointer by 1 `sizeof(int)`
Remember that cpp only guarantees `sizeof(char)` is 1.
### Array of (and Pointers to) Pointers
```cpp
#include <iostream>
using namespace std;
int main (int argc, char *argv[]) {
// could declare char ** argv
for (int i = 0; i < argc; i++) {
cout << argv[i] << endl;
}
return 0;
}
```
Can have array of pointers to pointers
Can also have an array of pointers to arrays
- `int (*a)[10];`
- `a[0]` is an array of 10 ints
- `a[0][0]` is the first int in the first array
### Rules for pointer arithmetic
```cpp
#include <iostream>
using namespace std;
int main (int argc, char *argv[]) {
int a[10];
int *p = &a[0];
int *q = p + 1;
return 0;
}
```
You can subtract pointers to get the number of elements between them (no addition, multiplication, or division)
- `int n = q - p;`
- `n` is the number of elements between `p` and `q`
You can add/subtract an integer to a pointer to get a new pointer
- `int *p2 = p + 1;`
- `p2` is a pointer to the second element of the array
- `p+(q-p)/2` is allowed but not `(p+q)/2`
Array and pointer arithmetic: Given a pointer `p` and integer `n`, `p[n]` is equivalent to `*(p+n)`.
Dereferencing a 0 pointer is undefined behavior.
Accessing memory outside of an array may
- Crash the program
- Let you read/write memory you shouldn't (hard to debug)
Watch out for:
- Uninitialized pointers
- Failing to check for null pointers
- Accessing memory outside of an array
- Error in loop initialization, termination, or increment
### Dynamic Memory Allocation
Aray can be allocated, and deallocated dynamically
Arrays have particular syntax for dynamic allocation
Don't leak, destroy safely.
```cpp
Foo * baz (){
// note the array form of new
int * const a = new int[3];
a[0] = 1; a[1] = 2; a[2] = 3;
Foo *f = new Foo;
f->reset(a);
return f;
}
void Foo::reset(int *a) {
// ctor must initialize to 0
delete [] this->array_ptr;
this->array_ptr = a;
}
void Foo::~Foo() {
// note the array form of delete
delete [] this->array_ptr;
}
```
## Vectors
```cpp
#include <iostream>
#include <vector>
using namespace std;
int main (int argc, char *argv[]) {
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
// note that size_t is an unsigned type that is guaranteed to be large enough to hold the size of v.size(), determined by the compiler.
for (size_t i = 0; i < v.size(); i++) {
cout << v[i] << endl;
}
// this will print 1, 2, 3
return 0;
}
```
### Motivation to use vectors
Vector do a lot of (often tricky) dynamic memory management.
- use new[] and delete[] internally
- resize, don't leak memory
Easier to pass to functions
- can tell you their size by `size()`
- Don't have to pass a separate size argument
- Don't need a pointer by reference in order to resize
Still have to pay attention
- `push_back` allocates more memory but `[]` does not
- vectors copy and take ownership of elements
## IO classes
```cpp
#include <iostream>
using namespace std;
int main (int argc, char *argv[]) {
int i;
// cout == std::ostream
cout << "Enter an integer: ";
// cin == std::istream
cin >> i;
cout << "You entered: " << i << endl;
return 0;
}
```
`<iostream>` provides classes for input and output.
- Use `<istream>` for input
- Use `<ostream>` for output
Overloaded operators
- `<<` for insertion
- `>>` for extraction (terminates on whitespace)
Other methods
- `ostream`
- `write`
- `put`
- `istream`
- `get`
- `eof`
- `good`
- `clear`
Stream manipulators
- `ostream`: `flush`, `endl`, `setwidth`, `setprecision`, `hex`, `boolalpha` (boolalpha is a manipulator that changes the way bools are printed from 0/1 to true/false).
### File I/O
```cpp
#include <iostream>
#include <fstream>
using namespace std;
int main (int argc, char *argv[]) {
ifstream ifs;
ifs.open("input.txt", ios::in);
ofstream ofs ("output.txt", ios::out);
if (!ifs.is_open() && ofs.is_open()) {
int i;
ifs >> i;
ofs << i;
}
ifs.close();
ofs.close();
return 0;
}
```
`<fstream>` provides classes for file input and output.
- Use `<ifstream>` for input
- Use `<ofstream>` for output
Other methods
- `open`
- `close`
- `is_open`
- `getline` parses a line from the file, defaults to whitespace
- `seekg`
- `seekp`
File modes:
- `in` let you read from the file
- `out` let you write to the file
- `ate` let you write to the end of the file
- `app` let you write to the end of the file
- `trunc` let you truncate the file
- `binary` let you read/write binary data
### String Streams Classes
```cpp
#include <iostream>
#include <fstream>
#include <sstream>
using namespace std;
int main (int argc, char *argv[]) {
ifstream ifs("input.txt", ios::in);
if (!ifs.is_open()) {
string line_1, word_1;
getline(ifs, line_1);
istringstream iss(line_1);
iss >> word_1;
cout << word_1 << endl;
}
ifs.close();
return 0;
}
```
`<sstream>` provides classes for string streams.
- Use `<istringstream>` for input
- Use `<ostringstream>` for output
Useful for scanning input
- Get a line form file into a string
- Wrap a string into a stream
- Pull words off the stream
```cpp
#include <iostream>
#include <sstream>
using namespace std;
int main (int argc, char *argv[]) {
if (argc < 3) return 1;
ostringstream argsout;
argsout << argv[1] << " " << argv[2] << endl;
istringstream argsin(argsout.str());
float f,g;
argsin >> f;
argsin >> g;
cout << f << "/" << g << "is" << f/g << endl;
return 0;
}
```
Useful for formatting output
- Using string as format buffer
- Wrapping a string into a stream
- Push formatted values into the stream
- Output the stream to file
Program gets arguments as C-style strings
Formatting is tedious and error prone in C-style strings (`sprintf`, etc.)
`iostream` formatting is friendly.

View File

@@ -0,0 +1,226 @@
# CSE332S Lecture 5
## Function and the Call Stack
### Function lifecycle
Read variable declaration from right to left
eg:
```c
int i; // i is a integer variable
int & r = i; // r is a reference to i
int *p = &i; // p is a pointer to i
const int * const q = &i; // q is a constant pointer to a constant integer
```
Read function declaration from inside out
eg:
```c
int f(int x); // f is a function that takes an integer argument and returns an integer
```
Cpp use the "**program call stack**" to manage active function invocations
When a function is called:
1. A stack frame is "**pushed**" onto the call stack
2. Execution jumps fro the calling functio's code block to the called function's code block
Then the function is executed the return value is pushed onto the stack
When a function returns:
1. The stack frame is "**popped**" off the call stack
2. Execution jumps back to the calling function's code block
The compiler manages the program call stack
- Small performance overhead associated with stack frame management
- Size of a stack frame must be known at compile time - cannot allocate dynamically sized objects on the stack
#### Stack frame
A stack frame represents the state of an active function call
Each frame contains:
- **Automatic variables** - variables local to the function. (automatic created and destroyed when the function is called and returns)
- **Parameters** - values passed to the function
- **A previous frame pointer** - used to access the caller's frame
- **Return address** - the address of the instruction to execute after the function returns
### Recursion for free
An example of call stack:
```cpp
void f(int x) {
int y = x + 1;
}
void main(int argc, char *argv[]) {
int z = 1;
f(z);
}
```
when f is called, a stack frame is pushed onto the call stack:
- function `f`
- parameter `x`
- return address
- function `main`
- parameter `argc`
- parameter `argv`
- return address
On recursion, the call stack grows for each recursive call, and shrinks when each recursive call returns.
```cpp
void f(int x) {
if (x > 0) {
f(x - 1);
}
}
int main(int argc, char *argv[]) {
f(10);
}
```
The function stack will look like this:
- function `f(0)`
- parameter `x`
- return address
- function `f(1)`
- parameter `x`
- return address
- ...
- function `f(10)`
- parameter `x`
- return address
- function `main`
- parameter `argc`
- parameter `argv`
- return address
### Pass by reference and pass by value
However, when we call recursion with pass by reference.
```cpp
void f(int & x) {
if (x > 0) {
f(x - 1);
}
}
int main(int argc, char *argv[]) {
int z = f(10);
}
```
The function stack will look like this:
- function `f(0)`
- return address
- function `f(1)`
- return address
- ...
- function `f(10)`
- return address
- function `main`
- parameter `z`
- parameter `argc`
- parameter `argv`
- return address
This is because the reference is a pointer to the variable, so the function can modify the variable directly without creating a new variable.
### Function overloading and overload resolution
Function overloading is a feature that allows a function to have multiple definitions with the same name but **different parameters**.
Example:
```cpp
void errMsg(int &x){
cout << "Error with code: " << x << endl;
}
void errMsg(const int &x){
cout << "Error with code: " << x << endl;
}
void errMsg(const string &x){
cout << "Error with message: " << x << endl;
}
void errMsg(const int &x, const string &y){
cout << "Error with code: " << x << " and message: " << y << endl;
}
int main(int argc, char *argv[]){
int x = 10;
const int y = 10;
string z = "File not found";
errMsg(x); // this is the first function (best match: int to int)
errMsg(y); // this is the second function (best match: const int to const int)
errMsg(z); // this is the third function (best match: string to const string)
errMsg(x, z); // this is the fourth function (best match: int to const int, string to const string)
}
```
When the function is called, the compiler will automatically determine which function to use based on the arguments passed to the function.
BUT, there is still ambiguity when the function is called with the same type of arguments.
```cpp
void errMsg(int &x);
void errMsg(short &x);
int main(int argc, char *argv[]){
char x = 'a';
errMsg(x); // this is ambiguous, cpp don't know which function to use since char can both be converted to int and short. This will throw an error.
}
```
#### Default arguments
Default arguments are arguments that are provided by the function caller, but if the caller does not provide a value for the argument, the function will use the default value.
```cpp
void errMsg(int x = 0, string y = "Unknown error");
```
If the caller does not provide a value for the argument, the function will use the default value.
```cpp
errMsg(); // this will use the default value for both arguments
errMsg(10); // this will use the default value for the second argument
errMsg(10, "File not found"); // this will use the provided value for both arguments
```
Overloading and default arguments
```cpp
void errMsg(int x = 0, string y = "Unknown error");
void errMsg(int x);
```
This is ambiguous, because the compiler don't know which function to use. This will throw an error.
We can only default the rightmost arguments
```cpp
void errMsg(int x = 0, string y = "Unknown error");
void errMsg(int x, string y = "Unknown error"); // this is valid
void errMsg(int x = 0, string y); // this is invalid
```
Caller must supply leftmost arguments first, even they are same as default arguments
```cpp
void errMsg(int x = 0, string y = "Unknown error");
int main(int argc, char *argv[]){
errMsg("File not found"); // this will throw an error, you need to provide the first argument
errMsg(10, "File not found"); // this is valid
}
```

View File

@@ -0,0 +1,231 @@
# CSE332S Lecture 6
## Expressions
### Expressions: Operators and Operands
- **Operators** obey arity, associativity, and precedence
```cpp
int result = 2 * 3 + 5; // assigns 11
```
- Operators are often overloaded for different types
```cpp
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 (e.g., `++a` and `--a`)
- Postfix versions (e.g., `a++` and `a--`) take lvalues, produce rvalues
- Beware accidentally using the “future equivalence” operator, e.g.,
```cpp
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)
When compiling an expression, the compiler uses the precedence of the operators to determine which subexpression to execute first. Operator precedence defines the order in which different operators in an expression are evaluated.
### Expressions are essentially function calls
```cpp
int main(int argc, char *argv[]){
string h="hello";
string w="world";
h=h+w;
return 0;
}
```
Compiler will generate a function call for each expression.
The function name is `operator+` for the `+` operator.
```cpp
string operator+(const string & a, const string & b){
// implementation ignored.
}
```
### Initialization vs. Assignment
`=` has dual meaning
When used in declaration, it is an **initializer** (constructor is called)
```cpp
int a = 1;
```
When used in assignment, it is an **assignment**
```cpp
a = 2;
```
## Statements and exceptions
In C++, **statements** are the basic units of execution.
- Each statement ends with a semicolon (`;`) and can use expressions to compute values.
- Statements introduce scopes, such as those for temporary variables.
A useful statement usually has a **side effect**:
- Stores a value for future use, e.g., `j = i + 5;`
- Performs input or output, e.g., `cout << j << endl;`
- Directs control flow, e.g., `if (j > 0) { ... } else { ... }`
- Interrupts control flow, e.g., `throw out_of_range;`
- Resumes control flow, e.g., `catch (RangeError &re) { ... }`
The `goto` statement is considered too low-level and is usually better replaced by `break` or `continue`.
- If you must use `goto`, you should comment on why it is necessary.
### Motivation for exceptions statements
Need to handle cases where program cannot behave normally
- E.g., zero denominator for division
Otherwise bad things happen
- Program crashes
- Wrong results
Could set value to `Number::NaN`
- I.e., a special “not-a-number” value
- Must avoid using a valid value…
… which may be difficult (e.g., for int)
- Anyway, caller might fail to check for it
Exceptions offer a better alternative
```cpp
void Number::operator/(const Number & n){
if (n.value == 0) throw DivisionByZero();
// implementation ignored.
return *this / n.value;
}
```
### Exceptions: Throw Statement Syntax
- Can throw any object
- Can catch, inspect, user, refine, rethrow exceptions
- By value makes local copy
- By reference allows modification to be made to the original exception
- Default catch block is indicated by `...`
```cpp
void f(){
throw 1;
}
int main(int argc, char *argv[]){
try{
f();
}
catch (int &e){
cout << "caught an exception: " << e << endl;
}
catch (...){
cout << "caught an non int exception" << endl;
}
return 0;
}
```
### C++ 11: Library Exception Hierarchy
- **C++11 standardizes a hierarchy of exception classes**
- To access these classes `#include <stdexcept>`
- **Two main kinds (subclasses) of `exception`**
- Run time errors (overflow errors and underflow errors)
- Logic errors (invalid arguments, length error, out of range)
- **Several other useful subclasses of `exception`**
- Bad memory allocation
- Bad cast
- Bad type id
- Bad exception
- **You can also declare other subclasses of these**
- Using the class and inheritance material in later lectures
## Exception behind the scenes
- **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
### Exceptions Manipulate the Function Call Stack
- **In general, the call stacks structure is fairly basic**
- A chunk of memory representing the state of an active function call
- Pushed on program call stack at run-time (can observe in a debugger)
- **`g++ -s` generates machine code (in assembly language)**
- A similar feature can give exact structure for most platforms/compilers
- **Each stack frame contains:**
- A pointer to the previous stack frame
- The return address (i.e., just after point from which function was called)
- The parameters passed to the function (if any)
- Automatic (local) variables for the function
- Sometimes called “stack variables”
_basically, you can imageing the try/catch block as a function that is called when an exception is thrown._
## Additional Details about Exceptions
- Control jumps to the 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
- `catch(...)` is a catch-all block
- Often should at least free resources, generate an error message, and else.
- May rethrow exception for another handler to catch and do more
- `throw`;
- As of C++11, rethrows a caught exception
### Depreciated Exception Specifications
- **Exception specifications**
- Used to specify which exceptions a function can throw
- Depreciated in C++11
- **Exception specifications are now deprecated**
- **Use `noexcept` instead**
- **`noexcept` is a type trait that indicates a function does not throw exceptions**
```cpp
void f() throw(int, double); // prohibits throwing int or double
```
```cpp
void f() noexcept; // prohibits throwing any exceptions
```
### Rule of Thumb for Using C++ Exceptions
- **Use exceptions to handle any cases where the program cannot behave normally**
- Do not use exceptions as a way to control program execution under normal operating conditions
- **Don't let a thrown exception propagate out of the main function uncaught**
- Instead, always catch any exceptions that propagate up
- Then return a non-zero value to indicate program failure
- **Dont rely on exception specifications**
- May be a false promise, unless you have fully checked all the code used to implement that interface
- No guarantees that they will work for templates, because a template parameter could leave them off and then fail

View File

@@ -0,0 +1,79 @@
# CSE332S Lecture 7
## Debugging
Debugger lets us:
1. Execute code incrementally
a. Line by line, function to function, breakpoint to breakpoint
2. Examine state of executing program
a. Examine program call stack
b. Examine variables
When to debug:
1. Trace how a program runs
2. Program crashes
3. Incorrect result
### Basic debugging commands
Set breakpoints
Run program - program stops on the first breakpoint it encounters
From there:
- Execute one line at a time
- Step into (step out can be useful if you step into a function outside of your code)
- Step over
- Execute until the next breakpoint (continue)
While execution is stopped:
- Examine the state of the program
- Call stack, variables, ...
### Lots of power, but where to start?
Stepping through the entire program is infeasible
Think first!!!
- What might be going wrong based on the output or crash message?
- How can I test my hypothesis?
- Can I narrow down the scope of my search?
- Can I recreate the bug in a simpler test case/simpler code?
- Set breakpoints in smart locations based on my hypothesis
### Todays program
A simple lottery ticket game
1. User runs the program with 5 arguments, all integers (1-100)
2. Program randomly generates 10 winning numbers
3. User wins if they match 3 or more numbers
At least thats how it should run, but you will have to find and fix a few issues first
First, lets look at some things in the code
- Header guards/pragma once
- Block comments: Who wrote this code? and what does it do?
- Multiple files and including header files
- **Do not define functions in header files, declarations only**
- **Do not #include .cpp files**
- Function or data type must be declared before it can be used
#### Header Guards
```cpp
#pragma once // alternative to traditional header guards, don't need to do both.
#ifndef ALGORITHMS_H
#define ALGORITHMS_H
#include<vector>
void insertion_sort(std::vector<int> & v);
bool binary_search(const std::vector<int> & v, int value);
#endif // ALGORITHMS_H
```
The header guard is used to prevent the header file from being included multiple times in the same file.

View File

@@ -0,0 +1,236 @@
# CSE332S Lecture 8
## From procedural to object-oriented programming
Procedural programming
- Focused on **functions** and the call stack
- Data and functions treated as **separate** abstractions
- Data must be passed into/returned out of functions, functions work on any piece of data that can be passed in via parameters
Object-oriented programming
- Data and functions packaged **together** into a single abstraction
- Data becomes more interesting (adds behavior)
- Functions become more focused (restricts data scope)
## Object-oriented programming
- Data and functions packaged together into a single abstraction
- Data becomes more interesting (adds behavior)
- Functions become more focused (restricts data scope)
### Today:
- An introduction to classes and structs
- Member variables (state of an object)
- Constructors
- Member functions/operators (behaviors)
- Encapsulation
- Abstraction
At a later date:
- Inheritance (class 12)
- Polymorphism (12)
- Developing reusable OO designs (16-21)
## Class and struct
### From C++ Functions to C++ Structs/Classes
C++ functions encapsulate behavior
- Data used/modified by a function must be passed in via parameters
- Data produced by a function must be passed out via return type
Classes (and structs) encapsulate related data and behavior (**Encapsulation**)
- Member variables maintain each objects state
- Member functions (methods) and operators have direct access to member variables of the object on which they are called
- Access to state of an object is often restricted
- **Abstraction** - a class presents only the relevant details of an object, through its public interface.
### C++ Structs vs. C++ Classes?
Class members are **private** by default, struct members are **public** by default
When to use a struct
- Use a struct for things that are mostly about the data
- **Add constructors and operators to work with STL containers/algorithms**
When to use a class
- Use a class for things where the behavior is the most important part
- Prefer classes when dealing with encapsulation/polymorphism (later)
```cpp
// point2d.h - struct declaration
struct Point2D {
Point2D(int x, int y);
bool operator< (const Point2D &) const; // a const member function
int x_; // promise a member variable
int y_;
};
```
```cpp
// point2d.cpp - methods functions
#include "point2d.h"
Point2D::Point2D(int x, int y) :
x_(x), y_(y) {}
bool Point2D::operator< (const Point2D &other) const {
return x_ < other.x_ || (x_ == other.x_ && y_ < other.y_);
}
```
### Structure of a class
```cpp
class Date {
public: // public stores the member functions and variables accessible to the outside of class
Date(); // default constructor
Date (const Date &); // copy constructor
Date(int year, int month, int day); // constructor with parameters
virtual ~Date(); // (virtual) destructor
Date& operator= (const Date &); // assignment operator
int year() const; // accessor
int month() const; // accessor
int day() const; // accessor
void year(int year); // mutator
void month(int month); // mutator
void day(int day); // mutator
string yyymmdd() const; // generate a string representation of the date
private: // private stores the member variables that only the class can access
int year_;
int month_;
int day_;
};
```
#### Class constructor
- Same name as its class
- Establishes invariants for objects of the class
- **Base class/struct and member initialization list**
- Used to initialize member variables
- Used to construct base class when using inheritance
- Must initialize const and reference members there
- **Runs before the constructor body, object is fully initialized in constructor body**
```cpp
// date.h
class Date {
public:
Date();
Date(const Date &);
Date(int year, int month, int day);
~Date();
// ...
private:
int year_;
int month_;
int day_;
};
```
```cpp
// date.cpp
Date::Date() : year_(0), month_(0), day_(0) {} // initialize member variables, use pre-defined values as default values
Date::Date(const Date &other) : year_(other.year_), month_(other.month_), day_(other.day_) {} // copy constructor
Date::Date(int year, int month, int day) : year_(year), month_(month), day_(day) {} // constructor with parameters
// ...
```
#### More on constructors
Compiler defined constructors:
- Compiler only defines a default constructor if no other constructor is declared
- Compiler defined constructors simply construct each member variable using the same operation
Default constructor for **built-in types** does nothing (leaves the variable uninitialized)!
It is an error to read an uninitialized variable
## Access control and friend declarations
Declaring access control scopes within a class - where is the member visible?
- `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
- Its 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”
### Issues with Encapsulation in C++
Encapsulation - state of an object is kept internally (private), state of an object can be changed via calls to its public interface (public member functions/operators)
Sometimes two classes are closely tied:
- One may need direct access to the others internal state
- But, other classes should not have the same direct access
- Containers and iterators are an example of this
We could:
1. Make the internal state public, but this violates **encapsulation**
2. Use an inheritance relationship and make the internal state protected, but the inheritance relationship doesnt make sense
3. Create fine-grained accessors and mutators, but this clutters the interface and violates **abstraction**
### Friend declarations
Offer a limited way to open up class encapsulation
C++ allows a class to declare its “friends”
- Give access to specific classes or functions
Properties of the friend relation in C++
- Friendship gives complete access
- Friend methods/functions behave like class members
- public, protected, private scopes are all accessible by friends
- Friendship is asymmetric and voluntary
- A class gets to say what friends it has (giving permission to them)
- But one cannot “force friendship” on a class from outside it
- Friendship is not inherited
- Specific friend relationships must be declared by each class
- “Your parents friends are not necessarily your friends”
```cpp
// in Foo.h
class Foo {
friend ostream &operator<< (ostream &out, const Foo &f); // declare a friend function, can be added at any line of the class declaration
public:
Foo(int x);
~Foo();
// ...
private:
int baz_;
};
ostream &operator<< (ostream &out, const Foo &f);
```
```cpp
// in Foo.cpp
ostream &operator<< (ostream &out, const Foo &f) {
out << f.baz_; // access private member variable via friend declaration
return out;
}
```

View File

@@ -0,0 +1,199 @@
# CSE332S Lecture 9
## Sequential Containers
Hold elements of a parameterized type (specified when the container variable is
declared): `vector<int> v; vector<string> v1;`
Elements are inserted/accessed based on their location (index)
- A single location cannot be in more than 1 container
- Container owns elements it contains - copied in by value, contents of container are destroyed when the container is destroyed
Containers provide an appropriate interface to add, remove, and access elements
- Interface provided is determined by the specifics of the container - underlying data structure
_usually the provided interface of the container runs in constant time_
### Non-random access containers
Cannot access elements in constant time, must traverse the container to get to the desired element.
#### Forward list
- implemented as a singly linked list of elements
- Elements are not contiguous in memory (no random access)
- Contains a pointer to the first element (can only grow at front, supplies a `forward_iterator`)
#### List
- implemented as a doubly linked list of elements
- Elements are not contiguous in memory (no random access)
- Contains a pointer to front and back (can grow at front or back, supplies a `bidirectional_iterator`)
### Random access containers
Add, remove, and access elements in constant time.
#### Vector
- implemented as a dynamically sized array of elements
- Elements are contiguous in memory (random access)
- Can only grow at the back via `push_back()` (amortized constant time, _may expand the array takes linear time_)
#### Deque
- double-ended queue of elements
- Elements do not have to be contiguous in memory, but must be accessible in constant time (random access)
- Can grow at front or back of the queue
## Iterators and iterator types
Could use the subscript/indexing (operator[]) operator with a loop
- Not all containers supply an [] operator, but we should still be able to traverse and access their elements
Containers provide iterator types:
- `vector<int>::iterator i; // iterator over non-const elements`
- `vector<int>::const_iterator ci; // iterator over const elements`
Containers provide functions for creating iterators to the beginning and just past
the end of the container:
```cpp
vector<int> v = { 1, 2, 3, 4, 5 };
auto start = v.cbegin(); // cbegin() gives const iterator, you can't modify the elements, you can use .begin() to get a non-const iterator
while (start != v.cend()) { // over const elements, v.cend() is not a valid element, it's just one pass the end.
cout << *start << endl;
++start;
}
```
### More on iterators
- Iterators generalize different uses of pointers
- Most importantly, define left-inclusive intervals over the ranges of elements in a container `[begin, end)`
- Iterators interface between algorithms and data structures (Iterator design pattern)
- Algorithms manipulate iterators, not containers
- An iterators value can represent 3 kinds of `states`:
- `dereferencable` (points to a valid location in a range), eg `*start`
- `past the end` (points just past last valid location in a range), eg `v.cend()`
- `singular` (points to nothing), eg `nullptr`
- Can construct, compare, copy, and assign iterators so that native and library types
can inter-operate
### Properties of Iterator Intervals
- Valid intervals can be traversed safely with an iterator
- An empty range `[p,p)` is valid
- If `[first, last)` is valid and non-empty, then `[first+1, last)` is also valid
- Proof: iterative induction on the interval
- If `[first, last)` is valid
- and position `mid` is reachable from `first`
- and `last` is reachable from `mid`
- then `[first, mid)` and `[mid, last)` are also valid
- Proof: divide and conquer induction on the interval
- If `[first, mid)` and `[mid, last)` are valid, then `[first, last)` is valid
- Proof: divide and conquer induction on the interval
### Interface supplied by different iterator types
- Output iterators: used in output operations (write),
- "destructive" read at head of stream (istream)
- Input iterators: used in input operations (read),
- "transient" write to stream (ostream)
- Forward iterators: used in forward operations (read, write), common used in forward linked list
- Value _persists_ after read/write
- Bidirectional iterators: used in bidirectional operations (read, write), common used in doubly linked list
- Value have _locations_
- Random access iterators: used in random access operations (read, write), common used in vector
- Can express _distance_ between two iterators
| Category/Operation | Output | Input | Forward | Bidirectional | Random Access |
| ------------------ | -------------- | -------------- | -------------- | -------------- | ----------------- |
| Read | N/A | `=*p`(r-value) | `=*p`(r-value) | `=*p`(r-value) | `=*p`(r-value) |
| Access | N/A | `->` | `->` | `->` | `->,[]` |
| Write | `*p=`(l-value) | N/A | `*p=`(l-value) | `*p=`(l-value) | `*p=`(l-value) |
| Iteration | `++` | `++` | `++` | `++,--` | `++,--,+,-,+=,-=` |
| Comparison | N/A | `==,!=` | `==,!=` | `==,!=` | `==,!=,<,>,<=,>=` |
## Generic algorithms in CPP
A standard collection of generic algorithms
- Applicable to various types and containers
- E.g., sorting integers (`int`) vs. intervals (`pair<int, int>`)
- E.g., sorting elements in a `vector` vs. in a C-style array
- Polymorphic even without inheritance relationships - interface polymorphism
- Types substituted need not have a common base class
- Must only provide the interface the algorithm requires
- Common iterator interfaces allow algorithms to work with many types of
containers, without knowing the implementation details of the container
- Significantly used with the sequence containers
- To reorder elements within a containers sequence
- To store/fetch values into/from a container
- To calculate various values and properties from it
### Organization of C++ Algorithm Libraries
The `<algorithm>` header file contains
- Non-modifying sequence operations
- Do some calculation but dont change sequence itself
- Examples include `count`, `count_if`
- Mutating sequence operations
- Modify the order or values of the sequence elements
- Examples include `copy`, `random_shuffle`
- Sorting and related operations
- Modify the order in which elements appear in a sequence
- Examples include `sort`, `next_permutation`
- The `<numeric>` header file contains
- General numeric operations
- Scalar and matrix algebra, especially used with `vector<T>`
- Examples include `accumulate`, `inner_product`
### Using Algorithms
Example using `std::sort()`
- `sort` algorithm
- Reorders a given range
- Can also plug in a functor to change the ordering function
- http://www.cplusplus.com/reference/algorithm/sort/
- Requires random access iterators.
- Requires elements being sorted implement `operator<` (less than)
```cpp
#include <algorithm>
#include <vector>
#include <iostream>
using namespace std;
int main(int argc, char* argv[]) {
vector<int> v = { 3, 1, 4, 1, 5, 9 };
sort(v.begin(), v.end()); // sort the vector
for (int i : v) {
cout << i << " ";
}
return 0;
}
```
Sort forward list of strings
```cpp
forward_list<string> fl = { "hello", "world", "this", "is", "a", "test" };
sort(fl.begin(), fl.end());
```
**This is not valid because forward list does not support random access iterators.**
Sort vector of strings
```cpp
vector<string> v = { "hello", "world", "this", "is", "a", "test" };
sort(v.begin(), v.end());
```

23
content/CSE332S/_meta.js Normal file
View File

@@ -0,0 +1,23 @@
export default {
index: {type:"page",title:"Course Description",href:"/CSE332S/index.mdx"},
"---":{
type: 'separator'
},
CSE332S_L1: "Object-Oriented Programming Lab (Lecture 1)",
CSE332S_L2: "Object-Oriented Programming Lab (Lecture 2)",
CSE332S_L3: "Object-Oriented Programming Lab (Lecture 3)",
CSE332S_L4: "Object-Oriented Programming Lab (Lecture 4)",
CSE332S_L5: "Object-Oriented Programming Lab (Lecture 5)",
CSE332S_L6: "Object-Oriented Programming Lab (Lecture 6)",
CSE332S_L7: "Object-Oriented Programming Lab (Lecture 7)",
CSE332S_L8: "Object-Oriented Programming Lab (Lecture 8)",
CSE332S_L9: "Object-Oriented Programming Lab (Lecture 9)",
CSE332S_L10: "Object-Oriented Programming Lab (Lecture 10)",
CSE332S_L11: "Object-Oriented Programming Lab (Lecture 11)",
CSE332S_L12: "Object-Oriented Programming Lab (Lecture 12)",
CSE332S_L13: "Object-Oriented Programming Lab (Lecture 13)",
CSE332S_L14: "Object-Oriented Programming Lab (Lecture 14)",
CSE332S_L15: "Object-Oriented Programming Lab (Lecture 15)",
CSE332S_L16: "Object-Oriented Programming Lab (Lecture 16)",
CSE332S_L17: "Object-Oriented Programming Lab (Lecture 17)"
}

View File

@@ -0,0 +1,6 @@
# CSE332S
**Object-Oriented Software Development Laboratory**
**Spring 2025**
Instructor: **Jon Shidal**