原文地址:http://www.codeproject.com/Articles/312029/Cplusplus-A-Glance-part-of-n
1、介绍
C++0x现在是一个正式标准了,被称作C++11。ISO C++在2011年正式通过这个标准。这篇文章的目的是浏览C++11的特性和介绍VS2010中支持的特性,且可以作为你研究个别特性的一个开端。因为长度原因,文章被分为了几部分。我自己也十分害怕一次性阅读那么长的文档,再说一次把C++11特性写完将会是工作变得无聊。
这是我在codeproject的第一篇文章,如果有格式或其他错误请包涵。
2、背景
在Charles Babbage发明了差分机后,过了近一个世纪才进入电子计算机时代。在1940s,由于电子计算机处理能力和内存的限制,只有汇编语言。在过了十多年后,1950到1970之间,时代见证了许多编程语言的兴起,其中有些至今还被使用。在1979年,Bjarne Stroustrup在贝尔实验室工作,他的工作时加强C语言,首先增加了类,然后增加了虚函数、运算符重载、多继承、模板异常处理。他把加强的C言语叫做“带类的C语言”。1983年被改名为C++(++可能就是指继承自C语言)。
C++的里程碑
1983-第一个商业化的C++编译器
1998-C++标准化委员会标准化了C++[C++98]
2003-发布了一个补丁包,无新特性[C++03]
2005-一个技术报告“库技术报告”(简称TR1)公布。
2011-引进了许多重要的特性,并且增强了C++标准库
从上面可以看出,这次迭代是有史以来最更改最大的(是的,STL或许增加很多)。
我们需要了解这个新标准吗??
当然要。越快越好。抵抗变化时我们人类的天性。当我们掌握的语言或项目不再变化处于静态时,我们开发者离失业也就不远了。我们喜欢动态变化的项目,对于编程语言也一样。变化时不可避免的,专家委员会经过近十年的头脑风暴的成果显然十分有意义。
及时我们对这次在代码迭代中的更新不感兴趣,快速浏览一下这些特性也会帮助我们避免在编码之前使用旧的编译器。再说,只是简单把编译器换成支持C++11特性的,我们即可享受到C++11带来的好处,因为他的标准库增强了,性能更好了。所以如果你的项目用STL容器或算法,尽快更换编译器。
C++11特性
下面这个表总结了C++11的特性以及VS2010是否支持这些特
特性 | 目的 | vs2010支持? |
auto | 可用性增强 | 支持 |
decltype | 可用性增强 | 支持 |
Trailing return types | 可用性和性能增强 | 支持 |
Right angle brackets | 可用性增强 | 支持 |
static_assert | 可用性增强 | 支持 |
nullptr | 可用性和性能增强 | 支持 |
Strongly typed enums | 类型安全增强 | 部分支持 |
Rvalue references | 性能增强 | 支持 |
Move semantics | 性能增强 | 支持 |
long long | 性能增强 | 支持 |
Override control | 可用性增强 | 支持 |
Lambda expressions | 可用性和性能增强 | 支持 |
preventing narrowing | 性能增强 | 不支持 |
Range based for-loops | 可用性和性能增强 | 不支持 |
array | 支持 |
Really smart pointers [unique_ptr, shared_ptr, weak_ptr] | 支持 |
bind and function | 支持 |
tuple | 支持 |
unoredered_map,unordered_set | 支持 |
forward_list | 支持 |
…… |
C++11特性
auto 关键字
这个特性使C++更加易用。委员会赋予了auto新的含义(提醒读者,旧的auto关键修饰的变量是说明这个变量作用域限于局部,除非有关键字static、extern、static修饰,现在auto隐式转换为自动存储)
新的标准中,auto可以根据初始化的值或初始化表达式来推论其修饰变量的类型。
auto i=5;//i将会定义为int类型
int n=3;
double pi=3.14;
auto j=pi*n;//j将会是double类型
现在来看一个变量类型难以写的例子
//假设有一个Map,存储 int和map(int,int)
map<int, map<int,int> > _Map;
//定义这个map的迭代器
map<int, map<int,int>>::const_iterator itr1=_Map.begin();
//如果使用auto将会变得简单
const auto itr2=_Map.begin();
现在来看一个变量类型不易知的例子
template<class U, class V>
void Somefunction(U u, V v)
{
??? result = u*v; // result的类型是什么呢 ???
// 使用auto的话,让编译器来决定result的类型
auto result = u*v;
}
我将用继续讲解下个特性一遍你们了解auto带来的易用性。当使用lambda表达式时,auto也十分易用(我们很快介绍lambdas)。
关于这个特性的给几个例子:
1、我们可以使用const、volatile、引用&、右值rvalue引用&&(&&-将会很快了解)修饰auto关键
auto k = 5;
auto* pK = new auto(k);
auto** ppK = new auto(&k);
const auto n = 6;
2、auto修饰的变量必须要初始化
auto m; // m 应该初始化
3、auto不能与类型修饰符一起用
auto int p; // 不能这样用
4、函数和模板的参数不能用auto修饰
void MyFunction(auto parameter){} // 不能用auto修饰函数参数
template<auto T> // 没意义-不允许
void Fun(T t){}
5、在堆上定义的变量,如果用auto修饰,必须初始化
int* p = new auto(0); //正确
int* pp = new auto(); // 应该初始化
auto x = new auto(); // Hmmm ... 没初始化
auto* y = new auto(9); // 正确,这里y是int*
auto z = new auto(9); //正确,这里z是int* (不是int)
6、auto是一个类型占位符,但是auto不能定义自己的类型。因此auto不能用来做类中转换或者与sizeof和typeid相关的操作
int value = 123;
auto x2 = (auto)value; // 不能用auto转换
auto x3 = static_cast<auto>(value); // 同上
7、用一个auto修饰的多个变量必须初始化为同样的类型
auto x1 = 5, x2 = 5.0, x3='r'; // 这里太多类型,不能这样用
8、除非修饰引用,auto把变量类型转换为CV修饰符(const和Volatile修饰符)
const int i = 99;
auto j = i; // j 是int类型,而不是const int
j = 100 // 正确,j不是const
// 试一下引用
auto& k = i; // k是const int&
k = 100; // 错误,ks是const
// 对volatile修饰符,同理
9、除非定义引用,否则auto将把数组退化为指针
int a[9];
auto j = a;
cout<<typeid(j).name()<<endl; // 将会输出 int*
auto& k = a;
cout<<typeid(k).name()<<endl; // T将会输出 int [9
return_value decltype(表达式)
return_value是表达式的类型参数
这样可以用来决定表达式的类型,正如Bjarne所暗示的,如果我们只是需要一个变量的类型,使用初始化auto是个简单的选择。如果我们需要非变量的类型,例如
返回值类型,那么decltype就是我们需要的。
现在让我们看看刚才的一个例子
template<class U, class V>
void Somefunction(U u, V v)
{
result = u*v; // result是什么类型呢
decltype(u*v) result = u*v; // 哈哈 ....我们得到了我们想要的
}
在下一节,我将会介绍联合使用auto和decltype来声明模板函数,模板函数的返回值局定于模板的参数使用decltype的几点建议:
1、如果表达式是一个函数,那么decltype的类型是函数的返回类型
int add(int i, int j) { return i+j; }
decltype( add(5,6) ) var = 5; // var的类型是add函数的返回值类型-int
2、如果表达式是左值,那么decltype
struct M { double x; };
double pi = 3.14;
const M* m = new M();
decltype( (m->x) ) piRef = pi;
// 注意,由于内部括号,内部的声明被当做表达式
// 而不是当做成员变量x, x类型是double,且是左值
// declspec是double&,‘m’是const指针
// 所以返回是const double&
// 所以变量piRef的类型是const double&
3、有一点值得注意,decltype不像auto估算表达式,它只是推断表达式
int foo(){}
decltype( foo() ) x; // x是int类型,在运行时不调用foo(
这个是C++11的全新特性。直到现在,函数的返回值类型应在函数名前面。从C++11起,我们可以把函数返回类型放在函数声明的后面,仅仅在替换
auto修饰的返回类型。现在我们来看一下为什么这样做:
template<class U, class V>
??? Multiply(U u, V v) // 怎么确定返回值类型
{
return u*v;
}
我们不能这样做:
template<class U, class V>
decltype(u*v) Multiply(U u, V v) // 因为在Multiply之前,u和v没有定义,怎么办!!
{
return u*v;
}
在这种情况下,我们可以使用auto,一旦u和v定义知道时,我们就可以确定返回值类型。
很酷,不是吗?
template<class U, class V>
auto Multiply(U u, V v) -> decltype(u*v) // 注意->在定义函数括号之后.
{
return u*v;
}
右尖括号
看这个声明,
map<int, vector<int>> _Map;
在早些的编译器中这是错误的,因为在两个右尖括号之间没有空格,编译器将会把它当做右移操作符。
但是C++11编译器将会语法分析模板参数结束时的多重右尖括号,让我们免于在'>'输入空格。
与其他特性相比,这个不一个大的特性。但是C++开发者追求完美,这就是一个例子。
static_assert:
这个宏可以用来检验编译时的错误。编译阶段是相对运行阶段而言的。这个特性可以用来在编译阶段检查程序的常量的值。
这个宏的用法是用一个bool值和string值来测试表达式。如果表达式值为false,那么编译器给出错误,这个错误包含一个string值;如果
表达式为true,那么这个宏没有任何影响。
我们可以在以下地方使用static_assert
A、命名空间/全局作用域
static_assert(sizeof(void *) == 4, "Oops...64-bit code generation is not supported.");
B、类作用域
template<class T, int _n>
class MyVec
{
static_assert( _n > 0 , "How the hell the size of a vector be negative");
};
void main()
{
MyVec<int, -2> Vec_;
// 上面这一行将会抛出如下错误( 在vs2010编译器):
// > \main_2.cpp(120) : error C2338: How the hell the size of a vector be negative
// > main_2.cpp(126) : see reference to class template instantiation 'MyVec<t,_n />'
// being compiled
// > with
// > [
// > T=int,
// > _n=-2
// > ]
// 这个就会不
MyVec<int, 100> Vec_;
}
C、块作用域
template<typename T, int div>
void Divide( )
{
static_assert(div!=0, "Bad arguments.....leading to division by zero");
}
void main()
{
Divide<int,0> ();
// 上面这一行将会产生如下错误
// error C2338: Bad arguments.....leading to division by zero
}
务必记住,static_assert是用来检验编译时错误的,不能用来检查在运行阶段才能确定的值,例如函数的参数。
void Divide(int a, int b)
{
static_assert(b==0, “Bad arguments.....leading to division by zero”);
// 对不起,伙计!上面的不能用static_assert,用其他方法吧
}
static_assert在调试模板时非常有用。如果模板的常量表达式不依赖模板参数,编译器立即计算常量表达式的值。否则在模板被实例化时,编译器计算常量表达式的值。
nullptr:
这个特性被引进主要是考虑到了使用NULL宏时,容易出错。在编译阶段,NULL预处理时替换为0,这容易导致歧义。例如
void SomeFunction(int i){ }
void SomeFunction(char* ch) { }
现在这样调用SomeFunction(NULL)的话,编译器会调用SomeFunction(int i),即使我们想使用NULL指针调用SomeFunction(char* ch)。
为了强制转换,我们必须这样调用SomeFunction( (char*) NULL ) // yak ..ugly
为了避免这样的麻烦,nullptr被引进了。nullptr明确表明是个指针而不是整数。
这样一来就可以安全的表示对象句柄、内部指针或空指针了。
一些特性在第二篇。请参考" C++11 – A Glance [part 2 of n]"
其他特性将会在后面的部分介绍。