C++11新特性

c++11新特性:

一.类型推导:

1.auto:

(1)区别:

  • 在c++11之前,auto 关键字用来指明变量的存储类型,它和 static 关键字是相对的。auto 表示变量是自动存储的,是编译器的默认规则,写不写都一样
  • 在c++11之后,使用它来做自动类型推导

(2)auto 与 const 结合的用法:

  • 当类型不为引用时,auto 的推导结果将不保留表达式的 const 属性;
  • 当类型为引用时,auto 的推导结果将保留表达式的 const 属性。

(3)限制:

  • auto 不能在函数的参数中使用
  • auto 不能作用于类的非静态成员变量
  • auto 关键字不能定义数组
  • auto 不能用于模板参数

(4)常用用法:

  • 使用 auto 定义迭代器,比如stl容器的迭代器
  • auto 用于泛型编程,编译器自行推导

2.decltype:

(1)概念:
是c++11新出的关键字,跟auto关键字一样用于类型推导。

template <typename R, typename T, typename U>
R add(T t, U u)
{
    return t+u;
}
int a = 1; float b = 2.0;
auto c = add<decltype(a + b)>(a, b);

(2)与auto的区别:

auto varname = value;
decltype(exp) varname = value;
  • 对于varname:auto 根据=右边的初始值 value 推导出变量的类型,而 decltype 根据 exp 表达式推导出变量的类型,跟=右边的 value 没有关系。
  • auto 要求变量必须初始化,而 decltype 不要求。这很容易理解,auto 是根据变量的初始值来推导出变量类型的,如果不初始化,变量的类型也就无法推导了。

(3)推导规则:

  • 如果 exp 是一个不被括号( )包围的表达式,或者是一个类成员访问表达式,或者是一个单独的变量,那么 decltype(exp) 的类型就和 exp 一致,这是最普遍最常见的情况。
  • 如果 exp 是函数调用,那么 decltype(exp) 的类型就和函数返回值的类型一致。
  • 如果 exp 是一个左值,或者被括号( )包围,那么 decltype(exp) 的类型就是 exp 的引用;假设 exp 的类型为 T,那么 decltype(exp) 的类型就是 T&。

注意:exp 就是一个普通的表达式,它可以是任意复杂的形式,但是我们必须要保证 exp 的结果是有类型的,不能是 void。

decltype和auto关键字对待cv限定符的区别:

「cv 限定符」是 const 和 volatile 关键字的统称:

  • const 关键字用来表示数据是只读的,也就是不能被修改;
  • volatile 和 const 是相反的,它用来表示数据是可变的、易变的,目的是不让 CPU 将数据缓存到寄存器,而是从原始的内存中读取。

在推导变量类型时,auto 和 decltype 对 cv 限制符的处理是不一样的。decltype 会保留 cv 限定符,而 auto 有可能会去掉 cv 限定符。

以下是 auto 关键字对 cv 限定符的推导规则:

  • 如果表达式的类型不是指针或者引用,auto 会把 cv 限定符直接抛弃,推导成 non-const 或者 non-volatile 类型。
  • 如果表达式的类型是指针或者引用,auto 将保留 cv 限定符。

3.返回值类型后置(跟踪返回值类型):

(1)概念:
一般在泛型编程中使用,函数返回值类型通过泛型参数或泛型参数表达式来推导返回值类型。
(2)理解:

template <typename T, typename U>
decltype(t + u) add(T t, U u)  // error: t、u尚未定义
{
    return t + u;
}

t、u 在参数列表中,而 C++ 的返回值是前置语法,在返回值定义的时候参数变量还不存在。

下面是正确的做法,但是太晦涩:

template <typename T, typename U>
decltype((*(T*)0) + (*(U*)0)) add(T t, U u)
{
   return t + u;
}

在c++11中增加返回类型后置,auto和decltype结合使用完成:

