参考:http://www.weixueyuan.net/view/6344.html

总结:

  如果拷贝构造函数的参数不是对象的引用,则是不允许的。如 book(book b); 是无法编译通过的。

  拷贝构造函数除了能有对象引用这样的参数之外,同样也能有其它参数。但是其它参数必须给出默认值。

  默认拷贝构造函数的问题, 拷贝构造函数参数为引用,系统自动生成的拷贝构造函数功能简单,只是将arr1的数组首地址直接赋值给arr2的数组首地址。

  如果类的设计人员不在类中显示的声明一个拷贝构造函数,则系统会自动地为类生成一个拷贝构造函数,自动生成的拷贝构造函数功能简单。

  由于类会自动生成拷贝构造函数,因此有些时候为了不让对象发生拷贝行为,我们可以显示声明一个拷贝构造函数,并将其设置为private属性。

拷贝构造函数,顾名思义,就是通过拷贝对象的方式创建一个新对象。拷贝构造函数有两种原型(我们继续以book类来说明拷贝构造函数原型):

  1. book(book &b);
  2. book(const book &b);

这两种原型都是book类对象的引用。下面一种原型则规定在创建新对象的时候不得修改被拷贝的对象。如果拷贝构造函数的参数不是对象的引用,则是不允许的。如下面这种构造函数形式则是无法编译通过的。

  1. book(book b);

为什么拷贝构造函数的参数一定要是对象的引用呢?我们可以想一下,如果不是引用,而是通过传值的方式将实参传递给形参,这中间本身就要经历一次对象的拷贝的过程,而对象拷贝则必须调用拷贝构造函数,如此一来则会形成一个死循环,无解。所以拷贝构造函数的参数必须是对象的引用。

拷贝构造函数除了能有对象引用这样的参数之外,同样也能有其它参数。但是其它参数必须给出默认值。例如下面这种拷贝构造函数声明方式。

  1. book(const book &b, price = 5.0);

如果类的设计人员不在类中显示的声明一个拷贝构造函数,则系统会自动地为类生成一个拷贝构造函数,自动生成的拷贝构造函数功能简单,只能将源对象的所有成员变量一一复制给当前创建的对象。

例1:

class book
{
public:
book(){}
book(book &b);
book(char* a, double p = 5.0);
void display();
private:
double price;
char * title;
}; book::book(book &b)
{
price = b.price;
title = b.title;
} book::book(char* a, double p)
{
title = a;
price = p;
} void book::display()
{
cout<<"The price of "<<title<<" is $"<<price<<endl;
}

在本例中的book类中就声明了一个拷贝构造函数book(book &b);当然这个拷贝构造函数跟系统默认生成的拷贝构造函数功能是一样的,也就只是实现了数据成员的对应拷贝功能。

了解了拷贝构造函数的声明及定义方式,我们再来看一下我们在设计类的时候,什么时候才需要设计拷贝构造函数,我们先来看下面一个例子,相信看完之后会有一定领会,之后再来揭晓答案。

例2:

#include<iostream>
using namespace std; class Array
{
public:
Array(){length = ; num = NULL;};
Array(int * A, int n);
void setnum(int value, int index);
int * getaddress();
int getlength(){return length;}
void display();
private:
int length;
int * num;
}; Array::Array(int *A, int n)
{
num = new int[n];
length = n;
for(int i=; i<n; i++)
num宁波优学智能科技有限公司[i] = A[i];
} void Array::setnum(int value, int index)
{
if(index < length)
num[index] = value;
else
cout<<"index out of range!"<<endl;
} void Array::display()
{
for(int i=; i<length; i++)
cout<<num[i]<<" ";
cout<<endl;
} int * Array::getaddress()
{
return num;
} int main()
{
int A[] = {,,,,};
Array arr1(A, );
arr1.display();
Array arr2(arr1);
arr2.display();
arr2.setnum(,);
arr2.display();
arr1.display();
cout<<arr1.getaddress()<<" "<<arr2.getaddress()<<endl;
return ;
}

程序运行结果如下:

1 2 3 4 5
1 2 3 4 5
1 2 8 4 5
1 2 8 4 5
00331F58 00331F58

在本例中,我们重新定义了一个Array类,可以理解为一个整形数组类,这个类中我们定义了两个成员变量:整形指针num和数组长度length。

类中定义了一个默认构造函数,声明了一个带参构造函数。默认构造函数很简单,带参构造函数则是用于将一个已有的数组全部拷贝给类对象。

除了两个构造函数之外,我们还定义四个成员函数,一个是用于修改数组中数值的setnum函数、一个打印数组中所有元素的display函数、一个返回数组首地址的函数getaddress和一个返回数组长度的函数getlength。除了默认构造函数之外和getlength函数之外,所有的函数在类外都有定义。

