为什么会出现构造函数 与 析构函数
[email protected]:~/c++$ cat main.cpp #include <iostream> #include <stdlib.h> #include <string.h> using namespace std; class Test { public: void init() { a = 1; b = 2; } private: int a; int b; }; int main() { Test arr[3] ; arr[0].init(); //显式的执行初始化 arr[1].init(); arr[2].init(); Test haha[19999] ; //请问这个怎么去初始化 return 0; } [email protected]:~/c++$ g++ -g main.cpp && ./a.out
类的构造函数 与 析构函数
[email protected]:~/c++$ cat main.cpp #include <iostream> using namespace std; class Test { public: Test() { cout << "构造函数 \n"; } ~Test() { cout << "析构函数 \n"; } }; int main() { Test t1; Test t2; cout << "Hello World \n"; return 0; } [email protected]:~/c++$ g++ -g -Wall main.cpp && ./a.out 构造函数 构造函数 Hello World 析构函数 析构函数
构造函数初始化变量:
析构函数释放内存
[email protected]:~/c++$ cat main.cpp #include <iostream> #include <stdlib.h> #include <string.h> using namespace std; class Test { public: Test() { a = 10; p = (char *)malloc(100); strcpy(p,"Hello Linux!"); cout << "构造函数 \n"; } void printf_var() { cout << a << endl; cout << p << endl; } ~Test() { if(p != NULL) { free(p); } cout << "析构函数 \n"; } private: char *p; int a; }; void fun() { Test t1; Test t2; t1.printf_var(); } int main() { fun(); cout << "-------------\n"; return 0; } [email protected]:~/c++$ g++ -Wall -g main.cpp && ./a.out 构造函数 构造函数 10 Hello Linux! 析构函数 析构函数 ------------- [email protected]:~/c++$
类的构造函数3种初始化方式:
[email protected]:~/c++$ cat main.cpp #include <iostream> #include <stdlib.h> #include <string.h> using namespace std; class Test { public: Test() { cout << "没有默认参数的构造函数 \n"; } Test(int _a) { a = _a; cout << "a=" << a <<"带有1个默认参数的构造函数 \n"; } Test(int _a,int _b) { a = _a; b = _b; cout << "a=" << a << " b=" << b<< "带有2个默认参数的构造函数 \n"; } Test(const Test &OBJ) //用一个对象初始化另一个对象 { cout << "赋值构造函数构造函数 \n"; } ~Test() { cout << "析构函数 \n"; } private: int a; int b; }; void fun() { Test t1; //调用无参数的构造函数 //===== 对象的初始化 ========== /*1*/Test t2(1,2); //逗号表达式的最后一个值是整个表达式的值 g++ -Wall 编译有警告:逗号操作符的左操作数没有效果 /*2*/Test t3 = (3,4,5,6,7); //这个等号C++编译器做了增强 /*3*/Test t4 = Test(1,4);/*匿名对象*/ t1 = t4; //对象的赋值,这个等号是普通的赋值操作 Test t5(t2); } int main() { fun(); cout << "-------------\n"; return 0; } 正常编译: [email protected]:~/c++$ g++ -g main.cpp && ./a.out 没有默认参数的构造函数 a=1 b=2带有2个默认参数的构造函数 a=7带有1个默认参数的构造函数 a=1 b=4带有2个默认参数的构造函数 赋值构造函数构造函数 析构函数 析构函数 析构函数 析构函数 析构函数 ------------- g++ -Wall编译 [email protected]:~/c++$ g++ -Wall -g main.cpp && ./a.out main.cpp: In function ‘void fun()’: main.cpp:44:20: warning: left operand of comma operator has no effect [-Wunused-value] /*2*/Test t3 = (3,4,5,6,7); ^ 没有默认参数的构造函数 a=1 b=2带有2个默认参数的构造函数 a=7带有1个默认参数的构造函数 a=1 b=4带有2个默认参数的构造函数 赋值构造函数构造函数 析构函数 析构函数 析构函数 析构函数 析构函数 ------------- [email protected]:~/c++$
【拷贝构造函数应用的4种时机】
1,抛题 -- 乱码的出现:
#include <iostream> #include <stdlib.h> #include <string.h> using namespace std; class Test { public: Test(int _a,int _b) { cout << "2个参数\n"; a = _a; b = _b; } Test(Test &OBJ) { cout << "赋值构造函数被调用 \n"; } void fun1() { cout << "a="<< a << " "; cout << "b="<< b << " \n"; } private: int a; int b; }; int main() { Test t1(1,5); t1.fun1(); Test t2 = t1; //用t1 的参数初始化t2.会调用赋值构造函数, //如果没有编译器自己造,如果自定义了赋值构造函数,就直接调用 t2.fun1(); //此时 输出属性值是乱码。因为我们自定义的构造函数什么也没有做 return 0; } [email protected]:~/c++$ g++ -g main.cpp && ./a.out 2个参数 a=1 b=5 拷贝构造函数被调用 a=-336889536 b=32764
拷贝构造函数的使用:
第1种调用方法: Test t2 = t1;
[email protected]:~/c++$ cat main.cpp //拷贝构造函数应用的4种时机 #include <iostream> #include <stdlib.h> #include <string.h> using namespace std; class Test { public: Test(int _a,int _b) { cout << "2个参数\n"; a = _a; b = _b; } Test(const Test &OBJ) { a = OBJ.a + 100; b = OBJ.a + 150; cout << "拷贝构造函数被调用 \n"; } void fun1() { cout << "a="<< a << " "; cout << "b="<< b << " \n"; } private: int a; int b; }; int main() { Test t1(1,5); t1.fun1(); Test t2 = t1; t2.fun1(); return 0; } chu[email protected]:~/c++$ g++ -g main.cpp && ./a.out 2个参数 a=1 b=5 拷贝构造函数被调用 a=101 b=151
拷贝构造函数的第2 中调用方式:Test t2(t1);
[email protected]:~/c++$ cat main.cpp //拷贝构造函数应用的4种时机 #include <iostream> #include <stdlib.h> #include <string.h> using namespace std; class Test { public: Test(int _a,int _b) { cout << "2个参数\n"; a = _a; b = _b; } Test(const Test &OBJ) { a = OBJ.a + 100; b = OBJ.a + 150; cout << "拷贝构造函数被调用 \n"; } void fun1() { cout << "a="<< a << " "; cout << "b="<< b << " \n"; } private: int a; int b; }; int main() { Test t1(1,5); t1.fun1(); Test t2(t1); t2.fun1(); return 0; } [email protected]:~/c++$ g++ -g main.cpp && ./a.out 2个参数 a=1 b=5 拷贝构造函数被调用 a=101 b=151
【答疑】对象的赋值操作t2 = t1 ,不会调用赋值构造函数:
[email protected]:~/c++$ cat main.cpp [email protected]:~/c++$ cat main.cpp //拷贝构造函数应用的4种时机 #include <iostream> #include <stdlib.h> #include <string.h> using namespace std; class Test { public: Test(int _a,int _b) { cout << "2个参数\n"; a = _a; b = _b; } Test(const Test &OBJ) { a = OBJ.a + 100; b = OBJ.a + 150; cout << "赋值构造函数被调用 \n"; } void fun1() { cout << "a="<< a << " "; cout << "b="<< b << " \n"; } private: int a; int b; }; int main() { Test t1(1,5); t1.fun1(); Test t2(99,99); t2.fun1(); t2 = t1; t2.fun1(); return 0; } [email protected]:~/c++$ g++ -g main.cpp && ./a.out 2个参数 a=1 b=5 2个参数 a=99 b=99 a=1 b=5
赋值构造函数的第3中应用:函数
[email protected]:~/c++$ cat main.cpp //拷贝构造函数应用的4种时机 #include <iostream> #include <stdlib.h> #include <string.h> using namespace std; class Test { public: Test(int _a,int _b) { cout << "2个参数\n"; a = _a; b = _b; } Test(const Test &OBJ) { a = OBJ.a + 100; b = OBJ.a + 150; cout << "赋值构造函数被调用 \n"; } ~Test() { cout << "a=" << a <<" b =" << b<< "析构函数被调用\n"; } void fun1() { cout << "a="<< a << " "; cout << "b="<< b << " \n"; } private: int a; int b; }; void fun2(Test OBJ) //会自动调用类的赋值构造函数 { OBJ.fun1(); } void fun1() { Test t1(1,5); t1.fun1(); Test t2 = t1; t2.fun1(); cout << "t2已经完成初始化\n"; fun2(t2); //t2实参会初始化形参OBJ,会调用赋值构造函数 } int main() { fun1(); return 0; } [email protected]:~/c++$ g++ -Wall -g main.cpp && ./a.out 2个参数 a=1 b=5 赋值构造函数被调用 a=101 b=151 t2已经完成初始化 赋值构造函数被调用 a=201 b=251 a=201 b =251析构函数被调用 a=101 b =151析构函数被调用 a=1 b =5析构函数被调用
【难点】赋值构造函数应用4
【匿名对象的去和留】-- 去
1,没有对象接收函数的返回
在VS编译下,返回的对象会调用一次析构函数,然后没人接收就析构掉
在GCC下,不会出现这种情况
【这第4种方法 VS环境 与 GCC 表现不一样】
//拷贝构造函数应用的4种时机 #include <iostream> #include <stdio.h> using namespace std; class Test { public: Test(int _a, int _b) { a = _a; b = _b; cout << "a=" << a << " b =" << b << "构造函数初始化, 有2个参数\n"; } Test(const Test &OBJ) { a = OBJ.a + 1; b = OBJ.b + 1; cout << "a=" << a << " b =" << b << "赋值构造函数被调用 \n"; } ~Test() { cout << "a=" << a << " b =" << b << "析构函数被调用\n"; } void fun1() { cout << "a=" << a << " "; cout << "b=" << b << " \n"; } private: int a; int b; }; //函数返回一个Test 对象 (复杂类型的) //返回的是一个新的匿名对象 //所以会调用匿名对象类的copy构造函数 Test fun1() { Test A(66, 77); return A; //新的对象作为匿名对象返回 } //用这个函数观测对象的生命周期 void fun2() { //微软VS环境有效:用b对象b来接返回的匿名对象,不会直接析构 //GCC 既不执行copy构造函数,也不执行析构函数 fun1(); } int main() { fun2(); getchar(); return 0; } GCC编译运行: [email protected]:~/c++$ g++ -Wall -g main.cpp && ./a.out a=66 b =77构造函数初始化, 有2个参数 a=66 b =77析构函数被调用 VS编译运行: C:\Users\chunli\Documents\c_c++\ConsoleApplication2\Debug>ConsoleApplication2 a=66 b =77构造函数初始化, 有2个参数 a=67 b =78赋值构造函数被调用 a=66 b =77析构函数被调用 a=67 b =78析构函数被调用
【难点】赋值构造函数应用4
【匿名对象的去和留】-- 留
1,创建一个对象来接收函数的返回
在VS编译下,返回的对象会调用一次析构函数,然后被接收就不会直接析构掉
在GCC下,不会出现这种情况
//拷贝构造函数应用的4种时机 #include <iostream> #include <stdio.h> using namespace std; class Test { public: Test(int _a, int _b) { a = _a; b = _b; cout << "a=" << a << " b =" << b << "构造函数初始化, 有2个参数\n"; } Test(const Test &OBJ) { a = OBJ.a + 1; b = OBJ.b + 1; cout << "a=" << a << " b =" << b << "赋值构造函数被调用 \n"; } ~Test() { cout << "a=" << a << " b =" << b << "析构函数被调用\n"; } void fun1() { cout << "a=" << a << " "; cout << "b=" << b << " \n"; } private: int a; int b; }; //函数返回一个Test 对象 (复杂类型的) //返回的是一个新的匿名对象 //所以会调用匿名对象类的copy构造函数 Test fun1() { Test A(66, 77); return A; //新的对象作为匿名对象返回 } //用这个函数观测对象的生命周期 void fun2() { //微软VS环境有效:用b对象b来接返回的匿名对象,不会直接析构 //GCC 既不执行copy构造函数,也不执行析构函数 //注意此时是一个新的对象,VS会调用copy构造函数,不会析构 Test b = fun1(); b.fun1(); } int main() { fun2(); getchar(); return 0; } GCC下编译运行: [email protected]:~/c++$ g++ -Wall -g main.cpp && ./a.out a=66 b =77构造函数初始化, 有2个参数 a=66 b=77 a=66 b =77析构函数被调用 VS下编译运行: C:\Users\chunli\Documents\c_c++\ConsoleApplication2\Debug>ConsoleApplication2 a=66 b =77构造函数初始化, 有2个参数 a=67 b =78赋值构造函数被调用 a=66 b =77析构函数被调用 a=67 b=78 a=67 b =78析构函数被调用
【难点】赋值构造函数应用4
【匿名对象的去和留】-- 去
1,创建一个对象来接收函数的返回
在VS编译下,返回的对象会调用一次析构函数,然后被接收就不会直接析构掉
在GCC下,不会出现这种情况
[email protected]:~/c++$ cat main.cpp //拷贝构造函数应用的4种时机 #include <iostream> #include <stdio.h> using namespace std; class Test { public: Test(int _a, int _b) { a = _a; b = _b; cout << "a=" << a << " b =" << b << "构造函数初始化, 有2个参数\n"; } Test(const Test &OBJ) { a = OBJ.a + 1; b = OBJ.b + 1; cout << "a=" << a << " b =" << b << "赋值构造函数被调用 \n"; } ~Test() { cout << "a=" << a << " b =" << b << "析构函数被调用\n"; } void fun1() { cout << "执行对象的成员函数 "; cout << "a=" << a << " "; cout << "b=" << b << " \n"; } private: int a; int b; }; //函数返回一个Test 对象 (复杂类型的) //返回的是一个新的匿名对象 //所以会调用匿名对象类的copy构造函数 Test fun1() { cout << "fun1 start \n" ; //A 是局部变量,会被析构 Test A(66, 77); return A; //新的对象作为匿名对象返回 cout << "fun1 end \n" ; } //用这个函数观测对象的生命周期 void fun2() { //微软VS环境有效:用b对象b来接返回的匿名对象,不会直接析构 //GCC 既不执行copy构造函数,也不执行析构函数 cout << "fun2 start \n" ; Test b(1,2); b.fun1(); cout << "因为此时匿名对象并没有转换成新对象,匿名对象就会析构\n"; b = fun1(); b.fun1(); cout << "fun2 end \n" ; } int main() { fun2(); getchar(); return 0; } [email protected]:~/c++$ GCC编译运行: [email protected]:~/c++$ g++ -Wall -g main.cpp && ./a.out fun2 start a=1 b =2构造函数初始化, 有2个参数 执行对象的成员函数 a=1 b=2 因为此时匿名对象并没有转换成新对象,匿名对象就会析构 fun1 start a=66 b =77构造函数初始化, 有2个参数 a=66 b =77析构函数被调用 执行对象的成员函数 a=66 b=77 fun2 end a=66 b =77析构函数被调用 [email protected]:~/c++$ VS编译运行: C:\Users\chunli\Documents\c_c++\ConsoleApplication2\Debug>ConsoleApplication2 fun2 start a=1 b =2构造函数初始化, 有2个参数 执行对象的成员函数 a=1 b=2 因为此时匿名对象并没有转换成新对象,匿名对象就会析构 fun1 start a=66 b =77构造函数初始化, 有2个参数 a=67 b =78赋值构造函数被调用 a=66 b =77析构函数被调用 a=67 b =78析构函数被调用 执行对象的成员函数 a=67 b=78 fun2 end a=67 b =78析构函数被调用
结论: 有关 匿名对象的去和留【VS环境】
如果用匿名对象 初始化 另外一个同类型的对象, 匿名对象 转成有名对象
如果用匿名对象 赋值给 另外一个同类型的对象, 匿名对象 被析构
老师的笔记与图:老师的程序:
#include <iostream> using namespace std; class Location { public: Location( int xx = 0 , int yy = 0 ) { X = xx ; Y = yy ; cout << "Constructor Object.\n" ; } //copy构造函数 完成对象的初始化 Location(const Location & obj) //copy构造函数 { X = obj.X; Y = obj.Y; } ~Location() { cout << X << "," << Y << " Object destroyed." << endl ; } int GetX () { return X ; } int GetY () { return Y ; } private : int X , Y ; } ; //g函数 返回一个元素 //结论1 : 函数的返回值是一个元素 (复杂类型的), 返回的是一个新的匿名对象(所以会调用匿名对象类的copy构造函数) // //结论2: 有关 匿名对象的去和留 //如果用匿名对象 初始化 另外一个同类型的对象, 匿名对象 转成有名对象 //如果用匿名对象 赋值给 另外一个同类型的对象, 匿名对象 被析构 // //你这么写代码,设计编译器的大牛们: //我就给你返回一个新对象(没有名字 匿名对象) Location g() { Location A(1, 2); return A; } // void objplay2() { g(); } // void objplay3() { //用匿名对象初始化m 此时c++编译器 直接把匿名对转成m;(扶正) 从匿名转成有名字了m Location m = g(); printf("匿名对象,被扶正,不会析构掉\n"); cout<<m.GetX()<<endl;; } void objplay4() { //用匿名对象 赋值给 m2后, 匿名对象被析构 Location m2(1, 2); m2 = g(); printf("因为用匿名对象=给m2, 匿名对象,被析构\n"); cout<<m2.GetX()<<endl;; } void main() { //objplay2(); //objplay3(); objplay4(); cout<<"hello..."<<endl; system("pause"); return ; }
【回顾上午】
构造函数初始化的三种方式:
[email protected]:~/c++$ cat main.cpp #include <iostream> #include <stdio.h> #include <stdlib.h> using namespace std; class Test { public: Test(int _a) { a = _a; cout << "a=" << a << "构造函数初始化, 有1个参数\n"; } Test(int _a,int _b) { a = _a; b = _b; cout << "a=" << a << " b= " << b << "构造函数初始化, 有2个参数\n"; } void fun() { cout << "执行对象的成员函数 "; cout << "a=" << a << " "; cout << "b=" << b << " \n"; } private: int a; int b; }; int main() { Test t1(1,2); t1.fun();//C++编译器自动调用构造函数 Test t2 = (1,5,6); t2.fun();//C++编译器自动调用构造函数 Test t3 = Test(4,5); //程序员手动调用构造函数 return 0; } [email protected]:~/c++$ g++ -g main.cpp && ./a.out a=1 b= 2构造函数初始化, 有2个参数 执行对象的成员函数 a=1 b=2 a=6构造函数初始化, 有1个参数 执行对象的成员函数 a=6 b=0 a=4 b= 5构造函数初始化, 有2个参数
【回顾】拷贝构造函数的4种使用时机
GCC编译器 与 VS编译有些区别:
[email protected]:~/c++$ cat main.cpp #include <iostream> #include <stdio.h> #include <stdlib.h> using namespace std; int num = 0; class Test { public: Test(int _a) { a = _a; cout << "a=" << a << "构造函数初始化, 有1个参数\n"; } Test(int _a,int _b) { a = _a; b = _b; cout << "a=" << a << " b=" << b << "构造函数初始化, 有2个参数\n"; } Test(const Test &OBJ) { a = OBJ.a + 1; b = OBJ.b + 1; num++; cout << "第"<< num <<"次调用拷贝构造函数"; cout << "a=" << a << " b=" << b <<endl ; } ~Test() { cout << "a=" << a << " b=" << b << "析构函数被调用\n"; } void fun() { cout << "执行对象的成员函数 "; cout << "a=" << a << " "; cout << "b=" << b << " \n"; } private: int a; int b; }; void fun1() { cout << "拷贝函数的调用时机,第1种和第2种 \n"; Test t1(1,2); t1.fun(); Test t2 = t1; t2.fun(); //C++编译器会调用拷贝构造函数 Test t3(t2) ; t3.fun(); //C++编译器会调用拷贝构造函数 } void fun2(Test OBJ) { cout << "拷贝函数的调用时机,第3种\n"; } Test fun3() //返回一个Test类 { cout << "拷贝函数的调用时机,第4种\n"; Test t1(7,8); return t1; } int main() { fun1(); //拷贝函数的调用时机,第1种和第2种 Test t1(4,5); fun2(t1); //拷贝函数的调用时机,第3种 Test t2 = fun3(); //弄了一个新的对象接受了匿名对象,这个不会直接执行析构函数 Test t3(11,12); t3 = fun3(); //t3不是一个新的对象,匿名类会执行析构函数 return 0; } 1 GCC编译执行: [email protected]:~/c++$ g++ -Wall -g main.cpp && ./a.out 拷贝函数的调用时机,第1种和第2种 a=1 b=2构造函数初始化, 有2个参数 执行对象的成员函数 a=1 b=2 第1次调用拷贝构造函数a=2 b=3 执行对象的成员函数 a=2 b=3 第2次调用拷贝构造函数a=3 b=4 执行对象的成员函数 a=3 b=4 a=3 b=4析构函数被调用 a=2 b=3析构函数被调用 a=1 b=2析构函数被调用 a=4 b=5构造函数初始化, 有2个参数 第3次调用拷贝构造函数a=5 b=6 拷贝函数的调用时机,第3种 a=5 b=6析构函数被调用 拷贝函数的调用时机,第4种 a=7 b=8构造函数初始化, 有2个参数 a=11 b=12构造函数初始化, 有2个参数 拷贝函数的调用时机,第4种 a=7 b=8构造函数初始化, 有2个参数 a=7 b=8析构函数被调用 a=7 b=8析构函数被调用 a=7 b=8析构函数被调用 a=4 b=5析构函数被调用 [email protected]:~/c++$ 2 VS环境编译执行: C:\Users\chunli\Documents\c_c++\ConsoleApplication2\Debug>ConsoleApplication2 拷贝函数的调用时机,第1种和第2种 a=1 b=2构造函数初始化, 有2个参数 执行对象的成员函数 a=1 b=2 第1次调用拷贝构造函数a=2 b=3 执行对象的成员函数 a=2 b=3 第2次调用拷贝构造函数a=3 b=4 执行对象的成员函数 a=3 b=4 a=3 b=4析构函数被调用 a=2 b=3析构函数被调用 a=1 b=2析构函数被调用 a=4 b=5构造函数初始化, 有2个参数 第3次调用拷贝构造函数a=5 b=6 拷贝函数的调用时机,第3种 a=5 b=6析构函数被调用 拷贝函数的调用时机,第4种 a=7 b=8构造函数初始化, 有2个参数 第4次调用拷贝构造函数a=8 b=9 a=7 b=8析构函数被调用 a=11 b=12构造函数初始化, 有2个参数 拷贝函数的调用时机,第4种 a=7 b=8构造函数初始化, 有2个参数 第5次调用拷贝构造函数a=8 b=9 a=7 b=8析构函数被调用 a=8 b=9析构函数被调用 a=8 b=9析构函数被调用 a=8 b=9析构函数被调用 a=4 b=5析构函数被调用
【关于默认构造函数】
默认构造函数:
二个特殊的构造函数
1)默认无参构造函数
当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空
2)默认拷贝构造函数
当类中没有定义拷贝构造函数时,编译器默认提供一个默认拷贝构造函数,简单的进行成员变量的值复制
【构造函数调用规则研究 】
1)当类中没有定义任何一个构造函数时,c++编译器会提供默认无参构造函数和默认拷贝构造函数
2)当类中定义了拷贝构造函数时,c++编译器不会提供无参数构造函数
3) 当类中定义了任意的非拷贝构造函数(即:当类中提供了有参构造函数或无参构造函数),c++编译器不会提供默认无参构造函数
4 )默认拷贝构造函数成员变量简单赋值
总结:只要你写了构造函数,那么你必须用。
1,【当你定义了构造函数,你必须使用】:
先演示没有任何构造函数的情景:
[email protected]:~/c++$ cat main.cpp #include <iostream> #include <stdio.h> #include <stdlib.h> using namespace std; int num = 0; class Test { public: void fun() { cout << "执行对象的成员函数 "; cout << "a=" << a << " b=" << b << " \n"; } private: int a; int b; }; int main() { Test t1; t1.fun(); return 0; } 编译运行OK: [email protected]:~/c++$ g++ -Wall -g main.cpp && ./a.out 执行对象的成员函数 a=2071403600 b=32765
当类中定义了构造函数,如果不使用,对象初始化就会报错!
示范:
[email protected]:~/c++$ cat main.cpp #include <iostream> #include <stdio.h> #include <stdlib.h> using namespace std; int num = 0; class Test { public: Test(int _a,int _b) { a = _a; b = _b; cout << "a=" << a << " b=" << b << "构造函数初始化, 有2个参数\n"; } void fun() { cout << "执行对象的成员函数 "; cout << "a=" << a << " b=" << b << " \n"; } private: int a; int b; }; int main() { Test t1; t1.fun();//定义一个对象 return 0; } 编译运行:直接报错 [email protected]:~/c++$ g++ -Wall -g main.cpp && ./a.out main.cpp: In function ‘int main()’: main.cpp:43:7: error: no matching function for call to ‘Test::Test()’ Test t1; t1.fun(); ^
手动定义一个空的构造函数,编译通过!
[email protected]:~/c++$ cat main.cpp #include <iostream> #include <stdio.h> #include <stdlib.h> using namespace std; int num = 0; class Test { public: Test() { } void fun() { cout << "执行对象的成员函数 "; cout << "a=" << a << " b=" << b << " \n"; } private: int a; int b; }; int main() { Test t1; t1.fun(); return 0; } [email protected]:~/c++$ g++ -Wall -g main.cpp && ./a.out 执行对象的成员函数 a=89452272 b=32765 [email protected]:~/c++$
【结论】
在类的定义时,只要写了构造函数,编译器就不会再自动为你定义无参的构造函数,这个构造函数必须要使用!
构造析构阶段性总结
1)构造函数是C++中用于初始化对象状态的特殊函数
2)构造函数在对象创建时自动被调用
3)构造函数和普通成员函数都遵循重载规则
4)拷贝构造函数是对象正确初始化的重要保证
5)必要的时候,必须手工编写拷贝构造函数
【浅拷贝 与 深拷贝】
浅拷贝 示范,程序编译通过,运行就死掉,
因为释放已经释放过的内存,所以死掉
[email protected]:~/c++$ cat main.cpp #include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> using namespace std; int num = 0; class Test { public: Test(const char *str) { cout << "I‘m init....\n"; len = strlen(str); p = (char *)malloc(len +1); strcpy(p,str); *(p + len) = ‘\0‘; } ~Test() { if(p != NULL) { free(p); p = NULL; len = 0; } cout << "I‘m Free!\n"; } void show() { cout << p <<"\n"; } private: char *p; int len; }; void fun1() { Test t1("hello world"); t1.show(); Test t2(t1); t2.show(); } int main() { fun1(); return 0; } 编译运行: [email protected]:~/c++$ g++ -Wall -g main.cpp && ./a.out I‘m init.... hello world hello world I‘m Free! *** Error in `./a.out‘: double free or corruption (fasttop): 0x0000000001a93010 *** Aborted (core dumped) [email protected]:~/c++$ gdb调试: [email protected]:~/c++$ gdb ./a.out (gdb) run (gdb) where #5 0x0000000000400b35 in Test::~Test (this=0x7fffffffead0, __in_chrg=<optimized out>) at main.cpp:23 会看到 at main.cpp:23 这一行就是 free(p);
手动写一个拷贝构造函数吗,完成深拷贝:
[email protected]:~/c++$ cat main.cpp #include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> using namespace std; int num = 0; class Test { public: Test(const char *str) { cout << "I‘m init....\n"; len = strlen(str); p = (char *)malloc(len +1); strcpy(p,str); *(p + len) = ‘\0‘; } Test(const Test &obj) // 当外部调用 Test t2(t1); { //深度拷贝 len = obj.len; p = (char *)malloc(len +1);; strcpy(p,obj.p); *(p + len) = ‘\0‘; } ~Test() { if(p != NULL) { free(p); p = NULL; len = 0; } cout << "I‘m Free!\n"; } void show() { cout << p <<"\n"; } private: char *p; int len; }; void fun1() { Test t1("hello world"); t1.show(); Test t2(t1); t2.show(); //Test t3 = t1; =等号也是浅拷贝 } int main() { fun1(); return 0; } 编译运行: [email protected]:~/c++$ g++ -Wall -g main.cpp && ./a.out I‘m init.... hello world hello world I‘m Free! I‘m Free!
还是会存在宕掉的可能:
Test t1("hello world!"); t1.show();
Test t2("hello Linux!"); t2.show();
t1 = t2; //=等号也是浅拷贝
这样会再次导致一片内存两次free,继续宕机!
[email protected]:~/c++$ cat main.cpp #include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> using namespace std; int num = 0; class Test { public: Test(const char *str) { cout << "I‘m init....\n"; len = strlen(str); p = (char *)malloc(len +1); strcpy(p,str); *(p + len) = ‘\0‘; } Test(const Test &obj) // 当外部调用 Test t2(t1); { //深度拷贝 len = obj.len; p = (char *)malloc(len +1);; strcpy(p,obj.p); *(p + len) = ‘\0‘; cout << "I‘m in copy \n"; } ~Test() { if(p != NULL) { free(p); p = NULL; len = 0; } cout << "I‘m Free!\n"; } void show() { cout << p <<"\n"; } private: char *p; int len; }; void fun1() { Test t1("hello world!"); t1.show(); Test t2("hello Linux!"); t2.show(); t1 = t2; //=等号也是浅拷贝 } int main() { fun1(); return 0; } 编译通过,运行出错! [email protected]:~/c++$ g++ -Wall -g main.cpp && ./a.out I‘m init.... hello world! I‘m init.... hello Linux! I‘m Free! *** Error in `./a.out‘: double free or corruption (fasttop): 0x000000000249b030 *** Aborted (core dumped) 【gdb调试】: [email protected]:~/c++$ gdb ./a.out (gdb) run (gdb) where #5 0x0000000000400b43 in Test::~Test (this=0x7fffffffead0, __in_chrg=<optimized out>) at main.cpp:32 32行就是 free(p);
构造函数 初始化 列表:
1,首先会执行被组合对象的构造函数
2,如果组成对象有多个,安照对象定义的顺序初始化,而不是按照列表的顺序
3,被组合对象的析构顺序与构造顺序相反
[email protected]:~/c++$ cat main.cpp #include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> using namespace std; int num = 0; class Test1 { public: Test1(int _a) { a = _a; cout << a << " Test1 I‘m init....\n"; } ~Test1() { cout << a << " Test1 I‘m Free!\n"; } private: int a; }; class Test2 { public: //在初始化参数的同时,将Test1对象完成初始化 Test2(int _a):t11(1),t12(2) { a = _a; cout << "Test2 1 I‘m init....\n"; } //经典用法,参数传递 Test2(int _a,int _b,int _c,int _d):t11(_c),t12(_d) { a = _a; b = _b; cout << "Test2 2 I‘m init....\n"; } ~Test2() { cout << "Test2 I‘m Free!\n"; } private: int a; int b; Test1 t11; Test1 t12; }; int main() { //1 构造函数的初始化列表 解决: 在B类中 组合了一个 A类对象 (A类设计了构造函数) //根据构造函数的调用规则 设计A的构造函数, 必须要用;没有机会初始化A //新的语法 Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3) Test2 t22(1,3,1,4); return 0; } [email protected]:~/c++$ g++ -Wall -g main.cpp && ./a.out 1 Test1 I‘m init.... 4 Test1 I‘m init.... Test2 2 I‘m init.... Test2 I‘m Free! 4 Test1 I‘m Free! 1 Test1 I‘m Free! [email protected]:~/c++$