Yao Lirong's Blog

CS2024 C++ Programming

2020/09/07

The goal of CS2024 is to teach as much of the C++ language as possible with an eye towards your being able to use it effectively in future classes that may depend on it and/or in a professional setting. C++ is ever changing with new standards released every three years. We look to strike a balance between making sure you thoroughly understand “historic” C++ as well as introducing you to new features enabled in the language in the past decade.

Lec01 Introduction

Explaining our First Program

  • #include <iostream>

    Tells the compiler that we would like to load definitions from a header file named “iostream”.

    The # (pound sign) indicates this is a preprocessor directive, it gets dealt with BEFORE your code is compiled

  • std::cout << “Hello World!” << std::endl;

    << is an operator that directs content from the right to the left. In this case, we direct the string “Hello World” to std::cout, which is the console

Compiling C++

  • Windows: use Visual Studio
  • Linux: g++ -std=c++11 -lstdc++ -o demo1 demo1.cpp: -o specifies the name of the compiled file

Compiler takes the text of the source code and converts it into a binary object so that it can execute it a bit more efficiently.

Lec02 Input/Output and Operators

Input and Output

  • >> stream extraction operator
  • std::cin >> k take a value from cin, which is the input stream keyboard, and assign it to k
  • getline(cin,str): cin uses space as delimiter so it won’t read in a whole line. Use this to read a full line

Using

using is similar to import in java, so that you don’t have to use the full name of a function when calling it.

1
2
3
4
5
6
7
using std::cout;
using std::endl;
int main(int argc, char *argv[]) {
// No longer need to use the std:: prefix
cout << “Hello World” << endl;
}

Lec03 Introduction to Classes

Struct

C-Style structure definition: (Define a structure called Course, which has three fields )

1
2
3
4
5
typedef struct {
string name;
string instructor;
int numStudents;
} Course;

Classes

Variables defined inside that class are called member variables.

Functions defined inside the class are called member functions

Public vs. Private

public and private keywords can appear as many times as you want in the class definition.

1
2
3
4
5
6
7
8
9
10
11
class Course {
public: // These can be seen outside the class
// Define member functions
int getStudentCount() { return numStudents; }

private: // These can be seen inside the class only
// Define member variables
string name;
string instructor;
int numStudents;
}

Declaration and Definition of Member Functions

You don’t have to define the functions where they are declared. Instead, you can define them outside of the class declaration. When you define them outside of the class declaration, you can still access the member variables inside that class. That’s because you are telling the compiler that this is a member function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Course {
public: // These can be seen outside the class
// Define member functions
int getStudentCount();
void setStudentCount(int count);

private:
...
}

string Course::getCourseName()
{return name;}

int Course::getStudentCount()
{return numStudents;}

You usually want to define your getter and setter functions inside class definition.

When other functions you are trying to define are too big, we usually define them outside the class definition and usually in a separate file. So we declare the functions in header file **.h and define them in another file **.cpp

1
2
3
4
5
6
7
8
9
10
11
12
/* <Courses.h> */
class Course {
private:
void complexLogic();
}

/* <Courses.cpp> */
#incldue "Courses.h"
void Courses::complexLogic(){
...
};

Constructors

Constructors have to have the same name as the class. Constructors have no return type. You can define Constructors outside of class definition too.

Constructors are called when you declare an instance of that type: MyClass instance. Note defining a pointer of that class without allocating memory to that pointer MyClass *p will not call the constructor, but declaring a pointer and allocating memory will call the constructor, because that’s the real time an instance is created MyClass *p = new MyClass().

Lec5 Functions I

Enum

If you don’t assign values to the ones following the first, they will all have value of previous increment 1.

1
2
3
4
5
6
7
8
// Define error codes
enum RonsError {
cNoError = 0, // Values are optional, default is 0
cBadArg, // If a value is not present,
cBadResult, // assign previous value + 1
cUnknownErr
};

In C++11, we can use the class keyword to define sort of a “namespace” for the enum.

1
2
3
4
5
6
7
8
enum class Months {
JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC
}
if ((month == Months::DEC) || (month < Months::MAR))
...
Months get_march(){
return Months::MAR;
}

Function Declaration and Definition Revisited

1
2
3
4
5
6
7
8
9
10
11
12
// mymath.h -- header file for math functions
long squareIt(long);

// mymath.cpp -- implementation of math functions
long squareIt(long x)
{ return x * x;}

// main.cpp
#include “mymath.h”
void main()
{ cout << “5 squared is “ << squareIt(5) << endl;}

You should never include a “.c++” file in another c++ file.

Lec6 Function II

Inline Functions

1
2
3
4
5
inline int performAddition(int x,int y) 
{
return x+y;
}

Wherever this function is called the compiler has the option of replacing the call with the body of the actual function, instead of creating a memory stack for that function call and etc.

The compiler may not do that when it’s a recursive call or that function is really long.

Pass By Reference

Why and when do you want to use pass by reference?

  • You need to return multiple values. C++ only allows you to return one value. So you send those values as pass by reference parameters
  • You are passing a large structure/class. When passing values, the compiler will make a copy of those structure/class and pass them, which takes up a lot of stack space.

In the second case, maybe you don’t want to change anything in the structure, but passing by reference makes such a mistake likely to happen. To fix this, you can declare this passed by value as const, so when you accidentally modify it, you will get a compile-time error.

1
2
3
4
5
bool isBusy(const BIGDataType &arg1)
{
if (arg1.busyField = 0)
return true;
return false;}

