栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

侯捷 C++11/14 笔记

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

侯捷 C++11/14 笔记

侯捷 C++11/14 笔记
    • Variadic Template
      • 概述
      • 应用
    • 模板表达式中的空格
    • nullptr
    • auto
    • 一致性初始化(uniform initialization)
    • Initializer_list
    • explict
    • 基于范围的for循环
    • = default, = delete
    • Alias Template
    • Type Alias
    • noexpect
    • override
    • final
    • decltype
      • decltype 定义
      • decltype 用法
    • Lambdas
      • lambda 语法以及调用方式

原文地址:侯捷 C++11/14 笔记

Variadic Template 概述

可变参数模板。

  1. 谈的是模板Templates:

    • 函数模板
    • 类模板
  2. 变化的是模板参数:

    • 参数个数:利用参数个数逐一递减的特性,实现递归函数的调用,使用函数模板完成。
    • 参数类型:利用参数个数逐一递减以致参数类型也逐一递减的特性,实现递归继承或递归复合,以类模板完成。
void print()
{
}
 
template                 //这里的...是关键字的一部分:模板参数包
void print(const T& firstArg, const Types&... args)     //这里的...要写在自定义类型Types后面:函数参数类型包
{
    cout << firstArg << endl;
    print(args...);                                     //这里的...要写在变量args后面:函数参数包
}
  1. 注意三种不同的...的应用环境,这些都是语法规则,所以记住即可。

  2. 还要注意的是,在可变模板参数内部可以使用sizeof...(args)得到实参的个数。

  3. 如果同时定义了:

template 
void print(const Types&... args)
{}

该函数重载了void print(),void print(const T& firstArg, const Types&... args) 是其特化版本,编译器会优先调用特化版本。

应用
  1. 实现函数的 递归调用

    举了一个unordered容器中hash函数的计算例子:万用的哈希函数,函数入口return hash_val(c.fname, c.lname, c.no);

class CustomerHash{
public:
    std::size_t operator() (const Customer& c) const {
        return hash_val(c.fname, c.lname, c.no);
        // 2-1-1-...-1-3
    }
};
 
template  // 1
inline void hash_val(size_t& seed, const T& val, const Types&... args){
    hash_combine(seed, val);
    hash_val(seed, args);
}
 
template  // 2
inline size_t hash_val(const Types&... args){
    size_t seed = 0;
    hash_val(seed, args...);
    return seed;
}
 
