C++11 常用新特性总结
C ++ 11新特性
一,什么是C ++ 11
C ++ 11标准由国际标准化组织(ISO)和国际电工委员会(IEC)旗下的C ++标准委员会(ISO / IEC JTC1 / SC22 / WG21)于2011年8月12日公布 ,并于2011年9月出版。2012年2月28日的国际标准草案(N3376)是最接近于C ++ 11标准的草案(仅编辑上的修正)。此次标准为C ++ 98发布后13年来第一次重大修正。
C ++ 11 是曾经被叫做C ++ 0x ,是对目前C ++ 语言的扩展和修正,C ++ 11 不仅包含核心语言的新机能,而且扩展了C ++ 的标准程序库(STL ),并入了大部分的C ++ Technical Report 1 (TR1 )程序库(数学的特殊函数除外)。
C ++ 11 包括大量的新特性:包括lambda 表达式,类型推导关键字auto ,decltype ,和模板的大量改进。
二,新的关键字
auto decltype nullptr
1 ,自动
C ++ 11 中引入自动第一种作用为为了自动类型推导
汽车的自动类型推导,用于从初始化表达式中推断出变量的数据类型。通过汽车的自动类型推导,可以大大简化我们的编程工作
汽车实际上实在compile-网络出版总库时对变量进行了类型推导,所以不会对程序的运行效率造成不良影响
另外,似乎汽车并不会影响compile-速度,因为编译时本来也要右侧推导然后判断与左侧是否匹配。
auto a;// 错误,auto是通过初始化表达式进行类型推导,如果没有初始化表达式,就无法确定a类型
auto i = 1;
auto d = 1.0;
auto str = "Hello World";
auto ch = 'A';
auto func = less<int>();
vector<int> iv;
auto ite = iv.begin();
auto p = new foo() // 对自定义类型进行类型推导
汽车不光有以上的应用,它在模板中也是大显身手,比如下例这个加工产品的例子中,如果使用不汽车就必须声明产品这一模板参数:
template <typename Product, typename Creator>
void processProduct(const Creator& creator) {
Product* val = creator.makeObject();
// do somthing with val
}
如果使用auto ,则可以这样写:
template <typename Creator>
void processProduct(const Creator& creator) {
auto val = creator.makeObject();
// do somthing with val
}
抛弃了麻烦的模板参数,整个代码变得更加正解了。
2 ,decltype
decltype 实际上有点像auto 的反函数,auto 可以让你声明一个变量,而decltype 则可以从一个变量或表达式中得到类型,有实例如下:
- int x = 3;
- decltype(x)y = x;
有人会问,decltype 的实用之处在哪里呢,我们接着上边的例子继续说下去,如果上文中的加工产品的例子中我们想把产品作为返回值该怎么办呢?我们可以这样写:
template <typename Creator>
auto processProduct(const Creator& creator) -> decltype(creator.makeObject())
{
auto val = creator.makeObject();
// do somthing with val
}
3 ,nullptr
nullptr 是为了解决原来C ++ 中NULL 的二义性问题而引进的一种新的类型,因为NULL 实际上代表的是0 ,
void F(int a){
cout<<a<<endl;
}
void F(int *p){
assert(p != NULL);
cout<< p <<endl;
}
int main(){
int *p = nullptr;
int *q = NULL;
bool equal = ( p == q ); // equal的值为true,说明p和q都是空指针
int a = nullptr; // 编译失败,nullptr不能转型为int
F(0); // 在C++98中编译失败,有二义性;在C++11中调用F(int)
F(nullptr);
return 0;
}
三,新特性
1 ,序列为循环
在C ++ 中for 循环可以使用类似java 的简化的for 循环,可以用于遍历数组,容器,string 以及由begin 和end 函数定义的序列(即有Iterator ),示例代码如下:
map<string, int> m{{"a", 1}, {"b", 2}, {"c", 3}};
for (auto p : m){
cout<<p.first<<" : "<<p.second<<endl;
}
2 ,更加优雅的初始化方法
在引入C ++ 11 之前,只有数组能使用初始化列表,其他容器想要使用初始化列表,只能用以下方法:
int arr[3] = {1, 2, 3}
vector<int> v(arr, arr + 3);
在C ++ 11 中,我们可以使用以下语法来进行替换:
int arr[3]{1, 2, 3};
vector<int> iv{1, 2, 3};
map<int, string>{{1, "a"}, {2, "b"}};
string str{"Hello World"};
3 ,Lambda 表达式
拉姆达表达式类似的的JavaScript 中的闭包,它可以用于创建并定义匿名的函数对象,以简化编程工作。拉姆达的语法如下:
[ 函数对象参数] (操作符重载函数参数)- > 返回值类型{ 函数体}
vector<int> iv{5, 4, 3, 2, 1};
int a = 2, b = 1;
for_each(iv.begin(), iv.end(), [b](int &x){cout<<(x + b)<<endl;}); // (1)
for_each(iv.begin(), iv.end(), [=](int &x){x *= (a + b);}); // (2)
for_each(iv.begin(), iv.end(), [=](int &x)->int{return x * (a + b);});// (3)
- [] 内的参数指的是LAMBDA 表达式可以取得的全局变量。 (1)函数中的b 就是指函数可以得到在LAMBDA 表达式外的全局变量,如果在[] 中传入= 的话,即是可以取得所有的外部变量,如(2 )和(3 )Lambda 表达式
- ()内的参数的英文每次调用函数时传入的参数。
- - > 后加上的是Lambda 表达式返回值的类型,如(3 )中返回了一个int 类型的变量
4 ,变长参数的模板
我们在C ++ 中都用过pair ,pair 可以使用make_pair 构造,构造一个包含两种不同类型的数据的容器。比如,如下代码:
- auto p = make_pair(1, “C ++ 11” );
由于在C ++ 11 中引入了变长参数模板,所以发明了新的数据类型:tuple ,tuple 是一个N 元组,可以传入1 个,2 个甚至多个不同类型的数据
auto t1 = make_tuple(1, 2.0, "C++ 11");
auto t2 = make_tuple(1, 2.0, "C++ 11", {1, 0, 2});
就这样避免了从前的一对中嵌套成对的丑陋做法,使得代码更加整洁
另一个经常见到的例子是打印函数,在Ç 语言中的printf 可以传入多个参数,在C ++ 11 中,我们可以用变长参数模板实现更简洁的打印
template<typename head, typename... tail>
void Print(Head head, typename... tail) {
cout<< head <<endl;
Print(tail...);
}
打印中可以传入多个不同种类的参数,如下:
- 打印(1,1.0, “C ++ 11” );
Template<typename …_Args>
void fun(_Args …args)
{
cout<<sizeof …(args)<<endl;
}
Int main()
{
fun(1,2,3,4,5);
return 0;
}
5 ,alignof 和 alignas
C ++ 11 在新标准中为了支持对齐,引入了两个关键字:操作符alignof ,对齐描述符alignas 。
操作符alignof 的操作数表示一个定义完整的自定义类型或者内置类型或者变量,返回的值是一个为size_t 类型的整数常量,
Alignas可以用来改变一个数据类型的对齐属性
1,offsetof可以求得结构体或类里面各成员的偏移量
#include<stddef.h>
Typedef struct Test
{
char a,
int b;
}Test;
int main()
{
Cout<<”a offset : ”<<offsetof(Test, a)<<endl;
Cout<<”b offset : ”<<offsetof(Test, b)<<endl;
Return 0;
}
重要特征介绍
为什么需要拉姆达函数
匿名函数是许多编程语言都支持的概念,有函数体,没有函数名。1958 年,口齿不清首先采用匿名函数,匿名函数最常用的是作为回调函数的值。正因为有这样的需求,C ++ 引入了拉姆达函数,你可以在你的源码中内联一个拉姆达函数,这就使得创建快速的,一次性的函数变得简单了。例如,你可以把拉姆达函数可在参数中传递给的std ::排序函数
#include <algorithm>
#include <cmath>
void abssort(float* x, unsigned N)
{
std::sort(x, x + N,
// Lambda expression begins
[](float a, float b) {
return std::abs(a) < std::abs(b);
});
}
你可能会问,使用函数对象不是也可以吗?是的,函数对象当然没问题,自己写的回调函数,你可以传个函数指针也没有问题。他们有优点也有缺点。函数对象能维护状态,但语法开销大,而函数指针语法开销小,却没法保存范围内的状态。如果你觉得鱼和熊掌不可兼得,那你可错了。拉姆达函数结合了两者的优点,让你写出优雅简洁的代码。
基本拉姆达语法
基本形式如下:
[capture](参数) - > return-type {body}
[] 叫做捕获说明符,表示一个lambda 表达式的开始。接下来是参数列表,即这个匿名的lambda 函数的参数,- > return-type 表示返回类型,如果没有返回类型,则可以省略这部分。想知道为什么返回类型可以这么表示,这涉及到C ++ 11 的另一特性,参见自动类型推导,最后就是函数体部分了。
我们可以这样输出“你好,世界”
auto func = [](){cout <<“你好,世界”; };
FUNC(); //现在调用该函数
变量捕获与拉姆达闭包实现
字符串名称;
cin >> name;
[&](){cout << name;}();
拉姆达函数能够捕获拉姆达函数外的具有自动存储时期的变量。函数体与这些变量的集合合起来叫闭包。
- []不截取任何变量
- [&}截取外部作用域中所有变量,并作为引用在函数体中使用
- [=]截取外部作用域中所有变量,并且贝一份在函数体中使用
- [=,&foo]截取外部作用域中所有变量,并且复制贝一份在函数体中使用,但是对foo变量使用引用
- [bar]截取bar变量并且拷贝一份在函数体中使用,同时不截取其他变量
- [x,&y] x按值传递,y按引用传递
- [本]截取当前类中的这个指针。如果已经使用了&或者=就默认添加此选项。
看到这,不禁要问,这魔法般的变量捕获是怎么实现的呢?原来,lambda 是通过创建个小类来实现的。这个类重载了操作符(),一个lambda 函数是该类的一个实例。当该类被构造时,周围的变量就传递给构造函数并以成员变量保存起来。看起来跟函数对象很相似。
最后,lambda 函数的类型是什么呢,答案是std:function 。