# 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 类称为父类或基类
派生类中的成员 包含两大部分
一部分是继承的父类的成员,一部分是自己的成员
# 继承方

# 继承中的对象模型
子类包含父类的所有成员(包括私有)
# 继承中的构造和析构顺序
父类构造函数
子类析构函数
子类构造函数
父类析构函数
# 继承同名成员处理方式
子类访问同名成员,默认访问的是自身的成员
想要访问父类的成员,需要在成员前加上作用域
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 指向父类的成员
# 多态
优点:
代码组织结构清晰
可读性强
便于前期和后期的扩展和维护
# 多态的基本概念
多态分为两类:
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名
- 动态多态:指程序在运行时再决定调用哪个函数
区别:
- 静态多态的函数地址是早绑定,即编译阶段就已经确定函数地址
- 动态多态的函数地址是晚绑定,即运行阶段再确定函数地址
# 虚函数
在函数前加上 virtual ,变为虚函数
虚函数允许子类提供自身特定的函数实现,运行时根据对象的实际类型来调用对应的函数,实现动态多态
条件:
- 父类有虚函数
- 子类中有对应函数的重写(函数返回值,函数名,参数都一样叫函数重写)
使用:
通过父类指针或者引用指向子类对象或者调用虚函数
override:
override 是一个关键字,用于显式声明一个成员函数是重写基类中的虚函数。它是 C++11 引入的特性,目的是为了提高代码的可读性和安全性。
例子:
void func() override | |
{ }; |
# 纯虚函数和抽象类
纯虚函数:
virtual 返回值类型 函数名(参数)=0;
只要有一个纯虚函数,就属于抽象类
抽象类特点:
- 无法实例化对象
- 子类如果不重写父类中的纯虚函数,否则也属于抽象类
# 虚析构和纯虚析构
虚析构函数是用于确保通过基类指针删除对象时,能够正确调用派生类的析构函数。
即在使用时,如果子类有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构函数
解决方法:将父类的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构的共性:
- 都可以解决父类指针释放子类对象的问题
- 都需要有具体的函数实现
区别:
如果有纯虚析构,该类属于抽象类,不能实例化对象
语法:
1. 虚析构: virtual ~类名( ){ }
2. 纯虚析构 在类内声明 virtual ~类名()=0; 在类外 ~类名 (){}
如果子类中没有堆区数据可以不写虚析构
# 文件操作
# 写文件
- 包含头文件
<fstream> - 创建流对象
ofstream p; - 指定打开路径(或文件名)、打开方式
p.open(”文件路径“,打开方式); - 写内容
p << ” ”; - 关闭文件
p.close( );
打开方式:
| 打开方式 | 功能 |
|---|---|
| ios::in | 为读文件而打开文件 |
| ios::out | 为写文件而打开文件 |
| ios::ate | 初始位置为文件尾部 |
| ios::app | 追加方式写文件 |
| ios::trunc | 如果文件存在,先删除后再创建 |
| ios::binary | 二进制方式 |
报错:
在想打开特定文件路径的文件时,
“通用字符名的格式不正确”
把文件路径的 \ 反斜杠都变为 \\ 两个反斜杠,顺利解决
# 模板
# 语法
template<typename T> | |
函数声明或定义 |
typename (可替换为 class) 表明其后面的符号 T 是一种数据类型
# 注意事项
在使用模板时,编译器可以自动推导数据类型 T
但需要注意
- 推导的数据类型
T必须一致才能使用 - 使用模板时,即使不传入参数,也要指定数据类型后才能使用模板
指定数据类型:
func<int>( );// 技术没有参数也要指定数据类型 |
# 普通函数与函数模版的区别
- 普通函数可以进行隐式转换
- 函数模版在没有指定类型时(即自动推导数据类型)不能隐式转换
- 函数模版在指定类型后 (即
<datatype>),可以发生隐式转换