接下来我们看一下主函数。主函数中,我们先定义了一个数组,包含五个元素,分别是从1到5。之后用Array类创建对象arr1,并且用A数组初始化对象arr1,此时arr1对象相当于拥有一个数组,该数组包含5个元素,打印出来的结果是“1 2 3 4 5 ”,没有问题。之后用arr1对象初始化arr2对象,因为我们在类中没有显示地定义一个拷贝构造函数,因此系统会自动为我们生成一个拷贝构造函数,该拷贝构造函数的定义如下:

  1. Array::Array(Array &a)
  2. {
  3. length = a.length;
  4. num = a.num;
  5. }

通过系统自动生成的拷贝构造函数完成arr2对象的创建,同样的arr2也是有5个元素的数组,打印出来的结果是“1 2 3 4 5 ”,同样没有问题。

之后我们调用成员函数setnum,将arr2对象下标为2的元素修改为8(原先是3)。此时打印arr2中数组元素,结果为“1 2 8 4 5 ”,正确,arr2第三个元素确实被修改掉了。

之后我们再调用arr1.display(),奇怪的事情发生了,它的打印结果竟然也是“1 2 8 4 5 ”!我们之前并未修改过第三个元素的值的,这是怎么一回事呢?不急,我们再来看一下最后一句“cout<<arr1.getaddress()<<" "<<arr2.getaddress()<<endl;”其显示结果竟然是一样的!看到这里是不是有些明白了上面的问题呢?很明显,arr1和arr2所指向的数组是同一个数组,在内存中的位置是一致的,因此当我们利用对象arr2去修改数组中第三个元素的数值的时候,arr1中的数组也被修改了,其实它们本来就是使用的是同一个内存中的数组而已。

这问题是怎么产生的呢?不难想到拷贝构造函数参数为引用,系统自动生成的拷贝构造函数功能简单,只是将arr1的数组首地址直接赋值给arr2的数组首地址,也即num = a.num;这必然导致两个对象指向同一块内存。既然问题出在系统自动生成的拷贝构造函数上,自然要从拷贝构造函数上下手了。下面我们将正确的程序展示如例3。

例3:

#include<iostream>
using namespace std; class Array
{
public:
Array(){length = ; num = NULL;};
Array(int * A, int n);
Array(Array &a);
void setnum(int value, int index);
int * getaddress();
void display();
int getlength(){return length;}
private:
int length;
int * num;
}; Array::Array(Array & a)
{
if(a.num != NULL)
{
length = a.length;
num = new int[length];
for(int i=; i<length; i++)
num[i] = a.num[i];
}
else
{
length = ;
num = ;
}
} Array::Array(int *A, int n)
{
num = new int[n];
length = n;
for(int i=; i<n; i++)
num[i] = A[i];
} void Array::setnum(int value, int index)
{
if(index < length)
num[index] = value;
else
cout<<"index out of range!"<<endl;
} void Array::display()
{
for(int i=; i<length; i++)
cout<<num[i]<<" ";
cout<<endl;
} int * Array::getaddress()
{
return num;
} int main()
{
int A[] = {,,,,};
Array arr1(A, );
arr1.display();
Array arr2(arr1);
arr2.display();
arr2.setnum(,);
arr2.display();
arr1.display();
cout<<arr1.getaddress()<<" "<<arr2.getaddress()<<endl;
return ;
}
  1. 程序运行结果如下所示:

1 2 3 4 5
1 2 3 4 5
1 2 8 4 5
1 2 3 4 5
00311F58 00487268

看例3运行结果,如此一来,程序运行结果正确,而且两个对象arr1和arr2所指向的内存空间也是不一样的。我们在例3中自己定义了一个拷贝构造函数,并且开辟了一个新的空间用于存储数据。如此一来当然是不会有问题的了。本例中所介绍的是一个非常微妙的错误,在程序设计过程中,一定要加以避免。

从这个例子中我们是不是领会到了什么呢?通常,如果一个类中包含指向动态分配存储空间的指针类型的成员变量时,就应该为这个类设计一个拷贝构造函数,除了需要设计一个拷贝构造函数之外,还需要为它添加一个赋值操作符重载函数(即重载“=”操作符,这将会在操作符重载那一章加以介绍)。

由于类会自动生成拷贝构造函数,因此有些时候为了不让对象发生拷贝行为,我们可以显示声明一个拷贝构造函数,并将其设置为private属性。这跟通过将默认构造函数设置成private属性限制对象的创建时一样的道理。当然,禁止对象发生拷贝的需求较少,如果有这样的需求的话,知道还可以这么做就足够了,这是一个类设计的技巧。