template <typename T, typename U>
auto add(T t, U u) -> decltype(t + u)
{
   return t + u;
}

二.定义别名:

(1)方式:
方式有两种,typedef、using
using是c++11新增的。

(2)用法:
typedef unsigned int uint_t;
using uint_t = unsigned int;

typedef int (p)(int, int);
using p = int (
)(int, int);

(3)细节:

  • 两种定义别名的方式定义的别名不能用作函数重载,因为只是别名,实际类型还是一样的,则会引起“重复定义”的错误,如:
    void func(unsigned int a);
    void func(uint_t a);
  • using在泛型别名中的优势:
template <typename Val>
using str_map_t = std::map<std::string, Val>

使用:
str_map_t<int> testMap;

template与typedef不能直接配合使用,需要使用class或struct来配合,如:
template <typename Val>
struct str_map
{
    typedef std::map<std::string, Val> type;
};

使用:
str_map<int>::type map1;

三.模板:

c++11之前支持类模板的默认参数如下:

template <typename T, typename U = int, U N = 0>
struct Foo
{
    // ...
};

但是不支持函数模板的默认参数,c++11之后放开了这一限制:

template <typename T = int>  // error in C++98/03: default template arguments
void func()
{
    // ...
}

使用:

func();//其中默认使用int类型模板实例

四.Union:

C++98 不允许联合体的成员是非 POD 类型,但是 C++1 1 取消了这种限制。

POD:

POD 是 C++ 中一个比较重要的概念,在这里我们做一个简单介绍。POD 是英文 Plain Old Data 的缩写,用来描述一个类型的属性。

POD 类型一般具有以下几种特征(包括 class、union 和 struct等):

  1. 没有用户自定义构造函数、析构函数、拷贝构造函数和移动构造函数

  2. 不能包含虚函数虚基类

  3. 非静态成员必须声明为 public。

  4. 类中的第一个非静态成员的类型与其基类不同,例如:
    class B1{};
    class B2 : B1 { B1 b; };
    class B2 的第一个非静态成员 b 是基类类型,所以它不是 POD 类型。

  5. 在类或者结构体继承时,满足以下两种情况之一:

    • 派生类中有非静态成员,且只有一个仅包含静态成员的基类;
    • 基类有非静态成员,而派生类没有非静态成员。

    我们来看具体的例子:
    class B1 { static int n; };
    class B2 : B1 { int n1; };
    class B3 : B2 { static int n2; };
    对于 B2,派生类 B2 中有非静态成员,且只有一个仅包含静态成员的基类 B1,所以它是 POD 类型。对于 B3,基类 B2 有非静态成员,而派生类 B3 没有非静态成员,所以它也是 POD 类型。

  6. 所有非静态数据成员均和其基类也符合上述规则(递归定义),也就是说 POD 类型不能包含非 POD 类型的数据

  7. 此外,所有兼容C语言的数据类型都是 POD 类型(struct、union 等不能违背上述规则)。

C++11 规定,如果非受限联合体内有一个非 POD 的成员,而该成员拥有自定义的构造函数,那么这个非受限联合体的默认构造函数将被编译器删除;其他的特殊成员函数,例如默认拷贝构造函数、拷贝赋值操作符以及析构函数等,也将被删除。

union U {
    std::string s;
    int n;
};
那么:
U u;//这句代码会编译报错,提示s的构造函数被删除。

解决上面问题的一般需要用到 placement new:

union U {
    std::string s;
    int n;
public:
    U() { new(&s) std::string; }
    ~U() { s.~string(); }
};
U u;//能够编译和运行成功。

五.for循环:

c++11支持基于范围的循环,类似于Java的增强型for循环。语法:

for (declaration : expression){
    //循环体
}

