├── lecture17 ├── utils │ └── utils.hpp ├── CMakeLists.txt ├── Makefile ├── src │ ├── main.cpp │ ├── IntVector.cpp │ └── StudentID.cpp ├── include │ ├── IntVector.h │ └── StudentID.h └── README.md ├── lecture05 ├── input.txt ├── hello.txt ├── main.cpp ├── verifyPI.cpp ├── README.md ├── cinGetlineBug.cpp ├── cinGetline.cpp ├── inputFileStreams.cpp ├── outputFileStreams.cpp ├── stringstreamExampleBug.cpp └── stringstreamExample.cpp ├── .gitignore ├── lecture03 ├── meme.jpeg ├── initialization.cpp ├── const.cpp ├── README.md ├── references.cpp └── Reactor.cpp ├── lecture12 ├── soundex.h ├── README.md ├── soundex.cpp ├── soundex-ranges.cpp └── main.cpp ├── lecture13 ├── README.md ├── main.cpp ├── include │ └── StanfordID.h └── src │ └── StanfordID.cpp ├── lecture11 ├── README.md ├── utils.hpp └── main.cpp ├── lecture08 ├── README.md ├── main.cpp ├── IntVector.h ├── StudentID.h ├── IntVector.cpp └── StudentID.cpp ├── lecture15 ├── CMakeLists.txt ├── README.md └── main.cpp ├── lecture02 ├── README.md └── main.cpp ├── lecture10 ├── README.md ├── main.cpp ├── Vector.h └── Vector.cpp ├── README.md ├── unit_testing ├── bank_account.hpp ├── bank_account.cpp ├── CMakeLists.txt ├── README.md └── main.cpp ├── lecture16 └── main.cpp └── lecture09 ├── part1.cpp ├── part2.cpp └── part3.cpp /lecture17/utils/utils.hpp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lecture05/input.txt: -------------------------------------------------------------------------------- 1 | line1 2 | line2 -------------------------------------------------------------------------------- /lecture05/hello.txt: -------------------------------------------------------------------------------- 1 | Hello CS106L ! 2 | this will though! It’s open again -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | *.out 3 | main 4 | main.exe 5 | */.vscode 6 | .DS_Store -------------------------------------------------------------------------------- /lecture03/meme.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cs106l/cs106l-lecture-code/HEAD/lecture03/meme.jpeg -------------------------------------------------------------------------------- /lecture12/soundex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | std::string soundex(const std::string& s); 6 | std::string soundexRanges(const std::string& s); -------------------------------------------------------------------------------- /lecture13/README.md: -------------------------------------------------------------------------------- 1 | # Operator Overloading Lecture Code 2 | 3 | ## Compiling Instructions 4 | 5 | ```sh 6 | g++ -std=c++20 main.cpp src/StanfordID.cpp -o main 7 | ``` -------------------------------------------------------------------------------- /lecture05/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | std::cout << "Read the README and run the examples from lecture!" << std::endl; 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /lecture11/README.md: -------------------------------------------------------------------------------- 1 | # Lecture 11: Template Functions 2 | 3 | To compile this you can use the following command: 4 | 5 | ```sh 6 | g++ -std=c++20 main.cpp -o main 7 | ``` 8 | -------------------------------------------------------------------------------- /lecture05/verifyPI.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | double pi; 5 | std::cout << "Enter pi: " << '\n'; 6 | std::cin >> pi; 7 | std::cout << pi/2 << '\n'; 8 | return 0; 9 | } -------------------------------------------------------------------------------- /lecture08/README.md: -------------------------------------------------------------------------------- 1 | # Lecture 7: Classes 2 | 3 | You can take a look at the driver code in `main.cpp`. 4 | 5 | To compile this you can use the following command: 6 | 7 | ```sh 8 | g++ -std=c++20 main.cpp StudentID.cpp IntVector.cpp -o main 9 | ``` -------------------------------------------------------------------------------- /lecture17/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(cs106l_classes) 4 | 5 | set(CMAKE_CXX_STANDARD 20) 6 | 7 | include_directories(include utils) 8 | 9 | file(GLOB SRC_FILES "src/*.cpp") 10 | 11 | add_executable(main ${SRC_FILES}) 12 | -------------------------------------------------------------------------------- /lecture15/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(cs106l_movesemantics) 4 | 5 | set(CMAKE_CXX_STANDARD 20) 6 | 7 | if(ENABLE_MOVE_SEMANTICS) 8 | add_definitions(-DENABLE_MOVE_SEMANTICS) 9 | endif() 10 | 11 | add_executable(main main.cpp) -------------------------------------------------------------------------------- /lecture03/initialization.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | // Uniform Initialization, will fail 5 | int numOne{12.0}; 6 | float numTwo{12.0}; 7 | 8 | std::cout << "numOne is: " << numOne << std::endl; 9 | std::cout << "numTwo is: " << numTwo << std::endl; 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /lecture02/README.md: -------------------------------------------------------------------------------- 1 | # Lecture 2: Types and Structs 2 | 3 | To run this code, make sure you have followed [the setup instructions in A0](https://github.com/cs106l/cs106l-assignments/tree/main/assign0). Then, you can compile and run using: 4 | 5 | ```sh 6 | g++ main.cpp -o main 7 | ``` 8 | 9 | and 10 | 11 | ```sh 12 | ./main 13 | ``` -------------------------------------------------------------------------------- /lecture17/Makefile: -------------------------------------------------------------------------------- 1 | # Compiler 2 | CXX = g++ 3 | 4 | # Compiler flags 5 | CXXFLAGS = -std=c++20 6 | 7 | # Source files and target 8 | SRCS = $(wildcard src/*.cpp) 9 | TARGET = main 10 | 11 | # Default target 12 | all: 13 | $(CXX) $(CXXFLAGS) $(SRCS) -Iinclude -o $(TARGET) 14 | 15 | # Clean up 16 | clean: 17 | rm -f $(TARGET) 18 | -------------------------------------------------------------------------------- /lecture10/README.md: -------------------------------------------------------------------------------- 1 | # Lecture 10: Template Classes and Const Correctness 2 | 3 | Some example code that instantiates different `Vector` has been included in `main.cpp`. The code written in lecture can be found in `Vector.h` and `Vector.cpp`. 4 | 5 | To compile this you can use the following command: 6 | 7 | ```sh 8 | g++ -std=c++20 main.cpp -o main 9 | ``` -------------------------------------------------------------------------------- /lecture05/README.md: -------------------------------------------------------------------------------- 1 | # Lecture 4: Streams 2 | 3 | To compile this code, run: 4 | 5 | ```sh 6 | g++ -std=c++23 .cpp -o 7 | ``` 8 | 9 | A lot of this code is expecting input from the terminal, so read throught he code and figure out what you're supposed to type in and where. If the terminal is "hanging" or not doing anything it's likely because it's expecting you to do something. -------------------------------------------------------------------------------- /lecture15/README.md: -------------------------------------------------------------------------------- 1 | # Lecture 15 2 | 3 | Move Semantics 4 | 5 | This project showcases the difference in performance between code that uses move semantics and code which does not. By default, move semantics operations for the `Photo` class are disabled. To enable them and see the difference in performance, run `cmake` like so: 6 | 7 | ```sh 8 | cmake . -DENABLE_MOVE_SEMANTICS=1 9 | ``` 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /lecture05/cinGetlineBug.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | void cinGetlineBug() { 5 | double pi; 6 | double tao; 7 | std::string name; 8 | std::cin >> pi; 9 | std::getline(std::cin, name); 10 | std::cin >> tao; 11 | std::cout << "my name is : " << name << " tao is : " << tao 12 | << " pi is : " << pi << '\n'; 13 | } 14 | 15 | int main() { 16 | cinGetlineBug(); 17 | return 0; 18 | } -------------------------------------------------------------------------------- /lecture05/cinGetline.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void cinGetline() { 4 | double pi; 5 | double tao; 6 | std::string name; 7 | std::cin >> pi; 8 | std::getline(std::cin, name); 9 | std::getline(std::cin, name); 10 | std::cin >> tao; 11 | std::cout << "my name is : " << name << " tao is : " << tao 12 | << " pi is : " << pi << '\n'; 13 | } 14 | 15 | int main() { 16 | cinGetline(); 17 | return 0; 18 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CS106L Lecture Code 2 | 3 | This repository contains code examples and lecture materials for Stanford CS106L, a course on Standard C++ programming. The code in this repository is meant to supplement the lectures and provide hands-on examples of the concepts discussed in class. 4 | 5 | To compile and run code in this repository, make sure you have [completed A0 to setup your environment](https://github.com/cs106l/cs106l-assignments/tree/main/assign0). -------------------------------------------------------------------------------- /lecture08/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "StudentID.h" 3 | #include "IntVector.h" 4 | 5 | void createStudentID() { 6 | StudentID sid{"Jacob Roberts-Baca", "jtrb", 545}; 7 | std::cout << "Name: " << sid.getName() << std::endl; 8 | std::cout << "Sunet: " << sid.getSunet() << std::endl; 9 | std::cout << "ID Number: " << sid.getIdNumber() << std::endl; 10 | } 11 | 12 | int main() { 13 | createStudentID(); 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /unit_testing/bank_account.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __BANK_ACCOUNT_H__ 2 | #define __BANK_ACCOUNT_H__ 3 | 4 | struct BankAccount { 5 | 6 | // member variables 7 | double balance; 8 | 9 | // constructors 10 | BankAccount(); 11 | explicit BankAccount(const double initial_balance); 12 | 13 | // member functions 14 | void deposit(double amount); 15 | bool withdraw(double amount); 16 | 17 | }; 18 | 19 | #endif // __BANK_ACCOUNT_H__ -------------------------------------------------------------------------------- /lecture17/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "StudentID.h" 3 | #include "IntVector.h" 4 | 5 | void createStudentID() { 6 | StudentID sid{"Jacob Roberts-Baca", "jtrb", 545}; 7 | std::cout << "Name: " << sid.getName() << std::endl; 8 | std::cout << "Sunet: " << sid.getSunet() << std::endl; 9 | std::cout << "ID Number: " << sid.getIdNumber() << std::endl; 10 | } 11 | 12 | int main() { 13 | createStudentID(); 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /lecture05/inputFileStreams.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | std::ifstream ifs("input.txt"); 6 | if (ifs.is_open()) { 7 | std::string line; 8 | std::getline(ifs, line); 9 | std::cout << "Read from the file: " << line << '\n'; 10 | } 11 | if (ifs.is_open()) { 12 | std::string lineTwo; 13 | std::getline(ifs, lineTwo); 14 | std::cout << "Read from the file: " << lineTwo << '\n'; 15 | } 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /lecture08/IntVector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class IntVector { 6 | public: 7 | using iterator = int*; 8 | 9 | IntVector(); 10 | ~IntVector(); 11 | 12 | void push_back(const int& value); 13 | 14 | int& at(size_t index); 15 | int& operator[](size_t index); 16 | 17 | size_t size(); 18 | bool empty(); 19 | 20 | iterator begin(); 21 | iterator end(); 22 | private: 23 | size_t _size; 24 | size_t _capacity; 25 | int* _data; 26 | 27 | void resize(); 28 | }; -------------------------------------------------------------------------------- /lecture05/outputFileStreams.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | /// associating file on construction 5 | std::ofstream ofs("hello.txt"); 6 | if (ofs.is_open()) { 7 | ofs << "Hello CS106L !" << '\n'; 8 | } 9 | ofs.close(); 10 | ofs << "this will not get written"; 11 | 12 | /* try adding a 'mode' argument to the open method, like std::ios:app 13 | * What happens? 14 | */ 15 | ofs.open("hello.txt"); 16 | ofs << "this will though! It’s open again"; 17 | return 0; 18 | } 19 | -------------------------------------------------------------------------------- /lecture17/include/IntVector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class IntVector { 6 | public: 7 | using iterator = int*; 8 | 9 | IntVector(); 10 | ~IntVector(); 11 | 12 | void push_back(const int& value); 13 | 14 | int& at(size_t index); 15 | int& operator[](size_t index); 16 | 17 | size_t size(); 18 | bool empty(); 19 | 20 | iterator begin(); 21 | iterator end(); 22 | private: 23 | size_t _size; 24 | size_t _capacity; 25 | int* _data; 26 | 27 | void resize(); 28 | }; -------------------------------------------------------------------------------- /lecture03/const.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() 5 | { 6 | std::vector vec{ 1, 2, 3 }; /// a normal vector 7 | const std::vector const_vec{ 1, 2, 3 }; /// a const vector 8 | std::vector& ref_vec{ vec }; /// a reference to 'vec' 9 | const std::vector& const_ref{ vec }; /// a const reference 10 | 11 | vec.push_back(3); 12 | const_vec.push_back(3); 13 | ref_vec.push_back(3); 14 | const_ref.push_back(3); 15 | 16 | return 0; 17 | } 18 | 19 | -------------------------------------------------------------------------------- /lecture13/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "include/StanfordID.h" 3 | 4 | int main() { 5 | StanfordID jacob{ "Jacob Roberts-Baca", "jtrb", 12345678 }; 6 | StanfordID fabio{ "Fabio Ibanez", "fabioi", 87654321 }; 7 | if (jacob < fabio) { 8 | std::cout << jacob.getName() << " has a lower ID number than " << fabio.getName() << std::endl; 9 | } else { 10 | std::cout << fabio.getName() << " has a lower ID number than " << jacob.getName() << std::endl; 11 | } 12 | auto result = jacob < fabio; 13 | return 0; 14 | } -------------------------------------------------------------------------------- /lecture08/StudentID.h: -------------------------------------------------------------------------------- 1 | #include 2 | class StudentID { 3 | protected: 4 | std::string name; 5 | std::string sunet; 6 | int idNumber; 7 | public: 8 | StudentID(std::string name, std::string sunet, int idNumber); 9 | // default constructor 10 | StudentID(); 11 | ~StudentID(); 12 | 13 | // Getter functions 14 | std::string getName(); 15 | std::string getSunet(); 16 | int getIdNumber(); 17 | 18 | // Setter functions 19 | void setName(std::string name); 20 | void setSunet(std::string sunet); 21 | void setIdNumber(int idNumber); 22 | }; -------------------------------------------------------------------------------- /lecture17/include/StudentID.h: -------------------------------------------------------------------------------- 1 | #include 2 | class StudentID { 3 | protected: 4 | std::string name; 5 | std::string sunet; 6 | int idNumber; 7 | public: 8 | StudentID(std::string name, std::string sunet, int idNumber); 9 | // default constructor 10 | StudentID(); 11 | ~StudentID(); 12 | 13 | // Getter functions 14 | std::string getName(); 15 | std::string getSunet(); 16 | int getIdNumber(); 17 | 18 | // Setter functions 19 | void setName(std::string name); 20 | void setSunet(std::string sunet); 21 | void setIdNumber(int idNumber); 22 | }; -------------------------------------------------------------------------------- /lecture03/README.md: -------------------------------------------------------------------------------- 1 | # Initialization & References Lecture Code 2 | 3 | ## Overview 4 | In this folder you'll find that there is code for the code that was in the lecture slides for your perusal. 5 | We encourage you to take a look at this and play around with the code, try to break it, etc. This is where 6 | the real learning really happens. 7 | 8 | 9 | ## To Compile 10 | Not to make you fetch-quest but take a look at the lecture for information on how to do this, it should be relatively 11 | simple. We really want you to get comfortable with compiling C++ programs from the terminal/command line. 12 | 13 | ![](meme.jpeg) 14 | -------------------------------------------------------------------------------- /lecture03/references.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define WITH_REF 1 5 | 6 | /* 7 | * This is called a preprocessor macro, in the compilation step 8 | * the preprocessor, will decide which one of the function 9 | * signatures to keep depending on whether or not the variable 10 | * 'WITH_REF' is truthy. 11 | */ 12 | #if WITH_REF 13 | void squareN(int& n) 14 | #else 15 | void squareN(int n) 16 | #endif 17 | { 18 | n = std::pow(n, 2); 19 | } 20 | 21 | 22 | int main() { 23 | int num = 5; 24 | std::cout << "(1) num is: " << num << std::endl; 25 | squareN(5); 26 | std::cout << "(2) num is " << num << std::endl; 27 | return 0; 28 | } 29 | 30 | -------------------------------------------------------------------------------- /lecture12/README.md: -------------------------------------------------------------------------------- 1 | # Lecture 12: Functions and Lambdas 2 | 3 | > [!IMPORTANT] 4 | > Compiling the project requires a compiler that can compile C++26. For reference, GCC 14 and above should work. 5 | 6 | To compile this code, run: 7 | 8 | ```sh 9 | g++ -std=c++26 main.cpp soundex-ranges.cpp soundex.cpp -o main 10 | ``` 11 | 12 | Pass the `-O3` flag to compile the code with all optimizations enabled (it will run much faster if you do this). 13 | 14 | To run the code, use: 15 | 16 | ```sh 17 | ./main [soundex] [soundexRanges] 18 | ``` 19 | 20 | Where `soundex` will run the standard STL algorithm and `soundexRanges` will run the STL ranges/views algorithm. You can specify both to run them side by side! 21 | -------------------------------------------------------------------------------- /unit_testing/bank_account.cpp: -------------------------------------------------------------------------------- 1 | #include "bank_account.hpp" 2 | 3 | // default constructor initializes to balance of 0 4 | BankAccount::BankAccount() 5 | : balance(0) {} 6 | 7 | // custom constructor that initializes balance to initial_balance 8 | BankAccount::BankAccount(const double initial_balance) 9 | : balance(initial_balance) {} 10 | 11 | // deposit amount into the account 12 | void BankAccount::deposit(double amount) { 13 | balance += amount; 14 | } 15 | 16 | // withdraws amount from balance if funds exist. 17 | bool BankAccount::withdraw(double amount) 18 | { 19 | if (amount <= balance) { 20 | balance -= amount; 21 | return true; 22 | } 23 | return false; 24 | } 25 | -------------------------------------------------------------------------------- /lecture13/include/StanfordID.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | class StanfordID { 4 | private: 5 | std::string name; 6 | std::string sunet; 7 | int idNumber; 8 | public: 9 | StanfordID(std::string name, std::string sunet, int idNumber); 10 | // default constructor 11 | StanfordID(); 12 | std::string getName() const; 13 | std::string getSunet() const; 14 | int getIdNumber() const; 15 | #ifdef MEMBER_WISE 16 | bool operator<(const StanfordID& other) const; 17 | #endif // MEMBER_WISE 18 | bool operator==(const StanfordID& other) const; 19 | bool operator!=(const StanfordID& other) const; 20 | friend bool operator<(const StanfordID& lhs, const StanfordID& rhs); 21 | }; -------------------------------------------------------------------------------- /lecture05/stringstreamExampleBug.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void foo() { 6 | /// partial Bjarne Quote 7 | std::string initial_quote = "Bjarne Stroustrup C makes it easy to shoot yourself in the foot"; 8 | 9 | /// create a stringstream 10 | std::stringstream ss(initial_quote); 11 | 12 | /// data destinations 13 | std::string first; 14 | std::string last; 15 | std::string language, extracted_quote; 16 | 17 | ss >> first >> last >> language; 18 | std::cout << first << " " << last << " said this: "<< language << " " << extracted_quote << std::endl; 19 | } 20 | 21 | int main() { 22 | foo(); 23 | return 0; 24 | } 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /lecture05/stringstreamExample.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | 6 | void foo() { 7 | /// partial Bjarne Quote 8 | std::string initial_quote = "Bjarne Stroustrup C makes it easy to shoot yourself in the foot"; 9 | 10 | /// create a stringstream 11 | std::stringstream ss(initial_quote); 12 | 13 | /// data destinations 14 | std::string first; 15 | std::string last; 16 | std::string language, extracted_quote; 17 | ss >> first >> last >> language; 18 | std::getline(ss, extracted_quote); 19 | std::cout << first << " " << last << " said this: \'" << language << " " << extracted_quote + "‘" << std::endl; 20 | } 21 | 22 | 23 | int main() { 24 | foo(); 25 | return 0; 26 | } -------------------------------------------------------------------------------- /lecture17/README.md: -------------------------------------------------------------------------------- 1 | # Lecture 17: RAII & Building Projects 2 | 3 | You can take a look at the driver code in `main.cpp`. 4 | 5 | To compile this you can use the following command: 6 | 7 | ```sh 8 | g++ -std=c++20 main.cpp StudentID.cpp IntVector.cpp -o main 9 | ``` 10 | 11 | Notice that you're using multiple source files to compile the target `main`. 12 | 13 | You can instead do the following: 14 | 15 | ```sh 16 | mkdir build 17 | ``` 18 | 19 | ```sh 20 | cd build 21 | ``` 22 | 23 | 24 | ```sh 25 | cmake .. 26 | ``` 27 | 28 | ```sh 29 | make 30 | ``` 31 | 32 | ```sh 33 | ./main 34 | ``` 35 | 36 | This might seem a bit over-cooked for a project as simple as this (and you're probably right), but this is good measure for 37 | situations where you may have hundreds our thousands of C++ source files. The principles remain the same. 38 | -------------------------------------------------------------------------------- /lecture16/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Credit to Sarah McCarthy '23 for this example 3 | */ 4 | 5 | #include 6 | #include 7 | 8 | std::optional divide(int numerator, int denominator) { 9 | if (denominator != 0) { 10 | return numerator / denominator; 11 | } else { 12 | return std::nullopt; 13 | } 14 | } 15 | 16 | int main() { 17 | int a = 10; 18 | int b = 2; 19 | 20 | std::optional result = divide(a, b); 21 | 22 | if (result) { 23 | std::cout << "Result: " << result.value() << std::endl; 24 | } else { 25 | std::cout << "Division by zero occurred." << std::endl; 26 | } 27 | 28 | result = divide(10, 0); 29 | 30 | if (result) { 31 | std::cout << "Result: " << result.value() << std::endl; 32 | } else { 33 | std::cout << "Division by zero occurred." << std::endl; 34 | } 35 | 36 | return 0; 37 | } -------------------------------------------------------------------------------- /lecture10/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "Vector.h" 5 | 6 | int main() { 7 | /* Call default vector constructor */ 8 | Vector vector; 9 | Vector doubleVector; 10 | Vector sv; 11 | 12 | /* Push 106 onto the end of the vector 5 times */ 13 | for (int i = 0; i < 5; i++) { 14 | vector.push_back(106); 15 | } 16 | 17 | /* Modify vector using both at and subscript operator */ 18 | vector.at(0) = 100; 19 | vector[1] = 200; 20 | vector[2] = 300; 21 | 22 | /* Print contents of vector using for-each loop */ 23 | std::cout << "Size of Vector: " << vector.size() << "\n"; 24 | std::cout << "Vector is Empty: " << std::boolalpha << vector.empty() << "\n"; 25 | std::cout << "Contents of vector: "; 26 | for (const auto& element : vector) { 27 | std::cout << element << " "; 28 | } 29 | std::cout << "\n"; 30 | 31 | return 0; 32 | } -------------------------------------------------------------------------------- /unit_testing/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(google_test_example_project) 3 | 4 | # GoogleTest requires at least C++17 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | include(FetchContent) 9 | FetchContent_Declare( 10 | googletest 11 | URL https://github.com/google/googletest/archive/1b96fa13f549387b7549cc89e1a785cf143a1a50.zip 12 | ) 13 | # For Windows: Prevent overriding the parent project's compiler/linker settings 14 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 15 | FetchContent_MakeAvailable(googletest) 16 | 17 | enable_testing() #to discover tests in test explorer 18 | 19 | file(GLOB SRC_FILES "*.cpp") 20 | add_executable(main ${SRC_FILES}) # add this executable 21 | 22 | target_link_libraries(main PRIVATE GTest::gtest_main) # link google test to this executable 23 | 24 | include(GoogleTest) 25 | gtest_discover_tests(main) # discovers tests by asking the compiled test executable to enumerate its tests 26 | -------------------------------------------------------------------------------- /lecture03/Reactor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | /* 5 | * I don't know whether or not Reactor code is written in C++, 6 | * but I wouldn't be surprised. Oftentimes these systems are controlled 7 | * using industrial PLCs, which may run C++ code. The reason I'm telling 8 | * you this is because oftentimes these narrowing conversions 9 | * seem trivial, but they're actually quite consequential :) 10 | */ 11 | class Reactor { 12 | public: 13 | // this is called a Constructor, more on this later in the course 14 | Reactor(double temperature): temperature(temperature) {} 15 | 16 | void checkCool() { 17 | if (temperature > 100.0) { 18 | std::cout << "Emergency cooling!" << std::endl; 19 | } 20 | else { 21 | std::cout << "Temperature is normal. No emergency cooling required" << std::endl; 22 | } 23 | } 24 | 25 | 26 | private: 27 | double temperature; 28 | }; 29 | 30 | 31 | int main () { 32 | // narrowing conversion, saving 100.8 into an integer, see type on line 32 33 | int criticalTemperature(100.8); 34 | Reactor reactor(criticalTemperature); 35 | reactor.checkCool(); 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /lecture08/IntVector.cpp: -------------------------------------------------------------------------------- 1 | #include "IntVector.h" 2 | #include 3 | 4 | IntVector::IntVector() 5 | { 6 | _size = 0; 7 | _capacity = 4; 8 | _data = new int[_capacity]; 9 | } 10 | 11 | IntVector::~IntVector() 12 | { 13 | delete[] _data; 14 | } 15 | 16 | void IntVector::resize() 17 | { 18 | _capacity *= 2; 19 | int* newVec = new int[_capacity]; 20 | for (int i = 0; i < this->_size; i++) { 21 | newVec[i] = _data[i]; 22 | } 23 | 24 | delete[] _data; 25 | _data = newVec; 26 | } 27 | 28 | void IntVector::push_back(const int& value) 29 | { 30 | if (_size == _capacity) { 31 | resize(); 32 | } 33 | _data[_size] = value; 34 | _size++; 35 | } 36 | 37 | int& IntVector::at(size_t index) 38 | { 39 | if (index < 0 || index >= _size) { 40 | // Call std::out_of_range error 41 | } 42 | else { 43 | return _data[index]; 44 | } 45 | } 46 | 47 | int& IntVector::operator[](size_t index) 48 | { 49 | return _data[index]; 50 | } 51 | 52 | size_t IntVector::size() 53 | { 54 | return _size; 55 | } 56 | 57 | bool IntVector::empty() 58 | { 59 | return _size == 0; 60 | } 61 | 62 | IntVector::iterator IntVector::begin() 63 | { 64 | return _data; 65 | } 66 | 67 | IntVector::iterator IntVector::end() 68 | { 69 | return _data + _size; 70 | } -------------------------------------------------------------------------------- /lecture17/src/IntVector.cpp: -------------------------------------------------------------------------------- 1 | #include "IntVector.h" 2 | #include 3 | 4 | IntVector::IntVector() 5 | { 6 | _size = 0; 7 | _capacity = 4; 8 | _data = new int[_capacity]; 9 | } 10 | 11 | IntVector::~IntVector() 12 | { 13 | delete[] _data; 14 | } 15 | 16 | void IntVector::resize() 17 | { 18 | _capacity *= 2; 19 | int* newVec = new int[_capacity]; 20 | for (int i = 0; i < this->_size; i++) { 21 | newVec[i] = _data[i]; 22 | } 23 | 24 | delete[] _data; 25 | _data = newVec; 26 | } 27 | 28 | void IntVector::push_back(const int& value) 29 | { 30 | if (_size == _capacity) { 31 | resize(); 32 | } 33 | _data[_size] = value; 34 | _size++; 35 | } 36 | 37 | int& IntVector::at(size_t index) 38 | { 39 | if (index < 0 || index >= _size) { 40 | // Call std::out_of_range error 41 | throw std::out_of_range("Out of range error"); 42 | } 43 | else { 44 | return _data[index]; 45 | } 46 | } 47 | 48 | int& IntVector::operator[](size_t index) 49 | { 50 | return _data[index]; 51 | } 52 | 53 | size_t IntVector::size() 54 | { 55 | return _size; 56 | } 57 | 58 | bool IntVector::empty() 59 | { 60 | return _size == 0; 61 | } 62 | 63 | IntVector::iterator IntVector::begin() 64 | { 65 | return _data; 66 | } 67 | 68 | IntVector::iterator IntVector::end() 69 | { 70 | return _data + _size; 71 | } -------------------------------------------------------------------------------- /lecture12/soundex.cpp: -------------------------------------------------------------------------------- 1 | #include "soundex.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | static char soundexEncode(char c) 10 | { 11 | static const std::map encoding = { 12 | {'A', '0'}, {'E', '0'}, {'I', '0'}, {'O', '0'}, {'U', '0'}, {'H', '0'}, {'W', '0'}, {'Y', '0'}, 13 | {'B', '1'}, {'F', '1'}, {'P', '1'}, {'V', '1'}, 14 | {'C', '2'}, {'G', '2'}, {'J', '2'}, {'K', '2'}, {'Q', '2'}, {'S', '2'}, {'X', '2'}, {'Z', '2'}, 15 | {'D', '3'}, {'T', '3'}, 16 | {'L', '4'}, 17 | {'M', '5'}, {'N', '5'}, 18 | {'R', '6'} 19 | }; 20 | return encoding.at(std::toupper(c)); 21 | } 22 | 23 | static bool notZero(char c) 24 | { 25 | return c != '0'; 26 | } 27 | 28 | std::string soundex(const std::string& s) 29 | { 30 | std::string letters; 31 | std::copy_if(s.begin(), s.end(), std::back_inserter(letters), ::isalpha); 32 | 33 | char first_letter = letters[0]; 34 | 35 | std::transform(letters.begin(), letters.end(), letters.begin(), soundexEncode); 36 | 37 | std::string unique; 38 | std::unique_copy(letters.begin(), letters.end(), std::back_inserter(unique)); 39 | 40 | unique[0] = std::toupper(first_letter); 41 | 42 | std::string no_zeros; 43 | std::copy_if(unique.begin(), unique.end(), std::back_inserter(no_zeros), notZero); 44 | 45 | no_zeros += "0000"; 46 | return no_zeros.substr(0, 4); 47 | 48 | return s; 49 | } -------------------------------------------------------------------------------- /lecture02/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | using Zeros = std::pair; 7 | using Solution = std::pair; 8 | 9 | /** 10 | * Solves the equation ax^2 + bx + c = 0 11 | * @param a The coefficient of x^2 12 | * @param b The coefficient of x 13 | * @param c The constant term 14 | * @return A pair. The first element (bool) indicates if the equation has a solution. 15 | * The second element is a pair of the roots if they exist. 16 | */ 17 | Solution solveQuadratic(double a, double b, double c) 18 | { 19 | // Your code here... 20 | double discrim = b * b - 4 * a * c; 21 | if (discrim < 0) return { false, { 106, 106 }}; 22 | 23 | double root = sqrt(discrim); 24 | return { true, { (-b - root) / (2 * a), (-b + root) / (2 * a) }}; 25 | } 26 | 27 | int main() { 28 | // Get the values for a, b, and c from the user 29 | double a, b, c; 30 | std::cout << "a: "; std::cin >> a; 31 | std::cout << "b: "; std::cin >> b; 32 | std::cout << "c: "; std::cin >> c; 33 | 34 | // Solve the quadratic equation, using our quadratic function above 35 | auto result = solveQuadratic(a, b, c); 36 | if (result.first) { 37 | auto solutions = result.second; 38 | std::cout << "Solutions: " << solutions.first << ", " << solutions.second << std::endl; 39 | } else { 40 | std::cout << "No solutions" << std::endl; 41 | } 42 | 43 | return 0; 44 | } -------------------------------------------------------------------------------- /lecture09/part1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class HitBox { 5 | public: 6 | double x, y, width, height; 7 | }; 8 | 9 | class Entity { 10 | protected: 11 | double x, y, z; 12 | HitBox hitbox; 13 | public: 14 | void update() {}; 15 | void render() {}; 16 | }; 17 | 18 | class Player : public Entity { 19 | double hitpoints = 100; 20 | public: 21 | void damage(double hp) { 22 | hitpoints -= hp; 23 | } 24 | 25 | void update() { 26 | std::cout << "Updating Player!" << std::endl; 27 | } 28 | 29 | void render() { 30 | std::cout << "Rendering Player!" << std::endl; 31 | } 32 | }; 33 | 34 | class Tree : public Entity { 35 | public: 36 | void update() { 37 | std::cout << "Updating Tree!" << std::endl; 38 | } 39 | 40 | void render() { 41 | std::cout << "Rendering Tree!" << std::endl; 42 | } 43 | }; 44 | 45 | class Projectile : public Entity { 46 | double vx, vy, vz; 47 | public: 48 | void update() { 49 | std::cout << "Updating Projectile!" << std::endl; 50 | } 51 | 52 | void render() { 53 | std::cout << "Rendering Projectile!" << std::endl; 54 | } 55 | }; 56 | 57 | int main() { 58 | std::vector entities { Player(), Tree(), Projectile() }; 59 | while (true) { 60 | std::cout << "Rendering frame..." << std::endl; 61 | for (auto& entity : entities) { 62 | entity.update(); 63 | entity.render(); 64 | } 65 | } 66 | return 0; 67 | } 68 | -------------------------------------------------------------------------------- /lecture12/soundex-ranges.cpp: -------------------------------------------------------------------------------- 1 | #include "soundex.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static char soundexEncode(char c) 9 | { 10 | static const std::map encoding = { 11 | {'A', '0'}, {'E', '0'}, {'I', '0'}, {'O', '0'}, {'U', '0'}, {'H', '0'}, {'W', '0'}, {'Y', '0'}, 12 | {'B', '1'}, {'F', '1'}, {'P', '1'}, {'V', '1'}, 13 | {'C', '2'}, {'G', '2'}, {'J', '2'}, {'K', '2'}, {'Q', '2'}, {'S', '2'}, {'X', '2'}, {'Z', '2'}, 14 | {'D', '3'}, {'T', '3'}, 15 | {'L', '4'}, 16 | {'M', '5'}, {'N', '5'}, 17 | {'R', '6'} 18 | }; 19 | // std::cout << c << " -> " << encoding.at(std::toupper(c)) << std::endl; 20 | return encoding.at(std::toupper(c)); 21 | } 22 | 23 | static bool notZero(char c) 24 | { 25 | return c != '0'; 26 | } 27 | 28 | std::string soundexRanges(const std::string& s) 29 | { 30 | namespace rv = std::ranges::views; 31 | 32 | auto first = *std::ranges::find_if(s, ::isalpha); // Get first letter 33 | auto v = s | rv::filter(::isalpha) | rv::transform(soundexEncode); 34 | 35 | std::string encoded; 36 | std::ranges::unique_copy(v, std::back_inserter(encoded)); 37 | encoded[0] = std::toupper(first); 38 | 39 | return encoded 40 | | rv::filter(notZero) // Get rid of zeros 41 | // | rv::concat("0000") // Ensure length >= 4 (C++26) 42 | | rv::take(4) // Take first four 43 | | std::ranges::to(); // Convert to string 44 | } -------------------------------------------------------------------------------- /lecture09/part2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class HitBox { 5 | public: 6 | double x, y, width, height; 7 | }; 8 | 9 | class Entity { 10 | protected: 11 | double x, y, z; 12 | HitBox hitbox; 13 | public: 14 | void update() {}; 15 | void render() {}; 16 | }; 17 | 18 | class Player : public Entity { 19 | double hitpoints = 100; 20 | public: 21 | void damage(double hp) { 22 | hitpoints -= hp; 23 | } 24 | 25 | void update() { 26 | std::cout << "Updating Player!" << std::endl; 27 | } 28 | 29 | void render() { 30 | std::cout << "Rendering Player!" << std::endl; 31 | } 32 | }; 33 | 34 | class Tree : public Entity { 35 | public: 36 | void update() { 37 | std::cout << "Updating Tree!" << std::endl; 38 | } 39 | 40 | void render() { 41 | std::cout << "Rendering Tree!" << std::endl; 42 | } 43 | }; 44 | 45 | class Projectile : public Entity { 46 | double vx, vy, vz; 47 | public: 48 | void update() { 49 | std::cout << "Updating Projectile!" << std::endl; 50 | } 51 | 52 | void render() { 53 | std::cout << "Rendering Projectile!" << std::endl; 54 | } 55 | }; 56 | 57 | 58 | int main() { 59 | Player player; 60 | Tree tree; 61 | Projectile proj; 62 | 63 | std::vector entities { &player, &tree, &proj }; 64 | while (true) { 65 | std::cout << "Rendering frame..." << std::endl; 66 | for (auto& entity : entities) { 67 | entity->update(); 68 | entity->render(); 69 | } 70 | } 71 | return 0; 72 | } -------------------------------------------------------------------------------- /lecture09/part3.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class HitBox { 5 | public: 6 | double x, y, width, height; 7 | }; 8 | 9 | class Entity { 10 | protected: 11 | double x, y, z; 12 | HitBox hitbox; 13 | public: 14 | virtual void update() {}; 15 | virtual void render() {}; 16 | }; 17 | 18 | class Player : public Entity { 19 | double hitpoints = 100; 20 | public: 21 | void damage(double hp) { 22 | hitpoints -= hp; 23 | } 24 | 25 | void update() override { 26 | std::cout << "Updating Player!" << std::endl; 27 | } 28 | 29 | void render() override { 30 | std::cout << "Rendering Player!" << std::endl; 31 | } 32 | }; 33 | 34 | class Tree : public Entity { 35 | public: 36 | void update() override { 37 | std::cout << "Updating Tree!" << std::endl; 38 | } 39 | 40 | void render() override { 41 | std::cout << "Rendering Tree!" << std::endl; 42 | } 43 | }; 44 | 45 | class Projectile : public Entity { 46 | double vx, vy, vz; 47 | public: 48 | void update() override { 49 | std::cout << "Updating Projectile!" << std::endl; 50 | } 51 | 52 | void render() override { 53 | std::cout << "Rendering Projectile!" << std::endl; 54 | } 55 | }; 56 | 57 | 58 | int main() { 59 | Player player; 60 | Tree tree; 61 | Projectile proj; 62 | 63 | std::vector entities { &player, &tree, &proj }; 64 | while (true) { 65 | std::cout << "Rendering frame..." << std::endl; 66 | for (auto& entity : entities) { 67 | entity->update(); 68 | entity->render(); 69 | } 70 | } 71 | return 0; 72 | } -------------------------------------------------------------------------------- /lecture10/Vector.h: -------------------------------------------------------------------------------- 1 | /* This line makes sure that if another file #includes Vector.h 2 | * more than once, it will be as if it included it only once. 3 | * 4 | * Multiple includes can happen if, for example, we #include Vector.h 5 | * and also #include another file that itself #includes Vector.h 6 | * 7 | * #pragma once is technically not part of the C++ language, but is a 8 | * compiler extension that virtually all compilers support 9 | */ 10 | #pragma once 11 | 12 | #include 13 | 14 | template 15 | class Vector { 16 | public: 17 | using iterator = T*; 18 | 19 | Vector(); 20 | ~Vector(); 21 | 22 | void push_back(const T& value); 23 | 24 | T& at(size_t index); 25 | T& operator[](size_t index); 26 | 27 | size_t size(); 28 | bool empty(); 29 | 30 | iterator begin(); 31 | iterator end(); 32 | private: 33 | size_t _size; 34 | size_t _capacity; 35 | T* _data; 36 | 37 | void resize(); 38 | }; 39 | 40 | 41 | /* Notice that the .h file for template #includes the .cpp file! 42 | * This is because, when instantiating templates, the compiler 43 | * must have full knowledge of both the declaration (this file) 44 | * and the definitions (.cpp file) of the template. 45 | * 46 | * Another way around this is to implement the template entirely 47 | * inside on file. Typically, these files are given the extension 48 | * .hpp, but this is arbitrary. 49 | * 50 | * If you want to learn more about why this is, please check out 51 | * this C++ blog (also check out other pages in this, as it's a 52 | * great reference for learning about C++): 53 | * 54 | * https://isocpp.org/wiki/faq/templates#templates-defn-vs-decl 55 | */ 56 | #include "Vector.cpp" -------------------------------------------------------------------------------- /lecture10/Vector.cpp: -------------------------------------------------------------------------------- 1 | /* Notice: there's no #include "Vector.h" here, 2 | * so your IDE (e.g. VSCode) might not know about 3 | * what a Vector is. See the comment at the 4 | * bottom of "Vector.h" for more information about 5 | * this. 6 | */ 7 | 8 | #include 9 | 10 | template 11 | Vector::Vector() 12 | { 13 | _size = 0; 14 | _capacity = 4; 15 | _data = new T[_capacity]; 16 | } 17 | 18 | template 19 | Vector::~Vector() 20 | { 21 | delete[] _data; 22 | } 23 | 24 | template 25 | void Vector::resize() 26 | { 27 | auto newData = new T[_capacity * 2]; 28 | for (size_t i = 0; i < _size; i++) 29 | { 30 | newData[i] = _data[i]; 31 | } 32 | 33 | delete[] _data; 34 | _capacity *= 2; 35 | _data = newData; 36 | } 37 | 38 | template 39 | void Vector::push_back(const T& value) 40 | { 41 | if (_size == _capacity) { 42 | resize(); 43 | } 44 | 45 | _data[_size] = value; 46 | _size++; 47 | } 48 | 49 | template 50 | T& Vector::at(size_t index) 51 | { 52 | if (index >= _size) 53 | { 54 | throw std::out_of_range("Out of range!"); 55 | } 56 | 57 | return _data[index]; 58 | } 59 | 60 | template 61 | T& Vector::operator[](size_t index) 62 | { 63 | return _data[index]; 64 | } 65 | 66 | template 67 | size_t Vector::size() 68 | { 69 | return _size; 70 | } 71 | 72 | template 73 | bool Vector::empty() 74 | { 75 | return _size == 0; 76 | } 77 | 78 | template 79 | Vector::iterator Vector::begin() 80 | { 81 | return _data; 82 | } 83 | 84 | template 85 | Vector::iterator Vector::end() 86 | { 87 | return _data + _size; 88 | } -------------------------------------------------------------------------------- /lecture13/src/StanfordID.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/StanfordID.h" 2 | 3 | // List initialization constructor 4 | // StanfordID::StanfordID(std::string name, std::string sunet, int idNumber) : name(name), sunet(sunet), idNumber(idNumber) {} 5 | 6 | // Default Constructor 7 | StanfordID::StanfordID() { 8 | name = "John Appleseed"; 9 | sunet = "jappleseed"; 10 | idNumber = 00000001; 11 | } 12 | 13 | StanfordID::StanfordID(std::string name, std::string sunet, int idNumber) { 14 | this->name = name; 15 | this->sunet = sunet; 16 | if (idNumber >= 0) { 17 | this->idNumber = idNumber; 18 | } else { 19 | this->idNumber = 0; 20 | } 21 | } 22 | 23 | std::string StanfordID::getName() const { 24 | return name; 25 | } 26 | 27 | std::string StanfordID::getSunet() const { 28 | return sunet; 29 | } 30 | 31 | int StanfordID::getIdNumber() const { 32 | return idNumber; 33 | } 34 | 35 | /* Member functions */ 36 | #ifdef MEMBER_WISE 37 | bool StanfordID::operator<(const StanfordID& other) const { 38 | return idNumber < other.idNumber; 39 | } 40 | #endif // MEMBER_WISE 41 | 42 | bool StanfordID::operator==(const StanfordID& other) const { 43 | return idNumber == other.idNumber; 44 | } 45 | 46 | bool StanfordID::operator!=(const StanfordID& other) const { 47 | return !(idNumber == other.idNumber); 48 | } 49 | 50 | 51 | /** 52 | * Some operators cannot be overloaded: 53 | * For example, there is no `operator@`, so it can't be overloaded 54 | */ 55 | 56 | // void StanfordID::operator@(const StanfordID& other) const { 57 | // std::cout << "It works!" << std::endl; 58 | // } 59 | 60 | /* End member operators */ 61 | 62 | 63 | #ifndef MEMBER_WISE 64 | // non-member function 65 | bool operator<(const StanfordID& lhs, const StanfordID& rhs){ 66 | return lhs.getIdNumber() < rhs.getIdNumber(); 67 | } 68 | #endif // MEMBER_WISE -------------------------------------------------------------------------------- /lecture08/StudentID.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "StudentID.h" 3 | 4 | #define LIST_INITIALIZATION 0 5 | 6 | /// @brief Default constructor for the StudentID class 7 | StudentID::StudentID() { 8 | name = "John Appleseed"; 9 | sunet = "jappleseed"; 10 | idNumber = 00000001; 11 | } 12 | 13 | /// @brief Destructor for the StudentID class 14 | StudentID::~StudentID() { 15 | std::cout << "Destructor is called" << std::endl; 16 | } 17 | 18 | #if LIST_INITIALIZATION 19 | StudentID::StudentID(std::string name, std::string sunet, int idNumber) : name{name}, sunet{sunet}, idNumber{idNumber} {} 20 | #else 21 | 22 | /// @brief Parameterized constructor for the StudentID class 23 | /// @param name 24 | /// @param sunet 25 | /// @param idNumber 26 | StudentID::StudentID(std::string name, std::string sunet, int idNumber) { 27 | this->name = name; 28 | this->sunet = sunet; 29 | if (idNumber >= 0) { 30 | this->idNumber = idNumber; 31 | } else { 32 | this->idNumber = 0; 33 | } 34 | } 35 | 36 | /// @brief Getter function for the name member variable in a StudentID 37 | /// @return The StudentID object's name 38 | std::string StudentID::getName() { 39 | return name; 40 | } 41 | 42 | /// @brief Getter function for the sunet member variable in a StudentID 43 | /// @return The StudentID object's sunet 44 | std::string StudentID::getSunet() { 45 | return sunet; 46 | } 47 | 48 | /// @brief Getter function for the idNumber member variable in a StudentID 49 | /// @return The StudentID object's idNumber 50 | int StudentID::getIdNumber() { 51 | return idNumber; 52 | } 53 | 54 | /// @brief Setter function for the name member variable in a StudentID 55 | /// @param name 56 | void StudentID::setName(std::string name) { 57 | this->name = name; 58 | } 59 | 60 | /// @brief Setter function for the sunet member variable in a StudentID 61 | /// @param sunet 62 | void StudentID::setSunet(std::string sunet) { 63 | this->sunet = sunet; 64 | } 65 | 66 | /// @brief Setter function for the idNumber member variable in a StudentID 67 | /// @param id 68 | void StudentID::setIdNumber(int id) { 69 | this->idNumber = id; 70 | } 71 | #endif -------------------------------------------------------------------------------- /lecture17/src/StudentID.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "StudentID.h" 3 | 4 | #define LIST_INITIALIZATION 0 5 | 6 | /// @brief Default constructor for the StudentID class 7 | StudentID::StudentID() { 8 | name = "John Appleseed"; 9 | sunet = "jappleseed"; 10 | idNumber = 00000001; 11 | } 12 | 13 | /// @brief Destructor for the StudentID class 14 | StudentID::~StudentID() { 15 | std::cout << "Destructor is called" << std::endl; 16 | } 17 | 18 | #if LIST_INITIALIZATION 19 | StudentID::StudentID(std::string name, std::string sunet, int idNumber) : name{name}, sunet{sunet}, idNumber{idNumber} {} 20 | #else 21 | 22 | /// @brief Parameterized constructor for the StudentID class 23 | /// @param name 24 | /// @param sunet 25 | /// @param idNumber 26 | StudentID::StudentID(std::string name, std::string sunet, int idNumber) { 27 | this->name = name; 28 | this->sunet = sunet; 29 | if (idNumber >= 0) { 30 | this->idNumber = idNumber; 31 | } else { 32 | this->idNumber = 0; 33 | } 34 | } 35 | 36 | /// @brief Getter function for the name member variable in a StudentID 37 | /// @return The StudentID object's name 38 | std::string StudentID::getName() { 39 | return name; 40 | } 41 | 42 | /// @brief Getter function for the sunet member variable in a StudentID 43 | /// @return The StudentID object's sunet 44 | std::string StudentID::getSunet() { 45 | return sunet; 46 | } 47 | 48 | /// @brief Getter function for the idNumber member variable in a StudentID 49 | /// @return The StudentID object's idNumber 50 | int StudentID::getIdNumber() { 51 | return idNumber; 52 | } 53 | 54 | /// @brief Setter function for the name member variable in a StudentID 55 | /// @param name 56 | void StudentID::setName(std::string name) { 57 | this->name = name; 58 | } 59 | 60 | /// @brief Setter function for the sunet member variable in a StudentID 61 | /// @param sunet 62 | void StudentID::setSunet(std::string sunet) { 63 | this->sunet = sunet; 64 | } 65 | 66 | /// @brief Setter function for the idNumber member variable in a StudentID 67 | /// @param id 68 | void StudentID::setIdNumber(int id) { 69 | this->idNumber = id; 70 | } 71 | #endif -------------------------------------------------------------------------------- /unit_testing/README.md: -------------------------------------------------------------------------------- 1 | # Unit Testing using Google Test 2 | 3 | Google Test Documentation: https://google.github.io/googletest/ 4 | 5 | This project consists of 3 files: 6 | 1. `bank_account.hpp`: Header file for BankAccount class. 7 | 2. `bank_account.cpp`: Implementation of BankAccount class. 8 | 3. `main.cpp`: Example test suite for the BankAccount class. 9 | 10 | Because we are using the Google Test library, we have to make sure to include it properly in our build. The `CMakeLists.txt` file does this for you already. 11 | 12 | To build and run the project, do the following (make sure you are inside the `unit_testing` directory): 13 | 14 | ```sh 15 | mkdir build 16 | ``` 17 | 18 | ```sh 19 | cd build 20 | ``` 21 | 22 | Just to be transparent, running the following command will download a version of the Google Test project into the `build` directory. If you do not want this to be automatically done or want a newer version of the Google Test project, remove the `FetchContent` from the `CMakeLists.txt` file, download the project manually, and reconfigure the `CMakeLists.txt` to work with the manually downloaded project. 23 | ```sh 24 | cmake .. 25 | ``` 26 | 27 | ```sh 28 | make 29 | ``` 30 | 31 | ```sh 32 | ./main 33 | ``` 34 | 35 | Upon building and running, you should see something similar in your terminal: 36 | ```sh 37 | [==========] Running 5 tests from 3 test suites. 38 | [----------] Global test environment set-up. 39 | [----------] 1 test from AccountTest 40 | [ RUN ] AccountTest.BankAccountStartsEmpty 41 | [ OK ] AccountTest.BankAccountStartsEmpty (0 ms) 42 | [----------] 1 test from AccountTest (0 ms total) 43 | 44 | [----------] 2 tests from BankAccountTest 45 | [ RUN ] BankAccountTest.BankAccountStartsEmpty 46 | [ OK ] BankAccountTest.BankAccountStartsEmpty (0 ms) 47 | [ RUN ] BankAccountTest.CanDepositMoney 48 | [ OK ] BankAccountTest.CanDepositMoney (0 ms) 49 | [----------] 2 tests from BankAccountTest (0 ms total) 50 | 51 | [----------] 2 tests from DEFAULT/WithdrawAccountTest 52 | [ RUN ] DEFAULT/WithdrawAccountTest.FinalBalance/0 53 | [ OK ] DEFAULT/WithdrawAccountTest.FinalBalance/0 (0 ms) 54 | [ RUN ] DEFAULT/WithdrawAccountTest.FinalBalance/1 55 | [ OK ] DEFAULT/WithdrawAccountTest.FinalBalance/1 (0 ms) 56 | [----------] 2 tests from DEFAULT/WithdrawAccountTest (0 ms total) 57 | 58 | [----------] Global test environment tear-down 59 | [==========] 5 tests from 3 test suites ran. (0 ms total) 60 | [ PASSED ] 5 tests. 61 | ``` 62 | 63 | See if you can make your own tests/test fixtures that reveal bugs in the BankAccount class! A few examples are overflows, underflows, and negative deposits. 64 | -------------------------------------------------------------------------------- /lecture12/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "soundex.h" 11 | 12 | static const std::map> algorithms = { 13 | {"soundex", soundex}, 14 | {"soundexRanges", soundexRanges}}; 15 | 16 | std::vector getAlgorithms(int argc, char *argv[]) 17 | { 18 | 19 | auto printOptions = [&]() 20 | { 21 | std::cerr << "Options are: " << std::endl; 22 | for (const auto &[name, _] : algorithms) 23 | { 24 | std::cerr << " - " << name << std::endl; 25 | } 26 | }; 27 | 28 | std::vector arguments(argv + 1, argv + argc); 29 | if (arguments.empty()) 30 | { 31 | std::cerr << "No arguments provided. "; 32 | printOptions(); 33 | exit(1); 34 | } 35 | 36 | for (const auto &algo : arguments) 37 | { 38 | if (algorithms.find(algo) == algorithms.end()) 39 | { 40 | std::cerr << "Unknown algorithm: '" << algo << "'. "; 41 | printOptions(); 42 | exit(1); 43 | } 44 | } 45 | 46 | return arguments; 47 | } 48 | 49 | auto readLines(const std::string &path) 50 | { 51 | std::ifstream file(path); // Open the file 52 | std::vector lines; 53 | 54 | for (std::string line; std::getline(file, line);) 55 | { 56 | lines.push_back(line); 57 | } 58 | 59 | return lines; 60 | } 61 | 62 | auto sampleN(const std::vector &names, size_t n) 63 | { 64 | std::random_device rd; 65 | std::mt19937 gen(rd()); 66 | std::vector result(n); 67 | std::ranges::sample(names, result.begin(), result.size(), gen); 68 | return result; 69 | } 70 | 71 | template 72 | void showSoundexCodes(const std::vector &names, const std::string &algo, Soundex soundex) 73 | { 74 | std::cout << "Soundex output for '" << algo << "'" << std::endl; 75 | std::cout << "-----------------------------------" << std::endl; 76 | for (const auto &name : names) 77 | { 78 | std::cout << name << " -> " << soundex(name) << std::endl; 79 | } 80 | std::cout << std::endl; 81 | } 82 | 83 | template 84 | void timeSoundex( 85 | const std::vector &names, 86 | const std::string &algo, 87 | Soundex soundex, 88 | size_t rounds = 8) 89 | { 90 | using namespace std::chrono; 91 | 92 | auto start = high_resolution_clock::now(); 93 | 94 | for (size_t i = 0; i < rounds; ++i) 95 | { 96 | for (const auto &name : names) 97 | { 98 | soundex(name); 99 | } 100 | } 101 | 102 | auto end = high_resolution_clock::now(); 103 | auto duration = duration_cast(end - start).count(); 104 | auto num_rounds = names.size() * rounds; 105 | auto average_time = duration / num_rounds; 106 | 107 | std::cout << "Ran algorithm '" << algo << "' " << num_rounds << " times" << std::endl; 108 | std::cout << "Average time per call: " << average_time << " nanoseconds\n" 109 | << std::endl; 110 | } 111 | 112 | int main(int argc, char *argv[]) 113 | { 114 | // Get algorithms from command line 115 | auto choices = getAlgorithms(argc, argv); 116 | 117 | // Read names from file 118 | auto names = readLines("names.txt"); 119 | 120 | // Randomly sample 10 names and print soundex output 121 | auto sample = sampleN(names, 5); 122 | sample.push_back("Roberts-"); 123 | 124 | for (const auto &algo : choices) 125 | { 126 | showSoundexCodes(sample, algo, algorithms.at(algo)); 127 | timeSoundex(names, algo, algorithms.at(algo)); 128 | } 129 | 130 | return 0; 131 | } -------------------------------------------------------------------------------- /lecture15/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | class Photo { 8 | public: 9 | Photo(int width, int height, const char* tag = nullptr); 10 | Photo(const Photo& other); 11 | Photo& operator=(const Photo& other); 12 | ~Photo(); 13 | 14 | #ifdef ENABLE_MOVE_SEMANTICS 15 | // Move SMFs 16 | Photo(Photo&& other); 17 | Photo& operator=(Photo&& other); 18 | #endif 19 | 20 | private: 21 | int width; 22 | int height; 23 | int* data; 24 | 25 | std::ostream& log() const; 26 | const char* tag = nullptr; 27 | }; 28 | 29 | Photo::Photo(int width, int height, const char* tag) 30 | : width(width) 31 | , height(height) 32 | , data(new int[width * height]) 33 | , tag(tag) 34 | { 35 | log() << "Photo(" << width << ", " << height << ")" << std::endl; 36 | } 37 | 38 | Photo::Photo(const Photo& other) 39 | : width(other.width) 40 | , height(other.height) 41 | , data(new int[width * height]) 42 | { 43 | log() << "Photo(const Photo&)" << std::endl; 44 | log() << " ↪ copying " << width << "x" << height << " pixels..." << std::endl; 45 | std::copy(other.data, other.data + width * height, data); 46 | } 47 | 48 | Photo& Photo::operator=(const Photo& other) { 49 | log() << "Photo::operator=(const Photo&)" << std::endl; 50 | // Check for self assignment 51 | if (this == &other) return *this; 52 | 53 | log() << " ↪ cleaning up " << width << "x" << height << " pixels..." << std::endl; 54 | delete[] data; // Clean up old pixels! 55 | 56 | // Copy over new pixels! 57 | width = other.width; 58 | height = other.height; 59 | data = new int[width * height]; 60 | 61 | log() << " ↪ copying " << width << "x" << height << " pixels..." << std::endl; 62 | std::copy(other.data, other.data + width * height, data); 63 | return *this; 64 | } 65 | 66 | Photo::~Photo() 67 | { 68 | log() << "~Photo()" << std::endl; 69 | delete[] data; 70 | } 71 | 72 | #ifdef ENABLE_MOVE_SEMANTICS 73 | 74 | Photo::Photo(Photo&& other) 75 | : width(other.width) 76 | , height(other.height) 77 | , data(other.data) 78 | { 79 | log() << "Photo(Photo&&)" << std::endl; 80 | other.data = nullptr; 81 | } 82 | 83 | Photo& Photo::operator=(Photo&& other) 84 | { 85 | log() << "Photo::operator=(Photo&&)" << std::endl; 86 | 87 | // Clean up our data before assigning into this 88 | delete[] data; 89 | 90 | width = other.width; 91 | height = other.height; 92 | data = other.data; 93 | other.data = nullptr; 94 | 95 | return *this; 96 | } 97 | 98 | #endif 99 | 100 | std::ostream& Photo::log() const 101 | { 102 | static std::ostream null(nullptr); 103 | if (!tag) return null; 104 | return std::cout << "[" << tag << "] \t\t"; 105 | } 106 | 107 | template 108 | void time_func(Func f, size_t invocations) 109 | { 110 | // Temporarily disable logging 111 | std::cout.setstate(std::ios_base::failbit); 112 | 113 | auto start = std::chrono::high_resolution_clock::now(); 114 | for (size_t i = 0; i < invocations; ++i) 115 | f(); 116 | auto end = std::chrono::high_resolution_clock::now(); 117 | 118 | // Re-enable logging 119 | std::cout.clear(); 120 | 121 | std::cout << "Average time spent per call: " << std::chrono::duration_cast(end - start).count() / invocations << "ns" << std::endl; 122 | } 123 | 124 | Photo take_photo() 125 | { 126 | Photo photo(500, 500, "take_photo()"); 127 | return photo; 128 | } 129 | 130 | void run_example() 131 | { 132 | Photo selfie(0, 0, "selfie"); 133 | selfie = take_photo(); 134 | } 135 | 136 | int main() { 137 | std::cout << "Example run:\n\n"; 138 | run_example(); 139 | std::cout << "\n\n"; 140 | 141 | constexpr size_t invocations = 10000; 142 | std::cout << "Timing " << invocations << " attempts to move/copy a Photo..." << std::endl; 143 | time_func(run_example, invocations); 144 | return 0; 145 | } -------------------------------------------------------------------------------- /lecture11/utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | /* ========================================================================= * 15 | * Printing typenames/demangling * 16 | * ========================================================================= */ 17 | 18 | namespace demangling 19 | { 20 | 21 | static std::optional execute(const std::string &command, int* exit_status = nullptr) 22 | { 23 | std::string result; 24 | 25 | FILE *pipe = popen(command.c_str(), "r"); 26 | if (!pipe) 27 | return std::nullopt; 28 | char buffer[128]; 29 | while (fgets(buffer, sizeof(buffer), pipe) != nullptr) 30 | { 31 | result += buffer; 32 | } 33 | int status = pclose(pipe); 34 | if (exit_status) *exit_status = status; 35 | if (status != 0) 36 | return std::nullopt; 37 | 38 | return result; 39 | } 40 | 41 | static bool can_demangle() 42 | { 43 | static std::optional available; 44 | 45 | if (available.has_value()) 46 | return *available; 47 | 48 | int exec_status; 49 | execute("c++filt --version", &exec_status); 50 | available = (exec_status == 0); 51 | 52 | if (!available) 53 | std::cerr << "\033[33mWarning: c++filt could not be found, so type names will be mangled.\033[0m\n\n"; 54 | 55 | return *available; 56 | } 57 | 58 | static std::string demangle(const std::string &mangled) 59 | { 60 | if (!can_demangle()) 61 | return mangled; 62 | auto demangled = execute("echo " + mangled + " | c++filt -t"); 63 | if (!demangled.has_value()) 64 | return mangled; 65 | 66 | auto rstrip = [](std::string str){ 67 | auto end = std::find_if_not(str.rbegin(), str.rend(), isspace).base(); 68 | str.erase(end, str.end()); 69 | return str; 70 | }; 71 | 72 | return rstrip(*demangled); 73 | } 74 | 75 | template 76 | struct Demangle 77 | { 78 | friend std::ostream &operator<<(std::ostream &os, const Demangle &t) 79 | { 80 | return os << t.name(); 81 | } 82 | 83 | static const std::string& name() { 84 | static auto name = demangle(typeid(T).name()); 85 | return name; 86 | } 87 | }; 88 | 89 | } 90 | 91 | template 92 | static auto type() 93 | { 94 | return demangling::Demangle{}; 95 | } 96 | 97 | template 98 | static auto type(T&& v) 99 | { 100 | return type>(); 101 | } 102 | 103 | template 104 | std::string pit(Container &c, typename Container::iterator it) 105 | { 106 | using Iterator = typename Container::iterator; 107 | 108 | std::stringstream ss; 109 | ss << "Iterator{ "; 110 | 111 | ss << "pos = "; 112 | if (std::begin(c) != std::end(c) && it == std::end(c)) 113 | ss << "end()"; 114 | else 115 | { 116 | auto pos = std::distance(std::begin(c), it); 117 | ss << "begin()"; 118 | if (pos > 0) 119 | ss << " + " << pos; 120 | } 121 | 122 | if (it != std::end(c)) 123 | { 124 | ss << ", "; 125 | ss << "element = " << *it; 126 | } 127 | 128 | ss << " }"; 129 | 130 | return ss.str(); 131 | } 132 | 133 | /* ========================================================================= * 134 | * Test functions/output * 135 | * ========================================================================= */ 136 | 137 | static std::ostream& out() { 138 | return std::cout << "\033[90m" << std::left << std::setw(60); 139 | } 140 | 141 | static constexpr auto result = "\033[0m"; 142 | static constexpr auto end = "\n"; 143 | 144 | namespace testing 145 | { 146 | 147 | struct Test 148 | { 149 | std::string name; 150 | std::function func; 151 | }; 152 | 153 | static std::vector tests; 154 | 155 | struct TestRegistrar 156 | { 157 | TestRegistrar(std::string name, std::function func) { tests.push_back({name, func}); } 158 | }; 159 | 160 | static int run_tests() 161 | { 162 | demangling::can_demangle(); 163 | const std::string style_bg = "\033[48;5;45m"; 164 | const std::string style_reset = "\033[0m"; 165 | 166 | for (const auto &test : tests) 167 | { 168 | std::cout << style_bg 169 | << std::setw(100) << std::setfill(' ') 170 | << std::left << test.name 171 | << style_reset << std::endl; 172 | 173 | test.func(); 174 | std::cout << "\n\n"; 175 | } 176 | 177 | return 0; 178 | } 179 | 180 | #define test(func) static testing::TestRegistrar registrar_##func(#func, func) 181 | 182 | } 183 | -------------------------------------------------------------------------------- /unit_testing/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "bank_account.hpp" 4 | 5 | /** Struct: BankAccountTest 6 | * ----------------------- 7 | * Test wrapper for the BankAccount struct. Because we are inheriting from 8 | * testing::Test, we can use this struct to create test fixtures (TEST_F) 9 | * that will automatically construct and destruct the BankAccount object 10 | * for us. 11 | */ 12 | struct BankAccountTest : testing::Test{ 13 | BankAccount* account; 14 | 15 | BankAccountTest() { 16 | account = new BankAccount; 17 | } 18 | 19 | virtual ~BankAccountTest() { 20 | delete account; 21 | } 22 | }; 23 | 24 | /** Struct: account_state 25 | * --------------------- 26 | * This struct contains state that we will use as the parameters to the 27 | * WithdrawAccountTest test fixture (below). 28 | */ 29 | struct account_state { 30 | int initial_balance; 31 | int withdraw_amount; 32 | int final_balance; 33 | bool success; 34 | 35 | // operator overload to print nicer error messages. Try making a WithdrawAccountTest 36 | // fail with and without this overload to see the difference in error message outputs. 37 | friend std::ostream& operator<<(std::ostream& os, const account_state& obj) { 38 | return os 39 | << "initial_balance: " << obj.initial_balance 40 | << " withdraw_amount: " << obj.withdraw_amount 41 | << " final_balance: " << obj.final_balance 42 | << " success: " << obj.success; 43 | } 44 | }; 45 | 46 | /** Struct: WithdrawAccountTest 47 | * --------------------------- 48 | * This struct will be used for a parameterized test fixture. By inheriting 49 | * from testing::WithParamInterface, we can instantiate this 50 | * test fixture with parameterized inputs. (see below) 51 | */ 52 | struct WithdrawAccountTest : BankAccountTest, testing::WithParamInterface { 53 | WithdrawAccountTest() { 54 | account->balance = GetParam().initial_balance; 55 | } 56 | }; 57 | 58 | /** Test: AccountTest->BankAccountStartsEmpty 59 | * ----------------------------------------- 60 | * Test to ensure bank accounts are initialized with a balance of 0. 61 | * Notice how we have to manually create and destroy the BankAccount object. 62 | * See below for how we can avoid this using a test fixture! 63 | */ 64 | TEST(AccountTest, BankAccountStartsEmpty) { 65 | BankAccount* account = new BankAccount; 66 | EXPECT_EQ(0, account->balance); 67 | delete account; 68 | } 69 | 70 | /** Test Fixture: BankAccountTest->BankAccountStartsEmpty 71 | * ----------------------------------------------------- 72 | * Same as the above test, but notice that we do not have to create and 73 | * destroy the BankAccount object manually. Because we create the BankAccountTest 74 | * struct and used a test fixture (TEST_F), the constructor and destructor of the 75 | * BankAccountTest will automatically be called for us. 76 | * 77 | * In this case, it doesn't save us too much work, but imagine if BankAccount had 78 | * hundreds of fields that had to be initialized properly. Having the BankAccountTest 79 | * wrapper would be super helpful. 80 | */ 81 | TEST_F(BankAccountTest, BankAccountStartsEmpty) { 82 | EXPECT_EQ(0, account->balance); 83 | } 84 | 85 | /** Test Fixture: BankAccountTest->CanDepositMoney 86 | * ---------------------------------------------- 87 | * Test fixture to test depositing works. 88 | * Should probably have more tests here (e.g. what if we deposit a negative value?) 89 | */ 90 | TEST_F(BankAccountTest, CanDepositMoney) { 91 | account->deposit(100); 92 | EXPECT_EQ(100, account->balance); 93 | } 94 | 95 | /** Parameterized Test Fixture: WithdrawAccountTest->FinalBalance 96 | * ------------------------------------------------------------- 97 | * This is a test fixture that expects parameter inputs. This test fixture is kind 98 | * of like a template, where it defines a blueprint for how to make the true test 99 | * fixture but expects an input (the parameters) to be able to do that. 100 | * 101 | * GetParam() is how we access these inputs when the test fixture is instantiated. 102 | */ 103 | TEST_P(WithdrawAccountTest, FinalBalance){ 104 | auto as = GetParam(); 105 | auto success = account->withdraw(as.withdraw_amount); 106 | EXPECT_EQ(as.final_balance, account->balance); 107 | EXPECT_EQ(as.success, success); 108 | } 109 | 110 | /** Instantiation of Parameterized Test Fixture: WithdrawAccountTest->FinalBalance 111 | * ------------------------------------------------------------------------------ 112 | * Instantiates the parameterized test fixture by providing two different input 113 | * account_states. The WithdrawAccountTest is defined to be inherited from 114 | * testing::WithParamInterface which is why we need to provide the 115 | * instantiation with an account_state. 116 | * 117 | * This will instatiate two separate tests. 118 | */ 119 | INSTANTIATE_TEST_SUITE_P(DEFAULT, WithdrawAccountTest, 120 | testing::Values( 121 | account_state{100,50,50,true}, 122 | account_state{100,200,100,false} 123 | )); 124 | 125 | /** Main Function 126 | * ------------- 127 | * Initializes Google Test and runs all tests. 128 | */ 129 | int main(int argc, char* argv[]) 130 | { 131 | testing::InitGoogleTest(&argc, argv); 132 | return RUN_ALL_TESTS(); 133 | } 134 | -------------------------------------------------------------------------------- /lecture11/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "utils.hpp" 7 | 8 | /* ========================================================================= * 9 | * Template Functions: * 10 | * Min function * 11 | * ========================================================================= */ 12 | 13 | /* This is a simple function to return the min of two values. 14 | * Notice that it takes in it's parameters by value and assumes 15 | * that both parameters have the same type. */ 16 | template 17 | T min_basic(T a, T b) { 18 | return a < b ? a : b; 19 | } 20 | 21 | void test_min_basic() { 22 | // Explicit instantiation 23 | int int_min = min_basic(106, 107); 24 | out() << "min_basic(106, 107): " << result << int_min << end; 25 | 26 | double double_min = min_basic(42.5, 3.14); 27 | out() << "min_basic(42.5, 3.14): " << result << double_min << end; 28 | 29 | std::string string_min = min_basic("Jacob", "Fabio"); 30 | out() << "min_basic(\"Jacob\", \"Fabio\"): " << result << string_min << end; 31 | 32 | // Careful using implicit instantiation 33 | std::string expr { "min_basic(\"Jacob\", \"Fabio\"): "}; 34 | auto string_min_bad = min_basic("Jacob", "Fabio"); 35 | out() << "Type of " + expr << result << type(string_min_bad) << end; 36 | out() << expr << result << string_min_bad << end; 37 | } 38 | 39 | /* Here is another version of the same function. 40 | * Note that we are taking `a` and `b` in by reference, 41 | * so we'd expect to see a performance improvement for more complex 42 | * types like `std::string`. */ 43 | template 44 | T min_ref(const T& a, const T& b) { 45 | return a < b ? a : b; 46 | } 47 | 48 | void test_min_ref() { 49 | // Implicit Instantiation 50 | int int_min = min_ref(106, 107); 51 | out() << "min_ref(106, 107): " << result << int_min << end; 52 | 53 | double double_min = min_ref(42.5, 3.14); 54 | out() << "min_ref(42.5, 3.14): " << result << double_min << end; 55 | 56 | // Must use explicit instantiation here 57 | std::string string_min = min_ref("Arwen", "Aragorn"); 58 | out() << "min_ref(\"Arwen\", \"Aragorn\"): " << result << string_min << end; 59 | } 60 | 61 | /* Here is a more flexible min function that will work 62 | * even if the types differ (as long as they can be converted 63 | * to the same type). 64 | * 65 | * Notice that it uses an auto return type: 66 | * we will let the compile figure out what the return type 67 | * depending on the type of what gets returned. */ 68 | template 69 | auto min_flex(const U& a, const V& b) { 70 | return a < b ? a : b; 71 | } 72 | 73 | void test_min_flex() { 74 | std::string expr { "min_flex(106, 107.5): " }; 75 | auto min_elem = min_flex(106, 107.5); 76 | 77 | out() << "Type of " + expr << result << type(min_elem) << end; 78 | out() << expr << result << min_elem << end; 79 | } 80 | 81 | 82 | /* ========================================================================= * 83 | * Code Demo: * 84 | * `find` function * 85 | * ========================================================================= */ 86 | 87 | /* Here is a generic version of a find function that will work 88 | * for any container and any value type. */ 89 | template 90 | TIterator find(TIterator begin, TIterator end, TValue value) { 91 | for (auto it = begin; it != end; ++it) { 92 | if (*it == value) return it; 93 | } 94 | return end; 95 | } 96 | 97 | void test_find() { 98 | // We could use explicit instantiation, although this is cumbersome 99 | // 100 | // Notice that we are calling `find` as `::find` in these examples. 101 | // This is because of a feature called ADL (argument-dependent lookup). 102 | // 103 | // Basically, C++ says "if the arguments to a fucntion are in `namespace std`, 104 | // then most likely the function is in `namespace std` too!". Since `v.begin()` 105 | // and `v.end()` are `std::vector::iterator`, it thinks that `find` 106 | // is actually `std::find` (not the `find` we defined above). 107 | // 108 | // To get it to select the right find, we explicitly tell it to search 109 | // the *global namespace* by prefixing it with :: 110 | // 111 | std::vector v { 1, 2, 3, 4, 5 }; 112 | auto it1 = ::find::iterator, int>(v.begin(), v.end(), 3); 113 | out() << "find::iterator, int>(v.begin(), v.end(), 3): " << result << pit(v, it1) << end; 114 | 115 | // In practice, we will usually use implicit instantiation! 116 | std::string expr2 { "find(v.begin(), v.end(), 10): " }; 117 | auto it2 = ::find(v.begin(), v.end(), 10); // Should be end() 118 | 119 | 120 | using TIterator2 = decltype(it2); 121 | using TValue2 = typename std::iterator_traits::value_type; 122 | out() << expr2 << result << "TIterator = " << type() << end; 123 | out() << expr2 << result << "TValue = " << type() << end; 124 | out() << expr2 << result << pit(v, it2) << end; 125 | 126 | // Notice, because we're using templates, `find` generalizes to other 127 | // container types/values seamlessly! 128 | 129 | std::unordered_set us { "hello", "there", "welcome", "to", "cs106l!" }; 130 | std::string expr3 { "find(us.begin(), us.end(), \"welcome\"): "}; 131 | auto it3 = ::find(us.begin(), us.end(), "welcome"); 132 | 133 | using TIterator3 = decltype(it3); 134 | using TValue3 = typename std::iterator_traits::value_type; 135 | out() << expr3 << result << "TIterator = " << type() << end; 136 | out() << expr3 << result << "TValue = " << type() << end; 137 | out() << expr3 << result << pit(us, it3) << end; 138 | } 139 | 140 | /* ========================================================================= * 141 | * Concepts * 142 | * ========================================================================= */ 143 | 144 | /* Concepts are a way to constrain what types a template parameter can represent. 145 | * By default, writing `typename T` allows `T` to be instantiated with any type! 146 | * That doesn't mean, however, that after instantiation, a specific choice of `T` 147 | * won't necessarily cause a compiler error. 148 | * 149 | * For example, if we tried to instantiate `min`, the function 150 | * would still be instantiated with `T = std::ifstream`, however, when it tries 151 | * to compare the two `std::ifstream` using `operator<`, we'd get a compiler error! 152 | * 153 | * Concepts are a way to constrain these types, so that `min` for example doesn't 154 | * even get instantiated in the first place if `T` doesn't have an `operator<`. 155 | * This leads to all around better error messages, faster compile times, etc. */ 156 | 157 | template 158 | concept Comparable = requires(T a, T b) { 159 | // For T to model Comparable, a < b must a valid expression at compile time 160 | // Furthermore, we require that the result type of `a < b` models `std::convertible_to`. 161 | // 162 | // Aside: `std::convertible_to` is actually a concept that takes in two type 163 | // parameters and says whether `From` can be converted to `To`. Notice, however, that 164 | // in the constraint below, `From` is implicitly replaced with the type of `a < b`, 165 | // e.g. `decltype(a < b)`. 166 | { a < b } -> std::convertible_to; 167 | 168 | // You can put more constraints here if you'd like! 169 | }; 170 | 171 | /* Here's how we can use the Comparable concept. 172 | * Another way to write this is: 173 | * 174 | * template requires Comparable 175 | * 176 | */ 177 | template 178 | T min_constrained(const T& a, const T& b) { 179 | return a < b ? a : b; 180 | } 181 | 182 | void test_min_constrained() { 183 | // This works: `int` has `operator<` 184 | min_constrained(10, 20); 185 | 186 | // This does not: `std::stringstream` lacks `operator<` 187 | // Try uncommenting this line and re-compiling 188 | // min_constrained(std::stringstream(), std::stringstream()); 189 | 190 | // We can check, at compile time, whether a type satisfies a concept 191 | auto satisfies = [](auto val){ 192 | using T = decltype(val); 193 | out() << type(val).name() + " is Comparable: " << result; 194 | 195 | // `if constexpr` evaluates an if statement at compile time 196 | if constexpr (Comparable) std::cout << "Yes!"; 197 | else std::cout << "No!"; 198 | 199 | std::cout << end; 200 | }; 201 | 202 | satisfies(5); 203 | satisfies(std::stringstream {}); 204 | satisfies("Hello World!"); 205 | satisfies(std::vector { 1, 2, 3 }); 206 | } 207 | 208 | 209 | /* ========================================================================= * 210 | * Variadic Templates: * 211 | * Building a variadic min function * 212 | * ========================================================================= */ 213 | 214 | /* Variadic functions support any number of arguments. 215 | * They work by recursively generating overloads for a function at instantiation 216 | * time. */ 217 | 218 | /* Here is our base case function: 219 | * The min of a single `v` is just `v` */ 220 | template 221 | T min_var(const T& v) { return v; } 222 | 223 | 224 | /* Here is our recursive case function: 225 | * The min of `v, ...rest` is just `min(v, min_var(rest...))` 226 | * Notice that the `...` **unpacks** its arguments at compile time. */ 227 | template 228 | T min_var(const T& v, const Args&... rest) { 229 | auto m = min_var(rest...); 230 | return v < m ? v : m; 231 | } 232 | 233 | void test_min_var() { 234 | auto m1 = min_var(1); 235 | out() << "min_var(1): " << result << m1 << end; 236 | 237 | auto m2 = min_var(2, 1); 238 | out() << "min_var(2, 1): " << result << m2 << end; 239 | 240 | // This works for other types too! 241 | auto m3 = min_var("cool", "variadic", "template!"); 242 | out() << "min_var(\"cool\", \"variadic\", \"template!\"): " << result << m3 << end; 243 | 244 | // We can technically have different types for each parameter, 245 | // but due to how the recursive case is defined, the type of the 246 | // first parameter determines the type of the result 247 | std::string expr4 { "min_var(10, 2.5, 3.0f): " }; 248 | auto m4 = min_var(10, 2.5, 3.0f); 249 | out() << expr4 << result << m4 << end; 250 | out() << "Type of " + expr4 << result << type(m4) << end; 251 | 252 | // To get the right type, we should explicitly instantiate 253 | // 254 | // Notice that the recursive case function also explicitly instantiates 255 | // for the same reason! 256 | std::string expr5 { "min_var(10, 2.5, 3.0f): " }; 257 | auto m5 = min_var(10, 2.5, 3.0f); 258 | out() << expr5 << result << m5 << end; 259 | out() << "Type of " + expr5 << result << type(m5) << end; 260 | } 261 | 262 | 263 | /* ========================================================================= * 264 | * Harness for these examples * 265 | * ========================================================================= */ 266 | 267 | 268 | test(test_min_basic); 269 | test(test_min_ref); 270 | test(test_min_flex); 271 | test(test_find); 272 | test(test_min_constrained); 273 | test(test_min_var); 274 | 275 | int main() { 276 | return testing::run_tests(); 277 | } --------------------------------------------------------------------------------