Default Argument

When we declare a function, we can set a default value to its argument. (Don’t set a default value in function definition)

1
2
3
4
5
6
7
8
9
10
class Counter {

void increment(int incrementBy=1);
… };
void Counter::increment(int incrementBy);
{ mycount += incrementBy;}

x.increment(); // increment x by 1
y.increment(2); // increment y by 2

Unary Scope Operator

When you have 3 variables with the same name defined in global scope, local scope, and a nested scope inside local scope and you want to access the variable in the global scope inside some scope, you can use the :: before calling this variable. There is no way for you in the nested scope to access the variable with a same name in local scope (parent scope).

1
2
3
4
5
6
7
8
9
int x=1;    // in the global scope
int main(int argc, char *argv[]) {
int x = 6; // local variable to main()
// cannot be accessed in the following nested scope
{ int x = 5; // local variable in a sub-scope of main()
cout << “x is : “ << ::x << endl; // "x is : 1"
}
}

Lec7 Function III

Function Templates

1
template <typename a,typename b,…> return_type function_name (formal args)

At compilation time the compiler will look at your code and generate a separate function for each type used throughout your code when calling template functions. For example, for this maximum below, when the compiler sees the call to maximum(3,5,8), it uses the function template to automatically generate an overloaded version of maximum() that takes three variables of type int as its arguments.

1
2
3
4
5
6
7
8
9
10
template <class T> T maximum(T val1, T val2, T val3)
{ T maxValue = val1;
if (val2 > maxValue)
maxValue = val2;
if (val3 > maxValue)
maxValue = val3;
return maxValue;
}

return maximum(3,5,8);

Lec8 Arrays and Vectors

Arrays

Arrays don’t have boundary checking.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <array>

// initialization
const int size = 5;
array<int,size> myArray;

// range based for-loop
for (int item : myArray)
cout << “Next item is: “ << item;

// sorting and searching
sort(myArray.begin(),myArray.end()); //ascending order
bool found = binary_search(myArray.begin(),myArray.end(),2);

Vectors

1
2
3
4
5
6
#include<vectors>

vector<int> primeVector{2,3,5,7,11,13};

primeVector[6] = 17; //valid syntax but can crash the program
primeVector.at(6) = 17; //involves boundary checking and throw an error

Lec9 Pointers

Dynamic Allocation

1
2
3
int *iPtr;  // declares a pointer to int
iPtr = new int; // "new int" gives a dynamically allocated instance of int
// then we assign this space to iPtr

Note: in the above example, a memory in the heap is allocated to this pointer

iPtr contains one of the following:

  • A pointer to the newly allocated data type (in this case, an int)
  • NULL (if the pointer could not be allocated due to insufficient memory)

We should always check whether it is NULL before using a dynamically allocated pointer.

We can use delete iPtr to dispose a dynamically allocated pointer.

1
2
3
4
5
int *iPtr; // iPtr points to some random memory
iPtr = new int; // iPtr points to some memory allocated to it in heap
*iPtr = 5; // write 5 to the memory iPtr is allocated to
delete iPtr; // release the memory assigned to iPtr / iPtr now no longer points to that memory
return 0;

Pointers to Already Existing Values

Existing values are in stack frame, so when our pointers point to something already existed, they point to something in the stack frame, but remember variables in stack frame can disappear when out of scope.

1
2
3
4
5
6
7
8
int main()
{
int *iPtr;
if (true) {
int p = 5;
iPtr = &p; }
cout << “*iPtr is “ << *iPtr << endl;
}

So the danger is you will have to know how long this stack frame will live, or you will lose track of what you are pointing to and end up pointing something totally irrelevant.

Common Confusion with *

  • int *p - declaring a pointer: The star is part of the type name, and says that we want a pointer to some other type (in our example, int * is the type of p).
  • r = *p - dereferencing a pointer (RHS): The star is the dereference operator. This assignment gives the variable r a new value, namely the value inside the box that the pointer p points to.
  • *p = r - dereferencing a pointer (LHS): The star is the dereference operator. This assignment changes the value inside the box that p points to be a new value, namely the value of the variable r.

Pointer Chaos

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int *a = 5, *b = 7;

// dereference a, get the value stored in the memory a is pointing to,
// and write a same value to the memory b is pointing to
*b = *a;

// let b point to the same address as a is pointing to
b = a;

// release the memory allocated to a,
// also doing that for b since they are pointing at the same thing
delete a;

// throw "pointer being freed is not allocated" error
// since we already deleted it when we did that for a
delete b;

Pointers to User-Defined Types

When we want to use member member (functions/ variables), we can use one of the following:

1
2
3
Course *aCourse = new Course;
(*aCourse).setStudentCount(45);
aCourse->setStudentCount(45);

Passing Pointers as Arguments

1
2
3
4
5
6
7
int *a = new int;
int x = 5;
// store 0 in the memory location pointed at by intPtr
void setToZero(int *intPtr) { *intPtr = 0; }

setToZero(a); // pass to it a pointer whose value is some address
setToZero(&x); // pass the address of some variable to it

Const with Pointers

Principle of Least Privilege: Any operation you do should only be given the opptunity to happen if it absolutely needs to.

Following this principle, we don’t want to give writing privilege to functions doing reading.

There are four possibilities between constant/non-constant pointers pointing to constant/non-constant data:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Non-Constant Pointer, Non-Constant Data
// Free for the pointer to point to something else,
// Free for the data it is pointing to be written as something else
int *intPtr = new int;

