C++提供构造函数和析构函数用于 数据成员的初始化和清理。
构造函数:
定义:
1、C++定义与类名相同的特殊成员函数,即构造函数。
2、有参或无参。
3、无返回类型。
调用:
自动调用,C++编译器会自动调用构造函数;
手动调用:在一些情况需要手动调用。
先后定义两个对象T1和T2,构造函数先调用T1后调用T2,析构函数倒序调用,先调用T2后调用T1。
无参构造函数:
在类外定义对象后,自动调用无参构造函数,并在内部进行对象数据成员的赋值;
有参构造函数:
括号法:Text T1(3,4);
等号法:Text T2 = (1,2,3,4); //逗号表达式将最右边的数字返回。一个参数
手动调用:Text T3 = T1(3,4); //对象的初始化,赋值构造函数
Text T4 ; T4 = T3; //对象的赋值 调用的是C++自带的 operator = () 或者用户重载的等号操作函数。
对象的初始化 与 对象的赋值 是不同的概念。
拷贝(copy)构造函数:用1个对象去初始化另一个对象 :
1、Text T2 = T1;
2、Text T2(T1);
3、 调用f(T2)时,p调用拷贝构造函数,用T2完成自己的初始化。此时,p是一个与T2等价的元素(非指针,非引用)
void f(Text p) { cout << p.fun() << endl; }
4、函数的返回值是一个元素,返回的是一个匿名对象,所以会调用匿名对象类的拷贝构造函数
void display() { g(); //析构A后,匿名对象未使用,匿名对象析构。// Text m = g(); 匿名对象初始化一个同类型的对象,匿名对象转成有名对象// Text n(1,2);// n = g(); 匿名对象 赋值 给另外一个同类型的对象,匿名对象被析构。} Text g() { Text A(1,2); //调用有参构造函数 return A; //调用拷贝构造函数,创建一个匿名对象。 }
构造函数调用规则研究:
在定义类时,没有写构造函数,C++编译器会提供一个默认构造函数。
写了构造函数就必须要使用它。
浅拷贝问题:(自己编写拷贝构造函数即可:主要针对有指针成员)
1、拷贝构造函数
Text obj2 = obj1; //调用obj2的拷贝构造函数 Text(Text& zty) {}
如果Text里面有两个变量,其中一个为指针。
char* p;
int len;
如果没有定义拷贝构造函数,就会调用C++自带的拷贝构造函数,这个时候:
obj1的成员变量p和len都在栈区,p指针存放的是地址信息,指向堆中的数据(全局变量区复制过来的,注意:全局变量区和堆区都有这部分数据)
C++自带的拷贝构造函数,执行了浅拷贝,只是在栈区把p和len复制了一份给obj2,故p也指向堆中的数据区域。在两个变量生命周期结束,需要析构时,obj2正常析构,数据析构,指针复NULL,obj1中的p就变成了野指针。
2、等号操作符
Text obj1("sf");
Text obj2("safdsg"); //调用类中的有参构造函数
obj2 = obj1; //如果不自己重载operator=(),则调用C++自己的=操作符进行p和len简单的值拷贝,指针所指的堆中区域没有拷贝,obj1和obj2中的指针p都指向堆中相同区域的数据。
构造函数的初始化列表:
解决了在B类中组合了一个A类对象的(其中,A类设计了构造函数)
根据构造函数的调用规则,设计A的构造函数,必学要用,在B中没有机会初始化A
新的语法:
class A { public: A(int _a) { a = _a; } ~A() {} private: int a; } class B { public: B::B(int _b1):a2(2),a3(3) { b1 = _b1; } B::B(int _b1, int v2, int v3):a2(v2),a3(v3) { b1 = _b1; } private: int b1; A a2; A a3; } void main() { B zty(1); B lunais(1,2,3); }
// 先执行被组合对象的构造函数A,按照定义顺序,而不按照初始化列表顺序。析构函数相反。
如果类中有const对象,必须用初始化列表进行赋初值
class B { public: B::B(int _b1):a2(2),a3(3),c(20) { b1 = _b1; } B::B(int _b1, int v2, int v3):a2(v2),a3(v3),c(20) //c也可以赋值为其他值 { b1 = _b1; } private: const int c; int b1; A a2; A a3; }