其中,两个参数各自的含义如下:

  • declaration:表示此处要定义一个变量,该变量的类型为要遍历序列中存储元素的类型。需要注意的是,C++ 11 标准中,declaration参数处定义的变量类型可以用 auto 关键字表示,该关键字可以使编译器自行推导该变量的数据类型。
  • expression:表示要遍历的序列,常见的可以为事先定义好的普通数组或者容器,还可以是用 {} 大括号初始化的序列。

注意:

  • expression需要是一个有范围的集合,如果是char*,编译会报错,因为编译器并不知道char*的范围是多少。expression可以是std::string,此时declaration是char;
  • 在这种循环的时候,不要去修改容器元素的个数。以避免因扩容造成的不可预期的错误。

六.constexpr:

概念:
constexpr 关键字的功能是使指定的常量表达式获得在程序编译阶段计算出结果的能力,而不必等到程序运行阶段。C++ 11 标准中,constexpr 可用于修饰普通变量、函数(包括模板函数)以及类的构造函数

常量表达式:
所谓常量表达式,指的就是由多个(≥1)常量组成的表达式。换句话说,如果表达式中的成员都是常量,那么该表达式就是一个常量表达式。这也意味着,常量表达式一旦确定,其值将无法修改。
常量表达式的应用场景还有很多,比如匿名枚举、switch-case 结构中的 case 表达式等
常见的例子:
数组的长度就必须是一个常量表达式:
// 1)
int url[10];//正确
// 2)
int url[6 + 4];//正确
// 3)
int length = 6;
int url[length];//错误,length是变量

(1) constexpr修饰普通变量:

constexpr int len = 3;
int arr[len] = {1, 2, 3};//此处编译不会出错,因为在编译期len是常量。

(2) constexpr修饰函数:
函数要想成为常量表达式函数,必须满足如下 4 个条件:

  • 整个函数的函数体中,除了可以包含 using 指令、typedef 语句以及 static_assert 断言外,只能包含一条语句: return 返回语句;
  • 该函数必须有返回值,即函数的返回值类型不能是 void;
  • 函数在使用之前,必须有对应的定义语句。普通函数在使用的时候,需要先声明,定义可以放在后面。但是常量表达式函数必须在使用前,先声明+定义;
  • return 返回的表达式必须是常量表达式。在表达式中不能有在运行时才能决定值的变量。

(3) constexpr修饰类的构造函数:
constexpr不能直接修饰类,只能修饰构造函数和成员函数。

//自定义类型的定义
class myType {
public:
    constexpr myType(const char *name):name(name){};
    constexpr const char * getname(){
        return name;
    }
private:
    const char* name;
};
使用:
constexpr struct myType mt { "zhangsan", 10 };

注意:C++11 标准中,不支持用 constexpr 修饰带有 virtual 的成员方法。

在 C++ 11 标准中,const 用于为修饰的变量添加“只读”属性;而 constexpr 关键字则用于指明其后是一个常量(或者常量表达式),编译器在编译程序时可以顺带将其结果计算出来,而无需等到程序运行阶段,这样的优化极大地提高了程序的执行效率。

七.右值引用:

左值的英文简写为“lvalue”,右值的英文简写为“rvalue”。很多人认为它们分别是"left value"、“right value” 的缩写,其实不然。lvalue 是“loactor value”的缩写,可意为存储在内存中、有明确存储地址(可寻址)的数据,而 rvalue 译为 “read value”,指的是那些可以提供数据值的数据(不一定可以寻址,例如存储于寄存器中的数据)。

通常情况下,判断某个表达式是左值还是右值,最常用的有以下 2 种方法,这 2 种判定方法只适用于大部分场景:

  • 可位于赋值号(=)左侧的表达式就是左值;反之,只能位于赋值号右侧的表达式就是右值。C++ 中的左值也可以当做右值使用。
  • 有名称的、可以获取到存储地址的表达式即为左值;反之则是右值。

其实 C++98/03 标准中就有引用,使用 “&” 表示。但此种引用方式有一个缺陷,即正常情况下只能操作 C++ 中的左值,无法对右值添加引用:

