Skip to article frontmatterSkip to article content

CS2024 C++ Programming

Cornell University

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

Compiling C++

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

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.

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 )

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.

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.

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

/* <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.

// 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.

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

// 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

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?

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.

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)

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).

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

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.

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.

#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

#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

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:

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.

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.

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 *

Pointer Chaos

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:

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

Passing Pointers as Arguments

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:

// 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

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

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.

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.

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.

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

Memory Allocation with malloc and sizeof

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

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

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.

// 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.

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.

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

static in class

// "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

Lec12 Operator Overloads

Unary Operator Overloads

we just have to use the operator keyword.

// "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.

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.

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!

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.

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 >>

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

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.

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.

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.

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.

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.

// 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

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).

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 >> id;
while (cin.fail() || id<0 || id>99) {
    cin.clear(); cin.ignore(99, '\n');
    cout << "invalid number, try again > ";
    cin >> id;
}

Int Stream Manipulator

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.

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

Float Stream Manipulator

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

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

Fixed Width

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> .

#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:

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).

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.

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.

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.

// 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.

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.

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.

#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.

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?

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

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.

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

Vector

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.

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.

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.

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

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.

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.

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.

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.

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

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

    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.

    using namespace CornellCS2024;
    MyString aString;

C/C++ Difference

Lec99 From Assignments