template  // 3
inline void hash_val(size_t& seed, const T& val){
    hash_conbine(seed, val);
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KhH0fIJN-1659011616703)(https://aweiii.coding.net/p/blog/d/img/git/raw/main/posts/p2.png)]

  1. 实现递归继承
template  class tuple;
template <> class tuple<> {};

template 
class tuple
    : private tuple                  //注意这里的私有继承
{
    typedef tuple inherited;
public:
    tuple() {}
    tuple(Head v, Tail... vtail)
        :m_head(v), inherited(vtail...) {}

    Head head() { return m_head; }
    inherited& tail() { return *this; }       //这里涉及派生类到基类的类型转换

protected:
    Head m_head;
};

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cQURuTRG-1659011616704)(https://aweiii.coding.net/p/blog/d/img/git/raw/main/posts/20220720124019.png)]

模板表达式中的空格

C++11可以去掉模块表达式前面的空格。

nullptr

标准库允许使用nullptr取代0或者NULL来对指针赋值。

  • nullptr 是个新关键字
  • nullptr 可以被自动转换为各种 pointer 类型,但不会被转换为任何整数类型
  • nullptr的类型为std::nullptr_t,定义于 头文件中
void f(int);
void f(void *);

f(0);        // 调用 f(int).
f(NULL);     // 如果定义NULL为0,则调用 f(int),否则具有二义性
f(nullptr);  // 调用 f(void *).
auto
  1. C++11 auto可以进行自动类型推导。
    • C语言默认的局部变量是auto类型的
    • C++11 auto可以进行自动类型推导
  2. 使用auto的场景:类型太长或者类型太复杂
一致性初始化(uniform initialization)

C++11之前初始化时存在多个版本 {},(),=。让使用者使用时比较混乱,C++11提供一种万用的初始化方法,就是使用大括号{}。

原理解析:当编译器看到大括号包起来的东西{t1,t2...tn}时,会生成一个initializer_list(initializer_list关联至一个array)。调用函数(例如构造函数ctor)时该array内的元素可被编译器分解逐一传给函数;元素逐一分解传递给函数进行初始化。

但是如果调用函数自身提供了initializer_list参数类型的构造函数时,则不会分解而是直接传过去。直接整包传入进行初始化。所有的容器都可以接受这样的参数。

Initializer_list
int i;      // 未初始化
int j{};    // j = 0
int* p;     // 未初始化
int* q{};   // q = nullptr
  1. initializer_list使用举例:

    • initializer_list是一个class(类模板),这个必须类型要一致,跟模板不定的参数类型相比,模板不定的参数类型可以都不一样。
    • initializer_list类似于容器的使用方法
  2. initializer_list源码剖析:

    • initializer_list背后有array数组支撑,initializer_list关联一个array
    • initializer_list包含一个指向array的指针,它的拷贝只是一个浅拷贝,比较危险,两个指针指向同一个内存。
  3. initializer_list在STL中的使用:

    • 所有容器都接受指定任意数量的值用于构造或赋值或者insert()或assign()。
    • 算法max()和min()也接受任意参数。
explict

explicit关键字一直存在,只能作用在构造函数中,目的是阻止编译器进行不应该允许的构造函数进行隐式转换。声明为explicit的构造函数不能进行隐式转换,只能允许使用者明确调用构造函数。

C++11之前,只有non-explicit one argument的构造函数才能进行隐式转换,2.0之后支持more than one argument的构造函数的隐式转换。

基于范围的for循环
for (decl : coll) {
    statement
}

// 例子
vector vec;

//...
for(auto elem:  vec) {...};     // 赋值,无法改变容器的内容
for(auto& elem:  vec) {...};    // 引用

基于范围的for循环对于explicit类型申明的转换是不可以的。

= default, = delete

在 C++ 中,如果自定义了 big-five 函数,编译器就不会再生成默认的相关函数,但是如果我们在后边加上= default 关键字,就可以重新获得并使用编译器为我们生成的默认函数(显式缺省:告知编译器即使自己定义了也要生成函数默认的缺省版本);

=delete关键字相对于上面来说则是相反的,=delete表示不要这个函数,就是说这个函数已经删除了不能用了,一旦别人使用就会报错(显式删除:告知编译器不生成函数默认的缺省版本),引进这两种新特性的目的是为了增强对“类默认函数的控制”,从而让程序员更加精准地去控制默认版本的函数。

补充:

1、编译器提供的默认函数:

C++中,当我们设计与编写一个类时,若不显著申明,则类会默认为我们提供如下几个函数:

  1. 构造函数(A())
  2. 析构函数(~A())
  3. 拷贝构造函数(A(A&))
  4. 拷贝赋值函数(A& operator=(A&))
  5. 移动构造函数(A(A&&))
  6. 移动赋值函数(A& operator=(A&&))

注意:拷贝函数如果涉及指针就要区分浅拷贝(指针只占4字节,浅拷贝只把指针所占的那4个字节拷贝过去)和深拷贝(不仅要拷贝指针所占的字节,还要把指针所指的东西也要拷贝过去);

默认提供全局的默认操作符函数:

  1. operator
  2. operator &
  3. operator &&
  4. operator *
  5. operator->
  6. operator->*
  7. operator new
  8. operator delete

2、何时需要自定义big-three(构造函数、拷贝构造、拷贝赋值)/big-five(新增移动构造函数、移动赋值函数)?

如果类中带有pointer member(指针成员),那我们就可以断定必须要给出 big-three ;
如果不带,绝大多与情况下就不必给出 big-three 。

3、default、delete关键字使用示例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gaPRL41C-1659011616705)(https://aweiii.coding.net/p/blog/d/img/git/raw/main/posts/20220720144851.png)]

在c++中,如果你自定义了big-five函数,编译器就不会再为你生成默认的相关函数,但是如果我们在后边加上= default关键字,就可以重新获得并使用编译器为我们生成的默认函数(显式缺省:告知编译器即使自己定义了也要生成函数默认的缺省版本);

=delete关键字相对于上面来说则是相反的,=delete表示不要这个函数,就是说这个函数已经删除了不能用了,一旦别人使用就会报错(显式删除:告知编译器不生成函数默认的缺省版本),引进这两种新特性的目的是为了增强对“类默认函数的控制”,从而让程序员更加精准地去控制默认版本的函数。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-amI5nlEB-1659011616705)(https://aweiii.coding.net/p/blog/d/img/git/raw/main/posts/20220720145538.png)]

Alias Template
template 
using Vec = std::vector>

//使用
Vec coll;

Alias Template 无法特化。

应用实例(引出模板模板参数)

考虑这样一种需求,假设我们需要实现一个函数test_moveable(容器对象,类型对象),从而能实现传入任意的容器和类型,都能将其组合为一个新的东西:容器<类型>,这样的话我们的函数应该怎么设计呢?

(1)解法一:函数模板(无法实现)

template 
void test_moveable(Container cntr, T elem)
{
    Container c;            //[Error] 'Container' is not a template
    
    for(long i=0; i c1(c);
    Container c2(std::move(c));
    c1.swap(c2);
}

(2)解法二:函数模板+iterator+traits(可以实现)

template
void test_moveable(Container c)
{
    typedef typename iterator_traits::value_type Valtype;
    
    for(long i=0; i c1(c);
    Container c2(std::move(c));
    c1.swap(c2);
}

这样做是可以达到效果的,但是却改变了函数签名,使用的时候我们需要这样调用:test_moveable(list()),和我们开始设计的是不一样的。那么,有没有 template 语法能够在模板接受一个 template 参数 Container 时,当 Container 本身又是一个 class template ,能取出 Container 的template 参数?例如收到一个vector,能够取出其元素类型string?那么这就引出了模板模板参数的概念。也就是下面的解法三。

(3)解法三:模板模板参数 + alias template(可以实现)

template  // 模板模板参数中的T可以不写,默认就是前面的T
              class Container
         >
class XCls
{
private:
    Container c;
public:
    XCLs()
    {
        for(long i=0; i c1(c);
        Container c2(std::move(c));
        c1.swap(c2);
    }
};

// 使用时会报错
XCls c1;        //[Error] vector的实际类型和模板中的Container类型不匹配

这是因为 vector 其实有两个模板参数,虽然第二个有默认值,我们平时也可以像vector这样用。但是在模板中直接这样写类型是不匹配的( Container 只有一个模板参数 )。所以这里就用到了我们一开始提到的模板别名,只要传入的是vector的模板别名就可以了,如下所示:

//不得在function body之内声明
template
using Vec = vector>;
 
XCls c1;
Type Alias

类型别名类似于typedef。

using func = void(*)(int, int);
//相当于
typedef void (*func)(int, int);

using 的用法:

  1. 打开命令空间或者命令空间的成员
  2. 类似第一种,打开类的成员
  3. 类型别名和模板别名(C++ 11开始支持)
using std::cin;                 //1
using _Base::_M_alloacte;       //2
using func = void(*)(int, int); //3
noexpect

保证该函数不会丢出异常,可以在后面加上条件,也就是说在某种条件满足情况下,不会抛出异常。

void foo() noexpect;
void foo() noexpect(true);

一般异常处理流程:当程序发生异常时会将异常信息上报返回给调用者,如果有异常处理则处理,如果该调用者没有处理异常则会接着上报上一层,若到了最上层都没有处理,就会调用std::terminate()->std::abort(),然后终止程序。

{% note warning %}
移动构造函数和移动赋值函数。如果构造函数没有noexcept,vector将不敢使用它。
{% endnote %}

override

override用于明确要重写父类的虚函数上,相当于告诉编译器这个函数就是要重写父类虚函数这样一个意图,让编译器帮忙检查,而没有这个关键字,编译器是不会帮你检查的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FTFTGiVc-1659011616705)(https://aweiii.coding.net/p/blog/d/img/git/raw/main/posts/20220720163404.png)]

final

final新增两种功能:

  1. 禁止基类被继承
  2. 禁止虚函数被重写
decltype decltype 定义

引入新关键字decltype可以让编译器找出表达式的类型,为了区别typeof,以下做一个概念区分:

  • typeof是一个一元运算,放在一个运算数之前,运算数可以是任意类型,非常依赖平台,已过时,由decltype代替;理解为:我们根据typeof()括号里面的变量,自动识别变量类型并返回该类型;
  • typedef:定义一种类型的别名,而不只是简单的宏替换;
  • define:简单的宏替换;
  • typeid() 返回变量类型的字符串,用于print变量类型。
decltype 用法
  1. 用来声明函数的返回值类型,一种新的指定函数返回值类型的方式;
template
auto Add(T1 x, T2 y) -> decltype(x + y);
  1. 模板之间的应用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kckAIBhG-1659011616706)(https://aweiii.coding.net/p/blog/d/img/git/raw/main/posts/20220720172056.png)]

  1. 用来求 lambda 表达式的类型

    lambda 是匿名的函数对象或仿函数,每一个都是独一无二的;如果需要声明一个这种对象的话,需要用模板或者 auto ;如果需要他的 type ,可以使用 decltype ;lambda 没有默认构造函数和析构函数。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FiFVVBk6-1659011616706)(https://aweiii.coding.net/p/blog/d/img/git/raw/main/posts/20220720172547.png)]

Lambdas lambda 语法以及调用方式

定义: lambda 是一组功能的组合定义, lambda 可以定义为内联函数,可以被当做一个参数或者一个对象,类似于仿函数。

最简单的形式:

[] {
    statements
};

auto l = [] {
    statements
};

l(); 

完整形式:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3kQtSWAV-1659011616706)(https://aweiii.coding.net/p/blog/d/img/git/raw/main/posts/20220720175418.png)]

含义
[]lambda 导入器,取用外部变量
()类似函数参数
mutable[]中的导入数据是否可变
throwSpec抛出异常
retType类似函数返回值
{}类似函数体

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gsSKP8FG-1659011616706)(https://aweiii.coding.net/p/blog/d/img/git/raw/main/posts/20220720175204.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wKq811UD-1659011616706)(https://aweiii.coding.net/p/blog/d/img/git/raw/main/posts/20220720175730.png)]

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1030211.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号