目录
C++11标准为C++编程语言的第三个官方标准,于2011年8月12日公布。语言本身和标准库都增加了很多新内容。一个C++程序必须恰好包含一个main函数,所有C++实现都支持以下两种声明:
int main()
int main(int agrc, char* argv[])
功能扩展与增强
C++11中扩展与增强了很多新功能。
右值概念
C++11中引入右值,以便进行转移优化。使用T&&表示右值引用,标记此对象为右值。函数参数若是T&&表示总保持原型不变(左引用保持左引用,右引用保持右引用)。
- 所有表达式的结果不是左值就是右值;
- 左值(lvalue)是一个函数或者对象实例;
- 失效值(xvalue,eXpiring value)是生命周期即将结束的对象;
- 广义左值(glvalue,generalized lvalue)包括左值和失效值;
- 右值(rvalue)包括失效值、临时对象、以及不关联对象的值(如字面值);
- 纯右值(prvalue)是非失效值的那些右值;
通俗解释:左值是一个可以用来存储数据的变量,有实际的内存地址(即变量名),表达式结束后依然存在;右值是一个‘匿名’的‘临时’变量,他在表达式结束时生命周期终止,不能存放数据。
int nNum = 0; // ++x为左值,x++为右值;
int *p=++x;
int &lr=++x;
int &&rr=x++;
++x = 5;
// p=x++; //非法
类中右值扩展
移动语义就是把右值移为己用
,把一个对象的资源移动给另外一个对象,避免不必要的拷贝。在类中通过移动构造函数(接受一个右值引用参数,非const的)实现。
T(T&& other){...}
T& operator=(T&& other){...}
拷贝构造函数、拷贝赋值函数、移动赋值函数、析构函数,只要自定义其中的任何一个,编译器将不再生成默认的移动构造函数。默认的移动构造函数函数实际与默认拷贝构造函数一样,只是做一些按位拷贝工作。
标准库中右值扩展
针对标准容器,增加了emplace**()系列函数,可以通过转移语义直接插入元素,提高性能。同时新增:
- std::move()用来标记右值,便于优化。传递的参数若是要作为右值使用,必须使用move()标记。
- std::forward()完美转发,用于保留右值标记。当右值需要再次传递给其他函数使用时,必须使用forward()以保留右值标记。
template<typename T>
void swap(T& first, T& second){
T tmp(move(first));
a = move(second);
second = move(tmp);
}
内联命名空间
在匿名空间之外,新增内联(inline)命名空间;使外部程序无需限定名直接访问命名空间内的成员。
namespace release{
namespace V10{
int getCount();
}
inline namespace V20{
int getCount();
}
}
release::getCount(); // 调用的是V20下的,可以用于版本控制
初始化
C++11中,统一了初始化;使用初始化列表{}
统一初始化各种类型的数据(自定义、简单、集合等),并禁止类型自动窄化转换。
int a=1.2; // 正确,会截断
int a{1}; // 初始化列表
int a{1.2}; // 报错,类型不匹配
int *arr = new int[] {1, 2, 3};
int i = 1;
bool b1 = i; // 警告
bool b{ i }; // 错误,可改为{i!=0}
char *p{}; // 初始化为默认值(nullptr)
vector<int> v1{99}; // 只有99一个元素
vector<int> v2(99); // 有99个元素,且取默认值0
int *pCount=new int(1);
double *pTotal=new double{1.2f};
同时C++11中增强了类成员的初始化方式,类中的非静态成员也可以直接在类中定义时初始化(不用必须在构造函数上,通过初始化列表)
class initTest{
int a=1;
double b{1.2};
};
initialzier_list
标准模板库中提供了只需要声明以initialzier_list为参数的构造函数,即可使自定义类拥有初始化列表能力。除此之外,也可用于普通函数的参数上,使函数拥有接收列表的能力。
class Person{
public:
Person{initializer_list<pair<string,Gender>> lstInit_){
for(auto v=lstInit_.begin(); v!=lstInit_.end(); ++v)
m_data.push_back(*v);
}
private:
vector<pair<string, Gender>> m_data;
}
double sum(initializer_list<double> L){
...
}
double tol = sum({1.0, 2, 3, 4.5});
原始字符串
C++11新增的原始字符串raw,可方便用于正则表达式中。原始字符串中的字符表示的就是自己,例如"\n"表示的不是换行符,而是两个字符:斜杠和n。
R"(…)"括号内的所有的字符会原样保留(不进行特殊字符转义);为避免与字符串中的内容冲突,也可使用扩展形式:在圆括号两边加上最多16个字符delimiter(括号两边必须相同);
R"===(^\d+\w+)==="; //等价"^\\d+\\w+"
自定义字面值
用户自定义字面值,或者叫“自定义后缀”更直观些,其主要作用是简化代码的读与写。重载新的操作符(""),即连续的两个双引号;当编译器发现字面值使用了自定义后缀时会转换为函数调用。
- 必须是以下划线开始的(因为没有下划线的后缀是留给std用的);
- 参数为需为char, char16_t, char32_t, unsigned long long等固定的几种形式;
int operator"" _kb(unsigned long long nSize){
return (int)nSize*1024;
}
auto x=2_kb; //2048
类型自动推导
auto
auto用于根据初始值来推导类型,从而获得定义变量的类型,所以说 auto 定义的变量必须有初始值。使用auto也能在一个语句中声明多个变量,因为一个声明语句只能有一个基本数据类型,所以该语句中所有变量的初始基本数据类型(修饰符可不一样)都必须是一样的。
- 只能用于赋值语句里的类型推导;
- auto总是推断出值类型(非引用);
- 允许使用‘const/volatitle/&/*’等修饰,得到新的类型;
- auto&&总是推断出引用类型。
int i = 3;
auto a = i,&b = i,*c = &i;//正确: a初始化为i的副本,b初始化为i的引用,c为i的指针.
auto sz = 0, pi = 3.14; //错误,两个变量的类型不一样。
decltype
decltype用于推导表达式的数据类型。,编译器只是分析表达式并得到它的类型,却不进行实际的计算表达式的值:
- 从表达式中推断出要定义变量的类型;
- 获取函数的返回类型;
decltype(expressioin):在编译期间计算类型;
decltype(e):获得表达式计算结果的值类型;
decltype((e)):获得表达式计算结果的引用类型,类似auto&&的效果;
int x=0;
decltype(x)& d1=x; // int&
decltype((x)) d2=x; // int&
decltype(&x) d3=&x; // int*
后置函数声明:自动推导返回值类型
template<typename T, typename U>
auto calc(T t, U u) -> decltype(t+u)
{ return t+u; }
常量表达式函数constexpr
在函数返回类型前增加关键字constexpr使其成为常量表达式函数;要成为常量表达式函数,要求:
- 函数体只有单一的return返回语句,且有返回值;
- 使用前必须已有定义;可以递归;
- return返回语句表达式不能使用非常量表达式函数、全局数据,且必须是一个常量表达式;
- 支持浮点运算、三元表达式(?:)、逗号表达式;
constexpr int Fibonacci(int n){
return (n==1)?1:((n==2)?1:Fibonacci(n-1)+Fibonacci(n-2));
}
const auto Fib9=Fibonacci(9);
变长模板
std::tuple是一个泛化的pair;C++11中通过‘模板参数包’(typename…)使模板接受任意多个参数。
std::tuple<double, char, std::string> collects;
std::make_tuple(9.8, 'a', "great");
template<typename... Eles> class MyTuple;
template<typename Head, typename... Tail>
class MyTuple<Head, Tail...> : private<Tail...>{
Head head; };
template<> class MyTuple<> {};
可变参数模板和普通模板的语义是一样的,只是声明可变参数模板时需要在typename或class后面带上省略号...
:
- 声明一个参数包T… args,这个参数包中可以包含0到任意个模板参数;
- sizeof…(T) 可以用来获知参数包中打包了几个参数。
templatevoid fun(T... args);
template class tuple;
空指针nullptr
引入nullptr明确表示空指针概念,代替以前NULL等;
- 可以隐式转化为任意类型的指针,也可以与指针进行比较,但不能转化为非指针类型。
- nullptr并不是指针,而是类型为nullptr_t的编译期常量对象实例。
类定义中新增关键字
类中新增以下关键字,用于增强约束:
- default:显示声明缺省构造/析构等特殊成员函数;不影响其他构造函数的重载与实现;
- delete:显示禁用某些特殊成员函数;
- override:明确标记虚函数重载(放在函数名后面);
- noexcept:放在函数后面,表明不会抛出异常(异常规范已被抛弃);
新增final用于禁止继承
- 类名后面:显示禁止类被继承,即不能有派生类;
- 虚函数后面:显示禁止该函数在子类中再被重载;
- 类成员初始化:非静态类中,允许在定义时即使用赋值或花括号直接初始化;
class demo{
public:
demo() = default;
~demo() = default;
demo(const demo&) = delete;
demo& operator=(const demo&) = delete;
int x=0;
string s="test";
vector<int> v{1,2,3};
}
增强using
别名
using定义类型别名:对于模板,可以通过using简化使用形式。
using alias=type;
template<typename T>
using demo_type=demo<T, long>;
demo_type<char> dTest;
引入父类函数
在C++中可以通过using父类的函数以引入当前类,C++11中可以通过using引入父类的构造函数;同时也可以委派构造函数(即构造函数调用构造函数,此时不能使用初始化列表了,变量赋值只能在函数体内实现了),对委派构造函数可通过try来捕捉其抛出的异常。
class Info{
public:
Info():info(1){m_strName="test";}
Info(int i)
try : Info(1, "a"){
...
}
catch(...){ ... }
private:
int m_nCount;
string m_strName;
Info(int i, string str) { ... }
}
class SubExtend:Info{
public:
using Info::Info;
private:
double m_dNew{1.0};
}
智能指针
C++11中新增了三种智能指针(详细介绍参见:C++11智能指针),在初始化指针后,如果类被析构,那指针所指向的对象会被自动析构,方便了程序员对指针的控制。
- share_ptr:共享的智能指针
- weak_ptr:配合share_ptr使用的弱指针
- unique_ptr:独占的智能指针
多线程
C++11提供了一套精练的线程库,小巧且易用。运行一个线程,可以直接创建一个std::thread的实例,线程在实例成功构造成时启动。若有底层平台支持,成员函数std::thread::native_handle()将可提供对原生线程对象运行平台特定的操作。
主要是通过库, , 等提供支持;并引入新的关键字thread_local实现线程本地存储。
详细介绍参见:C++11多线程简介
模板元编程
模板元编程产生的元程序是在编译器执行的程序,可用的语法相当有限,最常用的:
- enum、static,定义编译期的整数常量;
- typedef、using:定义元数据,最重要的元编程关键字;
- template:元编程的‘起点’,定义元函数;
::
:域运算符,解析类型作用域获取计算结果;
元数据都是不可变的,不能修改的,最常见的是整数和C++的类型;元函数就是一个形式上很像函数的一个模板类,用于计算(推导)类型。
template<int M, int N>
struct metaAdd{
static const int value=M+N;
};
cout<<metaAdd<3, 5>::value<<endl; // 参数只能是常量,不能是变量(在编译期已经完成了所有计算)
模板元编程中不能使用if-else分支语句,主要的手段是模板特化。如下通过特化,保证获取到的类型是指针类型:
template<T>
struct getPointType{
using type=const T*;
};
template<T>
struct getPointType<T*>{
using type=const T*;
};
static_assert(is_same<getPointType<int>, const int*>::value);
递归
使用模板元编程,也可实现类似前面常量表达式函数的递归功能:
template<long num>
struct Fibonacci{
static const long val=Fibonacci<num-1>::val + Fibonacci<num-2>::val;
};
template<> struct Fibonacci<2> { static const long val=1; };
template<> struct Fibonacci<1> { static const long val=1; };
template<> struct Fibonacci<0> { static const long val=0; };
const auto Fib9=Fibonacci(9);
type_traits库
type_traits提供一组特征类,即元函数,可以在编译期确定类型是否具有某些特征(如是否是原生数组、是否是整数等),以及为类型增加或移除修饰词(如const、volatile)。
- 值元函数:以::value返回bool或整数,以is_、has_开头;
- 标准元函数:以::type返回新的元数据(类型);
根据功能可分为:
- 检查元数据的类别:均以is_开头,都为值元函数;
- 检查元数据的属性:大部分以is_、has_开头,都为值元函数;
- 检查元函数间的关系:均以is_开头,都为值元函数;
- 检查操作符重载:均以is_开头,都为值元函数;
- 转换元数据:为标准元函数,返回转换后的类型;
- 解析函数元数据:为非标准元函数。