拷贝和复制是一个意思,对应的英文单词都是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.ttt.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.ttt.nethttp://c.ttt.nethttp://c.ttt.nethttp://c.ttt.nethttp://c.ttt.net http://c.ttt.net
s1、s2、s3、s4 以及 func() 的形参 str,都是使用拷贝的方式来初始化的。
对于 s1,表面上看起来是将一个字符串直接赋值给了 s1,实际上在内部进行了类型转换,将 const char * 类型转换为 string 类型后才赋值的。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 引用。
如果拷贝构造函数的参数不是当前类的引用,而是当前类的对象,那么在调用拷贝构造函数时,会将另外一个对象直接传递给形参,这本身就是一次拷贝,会再次调用拷贝构造函数,然后又将一个对象直接传递给了形参,将继续调用拷贝构造函数……这个过程会一直持续下去,没有尽头,陷入死循环。
只有当参数是当前类的引用时,才不会导致再次调用拷贝构造函数,这不仅是逻辑上的要求,也是 c++ 语法的要求。
拷贝构造函数的目的是用其它对象的数据来初始化当前对象,并没有期望更改其它对象的数据,添加 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 引用。
在前面的教程中,我们还没有讲解拷贝构造函数,但是却已经在使用拷贝的方式创建对象了,并且也没有引发什么错误。这是因为,如果程序员没有显式地定义拷贝构造函数,那么编译器会自动生成一个默认的拷贝构造函数。这个默认的拷贝构造函数很简单,就是使用“老对象”的成员变量对“新对象”的成员变量进行一一赋值,和上面 student 类的拷贝构造函数非常类似。
对于简单的类,默认拷贝构造函数一般是够用的,我们也没有必要再显式地定义一个功能类似的拷贝构造函数。但是当类持有其它资源时,如动态分配的内存、打开的文件、指向其他数据的指针、网络连接等,默认拷贝构造函数就不能拷贝这些资源,我们必须显式地定义拷贝构造函数,以完整地拷贝对象的所有数据。
到此这篇关于c++中的拷贝构造函数详解的文章就介绍到这了,更多相关c++拷贝构造函数内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!
本文发布于:2023-04-05 00:10:39,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/fanwen/zuowen/5c3efef9f534db8d0d9860fdab8195e8.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文word下载地址:C++中的拷贝构造函数详解.doc
本文 PDF 下载地址:C++中的拷贝构造函数详解.pdf
留言与评论(共有 0 条评论) |