您的位置:首页 > 编程语言 > C语言/C++

C++编程思想--拷贝构造函数

2013-07-25 13:04 204 查看

拷贝构造函数

1. 引用

就像能自动地被编译器间接引用的常量型指针。通常用于函数的参数表和函数的返回值,但也可以独立使用。

引用规则:

1)引用被创建的时候必须初始化

2)引用被初始化为一个对象的引用就不能改变为另一个对象的引用

3)不能有NULL引用。

2. 函数中的引用

1、引用作为函数的参数时,在函数内做的修改会影响外面的参数。必须确保返回的指针,引用的内存是存在的。

2、常量引用。Int g(const int&),这就意味着不会改变参数。对于用户定义的类型,应该使用常量成员函数,如此才不会改边公共的数据成员。且我们的函数也许会接受临时对象,而临时对象是个常量。

3、指针引用。可以改变指针本身而不是它所指向的内容。

Increment(int *&);

4、对象中的常量引用,尽管这种方式可以比传值方式的调用构造函数和析构函数的效率更高,且仅需要将地址入栈,但是一旦原来对象的错误将导致后面的都会出错。

3. 拷贝构造函数

定义:通过引用来实现从现有的相同类型的对象中产生新的对象,编译器使用拷贝构造函数通过按值传递的方式在函数中传递和返回对象。

首先让我们回顾下原来最简单的函数调用的过程

int Add(int a ,int b);
int g=Add(a,b);


这种方式首先 1)会分别将b、a入栈

2) 调用函数

3)调用代码复制清理栈中的参数

4)返回在寄存器中的值

这种按值传递的方式传递参数,编译会将参数拷贝压栈,因为编译器知道拷贝有多大,并知道如何对参数压栈,然后正确的拷贝但是对于用户自定义的类型,编译器无法得知。

4. 函数调用栈框架

当编译器为函数产生代码时,会将所有的参数压栈,然后调用函数,在函数内部,产生代码,并为局部变量提供存储单元

由此函数框架为:函数参数、返回地址、局部变量

当汇编语言的RETURN执行时,会返回到原来的调用点。此时堆栈指针必须指向返回地址。于是函数必须将堆栈中的指针移动,便清除了所有的局部变量。因此试图在堆栈中创建对象,且将其返回是不可能的。因此在调用函数之前,调用者应该负责在堆栈中为返回值分配额外的存储单元。由于重入等机制,无论在全局中还是在堆栈中返回都不安全,那么采用将返回值放在寄存器中,但是在寄存器中并无返回值足够大小内存,于是将返回值的地址像函数参数一样压栈,让函数直接将返回值信息拷贝到目的地,也就是说这整个函数框架将会位于寄存器中。

当对象使用位拷贝的时候,也就是进行按值传递的方式,但是没有自定义拷贝构造函数。位拷贝拷贝的是地址,而值拷贝的是内容,深拷贝的对象,将每一成员拷贝到目标对象的成员中。浅拷贝只能完成基本类型数据类型(如int型变量)的拷贝,若类中有动态数组等数据类型,浅拷贝就会出问题.。此时参数的传递使用的还是C中的按位传递。这种方式并不能维护整个类的完整性。下面的例子中显示并没有调用构造函数。

#include <string>
#include <iostream>
using namespace std;

class HowMany
{
static int objectCount;
int m_na;
int m_nb;
public:
HowMany(int a,int b):m_na(a),m_nb(b)
{
objectCount++;
}
HowMany(const HowMany &h)
{
objectCount++;
print("after Call CopyContruction");
}
static void print(const string& msg = "")
{
if (msg.size()!=0)
{
cout<<msg<<": ";
}
cout<<"objectCount = "<<objectCount<<endl;
}
~HowMany()
{
objectCount--;
print("~HowMany()");
}
};
int HowMany::objectCount = 0;

//在使用默认的拷贝构造函数时,若类中有指针,则成员复制的方式会产生错误。
//没有返回值
HowMany f(HowMany x)
{
cout<<&x<<endl;
x.print("x argument inside f()");
return x;//在返回前将局部变量块中的数据拷贝到这个新的变量中,然后进行释放内存。
}

int main()
{
HowMany h(1,2);
cout<<&h<<endl;
HowMany::print("after construction of h");
HowMany &h2=f(h);//默认的拷贝构造函数中可以将值赋给与这个新的对象实例,但是若存在指针就不能正确的表达???
cout<<&h2<<endl;
HowMany::print("after call to f()");
return 0;
}


于是乎我们会看到下面的运行结果如图1

图1 使用传值方式,使用默认的拷贝构造函数



这个对象的例子说明在调用 f函数的时候并没有调用构造函数,此时从参数中传递进来的对象会将数据成员按值传递给在函数内部创建的对象。至于为什么没有调用构造函数,在函数返回之前,又将创建一个新的对象为我们的h2,同样采用的是按位传值,可以从输出上看出来,这分别为不同的内存。复制结束之后因为局部对象超出了其返回,那么析构函数被调用。那么可能我们的疑问是:在对象内部不是存在着默认的拷贝构造函数。的确,这个默认的拷贝构造函数在没有指针的情况下可以完美的运作,但是如果有指针,带来的问题是:他们指向什么内存,是否拷贝它们或它们是否与新的一块内存相连?于是会引申出我们的自定义的拷贝构造函数。

于是我们将注释部分去掉,这个时候会调用我们自定义的拷贝构造函数,当然这是我们想的。但是编译器告诉我们:HowMany”: 非法的复制构造函数: 第一个参数不应是“HowMany”。其实这依旧是按值传参,效果是一样的,那么这个时候必须使用引用的方式。

我们添加这样的一个函数:

HowMany(const HowMany &h)
{
objectCount++;
print("after Call CopyContruction");
}




图2 使用自定义的拷贝构造函数

从图中我们进行分析可知最后的返回值为传递进去参数的拷贝的拷贝。

最后说说如何建立和使用指向数据成员的指针。这是一种新方式,其实我们在类中自己定义也是可以的。

Data d;
Data *dp=&d;
int Data::*pmInt=&Data::a;//建立指向数据成员的指针
dp->*pmInt=47;//使用这个指针
pmInt=&Data::b;
d.*pmInt=48;


小结:1、讲述了引用与指针差别

2、再次涉及到常量

3、通过函数框架的分析,提出使用拷贝构造函数的内在机理

疑问:为什么会调用析构函数而不调用构造函数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: