第四章-类与对象
- 抽象
对具体问题进行概括,抽出一类对象的公共性质并加以描述的过程。
数据抽象(功能抽象):描述某类对象的属性或状态
行为抽象(代码抽象):描述某类对象的共同行为或功能特征 - 封装
将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体。
将数据和操作数据的函数代码进行有机的结合,形成类。 - 继承
继承使得特殊概念之间既能共享一般的属性和行为,又能具有特殊的属性的行为。
允许程序员在保持原有类特性的基础上,进行更具体、更详细的说明。 - 多态:强制多态、重载多态、类型参数化多态、包含多态
指一段程序能够处理多种类型对象的能力。
特殊多态性:
强制多态是通过数据类型转换(显式或隐式)实现的
重载包括函数重载、运算符重载
一般多态性:
包含多态使用虚函数实现
参数化多态使用模板实现,分为函数模板和类模板
-
公有类型public
定义了类的外部接口 -
私有类型private
私有成员只能被本类的成员函数访问,来自类外部的任何访问都是非法的。 -
保护成员protected
与私有成员的性质相似,其差别在于继承过程中对产生的新类影响不同。
-
对象所占据的内存空间只用于存放数据成员,函数成员不在每一个对象中存储副本,每个函数的代码在内存中只占据一份空间。
-
在类的成员函数中,既可以访问目的对象的私有成员,又可以访问当前类的其他对象的私有成员。
-
类成员函数的默认值,一定要写在类定义中,而不能写在类定义之外的函数实现中。
-
内联成员函数
隐式声明:将函数体直接放在类体内
显式声明:在函数体实现时,在函数返回值类型前加上inline -
构造函数
作用: 在对象被创建时利用特定的值构造对象,将对象初始化为一个特定的状态。
一些特殊的性质: 构造函数的函数名与类名相同,而且没有返回值;
构造函数通常被声明为共有函数;
构造函数在对象被创建的时候被自动调用;
类中没有写构造函数,编译器会自动生成一个隐含的默认构造函数,参数列表和函数体皆为空
作用: 使用一个已经存在的对象(由复制构造函数的参数指定),去初始化同类的一个新对象。
具有一般构造函数的所有特性,其形参是本类的对象的引用。
如果程序员没有定义类的复制构造函数,系统会自动生成一个隐含的复制构造函数,它会把初始值对象的每个数据成员的值都复制到新建立的对象中。
复制构造函数被调用的三种情况:
- 当用类的一个对象去初始化该类的另一个对象时。
- 如果函数的形参是类的对象,调用函数时,进行形参和实参结合时(只有值传递时,引用传递则不会调用复制构造函数)
- 如果函数的返回值是类的对象,函数执行完成返回调用者时(编译系统会在主调函数中创建一个无名临时对象,其生存期只在函数调用所处的表达式中,当return a时会使用复制构造函数将a的值复制到临时对象中去)
复制构造函数的必要性:
可以有选择、有变化的复制;
深复制必须编写复制构造函数。
作用: 用来完成对象被删除前的一些清理工作
析构函数是在对象的生存期即将结束的时刻被自动调用的。
析构函数不接收任何参数,没有返回值,但可以是虚函数。
系统也可能生成一个函数体为空的隐含析构函数。
- 一个类内嵌其他类的对象作为成员的情况,它们之间的关系是一种包含与被包含的关系。
- 当创建类的对象时,如果这个类具有内嵌对象成员,那么各个内嵌对象将首先被自动创建。
- 组合类构造函数定义的形式:
类名::类名(形参表):内嵌对象1(形参表),内嵌对象2(形参表),…
{类的初始化} - 组合类的构造函数调用顺序:
首先,调用内嵌对象的构造函数,按照内嵌对象在组合类的定义中出现的次序,而与在初始化列表中出现的次序无关。
然后 调用本类构造函数的函数体。 - 若某类包含某两类数据成员,编译器不能为这个类提供隐含的默认构造函数,必须编写显式的构造函数,并且在每个构造函数的初始化列表中为这两类数据成员初始化:
第一类 没有默认构造函数的内嵌对象,这类对象初始化时必须提供参数
第二类 引用类型的数据成员,因为引用型变量必须在初始化时绑定引用的对象
其实还有一类,常数据成员
解决的问题: 两个类相互引用的情况,也称循环依赖。
在引用未定义的类之前,将该类的名字告诉编译器,使得编译器知道那是一个类名
注意⚠️:
在提供一个完整的定义之前,不能定义该类的对象,也不能在内联成员函数中使用该类的对象。
可以声明该类的对象引用或指针。
当使用前向引用声明时,只能使用被声明的符号,而不能涉及类的任何细节。
结构体与类的唯一区别:
对于未指定访问控制属性的成员,其访问控制属性
在类中,默认为私有类型private
在结构体中,默认为共有类型public
引入结构体的原因:
为了保持和C程序的兼容,C++保留了struct关键字。
有时需要定义一些数据类型,它们没有什么操作,定义他们的目的只是将一些不同类型的数据组合成一个整体,从而方便地保存数据,此时可以定义结构体。
联合体的特点与限制:
联合体的全部数据成员共享同一组内存单元;
它的默认访问控制属性也是公共类型的;
联合体变量中的成员同时至多只有一个是有意义的;
联合体的各个对象成员,不能有自定义的构造函数、析构函数和重载的复制赋值运算符,这些对象成员的对象成员也不能有;
联合体不能继承。
定义方式:
数据类型说明符 成员名 : 位数
出现的原因:
某些数据只需要几个二进制位就可以保存,因此可以将类中多个这样的数据成员“打包”,让他们不必从整字节开始,而是可以只占据某些字节的某几位。
注意事项:
编译器对打包的处理不同;
只有bool、char、int、enum的成员才可以被定义为位域;
位域虽然节省了内存空间,但是由于打包和解包的消耗,运行时间很有可能会增加。
explicit关键字 使得该构造函数定义的类型转换只能通过显式转换的方式完成。
当函数的实现与在类定义中的声明是分离的,该关键字只能写在类定义中的函数原型声明处。
通过运行栈,在主调函数和被调函数之间,有一块二者都要访问的公共区域,主调函数把实参值写入其中,函数调用发生后,被调函数通过读取这段区域就可得到形参值。
对于基本数据类型的参数,做一般的内存写操作即可,但是对于对象参数,需要调用复制构造函数。
复制构造函数可以用于产生对象;
赋值运算符要求左右两边的对象均已经存在;
复制构造函数需为指针变量分配内存空间,并将实参的值拷贝到其中;
而赋值操作符它实现的功能仅仅是将‘=’号右边的值拷贝至左值,赋值操作符必须检测是否是自身赋值,若是则直接返回当前对象的引用而不进行赋值操作。