关于c++:C11拾穗

3次阅读

共计 7312 个字符,预计需要花费 19 分钟才能阅读完成。

C++11 新关键字

alignas:指定对齐大小

alignof:获取对齐大小

decltype

auto(从新定义):可作为返回值类型后置时的占位符

static_assert:动态断言

using(从新定义):类型别名或者模板别名

noexcept:申明函数不能够抛出任何异样

export(弃用,不过将来可能留作他用)

nullptr

constexpr:可在在编译期确认的常量表达式

thread_local:等价于 TLS

疾速初始化成员变量

C++11 中反对应用等号 = 或者花括号 {} 进行就地的(也就是在申明的时候)非动态成员变量初始化。例如:

struct init{
    int a = 1;
    double b {1.2};
}

在 C ++11 规范反对了就地初始化非动态成员的同时,初始化列表这个伎俩也被保留下来了。只不过初始化列表总是看起来“后作用于”非动态成员。

final/override 管制

C++11 提供关键字 final,作用是使派生类不可笼罩它所润饰的虚函数。final 关键字也可 用于基类中,然而这样定义的虚函数就没有意义了。final 通常就是在继承关系的“中途”终止派生类的重载。

在 C ++ 中对于基类申明为 virtual 的函数,之后的重载版本都不须要再申明该重载函数为 virtual。即便在派生类中申明了 virtual,该关键字也是编译器能够疏忽的。另外,有的虚函数会“跨层”,没有在父类中申明的接口有可能是先人的虚函数接口。所以 C\++11 引入了虚函数描述符 override,来帮忙程序员写继承结构复杂的类型。如果派生类在虚函数申明时应用了 override 描述符,那么该函数必须重载其基类中的同名函数,否则代码将无奈通过编译。

继承构造函数

C++11 提供了继承构造函数,在派生类中应用 using 申明,就能够把基类中的构造函数继承到派生类中。但其实质是编译器主动生成代码,通过调用父类构造函数来实现,不是真正意义上的“继承”,仅仅是为了缩小代码书写量。

class A {A(int i) {}
    A(double d, int i) {}
    A(char *c , double d, int i) {}
    //... 更多构造函数
};

class B : A {
    using A::A; // 继承构造函数
    //...
}

委派构造函数

C++11 提供委派构造函数,能够简化多构造函数的类的编写。如果咱们能将一个构造函数设定为“基准版本”,则其余构造函数能够通过委派“基准版本”来进行初始化。咱们将这个“基准版本”称为指标构造函数。

class Info {
public:
    Info() { InitRest(); }// 指标构造函数
    Info(int i) : Info() { type = i;}// 委派构造函数
    Info(char c) : Info() { name = c;}// 委派构造函数
    
private:
    void InitRest(); { /* 其余初始化 */}
    int type {1};
    char name {'a'};
    //...
}

留神:委派构造函数不能有初始化列表。如果委派构造函数要给变量赋初值,初始化代码必须放在函数体中。

在应用委派构造函数时,倡议程序员形象出最为“通用”的行为作为指标构造函数。

挪动语义


在 C ++11 中,这样的“偷走”长期变量中资源的构造函数,被称为挪动构造函数。而这样的“偷”的行为,则称之为“挪动语义”。

class HasPtrMem(){
public:
    HasPtrMem() : d(new int(3)) {}
    HasPtrMem(const HasPtrMem & h) : d(new int(*h.d)) {}// 拷贝构造函数
    HasPtrMem(HasPtrMem && h) : d(h.d) {h.d = nullptr;}// 挪动构造函数,需将长期值的指针成员置空
    ~HasPtrMem() {delete d;}
private:
    int *d;
}

左值,右值,右值援用

对于左值,右值很难去做一个十分明确的定义,然而在 C ++ 中有一个被宽泛认同的说法,那就是能够取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值。

无论是申明一个左值援用还是右值援用,都必须立刻进行初始化。左值援用是具名变量值的别名,而右值援用则是不具名(匿名)变量的别名。

规范库在 <type_traits> 头文件中提供了 3 个模板类:is_rvalue_reference、is_lvalue_reference、is_reference,可供咱们进行判断援用类型是左值援用还是右值援用。

