使用 C++11 让程序更简洁、更现代
阅读 《深入应用C++11优化代码与工程应用》 学习笔记
初始化
C++ 11 对象初始化
// C++ 11 初始化
int i {1};
int i_arr[]{ 1, 2, 3, 4, 5 };
int * pint = new int{ 1 };
int * parr = new int[2]{ 1,2 };
struct A {
int x;
int y;
} A_a = { 1, 2 };
struct B {
int x;
int y;
B(int _x, int _y) :x(_x), y(_y) {}
} B_b = { 1, 2 };
/*
A_a 的初始化过程是C++98/03 中就有的聚合类型(Aggregates)的初始化。 它将以拷贝的形式,用初始化列表中的值来初始化Struct A 成员。
B_b 的初始化时自定义了一个构造函数, 因此, 实际上初始化时以构造函数进行的。
C++ 聚合类型的定义:
1、类型是个普通数组 (如 int[10]、 char[2][3])
2、类型是一个类(class、struct、union)
* 无自定义的构造函数。
* 无私有(private)或保护(protected)的非静态数据成员。
* 无基类。
* 无虚函数。
* 不能有{} 和 = 直接初始化的非静态数据成员。
*/
// 实际上 stl 中的 容器是通过 std::initializer_list 这个轻量级的类模板来完成上述功能支持的
vector <int> varr{ 1, 2, 3, 4, 5 };
// initializer_list 是非常高效的, 它内部并不负责保持初始化列表中元素的拷贝,仅仅存储了列表中元素的引用而已。
vector(_XSTD initializer_list<value_type> _Ilist,
const _Alloc& _Al = allocator_type())
: _Mybase(_Al)
{ // construct from initializer_list
_Construct(_Ilist.begin(), _Ilist.end());
}
std::initializer_list模板定义:
template<class _Elem>
class initializer_list
{ // list of pointers to elements
public:
typedef _Elem value_type;
typedef const _Elem& reference;
typedef const _Elem& const_reference;
typedef size_t size_type;
typedef const _Elem* iterator;
typedef const _Elem* const_iterator;
_CONST_FUN initializer_list() _NOEXCEPT
: _First(0), _Last(0)
{ // empty list
}
_CONST_FUN initializer_list(const _Elem *_First_arg,
const _Elem *_Last_arg) _NOEXCEPT
: _First(_First_arg), _Last(_Last_arg)
{ // construct with pointers
}
_CONST_FUN const _Elem *begin() const _NOEXCEPT
{ // get beginning of list
return (_First);
}
_CONST_FUN const _Elem *end() const _NOEXCEPT
{ // get end of list
return (_Last);
}
_CONST_FUN size_t size() const _NOEXCEPT
{ // get length of list
return ((size_t)(_Last - _First));
}
private:
const _Elem *_First;
const _Elem *_Last;
};
类型推导
auto 用于通过一个表达式在编译时正确待定义的变量类型, auto所修饰的变量必须被初始化, 编译器需要通过初始化来确定auto所代表的类型。
// auto 类型推导
auto x = 1; //OK: x 是 int 类型
auto pi = new auto(1); //OK: pi是 int * 类型
const auto u = 1; //OK: u 是 const int 类型
const auto *pv = &x; //OK: pv是 const int * 类型
// auto 使用
map<int, int> resultMap { { 1, 10 }, { 2, 20 }, };
auto miter = resultMap.begin(); //map<int, int>::iterator miter = resultMap.begin();
for (; miter != resultMap.end(); ++ miter)
{
cout << miter->second << endl;
}
decltype:
<span style="white-space:pre"> </span>// C++ 11 decltype
int y = 1;
decltype(y) dv = 2; // dv -> int
模板改进
// C++ 11 auto 和 decltype 的结合使用
// 返回类型后置语法
// C++ 11 中添加了返回类型后置(trailing-return-type, 又称跟踪返回类型)语法,将decltype 和 auto 结合起来完成返回值类型推导。
template <typename T, typename U>
auto test_add(T t, U u) -> decltype(t + u)
{
return t + u;
}
// 模板使用
// eg: auto res = test_add<int, double>(1, 1.2);
// C++ 11 模板的改进 (using 别名语法)
// 通过using 定义模板别名的语法, 只是在普通类型别名语法的基础上添加template的参数列表,
// 使用using 可以轻松的创建一个新的模板别名,而不需要像C++98/03那样使用繁琐的外敷模板。
// C++ 98/03
template <typename T>
struct map_t1
{
typedef std::map<int, T> mapT;
};
// eg: map_t1<int>::mapT map_1;
// C++ 11
template <typename T>
using map_t2 = std::map<int, T>;
// eg: map_t2<int> map_2;
// 函数模板的默认模板参数
// 当模板有默认参数时,函数模板的调用如同一个普通函数
template <typename T = int>
T func(T value)
{
return value;
}
基于范围的 for 循环
<span style="white-space:pre"> </span>map<int, int> resultMap { { 1, 10 }, { 2, 20 }, };
// C++ 11 for 循环
for(const auto &v : resultMap)
{
cout << v.second << endl;
}
std::function 和 bind 绑定器
添加头文件 #include <functional>
在 C++ 98/03中 , 存在“可调用对象” 这个概念; 调用对象有如下几种定义:
- 函数指针
- 具有 operator()成员函数的类对象(仿函数)
- 可被转换为函数指针的类对象
- 类成员(函数)指针
void func_t(void)
{
//......
}
struct Foo1 {
void operator()(int x) {
cout << x<< endl;
}
};
struct Foo2 {
using foo_t = void(*) (void);
static void func(void) {
//......
}
operator foo_t(void) {
return func;
}
};
struct Foo3 {
int m_a;
void func(void) {
//......
}
};
// 函数指针
void (* p_func)(void) = &func_t;
// 仿函数
vector<int> arr{ 1,2,3 };
std::for_each(arr.begin(), arr.end(), Foo1());
// 可被转换为函数指针的类对象
Foo2 foo_2;
foo_2()
// 成员函数指针
void (Foo3::*p_func)(void) = &Foo3::func;
Foo3 foo3;
(foo3.*p_func)();
C++ 11 可调用对象包装器 std::function
std::function<void(void)> fr1 = func_t; // 可以取代函数指针的作用
Foo2 foo2;
fr1 = foo2;
C++ 11 绑定器 std:bind
std:bind 返回类型是stl 内部定义的仿函数类型
std::placeholders::_1 是一个占位符, 代表这个位置将在函数调用时,被传入的第一个参数所替代
std::placeholders::_1 是一个占位符, 代表这个位置将在函数调用时,被传入的第一个参数所替代
void func_bind(int x, int y) {
cout << "x = " << x << endl;
cout << "y = " << y << endl;
}
int main(){
auto fb1 = std::bind(func_bind, std::placeholders::_1, 100);
fb1(101, 200); // out: x= 101 y =100
auto fb2 = std::bind(func_bind, std::placeholders::_2, 200);
fb2(101, 301); // out: x= 301 y =200
auto fb3 = std::bind(func_bind, std::placeholders::_1, std::placeholders::_2);
fb3(100, 200); // out: x= 100 y =200
auto fb4 = std::bind(func_bind, std::placeholders::_2, std::placeholders::_1);
fb4(100, 200); // out: x= 200 y =100
<span style="white-space:pre"> </span>return 0
}
lambda 表达式
lambda 表达式有如下优点:
表达式
[ capture ] ( params ) opt -> ret { body; };
capture 是捕获列表;params 是参数表;opt 是函数选项;ret 是返回值类型;body 是函数体。
- 声明式编程风格:就地匿名定义目标函数或函数对象,不需要额外写一个命名函数或者函数对象。以更直接的方式去写程序,好的可读性和可维护性。
- 简洁:不需要额外再写一个函数或者函数对象,避免了代码膨胀和功能分散,让开发者更加集中精力在手边的问题,同时也获取了更高的生产率。
- 在需要的时间和地点实现功能闭包,使程序更灵活。
表达式
[ capture ] ( params ) opt -> ret { body; };
capture 是捕获列表;params 是参数表;opt 是函数选项;ret 是返回值类型;body 是函数体。
<span style="white-space:pre"> </span>auto lambad_fun1 = [](int i) { return i; };
//lambda 表达式在没有参数列表时,参数列表是可以省略的。
auto lambad_fun2 = [] { return 1; };
- [] 不捕获任何变量。
- [&] 捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。
- [=] 捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。
- [=, &foo] 按值捕获外部作用域中所有变量,并按引用捕获 foo 变量。
- [bar] 按值捕获 bar 变量,同时不捕获其他变量。
- [this] 捕获当前类中的 this 指针,让 lambda 表达式拥有和当前类成员函数同样的访问权限。如果已经使用了 & 或者 = ,就默认添加此选项。捕获 this 的目的是可以在lamda 中使用当前类的成员函数和成员变量。
lambda 表达式的捕获列表精细地控制了 lambda 表达式能够访问的外部变量,以及如何访问这些变量。
class Lambda_Test
{
public:
int i_ = 0;
void func(int x, int y)
{
//auto x1 = [] { return i_; }; // error,没有捕获外部变量
auto x2 = [=] { return i_ + x + y; }; // OK,捕获所有外部变量
auto x3 = [&] { return i_ + x + y; }; // OK,捕获所有外部变量
auto x4 = [this] { return i_; }; // OK,捕获 this 指针
//auto x5 = [this] { return i_ + x + y; }; // error,没有捕获 x、y
auto x6 = [this, x, y] { return i_ + x + y; }; // OK,捕获 this 指针、x、y
auto x7 = [this] { return i_++; }; // OK,捕获 this 指针,并修改成员的值
}
};
int main(){
int a = 0, b = 1;
auto f1 = [] { return a; }; // error,没有捕获外部变量
auto f2 = [&] { return a++; }; // OK,捕获所有外部变量,并对 a 执行自加运算
auto f3 = [=] { return a; }; // OK,捕获所有外部变量,并返回 a
auto f4 = [=] { return a++; }; // error,a 是以复制方式捕获的,无法修改
auto f5 = [a] { return a + b; }; // error,没有捕获变量 b
auto f6 = [a, &b] { return a + (b++); }; // OK,捕获 a 和 b 的引用,并对 b 做自加运算
auto f7 = [=, &b] { return a + (b++); }; // OK,捕获所有外部变量和 b 的引用,并对 b 做自加运算
auto f = [=] { return a; }; <span style="white-space:pre"> </span>// 按值捕获外部变量
a += 1; // a 被修改了
std::cout << f() << std::endl; <span style="white-space:pre"> </span>// 输出?
//lambda 表达式按值捕获了所有外部变量。在捕获的一瞬间,a 的值就已经被复制到 f 中了。之后 a 被修改,但此时 f 中存储的 a 仍然还是捕获时的值,因此,最终输出结果是 0。
//如果希望去修改按值捕获的外部变量应当怎么办呢?这时,需要显式指明 lambda 表达式为 mutable:
//需要注意的一点是,被 mutable 修饰的 lambda 表达式就算没有参数也要写明参数列表。
int a = 0;
auto f2 = [=]() mutable { return a++; }; // OK, mutable
<span style="white-space:pre"> </span>return 0;
}
示例:
#include <iostream>
#include <algorithm>
#include <functional>
#include <map>
#include <vector>
using namespace std;
int main(){
<span style="white-space:pre"> </span>std::vector<int> v = { 1, 2, 3, 4, 5, 6 };
int even_count = 0;
for_each(v.begin(), v.end(), [&even_count](int val)
{
if (!(val & 1)) // val % 2 == 0
{
++even_count;
}
});
std::cout << "The number of even is " << even_count << std::endl;
getchar();
<span style="white-space:pre"> </span>return 0;
}