C++类中拷贝构造函数详解
a. C++标准中提到“The default constructor, copy constructor and copy assignment operator, and destructor are special member functions.[Note: The implementation will implicitly declare these member functions for some
class types when the program does not explicitly declare them. The implementation will implicitly define them if they are used.]”。即缺省构造函数、拷贝构造函数、拷贝赋值操作符和析构函数是特殊成员函数。
b. “Constructors do not have names. A special declarator syntax using an optional sequence of function- specifiers(inline, virtual and explicit) followed by the constructor’s class name followed by a parameter
list is used to declare or define the constructor.” 构造函数没有名称。
c. 构造函数不能有返回类型,也不能由virtual, const, static 和 volatile来修饰。但可以由inline来修饰,事实上隐式构造函数就是用inline来修饰的。inline表示编译时展开,通常速度块;virtual表示运行时绑定,通常意味着灵活。
d. 类中存在虚函数或者有虚基类的情况下需要显式声明构造函数。拷贝构造函数也是如此。
#include <iostream>
using namespace std; class A{
public:
A(){ cout << "A" << endl;}
//virtual ~A(){ cout << "~A" << endl;}
~A(){ cout << "~A" << endl;}
};
class B : public A{
public:
B(){ cout << "B" << endl;}
~B(){ cout << "~B" << endl;}
};
int main()
{
A *a = new B();
delete a;
return 0;
}
输出:
A
B
~A
请按任意键继续. . .
一般情况下类的析构函数里面都是释放内存资源,而析构函数不被调用的话就会造成内存泄漏。我想所有的C++程序员都知道这样的危险性。当然,如果在析构函数中做了其他工作的话,那你的所有努力也都是白费力气。
这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。
当然,并不是要把所有类的析构函数都写成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。
f. 构造函数是一种特殊函数,而拷贝构造函数是一种特殊的构造函数。类X的构造函数的第一个参数必须为X&,或者const X&;除了第一个参数外,构造函数要么不存在其他参数,如果存在其他参数,其他参数必须有默认值。一个类可以有多个拷贝构造函数。它的形式如下:
X::X(X& x)
X::X(const X& x)
X::X(X& x, int a = 0, int b = 1…)
g. 什么时候会调用拷贝构造函数?
以下三种情况出现时,会调用一个类的拷贝构造函数:
1) 用一个已经实例化了的该类对象,去实例化该类的另外一个对象;
2) 用该类的对象传值的方式作为一个函数的参数;
3) 一个函数返回值为该类的一个对象。
#include <iostream>
using namespace std; class CA
{
public:
int a;
int b;
public:
inline CA()
{
a = 1;
b = 1;
} inline CA(int A, int B)
{
a = A;
b = B;
} inline CA(CA& x)
{
a = x.a;
b = x.b;
cout << "copy constructor is called." << endl;
} void printInfo()
{
cout << "a = " << a << ", b = " << b << endl;
}
}; int someFun1(CA x)
{
return x.a + x.b;
} CA someFun2(int a, int b)
{
CA ca(a, b);
return ca;
} int main(void)
{
CA a;
// CA b(); // 不能用这种方式声明CA的对象b!
CA c(10, 10);
CA d(c); // 情况1) -> 调用拷贝构造函数
int anInt = someFun1(c); // 情况2) -> 调用拷贝构造函数
CA e = someFun2(11, 11); // 情况3) -> 调用拷贝构造函数 return 0;
}
运行结果:
copy constructor is called.
copy constructor is called.
copy constructor is called.
运行结果表明,上述结论是正确的。
h. 什么时候必须要显式声明拷贝构造函数?
拷贝构造函数的作用就是用一个已经实例化了的该类对象,去实例化该类的另外一个对象。
1) 下面的代码并没有显式声明一个构造函数,编译器会自动为类CExample1生成一个缺省的隐式拷贝构造函数:
#include <iostream>
using namespace std; class CExample1
{
private:
int a; public:
CExample1(int b){a = b;}
void SetValue(int a){this->a = a;}
void Show(){cout << a << endl;}
}; int main(void)
{
CExample1 A(100);
CExample1 B = A; // 调用了缺省的隐式拷贝构造函数
CExample1 C(B); // 调用了缺省的隐式拷贝构造函数 B.Show(); // 输出应该是100
B.SetValue(90);
B.Show(); // 输出应该是90
A.Show(); // 输出应该是100
C.Show(); // 输出应该是100 return 0;
}
输出为:
100
90
100
100
2) 如果有成员变量以指针形式存在,涉及动态内存分配等情况下,一定要显式声明拷贝构造函数。要注意到,如果需要显式定义拷贝构造函数,那么通常都是需要同时定义析构函数(因为通常涉及了动态内存分配),至于是否必须重载操作符“=”,要视情况而定。
#include <iostream>
using namespace std; class CSomething
{
public:
int a;
int b; public:
CSomething(int a, int b)
{this->a = a; this->b = b;}
}; class CA
{
private:
CSomething* sth; // 以指针形式存在的成员变量 public:
CA(CSomething* sth){this->sth = new CSomething(sth->a, sth->b);}
~CA()
{
cout << "In the destructor of class CA..." << endl;
if (NULL != sth) delete sth; }
void Show(){cout << "(" << sth->a << ", " << sth->b << ")" << endl;}
void setValue(int a, int b){sth->a = a; sth->b = b;}
void getSthAddress()
{
cout << sth << endl;
}
}; int main(void)
{
CSomething sth(1, 2);
CA ca(&sth);
ca.Show(); CA cb(ca); // 调用缺省的隐式拷贝构造函数
cb.Show(); cb.setValue(2, 3);
ca.Show();
cb.Show(); ca.getSthAddress();
cb.getSthAddress(); return 0;
}
上面的程序没有显式声明拷贝构造函数,运行结果如下:
可见,ca和cb中的指针成员变量sth指向的是同一个内存地址(Console输出的第5、6行),这就是为什么在cb.setValue(2, 3)后,ca对应的内容也发生了改变(Console输出的第3、4行),而这不是我们所期望的;其次,我们生成了两个对象ca和cb,因此对两次调用析构函数,第一次调用析构函数的时候没有问题,因为此时sth里面有内容,第二次调用析构函数时,sth里面的内容由于在第一次调用析构函数的时候已经被delete了,所以会出现如上的错误提示。
保持其他代码不变,现在我们增加一个拷贝构造函数如下:
CA(CA& obj)
{
sth = new CSomething((obj.sth)->a, (obj.sth)->b);
}
再运行上面的程序,所得到的结果如下:
这次,ca和cb中的指针成员变量sth指向的不是同一个内存地址(Console输出的第5、6行)了,这就是为什么在cb.setValue(2, 3)后,ca对应的内容保持不变,而cb的内容该如愿地改为(2, 3)(Console输出的第3、4行);其次,析构函数也不会报告错误了。
3) 关于拷贝构造函数另外一个完整的例子,其中包含了copy constructor,destructor 和copy assignment operator。
#include <iostream>
using namespace std; class Point
{
public:
int _x;
int _y; public:
Point();
Point(int, int);
}; Point::Point()
{
_x = 0;
_y = 0;
} Point::Point(int x, int y)
{
_x = x;
_y = y;
} class CA
{
public:
Point* _point; public:
CA()
{
_point = NULL;
}
CA(const Point*);
void setPointValues(int, int);
void printCoordinates(); // 需要增加的拷贝构造函数
CA(const CA&);
// 需要增加的析构函数
virtual ~CA();
// 需要增加的拷贝赋值函数
CA& operator = (const CA&);
}; CA::CA(const Point* point)
{
_point = new Point(); // 发生了动态内存分配!因此不能缺少析构函数。
_point->_x = point->_x;
_point->_y = point->_y;
} // 需要增加的拷贝构造函数的实现
CA::CA(const CA& ca)
{
_point = new Point();
_point->_x = (ca._point)->_x;
_point->_y = (ca._point)->_y;
} // 需要增加的析构函数的实现
CA::~CA()
{
if(NULL != _point) delete _point;
_point = NULL;
} // 需要增加的拷贝赋值函数的实现
CA& CA::operator = (const CA& ca)
{
_point = new Point();
_point->_x = (ca._point)->_x;
_point->_y = (ca._point)->_y; return *this;
} void CA::setPointValues(int x, int y)
{
_point->_x = x;
_point->_y = y;
} void CA::printCoordinates()
{
cout << "Coordinates = (" << _point->_x << ", " << _point->_y << ")" << endl;
} int main(void)
{
Point apoint(1, 2);
CA ca(&apoint);
ca.printCoordinates(); CA cb(ca); // 调用拷贝构造函数
cb.printCoordinates(); cb.setPointValues(12, 12);
cb.printCoordinates();
ca.printCoordinates(); CA cc;
cc = cb; // 调用拷贝赋值函数
cc.printCoordinates();
cc.setPointValues(13, 13); ca.printCoordinates();
cb.printCoordinates();
cc.printCoordinates(); return 0;
}
C++类中拷贝构造函数详解的更多相关文章
- 转 C++拷贝构造函数详解
C++拷贝构造函数详解 一. 什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a = 100; int b = a; 而类对象与普通对象不同,类对象内部结构一 ...
- 08--C++拷贝构造函数详解
C++拷贝构造函数详解 一. 什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: [c-sharp] view plain copy int a = 100; int b ...
- c++之拷贝构造函数详解
C++中经常使用一个常量或变量初始化另一个变量,例如: double x=5.0: double y=x; 使用类创建对象时,构造函数被自动调用以完成对象的初始化,那么能否象简单变量的初始化一样,直接 ...
- [Reprint]C++友元函数与拷贝构造函数详解
这篇文章主要介绍了C++友元函数与拷贝构造函数,需要的朋友可以参考下 一.友元函数 1.友元函数概述: (1)友元函数是定义在一个类外的普通函数.友元函数和普通函数的定义一样;在类内必须将该普通函 ...
- C++拷贝构造函数详解(转载)
一. 什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a = 100; int b = a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员 ...
- [016]转--C++拷贝构造函数详解
一. 什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a = 100; int b = a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员 ...
- C++拷贝构造函数详解 转
一. 什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: [c-sharp] view plaincopy int a = 100; int b = a; 而类对象与普通 ...
- C++拷贝构造函数详解
转自:http://blog.csdn.net/lwbeyond/article/details/6202256 对于一个空类,编译器默认生成四个成员函数:默认构造函数.析构函数.拷贝构造函数.赋值函 ...
- 【转】C++拷贝构造函数详解
一.什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: ; int b=a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量. 下面看一个类对象拷贝 ...
随机推荐
- Spark学习笔记(三)—— Standalone模式
上篇笔记记录了Local模式的一些内容,但是实际的应用中很少有使用Local模式的,只是为了我们方便学习和测试.真实的生产环境中,Standalone模式更加合适一点. 1.基础概述 Standalo ...
- Quartz.NET总结(八)如何根据自己需要配置Topshelf 服务
前面讲了如何使用Topshelf 快速开发windows服务, 不清楚的可以看之前的这篇文章:https://www.cnblogs.com/zhangweizhong/category/771057 ...
- JSP九大内置对象之session以及eclispe如何关联源码
一.session的特点及其实例 session:同一次会话共享 a.浏览网站:开始->关闭 b.购物:浏览.付款.退出 c.电子邮件:浏览.写邮件.退出 从一次开始到一次结束,是一次会话. ...
- 简单的c语言程序
我猜大部分人第一门掌握的语言是c语言吧(我第一门学的是python). 在接下来的文章里我会逐步的介绍有关自己对c语言的理解,希望对一些小伙伴有帮助,有哪里有错误希望大佬指出. 首先这是第一个c语言程 ...
- Elasticsearch系列---搜索执行过程及scroll游标查询
概要 本篇主要介绍一下分布式环境中搜索的两阶段执行过程. 两阶段搜索过程 回顾我们之前的CRUD操作,因为只对单个文档进行处理,文档的唯一性很容易确定,并且很容易知道是此文档在哪个node,哪个sha ...
- 《【面试突击】— Redis篇》--Redis Cluster及缓存使用和架构设计的常见问题
能坚持别人不能坚持的,才能拥有别人未曾拥有的.关注编程大道公众号,让我们一同坚持心中所想,一起成长!! <[面试突击]— Redis篇>--Redis Cluster及缓存使用和架构设计的 ...
- 查看JVM参数
如何查看一个正在运行中的java程序,它的某个jvm参数是否开启?具体值是多少? jps jinfo jvm的参数类型: 1.标配参数:java -version ,java -help , jav ...
- Java多线程的创建(二)
前言: 虽然java的API中说创建多线程的方式只有两种(There are two ways to create a new thread of execution),分别是继承Thread类创建和 ...
- 大厂面试中三次握手延伸出来n连发你受得了?
目录 一.这是一次有故事的对话 二.三次握手的客户端服务端状态 1 先画个图看看有哪些状态 2 tcp协议内容解析 3 通过工具wireshark来验证我们所述 三.说下Linux网络编程常用API ...
- Qt Installer Framework翻译(2)
开始 Qt IFW作为Qt项目的一部分进行开发.该框架自身也使用Qt.然而,它能被用于安装所有类型的应用程序,包括(但不限于)使用Qt编译的. 支持的平台 已在下列平台中进行了测试: > Mic ...