std::move: 强制转化为右值

C++11 中提供了 std::move 这个函数使咱们能将一个左值强制转化为右值援用,继而咱们能够通过右值援用应用该值,以用于挪动语义。

// 应用下面例子中的 HasPtrMem
HasPtrMem a;
HasPtrMem b(std::move(a));// 调用挪动构造函数 

事实上,为了保障挪动语义的传递,程序员在编写挪动构造函数的时候,应该总是记得应用 std::move 转换领有形如堆内存、文件句柄等资源的成员为右值,这样依赖,如果成语反对挪动结构的话,就能够实现其挪动语义。而即便成员没有挪动构造函数,那么承受常量左值的构造函数版本也会轻松地实现拷贝结构,因而也不会引起大的问题。

显示转换操作符

在 C ++ 中,有一个十分好也十分坏的个性,就是隐式类型转换。隐式类型转换的“自动性”能够让程序员免于层层构造类型。但也是因为它的自动性,会在一些程序员意想不到的中央呈现重大的但不易被发现的谬误。

关键字 explicit 次要用于润饰的构造函数,其作用次要就是阻止构造函数的隐式转换。

C\++11 中将 explicit 的应用范畴扩大到了自定义的类型转换操作符上,以反对所谓的“显示类型转换”。

列表初始化

在 C\++11 中能够应用花括号“{}”来进行初始化,这种初始化形式被称为列表初始化,曾经称为 C ++ 语言的一个基本功能。咱们甚至能够应用列表初始化的形式对 vector、map 等非内置的简单的数据类型进行初始化。

而且,C++11 中,规范总是偏向于应用更为通用的形式来反对新的个性。规范模板库中容器对初始化列表的反对源自 <initilizer_list> 这个头文件中的 initilizer_list 类模板的反对。咱们能够通过申明已 initilizer_list<T> 模板类为参数的构造函数,来使得自定义的类应用列表初始化。

enum Gender {boy, girl};
class People{
public:
    People(initilizer_list<pair<string, Gender>> l) {//initilizer_list 的构造函数
        auto i = l.begin();
        for (; i != l.end(); ++i)
            data.pushback(*i);
    }
private:
    vector<pair<string, Gender>> data;
};

People ship2012 = {{"Garfield", boy}, {"HelloKitty", girl}};

另外,应用列表初始化能够无效的避免类型收窄。类型收窄个别是指一些能够使得数据变动或者精度失落的隐式类型转换。

auto 类型推导

auto 申明的变量必须被初始化,以使编译器可能从其初始化表达式中推导出其类型。

auto 应用时需注意:

(1)、能够应用 const、volatile、pointer(*)、reference(&)、rvalue reference(&&) 等说明符和申明符来润饰 auto 关键字;

(2)、用 auto 申明的变量必须初始化;

(3)、auto 不能与其它任何类型说明符一起应用;

(4)、办法、参数或模板参数不能被申明为 auto;

(5)、定义在堆上的变量,应用了 auto 的表达式必须被初始化;

(6)、auto 是一个占位符,不是类型,不能用于类型转换或其它一些操作,如 sizeof、typeid;

(7)、auto 关键字内申明的申明符列表的所有符号必须解析为同一类型;

(8)、auto 不能主动推导成 CV-qualifiers(constant& volatile qualifiers),除非被申明为援用类型;

(9)、auto 会进化成指向数组的指针,除非被申明为援用;

(10)、auto 不能作为函数的返回型,在 C ++14 中是能够的。

decltype

decltype 的类型推导并不是像 auto 一样是从变量申明的初始化表达式取得变量的类型,decltype 总是以一个一般的表达式为参数,返回该表达式的类型。而与 auto 雷同的是,作为一个类型批示符,decltype 能够将取得的类型来定义另外一个变量。与 auto 雷同,decltype 类型推导也是在编译时进行的。

decltype 只能承受表达式做参数,像函数名做参数的表达式 decltype(hash) 这种是无奈通过编译的。

追踪返回类型

利用 auto 和 decltype 以及返回类型后置的语法就能实现追踪返回类型。比方,咱们想写一个泛型的加法函数时可能直观的写下如下代码。

template <typename T1, typename T2>
decltype(t1 + t2) sum(const T1& t1, const T2& t2) {return t1 + t2;}

然而,这样写是编译不过的,因为编译器只会从左往右地读入符号,这里的 t1 和 t2 在编译器看来都是未声明的。正确的写法是这样的。

template <typename T1, typename T2>
auto sum(const T1& t1, const T2 & t2) -> decltype(t1 + t2) {return t1 + t2;}

基于范畴的 for 循环

语法很简略,不赘述。次要应用时须要留神两点。一是应用基于范畴的 for 循环须要 for 循环迭代的范畴是可确定的。二是,基于范畴的 for 循环要求迭代对象实现 ++ 和 == 等操作符,这点规范库中的容器不会有问题,但用户自定义的类须要本人实现。

强类型枚举

原来的枚举类型有非强类型作用域,容许隐式转换为整型,占用存储控件及符号性不确定的毛病。C++11 引入了强类型枚举来解决问题。

申明强类型枚举只须要在 enum 后加上关键字 class。比方:

enum class Type {General, Light, Medium, Heavy};

强类型枚举具备一下几点劣势:

  • 强作用域,强类型枚举成员的名称不会输入到其父作用域空间。
  • 转换限度,强类型枚举成员的值不能够与整型隐式地互相转换。
  • 能够指定底层类型。强类型枚举默认的底层类型为 int,但也能够显式地指定底层类型,具体方法为在枚举名称前面加上 ”:type”, 其中 type 能够是除了 wchar_t 以外的任何整型。比方:

enum class Type: char {General, Light, Medium, Heavy};

因为 enum class 是强类型作用域的,故匿名的 enum class 很可能什么都做不了。

常量表达式函数

常量表达式函数须要满足几个条件,否则不能用 constexpr 关键字进行润饰:

  • 函数只能蕴含 return 语句。
  • 函数必须有返回值。
  • 在应用前必须曾经定义。
  • return 返回语句中不能应用十分量表达式的函数、全局数据,且必须是一个常量表达式。

constexpr int GetConst() { return 1;}

常量表达式值

const int i = 0;

constexpr int j = 0;

constexpr 示意的就是编译期常量,const 示意的是运行期常量。

大部分状况下这两个定义是没有区别的。不过 i 只有在全局范畴内申明,编译器肯定会为它产生数据;而对于 j,如果没有中央调用它,编译器能够抉择不为它生成数据。

并且,默认只有内置类型能力润饰为常量表达式值,自定义类型如果要成为常量表达式值,必须定义一个 constexpr 润饰的构造函数。

tuple 元组

当咱们心愿将一些数据组合成繁多对象,但又不想麻烦地定义一个新的数据结构来示意这些数据时,tuple 是十分有用的。个别,tuple 能够用于函数返回多个返回值。

tuple 容器, 能够应用间接初始化, 和 ”make_tuple()” 初始化, 拜访元素应用 ”get<>()” 办法, 留神 get 外面的地位信息, 必须是常量表达式 (const expression)。

能够通过 ”std::tuple_size<decltype(t)>::value” 获取元素数量; “std::tuple_element<0, decltype(t)>::type” 获取元素类型。

如果 tuple 类型进行比拟, 则须要放弃元素数量雷同, 类型能够比拟。

#include <iostream>  
#include <vector>  
#include <string>  
#include <tuple>  
  
using namespace std;  
  
std::tuple<std::string, int>  
giveName(void)  
{std::string cw("Caroline");  
    int a(2013);  
    std::tuple<std::string, int> t = std::make_tuple(cw, a);  
    return t;  
}  
  
