C++11新特性简介

目录

功能扩展与增强

右值概念

类中右值扩展

标准库中右值扩展

内联命名空间

初始化

initialzier_list

原始字符串

自定义字面值

类型自动推导

auto

decltype

常量表达式函数constexpr

变长模板

空指针nullptr

类定义中新增关键字

增强using

别名

引入父类函数

智能指针

多线程

模板元编程

递归

type_traits库


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_开头,都为值元函数;
  • 转换元数据:为标准元函数,返回转换后的类型;
  • 解析函数元数据:为非标准元函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值