重学C++笔记之(十六)探讨C++11新标准

1. 复习前面介绍过的C++11功能

1.1 新类型

C++新增了long long和unsigned long long,以支持64位的整型;新增了类型char_16_t和char32_t,以支持16位和32位的字符表示;

1.2 统一的初始化

C++11扩大了用大括号括起来的列表(初始化列表)的适用范围。使用初始化列表时,可添加等号(=),也可不添加。

int x = {
    
    5};
double y {
    
    2.75};
short quar[5] {
    
    1, 2, 3, 4, 5};
int *ar = new int [4] {
    
    2, 4, 6, 7};
Class s1{
    
    315};//创建对象

如果类有将模板std::initializer_list作为参数的构造函数,则只有该构造函数可以使用列表初始化形式。另外,初始化列表不允许缩窄操作!可以进行宽操作。

char c1 {
    
    3.3};//不允许
char c2 {
    
    66}//范围内,允许

1.3 声明

1.3.1 auto

auto可用于自动类型推断(初始化时):

auto maton = 112;//int
auto pt = &maton;//指针
for(auto p = il.begin();p != il.end(); p++);

1.3.2 decltype

decltype(x) y;//让y的类型与x相同

1.3.3 返回类型后置

C++新增了一种函数声明语法:在函数名和参数列表后面指定返回类型:

double f1(double, int)//传统语法
auto f2(double, int) -> double//新语法,返回类型为double

这种方式配合decltype来使用

template<typename T, typename U>
auto eff(T t, U u) ->decltype(T*U)//类型未知,但是类型和T*U相同
{
    
    ...}

1.3.4 using =

用法和typedef相同:

typedef std::vector<std::string>::iterator itType;
using itType = std::vector<std::string>::iterator;

但是在模板部分具体化时,只能用using=:

template<typename T>
	using arr12 = std::arr<T,12>

//下列的实现相同:
std::array<double,12> a1;
arr12<double> a1;

1.3.4 nullptr

C++11新增了nullptr,用于表示空指针,它是指针类型,不能转化为整型类型。但是为了向后兼容,仍可以用于进行判断:

nullptr == 0;//可以判断,但不能转换为0

1.4 智能指针

一共有四个智能指针:auto_ptr(c++11摒弃)、unique_ptr、shared_ptr和weak_ptr。

1.5 作用域内枚举

C++11新增一种枚举,适用于class或struct:

enum Old1 {
    
    yes, no, maybe};
enum class New1 {
    
    never, sometimes, often, always};
enum struct New2 {
    
    never, lever, sever};

新枚举要求进行显式限定,避免名称冲突。New1::never和New2::never等

1.6 对类的修改

1.6.1 显式转换运算符

C++引入了关键字explicit,以禁止单参数构造函数导致的自动转换:

class Plebe
{
    
    
	Plebe(int);
	explicit Plebe(double);
};
Plebe a, b;
a = 5;//隐式转换
b = 0.5;//不允许
b = Plebe(0.5);//显式转换explicit

C++11拓展了explicit的这种用法,使得可对转换函数做类似的处理:

class Plebe
{
    
    
	//转换函数
	operator int()const;
	explicit operator double() const;
};
Plebe a, b;
int n = a;//自动转换
double x = b;//不允许
x = double (b);//显式转换explicit

1.6.2 类内成员初始化

class Session
{
    
    
	int mem1 = 10;//类内初始化
	double mem2 {
    
    1966.54};//类内初始化
	short mem3;;
public:
	
	Session(int n, double d, short s):mem1(n), mem2(d),mem3(s){
    
    }//覆盖类内初始化
}

如果构造函数提供了初始化列表,则类内初始化会被覆盖。

1.7 模板和STL方面的修改

1.7.1 基于范围的for循环

对于内置数组和包含方法begin()和end()的类(std::string)和STL容器,基于范围的for循环可简化为它们编写循环的工作。

double prices[5] = {
    
    3.4, 5.6, 4.6, 2,3, 5,9};
