C++11的部分新特性解析
1. 声明参数的简写形式
int i = 0;
可写为int i(0);
string s = “123”;
可写为 string s{“123”};
int a[3] = {1,2,3};
可写为int a[3]{1,2,3};
2. For循环的简写形式
例:
输出int list[10]中的每个元素。
原写法
for (int i(0); i < 10; i++)
cout << list[i] << endl;
新写法
for (int i : list)
cout << i << endl;
两种写法完全等价。
3. 自动类型
声明变量的时候,如果是声明+赋值的形式,则可以将类型一律声明为auto,而无须指定具体的类型。
例:
auto a = 1;
auto b = 1.6;
此处a的类型由后面1的类型决定,为int。
而decltype可以理解为auto的反函数,获取类型。
例:
用a的类型声明b。
auto a = 1;
decltype(a) b;
4. Tuple(元组)
Tuple可以理解为,将任意个数的完全不同的类型的变量放在同一个数组中。
可以类比python中的tuple,基本一致。可以参考我的另一篇文章 C++中使用tuple 。只将重点列出来。
例:
生成tuple
auto tup1 = std::make_tuple("Hello World!", 'a', 3.14, 0);
上述代码创建了一个tuple <const char*, char, double, int>
类型的元组。
拆开tuple
auto tup1 = std::make_tuple(3.14, 1, 'a');
double a;
int b;
char c;
std::tie(a, b, c) = tup1;
这样做的结果是a = 3.14, b = 1, c = 'a'
。
如果不想要某一位的值,可以直接将其用ignore代替。
std::tie(ignore, b, c) = tup1;
连接tuple
std::tuple<float, string> tup1(3.14, "pi");
std::tuple<int, char> tup2(10, 'a');
auto tup3 = tuple_cat(tup1, tup2);
输出第i个元素
std::tuple<float, string> tup1(3.14, "pi");
cout << get<i>(tup1);
5. Lambda语法
Lambda语法可类比python,在我的另一篇文章中有详细描述,C++中使用Lambda,可参考。只将重点列出来。
Lambda用于快速声明匿名的函数。格式如下:
1. 传递方式可以为空,&,=,或者为每一个外部参数指明传递方式。= 表示为值传递方式,& 表示为引用传递方式。就算为空,中括号也不可以省略。
2. 参数列表即函数的参数,参数可以按值或者按引用传递。如果没有参数,可以省略(包括小括号)。
3. mutable是可选的,如果声明了mutable则可以修改按值传递进来的参数的拷贝(在lambda表达式外修改不可见)。
4. throw也是可选的,用来抛出函数处理过程中的异常,需制定抛出的异常的类型。
5. 返回类型是函数返回值的类型,在两种情况下可以省略,在后面叙述。
6. 函数体,实现功能的部分。
例:
int add(int x, int y)
{
return x + y;
}
与下面的lambda表达式等价
auto add = [](int x, int y) -> int { return x + y; };
区别是第二种声明方式可以声明在函数内部。C++是不允许函数嵌套的。
当然lambda并不是只能用来这样声明函数(这样声明反而更繁琐),而是用来配合一些新的语法使用。
6. 可配合Lambda的语法
for_each,可以类比python中的map,即对与数组中的每一个元素都执行定义的方法。
例1:
将一个10个元素的int数组,int list[10]中每个元素乘以2。
for_each(list, list + 10, [=](int &i) {i = i * 2; });
可以看出for_each是由3个参数组成的,第一个参数为指向数组起始位置的指针,第二个参数为指向数组末尾的指针,第三个参数就是使用lambda定义的函数。执行的时候,实际上是对于数组中的每一个元素都执行这一个函数。其中的输入参数i在实际执行时就是数组中的一个元素。
如果list是vector、list等具有迭代器的stl模板,则写法为
for_each(list.begin(), list.end(), [=](int &i) {i = i * 2; });
例2:
将一个10个元素的int数组,int list[10]赋值为1~10;
for_each(list, list + 10, [=](int &i) {i = &i - list + 1; });
find_if,用于查找指定条件的元素(返回指向第一个满足条件的元素的指针或迭代器)
例:
查找第一个偶数的位置
vector<int> a = { 1, 2, 3, 4 };
auto res = find_if(a.begin(), a.end(), [](int x) -> bool { return x % 2 == 0; });
find_if也是三个元素,对比参照for_each理解即可。
count_if,用于统计满足指定条件的元素的个数
例:
统计偶数个数
vector<int> a = { 1, 2, 3, 4 };
auto res = count_if(a.begin(), a.end(), [](int x) -> bool { return x % 2 == 0; });
参数与find_if完全一致。
copy_if,提取满足条件的所有元素
例:
提取a中所有偶数并复制到b中
vector<int> a = { 1, 2, 3, 4 };
vector<int> b(a.size());
auto res = copy_if(a.begin(), a.end(), b.begin(), [](int x) -> bool { return x % 2 == 0; });
b.resize(distance(b.begin(), res));
参数与find_if完全一致。
关于find_if,copy_if和count_if的更详细用法,可以关注我的另一篇文章[C++快速开发] 查询 。
7. 空指针nullptr
在c++11以前一般空指针用NULL,而NULL的定义是0。
在c++11以后,尽量用nullptr,而不要用NULL。nullptr就是一个特殊的类型,代表空指针,他不等于0,也不是int类型。
这是一个编程习惯的问题,并不是强制性要求。
因为这样可以解决一个可能的歧义问题。因为函数可以重载,所以可以这样声明。
void F(int a)
{
cout << a << endl;
}
void F(int *p)
{
cout << *p << endl;
}
如果按照以前的定义方式,声明一个空指针,int *p = NULL
,那么调用F函数F(p)时会因为歧义而导致崩溃,因为NULL的定义就是0是int类型,那么参数该怎样被解释?是int还是int*?在某些编译器中执行时会有bug。如果改为使用nullptr则可以避免该问题。
8. 关键字final和override
override用于修饰虚函数,用override修饰的虚函数必须在派生类中进行重载,否则会报错。避免了以前可能错误使用未重载的虚函数而导致错误。
final与override作用完全相反,防止虚函数被重写。
9. 多线程
多线程可以参考我的另一篇文章 C++多线程 。下面只列出重点。
例:
创建线程并输出一句话
thread t([] {cout << "new thread!" << endl; });
t.join();
配合mutex可以执行锁操作。
例:
双线程对同一变量执行加一操作
mutex m;
int count = 0;
auto increase = [&] //定义增加函数
{
m.lock(); //用于互斥访问的锁
count++;
cout << "id: " << this_thread::get_id() << endl;
m.unlock();
this_thread::sleep_for(chrono::milliseconds(100));
};
thread t1([increase]
{
for (int i = 0; i < 5; i++)
increase();
});
thread t2([increase]
{
for (int i = 0; i < 5; i++)
increase();
});
t1.join();
t2.join();
10. function与bind
function适用于解决以前函数指针的不安全问题。用于将一个可调用实体进行包裹,实现泛型函数的回调。
理解的时候,就把它当作函数指针的安全替代品。
例:
//函数
int Test(int a)
{
return a;
}
//仿函数
class Test2
{
public:
int operator()(int a)
{
return a;
}
};
//lambda
auto Test3 = [](int a){ return a; };
//类成员函数
class TestClass
{
public:
int Test(int a) { return a; }
};
std::function< int(int)> F;
int main()
{
F = Test;
F(10);
Test2 T;
F = T;
F(10);
F = Test3;
F(10);
TestClass TClass;
std::function< int(int)> TFunc = std::bind(&TestClass::Test, TClass, std::placeholders::_1);
}
通过上面的例子,可以看出,其中所指的可调用实体可以为普通函数、lambda泛型函数、仿函数、类成员函数等。解释下bind,实际上是一种将类的成员函数绑定到类对象和参数的方法。placeholders是表示不绑定对象,后面的数字表示对象的位置。再给一个简单的例子,绑定普通函数。
int Test(int x, int y);
auto func = std::bind(Test, 10, std::placeholders::_1);
func(20);
执行func(20)
实际上相当于执行了Test(10,20);
11. 右值引用
右值引用以&&表示。可以优化代码,减少时间。
可参考我的另一篇文章C++右值引用 。不再多余叙述。
使用C++11的新语法,做的回溯生成数独的算法,可参考 [C++] 回溯法生成数独 。
代码通过Visual Studio 2015测试。