今天的知识内容:
1)为什么会出现构造函数与析构函数
构造函数3种初始化
2)拷贝函数4种应用时机
【新发现】在拷贝构造函数里面 可以直接访问私有成员属性
两个已经初始化完毕的对象 用= 号赋值不会调用构造函数
3)匿名对象的去与留
GCC 与 VS环境编译的差异
一旦自定义了构造函数,你必须去调用
4)【强化】构造函数 与 析构函数的执行顺序强化练习
对象中包含有其他对象时,先初始化别人,再初始化自己
对象中包含有其他对象时,先析构自己,再析构别人
默认构造函数是浅拷贝
5)拷贝构造函数的深拷贝与浅拷贝
【注意】两个已经初始化完毕的对象 用= 号赋值不会调用构造函数,但是会调用拷贝构造函数
6)构造函数初始化列表
类中含有const属性变量,必须在构造函数的初始化列表赋值
7)匿名对象的生命周期,被接收 OR 无接收
8)在构造函数中调用构造函数,是一种危险的行为
9)C++ new delete 基础数据类型,数组 ,对象
new delete 与 malloc free 互用
10)类中静态变量,所有对象公用
类中静态变量,静态函数基本调用
类中静态函数只能调用静态变量
11)面向对象模型初探--其实还是C语言的面向过程
12)this指针初步
今天作业
为什么会出现构造函数 与 析构函数
chunli@Linux:~/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;
}
chunli@Linux:~/c++$ g++ -g main.cpp && ./a.out
类的构造函数 与 析构函数
chunli@Linux:~/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;}chunli@Linux:~/c++$ g++ -g -Wall main.cpp && ./a.out 构造函数 构造函数 Hello World 析构函数 析构函数
构造函数初始化变量:
析构函数释放内存
chunli@Linux:~/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;}chunli@Linux:~/c++$ g++ -Wall -g main.cpp && ./a.out 构造函数 构造函数 10Hello Linux!析构函数 析构函数 -------------chunli@Linux:~/c++$
类的构造函数3种初始化方式:
chunli@Linux:~/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;}正常编译:chunli@Linux:~/c++$ g++ -g main.cpp && ./a.out 没有默认参数的构造函数 a=1 b=2带有2个默认参数的构造函数 a=7带有1个默认参数的构造函数 a=1 b=4带有2个默认参数的构造函数 赋值构造函数构造函数 析构函数 析构函数 析构函数 析构函数 析构函数 -------------g++ -Wall编译chunli@Linux:~/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个默认参数的构造函数 赋值构造函数构造函数 析构函数 析构函数 析构函数 析构函数 析构函数 -------------chunli@Linux:~/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;}chunli@Linux:~/c++$ g++ -g main.cpp && ./a.out 2个参数a=1 b=5 拷贝构造函数被调用 a=-336889536 b=32764
拷贝构造函数的使用:
第1种调用方法:Test t2 = t1;
chunli@Linux:~/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;}chunli@Linux:~/c++$ g++ -g main.cpp && ./a.out 2个参数a=1 b=5 拷贝构造函数被调用 a=101 b=151
拷贝构造函数的第2 中调用方式:Test t2(t1);
chunli@Linux:~/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;}chunli@Linux:~/c++$ g++ -g main.cpp && ./a.out 2个参数a=1 b=5 拷贝构造函数被调用 a=101 b=151
【答疑】对象的赋值操作t2 = t1 ,不会调用赋值构造函数:
chunli@Linux:~/c++$ cat main.cpp chunli@Linux:~/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;}chunli@Linux:~/c++$ g++ -g main.cpp && ./a.out 2个参数a=1 b=5 2个参数a=99 b=99 a=1 b=5
赋值构造函数的第3中应用:函数
chunli@Linux:~/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;}chunli@Linux:~/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编译运行:chunli@Linux:~/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>ConsoleApplication2a=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下编译运行:chunli@Linux:~/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>ConsoleApplication2a=66 b =77构造函数初始化, 有2个参数a=67 b =78赋值构造函数被调用a=66 b =77析构函数被调用a=67 b=78a=67 b =78析构函数被调用
【难点】赋值构造函数应用4
【匿名对象的去和留】-- 去
1,创建一个对象来接收函数的返回
在VS编译下,返回的对象会调用一次析构函数,然后被接收就不会直接析构掉
在GCC下,不会出现这种情况
chunli@Linux:~/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;}chunli@Linux:~/c++$ GCC编译运行:chunli@Linux:~/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析构函数被调用chunli@Linux:~/c++$ VS编译运行:C:\Users\chunli\Documents\c_c++\ConsoleApplication2\Debug>ConsoleApplication2fun2 starta=1 b =2构造函数初始化, 有2个参数执行对象的成员函数 a=1 b=2因为此时匿名对象并没有转换成新对象,匿名对象就会析构fun1 starta=66 b =77构造函数初始化, 有2个参数a=67 b =78赋值构造函数被调用a=66 b =77析构函数被调用a=67 b =78析构函数被调用执行对象的成员函数 a=67 b=78fun2 enda=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;(扶正) 从匿名转成有名字了mLocation 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 ;}
【回顾上午】
构造函数初始化的三种方式:
chunli@Linux:~/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;}chunli@Linux:~/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编译有些区别:
chunli@Linux:~/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编译执行:chunli@Linux:~/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析构函数被调用chunli@Linux:~/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=4a=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=9a=7 b=8析构函数被调用a=11 b=12构造函数初始化, 有2个参数拷贝函数的调用时机,第4种a=7 b=8构造函数初始化, 有2个参数第5次调用拷贝构造函数a=8 b=9a=7 b=8析构函数被调用a=8 b=9析构函数被调用a=8 b=9析构函数被调用a=8 b=9析构函数被调用a=4 b=5析构函数被调用
在拷贝构造函数里面 可以直接访问私有成员属性
chunli@Linux:~$ cat main1.cpp #include <iostream>#include <string.h>using namespace std;class Str{public:Str(const char *p = NULL){if(p == NULL){this->p = NULL;this->len = 0;}else{this->len = strlen(p);this->p = new char[this->len +1 ];strncpy(this->p,p,this->len);this->p[this->len] = '\0';}}Str(const Str &from){this->len = from.len;this->p = new char[this->len +1];strncpy(this->p,from.p,this->len);this->p[this->len] = '\0';}void print(){if(this->p != NULL){cout<<this->p<<endl;}}private:int len;char *p;};int main(){Str s1;s1.print();Str s2("Hello World!");s2.print();Str s3 = "Hello Linux!";s3.print();Str s4 = s3;s4.print();return 0;}chunli@Linux:~$ g++ -o run main1.cpp && ./run Hello World!Hello Linux!Hello Linux!chunli@Linux:~$
【关于默认构造函数】
默认构造函数:
二个特殊的构造函数
1)默认无参构造函数
当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空
2)默认拷贝构造函数
当类中没有定义拷贝构造函数时,编译器默认提供一个默认拷贝构造函数,简单的进行成员变量的值复制
【构造函数调用规则研究 】
1)当类中没有定义任何一个构造函数时,c++编译器会提供默认无参构造函数和默认拷贝构造函数
2)当类中定义了拷贝构造函数时,c++编译器不会提供无参数构造函数
3) 当类中定义了任意的非拷贝构造函数(即:当类中提供了有参构造函数或无参构造函数),c++编译器不会提供默认无参构造函数
4 )默认拷贝构造函数成员变量简单赋值
总结:只要你写了构造函数,那么你必须用。
1,【当你定义了构造函数,你必须使用】:
先演示没有任何构造函数的情景:
chunli@Linux:~/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:chunli@Linux:~/c++$ g++ -Wall -g main.cpp && ./a.out 执行对象的成员函数 a=2071403600 b=32765
当类中定义了构造函数,如果不使用,对象初始化就会报错!
示范:
chunli@Linux:~/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;}编译运行:直接报错chunli@Linux:~/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(); ^
手动定义一个空的构造函数,编译通过!
chunli@Linux:~/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;}chunli@Linux:~/c++$ g++ -Wall -g main.cpp && ./a.out 执行对象的成员函数 a=89452272 b=32765 chunli@Linux:~/c++$
【结论】
在类的定义时,只要写了构造函数,编译器就不会再自动为你定义无参的构造函数,这个构造函数必须要使用!
构造析构阶段性总结
1)构造函数是C++中用于初始化对象状态的特殊函数
2)构造函数在对象创建时自动被调用
3)构造函数和普通成员函数都遵循重载规则
4)拷贝构造函数是对象正确初始化的重要保证
5)必要的时候,必须手工编写拷贝构造函数
【浅拷贝 与 深拷贝】
浅拷贝 示范,程序编译通过,运行就死掉,
因为释放已经释放过的内存,所以死掉
chunli@Linux:~/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;}编译运行:chunli@Linux:~/c++$ g++ -Wall -g main.cpp && ./a.out I'm init....hello worldhello worldI'm Free!*** Error in `./a.out': double free or corruption (fasttop): 0x0000000001a93010 ***Aborted (core dumped)chunli@Linux:~/c++$ gdb调试:chunli@Linux:~/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);
手动写一个拷贝构造函数吗,完成深拷贝:
chunli@Linux:~/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;}编译运行:chunli@Linux:~/c++$ g++ -Wall -g main.cpp && ./a.out I'm init....hello worldhello worldI'm Free!I'm Free!
还是会存在宕掉的可能:
Test t1("hello world!");t1.show();
Test t2("hello Linux!");t2.show();
t1 = t2;//=等号也是浅拷贝
这样会再次导致一片内存两次free,继续宕机!
chunli@Linux:~/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;}编译通过,运行出错!chunli@Linux:~/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调试】:chunli@Linux:~/c++$ gdb ./a.out (gdb) run(gdb) where#5 0x0000000000400b43 in Test::~Test (this=0x7fffffffead0, __in_chrg=<optimized out>) at main.cpp:3232行就是 free(p);
构造函数 初始化 列表:
1,首先会执行被组合对象的构造函数
2,如果组成对象有多个,安照对象定义的顺序初始化,而不是按照列表的顺序
3,被组合对象的析构顺序与构造顺序相反
chunli@Linux:~/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;}chunli@Linux:~/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!chunli@Linux:~/c++$
如果类的属性有const这么一个属性,必须在构造函数的初始化列表中
chunli@Linux:~/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),c(3){a = _a;cout << "Test2 1 I'm init....\n";}//经典用法,参数传递Test2(int _a,int _b,int _c,int _d):t11(_c),t12(_d),c(_d){a = _a;b = _b;cout << "Test2 2 I'm init....\n";}~Test2(){cout << "Test2 I'm Free!\n";}private:int a;int b;const int c;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;}编译运行:chunli@Linux:~/c++$ g++ -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!
构造函数,拷贝函数,析构函数综合练习题,根据程序,写出屏幕输出:
【提醒:】
对象中包含有其他对象时,先初始化别人,再初始化自己
对象中包含有其他对象时,先析构自己,再析构别人
chunli@Linux:~/c++$ cat 123.cpp //对象做函数参数//1 研究拷贝构造 //2 研究构造函数,析构函数的调用顺序//总结 构造和析构的调用顺序#include "iostream"#include "stdio.h"using namespace std;class ABCD {public:ABCD(int a, int b, int c){this->a = a;this->b = b;this->c = c;printf("ABCD() construct, a:%d,b:%d,c:%d \n", this->a, this->b, this->c);}~ABCD(){printf("~ABCD() construct,a:%d,b:%d,c:%d \n", this->a, this->b, this->c);}int getA() {return this->a;}protected:private:int a;int b;int c;};class MyE{public:MyE():abcd1(1,2,3),abcd2(4,5,6),m(100){cout<<"MyD()"<<endl;}~MyE(){cout<<"~MyD()"<<endl;}MyE(const MyE & obj):abcd1(7,8,9),abcd2(10,11,12),m(100){printf("MyD(const MyD & obj)\n");}protected://private:public:ABCD abcd1; //c++编译器不知道如何构造abc1ABCD abcd2;const int m;};int doThing(MyE mye1){printf("doThing() mye1.abc1.a:%d \n", mye1.abcd1.getA()); return 0;}int run2(){MyE myE;doThing(myE);return 0;}//int run3(){printf("run3 start..\n");//ABCD(400, 500, 600); //临时对象的生命周期 ABCD abcd = ABCD(100, 200, 300);//若直接调用构造函数呢?//想调用构造函数对abc对象进行再复制,可以吗?//在构造函数里面调用另外一个构造函数,会有什么结果?printf("run3 end\n");return 0;}int main(){run2();//run3();return 0;}先不要看答案,自己分析一下【注意子对象的构造 与 析构 顺序!】chunli@Linux:~/c++$ g++ 123.cpp && ./a.out ABCD() construct, a:1,b:2,c:3 ABCD() construct, a:4,b:5,c:6 MyD()ABCD() construct, a:7,b:8,c:9 ABCD() construct, a:10,b:11,c:12 MyD(const MyD & obj)doThing() mye1.abc1.a:7 ~MyD()~ABCD() construct,a:10,b:11,c:12 ~ABCD() construct,a:7,b:8,c:9 ~MyD()~ABCD() construct,a:4,b:5,c:6 ~ABCD() construct,a:1,b:2,c:3 chunli@Linux:~/c++$
匿名对象的生命周期 强化训练:
分析下列程序的输出结果:
chunli@Linux:~/c++$ cat 123.cpp #include "iostream"#include "stdio.h"using namespace std;class ABCD {public:ABCD(int a, int b, int c){this->a = a;this->b = b;this->c = c;printf("ABCD() construct, a:%d,b:%d,c:%d \n", this->a, this->b, this->c);}~ABCD(){printf("~ABCD() construct,a:%d,b:%d,c:%d \n", this->a, this->b, this->c);}int getA() {return this->a;}protected:private:int a;int b;int c;};class MyE{public:MyE():abcd1(1,2,3),abcd2(4,5,6),m(100){cout<<"MyD()"<<endl;}~MyE(){cout<<"~MyD()"<<endl;}MyE(const MyE & obj):abcd1(7,8,9),abcd2(10,11,12),m(100){printf("MyD(const MyD & obj)\n");}protected:public:ABCD abcd1; //c++编译器不知道如何构造abc1ABCD abcd2;const int m;};int run2(){printf("run2 start..\n");ABCD(400, 500, 600); //临时对象的生命周期 printf("run2 end\n");return 0;}int run3(){printf("run3 start..\n");ABCD abcd = ABCD(100, 200, 300);//这个动作不会调用拷贝构造函数printf("run3 end\n");return 0;}int main(){run2();cout << "\n\n";run3();return 0;}编译运行:chunli@Linux:~/c++$ g++ 123.cpp && ./a.out run2 start..ABCD() construct, a:400,b:500,c:600 ~ABCD() construct,a:400,b:500,c:600 run2 endrun3 start..ABCD() construct, a:100,b:200,c:300 run3 end~ABCD() construct,a:100,b:200,c:300
在构造函数中调用构造函数 是一种危险的行为:
chunli@Linux:~/c++$ cat 123.cpp #include "iostream"#include "stdio.h"using namespace std;//构造中调用构造是危险的行为class MyTest{public:MyTest(int a, int b, int c){this->a = a;this->b = b;this->c = c;}MyTest(int a, int b){this->a = a;this->b = b;MyTest(a, b, 100); //产生新的匿名对象}~MyTest(){printf("MyTest~:%d, %d, %d\n", a, b, c);}protected:private:int a;int b;int c;public:int getC() const { return c; }void setC(int val) { c = val; }};int main(){MyTest t1(1, 2);printf("c:%d", t1.getC()); //请问c的值是?return 0;}看分析图:编译运行:chunli@Linux:~/c++$ g++ 123.cpp && ./a.out MyTest~:1, 2, 100c:4196128MyTest~:1, 2, 4196128
C++ new delete
1,基础类型:
chunli@Linux:~/c++$ cat main.cpp #include <iostream>#include <stdlib.h>using namespace std;int main(){int *p1 = (int *)malloc(sizeof(int));*p1 = 10;cout << *p1 << endl;free(p1);int *p2 = new int;*p2 = 20;cout << *p2 << endl;detele p2;//分配的同时初始化int *p3 = new int(30);cout << *p3 << endl;delete p3;return 0;}chunli@Linux:~/c++$ g++ -g main.cpp && ./a.out 102030
数组类型的建立与释放:
chunli@Linux:~/c++$ cat main.cpp #include <iostream>#include <stdlib.h>using namespace std;int main(){int *p1 = new int[10];p1[1] = 1;delete [] p1;//数组的释放return 0;}chunli@Linux:~/c++$ g++ -g main.cpp && ./a.out chunli@Linux:~/c++$
new delete 动态创建对象
C 与 C++ 的差异
chunli@Linux:~/c++$ cat main.cpp #include <iostream>#include <stdlib.h>using namespace std;class Test{public:Test(int _a){a = _a;cout << a << " init \n";}~Test(){cout <<a<< " free \n";}private:int a;};int main(){//分配对象new delete//相同 和 不同的地方 new能执行类型构造函数 delete操作符 能执行类的析构函数//CTest *p1 =(Test *)malloc(sizeof(Test));free(p1);//C++Test *p2 = new Test(10);delete p2;return 0;}chunli@Linux:~/c++$ g++ -g main.cpp && ./a.out 10 init 10 free
new delete深入
【结论】
malloc只会分配内存的大小,不会调用类的构造函数,free也不会调用类的析构函数
但是new会调用类的构造函数,完成类的初始化,delete会调用类的析构函数
chunli@Linux:~/c++$ cat main.cpp #include <iostream>#include <stdlib.h>using namespace std;class Test{public:Test(int _a){a = _a;cout << a << " init \n";}~Test(){cout <<a<< " free \n";}private:int a;};int main(){//malloc只会分配内存的大小,不会调用类的构造函数,free也不会调用类的析构函数//但是new会调用类的构造函数,完成类的初始化,delete会调用类的析构函数// malloc -- > deleteint *p1 = (int *)malloc(sizeof(int));*p1 = 10;cout << *p1 << endl;delete p1;// new -- > freeint *p2 = new int(30);cout << *p2 << endl;free(p2);// new -- > freeint *p3 = new int[10];p3[1] = 1;free(p3);// malloc -- > delete触发析构Test *p4 =(Test *)malloc(sizeof(Test));delete p4;// new -- > free触发构造Test *p5 = new Test(10);free(p5);return 0;}chunli@Linux:~/c++$ g++ -g main.cpp && ./a.out 10300 free 10 init
类中静态成员,所有对象都共用:
chunli@Linux:~/c++$ cat main.cpp #include <iostream>#include <stdlib.h>using namespace std;class Test{public:void print_c(){cout << c << endl;}void add_c(){c += 1;}Test(int _a){a = _a;//cout << a << " init \n";}~Test(){//cout <<a<< " free \n";}private:int a;int b;static int c;};int Test::c = 10;int main(){Test t1(1);t1.add_c();t1.print_c();Test t2(2);t2.add_c();t2.print_c();Test t3(3);t3.add_c();t3.print_c();return 0;}chunli@Linux:~/c++$ g++ -g main.cpp && ./a.out 111213
类中静态成员函数的基本调用:
chunli@Linux:~/c++$ cat main.cpp #include <iostream>#include <stdlib.h>using namespace std;class Test{public:static void print_c(){cout << c << endl;}void add_c(){c += 1;}Test(int _a){a = _a;//cout << a << " init \n";}~Test(){//cout <<a<< " free \n";}private:int a;int b;static int c;};int Test::c = 10;int main(){Test t1(1);t1.add_c();//静态成员函数的调用方法t1.print_c();Test::print_c();return 0;}chunli@Linux:~/c++$ g++ -g main.cpp && ./a.out 1111
在类中静态函数中不能使用普通成员变量,普通成员变量
chunli@Linux:~/c++$ cat main.cpp #include <iostream>#include <stdlib.h>using namespace std;class Test{public:static void print_c(){cout << c << endl;cout << a << endl;}void add_c(){c += 1;}Test(int _a){a = _a;}~Test(){}private:int a;static int c;};int Test::c = 10;int main(){Test t1(1);t1.add_c();//静态成员函数的调用方法t1.print_c();Test::print_c();return 0;}编译就报错:chunli@Linux:~/c++$ g++ -g main.cpp && ./a.out main.cpp: In static member function ‘static void Test::print_c()’:main.cpp:27:7: error: invalid use of member ‘Test::a’ in static member function int a; ^
面向对象模型 初探:
前言
C++对象模型可以概括为以下2部分:
1. 语言中直接支持面向对象程序设计的部分,主要涉及如构造函数、析构函数、虚函数、继承(单继承、多继承、虚继承)、多态等等。
2. 对于各种支持的底层实现机制。
在c语言中,“数据”和“处理数据的操作(函数)”是分开来声明的,也就是说,语言本身并没有支持“数据和函数”之间的关联性。在c++中,通过抽象数据类型(abstractdata type,ADT),在类中定义数据和函数,来实现数据和函数直接的绑定。
概括来说,在C++类中有两种成员数据:static、nonstatic;三种成员函数:static、nonstatic、virtual。
C++中的class从面向对象理论出发,将变量(属性)和函数(方法)集中定义在一起,用于描述现实世界中的类。从计算机的角度,程序依然由数据段和代码段构成。
C++编译器如何完成面向对象理论到计算机程序的转化?
换句话:C++编译器是如何管理类、对象、类和对象之间的关系
具体的说:具体对象调用类中的方法,那,c++编译器是如何区分,是那个具体的类,调用这个方法那?
根据程序,写出屏幕输出结果:
chunli@Linux:~/c++$ cat main.cpp #include "iostream"#include "stdio.h"using namespace std;class C1{public:int i; //4int j; //4int k; //4protected:private:}; //12class C2{public:int i; int j; int k; static int m; //4public:int getK() const { return k; } //4void setK(int val) { k = val; } //4protected:private:}; struct S1{int i;int j;int k;}; struct S2{int i;int j;int k;static int m;};int main(){printf("c1:%ld \n", sizeof(C1));printf("c2:%ld \n", sizeof(C2));printf("s1:%ld \n", sizeof(S1));printf("s2:%ld \n", sizeof(S2));}编译运行:chunli@Linux:~/c++$ g++ -g main.cpp && ./a.out c1:12 c2:12 s1:12 s2:12
通过上面的案例,我们可以的得出:
1)C++类对象中的成员变量和成员函数是分开存储的
成员变量:
普通成员变量:存储于对象中,与struct变量有相同的内存布局和字节对齐方式
静态成员变量:存储于全局数据区中
成员函数:存储于代码段中。
问题出来了:很多对象共用一块代码?代码是如何区分具体对象的那?
换句话说:int getK() const { return k; },代码是如何区分,具体obj1、obj2、obj3对象的k值?
this指针初步:
chunli@Linux:~/c++$ cat main.cpp #include <iostream>#include <stdlib.h>using namespace std;class Test{public:Test(int a,int b){this->a = a;this->b = b;}void print_var(){cout << a << " " << b << endl; }private:int a;int b;};int main(){Test t1(1,3);t1.print_var();Test t2(5,7);t2.print_var();return 0;}编译运行:chunli@Linux:~/c++$ g++ -g main.cpp && ./a.out 1 35 7chunli@Linux:~/c++$
1、C++类对象中的成员变量和成员函数是分开存储的。C语言中的内存四区模型仍然有效!
2、C++中类的普通成员函数都隐式包含一个指向当前对象的this指针。
3、静态成员函数、成员变量属于类
静态成员函数与普通成员函数的区别
静态成员函数不包含指向具体对象的指针
普通成员函数包含一个指向具体对象的指针
今天作业:
本文出自 “李春利” 博客,请务必保留此出处http://990487026.blog.51cto.com/10133282/1794395