C++

作者: Catherin_gao | 来源:发表于2021-01-15 11:22 被阅读0次

[Toc]

构造函数

注意

  • 使用没有形参的构造函数时,定义对象时不需要加括号,使用有形参的构造函数,如果形参全部有默认值,也可以不传参数,也是不用加括号
Dog* a = new Dog;
Dog* b = new Dog(); // 上述这两种方式均可
  • 类成员有指针变量时,通过拷贝构造给同类的对象初始化可以考虑使用深拷贝避免空间的重复释放,若没有指针变量使用浅拷贝就可以满足要求
  • 类中包含以下成员,一定要放在初始化列表位置进行初始化:
    • 引用成员变量
    • const成员变量
    • 类类型成员(该类有非缺省的构造函数)

构造函数为什么不能是虚函数

  • 存储空间角度:虚函数对应一个vtable,vtable存储于对象的内存空间

    若构造函数是虚的,则需要通过 vtable来调用,若对象还未实例化,即内存空间还没有,无法找到vtable

  • 使用角度:虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。

    构造函数本身就是要初始化实例,那使用虚函数就没有实际意义

  • 从实际含义上看,在调用构造函数时还不能确定对象的真实类型(因为子类会调父类的构造函数);而且构造函数的作用是提供初始化,在对象生命期只执行一次,不是对象的动态行为,也没有太大的必要成为虚函数

拷贝构造函数

以下情况都会调用拷贝构造函数:

  • 一个对象以值传递的方式传入函数体
  • 一个对象以值传递的方式从函数返回
  • 一个对象需要通过另外一个对象进行初始化。

析构函数

继承与多态

  • 派生类中包含基类中多有的成员,只是根据继承方式的不同,部分成员的访问权限不同。例如:在派生类中无法访问父类中 private的变量
  • 不管基类以何种方式被继承,基类的私有成员,仍然保有其私有性,被派生的子类不能访问基类的私有成员
  • 在C++中派生类可以同时从多个基类继承,Java 不充许这种多重继承,当继承多个基类时,使用逗号将基类隔开

特殊的用法与关键字

explict

class CxString  // 没有使用explicit关键字的类声明, 即默认为隐式声明  
{  
public:  
    char*_pstr;  
    int _size;  
    CxString(int size)  
    {  
        _size = size;                // string的预设大小  
        _pstr = malloc(size + 1);    // 分配string的内存  
        memset(_pstr, 0, size + 1);  
    }  
    CxString(const char *p)  
    {  
        int size = strlen(p);  
        _pstr = malloc(size + 1);    // 分配string的内存  
        strcpy(_pstr, p);            // 复制字符串  
        _size = strlen(_pstr);  
    }  
    // 析构函数这里不讨论, 省略...  
};  
  
    // 下面是调用:  
  
    CxString string1(24);     // 这样是OK的, 为CxString预分配24字节的大小的内存  
    CxString string2 = 10;    // 这样是OK的, 为CxString预分配10字节的大小的内存  
    CxString string3;         // 这样是不行的, 因为没有默认构造函数, 错误为: “CxString”: 没有合适的默认构造函数可用  
    CxString string4("aaaa"); // 这样是OK的  
    CxString string5 = "bbb"; // 这样也是OK的, 调用的是CxString(const char *p)  
    CxString string6 = 'c';   // 这样也是OK的, 其实调用的是CxString(int size), 且size等于'c'的ascii码  
    string1 = 2;              // 这样也是OK的, 为CxString预分配2字节的大小的内存 (利用隐式转换)
    string2 = 3;              // 这样也是OK的, 为CxString预分配3字节的大小的内存  
    string3 = string1;        // 这样也是OK的, 至少编译是没问题的, 但是如果析构函数里用free释放_pstr内存指针的时候可能会报错, 完整的代码必须重载运算符"=", 并在其中处理内存释放

