【content】
▌多态(待更新)
▌函数
▷ 函数调用
函数允许嵌套调用,但不允许嵌套定义
函数可以直接或间接调用自身,即递归调用(有限递归调用才是有意义的)
▷ 参数传递
即形参与实参的结合,分为 值传递 和 引用传递
值传递:参数值的单向传递,即实参 —> 形参,形参的改变不会影响实参;
引用传递:引用是一种特殊类型的变量,即共享内存,作为另一个变量的别名
▷ 默认形参值的函数
▷ 内联函数
对于一些功能简单、规模较小又使用频繁的函数,可以设计成内联函数
内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入每一个调用处
▷ 重载函数
两个以上的函数,具有相同的函数名,但是形参的个数或类型不同,编译器根据形参的类型和个数 的最佳匹配,自动确定调用的函数。
重载函数的有效区分:形参的类型 和 个数 不同(与形参名和函数的返回值/函数的类型无关)
▌类与对象
▷ 多态
四种表现形式:
强制多态——“显式”或“隐式”类型转换
重载多态——函数重载和运算符重载
类型参数化多态——模板(函数模板和类模板)
包含多态——虚函数
▷ 构造函数和析构函数
构造函数——在对象被创建时,利用特定的值构造对象,并对数据成员初始化(作用)
- 构造函数的函数名与类名相同,没有返回值
- 构造函数一般被声明为公有函数
- 构造函数在对象被创建时将会被自动调用
- 构造函数的形参不能是本类对象的引用;拷贝构造函数的形参是对本类对象的引用
在构造函数中,如果是直接用初始值进行初始化,则首选初始化列表的形式 (效率更高)
Clock(int h, int m, int s) :hour(h), minute(m), second(s){}
//创建对象时传入参数进行初始化
Clock mclock(0, 0, 0);
①默认构造函数
调用时无须提供参数的构造函数成为默认构造函数
若编译时,类中没有写构造函数,编译器胡自动生成一个隐含的默认构造函数(参数列表和函数体皆为空)
一般要定义类的默认构造函数,原因——
- 编译器只有在类不包含任何构造函数的前提下才会自动生成默认构造函数,如果我们定义了其他的构造函数而不定义默认构造函数,该类中将没有构造函数;
- 编译器合成的默认构造函数可能导致创建对象时得到未定义的值;
- 在含有其他类类型成员的类中,如果该类没有默认构造函数,则编译器不能对该成员进行初始化(如果A中包含B类的成员,而B类没有默认构造函数,则B类的成员无法初始化;但是如果B中有含参的构造函数,传入参数即可初始化)
② 拷贝构造函数(复制构造函数):
拷贝构造函数的作用是使用一个已经存在的对象,去初始化同类的一个新对象;其形参是本类对象的引用。
系统在必要时自动生成拷贝构造函数,是为了把初始值对象的每个数据成员的值都复制到新建立的对象中
▲普通构造函数在 创建新对象时 被调用
▲拷贝构造函数被调用:
- 用类的一个对象去初始化该类的另一个对象时;
- 如果函数的参数时类的对象,调用函数时,形实参结合时;
- 如果函数的返回值是类的对象,函数执行完成返回调用者时;
析构函数——完成对象被删除后的一些清理工作(作用)
析构函数不接受任何参数
若没有显式说明,系统也会自动生成函数体为空的隐含析构函数
在对象的生存期即将结束的时候调用,析构函数的调用顺序与构造函数恰好相反。
除析构函数外,用户都可以制定为delete删除
▷ 类的组合
一个类内嵌到其他类的对象作为成员,二者是被包含和包含的关系。
当创建类的对象时,如果这个类有内嵌对象成员,则各个内嵌对象将被自动创建,即创建对象时既要对本类的数据成员进行初始化,还要对内嵌对象进行初始化。
构造函数的写法 —— 例:B类对象内嵌到A类中,A类中定义B类对象为b1,b2
//在类内定义
类名(形参表):内嵌对象1(形参1),内嵌对象2(形参2),... {}
//在类外定义
类名::类名(形参表):内嵌对象1(形参1),内嵌对象2(形参2),...{}
A::A(B bb1,B bb2):b1(bb1),b2(bb2)...{}
拷贝构造函数的写法 —— 例:B类对象内嵌到A类,b为A类中定义的B类对象
//在类外定义
类名1::类名1(类名1 &对象参数):内嵌对象成员(对象参数.内嵌对象成员) {}
A::A(A& a):b(a.b) {}
▲构造函数的调用顺序:内嵌对象 —> 本类对象
(1)首先调用内嵌对象的构造函数,调用顺序按照内嵌对象在组合类定义中出现的次序(内嵌对象在构造函数的初始化列表中出现的顺序与内嵌对象构造函数的调用无关,与在类中的定义顺序有关)
(2)调用本类的构造函数
拷贝构造函数调用(参考上文拷贝构造函数何时被调用)
▷ UML图
访问控制属性 +(public)-(private)#(protected)
[访问控制属性] 名称:[类型] = [默认值]
①依赖关系——一个类使用另外一个类作为它的成员参数时,就使用依赖关系,如A使用了B,则称A依赖于B
②作用关系——关联,表述一个对象和另一个对象相互作用的连接【重数】
③包含关系——聚集(类之间的关系是整体与部分的关系)和组合
共享聚集:部分可以参加多个整体;
组合:整体拥有各部分,部分与整体并存(实心菱形)
④继承关系——泛化
三角的尖指向父类,线指向子类
- 静态数据成员通过在其下方画线表示 ;静态函数成员在其开头加上<<static>>
- 虚基类在类名前加<<virtual>>
▷ 结构体与联合体
结构体struct
——对于未指定访问属性的成员,其访问属性为public
何时选用结构体:程序中需要定义一些数据类型,它们并没有什么操作,只是为了将这些不同类型的数据组合成一个整体,从而方便地保存数据。
联合体union
——默认访问控制属性:public
联合体的所有数据成员全部共享同一内存单元,不能有自定义的构造函数、析构函数和重载的赋值运算符;不能继承,不支持包含多态,一般不为联合体定义函数成员。
▷ 枚举类型——enum
变量的可取值一一列举出来便构成了一个枚举类型
①不限定作用域的枚举类型
enum (枚举类型名) {变量值列表};
enum weekday{SUN,MON,TUE,WED,THU,FRI,SAT};
//详情见枚举类型&一些经典题目_◎菜澜子的博客-CSDN博客
②限定作用域的枚举类型
enum class xxx {};
//限定作用域的枚举元素被隐藏了
enum class color{red,yello,pink};
color c = color::red;
▌数据保护与共享
▷ 标识符的作用域和可见性
作用域——标识符的有效范围
①函数原型作用域(局部作用域):函数原型声明时形参的作用域
②类作用域
③文件作用域(全局作用域)
④命名空间作用域
▷ 对象生存期
静态生存期——对象的生存期与程序的运行期相同
- 文件作用域中声明的对象
- 函数内部的局部作用域中用static关键字修饰的对象(static默认初始值为0)(局部变量,具有全局寿命)
动态生存期—— (局部生存期)诞生于声明点,结束于声明所在的块执行完毕之时
▷ 类的静态成员
结构化程序设计的基本模块是函数,模块间内存数据的共享是通过函数与函数之间的数据共享来实现的,包括——参数传递和全局变量
静态数据成员
- 实例属性:一个类的对象的所有对象具有相同的属性,属性的个数、名称、数据类型相同,属性值不同
- 类属性(用static修饰):描述类的所有对象共同特征的一个数据项,对于任何对象实例,它的属性值是相同的。(该属性不属于任何一个对象,为整个类所有)
在类的定义中仅仅对静态数据成员进行引用性声明,必须在文件作用域的某个地方使用类名限定进行定义性声明+初始化
静态函数成员
静态函数成员不依赖于类的对象而存在。
对于静态成员函数可以通过类名或对象名进行访问,非静态成员函数只能通过对象名来调用
▌类的继承
类的派生机制的好处在于代码的重用性和可扩充性;
派生新类的过程一般包括 吸收已有类的成员、调整已有类的成员、添加新的成员
类的继承:是新的类从已有类那里得到已有的特性(从已有类产生新类的过程)
继承与派生机制允许在保持原有类的基础上,进行更加具体、更详细的修改与扩充;
class 派生类名: 继承方式 基类名1,继承方式 基类名2{}
直接参与派生出某类的基类——直接基类
基类的基类甚至更高层的基类——间接基类
派生类成员是指除了基类继承来的所有成员外,新增加的数据和函数成员
派生类的生成过程
- 吸收基类成员
- 改造基类成员:①基类成员的访问控制问题;②对基类数据或函数成员的覆盖和隐藏。 同名隐藏:派生类声明了一个与基类成员同名的新成员,则派生的新成员就隐藏了外层同名成员
- 添加新的成员
public 公有继承——public和protected不变,private不可直接访问
private 私有继承——public和 protected以private形式出现,private不可直接访问
protected 保护继承——在基类中,protected与private相同,在派生类中,protected与public相同(派生类的其他成员可以访问从基类继承来的protected和public成员,但在类外通过派生类对象无法直接访问)
类型兼容规则:在需要基类的任何地方都可以用公有派生类的对象来替代
- 派生类对象可以隐含转化为基类的对象;
- 派生类的指针可以隐含转换为基类对象的指针;
- 派生类的对象可以初始化基类对象的引用。
派生类的构造函数和析构函数:(基类的构造函数和析构函数是不能被继承的)
构造函数:构造派生类的对象时,就要完成对基类的成员对象(调用基类的构造函数)和新增成员对象的初始化
//在类外定义
派生类名::派生类名(参数总表):基类名1(基类1初始化参数列表),基类名2(基类2初始化参数列表),...,
成员对象名1(成员对象1初始化参数列表) {}
何时需要声明派生类的构造函数:
对基类初始化,需要调用基类带有形参表的构造函数时,派生类必须声明构造函数
派生类构造函数的调用顺序:基类—>派生类
- 调用基类的构造函数,按照基类被继承时的声明顺序,从左到右
- 对派生类新增成员初始化,按照新增成员在类中的声明顺序
- 执行派生类构造函数体中的内容
#include<iostream>
using namespace std;
class b1 {
public:
b1(int i) { cout << "b1" << endl; }
};
class b2 {
public:
b2(int j){ cout << "b2" << endl; }
};
class b3 {
public:
b3(){ cout << "b3" << endl; }
};
class Derived :public b2, public b1, public b3 {
b1 m1;
b2 m2;
b3 m3;
public:
Derived(int a, int b, int c, int d) :b1(a), m2(d), m1(c), b2(b) { cout << "Derived" << endl; }
};
int main() {
Derived(1, 2, 3, 4);
return 0;
}
输出结果为:
b2
b1
b3
b1
b2
b3
Derived
拷贝构造函数:派生类负责新增数据和函数成员的拷贝构造,对于基类继承的成员则通过类型兼容规则,由派生类对象(的引用)去初始化基类的引用
Derived(const Derived& d, const Derived& dd):b1(d),b2(dd){}
析构函数:在该类对象消亡之前做一些必要的清理工作
执行次序与构造函数严格相反
构造一个类的基本对象的顺序:
①虚基类的构造函数(由最远派生类仅执行一次);
②其他基类的构造函数,按照继承顺序进行调用;
③派生类新增成员的初始化,按照在类中定义的顺序进行调用;
④执行构造函数的函数体。
▌多态
同样的消息被不同类型的对象接受时导致的不同的行为
消息:对类成员函数的调用
行为:对函数的实现,不同的行为即调用不同的函数
面向对象的多态分为:(专用多态)重载多态、强制多态、(通用多态)包含多态、参数多态
- 重载多态:
- 包含多态:不同类的同名函数的多态行为,主要是通过虚函数来实现;
多态从实现的角度可以分为:编译时的多态、运行时的多态
后续更新~