C++11新特性

C++11是对C++的一次巨大的改进和扩充。

1. 列表初始化

在C++98中,不同的对象有着不同的初始化方法。对普通数组和POD(plain old data,没有构造、析构和虚函数的类或结构体)类型可以使用{ } 进行初始化,也就是我们所说的初始化列表。而对于类对象的初始化,要么需要通过拷贝构造、要么就需要使用 () 进行。对于自定义的类型,如vector,若要初始化其内容,则需要每次进行push_back 或使用迭代器去初始化,这是极其不便的。C++11 将会提供一种统一的语法初始化任意的对象,它扩充了初始化串行语法。

C++98中的初始化风格,大体有如下形式:

int a = 2; //"赋值风格"的初始化
int aa [] = { 2, 3 }; //用初始化列表进行的赋值风格的初始化
complex z(1, 2); //"函数风格"的初始化

C++11首先把初始化列表的概念绑定到了类型上,并将其称之为 initializer_list,允许构造函数或其他函数像参数一样使用初始化列表,这就为类对象的初始化与普通数组和 POD 的初始化方法提供了统一的桥梁。C++ 11 中,允许通过以花括号的形式来调用构造函数。这样多种对象构造方式便可以统一起来了:

int a = { 2 };
int aa [] = { 2, 3 };
complex z = { 1, 2 };

另外,可以使用类模板initializer_list作为构造函数的参数,则初始化列表就智能用于构造该函数。值得注意的是列表中的元素必须是同一种类型或者可以转化为同一种类型。STL中提供了initializer_list作为参数构造函数,考虑vector的下列实例:

vector<int> a1(10);         // 定义了大小为10未初始化的int型向量
vector<int> a2{10};        // 使用initializer-list初始化,a2只有一个元素初始化为10
vector<int> a3{1, 2, 3};  // 使用initializer-list初始化,a3有三个元素1,2,3

更具体的描述可见另一转载的文章 C++11新特性之列表初始化

2. 类型推导

在标准C++,使用变量必须明确的指出其类型。然而,随着模版类型的出现以及模板超编程的技巧,某物的类型,特别是函数定义明确的返回类型,就不容易表示。在这样的情况下,将中间结果存储于变量是件困难的事,可能会需要知道特定的超编程程序库的内部情况。C++11 提供两种方法缓解上述所遇到的困难。

auto
  • auto 能够实现类型的自我推导,并不代表一个实际的类型声明。
  • auto 只是一个类型声明的占位符。
  • auto 声明的变量,必须马上初始化,以让编译器推断出它的实际类型,并在编译时将 auto 占位符替换为真正的类型。
  • 用auto声明指针类型肘,auto和auto*没有任何区別,但用auto声明引用类型吋必须加&
  • 在同一行定义多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其它变量
  • auto 不能用于函数传参,考虑重载的问题,我们应该使用模板。
  • auto 不能用于推导数组类型
  • 使用auto关键字声明变量的类型,不能自动推导出顶层的CV-qualifiers和引用类型,除非显示声明;
  • 使用auto关键字进行类型推导时,如果初始化表达式是引用类型,编译器会去除引用,除非显示声明;
  • 使用auto关键字进行类型推导时,编译器会自动忽略顶层const,除非显示声明;
int main(int argc, char *argv[]) {
    auto i = 5;
    auto &ri = i;
    const auto c = i;      // c 的类型是const int
    auto rf = func();
    const auto *p = &ri;
    static auto si = 100;
    return 0;
}
3. decltype

decltype在编译过程中返回表达式的数据类型,在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值;decltype推导所声明过的变量,可初始化,也可不初始化。https://www.cnblogs.com/cauchy007/p/4966485.html

  • 基本用法
int tempA = 2;
   
/* 1.dclTempA为int */
decltype(tempA) dclTempA;
/* 2.dclTempB为int,对于getSize根本没有定义,
    但是程序依旧正常,因为decltype只做分析,并不调用getSize,
*/
decltype(getSize()) dclTempB;
  • 结合const
double tempA = 3.0;
const double ctempA = 5.0;
const double ctempB = 6.0;
const double *const cptrTempA = &ctempA;

