首页IT科技拷贝构造函数原理(C++:拷贝构造函数)

拷贝构造函数原理(C++:拷贝构造函数)

时间2025-05-03 20:54:51分类IT科技浏览3384
导读:1. 拷贝和拷贝构造函数 拷贝和复制是一个意思,对应的英文单词都是copy。对于计算机来说,拷贝是指用一份原有的、已经存在的数据创建出一份新的数据,最终的结果是多了一份相同的数据。例如,将 Word 文档拷贝到U盘去复印店打印,将 D 盘的图片拷贝到桌面以方便浏览,将重要的文件上传到百度网盘以防止丢失等,都是「创建...

1. 拷贝和拷贝构造函数

拷贝和复制是一个意思           ,对应的英文单词都是copy           。对于计算机来说                ,拷贝是指用一份原有的           、已经存在的数据创建出一份新的数据    ,最终的结果是多了一份相同的数据                。例如        ,将 Word 文档拷贝到U盘去复印店打印                 ,将 D 盘的图片拷贝到桌面以方便浏览      ,将重要的文件上传到百度网盘以防止丢失等     ,都是「创建一份新数据」的意思    。

在 C++ 中                  ,拷贝并没有脱离它本来的含义         ,只是将这个含义进行了“特化          ”  ,是指用已经存在的对象创建出一个新的对象        。从本质上讲                 ,对象也是一份数据            ,因为它会占用内存                 。严格来说,对象的创建包括两个阶段              ,首先要分配内存空间               ,然后再进行初始化:

分配内存很好理解  ,就是在堆区                、栈区或者全局数据区留出足够多的字节      。这个时候的内存还比较“原始               ”           ,没有被“教化      ”                ,它所包含的数据一般是零值或者随机值    ,没有实际的意义     。 初始化就是首次对内存赋值        ,让它的数据有意义                  。注意是首次赋值                 ,再次赋值不叫初始化         。初始化的时候还可以为对象分配其他的资源(打开文件    、连接网络        、动态分配内存等)      ,或者提前进行一些计算(根据价格和数量计算出总价                 、根据长度和宽度计算出矩形的面积等)等  。说白了     ,初始化就是调用构造函数                 。

很明显                  ,这里所说的拷贝是在初始化阶段进行的         ,也就是用其它对象的数据来初始化新对象的内存            。那么  ,如何用拷贝的方式来初始化一个对象呢?其实这样的例子比比皆是                 ,string 类就是一个典型的例子。

#include <iostream> #include <string> using namespace std; void func(string str){ cout<<str<<endl; } int main(){ string s1 = "http://c.biancheng.net"; string s2(s1); string s3 = s1; string s4 = s1 + " " + s2; func(s1); cout<<s1<<endl<<s2<<endl<<s3<<endl<<s4<<endl; return 0; }

运行结果:

http://c.biancheng.net http://c.biancheng.net http://c.biancheng.net http://c.biancheng.net http://c.biancheng.net http://c.biancheng.net

s1      、s2     、s3                  、s4 以及 func() 的形参 str            ,都是使用拷贝的方式来初始化的              。

对于 s1,表面上看起来是将一个字符串直接赋值给了 s1              ,实际上在内部进行了类型转换               ,将 const char * 类型转换为 string 类型后才赋值的  ,这其实涉及到C++转换构造函数的知识               。s4 也是类似的道理  。

对于 s1         、s2  、s3                 、s4           ,都是将其它对象的数据拷贝给当前对象                ,以完成当前对象的初始化           。

对于 func() 的形参 str    ,其实在定义时就为它分配了内存        ,但是此时并没有初始化                 ,只有等到调用 func() 时      ,才会将其它对象的数据拷贝给 str 以完成初始化                。

当以拷贝的方式初始化一个对象时     ,会调用一个特殊的构造函数                  ,就是拷贝构造函数(Copy Constructor)    。

下面的例子演示了拷贝构造函数的定义和使用:

