【C++系列P4】‘类与对象‘-三部曲——[类](2/3)
前言
- 大家好吖,欢迎来到 YY 滴 C++系列 ,热烈欢迎!
- 【 '类与对象'-三部曲】的大纲主要内容如下:
- 如标题所示,本章是【 '类与对象'-三部曲】三章中的第二章节——类章节,主要内容如下:
目录
一.类
- C++兼容C,C语言中的结构体strcut也算是一种类,是public(公有)的,可以被类外直接访问。
- 类中的函数默认是内联函数,具体是否是内联函数编译器会判断。如果将其定义和声名分开,即类放在.h文件,定义函数放在.cpp文件,函数不为内联函数;
1.类的组成与计算类的大小(含结构体内存对齐规则)
- 类由访问限定符划分,类中既有成员变量,又有成员函数;
计算类的大小,只用考虑成员变量的大小
例如:上图中,类的大小为8字节
PS:内存对齐,本质上是牺牲空间换取效率。通过调整默认对齐数可以对这一过程进行动态调整。
二. 空类的大小
- 没有成员变量的类对象,需要 1byte ,是为了占位,表示对象存在.
三.内部类
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中 的所有成员。但是外部类不是内部类的友元。
特性:
- 内部类可以定义在外部类的public、protected、private都是可以的。
- 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
- sizeof(外部类)=外部类,和内部类没有任何关系。
四.类的六个默认成员函数
特点:
- 当没有显式定义(我们不主动写时),编译器会自动生成
1.构造函数(第一个)
- 默认构造函数(3种):(1) 类自己生成的函数(2)无参 (3)全缺省的函数
- 特征: (不传参就可以调用)
构造函数的主要任务是初始化对象,如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义,编译器将不再生成。
- 运作上看,当对象实例化时,编译器会自动调用它
- 形态上看,其名字与类名相同,且无返回值
- 注意点,构造函数允许重载
一.什么时候需要自己写构造函数?
需要自己写的情况:
- 一般情况下,有内置类型成员,要自己写(否则会初始化成随机值)
不需要自己写的情况:
- 当内置类型成员都有缺省值时,且初始化符合要求,可以考虑让编译器自己生成
- 全部都是自定义类型成员(例如:Stack),可以考虑让编译器自己生成
注意!!!
二.构造函数可以使用重载和不可以使用重载的情况
- 构造函数可以用重载的情况:
- 构造函数不能用重载的情况:无参调用存在歧义
2.析构函数 (第二个)
析构函数的主要任务是清理对象;
- 运作上看,当对象生命周期结束时,编译器会自动调用它
- 形态上看,其在类名前加上~,且无返回值
- 注意点,析构函数不允许重载。
默认析构函数:与默认构造函数类似,编译器对内置类型成员不做处理,对自定义类型会去调用它的析构函数。
一.什么时候需要自己写析构函数?
需要自己写的情况:
- 有动态申请资源时,需要自己写析构函数释放空间。(防止内存泄漏)
不需要自己写的情况:
- 没有动态申请资源时,不需要自己写,系统会自动回收空间。
- 需要释放资源的对象都是自定义类型时,不需要自己写。
3.拷贝构造函数 (第三个)
行为:
- 在创建对象时,创建一个与已存在对象一模一样的新对象
拷贝构造函数:
- 只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰)
- 在用已存在的类类型对象创建新对象时由编译器自动调用(区分于构造函数)
- 拷贝构造函数是构造函数的一个重载形式
- 拷贝构造函数的参数只有一个且必须是类类型对象的引用
- 原因:【使用传值方式编译器直接报错,因为会引发无穷递归调用】(错误方式)
一.什么时候需要自己写拷贝构造函数?
默认生成的拷贝构造函数为:浅拷贝
需要自己写的情况:
- 自定义类型必须使用拷贝构造(深拷贝)
不需要自己写的情况:
- 内置类型直接拷贝(浅拷贝/值拷贝)
例:Date类中都是内置类型,默认生成的拷贝构造函数为浅拷贝可以直接用;
而Stack类为自定义类型,其中有a指针指向一块新开辟的空间。此时需要自己写拷贝构造函数。
二.默认拷贝构造(浅拷贝)的缺陷:
浅拷贝的缺陷:(默认拷贝构造运用 引用 防止死递归的后遗症)
4.运算符重载函数(第四个)
运算符重载:
- 参数类型:const T& (传递引用可以提高传参效率)
- 函数名:关键字operator后面接需要重载的运算符符号
- 函数原型:返回值类型+operator操作符+(参数列表)
运算符重载 底层转化演示:
注意:
- 不能通过连接其他符号来创建新的操作符:例如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变:例如+
- 作为类成员函数重载时,其形参看起来比操作数少一个(因为成员函数的第一个参数为隐藏的this)
- .* / :: /sizeof/ ?: /./这五个运算符不能重载
一.运算符重载函数和构造函数使用区别:
5.赋值重载函数(第四个的分支)
赋值运算符重载格式:
- 参数类型:const T& (传递引用可以提高传参效率)
- 返回值类型:T& (返回引用可以提高返回的效率,有返回值的目的是为了支持连续赋值)
- 检测是否可以自己给自己赋值
- 返回 *this:(对this指针解引用,要符合连续赋值的含义)
- 赋值运算符只能重载成为类的成员函数而不能重载成全局函数(如果重载成全局函数,编译器会生成一个默认运算符重载)
- 用户没有显示实现时,编译器会生成一个默认赋值运算符重载,以值的方式(浅拷贝)逐字节拷贝。(注意点:内置类型成员变量直接赋值,只有自定义成员变量需要调用对应的赋值运算符重载)
6.取地址与取地址重载(第五个&第六个)
引入: 内置类型取地址时有取地址操作符,而自定义类型呢?于是出现了取地址重载。它用到的场景非常少,可以说取地址重载——补充这个语言的完整性,更加系统。
这两个默认成员函数一般不用重新定义 ,编译器默认会生成
- 这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如不想让别人获取到指定的内容! (设为nullptr)
代码演示:
五.初始化列表
一.初始化列表和构造函数的关系
引入:构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化, 构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
二.初始化列表基本结构
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
代码展示:
三.初始化列表使用场景
- 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
- 类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用成员变量
- const成员变量
- 自定义类型成员(且该类没有默认构造函数时 )
缺省值与初始化列表的关系: (下列代码中 int x 有演示)
- 初始化列表没显式定义,缺省值给到初始化列表
- 初始化列表显式定义,以初始化列表为主
代码展示:
四.尽量使用初始化列表初始化
- 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
五.成员变量在初始化列表中的初始化顺序
- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
图示:
- 点赞
- 收藏
- 关注作者
评论(0)