C++雾中风景6:拷贝构造函数与赋值函数
在进行C++类编写的过程之中,通常会涉及到类的拷贝构造函数与类的赋值函数。初涉类编写的代码,对于两类函数的用法一直是挺让人困惑的内容。这篇文章我们会详细来梳理拷贝构造函数与赋值函数的区别。
1.调用了哪个函数?
上述两种函数的使用和C++之中类的定义紧密相关,所以我们先定义一个类:
class Line {
public:
int getLength( void );
Line( int len ); //简单的构造函数
Line( const Line &obj) { //拷贝构造函数
cout << "调用拷贝构造函数" << endl;
ptr = new int;
*ptr = *obj.ptr;
};
Line& operator=(const Line &obj) { //赋值函数
cout << "调用赋值函数" << endl;
if(this == &obj) {
return *this;
}
delete ptr;
ptr = new int;
*ptr = *obj.ptr;
return *this;
};
~Line(); // 析构函数
private:
int *ptr;
};
这里我们显式声明了拷贝构造函数与赋值构造函数,接下来我们用一小段代码测试一下上面定义的类。(其他函数的定义并不完整,读者可以之行补全)
int main() {
Line l1(10);
Line l2 = l1;
Line l3(5);
l3 = l2;
}
输出结果如下:
调用拷贝构造函数
调用赋值函数
看似很相似的两个表达式,却调用了不同的两个函数。初学C++时,这样的结果让我很困惑,所以我们接下来梳理一下这两个函数。
2.拷贝构造函数
上面的代码我们可以看到代码 Line l2 = l1调用了拷贝构造函数。
拷贝构造函数,顾名思义,是一个构造函数,但是它特殊的点就在于在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。所以对于它的使用场合也很简单,只有在构造对象时才会调用到拷贝构造函数,显然Line l2 = l1是一个对象初始化的过程。我们知道每个类都会有构造函数,在对象初始化的过程之中,拷贝构造函数提供了一个通过一个同类型的对象对它进行初始化。
C++支持两种初始化形式:拷贝初始化(int a = 5;)和直接初始化(int a(5);)对于其他类型没有什么区别,对于类类型直接初始化直接调用实参匹配的构造函数,拷贝初始化总是调用拷贝构造函数,也就是说:
A x(2); //直接初始化,调用构造函数
A y = x; //复制初始化,调用拷贝构造函数
在C++中,下面几个场景中,拷贝构造函数会被调用:
- 一个对象需要通过另一个对象进行初始化
- 一个对象以值传递的方式作为参数传入函数
- 一个对象以值传递的方式作为返回值从函数返回
如果我们没有显式声明定义对应类的拷贝构造函数,C++编译器会默认生成对应的拷贝构造函数。既然C++编译器会自动生成拷贝构造函数,为什么我们又需要显式的去定义它呢?
因为由C++编译器提供的拷贝构造函数工作方式是浅拷贝。它单纯的使用了=操作符来拷贝类中的成员。但是如果类中用到了需要动态分配内存的成员(如Line类之中的ptr指针),则会出现内存安全的问题。同时对于类的封装性也是一种变相破坏,因为浅拷贝只是单纯拷贝了该成员的内存地址,但所指向的空间内容并没有复制,而是由两个对象共用,就容易出现double free等问题。所以此时就要手动重载拷贝构造函数,实现深拷贝。
3.赋值函数
许多文章,博客喜欢把Line& operator=(const Line &obj)重载=操作符的函数称之为赋值构造函数。个人认为其实是不准确的,会产生一个理解误区。其实重载的=操作符就是一个赋值函数。
赋值函数:是把一个新的对象赋值给一个原有的对象,如果原来的对象中有内存分配需要先把内存释放掉。如果我们没有在类之中显式重载对应类的赋值函数,C++编译器也会默认生成对应的赋值函数。生成的规则与拷贝构造函数类似,也是一种浅拷贝的形式。所以我们重载赋值函数的原因也与拷贝构造函数类型,需要实现深度赋值。
由上文的代码也可以看出,赋值函数与拷贝构造函数定义的内容之中,所做的工作大同小异。唯一需要注意的点是:在赋值函数之中需要检察一下两个对象是不是同一个对象,如果是,不做任何操作,直接返回。
x = x //可能会出现赋值给自身的操作,所以需要对赋值对象进行检查
所以现在回头再来看看前文的代码,我们应该可以理解C++编译器调用不同函数的理由了:
int main() {
Line l1(10);
Line l2 = l1;
Line l3(5);
l3 = l2;
}
当对象不存在,且用别的对象来初始化,此时调用的便是拷贝构造函数。而当对象已经存在,用别的对象来给它进行赋值操作时,调用的就是赋值函数了。
最后的小Tips:一旦在类之中声明了拷贝构造函数与赋值函数,编译器将不会生成缺省的对应函数。所以我们可以通过将拷贝构造函数和赋值函数声明为私有函数,来阻止编译器生成的缺省函数,在编译阶段来拒绝不安全的浅拷贝。
class Line {
public:
int getLength( void ) {
return *ptr;
};
Line( int len ) {
ptr = new int;
*ptr = len;
} //简单的构造函数
// ~Line(); // 析构函数
private:
int *ptr;
Line( const Line &obj);
Line& operator=(const Line &obj);
};
int main() {
Line l1(10);
Line l2 = l1; //编译器报错,无法通过编译
Line l3(5);
l3 = l2; //同样的的编译器报错,无法通过编译
}
好的,关于拷贝构造函数与赋值函数,就先写到这里。下一篇雾中风景系列,来聊一聊命名空间吧~~
C++雾中风景6:拷贝构造函数与赋值函数的更多相关文章
- 关于C++中的拷贝构造函数和赋值函数
如果类定义的数据成员中存在指针或引用,那么最好重载这两个函数. 1. 定义 拷贝构造函数的定义格式:构造函数名(const 源类名& 引用对象形参名){} 赋值函数定义格式:源类名 & ...
- C++中:默认构造函数、析构函数、拷贝构造函数和赋值函数——转
对于一个空类,编译器默认产生4个成员函数:默认构造函数.析构函数.拷贝构造函数和赋值函数.1.构造函数:构造函数是一种特殊的类成员,是当创建一个类的时候,它被调用来对类的数据成员进行初始化和分配内存. ...
- CPP_类默认函数:构造函数,拷贝构造函数,赋值函数和析构函数
类默认函数:构造函数,拷贝构造函数,赋值函数和析构函数 // person.h #ifndef _PERSON_H_ #define _PERSON_H_ class Person{ public : ...
- C++中构造函数,拷贝构造函数和赋值函数的区别和实现
C++中一般创建对象,拷贝或赋值的方式有构造函数,拷贝构造函数,赋值函数这三种方法.下面就详细比较下三者之间的区别以及它们的具体实现 1.构造函数 构造函数是一种特殊的类成员函数,是当创建一个类的对象 ...
- C++中的构造函数,拷贝构造函数,赋值函数
C++中一般创建对象,拷贝或赋值的方式有构造函数,拷贝构造函数,赋值函数这三种方法.下面就详细比较下三者之间的区别以及它们的具体实现 1.构造函数 构造函数是一种特殊的类成员函数,是当创建一个类的对象 ...
- C++ 拷贝构造函数与赋值函数的区别(很严谨和全面)
这里我们用类String 来介绍这两个函数: 拷贝构造函数是一种特殊构造函数,具有单个形参,该形参(常用const修饰)是对该类类型的引用.当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式 ...
- 《剑指offer》面试题1:为类CMyString添加赋值运算符函数——C++拷贝构造函数与赋值函数
题中已给出CMyString的类定义,要求写赋值运算符函数. #include<iostream> #include<cstring> using namespace std; ...
- String类的构造函数,析构函数、拷贝构造函数和赋值函数
(1)构造函数 String::String(const char *str) { if(str==NULL) { m_data = new char[1]; *m_data = ‘\0’; } el ...
- C++类中的一些细节(重载、重写、覆盖、隐藏,构造函数、析构函数、拷贝构造函数、赋值函数在继承时的一些问题)
1 函数的重载.重写(重定义).函数覆盖及隐藏 其实函数重载与函数重写.函数覆盖和函数隐藏不是一个层面上的概念.前者是同一个类内,或者同一个函数作用域内,同名不同参数列表的函数之间的关系.而后三者是基 ...
随机推荐
- Ubuntu SSH root 登录 Permission denied 错误
问题: $ ssh root@40.125.21.75 root@40.125.21.75's password: Permission denied, please try again. 解决方式, ...
- 一、Spring Cloud介绍
springcloud 介绍 研究了一段时间spring boot了准备向spirng cloud进发,公司架构和项目也全面拥抱了Spring Cloud.在使用了一段时间后发现Spring Clou ...
- mongodb 3.4 集群搭建:分片+副本集
mongodb是最常用的nodql数据库,在数据库排名中已经上升到了前六.这篇文章介绍如何搭建高可用的mongodb(分片+副本)集群. 在搭建集群之前,需要首先了解几个概念:路由,分片.副本集.配置 ...
- Hadoop:Rack Awareness
副本的放置对HDFS可靠性和性能至关重要. 优化副本放置HDFS有别于其他大多数分布式文件系统. 这是一个功能,需要大量的调优和经验. 基于机架感知(rack awareness)的副本放置策略的目的 ...
- python列表的操作
1.列表的增加: li=[] li.append("hha") print(li) li=[] li.append("hha") print(li) li.in ...
- 关于《Head First Python》一书中print_lol()函数的思考
关于<Head First Python>一书中print_lol()函数的思考 在<Head First Python>第一章中,讲述到Python处理复杂数据(以电影数据列 ...
- Fiori缓存与它的清除
最近在修改已有的Fiori应用,遇到了缓存上的一点问题,导致对Fiori应用的代码修改不能在前端页面生效.现将自己查到的一篇好资料翻译过来,以供参考.以下为正文. 2017.12.19更新:最近又遇到 ...
- 7、树莓派编程;gpio编程;led闪烁
本博文仅作本人操作过程的记录,留作备忘.自强不息 QQ12226981 1.树莓派接口对照,一定要找到对应的引脚,不要接错了.我画上箭头. 2.安装 下载地址,https://git.drogon.n ...
- VS访问不到TFS、VS连接TFS报TF30063
=============================================== 20170704_第一次修改 ccb_warlock === ...
- Linux(CentOS6.5)下修改Nginx初始化配置
本文地址http://comexchan.cnblogs.com/,作者Comex Chan,尊重知识产权,转载请注明出处,谢谢! 首先备份相关文件: cp /comexHome/nginx/conf ...