首页 > 编程语言 >《C++ Primer》导学系列:第 13 章 - 拷贝控制

《C++ Primer》导学系列:第 13 章 - 拷贝控制

时间:2024-07-02 15:26:53浏览次数:3  
标签:运算符 13 C++ 导学 other 拷贝 构造函数 Example 赋值

13.1 拷贝、赋值与析构函数

拷贝控制是C++中类设计的重要组成部分,用于管理对象的复制、赋值和销毁过程。理解并正确实现拷贝控制函数(拷贝构造函数、拷贝赋值运算符和析构函数)对于编写健壮和高效的C++程序至关重要。

13.1.1 拷贝构造函数

拷贝构造函数用于创建对象的副本。它的声明形式如下:

ClassName(const ClassName &other);

当对象被复制时,拷贝构造函数会被调用。例如,当对象以值传递的方式作为参数传递或从函数返回时,拷贝构造函数将被调用。

示例代码

#include <iostream>

class Example {
public:
    Example(int v) : value(v) {}
    Example(const Example &other) : value(other.value) {
        std::cout << "Copy constructor called" << std::endl;
    }
    int getValue() const { return value; }
private:
    int value;
};

void printExample(Example e) {
    std::cout << "Value: " << e.getValue() << std::endl;
}

int main() {
    Example ex1(10);
    Example ex2 = ex1;  // 调用拷贝构造函数
    printExample(ex1);  // 调用拷贝构造函数
    return 0;
}

13.1.2 拷贝赋值运算符

拷贝赋值运算符用于将一个对象的值赋给另一个对象。它的声明形式如下:

ClassName& operator=(const ClassName &other);

拷贝赋值运算符通常用于对象已经存在的情况下进行赋值操作。

示例代码

#include <iostream>

class Example {
public:
    Example(int v) : value(v) {}
    Example(const Example &other) : value(other.value) {
        std::cout << "Copy constructor called" << std::endl;
    }
    Example& operator=(const Example &other) {
        if (this != &other) {
            value = other.value;
            std::cout << "Copy assignment operator called" << std::endl;
        }
        return *this;
    }
    int getValue() const { return value; }
private:
    int value;
};

int main() {
    Example ex1(10);
    Example ex2(20);
    ex2 = ex1;  // 调用拷贝赋值运算符
    std::cout << "ex2 value: " << ex2.getValue() << std::endl;
    return 0;
}

13.1.3 析构函数

析构函数用于释放对象使用的资源。它的声明形式如下:

~ClassName();

析构函数在对象的生命周期结束时被自动调用,例如当对象超出作用域或被显式删除时。

示例代码

#include <iostream>

class Example {
public:
    Example(int v) : value(v) {}
    ~Example() {
        std::cout << "Destructor called" << std::endl;
    }
    int getValue() const { return value; }
private:
    int value;
};

int main() {
    Example ex1(10);
    std::cout << "ex1 value: " << ex1.getValue() << std::endl;
    return 0;
}

13.1.4 三/五法则

C++中有一个重要的设计原则,称为“三/五法则”,意思是如果一个类定义了析构函数、拷贝构造函数或拷贝赋值运算符中的一个,那么它很可能需要定义所有这三个函数。随着C++11标准引入移动语义,这个原则扩展为“五法则”,包括移动构造函数和移动赋值运算符。

示例代码

#include <iostream>
#include <utility>

class Example {
public:
    Example(int v) : value(new int(v)) {}
    Example(const Example &other) : value(new int(*other.value)) {
        std::cout << "Copy constructor called" << std::endl;
    }
    Example& operator=(const Example &other) {
        if (this != &other) {
            delete value;
            value = new int(*other.value);
            std::cout << "Copy assignment operator called" << std::endl;
        }
        return *this;
    }
    ~Example() {
        delete value;
        std::cout << "Destructor called" << std::endl;
    }

    // C++11移动构造函数
    Example(Example &&other) noexcept : value(other.value) {
        other.value = nullptr;
        std::cout << "Move constructor called" << std::endl;
    }

