内容整理自:
函数原型
在C++中建立一个类,这个类中肯定会包括构造函数、析构函数、复制构造函数和重载赋值操作。
复制构造函数是一种特殊的构造函数,其作用也是为类的成员初始化以及为对象的构造分配存储空间。函数的名称必须和类名称一致,无返回类型,它的唯一的一个参数是本类型的一个引用变量,该参数是const类型,不可变。
复制构造函数原型如下:
class_name(const class_name &src);
对于一个类X, 如果一个构造函数的第一个参数是下列之一:a) & X; b) const & X; c) volatile & X; d) const volatile & X;
且没有其他参数或其他参数都有默认值,那么这个函数是拷贝构造函数,如下:
X::X(const & X);
X::X(& X, int=1);
X::X(& X, int a=1, int b=2);
重载赋值操作符是一个特别的赋值运算符,通常是用来把已存在的对象赋值给其它相同类型的对象。
重载赋值操作符的原型如下:
class_name& operator=(const class_name &src);
复制构造函数与重载赋值操作符的调用
当类的对象需要拷贝时,复制构造函数将会被调用。以下情况都会调用复制构造函数:
一个对象以值传递的方式传入函数体;
一个对象以值传递的方式从函数返回;
一个对象需要通过另外一个对象进行初始化。
如果对象在声明的同时将另一个已存在的对象赋给它,就会调用复制构造函数;如果对象已经存在了,然后再将另一个已存在的对象赋给它,调用的就是重载赋值运算符。
#include <iostream>
using namespace std;
class CTest
{
public:
CTest(){}
~CTest(){}
CTest(const CTest &test)
{
cout<<"copy constructor."<<endl;
}
void operator=(const CTest &test)
{
cout<<"operator="<<endl;
}
void Test(CTest test)
{}
CTest Test2()
{
CTest a;
return a;
}
void Test3(CTest &test)
{}
CTest &Test4()
{
CTest *pA = new CTest;
return *pA;
}
};
int main()
{
CTest obj;
CTest obj1(obj); // 调用复制构造函数
obj1 = obj; // 调用重载赋值操作符
/* 传参的过程中,要调用一次复制构造函数
* obj1入栈时会调用复制构造函数创建一个临时对象,与函数内的局部变量具有相同的作用域
*/
obj.Test(obj1);
/* 函数返回值时,调用复制构造函数;将返回值赋值给obj2时,调用重载赋值操作符
* 函数返回值时,也会构造一个临时对象;调用复制构造函数将返回值复制到临时对象上
*/
CTest obj2;
obj2 = obj.Test2();
obj2.Test3(obj); // 参数是引用,没有调用复制构造函数
CTest obj3;
obj2.Test4(); // 返回值是引用,没有调用复制构造函数
return 0;
}
深拷贝(deep copy)与浅拷贝(shallow copy)
如果在类中没有显式地声明,那么编译器会自动生成默认的复制构造函数和重载赋值操作符。默认的复制构造函数和赋值运算符进行的都是“shallow copy”,只是简单地复制字段,把值一一赋给要拷贝的值。因此如果对象中含有动态分配的内存,就需要我们自己重写复制构造函数和重载赋值操作符来实现“deep copy”,确保数据的完整性和安全性。
例如:类内成员变量需要动态开辟堆内存,如果实行浅拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。
深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源(堆,或者是其它系统资源),当这个类的对象发生复制过程的时候,资源重新分配,使对象拥有不同的资源,但资源的内容是一样的,这个过程就是深拷贝;反之,没有重新分配资源,两个对象就有用共同的资源,同时对资源可以访问,就是浅拷贝。浅拷贝,只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。
#include <iostream>
using namespace std;
class CA
{
public:
CA(int b,char* cstr)
{
a=b;
str=new char[b];
strcpy(str,cstr);
}
CA(const CA& C)
{
a=C.a;
str=new char[a]; //深拷贝
if(str!=0)
strcpy(str,C.str);
}
void Show()
{
cout<<str<<endl;
}
~CA()
{
delete str;
}
private:
int a;
char *str;
};
int main()
{
CA A(10,"Hello!");
CA B=A;
B.Show();
return 0;
}
三法则(英语:rule of three,the Law of The Big Three,The Big Three;三法则,三大定律)在 C++ 程序设计里,它是一个以设计的基本原则而制定的定律,三法则的要求在于,假如类型有明显地定义下列其中一个成员函数,那么程序员必须连其他二个成员函数也一同编写至类型内,亦即下列三个成员函数缺一不可:
- 析构函数(Destructor)
- 复制构造函数(copy constructor)
- 复制赋值运算符(copy assignment operator)
有时候为了防止默认拷贝发生,可以将复制构造函数和重载赋值运算符设为private来禁止拷贝,并且只是声明,不用实现。这样的话,如果试图调用 A b(a); 就调用了私有的复制构造函数,编译器会报错。
class Widget
{
public:
int* pi;
private:
Widget(const Widget&);
Widget& operator=(const Widget&);
};
这种方法的一点小缺陷是,如果该类的成员函数或其友元函数调用复制构造函数或赋值操作符函数,会将错误推后到连接期。有一种方法可以将连接期错误移至编译期:先定义一个基类,然后让你的类继承该类。