/*1.dclTempA推断为const double(保留顶层const,此处与auto不同)*/
decltype(ctempA) dclTempA = 4.1;
/*2.dclTempA为const double,不能对其赋值,编译不过*/
dclTempA = 5;
/*3.dclTempB推断为const double * const*/
decltype(cptrTempA) dclTempB = &ctempA;
/*4.输出为4(32位计算机)和5*/
cout<<sizeof(dclTempB)<<"    "<<*dclTempB<<endl;
/*5.保留顶层const,不能修改指针指向的对象,编译不过*/
dclTempB = &ctempB;
/*6.保留底层const,不能修改指针指向的对象的值,编译不过*/
*dclTempB = 7.0;
  • 与引用结合
 int tempA = 0, &refTempA = tempA;

/*1.dclTempA为引用,绑定到tempA*/
decltype(refTempA) dclTempA = tempA;
/*2.dclTempB为引用,必须绑定到变量,编译不过*/
decltype(refTempA) dclTempB = 0;
/*3.dclTempC为引用,必须初始化,编译不过*/
decltype(refTempA) dclTempC;
/*4.双层括号表示引用,dclTempD为引用,绑定到tempA*/
decltype((tempA)) dclTempD = tempA;

const int ctempA = 1, &crefTempA = ctempA;
    
/*5.dclTempE为常量引用,可以绑定到普通变量tempA*/
decltype(crefTempA) dclTempE = tempA;
/*6.dclTempF为常量引用,可以绑定到常量ctempA*/
decltype(crefTempA) dclTempF = ctempA;
/*7.dclTempG为常量引用,绑定到一个临时变量*/
decltype(crefTempA) dclTempG = 0;
/*8.dclTempH为常量引用,必须初始化,编译不过*/
decltype(crefTempA) dclTempH;
/*9.双层括号表示引用,dclTempI为常量引用,可以绑定到普通变量tempA*/
decltype((ctempA))  dclTempI = ctempA;

int重载了()操作符,返回引用类型

  • 与指针结合
    int tempA = 2;
    int *ptrTempA = &tempA;
    /*1.常规使用dclTempA为一个int *的指针*/
    decltype(ptrTempA) dclTempA;
    /*2.需要特别注意,表达式内容为解引用操作,dclTempB为一个引用,
    引用必须初始化,故编译不过*/
    decltype(*ptrTempA) dclTempB;

decltype和auto都可以用来推断类型,但是二者有几处明显的差异:
1、auto忽略顶层const,decltype保留顶层const;
2、对引用操作,auto推断出原有类型,decltype推断出引用;
3、对解引用操作,auto推断出原有类型,decltype推断出引用;
4、auto推断时会实际执行,decltype不会执行,只做分析。
总之在使用中过程中和const、引用和指针结合时需要特别小心。

4. 基于范围for的循环

基于范围的for语句是c++11新标准一个重要的引用,这种遍历语句遍历指定序列的每个元素,并且可以对每个元素进行某种操作。它的语法格式是:

for(declaration : expression)
    statement;

其中,declaration是一个变量,它用于表示一个原子元素或者基础元素,而expression是一个对象或者是一个序列,是被遍历的对象。这个对象将被访问里面的原子元素。访问一个元素之后,将会被推进至下一个需要被访问的值。

int arr[] = {1,2,3,4};
//!普通数组
for(auto i: arr) {
 cout<<i<<endl;
}
 
vector<string> vs = {"abc","xyz","mnq"};
//!vector
for(auto &s : vs) {
 cout<<s<<endl;
}

map<int,string> mis={{1,"c++"},{2,"java"},{3,"python"}};
//!map
for(auto &pair: mis) {
 cout<<pair.first<<"\t"<<pair.second<<endl;
} 
5. lambda

Lambda 表达式,实际上就是提供了一个类似匿名函数的特性,而匿名函数则是在需要一个函数,但是又不想费力去命名一个函数的情况下去使用的。

Lambda 表达式的基本语法如下:

