第11章 选择适当的 操作
11.1 逻辑 / 位 / 递增减
1 逻辑
&& || !
(1) 操作数
指针/算术 类型
(2) 短路特性: 逻辑上 需要 时, && 和 || 才会对 第2实参求值
while (p && !whitespace(*p) ) ++p;
2 位
& | ~(非) ^(异或) >> <<
含义 & 应用(位向量)
unsigned 整型
每1位 表示 集合的1个成员
(1) & 求 交
从 32 位 int 中 提前中间 16位
constexpr unsigned short middle(int n)
{
static_assert( sizeof(int) == 4, "unexpected int size" );
static_assert( sizeof(short) == 2, "unexpected short size" );
return (n >> 8) & 0xFFFF;
}
int x = 0xFF00FF00; // 假定 sizeof(int) = 4
short y = middle(x); // y = 0x00FF
(2) | 求 并
在 现有状态 上 添加内容
enum ios_base::iostate
{
goodbit = 0, eofbit = 1, failbit = 2, badbit = 4
};
state |= eofbit;
(3) ~ 求 补
(4) ^ 异或: 相异( 有变化 ) 为 1
判 `状态 是/否 变化`
现值 与 旧值 异或 => result 作 条件 转换为 `bool 值 true/false`
if ( 结果 可转换为 true <=> 至少有 1 位 有变化 => 有变化 )
int old = cin.rdstate(); // rdstate() 返回 状态
// ...
if( cin.rdstate() ^ old ) // 有变化吗 ?
3 递 增/减
(1) char* strcpy(char*, const char*); // <string.h>
循环 拷贝 以 0 结尾的 C 风格字符串
void cpy(char* p, const char* q);
6 个版本
1) `读 2 次 字符串`
先求 strlen(): 第 1 次 读
int length = strlen(q);
for(int i = 0; i <= length; i++)
p[i] = q[i];
|
| 性能提升
|/
2) `读 1 次 字符串`
没遇到 结束符 0 时, copy
+
遇到时, 跳出 + dst 置 结束符 0
for(int i = 0; q[i] != 0; i++)
p[i] = q[i];
p[i] = 0;
3) 用 `指针操作 替换 数组下标操作`
while (*q != 0)
{
*p = *q;
p++;
q++;
}
*p = 0;
4) `后置递增: 允许 先使用值 再 递增`
while(*q != 0)
**p++ = *q++;
*p = 0;
5) `赋值表达式 的 值` 是 右操作数的值/赋值后左操作数 的值
*p++ = *q++ 的值是 *q
=> while(*p++ = *q++) 终止条件是 *q == 0
1> copy: *p = *q -> 结果 *q 转换为 bool 值 -> 编译器 先存着
2> 递增
3> 判 递增前 *q (若 0) 转换的 bool 值 -> 若 false -> exit => 结束符 已 copy 给 dst
while(*p++ = *q++)
{}
6) 循环体空 => 略去
while(*p++ = *q++);
11.2 自由存储
(1) 引入
命名对象 lifetime 由其 scope 决定
|
| 希望 `分离 对象 与` 对象创建语句所在 `scope`
|/
new 运算符
分配的对象 `位于 自由存储 之上`
(2) delete 运算符
1) delete / delete[] nullptr; 什么也不做
2) 被 delete 的对象类型 含 dtor
delete 将
[1] 调用 该 dtor
释放 该对象 所管理的资源 (若有)
[2] 释放 该对象所占内存空间
以供后续使用
1 内存管理
(1) 自由存储 的 问题
1) 对象泄露
new 但忘了 delete ---> 可能资源耗尽
2) 过早 delete
3) 重复 delete
同一对象被释放 2 次
2 次调用 dtor
(2) 解决
1) 优先`scoped 对象`
必要时再 自由存储
2) RAII
[1] 将 自由存储 对象 的 `ptr` 放到 `handle 对象` 中
|
| 尽可能 作
|/
`scope 内 变量`
string / vector / unique_ptr / shared_ptr
[2] 很多用 自由存储 的场合
都可以用 `move 语义` 替代
从 函数返回 表示 大对象的 句柄 即可
RAII
字面含义
句柄 ctor 中 创建资源, dtor 中 释放资源
本质含义
让 句柄 own 资源, dtor 中 释放资源
=>
`资源 与 句柄 的 lifetime 管理 统一起来`
只要不泄露句柄, 就能不泄露资源
至于让 ctor 创建资源, 还是 把资源创建好, 再交给 ctor 保管, 无优劣之分
可用 Factory Method 返回 创建好的资源, client 就不用管 里面怎么实现了
2 除非必须直接使用 char*, 否则一般而言, std::string 更好
(1) new 创建 对象的数组
char* save_string(const char* p)
{
char* s = new char[strlen(p) + 1];
strcpy(s, p);
return s;
}
int main(int argc, char* argv[])
{
// ...
char* p = save_string( argv[1] );
// ...
delete[] p;
}
|
| (2)
|
| 简化 + 不操心 delete / delete[]
|/
string save_string(const char* p)
{
return string{p};
}
int main(int argc, char* argv[])
{
// ...
string s = save_string( argv[1] );
// ...
}
(2) delete / delete[] 必须清楚 分配的对象 有多大
new 分配的对象 比 静态对象 所占空间 稍大(至少存得下 对象 size)
3 重载 new
默认下, new 运算符 在 自由存储 上 创建对象
|
|/
想在 `指定空间` 分配对象
|
| 重载 new: placement new <new>
|/
目标空间地址 作 参数
void* buf = reinterpret_cast<void*>(0xF00F); // 明确的地址
void* operator new(size_t, void* p) { return p; } // placement new <new>
/ |\
_ _ _ /_ _ _ _ _ _ _|
| /
|/ /
X* p = new(buf) X; // 在 buf 处 构建 X,
// 调 operator new( sizeof(X), buf )
// ===
clas B
{
public:
virtual void* alloc(size_t) = 0;
virtual void free(void*) = 0;
// ...
};
void* operator new(size_t sz, B* pB)
{
return pB->alloc(sz);
}
extern B* gpPersistent;
extern B* gpShared;
void g(int n)
{
X* p = new(gpPersistent) X(i); // 在 持续存储 上 分配 X
X* q = new(gpShared) X(i); // 在 共享内存 上 分配 X
// ...
}
// 销毁: 显式调用 dtor
void destory(X* pX, B* pB)
{
pX->~X(); // 显式调用 dtor: 清理资源
pB->free(pX); // 释放内存
}
11.3 列表 {}
{} 列表
|
| 及其 底层数组 `lifetime` 与 initializer_list 对象 相同
|/
编译器 构建 临时数组 作 `底层数组`
|
| 不可修改
|/
构建 `initializer_list 对象` 管理之
小对象: 以 值传递 可行
11.4 lambda 表达式
(1) `回调`
1) 操作 作 实参 传给 算法
2) 再通过 function pointer / function object / lambda
去调 函数调用运算符 operator() (paraList)
function pointer 本身不能 持有 state, 只能由 算法 提供
(2) mutable 修饰符
默认下, operator() 是 const: const 含义
lambda body 内
不修改 通过 `值捕获的变量 的 copy`
note:而不是 不修改 通过引用捕获的变量的状态
(3) vs. 函数
函数 无 捕获功能
=> lambda 可作 局部函数, 普通函数不能
1 实现
函数对象
void f(const vector<int>& v, ostream& os, int n)
{
for_each(v.begin(), v.end(),
[&os, n](int x) { if(x % n == 0) os << x << '\n'; } );
} | |
|
class F | |
{ /|
ostream& os;| |
int n; |
public: / |
F(ostream& s, int nn): os(s), n(nn) {}
/
void operator() (int x) const
{
if(x % n == 0) os << x << '\n'; }
}
};
void f(const vector<int>& v, ostream& os, int n)
{
for_each(v.begin(), v.end(), F{os, n} );
}
2 捕获
(1) lambda 主要用途
`封装` 部分代码 以便将其用 `作 参数`
定义处使用
而不用像 函数/函数对象 那样, 分离定义与使用
(2) 3种捕获形式
1) 隐式捕获
[=] 引用传递
[&] 值传递
2) 显式捕获
[捕获列表]
可捕获 this
3) 混合 隐式 与 显式 捕获
捕获列表中 `没出现的 名字(外层 context 局部变量)`
通过 引用 / 值 传递, 列出的 通过 值/引用
/ |
/ [=, 捕获列表]
/ |
[&, 捕获列表] 必须 以 & 为 前缀
| => 不可出现 this
|/
不能 以 & 为 前缀
=> 可出现 this
(3) 很多时候, 既可 捕获, 也可 传实参
(4) lambda 与 this
1) 捕获 this: [this]
通过 this 访问 `类对象的 成员`
2) [this] 与 [=] 不兼容
=> 多线程 中易发生 `竞争条件`
class A
{
int value;
int result;
public:
void execute()
{
[this]() { result = f(value); }
}
int f(int v);
};
11.5 显式 类型转换 / 强转 casting
1 列表 T {v}
(1) 用 值 v 构造 T 类型的临时对象
(2) 好处
只执行 "行为良好的" 类型转换
存在
[1] v 向 T 的 `非窄化` 类型转换, 或
[2] 正确的 `T 类型 ctor`
T{}
构造函数: 类型 T 默认值
2 4种 命名转换
const_cast
为某些 const 对象 获得 写入权利
仅对 const / volatile
reinterpret_cast
改变 位模式 的 含义
`非关联类型 间` 转换
1> 整数 -> 指针
2> 指针 -> 非关联指针
IO_device* p = reinterpret_cast<IO_device*>(0xFF00); // 0xFF00 处的 设备
static_cast
`关联类型 间` 转换
`原始内存` 指针转换
int* p = static_cast<int*>(my_allocator(100) );
1> 整数 -> 枚举 / 浮点 -> 整数
2> 指针 -> 同一类层次中 其他指针
3> (单参数) explicit ctor
|
|
|/
X::X(T v)
4> `类型转换运算符`
|
| X(类类型) 向 T(任意类型) 的 类型转换
|/
X::operator T()
dynamic_cast
`动态检查` 类层次 关系
执行 `运行时检查` // static_cast 不做 运行时检查
3 C 风格 转换
(T)v
|
|/
可执行 命名转换 中 除 dynamic_cast 之外的 3种转换的 任意组合
4 函数形式 的转换
T(v)
|
| 对 内置类型 T <=>
|/
(T) V
第12章 函数
12.1 函数声明
1 返回 void
并不存在 void 值, 但可 `调用 void 函数` 作 `另一 void 函数 的 返回值`
void g(int*p);
void h(int* p)
{
// ...
return g(p); // <=> g(p); return;
}
2 inline 函数
遵循 ODR(一次定义法则)
定义出现在 多个编译单元 (通常是因为 inline 放 头文件)
定义必须保持一致
3 constexpr 函数
遵循 ODR(一次定义法则)
可视为 更严格的 inline
4 local static 对象
在 `函数多次调用` 间 `维护1份公共信息`,
而无需使用 `global 变量` -> 可能被 其他不相关含访问
12.2 传参
1 引用传递
与 引用初始化 规则相同
`字面值 / 常量 / 需类型转换的参数` 可传给 `const 引用` (const t&)
|
|/
转换后的值 存入 临时变量
|
| 传给
|/
const 引用
float fsqrt(const float&);
void g(double d)
{
float r = fsqrt(d); // 传递 存 static_cast<float>(d) 的 临时变量
}
2 传 数组的引用
: paraType 数组引用
类型
函数模板 实参推断: size 也是 paraType 的一部分
template<typename T, int N>
void f( T(&r)[N] );
12.3 重载函数
1 自动 重载解析
(1) 概念
调用函数 f 时, 由 compiler 据 `实参类型`
与 `scope 中` 名为 f 的 各函数 作 `最佳匹配`
(2) 匹配顺序
1) 精确 匹配
[1] 无须 类型转换
[2] 仅需 简单类型转换
数组名 -> 指针
函数名 -> 函数指针
T -> const T
2) 提升 后匹配
[1] bool/char/short -> int
... unsigned 版本
[2] float -> double
3) 标准类型转换 后匹配
[1] 算术类型 间 `强转`
整型间、整型与浮点型间
[2] OO/GP 中 指针 `隐转`
类层次 中
Derived* -> Base*
模板中
T* -> void*
4) 用户自定义类型转换 后匹配
[1] 单参数 explicit ctor
[2] 类型转换运算符 X::operator T()
5) 使用 函数声明中 省略号 ...匹配
二义性 => compile error
能找到的 最高层级(同一层) 中 不止1个匹配
(3) 函数模板 的 special
模板实参推断 -> 特例化版本 -> 参与 重载解析规则
23.4.2 节
——————————————————————————————————————————————————————————————————————————————————————————————
模板 函数 | (先) `引用推断` (模板实参推断) + (再决定) 右值引用传递 / (还是) 左值引用传递
| |
右值引用模板实参| | 区分 左值 和 右值
| |/
| X 类型 的 `左值 / 右值` 被 `推断为 X& / X`
|
———————————————————————————————————————————————————————————————————————————————————————————————
非模板 函数 | (右) 值 绑定到 右值引用
| |
右值引用实参 | 移动
——————|—————————————————————————————————————————————————————————————————————————————————————————
|
|/
void f(vector<int>&& );
(4) 模板函数 重载: 23.4.3 节
——————————————————————————————————————————————————————
5句话
——————————————————————————————————————————————————————
1) 找 `所有 特例化版本`
——————————————————————————————————————————————————————
2) 找 `更特殊的`
——————————————————————————————————————————————————————
3) 与 普通函数 一起, 按 `普通函数 重载解析` 规则
——————————————————————————————————————————————————————
4) 普通函数 与 特例化版本 匹配得一样好, `优选 普通函数`
——————————————————————————————————————————————————————
5) `无法确定 最佳匹配` => 二义性 => 编译报错
————————————————————————————|——————————————————————————
|
二义性消除
template<typename T>
T max(T, T);
max('a', 1); // max<char, char> 还是 max<int>(int)
max(2.7, 4); // max<double, double> 还是 max<int>(int)
1> 显式限定
max<int>('a', 1); // max<int>(int)
max<double>(2.7, 4); // max<double>(double)
2> 封装 1层
1) 找参与重~析的 `所有 特例化版本`
调 sqrt(z)
sqrt<double>(complex<double>) 与
sqrt<complex<double>>(complex<double>) 都成为 候选
2) 若 2个 模板函数 都可调用, 找`更特殊的`
调 sqrt(z)
sqrt<double>(complex<double>) 比
|
|
T
sqrt<T>(complex<T>)
sqrt< complex<double> >(complex<double>)
|
|
T
sqrt<T>(T)
更特殊
因为, 任何匹配 sqrt<T>(complex<T>) 的 调用, 也都能匹配 sqrt<T>(T)
3) 还留在 `候选集` 中的
|
|
`模板函数 和 普通函数`
|
| 用
|/
`普通函数 重载解析规则`
sqrt(2)
sqrt<int>(int) 优于 sqrt(double)
note:
参与了 `模板实参推断` 的 `(模板) 函数实参`,
不能再进行 提升、标准类型转换、用户自定义类型转换
4) 若 普通函数 与 特例化版本 `匹配的一样好`
优选 `普通函数`
sqrt<2.0>
sqrt(double) 优于 sqrt<double>(double)
5) 多个一样好的匹配, 且
`无法确定 最佳匹配` => 二义性 => 编译报错
2 重载 与 returnType
重载解析过程 `不考虑 returnType`
3 重载 scope
—————————————————————————————————————————————————————————————————
1) 显而易见 的 scope: context scope / 函数调用前的 global scope
显式
2) using 指示 / using 声明
引入的 跨 类 作用域
————————————————————————————————————————————————————————————————
3) 重载函数 被 类成员调用
引入 类 及其 基类 scope
隐式
4) ADL 引入的 scope / 关联 namespace
———————————————————————————————————————————————————————
| ADL 的 关联名字空间
| ( associated namespace ) ADL scope
———————————————————————————————————————————————————————
函数调用 f(arg...) |
的参数 args... 是 |
———————————————————————————————————————————————————
内置类型 | 无
———————————————————————————————————————————————————
namespace 成员 | namespace
———————————————————————————————————————————————————
class 成员 | 类 + 其基类 + 类所在的 namespace
———————————————————————————————————————————————————————————————————
4 函数 重载 优先级 (14.2.4 节)
———————————————————————————————————————————————————————————————————
函数 重载 优先级
函数调用 f(arg...) 在
———————————————————————————————————————————————————————————————————
(1) 非类成员调用
[1] 函数调用 所在 scope (global scope)
[2] ADL scope
———————————————————————————————————————————————————————————————————
(2) 类成员调用
———————————————————————————————————————————————————————————————
1) 命名函数调用
[1] 优先 类 scope + 基类 scope
[2] 再 ADL scope
———————————————————————————————————————————————————————————————
2) 匿名函数调用(运算符)
[1] 类 scope + 基类 scope + ADL scope
———————————————————————————————————————————————————————————————————
// === (1)
1) 命名函数调用 f(x) + 在 类 D 成员 g() 内调用, `实参 x` 类型 N::S
find scope
D+Base 的 scope
x 的 ADL scope
global
find order
[1] 先 D+Base 的 scope 中 找, 找到 Base::f(N::S) => 匹配调用
2) 实参1
find order
[1] 先 D+Base 的 scope 中 找, 没找到
[2] ADL 无法实现 => 查不到 h(int) => 编译报错
namespace N
{
struct S {int i};
void f(S);
void h(int);
};
struct Base
{
void f(N::S);
};
struct D: Base
{
void g(N::S x)
{
f(x); // 1)
h(1); // 2)
}
};
// === (2) 匿名函数调用
匿名函数调用 operator!(X) 在 类 Z 成员 f() 内调用, `实参 x` 类型 X
find scope:
Z 的 scope & global scope
find order: 2 个 scope 同优先级
Z::operator!() 不匹配
::operator!(X) 匹配 => 调用
X operator!(X);
struct Z
{
Z operator!();
X f(X x)
{
// ...
return !x; //(1)
}
int f(int x)
{ return !x; } //(2) 对 int 调 内置 !
};
// === 3 命名函数调用 f(x, 1) + 非类成员调用
find scope:
global scope
参数 x 的 ADL scope: N::X 是 namespace N 的 成员 => ADL scope = N
find order:
2 个 scope 同优先级
N::f(X, int) 比 N2::f(N::X, unsigned) 更匹配
=> 调 N::f(X, int)
namespace N
{
template <class T>
void f(T, int);
class X {};
};
namespace N2
{
N::X x;
void f(N::X, unsigned);
void g()
{
f(x, 1); // 调 N::f(X, int)
}
};
12.4 函数指针
1. C <-> C++ 函数指针 的 赋值/初始化
type `精确匹配`
(1) 实参 与 形参 `函数指针 类型` 必须 `精确匹配`
C 编译
using CFT = int(const void*, const void*);
void ssort(void* base, size_t n, size_t sz, CFT cmp);
C++ 编译
int cmp(const User* p, const User* q) // User: 用户自定义类型 struct
{
return strcmp(p->id, q->id);
}
cmp 作 实参 调 ssort => 两种 函数指针 类型 非 精确匹配 => 编译报错
解决
中间层
const void* p
|
|/
static_cast<const User*>(p)
int cmp(const void* p, const void* q)
{
return strcmp(static_cast<const User*>(p)->id, static_cast<const User*>(q)->id);
}
| C与C++ 链接方式/链接方式 (=>调用机制) 不同 =>
|/
仍报错
(2) C & C++ code 链接方式/链接规范 要相同 (=>调用机制 相同)
// file1.c
extern "C"
{
using CFT = int(const void*, const void*);
void ssort(void* base, size_t n, size_t sz, CFT cmp);
}
// file2.cpp
extern "C" int cmp(const void* p, const void* q);
2 作 操作参数
调 算法/函数
回调
3 成员 函数指针
与 普通函数指针 本质不同
|
|/
是 vtbl 的 index: 0 / 1 / 2 / ...
12.5 宏
编译器 接触 程序前, 宏 会 `重排程序文本`
=> 调试器、交叉引用、性能评测工具 很难发挥作用
(1) 宏名 不允许 `重载`
宏 无法处理 `递归`
(2) 尽管尽量用 括号() 将 `宏的参数 括起来`
只能解决 简单语法问题,
|
| 但对于
|/
`宏产生的 副作用` 仍 `无能为力`
#define MIN(a, b) ( ( (a) < (b) ) ? (a) : (b) )
| |
|/ |/
最外层 也加 括号 不是语句 => 末尾没 分号
int x = 1;
int y = 10;
int z = MIN(x++, y++); // x 变成 3, y 变成 11
? 会使 x/y 均 递增 => 选中 (x++) => x 再递增
|
|
( ( (x++) < (y++) ) ? (x++) : (y++) )
|
| (x++) < (y++) 是表达式
|/
其值为 true
(3) 宏 的应用
1) `字符串 拼接`
##
#define NAME2(a, b) a##b
int NAME2(hack, cah)();
展开为
int hackcah();
2) 宏 的 `空 参数列表`
#define EMPTY() std::cout << "empty\n"
EMPTY(); // 输出 "empty\n"
3) 变长参数 宏
#define err_print(...) fprintf(stderr, "error: %s %d\n", __VA_ARGS__)
|
|/
把 实参 当作 字符串
err_print("The answer", 54);
// 输出: The answer 54
1 条件编译
有一种 宏 无法替代
条件编译
#ifdef 等
2 预定义宏
__cplusplus
C++ 编译器中 定义, C 编译器 没有
C++11 中其 值为 201103L
__LINE__
当前源文件 的 代码行数
3 编译指令
网友评论