C++中经常使用一个常量或变量初始化另一个变量,例如:

double x=5.0;

double y=x;

使用类创建对象时,构造函数被自动调用以完成对象的初始化,那么能否象简单变量的初始化一样,直接用一个对象来初始化另一个对象呢?

答案是肯定的,以point类为例:

point pt1(2,3);

point pt2=pt1;

后一个语句也可写成:

point pt2( pt1);

上述语句用pt1初始化pt2,相当于将pt1中每个数据成员的值复制到pt2中,这是表面现象。实际上,系统调用了一个复制构造函数。如果类定义中没有显式定义该复制构造函数时,编译器会隐式定义一个缺省的复制构造函数,它是一个inline、public的成员函数,其原型形式为: 类名::类名(const 类名 &) 如:

point:: point (const point &);

注意:当我们自己定义了有参构造函数时,系统不再提供默认构造函数。这是容易忽略的一点。

复制构造函数调用机制

复制构造函数的调用示例:

point pt1(3,4); //构造函数 p

oint pt2(pt1); //复制构造函数

point pt3 = pt1;//复制构造函数

#include <iostream>
using namespace std;
class point
{
private:
int xPos;
int yPos;
public:
point(int x = 0, int y = 0)
{
cout << "调用构造函数" << endl;
xPos = x;
yPos = y;
}
point(const point & pt)//复制构造函数的定义及实现
{
cout << "调用复制构造函数" << endl;
xPos = pt.xPos;
yPos = pt.yPos;
}
void print()
{
cout << "xPos: " << xPos << ",yPos: " << yPos << endl;
}
};
#include "point.h" int main()
{
point pt1(3, 4);
pt1.print(); point pt2 = pt1; //等价于point pt2(pt1),调用复制构造函数
pt2.print(); point pt3;
pt3.print(); point pt4(pt3); //等价于point pt4=pt3,调用复制构造函数
pt4.print(); // pt2 = pt1; //调用默认的赋值运算符重载函数
// pt2.print();
return 0;
} #include <iostream>
using namespace std; class CPoint
{
private:
int x;
int y; public:
//缺省构造函数,如果定义类时未指定任何构造函数,
//系统将自动生成不带参数的缺省构造函数
CPoint()
{
cout << "默认构造函数 " << this << " " << endl;
x = 0;
y = 0;
} //带一个参数的可用于类型转换的构造函数
CPoint(int ix)
{
cout << "1参数构造函数 " << this << " " << endl;
x = ix;
y = 0;
} //带参数的构造函数
CPoint(int ix, int iy)
{
cout << "2参数构造函数 " << this << " " << endl;
x = ix;
y = iy;
} //拷贝构造函数,如果此函数不定义,系统将生成缺省拷贝构造函数功能,
//缺省拷贝构造函数的行为是:用传入的对象参数的成员初始化正要建立的对象的相应成员
CPoint(const CPoint &cp)
{
cout << "拷贝构造函数 " << this << " " << endl;
x = cp.x;
y = cp.y;
} CPoint &operator=(const CPoint &cp)
{
cout << "赋值重载函数 " << this << " " << endl;
if (this != &cp)
{
x = cp.x;
y = cp.y;
}
return (*this);
} //析构函数,一个类中只能有一个析构函数,如果用户没有定义析构函数,
//系统会自动未类生成一个缺省的析构函数
~CPoint()
{
cout << "析构函数 " << this << " " << endl;
}
}; void fun1(CPoint pt)
{
} CPoint fun2()
{
CPoint a;
return a;
} CPoint fun(CPoint a)
{
return a;
} int main(int argc, char* argv[])
{
//第1类
// CPoint pt1 = CPoint();
//当有析构函数的时候,CPoint()不会生成调用构造函数生成临时的匿名对象。
//当没有析构函数的时候,CPoint()会生成一个临时的匿名对象,等价于CPoint pt1;这句话只会调用无参构造函数,不会调用拷贝构造函数 // CPoint pt2 = CPoint(1);
//当有析构函数的时候,CPoint(1)不会生成调用构造函数生成临时的匿名对象。
//当没有析构函数的时候,CPoint()会生成一个临时的匿名对象,等价于CPoint pt(1);这句话只会调用一个参数的构造函数,不会调用拷贝构造函数 // CPoint pt3 = 1;
//普通数据类型转换为类类型,利用相应的构造函数就可以实现。等价于CPoint pt(1); //第2类
/*拷贝构造函数与赋值运算符重载函数的区别:
1. 拷贝构造函数是用已经存在的对象的各成员的当前值来创建一个相同的新对象。
在下述3种情况中,系统会自动调用所属类的拷贝构造函数。
1.1 当说明新的类对象的同时,要给它赋值另一个已经存在对象的各成员当前值。
1.2 当对象作为函数的赋值参数而对函数进行调用要进行实参和形参的结合时。
1.3 当函数的返回值是类的对象,在函数调用结束后返回主调函数处的时候。
2. 赋值运算符重载函数要把一个已经存在对象的各成员当前值赋值给另一个已经存在的同类对象
*/
CPoint pt4; //调用无参构造函数
// CPoint pt5 = pt4; //调用拷贝构造函数 属于1.1
// fun1(pt4); //调用拷贝构造函数 属于1.2
// fun2(); //调用拷贝构造函数 属于1.3 // CPoint pt6 = fun2();//调用无参构造函数,拷贝构造函数,此处如果没有写析构函数,则还会调用一次拷贝构造函数
//因为函数返回会生成一个临时对象,然后再将这个临时对象赋值给pt6,所以多调用一次拷贝构造函数;如果有析构函数
//则不会生成中间的临时变量,所以少一次拷贝构造函数的调用 //还可以通过下面函数验证
// CPoint pt7 = fun(pt4);
//如果没有析构函数,会调用3次拷贝构造函数 return 0;
}

  

