C++ 总结 基本成员函数

欢迎访问我的博客首页


  类的构造函数和赋值运算符重载函数是常用且重要的两大类成员函数, 前者用于控制对象的初始化,后者用于控制对象的拷贝。本节介绍 11 个成员函数,包括 4 种构造函数、2 种赋值运算符重载函数、输入输出运算符重载函数、2 个取地址运算符函数、1 个析构函数。

1. 无参构造函数


  无参构造函数。如果我们不定义任何构造函数,编译器将合成一个无参构造函数,称为合成的构造函数。合成的构造函数按如下规则初始化类的数据成员:

  • 如果存在类内的初始值,用它来初始化成员。
  • 否则,默认初始化该成员。

2. 有参构造函数


  除了聚合类,如果我们想通过指定的参数初始化类,必须定义有参构造函数。可以使用直接初始化、列表初始化两种方式调用有参构造函数。

3. 拷贝构造函数


3.1 非 explict


  如果一个类没有移动操作(没有合成的移动操作,也没有显示定义的移动操作),类会使用对应的拷贝操作来代替移动操作。这是因为 const 引用可以绑定右值,即 T&& 类型隐式转换为 const T& 类型:

// const 引用绑定右值。
const int &r = i * 42;

  因此,explict 的拷贝构造函数无法代替移动构造函数。

3.2 合成的拷贝构造函数是删除的

  如果我们没有定义拷贝构造函数,编译器会为我们合成一个拷贝构造函数。如果我们定义了移动构造函数或(和)移动赋值运算符重载函数,有些编译器合成的拷贝构造函数是删除的。

3.3 合成的拷贝构造函数怎么拷贝


  合成的拷贝构造函数拷贝方法:

  • 类类型的成员:使用其拷贝构造函数。
  • 内置类型:直接拷贝。
  • 数组:逐元素拷贝。如果元素是类类型,则使用其拷贝构造函数拷贝。

  合成的拷贝操作只能进行浅拷贝,这很容易引起错误:

struct Example2 {
    Example2(int val) : val(val), ptr(new int(val)) {
    }
    ~Example2() {
        cout << "析构开始。类地址:" << this << ",指针地址:" << &ptr << ",待释放内存地址:" << ptr << "。" << endl;
        delete ptr;
        ptr = nullptr;
        cout << "析构完成。类地址:" << this << ",指针地址:" << &ptr << ",待释放内存地址:" << ptr << "。" << endl;
    }
    int val, *ptr;
};

int main() {
    Example2 ex1(1);
    Example2 ex2(ex1);
}

  运行上面的代码会报错如下。对象 ex2 是我们调用合成的拷贝构造函数创建的。因为合成的拷贝构造函数进行浅拷贝,所以 ex1 和 ex2 的指针指向同一个地址 0x560f0f10ceb0。

  同一作用域内,对象的析构顺序与创建顺序相反,即,先析构 ex2 再析构 ex1。ex2 的析构函数释放地址 0x560f0f10ceb0,ex1 的析构函数去释放地址 0x560f0f10ceb0 时就出现错误。

析构开始。类地址:0x7ffe407c1c70,指针地址:0x7ffe407c1c78,待释放内存地址:0x560f0f10ceb0。
析构完成。类地址:0x7ffe407c1c70,指针地址:0x7ffe407c1c78,待释放内存地址:0。
析构开始。类地址:0x7ffe407c1c60,指针地址:0x7ffe407c1c68,待释放内存地址:0x560f0f10ceb0。
free(): double free detected in tcache 2
已放弃 (核心已转储)
(base) jyy@pc:~/wor

4. 移动构造函数


4.1 合成的移动构造函数


  以下 3 个条件都成立时,编译器才会为我们合成移动操作(移动构造函数和移动赋值运算符重载函数):

  • 没有定义拷贝控制成员(拷贝构造函数、拷贝赋值运算符重载函数)。
  • 没有定义析构函数。
  • 类的每个 static 数据成员都可以移动。

  可移动的数据成员:

  • 内置类型。
  • 有移动操作的类类型。这种类有合成的移动操作或显示地定义了移动操作。

  下面的代码利用合成的移动构造函数实现移动构造。

struct Example2 {
    Example2(int val) : val(val), ptr(new int(val)) {
    }
    int val, *ptr;
};

int main() {
    Example2 ex1(1);
    cout << &ex1 << endl;    // 地址 1。
    cout << ex1.ptr << endl; // 地址 2。

    Example2 ex2 = std::move(ex1);
    cout << &ex2 << endl;    // 地址 3。
    cout << ex2.ptr << endl; // 地址 2。
}

4.2 std::move 匹配拷贝构造函数


  如果一个类没有移动操作(没有合成的移动构造函数和移动赋值运算符重载函数,也没有显示定义的移动构造函数和移动赋值运算符重载函数),类会使用对应的拷贝操作来代替移动操作。这是因为 const 引用可以绑定右值。

// const 引用绑定右值。
const int &r = i * 42;

  左值引用通过 & 获得,右值引用通过 && 获得。因为 const 引用可以绑定右值,所以没有移动构造函数时,形参为 const 引用的拷贝构造函数会代替移动构造函数被 std::move 调用。

struct Example2 {
    Example2(int val) : val(val), ptr(new int(val)) {
    }
    Example2(const Example2& e) : val(e.val), ptr(nullptr) {
        cout << "拷贝构造函数(复制构造函数)。" << endl;
        if (e.ptr) {
            ptr = new int;
            memcpy(ptr, e.ptr, sizeof(int));
        }
    }
    ~Example2() {
        delete ptr;
        ptr = nullptr;
    }
    int val, *ptr;
};

int main() {
    Example2 ex1(1);
    cout << &ex1 << endl;    // 地址 1。
    cout << ex1.ptr << endl; // 地址 2。

    Example2 ex2 = std::move(ex1);
    cout << &ex2 << endl;    // 地址 3。
    cout << ex2.ptr << endl; // 地址 4。
}

4.2 支持移动构造


  如果编译器不为我们合成移动操作,移动操作就会被拷贝操作代替。因此,如果没有合成的移动操作且我们不想让拷贝操作代替移动操作,就必须定义移动构造函数。

5. 拷贝赋值运算符重载函数

6. 移动赋值运算符重载函数

7. 输入输出运算符重载函数

8. 取地址操作符重载函数

9. 析构函数

10. 合成的成员函数


  如果我们不声明自己的拷贝构造函数或拷贝赋值运算符,编译器总会为我们合成这些操作。编译器合成的这两个拷贝操作,要么被定义为逐成员拷贝,要么被定义为对象赋值,要么被定义为删除的函数。

  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值