[类和对象]2 构造和析构
1.构造和析构
有关构造函数
1构造函数定义及调用
1)C++中的类可以定义与类名相同的特殊成员函数,这种与类名相同的成员函数叫做构造函数;
2)构造函数在定义时可以有参数;
3)没有任何返回类型的声明。
2构造函数的调用
自动调用:一般情况下C++编译器会自动调用构造函数
手动调用:在一些情况下则需要手工调用构造函数
有关析构函数
析构函数定义及调用
1)C++中的类可以定义一个特殊的成员函数清理对象,这个特殊的成员函数叫做析构函数
语法:~ClassName()
2)析构函数没有参数也没有任何返回类型的声明
3)析构函数在对象销毁时自动被调用
4)析构函数调用机制
C++编译器自动调用
先创建的对象 后释放
Q对象销毁是发生在什么时候?
A对象的析构函数在的对象销毁前被调用,对象何时销毁也与其作用域有关。
例如,全局对象是在程序运行结束时销毁,自动对象是在离开其搜索作用域时销毁,而动态对象则是在使用delete运算符时销毁。
无参构造函数
有参构造函数[c++编译器默认调用有参构造函数]
赋值构造函数
#include <iostream> using namespace std; class Test2 { public: //无参数构造函数 Test2() { m_a = 0; m_b = 0; cout<<"无参数构造函数"<<endl; } //有参数构造函数 Test2(int a) { m_a = a; m_b = 0; } Test2(int a, int b) { m_a = a; m_b = b; cout<<"有参数构造函数"<<endl; } //赋值构造函数 (copy构造函数) Test2(const Test2& obj) { cout<<"我也是构造函数 " <<endl; } public: void printT() { cout<<"普通成员函数"<<endl; } private: int m_a; int m_b; }; int main01() { Test2 t1; //调用无参数构造函数 cout<<"hello..."<<endl; return 0; } //调用 调用有参数构造函数 3 int main(void) { //1括号法 Test2 t1(1, 2); //调用参数构造函数 c++编译器自动的调用构造函数 t1.printT(); // 2 =号法[但这是一个很鸡肋的用法] Test2 t2 = (3, 4); // = c++对等号符 功能增强 c++编译器自动的调用构造函数 //对象的初始化 和 对象的赋值 是两个不同的概念 //3 对象的初始化 Test2 t4 = Test2(1, 2); //匿名对象 t1 = t4; //把t4 copy给 t1 //赋值操作
//4 赋值构造函数 (copy构造函数) Test2 t5 = Test2(t1); //等价于Test2 t5(t1); 等价于 Test2 t5 = t1;
//☆ Test2 t5 = Test2(t1);是程序员手工的调用构造函数
cout<<"hello..."<<endl; return 0; } /*============================================== END File ==============================================*/
有参数构造函数
普通成员函数
有参数构造函数
我也是构造函数
hello...
2.赋值构造函数的4种应用场景
这是初始化的两种; Test t2 = t1; Test t2(t1);
第三种是功能函数的形参是一个对象的时候{非指针,非引用},这个时候实参给形参的时候必然调用拷贝构造
第四种是你说的返回,返回一个对象的时候,但这个时候返回的是匿名对象,
如果是初始化,是直接会转换为要初始化的那个对象的,而且不会被析构掉
如果是赋值操作,这个时候确实会调用copy构造,但是这个对象的生命周期只有那么一行,用完立即被析构
2.1 copy构造第一, 二种
#include <iostream> using namespace std; class Test4 { public: Test4() //无参数构造函数 { m_a = 0; m_b = 0; cout<<"无参数构造函数"<<endl; } Test4(int a) { m_a = a; m_b = 0; } Test4(int a, int b) //有参数构造函数 { m_a = a; m_b = b; cout<<"有参数构造函数"<<endl; } Test4(const Test4& obj )//赋值构造函数 (copy构造函数) { cout<<"赋值构造函数 " <<endl; m_b = obj.m_b + 100; m_a = obj.m_a + 100; } public: void printT() { cout<<"普通成员函数"<<endl; cout<<"m_a"<<m_a<<" m_b"<<m_b<<endl; } private: int m_a; int m_b; }; //1 赋值构造函数 用1个对象去初始化另外一个对象 int main(void) { Test4 t0(1, 2); Test4 t1(1, 2); /*赋值操作 和 初始化是两个不同的概念 =的赋值操作 不会调用构造函数 =的初始化操作 会调用构造函数 */ //operator=()//抛砖 t0 = t1; //用t1 给 t0赋值 //第1种调用方法 Test4 t2 = t1; //用t1来初始化 t2 【one】 t2.printT(); //第2种调用方法 Test4 t3(t1); //用t1对象 初始化 t2对象 【two】 t2.printT(); cout<<"hello..."<<endl; return 0; }
2.2 copy构造第三种
//[Three]
#include <iostream> using namespace std; class Location { public: Location( int xx = 0 , int yy = 0 ) { X = xx ; Y = yy ; cout << "None Param Constructor Func.\n" ; } //copy构造函数 完成对象的初始化 Location(const Location & obj) //copy构造函数 { cout << "Copy Constructor func.\n" ; 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 ; }; //业务函数 形参是一个元素 void f(Location p) { cout<<"业务函数"<<endl; cout<<p.GetX()<<endl; } void playobj() { Location a(1, 2); Location b = a; cout<<"b对象已经初始化完毕"<<endl; f(b); //实参b传给形参p会调用copy构造函数 } int main() { playobj(); cout<<"hello..."<<endl; return 0; }
None Param Constructor Func.
Copy Constructor func.
b对象已经初始化完毕
Copy Constructor func.
业务函数
1
1,2 Object destroyed.
1,2 Object destroyed.
1,2 Object destroyed.
hello...
2.3 copy构造第四种
//[Four]
#include <iostream> #include <cstdio> 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("\r\n匿名对象,被扶正,不会析构掉\n"); cout<<m.GetX()<<endl; } void objplay4() { //用匿名对象 赋值给 m2后, 匿名对象被析构 Location m2(1, 2); m2 = g(); printf("\r\n因为用匿名对象=给m2, 匿名对象,被析构\n"); cout<<m2.GetX()<<endl;; } int main(void) { objplay2(); // objplay3(); // objplay4(); cout<<"hello..."<<endl; return 0; }
3.二个系统提供的特殊构造函数
1) 默认无参构造函数
当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空
2) 默认拷贝构造函数
当类中没有定义拷贝构造函数时,编译器默认提供一个默认拷贝构造函数,简单的进行成员变量的值复制
1)当类中没有定义任何一个构造函数时, c++编译器会提供默认无参构造函数和默认拷贝构造函数
2)当类中定义了拷贝构造函数时, c++编译器不会提供无参数构造函数
3)当类中定义了任意的非拷贝构造函数(即:当类中提供了有参构造函数或无参构造函数), c++编译器不会提供默认无参构造函数
4 )默认拷贝构造函数成员变量简单赋值
总结:只要你写了构造函数,那么你必须用。
构造析构阶段性总结
1)构造函数是C++中用于初始化对象状态的特殊函数
2)构造函数在对象创建时自动被调用, 在对象离开其作用域时候自动销毁
3)构造函数和普通成员函数都遵循重载规则
4)拷贝构造函数是对象正确初始化的重要保证
5) 必要的时候,必须手工编写拷贝构造函数 eg:深拷贝和浅拷贝
4.深拷贝和浅拷贝
默认拷贝构造函数可以完成对象的数据成员值简单的赋值
对象的数据资源是由指针指示的堆时,默认复制构造函数仅作指针值复制
上图分析:
1. Name obj1("abcdefg");
2. Name obj2 = obj1; //C++编译器提供的 默认的copy构造函数 【浅拷贝】
因为类Name里没有copy构造函数,所以要使用默认的copy构造函数,
但这种拷贝只是浅拷贝,只会拷贝p 和 len 的数值,并不会对obj2进行开辟内存
所以,当析构的时候,先析构obj2,但在析构obj1的时候,发生内存错误
解决方案:Name(const Name& obj1)
下图分析:
3. Name obj4("obj4");
4. obj4 = obj1; // C++编译器提供的 等号操作 也属 浅拷贝
同理
解决方案: void operator=(Name &obj4)
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> #include <cstdio> #include <cstdlib> using namespace std; class Name { public: Name(const char *myp) { cout<<"普通构造函数"<<endl; m_len = strlen(myp);//strlen只关心字符串长度[不含字符串结束标志'\0'] m_p =(char *) malloc(m_len + 1); // +1 是因为 '\0',否则会内存越界 strcpy(m_p, myp); } //解决方案: 手工的编写拷贝构造函数 使用 【深拷贝】 本质上为构造一块内存区域,而不是浅拷贝里简单的数值复制 Name(const Name& obj1) //Name obj2 = obj1; { cout<<"手工的编写拷贝构造函数"<<endl; m_len = obj1.m_len; m_p = (char *)malloc(m_len + 1); strcpy(m_p, obj1.m_p); } ~Name() { cout<<"析构了..."<<endl; if (m_p != NULL) { free(m_p); m_p = NULL; m_len = 0; } } void operator=(Name &obj4) { if (m_p != NULL) { free(m_p); m_p = NULL; m_len = 0; } cout<<"等号操作符重载"<<endl; //用obj4来=自己 m_p = (char *)malloc(obj4.m_len + 1); strcpy(m_p, obj4.m_p); m_len = obj4.m_len; } protected: private: char *m_p ; int m_len; }; //对象析构的时候 出现coredump void objplaymain() {
Name obj1("abcdefg"); //普通构造函数
Name obj2 = obj1;//用手工编写的copy构造函数进行初始化
Name obj3("obj4");
//普通构造函数
obj3 = obj1;//等号操作符重载
Name obj4(obj1); //用手工编写的copy构造函数进行初始化
} int main(void) { objplaymain(); cout<<"hello..."<<endl; return 0; }
5.多个对象的构造和析构:对象初始化列表
1)对象初始化列表出现原因
1.必须这样做:
如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,没有默认构造函数。
这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数,如果没有初始化列表,那么他将无法完成第一步,就会报错。
2、类成员中若有const修饰,必须在对象初始化的时候,给const int m 赋值
当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化,
因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的。
2)C++中提供初始化列表对成员变量进行初始化
语法规则
Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3)
{
// some other assignment operation
}[B(int _b1, int _b2) : a1(1), a2(2), c(0)]
3)注意概念
初始化:被初始化的对象正在创建
赋值:被赋值的对象已经存在
#include <iostream> using namespace std; class A { public: A(int _a) { a = _a; cout << "Class A 构造函数" << "a:" << a << endl; } ~A() { cout << "Class A 析构函数" << "a:" << a << endl; } protected: private: int a; }; //0 类里如果复合了别的类成员,是没有默认的构造函数的,需要自己手动编写 格式就是对象初始化列表的格式 //1 构造函数的初始化列表 解决: 在B类中 组合了一个 A类对象 (A类设计了构造函数) //根据构造函数的调用规则 设计A的构造函数, 必须要用;没有机会初始化A //新的语法 Constructor::Constructor() : m1(v1), m2(v1,v2), m3(v3) //类成员中若有const修饰,必须在对象初始化的时候,给const int m 赋值 //当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化 //2 先执行 被组合对象的构造函数 //如果组合对象有多个,按照定义(声明)的顺序, 而不是按照初始化列表的顺序 //3析构函数 : 和构造函数的调用顺序相反 //4 初始化列表 用来 给const 属性赋值 class B { public: //B ojbB2(1, 2, 3, 4); B(int _b1, int _b2, int m, int n, int k) : a1(m), a2(n), c(k) //构造函数始化列表 [无关] { b1 = _b1; b2 = _b2; cout <<"B的构造函数"<<endl; } ~B() { cout<<"B的析构函数" <<endl; } protected: private://声明的顺序[有关] int b1; int b2; A a2; A a1; const int c; }; void obj10play() { A a1(10); //1参数传递 B ojbB2(1, 2, 3, 4, 0); //2 调用顺序 return ; } int main(void) { obj10play(); return 0; }
Class A 构造函数a:10
Class A 构造函数a:4
Class A 构造函数a:3
B的构造函数
B的析构函数
Class A 析构函数a:3
Class A 析构函数a:4
Class A 析构函数a:10
6.[专题]匿名对象
ABCD(400, 500, 600); //临时[匿名]对象的生命周期,如果没有对象来接,只有这么一句话,如果有,则不用析构
7.对象的动态建立和释放
7.1 new和delete基本语法
1)在软件开发过程中,常常需要动态地分配和撤销内存空间,例如对动态链表中结点的插入与删除。在C语言中是利用库函数malloc和free来分配和撤销内存空间的。
C++提供了较简便而功能较强的运算符new和delete来取代malloc和free函数。
注意: new和delete是运算符,不是函数,因此执行效率高。
2)虽然为了与C语言兼容,C++仍保留malloc和free函数,但建议用户不用malloc和free函数,而用new和delete运算符。
new运算符的例子:
new int; //开辟一个存放整数的存储空间,返回一个指向该存储空间的地址(即指针) new int(100); //开辟一个存放整数的空间,并指定该整数的初值为100,返回一个指向该存储空间的地址 new char[10]; //开辟一个存放字符数组(包括10个元素)的空间,返回首元素的地址 new int[5][4]; //开辟一个存放二维整型数组(大小为5*4)的空间,返回首元素的地址 float *p = new float (3.14159); //开辟一个存放单精度数的空间,并指定该实数的初值为3.14159,将返回的该空间的地址赋给指针变量p
3)new和delete运算符使用的一般格式为:
用new分配数组空间时不能指定初值。
如果由于内存不足等原因而无法正常分配空间,则new会返回一个空指针NULL,用户可以根据该指针的值判断分配空间是否成功。
4)
7.2 类对象的动态建立和释放
使用类名定义的对象都是静态的,在程序运行过程中,对象所占的空间是不能随时释放的。
但有时人们希望在需要用到对象时才建立对象,在不需要用该对象时就撤销它,释放它所占的内存空间以供别的数据使用。这样可提高内存空间的利用率。[类似于C中的动态内存分配]
C++中,可以用new运算符动态建立对象,用delete运算符撤销对象
比如:
Box *pt; //定义一个指向Box类对象的指针变量pt pt=new Box; //在pt中存放了新建对象的起始地址
在程序中就可以通过pt访问这个新建的对象。
如
cout<<pt->height; //输出该对象的height成员 cout<<pt->volume(); //调用该对象的volume函数,计算并输出体积
C++还允许在执行new时,对新建立的对象进行初始化。如
Box *pt=new Box(12,15,18);
这种写法是把上面两个语句(定义指针变量和用new建立新对象)合并为一个语句,并指定初值。这样更精炼
新对象中的height,width和length分别获得初值12,15,18。调用对象既可以通过对象名,也可以通过指针。
在执行new运算时,如果内存量不足,无法开辟所需的内存空间,目前大多数C++编译系统都使new返回一个0指针值。只要检测返回值是否为0,就可判断分配内存是否成功。
在不再需要使用由new建立的对象时,可以用delete运算符予以释放。如
delete pt; //释放pt指向的内存空间
这就撤销了pt指向的对象。此后程序不能再使用该对象。
如果用一个指针变量pt先后指向不同的动态对象,应注意指针变量的当前指向,以免删错了对象。
7.3 基础用法
#include <iostream> #include <cstring> #include <cstdio> #include <cstdlib> using namespace std; class Test { public: Test(int _a) { a = _a; cout<<"构造函数执行" <<endl; } ~Test() { cout<<"析构函数执行" <<endl; } protected: private: int a; }; //1 // malloc free c语言的函数 // new delete c++的运算符 int main(void) { /**************************** * 分配基础类型 ****************************/ //c语言分配内存 int *p = (int *)malloc(sizeof(int)); *p = 10; free(p); //c++语言分配内存 int *p3 = new int(30); printf("*p3:%d \n", *p3);//30 delete p3; /**************************** * 分配数组变量 ****************************/ //c语言分配数组 int *ptr = (int *)malloc(sizeof(int) * 10); //int array[10]; ptr[0] = 1; free(ptr); //c++分配数组 int *pArray = new int[10] ; pArray[1] = 2; delete [] pArray; //数组不要把[] 忘记 char *pArray2 = new char[25] ; //char buf[25] delete [] pArray2; /**************************** * 分配对象 * new能执行类的构造函数 delete能执行类的析构函数 ****************************/ //c Test *pT1 = (Test *)malloc(sizeof(Test)); free(pT1); //c++ Test *pT2 = new Test(10);//输出 构造函数执行 并初始化Test的变量为10 delete pT2;//输出 析构函数执行 cout<<"hello..."<<endl; return 0; }
*p3:30
构造函数执行
析构函数执行
hello...