// Non-Constnat Pointer, Constant Data
// We can’t modify the data pointed at by coursePtr
// We CAN set coursePtr to a different value
void printAllCourseData(const Course *coursePtr, const int size)
{
// For this function, maybe we will direct pointer to some other course
// once one course's info has been printed,
// while we don't want to change that info
// because this is just a reading function
}

// Constant Pointer, Non-Consant Data
// Pointer can only point to a specific memory
// The data it is pointing to can be changed
void setupCourse(Course *const coursePtr)
{
// For this function, we only want to change information of this course passed in.
}

// Constant Pointer, Constant Data
// We can’t modify the data pointed at by coursePtr
// We can’t set coursePtr to a different value either
void printCourseData(const Course *const coursePtr)
{
// We only want to print out the info of this course passed in and do nothing else
}

Lec10 Classic Arrays and Pointer Arithmetic

Classic Array

1
2
int *j[4]; == (int *) j[4]// array of 4 pointers
int (*p)[4]; // a pointer to an array of 4 integers

Arrays are somewhat pointers. For example, if we have int b[10], b always points to the first element in this array: b == &b[0]

Pointer Arithmetic

For any array p[n] == *(p+n). In particular, *(p+n) gives the contents of we have after advancing n steps from p. In fact, we also have p[n] == n[p], because our a[m] is just a syntactic sugar for *(a+m)

Dynamic Allocation of Arrays

1
2
3
int a1[8] = new int; // WRONG
int *a = new int[8]; // RIGHT
delete [] a; // Must use this, ”delete a” is undefined

There are more scope issues when you use arrays as pointers. For example, the following code returns a pointer to something inside current call stack frame. It will disappear when out of the scope. Therefore, the returned pointer from function MakeArray() actually points to something undefined.

1
2
3
int *MakeArray() {
int iArray[50];
return iArray; }

The following code behaves differently. Instead of returning a pointer to something in the call stack, it returns something in the heap, which will not disappear after the function finishes execution.

1
2
3
int *MakeArray(int size) {
int *anArray = new int[size];
return anArray; }

Passing Arrays as Parameters

Since arrays are pointers, you can only pass the real array to a function. There is no concept of passing a copy of that array. These are standard ways of declaring a function taking in arrays as its parameters.

1
2
void swap(int *A, int j, int k);
void swap(int A[], int j, int k);

Memory Allocation with malloc and sizeof

  • malloc is a function for dynamic memory allocation and it only takes in byte.
  • sizeof(SomeDataType) returns the number of bytes this data type needs.

Say we want to declare an array of 6 Courses in heap here.

1
2
Course *courseArray = malloc(sizeof(Course) * 6); // Old C way to initialize array in heap
Course *courseArray = new Course[6]; // The C++ way to do it

Lec11 Classes – A Deeper Look

1
clang -std=c+11 -lstdc++ -c MyString.cpp

Implicit Inline

When you define a function right in the class definition, you make this function implicitly inline. Therefore, there’s no actual method/function created; the code of the method is substituted through the rest of the code wherever that method is called.

Multiple Constructors

You can use a delegate constructors to save yourself from writing duplicate code. It will just call that constructor, if the delegate constructors take in arguments, you can just pass in those arguments there.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// older c++ style
MyString::MyString(string initValue) : MyString() {
if (growStorage(initValue.length())) {
strcpy(storagePtr, initValue.c_str())
stringLength = initValue.length();
}}

// c++ 11 style
MyString::MyString(string initValue) : MyString{} { ... }}

// Another Example
Menu::Menu(MenuItem* list[], int n, char prom, string title) : MenuItem(prom, title){
for (int i = 0; i < n; i++)
items.push_back(list[i]);
};

Destructor

The destructor is a special method (similar to constructor) that is called just before an object is destroyed. There is only one destructor per class (can’t overload). It takes no arguments. A destructor should be used to clean up any dynamically allocated resources (memory, OS objects). You call the destructor when using delete keyword.

Passing and Returning Reference

Passing Reference

If you modified a parameter passed by reference in a function, the change would persist in the calling function. Note that the way we call this function has not changed. We still pass in two strings instead of pointers.

You don’t have to do anything differently to specify that the string arguments are being passed “pass-by-reference” when I call the function; I only need to specify that I want to use pass-by-reference when I declare the getTimeAndTemp function.

1
2
3
4
5
6
7
8
void getTimeAndTemp(string &time,string &temp){ 
time = getTheREALTime();
temp = getTheREALTemp();}

int main() {
string theTime,theTemp;
getTimeAndTemp(theTime,theTemp); // theTime and theTemp will be changed.
}

Returning Reference

When we add a & before the function name, the function still returns whatever type it returns, but now the function call can appear on left side of assignment operator and we can write a new value to the memory address the returned value is stored in.

For the following example, charAt still returns a char type. The only difference is that we can now directly change the returned value stored in the object by using the assignment operator.

1
2
3
4
5
6
7
8
9
10
11
12
char &MyString::charAt(int index) {
// boundary checking is omitted for clarity
return storagePtr[index];
}

int main() {
MyString str(“Hello World!”);
char c =str.charAt(11); cout << c; // '!'
str.charAt(11) = ‘?’; // legal because we are returning reference
cout << str.charAt(11); // '?'
cout << “str is now: “ << str.MakeString() << endl; // Hello World?
}