    // C++11移动赋值运算符
    Example& operator=(Example &&other) noexcept {
        if (this != &other) {
            delete value;
            value = other.value;
            other.value = nullptr;
            std::cout << "Move assignment operator called" << std::endl;
        }
        return *this;
    }

    int getValue() const { return *value; }
private:
    int* value;
};

int main() {
    Example ex1(10);
    Example ex2 = std::move(ex1);  // 调用移动构造函数
    Example ex3(20);
    ex3 = std::move(ex2);  // 调用移动赋值运算符
    return 0;
}

13.1.5 阻止拷贝

在某些情况下,我们可能希望阻止对象被拷贝或赋值。可以通过将拷贝构造函数和拷贝赋值运算符声明为delete来实现这一点。

示例代码

class NonCopyable {
public:
    NonCopyable() = default;
    NonCopyable(const NonCopyable &) = delete;
    NonCopyable& operator=(const NonCopyable &) = delete;
};

int main() {
    NonCopyable obj1;
    // NonCopyable obj2 = obj1;  // 错误:拷贝构造函数被删除
    // NonCopyable obj3;
    // obj3 = obj1;  // 错误:拷贝赋值运算符被删除
    return 0;
}

重点与难点分析

重点

  1. 拷贝构造函数:了解何时会调用拷贝构造函数,掌握其实现方法。
  2. 拷贝赋值运算符:理解拷贝赋值运算符的使用场景和实现细节。
  3. 析构函数:熟悉析构函数的作用和调用时机。
  4. 三/五法则:掌握何时需要同时实现拷贝构造函数、拷贝赋值运算符和析构函数,理解移动语义下的五法则。
  5. 阻止拷贝:了解如何通过delete关键字阻止对象的拷贝和赋值。

难点

  1. 自我赋值检测:在拷贝赋值运算符中处理自我赋值的情况。
  2. 资源管理:确保在析构函数中正确释放资源,避免内存泄漏和其他资源管理问题。
  3. 编写健壮的拷贝控制函数:理解并正确实现拷贝控制函数,以确保对象在复制、赋值和销毁过程中行为一致且安全。
  4. 实现移动语义:正确实现移动构造函数和移动赋值运算符,提高程序性能。

练习题解析

  1. 练习13.1:编写一个类,包含拷贝构造函数、拷贝赋值运算符和析构函数,测试它们的调用情况。
    • 示例代码
#include <iostream>

class Example {
public:
    Example(int v) : value(v) {}
    Example(const Example &other) : value(other.value) {
        std::cout << "Copy constructor called" << std::endl;
    }
    Example& operator=(const Example &other) {
        if (this != &other) {
            value = other.value;
            std::cout << "Copy assignment operator called" << std::endl;
        }
        return *this;
    }
    ~Example() {
        std::cout << "Destructor called" << std::endl;
    }
    int getValue() const { return value; }
private:
    int value;
};

int main() {
    Example ex1(10);
    Example ex2 = ex1;  // 调用拷贝构造函数
    ex2 = ex1;          // 调用拷贝赋值运算符
    return 0;
}
  1. 练习13.2:修改练习13.1的类,使其包含一个指向动态内存的指针,确保拷贝控制函数正确管理该内存。
    • 示例代码
#include <iostream>

class Example {
public:
Example(int v) : value(new int(v)) {}
Example(const Example &other) : value(new int(*other.value)) {
    std::cout << "Copy constructor called" << std::endl;
}
Example& operator=(const Example &other) {
    if (this != &other) {
        delete value;
        value = new int(*other.value);
        std::cout << "Copy assignment operator called" << std::endl;
    }
    return *this;
}
~Example() {
    delete value;
    std::cout << "Destructor called" << std::endl;
}
int getValue() const { return *value; }
private:
int* value;
};

int main() {
    Example ex1(10);
    Example ex2 = ex1;  // 调用拷贝构造函数
    ex2 = ex1;          // 调用拷贝赋值运算符
    std::cout << "ex2 value: " << ex2.getValue() << std::endl;
    return 0;
}
  1. 练习13.3:编写一个类,包含移动构造函数和移动赋值运算符,测试它们的调用情况。
    • 示例代码