[ caputrue ] ( params ) opt -> return type { body; };
  • capture是捕获列表;标识一个Lambda的开始,这部分必须存在,不能省略。编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表可以捕捉上下文中的变量供lambda函数使用;有以下形式:
形式说明
[]什么也没有捕获
[=]按值捕获任何用到的外部变量
[&]按引用捕获任何用到的外部变量
[this]按值捕获this指针
[a, &b]按值捕获a,按引用捕获b
[a, &]按值捕获a, 其它的变量按引用捕获
[&b,=]按引用捕获b,其它的变量按值捕获
[=,&a, &b]除a和b按引用进行传递外,其他参数都按值进行传递。=符号必须在最前面
[&, a, b]除a和b按值进行传递外,其他参数都按引用进行传递。&符号必须在最前面
  • params是参数列表;与普通函数的参数列表一致。特定情况下可以连同()一起省略;
  • opt是函数选项;可以为mutable,exception,attribute(选填)
选项说明
mutable表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获的对象的non-const方法。 使用该修饰符,参数列表不可以省略(即使参数列表为空)。
exception表达式是否抛出异常以及何种异常
attribute用来声明属性
  • return type是返回值类型;没有返回值时可以省略。返回值类型明确的情况下,也可以省略(body仅包含单一的return语句,那么返回值类型是返回表达式的类型)。

  • body是函数体;在该函数体,除了可以使用参数外,也可以使用捕捉到的所有变量。

auto f = [](){}; 
auto f = [](int a, int b)->int {return a + b; };
f(1, 2);//需要这么使用
6. 右值引用

右值引用是C++11中新增加的一个很重要的特性,主是要用来解决C++98/03中遇到的两个问题,第一个问题就是临时对象非必要的昂贵的拷贝操作,第二个问题是在模板函数中如何按照参数的实际类型进行转发。

转载了一篇很不错的文章<从4行代码看右值引用 >

7. 智能指针、nullptr

C++11提供了两种智能指针类型来管理动态对象。智能指针的行为类似常规指针,区别在于它负责自动释放所指向的对象。这两种智能指针的区别在于管理底层指针的方式:shared_ptr允许多个shared_ptr类型指针指向同一个对象;unique_ptr 则 “独占” 所指向的对象。标准库还定义了一个名为 weak_ptr 的伴随类,它是一种弱引用,指向 shared_ptr 所管理的对象。这三种类型都定义在 memory 头文件中。

注意:在用shared_ptr时,如果出现相互引用的情况下,则无法正常释放资源

同样,转载了一篇介绍不错的介绍智能指针的文章智能指针详解

nullptr

nullptr 出现的目的是为了替代 NULL。

在某种意义上来说,传统 C++ 会把 NULL、0 视为同一种东西,这取决于编译器如何定义 NULL,有些编译器会将 NULL 定义为 ((void*)0),有些则会直接将其定义为 0。

C++ 不允许直接将 void * 隐式转换到其他类型,但如果 NULL 被定义为 ((void*)0),那么当编译char *ch = NULL;时,NULL 只好被定义为 0。

而这依然会产生问题,将导致了 C++ 中重载特性会发生混乱,考虑:

void foo(char *);
void foo(int);

对于这两个函数来说,如果 NULL 又被定义为了 0 那么 foo(NULL); 这个语句将会去调用 foo(int),从而导致代码违反直观。

为了解决这个问题,C++11 引入了 nullptr 关键字,专门用来区分空指针、0。

nullptr 的类型为 nullptr_t,能够隐式的转换为任何指针或成员指针的类型,也能和他们进行相等或者不等的比较。

当需要使用 NULL 时候,养成直接使用 nullptr的习惯。

8. 类特性修改
=default和=delete

对于 C++的类,如果程序员没有为其定义特殊成员函数,那么在需要用到某个特殊成员函数的时候,编译器会隐式的自动生成一个默认的特殊成员函数,比如拷贝构造函数,或者拷贝赋值操作符。

C++11允许我们使用=default来要求编译器生成一个默认构造函数,也允许我们使用=delete来告诉编译器不要为我们生成某个默认函数

