拷贝构造函数,顾名思义,就是通过拷贝对象的方式创建一个新对象。拷贝构造函数有两种原型(我们继续以book类来说明拷贝构造函数原型):
book(book &b); book(const book &b); //下面一种原型则规定在创建新对象的时候不得修改被拷贝的对象
这两种原型都是book类对象的引用。下面一种原型则规定在创建新对象的时候不得修改被拷贝的对象。如果拷贝构造函数的参数不是对象的引用,则是不允许的。如下面这种构造函数形式则是无法编译通过的。
book(book b); //无法编译过去
为什么拷贝构造函数的参数一定要是对象的引用呢?我们可以想一下,如果不是引用,而是通过传值的方式将实参传递给形参,这中间本身就要经历一次对象的拷贝的过程,而对象拷贝则必须调用拷贝构造函数,如此一来则会形成一个死循环,无解。所以拷贝构造函数的参数必须是对象的引用。
拷贝构造函数除了能有对象引用这样的参数之外,同样也能有其它参数。但是其它参数必须给出默认值。例如下面这种拷贝构造函数声明方式。
book(const book &b, price = 5.0);
如果类的设计人员不在类中显示的声明一个拷贝构造函数,则系统会自动地为类生成一个拷贝构造函数,自动生成的拷贝构造函数功能简单,只能将源对象的所有成员变量一一复制给当前创建的对象。
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; } int main() { char A[5] = {0,1,1,1,1}; book math{A,5}; book yuwen(math); //book::price =8; //次行不能编译成功 因为price变量是私有的成员 类外不能访问 yuwen.display(); math.display(); return 0; }
在本例中的book类中就声明了一个拷贝构造函数book(book &b);当然这个拷贝构造函数跟系统默认生成的拷贝构造函数功能是一样的,也就只是实现了数据成员的对应拷贝功能。
了解了拷贝构造函数的声明及定义方式,我们再来看一下我们在设计类的时候,什么时候才需要设计拷贝构造函数,我们先来看下面一个例子,相信看完之后会有一定领会,之后再来揭晓答案。
#include<iostream> using namespace std; class Array { public: Array(){length = 0; 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=0; 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=0; i<length; i++) cout<<num[i]<<" "; cout<<endl; } int * Array::getaddress() { return num; } int main() { int A[5] = {1,2,3,4,5}; Array arr1(A, 5); arr1.display(); Array arr2(arr1); arr2.display(); arr2.setnum(8,2); arr2.display(); arr1.display(); cout<<arr1.getaddress()<<" "<<arr2.getaddress()<<endl; return 0; }
程序运行结果如下:
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函数之外,所有的函数在类外都有定义。
#include<iostream> using namespace std; class Array { public: Array(){length = 0; 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=0; i<length; i++) num[i] = a.num[i]; } else { length = 0; num = 0; } } Array::Array(int *A, int n) { num = new int[n]; length = n; for(int i=0; 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=0; i<length; i++) cout<<num[i]<<" "; cout<<endl; } int * Array::getaddress() { return num; } int main() { int A[5] = {1,2,3,4,5}; Array arr1(A, 5); arr1.display(); Array arr2(arr1); arr2.display(); arr2.setnum(8,2); arr2.display(); arr1.display(); cout<<arr1.getaddress()<<" "<<arr2.getaddress()<<endl; return 0; }
看看这个两个有什么不同。对象的地址变了。