# C++ 核心编程

# 面向对象的三大特性:封装,继承,多态

# 类和对象

# 静态成员

# 静态成员变量

所有对象共享同一份数据

编译阶段就已经分配内存

类内声明,类外初始化

声明: static datatype p

初始化: datatype classname::p = val ;

可以通过对象访问静态成员变量

也可以通过类名访问 classname::p

# 静态成员函数

静态成员函数只能访问静态成员变量

所有对象共享同一个函数

函数前面加一个 static

调用函数: classname::func();

# 构造函数与析构函数

# this 指针

# 常函数和常对象

# 友元

# 运算符重载

对已有的运算符重载,以适应自定义的数据类型,不要滥用运算符重载

# 1. 加号重载

//1. 用成员函数来重载加号
person operator+ (person &b)
{
	person temp(0,0);
	temp.p_A= this->p_A+b.p_A;
	temp.p_B= this->p_B+b.p_B;
	return temp;
}
//2. 用全局函数重载加号
person operator+ (person &a,person &b)
{
	person temp(0,0);
	temp.p_A= a.p_A+b.p_A;
	temp.p_B= a.p_B+b.p_B;
	return temp;
}

# 2. 左移运算符重载

// 只能用全局函数重载 & lt;<
ostream& operator<<(ostream &cout,person T)// 只传入 person 的值
{
cout<<"p_A:"<<T.p_A<<" p_B:"<<T.p_B;
	return cout;
}// 返回 cout, 链式编程思想,ostream 是 cout 的类

# 3. 递增运算符重载

// 在成员函数内重载 前置 ++
person& operator++ ()
{
	p_A++;
	p_B++;
	return *this;// 返回引用
}
// 在成员函数内重载 后置 ++
person operator++ (int)//int 为占位参数,与前置 ++ 区分开来
{
	person temp=*this;
	p_A++;
	p_B++;
	return temp;// 返回值
}

# 4. 赋值运算符重载

person& operator=(person &T)
{
	if(p_A!=NULL)// 先判断是否有值在堆区,如果有则 delete 释放,然再深拷贝
	{
		delete p_A;
		p_A=NULL;
	}
	p_A = new int(*T.p_A);
	return *this;// 返回赋值后的对象,以便连续赋值
}

# 5. 关系运算符重载

关系运算符有很多,<,>,==,! =

bool operator==(person &T)// 重载 ==
	{
		if(p_A==T.p_A)
			return true;
		else return false;
	}

# 6. 函数调用运算符重载

person( ) 为创建一个匿名对象,是临时的,当前行执行完后,立即释放

因为重载()后的使用方式非常像函数调用,因此也叫仿函数

仿函数非常的灵活,没有固定的写法

string operator()(string s1,string s2)
	{
		string s3;
		s3=s1+s2;
		return s3;
	}// 实现字符串相加

# 继承

# 继承的基本语法

class A : 继承方式 B { };

A 类称为子类或者派生类

B 类称为父类或基类

派生类中的成员 包含两大部分

一部分是继承的父类的成员,一部分是自己的成员

# 继承方

https://vip.helloimg.com/i/2024/07/28/66a6694d27b27.png

# 继承中的对象模型

子类包含父类的所有成员(包括私有)

# 继承中的构造和析构顺序

父类构造函数

子类析构函数

子类构造函数

父类析构函数

# 继承同名成员处理方式

子类访问同名成员,默认访问的是自身的成员

想要访问父类的成员,需要在成员前加上作用域

p. baseclass:: 成员

对于静态成员函数也可以这样,但也可以通过类名访问

subclass:: baseclass:: 成员

# 多继承语法

实际开发不建议使用多继承

class 子类 : 继承方式 父类1 ,继承方式 父类2

# 菱形继承

一个子类继承了多个父类,且这多个父类作为子类继承同一个父类

主要带来的问题是继承两份相同的数据,导致二义性问题

class A{};
class B: public A{};
class C: public A{};
class D: public B,public C{};

利用虚继承可以解决菱形继承问题

在两个子类在继承同一个父类时,加上 virtual 进行虚继承

这时两个子类的子类只会继承一份成员

class A{};
class B: virtual public A{};
class C: virtual public A{};
class D: public B,public C{};

虚继承时通过继承虚基类指针 vbptr 指向父类的成员

# 多态

优点:

代码组织结构清晰

可读性强

便于前期和后期的扩展和维护

# 多态的基本概念

多态分为两类:

  1. 静态多态:函数重载和运算符重载属于静态多态,复用函数名
  2. 动态多态:指程序在运行时再决定调用哪个函数

区别:

  1. 静态多态的函数地址是早绑定,即编译阶段就已经确定函数地址
  2. 动态多态的函数地址是晚绑定,即运行阶段再确定函数地址

# 虚函数

在函数前加上 virtual ,变为虚函数

虚函数允许子类提供自身特定的函数实现,运行时根据对象的实际类型来调用对应的函数,实现动态多态

条件:

  1. 父类有虚函数
  2. 子类中有对应函数的重写(函数返回值,函数名,参数都一样叫函数重写)

使用:

通过父类指针或者引用指向子类对象或者调用虚函数

override:

override 是一个关键字,用于显式声明一个成员函数是重写基类中的虚函数。它是 C++11 引入的特性,目的是为了提高代码的可读性和安全性。
例子:

void func() override
{   };

# 纯虚函数和抽象类

纯虚函数

virtual 返回值类型 函数名(参数)=0;

只要有一个纯虚函数,就属于抽象类

抽象类特点:

  1. 无法实例化对象
  2. 子类如果不重写父类中的纯虚函数,否则也属于抽象类

# 虚析构和纯虚析构

虚析构函数是用于确保通过基类指针删除对象时,能够正确调用派生类的析构函数。

即在使用时,如果子类有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构函数

解决方法:将父类的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构的共性:

  1. 都可以解决父类指针释放子类对象的问题
  2. 都需要有具体的函数实现

区别:

如果有纯虚析构,该类属于抽象类,不能实例化对象

语法:

1. 虚析构: virtual ~类名( ){ }

2. 纯虚析构 在类内声明 virtual ~类名()=0; 在类外 ~类名 (){}

如果子类中没有堆区数据可以不写虚析构

# 文件操作

# 写文件

  1. 包含头文件 <fstream>
  2. 创建流对象 ofstream p;
  3. 指定打开路径(或文件名)、打开方式 p.open(”文件路径“,打开方式);
  4. 写内容 p << ” ”;
  5. 关闭文件 p.close( );

打开方式:

打开方式 功能
ios::in 为读文件而打开文件
ios::out 为写文件而打开文件
ios::ate 初始位置为文件尾部
ios::app 追加方式写文件
ios::trunc 如果文件存在,先删除后再创建
ios::binary 二进制方式

报错:

在想打开特定文件路径的文件时,

通用字符名的格式不正确

把文件路径的 \ 反斜杠都变为 \\ 两个反斜杠,顺利解决

# 模板

# 语法

template<typename T>
函数声明或定义

typename (可替换为 class) 表明其后面的符号 T 是一种数据类型

# 注意事项

在使用模板时,编译器可以自动推导数据类型 T
但需要注意

  1. 推导的数据类型 T 必须一致才能使用
  2. 使用模板时,即使不传入参数,也要指定数据类型后才能使用模板
    指定数据类型:
func<int>( );// 技术没有参数也要指定数据类型

# 普通函数与函数模版的区别

  1. 普通函数可以进行隐式转换
  2. 函数模版在没有指定类型时(即自动推导数据类型)不能隐式转换
  3. 函数模版在指定类型后 (即 <datatype> ),可以发生隐式转换