class B {
    B() = default; //显示声明使用默认构造函数
    B(const B&) = delete; //禁止使用类对象之间的拷贝
    ~B() = default;  //显示声明使用默认析构函数
    B& operator=(const B&) = delete;  //禁止使用类对象之间的赋值
    B(int a);  
};
委托构造函数、继承构造函数(using)

C++11提供了两种新的构造函数特性,用于提升类构造的效率,分别是委托构造和继承构造,前者主要用于多构造函数的情况,而后者用在类继承方面。

  • 委托构造函数
    委托构造的本质为了简化函数代码,做到复用其他构造函数代码的目的。用于多构造函数情况
class Base {
public:
    int value1;
    int value2;
    Base() {
        value1 = 1;
    }
    Base(int value) : Base() {  // 委托 Base() 构造函数
        value2 = 2;
    }
};
  • 继承构造函数(using关键字)
    c++在继承的时候,需要将构造函数的参数逐个传递到父类的构造函数中完成父类的构造,这种效率是很低下的,因此c++11引入了继承构造的特性,使用using关键字用于类继承方面
struct A {
  A(int i) {}
  A(double d,int i){}
  A(float f,int i,const char* c){}
  //...等等系列的构造函数版本
};
//C++98
struct B:A {
  B(int i):A(i){}
  B(double d,int i):A(d,i){}
  B(folat f,int i,const char* c):A(f,i,e){}
  //......等等好多个和基类构造函数对应的构造函数
};
//C++11
struct B:A {
  using A::A;
  //关于基类各构造函数的继承一句话搞定
  //......
};
显示控制虚函数重载(override、final)

由于虚函数的特性,可能会被意外进行重写,为了做到精确对虚函数重载的控制,c++11使用了override和final关键字完成对这一特性的实现.
override关键字 : 显式声明对虚函数进行重载
final关键字 : 显式终结类的继承和虚函数的重载使用

9. 新增STL容器

std::array
std::array 保存在栈内存中,相比堆内存中的 std::vector,我们能够灵活的访问这里面的元素,从而获得更高的性能。

std::array 会在编译时创建一个固定大小的数组,std::array 不能够被隐式的转换成指针,使用 std::array只需指定其类型和大小即可. std::array<int, 4> arr= {1,2,3,4};

std::forward_list
std::forward_list 是一个列表容器,使用方法和 std::list 基本类似。
和 std::list 的双向链表的实现不同,std::forward_list 使用单向链表进行实现,提供了 O(1) 复杂度的元素插入,不支持快速随机访问(这也是链表的特点),也是标准库容器中唯一一个不提供 size() 方法的容器。当不需要双向迭代时,具有比 std::list 更高的空间利用率。

无序容器unordered_map、unordered_set
C++11 引入了两组无序容器,无序容器中的元素是不进行排序的,内部通过 Hash 表实现,插入和搜索元素的平均复杂度为 O(constant)。

  • std::unordered_map/std::unordered_multimap
    std::map使用的数据结构为二叉树,而std::unordered_map内部是哈希表的实现方式,哈希map理论上查找效率为O(1)。但在存储效率上,哈希map需要增加哈希表的内存开销。

  • std::unordered_set/std::unordered_multiset
    std::unordered_set的数据存储结构也是哈希表的方式结构,除此之外,std::unordered_set在插入时不会自动排序,这都是std::set表现不同的地方

元组std::tuple
tuple是一个固定大小的不同类型值的集合,是泛化的std::pair,其中的元素个数不再限于两个,而且功能更加丰富.
元组的使用有三个核心的函数:
std::make_tuple: 构造元组
std::get: 获得元组某个位置的值
std::tie: 元组拆包
std::tuple_cat:合并两个元组,可以通过 std::tuple_cat 来实现

10.多线程

在C++11以前,C++的多线程编程在不同到平台使用不同的API,比如linux平台使用pthread,windows平台使用winSDK中的Create,或者依赖其他第三方接口实现,一定程度上影响了代码的移植性。C++11中,引入了boost库中的多线程部分内容,形成C++标准,形成标准后的boost多线程编程部分接口基本没有变化,这样方便了以前使用boost接口开发的使用者切换使用C++标准接口,把容易把boost接口升级为C++接口。