const in class

  • As a qualifier to a member variable. It means that the member variable cannot be changed

  • As a qualifier to a member function. It means that the member function cannot change anything in the class:

    1
    string getName() const { return mName; }

static in class

  • There is ever only one copy of that variable that is shared among all the instances of the class.
  • The storage for this variable must be declared in the global scope using the fully qualified name of the variable (classname::static_variable_name)
  • The shared copy of the variable can be accessed either as a field of any instance or using the fully qualified name of the variable
1
2
3
4
5
6
7
8
9
10
11
12
// "Person.h"
class Person {
static int number_of_persons;
}

// "Person.cpp"
int Person::number_of_person = 0;

// "main.cpp"
cout << Person::number_of_person; // 0
Person p("Harmony"); // increment number_of_person by 1 in the constructor
cout << p.number_of_person; // 1

this in class

  • Its “type” is pointer to class type. So, if we have a Person class, Person has an implicitly defined member variable named this that is of type Person *
  • Any of the member variable and functions in the class can be referenced from this

Lec12 Operator Overloads

Unary Operator Overloads

we just have to use the operator keyword.

1
2
3
4
5
6
7
8
9
10
11
// "MyString.h"
int operator~();
std::string operator+();

// "MyString.cpp"
int MyString::operator~(){
return stringLength;
}
string MyString::operator+(){
return MakeString(); // returns a std::string from our MyString instance
}

Binary Operator Overloads

We define most binary operator overloads globally when it doesn’t make “sense” which of the two instances of the operands should “host” the overload. (Expressions on both ends are to some extent equal to the other)

We use inline to allow us to place this in the header file without causing multiple definition errors, so we are never really “defining” it, but just replace the code whenever it is called.

1
2
3
4
5
6
7
8
inline MyString operator+(const MyString &str1,
const MyString &str2)
{
// use the overloaded unary + sign to return a std::string
// then use the std::string overloaded binary + sign to concatenate two strings
MyString temp( (+str1) + (+str2) );
return temp;
}

Here we have an instance of binary overload not done globally. It is a “binary operator” but only takes one argument.

1
2
3
T &operator[](int i) {
return *(mStoragePtr + i); // equivalent to return mStoragePtr[i];
}

Copy Constructors

Whenever we use the assignment operator to initialize a variable when it is declared, the compiler actually looks for a constructor that takes in a single argument that matches the type of the value you are assigning to the newly declared instance.

If we have MyString str2 = 1;, the compiler would look for a constructor for MyString that took a single integer: MyString::MyString(int arg). If you copy constructors take in some object, it must be pass-by-reference!

1
2
3
4
5
6
7
8
9
Point::Point(Point &anotherPoint) {
// ...
}

int main(){
Point p1(4,5); // will use our custom constructor
Point p2(p1); // will use the copy constructor (just as a constructor function)
Point p3 = p1; // will use the copy constructor
}

Overloading Assignment =

Rather than initialize some variable, we want now to assign a new value to an existing variable. Rather than a global function, we will define it as a member function in our class.

1
2
3
4
5
6
7
MyString &MyString::operator=(const MyString &sourceStr)
{ // convert sourceStr to a std::String with our predefined unary +
// setValue takes a C++ string
// return the address of this object
setValue(+sourceStr);
return *this;
}

Overloading Stream Direction << and >>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
inline ostream& operator<<(ostream &os, MyString &str) {  
os << +str;
// we must always return the stream that was passed in. That allows "chaining"(cout<<a<<"good"<<endl;) to work
return os;
}

inline istream& operator>>(istream &is, MyString &str) {
int allocatedSpace = str.getAllocatedSpace();
char *tempBuf = new char[allocatedSpace]; // allocate temp
is.get(tempBuf,allocatedSpace-1); // read from instream into location of tempBuf [tempBuf] up to [tempBuf + allocatedSpace - 1]
string tempStr = tempBuf; // convert tempBuf to a std::string
str.setValue(tempStr); // set str of MyString class to be tempStr
delete [] tempBuf; // delete temp memory, realease space
return is; // return stream
}

Lec13 Inheritance

Basic Syntax

1
2
3
4
5
6
7
8
9
10
class DerivedClass : public BaseClass
{
<member variables unique to Derived Class>
...
};

class Student : public Person
{
int studentID;
};

Override

We can override a function by just reimplementing it in our derived class. To access the original implementation from the base class, we use its fully qualified name in the derived class.

1
2
3
4
5
6
7
8
9
10
11
12
void Person::printInfo()
{
cout << “Name: “ << name << endl;
cout << “Addr: “ << address << endl;
cout << “Phone: ” << phone << endl;
};

void Student::printInfo()
{
Person::printInfo();
cout << “Student ID: “ << studentID << endl;
};

Virtual Functions

Say we overwrite the printInfo function in Person and define a global function that takes in a Person class and call the printInfo function on that class. When we pass a Student instance to it, it will actually use the printInfo function of Person instead of Student. That’s because the compiler thinks the function just takes in a Person.

1
2
3
4
5
6
7
void printPersonInfo(Person &aPerson)
{
aPerson.printInfo();
};

Student s;
printPersonInfo(s); // prints out Name, Addr, Phone

If you want to use the overridden version of the function, you will have to declare the function in base class as a virtual function. By defining a virtual function, we tell the compiler to call the overridden version no matter what type that instance may be cast to. However, to achieve this effect, we should also pass in an reference or pointer of instance of our derived class. Only in this way can the compiler knows what type our object was declared as. If we just pass by value (a copy of that instance), it will create a copy of our instance with whatever type specified in the function. More specifically, it calls the copy constructor of the specified class. It has no knowledge of what the original type of the argument was.

