Ox00 前言
- C++作者
- 本贾尼·斯特劳斯特卢普(Bjarne Stroustrup,1950年6月11日
- 1979年,Bjame Sgoustrup到了Bell实验室,开始从事将C改良为带类的C(C with classes)的工作。1983年该语言被正式命名为C++。自从C被发明以来,它经历了3次主要的修订,每一次修订都为C增加了新的特征并作了一些修改。第一次修订是在1985年,第二次修订是在1990年,而第三次修订发生在c++的标准化过程中。
Ox01 相较C语言的改动
- 头文件
- 尽可能在包含头文件时选择C库文件时,将名称去掉.h后缀,添加c前缀,用以区分
- // #include<stdio.h>
- #include
- 尽可能使用nullptr而非NULL.
- 申请堆空间方式
- int *p = new int[10]{}; // 用于替换 malloc (c的库函数)
- delete []p; // 替换free (c的库函数)
- p = nullptr; // 及时置空,防止成为悬空指针
- 名称粉碎机制
- 引用
- 命名空间
- 常见概念
- 函数重载和带默认参数的函数容易造成二义性
- OOP三要素为: 类 对象 继承
- 空类占据1个字节的空间
- 友元并非成员函数,调用无需类作用域
- 类的成员函数不占类空间(并非属于实例独有)
- 类的静态成员变量也是
- 接口: 类的公有成员函数故而公有继承也称作接口继承,保护和私有继承称为实现继承
// 变参函数示例
void _trace(int n...)//n需要传参时表示数据数量
{
va_list ap;
va_start(ap, n);
for (int i = 0; i < n; i++)
{
auto nNum = va_arg(ap, int);//传入类型,自动获取下一个
printf("%d", nNum);
}
va_end(ap);
}
Ox02 面向对象程序设计(OOP)
- 三大特性
- 封装
- C++引入类概念,利用class或struct关键词创建,两者区别仅在默认成员属性与继承属性类将具体事务(函数)与操作的对象(成员变量)封装到了一起
- 继承
- 继承用于减少重复代码,提高代码复用率,一定程度上完全包含父类,在此基础上用于更进一步
- 多态
- 同一事件发生在不同的对象上产生不一样的效果,即接口复用.
- 1.静态多态,在程序编译时期体现多态特性
- 函数重载(运算符重载)
- 模板重载
- 2.动态多态,在程序运行时体现多态特性
- 动态多态,在程序运行时体现多态特性
- 虚函数
| 种类 | 单词 | 作用域 | 函数名 | 参数 | 注 |
| --- | --- | --- | --- | --- | --- |
| 重载 | overload | 相同 | 相同 | 不同 | 同一作用域下同名不同参函数 |
| 重定义 | override | 不同 | 相同 | 不同 | 基类和子类的同名不同参函数 |
| 重写 | overwrite | 不同 | 相同 | 相同 | 基类和子类的同名同参函数 |
Ox10 新特性
- 常量const
- const定义的常量可用于定义数组时作为数组最大下标
- const定义的常量具有类型.
- const的应用场景(在函数形参中使用)
- const定义的常量必须被初始化
- const常见的用法
- 1.定义常量: const int PI = 3.14;
- 2.用作修饰函数的参数,确保不被修改,参考strstr
- 3.函数返回一个常量。
- 4.修饰当前对象的内容(成员函数)不能被修改。int get() const;
- 5.const 修饰的是当前对象this(ecx+偏移)
- 内联函数inline
- 定义方式: 在函数定义的函数头前加上inline关键字
- 逻辑过于复杂的函数不能成为内联函数
- 成为内联函数时, 函数的代码被直接嵌入在函数调用处, 而不会生成函数调用的代码.
- 引用
- 引用类型变量的定义方式
- 引用类型变量的性质
- 引用类型的变量并不占用内存空间,其内存空间来自被引用的变量
- 引用类型的变量必须被初始化
- 引用类型的变量定义之后, 就不能再引用其它的变量
- 引用类型的和指针类型的异同
- 相同点
- 引用和指针都可以修改指向的变量的内容
- 不同点
- 指针需要通过*运算符来修改指向的变量
- 引用类型是直接修改的.
- 相同点
- 函数重载
- 函数重载的方式
- 相同作用域下, 函数名相同的函数
- 函数重载的规则
- 函数的参数不能相同
- 参数个数
- 参数类型
- 参数顺序
- 函数的参数不能相同
- 函数重载的原理
- C++有名称粉碎机制, 每个函数的名字都是和参数的类型,顺序有关的. 这样一来, 虽然源码中的名字相同, 但实际编译后的函数名是不一样的.
- 使用extern"C" 可以禁用一个函数的名称粉碎机制
- 函数重载的方式
- 形参默认值
- 设置时, 必须从右往左依次设置
- 调用函数时, 没有给出的实参会被形参默认值自动赋值
- 类型转换
- static_cast : 基本类型的相互转换
- const_cast : 去除const属性
- reinterpret_cast : 重新解释
- dynamic_cast : 动态转换,转换时做转换判断,常用于父类指针/引用和子类指针/引用的转换.
- 内存申请和释放
- new 和 new[]
- delete 和delete[]
- new 的返回值是类型的指针,但是malloc的返回值始终是void*
- new/delete是运算符(关键字),malloc和free是库函数.
- new/delete的底层实现通常都是 malloc/free
- 输入和输出
- cin
- std::cin >> 变量1 >> 变量2;
- (std::cin.operator>>(变量1)).operator>>(变量2);
- cout
- std::cout << 变量1 << 变量2 << std::endl << std::ends;
- 格式化输出数据 iomanip
- setfill: 设置填充数据
- setw: 设置宽度
- hex: 十六进制显示
- 推荐使用printf格式化输出
- cin
Ox20 类的基本使用
- 类的组成
- 成员变量
- 基本类型成员变量
- 常量类型成员变量
- 引用类型成员变量
- 类类型成员变量
- 成员函数
- 数据的定义
- 再类内定义的数据都是数据成员,作用域是当前类
- 成员函数的定义
- 1.在类内编写声明和实现
- 2.在类内声明,在类外实现(类外实现必须加上类域).
- 3.在单独的头文件中声明,并且在单独的cpp中实现.
- 数据的定义
- 静态成员
- 静态成员函数
- 静态函数的定义就是在函数前使用static,静态函数没有this指针,并且不能访问非静态函数以及非静态成员,通常使用静态函数访问静态成员。
- 静态成员变量
- 静态数据:
- 静态数据的定义: 在类内数据的定义前使用static关键字进行*声明.
- COBJ 类内的 static int n; 不能初始化的必须要在类外进行初始化,否则报错: 无法解析的外部符号
- int COBJ::n = 0;
- 访问静态数据的方法:
- 直接用对象访问
TEST test; test.static_int;. - 直接用类名访问(推荐)
TEST::static_int.
- 直接用对象访问
- 静态成员函数
- 成员变量
- 类的封装性
- private 私有
- protected 保护
- public 公有
- 构造函数
- 初始化列表
- 一般用于初始化成员变量, 常量类型的成员变量 , 引用类型的成员变量以及无默认构造函数的类类型的成员变量都需要在初始化列表中进行初始化
- 默认构造
- 没有参数的构造函数.
- 自动调用1 : 当定义一个对象没有传参时被调用
- 自动调用2: 当类对象作为其它类的成员变量, 类对象被构造时也会被自动调用, 如果缺少了默认构造,, 将导致其它类的构造无法被调用从而产生编译错误.
- 转换构造
- 只有一个参数, 且参数是非本类类型
- 自动调用1 : 当使用类对象进行赋值或运算 , 但是运算的两种类型不一致(例如: 一个类对象加上一个整数). 此时会产生隐式转换, 编译器会将另一个不同的数据类型转换成本类类型. 转换的方法就是调用本类的构造函数构造一个对象, 调用构造函数时, 会将这个整数作为构造函数的参数.
- 拷贝构造
- 只有一个参数, 且参数类型只能是本类的引用类型
- 构造一个新的类对象, 需要使用一个已存在的类对象进行初始化的时候, 拷贝构造就被调用.
- 一般情况下不需要重新编写拷贝构造, 因为默认的拷贝构造完全由能力将对象的字段进行拷贝. 只有当类中函数有指针成员, 并希望使用深拷贝时才需要重载
- 深拷贝
- 深拷贝: 深拷贝就是指针的内存拷贝
- 可能会用到的函数(深拷贝必须涉及到内存的拷贝):
- memcpy
- strcpy
- _strdup
- new \ malloc
- 可能会用到的函数(深拷贝必须涉及到内存的拷贝):
- 深拷贝: 深拷贝就是指针的内存拷贝
- 浅拷贝
- 浅拷贝: 默认生成的拷贝构造函数是浅拷贝,浅拷贝就是指针的值拷贝
- 深拷贝
- 其它带参构造
- 初始化列表
- 析构函数
- 特点: 析构函数只能有一个,没有返回值,没有参数,名字是~类名();
- 用途: 对象销毁的时候自动调用一次,通常用于清理内存关闭句柄或文件。
- 析构函数应该被设置成虚函数,目的是防止子类对象的析构函数不被调用
- 友元
- 友元: friend
- 破坏封装性,为了访问其它类中的私有数据。
- 友元的声明应该写在类内,也就是主人家里,主人要求客人。
- 友元普通函数
- 友元函数是不是成员函数?不是,通产是一个全局函数
- 使用友元函数可以访问到类内的所有私有数据
- friend void show(TEST &test);
- 友元成员函数
- 指定某一个类的某一个函数能够访问当前类的所有私有数据
- friend void 类名:show(TEST &test);
- 使用十分的复杂,封装性高于友元类和友元函数
- 指定某一个类的某一个函数能够访问当前类的所有私有数据
- 友元类
- 友元类的声明同样写在主人类里面
- friend class XXX;
- 缩写自: class XXX;(类外) friend XXX;(类内)
- friend class XXX;
- 友元类的声明同样写在主人类里面
- 友元: friend
Ox30 运算符重载
- 不能重载的运算符:'.'
- 不能重载的运算符:
.,sizeof,::,? :
- 不能重载的运算符:
- 规则
- 不能重载不存在的运算符
- 不能改变运算符的参数个数
- 不能改变运算符的优先级和结合性
-
不能被重载的运算符
- :: 、:? 、. 、sizeof
-
只能被重载为非静态成员函数的运算符
- =,[ ],(),->*
-
使用成员函数重载和使用友元函数重载
-
成员函数重载运算符,参数最少为0个
-
友元函数重载运算符,参数最少为1个
-
-
需要特殊对待的运算符
-
自增自减: int operator++(int); 后置++ ,括号内有int 的是后置++
-
流运算符: << >>, 通常被重载为友元函数
-
cin >> xxxx; xxx >> cin;(不推荐的,修改了运算符的意义)
-
返回值和参数是引用类型,原因是 istream/ostream的拷贝构造被删除了
- xxxx(const ostream& o) = delete;(了解)
-
-
Ox40 类的继承
- 继承方式
- private
- private:私有继承下,除了私有属性,全都变成私有的
- protected
- protected: 保护继承下,除了私有属性,其余全都变成保护的
- public
- public: 公有继承下,除了私有属性,其余在子类中保持不变
- private
- 结构体和类的区别
- class默认的继承方式是private,struct默认继承方式是public
- class默认的访问属性是private,struct默认访问属性是public
- 单继承
- 类对象大小的计算方式: 父类占用的大小 + 子类自身成员的大小
- 构造函数的调用顺序: 先调用父类构造, 再调用子类构造
- 析构函数的调用顺序 : 先调用子类的再调用父类的
- 子类函数和父类函数重名冲突
- 成员变量同名
- 会产生两份内存, 一份是父类的成员变量,一份是子类的成员变量, 当使用这个同名成员变量时,采取就近原则(在子类成员函数中使用时就用子类的成员变量).
- 成员函数同名
- 使用就近原则调用成员函数(在父类则默认调用父类的, 在子类则默认调用子类的)
- 成员变量同名
- 多继承
- 构造函数调用顺序
- 按照继承顺序先调用父类的构造, 再调用子类构造
- 多继承时的二义性
- 成员函数二义性
- 使用
类名::成员函数名
- 使用
- 菱形继承产生的成员变量的二义性
- 虚继承
- 虚基表指针
- 成员函数二义性
- 构造函数调用顺序
Ox50 类的多态
- C++中的多态
- 函数重载(静态联编)
- 函数重定义(静态联编)
- 模板(静态联编)
- 虚函数(动态联编)
- 动态联编
-
- 父类指针指向子类对象
-
- 通过父类指针调用虚函数
- 动态联编指在编译代码时生成的汇编代码不是call 偏移的形式, 而是通过对象的虚函数表动态获取被调函数的地址来调用函数.
- 静态联编:在程序编译时就确定了调用的是哪一个函数
-
- 虚函数原理
- 虚函数表与虚表指针
- 当一个类有一个或以上的虚函数时, 这个类就会有虚函数表 , 这个类定义出来的对象前4字节保存的是虚函数表的首地址.
Ox60 抽象类
-
虚函数的定义
-
在普通成员函数的前面加上 virtual 关键字
-
实现起来和普通的函数是一样的
-
使用父类指针指向不同子类,调用的是对应子类类型的虚函数,构成了多态
-
子类重写父类的虚函数时,可以不添加 virtual 关键字,虚函数是向下继承的,也就是说子类中额外添加的的虚函数在父类中不存在
-
-
虚函数表和虚表指针
-
当一个类中拥有虚函数时,就会存在虚函数指针
-
虚函数指针的数量由存在虚函数的父类决定
- 假设继承三个类,其中两个类有虚函数,那么有两个虚表指针
-
关于虚函数表
-
每个类都有对应的虚函数表,相同的类对象,使用的是同一张虚函数表
- 虚函数表在构造函数内被初始化(深入探索C++对象模型)
-
虚函数表内存储的是什么?
-
1.父类的虚函数(纯虚函数)
-
2.子类重写的虚函数
-
3.子类多写的虚函数(子类中多添加的虚函数被添加在第一张虚函数表内)
-
-
-
-
虚析构函数的使用
- 为什么有虚析构函数?
-
答: 如果不存在虚析构函数,那么当父类指针指向子类对象的时候,释放父类指针,只会调用父类的析构函数,可能导致子类对象的内存泄漏等问题。
-
虚析构函数的目的是为了防止子类对象的析构函数不被调用。
-
- 为什么有虚析构函数?
-
纯虚函数和抽象类
-
抽象类: 有纯虚函数的类就是抽象类
- 抽象类不能被实例化,如果一个类继承自抽象类,没有实现它的所有纯虚函数,那么它仍然是一个抽象类。
-
纯虚函数:用于定义一个必须要子类实现的函数
- 语法: 虚函数 = 0;
-
-
纯虚函数
- 只能声明不能实现. 在声明时需要加上
= 0
- 只能声明不能实现. 在声明时需要加上
-
含有纯虚函数的类被成为抽象类
-
抽象类不能实例化对象
-
子类继承了抽象类, 如果没有实现所有纯虚函数, 则该子类仍还是抽象类
Ox70 模板
- 模板的使用:模板用于实现类型不同但是逻辑相同的类和函数
- template [class | typename]
- class 和 typename 没有区别
- template [class | typename]
- 函数模板的使用
- 函数的寻找优先级: 普通函数 > 特化函数 > 模板函数
- 函数模板和模板函数:
- 函数模板:是一个模板,不会生成具体的代码,只提供逻辑
- 模板函数:当一个函数模板被使用的时候,会检查参数,并根据传入的参数实例化出一个模板函数,模板函数会生成实际的代码;
- 函数模板有全特化,但是没有偏特化,函数模板的偏特化就是函数的重载。
- 特化的目的是为了对一些特殊的类型进行特殊的操作,比如为字符串比较大小,对类对象进行排序操作。
- 类模板的使用
- 类模板可以全特化也可以偏特化
- 全特化就是特化所有的类型
- 偏特化就是特化部分的类型
- 类模板在定义对象的时候必须提供类型 vector< int>
- 类模板可以全特化也可以偏特化
- 类模板的实现和定义必须要放在同一个文件,否则会报错
- 当在类模板外实现函数时,需要重新的指定模板关键字和参数
- ```cpp
template <class T>
class Test
{
public:
T number;
T operator-();
};
template <class T>
// 需要加上T表示当前是一个模板
T Test<T>::operator-()
{
return -number;
}
int main()
{
// 使用模板类需要指定类的类型
Test<int> test;
return 0;
}
- **不管是函数模板还是类模板的特化都必须写在模板的下面**
Ox80 重载重写和重定义
-
重载
- 作用域相同,参数不同,名称相同。
-
重写 override
- 作用域不同,必须存在虚函数,参数和返回值需要一致。
-
重定义
- 作用域不同, 参数可以相同也可以不同,子类的同名函数会隐藏父类的函数。
Ox90 命名空间的使用
- 命名空间的作用
- 命名空间用于解决变量名冲突的问题
- 访问命名空间中的数据
- 添加作用域: std::cout
- 使用 using namespace std; cout
- 缺点是,可能会造成名称的冲突
- 推荐使用: using std::cout:
- 缺点是太长了
OxA0 STL库的使用
-
vector: 动态数组(顺序表) STL 源码剖析
-
push_back
-
pop_back
-
at,安全版本的 [ ]
-
erase 迭代器
-
insert 迭代器
-
-
list: 双向循环链表
- 它没有重载[]运算符,原因是链表不能动态存取
-
map: 红黑树(不那么平衡的平衡二叉树)
- 键值对,相同的键只有一个,后面的值会覆盖前面的值,可以使用中括号访问值
-
string: 字符串,C++推荐使用
-
+: 拼接字符串,没有 改变原有的值
-
+=: strcat,拼接字符串
-
=: strcpy 拷贝字符串
-
.length(): strlen() 获取字符串的长度
-
append: 追加字符串
-
OxB0 异常处理
- try: 包含的是可能产生异常的代码 ,用于捕获异常
- throw : 用于主动的抛出异常,RaiseException->KiDispatchException(分发函数)
- catch: 用于获取对应类型的异常,可以有多个catch,如果最后一个catch内参数是...表示捕获所有类型的异常
OxC0 数据结构
OxC1 数据结构基础
- 算法的特性
- 有穷性:算法不会产生死循环。
- 可行性:算法是可以实施的。
- 输入输出:可以没有输入,但是必须要有输出
- 确定性:输入同样的数据,返回的结果是相同的
- 算法的度量
- 事前估算:大O记法
- 事后统计:成本大,不用
- 物理结构和逻辑结构
- 物理结构
- 顺序结果
- 链式结构
- 逻辑结构
- 集合(相互之间没有关联)
- 线性(一对一)
- 树形(一对多)
- 图(多对多)
- 物理结构
OxC2 线性表(一对一)
-
顺序表
-
增删: O(n),在第n个位置添加元素,需要移动size-n个元素的位置
-
访问: O(1),直接用下标
-
当数据访问较多,增删较少的时候,使用顺序表
-
-
链表
- 增删: O(1),前提是已经找到了
- 添加节点
- 增删: O(1),前提是已经找到了
prenode -> nextnode
\ newnode /
// 先让新的节点指向下一个节点,再让上一个节点指向新的节点
newnode->next = pre->next(nextnode);
pre->next = newnode;
- 删除节点
prenode -> delnode -> nextnode
// 将要删除的前一个节点指向要删除的下一个节点,然后再释放要删除的节点
prenode->next = delnode->next(nextnode);
delete delnode;
-
访问:O(n)
-
当数据的访问较少,增删较多时,使用链表
-
栈
- 特性: 先进后出
-
队列
- 特性:先进先出
OxC3 树和二叉树(一对多)
-
二叉排序树BST的性质
-
左边的节点小于根节点,右边的节点大于根节点
-
每个节点最多只有两个子节点
-
-
节点的计算
-
满二叉树: 最底层的节点填满的树
- 求节点的个数 2n-1
-
完全二叉树:只有最底层的右边的缺省的
- 求最少的节点个数 2(n-1)-1+1-> 2(n-1)
-
-
节点的添加
-
左边的节点小于根节点,右边的节点大于根节点
-
在某一棵树中添加一个节点,需要比较多少次
-
-
节点的删除
- 在某一棵树中删除一个节点,需要寻找多少次
-
节点的遍历
-
前序遍历:根 - 左 - 右 => 16 12 15 19 25 36
-
中序遍历:左 - 根 - 右 => 12 15 16 19 25 36,中序遍历的是有序树
-
后序遍历:左 - 右 - 根 => 15 12 36 25 19 16
-
层序遍历:16 12 25 15 19 36
-
-
平衡二叉树的性质
- 左子树和右子树的高度小于2
OxC4 A* 算法
- A* 算法是什么?
- A* 算法是一个寻路算法