c++11/14 新标准概述和例子

c++11/14 新标准概述和例子

1.1 概述

相对于c++98/99 版本,c11/14增加了许多新特性,其目标主要有两个
1.更适合系统编程和构建程序库
2. 更加容易学习
下面来介绍一些主要改变

1.2左值与右值

1.2.1定义

c++11/14标准描述了左值和右值的定义,但是比较难以理解:
以赋值符号 = 为界,= 左边的就是左值,= 右边就是右值。
下面简略说一下摘要:
1.所有表达式的结果不是左值就是右值
2.左值:一个函数或者对象的实例
3.失效值:生命周期即将结束的对象
4.广义左值 :左值和失效值
5.右值:失效值,临时对象
6.纯右值 : 非失效值的右值
在这里插入图片描述

注:
左值:是一个可以用来存储数据的变量,有实际的内存地址,表达式结束后依然存在
右值: 临时变量,表达式结束,变量销毁,不能取地址 (返回值不能返回局部变量的应用就是这个道理)

1.2.2右值引用 &&

对一个对象使用右值引用:相当于添加了一个临时名字,生命周期得到延长(主要用来支持move语义)

1.2.3 move语义

move语义是指将一个同类型的对象A中的资源(可能是在堆上分配,也可能是一个文件句柄或者其他系统资源)搬移到另一个同类型的对象B中,解除对象A对该资源的所有权。这样可以减少不必要的临时对象的构造、拷贝以及析构等动作。

比如我们经常使用的std::vector,当两个相同的std::vector类型赋值时,一般的步骤如下:
1.内部的赋值构造函数一般是先分配指定大小的内存,
2.从源std::vector中拷贝到新申请的内存,
3.再把原有的对象实例析构掉,最后接管新申请的数据。

这就是c++11之前的深拷贝,move语义拷贝语义相对,类似于浅拷贝,但是资源的所有权发生了转移。move语义的实现可以减少拷贝动作,大幅提高程序的性能

为了实现move语义的构造,就需要对应的语法来支持。原有的拷贝构造函数等不能够满足该需求。最典型的例子就是C++11废弃的std::auto_ptr,其构造函数会产生不明确的拥有权关系,很容易滋生BUG。这也是很多人不喜欢std::auto_ptr的原因。C++11为此增加了相应的构造函数。

在这里插入图片描述
这里可以明显看到两个函数中的参数类型是Foo&&。这就是右值引用的基本语法

c++ 11 swap函数实现
在这里插入图片描述

1.2.4 完美转发 forward()

可以把函数的参数原封不动的转发给其他函数

在这里插入图片描述

1.3 自动类型推导

新增两个两个关键字 auto / decltype 他们可以自动推导表达式的信息,可以大量简化代码

1.3.1 auto

c++作为一种强类型的静态语言,任何变量和表达式都要有明确的类型。例如:
int a ;
std::map< std::string,std::string >::iterator iter = m.begin();
上面例子中对于map的一个迭代器声明就很麻烦,对应的就可以使用auto简化代码
auto iter = m.begin();
注 : auto不会有任何的效率的损失,可以放心使用,而且带来了更好的安全性,不在为类型纠结

1.3.2 decltype

返回表达式的类型,例:

int x =10;
long y =11000;
decltype(x + y) dest =x+y;

使用: 1.decltype(e) 获得表达式计算结果的值类型。 2. decltype((e))获得表达式计算结果的引用类型

1.3.3 decltypr(auto)

auto 只能用于赋值语句,用途有限,decltype()可以推导任意表达式类型,但是必须在()写全表达式,故而引申出decltypr(auto), 例:
*decltypr(auto) z =x+y <====> decltype(x + y) dest =x+y;*

1.4 面向过程

c++继承了c的传统,支持最基本的面向过程,在c11/c14中的变化不多,但是增加的新特性可以很好的改进程序

1.4.1 空指针

在c11/14之前空指针都是用宏NULL表示,NULL的定义通常为0,是一个宏定义:
#define NULL 0
但是NULL有着严重的缺陷,他是一个实际的数,而不是一个真的指针,而c11/14增加了关键字:nullptr ,他明确的表示空指针,并且可以隐式转换成任意类型的指针,也可与指针进行比较运算,但是绝不能转换成非指针的其他类型,例:

 class A
 {.......};
 A* a=new A();
 if ( a == nullptr)
 {......}

