构造与析构方案
c++为初始化提供了三种方案:
1、无参构造函数——完成对象的初始化,当写了构造函数,调用写的构造函数;如果没写,调用默认构造函数。
2、带参构造函数(调用方法有三种)
3、复制构造函数(拷贝构造函数)(调用方法有4种)
带参构造函数的用法:
teacher techr("wangling" ,12 ,0001);
class _test { public: test(int ad) { a = ad; } private: int a; }; test t2 = 11; //调用有参构造函数
test t3 = (test)(12); //程序员手动调用带参构造函数
拷贝构造函数的四种应用场景:
//语法: 类名::类名(const 类名& 对象名)
A a1; A a2= a1; //场景1,区别 A a2; a2=a1;赋值操作符重载调用
A a1; A a2(a1); //场景2
void fun(A a) { } A a1; fun(a1); //传递参数时,调用拷贝构造函数,相当于 A a = a1; 怎么理解,因为形参a是要被分配空间的,要被构造,所以调用构造函数,而不是赋值重载调用
A fun() { A a; return a; } void main() { A a1; a1 = fun(); //先调用拷贝构造函数,再析构a,再调用赋值重载函数,再析构匿名空间
A a2 = fun(); //调用拷贝构造函数,再析构a,不会调用赋值重载函数,不析构匿名空间 } /* 为什么要调用拷贝构造函数? 因为,在完成函数调用时,函数要求返回一个对象,那么会首先创建一个临时的匿名对象空间,并把 return的 a拷贝给匿名的对象。所以调用了拷贝构造函数。
在执行完 a1 = fun();之后,匿名空间被释放,自动调用析构函数
对于 a2,在调用fun函数时,生成的匿名对象,被取名为a2,这样处理,可以提高效率,没有必要再构造一个新的对象了,因为匿名对象的存在,不用白不用。
所以调用完fun函数生成的匿名对象,也不马上析构它。当a2释放时才析构。不要认为匿名对象到a2又有一次拷贝构造函数调用。 */
默认拷贝构造函数,也叫做浅拷贝构造函数,在未定义拷贝构造函数时,在用到上述场景的初始化时,调用默认拷贝构造函数,如果定义了拷贝构造函数,那么调用自己定义的。浅拷贝的特点:只是对象变量的简单复制。
深拷贝构造函数,一般是自己动手写的拷贝构造函数。
拷贝特点:拷贝对象成员变量和内存空间
class name { public: name(const char* p_name) { pname=(char*)malloc(sizeof(p_name)+1); strcpy(pname, p_name); } name(const name &obj) { pname=(char*)malloc(sizeof(obj.pname)+1); strcpy(pname , obj.pname); } ~name() { free(pname);
pname = NULL; } private: char* pname; }
对象赋值操作:
name obj1;
name obj2;
obj2 = obj1;
默认赋值操作与浅拷贝,可能导致对象析构时出错,原因是:默认赋值操作函数也默认的拷贝操作都是成员变量的简单赋值,只要类中有指针域,那么可能有两个对象的指针指向同一块内存空间,由于不能同时释放两次,所以可能出现错误。怎么解决这个问题——赋值重载。
name operator=(const name obj) { pname=(char*)malloc(sizeof(obj.pname)+1); strcpy(pname , obj.pname); return *this; }
这样存在一个问题:例如
void main() { name obj1("helloworld"); name obj2("hahahahah"); obj2 = obj1; }
因为在构建obj2时创建了一个对象,它指向一段内存空间存放的是:“”hahahah" ,但是在执行赋值操作时,又malloc了一个新的空间,然后用obj2的pname执行了这个新的空间,并往这个新空间赋值“helloworld”,那么之前的"hahahahah"空间没有释放的机会——导致内存泄漏。怎么解决这个问题——:
name operator=(const name obj) { if(pname != NULL) { free(pname); //释放之前的空间 pname = NULL; } pname=(char*)malloc(sizeof(obj.pname)+1); strcpy(pname , obj.pname); return *this; }
构造函数规则:
当类中没有定义构造函数时,编译器会生成默认构造函数和拷贝构造函数。
当类中定义了构造函数(无参或带参或拷贝),那么编译器不会生成默认构造函数(无参)。
默认的拷贝构造函数是浅拷贝构造函数。
构造函数的初始化列表:
必须用初始化列表初始化的情况:
1、const成员
2、引用类型成员
3、对象
初始化列表的初始化顺序与构造函数调用顺序是无关的,构造函数的调用遵行:基类构造函数-->成员对象构造函数(先定义先构造)--->派生类构造函数
为什么要引入初始化列表来初始化这些成员变量?因为普通初始化方式的构造函数不能进行它们的初始化。
class A { public: A(int a1) { mya=a1; } private: int mya; } class B { public: B(int x ,int y):mya(x) { myb = y; } private: A mya; int myb; }
匿名对象的生命周期:
class A { public: A(int a) { mya = a; } ~A() { } private: int mya; } void main() { A(3); //调用构造函数,生成匿名对象,然后析构 A a(4); //调用构造函数,生成匿名对象取名为a,不马上析构 }
构造函数中能不能调用构造函数?
class A { public: A(int a,int b,int c) { mya = a; myb = b; myc = c; } A(int a,int b) { mya = a; myb = b; A(a,b,100); //怎么执行 } ~A() { } private: int mya; }
编译是能够通过的,正如前面所说,直接调用构造函数时,是会生成一个匿名的对象的,A(a,b,100); 生成的匿名对象的确能够将c赋值为100,但是执行完这个语句之后,马上析构掉匿名对象,所以c的空间又被释放掉了。所以最中c的值还是垃圾值。

浙公网安备 33010602011771号