- 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 概述可变参数模板。
-
谈的是模板Templates:
- 函数模板
- 类模板
-
变化的是模板参数:
- 参数个数:利用参数个数逐一递减的特性,实现递归函数的调用,使用函数模板完成。
- 参数类型:利用参数个数逐一递减以致参数类型也逐一递减的特性,实现递归继承或递归复合,以类模板完成。
void print()
{
}
template //这里的...是关键字的一部分:模板参数包
void print(const T& firstArg, const Types&... args) //这里的...要写在自定义类型Types后面:函数参数类型包
{
cout << firstArg << endl;
print(args...); //这里的...要写在变量args后面:函数参数包
}
-
注意三种不同的...的应用环境,这些都是语法规则,所以记住即可。
-
还要注意的是,在可变模板参数内部可以使用sizeof...(args)得到实参的个数。
-
如果同时定义了:
templatevoid print(const Types&... args) {}
该函数重载了void print(),void print(const T& firstArg, const Types&... args) 是其特化版本,编译器会优先调用特化版本。
应用-
实现函数的 递归调用
举了一个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)]
- 实现递归继承
templateclass 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
- C++11 auto可以进行自动类型推导。
- C语言默认的局部变量是auto类型的
- C++11 auto可以进行自动类型推导
- 使用auto的场景:类型太长或者类型太复杂
C++11之前初始化时存在多个版本 {},(),=。让使用者使用时比较混乱,C++11提供一种万用的初始化方法,就是使用大括号{}。
原理解析:当编译器看到大括号包起来的东西{t1,t2...tn}时,会生成一个initializer_list
但是如果调用函数自身提供了initializer_list
int i; // 未初始化
int j{}; // j = 0
int* p; // 未初始化
int* q{}; // q = nullptr
-
initializer_list
使用举例: - initializer_list
是一个class(类模板),这个必须类型要一致,跟模板不定的参数类型相比,模板不定的参数类型可以都不一样。 - initializer_list
类似于容器的使用方法
- initializer_list
-
initializer_list源码剖析:
- initializer_list
背后有array数组支撑,initializer_list关联一个array - initializer_list
包含一个指向array的指针,它的拷贝只是一个浅拷贝,比较危险,两个指针指向同一个内存。
- initializer_list
-
initializer_list在STL中的使用:
- 所有容器都接受指定任意数量的值用于构造或赋值或者insert()或assign()。
- 算法max()和min()也接受任意参数。
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++中,当我们设计与编写一个类时,若不显著申明,则类会默认为我们提供如下几个函数:
- 构造函数(A())
- 析构函数(~A())
- 拷贝构造函数(A(A&))
- 拷贝赋值函数(A& operator=(A&))
- 移动构造函数(A(A&&))
- 移动赋值函数(A& operator=(A&&))
注意:拷贝函数如果涉及指针就要区分浅拷贝(指针只占4字节,浅拷贝只把指针所占的那4个字节拷贝过去)和深拷贝(不仅要拷贝指针所占的字节,还要把指针所指的东西也要拷贝过去);
默认提供全局的默认操作符函数:
- operator
- operator &
- operator &&
- operator *
- operator->
- operator->*
- operator new
- 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 Templatetemplateusing Vec = std::vector > //使用 Vec coll;
Alias Template 无法特化。
应用实例(引出模板模板参数)
考虑这样一种需求,假设我们需要实现一个函数test_moveable(容器对象,类型对象),从而能实现传入任意的容器和类型,都能将其组合为一个新的东西:容器<类型>,这样的话我们的函数应该怎么设计呢?
(1)解法一:函数模板(无法实现)
templatevoid 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(可以实现)
templatevoid 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
(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
//不得在function body之内声明 templateType Aliasusing Vec = vector >; XCls c1;
类型别名类似于typedef。
using func = void(*)(int, int); //相当于 typedef void (*func)(int, int);
using 的用法:
- 打开命令空间或者命令空间的成员
- 类似第一种,打开类的成员
- 类型别名和模板别名(C++ 11开始支持)
using std::cin; //1 using _Base::_M_alloacte; //2 using func = void(*)(int, int); //3noexpect
保证该函数不会丢出异常,可以在后面加上条件,也就是说在某种条件满足情况下,不会抛出异常。
void foo() noexpect; void foo() noexpect(true);
一般异常处理流程:当程序发生异常时会将异常信息上报返回给调用者,如果有异常处理则处理,如果该调用者没有处理异常则会接着上报上一层,若到了最上层都没有处理,就会调用std::terminate()->std::abort(),然后终止程序。
{% note warning %}
移动构造函数和移动赋值函数。如果构造函数没有noexcept,vector将不敢使用它。
{% endnote %}
override用于明确要重写父类的虚函数上,相当于告诉编译器这个函数就是要重写父类虚函数这样一个意图,让编译器帮忙检查,而没有这个关键字,编译器是不会帮你检查的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FTFTGiVc-1659011616705)(https://aweiii.coding.net/p/blog/d/img/git/raw/main/posts/20220720163404.png)]
finalfinal新增两种功能:
- 禁止基类被继承
- 禁止虚函数被重写
引入新关键字decltype可以让编译器找出表达式的类型,为了区别typeof,以下做一个概念区分:
- typeof是一个一元运算,放在一个运算数之前,运算数可以是任意类型,非常依赖平台,已过时,由decltype代替;理解为:我们根据typeof()括号里面的变量,自动识别变量类型并返回该类型;
- typedef:定义一种类型的别名,而不只是简单的宏替换;
- define:简单的宏替换;
- typeid() 返回变量类型的字符串,用于print变量类型。
- 用来声明函数的返回值类型,一种新的指定函数返回值类型的方式;
templateauto Add(T1 x, T2 y) -> decltype(x + y);
- 模板之间的应用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kckAIBhG-1659011616706)(https://aweiii.coding.net/p/blog/d/img/git/raw/main/posts/20220720172056.png)]
-
用来求 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)]



