Skip to content

C++

old name is C with Classes.

Contains 4 parts, each designed with different philosophy.

  • C
  • Object-oriented programming
  • Template (generic programming)
  • STL (standard library)

Tutorial

C Library

#include <cstdio>
#include <cstdlib>
#include <cstring> // memset
#include <cmath>

C++ Library

#include \<iostream>

#include <iostream>

using namespace std;

int main() {
    char name[50];

    cin >> name;
    cout << "Name: " << name << endl;

}

#include \<string>

#include <iostream>
#include <string>

using namespace std;

int main ()
{
    string str1 = "Hello";
    string str2 = "World";
    string str3;

    str3 = str1 + str2;
    s = s + 'c';

    int len = str3.size(); // or str3.length()

    int idx = str3.find("pattern"); // -1 (string::npos) if not found

    char* cstr3 = str3.c_str();

    // erase
    s.erase(s.begin() + i); // erase one char
    s.erase(start, num); // erase from start for num chars

    // string to int
    // int stoi(string& s, size_t* idx=0, int base=10)
    string s = "123";
    int i = stoi(s); // 123

    string s = "123 xyz";
    size_t sz;
    int i = stoi(s, &sz); // 123, sz = 3

    string s = "-10010110001";
    int b = stoi(s, nullptr, 2); // -1201, binary.

    // string to float
    string s = "12.34";
    float f = stof(s); // 12.34

    // similarly, we have `stol, stoll, stod, stold, stoul, stoull`

    // int/float/double to string
    string s = to_string(42); // "42"
    string s = to_string(12.23); // "12.23"
    string s = to_string(1e40); // "10000000000000000303786028427003666890752.000000"
}

#include \<vector>

#include <iostream>
#include <vector>
using namespace std;

int main() {
    // init
    vector<int> vec; 
    vector<int> vec = {1, 2, 3};
    vector<int> vec(size[, init_value]);

    // 2d init: [N, M], -inf
    vector<vector<int>> dp(N, vector<int>(M, -0x3f3f3f3f));

    // reserve v.s. resize
    vec.reserve(10); // only increase capacity, will not push empty elements! vec.size() == 0
    vec.resize(10); // push 10 empty elements into vec. vec.size() == 10

    // push_back
    for(int i = 0; i < 5; i++){
       vec.push_back(i);
    }

    // clear
    v.clear();

    // size
    cout << vec.size() << endl;

    // access by index
    for(i = 0; i < 5; i++){
       cout << i << " = " << vec[i] << endl;
    }

    // iterator
    vector<int>::iterator v = vec.begin();
    while(v != vec.end()) {
       cout << *v << endl; // same as a pointer
       v++;
    }

    // loop and reversed loop
    for (auto it = v.begin(); it != v.end(); it++) {...}
    for (auto it = v.rbegin(); it != v.rend(); it++) {...} // also use ++!

    // erase i-th element
    v.erase(v.begin() + i);

    // copy another vector
    vector<int> v2(v1);

    // insert x at i-th position
    v.insert(v.begin() + i, x);

    // extend another vector (also by insert)
    v1.insert(v1.end(), v2.begin(), v2.end());

    // emplace_back: in-place push_back  (cpp11, but not supported in msvc10)
    // use condition: push a complicated object to a vector, but don't want to create a temporary object and then copy it to the vector.
    // assume we have a Complicated class initialized by Complicated(x, y, z)
    vec<Complicated> v;
    v.push_back(Complicated(x, y, z)); // create temporary then copy, slow.
    v.emplace_back(x, y, z); // no temporary, fast.

    // emplace: in-place insert.
    v.emplace(v.end(), x, y, z);

    // slice subvector
    vector<int> v2(v.begin(), v.begin() + 5); // select and copy [0, 5)
    v.resize(5); // inplace trick, keep first 5 elements

    // iterator
    auto it = v.begin();
    auto next = it + 1;
    auto nnnnext = it + 4; // vector is sequence container, so we can add an int to it.
    int x = *it; 
    v.erase(next);
    v.insert(next, 0);


    return 0;
}

Note: vector<bool> is not a STL container! It optimizes space and use bit to save each bool variable, so many STL operations are not supported. Use bitset or deque<bool> instead.

#include \<set>

set implements a binary search tree, so querying existence and order is in O(log n)

#include <iostream>
#include <set>
using namespace std;

