chapter1 让程序 更简洁、更现代: auto & dec
2022-06-06 本文已影响0人
my_passion
1.1 类型推导
1 auto
(1) 本质/机制/应用
1) 本质: auto 是 类型声明 的 "占位符"
2) 机制: 编译时 类型推断 + 替换占位符 auto
3) 应用
2种必须: 编译器 才能 编译时 类型推断 + ...
[1] 声明 变量, `必须` 马上 初始化
[2] 声明 函数返回类型, `必须` 结合 decltype 来 `后置返回类型`
template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u);
template <typename T>
auto func(T& val) -> decltype( anotherFunc(val) );
(2) 如何用
1) 何时用 ?
无须关心 变量类型时, 尤其用于 模板
2) 不能用 auto 的场景
[1] non-static 成员变量
[2] 函数参数
[3] 模板参数
(3) auto 与 指针 / 引用 / cv 限定符( const / volatile )
2大类 丢弃
———————————————————————————————————————————————————————————————————————————
auto 声明的 变量类型
=> auto 的 推导结果(是1个类型)
———————————————————————————————————————————————————————————————————————————
[1] 声明为 指针 或 引用 不丢弃 初始化表达式的 单纯 cv / 指针 + cv / 引用 + cv 属性
int x = 0;
const int* p = &x; // p: const int*
auto* p2 = p; // p2 -> const int*
const int& r = x; // r: const int&
auto& r2 = r; // r2 -> const int&, auto 被推导为 const int
———————————————————————————————————————————————————————————————————————————
[2] 不声明为 指针 不丢弃 单纯指针 & 指针 + cv
1) 指针 -> 不丢弃
auto p = &x; // p -> int*, auto 被推导为 int*
2) 指针 + cv -> 不丢弃
const int* p = &x; // p: const int*
auto p2 = p; // p2 -> const int*
———————————————————————————————————————————————————————————————————————————
[3] 不声明为 引用 `丢弃` 单纯引用 & 引用 + cv
1) 引用 -> 丢弃
int &r = x; // r: int&
auto r2 = r; // r2 -> int, auto 被推导为 int
3) 引用 + cv -> 丢弃
const int& r = x; // r: const int&
auto r2 = r; // r2 -> int
———————————————————————————————————————————————————————————————————————————
[4] 不声明为 指针 或 引用 `丢弃` 单纯 cv
const int y = 0; // y: const int
auto a = y; // a -> int, auto 被推导为 int
———————————————————————————————————————————————————————————————————————————
2 decltype(expr)
(1) `编译时 推断 表达式 类型`
`不会真正计算 表达式的值`
可视为 `类型函数`: 用于 `抽取 类型`
(2) 应用
只 希望得到 `类型`, `不需要或不能 定义变量` 时, 用
1) GP
2) auto + decltype(): 3 小节
(3) Note
1) decltype 与 引用 (&): 引用折叠
3) 推导规则
——————————————————————————————————————————————————————————————————
expr 是 decltype(expr) 是
——————————————————————————————————————————————————————————————————
[1] 标识符 / 类访问表达式 expr 类型
[2] 函数调用 expr 返回类型
返回 纯右值 时, 若 返回类型为 class 类型, cv 才不会被丢弃
[3] else expr 类型的 左值引用 <- expr 是 左值, 但不是 标识符 / 类访问表达式
expr 类型 <- expr 是 右值
——————————————————————————————————————————————————————————————————
[1]
decltype(foo.x) d = 0; // Foo::int x; => d -> int
[2]
const int& func(); // 返回 左值
const int func2(); // 返回 纯右值 & not class
const Foo func3(); // 返回 纯右值 & class
int x = 0;
decltype( func() ) a = x; // a -> const int&
decltype( func2() ) b = x; // b -> int (const 被 丢弃)
decltype( func3() ) c = x; // c -> const Foo
[3]
// Note: 与 [1] 不同, 括号表达式 是 左值, d -> int&
decltype( (foo.x) ) d = x;
int n = 0, m = 0;
decltype(n + m) e = 0; // c -> int
decltype(n += m) f = e; // d -> int&
3 后置返回类型: 组合 auto + decltype()
解决的问题
returnType 依赖 func 参数 或 another func, 而导致的 returnType 难推断 的 问题
(1) 返回类型 仅依赖 func 参数
问题: 加法 2 个参数 类型不同
解决
1) returnType 用 模板参数 + 调用时 用 decltype(...) 显式指定
template<typename T, typename U, typename R>
R add(T t, U u);
int a = 1;
float b = 2.0;
auto c = add<decltype(a + b) >(a + b);
2) returnType 用 decltype(paraVar ...)
paraVar 尚未定义 -> 语法错
template<typename T, typename U>
decltype(t + u) add(T t, U u);
3) returnType 用 decltype(paraType() )
paraType 无 默认 Ctor 时, error
template<typename T, typename U>
decltype(T() + U() ) add(T t, U u);
4) returnType 用 decltype(*(paraType*)0 ) -> 通法
0 地址处用 paraType 指针强转, 再 解引用 来 get 1 个 未初始化 的 paraVar
template<typename T, typename U>
decltype( *(T*)0 + *(U*)0 ) add(T t, U u);
5) C++11: auto + decltype
template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u);
(2) ... 还依赖于 another 函数 -> C++98/03 无法解决, 只能是 C++11 的 auto + decltype
int& func2(int& i);
float func2(float& f);
template<typename T>
auto func(T& val) -> decltype( func2(val) )
{
return func2(val);
}
1.2 std::function() 和 std:bind() 绑定器
1 可调用对象: 4 种
——————————————————————————————————————————————————————————————————————————
[1] 函数指针
void(* funcPtr)(void) = &func;
funcPtr();
——————————————————————————————————————————————————————————————————————————
[2] 含 `函数调用运算符 operator()` 的 `函数对象` -> 所有 `lambda` 均可以
Functor f; // void A::operator()() { /* */ };
f();
——————————————————————————————————————————————————————————————————————————
[3] 可被 `转换为 函数指针` 的 类对象
|
| 指 普通函数指针, 不含 成员函数指针
|/
类型转换运算符 X::operator T()
——————————————————————————————————————————————————————————————————————————
[4] 成员函数指针
void (A::*memFuncPtr)(void) = &A::memFunc;
A a;
(a.*memFuncPtr)();
——————————————————————————————————————————————————————————————————————————
Note: 可调用类型 不包括 函数类型 & 函数引用
1] 函数类型
不能直接 定义对象
2] 函数引用
可视为 const 函数指针
callableObj
1] 调用 方法: 除 成员函数指针 外, 都 像函数那样
2] 定义 方法: 多样
=> 保存 & 传递 时, 繁琐
|
| 解决
|/
C++11 std::function & std::bind()
统一 `保存 并 延迟调用` callableObj
std::function<void(int, int)> f = callableObj; // 用 callableObj 初始化
2 callableObj 的 包装器(wrapper) std::function()
(1) 是 类模板
<functional>
template< class R, class... Args >
class function<R(Args...)>;
static 成员函数指针: 本质是 普通函数指针, 可 单独用于 函数调用 => 可被 std::function 容纳
|\
|
|
(2) 可容纳 除 `non-static 成员函数指针` 之外的 3 种 callableObj
|
|
|/
本质是 偏移量 -> 不能单独用于 函数调用, 必须 结合 对象(指针)
|
| 解决
|/
std::bind `第 2 参数` 提供 `对象指针`
(3) `模板参数` 为 (3种) callableObj 的 `函数调用运算符 的 signature: returnType (paraListTypes)`
(4) `结果` 视为 `函数对象`
可作
算法参数/回调函数
函数参数 k
int f1(int x)
{
return 2*x;
}
class A
{
public:
int operator()(int x)
{
return 2*x;
}
};
A a;
std::function<int(int)> f = f1; // 绑定 普通函数
std::cout << f(1) << std::endl;
f = a; // 绑定 函数对象
std::cout << f(2) << std::endl;
3 std::bind() 绑定器
<functional>
template< class F, class... Args >
/*unspecified*/ bind( F&& f, Args&&... args );
(1) 弥补了 std:function 不能 容纳/绑定 non-static 成员函数指针 的 callableObj
bind 配合 function
=> 统一 对 callableObj 的 操作
(2) 机制
绑定 `可调用对象` 与其 `参数`
|
| 结果/返回
|/
函数对象: 其 参数` = `未绑定的 剩余参数`
|
|
|/
可
———————————————————————————————————
1) 直接调用
2) auto 推断出 变量类型 + 变量定义
3) 用 std::function() 保存
———————————————————————————————————
(3) 2 大作用
[1] 将 callableObj 与其 参数 绑定为 1个 函数对象
[2] 将 多元(参数个数 n > 1) callableObj 转成 1元或 (n-1) 元 callableObj, 即 只绑定部分参数
(4) "占位符" std::placeholders::_1/...
该位置 将在 函数调用时, 被 传入的 第1/... 个实参替代
(5) 简化和增强了 标准库 bind1st / bind2nd
bind1st / bind2nd
将 二元算子 变为 一元算子
绑定 左 右 操作数
// 找 elemValue > 10 的 元素数
int count = std::count_if(collection.begin(), collection.end(),
std::bind1st(std::less<int>(), 10) ); // 10 < rOperNum
// 找 elemValue < 10 的 元素数
int count = std::count_if(collection.begin(), collection.end(),
std::bind2nd(std::less<int>(), 10) ); // lOperNum < 10
// bind: 统一调用方式
int count = std::count_if(collection.begin(), collection.end(),
std::bind(std::less<int>(), 10, _1) ); // 10 < rOperNum
int count = std::count_if(collection.begin(), collection.end(),
std::bind(std::less<int>(), _1, 10) ); // lOperNum < 10
(6) 组合使用 bind
1) 用于 判断 是否 > 5 的 `闭包`
std::bind(std::greater<int>(), std::placeholders::_1, 5 )
|
|/
返回的对象 只有1个 int 参数
2) `逻辑与` 组合 `是否 > 5` 与 `是否 < 10` 的 `2个 闭包`
using std::placeholders::_1;
auto f = std::bind(std::logical_and<bool>(),
std::bind(std::greater<int>(), _1, 5 ),
std::bind(std::less_equal<int>(), _1, 10 )
);
int count = count_if(v.begin(), v.end(), f);
例:
void f(int n1, int n2, int n3, const int& n4, int n5)
{
std::cout << n1 << ' ' << n2 << ' ' << n3 << ' ' << n4 << ' ' << n5 << '\n';
}
n = 7;
auto f1 = std::bind(f, _2, 42, _1, std::cref(n), n); // std::cref(n) 引用传递,; n 值传递 => 此处绑定为 7, 后面都是 7
n = 10;
f1(1, 2, 1001); // 1 is bound by _1, 2 is bound by _2, 1001 is unused
// makes a call to f(2, 42, 1, n, 7) // Note: 最后1个参数 已被绑定为 7
(1)
class A
{
public:
void f(int x, int y);
// ...
};
void test()
{
A a;
std::function<void(int, int)> f =
std::bind(&A::f, &a,
std::placeholders::_1, std::placeholders::_2 );
f(1, 2);
}
(3)
void output(int x) { cout << x << " "; }
void call_when_even(int x, const std::function< void(int) >& f)
{
if( ! (x&1) ) // x % 2
f(x);
}
void test()
{
auto f = std::bind(output, std::placeholders::_1); // output 第1实参 未绑定, 传入 std::placeholders::_1 指定的位置
call_when_even(1, f); // 输出 1
}
void f2(int x, int y) { cout << x << " " << y << " "; }
void test2()
{
std:bind(f2, 1, 2)();
std:bind(f2, 2, std::placeholders::_1)(1);
|\ | 第1实参 1 -> 传入 std::placeholders::_1
|_ _|
std:bind(f2, std::placeholders::_2, std::placeholders::_1)(1, 2);
} |\ | 第 2 实参 2 -> 传入 std::placeholders::_2
|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
1.3 lambda 表达式
源于 FP
1 完整 lambda
[captureList] (paraList) -> returnType { funcBody; }
-> returnType 可选
auto f = [](int x) -> int { return x + 1; };
(1) 无参 时, (paraList) 可省
[] { return 1; };
(2) 通常, 编译器 可据 return 语句 自动推导出 returnType => -> returnType 可省
auto f = [](int x) { return x + 1; };
例外: 初始化列表 {} -> 必须 explicit 指定 returnType
auto f2 = []() { return {1, 2}; }; // => 编译 error
auto f2 = []() -> std::vector<int> { return {1, 2}; };
(3) 捕获列表
控制 lambda 能访问的 external variable, 及 如何访问 它们
——————————————————————————————————————————————————————————————————————————————
某变量 前 用 & => 引用捕获 [=, &foo]
else => 值捕获 [bar]
——————————————————————————————————————————————————————————————————————————————
& 引用捕获 所有 (other) 外部 scope 变量
= 值捕获 所有 ...
——————————————————————————————————————————————————————————————————————————————
[this] 捕获 current Class 中 this 指针,
使 lambda 拥有与 current Class 成员函数 同样的 访问权限
Note: 若已使用 &/=, 默认 添加 this
——————————————————————————————————————————————————————————————————————————————
class A
{
public:
int d;
void f(int x, int y)
{
auto f = [=, &y] { return d + x + y; }; // 已使用 &/=, 默认 添加 this, d <=> this->d
}
};
2 lambda 与 仿函数
lambda 称为 `闭包` 类型: 匿名 类类型
可视为 仿函数: 带 operator() 的 类
[captureList] (paraList) -> returnType { funcBody; }
————————————————————————————————————————————————
lambda 仿函数
————————————————————————————————————————————————
captureList ctor 的 参数 + 传递方式(值/引用)
memData + 类型(值/引用)
paraList operator()(paraList) 的 参数列表
returnType operator() 的 returnType
funcBody operator() 的 funcBody
————————————————————————————————————————————————
=> lambda 可用 std::function 和 std::bind 存储/操作
std::function<int(int)> f1 = [](int x) { return x; };
std::function<int(void)> f2 = std::bind( [](int x) { return x; }, 1 );
| | |
|_ _ _ _ _ _ _ _ _ _ _ _ _ _| _ _ _ _ _ _ _ _ |
lambda 的 参数 x 被绑定为 1 => bind 所得的 仿函数 无参
3 3 点注意
(1) `延迟调用` lambda 时, 对 捕获的 外部变量 的 访问
[1] 按 捕获时的值 访问 <- 值捕获
[2] 即时访问 <- 引用捕获
(2) lambda 中, `修改` 捕获的外部变量 (的 copy)
默认 + 值捕获 -> 无法修改
原因: lambda 的 operator() 是 const => 无法修改 memData(由 值捕获的外部变量 值传递)
[1] 修改 外部变量 的 copy(lambda 的 memData)
值捕获 + mutable
|
取消 operator() 的 const
Note: mutable 时, lambda 无参 也要写明 参数列表
[2] 修改 外部变量
引用捕获
(3) lambda 能否 直接 `转换为 函数指针`
无 捕获变量 -> 能
else -> 不能
using Func = int (*)(int);
Func f = [](int x) { return x; };
std::cout << f(1) << std::endl;
例
#include <iostream>
void test1()
{
int x = 0;
auto f = [=] { return x; };
x += 1;
std::cout << f() << std::endl; // 0
}
void test2()
{
// (2) `修改` 捕获的外部变量 的 copy
int x = 0;
auto f = [=]() mutable { return ++x; };
// 1 0
std::cout << f() << " ";
std::cout << x << std::endl;
}
void test3()
{
// (3) 修改 外部变量
int x = 0;
auto f = [&] { return ++x; };
// 1 1
// Note, std::cout 依赖于编译器实现, vs 下可能从右到左算, gcc 下 可能从左到右算
// => 依赖与顺序的计算 不要放同一条 std::cout 语句
std::cout << f() << " ";
std::cout << x << std::endl;
}
int main()
{
test3();
}
4 lambda 2大 优点
(1) 声明式编程 => code 简洁
就地定义 闭包, 不需要定义 仿函数 对象, 大大简化了 标准库算法的调用
int evenCount = 0;
for_each(v.begin(), v.end(),
[&evenCount](int value)
{
if( !(value & 1) )
++evenCount;
}
);
|\
|
|
class CountEven
{
private:
int& count; //Note: Ctor 参数 与 memData 都是 引用
public:
CountEven(int& count_): count(count_) { }
void operator() (int value)
{
if( !(value & 1) )
++count;
}
};
int evenCount = 0;
for_each(v.begin(), v.end(), CountEven(evenCount) );
(2) 就地封装短小的 `闭包`, 灵活性更好
1) 比 std::bind 更灵活
int count = count_if(v.begin(), v.end(),
[](int x){ return x > 5 && x < 10; } );
int count = count_if(v.begin(), v.end(),
[](int x){ return x > 10; } );
|\
|
|
using std::placeholders::_1;
auto f = std::bind(std::logical_and<bool>(),
std::bind(std::greater<int>(), _1, 5 ),
std::bind(std::less_equal<int>(), _1, 10 )
);
int count = count_if(v.begin(), v.end(), f);
2) 与 std:function 效果一样, 还更简洁
通常, 可用 lambda 代替 std::function
不能完全替代 std:function
老库, 如 boost 库 不支持 lambda
1.4 tuple 元组: 编译时 + 可变模板实参
可容纳 任意类型、任意数量 的 元素
1.5 模板改进
1 右尖括号
2 模板别名: using 而不能是 typedef
using FuncType = void (*)(int);
3 函数模板 的 默认模板参数
函数模板 实参推断 生效时, 默认模板参数 被 忽略
template<typename T = int> // error in C++03, OK in C++11
void func(T t = 0);
func(1.0); // T -> double
1.6 列表初始化 {}
1 统一 初始化
2 防止窄化转换
3 同类型 任意长度的初始化列表
只保存 初始化列表 中 元素的引用
1.7 范围 for 循环
统一 容器 和 数组 的 遍历方法
1 ——————————————————————————————————————————————————————————
值传递 : <=> auto + 元素 copy
for(auto x: v)
auto 自动推断出的 是 `序列` 的 元素类型/value_type, 而不是 迭代器
——————————————————————————————————————————————————————————
引用传递: <=> auto + 迭代器 版本
for(auto& x: v)
<=> for(auto iter = v.begin(); iter != v.end(); ++iter)
for(const auto& x: v)
——————————————————————————————————————————————————————————
std::vector<int> arr;
for(char elem: arr); // int 隐式转换为 char
2 Note
范围 for 循环
[1] 冒号后 expr 只执行 1 次
[2] 迭代时 修改 容器, 可能使 迭代器失效
[3] 通常 循环开始前 确定好迭代范围