概述
c++学习笔记详解
- 0x00 简介
- 0x01 编译
- g++
- 常用命令选项
- 示例
- 过程
- 扩展名
- 链接库
- 参考
- ANSI标准C
- C预编译器
- 共享目标文件
- 编译语言标准
- 目标代码
- 0x02 基础
- 特性
- main()
- namespace
- 头文件
- 插入运算符 <<
- 符号常量
- 初始化
- 控制符
- 常量类型
- 宽字符类型
- 浮点数
- auto声明
- string类
- 位字段
- new和delete
- 自动、静态和动态存储
- 容器
- 字符函数库
- cin
- 逻辑判断
- 文件操作
- 可变参数列表
- 指针减法
- const指针
- 二维数组
- 函数指针
- 智能指针
- 内联函数
- 引用变量
- 模板
- 存储持续性
- 存储说明符
- 异常处理
- 参考
- 动态链接库DLL
- using编译指令
- 重载
- cin.clear()
- 编译代码
- 实例化和具体化
- 链接性
- 右值
- std::move
- 0x03 面向对象
- 类
- 构造函数
- 析构函数
- 重载
- 参数传递
- 友元
- 返回值
- 操作符重载
- 临时对象
- const成员函数
- 对象数组
- 作用域为类的常量
- 类模板
- 类的自动转换
- 复合
- has-a
- 构造和析构
- 委托
- pImpl
- 继承
- 公有派生
- 私有继承
- 保护继承
- 继承规则
- 虚函数
- 指针转化
- 抽象基类
- 引用转换
- 多重继承
- RTTI
- 参考
- 虚基类
- 0x04 设计模式
- 工厂模式
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
- 单例模式
- 懒汉单例模式
- 饿汉单例模式
- 策略模式
- 传统策略模式
- 函数指针实现
- 适配器模式
- 复合适配器
- 继承适配器
0x00 简介
- 打算正式入手安全研发了,师傅说先把
C++
基础打好,所以刷一遍C++ Primier Plus
,这里做一下记录。
0x01 编译
g++
g++
其实是gcc
针对c++
的一个版本。- 链接使用
c++
的标准库。
- 链接使用
常用命令选项
选项 | 解释 |
---|---|
-ansi | 只支持ANSI 标准的C 语法 |
-c | 只编译并生成目标文件 |
-E | 只运行C 预编译器 |
-g | 生成调试信息 |
-IDIRECTORY | 指定额外头文件搜索路径 |
-LDIRECTORY | 指定额外函数库搜索路径 |
-o | 生成指定输出文件 |
-O0 | 不进行优化处理 |
-O (同-O1 ) | 生成优化代码 |
-O3 | 最高级优化,包括inline 函数 |
-shared | 生成共享目标文件 |
-static | 禁止使用共享链接 |
-w | 不生成任何警告信息 |
-Wall | 生成所有警告信息 |
-std | 选择编译语言的标准 |
示例
- 默认:编译文件
filename
为可执行文件a.out
。
g++ <filename>
- 重命名输出:编译文件
filename
指定输出文件名为execname
。
g++ <filename> -o <execname>
- 生成目标代码:编译文件
filename
为目标代码filename.o
。
g++ -c <filename>
- 链接目标代码:将目标代码
filename.o
与其任何库链接起来,并创建一个名为execname
的可执行文件。
g++ -o <execname> <filename>.o
创建目标代码后,可以通过将目标代码与其他预编译代码段(其他
.o
文件)链接来创建可执行文件。
- 链接扩展类库:使用包含套接字对象代码的库 (
sys/socket.h
),从名为filename
的文件编译名为execname
的可执行文件。
g++ -o <execname> <filename> -lxnet
过程
扩展名
平台 | 扩展名 |
---|---|
UNIX | C、cc、cxx |
GNU C++ | C、cc、cxx、cpp、c++ |
Microsoft Visual C++ | cpp、cxx、cc |
链接库
参数 | 链接库 |
---|---|
-lxnet | 套接字对象库 |
-lm | 数学库 |
-lpthread | 线程库 |
-lg++ | C++库 |
参考
ANSI标准C
wiki
c99.pdf
ANSI C
现在被几乎所有广泛使用的编译器支持。现在多数C
代码是在ANSI C
基础上写的。- 如果没有这种预防措施,多数程序只能在一种特定的平台或特定的编译器上编译。
C预编译器
wiki
- 用于在编译器处理程序之前预扫描源代码,完成头文件的包含, 宏扩展, 条件编译, 行控制(line control)等操作。
共享目标文件
oracle.docs
- 一种由链接编辑器创建并通过指定
-G
选项生成的输出形式。 - 共享目标文件是一个不可分割的单元,根据一个或多个可重定位目标文件生成。
- 共享目标文件可以与动态可执行文件绑定在一起以形成可运行进程。共享目标文件可供多个应用程序共享。
编译语言标准
csdn
- 借助
-std
选项即可手动控制G++
编译程序时所使用的编译标准。 - 例如
-std=c++11
表示支持C++11
标准,-std=gnu++11
表示支持C++11
标准和GNU
拓展。
目标代码
fsu.edu
- 目标代码是已编译为机器语言(二进制)但尚未与所有库和其他文件链接的源代码。
0x02 基础
特性
main()
- 在
main()
函数中隐含语句结尾:
return 0;
- 运行
C++
程序时,通常从main()
函数开始执行。但在Windows
编程中,可以编写动态链接库DLL
,不需要main()
。
有些编程环境提供一个框架程序,其中隐藏的
main()
调用_tmain()
。
return
的值最终返回给操作系统,表示运行程序的结果。由于返回值的如此效果,可以设计一个外壳脚本或批处理文件来运行该程序。
namespace
- 使用
using
编译指令简化程序:
using namespace std;
若编译器不接受,说明它不遵守
C++98
标准。
- 为了防止多个厂商封装好的函数冲突(同名),使用名称空间进行区分。
头文件
- 新版本
C++
头文件没有拓展名,部分C
头文件去掉.h
并在开头加上c
变为C++
的头文件。
#include<math.h> //c
#include<cmath> //cpp
头文件类型 | 方式 | 说明 |
---|---|---|
C 旧式 | .h 结尾 | C、C++都可用 |
C++旧式 | .h 结尾 | 仅C++可用 |
C++新式 | 无拓展名 | 仅C++可用,使用namespace |
转换后的C | 加前缀c,无拓展名 | 仅C++可用,使用namespace |
插入运算符 <<
- 看上去像左移运算符,在
cout
中是运算符重载,使得其有不同的作用。
符号常量
符号常量 | 表示 |
---|---|
CHAR_BIT | char的位数 |
CHAR_MAX | char的最大值 |
CHAR_MIX | char的最小值 |
SCHAR_MAX | signed char的最大值 |
SCHAR_MIN | signed char的最小值 |
UCHAR_MAX | unsigned char的最大值 |
SHRT_MAX | short的最大值 |
SHRT_MIN | short的最小值 |
USHRT_MAX | unsigned short的最大值 |
INT_MAX | int的最大值 |
INT_MIN | int的最小值 |
UNIT_MAX | unsigned int的最大值 |
LONG_MAX | long的最大值 |
LONG_MIN | long的最小值 |
ULONG_MAX | unsigned long的最大值 |
LLONG_MAX | long long的最大值 |
LLONG_MIN | long long的最小值 |
ULLONG_MAX | unsigned long long的最大值 |
初始化
C++
中可以使用{}
赋值,这种情况下可以不加=
。
int c{5}; //初始化c为5
控制符
- 换行
cout << endl;
和
n
不同在于,endl
除了换行还能清除缓冲槽。
- 进制转换
cout << hex; //输出整型变量的16进制
cout << dec; //输出整型变量的10进制
cout << oct; //输出整型变量的8进制
这些控制符都在
std
名称空间中。
常量类型
- 整型默认存储为
int
,除非使用了特定的后缀或者超过了int
的范围。
后缀 | 类型 |
---|---|
123u | unsigned 123 |
123ul | unsigned long 123 |
123ull | unsigned long long 123 |
宽字符类型
wchar_t
类型是一种整数类型,占两个字节。char16_t
和char32_t
为C++新增的类型。
浮点数
d.dddE+n == d.dddEn //小数点向右移动n位
d.dddE~n //小数点向左移动n位
类型 | 位数 |
---|---|
float | 32 |
double | 64 |
long double | 80/96/128 |
auto声明
- 声明的变量类型以初始值类型相同。
auto n = 1; // n is int
auto n = 1.0; // n is double
auto n = 1.0e12L // n is long double
string类
- 可以直接赋值和拼接。
string s1 = {"123"};
string s2 = s1;
string s3 = s1 + s2;
位字段
- C++允许指定占用特定位数的结构成员,这使得创建与某个硬件设备上的寄存器对应的数据结构更方便。
struct bits
{
unsigned int a : 4; //最大为2^4-1
unsigned int : 4; //不可用
bool ifIn : 1;
bool ifTorgle : 1;
};
bits b = {14, true, true};
new和delete
new
为数据对象分配内存,delete
释放动态分配的内存。
int * a = new int (4);
int * b = new int [10];
delete a;
delete [] b;
- 不要使用
delete
释放不是new
分配的内存。- 不要使用
delete
释放同一个内存块两次。- 如果使用
new []
为数组分配内存,则应使用delete []
来释放。- 如果使用
new []
为一个实体分配内存,则应使用delete
(无[]
)来释放。- 对空指针应用
delete
是安全的。new <typename> ()
可设置初始值。
自动、静态和动态存储
-
自动存储
- 变量在所属函数被调用时自动产生,在该函数结束时消亡。
- 自动变量存储在栈中,释放时按照相反顺序释放。
-
静态存储
- 整个程序执行期间都存在。
- 通过在函数外定义变量或者使用
static
关键字声明变量,可得到静态变量。
-
动态存储
new
和delete
管理一个内存池,称作堆。- 内存池同静态变量和自动变量的内存是分开的,所以数据的生命周期不完全受程序或函数的生存时间控制。
- 可能导致占用的自由存储区不连续。
动态分配的内存空间没有
delete
的话,即使由于作用域规则和对象生命周期的原因而导致包含指针的内存被释放,在堆上动态分配的变量或结构也将继续存在。导致这些内存被分配出去无法回收。
容器
vector
在std
名称空间中,内部使用new
和delete
来管理内存。
#include <vector>
using namespace std::vector;
using namespace std::list;
vector<int> a; //int向量
vector<double> b(3); //大小为3的double向量
b.at(1)=2.0; //设置下标1的变量为2.0
at()
函数在运行期间捕获非法索引,而程序默认将中断,降低意外超界错误的概率。
- 指定的类型为没有默认构造函数的类型:
class A
{
public:
A(string& s) :a(s) {}
private:
string a;
};
...
string Ainit("init");
vector<A> objA(10, Ainit); //生成的vector大小为10,每个A元素使用"init"初始化
- 迭代器:
iterator
//打印矢量v的每一个值
for(auto it = v.begin(); it != v.end(); it++){
cout << *it << endl; //通过解引用访问值
}
begin()
和end()
有多个版本
- 前缀带
r
表示反向迭代器(和原版相反);- 前缀带
c
表示常量迭代器(不可修改);end()
是最后一个元素后的一个位置,如下公式所示
[ b e g i n , e n d ) [begin,end) [begin,end)
- 容器初始化为其他容器的拷贝:
list<string> l1{"111", "aaa"};
list<string> l2(l1.begin(), l1.end());
- 顺序容器(表中为
c
)插入操作
操作 | 作用 |
---|---|
c.push_back(t)/c.emplace_back(args) | 在c的尾部创建一个值为t或由args创建的元素 |
c.push_front(t)/c.push_front(args) | 在c的头部创建一个值为t或由args创建的元素 |
c.insert(p, t)/c.emplace(p, args) | 在迭代器p指向的元素之前插入一个值为t或由args创建的元素,返回指向新添加元素的迭代器 |
c.insert(p, n, t) | 在迭代器p指向的元素之前插入n个值为t的元素,返回指向新添加元素的迭代器;若n为0,返回p |
c.insert(p, b, e) | 在迭代器p指向的元素之前插入迭代器b和e指定范围的元素,返回指向新添加元素的迭代器;若n为0,返回p |
c.insert(p, il) | 再迭代器p指向的元素之前插入元素值列表il的内容,返回指向新添加的第一个元素的迭代器;若列表为空,则返回p |
emplace
是构造元素而不是拷贝元素;
forward_list
有自己的insert
和emplace
,不支持push_back
和emplace_back
;
vector
和string
不支持push_front
和emplace_front
;向
vector
/string
/deque
插入元素会导致所有指向容器的迭代器、指针和引用失效。
- 顺序容器访问元素:
操作 | 作用 |
---|---|
c.back() | 返回c中尾元素的引用 |
c.front() | 返回c中首元素的引用 |
c[n]/c.at(n) | 返回c中下标为n的元素的引用 |
必须定义为引用类型才能改变元素的值
- 删除元素操作
操作 | 作用 |
---|---|
c.pop_back() | 删除c中尾元素 |
c.pop_front() | 删除c中首元素 |
c.erase§ | 删除迭代器p指向的元素,返回一个指向被删元素之后元素的迭代器 |
c.erase(b, e) | 删除迭代器b和e所指定范围内的元素 |
c.clear() | 删除c中所有元素 |
- 删除
deque
除首尾的之外元素会使得迭代器、引用和指针都失效;- 指向
vector
和string
中删除点之后位置的迭代器、引用和指针都失效。
forward_list
操作
操作 | 作用 |
---|---|
c.resize(n) | 调整c大小为n |
c.resize(n, t) | 调整c大小为n,并初始化为t |
修改大小之后迭代器、引用和指针都失效。
- 改变容器大小操作
操作 | 作用 |
---|---|
lst.before_begin() | 返回指向链表首元素之前不存在的元素的迭代器(不能解引用) |
lst.insert_after(…) | 类似vector的insert()系列 |
lst.emplace_after(…) | 类似vector的emplace()系列 |
lst.erase_after(…) | 类似vector的erase()系列 |
array
的长度固定,也在std
名称空间中,使用栈(静态内存分配)。
#include <array>
using namespace std::array;
array<int, 5> a;
array<double, 3> b = {1.0, 2.0, 3.0};
内置数组无法赋值,
array
可以赋值
字符函数库
#include <cctype>
- 判断字符的类型
函数 | 意义 |
---|---|
isalnum | 字母或者数字 |
isalpha | 字母 |
iscntrl | 控制字符 |
isdigit | 数字 |
isgraph | 除空格外的打印字符 |
islower | 小写字母 |
isprint | 包括空格的打印字符 |
ispunct | 标点符号 |
isspace | 空白字符 |
isupper | 大写字母 |
isxdigit | 十六进制数字(0~9 +a~f +A~F ) |
tolower | 返回字符的小写 |
toupper | 返回字符的大写 |
cin
int n;
cin >> n;
>>
执行后会返回一个cin
,如果cin
在测试条件中则将被转化为bool
类型。- 此处如果输入为
int
则输入成功,否则为失败。
- 此处如果输入为
- 程序发现用户输入了错误内容时,应采取
3
个步骤:- 重置
cin
以接收新的输入。 - 删除错误输入。
- 提示用户再输入。
- 重置
//示例代码
int n;
while (!(cin >> n)) { //输入错误时进入循环
cin.clear(); //重置cin
while(cin.get() != 'n'){ //删除错误的回车
continue; //重新进行读取
}
cout << "Plz input int :";
}
逻辑判断
&&
连接的判断语句,从左向右依次判断,如果左边判断失败,则不会判断右边的式子。
文件操作
#include <fstream>
输出到文件
fstream
头文件中包含ofstream
类,需要使用该类来关联文件。
ofstream fout;
cout
是iostream
中ostream
类的对象,在其中已经定义好了。而此处需要手动声明对象。所有用于
cout
的操作都可以作用于fout
(此处定义的名称)。
fout.open("test.txt");
fout << "test1" << endl;
fout << "test2" << endl;
- 用完一定要
close()
。
fout.close();
从文件中读取
fstream
头文件中包含ifstream
类,需要使用该类来关联文件。
ifstream fin;
同理
cin
。
char words[10];
fin.open("test.txt");
fin.getline(words, 10);
- 判断文件是否成功打开。
if(!fin.is_open()){
exit(EXIT_FAILURE);
}
exit()
的参数用于和操作系统通信。
常量 | 意义 |
---|---|
EXIT_SUCCESS | 运行程序成功 |
EXIT_FAILURE | 运行程序失败 |
- 判断文件是否读到末尾。
if(fin.eof()){...}
可变参数列表
void function(...);
指针减法
- 指针减指针是一个整数值,表示两个指针之间间隔多少个元素。
const指针
可否赋值 | 非const指针 | const指针 |
---|---|---|
const数据(非指针) | × | √ |
非const数据(非指针) | √ | √ |
const
指针指向非const
数据时,表示不能修改指针指向的值,但是可以修改指针的值。
二维数组
- 数组作为传入参数时,需要给出数组长度,而二维数组有两个参数,于是有如下传入:
int a[2][4] = {{1,2,3,4},{5,6,7,8}};
int sum(int (*a)[4], int size);
//or
int sum(int a[][4], int size);
表示传入的是由4个指针组成的数组的指针。
函数指针
- 函数声明。
void f(int);
//转化为
void (*pf)(int);
- 函数用法。
//直接使用
(*pf)(1);
//作参数传入
void d(void (*pf)(int), ...);
智能指针
- 可以指向各种类型的指针,是一种模板。
#include <memory>
...
shared_ptr<string> p1; //指向string的指针
shared_ptr<vector<int>> p2; //指向vector,int>的指针
- 分配和使用内存的函数
make_shared
:
p1 = make_shared<string>("abc"); //p1指向的内存中内容为 a
内联函数
- 内联函数的编译代码已经嵌入在需要调用的位置,程序无需跳转执行。
引用变量
int rats;
int & rodents = rats;
&
是类型标识符的一部分,这两个变量可以互换(指向相同的内存单元)。
引用变量主要用作函数的形参,这样函数就可以直接使用原始数据。
- 必须声明引用时初始化,不能先声明再赋值。
int rat;
int & rodent;
rodent = rat; //报错
- 如果实参和引用参数不匹配,并且参数为
const
引用时,C++会生成临时变量。(现已禁止)- 实参的类型正确,但不是左值;
- 实参的类型不正确,但可以转换为正确的类型。
左值参数是可被引用的数据对象,如变量、数组元素等。
非左值就是一些字面常量(除括号引起来的字符串)。
- 为何使用引用参数?
- 可以修改调用函数中的数据对象。
- 传递引用而不是整个数据对象,可以提高速度。
- 引用参数/按置传递/使用指针?
数据对象(不改变) | 传值方法 |
---|---|
小 | 值 |
数组 | const指针 |
较大结构 | const指针/const引用 |
类对象 | const引用 |
数据对象(可修改) | 传值方法 |
---|---|
内置数据类型 | 指针 |
数组 | 只能使用指针 |
结构 | 引用/指针 |
类对象 | 引用 |
- 右值引用的标志为
&&
,只能指向右值,不能指向左值。
int &&right_reference = 4;
void func(int &&right_reference); //该函数只能传入右值
- 通过std::move可以将左值转换为右值。
int a = 10;
int& left_ref = a;
int&& right_ref = std::move(a);
left_ref = 20; //三者访问同一块内存,所以a和right_ref都变成20
模板
- 创建一个通用的函数以支持多种不同类型的形参,避免函数体的重复设计。
template <typename T>
void Swap(T &a, T &b);
模板的头文件通常既包括声明也包括定义。
- 实例化和具体化
//隐式实例化
template <typename T> void Swap(T &a, T &b){
T t;
t = a;
a = b;
b = t;
}
//显式实例化
template void Swap<int>(int &, int &);
//显式具体化
template <> void Swap<int>(int &, int &);
template <> void Swap(int &, int &);
- 概念区分
...
template <typename T>
void Swap(T &a, T &b); //模板原型
template <> void Swap<int>(job &, job &); //显式具体化
int main(){
template void Swap<char>(char &,char &); //显示实例化
short a,b;
...
Swap(a,b); //调用隐式实例化
job n, m;
...
Swap(n, m); //调用显示具体化
char g, h;
...
Swap(g, h); //调用显示实例化
...
}
会导致代码膨胀,不过是正常的。
- 非类型模板参数表示一个值而不是一个类型,用作常量表达式。
template<unsigned N, unsigned M>
int compaer(const char (&p1)[N], const char (&p2)[M])
{
return strcmp(p1, p2);
}
非类型模板参数被一个用户提供的或编译器推断出的值所替代。
存储持续性
类型 | 意义 | 生命周期 |
---|---|---|
自动存储持续性 | 在函数定义中声明的变量 | 函数开始——执行完毕 |
静态存储持续性 | 在函数外定义的变量或static 定义的 | 整个程序运行期间 |
线程存储持续性(不深究) | 使用thread_local 声明的变量 | 同所属的线程 |
动态存储持续性 | new 分配的内存 | delete 释放或程序结束 |
存储说明符
cv-限定符
const
:初始化后不可被修改。volatile
:即使代码没有进行修改,其内容也有可能改变。- 用于改善编译器优化能力。
默认情况下,全局变量的链接性为外部的,但
const
全局变量的链接性是内部的。
mutable
- 即使结构/类变量为
const
,某个成员也可以被修改。
struct data
{
char name[10];
mutable int accesses;
...
};
const data d = {"dddd", 0, ...};
strcpy(d.name, "eeee"); // not allowed
d.accesses = 1; // allowed
异常处理
- 抛出异常
throw runtime_error("Error Message");
- 经典
try-catch
语句捕捉处理异常:
#include <stdexcept> //包含异常类型,如下runtime_error
try{
...
}catch(std::runtime_error err){ //捕捉到runtime_error
...
}
- 打印异常信息
cout << err.what(); //err.what()返回抛出异常时的信息(const char*)
头文件 | 作用 |
---|---|
exception | 通用的异常类(只报告异常,不提供额外信息) |
stdexcept | 常用的异常类 |
new | bad_alloc异常类(内存分配) |
typeinfo | bad_cast异常类(类型转换) |
noexcept
异常说明有两层含义:- 跟在函数参数列表后,为异常说明符;
- 作为
bool
型的实参出现时,它是一个运算符。
void f() noexcept {} //不捕捉异常
void g() {}
...
void h() noexcept(noexcept(f())) { f(); } //noexcept(f())返回true,不捕捉异常
void i() noexcept(noexcept(g())) { g(); } //noexcept(g())返回false,捕捉异常
函数指针声明为
noexcept
时,指向的函数必须一致。
noexcept
派生的虚函数也必须一致。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g3r7fHuG-1635140008096)(image/exception.svg)]
参考
动态链接库DLL
wiki
- 所谓动态链接,就是把一些经常会共享的代码(静态链接的OBJ程序库)制作成
DLL
档,当可执行文件调用到DLL
档内的函数时,Windows
操作系统才会把DLL
档加载存储器内,DLL
档本身的结构就是可执行档,当程序有需求时函数才进行链接。
using编译指令
microsoft
- 许多用户不喜欢将引入名称空间之前的代码(使用
iostream.h
和cout
)转换为名称空间代码(使用iostream
和std::cout
),所以using
编译指令简化了这个过程。
// using声明
using std::cout; //使cout有效
using std::cin; //使cin有效
// using编译指令
using namespace std; //使std中所有变量名有效
重载
runoob
- 重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不相同。
cin.clear()
cplusplus
- 因输入错误等原因会导致
cin
中的错误标志被设置,clear()
可以重设以清除标志。
编译代码
baike
- 即源程序编译后得到的汇编代码。
实例化和具体化
csdn
- 隐式实例化:使用模板之前,编译器不生成模板的声明和定义示例,后面有程序用了,编译器才会根据模板生成一个实例函数。
- 显式实例化:是无论是否有程序用,编译器都会生成一个实例函数。
- 显示具体化:因为对于某些特殊类型,可能不适合模板实现,需要重新定义实现,此时就是使用显示具体化的场景。
链接性
wiki
- 链接性(linkage)是程序编译时,程序中的名字(name,也可称标识符identifier)在作用域中不同位置的出现能够绑定到同一对象或函数。C++语言中,链接性描述了名字在整个程序或单独编译单元中能否绑定到同一实体(entity)。
右值
wiki
- 简单理解为
=
右边的变量,该变量不能通过&
取地址。
std::move
cppreference
- 唯一的功能是把左值强制转化为右值,让右值引用可以指向左值。其实现等同于一个类型转换:
static_cast<T&&>(lvalue);
0x03 面向对象
类
class complex; //声明一下
class complex{
public:
...
private:
...
};
构造函数
complex (double r = 0, double i = 1) //构造函数
: re (r), im (i) //初值列
{ }
- 第二行专门用于赋初值,故不将赋值放在
{}
里(虽然是同一个结果)。
singleton
模式将构造函数放在private
中,表示不能自主创建对象。
析构函数
- 与构造函数对应,负责将申请的内存释放。
StringBad::~StringBad()
{
std::cout << str << " deleted.n"
<< --num_strings << " left.n";
delete [] str;
}
- 类对象在创建时会调用构造函数,在释放时会调用析构函数;
- 函数中的局部变量在函数返回后,会由于弹栈从而内存被释放;
- 如果函数参数是采用值传递的方式传入类对象,则由于值传递的性质,先调用类复制构造函数产生一个临时对象,然后在函数返回后会调用类析构函数将该临时对象内存释放。
重载
- 一个函数有多种写法(参数不同)。
double real () const { return re; }
double real (double r) { re = r; }
第一个函数后
const
表示执行后不会改变数据内容(一定要加)。
- 重载
<<
用于显示对象的内容,使用以下通用格式:
ostream & operator<< (ostream & os, const c_name & obj)
{
os << ... ;
return os;
}
参数传递
- 传值时是将值压入栈,如果值太大,应该传递引用(底部就是指针,一般是
const
即不修改原内容)。 - 变量名后加上
&
即表示引用。
complex& operator += (const complex&);
最好所有的参数传递都传引用。
- 传递者无需知道接收者是以
reference
形式接收。
complex& __doapl(complex* ths, const complex& r)
{
...
return *ths; //返回一个对象,此处只需要表示传回的形式
}
友元
- 友元
friend
可以直接访问类中的数据。 - 相同类的对象互为友元,可以通过函数直接访问对方数据。
返回值
- 尽量返回引用,如果返回函数中创建的对象便不能返回引用,因为函数执行之后相关内存清除,返回的引用无法正确找到指针指向空间的内容。
操作符重载
成员函数
- 所有成员函数默认含有参数
this
(是指针,指向当前对象)。
complex::operator += (this, const complex& r)
{
...
}
//同
complex::operator += (const complex& r)
{
...
}
非成员函数
- 相对于成员函数来说没有
this
,下列代码解决了+
号运算的各种情况。
inline complex
operator + (const complex& x, const complex& y)
{
return complex(real(x) + real(y),
imag(x) + imag(y));
}
inline complex
operator + (const complex& x, double y)
{
return complex(real(x) + y, imag(y));
}
inline complex
operator + (double x, const complex& y)
{
return complex(x + real(y), imag(y));
}
上述代码绝不可以用引用传递,因为它们一定返回一个对象(在函数中创建)。如果传的是指针,在函数结束后,指向的内容消失。
临时对象
typename(...)
的方式创建临时对象。
complex ();
complex (3,4);
临时对象的生命周期到下一行代码就结束。
const成员函数
//声明
void show() const;
//定义
void stock::show() const{...}
- 将
const
加在参数括号后,表示传入的对象不做修改。
对象数组
- 初始化
Stock s[3] = {
Stock("aaa", 1, 1.1),
Stock(),
Stock("ccc", 3, 1.3),
}
作用域为类的常量
- 创建由所有对象共享的常量。
class Bakery
{
private:
enum {Months = 12};
double consts[Months];
...
}
//or
class Bakery
{
private:
static const int Month = 12;
double consts[Months];
...
}
- 对于静态成员变量,不能在类声明中初始化,因为声明只负责描述如何分配内存,但并不分配内存。
class StringBad
{
private:
static int num_strings;
...
};
int StringBad::num_strings = 0;
- 静态成员函数没有
this
指针,只能处理静态数据。
class Account
{
public:
static double m_rate;
static void set_rate(const double & x) { m_rate = x; };
};
double Account::m_rate = 1.0;
...
int main()
{
Account::set_rate(2.0);
Account a;
a.set_rate(3.0); //对于所有类而言,m_rate都改变了
}
- 若要对象为静态对象,即全局只使用唯一的对象,可用如下方法:
class A
{
public:
static A& getInstance() { return a; }
setup() {...}
private:
A();
A(const A & rhs);
static A a;
...
}
类模板
- 若类需要处理的数据类型不唯一或者数据组织的方式与类型无关,此时使用类模板:
template<typename T>
class complex
{
public:
complex (T r = 0, T i = 0)
: re (r), im(i)
{}
T real() const { return re; }
T imag() const { return im; }
void show();
private:
T re, im;
};
template<typename T> //成员函数在类外的实例化姿势
void complex<T>::show()
{
...
}
...
{
complex<double> c1(2.5, 1.5);
complex<int> c2(2, 6);
...
}
实例化之后的类模板,其成员只有在使用的时候才被实例化;
注意:模板类方法的声明和实现必须放在同一个头文件中!!!
- 在类模板中指定友元
friend class BlobPtr<
friend bool operator==<T>
(const Blob<T>&, const Blob<T>&);
- 模板类型别名
template<typename T> using twin = pair<T, T>;
template<typename T> using part = pair<T, int>;
...
twin<double> t; //pair<double, double>
part<double> p; //pair<double, int>;
- 按由左至右的顺序与对应的模板参数匹配:
template<typename T1, typename T2, typename T3>
T1 sum(T2, T3);
...
auto v = sum<long long>(i, lng); //显式指定,调用long long sum(int, long);
顺序决定了显式指定时的匹配顺序。
- 显式指定可完成正常类型转换
long lng;
compare<int>(lng, 1024); //lng转化为int类型,调用compare(int, int);
compare<long>(lng, 1024); //1024转化为long类型,调用compare(long, long);
- 尾置返回允许在参数列表之后声明返回类型:
template<typename T>
auto fcn(T beg, T end)->decltype(*beg)
{
return *beg;
}
- 可变参数模板
template<typename T, typename...Args>
void foo(const T& t, const Args&...rest); //rest是个函数参数包
...
foo(i); //空包
foo(i, d); //包大小为1
foo(i, d, s); //包大小为2
- 可变参数使用时为递归调用:
template<typename T>
void foo(const T& t)
{
cout << t;
}
template<typename T, typename...Args>
void foo(const T& t, const Args&...rest)
{
cout << t;
foo(rest...); //递归传入rest包内参数
}
- 参数包的
...
就是把包打开,即包扩展。
template<typename T, typename...Args>
void foo(const T& t, const Args&...rest)
{
foo(rest...); //等价于foo(arg1, arg2, ..., argn);
print(t, test(rest)...); //等价于print(t, test(arg1), test(arg2), ..., test(argn));
}
类的自动转换
- 若构造函数所需参数和提供参数值匹配,可以完成自动转换。
Stonewt myStone;
myStone = 19.6; //若Stonewt(double)是explicit声明就不合法
myStone = Stonewt(19.6);
myStone = (Stonewt)19.6;
- 使用转换函数,可以将对象转化为基础类型。
operator typeName(); //typeName为要转换的数据类型
//例
operator int();
...
Stonewt::operator int() const
{
return int(pounds + 0.5); //四舍五入制
}
- 有了转换函数之后,就可实现以下代码。
Stonewt wells(20, 3);
double star = wells; //隐式转换
转换函数要求:
- 必须是类方法。
- 不能指定返回类型。
- 不能有参数。
复合
has-a
- 使用其他现有类的功能,封装一层容器。
template <class T>
class queue
{
protected:
deque<T> q;
public:
bool empty() const { return q.empty(); } //完全封装
void pop() { q.pop_front(); } //函数更名
...
}
是一种
Adapter
的设计模式,表示对底层容器的适用。
构造和析构
外部容器为
Container
,内部元件为Component
。
- 构造由内而外
Container::Container(...) //外部容器构造函数
: Component() //首先构造内部元件(编译器自动加入)
{...} //然后构造外部容器
- 析构由外而内
Container::~Container(...) //外部容器析构函数
{
... //首先析构外部容器
: Component() //然后析构内部元件(编译器自动加入)
}
委托
pImpl
pImpl
(Pointer to Implementation)即具体实现的指针(一个指针指向为自己实现所有功能的类)。
class String
{
public:
String();
String(const char * s);
String(const String & s);
String &operator=(const String & s);
~String();
...
private:
StringRep * rep;
}
...
class StringRep
{
friend class String;
StringRep(const char *);
~StringRep();
int count;
char * rep;
};
包含指针的方式,使得创建对象时不同步,指针也可以指向不同的实现类。
String
内部如何设计不暴露,仅作对外接口使用,通过StringRep
来修改内部设计方式。这种设计方式使得无论内部如何修改,都不会影响外部的客户端接口。
继承
公有派生
class RatedPlayer : public TableTennisPlayer
{
...
};
基类的公有/私有成员都将成为派生类的一部分,但私有部分只能通过基类的公有和保护方法访问。
- 需要自己的构造函数,可以添加额外的数据成员和成员函数。
RatedPlayer::RatedPlayer(unsigned int r, const string & fn,
const string & ln, bool ht) : TableTennisPlayer(fn, ln, ht) //继承的数据使用基类的构造函数
{
rating = r; //额外的数据手动初始化
}
构造函数要求如下:
- 创建基类对象;
- 使用成员初始化列表将基类信息传递给基类构造函数;
- 初始化派生类新增的数据成员。
私有继承
- 使用私有继承,基类的公有方法将成为派生类的私有方法,同时继承基类的接口。
class Student : private std::string, private std::valarray<double>
{
public:
...
};
与包含区别在于初始化时,私有继承在成员初始化列表使用基类构造函数初始化。
通常使用包含来建立
has-a
关系;如果需要访问原有类的保护成员或重新定义虚函数,则应该使用私有继承。
保护继承
- 使用保护继承,基类的公有成员和保护成员都将成为派生类的保护成员。
class Student : protected std::string, protected std::valarray<double>
{
public:
...
};
私有继承和保护继承的区别:
- 私有继承的第三代类无法使用基类的接口,是因为基类的公有方法在派生类中变成私有方法。
- 使用保护继承时,基类的公有方法在第二代中将变成受保护的,第三代类可以使用。
protected
- 在类外,只能用公有类成员来访问
protected
部分的类成员(这点同private
)。 - 派生类的成员可以直接访问基类的保护成员,但不能直接访问基类的私有成员。
- 在类外,只能用公有类成员来访问
保护访问控制很有用,可以使得派生类能够访问公众不能使用的内部函数。
private
、protected
、public
的访问范围:
访问属性 | 该类函数 | 子类函数 | 友元函数 | 该类对象 |
---|---|---|---|---|
public | 可 | 可 | 可 | 可 |
protected | 可 | 可 | 可 | 否 |
private | 可 | 否 | 可 | 否 |
- 继承之后访问属性的变化:
public
:不发生变化;protected
:protected
/public
变为protected
;private
:全变为private
。
继承规则
- 派生类的成员将隐藏同名的基类成员,使用作用域运算符来区分。
struct Derived : Base
{
int get_base_mem() { return Base::mem; }
};
- 构造函数+析构函数+赋值运算符是不能继承的。
函数 | 能否继承 | 成员还是友元 | 能否默认生成 | 能否作虚函数 | 可否有返回类型 |
---|---|---|---|---|---|
构造函数 | 否 | 成员 | 能 | 否 | 否 |
析构函数 | 否 | 成员 | 能 | 能 | 否 |
= | 否 | 成员 | 能 | 能 | 能 |
op= | 能 | 任意 | 否 | 能 | 能 |
& | 能 | 任意 | 能 | 能 | 能 |
转换函数 | 能 | 成员 | 否 | 能 | 否 |
new | 能 | 静态成员 | 否 | 否 | void* |
delete | 能 | 静态成员 | 否 | 否 | void |
友元 | 否 | 友元 | 否 | 否 | 能 |
op=
表示+=
、*=
之类的赋值运算符。
虚函数
函数的继承是继承的调用权
- 希望派生类复写基类的一个方法,并且它已有默认定义,则为虚函数;若没有默认定义,则为纯虚函数。
class Shape
{
public:
virtual void draw() const = 0; //纯虚函数
virtual void error(const std::string& msg); //虚函数
int objectID() const; //非虚函数
...
};
- 如果没有使用
virtual
,程序将根据引用类型或指针类型而不是对象来调用;使用了virtual
,程序将根据引用或指针指向的对象的类型来选择方法。
Base b;
Son s;
Base* p = &s;
Base& r = s;
b.non_vir(); //Base::non_vir()
b.vir(); //Base::vir()
s.non_vir(); //Son::non_vir()
s.vir(); //Son::vir()
p->non_vir(); //Base::non_vir()
p->vir(); //Son::vir()
r.non_vir(); //Base::non_vir()
r.vir(); //Son::vir()
若派生类方法中调用了基类的方法,标准技术是使用作用域解析运算来调用基类方法。
-
构造函数不能是虚函数:创建派生类会在调用派生类的构造函数时调用基类的构造函数;
-
析构函数必须是虚函数:可以确保正确的析构函数序列被调用;
-
友元不能是虚函数:只有类成员才能是虚函数,友元不是类成员。
-
编译器处理虚函数的方式:
- 每个拥有虚函数的对象都会包含一个指向虚函数表的指针,虚函数表是存放虚函数地址的数组;
- 无论对象拥有多少个虚函数,都只有一个指向地址表的指针;
- 每当通过指针或引用调用对象的虚函数时,就会得到虚函数表的地址,再通过虚函数表获得虚函数地址。
虚函数的缺点:
- 增大了对象;
- 每个类都会创建一个虚函数地址表;
- 每个函数调用都需要先到表中查找地址。
- 返回类型协变:若重新定义继承的方法,要保持原型不变,但如果返回类型是基类引用或指针,则可以修改成指向派生类的引用或指针。
class A
{
public:
virtual A & build(int n);
...
};
class B : public A
{
public:
virtual B & build(int n);
...
};
若基类声明被重载了,则应在派生类中重新定义所有的基类版本。
指针转化
- C++不允许将一种类型的地址赋给另一种类型的指针,也不允许一种类型的引用指向另一种类型:
double x = 1.0;
int * pi = &x; //不允许
long & r1 = x; //不允许
- 指向基类的引用或指针可以引用派生类对象(向上强制转换),不必进行显式类型转换。
抽象基类
- 当类声明中包含纯虚函数时,则不能创建该类的对象。该类只能用作基类。
class AcctABC
{
public:
virtual double Area() const = 0;
...
}
抽象基类被看作一种必须实施的接口,这样使得组件设计人员能够制定接口约定。
引用转换
- 隐式引用转换
- 所有引用类型可以被隐式转换为
object
类型; - 任何类型可以隐式转换到它继承的接口;
- 类可以隐式转换到它继承的链中的任何类和实现的任何接口。
- 所有引用类型可以被隐式转换为
- 显式引用转换:从一个普通类型到一个更精确类型的转换
- 从
object
到任何引用类型的转换; - 从基类到从它继承的类的转换。
- 从
多重继承
- C++支持对多个类的继承,并且对继承方式没有限制。
class D : public A, private B, protected C
{
...
}
- 析构函数和构造函数执行的顺序相反:
D::D(int a, int b, int c, int d) : A(a), B(b), C(c)
{
...
}
...
D::~D()
{
~C(); //隐含
~B(); //隐含
~A(); //隐含
...
}
- 如果继承的基类中出现重复函数名,需要指定基类:
void D::display() const
{
A::show();
B::show();
}
- 使用虚基类让成员只保留一个副本,在派生类中可以直接使用:
class A
{
public:
int value;
};
class B : virtual public A
{};
class C : virtual public A
{};
class D : public B, public C
{};
...
int main()
{
D d;
cout << d.value; //此时只有一个副本,可以直接调用
}
RTTI
RTTI
(Runtime Type Information
)运行时类型信息,提供在运行时确定对象类型的方法。
运算符 | 作用 |
---|---|
typeid | 返回表达式的类型 |
dynamic_cast | 将基类指针或引用安全的转换为派生类的指针或引用 |
typeid
Derived* dp = new Derived;
Base* bp = dp;
if(typeid(*bp) == typeid(*dp)) //比较bp和dp指向的是否为同一种类型
if(typeid(*bp) == typeid(Derived)) //比较bp指向的和Derived是否为同一种类型
当类中不存在虚函数时,
typeid
是编译时期的事情,也就是静态类型;
当类中存在虚函数时,typeid
是运行时期的事情,也就是动态类型。
dynamic_cast
/*指针转换*/
Base* bp = new Derived;
Derived* dp = dynamic_cast<Derived*>(bp); //转换成Derived指针可以访问Derived对象独有部分
/*引用转换*/
Derived d;
const Base& bf = d;
const Derived& d = dynamic_cast<const Derived&>(b); //转换成Derived引用
参考
虚基类
baike
- 当在多条继承路径上有一个公共的基类,在这些路径中的某几条汇合处,这个公共的基类就会产生多个实例(或多个副本),若只想保存这个基类的一个实例,可以将这个公共基类说明为虚基类。
0x04 设计模式
工厂模式
- 创建对象时,不对客户端暴露创建逻辑,而是通过使用一个共同的接口来指向新创建的对象;
- 是一种创建模式
- 复杂对象常用;
- 简单对象直接
new
一个。
简单工厂模式
-
特点
- 在工厂类中判断是否创建新产品;
- 增加新产品则在工厂类中补充,一个产品型号对应一个产品。
-
缺点
- 工厂类集中了所有产品类的创建逻辑,产品越多,工厂类越庞大。
-
示例
typedef enum
{
Tank_Type_56,
Tank_Type_96,
Tank_Type_Num
}Tank_Type;
class Tank
{
public:
virtual const string& type() = 0;
};
class Tank56 : public Tank
{
public:
Tank56() :Tank(), m_strType("Tank56") {}
const string& type()
{
return m_strType;
}
private:
string m_strType;
};
class Tank96 : public Tank
{
public:
Tank96() :Tank(), m_strType("Tank96") {}
const string& type()
{
return m_strType;
}
private:
string m_strType;
};
class TankFactory
{
public:
Tank* createTank(Tank_Type type)
{
switch (type) {
case Tank_Type_56:
return new Tank56();
case Tank_Type_96:
return new Tank96();
default:
return nullptr;
}
}
};
- 用法
TankFactory* factory = new TankFactory();
Tank* tank56 = factory->createTank(Tank_Type_56);
cout << tank56->type(); //Tank56
工厂方法模式
-
特点
- 定义一个创建对象的接口,其子类实现接口完成具体的创建工作;
- 新增产品类时,只需要扩展一个相应的工厂类。
-
缺点
- 产品类数据较多时,需要大量工厂类。
-
示例
class Tank
{
public:
virtual const string& type() const = 0;
};
class Tank56 :public Tank
{
public:
Tank56() : Tank(), m_strType("Tank56"){}
const string& type() const
{
return m_strType;
}
private:
string m_strType;
};
class Tank96 :public Tank
{
public:
Tank96() : Tank(), m_strType("Tank96") {}
const string& type() const
{
return m_strType;
}
private:
string m_strType;
};
class TankFactory
{
public:
virtual Tank* createTank() = 0;
};
class Tank56Factory :public TankFactory
{
public:
Tank* createTank()
{
return new Tank56();
}
};
class Tank96Factory :public TankFactory
{
public:
Tank* createTank()
{
return new Tank96();
}
};
- 用法
TankFactory* factory56 = new Tank56Factory();
Tank* tank56 = factory56->createTank();
cout << tank56->type(); //Tank56
抽象工厂模式
-
特点
- 提供创建一系列相关或者相互依赖对象的接口,无需指定具体的类;
- 当有多个产品系列,而客户端只使用一个系列的产品时使用抽象工厂模式。
-
缺点
- 当增加一个新系列产品时,不仅需要实现具体的产品类,还需要增加一个新的创建接口。
-
示例
class Coat
{
public:
virtual const string& color() const = 0;
};
class WhiteCoat :public Coat
{
public:
WhiteCoat() :Coat(), m_strColor("White Coat") {}
const string& color() const
{
return m_strColor;
}
private:
string m_strColor;
};
class BlackCoat :public Coat
{
public:
BlackCoat() :Coat(), m_strColor("Black Coat") {}
const string& color() const
{
return m_strColor;
}
private:
string m_strColor;
};
class Pants
{
public:
virtual const string& color() const = 0;
};
class WhitePants :public Pants
{
public:
WhitePants() :Pants(), m_strColor("White Pants") {}
const string& color() const
{
return m_strColor;
}
private:
string m_strColor;
};
class BlackPants :public Pants
{
public:
BlackPants() :Pants(), m_strColor("Black Pants") {}
const string& color() const
{
return m_strColor;
}
private:
string m_strColor;
};
class Factory
{
public:
virtual Coat* createCoat() const = 0;
virtual Pants* createPants() const = 0;
};
class WhiteFactory :public Factory
{
public:
Coat* createCoat() const
{
return new WhiteCoat();
}
Pants* createPants() const
{
return new WhitePants();
}
};
class BlackFactory :public Factory
{
public:
Coat* createCoat() const
{
return new BlackCoat();
}
Pants* createPants() const
{
return new BlackPants();
}
};
- 用法
Factory* whiteFactory = new WhiteFactory();
Coat* whiteCoat = whiteFactory->createCoat();
cout << whiteCoat->color();
单例模式
- 保证一个类仅可以有一个实例化对象,并且提供一个可以访问它的全局接口。
- 注意:
- 必须由自己提供一个实例化对象;
- 必须提供一个可以访问唯一对象的接口。
懒汉单例模式
- 特点
- 第一次使用类实例时才实例化一个对象;
- 访问量很小时,使用该模式。
时间换空间
- 非线程安全的懒汉单例模式
//Singleton.h
class lazeSingleton
{
public:
static lazeSingleton* getInstance()
{
if (m_pSingleton == NULL)
{
m_pSingleton = new lazeSingleton;
}
return m_pSingleton;
}
const void show() const
{
cout << "lazy Single" << endl;
}
~lazeSingleton() {}
private:
lazeSingleton() {}
lazeSingleton(const lazeSingleton&) = delete;
lazeSingleton& operator=(const lazeSingleton&) = delete;
static lazeSingleton* m_pSingleton;
};
//Singleton.cpp
lazeSingleton* lazeSingleton::m_pSingleton = NULL; //注意:这里不能放在Singleton.h头文件中
- 线程安全的懒汉单例模式
//其余部分无太大差异
std::mutex mt;
lazySingletonSafe* lazySingletonSafe::getInstance()
{
if (m_pSingleton == NULL)
{
mt.lock(); //申请锁
if(m_pSingleton == NULL)
m_pSingleton = new lazySingletonSafe;
mt.unlock(); //s
}
return m_pSingleton;
}
- 返回
local static
对象引用
//Singleton.h
class lazySingletonReference
{
public:
static lazySingletonReference& getInstance();
const void show() const
{
cout << "lazy Single Reference" << endl;
}
~lazySingletonReference() {}
private:
lazySingletonReference() {}
lazySingletonReference(const lazySingletonReference&) = delete;
lazySingletonReference& operator=(const lazySingletonReference&) = delete;
};
//Singleton.cpp
lazySingletonReference& lazySingletonReference::getInstance()
{
static lazySingletonReference singleton;
return singleton;
}
非常量静态对象在多线程环境下存在很大不确定性;
使用返回引用的方式替代,最后也不用
delete
。
饿汉单例模式
- 特点
- 定义时就实例化;
- 访问量大或者访问线程比较多时,采用饿汉单例模式。
空间换时间
- 示例
hungrySingleton* hungrySingleton::m_pSingleton = new hungrySingleton();
hungrySingleton* hungrySingleton::getInstance()
{
return m_pSingleton;
}
策略模式
- 定义一系列算法,将其单独封装并且可以相互替换,使得算法独立于客户端而变化;
- 算法完成的功能类型和对外接口是一致的,不同的策略为角色表现出不同行为;
- 是行为型模式,用于让一个对象在许多行为中选择一种行为。
模式结构类似于工厂模式,不过两者关注点不同。
- 缺点
- 需要定义大量提供给客户端的策略类。
传统策略模式
- 示例
class Hurt
{
public:
virtual void blood() const = 0;
};
class AdHurt : public Hurt
{
public:
void blood() const
{
cout << "Ad hurt, bleeding..." << endl;
}
};
class ApHurt : public Hurt
{
public:
void blood() const
{
cout << "Ap hurt, bleeding..." << endl;
}
};
//传入策略类指针参数
class Soldier
{
public:
Soldier(Hurt* hurt) :m_pHurt(hurt) {}
void attack() { m_pHurt->blood(); }
private:
Hurt* m_pHurt;
};
//定义策略标签
typedef enum
{
Hurt_Type_Adc,
Hurt_Type_Apc,
Hurt_Type_Num
}HurtType;
//传入策略类标签
class Mage
{
public:
Mage(HurtType type)
{
switch (type) {
case Hurt_Type_Adc:
m_pHurt = new AdHurt();
break;
case Hurt_Type_Apc:
m_pHurt = new ApHurt();
break;
default:
m_pHurt = nullptr;
}
}
~Mage() { delete m_pHurt; }
void attack() { m_pHurt->blood(); }
private:
Hurt* m_pHurt;
};
//模板传递策略
template<typename T>
class Archer
{
public:
void attack() { m_hurt.blood(); }
private:
T m_hurt;
};
上述代码定义了三种调用策略类的方法
- 用法
Soldier soldier(new AdHurt());
soldier.attack();
Mage mage(Hurt_Type_Apc);
mage.attack();
Archer<AdHurt> archer;
archer.attack();
函数指针实现
- 示例
//传统函数指针
class Soldier
{
public:
typedef void (*Function)();
Soldier(Function func) :m_func(func) {}
void attack() { m_func(); }
private:
Function m_func;
};
//使用std::function<>
class Mage
{
public:
typedef std::function<void()> Function;
Mage(Function func) :m_func(func) {}
void attack() { m_func(); }
private:
Function m_func;
};
- 用法
Soldier soldier(adHurt);
soldier.attack();
Mage mage(apHurt);
mage.attack();
适配器模式
- 将一个类的接口转换成客户端希望的另一个接口,使得原本接口不兼容的类可以在一起工作;
- 适配器类需要继承/依赖已有的类,实现目的接口。
- 缺点
- 适配器过多,不易整体调控,容易套娃。
复合适配器
- 示例
class Deque
{
public:
void push_back(int x)
{
cout << "Deque push_back:" << x << endl;
}
void push_front(int x)
{
cout << "Deque push_front:" << x << endl;
}
void pop_back()
{
cout << "Deque pop_back" << endl;
}
void pop_front()
{
cout << "Deque pop_front" << endl;
}
};
class Sequence
{
public:
virtual void push(int x) = 0;
virtual void pop() = 0;
};
class Stack :public Sequence
{
public:
void push(int x)
{
m_deque.push_front(x);
}
void pop()
{
m_deque.pop_front();
}
private:
Deque m_deque;
};
class Queue :public Sequence
{
public:
void push(int x)
{
m_deque.push_back(x);
}
void pop()
{
m_deque.pop_front();
}
private:
Deque m_deque;
};
Deque
提供的功能被适配器Stack
和Queue
封装起来
继承适配器
- 示例
//q
class Stack :public Sequence, private Deque
{
public:
void push(int x)
{
push_front(x);
}
void pop()
{
pop_front();
}
};
class Queue :public Sequence, private Deque
{
public:
void push(int x)
{
push_back(x);
}
void pop()
{
pop_front();
}
};
最后
以上就是魔幻大船为你收集整理的c++学习笔记详解0x00 简介0x01 编译0x02 基础0x03 面向对象0x04 设计模式的全部内容,希望文章能够帮你解决c++学习笔记详解0x00 简介0x01 编译0x02 基础0x03 面向对象0x04 设计模式所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复