int main() {
    set<int> s;

    // insert element
    s.insert(1);

    // count element, can check if an element is in the set.
    cout << s.count(1) << endl;
    bool is_in = s.count(1);

    // find the first element, another way to check is in. (faster than count!)
    bool is_in2 = s.find(1) != s.end();
    auto it = s.find(1); // return iterator to the first found element.

    // [c++20] contains
    bool is_in3 = s.contains(1);

    // delete by value
    s.erase(2); // erase(value)

    // size
    cout << s.size() << endl;

    // clear
    s.clear();

    // is empty
    if (s.empty()) {
        //...
    }

    // loop all elements (increasing order)
    for (auto it = s.begin(); it != s.end(); it++) {
        cout << *it << endl;
    }
    // loop with index (enumerate)
    for (auto [it, i] = tuple{s.begin(), 0}; it != s.end(); it++, i++) m[*it] = i;

    // get the smallest (first) or largest (last) element
    int mn = *s.begin();
    int mx = *s.end();

    // get the k-th smallest element
    auto i = s.begin();
    cout << *i << endl; // s[0]
    auto next = ++i; // s[1], DO NOT USE i++, then next is still s[0]

    advance(i, k); // in-place modification of i (since set is an associative container, i + k will not work)

    auto i = s.begin();
    auto i2 = next(i, 1); // not in-place ver
    auto i3 = prev(i2, 1);

    int mmn = *next(s.begin(), 1);
}

#include \<map>

map implements binary search tree to store keys.

#include <iostream>
#include <map>
using namespace std;

int main() {
    // init
    map<int, string> m;
    map<int, int> n = {{0:1}, {1:2}};
    // insert
    m[0] = "zero"; // m[key] = val
    m.insert(pair<int, string>(1, "one")); // m.insert(pair)

    // get
    cout << m[-1] << endl; // if not exist, will insert & initialize it ! (here int --> 0)
    cout << m.at(-1) << endl; // check if exist first, throw an error

    // size
    m.size();
    m.empty();
    m.clear();

    // iterator
    // auto == std::map<int,string>::iterator == pair<int,string>
    for (auto it = m.begin(); it != m.end(); it++) {
        cout << it->first << ": " << it->second << endl;
    }
    for (auto& it : m) {
        cout << it.first << ": " << it.second << endl;
    }
    for (auto& [k, v]: m) { // c++17
        cout << k << ": " << v << endl;
    }

    // delete
    m.erase(0); // erase(key)

    // find
    auto it = m.find(2);
    if (it != m.end()) {
        m.erase(it);
    }

    // check if a key is in map
    bool is_in = m.find(0) != m.end();
    bool is_in2 = m.count(0); // also work in map

    // binary search keys
    // assume the keys are {0,1,3,5,7}
    auto it = m.lower_bound(3); // will get iterator to 3
    auto it = m.lower_bound(2); // will get iterator to 3
    auto it = m.lower_bound(-1); // will get iterator to 0
    auto it = m.lower_bound(8); // will get map.end(), which has `it->first = map.size(); it->second = 0` (注意不要用key来判断是否达到了map.end()!这里key等于map中的元素数量。要用it == map.end()。)

    auto it = m.upper_bound(3); // will get iterator to 5
    auto it = m.upper_bound(7); // will get map.end()


}

#include \<unordered_set>

unordered_set implements a hash table, thus faster than ordered set.

API is the same with set.

#include <iostream>
#include <unordered_set>
using namespace std;

int main() {
    unordered_set<int> s;
    s.insert(1);
    s.erase(1);
    s.empty();
    s.clear();
    s.find(1);
    s.count(1);

    // regular use to check duplicate
    unordered_set<int> vis;
    while(...) {
        if (vis.count(x)) return true;
        else vis.insert(x);
    }
    return false;
}

#include \<unordered_map>

unordered_map implements a hash table.

API is the same with map.

#include \<queue>

#include <iostream>
#include <queue>
using namespace std;

int main() {
    queue<int> q;
    q.push(1); // not push_back
    while (!q.empty()) {
        int a = q.front(); 
        int b = q.back(); 
        q.pop(); // return void
        cout << q.size() << endl;
    }
}

// a trick in BFS to get each layer of a tree (only use one queue!)
bool BFS_leveling(TreeNode* root) {
    // bfs leveling
    queue<TreeNode*> q;
    q.push(root);
    int layer = 0;
    while (!q.empty()) {
        // record current size
        int cur_size = q.size();
        // lnodes in current layer
        for (int i = 0; i < cur_size; i++) {
            TreeNode* p = q.front(); q.pop();
            // do something...
            if (p->left) q.push(p->left);
            if (p->right) q.push(p->right);
        }
        layer++;
    }
}

#include \<stack>

#include <iostream>
#include <stack>
using namespace std;

int main() {
    stack<int> s;
    s.push(2); // not push_back
    int x = s.top();
    s.pop(); // return void
    s.empty();
}

#include \<priority_queue>

#include <iostream>
#include <queue> // priority_queue is define
using namespace std;

// < means larger-first
struct cmp {
    bool operator()(int a, int b) {
        return a < b;
    }
};

