类的六个默认成员函数: 1. 构造函数 2. 拷贝构造函数 3. 析构函数 4. 赋值运算符重载 5. 取址(&)运算符重载 6. const修饰的取址运算符重载
我们先定义一个空类:
class A { };在经过编译器处理之后它就不在为空,编译器会自动加入一些默认的成员函数,即使在这些函数中什么也不做。编译器处理之后的类相当于:
class A { public: A(); //构造函数 A(const A& a); //拷贝构造函数 ~A(); //析构函数 A& operator =(const A& a); //赋值运算符重载 A* operator &(); //取址运算符重载 const A* operator &() const; //取址运算符重载 };注意:这些函数在我们没有显式给出时编译器会为我们自动合成。
它是一个特殊的成员函数,函数名与类名相同,无返回值,在创建对象时由编译器自动调用,以保证每个对象都有一个合适的初始值。在该对象的整个生命周期内只被调用一次。
class Time { public: //构造函数 Time(int hour = 0, int minute = 0, int second = 0) :_hour(hour) ,_minute(minute) ,_second(second) { doSomeThing... } private: int _hour; int _minute; int _second; };用于对对象成员进行初始化,格式为在函数名和函数体之间,以一个冒号开始,后面跟着以逗号隔开的数据成员列表,在每个成员后接一个圆括号,括号中为初始化的内容。
即使初始化列表中的成员顺序与定义顺序不同,初始化顺序实际也与数据成员定义的顺序一致。 我们注意到,编译器在底层处理初始化列表时,是根据成员的实际定义顺序来一一初始化的,即先给hour赋值,接着minute,最后second,我们尝试修改了数据成员在初始化列表中的顺序后,它在底层的处理顺序人没有变。
默认构造函数使用与成员变量初始化相同的规则初始化成员,对于内置类型和复合类型的成员,如数组、指针,只对定义在全局定义作用于的对象初始化,对于局部作用域的内置和复合类型不作初始化。
explicit(明确的)关键字可以阻止构造函数对类型的转换。 有时你可能会写出下面的代码(Time为上面的时间类)
Time t; int a = 10; t = a;因为该对象的第一个成员也为int型,所以编译器会将这种代码视为正常,可实际上使用int型的变量给一个类类型的对象可能不是我们期望的做法。若果你这样做了,结果将如下: 它将int型的变量赋给第一个成员,这在某些情况下会造成严重的后果。为了防止这种情况发生,我们在构造函数前加上explicit关键字,这样在你企图像上面那样赋值时,编译器就会给我们报错。
函数名与类名相同,无返回值,有一个形参(常用const修饰),该参数是本类类型的引用。是构造函数的重载,通过已经存在的对象来创建并初始化对象。
class Time { public: //构造函数 Time(int hour = 0, int minute = 0, int second = 0) :_hour(hour) ,_minute(minute) ,_second(second) { doSomeThing... } //拷贝构造函数 Time(const Time& t) :_hour(t._hour) ,_minute(t._minute) ,_second(t._second) { doSomeThing... } private: int _hour; int _minute; int _second; };1、使用已经存在的对象创建新的对象
Time t1(12,01,59); Time t2(t1);2、传值方式作为函数的参数
void FunTest1(const Time t) {}3、传值方式作为函数返回值
Time FunTest2() { Time t; return t; }对于普通类型的数据,它们之间的拷贝是很简单的,比如:
int a = 10; int b = a;而类类型的对象,由于它们内部有着复杂的结构,所以就不能使用简单的赋值运算就完成。
class String { //构造函数 ... //拷贝构造 String(const String& s) :_pStr(s) { doSomeThing... } private: char* _pStr; };像上面这种做法,只是简单的将s赋给 _pStr,即让 _pStr也指向字符串s,这样造成的后果是多个对象指向同一空间,析构(关于析构的概念在下面介绍)出错,这种拷贝方式叫做浅拷贝。 注意:系统默认合成的为浅拷贝,在大多数情况下,我们应该自己写出拷贝构造函数,即使系统会给我们合成它。
对于上面这种情况,我们需要主动给他重新开辟空间
class String { //构造函数 ... //拷贝构造函数 String(const String& s) :_pStr(new char[strlen(s._pStr)+1]) { strcpy(_pStr, s._pStr); doSomeThing... } private: char* _pStr; };像这样,给新创建的对象开辟一块独立的空间,再将旧对象的内容拷贝过来,这样就不会发生如上的错误了,这种拷贝方式叫作深拷贝。
析构函数(destructor) 与构造函数相反,当对象脱离其作用域时(例如对象所在的函数已调用完毕),系统自动执行析构函数。 析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,应在退出前在析构函数中用delete释放)。
class String { //构造函数 String(const char* pStr = '') { if(NULL == pStr) { _pStr = new char[1]; *pStr = '\0'; } else { _pStr = new char[strlen(pStr)+1]; strcpy(_pStr, pStr); } } //析构函数 ~String() { if(_pStr) { delete[] _pStr; _pStr = NULL; } } private: char* _pStr; };如上所示,我们在创建对象时,给对象申请了空间,申请的空间必须手动去释放,所以我们在析构函数中去释放空间。 析构函数在对象生命周期结束前由系统自动调用。
普通类型之间的赋值通过简单的=完成
int a = 10; int b = 20; a = b;对于类类型的对象我们需要对‘=’重载,以完成类类型对象之间的赋值。
class String { //构造函数 ... //拷贝构造 ... //赋值运算符重载 String& operator=(String s) { std::swap(_pStr, s._pStr); return *this; } private: char* _pStr; };重载的格式:返回值为该类类型的引用,通过operator关键字后加上要重载的符号完成,它视情况也有参数。 如同上面拷贝构造函数那样,赋值运算符重载也存在深浅拷贝的问题,上例中直接给出深拷贝的重载。
取址操作符重载函数返回值为该类型的指针,无参数。
与5中不同的是在函数名前和函数体前各加一个const
【作者:果冻 http://blog.csdn.net/jelly_9】