int main()  
{std::tuple<int, double, std::string> t(64, 128.0, "Caroline");  
    std::tuple<std::string, std::string, int> t2 =  
            std::make_tuple("Caroline", "Wendy", 1992);  
  
    // 返回元素个数  
    size_t num = std::tuple_size<decltype(t)>::value;  
    std::cout << "num =" << num << std::endl;  
  
    // 获取第 1 个值的元素类型  
    std::tuple_element<1, decltype(t)>::type cnt = std::get<1>(t);  
    std::cout << "cnt =" << cnt << std::endl;  
  
    // 比拟  
    std::tuple<int, int> ti(24, 48);  
    std::tuple<double, double> td(28.0, 56.0);  
    bool b = (ti < td);  
    std::cout << "b =" << b << std::endl;  
  
    //tuple 作为返回值  
    auto a = giveName();  
    std::cout << "name:" << get<0>(a)  
            << "years:" << get<1>(a) << std::endl;  
  
    return 0;  
}  

nullptr

在 C ++11 中,nullptr 是一个所谓“指针空值类型”的编译期常量。指针空值类型被命名为 nullptr_t。

须要留神的是,nullptr 是 C ++11 中的关键字,它是有类型的,且仅能够被隐式转化为指针类型,其类型定义是:

typedef decltype(nullptr) nullptr_t;

lambda 函数

lambda 表达式的语法定义如下:

\[capture](parameters)mutable ->return-type {statement};

  • \[capture]: 捕获列表。捕获列表总是呈现在 lambda 函数的开始处。本质上,[] 是 lambda 引出符(即独特的标志符),编译器依据该引出符判断接下来的代码是否是 lambda 函数。捕获列表可能捕获上下文中的变量以供 lambda 函数应用。捕获列表由一个或多个捕获项组成,并以逗号分隔,捕获列表个别有以下几种模式:

    • [var] 示意值传递形式捕获变量 var。
    • [=] 示意值传递形式捕获所有父作用域的变量(包含 this 指针)。
    • [&var] 示意援用传递捕获变量 var。
    • [&] 示意援用传递捕获所有父作用域的变量(包含 this 指针)。
    • [this] 示意值传递形式捕获以后的 this 指针。
    • [=,&a,&b] 示意以援用传递的形式捕获变量 a 和 b,而以值传递形式捕获其余所有的变量。
    • [&,a,this] 示意以值传递的形式捕获 a 和 this,而以援用传递形式捕获其余所有变量。
 备注:父作用域是指蕴含 lambda 函数的语句块。
  • (parameters): 参数列表。与一般函数的参数列表统一。如果不须要参数传递,则能够连同括号()一起省略。
  • mutable :mutable 修饰符。默认状况下,lambda 函数总是一个 const 函数,mutable 能够勾销其常量性。在应用该修饰符时,参数列表不可省略(即便参数为空)。
  • ->return-type : 返回类型。用追踪返回类型模式申明函数的返回类型。出于不便,不须要返回值的时候也能够连同符号 -> 一起省略。此外,在返回类型明确的状况下,也能够省略该局部,让编译器对返回类型进行推导。
  • {statement} : 函数体。内容与一般函数一样,不过除了能够应用参数之外,还能够应用所有捕捉的变量。
// 简略示例
int a = 20, b = 10;

auto totalAB = [] (int x, int y)->int{return x + y;};
int aAddb = totalAB(a, b);
cout << "aAddb :" << aAddb << endl;

// lambda 与 STL
vector<int> v{1, 2, 3, 4, 5}; 
  
for_each(v.begin(), v.end(), [] (int val)  
{cout << val;} );  

在现阶段,通常编译器都会把 lambda 函数转化为一个仿函数对象。这是编译器实现 lambda 函数的一种形式。因而,C++11 中,lambda 函数能够视为仿函数的一种等价模式。

原生字符串字面量

原生字符串字面量的意思就是所见即所得,在代码中的字符串常量是怎么样的,咱们失去的就是怎么样的,不须要转义字符来管制特定的字符。

在 C ++11 中程序员只须要在字符串后面退出前缀字母 R,并在引号中应用括号左右标识即可将该字符串申明为原生字符串了。

// 输入带引号的字符串 "你好"
int main()
{
    std::string str = "\" 你好 \"";
    std::cout << str << std::endl;

    const char* str1 = R"(" 你好 ")";
    std::cout << str1 << std::endl;

    return 0;
}
正文完
 0