【C++知识点总结全系列 (08)】:面向对象编程OOP

目录

前言

C++知识点全系列文章汇总,欢迎读者阅览:

【C++知识点总结全系列 (01)】:数据类型、数据类型转换和变量
【C++知识点总结全系列 (02)】:C++中的语句、运算符和表达式详细总结
【C++知识点总结全系列 (03)】:函数(函数参数传递、可变参数、函数返回值等详细介绍)
【C++知识点总结全系列 (04)】:C++类的详细总结与分析
【C++知识点总结全系列 (05)】:IO 类的详细总结和分析
【C++知识点总结全系列 (06)】:STL六大组件总结- 配置器、容器、迭代器、适配器、算法和仿函数
【C++知识点总结全系列 (07)】:模板与泛型编程详细总结与分析
【C++知识点总结全系列 (08)】:面向对象编程OOP ==》 当前位置

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.What(函数对象)

如果类定义了函数调用运算符,则该类的对象称为函数对象

在这里插入图片描述

B.Which(有哪些可调用函数对象)

函数
函数指针
lambd函数对象
bind创建的对象
重载了函数调用符的类对象

C.函数对象lambda

在这里插入图片描述

  • lambda的引用捕获

在这里插入图片描述
注意:使用[&]可以引用捕获作用域内所有变量

  • lambda的值捕获:

在这里插入图片描述

D.标准库中的函数对象

在这里插入图片描述

(10)包装器function

A.What(什么是包装器)

一种将一段代码或功能封装在一个接口下的技术或类模板

B.Why(包装器的作用)

提供更一致或更合适的接口,以简化代码结构、提高可维护性,并允许更容易地使用特定的功能
它可以包装任何类型的可调用实体,如普通函数、函数对象、lambda 表达式、类的成员函数等

在这里插入图片描述

C.How(如何使用包装器)

在这里插入图片描述

在这里插入图片描述

(11)类型转换运算符type()

A.如何定义类型转换运算符

在这里插入图片描述

B.如何使用类型转换运算符

在这里插入图片描述

C.类型转换运算符的注意事项

  • 类型转换运算符一定要用explicit修饰,禁止隐式转换
  • 不要为类定义相同的类型转换运算符
  • 较大的数据类型可以隐式转换为较小的数据类型,但可能会出现精度损
    失或溢出

【C++知识点总结全系列 (01)】:数据类型、数据类型转换和变量
【C++知识点总结全系列 (02)】:C++中的语句、运算符和表达式详细总结
【C++知识点总结全系列 (03)】:函数(函数参数传递、可变参数、函数返回值等详细介绍)
【C++知识点总结全系列 (04)】:C++类的详细总结与分析
【C++知识点总结全系列 (05)】:IO 类的详细总结和分析
【C++知识点总结全系列 (06)】:STL六大组件总结- 配置器、容器、迭代器、适配器、算法和仿函数
【C++知识点总结全系列 (07)】:模板与泛型编程详细总结与分析
【C++知识点总结全系列 (08)】:面向对象编程OOP ==》 当前位置

  • 59
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值