class Mystring {
friend bool operator==(const Mystring &lhs, const Mystring &rhs); // compare
friend Mystring operator-(const Mystring &obj); // -str
friend Mystring operator+(const Mystring &lhs, const Mystring &rhs); // concat
friend std::ostream &operator<<(std::ostream &os, const Mystring &rhs); // stream insertion
friend std::istream &operator>>(std::istream &in, Mystring &rhs); // stream extraction
private:
char *str;
public:
Mystring();
Mystring(const char *s);
Mystring(const Mystring &source);
~Mystring();
Mystring &operator=(const Mystring &rhs);
void display() const;
int get_length() const;
const char *get_str(); const;
}
Mystring &Mystring::operator=(const Mystring &rhs) {
if (this == &rhs)
return *this;
delete [] str;
str = new char[std::strlen(rhs.str) + 1];
std::strcpy(str, rhs.str);
return *this; // to chain s1 = s2 = s3
}
s1 = Mystring {"Joe"}; //move operator called
Mystring &Mystring::operator=(Mystring &&rhs) {
if (this == &rhs)
return *this;
delete [] str;
str = rhs.str;
rhs.str = nullptr;
return *this;
}
class Account {
public:
double balance;
std::string account_name;
void deposit(double amount);
void withdraw(double amount);
Account();
~Account();
}
class SavingAccount: public Account {
public:
double int_rate;
void deposit(double amount) { Account::deposit(amount); } // override
SavingAccount() : Account{} {} // call parent constructor
}
class DepartmentChair: public Faculty, public Administrator {
...
};
graph LR
A(Polymorphism) --> B(Compile-time)
A-->C(Run-time)
B-->D(Function Overloading)
B-->E(Operator Overloading)
C-->F(Function Overriding)
class Base {
public:
void say_hello() const {
std::cout << "Base Class" << std::endl;
}
};
class Derived: public Base {
public:
void say_hello() const {
std::cout << "Derived Class" << std::endl;
}
};
void greetings(const Base &obj) {
obj.say_hello();
};
int main() {
Derived d;
greetings(d); // outputs "Base Class"
Base *ptr = new Derived();
ptr->say_hello(); // outputs "Base Class"
delete ptr; // always delete pointer
// with a smart pointer
// include <memory>
std::unique_ptr<Base> ptr1 = std::make_unique<Derived>();
ptr1->say_hello(); // outputs "Base Class"
return 0;
}
- For dynamic polimorphism we must have
- Inheritance
- Base class pointer or Base class reference
- Virtual functions
class Account {
public:
virtual void withdraw(double amount);
}
class Savings: public Account{
public:
virtual void withdraw(double amount); // best practice to make it virtual again
}
Account *p1 = new Savings();
p1->withdraw(1000); // Savings::withdraw
delete p1;
Account *p1 = new Savings();
Account *p2 = new Trust();
vector<Account *> accounts {p1, p2};
for (auto acc_ptr: accounts) {
acc_ptr->withdraw(1000);
}
//delete pointers
// using class references
Savings s;
Account &ref1 = s;
ref1.withdraw(1000); // Savings::withdraw
- if a class has virtual functions
- ALWAYS provide a public virtual destructor
- if base class destructor is virtual then all derived class destructors are also virtual
- Add override at the end of the function if you override the base class functions so compiler will warn you if you didn't match the overridden function
class Savings: public Account{
public:
virtual void withdraw(double amount) override;
}
- when used at the class level
- prevents a class from being derived from
- when used at the method level
- prevents virtual method from being overridden in derived classes
- Abstract class
- Cannot instantiate objects
- These classes are used as base classes in inheritance hierarchies
- Often referred to as Abstract Base Classes
- Contains at least one pure virtual function
- Concrete classes must override pure virtual functions, if not Base class becomes also abstract
virtual void function() = 0; // pure virtual function
an abstract class that has only pure virtual functions. c++ doesnt have interface keyword so we achive that by making abstract classes all functions pure virtual
class I_Printable {
friend ostream &operator<<(ostream &os, const I_Printable &obj);
public:
virtual void print(ostream &os) const = 0;
};
ostream &operator<<(ostream &os, const I_Printable &obj) {
obj.print(os);
return os;
}
class Test: public I_Printable {
public:
void print(ostream &os) const override {
os << "Hi from Test";
}
};
void pprint(I_Printable &t) {
cout << t << endl;
}
int main() {
Test *t = new Test();
pprint(*t);
delete t;
return 0;
}
Smart pointers are objects, can only point to heap-allocated memory, auto delete, created from template library, pointer arithmetic (++, --, etc) is not supported
- Unique pointers
- Shared pointers
- Weak pointers
- Auto pointers (deprecated)
#include <memory>
std::unique_ptr<Base> ptr = std::make_unique<Class>();
ptr->method();
cout << (*ptr) << endl;
RAII -> Resource Acquisition Is Initialization
- common software design pattern
- RAII objects are allocated on the stack
- Resource Acquisition
- Open a file
- Allocate memory
- Acquire a lock
- Is Initialization
- The resource is acquired in a constructor
- simple smart pointer very efficient!
- unique_ptr <T>
- points to an object of type T on the heap
- there can only be one unique_ptr<T> pointing to the object on the heap
- Owns what it points to
- can not be assigned or copied
- can be moved
- when pointer is destroyed, what it points to is automatically destroyed
std::unique_ptr<int> p1 {new int {100}};
*p1 = 200;
p1.get(); // 0x412312
p1.reset(); // p1 is now nullptr
std::unique_ptr<int> p1 {new Account("Larry")};
std::vector<std::unique_ptr<int>> vec;
std::unique_ptr<int> ptr {new int {100}};
vec.push_back(ptr); // Error: copy not allowed
vec.push_back(std::move(ptr)); // ok
//more efficient methods
std::unique_ptr<int> p1 = make_unique<int>(100);
std::unique_ptr<Account> p2 = make_unique<Account>("Larry", 1000);
auto p3 = make_unique<Player>("Larry", 2000);
- provides shared ownership of heap objects
- shared_ptr <T>
- points to an object of type T on the heap
- there can be many shared_ptrs pointing the same object on the heap
- establishes shared ownership relationship
- can be assigned and copied
- can be moved
- doesn't support managing arrays by default
- when the use count is zero, the managed object on the heap is destroyed ( use_count() )
- provides a non-owning "weak" reference
- weak_ptr <T>
- points to an object of type T on the heap
- does not participate in owning relationship
- Always created from a shared_ptr
- does not increment or decrement reference use_count
- used to prevent strong reference cycles which could prevent objects from being deleted
- if you want use custom deleter you can not use make_unique or make_shared
void my_deleter (Account *raw_pointer) {
// do some stuff here
delete row_pointer;
}
shared_ptr<Account> ptr {new Account {}, my_deleter };
// using lambda
shared_ptr<Account> ptr (new Account {}, [] (Account *ptr) {
// do some stuff here
delete ptr;
});
double average {};
try {
if (total == 0)
throw 0;
average = sum / total;
} catch (int &ex) {
std::cerr << "can't divide by zero" << std::endl;
} catch (...) { // catches all exceptions in case type different from handled
std::cout << "unknown error" << std::endl;
}
double calculate_avg(int sum, int total) {
if (total == 0)
throw 0;
return static_cast<double>(sum) / total;
}
c++ try to find try except block in chain of called function stacked main try catch -> func a -> func b -> func c (throws exception) in this case exception will be catched on main. this is stack unwinding
class DivideByZeroException {}
if (total == 0)
throw DivideByZeroException();
try {} catch (const DivideByZeroException &ex) { ... }
Exceptions can also be thrown from within a class
- Method
- These work the same way like above
- Constructor
- an exception can be thrown from constructor if initializations fails
- Destructor
- do NOT throw exceptions from destructor
- C++ provides a class hierarchy of exception classes
- strd::exception is the base class
- all subclasses implement the what() virtual function
- we can create our own user-defined ception subclasses
virtual const char *what() const noexcept;
class IllegalBalanceException: public std::exception {
public:
IllegalBalanceException() noexcept = default; // no except means i will not throw an exception
~IllegalBalanceException() override = default;
[[nodiscard]] const char* what() const noexcept override {
return "Illegal balance exception";
}
};
Header File | Description |
---|---|
iostream | Provides definitions for formatted input and output from/to streams |
fstream | Provides definitions for formatted input and output from/to file streams |
iomanip | Provides definitions for manipulators used to format stream I/O |
Class | Description |
---|---|
ios | Provides basic support for formatted and unformatted I/O operations |
ifstream | Provides for high-level input operations on file based streams |
ofstream | Provides for high-level output operations on file based streams |
fstream | Provides for high-level I/O operations on file based streams |
stringstream | Provides for high-level I/O operations on memory based strings |
Manipulator | Description |
---|---|
booleans | |
std::boolalpha | will display true |
std::noboolalpha | will display 1,0 (default) |
integers | |
std::dec,hex,oct | base10 |
std::noshowbase | prefix used to show hexadecimal or octal |
std::nouppercase | when displaying a prefix and hex values it will be lowercase |
std::noshowpos | no '+' is displayed for positive numbers |
float | |
std::setprecision | number of digits displayed(6) |
std::fixed | not fixed to a specific number of digits after the decimal point |
std::noshowpoint | trailing zeroes are not displayed |
std::nouppercase | when displaying in scientific notation |
std::noshowpos | no '+' is displayed for positive numbers |
align, fill | |
std::setw | not set by default |
std::left | when no field width, right - when using field width |
std::fill | not set by default - blank space is used |
#include <iostream>
#include <fstream>
#include <iomanip>
int main() {
std::ifstream in_file;
std::string name;
int num;
double total;
in_file.open("../murat.txt");
if (!in_file) {
std::cerr << "Error opening file!" << std::endl;
return 1;
}
while (!in_file.eof()) {
// std::string line {};
// std::get_line(in_file, line)
// char c {};
// in_file.get(c);
in_file >> name >> num >> total;
std::cout << std::setw(10) << std::left << name
<< std::setw(10) << num
<< std::setw(10) << total
<< std::endl;
}
in_file.close();
return 0;
}
#include <iostream>
#include <fstream>
#include <iomanip>
int main() {
std::ofstream out_file {"../output.txt"}; // to append std::ios::app as second parameter
if (!out_file) {
std::cerr << "Error creating file!" << std::endl;
return 1;
}
std::string line {"Murasaki Shikibu - The Tale of Genji\n"};
out_file << line;
out_file.close();
return 0;
}
#include <iostream>
#include <sstream>
int main() {
int num {};
double total {};
std::string str {};
std::string info {"Moe 100 1234.5"};
std::istringstream iss {info};
iss >> str >> num >> total;
}
#include <iostream>
#include <sstream>
int main() {
int num {100};
double total {1234.5};
std::string info {"Moe"};
std::ostringstream oss {};
oss << name << " " << num << " " << total;
std::cout << oss.str() << std::endl;
}
#define SQRT(a) ((a) * (a))
result = SQRT(5); // ((5) * (5))
result = 100/SQRT(5); // 100 / ((5) * (5)) = 4
#include <iostream>
#include <string>
//it is same in class templates just use T for values in class or struct
template <typename T>
void min(T a, T b) {
return (a < b) ? a : b;
}
struct Person {
std::string name;
int age;
Person(std::string name, int age) : name(name), age(age) {}
bool operator<(const Person& p) const {
return age < p.age;
}
friend std::ostream& operator<<(std::ostream& os, const Person& p) {
os << p.name << " " << p.age;
return os;
}
};
int main() {
Person p1("John", 20);
Person p2("Alice", 30);
Person p = min(p1, p2);
std::cout << p << " is younger" << std::endl;
}
set, map, vector, stack, deck, queue, list what type of elements can we store in containers?
- A copy of the element will be stored in the container
- All primitives OK
- Element should be
- Copyable and assignable (copy constructor / copy assignment)
- Moveable for efficiency (move Constructor / move Assignment)
- Ordered associative containers must be able to compare elements
- operator<, operator==
// container_type::iterator_type iterator_name;
std::vector<int>::iterator it1;
std::map<std::string, std::string>::iterator it2;
std::vector<int> vec {1,2,3};
std::vector<int>::iterator it = vec.begin(); // or vec.end();
//or
auto it = vec.begin();
++it, it++, it = it1, *it, it->, it == it1, it != it1, --it, it--, it + i, it += i, it - i, it -= i, it <, >, <=, >= it1,
std::set<char> suits {'C', 'H', 'S', 'D'};
auto it = suits.begin();
while (it != suits.end()) {
std::cout << *it << " " << std::end;
++it;
}
- to see more detailed explanation about an algorith go to
#include <algorithm>
std::vector<int> vec {1, 2, 3};
auto loc = std::find(vec.begin(), vec.end(), 3);
if (loc != vec.end()) // found it!
std::cout << *loc << std::endl; // 3
// using Functor
struct Square_Functor {
void operator()(int x) { // overload () operator
std::cout << x * x << " ";
}
}
Square_Functor square;
// using function pointer
void square(int x) {
std::cout << x * x << " ";
}
std::vector<int> vec {1, 2, 3, 4};
std::for_each(vec.begin(), vec.end(), square); // 1, 4, 9, 16
// using lamdda
std::for_each(vec.begin(), vec.end(), [](int x) { std::cout << x * x << " "; }); // 1, 4, 9, 16
other algorithm samples: count, count_if, replace, all_of, string_transform
#include <array>
std::array<int, 5> arr1 {{1,2,3,4,5}};// fixed size
std::array<std::string, 3> stooges {std::string("Murat"), "Cem", std::string("Yalin")}
std::cout << arr1.at(0);
std::cout << arr1.size();
std::cout << arr1.front();
std::cout << arr1.back();
std::cout << arr1.empty();
std::cout << arr1.max_size();
int* data = arr.data();
Elements are NOT stored in contiguous memory so it is like linked list of vectors. if you add something to the front, if there is a space it will add. if not it will allocate a list space and element there and create a link to the current one.
#include <deque>
- Non-contiguous in memory, no direct access to elements, very efficient for inserting and deleting elements once an element is found, double linked list, use when u need a container where u will make lots of inserting and deleting and you don't need a direct access
#include <list>
std::list<int> l {1,2,3,4,5};
l.size(); // 5
l.max_size(); // a very large number
l.front(); // 1
l.back(); // 5
Person p1 {"Larry", 18};
std::list<Person> l;
l.push_back(p1); // add to the end of the list
l.pop_back(); // remove last element
l.push_front(p1) // add to front of the list
l.pop_front(); // remove first element
l.emplace_back("Larry", 18); // add to back efficient
l.emplace_front("Meo", 24); // add to front
std::list<int> l {1,2,3,4,5};
auto it = std::find(l.begin(), l.end(), 3);
l.insert(it, 10); // 1 2 10 3 4 5
l.erase(it); // 1 2 10 4 5
l.resize(2); // 1 2
l.resize(5); // 1 2 0 0 0
- like a list but only one direction - forward
#include <forward_list>
std::forward_list<int> l {1,2,3,4,5};
*** l.size();*** // not available
l.max_size(); // a very large number
l.front(); // 1
***l.back();*** // not available
Person p1 {"Larry", 18};
std::list<Person> l;
l.push_front(p1) // add to front of the list
l.pop_front(); // remove first element
l.emplace_front("Meo", 24); // add to front
std::list<int> l {1,2,3,4,5};
auto it = std::find(l.begin(), l.end(), 3);
l.insert_after(it, 10); // 1 2 3 10 4 5
l.emplace_after(it, 10); // 1 2 3 10 4 5
l.erase_after(it); // 1 2 3 4 5
l.resize(2); // 1 2
l.resize(5); // 1 2 0 0 0
Sets uses operator< to order. to insert use s.insert(); to find use s.find(); to understand if element in set use s.count()
- std::set (don't allow duplicates)
- std:unordered_set
- std::multiset (allows duplicates)
- std::unordered_multiset
similar to dictionary, elements are stored as key value pairs, order by key, no duplicate elements, direct access using key
#include <map>
std::map<std::string, int> m1 {
{"Larry", 18},
{"Moe", 25}
};
std::pair<std::string, int> p1 {"Murat", 39};
m.insert(p1);
m.insert(std::make_pair("Cem", 39));
m["Yalin"] = 39; // insert
m["Yalin"] = 40; // update
m.erase("Yalin");
#include <map>
- ordered by key, allow duplicates, ordered by key
#include <unordered_map> like multi map bu not ordered
std::stack #include <stack>
- Last-in First-out (LIFO) data structure
- can be implemented as a vector, list or deque
- all operations occur on the end of the stack
- no iterators supported
- push, pop, top, empty, size
std::stack<int> s; //deque
std::stack<int, std::vector<int>> s1; // vector
std::stack<int, std::list<int>> s2; // list
std::stack<int, std::deque<int>> s3; // deque
std::queue #include <queue>
- First-in First-out (FIFO) data structure
- can be implemented as a list or deque
- elements are pushed at the back and popped from the front
- no iterators supported
- push, pop, front, back, empty, size
std::queue<int> q; //deque
std::queue<int, std::list<int>> q2; // list
std::queue<int, std::deque<int>> q3; // deque
enum State {Engine_Failure = 0, Inclement_Weather = 1, Nominal = 2};
enum Sequence {Abort = 3, Hold = 4, Launch = 5};
int user_input;
std::cin >> user_input;
State state = State(user_input);
switch (state) {
case Engine_Failure:
initiate(Abort);
break;
}
enum State {Engine_Failure, Inclement_Weather, Nominal}; // 0, 1, 2 underlying type: int
std::istream& operator>>(std::istream& is, State& state) {
std::underlying_type_t<State> user_input;
is >> user_input;
switch (user_input) {
case Engine_Failure: state = State(user_input); break;
case Inclement_Weather: state = State(user_input); break;
case Nominal: state = State(user_input); break;
default: std::cout << "User input is not a valid state";
}
return is;
}
enum class Whale {Blue = 350, Beluga = 250, Gray = 150};
Whale whale = Whale::Beluga;
int wh = Whale::Blue; // Error!
int w = Whale::Blue + Whale::Beluga; // Error!
std::cout << Whale::Blue; // Error!
int whale_code = int(Whale::Blue); // 350
int whale_code = static_cast<int>(Whale::Blue); // 350
int total = int(Whale::Blue) + int(Whale::Beluga); // 600
std::cout << underlying_type_t<Whale>(Whale::Beluga); // 350