c++11特性学习总结
ubuntu 16.04 自带gcc 5.4 支持c++11
ubuntu 18.04 自带gcc 7.3 支持c++14
查看编译器支持:
c++11 feature
- nullptr/constexpr
- enum class
- auto/decltype
- for iteration
- initialize_list
- lamda
- template
- rvalue/move
nullptr
以前的编译器实现,可能会把NULL定义为0.所以,当你有两个同名函数foo(int),foo(char*)时,foo(NULL)你的本意可能是调用后者,但实际调用的是前者.nullptr的引入就是为了解决这个问题.
void foo(char *ch)
{
std::cout << "call foo(char*)" << std::endl;
}
void foo(int i)
{
std::cout << "call foo(int)" << std::endl;
}
void test_nullptr()
{
if (NULL == (void *)0)
std::cout << "NULL == 0" << std::endl;
else
std::cout << "NULL != 0" << std::endl;
foo(0);
//foo(NULL); // 编译无法通过
foo(nullptr);
}
constexpr
常量表达式的引入是为了提高性能,将运行期间的行为放到编译期间去完成.如下代码
constexpr long int fib(int n)
{
return (n <= 1)? n : fib(n-1) + fib(n-2);
}
void test_constexpr()
{
auto start = std::chrono::system_clock::now();
const long int res = fib(30);
auto end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed_seconds = end-start;
cout << "elapsed_seconds:"<<elapsed_seconds.count()<<endl;
start = std::chrono::system_clock::now();
long int res2 = fib(30);
end = std::chrono::system_clock::now();
elapsed_seconds = end-start;
cout << "elapsed_seconds:"<<elapsed_seconds.count()<<endl;
由于传给fib(int n)的参数是30,是个固定的值.所以可以在编译期间就求出来.当我们用const long和long声明返回值时,对前者会在编译期就做计算优化.如下图,可以看到二者运行时间有数量级上的差异.
enum class
c++11中把枚举认为是一个类,以前的标准中枚举值就是一个整数而已.看如下代码
void test_enum()
{
enum color {black,white};
//auto white = true; //redeclared
enum class Color{r,g,b};
auto r = 1;
}
以前的标准中enum color {black,white};相当于定义了两个int变量black,white.所以//auto white = true;编译期会报错.引入了enum class则不再有这个问题.
auto/decltype
这个是c++11中非常重要的一点特性,极大地简化了编码的复杂.编译期自动去推导变量的类型.再也不需要我们操心了.
auto做变量类型推导,decltype做表达式类型推导.
void test_auto()
{
std::vector<int> v;
v.push_back(1);
v.push_back(2);
for (std::vector<int>::iterator it = v.begin(); it != v.end(); it++)
{
cout << *it << endl;
}
for (auto it = v.begin(); it != v.end(); it++)
{
cout << *it << endl;
}
for (auto &i : v)
{
cout << i << endl;
i = 100; //修改掉v中的元素值
}
for (auto i : v)
{
cout << i << endl; //输出100
i = 200; //不会修改v中的元素值
}
for (auto i : v)
{
cout << i << endl; //输出为100
}
}
用法如上述代码所示.比如遍历vector,写法由for (std::vector::iterator it = v.begin(); it != v.end(); it++)简化到for (auto it = v.begin(); it != v.end(); it++),如果配合上for迭代,则进一步简化到for (auto &i : v).
注意c++11中,auto变量自动推导有2个例外
- //int add(auto x,auto y); //c++14才支持函数参数为auto
- //auto arr[10] = {0}; //编译错误 auto不能用于数组类型的推导
decltype做表达式类型推导.假设我们要写一个加法的模板函数,比如
template<typename R, typename T, typename U>
R add(T x, U y)
{
return x+y;
}
对于返回值类型R的话,我们必须在模板的参数列表中手动指明.调用的时候形式则为add<R,T,U>(x,y).而很可能我们并不知道返回类型是什么,比如我使用一个第三方库,我只是想对x,y做一个add操作,后续我可能会从x,y取一些数据做后续处理,此时我并不关心add操作返回值类型是什么.
c++11中用decltype自动推导表达式类型解决这个问题.
template<typename T, typename U>
auto add_cxx11(T x, U y) -> decltype(x+y)
{
return x+y;
}
用decltype(x+y)声明返回值类型,让编译器自动推导就好了.
在c++14中,有了更好的支持,已经不再需要显示地声明返回值类型了.
//c++14支持
/*
template<typename T, typename U>
auto add_cxx14(T x, U y)
{
return x+y;
}
*/
for迭代
基于范围的for迭代,非常类似与python中的用法了.代码在前面auto/decltype一节已经展示.需要注意的是for (auto i : v)拿出的i是副本,不会修改v中的元素的值.for (auto &i : v)拿到的是引用,会修改掉v中的值.
初始化列表
c++11之前对象的初始化并不具有统一的表达形式,比如
int a[3] = {4,5,6}
/*
以前类的初始化只能通过拷贝构造函数或者()
比如 */
class A
{
A(int x,int y,int z)
}
A b;
A a(b);
A a2(1,2,3)
c++11提供了统一的语法来初始化任意对象.
比如
class XX
{
public:
XX(std::initializer_list<int> v):v_int(v)
{
}
vector<int> v_int = {3,4,5};
};
XX xxxxxx = {6,7,8,9,10};
或者
struct A
{
int a_;
int b_;
};
A a {1,2};
此外,初始化列表还可以作为函数的入参.
void f_take_initialize(initializer_list<int> list)
{
int sum = 0;
for (auto l : list)
{
sum += l;
}
cout << sum << endl;
}
void test_initialize()
{
f_take_initialize({1, 2, 3});
}
using关键字
using并不是c++11才有的,但是c++11中提升了这个关键字的功能,用于取代typedef,提供更加统一的表达形式.
template <typename T, typename U>
class SuckType;
typedef SuckType<std::vector<int>, std::string> NewType;
using NewType = SuckType<std::vector<int>, std::string>;
typedef int(*mycallback)(void*);
using mycallback = int(*)(void*);
lamda表达式
即匿名函数,这也是c++11中一个相当重要的特性.有的时候,我们可能需要使用某个功能,但这个功能可能只在某一个函数内部有用,那么我们则没有必要去写一个全局函数或者类的成员函数去抽象这个功能.这时候就可以实现一个匿名函数.
[捕获列表](参数列表) -> 返回类型
{
// 函数体
}
匿名函数的形式如上所示.参数列表,返回类型都很好理解.默认情况下,匿名函数是不可以使用匿名函数外部的变量的,捕获列表就起到一个传递外部参数的作用
捕获列表有下述几种情况
- 值捕获
- 引用捕获
- 隐式捕获
- 表达式捕获 //c++14支持
值得注意的是,值捕获的话,值是在匿名函数定义好的时候就做传递,而不是调用的时候做传递.
如下代码
void test_lamda()
{
auto add_func = [](int x,int y) -> int{return x+y;};
auto sum = add_func(3,4);
cout<<"sum="<<sum<<endl;
int a = 1;
auto copy_a = [a]{return a;};
a = 100;
auto a_i_get = copy_a();
printf("a_i_get=%d,a=%d\n",a_i_get,a);
auto copy_a_refrence = [&a]{a=50;return a;};
auto a_refrence_i_get = copy_a_refrence();
printf("a_i_get=%d,a=%d\n",a_refrence_i_get,a);
int b = 1;
auto f = [&](){return b;} ;
}
注意,第一个输出的a_i_get=1而不是100.尽管a是100.这是因为在copy_a定义的时候,a的值是1. copy_a_refrence的捕获列表是引用,函数体内修改a=50,所以输出的是50
当要捕获的变量非常多的时候,一个个写是非常麻烦的,所以可以直接在捕获列表里用=和&表示传值和传引用
[](){} //传空
[=](){} //传值
[&](){} //传引用
变长模板
template<typename... Ts> class Magic;
c++11之前,模板的参数是固定个数的.c++11之后支持不定长参数的模板.用...表示不定长.
c++11标准库新引入的数据结构tuple就是用了这个特性实现的.
move语义和右值引用.
这也是c++11中引入的非常重要的一个特性.主要作用在于性能的提升.
通俗地讲,一个可以取地址的变量,即为左值,不可以取地址的即为右值.
以之前的vector.push_back()为例,插入的是数据的一份拷贝.当要插入的数据结构本身内存特别大的时候,这种拷贝带来的性能消耗是非常大的.
move的引入即用于解决此类问题,move()将一个值转换为右值. 标准库的数据结构里实现了void push_back( T&& value );的版本.
move()名字叫move,但他本身并不做任何move的操作,move更类似于浅拷贝,即拷贝待拷贝内容的地址过去.但是浅拷贝并不会使得之前的对象失去所有权,而move会,所以move所做的事情就是资源的所有权的转移
先看一下这段代码
void test_move()
{
std::string str = "Hello world.";
std::vector<std::string> v;
// 将使用 push_back(const T&), 即产生拷贝行为
v.push_back(str);
// 将输出 "str: Hello world."
std::cout << "str: " << str << std::endl;
// 将使用 push_back(const T&&), 不会出现拷贝行为
// 而整个字符串会被移动到 vector 中,所以有时候 std::move 会用来减少拷贝出现的开销
// 这步操作后, str 中的值会变为空
v.push_back(std::move(str));
// 将输出 "str: "
std::cout << "str: " << str << std::endl;
}
即我们前面提到的"hello world"这些内容的所有权的转移.move之后,原先的str已经失去了所有权,打印为空.
再来看一段代码.
void test_move_efficience()
{
vector<int> v_i;
for(auto i = 0;i<10000000;i++)
{
v_i.push_back(i);
}
auto f = [&](vector<int> v)
{
return v;
};
auto g = [&](vector<int>& v)
{
return v;
};
auto start = std::chrono::system_clock::now();
f(v_i);
auto end = std::chrono::system_clock::now();
std::chrono::duration<double,std::milli> elapsed_seconds = end-start;
cout << "f(v_i) elapsed_seconds:"<<elapsed_seconds.count()<<endl;
start = std::chrono::system_clock::now();
f(move(v_i));
end = std::chrono::system_clock::now();
elapsed_seconds = end-start;
cout << "f(move(v_i)) elapsed_seconds:"<<elapsed_seconds.count()<<endl;
start = std::chrono::system_clock::now();
g(v_i);
end = std::chrono::system_clock::now();
elapsed_seconds = end-start;
cout << "g(v_i) elapsed_seconds:"<<elapsed_seconds.count()<<endl;
list<int> li;
for(auto i = 0;i<10000000;i++)
{
li.push_front(i);
li.push_back(i+1);
}
vector<list<int>> vl;
start = std::chrono::system_clock::now();
vl.push_back(li);
end = std::chrono::system_clock::now();
elapsed_seconds = end-start;
cout << "vl.push_back(li) elapsed_seconds:"<<elapsed_seconds.count()<<endl;
start = std::chrono::system_clock::now();
vl.push_back(move(li));
end = std::chrono::system_clock::now();
elapsed_seconds = end-start;
cout << "vl.push_back(move(li)) elapsed_seconds:"<<elapsed_seconds.count()<<endl;
}
先贴输出
我们知道,函数传参的时候,值传递的话,实际上是做了一份参数的拷贝.所以f(v_i)的开销是最高的,达15ms,f(move(v_i))的话,没有了拷贝的操作,耗时1ms.对于g(v_i)的话,参数为引用,没有拷贝操作,耗时只有0.000594ms. 因为不管是f(),g(),函数内部都是非常简单的,没有任何操作,而move()本身会带来一定消耗,所以f(move(v_i))耗时比g(v_i)更高.
而move语义更常见的使用在于标准库里的封装.比如上述代码,vl.push_back(li)和vl.push_back(move(li))调用了不同的push_back(). 其性能是有着数量级的差异. 以我们的例子为例,其耗时分别为1207ms和0.001548ms.
所以如果我们自己的数据类型,内部含有大量的数据,我们应当自己去实现move构造函数.这样使用标准库时才可以更好的提高性能.
c++11特性学习总结的更多相关文章
- C++开发者都应该使用的10个C++11特性
转载自http://blog.jobbole.com/44015/ 在C++11新标准中,语言本身和标准库都增加了很多新内容,本文只涉及了一些皮毛.不过我相信这些新特性当中有一些,应该成为所有C++开 ...
- 转载:每个C++开发者都应该使用的十个C++11特性
这篇文章讨论了一系列所有开发者都应该学习和使用的C++11特性,在新的C++标准中,语言和标准库都加入了很多新属性,这篇文章只会介绍一些皮毛,然而,我相信有一些特征用法应该会成为C++开发者的日常用法 ...
- C++开发者都应该使用的10个C++11特性 转
http://blog.jobbole.com/44015/// | 分类: C/C++, 开发 | 条评论 | 标签: C++, C语言 分享到: 本文由 伯乐在线 - 治不好你我就不是兽医 翻译自 ...
- 开发者都应该使用的10个C++11特性
摘要: 在C++11新标准中,语言本身和标准库都增加了很多新内容,本文只涉及了一些皮毛.不过我相信这些新特性当中有一些,应该成为所有C++开发者的常规装备.你也许看到过许多类似介绍各种C++11特性的 ...
- C++11并发学习之三:线程同步(转载)
C++11并发学习之三:线程同步 1.<mutex> 头文件介绍 Mutex又称互斥量,C++ 11中与 Mutex 相关的类(包括锁类型)和函数都声明在 <mutex> 头文 ...
- java8 新特性学习笔记
Java8新特性 学习笔记 1主要内容 Lambda 表达式 函数式接口 方法引用与构造器引用 Stream API 接口中的默认方法与静态方法 新时间日期 API 其他新特性 2 简洁 速度更快 修 ...
- 201521123082 《Java程序设计》第11周学习总结
201521123082 <Java程序设计>第11周学习总结 标签(空格分隔):java 1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. Answe ...
- 201521123067 《Java程序设计》第11周学习总结
201521123067 <Java程序设计>第11周学习总结 1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 2. 书面作业 本次PTA作业题集多线 ...
- 201521123045 <java程序设计>第11周学习总结
201521123045 <java程序设计>第11周学习总结 1. 本周学习总结 2. 书面作业 2. 书面作业 Q1.1.互斥访问与同步访问完成题集4-4(互斥访问)与4-5(同步访问 ...
随机推荐
- Spring的依赖注入和管理Bean
采用Spring管理Bean和依赖注入 1.实例化spring容器 和 从容器获取Bean对象 实例化Spring容器常用的两种方式: 方法一: 在类路径下寻找配置文件来实例化容器 [推荐使用] Ap ...
- C#实现Hash应用全解
1.引言 HASH是根据文件内容的数据通过逻辑运算得到的数值, 不同的文件(即使是相同的文件名)得到的HASH值是不同的. 通过一定的哈希算法(典型的有MD5,SHA-1等),将一段较长的数据映射为较 ...
- Mysql的行级锁与表级锁
在计算机科学中,锁是在执行多线程时用于强行限制资源访问的同步机制,即用于在并发控制中保证对互斥要求的满足. 在DBMS中,可以按照锁的粒度把数据库锁分为行级锁(INNODB引擎).表级锁(MYISAM ...
- TensorFlow学习笔记——深层神经网络的整理
维基百科对深度学习的精确定义为“一类通过多层非线性变换对高复杂性数据建模算法的合集”.因为深层神经网络是实现“多层非线性变换”最常用的一种方法,所以在实际中可以认为深度学习就是深度神经网络的代名词.从 ...
- Activiti6系列(4)- 三个war包的数据源及密码修改
一.activiti-app修改数据源和密码 1.使用sublimetext工具打开tomcat,方便进行配置文件的修改. 找到被解压的war包,activiti-app/WEB-INF/classe ...
- Java中...的作用
Java中...的作用,代表接收若干个相同类型的参数 public void testFunction(int...arr){ //接收若干个int类型的参数 for (int i:ar ...
- java中File IO流的笔记
1.File文件的属性和操作 boolean exists( ) 判断文件或目录是否存在boolean isFile( ) 判断是否是文件boolean isDirectory( ) 判断是否是目 ...
- 《深入理解Java虚拟机》-Java代码是如何运行的
问题一:Java与C++区别 1.Java需要运行时环境,包括Java虚拟机以及Java核心类库等. 2.C++无需额外的运行时,通常编译后的代码可以让机器直接读取,即机器码 问题一:Java为什么要 ...
- java封装 redis 操作 对象,list集合 ,json串
/** * 功能说明: * 功能作者: * 创建日期: * 版权归属:每特教育|蚂蚁课堂所有 www.itmayiedu.com */package com.redis.service; import ...
- TCP/UDP对比总结
目录 1 TCP-UDP对比 2 UDP介绍 3 TCP介绍 3.1 可靠传输的原理和实现 3.1.1 可靠传输原理 3.1.2 可靠传输实现 3.2 TCP面向连接管理 3.2.1 建立连接 3.2 ...