1
2
3
4
5
6
7
8
9
class Person
{
virtual void printInfo();
}

void printPersonInfo(Person &aPerson) // use overriden version
void printPersonInfo(Person *aPerson) // use overriden version
void printPersonInfo(Person aPerson) // use function in Person;
//in fact in the last function, aPerson only has the "Person" part and doesn't contain any information specific to the derived class

Lec14 Polymorphism

We can dynamically allocate an instance of the derived class and store it in a base class pointer variable. Since Instructor is derived from Person, this is legal.

1
2
Person *aPerson = new Student(); // a pointer of base class(Person) pointing to its derived class(Student)
aPerson->printInfo(); // calls the overridden method in derived class

Abstract Class

We can make a function to be pure virtual (abstract) by adding a = 0 after its declaration. Any new class derived from this class must implement pure virtual methods if the class is going to work. A class with pure virtual functions is an abstract class.

Virtual Destructors

If you have an abstract class, you would need to have an abstract/virtual destructor. That is because when a derived class’s destructor is called, it will (implicitly) call destructors in all base classes it inherits from as well.

1
2
3
4
5
// Person.h
virtual ~Person() {cout<<"base class destructor called"<<endl;}

// Student.h
~Students() {cout<<"derived class Studenet destructor called"<<endl;}

Lec15 Stream

Simple Stream I/O

  • put/get: For any stream, the simplest I/O routines let you input or output one character at a time.
  • End of File eof: a special character (usually has value -1) that signals you’ve reached an end of file state. When we reach eof, we cannot read any further from the file. (Ctrl+Z on Windows, Ctrl+D on other OS)
  • getline: pass in a whole line of characters ( read in until encountering with a \n)

When you type in “This” while running the following code without hitting Enter, it will not print anything, because all characters you typed in have not been sent into the buffer yet. After you hit Enter, “This” will be echoed back. So everything got sent into the buffer, we get one out of it each time, and put it to the outstream, repeat the process until we encounter an eof (Ctrl+Z).

1
2
3
4
while (!cin.eof()) {
char c = cin.get();
cout.put(c);
}

Error Handling

Once an cin attempt failed, an error flag is set and future attempts to get input will fail. Failure happens when type entered doesn’t match the type of the variable you are assigning value to.

  • cin.fail(): returns true if the last cin assignment failed.
  • cin.clear(): repairs the stream by clearing the error flag in cin.
  • cin.ignore(n, c): ignores the following n characters or one c character. Therefore, cin.ignore(100,'\n') ignore all input until you’ve already ignored 100 of them or ignore 1 ‘\n’ character.
1
2
3
4
5
6
cin >> id;
while (cin.fail() || id<0 || id>99) {
cin.clear(); cin.ignore(99, '\n');
cout << "invalid number, try again > ";
cin >> id;
}

Int Stream Manipulator

  • dec: decimal, base 10
  • oct: octal, base 8
  • hex: hexadecimal, base 16
  • setbase(n): set to n base

These stream manipulators are “sticky”. They will remain the format of your output (even though you start another sentence of cout), until you set another stream manipulator.

1
2
3
cout << oct << 8; //10
cout << 9; // 11
cout << setbase(10) << 16; // 16

Float Stream Manipulator

  • fixed: print out float number in decimal/fixed point notation
  • scientific: print out float number in scientific notation
  • setprecision(n): always print out 3 digits after the decimal point

They are all “sticky”. You’ll have to manually set it back to previous state.

1
2
3
int curPrecision = cout.precision();  // current setting
cout << setprecision(2) << 3.12545 << endl; // 3.13
cout.precision(curPrecision); // Restore original setting

Fixed Width

  • left: align to left, the output is padded to the field width appending fill characters at the end
  • right: align to right, the output is padded to the field width by inserting fill characters at the beginning

These two stream manipulators are sticky.

We also use setw(n) to make sure at least n characters are printed. If the string to print has fewer than n characters, fill with space. If it has more than n characters, print everything. setw(n) is not sticky. That’s because most output methods automatically calls setw(0) each time you call them. setw(n) is in the library #include <iomanip> .

1
2
3
4
5
6
7
8
9
#include <iomanip>
int n = -77, m = 13579;
cout << setw(6) << left << n << endl;
cout << setw(6) << right << n << endl;
cout << setw(2) << m << endl;

//-77
// -77
//13579

Custom Manipulator

Manipulators are just globally defined functions that take an ostream reference and return an ostream reference. Following are some examples:

1
2
3
4
5
6
7
8
ostream& beep(ostream &output)
{ return output << “\a”;} // displaying \a causes beep

ostream &aReallyLongTokenForNewline(ostream &output)
{ return output << “\n”;}

cout << “This will cause a beep: “ << aReallyLongTokenForNewLine;
cout << beep;

Lec16 Functional Programming

auto keyword

The auto keyword is used to declare a variable whose type is determined by the value it is initialized to. It must be initialized at the moment it is declared (or it will cause a static time compiler error).

1
2
auto f = 3.14   // f is made a double
auto k; // NO INITIALIZER – This would be a compiler error

Function Pointers

