这里写目录标题
- 1、OOP概述
- (1)面向对象四大特征
- A.抽象
- B.封装
- C.继承
- D.多态
- (2)构造函数
- A.What(什么是构造函数)
- B.Why(构造函数的作用)
- C. Which(有哪些构造函数)
- (3)析构函数
- A.What(什么是析构函数)
- B.Why(析构函数的作用)
- (4) =default 和 =delete
- A.Why
- B.How
- 2、继承
- (1)What(什么是继承)
- (2)Why(继承的作用)
- (3)How(如何使用继承)
- (4)虚函数
- A.What
- B.Why(虚函数的作用)
- C.使用虚函数的注意事项
- (5)虚析构函数
- A.Why(虚析构函数的作用)
- B.What(什么是虚析构函数)
- (6)抽象基类
- A.What(纯虚函数&抽象基类)
- B.抽象基类的特点
- (7)继承关系中的访问控制
- A.类中成员的访问权限
- B.类继承中的访问权限
- C.派生类向基类转换的权限问题(向上转型)
- D.友元在继承中的访问权限
- (8)多重继承
- A.横向多重继承:
- B.纵向多重继承:
- C.联合多重继承:
- (9)虚继承
- A.What(什么是虚继承、虚基类)
- B.Why(虚继承的作用)
- C.How
- 3、多态(编译时多态)-重载
- (1)输入输出运算符重载
- (2)算术运算符:+、-、*、/
- (3)关系运算符:>、>=、==、<=、!=
- (4)赋值运算符
- (5)下标运算符
- (6)递增递减运算符
- (7)解引用运算符和箭头运算符
- (8)重载new和delete
- A.Why(为什么要重载new和delete运算符)
- B.How(如何重载)
- (9)函数调用运算符
- A.什么是函数对象
- B.函数对象lamda
1、OOP概述
(1)面向对象四大特征
A.抽象
把一类东西的共同属性和方法提取到一个类中,而不关注具体如何实现
B.封装
对象属性对外接是隐藏的,只能通过对象的方法进行访问和修改
通过类实现对现实情况的抽象,使用private权限实现对类的封装
class Caculater{
private:
int *Id; // 对数据进行封装,只有类能访问,外部智能通过提供的函数访问
public:
Caculater() { // 默认构造函数
std::srand(std::time(nullptr));
int randomNumber = std::rand();
Id = new int;
*Id = randomNumber;
}
Caculater(Caculater &caculater) { // 拷贝构造函数
Id = caculater.getId();
}
~Caculater() { delete Id; }
void showId()
{
if(Id) cout << "Id = " << *Id << endl;
else cout << "nullptr" << endl;
}
template <class T, class U>
auto add(const T &t, const U &u) const
{
return t + u;
}
int *getId()
{
return Id;
}
};
template <typename T>
void printf(const T &&t) { cout << t << endl; }
int main(){
// 隐式调用默认构造函数
Caculater caculater01;
// 调用成员函数
printf(caculater01.add(20, 10.1)); // 打印:30.1
return 1;
}
C.继承
派生类可以继承基类的非私有属性和方法,而无需自己重新定义
D.多态
静态多态:编译时多态
函数重载或模板重载
动态多态:运行时多态
父类型可以指向其子类型的实例,使子类型对同一方法作出不同的回应,也就是所谓的动态绑定
动态绑定
通过基类指针或引用调用虚函数时,会根据实际对象的类型来确定要调用的函数版本 基类的引用或指针调用虚函数 virtual 时发生动态绑定
(2)构造函数
A.What(什么是构造函数)
一种特殊的成员函数:
一方面它没有返回值
另一方面它和类名相同
B.Why(构造函数的作用)
主要作用是创建初始化类对象:为对象的成员赋初始值、执行一些必要的初始化操作等
C. Which(有哪些构造函数)
无参构造函数
如果没有显式定义任何构造函数,编译器将自动生成一个默认构造函数(合成默认构 造函数)。它不带任何参数,并对类的成员进行默认初始化
带参构造函数
参数列表不为空的构造函数
Student(string strStuName, int iAge )
{
m_strStuName = strStuName;
m_iAge = iAge;
}
Student(string strStuName, int iAge = 24)
{
m_strStuName = strStuName;
m_iAge = iAge;
}
拷贝构造函数
将参数中的对象深拷贝给当前对象,如果存在指针数据,一定要重新开辟空间,然后赋值
Student(const Student &stuObj)
{
this->strStuName = stuObj.getName();
this->iAge = stuObj.getAge();
this->ptrScore = new float(strObj.getScore());
}
移动构造函数
实现了数据的转移,相当于“鸠占鹊巢,还得把鹊赶尽杀绝”,移动赋值运算符同理
Student( Student &stuObj)
{
this->strStuName = stuObj.getName();
stuObj.setName("");
this->iAge = stuObj.getAge();
stuObj.setAge(0);
this->ptrScore = new float(strObj.getScore());
stuObj.score = nullptr;
}
转换构造函数
本质是带一个参数的构造函数,在需要时可以将其他类型的对象隐式转换为当前类的对象
(3)析构函数
A.What(什么是析构函数)
一个特殊的成员函数:
一方面,对象被销毁时自动调用,它不能是delete的
另一方面,和构造函数一样没有返回值
~Student(){
delete this->ptrScore;
ptrScore = nullptr;
}
B.Why(析构函数的作用)
- 可用于释放动态分配的内存
- 可用于关闭文件、数据库连接和网络连接资源
- 解锁互斥量或释放其它同步资源
(4) =default 和 =delete
A.Why
更精确地控制类的成员函数的行为,提高代码的可读性和安全性
B.How
class MyClass {
public:
// 默认构造函数
MyClass() = default; // 默认析构函数
~MyClass() = default; // 禁用拷贝构造函数
MyClass(const MyClass&) = delete; // 禁用赋值运算符
MyClass& operator=(const MyClass&) = delete; // 禁用移动构造函数
MyClass(MyClass&&) = delete; // 使用默认移动赋值运算符
MyClass& operator=(MyClass&&) = default;
};
2、继承
(1)What(什么是继承)
派生类从基类继承属性和方法
(2)Why(继承的作用)
重用性:派生类可以继承基类的属性和方法,减少重复代码的编写
扩展性:派生类可以在继承基类的基础上添加新的属性和方法,实现更强大的功能
多态性:实现对不同派生类的统一处理
说明:派生类的属性和基类重名时,会自动隐藏基类的成员
(3)How(如何使用继承)
class Derived: public Base{...}
(4)虚函数
A.What
一种特殊的成员函数,在基类中声明并用于被派生类重写的特殊成员函数
B.Why(虚函数的作用)
允许在运行时根据对象的实际类型来调用相应的函数实现,以实现多态性
C.使用虚函数的注意事项
- override 关键字的使用:只有虚函数才能用 override 修饰(在派生类中使用该关键字)
- 虚函数与默认实参:如果虚函数使用了默认实参, 则基类和派生类中定义的默认 实参应该一致,基类和派生类的虚函数必须接受相同的形参列表,否则无法实现动 态绑定
- 回避虚函数机制:通常情况下,只有在成员函数(或友元)中使用作用域运算符来回避虚函数机制
(5)虚析构函数
A.Why(虚析构函数的作用)
当我们使用基类指针或引用指向派生类对象,并且在基类指针或引用上删除该对象时, 如果基类的析构函数不是虚函数,则只会调用基类的析构函数,而不会调用派生类的 析构函数。这可能导致资源泄漏和未定义行为
B.What(什么是虚析构函数)
virtual ~Base();
注意:虚析构函数将阻止合成的移动构造函数和合成的移动赋值运算符,因为默认只 进行浅拷贝,而动态内存分配下的浅拷贝可能导致内存泄漏、悬挂指针等问题
(6)抽象基类
A.What(纯虚函数&抽象基类)
纯虚函数:
抽象基类:
含有纯虚函数的类叫做抽象基类
B.抽象基类的特点
- 至少包含一个纯虚函数
- 不能实例化对象,只能用作其他类的基类
- 继承抽象基类的派生类必须实现纯虚函数,否则派生类也会称为抽象基类
- 抽象基类可以包含非纯虚函数,提供默认或共享的功能
(7)继承关系中的访问控制
A.类中成员的访问权限
- public:类的对象(外部)可以访问,派生类也可以访问
- protected:类的对象(外部)不能访问,派生类可以访问
- private:类的对象(外部)不能访问,派生类也不可以访问
B.类继承中的访问权限
-
public继承:public->public, protected->protected
派生类可以继承基类中的公有成员和受保护成员,并将其作为自己的公有成员和受保护成员
-
protected继承:public&protected->protected
将基类中的公有成员和受保护成员作为派生类的受保护成员
-
private继承:public&protected->private
将基类中的公有成员和受保护成员作为派生类的私有成员,使得派生类无法直接访问这些成员
C.派生类向基类转换的权限问题(向上转型)
注意:派生类的成员函数和友元函数中,可以进行向上转型
D.友元在继承中的访问权限
- 友元不能被继承:友元函数和友元类类似于基类的私有成员
- 派生类的友元不可直接访问基类成员(包括公有成员)
(8)多重继承
A.横向多重继承:
B.纵向多重继承:
C.联合多重继承:
因为 single 和 waiter 都继承了一个 worker 组件,因此 SingingWaiter 将包含两个 worker 组件,那么将派生类对象的地址赋给基类指针将出现二义性
那么如何解决二义性问题呢?我们知道程序的执行一定是具有确定性的,在上述情况下,我们能想到的是进行强制转换,如下所示:
很显然,上述这种强制转换确实能够解决因联合继承带来的二义性问题,但是每次都进行这样的强制转换过于繁琐,那么有没有简单的办法解决二义性问题呢?答案就是:虚继承,所谓的虚继承,就是让共享一个祖父类
(9)虚继承
A.What(什么是虚继承、虚基类)
- 虚继承:
class Derived: public virtual Base, 如下例所示,展示了虚继承的形式
- 虚基类
被声明为虚继承的基类被称为虚基类
B.Why(虚继承的作用)
- 解决二义性冲突:当基类的指针指向孙子类的指针或引用时,会出现二义性,因为 孙子类对象包含多个祖父类对象,而虚继承只保留一个共享的祖父类
- 减少内存消耗:因为孙子类只包含一个祖父类对象
- 减少代码冗余:虚基类的成员只需在最终的派生类中定义一次
C.How
3、多态(编译时多态)-重载
(1)输入输出运算符重载
以友元的形式进行重载:
(2)算术运算符:+、-、*、/
以友元的形式重载:
(3)关系运算符:>、>=、==、<=、!=
以友元的形式重载:
(4)赋值运算符
(5)下标运算符
(6)递增递减运算符
前置版本:
后缀版本:
注意:显式调用后置版本(默认调用前置版本)
(7)解引用运算符和箭头运算符
箭头运算符和解引用运算符必须是类的成员函数
(8)重载new和delete
A.Why(为什么要重载new和delete运算符)
重载 new 运算符和 delete 运算符是为了对内存分配和释放过程进行自定义操作,以 满足特定的需求。通过重载这些运算符,我们可以提供自定义的内存管理方法,例如 使用自定义的内存池,跟踪内存分配和释放情况,或进行性能优化
B.How(如何重载)
(9)函数调用运算符
函数调用运算符必须是成员函数
A.什么是函数对象
如果类定义了调用运算符,则该类的对象称为函数对象
B.函数对象lamda
lamda的引用捕获:
注意:使用[&]可以引用捕获作用域内所有变量
lambda的值捕获:
注意:使用[=]可以值捕获作用域内所有变量