#include <iostream>

class Example {
public:
    Example(int v) : value(new int(v)) {}
    Example(const Example &other) : value(new int(*other.value)) {
        std::cout << "Copy constructor called" << std::endl;
    }
    Example& operator=(const Example &other) {
        if (this != &other) {
            delete value;
            value = new int(*other.value);
            std::cout << "Copy assignment operator called" << std::endl;
        }
        return *this;
    }
    Example(Example &&other) noexcept : value(other.value) {
        other.value = nullptr;
        std::cout << "Move constructor called" << std::endl;
    }
    Example& operator=(Example &&other) noexcept {
        if (this != &other) {
            delete value;
            value = other.value;
            other.value = nullptr;
            std::cout << "Move assignment operator called" << std::endl;
        }
        return *this;
    }
    ~Example() {
        delete value;
        std::cout << "Destructor called" << std::endl;
    }
    int getValue() const { return *value; }
private:
    int* value;
};

int main() {
    Example ex1(10);
    Example ex2 = std::move(ex1);  // 调用移动构造函数
    Example ex3(20);
    ex3 = std::move(ex2);          // 调用移动赋值运算符
    return 0;
}
  1. 练习13.4:编写一个类,阻止其对象的拷贝和赋值。
    • 示例代码
#include <iostream>

class NonCopyable {
public:
    NonCopyable() = default;
    NonCopyable(const NonCopyable &) = delete;
    NonCopyable& operator=(const NonCopyable &) = delete;
};

int main() {
    NonCopyable obj1;
    // NonCopyable obj2 = obj1;  // 错误:拷贝构造函数被删除
    // NonCopyable obj3;
    // obj3 = obj1;  // 错误:拷贝赋值运算符被删除
    return 0;
}

总结与提高

本节总结

  1. 掌握了拷贝控制的基本概念和操作,包括拷贝构造函数、拷贝赋值运算符和析构函数。
  2. 理解了拷贝控制函数在对象复制、赋值和销毁过程中的作用和调用时机。
  3. 学会了在类中正确实现拷贝控制函数,以确保对象在不同生命周期阶段的正确行为。
  4. 理解了“三/五法则”的重要性,学会了在类设计中同时实现必要的拷贝控制函数和移动语义。
  5. 掌握了如何通过delete关键字阻止对象的拷贝和赋值。

提高建议

  1. 多练习拷贝控制函数的实现:通过编写更多涉及拷贝控制的类,熟悉各种管理方法的用法,提高对拷贝控制的理解和实现能力。
  2. 深入理解资源管理的原理:通过阅读文档和相关书籍,深入理解资源管理的实现原理和使用场景,提高编写高效代码的能力。
  3. 优先使用智能指针:在实际项目中,尽量使用智能指针管理动态内存,以减少手动内存管理带来的错误,提高代码的可读性和可维护性。
  4. 优化移动语义:在类设计中,合理运用移动构造函数和移动赋值运算符,提高程序的性能和资源利用效率。

13.2 拷贝控制与资源管理

拷贝控制函数在管理资源时非常重要,特别是当类涉及动态内存分配或其他资源(如文件句柄、网络连接等)时。通过正确实现拷贝控制函数,可以确保资源在对象复制、赋值和销毁过程中得到正确的管理和释放。

13.2.1 拷贝控制与动态内存

在涉及动态内存的类中,拷贝构造函数、拷贝赋值运算符和析构函数的实现需要特别注意,以确保内存安全。

示例代码

#include <iostream>
#include <algorithm> // for std::copy