拷贝构造函数在以下三种情况会自动调用:

  当把一个已经存在的对象赋值给另一个新的对象时。

  当实参和形参都是对象,进行形参和实参的结合时。

  当函数的返回值是对象,函数调用完成返回时。

缺省复制构造函数带来的问题

缺省的复制构造函数并非万金油,在一些情况下,必须由程序员显式定义缺省复制构造函数,先来看一段错误代码示例,见备注代码。

其中语句

computer comp2(comp1)

等价于:

comp2.brand = comp1.brand;

comp2.price = comp1.price;

后一句没有问题,但comp2.brand = comp1.brand却有问题:经过这样赋值后,两个对象的brand指针都指向了同一块内存,当两个对象释放时,其析构函数都要delete[]同一内存块,便造成了2次delete[],从而引发了错误。

解决方案――显式定义复制构造函数

如果类中含有指针型的数据成员、需要使用动态内存,程序员最好显式定义自己的复制构造函数,避免各种可能出现的内存错误,见代码。

computer(const computer &cp) //自定义复制构造函数

{

  //重新为brand开辟和cp.brand同等大小的动态内存

  brand = new char[strlen(cp.brand) + 1];

  strcpy(brand, cp.brand); //字符串复制 price = cp.price;

}

关于构造函数和复制构造函数

复制构造函数可以看成是一种特殊的构造函数,这里姑且区分为“复制构造函数”和“普通构造函数”,因此,它也支持初始化表达式。

创建对象时,只有一个构造函数会被系统自动调用,具体调用哪个取决于创建对象时的参数和调用方式。C++对编译器何时提供缺省构造函数和缺省复制构造函数有着独特的规定,如下表所示:

构造函数调用实例

CTest t0(); //这是函数的声明,不是实例化类

CTest t1; //缺省构造函数

CTest t2(1); //一个参数的构造函数