#include <iostream> #include <string> using namespace std; class Student{ public: Student(string name = "", int age = 0, float score = 0.0f); //普通构造函数 Student(const Student &stu); //拷贝构造函数(声明) public: void display(); private: string m_name; int m_age; float m_score; }; Student::Student(string name, int age, float score): m_name(name), m_age(age), m_score(score){ } //拷贝构造函数(定义) Student::Student(const Student &stu){ this->m_name = stu.m_name; this->m_age = stu.m_age; this->m_score = stu.m_score; cout<<"Copy constructor was called."<<endl; } void Student::display(){ cout<<m_name<<"的年龄是"<<m_age<<"         ,成绩是"<<m_score<<endl; } int main(){ Student stu1("小明", 16, 90.5); Student stu2 = stu1; //调用拷贝构造函数 Student stu3(stu1); //调用拷贝构造函数 stu1.display(); stu2.display(); stu3.display(); return 0; }

运行结果:

Copy constructor was called. Copy constructor was called. 小明的年龄是16  ,成绩是90.5 小明的年龄是16                 ,成绩是90.5 小明的年龄是16            ,成绩是90.5

第 8 行是拷贝构造函数的声明,第 20 行是拷贝构造函数的定义        。拷贝构造函数只有一个参数              ,它的类型是当前类的引用               ,而且一般都是 const 引用                 。

1.1 为什么必须是当前类的引用呢?

如果拷贝构造函数的参数不是当前类的引用  ,而是当前类的对象           ,那么在调用拷贝构造函数时                ,会将另外一个对象直接传递给形参    ,这本身就是一次拷贝        ,会再次调用拷贝构造函数                 ,然后又将一个对象直接传递给了形参      ,将继续调用拷贝构造函数……这个过程会一直持续下去     ,没有尽头                  ,陷入死循环      。

只有当参数是当前类的引用时         ,才不会导致再次调用拷贝构造函数  ,这不仅是逻辑上的要求                 ,也是 C++ 语法的要求     。

1.2 为什么是 const 引用呢?

拷贝构造函数的目的是用其它对象的数据来初始化当前对象            ,并没有期望更改其它对象的数据,添加 const 限制后              ,这个含义更加明确了                  。

另外一个原因是               ,添加 const 限制后  ,可以将 const 对象和非 const 对象传递给形参了           ,因为非 const 类型可以转换为 const 类型         。如果没有 const 限制                ,就不能将 const 对象传递给形参    ,因为 const 类型不能转换为非 const 类型        ,这就意味着                 ,不能使用 const 对象来初始化当前对象了  。

以上面的 Student 类为例      ,将 const 去掉后     ,拷贝构造函数的原型变为:

Student::Student(Student &stu);

此时                  ,下面的代码就会发生错误:

const Student stu1("小明", 16, 90.5); Student stu2 = stu1; Student stu3(stu1);

stu1 是 const 类型         ,在初始化 stu2            、stu3 时  ,编译器希望调用Student::Student(const Student &stu)                 ,但是这个函数却不存在            ,又不能将 const Student 类型转换为 Student 类型去调用Student::Student(Student &stu),所以最终调用失败了                 。

当然              ,也可以再添加一个参数为 const 引用的拷贝构造函数               ,这样就不会出错了            。换句话说  ,一个类可以同时存在两个拷贝构造函数           ,一个函数的参数为 const 引用                ,另一个函数的参数为非 const 引用。

2. 默认拷贝构造函数

其实    ,即使我们没学习过拷贝构造函数        ,实际上却已经在使用拷贝的方式创建对象了                 ,并且也没有引发什么错误              。这是因为      ,如果程序员没有显式地定义拷贝构造函数     ,那么编译器会自动生成一个默认的拷贝构造函数               。这个默认的拷贝构造函数很简单                  ,就是使用“老对象        ”的成员变量对“新对象              ”的成员变量进行一一赋值         ,和上面 Student 类的拷贝构造函数非常类似  。

对于简单的类  ,默认拷贝构造函数一般是够用的                 ,我们也没有必要再显式地定义一个功能类似的拷贝构造函数           。但是当类持有其它资源时            ,如动态分配的内存、打开的文件              、指向其他数据的指针               、网络连接等,默认拷贝构造函数就不能拷贝这些资源              ,我们必须显式地定义拷贝构造函数               ,以完整地拷贝对象的所有数据                。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
javascript小白入门(javascript基础&实战) 并发处理和并行处理(这些并发容器的坑,你要谨记!)