今天一直在研究拷贝构造函数相关的东西,我这个大四老狗感觉又回到了大一学C++的时候。瞎捣鼓了一天,略微还是有些收获的,趁着脑子中的概念正热,把自己的心得赶紧整理出来分享给大家。

​       首先简单介绍下拷贝构造函数的概念:拷贝构造函数是形参是本类对象的引用的构造函数,它的一般声明形式诸如这样:

Location(const Location & obj)

其实我们经常在类中并未显示地定义类的拷贝构造函数,即当缺省拷贝构造函数时,这个时候系统会自动生成一个默认的拷贝构造函数,把成员值一一复制。这种缺省拷贝构造函数的情况很常见,一般也不会给程序带来问题,但是需要注意的是,如果一个类需要析构函数来释放资源,那么它需要定义一个显式拷贝构造函数来实现深拷贝,关于这一点,本文后面会详细说明缘由。

一、拷贝构造函数的调用

接下来说一下调用拷贝构造函数的三种情况:

  • 当用类的一个对象去初始化该类的另一个对象时。

  • 如果函数的形参是类的对象,调用函数时用实参的值初始化形参时

  • 如果函数的返回值是类的对象函数执行完成返回调用者时。

    附上代码,在示例代码中,覆盖了拷贝构造函数的三种情况。

#include <iostream>
#include <string.h>class student { public:// 普通构造函数student(int i, char* c, int a, float s) {// std::cout << "Constructing..." << std::endl;id = i;age = a;score = s;name = new char[strlen(c) + 1]; //申请堆空间if (name != 0) strcpy(name, c);}// 显示声明拷贝函数,自定义实现深拷贝student(const student& s) { std::cout << "Copy Constructing..." << std::endl;id = s.id; //一般成员简单复制age = s.age;score = s.score;name = new char[strlen(s.name) + 1]; //先申请堆空间if (name != 0) strcpy(name, s.name); //复制字符串};~student() {// std::cout <<"Destructing..."<< std::endl;delete []name;}int getid() {return id;}private:int id;char* name;int age;float score;
};// 函数返回值是类对象,函数执行完成返回调用时,系统会自动调用拷贝构造函数
student g() {student s1(10, "Lina", 18, 86);return s1;
}// 如果函数的形参是类的对象,调用函数时,用实参的值初始化形参时,系统会自动调用拷贝构造函数
void print_1(student s) {std::cout << "学号:" << s.getid() << std::endl;
}int main() {   student s1(10, "Wang", 18, 86); //创建和初始化对象std::cout << std::endl;// 第一种情况,用s1初始化s2。第一次调用拷贝构造函数student s2 = s1;std::cout << std::endl;// 第二种情况,对象s2作为print_1的实参。第二次调用拷贝构造函数print_1(s2);std::cout << std::endl;// 第三种情况,函数的返回值是类对象,函数返回时调用拷贝构造函数s2 = g();std::cout << std::endl;return 0;
}

需要提及的是,对于第三种情况,在IDE上运行时,并没有打印出 "Copy Constructing…" 这个结果:

Copy Constructing...Copy Constructing...
学号:10

看到打印出这个结果时,我也是很蒙圈的,搜索了网上的问答了解到这是编译器做了优化,例如我这边是gcc编译器,它做了优化,使得返回值为对象时,不会再产生临时对象,因而不会再调用拷贝构造函数。那么如果想要看到结果需要怎么做呢,因为我是linux系统,此处就简单说下Linux的处理方式,在终端输入:

g++ -w  -fno-elide-constructors  test.cpp -o test

这时再在终端运行,打印出理想的结果:

Copy Constructing...Copy Constructing...
学号:10Copy Constructing...

二、浅拷贝和深拷贝