CTest t3(1, 2); //两个参数的构造函数

CTest t4 = 1; //等价于CTest t4(1); //explicit

CTest t5 = t1; //CTest(t1);

CTest t6 = CTest();//CTest(1);

CTest(1,2);

t6 = CTest(1);

t6 = 1; //首先调用单个参数的构造函数,生成临时 //对象CTest(1), 然后调用赋值运算符函数

t6 = t1; //调用赋值运算符函数 见备注代码。请注意输出的地址值,观察构造函数和析构函数的配对情况。

//为了防止CPoint pt = 2;和CPoint pt2 = pt1;这种隐性转换,可以在相应的构造函数前增加explicit标识符
#include <iostream>
using namespace std; class CPoint
{
protected:
int x;
int y; public:
//缺省构造函数,如果定义类时未指定任何构造函数,
//系统将自动生成不带参数的缺省构造函数
CPoint()
{
cout << "默认构造函数 " << this << " ";
x = 0;
y = 0;
} //带一个参数的可用于类型转换的构造函数
// explicit //加上 explicit 可防止 CPoint pt1 = 1; 这种隐性转换
CPoint(int ix)
{
cout << "1参数构造函数 " << this << " ";
x = ix;
y = 0;
} //带参数的构造函数
CPoint(int ix, int iy)
{
cout << "2参数构造函数 " << this << " ";
x = ix;
y = iy;
} //拷贝构造函数,如果此函数不定义,系统将生成缺省拷贝构造函数功能,
//缺省拷贝构造函数的行为是:用传入的对象参数的成员初始化正要建立的对象的相应成员
// explicit //加上 explicit 可防止 CPoint pt2 = pt1; 这种隐性转换
CPoint(const CPoint &cp)
{
cout << "拷贝构造函数 " << this << " ";
x = cp.x;
y = cp.y;
} CPoint &operator=(const CPoint &cp)
{
cout << "赋值重载函数 " << this << " ";
if (this != &cp)
{
x = cp.x;
y = cp.y;
}
return (*this);
} //析构函数,一个类中只能有一个析构函数,如果用户没有定义析构函数,
//系统会自动未类生成一个缺省的析构函数
~CPoint()
{
cout << "析构函数 " << this << " ";
}
}; int main(int argc, char* argv[])
{
CPoint p0(); //这是函数的声明,不是实例化类 cout << endl << "CPoint pt1;\t\t";
CPoint pt1; //缺省构造函数 cout << endl << "CPoint pt2(1);\t\t";
CPoint pt2(1); //一个参数的构造函数 cout << endl << "CPoint pt3(1, 2);\t";
CPoint pt3(1, 2); //两个参数的构造函数 cout << endl << "CPoint pt4 = 1;\t\t";
CPoint pt4 = 1; //等价于CPoint t4(1); //explicit cout << endl << "CPoint pt5 = t1;\t";
CPoint pt5 = pt1; //CPoint(t1); cout << endl << "CPoint pt6 = CPoint();\t";
CPoint pt6 = CPoint(); //CPoint(1); CPoint(1,2); cout << endl << "pt6 = CPoint(1);\t";
pt6 = CPoint(1); cout << endl << "pt6 = 1;\t\t";
pt6 = 1; //首先调用单个参数的构造函数,生成临时对象CPoint(1), 然后调用赋值运算符函数 cout << endl << "pt6 = t1;\t\t";
pt6 = pt1; //调用赋值运算符函数 cout << endl << endl;
return 0;
}

  

