「地表最强」C++核心编程(五)类和对象--对象初始化和清理
环境:
编译器:CLion2021.3;操作系统:macOS Ventura 13.0.1
文章目录
- 一、构造函数和析构函数
- 1.1 构造函数
- 1.2 析构函数
- 1.3 示例
- 二、构造函数的分类及调用
- 1.1 构造函数的分类
- 1.2 构造函数的调用
- 三、拷贝构造函数调用时机
- 3.1 调用时机
- 3.2 返回值优化
- 四、构造函数调用规则
- 五、深拷贝与浅拷贝
- 5.1 浅拷贝
- 5.2 深拷贝
- 六、初始化列表
- 七、类对象作为类成员
- 八、静态成员
- 8.1 静态成员变量
- 8.2 静态成员函数
地表最强C++系列传送门:
「地表最强」C++核心编程(一)内存分区模型
「地表最强」C++核心编程(二)引用
「地表最强」C++核心编程(三)函数提高
「地表最强」C++核心编程(四)类和对象----封装
「地表最强」C++核心编程(五)类和对象----对象初始化和清理
「地表最强」C++核心编程(六)类和对象----对象模型和this指针
「地表最强」C++核心编程(七)类和对象----友元
「地表最强」C++核心编程(八)类和对象----运算符重载
「地表最强」C++核心编程(九)类和对象----继承
「地表最强」C++核心编程(十)类和对象----多态
「地表最强」C++核心编程(十一)文件操作
一、构造函数和析构函数
构造函数和析构函数是必须实现的两个函数,即使自己没有写,编译器也会默认调用默认的,空的构造和析构函数。
1.1 构造函数
构造函数主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
语法: 类名(){}
构造函数的特点:
- 没有返回值,不用写void
- 函数名与类名相同
- 构造函数可以有参数,可以发生重载
- 创建对象时自动调用,且只调用一次
1.2 析构函数
析构函数主要作用在于对象销毁前系统自动调用,执行一些清理工作。
语法: ~类名(){}
析构函数的特点:
- 没有返回值,不用写void
- 函数名与类名相同,前面加个~
- 析构函数不可以有参数,不可以发生重载
- 销毁对象时自动调用,且只调用一次
1.3 示例
class Person {public:Person() {cout << "Person的构造函数" << endl;//自己写了就自动调用自己写的。否则就调用默认的空的构造函数}~Person() {cout << "Person的析构函数" << endl;}
};//构造和析构函数都必须实现,自己不提供编译器会提供一个空的构造和析构函数
void test01(){Person p;
}int main() {test01();return 0;
}
二、构造函数的分类及调用
1.1 构造函数的分类
构造函数按照不同的标准可以分为不同的类型:
按参数分类可分为无参构造函数(也叫做默认构造函数)和有参构造函数;
按类型分类可分为普通构造函数和拷贝构造函数。
⚠️由于创建对象需要调用构造函数,因此构造函数的权限应该是public,否则类外无法调用构造函数,也就无法实例化对象。
⚠️拷贝构造函数的参数需要是常量对象
class Person {public://要加public作用域,否则默认是private,就无法调用构造析构函数,也就无法创建对象int age;Person() {cout << "Person的无参构造函数" << endl;}Person(int a) {age = a;cout << "Person的有参构造函数" << endl;}Person(const Person &p) {//拷贝构造函数,要用const和&的方式传参age = p.age;//将传入的人身上的所有属性,拷贝到自己身上cout << "Person的拷贝构造函数" << endl;}~Person() {cout << "Person的析构函数" << endl;}
};
1.2 构造函数的调用
构造函数的调用有三种方法,分别是括号法、显示法和隐式转换法。
void test() {//括号法Person p1;//默认构造函数调用Person p2(10);//有参构造函数调用Person p3(p2);//拷贝构造函数调用//⚠️括号法调用默认构造函数的时候不要加(),否则编译器会认为这是一个函数的声明
// Person p1();//虽然不报错,但是逻辑错误,编译器会认为有一个返回值类型是Person,名为p1的函数cout<<"p2的年龄是:"<<p2.age<<endl;//10cout<<"p3的年龄是:"<<p3.age<<endl;//10//显示法Person p1;Person p2 = Person(10);//有参构造,实际上是将匿名对象赋值给p2Person p3 = Person(p2);//拷贝构造
// Person(10);//这是一个创建匿名对象,但没有办法使用 特点:当前行执行结束后,系统会立即回收掉匿名对象
// cout<<"匿名对象已经没了"<<endl;//⚠️不要用拷贝构造函数初始化匿名对象,编译器会认为这是重定义。 Person(p3); 会被转换为 Person p3;
// Person(p3);//err,Redefinition of 'p3'//隐式转换法Person p4 = 10;//相当于Person p4 = Person(10); 有参构造Person p5 = p4;//相当于Person P5 = Person(p4); 拷贝构造
}
三、拷贝构造函数调用时机
3.1 调用时机
C++中拷贝构造函数调用时机通常有三种情况:
- 1使用一个已经创建完毕的对象来初始化一个新对象
- 2值传递的方式给函数参数传值
- 3以值方式返回局部对象
//1使用一个已经创建完毕的对象来初始化一个新对象
void test01() {Person p1(20);//括号法调用构造函数Person p2(p1);//用对象p1来初始化p2,实际上就是调用了拷贝构造函数cout << "p2的年龄:" << p1.mAge << endl;
}//2值传递的方式给函数参数传值
void doWork1(Person p) {}
void test02() {Person p;//调用默认构造函数doWork1(p);//值传递,doWork函数会调用拷贝构造函数给形参临时开辟空间
}//3以值方式返回局部对象
Person doWork2() {Person p;//调用默认构造函数return p;//返回的不是上边的p,因为已经被释放掉了。这里返回的是另一个创建的对象,这个对象是用拷贝构造函数创建的。
}
void test03() {Person p = doWork2();//CLion测试下不会调用析构函数,这是因为返回值优化技术。cout << &p << endl;
}
3.2 返回值优化
这里解释一下第3点以值的方式返回对象:doWork2函数中首先定义了一个Person类型变量p,我们假设这个对象的地址是add1,然后该函数返回了对象p。这一步的操作实际上是用拷贝构造函数的方法又创建了一个对象p’,我们假设p’的地址是add2,然后把p作为拷贝构造函数的参数传给p’。而实际返回的是p’而不是p,这两个对象的地址是不同的,是两个对象。
返回值优化是一项编译优化技术,使得返回对象时不必调用拷贝构造函数,经过测试后返回的就是已经创建好的p的地址而不会产生p’。
关于返回值优化,更多的可以参考一下这里:Return value optimization-Wikipedia
四、构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数:
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
- 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,c++不会再提供其他构造函数
class Person {public:int age;//无参(默认)构造函数Person() {cout << "无参构造函数!" << endl;}//有参构造函数Person(int a) {age = a;cout << "有参构造函数!" << endl;}//拷贝构造函数Person(const Person &p) {age = p.age;cout << "拷贝构造函数!" << endl;}//析构函数~Person() {cout << "析构函数!" << endl;}
};void test(){Person p;//若只写有参构造或者拷贝构造,不写无参,此处会报错。因为此行代码会调用无参,但是我们写了有参,编译器就不会提供无参Person p(28);//若没写无参和有参,只写了拷贝构造,此处会报错。因为写了拷贝构造,编译器就不会提供其他无参和有参Person p2(p);cout << "p2的年龄:" << p2.age << endl;
}
五、深拷贝与浅拷贝
5.1 浅拷贝
浅拷贝就是简单的赋值操作,默认的拷贝构造函数进行的就是浅拷贝。
class Person{private:string m_name;int m_age;
public:Person(const Person& p){//这里其实就是浅拷贝m_name = p.m_name;m_age = p.m_age;}
5.2 深拷贝
从下边这个实例说起,注意这里的身高我定义成了指针,指向的内容是身高
class Person {public:int m_age;int *m_height;//指针,指向的内容是身高public://有参构造函数Person(int age, int height) {m_age = age;m_height = new int(height);//用new创建在堆区}//拷贝构造函数Person(const Person &p) {m_age = p.m_age;m_height = p.m_height;//默认的浅拷贝}//析构函数~Person() {//堆区开辟的数据在此时可以释放了if (m_height != NULL) {delete m_height;m_height = NULL;}}
};void test01() {Person p1(18, 180);//调用有参构造Person p2(p1);//调用拷贝构造
}
如图,通过有参构造创建了p1,然后通过浅拷贝的拷贝构造创建了p2。而浅拷贝的只是在机械的赋值,因此p2的所有属性都和p1一样,此时他们的身高的指针都是指向同一堆空间的,那么此时他们中任何一个对这块空间的操作会直接影响到另一个人。在test()调用结束后,这两个对象也会调用自己的析构函数来销毁空间。p2调用了自己的析构函数,将0x003这块儿空间释放掉,然后p1才调用自己的析构函数(这里涉及到析构函数的调用时间,简单来说就是先构造的后析构,想想栈的特性就明白了),但此时此空间已经被释放掉了,还要释放那就是非法的。
想解决这个问题,只需要在构造p2的时候,让他的身高指针指向与p1不同的空间即可。因此只需重写拷贝构造函数即可:
Person(const Person &p) {//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题。若不自己重写拷贝构造函数,拷贝构造出来的对象和被拷贝的对象指向同一块儿空间。m_age = p.m_age;m_height = new int(*p.m_height);//重新申请一块堆区,属于深拷贝}
这样就不会有重复释放的问题,这就是深拷贝。
六、初始化列表
初始化列表是一种利用构造函数初始化属性的另一种方式。
语法: 构造函数():属性1(值1),属性2(值2)… {}
class Person {public:int m_A;int m_B;int m_C;public://传统方式初始化Person(int a, int b, int c) {m_A = a;m_B = b;m_C = c;}//初始化列表方式初始化Person() : m_A(10), m_B(20), m_C(30) {}Person(int a, int b, int c) : m_A(a), m_B(b), m_C(c) {}
};
七、类对象作为类成员
假设一个类的对象B是另一个类A的成员,那么在调用构造函数的时候,会先调用B的构造函数:
class Phone {public:string m_PhoneName;Phone(string name) {m_PhoneName = name;cout << "Phone构造" << endl;}~Phone() {cout << "Phone析构" << endl;}
};class Person {public:string m_Name;Phone m_Phone;//初始化列表可以告诉编译器调用哪一个构造函数 Phone m_Phone = pName;其实是隐式转换法Person(string name, string pName) : m_Name(name), m_Phone(pName) {cout << "Person构造" << endl;}~Person() {cout << "Person析构" << endl;}void playGame() {cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 手机! " << endl;}
};void test01() {//当类中成员是其他类对象时,我们称该成员为对象成员//构造的顺序是 :先调用对象成员的构造,再调用本类构造,析构顺序与构造相反Person p("张三", "苹果1024");//先构造phonep.playGame();
}
八、静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员。包括静态成员变量和静态成员函数。
8.1 静态成员变量
特点:
1.所有对象共享同一份数据,因此不属于某个对象
2.在编译阶段分配内存,全局区
3.类内声明,类外初始化
class Person {public:static int m_A; //静态成员变量
private:static int m_B; //静态成员变量也是有访问权限的
};//类外初始化,否则无法正常访问
int Person::m_A = 10;
int Person::m_B = 10;//private也需要类外初始化,这是可以的void test01() {//静态成员变量两种访问方式,这是由于静态成员变量被所有对象共享//1、通过对象 非静态成员变量只能这么访问Person p1;p1.m_A = 100;cout << "p1.m_A = " << p1.m_A << endl;//100Person p2;p2.m_A = 200;cout << "p1.m_A = " << p1.m_A << endl; //200 共享同一份数据,不属于某个对象cout << "p2.m_A = " << p2.m_A << endl; //200//2、通过类名 非静态成员变量不能这么访问cout << "m_A = " << Person::m_A << endl;//cout << "m_B = " << Person::m_B << endl; //err,私有权限访问不到
}
8.2 静态成员函数
特点:
1.所有对象共享同一个函数
2.静态成员函数只能访问静态成员变量
class Person
{public:static int m_A; //静态成员变量int m_B; //只属于对象本身,不能被对象共享//静态成员函数static void func(){cout << "func调用" << endl;m_A = 100;
// m_B = 100; //err,不可以访问非静态成员变量,无法区分到底是哪个对象的属性}private://静态成员函数也是有访问权限的static void func2(){cout << "func2调用" << endl;}
};int Person::m_A = 10;void test01()
{//静态成员变量两种访问方式//1、通过对象Person p1;p1.func();//2、通过类名Person::func();
// Person::func2(); //err,私有权限访问不到
}
「地表最强」C++核心编程(五)类和对象--对象初始化和清理相关推荐
- 「地表最强」C++核心编程(七)类和对象--友元
环境: 编译器:CLion2021.3:操作系统:macOS Ventura 13.0.1 文章目录 一.全局函数做友元 二.类做友元 三.成员函数做友元 地表最强C++系列传送门: 「地表最强」C+ ...
- 苹果M1「徒有其表」?「地表最强」芯只能剪视频引知乎热议
来源:新智元 [导读]5nm工艺,570亿晶体管,70%CPU性能提升,4倍GPU性能提升.号称史上最强芯片的M1 Max,只能「剪剪视频」? 最近,苹果开了一个芯片新品发布会. 光看参数,M1 Pr ...
- 第四部分—C++核心编程_4. 类和对象
4.1 类和对象的基本概念 4.1.1 C和C++中struct区别 c语言struct中只有变量 c++语言struct中既有变量,也有函数 4.1.2 类的封装 我们编写程序的目的是为了解决现实中 ...
- C++核心编程4— 类和对象
C++面向对象的三大特性为:封装.继承.多态 C++认为万事万物都皆为对象,对象上有其属性和行为 例如: 人可以作为对象,属性有姓名.年龄.身高.体重-,行为有走.跑.跳.吃饭.唱歌- 车也可 ...
- C++核心编程<类和对象>(4)
C++核心编程<类和对象> 4.类和对象 4.1封装 4.1.1封装的意义 封装的意义1 封装的意义2 4.1.2struct和class区别 4.1.3成员属性设置为私有 4.2对象的初 ...
- 【黑马程序员 C++教程从0到1入门编程】【笔记4】C++核心编程(类和对象——封装、权限、对象的初始化和清理、构造函数、析构函数、深拷贝、浅拷贝、初始化列表、友元friend、运算符重载)
黑马程序员C++教程 文章目录 4 类和对象(类属性[成员属性],类函数[成员函数]) 4.1 封装 4.1.1 封装的意义(三种权限:public公共.protected保护.private私有)( ...
- 「星火计划沙龙视频」Angel核心推荐算法及其应用探秘
腾讯大数据"星火计划"系列技术沙龙第4期,<Angel核心推荐算法及其应用探秘>直播专场回顾. 作为当前AI技术最常见的应用场景之一,推荐系统能够帮助用户更加高效地获取 ...
- 前端与移动开发----JS高级----面向对象编程,类与实例对象,继承,严格模式,模板字符串,class封装tab栏
JS高级01 回顾上阶段 Javascript组成 ECMAScript: 基础语法 (变量, 表达式, 循环, 判断, 函数, 对象等) DOM: document 操作标签(获取, 增加, 插入, ...
- 「科技与安全」RK3568J核心板让隔离网闸更强大
一句"科技与狠活"让食品安全的话题冲上了热搜,其实除了食品安全,生活中还有很多安全问题值得我们注意.在信息数字化给我们的生活带来种种便利的同时,信息泄露的风险也大大增加.如何有效地 ...
最新文章
- PCL两种方式的点云读写
- 收藏!超全机器学习资料合集!(附下载)
- Python中is和==有什么区别?
- dae怎么用草图大师打开_当 to C市场饱和,该怎么用场景化打开新市场?
- vue计算多列和_解决vue 表格table列求和的问题
- Educational Codeforces Round 60 D. Magic Gems
- oracle 全局搜索字符串,oracle操作字符串:拼接、替换、截取、查找 _ 学编程-免费技术教程分享平台...
- Eclipse-习惯设置/快捷键/插件
- 有人说智能制造装备前景大好,那么智能制造装备产业园的潜力如何?
- 笔记本重置找不到恢复环境_Win10 自带的疑问解答、备份、恢复还原、重置系统怎么使用?...
- 中小制造型企业如何成功实施5S管理?
- FAT文件系统详解(一)
- 在n*n方阵里填入1,2,...n*n,要求填成蛇形
- pearson相关性
- 薛定谔的猫、量子纠缠、和量子计算机
- SharePoint传出电子邮件配置
- 如何用ChemDraw Prime 绘制任意弧线箭头
- HTML hr 标签的用法
- 咸鱼软件应用—Cura3D切片
- 这七种职业男人让多少日本女性着迷