类不是实体,对象是实体
成员变量(filed)属于对象
成员函数(member function)属于类
Big Three(构造函数,拷贝构造,拷贝赋值)
构造函数
初始化列表
列表初始化(initialize list)仅对成员变量初始化。
在构造函数里对成员变量初始化则为先初始化(默认)后赋值,故所有成员变量必须要有默认的初始化方法(成员变量包含其他类但该类没有默认构造函数则会报错)。构造函数无法主动调用。
尽量使用列表初始化
1 | class A{ |
拷贝构造和拷贝赋值
浅拷贝和深拷贝有什么区别
浅拷贝为字节流的拷贝,对于指针来说,通过浅拷贝会使新对象和旧对象指向同一块内存(类默认的拷贝构造和拷贝赋值)
深拷贝在复制时为指针分配新的内存,新对象和旧对象有各自独立的空间。
对于成员变量含有指针的类,尽量自己实现拷贝构造和拷贝赋值,防止不同对象的成员变量的指针指向同一块内存
代码举例
1 | class Test |
封装
封装从字面上来理解就是包装的意思,专业点就是信息隐藏,是指利用抽象将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体。
数据被保护在类的内部,尽可能地隐藏内部的实现细节,只保留一些对外接口使之与外部发生联系。
其他对象只能通过已经授权的操作来与这个封装的对象进行交互。也就是说用户是无需知道对象内部的细节(当然也无从知道),但可以通过该对象对外的提供的接口来访问该对象。
使用封装有 4 大好处:
- 1、良好的封装能够减少耦合。
- 2、类内部的结构可以自由修改。
- 3、可以对成员进行更精确的控制。
- 4、隐藏信息,实现细节。
继承
为什么需要继承?
如果仅仅只有两三个类,每个类的属性和方法很有限的情况下确实没必要实现继承,但事情并非如此,事实上一个系统中往往有很多个类并且有着很多相似之处,比如猫和狗同属动物,或者学生和老师同属人。各个类可能又有很多个相同的属性和方法,这样的话如果每个类都重新写不仅代码显得很乱,代码工作量也很大。
这时继承的优势就出来了:可以直接使用父类的属性和方法,自己也可以有自己新的属性和方法满足拓展,父类的方法如果自己有需求更改也可以重写。这样使用继承不仅大大的减少了代码量,也使得代码结构更加清晰可见
访问属性
访问限制符\访问位置 | 当前类 | 子类 | 类外 |
---|---|---|---|
public | 可以 | 可以 | 可以 |
protected | 可以 | 可以 | 不可以 |
private | 可以 | 不可以 | 不可以 |
重载、重写和覆盖
重载(overloading)
在同一个作用域中,函数名相同,但参数列表(包括参数的类型、个数或顺序)不同,形成的多个函数称为函数重载。
重写(overriding)
在派生类中,定义了一个与基类中同名且参数列表完全相同的函数,并通过 virtual 声明基类函数实现动态多态。(虚函数实现)
覆盖(Hiding)
在派生类中,定义了一个与基类同名但参数列表不同的函数时,基类的函数被隐藏(覆盖)。函数覆盖不是动态多态的一部分,与重写不同。
1 | class A{ |
多态
概念
不同的对象对于同一消息作出不同的响应。子类在继承父类后可以设计自己的版本,在运行时动态选择调用哪个版本实现
静态绑定和动态绑定
在面向对象编程中,静态绑定和动态绑定是两种不同的函数调用机制。
静态绑定(Static Binding),也称为早期绑定或编译期绑定,是指在程序编译时就将函数调用与函数实现绑定起来,而不考虑对象的实际类型。这种绑定是通过函数的名称和参数列表来实现的。在静态绑定中,编译器会在编译期间确定调用哪个函数,而不是在运行时确定。静态绑定通常适用于非虚函数的调用,因为非虚函数的调用是在编译期间就可以确定的。
动态绑定(Dynamic Binding),也称为晚期绑定或运行时绑定,是指在程序运行时根据对象的实际类型来决定调用哪个函数。这种绑定是通过虚函数来实现的。在动态绑定中,编译器会在运行时确定调用哪个函数,而不是在编译期间确定。动态绑定适用于需要实现多态性的情况,可以让基类指针或引用调用派生类中的函数实现,实现运行时多态性。
函数重载或函数模版(编译时多态)
虚函数(运行时多态)
作用
- 实现动态多态性(Runtime Polymorphism):通过使用虚函数,可以在运行时动态地确定调用的是基类函数还是派生类函数,实现多态性。例如,如果我们有一个指向基类对象的指针,我们可以使用虚函数来调用派生类中的适当函数。
- 支持运行时类型识别(RTTI):通过使用虚函数和类型信息(type information),可以在运行时确定对象的实际类型,从而实现更加灵活的代码设计。
- 简化代码维护:使用虚函数可以将代码的实现细节从类的使用者中分离出来,使得修改基类的实现对派生类的影响最小。
实现
对于有虚函数的类,其对象会包含指向vtable的vptr指针(vtable中为所有虚函数的地址)。在运行时确定对象类型,来选取相应的vptr,进而找到vptr指向的vtable的虚函数的具体函数地址
要求
- 新建子类对象
- 通过父类指针指向该对象
- 用虚函数定义目标函数
代码
1 | class Base |
其他
如果派生类没有覆盖基类的虚函数,则派生类会直接继承基类中的版本
如果类存在虚函数,则需将析构函数设为虚函数,否则在基类指针调用析构时只能运行基类的析构
友元
类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数称为它的友元
1 | class Sales_data{ |
一般来说,最好在类定义开始货结束前的集中位置声明友元
友元的声明只指定了访问的权限,而非一个通常意义上的函数声明。如果我们希望类的用户能够调用某个友元函数,那么就必须在友元声明外再专门对函数进行一次声明
相同类的对象互为友元