一.模版
模版涉及的是泛型编程,即通过编译器去确定类型的编程方式,模版分为:类模板和函数模版,下面我们一一复习:
函数模版:
格式:
template<typename T1, typename T2,......,typename Tn> 返回值类型 函数名(参数列表){} //注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)//模版既可以为typename,也可以为size_t
对于函数模版,原理就是通过编译器在编译时通过传入的实参来推导形参对应的类型,然后产生专门对应的类型代码,如下:
//模版: template<class T> void swap(T& a,T& b) {T c=a;a=b;b=c; }int main() {//调用:int a=10,b=20;swap(a,b);//会去生成下面内容->最后面 return 0; }//生成的代码: void swap(int a,int b) {int c=a;a=b;b=c; }
函数模版的实例化分为显示实例化和隐式实例化,上面由编译器推导的都是隐式实例化,显示实例化是指调用时,由用户指定类型,从而直接传递类型,如下:
//直接写调用,还是swapint main() {double d1=1.2;double d2=2.5;swap<double>(d1,d2);return 0; }//注意点: //如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错
类模板:
格式:
template<class T1, class T2, ..., class Tn> class 类模板名 {// 类内成员定义 };
类模版大体与函数模版相似使用即可,不同的是类模板实例化必须在类模板名字后跟<>,然后将实例化的类型放在<>,类模板名字不是真正的类,而实例化的结果才是真正的类
然后就是大家还可以去复习下全特化与偏特化内容,然后还有模版最好不要分离编译,使用.hpp,大家可以参考下面博客复习:
C++之模版详解-CSDN博客
二.容器常见知识点
1.vector与list区别?
1.底层实现上: vector底层是一段连续的顺序表,而list是带头结点的双向循环链表,底层不连续 2.访问上: vector由于其底层连续,可以支持下标随机访问,list不支持随机访问,必须要遍历查找 3.操作上: 插入:vector任意位置插入由于可能需要挪动空间、开辟新空间,整体拷贝效率非常低下,list插入只需要改变指针指向和开辟一个节点空间即可,效率高 删除:vector除尾删外任意位置删除都要挪动空间,效率低,而list不需要挪动空间,只需要改变指针指向,效率高 4.空间利用上: vector是连续的顺序表,不易造成空间碎片化,而list是通过指针将节点相连,各节点之间不连续,空间碎片化严重,空间利用率低 5.迭代器上: vector是使用原生态的迭代器(即只是指针),list是将原生态迭代器进行了封装,可以参考我的github: https://github.com/zhutishou/C-STL-Copywriting/blob/main/list.cpp 6.使用场景上: vector适用于高效存储和随机访问上,不关系插入和删除效率;list适用于大量插入和删除上,不注重空间利用和随机访问
2.vector如何实现插入?
1.首先,先判断插入位置是否正确 2.检查空间是否需要扩容 3.将要插入以后的元素整体后移 4.插入当前位置 5.返回插入位置的迭代器 //具体代码如下: //插入: //插入: iterator insert(iterator pos,const T& val) {if(pos < _begin|| pos > _end){std::cerr<<"该位置不存在"<<std::endl;}//判断是否需要扩容:if(_end == _endofstorage){size_t len = _begin + pos;reserve(capacity()==0?4:2*capacity());pos = _begin + len;}//pos后续位置后移:iterator finish = _end -1;while(finish > pos){*(finish+1) = *finish;finish--;}*pos = val;_end++;return pos; } //vector实现代码如下: https://github.com/zhutishou/C-STL-Copywriting/blob/main/vector.cpp
3.vector和list都有缺陷,是否存在其他容器可以兼顾两者优点呢?
能够兼顾vector和list优点的容器就是deque(双端队列) 介绍: deque是双开口的“连续”空间的数据结构,注意:“连续”实际上是一段段小空间拼凑出来的,只是支持下标随机访问看做连续,支持头插尾插,并且不需要挪动元素 相比于vector和list优点: 1.相比于vector头插尾插O(1),不用挪动数据,并且扩容时,不需要整体拷贝 2.相比于list,空间利用率更高缺点: 不适合遍历,由于其底层是一个个小空间实现的,迭代器需要不断检查边界,效率低下,因此通常还是使用vector或list使用场景: deque通常是stack和queue空间适配器默认的容器,原因是因为两者都不需要遍历,而且只在首尾操作
4.map和set底层是什么?
对于map/set和unordered_map/unordered_set这是我们必须掌握的 map/set底层是红黑树,unordered_map/unordered_set底层是哈希表 该问题实际上是问红黑树的相关信息: 一.规则: 1. 每个结点不是红色就是黑色 2. 根节点是黑色的 3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点) 红黑树通过规则2、3保证树的其最长路径中节点个数不会超过最短路径节点个数的两倍 效率: 红黑树是一个高效的平衡二叉树,增删查改效率都是O(LogN) 实现: 可以参考我的github注意: multimap和multiset也是红黑树实现的,但是与set/map不同点在于这两个允许出现重复的key值
5.map的operator[]是如何实现的?
V& operator[](const K& key)//V --- value { return (*(_t.Insert(ValueType(key, V()))).first).second; }operator[]如果存在key,就返回对应的value,如果不存在key,就插入key,返回默认值
6.一个什么类型才能做map和set的key值?
map和set底层都是红黑树,而红黑树是平衡搜索树,所以一定支持比较大小,当然提供相应的仿函数来比较大小的类型也是可以的
7.unordered_map和unordered_set是如何实现的?
这个问题本质上是询问我们其底层实现,显然这两者都是通过哈希实现的,那么就讲述哈希特点 规则: 哈希是通过哈希函数进行相应的映射关系,使key与存储位置建立一一的映射,在查找时就可以快速找到 对比: 哈希在查找时效率为O(1),而红黑树效率为O(LogN),顺序表效率为O(N) 常见问题: 哈希冲突: 该问题无法避免,是指不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞,解决方法:建立合适的空间大小,减少通过哈希函数计算之后碰撞的发生,还有两种常见处理方法:开散列和闭散列
8.一个类型做unordered_map和unordered_set的key有什么要求?
哈希是需要通过哈希函数进行映射的,所以key值必须可以实现向整数的转换,这样才能映射到存储空间中
最后,感谢你的支持!!!