c++之拷贝构造函数详解的更多相关文章

  1. 转 C++拷贝构造函数详解

    C++拷贝构造函数详解 一. 什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a = 100; int b = a; 而类对象与普通对象不同,类对象内部结构一 ...

  2. 08--C++拷贝构造函数详解

    C++拷贝构造函数详解 一. 什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: [c-sharp] view plain copy int a = 100; int b ...

  3. [Reprint]C++友元函数与拷贝构造函数详解

    这篇文章主要介绍了C++友元函数与拷贝构造函数,需要的朋友可以参考下   一.友元函数 1.友元函数概述: (1)友元函数是定义在一个类外的普通函数.友元函数和普通函数的定义一样;在类内必须将该普通函 ...

  4. C++拷贝构造函数详解(转载)

    一. 什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a = 100; int b = a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员 ...

  5. [016]转--C++拷贝构造函数详解

    一. 什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a = 100; int b = a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员 ...

  6. C++拷贝构造函数详解 转

    一. 什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: [c-sharp] view plaincopy int a = 100; int b = a; 而类对象与普通 ...

  7. C++拷贝构造函数详解

    转自:http://blog.csdn.net/lwbeyond/article/details/6202256 对于一个空类,编译器默认生成四个成员函数:默认构造函数.析构函数.拷贝构造函数.赋值函 ...

  8. 【转】C++拷贝构造函数详解

    一.什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: ; int b=a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量. 下面看一个类对象拷贝 ...

  9. C++构造函数和拷贝构造函数详解

    构造函数.析构函数与赋值函数是每个类最基本的函数.它们太普通以致让人容易麻痹大意,其实这些貌似简单的函数就象没有顶盖的下水道那样危险. 每个类只有一个析构函数和一个赋值函数,但可以有多个构造函数(包含 ...

随机推荐

  1. Keras 最新《面向小数据集构建图像分类模型》

    本文地址:http://blog.keras.io/building-powerful-image-classification-models-using-very-little-data.html ...

  2. C++精华笔记

    牛客微信推送的C++笔记:2016-12-12   14:23:26 1.C++不仅支持面向对象,也可以像C一样支持面向过程. 2.OOP三大特性:封装 继承 多态 3.函数重载依据:函数类型and形 ...

  3. Hierarchical data in postgres

    https://coderwall.com/p/whf3-a/hierarchical-data-in-postgres --------------------------------------- ...

  4. Path SumI、II——给出一个数,从根到子的和等于它

    I.Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that adding up a ...

  5. 由于扩展配置问题而无法提供您请求的页面...请添加 MIME 映射.--解决方法

    http://blog.sina.com.cn/s/blog_4c78de680100quis.html HTTP 错误 404.3 - Not Found由于扩展配置问题而无法提供您请求的页面.如果 ...

  6. [转]FTP服务器搭建

    下面先说第一中方法: 1.在win7上先开启ftp服务:这里点击确定后,可能会要等一会儿,完成后有时系统会提示重启 2.打开   计算机-->管理-->   在这里我们可以看见刚刚添加的服 ...

  7. FPGA机器学习之机器学习的n中算法总结1

    机器学习是AI领域的重要一门学科.前面我描写叙述过.我计划从事的方向是视觉相关的机器学习分类识别,所以可能在每一个算法的分析中,仅仅增加在视频.视觉领域的作用. 我毛华望QQ849886241.技术博 ...

  8. iOS移动开发周报-第18期

    iOS移动开发周报_18期 [摘要]:本期iOS移动开发周报带来如下内容:苹果与 IBM 展开长期深度合作,Swift官方博客,Swift开发的视频教程等. 新闻 <苹果与 IBM 展开长期深度 ...

  9. API网关如何实现对服务下线实时感知

    上篇文章<Eureka 缓存机制>介绍了Eureka的缓存机制,相信大家对Eureka 有了进一步的了解,本文将详细介绍API网关如何实现服务下线的实时感知. 一.前言 在基于云的微服务应 ...

  10. 分布式开源调度框架TBSchedule原理与应用

    主要内容: 第一部分 TBSchedule基本概念及原理 1. 概念介绍 2. 工作原理 3. 源代码分析 4. 与其它开源调度框架对照 第二部分 TBSchedule分布式调度演示样例 1. TBS ...