​       完成简单的一一对应的复制的拷贝构造函数称为浅拷贝。在浅拷贝过程中,如果对象中含有指针变量,将使得所复制的对象中的指针成员与原来对象的指针成员指向相同的内存空间。在退出程序时,析构函数会释放指针成员指向的内存空间,譬如用对象a初始化对象b,调用拷贝构造函数实现浅拷贝后,程序退出运行时,析构对象a时,析构函数释放其指针成员指向的内存空间,而析构复制对象b时系统会调用相同的析构函数,也要释放其指针成员指向的内存空间。则出现同一内存空间,申请一次,释放两次的情况, new 与 delete不成对,系统会报错。附上简单的示例图:

给出浅拷贝的代码:

#include <iostream>
#include <string.h>class student { public:student(int i, char* c, int a, float s) {// std::cout << "Constructing..." << std::endl;id = i;age = a;score = s;name = new char[strlen(c) + 1]; //申请堆空间if (name != 0) strcpy(name, c);}~student() {// std::cout <<"Destructing..."<< std::endl;delete []name;}private:int id;char* name;int age;float score;
};int main() {  student s1(10, "Wang", 18, 86); //创建和初始化对象student s2 = s1;return 0;
}

此处用s1初始化s2时,调用了拷贝构造函数,因为缺省,默认用了浅拷贝,那么退出时,调用析构函数,因为同一内存空间释放了两次,系统会报错。正确的方法是定义一个显式的拷贝构造函数实现深拷贝,可以见示例代码:

#include <iostream>
#include <string.h>class student { public:student(int i, char* c, int a, float s) {// std::cout << "Constructing..." << std::endl;id = i;age = a;score = s;name = new char[strlen(c) + 1]; //申请堆空间if (name != 0) strcpy(name, c);}// 显示声明拷贝函数,自定义实现深拷贝student(const student& s) {  std::cout << "Copy Constructing..." << std::endl;id = s.id; //一般成员简单复制age = s.age;score = s.score;name = new char[strlen(s.name) + 1]; //先申请堆空间if (name != 0) strcpy(name, s.name); //复制字符串};~student() {// std::cout <<"Destructing..."<< std::endl;delete []name;}private:int id;char* name;int age;float score;
};int main() {  student s1(10, "Wang", 18, 86); //创建和初始化对象student s2 = s1;return 0;
}

这样的话便成功解决了内存空间多次释放报错的问题。

三、普通构造函数、拷贝构造函数、析构函数的调用分析

​ ​ ​ ​ ​ ​ ​ 前面两部分把拷贝构造函数已经分析清楚了,接下来就依据上面的程序简要分析下普通构造函数、拷贝构造函数、析构函数的调用,普通构造函数简单地说就是非拷贝构造函数范畴的构造函数,析构函数则是用于当对象生存期结束时调用它释放对象所占的内存。普通构造函数以及析构函数如果程序中没有显示定义的话,系统也会提供一个默认的析构函数,需要注意的是默认的析构函数只能用来释放对象的数据成员所占用的空间,但不包括堆内存空间。在本例中,为了方便观测,普通构造函数、拷贝构造函数、析构函数在程序中均显示地定义,下面看下示例代码:

#include <iostream>
#include <string.h>class student { public:// 普通构造函数student(int i, char* c, int a, float s) {std::cout << "Constructing..." << std::endl;id = i;age = a;score = s;name = new char[strlen(c) + 1]; //申请堆空间if (name != 0) strcpy(name, c);}// 显示声明拷贝函数,自定义实现深拷贝student(const student& s) {    std::cout << "Copy Constructing..." << std::endl;id = s.id; //一般成员简单复制age = s.age;score = s.score;name = new char[strlen(s.name) + 1]; //先申请堆空间if (name != 0) strcpy(name, s.name); //复制字符串};~student() {std::cout << "Destructing..." << std::endl;delete []name;}int getid() {return id;}private:int id;char* name;int age;float score;
};// 函数返回值是类对象,函数执行完成返回调用时,系统会自动调用拷贝构造函数
student g() {student s1(10, "Lina", 18, 86);return s1;
}// 如果函数的形参是类的对象,调用函数时,用实参的值初始化形参时,系统会自动调用拷贝构造函数
void print_1(student s) {std::cout << "学号:" << s.getid() << std::endl;
}int main() {   student s1(10, "Wang", 18, 86); //创建和初始化对象std::cout << std::endl;// 第一种情况,用s1初始化s2。第一次调用拷贝构造函数student s2 = s1;std::cout << std::endl;// 第二种情况,对象s2作为print_1的实参。第二次调用拷贝构造函数print_1(s2);std::cout << std::endl;// 第三种情况,函数的返回值是类对象,函数返回时调用拷贝构造函数s2 = g();std::cout << std::endl;return 0;
}
Constructing...Copy Constructing...Copy Constructing...
学号:10
Destructing...Constructing...
Copy Constructing...
Destructing...
Destructing...Destructing...
Destructing...

看一下打印的结果,发现对于拷贝构造函数调用的第二种及第三种情况,既打印了"Copy Constructing…“又打印了"Destructing…”,情况较为复杂,下面仔细地分析下:

  • 第二种情况:函数的形参为类对象,调用函数时,用实参初始化形参,系统自动调用拷贝构造函数
// 如果函数的形参是类的对象,调用函数时,用实参的值初始化形参时,系统会自动调用拷贝构造函数
void print_1(student s) {std::cout << "学号:" << s.getid() << std::endl;
}// 第二种情况,对象s2作为print_1的实参。第二次调用拷贝构造函数
print_1(s2);
std::cout << std::endl;
Copy Constructing...
学号:10
Destructing...

因为print_1()函数的形参为student对象,所以调用它时,用s2初始化形参时,会调用拷贝构造函数生成一个函数内的临时对象,这个临时对象那个随着函数的调用结束也就被析构了。

  • 第三种情况:函数的返回值是类对象,函数返回时调用拷贝构造函数
// 函数返回值是类对象,函数执行完成返回调用时,系统会自动调用拷贝构造函数
student g() {student s1(10, "Lina", 18, 86);return s1;
}// 第三种情况,函数的返回值是类对象,函数返回时调用拷贝构造函数
s2 = g();
std::cout << std::endl;
Constructing...
Copy Constructing...
Destructing...
Destructing...

在g()函数中,初始化s1对象时调用普通构造函数,打印"Constructing…";因为函数返回值是类对象,所以会调用拷贝构造函数返回一个新的匿名对象,这时候会执行拷贝构造函数,打印"Copy Constructing…";g()运行结束后,s1被析构,打印"Destructing…";接下来用g()返回的匿名对象赋值给s2,接下来匿名对象任务完成,就会被析构,打印"Destructing…"。

参考博客:
(copy)赋值构造函数的4种调用时机or方法
C++返回值为对象时复制构造函数不执行怎么破

