C++

生命周期

预处理阶段编程

#include <iostream> // 可以包含任意的文件,只是编译器无法识别// 避免头文件被多次包含#ifndef _XXX_H_INCLUDED_#define _XXX_H_INCLUDED_...    // 头文件内容#endif // _XXX_H_INCLUDED_#ifdef AUTH_PWD                  // 检查是否已经有宏定义#  undef AUTH_PWD                // 取消宏定义#endif                           // 宏定义检查结束#define AUTH_PWD "xxx"           // 重新宏定义// 条件编译#elif (NGX_LINUX)#  include <ngx_linux.h>#endif

编译阶段编程

属性:编译阶段的“标签”,用来标记变量、函数或者类

[[deprecated("过时")]] // c++14 or later//[[gnu::deprecated]] // c+11 or laterint old_func(){    //[[gnu::deprecated("I hate this")]]    int value = 0;    return value;}int main() {  old_func();  return 1;}

静态断言:在编译阶段计算常数和类型,如果断言失败就会导致编译错误

static_assert(1 != 1, "1 is 1");

面向对象

#include <iostream>class Animal {  private:    int age = 0;  public:    friend class Dog;    // 构造函数    Animal() = default; // 明确告诉编译器,使用默认实现    // 析构函数    ~Animal() {      std::cout << "~Animal\n" << std::endl;    }    void test() {      std::cout << "Animal\n";    }    virtual void test1() {      std::cout << "Animal\n";    }};// 继承class Dog: public Animal {private:  int name = 0;public:  Dog() = default;  Dog(int name) {    this->name = name;  }  void test() {    std::cout << "Dog\n";  }  // 方法覆写  void test1() override {    std::cout << "Dog\n";  }  void test2(const Animal& a) {    // 访问友元类的私有数据    a.age;  }  // 运算符重载  Dog operator+(const Dog & dog) {    std::cout << "two dog plus\n";    return Dog(2);  }};int main() {  Animal* a = new Dog();  a->test(); // Animal  a->test1(); // Dog  Animal b = Dog();  b.test1(); // Animal  Dog c;  Dog d;  Dog e = c + d;  }

变量

自动类型推导

auto        x = 10L;    // auto推导为long,x是longauto&       x1 = x;      // auto推导为long,x1是long&auto*       x2 = &x;    // auto推导为long,x2是long*const auto& x3 = x;        // auto推导为long,x3是const long&auto        x4 = &x3;    // auto推导为const long*,x4是const long*// c++14后 auto也能用在函数返回值中auto f() {  return 1;}

decltype 的形式很像函数,后面的圆括号里就是可用于计算类型的表达式(和 sizeof 有点类似),其他方面就和 auto 一样了,也能加上 const、*、& 来修饰

void (*signal(int signo, void (*func)(int)))(int);// 使用decltype可以轻松得到函数指针类型using sig_func_ptr_t = decltype(&signal);// 获取复杂类型std::set s = {1,2,3};using iterator_type = decltype(s.cbegin());iterator_type iterator = s.cbegin();

const/volatile/mutable

const

volatile

mutable

class A {  public:    int a = 1;    mutable int b = 2;    void test() {      a = 2;    }    void test1() const {      // a = 2; 不行      b = 2;    }};int main() {  const A a = A();  // a.test(); 无法调用  a.test1();  return 1;}

智能指针

智能指针是代理模式的具体应用,它使用 RAII 技术代理了裸指针,能够自动释放内存,无需程序员干预

unique_ptr:

unique_ptr 是一个对象。不要对它调用 delete,它会自动管理初始化时的指针,在离开作用域时析构释放内存,它也没有定义加减运算,不能随意移动指针地址,也不能通过拷贝赋值,要通过 move 语义进行指针的所有权转移

class A {public:  ~A() {    cout << "~A";  }};int main() {  {    unique_ptr<A> p1(new A());    // auto p2 = p1; ERROR    auto p2 = move(p1); // 要通过 move 进行指针所有权转移  }  cout << "exit";  return 1;}

shared_ptr:

其所有权是可以被安全共享的,其内部有个引用计数,会导致循环引用的问题。如果发生拷贝赋值——也就是共享的时候,引用计数就增加,而发生析构销毁的时候,引用计数就减少。只有当引用计数减少到 0,内存就会被回收

shared_ptr<A> p3(new A());auto p4 = p3;

weak_ptr:

只观察指针,不会增加引用计数(弱引用),但在需要的时候,可以调用成员函数 lock(),获取 shared_ptr(强引用)

异常

stateDiagram-v2  direction BT  bad_alloc --> expcetion  runtime_error --> expcetion  logic_error --> expcetion  bad_cast --> expcetion  range_error --> runtime_error  overflow_error --> runtime_error  invalid_argument --> runtime_error  length_error --> runtime_error
try {    throw runtime_error("runtime_error");  }catch(const exception& e) {    cout << e.what();  }catch(const runtime_error& e) { // 只会按照顺序匹配,而不会根据异常继承树匹配最佳    cout << e.what();  }// noexcept 告知编译器该函数不会抛异常,可以做一些优化,但如果真的发生异常,程序直接崩掉int f() noexcept {  throw runtime_error("runtime_error");}

lambda

int n = 1;// = 捕获外层变量,值类型。 &捕获外层变量,引用类型auto f1 = [=](const int& a, const int& b) {  return a + b + n;};

标准库

字符串

字面量后缀:

明确地表示它是 string 字符串类型,而不是 C 字符串

using namespace std::literals::string_literals; //必须打开名字空间auto str = "hello"s;

原始字符串:

auto s = R"(\n123\n)"; // 输出\n123\n

字符串转换函数:

using namespace std;cout << s;cout << stoi("123");cout << stol("123");cout << stod("123.0");

字符串视图:内部只保存一个指针和长度,无论是拷贝,还是修改,都非常廉价

auto view = new string_view("aaa")

容器

// 动态数组vector<int> v(2);v.emplace_back(3);// 队列deque<int> d;d.emplace_back(4);// 集合set s = {7, 3, 9};// 散列表unordered_map<int, int> map;map.emplace(1,2);

迭代器:

容器一般都会提供 begin()、end() 成员函数,调用它们就可以得到表示两个端点的迭代器。迭代器和指针类似,可以前进和后退,但不能假设它一定支持“++”“--”操作符

for(auto it = v.begin(); it != v.end(); it++) {  cout << *it;}// 更加通用的迭代器操作for(auto it = v.begin(); it != v.end(); advance(it, distance(it, next(it)))) {  cout << *it;}

线程

static once_flag flag;        // 全局的初始化标志thread_local int n = 0; // 线程本地变量atomic<int> at = {0}; // 原子变量thread t1([&](){  n += 10;  cout << "t1, " << n;  at++;  // 只会被调用一次  call_once(flag, []{ cout << "Hello, once" << endl; });  cout << "t1, " << this_thread::get_id() << endl;});thread t2([](){  n += 20;  cout << "t2, " << n;  // 只会被调用一次  call_once(flag, []{ cout << "Hello, once" << endl; });  cout << "t2, " << this_thread::get_id() << endl;});t1.join();t2.join();// 异步执行代码块返回futureauto f = async([](){cout << "async" << endl;});f.wait();