int main() {
    // heap, default is larger-first
    priority_queue<int> pq;
    pq.push(1);
    pq.push(2);
    pq.push(0);
    p.top(); // 2, yes it's top, not front.

    // smaller-first
    priority_queue<int, vector<int>, greater<int>> pq;


    // custom cmp function by struct
    set<int, cmp> s;
    priority_queue<int, vector<int>, cmp> pq;

    // custom cmp function by lambda
    auto cmp = [&](const pair<int, int>& x, const pair<int, int>& y) {
        return arr[x.first] * arr[y.second] > arr[x.second] * arr[y.first];
    };
    priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp)> q(cmp);
}

// you may need to store a pair in priority queue and sort by a key:
typedef pair<int,int> pii;
priority_queue<pii, vector<pii>, greater<pii>> pq; // smaller first
// unfortunately, you cannot update the pq in-place, if you would like to modify it, you have to pop and re-push.
auto [k, v] = pq.top(); pq.pop();
pq.push(pii(k))

#include \<algorithm>

//// quick sort
vector<int> v = {0, 2, 1, 3};
sort(v.begin(), v.end()); // small -> large

// reverse sort
sort(v.begin(), v.end(), greater<int>());  // large -> small, c++14
// or in two steps
sort(v.begin(), v.end());
reverse(v.begin(), v.end()); // large -> small

int v[4] = {4, 2, 3, 1};
sort(v, v + 4);

// sort with custom funciton / object.
bool cmp(int i, int j) {return i < j;} // True -> [i, j]
struct cmp {
    bool operator < (int i, int j) {
        return i < j;
    }
}
sort(v, v + 4, cmp); // same as default, small -> large
sort(v, v + 4, [](int i, int j) { return i < j; }) // lambda version

//// merge sort O(nlogn)
stable_sort(v, v + 4);

//// partial sort / topk, in O(klogn)
partial_sort(v, v + 2, v + 4); // only sort the first two elements

//// swap
swap(x, y);

//// is_sorted
bool flag = is_sorted(v, v+4);

//// min/max_element
cout << *min_element(v, v+4) << " ~ " << *max_element(v, v+4) << endl; // return pointer/iterator

//// nth_element, O(n) quick sort
nth_element(v, v+2, v+4); // rearrange v[2] as the second element if ordered.
cout << v[2] << endl;

//// prev/next_permutation
int xs[3] = {1, 2, 3};
do {
    for (int i=0; i<3; i++) cout << xs[i] << " ";
    cout << endl;
} while (next_permutation(xs, xs+3));

//// unique
auto end = unique(v.begin(), v.end());
v.erase(end, v.end());
int new_size = v.size();

int new_size = unique(v, v+4) - v;

//// lower/upper_bound
// check out of bound conditions!
// lower_bound(begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
// upper_bound(begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
// lower/upper_bound([1,2,3], 4) --> 3
// lower/upper_bound([1,2,3], 0) --> 0

// the first position >= target
int l = lower_bound(v.begin(), v.end(), target) - v.begin();
// the first position > target
int r = upper_bound(v.begin(), v.end(), target) - v.begin();

// border condition 
if (l == v.size()) {
    // v[l-1] < target, non-exist case
} else {
    // v[l] >= target
    if (v[l] == target) {}
    else {}
}

// find the last position <= target
if (l == v.size()) return l-1;
else if (l == 0) {
    if (v[l] == target) return l;
    else return -1; // non-exist case
}
else {
    if (v[l] == target) return l;
    else return l-1;
}


//// reverse
string s = "string";
reverse(s.begin(), s.end()); // inplace

//// argsort (has to use another vector of pair)
vector<pair<int, int>> v2;
for (int i = 0; i < v.size(); i++) {
    v2.push_back({v[i], i}); // pair value with index
}
sort(v2.begin(), v2.end()); // can use default sorting of pair<int, int>, lower to higher
vector<int> idx;
for (int i = 0; i < v2.size(); i++) {
    idx.push_back(v2[i].second);
}

#include \<bitset>

#include <bitset>

bitset<1000> bs; // 1000 bits

bitset<32> bs{10}; // binary form of x (only support unsigned int/long long)
bitset<8> bs{0b00001111}; // just 00001111
bitset<6> bs{"010101"}; // just 010101

cout << bs << endl; // bit format

// access pos bit, NOTE: the order is reversed! e.g., bs[0] = 1 for "010101"
// out-of-bound is undefined!
bool x = bs[pos]; 

bs.all();
bs.any();
bs.none();

int cnt = bs.count(); // count number of 1
int l = bs.size();

bs.set(); // set all to 1
bs.set(pos, val=true); // set pos to 1 or 0
bs.reset(); // set all to 0
bs.reset(pos); // set pos to 0, equals bs.set(pos, false);
bs.flip(); // flip all
bs.flip(pos); // flip pos

// count bit function
int countBit(int x) { // assume positive!
    return bitset<32>(x).count();
}