std::thread
std::thread为C++11的线程类,使用方法和boost接口一样,非常方便,同时,C++11的std::thread解决了boost::thread中构成参数限制的问题,我想着都是得益于C++11的可变参数的设计风格。

std::atomic
std::atomic为C++11封装的原子数据类型.
从功能上看,简单地说,原子数据类型不会发生数据竞争,能直接用在多线程中而不必我们用户对其进行添加互斥资源锁的类型。从实现上,大家可以理解为这些原子类型内部自己加了锁。

std::condition_variable
std::condition_variable就像Linux下使用pthread_cond_wait和pthread_cond_signal一样,可以让线程休眠,直到被唤醒,现在在从新执行。线程等待在多线程编程中使用非常频繁,经常需要等待一些异步执行的条件的返回结果。

std::condition_variable cv;
while (!ready) cv.wait(lck); //调用cv.wait(lck)的时候,线程将进入休眠
cv.notify_all(); //休眠结束
11. 正则表达式

正则表达式描述了一种字符串匹配的模式。一般使用正则表达式主要是实现下面三个需求:

  • 检查一个串是否包含某种形式的子串;
  • 将匹配的子串替换;
  • 从某个串中取出符合条件的子串。

C++11 提供的正则表达式库操作 std::string 对象,对模式 std::regex (本质是 std::basic_regex)进行初始化,通过 std::regex_match 进行匹配,从而产生 std::smatch (本质是 std::match_results 对象)。

我们通过一个简单的例子来简单介绍这个库的使用。考虑下面的正则表达式:

[a-z]+.txt: 在这个正则表达式中, [a-z] 表示匹配一个小写字母, + 可以使前面的表达式匹配多次,因此 [a-z]+ 能够匹配一个及以上小写字母组成的字符串。在正则表达式中一个 . 表示匹配任意字符,而 . 转义后则表示匹配字符 . ,最后的 txt 表示严格匹配 txt 这三个字母。因此这个正则表达式的所要匹配的内容就是文件名为纯小写字母的文本文件。
std::regex_match 用于匹配字符串和正则表达式,有很多不同的重载形式。最简单的一个形式就是传入std::string 以及一个 std::regex 进行匹配,当匹配成功时,会返回 true,否则返回 false。例如:

#include <iostream>
#include <string>
#include <regex>

int main() {
    std::string fnames[] = {"foo.txt", "bar.txt", "test", "a0.txt", "AAA.txt"};
    // 在 C++ 中 `\` 会被作为字符串内的转义符,为使 `\.` 作为正则表达式传递进去生效,需要对 `\` 进行二次转义,从而有 `\\.`
    std::regex txt_regex("[a-z]+\\.txt");
    for (const auto &fname: fnames)
        std::cout << fname << ": " << std::regex_match(fname, txt_regex) << std::endl;
}

另一种常用的形式就是依次传入 std::string/std::smatch/std::regex 三个参数,其中 std::smatch 的本质其实是 std::match_results,在标准库中, std::smatch 被定义为了 std::match_results,也就是一个子串迭代器类型的 match_results。使用 std::smatch 可以方便的对匹配的结果进行获取,例如:

std::regex base_regex("([a-z]+)\\.txt");
std::smatch base_match;
for(const auto &fname: fnames) {
    if (std::regex_match(fname, base_match, base_regex)) {
        // sub_match 的第一个元素匹配整个字符串
        // sub_match 的第二个元素匹配了第一个括号表达式
        if (base_match.size() == 2) {
            std::string base = base_match[1].str();
            std::cout << "sub-match[0]: " << base_match[0].str() << std::endl;
            std::cout << fname << " sub-match[1]: " << base << std::endl;
        }
    }
}

以上两个代码段的输出结果为:

foo.txt: 1
bar.txt: 1
test: 0
a0.txt: 0
AAA.txt: 0
sub-match[0]: foo.txt
foo.txt sub-match[1]: foo
sub-match[0]: bar.txt
bar.txt sub-match[1]: bar

另外, 查看维基百科C++11

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值