When we define a function pointer, we need to define its return type and what type of arguments it takes in: return_type (* function_name) (argument_type1, argument_type2, ...). Note: All these parameters are required. We can declare a function pointer with no allocation. We can assign it to any function that matches the argument type and return type as we do to most pointers.

We can also use the C++11 style function in STL functional: std::function< return_type (argument_type1, argument_type2, ...) > function_name, but this is much heavier.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int SimpleAdd(int arg1,int arg2)
{ return arg1 + arg2; }

int main(int argc, char *argv[])
{
int (*f)(int start,int stop); // define a function pointer that takes in two ints and returns an int
f = SimpleAdd; // f now points at the function “SimpleAdd”
int x = (*f)(3,4); // dereference f, get the function it points to and applies it to 3,4
int y = f(3,4); // A syntactic sugar provided. Compiler will do the dereference
cout << " x is: " << x << ", y is: << y << endl;

function<int(int,int)> g; g = simpleAdd;
cout << g(3,4) << endl; // also gives 7
// *g(3,4) doesn't work because g here is an std::function, not a pointer to a C-style function
}

Function as Parameter

When we want to pass a function as a parameter of another function, we can pass it as a C-style pointer or C++11 std::funciton . We can also use a template and let the compiler to figure it out.

1
2
3
4
5
6
7
8
9
10
void OldCallMe(int (*f)(int), int x) {...}
void NewCallMe(std::function<int(int)> f, int x) {...}

template<typename T>
void CallMe(T fn, int x)
{
// a syntax error will result if the fn passed in of type T doesn't support the following line
int newValue = fn(x);
cout << "CallMe-newValue is: " << newValue << endl;
}

Lambda Expressions