for(double x : prices)//for(auto x : prices)
	std::cout << x <<std::endl;

//如果需要修改数组或容器的每个元素,可使用引用类型:
std::vector<int> vi(6);
for(auto & x : vi)
	x = std::rand();

1.7.2 新的STL容器

C++11新增了STL容器forward_list(单向链表)、unordered_multimap、unordered_set和unordered_multiset。unordered的四种新增容器都是使用哈希表实现的。

C++11还新增了模板array。

1.7.3 新的STL方法

C++11新增了STL方法cbegin()和cend()。它们也是返回一个迭代器,指向容器的第一个元素和最后一个元素的后面,这些新方法将元素视为const,与此类似,crbegin()和crend()是cbegin()和rend()的const版本。

除了赋值构造函数和常规赋值运算符外,STL容器还有移动构造函数和移动赋值运算符。将在后面介绍。

1.7.4 valarray升级

新增了两个函数begin()和end(),可以将基于范围的STL算法用于valarray。

1.7.5 摒弃export

C++98新增了export,让程序员能够将模板定义放在接口文件和实现文件中。实践证明这不显示,因此C++11将其摒弃,但保留了,供以后使用。

1.7.6 尖括号

为避免运算符>>混淆,C++要求在声明嵌套模板时使用空格将尖括号分开:

std::vector<std::list<int> >v1;//C++98不能使用>>

C++11不再这么要求:

std::vector<std::list<int>>v1;

1.8 右值引用

int x = 10;
int y = 13;
int && r1 = l3;//关联到13
int && r2 = x + y;//r2关联到(x + y),也就是关联到了13.x,y改变不影响r2
double && r3 = std::sqrt(2.0);

&r2表示r2的地址。右值引用的主要目的之一是实现移动语义。下面将介绍。

2. 移动语义和右值引用

下面将介绍本书未讨论的主题。

复制临时对象时,需要在复制给另一个对象后,需要删除临时对象,但是这样做了无用功。移动语义将更高效,它不会移动原始数据,而只是修改了记录。

使用左值对象初始化对象时,将使用复制构造函数;使用右值对象初始化对象时,将使用移动构造函数。

Useless two = one;//复制构造函数
Useless four (one + three);//+运算符需要重载,移动构造函数

适用于构造函数的移动语义也适用于赋值运算符。

Useless & Useless::operator = (const Useless & f);//copy赋值
Useless & Useless::operator=(Useless && f);//移动语义

移动构造函数和移动赋值运算符使用右值,如果要让它们使用左值,可以使用c
++11提供的函数std::move(),该函数在头文件utility中声明。

four = move(one);//强制移动,std::move(one)是右值

如果没有定义移动赋值运算符,同时,也没有定义复制移动运算符,那么这种操作是不允许的。现在STL类中都有了复制构造函数、移动构造函数、复制赋值运算符和移动赋值运算符。

3. 新的类功能

3.1 默认的方法和禁用的方法

如果自己创建了构造函数,而导致没有默认构造函数,那么可以使用关键字default显式地声明这些方法的默认版本:

class Someclass
{
    
    
public:
	Someclass(Someclass &&);
	Someclass() = default;
	Someclass(const Someclass &) = default;
	Someclass & operator = (const Someclass &) = default;
}

另一方面,关键字delete可用于禁止编译器使用特定方法。例如如果要禁止复制对象,可禁用复制构造函数和复制赋值运算符:

class Someclass
{
    
    
public:
	Someclass() = default;
	Someclass(const Someclass &) = delete;
	Someclass& operator=(const Someclass &) = delete;
};

前面也介绍过,如果要禁止复制,可将复制构造函数和赋值运算符放在类定义的private部分,但使用delete也能达到这个目的,且更不容易犯错、更容易理解。

default只能用于6个特殊成员函数,但delete可用于任何成员函数。delete的一种可能用法是禁止特定的转换。

class Someclass
{
    
    
public:
	void redo(double);
	void redo(int) = delete;
};

