生成一个对象的副本有2种途径:
途径1:建立一个新对象,将一个已有对象数据成员的值取出来 赋给新对象。
途径2:使用复制构造函数。
复制构造函数是一种特殊的构造函数,用来生成一个对象的副本。
复制构造函数的作用:
使用一个已经存在的对象初始化一个同类的新对象。
复制构造函数的特点:
复制构造函数名与类名相同,并且也没有返回值类型(被系统自动调用)。
复制构造函数可写在类中,也可以写在类外。
复制构造函数要求有一个类类型的引用参数。
如果没有显式定义复制构造函数,系统自动生成一个默认形式的复制构造函数。
复制构造函数的形参为本类的对象引用。
class 类名
{
public : 类名(形参);//构造函数
类名([const ]类名 &引用名<形式参数名>);//复制构造函数
//[const]保护实参对象只读,不能被更改,可以不写
…
};
类名::类名([const] 类名 &引用名) //复制构造函数的实现
{ 函数体 }
举例
class Point
{
public:
Point(int xx=0,int yy=0) {X=xx; Y=yy;} //带默认形参值的构造函数
Point(Point& p); //复制构造函数 参数是同类的引用
int GetX() {return X;}
int GetY() {return Y;}
private:
int X,Y;
};
Point::Point (Point& p) //复制构造函数的实现
{
X=p.X; //把形参的值取出来赋给类的数据成员
Y=p.Y;
cout<<"复制构造函数被调用"<<endl;
}
在以下情况调用复制构造函数:
1、当用类的一个对象去初始化该类的另一个对象时,系统自动调用复制构造函数实现拷贝赋值。
#include <iostream>
using namespace std;
class Complex //复数类
{
public:
Complex(double r, double i) { real = r; imag = i; } //带参数的构造函数
Complex( Complex & c)
{
real = c.real;
imag = c.imag;
cout<<"copy constructor!"<<endl;
}
private:
double real, imag; //实部虚部
};
int main()
{
Complex c1(1,2); //定义c1类的对象 调用构造函数
Complex c2(c1); ///调用复制构造函数 没有调用构造函数时的传递,在复制构造函数中c为c1的别名
Complex c3= c1; //用对象c1去初始化该类 的另一个对象c2|c3时复制构造函数被调用
c3=c2; //非初始化,不调用复制构造函数(只有在新定义时调用构造函数)
return 0;
}
程序执行结果为:
copy constructor!
copy constructor!
2、若函数的形参为类对象,调用函数时,实参传递给形参,系统自动调用复制构造函数,用实参对象去初始化形参对象。
如果某函数有一个参数是类Complex的对象,那么该函数被调用时,类Complex的复制构造函数将被调用。
void func(Complex c) { }; //c是临时的,只有调用的时候申请存储空间
int main( )
{
Complex c1(1,2);
func(c1); //调用复制构造函数 实际参数c1传递给形式参数c 在复制构造函数中形参是实参的别名,参与引用的其实是实际参数
return 0;
}
程序执行结果为:
copy constructor!
3、如果函数的返回值是类的对象,函数执行完返回调用者时 ,需要生成一个临时对象作为函数返回结果,此时,系统需要调用复制构造函数完成临时对象的生成。
Complex func()
{
Complex c1(1,2);
return c1; //调用复制构造函数
};
除此之外,还将调用析构函数销毁临时对象。
如果有形如 a=func()的表达式,还将调用赋值函数完成赋值操作.
默认的复制构造函数
如果没有为类声明拷贝初始化构造函数,则编译器自己 生成一个默认的复制构造函数。
这个构造函数执行的功能:
用作为初始值的对象的 每个数据成员的值, 初始化将要建立的对 象的对应数据成员
#include <iostream>
using namespace std;
class CMyclass
{
int a;
public:
CMyclass(int aa =0) {a=aa;}
void disp( )
{cout<<a<<endl;}
};
int main( )
{
CMyclass c1(5);
CMyclass c2(c1); //c1和c2的值一样
c1.disp();
c2.disp();
return 0;
}
浅复制与深复制
浅复制:
被复制对象的所有变量都含有与原来的对象相同的值,而其所有的对其他对象的引用都仍然指向原来的对象。
说明:
一个对象中的数据成员:
有的是值类型的数据,它的值就是简单的值,
有的是引用类型的数据,它的值是地址,例如:指针类型的成员函数。
浅复制在复制时,将这个对象的值数据和引用数据(均为非静态数据)全部复制过去,获得了这个对象的值和地址。 即:当其中一个对象的引用字段所指向的地址中的变量变化时,所有浅复制对象中 的该引用字段都会发生变化。
默认复制构造函数所进行的是简单数据复制,即浅复制
浅复制的例子
#include <iostream>
using namespace std;
class Test
{
private:
int a,b;
public:
Test(int x, int y) //提供的形式参数,是为了给数据成员直接初始化的
{
a=x;
b=y;
}
Test(const Test& C) //复制构造函数,提供一个同类型对象作为参数
{
a=C.a;
b=C.b;
}
void show ()
{ cout<<a<<" "<<b<<endl; }
};
int main()
{
Test xx(100,10); //执行构造函数Test::Test(int x, int y)
Test yy(xx); //执行构造函数Test::Test(const Test& C)
Test zz=xx; //也执行构造函数Test::Test(const Test& C)
yy.show();
zz.show();
return 0;
}
浅复制存在问题
#include <iostream>
#include <cstring>
using namespace std;
class Test
{
private:
int a;
char *str; //指针类型
public:
Test(int b, char *s) //构造函数
{
a=b;
str=new char[strlen(s)+1]; //申请空间存储s的长度加一(结束字符)
strcpy(str,s);
}
void setA(int b)
{a=b; }
void setStr(char *s)
{ strcpy(str,s); }
void show ()
{cout<<a<<","<<str<<endl; }
};
int main()
{
Test xx(100,"hello");
Test yy(xx); //xx,yy指向相同的地址
xx.show(); yy.show();
xx.setA(80); xx.setStr("abc");
xx.show(); yy.show();
return 0;
}
运行结果
100,hello
100,hello
80,abc
100,abc
当类的数据成员含有指针类型时,浅复制构造函数存在问题。此时需要定义深复制构造函数。
深复制:通过一个对象初始化另一个对象时,不仅将被复制对象中所有非引用类型的字段复制给新对象, 也将引用类型所指向地址中存储的对象复制给新的对象。
定义支持深复制的复制构造函数
- 缺省的复制构造函数是浅复制构造函数
- 深复制构造函数必须显式定义
- 当成员变量中含有指针变量时,需要定义深复制构造函数
- 深复制构造函数的特点
① 定义:类名::类名([const] 类名 &对象名);
② 成员变量的处理:对指针类型的成员变量,使用new操作符进行空间的申请,然后进行相关的复制操作
修改
public:
Test(int b, char *s)
{
a=b;
str=new char[strlen(s)+1];
strcpy(str,s);
}
void setA(int b)
{a=b; }
void setStr(char *s)
{ strcpy(str,s); }
void show ()
{cout<<a<<","<<str<<endl; }
Test(const Test& C) //深复制
{
a=C.a;
str=new char[strlen(C.str)+1]; //申请了独立的存储空间
strcpy(str,C.str); //str=C.str;
}
};
运行结果
100,hello
100,hello
80,abc
100, hello