1.4.2 统一初始化(initialization)

在c++中初始化是一个基本操作,但是对于不同结构的数据初始化没有统一的语法,c11/14 标准给出了完美的解决方案,统一使用 {} 初始化变量,称为初始化列表,例:

       int x{};//缺省值为0

       double y{ 1.213 };

       string s{ "hello world" };

       vector<int> vec={ 1,2,3,4 };


实际上的语法为std:initialization_list 的对象来实现统一初始化,在标准库一书中(侯捷大师有介绍),当然有时间也可以看一下大师的公开课说的很明白侯捷,c++2.0 公开课

1.4.3 新的for循环

说到新的for 循环,这个搭配auto使用是我现在超级喜欢的东西,不说别的直接上代码:
c98/99的for循环:

       vector<int> vec={ 1,2,3,4 };
       for (vector<int>::iterator iter=vec.begin();iter != vec.end();iter++)
       {
              cout << *iter << endl;
       }

c11/14 新for循环:

       vector<int> vec={ 1,2,3,4 };
       
       for (auto i : vec)
       {
              cout << i << endl;
       }

1.4.4 新式函数声明(在泛型,lambda 里面还是很有用的)

c11/14 新增了函数语法,可以允许返回值 后置 ,他使用了auto/decltype 的类型推导能力
auto func(…) ->type {…}
这里返回值必须要用auto 来占位,然后要在“->”后声明真正的返回值类型,可以用decltype类型推导,例:

auto func(int a) ->decltype(a)
{
       return a * a;
}

泛型的示例 :

template<typename T,typename U>
auto func(T a, U b)->decltype(a + b)
{
       return a + b;
}

1.5 面对对象

1.5.1 default

允许程序员,显示的声明类的缺省构造/析构等特殊成员

class A
{
public:
       A() =default;//显示使用默认构造
       A(int a) { x = a; };//不影响其他构造的使用
       ~A();
       int x;
};

1.5.2 delete

显示的禁用某些函数

class A
{
public:
       A() =default;//显示使用默认构造
       A(int a) =delete ;// 这样就显示的禁用了这个构造 
       ~A();
       int x;
};

** 注意:**
delete不仅仅可以作用于类的成员函数,也可以用于普通函数,禁用某些重载

1.5.3 final

c11/14新增关键字 “final”,可以用来控制类禁止被继承,也可以控制虚函数禁止重载
使用:在类名 或者虚函数后直接使用,例子:

class A final //禁止被继承
{
public:
       A() =default;//显示使用默认构造
       A(int a)=delete ;//不影响其他构造的使用
       ~A();
       int x;
};

class B
{
public:
       B();
       ~B();
       virtual int func(int a) final {}//这个虚函数禁止被重载
};

1.5.4 成员变量初始化

c11/14 放松了对类成员变量初始化的要求,允许在类声明时使用赋值或者 {} 初始化,无须构造中特别指定:

class C
{
public:
       C();
       ~C();
       int a = 10;
       string s{ "hello" };
       vector<int> vec = { 1,2,3,43,5 };
       static int c;
};
C::C()
{
       c = 10;
}

如上代码,需要特别注意的是,静态变量不能直接赋值的,因为静态变量需要分配实际的内存空间,还有就是这种初始化不能用 auto 进行类型推导,因为类型推导也要基于真正存在在内存中的内容推导

1.5.6 委托构造

对于一个类,我们经常会有很多个构造函数,用于不同情况下构造对象,这些代码大多都是初始化成员变量,仅有少量不同,导致代码冗余,所以可以构建一个特殊的初始化函数(一般为init()函数),在构造中调用,这个就是委托构造:

class  D
{
public:
       int x, y;
       void init(int a, int b)
       {
              x = a; y = b;
       }
       D(){init(0, 0);}
       D(int a) { init(a, 0); }
       D(int a,int b) { init(a, b); }
       //D(int a, int b) :x(a), y(b) {}; 构造函数初始化成员列表 这只是个写法
       ~ D();
};

 D::~ D()
{
}

1.6 泛型编程(很多很多厉害的特性)

1.6.1 类型别名

新增关键字 “using”,可以完成"typedef" 相同的工作,例:

using int_64=long;//long的类型别名就是int64
typedef long long          int64_t; // long long 类型为int64_t