sc.redo(5)将会报错。这种错误为编译错误。

3.2 委托构造函数

C++11允许在一个构造函数的定义中使用另一个构造函数,这种方法被称为委托,因为构造函数暂时将创建对象的工作委托给另一个构造函数。

class Notes{
    
    
	int k;
	double x;
	std::string st;
public:
	Notes();
	Notes(int);
	Notes(int, double);
	Notes(int, double, std::string);
};
Notes::Notes(int kk, double xx, std::string stt) : k(kk),x(xx), st(stt){
    
    ...}
Notes::Notes(Notes(0, 0.01, "oh"){
    
    ...})//委托,下面也是
Notes::Notes(int kk):Notes(kk, 0.01,"Ah"){
    
    ...}
Notes::Notes(int kk, double xx):Notes(kk, xx, "Uh"){
    
    ...}

3.3 继承构造函数

C++11提供了一种让派生类继承基类构造函数的机制。

class C1
{
    
    
public:
	int fn(int j){
    
    ...}
	double fn(double w) {
    
    ...}
	void fn(const char (s) {
    
    ...}
};
class C2:public C1
{
    
    
public:
	using C1::fn;
	double fn(double){
    
    ...}
};
C2 c2;
int k = c2.fn(3);//C1::fn(int)
double z = c2.fn(2.4);//C2::fn(double)

C++11这种方法用于构造函数,这让派生类继承基类的所有构造函数,但不会使用与派生类构造函数的特征标匹配的构造函数。

class BS
{
    
    
public:
	BS();
	...
};
class DR :public BS
{
    
    
public:
	using BS::BS;
}

3.4 管理虚方法:override和final

虚方法对实现多态类层次结构很重要,但虚方法也带来了一些陷阱,例如,假设基类声明了一个虚方法,而您决定在派生类中提供不同的版本,这将覆盖旧版本。如果特征标不匹配,这将隐藏而不是覆盖旧版本:

class Action
{
    
    
	int a;
public:
	Action(int i = 0) : a(i){
    
    }
	int val()const{
    
    return a;};
	virtual void f(char ch )const {
    
    std::cout<<val()<<ch<<"\n";}
};
class Bingo : public Action
{
    
    
public:
	Bingo(int i = 0) : Action(i){
    
    }
	virtual voidf(char* ch)const{
    
    std::cout<<val()<<ch<<"!\n";}
};

由于Bingo定义的是f(char*ch)而不是f(char ch),将对Bingo对象隐藏f(char ch),则下面的代码不能使用:

Bingo b(10);
b.f('@');//f被隐藏,不能使用

在C++11中,可使用虚说明符override支出要覆盖的一个虚函数(加在函数末尾):如果声明与基类方法不匹配,编译器将视为错误。

//下面的方法将报错,因为与基类不匹配
virtual voidf(char* ch)const override{
    
    std::cout<<val()<<ch<<"!\n";}

说明符final解决了另一个问题,可禁止派生类覆盖特定的虚函数。

//下面的代码进制Action的派生类重新定义函数f()
virtual void f(char ch) const final{
    
    std::cout<<val()<<ch<<"ch";}

override和final并非关键字,而是具有特殊含义的标识符。

4. Lambda函数

4.1 比较函数指针、函数符和Lambda函数

举这样一个例子:生成一个随机整数列表,并判断多少个整数能被3整除。

函数指针的实现代码:

#include<vector>
#include<algorithm>
#include<cmath>
#include<iostream>
bool f3(int x) {
    
    return x%3 == 0;}//判断能否被3整除
int main()
{
    
    
    std::vector<int> numbers(10);
    std::generate(numbers.begin(),numbers.end(),std::rand);//生成随机数
    int count3 = std::count_if(numbers.begin(),numbers.end(),f3);//计算个数
    std::cout<<count3<<std::endl;
}

函数符的实现代码:

#include<vector>
#include<algorithm>
#include<cmath>
#include<iostream>
class f_mod
{
    
    
private:
    int dv;
public:
    f_mod(int d = 1) : dv(d){
    
    }
    bool operator()(int x){
    
    return x%dv == 0;}
};
int main()
{
    
    
    std::vector<int> numbers(10);
    std::generate(numbers.begin(),numbers.end(),std::rand);//生成随机数
    int count3 = std::count_if(numbers.begin(), numbers.end(),f_mod(3));
    std::cout<<count3<<std::endl;
}

在C++11中,对于接受函数指针或函数符的函数,可使用匿名函数定义(lambda)作为其参数。上面f3对应的lambda如下:

[](int x){
    
    return x%3 == 0};//f3:bool f3(int x) {return x % 3 == 0};

对比传统函数,Lambda函数的特点:用[]替代了函数名,没有声明返回类型(如果没有return,则为void)。

所以Lambda的实现形式为:

cout3 = std::count_if(numbers.begin(), numbers.end(),
						[](int x) {
    
    return x % 3 == 0});

注意仅当lambad表达式完全由一条返回语句组成时,自动类型推断才管用;否则需要使用新增的返回类型后置语法:

[](double x) ->double {
    
    int y = x;return x - y;}//返回类型为double     后置->double

4.2 为何使用lambda

原因:距离、简洁、效率和功能。

距离:可以在代码附近定义Lambda,而不能在函数内部定义其他函数。

简介:可以使用同一个Lambda两次,而且可给Lambda制定一个名称:

//使用两次:
cout1 = std::count_if(numbers.begin(), numbers.end(),
						[](int x) {
    
    return x % 3 == 0});
cout2 = std::count_if(numbers.begin(), numbers.end(),
						[](int x) {
    
    return x % 3 == 0});
		
//指定名称
auto mod3 = [](int x) {
    
    return x % 3 == 0;}
count1 = std::count_if(n1.begin(), n1,end(), mod3);
bool result = mod3(z);// return z%3 == 0

效率:函数指针不能使用内联,因为编译器不会内联其地址被获取的函数,因为函数地址意味着非内联函数。

功能:Lambda有一些额外的功能。Lambda可访问作用域内的任何动态变量。可分为按值捕获和按引用捕获。

int count3 =0;
int count13 = 0;
std::for_each(numbers.begin(), numbers.end(),
				[&](int x){
    
    count3 += x %3 == 0;count13 +=x%13 == 0; });

这里[&]可以让您能够在lamb表达式中使用所有的自动变量,包括count3和count13。

5. 包装器

C==提供了多个包装器(wrapper, 也叫适配器[adapter])。C++11提供了其他的包装器,包括bind、mem_fn和reference_wrapper以及包装器function。

其中模板bind可替代bind1st和bind2nd,但更灵活;模板mem_fn可以将成员函数作为常规函数进行传递;模板reference_wrapper能够创建行为像引用但可被复制的对象;包装器function可以以统一的方式处理多种类似函数的形式。

这一节未更新!!!感兴趣可自行查看。

6. C++11新增的其他功能

6.1 并行编程

C++定义了一个支持线程化执行的内存模型,添加了关键字thread_local(静态存储,持续性与特定线程相关),提供了相关的库支持。

库支持由原子操作(atomic operation)库和线程支持库组成,其中原子操作库提供了头文件atomic,而线程支持库提供了头文件thread、mutex、condition_variable和future。

6.2 新增的库

C++11添加了多个专用库。

  • 头文件random:支持的可扩展随机库提供了大量比rand()复杂的随机数据工具。例如,您可以选择随机数生成器和分布状态。
  • 头文件chrono:提供了处理时间间隔的途径。
  • 头文件tuple:支持模板tuple。tuple对象是广义的pair对象。pair对象可存储两个类型不同的值,而tuple对象可存储任意多个类型不同的值。
  • 头文件ratio:支持的编译阶段有理数算数库让您能够准确地表达任何有理数,
  • 头文件regex:支持正则表达式。

猜你喜欢

转载自blog.csdn.net/QLeelq/article/details/113511512
今日推荐