// -------------------------------//
class CxString  // 使用关键字explicit的类声明, 显示转换  
{  
public:  
    char *_pstr;  
    int _size;  
    explicit CxString(int size)  
    {  
        _size = size;  
        // 代码同上, 省略...  
    }  
    CxString(const char *p)  
    {  
        // 代码同上, 省略...  
    }  
};  
  
    // 下面是调用:  
  
    CxString string1(24);     // 这样是OK的  
    CxString string2 = 10;    // 这样是不行的, 因为explicit关键字取消了隐式转换  
    CxString string3;         // 这样是不行的, 因为没有默认构造函数  
    CxString string4("aaaa"); // 这样是OK的  
    CxString string5 = "bbb"; // 这样也是OK的, 调用的是CxString(const char *p)  
    CxString string6 = 'c';   // 这样是不行的, 其实调用的是CxString(int size), 且size等于'c'的ascii码, 但explicit关键字取消了隐式转换  
    string1 = 2;              // 这样也是不行的, 因为取消了隐式转换  
    string2 = 3;              // 这样也是不行的, 因为取消了隐式转换  
    string3 = string1;        // 这样也是不行的, 因为取消了隐式转换, 除非类实现操作符"="的重载 
  • explicit关键字只需用于类内的单参数构造函数前面。由于无参数的构造函数和多参数的构造函数总是显示调用,这种情况在构造函数前加explicit无意义
  • google的c++规范中提到explicit的优点是可以避免不合时宜的类型变换,缺点无。所以google约定所有单参数的构造函数都必须是显示的,只有极少数情况下拷贝构造函数可以不声明称explicit。例如作为其他类的透明包装器的类
  • effective c++中说:被声明为explicit的构造函数通常比其non-explicit兄弟更受欢迎。因为它们禁止编译器执行非预期(往往也不被期望)的类型转换。除非我有一个好理由允许构造函数被用于隐式类型转换,否则我会把它声明为explicit,鼓励大家遵循相同的政策。

final

