C++小工修炼之路XVII

mac2024-05-29  38

什么是STL? C++标准库:标准模板库 通俗说:对常见的数据结构的封装+通用的算法(算法可以操作任意类型的数据,因为这些数据都是按照模板的方式给 出的,可以是内置类型的数据,也可以是自定义类型的数据。而且这些算法对于各种数据结构都是适用的,无论你将 数据承载在哪种数据结构中都可以进行处理) sort:排序算法,无论你对vector,还是list都是通用的 STL六大组件: 容器: 组织数据,对常见数据结构的封装 序列式容器: string:深浅拷贝问题,底层是由动态类型的字符串封装的,默认给出的是15个有效的,静态的字符空间,再需要扩容时以动态的字符串进行扩容 vector:底层是由动态的顺序表封装的,考虑扩容机制 list:带头结点的双向的循环的链表: queue:底层由deque实现 stack:底层由deque实现 dueue, priority_queue优先级队列(默认是大堆) forward_list(带头结点的循环单链表c++11, array(c++11新增的,静态的顺序表) 关联式容器: 接口分类: 构造与销毁: 元素访问: 容量操作: 元素修改操作: 迭代器: 特殊操作:(不是所有的容器都有特殊操作) 关于算法的简单的介绍:STL通用的类型的算法---与数据类型无关,与数据结构无关。 与数据类型无关靠的是:template模板 与数据结构无关靠的是:迭代器

如何给一个类增加迭代器:

1.根据容器底层的数据结构封装一个迭代器类 实际上是对指针进行封装 构造函数 / * / -> / == / ++ / --(非必须,因为单链表就不需要) / !=,在迭代器类中给出这些操作, 2.在容器类中给迭代器类型取别名;其实不取也可以,取的话就会显得统一:typedef迭代器类型 iterator; 3.在容器类中给出begin()end()操作接口 所以算法就可以通过迭代器透明化(不必知道容器底层的数据结构)的去操作容器中的数据 迭代器分类: 原生态的指针 对原生态指针的封装 关于迭代器失效: 迭代器失效:迭代器对应的指针失效,指针为野指针,指针指向的 内容不存在 解决的办法就是给迭代器重新赋值 vector和list失效的场景?前面说过了 算法可以定制功能: 仿函数(函数对象):可以将一个类的对象按照函数的方式进行使用。仿函数的功能就是可以使算法,或者一个类更加的灵活。 在仿函数中就是定义一个类,在类中对()进行重载, 比如有一个算法for_ward(v.begin(),v.end(),op)意思就是在v的这段区间上的每一个元素,进行op 操作,op就是你所要进行的操作,可以是一个仿函数,为了定制这个函数的功能,你可以先定义一个类, 在类中对()重载,在函数体内是你要对每个元素进行的操作。 适配器: 比如我们平时用的stack和queue,就是对deque的封装 priority_queue:堆(默认大堆)想要使用小堆就要<存放的元素类型,vector<存放的元素类型>,great<存放的元素类型>> 注意:优先级队列中存放的必须是内置类型的话优先级队列可以直接使用 加入存放的是自定义的数据类型则必须要对>或者<进行重载,但是有的时候重载之后还是不行,那么可能就需要通过仿函数来进行完成了

C++适配器

模板参数分为:非类型模板参数,类型模板参数

template<class T,size_t N> 这个T就是类型模板参数,N就是非类型的模板参数 class array { public: ... private: T _array[N]; //N是常量 size_t _size; }; array<int,10> arr; //定义类型的时候必须要都给出,例如这个还要指定大小 注意: 1.浮点数,类对象以及字符串是不允许作为非类型的模板参数的(自定义的类型也不可以) 2.非类型的模板参数必须要求在编译期就能确定结果,否则就是失败

模板的特化:

#include<iostream> using namespace std; template<class T> T& MAX(T& left,T& right) { return left > right ? left : right; } class Date { friend ostream& operator<<(ostream& _cout, Date& p); public: Date(int year,int month,int day) :_year(year) , _month(month) , _day(day) {} bool operator>(Date& p) { if (_year > p._year || _year == p._year && _month > p._month || _year == p._year && _month == p._month&&_day > p._day) return true; return false; } private: int _year; int _month; int _day; }; ostream& operator<<(ostream& _cout, Date& p) { _cout << p._year << " " << p._month << " " << p._day << " "; return _cout; } int main() { int a = 10; int b = 20; cout << MAX(a, b) << endl; //内置类型可以直接比较 cout << MAX(b, a) << endl; Date d1(2019, 10, 22); //自定义类型需要在类中将比较方式给出,那么就可以使用MAX函数来进行比较了 Date d2(2019, 10, 23); cout << MAX(d1, d2) << endl; cout << MAX(d2, d1) << endl; //但是有的类型不能处理,或者处理出来就是错误 //通常情况下可以使用模板实现一些与类型无关的代码,但是对于一些特殊类型得到的就是错误的情况 char* p1 = "world"; char* p2 = "hello"; cout << MAX(p1, p2) << endl; //打印结果出错 cout << MAX(p2, p1) << endl; return 0; }

可以看出对于char*类型的变量的比较是存在错误的

模板的特化又分为函数模板的特化和类模板的特化: 函数模板一般特化的比较少 对于上面的函数来说就是MAX函数对于char*类型的变量在比较的时候会存在错误,所以接着我们就对char*类型来进行特化 在上面的代码段中插入下面的代码,那么在调用char*类型的函数的时候就会自动的去调用我们特化的MAX函数 //模板的特化 template<> char*& MAX<char*>(char*& left, char*& right) { if (strcmp(left, right) == 1) ///strcmp大于返回1,小于返回-1 return left; return right; }

特化后的打印结果: 函数模板特化的注意事项:

如果要进行特化,那么首先就得先有一个模板函数(例如上面第一次的MAX函数),其次是我们要清楚,已经给出的模板对于哪种类型是有问题的(比如char*),这样才知道要对哪种类型,进行特化。 //普通模板和特化模板的比较 template<class T> T& MAX(T& left,T& right) { return left > right ? left : right; } template<> char*& MAX<char*>(char*& left, char*& right) { if (strcmp(left, right) == 1) ///strcmp大于返回1,小于返回-1 return left; return right; }

但是一般函数模板是不需要特化的,我们对于上面的情况不是进行特化,而是直接将不能处理类的函数直接给出(例如直接将char*类型的比较函数给出),因为直接给出的话比较简单,容易,因为某些函数的特化很难搞定,或者说根本搞不定。

char* MAX(char* left,char* right) { if (strcmp(left, right) == 1) ///strcmp大于返回1,小于返回-1 return left; return right; }

反正就是函数特化,有时候容易出状况,就算你把特化版本写出来有时也不会调,所以说函数模板对于哪一种类型有问题,就直接把哪一种类型的具体函数给出来就可以了,特化容易出错。

2void func(const T& p) //那么按照常理来推测就是const int*& p ,也就是说const修饰的是p所只想的空间里面的内容不能修改 { 3*p = 100; //但是这里修改了 int b = 20; p = b; //这一步反而会报错 } int main() { int a = 10; int* p = &a; 1func(p); //这里传的是int* 类型 4》 cout << a << endl; //并且在外部还可以正常打印出100 return 0; } 由上可见其实对于函数参数的类型的推演,也就是第二步我们就判断错误了, const int a = 10; int const b = 10; 对于上面两个const修饰的都是变量a,b和位置并没有关系,也就是说在参数列表中T 就仅仅只是一个变量, 别管是int* ,int,int**,反正const修饰的只是后面的变量。

模板特化: 全特化:对所有类型的参数进行特化

template<class T1,class T2> class Date { public: Date() {} private: T1 _year; T2 _month; }; ///上面类的特化 template<> class Date { public: Date() {} private: int _year; int _month; }; Date<int,int> m; //调用特化版本 Date<int,double> w; // 调用模板类

偏特化: 又包含部分特化:—》将模板参数中的部分类型进行特化

template<class T1, class T2> class Date { public: Date() {} private: T1 _year; T2 _month; }; ///上面类的偏特化 //意思就是只要在定义类对象的时候,给出的第二个参数是int类型的就调用偏特化版本 template<class T1, int> class Date { public: Date() {} private: T1 _year; int _month; }; Date<int, int> m; //调用偏特化 Date<int, double> w; // 调用模板类

非类型模板参数必须在编译器就确认其结果,

template<class T, size_t N> class array { public: ... private: T _array[N]; //N是常量 size_t _size; }; int a = 10; int b = 20 array<int,a + b > arr;//必须在编译期给出结果,这里会编译报错

偏特化的第二个特性:让米板参数列表中的类型更加的严格

//假如我们想让模板只能处理指针类型的参数,那么就可以这样做 //让模板参数列表的限制更加的严格 template<class T1, class T2> class Date<T1*,T2*> { public: Date() {} private: T1* _year; T2* _month; }; Date<int*, int*> m; //调用偏特化 Date<int*, double*> w; // 调用模板类
最新回复(0)