C++拷贝构造函数(构造函数)详解
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网站站点" rel="nofollow" />
运行结果:拷贝构造函数(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) 为什么必须是当前类的引用呢?
如果拷贝构造函数的参数不是当前类的引用,而是当前类的对象,那么在调用拷贝构造函数时,会将另外一个对象直接传递给形参,这本身就是一次拷贝,会再次调用拷贝构造函数,然后又将一个对象直接传递给了形参,将继续调用拷贝构造函数……这个过程会一直持续下去,没有尽头,陷入死循环。只有当参数是当前类的引用时,才不会导致再次调用拷贝构造函数,这不仅是逻辑上的要求,也是 C++ 语法的要求。
2) 为什么是 const 引用呢?
拷贝构造函数的目的是用其它对象的数据来初始化当前对象,并没有期望更改其它对象的数据,添加 const 限制后,这个含义更加明确了。
另外一个原因是,添加 const 限制后,可以将 const 对象和非 const 对象传递给形参了,因为非 const 类型可以转换为 const 类型。如果没有 const 限制,就不能将 const 对象传递给形参,因为 const 类型不能转换为非 const 类型,这就意味着,不能使用 const 对象来初始化当前对象了。以上面的 Student 类为例,将 const 去掉后,拷贝构造函数的原型变为:
Student::Student(Student &stu);
此时,下面的代码就会发生错误:
stu1 是 const 类型,在初始化 stu2、stu3 时,编译器希望调用const Student stu1("小明", 16, 90.5);
Student stu2 = stu1;
Student stu3(stu1);
Student::Student(const Student &stu),但是这个函数却不存在,又不能将 const Student 类型转换为 Student 类型去调用Student::Student(Student &stu),所以最终调用失败了。当然,你也可以再添加一个参数为 const 引用的拷贝构造函数,这样就不会出错了。换句话说,一个类可以同时存在两个拷贝构造函数,一个函数的参数为 const 引用,另一个函数的参数为非 const 引用。
默认拷贝构造函数
在前面的教程中,我们还没有讲解拷贝构造函数,但是却已经在使用拷贝的方式创建对象了,并且也没有引发什么错误。这是因为,如果程序员没有显式地定义拷贝构造函数,那么编译器会自动生成一个默认的拷贝构造函数。这个默认的拷贝构造函数很简单,就是使用“老对象”的成员变量对“新对象”的成员变量进行一一赋值,和上面 Student 类的拷贝构造函数非常类似。
对于简单的类,默认拷贝构造函数一般是够用的,我们也没有必要再显式地定义一个功能类似的拷贝构造函数。但是当类持有其它资源时,如动态分配的内存、打开的文件、指向其他数据的指针、网络连接等,默认拷贝构造函数就不能拷贝这些资源,我们必须显式地定义拷贝构造函数,以完整地拷贝对象的所有数据,这点我们将在《C++深拷贝和浅拷贝》一节中深入讲解。
- 随机文章
- 马尔代夫海上航空(马尔代夫岛屿现为海上航空“拍卖品” 重塑旅游业)
- 入境马尔代夫体检(入境马尔代夫体检指南,保障健康出行)
- 冯策 马尔代夫(冯策马尔代夫之行 – 一场蓝天碧海的浪漫体验)
- 丹阳马尔代夫钓鱼(丹阳市民在马尔代夫进行深海垂钓体验)
- 小马尔代夫岛戏水(小马尔代夫:天堂岛上的畅游水上世界)
- 唯美马尔代夫风景(马尔代夫:大海的芭蕾,沙滩的交响。)
- 买家具送马尔代夫(家具购物联手旅行,享受马尔代夫假期)
- 万豪 马尔代夫 积分(用积分兑换!去万豪马尔代夫享受梦想假期)
- 上海直发马尔代夫(直飞马尔代夫,上海开通海岛旅游航线)
- 揭阳惠来马尔代夫(惠来旅游马尔代夫,开启度假闲适生活)
- 普宁马尔代夫攻略(普宁游客必看:马尔代夫旅游攻略分享)
- 南宁海滩马尔代夫(南宁的海滩重新打造,仿若马尔代夫!)
- 昌平马尔代夫酒店(昌平维景马尔代夫酒店:奢华度假胜地)
- 德阳人造马尔代夫(德阳建成人造马尔代夫,周边游受热捧)
- 枝江有马尔代夫吗(枝江是否有类似马尔代夫的旅游胜地?)
- 株洲马尔代夫照片(株洲市民拍下马尔代夫美景,网友点赞)
- 咸阳马尔代夫沙滩(咸阳市民在马尔代夫尽享沙滩晒日光浴)
- 木鱼马尔代夫出海(马尔代夫出海探险:迎接木鱼游泳之旅)
- 恭喜马尔代夫的话(恭喜马尔代夫荣膺全球最美海岛国家!)
- 襄阳马尔代夫火了(襄阳马尔代夫——这个水上乐园火了!)
- 方媛 马尔代夫(方媛与家人度假,晒出马尔代夫近照)
- 迪拜马尔代夫厨房(迪拜马尔代夫:一起探索当地厨房文化)
- 迪拜王储马尔代夫(迪拜王储造访马尔代夫,推进两国合作)
- 马尔代夫不会外语(马尔代夫强制要求本地人掌握外语能力)
- 马尔代夫中部酒店(马尔代夫中部酒店:瑰丽海景华丽再现)
- 香港足球马尔代夫(香港足球球队将前往马尔代夫参加比赛)
- 微博 马尔代夫(马尔代夫旅游业复苏,游客人数逐步增加)
- 南海马尔代夫旅游(南海度假胜地,马尔代夫旅游等你来!)
- 贝拉 马尔代夫(贝拉马尔代夫:白沙滩与蓝天交相辉映)
- 马尔代夫位置定位(马尔代夫地图:定位、位置、景点介绍)