int num = 10;
int &b = num; //正确
int &c = 10; //错误

虽然 C++98/03 标准不支持为右值建立非常量左值引用,但允许使用常量左值引用操作右值。也就是说,常量左值引用既可以操作左值,也可以操作右值:

int num = 10;
const int &b = num;
const int &c = 10;

实际开发中我们可能需要对右值进行修改(实现移动语义时就需要),显然左值引用的方式是行不通的。为此,C++11 标准新引入了另一种引用方式,称为右值引用,用 “&&” 表示。和声明左值引用一样,右值引用也必须立即进行初始化操作,且只能使用右值进行初始化。C++ 语法上是支持定义常量右值引用的。
在这里插入图片描述

八.移动构造函数:

移动构造函数的调用时机是:用同类的右值对象初始化新对象。

  • 拷贝构造的语法:
    classType (const classType & obj)
    {
    	//将对象中的指针成员进行深拷贝
    }
    
  • 移动构造的语法:
    classType (classType && obj)
    {
    	//将对象obj中的指针浅拷贝给当前对象,然后将obj中的指针置空,避免了“同一块对空间被释放多次”
    }
    

非 const 右值引用只能操作右值,程序执行结果中产生的临时对象(例如函数返回值、lambda 表达式等)既无名称也无法获取其存储地址,所以属于右值(临时变量为右值)。当类中同时包含拷贝构造函数和移动构造函数时,如果使用临时对象初始化当前类的对象,编译器会优先调用移动构造函数来完成此操作。只有当类中没有合适的移动构造函数时,编译器才会退而求其次,调用拷贝构造函数。优势:节省了临时变量深拷贝的性能。

std::move():

由于右值引用只能作用于右值,那么如果一个左值想要调用移动构造呢?C++11考虑到这种情况,使用**std::move()**将左值强制转化为右值。

需要注意的是,调用移动构造函数,由于函数内部会重置 对象的指针, 指针的指向为 NULL。

智能指针unique_ptr,把指针的所有权交给其他unique_ptr时,需要使用std::move(),或者release()释放指针所有权并返回,因为unique_ptr没有提供拷贝构造函数(不共享各自拥有的堆内存),只有移动构造函数

九.完美转发:(函数模板)

概念:
指的是函数模板可以将自己的参数“完美”地转发给内部调用的其它函数。所谓完美,即不仅能准确地转发参数的值,还能保证被转发参数的左、右值属性不变

template<typename T>
void function(T t) {
    otherdef(t);
}
此时t不是引用类型,可以取其名字和地址,那么t为左值,那么传递给otherdef()的一定是左值。
//接收右值参数
template <typename T>
void function(const T& t) {//T& t不能接收右值,但是const T& t能够接收右值
    otherdef(t);
}
//接收左值参数
template <typename T>
void function(T& t) {
    otherdef(t);
}
c++11之前,可以通过上面的模板函数实现“完美转发”,但是如果参数多了,就需要定义多个模板函数,因此也不“完美”。

C++11 标准中规定,通常情况下右值引用形式的参数只能接收右值,不能接收左值。但对于函数模板中使用右值引用语法定义的参数来说,它不再遵守这一规定,既可以接收右值,也可以接收左值(此时的右值引用又被称为“万能引用”)。

template <typename T>
void function(T&& t) {
    otherdef(t);
}
此时函数模板的参数可以是左值,也可以是右值。

C++ 11标准为了更好地实现完美转发,特意为其指定了新的类型匹配规则,又称为引用折叠规则(假设用 A 表示实际传递参数的类型):

  • 当实参为左值或者左值引用(A&)时,函数模板中 T&& 将转变为 A&(A& && = A&);
  • 当实参为右值或者右值引用(A&&)时,函数模板中 T&& 将转变为 A&&(A&& && = A&&)。