详见[final](#final 禁止)

override

参考资料: https://www.cnblogs.com/xinxue/p/5471708.html

智能指针

  • weak_ptr的作用 / weak_ptr的主要接口使用
  • unique_ptr的删除器
  • shared_ptr等智能指针到底存放着什么?该怎么使用
  • 回调函数(分别有几种,各自是什么)
  • 函数指针 / 仿函数 / lambda函数的应用
  • decltype / typedef
  • int arr[10] 该数组的类型为 int[10]

shared_ptr

多个指针指向相同对象,当最后一个shared_ptr离开作用域的时候,内存才会释放对象

shared_ptr<int> sptr1( new int );  //先申请数据内存,再申请内存块
shared_ptr<int> sptr1 = make_shared<int>(“Hello”);  //一次申请,但weak_ptr会锁内存

接口

  1. get(): 获取shared_ptr绑定的资源
  2. unique():判断是否唯一指向当前内存的shared_ptr
  3. reset():释放关联内存块的所有权,如果是最后一个指向该资源的指针,释放该内存
  4. operation bool :判断当前的shared_ptr是否指向一个内存块
  5. swap:交换指针
  6. use_count:返回引用计数

注意

  • 避免从naked pointer创建shared_ptr(不同的shared_ptr指向一个资源)
  • 循环引用问题
  • 多线程访问同一个shared_ptr需要加锁,shared_ptr引用计数是安全的,对象读写操作不是。

weak_ptr

指向shared_ptr对象,赋值不会增加引用计数

接口

  1. expire():如果use_count()为零,返回true,否则返回false
  2. lock():如果expire为true,返回空的shared_ptr,否则返回一个指向该弱指针的shared_ptr
  3. use_count():共享的shared_ptr数量

unique_ptr

unique_ptr<int[ ]> uptr( new int[5] ); //创建指向数组类型的unique_ptr

注意

  • 不支持直接拷贝,但支持移动语义
  • release()只释放所有权,reset()释放所有权和资源

可调用对象

func(param1, param2);

这儿使用func作为函数调用名,param1param2作为函数参数,在C++中func的类型可为:

  • 普通函数
  • 类成员函数
  • 类静态函数
  • 仿函数
  • 函数指针
  • lambda表达式
  • std::function

类成员函数

在一个类中定义的函数一般称为类的方法,分为成员方法和静态方法,区别是成员函数的参数类表隐藏着类的this指针

# include <iostream>

class Calcu {
  public:
    int base = 20;
    int class_func_add(const int a, const int b) const { return this->base + a + b; }
    static int class_static_func_add(const int a, const int b) const { return a + b; }
};
int main() {
  Calcu obj;
  int a = 10;
  int b = 20;
  
  // 类成员函数的调用
  obj.class_func_add(a, b);
  // 类静态方法的调用
  obj.class_static_func_add(a, b);
  Calcu::class_static_func_add(a, b);
  getchar();
  return 0;
}

仿函数

仿函数是使用类来模仿函数的行为,我们只要重载一个类的operator()方法,即可像调用一个函数一样调用类。

class ImitateAdd {
  public:
    int operator() (const int a, const int b) const { return a + b; }
};

int main(){
  ImitateAdd imitate;
  imitate(5, 10);
  getchar();
  return 0;
}

函数指针

函数指针可以理解为函数的指针,可以将函数名赋值给相同类型的函数指针,通过可调用函数指针实现函数的调用。

函数指针是标准C/C++的回调函数的使用解决方案,本身提供了很大的灵活性。

#include <iostream>

int max(int x, int y) { return x >=y ? x : y; }
int min(int x, int y) { return x <=y ? x : y; }
int add(int x, int y) { return x + y; }

int compute_2d(int x, int y, int(*compute)(int, int)) {
  return compute(x, y);
}
int main() {
  int x = 2;
  int y = 5;
  std::cout << compute_2d(x, y, max);
  std::cout <<compute_2d(x, y, min);
  std::cout <<compute_2d(x, y, add);
  getchar();
  return 0;
}

lambda表达式

lambda表达式:[capture](parameters) mutable throw() -> return type {body}

  • capture clause指的的捕获的变量
  • mutable指的是捕获的变量是否可以修改
  • throw()为异常设定

外部变量捕获的原则

  • 默认情况下,捕获字段为[]时,lambda表达式不能访问任何外部变量,即表达式的函数内无法访问当前作用域下的变量

  • [&]表示按照引用的方式访问外部变量,可以修改变量; [=]表示按照值传递(拷贝)的方式访问外部变量,无法修改变量

    特别注意:

  • 变化的捕获是发生在lambda表达式的声明之时,如果是用值传递的方式捕获,即使之后变量的值发生变化,lambda表达式也不会感知,仍会用最开始的值。

  • 如果想要使用外部变量的最新值就必须使用引用的捕获方式,但也需要当心变量的生命周期,防止引用失效。

[ ]       :无捕获,函数体内不能访问任何外部变量 
[=]       :以值(拷贝)的方式捕获所有外部变量,函数体内可以访问,但是不能修改。
[&]       :以引用的方式捕获所有外部变量,函数体内可以访问并修改(需要当心无效的引用);
[var]     :以值(拷贝)的方式捕获某个外部变量,函数体可以访问但不能修改。
[&var]    :以引用的方式获取某个外部变量,函数体可以访问并修改
[this]    :捕获this指针,可以访问类的成员变量和函数,
[=,&var]  :引用捕获变量var,其他外部变量使用值捕获。
[&,var]   :只捕获变量var,其他外部变量使用引用捕获。
/////////////////////////////////////
auto f4 = [x,&y](){ y += x; };     //以值方式捕获x,以引用方式捕获y,y可以修改
auto f5 = [&,y](){ x += y;};       //以引用方式捕获y之外所有变量,y不能修改
auto f6 = [&](){ y += ++x;};       //以引用方式捕获所有变量,可以修改
f4();                              //x,y均是0,运算后y仍然是0;
f5();                              //y是0;引用捕获的x是1,运算后x仍然为1;
f6();                              //x,y均引用捕获,运算后x,y均是2

std::function函数包装

类模版std::function是一种通用、多态的函数封装。通过std::function对C++中各种可调用实体(普通函数、Lambda表达式、函数指针、仿函数、类成员函数、静态成员函数、以及其它函数对象等)的封装,形成一个新的可调用的std::function对象;让我们不再纠结那么多的可调用实体。一切变的简单粗暴。

示例:

#include <iostream>
#include <functional>

std::function<int(int, int)> SumFunction;

// 普通函数
int func_sum(int a, int b) {
    return a + b;
}

class Calcu {
public:
    int base = 20;
    // 类的成员方法,参数包含this指针
    int class_func_sum(const int a, const int b) const { return this->base + a + b; };
    // 类的静态成员方法,不包含this指针
    static int class_static_func_sum(const int a, const int b) { return a + b; };
};

// 仿函数
class ImitateAdd {
public:
    int operator()(const int a, const int b) const { return a + b; };
};

// lambda函数
auto lambda_func_sum = [](int a, int b) -> int { return a + b; };

// 函数指针
int (*func_pointer)(int, int);

int main(void) 
{
    int x = 2; 
    int y = 5;

    // 普通函数
    SumFunction = func_sum;
    int sum = SumFunction(x, y);
    std::cout << "func_sum:" << sum << std::endl;

    // 类成员函数
    Calcu obj;
    SumFunction = std::bind(&Calcu::class_func_sum, obj, 
        std::placeholders::_1, std::placeholders::_2); // 绑定this对象
    sum = SumFunction(x, y);
    std::cout << "Calcu::class_func_sum:" << sum << std::endl;

    // 类静态函数
    SumFunction = Calcu::class_static_func_sum;
    sum = SumFunction(x, y);
    std::cout << "Calcu::class_static_func_sum:" << sum << std::endl;

    // lambda函数
    SumFunction = lambda_func_sum;
    sum = SumFunction(x, y);
    std::cout << "lambda_func_sum:" << sum << std::endl;

    // 带捕获的lambda函数
    int base = 10;
    auto lambda_func_with_capture_sum = [&base](int x, int y)->int { return x + y + base; };
    SumFunction = lambda_func_with_capture_sum;
    sum = SumFunction(x, y);
    std::cout << "lambda_func_with_capture_sum:" << sum << std::endl;

    // 仿函数
    ImitateAdd imitate;
    SumFunction = imitate;
    sum = SumFunction(x, y);
    std::cout << "imitate func:" << sum << std::endl;

    // 函数指针
    func_pointer = func_sum;
    SumFunction = func_pointer;
    sum = SumFunction(x, y);
    std::cout << "function pointer:" << sum << std::endl;

    getchar();
    return 0;
}

std::bind

std::bind函数将可调用对象(用法中所述6类)和可调用对象的参数进行绑定,返回新的可调用对象(std::function类型,参数列表可能改变),返回的新的std::function可调用对象的参数列表根据bind函数实参中std::placeholders::_x(x为0, 1....这样的数字)从小到大对应的参数确定。

  • std::bind绑定普通函数

    std:bind的第一个参数是函数名,若普通函数作为实参时,会隐式的转换为函数指针。

double my_divide (double x, double y) {return x/y;}
auto fn_half = std::bind (my_divide,_1,2);
// 等同于
//auto fn_half = std::bind (&my_divide,_1,2);
std::cout << fn_half(10) << '\n';                        // 5
  • std::bind绑定类成员函数

    • bind绑定类成员函数时,第一个参数表示对象的成员函数的指针,第二个参数表示对象的地址。

    • 必须显示的指定类成员函数的指针,因为编译器不会将对象的成员函数隐式转换成函数指针

    • 使用对象成员函数的指针时,必须要知道该指针属于哪个对象,因此第二个参数为对象的地址

      class Foo {
        public:
          void print_sum(int n1, int n2) {
              std::cout << n1+n2 << '\n';
          }
          int data = 10;
      };
      int main() {
          Foo foo;
          auto f = std::bind(&Foo::print_sum, &foo, 95, std::placeholders::_1);
          f(5); // 100
      }
      
  • std::bind绑定参数引用

    • 默认情况下,bind的那些不是占位符的参数被拷贝到bind返回的可调用对象中。
    • 若想一些参数通过引用的方式传递,或是要绑定参数的类型无法拷贝时,可以使用ref()绑定一个参数的引用
    #include <iostream>
    #incldue <functional>
    #include <string>
    #include <vector>
    #include <sstream>
    #include <algorithm>
    
    std::ostream & print(std::ostream &os, const std::string& s, char c) {
        os << s << c;
        return os;
    }
    
    int main() {
        std::vector<std::string> words{"helo", "world", "this", "is", "C++11"};
        std::ostringstream os;
        char c = ' ';
        std::for_each(words.begin(), words.end(), 
                       [&os, c](const std::string & s){os << s << c;} );
        std::cout << os.str() << std::endl;
    
        std::ostringstream os1;
        // ostream不能拷贝,若希望传递给bind一个对象,
        // 而不拷贝它,就必须使用标准库提供的ref函数
        std::for_each(words.begin(), words.end(),
                       std::bind(print, ref(os1), std::placeholders::_1, c));
        std::cout << os1.str() << std::endl;
    }
    
    • 指向成员函数的指针
    #include <iostream>
    struct Foo {
        int value;
        void f() { std::cout << "f(" << this->value << ")\n"; }
        void g() { std::cout << "g(" << this->value << ")\n"; }
    };
    void apply(Foo* foo1, Foo* foo2, void (Foo::*fun)()) {
        (foo1->*fun)();  // call fun on the object foo1
        (foo2->*fun)();  // call fun on the object foo2
    }
    int main() {
        Foo foo1{1};
        Foo foo2{2};
        apply(&foo1, &foo2, &Foo::f);
        apply(&foo1, &foo2, &Foo::g);
    }
    
    • 成员函数指针的定义:void (Foo::*fun)(),调用是传递的实参:&Foo::f

    • fun为类成员函数指针,所以调用是要通过解引用的方式获取成员函数*fun ,即(foo1->*fun)()

参考:

link_1 / link_2

各种对比 && 冷门知识

typename的具体详述

typename详情介绍 && 历史演变

打印变量的类型

std::cout << typeid(var).name() << std::endl; // var是想要确定类型的变量

decltype详述

decltype是用于生成变量名或表达式的类型,其结果是可预测的。与auto相比,decltype作用于变量名或表达式只是重复了一次变量名或表达式的确切类型。

C++11中,decltype的主要用于声明模板函数,此模板函数的返回值类型依赖于其参数类型。

const int i = 0;                         // decltype(i) 为 const int
bool f(const Widget& w);                 // decltype(w) 为 const Widget&
                                         // decltype(f) 为 bool(const Widget&)
struct Point {
    int x, y;                            // decltype(Point::x) 为 int
};                                       // decltype(Point::y) 为 int
Widget w;                                // decltype(w) 为 Widget
if (f(w)) …                              // decltype(f(w)) 为 bool

template<typename T>                     //  std::vector 的简易实现
class vector { 
public:
…
T& operator[](std::size_t index);
…
};
vector<int> v;                           // decltype(v) 为 vector<int> 
…
if (v[0] == 0) …                         // decltype(v[0]) 为 int&

参考:

文档1 文档2

拷贝

位拷贝拷贝的是地址,值拷贝拷贝的是内容

浅拷贝:位拷贝,拷贝构造函数,赋值重载

  • 多个对象共用同一块资源,同一块资源释放多次,崩溃或者内存泄漏

  • 上述错误一般多发生于指针类型上,会造成多个对象指向同一个内存

深拷贝:每个对象共同拥有自己的资源,必须显式提供拷贝构造函数和赋值运算符。

STL

heap相关

make_heap(),  pop_heap(),  push_heap(),  sort_heap(),  is_heap; 

is_heap()

原型如下 :

bool is_heap(iterator start, iterator end); 
  • 判断迭代器[start, end] 区间类的元素是否构成一个堆. 是返回true ,否则返回 false.
bool is_heap(iterator start, iterator end, StrictWeakOrdering cmp);
  • 判断迭代器[start, end] 区间类的元素在cmp条件下是否构成一个堆. 是返回true ,否则返回 false.

make_heap()

void make_heap( random_access_iterator start, random_access_iterator end );
void make_heap( random_access_iterator start, random_access_iterator end, StrictWeakOrdering cmp ); 
  • 以 迭代器[start , end] 区间内的元素生成一个堆. 默认使用 元素类型 的 < 操作符 进行判断堆的类型, 因此生成的是大顶堆 .

  • 当使用了 版本2时, 系统使用 用户定义的 cmp 函数来构建一个堆

  • 值得注意的是, make_heap 改变了 迭代器所指向的 容器 的值.

pop_heap()

 void pop_heap( random_access_iterator start, random_access_iterator end );
 void pop_heap( random_access_iterator start, random_access_iterator end, StrictWeakOrdering cmp );
  • pop_heap() 并不是真的把最大(最小)的元素从堆中弹出来. 而是重新排序堆. 它把首元素和末元素交换,然后将[first,last-1)的数据再做成一个堆。 此时, 原来的 首元素 位于迭代器 end-1 的位置, 它已不再属于堆的一员!

  • 如果使用了 版本2 , 在交换了 首元素和末元素后 ,使用 cmp 规则 重新构建一个堆.

push_heap()

void push_heap( random_access_iterator start, random_access_iterator end ); 
void push_heap( random_access_iterator start, random_access_iterator end, StrictWeakOrdering cmp ); 
  • 算法假设迭代器区间[start, end-1)内的元素已经是一个有效堆, 然后把 end-1 迭代器所指元素加入堆.

  • 如果使用了 cmp 参数, 将使用 cmp 规则构建堆.

sort_heap()

void sort_heap (random_access_iterator start, random_access_iterator end);
void sort_heap (random_access_iterator start, random_access_iterator end, StrictWeakOrdering cmp); 
  • 堆结构被完全破坏, 相当于对元素进行排序, 效果和排序算法类似.

  • 如果使用了 cmp 参数, 将使用 cmp 规则排序堆.

C++11新特性

初始化列表

数组之外,STL容器和自定义数据类型也支持初始化列表,而且格式统一

initializer_list是轻量级的容器,只有begin(),end(),size(),只能被整体初始化,要求内部类型一致。

初始化列表可以阻止类型收窄,即从高精度到低精度的隐式转化

#include <initializer_list>
std::map<int, std::string> m = { {1, "a"}, {2, "b"} }; //STL容器

class A {
public:
    A(const std::initializer_list<int>& items): m_items(items){}
private:
    std::vector<int> m_items;
};

A a1{ 1, 2, 3 };  //自定义数据类型

类型推导

auto: 主要用于容器迭代器,也可以推导函数返回类型

  • 当不声明为指针或引用时,推导结果会抛弃引用和volatile/const
  • 声明为指针或引用类型时,会保留volatile/const
  • 隐式类型定义的类型推导发生在编译器,而不像python发生在运行期

decltype: 推导表达式的类型

template<typename T, typename U>
auto add(T x, U y) -> decltype(x+y) {  //拖尾返回类型
    return x+y;
}

template<typename T, typename U>  //c++14中一般函数允许返回值推导
auto add(T x, U y) {
    return x+y;
}

空指针

NULL实际上是一个为0的int值

nullptr类型为nullptr_t

//f(NULL)调用,引发歧义,f(nullptr)调用
void f(int i) {}
void f(int* arr) {}

强枚举类型

enum class Direction {  //关键字从enum变成enum class
    Left, Right
};
enum class Answer {
    Right, Wrong
};
auto a = Direction::Left;  //引用时必须加上枚举名称,枚举值不再是全局的
auto b = Answer::Right;

if (a == b)
    std::cout << "a == b" << std::endl;
else
    std::cout << "a != b" << std::endl;

using关键字

取代typedef定义别名

using cbyte = char

定义模板别名,typedef实现较为麻烦

template<class T>
using Tlist = std::list<T>;
using Tlist = std::list<char>;

在子类中引用父类成员

class Base
{
    public:
        void func() { // some code}
        int func(int n) { // some code}
}
 
class Sub : public Base
{
    public:
        using Base::func;
        void func() { // some code}      
}
 
int main()
{
    Sub s;
    s.func();
    s.func(1); // Success!
}

final 禁止

禁止虚函数被重写

class A {
public:
    virtual void f1() final {}
};

class B : public A {
    virtual void f1() {}  //报错
};

禁止类被继承

class A final {
};

class B : public A {  //报错
};

override显示声明

class A {
public:
   virtual void f1() const {}
};

class B : public A {
   virtual void f1() override {} //编译器会报错,不加override会认为子类新增函数f1
};

详情可参见override

default默认构造函数

当自定义构造函数时,编译器不会自动生成无参构造函数,可以使用default关键字强制要求生成。

class A {
public:
    A(int i) {}
    A() = default;
};

delete 禁止函数调用

隐藏函数不必声明为private,可以使用delete关键字


随机数生成器

控制范围的均匀分布,不需要初始化种子

#include <random>
#include <iostream>
using namespace std;
int main()
{
    mt19937 mt(random_device{}());
    cout<<mt()%100;
}

字符串数值类型转换

数值 ----> 字符串

std::string to_string(int value);  //支持long,double,unsigned等其他数据类型

字符串 ----> 数值

stoi、stol、stoul、stoll、stoull、stof、stod、stold函数

委托构造函数

避免有多个参数表不同但是逻辑相近(或有公共部分)的构造函数的时候的代码重复

class A
{
    int i;
    double d;
public:
    A(int x1,double x2) :i(x1),d(x2){
        //do something here
    }
    A():A(0,0){}
    A(int i):A(i,0) {}
};

移动语义

std::move将一个左值强制转化为右值引用,对象的状态或者所有权从一个对象转移到另一个对象,没有内存搬迁或者内存拷贝

  • 左值是指表达式结束后依然存在的持久化对象
  • 右值是指表达式结束时就不再存在的临时对象
std::string str = "Hello";
std::vector<std::string> v;
//调用常规的拷贝构造函数,新建字符数组,拷贝数据
v.push_back(str);
//调用移动构造函数,无内存的额外创建,str在move之后为空
v.push_back(std::move(str));

noexecpt

c++的异常处理是在运行时而不是编译时检测,编译器会生成额外的代码。

noexcept告诉编译器函数不会发生异常,但如果发生异常,直接终止程序。

void swap(Type& x, Type& y) noexcept{  //整个函数都不会发生异常
    x.swap(y);  
}
void swap(Type& x, Type& y) noexcept(noexcept(x.swap(y))){  //x.swap(y)不发生异常,那么函数就不会异常
    x.swap(y);
}

鼓励使用noexcept的情形:

  • 移动构造函数
  • 移动分配函数
  • 析构函数

引用Effective Modern c++的一句话描述"noexcept is particularly valuable for the move operations, swap, memory deallocation functions, and destructors."

相关文章

网友评论

      本文标题:C++

      本文链接:https://www.haomeiwen.com/subject/ftvwaktx.html