同时using也可以结合template 一起使用为模板类(某种特质化的)声明别名,例 :

template<typename T>
using int_map = std::map<int, T>;

int_map<string> m;

1.6.2 静态断言 static_assert()

assert() 断言是一个宏,可以在运行时断言某些条件,但是在泛型编程的主要工作在编译期,assert(),无法使用,故而增加静态断言 static_assert(),他在编译期断言。
使用 static_assert(bool , string);bool类型的判断条件,string 抛出的消息

static_assert(sizeof(int) == 4, "int must be 32bit");

1.6.3 可变模板参数(很牛逼的东西)

说句实话我写的肯定不好,下面只是给个例子,看不懂的朋友可以去看一下侯捷的c++2.0公开课,我会给出链接的:
侯捷c++20
也可以看看这个博客:
泛型之美
例子

template <class... T>
void f(T... args)
{    
    cout << sizeof...(args) << endl; //打印变参的个数
}
/*
f();        //0
f(1, 2);    //2
f(1, 2.5, "");    //3
*/
#include <iostream>
using namespace std;
//递归终止函数
void print()
{
   cout << "empty" << endl;
}
//展开函数
template <class T, class ...Args>
void print(T head, Args... rest)
{
   cout << "parameter " << head << endl;
   print(rest...);
}


int main(void)
{
   print(1,"hello",false,4);
   return 0;
}

1.7 lambda表达式

不用羡慕go,python之类的语言有lambda,c11就已经加入了

1.7.1 使用

lambda实际上是对函数对象的强化和扩展,可以直接定义匿名对象,lambda的基本表达形式:
[ ] (参数){…}
lambda 表达式类型称为 “闭包” ,无法直接写出来,需要aotu推导,所以:

auto  f1 =[](int x ){
     return x*x;};
 cout <<f1(10)<<endl;

lambda 表达式类似函数对象,可以如上代码直接用operator()调用,也可以用于各种标准算法,而不用预定义函数对象和绑定器,而且更加具有可读性:

       vector<int > vec = { 1,2,3,4,5,6,7 };
       std::for_each(vec.begin(), vec.end(), [](int X) {
              cout << X << endl;
       });

指定返回类型
[] () -> type {…};

       auto  f1 = [](int x) ->long {
              return x * x; };
       cout << f1(10) << endl;

1.7.2 捕获外部变量

lambda表达式可以通过捕获列表捕获一定范围内的变量:
[]不捕获任何变量;[&]捕获外部作用域所有变量,并作为引用在函数体使用(按引用捕获);
[=]捕获外部作用域作用变量,并作为副本在函数体使用(按值捕获);
[=,&foo]按值捕获外部作用域所有变量,并按引用捕获foo变量;
[bar]按值捕获bar变量,同时不捕获其他变量;
[this]捕获当前类中的this指针,让lambda拥有和当前类成员函数同样的访问权限,如果已经使用了 &或者=,就默认添加此选项。捕获this的目的是可以在lambda中使用当前类的成员函数和成员变量。

class A
{
public:
    int mi = 0;

    void func(int x, int y)
    {
        auto x1 = []{return mi;};                      //error,没有捕获外部变量
        auto x2 = [=] {return mi + x + y;};            //ok,按值捕获所有外部变量
        auto x3 = [&] {return mi + x + y;};            //ok,按引用捕获所有外部变量
        auto x4 = [this] {return mi;};                 //ok,捕获this指针
        auto x5 = [this] {return mi + x + y;};         //error,没有捕获x,y
        auto x6 = [this,x,y] {return mi + x + y;};     //ok,捕获this,x,y
        auto x7 = [this] {return mi++;};               //ok,捕获this指针,并修改成员的值
    }
};

int a = 0, b = 2;
auto f1 = [] {return a;} ;                 //error,没有捕获外部变量
auto f2 = [&] {return a++;};               //ok,按引用捕获所有外部变量,并对a执行自加运算
auto f3 = [=] {return a;};                 //ok,按值捕获所有外部变量,并返回a
auto f4 = [=] {return a++;};               //error,按值引用不能改变值
auto f5 = [a] {return a + b;};             //error,没有捕获b
auto f6 = [a, &b] {return a + (b++);};     //ok,捕获a和b的值,并对b做自加运算
auto f7 = [=, &b] {return a + (b++);};     //ok,捕获所有外部变量和b的引用,并对b做自加运算
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值