代码地址
专栏介绍
本专栏会持续更新关于STL中的一些概念,会先带大家补充一些基本的概念,再慢慢去阅读STL源码中的需要用到的一些思想,有了一些基础之后,再手写一些STL代码。
(如果你有喜欢一些底层封装,执着于造轮子,我想这个一期不错的专栏)
函数模版
说下自己的理解,C++通过加入参数化编程可以有效的降低软件的成本。
- 优势
1.通过模版机制,在需要修改代码的时候,对于相同参数的修改不需要大批量修改,降低出错概率。
2.支持泛型编程,总的来说还是为了大量的、无意义的工作。这些工作只是因为数据类型的不同,其算法或者业务逻辑相同时,不需要键入多份代码。
- 特征
1.类型严格匹配是模版函数调用的先决条件
2.函数模板不提供隐式类型转化,因此必须严格按照T --> 为了方便理解,把T看做占位符
3.当模板函数和普通函数都符合调用规则时,优先使用普通函数,因为普通函数在编译期间就生成了函数体,而模板函数的生成需要在调用的时候。--> 这一点很重要,模版函数会进行二次编译来确定具体的类型,可以理解为替换占位符T
4.编译器在处理函数模版的时候能够生成任意类型的函数,根据调用的时机产生不同的函数,编译器会对函数模板进行二次编译。这个参数化编程的基础,也是成为编译时多态的由来。--> 在声明的地方对模板代码本身进行编译,在调用的地方对参数化以后的具体调用进行编译。这也导致了,在使用模板函数时,需要使用hpp文件。
demo.h
demo.cpp
main.cpp
解释下:
我们现在知道了,模版函数会进行两次编译。在第一次编译的时候,如果将声明和定义分开,编译不通过,无法确定类型。在运行的时候才会进行类型的绑定。
我们可以声明和定义都写在 .h中
demo.h
可以顺利编译成功,验证了我们的想法。
-------------------------------------------------------------------------------------------------------------------------
这里引出一个问题,虽然写在.h是可以的,但是这样破坏了C++将.h和.cpp分离编程风格的原则,似乎有些不妥。
hpp文件的由来
.h和.cpp分离之后模版第二次编译时会失败,虽然可以放到.h里面,但是这样破坏了.h和.cpp的传统。
命名空间的重要性
当使用模版时,当模版函数相同时,在调用的时候只会使用一个模板函数。当项目比较大时,通常会通过命名空间将其隔开,这很容易理解。在项目中其实用的不多,通常情况下会以类作为空间的区分。
类模版
类模板用于实现类所需数据的类型参数化。类模板在表示数组、表、图等数据结构时显得特别重要。这些数据结构的表示和算法不受所包含的元素类型的影响。道理跟函数模版一样,减少重复编程,降低代码错误发生的概率,降低代码冗余。
右值引用
[为什么需要右值引用?说白了是为了效率,对于右值引用而言,常用的场景在转义语义上,减少拷贝过程,提高程序的效率]
左值 --> 可以出现在赋值运算符的左边,往往代表的是一个存储空间(本质上就是一个块有名字的内存块)
右值 --> 就是我们所谓是数据,其实也不完全能这样描述。对于右值而言,他是具有存储空间的,只不过这个过程很短暂,只是用在计算过程中的,我们无法获取到,仅仅在某个表达式运行过程中存在。【通常右值是一个和运算过程相匹配的临时对象,这个临时对象在所对应的语句执行完毕之后,就销毁了。所以,我们无法从语法层面上直接访问】
说人话:
左值 --> 是一个有名字的,有固定地址的对象
右值 --> 是一个匿名的,没有固定地址的对象
程序中的体现:
int &a = x; --> 左值引用 [左值引用替代值传递,减少拷贝]、
int &&a = x + y; --> 右值引用[通过&&,形成的语法叫做右值引用,使得右值变成了一个与左值完全相同的持久对象]
右值引用:我们知道浅拷贝会带来资源二次释放问题,但是深拷贝在一些临时资源时又是没有必要的,这是右值引用拷贝构造函数的很大意义,即可以避免二次释放问题,又减少了数据拷贝的过程。
整个过程,看似很简单,但是对于一个追求性能和简洁的语言,是一个很大的进步。
右值引用 -- 完美转发(完美的按照我们的要求进行左值和右值转发)
这里我们需要通过move和forward函数来实现左值和右值的转化。
#include <iostream> using namespace std; void Func(int &x) { cout << "左值" <<endl; } void Func(int &&x) { cout << "右值" << endl; } void Func(const int &x) { cout << "左值常" << endl; } void Func(const int &&x) { cout << "右值常" << endl; } template<typename T> void FuncT(T &&a) { Func(std::forward<T> (a)); } int main() { int a = 10; FuncT(10); // 右值 FuncT(a); // 左值 FuncT(move(a)); // 右值 const int b = 8; FuncT(b); // 左值常 FuncT(move(b)); // 右值常 return 0; }
可以看到按照我们要求进行参数匹配...这就是所谓的完美转发,笑死