2.13 C++拷贝构造函数的更多相关文章

  1. 深入C++中构造函数、拷贝构造函数、赋值操作符、析构函数的调用过程总结

    转自 http://www.jb51.net/article/37527.htm,感谢作者 #include "stdafx.h"      #include <iostre ...

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

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

  3. C++的转换构造函数、拷贝构造函数、赋值运算符重载

    1 转换构造函数     C++的转换构造函数是只有一个参数的构造函数.当程序试图将一个其他类型的对象或基本类型值赋给该类的一个待初始化对象时(如Person p="Dean";) ...

  4. 拷贝构造函数,深拷贝,大约delete和default相关业务,explicit,给定初始类,构造函数和析构函数,成员函数和内联函数,关于记忆储存,默认参数,静态功能和正常功能,const功能,朋友

     1.拷贝构造 //拷贝构造的规则,有两种方式实现初始化. //1.一个是通过在后面:a(x),b(y)的方式实现初始化. //2.另外一种初始化的方式是直接在构造方法里面实现初始化. 案比例如以 ...

  5. 从零开始学C++之构造函数与析构函数(二):初始化列表(const和引用成员)、拷贝构造函数

    一.构造函数初始化列表 推荐在构造函数初始化列表中进行初始化 构造函数的执行分为两个阶段 初始化段 普通计算段 (一).对象成员及其初始化  C++ Code  1 2 3 4 5 6 7 8 9 1 ...

  6. C++知识点:拷贝构造函数例子

    //拷贝构造函数: //函数参数传递时调用一次拷贝构造函数,给对象赋值时调用一次拷贝构造函数,对象作为参数传递后会被及时销毁. #include <fstream> #include &l ...

  7. 【C++】拷贝构造函数和赋值符函数

    在C++中,调用拷贝构造函数有三种情况: 1.一个对象作为函数参数,以值传递的方式传入函数体. 2.一个对象作为函数返回值,以值传递的方式从函数返回. 3.一个对象用于给另外一个对象进行初始化(复制初 ...

  8. [C++ Primer] : 第13章: 拷贝控制

    拷贝, 赋值与销毁 当定义一个类时, 我们显示地或隐式地指定在此类型的对象拷贝, 移动, 赋值和销毁时做什么. 一个类通过定义5种特殊的成员函数来控制这些操作, 包括: 拷贝构造函数, 拷贝赋值运算符 ...

  9. 初始化列表(const和引用成员)、拷贝构造函数

    一.构造函数初始化列表 推荐在构造函数初始化列表中进行初始化 构造函数的执行分为两个阶段 初始化段 普通计算段 (一).对象成员及其初始化  C++ Code  1 2 3 4 5 6 7 8 9 1 ...

随机推荐

  1. learn python the hard way 习题6~10总结

    习题6总结 定义字符串: 名字 = 值 其他 你也可以用 {types_of_people}的方式把它放在任何字符串中. 也就是说你可以在其他字符串中添加{},然后前面加一个 f,可用print()进 ...

  2. 宿主iis部署wcf

    WCF学习笔记(4)——宿主iis部署wcf 本文将部署一个wcf+silverlight简单实例,以下是详细步骤: (环境:服务端win2003,iis6.0,asp.net4.0:客户端winXP ...

  3. C#使用 System.Net.Mail发送邮件功能

    .NET 里包含了很多很丰富的邮件发送与接受的API在 System.Net.Mail命名空间里,使得我们开发发送和接受邮件相关功能变得简单,下面是一个简单发送邮件的功能: private void ...

  4. Vue.js简单的状态管理和 Vuex的几个核心概念使用。

    由于状态零散地分布在许多组件和组件之间的交互中,大型应用复杂度也经常逐渐增长. 如果多层组件嵌套使用,传递prop,和事件emit.都很不方便. 不方便对数据的修改进行历史记录.影响后续的调试! 为了 ...

  5. Linux下安装 jdk

    转自 http://www.cnblogs.com/shihaiming/p/5809553.html 0.下载jdk8 登录网址:http://www.oracle.com/technetwork/ ...

  6. Python实现一条基于POS算法的区块链

    区块链中的共识算法 在比特币公链架构解析中,就曾提到过为了实现去中介化的设计,比特币设计了一套共识协议,并通过此协议来保证系统的稳定性和防攻击性. 并且我们知道,截止目前使用最广泛,也是最被大家接受的 ...

  7. [LintCode] Binary Tree Level Order Traversal(二叉树的层次遍历)

    描述 给出一棵二叉树,返回其节点值的层次遍历(逐层从左往右访问) 样例 给一棵二叉树 {3,9,20,#,#,15,7} : 3 / \ 9 20 / \ 15 7 返回他的分层遍历结果: [ [3] ...

  8. centos 安装 和 linux 简单命令

    1. centos 安装 参照:https://www.cnblogs.com/tiger666/articles/10259102.html 安装过程注意点: 1. 安装过程中的选择安装Basic ...

  9. python基础之小数据池,is和==区别 编码问题

    主要内容 小数据池,is和==区别 编码问题 小数据池 一种缓存机制,也称为驻留机制,是为了能更快提高一些字符串和整数的处理速度is 和 == 的区别 == 主要指对变量值是否相等的判断,只要数值相同 ...

  10. hdu 1542 Atlantis (线段树扫描线)

    大意: 求矩形面积并. 枚举$x$坐标, 线段树维护$[y_1,y_2]$内的边是否被覆盖, 线段树维护边时需要将每条边挂在左端点上. #include <iostream> #inclu ...