拷贝构造函数的调用以及浅拷贝与深拷贝的理解相关推荐

  1. C++的拷贝构造函数、operator=运算符重载,深拷贝和浅拷贝、explicit关键字

    1.在C++编码过程中,类的创建十分频繁. 简单的功能,当然不用考虑太多,但是从进一步深刻理解C++的内涵,类的结构和用法,编写更好的代码的角度去考虑,我们就需要用到标题所提到的这些内容. 最近,在看 ...

  2. c/c++教程 - 2.4.2.3~4 拷贝构造函数的调用时机,构造函数调用规则

    目录 4.2.3 拷贝构造函数的调用时机 4.2.4 构造函数调用规则 相关教程 4.2.3 拷贝构造函数的调用时机 C++中拷贝构造函数调用时机,通常有三种情况: 使用一个已经创建完毕的对象来初始化 ...

  3. c++中拷贝构造函数被调用的时机

    1 c++中拷贝构造函数被调用的时机 拷贝构造函数被调用的几种情况: (1)当用类的一个对象去初始化该类的另一个对象时,系统会自动调用拷贝构造函数: (2)将一个对象作为实参传递给一个非引用类型的形参 ...

  4. javascript中浅拷贝和深拷贝的理解

    javascript中浅拷贝和深拷贝的理解 什么是拷贝? 简单地说就是复制,对数据的复制 浅拷贝:改变拷贝者的值,被拷贝者的值也会变化 深拷贝:改变拷贝者的值,被拷贝者的值不会变化 由于基本数据类型是 ...

  5. Python中浅拷贝和深拷贝的理解与研究

    Python中浅拷贝和深拷贝的理解与研究 单层浅拷贝 import copy a = 1 # 不可变数据类型 copy_a = copy.copy(a) print(id(a),id(copy_a)) ...

  6. Python中浅拷贝和深拷贝的理解与研究 1

    Python中浅拷贝和深拷贝的理解与研究 单层浅拷贝 import copy a = 1 # 不可变数据类型 copy_a = copy.copy(a) print(id(a),id(copy_a)) ...

  7. 对象特性-----拷贝构造函数的调用

    C++中拷贝构造函数调用通常三种情况: ****使用一个已经创建完毕的对象来初始化一个新对象 ****值传递的方式给函数参数传值 ****以值方式返回局部对象 #include<iostream ...

  8. C++拷贝构造函数的调用时机

    #define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std;class Person { public:P ...

  9. 关于js浅拷贝与深拷贝的理解

    前端开发中,一般情况下,很少会去在意深拷贝与浅拷贝的关系. 大家知道,js变量有2种数据类型:基本类型和引用类型.基本类型的拷贝是将整个值完全拷贝一份的,也就是深拷贝.就是开辟了新的堆内存.所以基本类 ...

最新文章

  1. WINDOWS和LINUX下带时间的PING包监控脚本
  2. AutoComplete选择之后,传PK触发动作
  3. 两个ListBox的相互操作
  4. 技术高手如何炼成?--转自知乎
  5. python怎么写多行_python 多行字符串怎么写才能不破坏缩进
  6. Arduino--二维码显示
  7. AWS还是Firebase?在移动应用后端应该使用哪个?
  8. 黑马程序员-Java-面向对象篇上《二》
  9. nginx ngx_http_access_module
  10. uboot环境变量(设置bootargs向linux内核传递正确的参数)
  11. 蚂蚁员工人均都能买1套杭州的房子了?!好后悔,当初错失了蚂蚁...
  12. [原创]Jenkins持续集成工具介绍
  13. 诺基亚java软件下载_诺基亚手机安装JAVA软件程序
  14. 巧用JSON.stringify()生成漂亮格式的JSON字符串
  15. Helio X30剩魅族一家客户 联发科恢复元气要一年半
  16. TypeError: __init__() got an unexpected keyword argument ‘rate‘
  17. 【taro react】---- 兼容微信小程序和H5的海报绘制插件
  18. 如何在线引入 阿里巴巴矢量图标库?
  19. 将DXSDK的帮助文档加入到VS6的MSDN中
  20. 计算机专业要学视频剪辑吗,想要成为入门剪辑师?必须做到这五点,才能坚持下去...

热门文章

  1. 【Tools】怎样转载博客到CSDN博客(很实用)
  2. 如何高效的学习掌握新技术
  3. Spring - Java/J2EE Application Framework 应用框架 第 6 章 集成AspectJ
  4. 鸟哥的Linux私房菜(服务器)- 第二十章、WWW 伺服器
  5. PHP实现飞信接口来通过网页免费发短信
  6. Python爬虫入门(6):Cookie的使用
  7. Stanford UFLDL教程 白化
  8. 信息系统项目管理师-常用术语中英文对应
  9. pytorch的一些函数
  10. Spring(19)——Profile(二)