class DynamicArray {
public:
    DynamicArray(size_t size) : size(size), data(new int[size]()) {}
    DynamicArray(const DynamicArray &other) : size(other.size), data(new int[other.size]) {
        std::copy(other.data, other.data + other.size, data);
        std::cout << "Copy constructor called" << std::endl;
    }
    DynamicArray& operator=(const DynamicArray &other) {
        if (this != &other) {
    

标签:运算符,13,C++,导学,other,拷贝,构造函数,Example,赋值
From: https://blog.csdn.net/iShare_Carlos/article/details/140097649

相关文章

  • C++23特性一览
    NewlanguagefeaturesNewlanguagefeaturetestingmacrosExplicitobjectparameters,explicitobjectmemberfunctions(deducingthis)ifconsteval/ifnotconstevalMultidimensionalsubscriptoperator(e.g.v[1,3,7]=42;)staticoperator()static......
  • 最新版 SteinbergNuendo13.0.41 Win&Mac
    软件介绍2024.6.13官方最新发布WIN版本13.0.41,此版本为VR版本!资源包含6个版本。下载安装一个即可!新版本进行了大量的更新和修复,详情查看以下链接SteinbergNuendo是一款屡获殊荣的影视、游戏和沉浸式环绕声音频后期制作软件Nuendo在对白录音和编辑方面做了重大改进,为你的......
  • C++编译问题,解决arm下链接静态库,引起的relocation R_AARCH64_ADR_PREL_PG_HI21 agains
    显示的完整错误如下:relocationR_AARCH64_ADR_PREL_PG_HI21againstsymbol`ZN2c43yml9free_implEPvmS1'whichmaybindexternallycannotbeusedwhenmakingasharedobject;recompilewith-fPIC根据提示,在链接.a静态库时,应该在编译时加上参数-fPIC然而CMake文件中已......
  • 详解C++中的容器,其特点与常用方法
    1.容器的定义在C++中,容器的概念是指一种对象类型,它可以持有其他对象或指向其他对象的指针。这种对象类型在数据存储上提供了一种有效的方式来管理一组元素。容器在C++中通常是模板类的形式。一般来说,容器内的元素都是相同类型的。即如果该容器内存放的是int类型的数据,那么......
  • C++个人学习笔记,Typora编写
    第1章:杂叙1.名字空间namespacexxx在main函数中划分变量空间时,需要指定“xxx::a=1”2.倘若需要使用cin和cout,需要在main函数外使用usingstd::cin;或者从usingstd::cout;标准库中的名字都属于标准名字空间std3.变量存在的意义时为了方便管理内存空间4.程序块内定义内部变......
  • Qt/C++开发经验小技巧296-300
    使用QDir::setCurrent设置当前目录后,会影响程序中的所有相对目录的执行,导致可能的意外发生,一般相对目录都默认是可执行文件所在目录,所以如果程序中为了特殊处理临时调用了QDir::setCurrent设置过相对目录,建议处理完成以后立即切换回来。QDir::setCurrent("f:/");QImageimg(":......
  • C++:类与面向对象&static和this关键字&其他关键字
    类与面向对象struct和class(1)struct是C中用户自定义类型,主要功能是对功能相关数据的封装(2)struct不能直接封装函数,但可以通过封装函数指针来间接封装函数(3)struct就是class的初级阶段,class在struct基础上做了很多扩展,便有了面向对象访问权限(1)类是对数据(成员变......
  • 深入理解 C++11 多线程编程:从入门到实践
    C++多线程编程是指使用C++提供的多线程库来并行执行代码块,从而提高程序的性能和响应能力。C++11标准引入了多线程支持,使得在C++中进行多线程编程变得更加容易和直观。以下是C++多线程编程的基本知识,并附有例子代码。多线程的基本概念线程(Thread):线程是进程中的一个执......
  • P6754 [BalticOI 2013 Day1] Palindrome-Free Numbers
    数据范围一眼数位dp。关键条件为如果一个数字串的一个长度大于 11 的子串也为回文串的话,那么我们也定义这个数字串为回文串。仔细思考发现一旦两个连续的数相同(偶回文)或两个数隔一个数相同(奇回文)都是回文,所以要保证连续三个数不相同,记录前两位即可。注意事项:1.前导零不应为0......
  • C/C++ 赋值表达式注意事项
    在C/C++中,赋值表达式是基础且关键的一部分,它用于给变量赋值。理解和正确使用赋值表达式对于编写有效、可维护的代码至关重要。以下是一些关于C/C++赋值表达式的注意事项:赋值操作符:在C/C++中,赋值是通过赋值操作符=完成的。这意味着将右侧的值或表达式的结果赋给左侧的变......