此时函数模板虽然解决了参数传入问题,但是有t是有名字且可以取得地址,那么传入otherdef()时就把t当做了左值。

C++11 标准的开发者已经帮我们想好的解决方案,该新标准还引入了一个模板函数 forword(),forword() 函数模板用于修饰被调用函数中需要维持参数左、右值属性的参数

最终的函数模板完美转发的版本:

template <typename T>
void function(T&& t) {
    otherdef(forward<T>(t));
}

十.智能指针:

智能指针网上有很多教程,这里仅列出常用API和原理。

智能指针定义在头文件

  • shared_ptr:
    创建方式:

    1.
    std::shared_ptr<int> p3(new int(10));
    2.
    std::shared_ptr<int> p3 = std::make_shared<int>(10);
    

    在这里插入图片描述

  • unique_ptr:
    创建方式:
    没有std::make_unique,因为这是c++14里面的标准

    1.
    std::unique_ptr<int> p3(new int);
    或std::unique_ptr<int> p3(up.release());//其中up是unique_ptr
    2.
    std::unique_ptr<int> p5(p4);//错误,堆内存不共享,unique_ptr没有拷贝构造函数
    std::unique_ptr<int> p5(std::move(p4));//正确,调用移动构造函数
    

    在这里插入图片描述

  • weak_ptr:
    创建方式:

    1.std::weak_ptr<int> wp1;
    2.std::weak_ptr<int> wp2 (wp1);//其中wp1是weak_ptr
    3.std::weak_ptr<int> wp3 (sp);//其中sp是shared_ptr
    

    在这里插入图片描述

十一.其他:

  1. 连续右尖括号>>改进:
    c++11之前,模板实例化有一个很烦琐的地方,那就是连续两个右尖括号(>>)会被编译器解释成右移操作符,而不是模板参数表的结束。
    因此泛型中的(>>)中需要加入一个空格(> >)来标识。c++11之后取消了这种限制。

  2. lambda表达式:
    (1)语法:
    [外部变量访问方式说明符] (参数) mutable noexcept/throw() -> 返回值类型
    {
    函数体;
    }

    mutable:
    此关键字可以省略,如果使用则之前的 () 小括号将不能省略(参数个数可以为 0)。默认情况下,对于以值传递方式引入的外部变量,不允许在 lambda 表达式内部修改它们的值(可以理解为这部分变量都是 const 常量)。而如果想修改它们,就必须使用 mutable 关键字。

    [外部变量]的定义方式
    外部变量格式 功能
    [] 空方括号表示当前 lambda 匿名函数中不导入任何外部变量。
    [=] 只有一个 = 等号,表示以值传递的方式导入所有外部变量;
    [&] 只有一个 & 符号,表示以引用传递的方式导入所有外部变量;
    [val1,val2,…] 表示以值传递的方式导入 val1、val2 等指定的外部变量,同时多个变量之间没有先后次序;
    [&val1,&val2,…] 表示以引用传递的方式导入 val1、val2等指定的外部变量,多个变量之间没有前后次序;
    [val,&val2,…] 以上 2 种方式还可以混合使用,变量之间没有前后次序。
    [=,&val1,…] 表示除 val1 以引用传递的方式导入外,其它外部变量都以值传递的方式导入。
    [this] 表示以值传递的方式导入当前的 this 指针。

  3. nullptr:
    NULL 并不是 C++ 的关键字,它是 C++ 为我们事先定义好的一个宏,并且它的值往往就是字面量 0(#define NULL 0)。

    NULL的缺陷在于,在c++函数重载中,NULL默认匹配的是参数为整型的函数,这会导致函数调用的错误。
    C++11 标准并没有对 NULL 的宏定义做任何修改。为了修正 C++ 存在的这一 BUG,C++ 标准委员会最终决定另其炉灶,在 C++11 标准中引入一个新关键字,即 nullptr,称为“指针空值类型”。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值