A lambda expression evaluates to a function pointer. It takes the following format: [vars](args) -> returntype { // body of function };, where return type and arrow can be omitted.

1
2
3
4
5
6
7
8
9
10
11
12
13
// Declare a lambda with the auto keyword (we don't know what type of a function that is)
// func is a function that takes no variables or arguments and simply prints out Hello World
auto func = []() { cout << "Hello world!" << endl; };

// Declare a lambda with a function pointer:
// func2 is a pointer to a function that takes in a string as parameter
// More specifically, that function takes in a string and prints it out
void (*func2)(string) = [](string s) {
cout << “Hello “ << s << endl; };

// Use function template (C++11) to store lambda
std::function<void(string)> func3 = [](string s) {
cout << “Hello “ << s << endl; };

Capture Local Variables

We can use lambda expressions to capture local variables. This will be an important way to still be able to use variables in a function that no longer exists when the lambda finally gets executed. If you want to capture local variables, always use C++11 std::function when defining either the lambda expression or the function you want to take this lambda expression.

1
2
3
4
5
6
7
8
void CallMe(std::function<int()> fn) {...};

// template also works because it will automatically identify fn as an std::function
template<typename T>
void CallMe(T fn) {...};

int myX = 300;
CallMe([myX]()->int{ return myX*2; });

You also have the option of capturing local variables by reference. That means if the lambda expression modifies them, the modifications persist back into the “hosting” function where these variables were defined. (Just like any pass by reference function call). Pass by reference or pass a pointer will do.

1
2
3
4
5
int myX = 300; 
int *myY = new int; *myY = 3;
CallMe([&myX](){ myX *= 2; });
CallMe([myY](){ *myY *= 2; });
cout << "myX is " << myX << endl << "myY is " << *myY << endl; // 600

Lec17 Files I/O

ofstream

We use ofstream to write to file. A constructor of ofstream takes in two arguments

  1. name of the file to open
  2. specifies which mode to use:
    • ios::out open file for writing, overwrite existing file
    • ios::app open file for writing, append to existing file

We don’t have to close the stream after writing, ofstream has a destructor that is automatically called at the end of the program. That being said, we can still call out.close() manually.

We use out.is_open() to make sure the file is indeed successfully opened and ready to be written in. Directly evaluating the stream variable out itself as a boolean also does the check.

1
2
3
4
5
6
#include<fstream>

ofstream out(“myFile”,ios::out); // create an ofstream, pass in name of the file and ios::out to indicate you want to use it for output
if (out.is_open()) // make sure we successfully opend the file
out << “Hello world!” << endl;
out.close();

ifstream

We use ifstream to read from a file.

1
2
3
4
ifstream in(“myFile”,ios::in);
string str = "Hello";
if (in.is_open())
in >> str;

Sequential Files

Suppose we have a csv file that uses comma as the delimiter and space as a record separator. If we want to change only a specific record, how are we supposed to move around in that file?

  • tellg(): returns the offset from the beginning of the file where the next read operation will get data from.
  • tellp(): returns the offset from the beginning of the file where the next write operation will put data to.
  • seekg(n): sets the “get” offset to the nth character in the file
  • seekp(n): sets the “put” offset to the nth character in the file

Reading and Writing at the Same Time

When declare an fstream variable, we can specify using multiple “modes” at the same time by putting the or operator | between different modes. We can then use whatever function those modes give

1
2
3
fstream file(“ages.dat”, ios::in | ios::out) //reads and write to "ages.dat" at the same time
string str; file >> str; // read works
file << "That's good"; // write also works

Lec18 Standard Template Library

Iterator

begin() points to the first element in the object. end() points to one after the last element. Common operators like + < > are all overloaded for iterators.

1
2
3
for (vector<string>::iterator p = stringVector.begin();
p < stringVector.end(); ++p)
cout << “Next Vector Element is: “ << *p << endl;

Vector

1
2
3
vector<string>::iterator q = stringVector.begin();
stringVector.erase(q+5); // erase the 6th element
stringVector.erase(q,q+5); // erase [q, q+5), so erase 1st to the 5th element

Map

Map is based on valuetype, which has type <key, value>. All operations come from this pair. We can use typedef to name some very complicated data type that is frequently used.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef map<int,string>::value_type IDRecord; // IDRecord is in fact of "pair<int,string>" type
typedef map<int,string>::iterator IDRecordIterator;
int main()
{
map<int,string> ids;
IDRecord rec1(12345,"Ron DiNapoli");
IDRecord rec2(34564,"Darpan Kaplan");
ids.insert(rec1); // alway insert a key-value pair
ids.insert(rec2);

cout << "ID 34564 belongs to: " << ids[34564] << endl; // use array-like way to access map

IDRecordIterator p = ids.find(12345); // find returns the address of that entry with a matched key , returns map::end() if key doesn't exist
IDRecordIterator q = ++p;
cout << "Next entry of ID 12345 is: " << (*q).second << endl;
}

Lec19 Exceptions

Basic Syntax

Exceptions can be of any type. We can do throw 3.14, throw "Unexpected", or throw some object.

1
2
3
4
5
6
7
8
9
10
11
enum MathErr { noErr, divByZero, genericOverflow };
throw divByZero;

try {
...
} catch(MathErr e) {
...
}

// Or
catch(...) {} // catches all kinds of Exceptions

Exception Object and Inheritance

As said in previous section, we can throw an object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class MyIndexError {
MyIndexError(int i,char *msg):badIndex(i),theMsg(msg){}
int getBadIndex() { return badIndex; }
string getMessage() { return theMsg; }
private:
int badIndex;
string theMsg;
};
char &MyString::operator[](int index)
{
if ((index < 0) || (index >= stringLength))
throw MyIndexError(index,”Index out of bounds”);
return storagePtr[index];
}


class BaseException
{
public:
BaseException(string msg,int err=0):message(msg),
errorCode(err){}
virtual string getMessage()
{ return “BASE EXCEPTION: “ + message; }
int getErrorCode() { return errorCode; }
protected:
string message;
int errorCode;
};

Lec20 Custom Templates

Basic Syntax

1
2
3
4
5
6
7
8
9
10
11
template <class placeholder>	// declare placeholders
class SimpleClass // regular class definition
{
public:

};

// define a function outside of template class
void SimpleClass<placeholder>::FunctionName() {...}
// define constructor/destructor outside of template class
void SimpleClass<placeholder>::SimpleClass()

“Definition” template class should also be in the same .h file. Because the compiler needs to generate a separate set of member functions for each type used to create an instance of this class at compile time. That means that these definitions are needed at compile time and not at link time, so .cpp won’t enable us to actually call those functions.

Non-Type Parameters

We specified a data type calls placeholder in the template class. We can also specify a constant expression when we declare a template class. This will have the same effect as setting a const value specific for that instance, except previously we couldn’t assign values to const variable.

1
2
template <class storageType,int size> class MyArray {...}
template <class storageType=int,int size=5> class MyArray {...} // give a default value

Lec22 STL Algorithms

#include<algorithm> for all functions below.

  • fill(iterator begin, iterator end, T value): take two iterators/pointers and one value. Fill every position in between with that value.:

    1
    2
    char *ptr = new char[10]	  // An array of 10 chars
    fill(ptr,ptr+9,’A’);
  • generate(iterator begin, iterator end, function g): assigns every position in between the two iterators/pointers according to the generating function g.

    1
    2
    3
    4
    5
    6
    7
    int nextVal() {  static int number = 0;
    return number++;}

    int main(int argc,char *argv[]) {
    std::vector<int> intVector(10); // A vector of integers
    std::generate(intVector.begin(),intVector.end(),nextVal);
    }
  • fill_n(begin,count,value): fill from begin to begin+count with specified value

  • generate_n(begin,count,function): fill from begin to begin+count with generated value

  • remove(begin,end,value): remove all elements == value in range from begin to end

  • replace(begin,end,value,replaceWith): replace all elements == value in range from begin to end WITH replaceWith

Lec23 Smart Pointers

Shared Pointer

You can declare multiple pointers pointing to the same thing using shared pointer and they will all be released when you release one of them, so it’s safer than the classic pointer, where the pointer will hang over there.

You can call the use_count() method to get how many shared pointers are out there pointing to this same thing.

1
2
3
4
5
6
int main(int argc,char *argv[]) {
shared_ptr<Point> pointPtr(new Point(1,2));
shared_ptr<Point> pointPtr2(pointPtr);
cout << “x coordinate is: “ << (*pointPtr).x << endl;
cout << “reference count is: “ << pointPtr.use_count();
}

Unique Pointer

There is only this one pointer pointing to that thing. No other shared pointers can be created pointing to the same thing. For the same reason, use_count() is not available either.

Lec24 Namespaces and C/C++ Differences

Namespace

Declaring Namespace

We define a namespace by putting it inside a namespace declaration and its corresponding scope, just like what we do to a class. What’s different is that a single namespace may span multiple files. Therefore, we can declare/define a single namespace in multiple files.

1
2
3
4
5
6
namespace CornellCS2024	{ // Defines a namespace named CornellCS2024 
class MyString {
public:
...};
class AnotherClass {...}
}

Using Namespace

  1. We can use anything declared in the namespace by quoting the fully qualified name

    1
    CornellCS2024::MyString aString;
  2. We can designate a specific class to use in the rest of the file.

    1
    2
    using CornellCS2024::MyString;
    MyString aString;
  3. We can simply state that we want to use everything declared in this namespace. That’s what we usually do to std in small file.

    1
    2
    using namespace CornellCS2024;
    MyString aString;

C/C++ Difference

  • only supports /* block comments */
  • variable declarations had to appear the beginning of a scope before any other statements were encountered
  • only has struct, no class
  • no overloads, Namespaces, Declaring a counter variable in a loop, String type, Exceptions, Templates
  • does not use new/delete for dynamic memory allocation/deallocation. Instead, C uses
    • malloc() allocates memory. It needs to be given the exact number of bytes you want to dynamically allocate
    • calloc() is the same as malloc() but initializes all allocated memory to 0
    • realloc() “grow” a dynamic allocation: basically allocates new space and copies all original memory to new space.
    • free() releases allocated memory

Lec99 From Assignments

  • new keyword returns a pointer to an object. You don’t have to use new when creating a new object. Reference

    1
    2
    3
    test t = test("rrr", 8);
    test t("rrr", 8);
    test *t = new test("rrr", 8);
CATALOG
  1. 1. Lec01 Introduction
    1. 1.1. Explaining our First Program
    2. 1.2. Compiling C++
  2. 2. Lec02 Input/Output and Operators
    1. 2.1. Input and Output
    2. 2.2. Using
  3. 3. Lec03 Introduction to Classes
    1. 3.1. Struct
    2. 3.2. Classes
      1. 3.2.1. Public vs. Private
      2. 3.2.2. Declaration and Definition of Member Functions
      3. 3.2.3. Constructors
  4. 4. Lec5 Functions I
    1. 4.1. Enum
    2. 4.2. Function Declaration and Definition Revisited
  5. 5. Lec6 Function II
    1. 5.1. Inline Functions
    2. 5.2. Pass By Reference
    3. 5.3. Default Argument
    4. 5.4. Unary Scope Operator
  6. 6. Lec7 Function III
    1. 6.1. Function Templates
  7. 7. Lec8 Arrays and Vectors
    1. 7.1. Arrays
    2. 7.2. Vectors
  8. 8. Lec9 Pointers
    1. 8.1. Dynamic Allocation
    2. 8.2. Pointers to Already Existing Values
    3. 8.3. Common Confusion with *
    4. 8.4. Pointer Chaos
    5. 8.5. Pointers to User-Defined Types
    6. 8.6. Passing Pointers as Arguments
    7. 8.7. Const with Pointers
  9. 9. Lec10 Classic Arrays and Pointer Arithmetic
    1. 9.1. Classic Array
    2. 9.2. Pointer Arithmetic
    3. 9.3. Dynamic Allocation of Arrays
    4. 9.4. Passing Arrays as Parameters
    5. 9.5. Memory Allocation with malloc and sizeof
  10. 10. Lec11 Classes – A Deeper Look
    1. 10.1. Implicit Inline
    2. 10.2. Multiple Constructors
    3. 10.3. Destructor
    4. 10.4. Passing and Returning Reference
      1. 10.4.1. Passing Reference
      2. 10.4.2. Returning Reference
    5. 10.5. const in class
    6. 10.6. static in class
    7. 10.7. this in class
  11. 11. Lec12 Operator Overloads
    1. 11.1. Unary Operator Overloads
    2. 11.2. Binary Operator Overloads
    3. 11.3. Copy Constructors
    4. 11.4. Overloading Assignment =
    5. 11.5. Overloading Stream Direction << and >>
  12. 12. Lec13 Inheritance
    1. 12.1. Basic Syntax
    2. 12.2. Override
    3. 12.3. Virtual Functions
  13. 13. Lec14 Polymorphism
    1. 13.1. Abstract Class
    2. 13.2. Virtual Destructors
  14. 14. Lec15 Stream
    1. 14.0.1. Simple Stream I/O
  15. 14.1. Error Handling
  16. 14.2. Int Stream Manipulator
  17. 14.3. Float Stream Manipulator
  18. 14.4. Fixed Width
  19. 14.5. Custom Manipulator
  • 15. Lec16 Functional Programming
    1. 15.1. auto keyword
    2. 15.2. Function Pointers
    3. 15.3. Function as Parameter
    4. 15.4. Lambda Expressions
    5. 15.5. Capture Local Variables
  • 16. Lec17 Files I/O
    1. 16.1. ofstream
    2. 16.2. ifstream
    3. 16.3. Sequential Files
    4. 16.4. Reading and Writing at the Same Time
  • 17. Lec18 Standard Template Library
    1. 17.1. Iterator
    2. 17.2. Vector
    3. 17.3. Map
  • 18. Lec19 Exceptions
    1. 18.1. Basic Syntax
    2. 18.2. Exception Object and Inheritance
  • 19. Lec20 Custom Templates
    1. 19.1. Basic Syntax
    2. 19.2. Non-Type Parameters
  • 20. Lec22 STL Algorithms
  • 21. Lec23 Smart Pointers
    1. 21.1. Shared Pointer
    2. 21.2. Unique Pointer
  • 22. Lec24 Namespaces and C/C++ Differences
    1. 22.1. Namespace
      1. 22.1.1. Declaring Namespace
      2. 22.1.2. Using Namespace
    2. 22.2. C/C++ Difference
  • 23. Lec99 From Assignments