Class

class A {
// access modifier (default is private)
private:
    int p; // private variable
public:
    // constructor
    A() {} // default
    A(int _a) { a = _a; } 
    A(int _a): a(_a) {} // init-list

    A(const A& other) { if (this != &other) *this = other; } // default copy constructor

    // operator overload
    A& operator=(const A& other) {
        a = other.a;
        return *this;
    }

    // destructor
    ~ A() {}

    // member
    int a;

    // method
    void setA(int _a) { a = _a; }
};

A a;
A a(0);
A a = A(0);
A* pa = new A;
A* pa = new A(0);

printf("%d", a.a);
a.setA(1);

Struct is like an default-to-public Class:

struct A {
    int a; // everything is public
    void setA(int _a) { a = _a; }
};

Inheritance

class B: public A {
public:
    void printA() { printf("%d", a); } // member a is inherited from class A
};

operator overload

class A {
public:
    int x;
    // constructor 
    A (int x):x(x) {}

    // A = 1
    void operator= (int rhs) {
        x = rhs;
    }

    // ++A
    A& operator++ () {
        x++;
        return this;
    }
    // A++, use a fake parameter
    A operator++ (int) {
        A tmp(*this);
        x++;
        return tmp;
    }

    // -A
    A operator- () {
        return A(-x);
    }

    // A + A
    A operator+ (const A& rhs) {
        return A(x + rhs.x);
    }
    // A + 1
    A operator+ (const int rhs) {
        return A(x + rhs);
    }
    // 1 + A
    friend A operator+ (const int lhs, const A& rhs) {
        return A(lhs + rhs.x);
    }

    // A < 1
    bool operator< (const int rhs) {
        return x < rhs;
    }

    // A[i]
    int& operator[] (int i) {
        return arr[i];
    }

    // A(a)
    void operator() (int a) {
        return a + x;
    }

    // cout << A;
    friend ostream &operator << (ostream& output, const A& a) {
        output << a.x;
        return output;
    }

    // cin >> A;
    friend istream &operator >> (istream& input, A& a) {
        input >> a.x;
        return input;
    }

};

polymorphism


Template

Function

template <typename T>
T const& max(T const& a, T const& b) {
    return a < b ? b : a;
}

Class

template <typename T>
class A {
public:
    vector<T> a;
    void push(T const& x) { a.push_back(x); }
    T top() const { return a.back(); }
}

new & delete

char* p = new double;
delete p;

char* s = NULL;
s = new char[20];
delete [] s;

shared_ptr

a smart pointer class, that manages reference count of the object and automatically deconstruct the object if reference count is 0.

#include <memory>

std::shared_ptr<ClassName> p(new ClassName());
std::shared_ptr<ClassName> p = std::make_shared<ClassName>();

p.reset(new ClassName()); // the first object is deconstructed.

Reference

less powerful but safer than pointer. it works like an alias.

  • reference must be initialized as soon as created.
  • reference cannot be made to refer to another object once created. (cannot be re-initialized)
  • reference cannot be null. (thus there is no container of references.)
ref as an alias:
int i = 0;
int & j = i; // j is an alias of i
j = 1; // i == 1 is true

int & k; // error, not initialized !

int f() { return 42 ; };
int (& rf)() = f; // reference to a func
ref as function return value:
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
double& setValues(int i) { return vals[i]; }
setValues(0) = 0; // okay
ref as function args:
void swap(int& x, int& y) {
    int tmp = x; x = y; y = tmp;
}

Memory Layout

img

  • Text Segment: the executable program itself. often read-only.
  • Initialized Data Segment (DS, data segment): initialized global variables, static variables.
  • Uninitialized Data Segment (BSS, block started by symbol): uninitialized global variables, static variables.
  • Stack: for normal memory allocation (mostly int x[size]), also contains the program stack, to enable recursive functions.
  • Heap: for dynamic memory allocation.
// C++ code -- non-vector parts are also true for C 

char* s = "hello world!"; // DS, read-write
const int debug = 1; // DS, read-only

int arr1[100000]; // BSS 
int N; // BSS
vector<int> arr2; // HEAP 

struct DumbStruct { 
    int someArr[10000]; 
}; 

int main () { 
    int arr3[100000]; // STACK
    static int arr7[100000]; // BSS 

    vector<int> arr4; // HEAP 

    int* arr5 = new int[100000]; // HEAP 
    int* arr6 = (int*) malloc(100000 * sizeof(int)); // HEAP 

    DumbStruct struct; // STACK 

    DumbStruct* struct2 = new DumbStruct(); // HEAP 
    vector<DumbStruct> structarr; // HEAP 

    int n; 
    scanf("%d", &n); 
    int arr8[n]; // STACK (assuming C99 -- this does not compile in C++) 
}