认识headers
C++ 标准库(STL大部分属于C++标准库) —STL和标准库的关系
STL各组件应用实例
STL体系结构基础介绍
1.容器帮助我们吧内存的问题解决,需要一个分配器来支持容器,容器是一个模板类,有一些操作是自己做,更多的是独立出来成为算法。
2.算法和容器之间的桥梁是迭代器,迭代器是一种泛化的指针
STL分为六大组件:
容器(container):常用数据结构,大致分为两类,序列容器,如vector,list,deque,关联容器,如set,map。在实现上,是类模板(class template)
迭代器(iterator):一套访问容器的接口,行为类似于指针。它为不同算法提供的相对统一的容器访问方式,使得设计算法时无需关注过多关注数据。(“算法”指广义的算法,操作数据的逻辑代码都可认为是算法)
算法(algorithm):提供一套常用的算法,如sort,search,copy,erase … 在实现上,可以认为是一种函数模板(function template)。
配置器(allocator):为容器提供空间配置和释放,对象构造和析构的服务,也是一个class template。
仿函数(functor):作为函数使用的对象,用于泛化算法中的操作。
配接器(adapter):将一种容器修饰为功能不同的另一种容器,如以容器vector为基础,在其上实现stack,stack的行为也是一种容器。这就是一种配接器。除此之外,还有迭代器配接器和仿函数配接器。
STL 六大组件的交互关系
Container 通过 Allocator 取得数据储存空间
Algorithm 通过 Iterator 存取 Container 内容
Functor 可以协助 Algorithm 完成不同的策略变化
Adapter 可以修饰或套接 Functor、Iterator。
vector<> 尖括号说明是一个模板
//一个例子说明六大部件
#include <vector>
#include <algorithm>
#include <functional>
#include <iostream>
using namespace std;
int main()
{
int ia[6] = {27, 210, 12, 47, 109, 83};
vector<int, allocator<int>> vi(va,ia + 6);//<>符号表示模板,allocator<int>是一个分配器模板,一般vector都会自动默认使用分配器
cout << count_if(vi.begin(), vi.end(),
not1(bind2nd(less<int>(), 40)));
return 0;
}
//vector是一个容器containers
//count_if是一个算法algorithm,计算vi里面的个数
//vi.begin(), vi.end()是一个迭代器iterator
//less<int>是一个仿函数function
//bind2nd是一个适配器function adapter,绑定第二个参数为40
//notl是一个适配器function adapter,表示否定
//整个表达,vi大于等于40的个数
容器规定:“前闭后开”区间(涵盖第一个不涵盖最后一个) [)
引用代表其自身
容器之分类与各种测试
容器种类:
1.顺序容器Sequence Containers
Array(固定元素个数)C++11
Vector(尾部个数可以扩充)
Deque(头尾个数可以扩充)
List(双向链表)
Forward-List(单向链表)C++11
2.关联容器Associative Containers:元素有key和value,适合做快速的查找(用key来找value)
底层实现是红黑树,可以自动左右平衡,是一种高度平衡的二叉树
Set/Multiset(key=value)
Map/Multimap(key对应value;multi map 允许重复元素,map不允许有重复)
不定序容器Unordered Containers(属于关联容器)
HashTable Separate chaining(不定序容器使用hashtable):同放一个内存,内存放这几个数据的链表
使用array
#define _CRT_SECURE_NO_WARNINGS
#include <array>
#include <iostream>
#include <ctime>
#include <cstdlib> //qsort, bsearch, NULL
using namespace std;
const long ASIZE = 50000L;
//用户输入需要查找的long类型元素
long get_a_target_long()
{
long target = 0;
cout << "target (0~" << RAND_MAX << "): ";
cin >> target;
return target;
}
//比较传入参数大小,a > b则返回1,否则返回-1
int compareLongs(const void* a, const void* b)
{
return (*(long*)a - *(long*)b);
}
namespace jj01
{
void test_array()
{
cout << "\ntest_array().......... \n";
array<long, ASIZE> c;//50000个long类型元素
clock_t timeStart = clock();//开始计时
for (long i = 0; i < ASIZE; ++i) {//为array随机生成50000个long类型元素
c[i] = rand();
}
cout << "milli-seconds : " << (clock() - timeStart) << endl; //打印初始化array时间
cout << "array.size()= " << c.size() << endl;//打印数组长度
cout << "array.front()= " << c.front() << endl;//打印数组首元素
cout << "array.back()= " << c.back() << endl;//打印数组最后元素
cout << "array.data()= " << c.data() << endl;//打印数组在内存的首地址
long target = get_a_target_long();
timeStart = clock();//排序查找开始计时
::qsort(c.data(), ASIZE, sizeof(long), compareLongs);//排序
long* pItem = (long*)::bsearch(&target, c.data(), ASIZE, sizeof(long), compareLongs);//二分法查找
cout << "qsort()+bsearch(), milli-seconds : " << (clock() - timeStart) << endl; // 打印排序查找时间
if (pItem != NULL)//判断是否找到
cout << "found," << *pItem << endl;
else
cout<< "not found! " << endl;
}
}
int main()
{
jj01::test_array();
return 0;
}
test_array()..........
milli-seconds : 6
array.size()= 50000
array.front()= 41
array.back()= 14499
array.data()= 003CE928
target (0~32767): 555
qsort()+bsearch(), milli-seconds : 36
found,555
tips1: array.data()返回数组在内存中起点的地址
tips2: 二分查找的数组肯定是已经经过排序的,否则无法使用二分查找
#define _CRT_SECURE_NO_WARNINGS
#include <vector>
#include <stdexcept>
#include <string>
#include <iostream>
#include <ctime>
#include <cstdio>//sprintf()
#include <cstdlib> //qsort, bsearch, NULL
#include <algorithm>//sort()
using namespace std;
const long ASIZE = 50000L;//随机生成字符数个数
//用户输入需要查找的string类型元素
string get_a_target_string()
{
long target = 0;
char buf[10];
cout<< "target (0~" << RAND_MAX << "): ";
cin >> target;
//printf 跟printf 在用法上几乎一样,只是打印的目的地不同而已
//前者打印到字符串中,后者则直接在命令行上输出
sprintf(buf, "%d", target);
return string(buf);
}
//字符串比较
int compareStrings(const void* a, const void* b)
{
if (*(string*)a > *(string*)b) {
return 1;
}
else if (*(string*)a < *(string*)b) {
return -1;
}
else {
return 0;
}
}
namespace jj02
{
void test_vector(const long& value)
{
cout << "\ntest_vector().......... \n";
vector<string> c;//定义一个vector容器存放string类型数据
char buf[10];
clock_t timeStart = clock();//开始计时
for (long i = 0; i < value; ++i) {
try {
sprintf(buf, "%d", rand());
c.push_back(string(buf));
}
catch (exception& p) {
cout << "i= " << i << " " << p.what() << endl;
abort();//中止程序执行
}
}
cout << "milli-seconds : " << (clock() - timeStart) << endl;//打印初始化array时间
cout << "vector.max_size()= " << c.max_size() << endl; //1073747823
cout << "vector.size()= " << c.size() << endl; // 打印vector长度
cout << "vector.front()= " << c.front() << endl;//打印vector首元素
cout << "vector.back()= " << c.back() << endl;//打印vector最后元素
cout << "vector.data()= " << c.data() << endl;//打印vector在内存的首地址
cout << "vector.capacity()= " << c.capacity() << endl << endl;//打印vector容量
string target = get_a_target_string();
//该代码块测试stl的find函数,查找用户输入字符串,并打印查找所花费时间
{
timeStart = clock();
sort(c.begin(), c.end());
auto pItem = find(c.begin(), c.end(), target);
cout << "std::find(), milli-seconds : " << (clock() - timeStart) << endl;
if (pItem != c.end())
cout << "found, " << *pItem << endl;
else
cout << "not found! " << endl << endl;
}
//该代码块测试c标准库的二分查找bsearch函数,查找用户输入字符串,并打印查找所花费时间
{
timeStart = clock();
sort(c.begin(), c.end());//排序
cout << "sort(), milli-seconds : " << (clock() - timeStart) << endl;//打印排序时间
timeStart = clock();
string* pItem = (string*)::bsearch(&target, c.data(), c.size(), sizeof(string), compareStrings);
cout << "bsearch(), milli-seconds : " << (clock() - timeStart) << endl;//打印二分查找时间
if (pItem != NULL)
cout << "found, " << *pItem << endl << endl;
else
cout << "not found! " << endl << endl;
}
}
}
int main()
{
jj02::test_vector(ASIZE);
return 0;
}
test_vector()..........
milli-seconds : 488
vector.max_size()= 153391689
vector.size()= 50000
vector.front()= 41
vector.back()= 14499
vector.data()= 00F3F060
vector.capacity()= 61447
target (0~32767): 33456
std::find(), milli-seconds : 835
not found!
sort(), milli-seconds : 390
bsearch(), milli-seconds : 0
not found!
tips1: try和catch是在抓取异常的发生,如果发生异常一定要abort退出程序
tips2: ::find表明是全局函数,如果不加的话程序最后也会去全局找
tips3:vector扩充是在别的地方找到一块内存,然后把原来的搬过去,找到的那一块必须是两倍大
使用list
#define _CRT_SECURE_NO_WARNINGS
#include <list>
#include <stdexcept>
#include <string>
#include <cstdlib> //abort()
#include <cstdio> //snprintf()
#include <algorithm> //find()
#include <iostream>
#include <ctime>
using namespace std;
namespace jj03
{
const long ASIZE = 50000L;//随机生成字符数个数
string get_a_target_string()
{
long target = 0;
char buf[10];
cout << "target (0~" << RAND_MAX << "): ";
cin >> target;
sprintf(buf, "%d", target);
return string(buf);
}
void test_list(const long& value)
{
cout << "\ntest_list().......... \n";
list<string> c;
char buf[10];
clock_t timeStart = clock();
for (long i = 0; i< value; ++i)
{
try {
sprintf(buf, "%d", rand());
c.push_back(string(buf));
}
catch (exception& p) {
cout << "i=" << i << " " << p.what() << endl;
abort();
}
}
cout << "milli-seconds : " << (clock() - timeStart) << endl;
cout << "list.size()= " << c.size() << endl;
cout << "list.max_size()= " << c.max_size() << endl;
cout << "list.front()= " << c.front() << endl;
cout << "list.back()= " << c.back() << endl;
string target = get_a_target_string();
timeStart = clock();
auto pItem = find(c.begin(), c.end(), target);//调用stl标准库算法find()
cout << "std::find(), milli-seconds : " << (clock() - timeStart) << endl;
if (pItem != c.end())
cout << "found, " << *pItem << endl;
else
cout << "not found! " << endl;
timeStart = clock();
c.sort();//调用list内置sort()算法
cout << "c.sort(), milli-seconds : " << (clock() - timeStart) << endl;
c.clear();
}
}
int main(void)
{
jj03::test_list(jj03::ASIZE);
return 0;
}
test_list()..........
milli-seconds : 231
list.size()= 50000
list.max_size()= 119304647
list.front()= 41
list.back()= 14499
target (0~32767): 7777
std::find(), milli-seconds : 4
found, 7777
c.sort(), milli-seconds : 279
tips1:动态内存
tips2:标准库提供全局sort,list容器自己也有sort,容器自己的sort一定是比较快(197行)
使用forward_list
#define _CRT_SECURE_NO_WARNINGS
#include <forward_list>
#include <stdexcept>
#include <string>
#include <iostream>
#include <ctime>
#include <cstdio>//sprintf()
#include <cstdlib> //qsort, bsearch, NULL
#include <algorithm>//sort()
using namespace std;
const long ASIZE = 50000L;//随机生成字符数个数
//用户输入需要查找的string类型元素
string get_a_target_string()
{
long target = 0;
char buf[10];
cout<< "target (0~" << RAND_MAX << "): ";
cin >> target;
//printf 跟printf 在用法上几乎一样,只是打印的目的地不同而已
//前者打印到字符串中,后者则直接在命令行上输出
sprintf(buf, "%d", target);
return string(buf);
}
namespace jj04
{
void test_forward_list(const long& value)
{
cout << "\ntest_forward_list().......... \n";
forward_list<string> c;//定义一个list容器存放string类型数据
char buf[10];
clock_t timeStart = clock();//开始计时
for (long i = 0; i < value; ++i) {
try {
sprintf(buf, "%d", rand());
c.push_front(string(buf));
}
catch (exception& p) {
cout << "i= " << i << " " << p.what() << endl;
abort();//中止程序执行
}
}
cout << "milli-seconds : " << (clock() - timeStart) << endl;
cout << "forward_list.max_size()= " << c.max_size() << endl;
cout << "forward_list.front()= " << c.front() << endl;
string target = get_a_target_string();
//该代码块测试stl的find函数,查找用户输入字符串,并打印查找所花费时间
{
timeStart = clock();
auto pItem = find(c.begin(), c.end(), target);
cout << "std::find(), milli-seconds : " << (clock() - timeStart) << endl;
if (pItem != c.end())
cout << "found, " << *pItem << endl;
else
cout << "not found! " << endl << endl;
}
//调用forward_list内置sort()算法
{
timeStart = clock();
c.sort();
cout << "c.sort(), milli-seconds : " << (clock() - timeStart) << endl;
c.clear();
}
}
}
int main()
{
jj04::test_forward_list(ASIZE);
return 0;
}
test_forward_list()..........
milli-seconds : 217
forward_list.max_size()= 134217727
forward_list.front()= 14499
target (0~32767): 3333
std::find(), milli-seconds : 9
found, 3333
c.sort(), milli-seconds : 300
使用deque
tips1:一段叫做一个buffer,指针走到一段buffer末尾,++操作符重载自动走到下一段,所以deque是分段连续
tips2:list每次扩充一个节点,效率最高,但是查找很慢;vector每次扩充两倍;deque扩充一个buffer
tips3:deque没有自己的sort,只能用全局sort
tips4:关联式容器的查找都非常快
使用stack,queue
tips1:stack和queue底层是通过deque实现的,从设计模式上来说,这两种容器本质上是deque的适配器.
tips2:这两个容器的元素进出是有严格顺序的,因此stack和queue不支持有关迭代器的操作.比如stack,防止iterator改变其先进后出的独特特性
**使用multiset **
tips1:multiset自带的find函数比全局find要快,但是insert要慢一点
使用multimap
tips:因为multimap支持重复的key,因此不能使用重载的[]运算符进行插入
**使用unordered_multiset/map **
tips1:unordered_multiset和unordered_multimap底层是使用hash+链表实现的.
tips2:unordered_multiset和unordered_multimap的元素个数小于篮子数目,若元素数目达到篮子个数,则容器扩容,将篮子数组扩充约一倍.
使用set /map
tips1:map可以用[],因为key不会重复
使用unordered_set /map
分配器之测试
容器的背后需要分配器支持其对内存的使用
STL容器默认的分配器是std::allocator,除此之外gcc额外定义了几个分配器,其头文件均在目录ext下.
gcc额外定义的分配器均位于__gnu_cxx命名空间下.分配器一般用于构建容器,不会直接使用.因为分配器想要直接使用也不好用(使用free关键字时不需要指定回收内存的大小,而分配器的deallocate函数需要指定回收内存大小).
STL容器源码分析
STL设计模式:OOP和GP
整个C++标准库并不是用面向对象实现的,而是泛型编程
OOP(Object-Oriented Programming)和GP(Generic Programming)是STL容器设计中使用的两种设计模式.
1.OOP的目的是将数据和方法绑定在一起,例如对std::list容器进行排序要调用std::list::sort方法(sort放到list里面).
2.GP的目的是将数据和方法分离开来,例如对std::vector容器进行排序要调用std::sort方法.
为什么list不能像vector和deque一样去用全局的sort,这种不同是因为std::sort方法内部调用了iterator的-运算,如果要运用需要满足一定的条件,std::list的iterator没有实现-运算符,而std::vector的iterator实现了-运算符.
运算符重载与模板特化
实现STL的两大基础就是运算符重载和模板特化.
特化又叫做全特化,即完整的特化,相对应的是局部的特化。偏特化:有两个模板参数,只指定其中一个,这是数量上的偏特化,还有范围上的偏特化
分配器
VC6.0的默认分配器std::allocator定义如下,可以看到VC6.0的分配器只是对::operator new和::operator delete的简单封装
allocator()创建一个临时对象
gcc2.9中的分配器std::allocator与VC6.0的实现类似,但std::allocator并非gcc2.9的默认分配器,观察容器源码,可以看到,gcc2.9的默认分配器为std::alloc.
做法是减少malloc的使用,因为malloc带有额外的开销,malloc每次分配的内存有大有小,所以需要cookie去记录,但容器里面元素的大小是一样的,所以似乎不需要吧每个元素的大小都记录。
std::alloc内部维护一个链表数组,数组中的每个链表保存某个尺寸的对象(以8的倍数增长),减少了调用malloc的次数,从而减小了malloc带来的额外开销.每个容器元素的大小都被调整为8的倍数(比如50->56)
在gcc4.9以后,默认分配器变为std::allocator,变回了对::operator new和::operator delete的简单封装.gcc2.9中的std::alloc更名为__gnu_cxx::__pool_alloc.
容器
list
1.在32位电脑上,一个指针4个字节
2.因为链表是一个非连续空间,所以iterator不能够是一个指针,但要模拟指针的操作(除了vector和array之外,所有容器的iterator必须是一个class,才能够完成足够聪明的动作)
3.为实现前闭后开的特性,在环形链表末尾加入一个用以占位的空节点,并将迭代器list::end()指向该节点.
iterator要模拟指针,即箭头符号,,++,–甚至+=,-=操作符。迭代器__list_iterator重载了指针的,->,++,–等运算符,并定义了iterator_category、value_type、difference_type、pointer和pointer5个关联类型(associated types),这些特征将被STL算法使用.
为了模仿整数i++不能连续做两次即(i++)++,所以i++的返回值是self。而++i是返回引用&
————————————————————————————————————————————————————————————————————————
iterator需要遵循的原则
1.算法需要知道iterator有哪些性质,因为他要做动作
2.下面的rotate例子中,iterator_category指的是其移动性质,有的iterator可以++,有的可以–,有的可以跳着走
difference_type:两个iterator之间的距离应该用什么type去表现
value_type:iterator所指元素本身的类型,如string等
另外两种性质reference和pointer从来没有被使用过
STL的算法传入的参数的一般是迭代器或指针,在算法内部,需要根据传入的迭代器或指针推断出迭代器的关联类型(associated types).
有时候呼叫的是一个指针,而不是泛化的指针iterator,但指针不能回答关联的问题,所以需要traits
1.迭代器的5个关联类型在类中均有定义,但是指针类型的关联类型需要根据指针类别进行确定,为了使STL算法同时兼容迭代器和一般指针,就在迭代器(指针)和算法之间加一个中间层萃取器(traits).
2.迭代器萃取器iterator_traits能够兼容迭代器和一般指针,获取其5个关联类型:iterator_category、value_type、difference_type、pointer和pointer.
在实现上,iterator_traits类使用模板的偏特化,对于一般的迭代器类型,直接取迭代器内部定义的关联类型;对于指针和常量指针进行偏特化,指定关联类型的值.
// 针对一般的迭代器类型,直接取迭代器内定义的关联类型
template<class I>
struct iterator_traits {
typedef typename I::iterator_category iterator_category;
typedef typename I::value_type value_type;
typedef typename I::difference_type difference_type;
typedef typename I::pointer pointer;
typedef typename I::reference reference;
};
// 针对指针类型进行特化,指定关联类型的值
template<class T>
struct iterator_traits<T *> {
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef T& reference;
};
// 针对指针常量类型进行特化,指定关联类型的值
template<class T>
struct iterator_traits<const T *> {
typedef random_access_iterator_tag iterator_category;
typedef T value_type; // value_tye被用于创建变量,为灵活起见,取 T 而非 const T 作为 value_type
typedef ptrdiff_t difference_type;
typedef const T* pointer;
typedef const T& reference;
};
想要在算法内获取关联类型的值,只需像下面这样写:
template<typename T>
void algorithm(...) {
typename iterator_traits<I>::value_type v1;
}
vector
1.容器vector的迭代器start指向第一个元素,迭代器finish指向最后一个元素的下一个元素,这两个迭代器对应begin()和end()的返回值,维持了前闭后开的特性.
2.vector对使用者是连续的,因此重载了[]运算符(所有连续的容器都可以).
3.vector的实现也是连续的,因此使用指针类型做迭代器(即迭代器vector::iterator的实际类型是原生指针T*).
vector::push_back方法先判断内存空间是否满,若内存空间不满则直接插入;若内存空间满则调用insert_aux函数先扩容两倍再插入元素.
insert_aux被设计用于在容器任意位置插入元素,在容器内存空间不足会现将原有容器扩容(insert函数也是调用这个函数)
(旧版)上面讲到链表list的迭代器被特殊设计,因为链表是分离的,但是vector是连续的,按照指针就可以当做迭代器
(新版)
array
将数组封装成容器array是为了使之与STL算法兼容(实现容器的规则,比如迭代器实现五种类型,使得算法可以采取优化的动作),其内部实现只是简单封装了一下数组,甚至**没有构造函数和析构函数.**与vector一样使用原生指针做迭代器.
deque
1.容器deque内部是分段连续的,对使用者表现为连续的.
2.deque的迭代器是一个class
3.deque::map的类型为二重指针T**,称为控制中心,控制中心是一个vector,其中每个元素指向一个buffer.,这里的名为map的vector扩充的时候是copy到中段,使得左右都有空间
4.迭代器deque::iterator的核心字段是4个指针:cur指向当前元素、first和last分别指向当前buffer的开始和末尾、node指向控制中心.
5.deque迭代器包含4个指针,即16个字节,一个deque包含start(deque 迭代器,16),finish(deque 迭代器,16),map(4),mapsize(4)一共40字节
template<class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_iterator {
// 定义5个关联类型
typedef random_access_iterator_tag iterator_category; // 关联类型1
typedef T value_type; // 关联类型2
typedef ptrdiff_t difference_type; // 关联类型3
typedef Ptr pointer; // 关联类型4
typedef Ref reference; // 关联类型5
typedef size_t size_type;
typedef T **map_pointer;
typedef __deque_iterator self;
// 迭代器核心字段:4个指针
T *cur; // **指向当前元素**
T *first; // 指向当前buffer的开始
T *last; // 指向当前buffer的末尾
map_pointer node; // **指向控制中心**
// ...
};
deque::insert方法先判断插入元素在容器的前半部分还是后半部分,再将数据往比较短的那一半推.
iterator insert(iterator position, const value_type &x) {
if (position.cur == start.cur) { // 若插入位置是容器首部,则直接push_front
push_front(x);
return start;
} else if (position.cur == finish.cur) { // 若插入位置是容器尾部,则直接push_back
push_back(x);
iterator tmp = finish;
--tmp;
return tmp;
} else {
return insert_aux(position, x);
}
}
template<class T, class Alloc, size_t BufSize>
typename deque<T, Alloc, BufSize>::iterator deque<T, Alloc, BufSize>::insert_aux(iterator pos, const value_type &x) {
difference_type index = pos - start; // 插入点前的元素数
value_type x_copy = x;
if (index < size() / 2) { // 1. 如果插入点前的元素数较少,则将前半部分元素向前推
push_front(front()); // 1.1. 在容器首部创建元素
// ...
copy(front2, pos1, front1); // 1.2. 将前半部分元素左移
} else { // 2. 如果插入点后的元素数较少,则将后半部分元素向后推
push_back(back()); // 2.1. 在容器末尾创建元素
copy_backward(pos, back2, back1); // 2.2. 将后半部分元素右移
}
*pos = x_copy; // 3. 在插入位置上放入元素
return pos;
}
迭代器deque::iterator模拟空间的连续性:操作符重载
queue和stack
容器queue和stack作为deque的**适配器(adapter),**其内部均默认封装了一个deque作为底层容器,通过该deque执行具体操作.
容器queue和stack的元素进出是严格有序的,因此两个容器都不允许遍历,其内部没有定义iterator.
实际上queue和stack的底层容器也可以指定为list;stack的底层容器也可以指定为vector,这些底层容器均实现了queue和stack内部用到的方法.
实际上,若指定了错误的底层容器但没有调用不支持的方法的话,程序仍能够编译通过,这说明编译器在处理模板时不会做全面的检查.
rb-tree
1.容器rb_tree封装了红黑树,是有序容器,提供了迭代器iterator用以遍历,但不应使用iterator直接改变元素值(虽然编程层面并没有禁止这样做).
2.rb_tree提供两种插入操作:insert_unique和insert_equal,前者表示节点的key一定在整棵树中独一无二,否则插入失败;后者表示节点的key可重复.
3.对于rb_tree,定义一个概念:节点的value包括其key和data,这里的data表示一般说法中的value.
红色和黑色代表容器的某种状态
rb_tree的header指向一个多余的空节点,用以维持其前闭后开的特性.
下面程序演示rb_tree的使用:
keyofvalue:怎么从value中提取key
set和multiset
1.**容器set和multiset以rb_tree为底层容器,**因此其中元素是自动排序的,排序的依据是key.set和multiset元素的value和key一致.
2.set和multiset提供迭代器iterator用以顺序遍历容器, 但无法使用iterator改变元素值,因为set和multiset使用的是内部rb_tree的const_iterator.
3.set元素的key必須独一无二,因此其insert()调用的是内部rb_tree的**insert_unique()**方法;multiset元素的key可以重复,因此其insert()调用的是内部rb_tree的insert_equal()方法.
map和multimap
1.容器map和multimap以rb_tree为底层容器,因此其中元素是自动排序的,排序的依据是key.
2.map和multimap提供迭代器iterator用以顺序遍历容器.无法使用iterator改变元素的key,但可以用它来改变元素的data, 因为map和multimap内部自动将key的类型设为const.
3.map元素的key必須独一无二,因此其insert()调用的是内部rb_tree的insert_unique()方法;multimap元素的key可以重复,因此其insert()调用的是内部rb_tree的insert_equal()方法.
map容器重载的[]运算符返回对应data的引用,只有map可以,multimap不可以
mapped_type& operator[](key_type&& __k)
{
// 先查找key
// 若key存在,则直接返回其data的引用
iterator __i = lower_bound(__k);
// 若key不存在,则先插入key,再返回对应的引用
if (__i == end() || key_comp()(__k, (*__i).first))
__i = _M_t._M_emplace_hint_unique(__i, std::piecewise_construct, std::forward_as_tuple(std::move(__k)), std::tuple<>());
return (*__i).second;
}
hashtable
空间足够时,编号有足够的空间独立放置;空间不足时,可能有几个编号对应到一个位置(冲突);哈希表做法:如果几个编号碰撞,那就吧他们串在一起
如果链表太长就要打散(所以又名散列表)
hashtable最开始只有53个桶,当元素个数大于桶的个数时,桶的数目扩大为最接近当前桶数两倍的质数,实际上,桶数目的增长顺序被写死在代码里。
如果对象object是整数则可以将其自身当做编号,不然需要hashFunc计算其编号hashcode,然后利用hashcode除以bucket的个数得到最终位置
虽然eqstr和strcmp功能近似,但eqstr要求TRUE或者FALSE,所以两个函数的接口不一致。
左:成员函数,右:一般函数(一般函数在创建容器的时候需要把函数地址customer_hash_func放进来)
unordered_set、unordered_multiset、unordered_map和unordered_multimap
C++11引入的容器unordered_set、unordered_multiset、unordered_map和unordered_multimap更名自gcc2.9的容器hash_set、hash_multiset、hash_map和hash_multimap,其底层封装了hashtable.用法与set、multiset、map和multimap类似.
STL算法源码分析
STL算法是看不到容器的,算法所需要的信息都是从迭代器取得的,因此迭代器内要存在与容器相关的信息,其中最重要的就是迭代器的5个关联类型.
迭代器对算法的影响
迭代器的iterator_category类型
1.迭代器的关联类型iterator_category表示迭代器类型,共5种,用类表示:input_iterator_tag、output_iterator_tag、forward_iterator_tag(单向)、bidirectional_iterator_tag(双向)和random_acess_iterator_tag.
2.不连续空间的迭代器都是不能跳跃的:list
之所以使用类而非枚举来表示迭代器类型,是出于一下两个考虑:
1.使用类的继承可以**表示不同迭代器类型的从属关系**.
2.STL算法可以**根据传入的迭代器类型调用不同版本的重载函数**.
下面的程序演示这两点.
下面程序用以演示不同容器的迭代器iterator_category值:
// 输出迭代器类型,使用函数重载以应对不同类型的迭代器
void _display_category(random_access_iterator_tag) { cout << "random_access_iterator" << endl; }
void _display_category(bidirectional_iterator_tag) { cout << "bidirectional_iterator" << endl; }
void _display_category(forward_iterator_tag) { cout << "forward_iterator" << endl; }
void _display_category(output_iterator_tag) { cout << "output_iterator" << endl; }
void _display_category(input_iterator_tag) { cout << "input_iterator" << endl; }
template<typename I>
void display_category(I itr) {
typename iterator_traits<I>::iterator_category cagy;
_display_category(cagy);
}
// 输出不同容器的迭代器类型
void test_iterator_category() {
display_category(array<int, 10>::iterator()); // random_access_iterator//临时对象
display_category(vector<int>::iterator()); // random_access_iterator
display_category(list<int>::iterator()); // bidirectional_iterator
display_category(forward_list<int>::iterator()); // forward_iterator
display_category(deque<int>::iterator()); // random_access_iterator
display_category(set<int>::iterator()); // bidirectional_iterator
display_category(map<int, int>::iterator()); // bidirectional_iterator
display_category(multiset<int>::iterator()); // bidirectional_iterator
display_category(multimap<int, int>::iterator()); // bidirectional_iterator
display_category(unordered_set<int>::iterator()); // forward_iterator
display_category(unordered_map<int, int>::iterator()); // forward_iterator
display_category(unordered_multiset<int>::iterator()); // forward_iterator
display_category(unordered_multimap<int, int>::iterator()); // forward_iterator
display_category(istream_iterator<int>()); // input_iterator
display_category(ostream_iterator<int>(cout, "")); // output_iterator
}
1.容器array、vector、deque对使用者来说是连续空间,是可以跳跃的,其迭代器是random_access_iterator类型.
2.容器list是双向链表,容器set、map、multiset、multimap本身是有序的,他们的迭代器都可以双向移动,因此是bidirectional_iterator类型.
3.容器forward_list是单向链表,容器unordered_set、unordered_map、unordered_multiset、unordered_map哈希表中的每个桶都是单向链表.因此其迭代器只能单向移动,因此是forward_iterator类型.
4.迭代器istream_iterator和ostream_iterator本质上是迭代器,后文会提到这两个类的源码.
iterator_traits和type_traits对算法的影响
1.STL种的大部分算法都根据传入的迭代器类型以及其他信息调用不同的重载函数,针对特定的数据结构执行特定的优化
2.STL中的算法distance(计算两个指针之间的距离)根据不同的iterator_category执行不同的重载函数
STL中的算法advance根据不同的iterator_category执行不同的重载函数
STL中的算法copy根据不同的iterator_category和type_traits执行不同的重载函数
copy时来源端要有起点和终点两个指针,目的端只要一个起点就好,所以copy需要三个参数
STL算法都是模板函数,无法对传入的iterator_category类型做出限定,但源码中的模板参数名还是对接收的iterator_category做出了一定的暗示.例如sort算法的模板参数类型名设为RandomAccessIterator,暗示了该算法只能接收random_access_iterator_tag类型的迭代器.
算法
C语言本身提供了一些算法,如qsort,不属于STL算法.STL算法应该满足下面的形式:模板函数,接收参数是迭代器和某些标准.
accumulate
累计,算法accumulate的默认运算是+,但是重载版本允许自定义运算,支持所有容器,源码如下:
template<class InputIterator, class T>
T accumulate(InputIterator first, InputIterator last, T init) {
for (; first != last; ++first)
init = init + *first;
return init;
}
template<class InputIterator, class T, class BinaryOperation>
T accumulate(InputIterator first, InputIterator last, T init, BinaryOperation binary_op {
for (; first != last; ++first)
init = binary_op(init, *first);
return init;
}
下面程序演示其使用:
#include <iostream> // std::cout
#include <functional> // std::minus
#include <numeric> // std::accumulate
using namespace std;
// 自定义函数
int myfunc(int x, int y) { return x + 2 * y; }
// 自定义仿函数
struct myclass {
int operator()(int x, int y) { return x + 3 * y; }
} myobj;
int main() {
int init = 100;
int nums[] = { 10, 20, 30 };
cout << accumulate(nums, nums + 3, init); // 使用默认运算`+`,输出160
cout << accumulate(nums, nums + 3, init, minus<int>()); // 使用仿函数指定运算`-`, 输出40
cout << accumulate(nums, nums + 3, init, myfunc); // 使用自定义函数指定运算, 输出220
cout << accumulate(nums, nums + 3, init, myobj); // 使用四定义仿函数,输出280
return 0;
}
for_each
对区间内的每一个元素做指定的事情
算法for_each支持所有容器,源码如下:
template<class InputIterator, class Function>
Function for_each(InputIterator first, InputIterator last, Function f) {
for (; first != last; ++first)
f(*first);
return f;
}
C++11中引入了新的range-based for语句,形式如下:
#include <iostream>
#include <functional>
#include <numeric>
#include <algorithm>
#include <vector>
using namespace std;
void myfunc(int i) { cout<<' '<<i; }
struct myclass {
void operator()(int i) { cout<<' '<<i; }
}myobj;
int main() {
vector<int> myvec;
myvec.push_back(10);
myvec.push_back(20);
myvec.push_back(30);
for_each(myvec.begin(), myvec.end(), myfunc);
cout << endl;
for_each(myvec.begin(), myvec.end(), myobj);
cout << endl;
for (auto& elem : myvec) {
elem += 5;
}
for (auto& elem : myvec) {
cout<<' '<<elem;
}
}
replace、replace_if、replace_copy
1.算法replace将范围内所有等于old_value的元素都用new_value取代.
2.算法replace_if将范围内所有满足pred()为true的元素都用new_value取代.
3.算法replace_copy将范围内所有等于old_value的元素都以new_value放入新区间,不等于old_value的元素直接放入新区间.
它们支持所有容器,源码如下:
count、count_if
1.算法count计算范围内等于value的元素个数.
2.算法count_if计算范围内所有满足pred()为true的元素个数.
它们支持所有容器,但关联型容器(set、map、multiset、multimap、unordered_set、unordered_map、unordered_multiset和unordered_map)含有更高效的count方法,不应使用STL中的count函数.源码如下:
find、find_if
1.算法find查找范围内第一个等于value的元素.
2.算法find_if查找范围内第一个满足pred()为true的元素.
它们支持所有容器,但关联型容器(set、map、multiset、multimap、unordered_set、unordered_map、unordered_multiset和unordered_map)含有更高效的find方法,不应使用STL中的find函数.源码如下:
sort
1.算法sort暗示参数为random_access_iterator_tag类型迭代器,因此该算法只支持容器array、vector和deque.
2.容器list和forward_list含有sort方法.
3.容器set、map、multiset、multimap本身是有序的,容器unordered_set、unordered_map、unordered_multiset和unordered_map本身是无序的,不需要排序.
template<typename RandomAccessIterator>
inline void sort(RandomAccessIterator first, RandomAccessIterator last)
{
// ...
}
// 自定义函数
bool myfunc(int i, int j) { return (i < j); }
// 自定义仿函数
struct myclass {
bool operator()(int i, int j) { return (i < j); }
} myobj;
int main() {
int myints[] = {32, 71, 12, 45, 26, 80, 53, 33};
vector<int> myvec(myints, myints + 8); // myvec内元素: 32 71 12 45 26 80 53 33
sort(myvec.begin(), myvec.begin() + 4); // 使用默认`<`运算定义顺序,myvec内元素: (12 32 45 71)26 80 53 33
sort(myvec.begin() + 4, myvec.end(), myfunc); // 使用自定义函数定义顺序,myvec内元素: 12 32 45 71(26 33 53 80)
sort(myvec.begin(), myvec.end(), myobj); // 使用自定义仿函数定义顺序,myvec内元素: (12 26 32 33 45 53 71 80)
sort(myvec.rbegin(), myvec.rend()); // 使用反向迭代器逆向排序,myvec内元素: 80 71 53 45 33 32 26 12
return 0;
}
上面程序中的rbegin和rend是迭代器适配器,生成一个逆向增长的迭代器,后文会提到这两个类的源码.
binary_search
1.算法binary_search从排好序的区间内查找元素value,支持所有可排序的容器.
2.算法binary_search内部调用了算法lower_bound ,使用二分查找方式查询元素.
3.算法lower_bound和upper_bound分别返回对应元素的第一个和最后一个可插入位置
STL仿函数源码分析
1.仿函数是一类重载了()运算符的类,其对象可当作函数来使用,常被用做STL算法的参数.
2.STL的所有仿函数都必须继承自基类unary_function或binary_function,这两个基类定义了一系列关联类型,这些关联类型可被STL适配器使用.为了扩展性,我们自己写的仿函数也应当继承自这两个基类之一.
前文程序中我们自定义的仿函数并没有继承自基类unary_function或binary_function,这样虽然能够运行,但是不好,因为没有继承自基类的仿函数无法与STL其他组件(尤其是函数适配器)结合起来.
1.继承一般是继承父类的函数或者数据,这里继承的是typedef
2.一个人仿函数如果要可适配的条件是能够回答适配器的问题,所以必须继承自某一个基类,这样才能被适配器所修饰改造
STL适配器源码分析
适配器的共性分析:A想要取用B的功能有两种做法,A继承或内含(复合)于B,这里的适配器全部是内含的方式,如下图所示
容器适配器
STL中:
1.容器stack和queue是容器deque的适配器.
2.容器set、map、multiset和multimap是容器rb_tree的适配器.
3.容器unordered_set、unordered_multiset、unordered_map和unordered_multimap是容器hashtable的适配器.
上述源码在前文中均已分析过.
仿函数适配器
仿函数适配器binder2nd及其辅助函数bind2nd
bind1st和bind2nd是将二元函数转换为一元函数,比如一个比较大小的函数是二元函数,当在某些情况下我们想要固定第一个参数或者第二个参数时,就成了一元函数,先看用法
#include <iostream>
#include <algorithm>
#include <vector>
#include <functional>
using namespace std;
int main() {
vector<int> v;
v.push_back(2);
v.push_back(1);
v.push_back(5);
v.push_back(9);
v.push_back(7);
for (int i = 0; i < v.size(); i++) {
cout << v[i] << " ";
}
cout << endl;
//cout << count_if(v.begin(), v.end(), bind2nd(less<int>(), 5)) << endl;
int cx = count_if(v.begin(), v.end(), bind2nd(less<int>(), 5));
cout << "There are " << cx << " elements that are less than 40.\n";//2
return 0;
}
上面这段代码统计了vector中元素值小于5的元素个数,使用bind2nd将less仿函数的第二个参数绑定为5。接下来看看整个代码的调用过程.
// 辅助函数
template<class Operation, class T>
inline binder2nd<Operation> bind2nd(const Operation &op, const T &x) {
typedef typename Operation::second_argument_type arg2_type;
// 传给bind2nd函数的第二参数必须能够转为Operation的第二参数类型,否则报错
return binder2nd<Operation>(op, arg2_type(x));
}
bind2nd是有两个模板参数的函数,从例子中调用的地方可以看到,第一个参数是less的临时匿名函数对象,第二个是int型的数字40,less的代码为
template<typename _Tp>
struct less : public binary_function<_Tp, _Tp, bool>
{
_GLIBCXX14_CONSTEXPR
bool
operator()(const _Tp& __x, const _Tp& __y) const
{ return __x < __y; }
};
1.可以看到less中重载了()运算符实现比较数字大小的功能,比如使用 less(2,3) 即可调用。在bind2nd中通过 _Operation 记录了less的类型,之后调用了binder2nd
2.仿函数适配器binder2nd可以绑定二元仿函数的第二参数,生成新的仿函数.其源码如下:
// 放函数适配器binder2nd也是仿函数类,因此继承自仿函数基类unary_function
template<class Operation>
class binder2nd : public unary_function<typename Operation::first_argument type,
typename Operation::result_type> {
protected:
// 内部成员,分别记录运算和第二实参
Operation op;
typename Operation::second_argument_type value;
public:
// 构造函数,将运算和第二实参记录下来
binder2nd(const Operation &x, const typename Operation::second_argument_type &y)
: op(x), value(y) {}
// 重载()运算符,传入第二参数
typename Operation::result_type operator()(const typename Operation::first_argument_type &x) const {
return op(x, value);
}
}
1.在binder2nd源码中,调用了Operation类的first_argument、second_argument_type和result_type,这些字段都是从STL仿函数基类binary_function继承得到的.因此我们自己写的仿函数也要继承自基类binary_function,才能使用适配器binder2nd进行增强.
2.binder2nd适配器增强得到的仍然是一个仿函数,因此也要继承基类unary_function,以便被其它适配器增强.
3.使用类模板时必须指定模板参数的取值,因此将binder2nd封装进函数bind2nd中,使用函数模板的参数推导功能
仿函数适配器unary_negate及其辅助函数not1
仿函数适配器unary_negate将仿函数的结果取反,生成新的仿函数.其源码如下:
// 仿函数适配器unary_negate也是仿函数类,因此继承自仿函数基类unary_function
template<class Predicate>
class unary_negate : public unary_function<typename Predicate::argument_type, bool> {
protected:
// 内部成员,记录被取反的仿函数
Predicate pred;
public:
// 构造函数使用explicit修饰,避免隐式类型转换
explicit unary_negate(const Predicate &x) : pred(x) {}
// 重载()运算符,将函数结果取反
bool operator()(const typename Predicate::argument_type &x) const {
return !pred(x);
}
};
// 辅助函数
template<class Predicate>
inline unary_negate<Predicate> not1(const Predicate &pred) {
return unary_negate<Predicate>(pred);
}
适用实例:
#include <iostream>
#include <algorithm>
#include <vector>
#include <functional>
using namespace std;
int main() {
vector<int> v;
v.push_back(2);
v.push_back(1);
v.push_back(5);
v.push_back(9);
v.push_back(7);
for (int i = 0; i < v.size(); i++) {
cout << v[i] << " ";
}
cout << endl;
//cout << count_if(v.begin(), v.end(), bind2nd(less<int>(), 5)) << endl;
int cx = count_if(v.begin(), v.end(), not1(bind2nd(less<int>(), 5)));//3
cout << "There are " << cx << " elements that are less than 5.\n";
return 0;
}
仿函数适配器bind
在库文件include/c++/backward/backward_warning.h中列出了一系列C++11中废弃了的STL类及其替代类.
其中用于绑定函数参数的类binder1st和binder2nd及其辅助函数bind1st和bind2nd都被替换为功能更强大的bind.
bind1st() 和 bind2nd(),在 C++11 里已经 deprecated 了.bind()可以替代他们,且用法更灵活更方便。
上面的例子可以写成下面的形式:
函数bind要和命名空间std::placeholders中的占位符_1、_2、_3…等占位符配合使用.bind函数可以绑定:
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
int main()
{
int numbers[] = { 10,20,30,40,50,10 };
int cx;
cx = count_if(numbers, numbers + 6, bind(less<int>(), std::placeholders::_1, 40));
cout << "There are " << cx << " elements that are less than 40.\n";
cx = count_if(numbers, numbers + 6, bind(less<int>(), 40, std::placeholders::_1));
cout << "There are " << cx << " elements that are not less than 40.\n";
system("pause");
return 0;
}
1.函数和函数对象.
2.成员函数(绑定成员函数时占位符_1必须是该类对象的地址).
3.成员变量(绑定成员变量时占位符_1必须是该类对象的地址).
#include <iostream> // std::cout
#include <functional> // std::bind
#include <vector>
#include <algorithm>
double my_divide(double x, double y) { return x / y; }
struct MyPair {
double a, b;
double multiply() { return a * b; }
};
int main() {
using namespace std::placeholders; // 引入占位符_1, _2, _3,...
// 将10和2绑定到函数的第一参数和第二参数上
auto fn_five = std::bind(my_divide, 10, 2); // returns 10/2
std::cout << fn_five() << '\n'; // 5
// 将2绑定到函数的第二参数上
auto fn_half = std::bind(my_divide, _1, 2); // returns x/2
std::cout << fn_half(10) << '\n'; // 5
// 将函数的第一参数和第二参数绑定到第二参数和第一参数上
auto fn_invert = std::bind(my_divide, _2, _1); // returns y/x
std::cout << fn_invert(10, 2) << '\n'; // 0.2
// 将int绑定到函数的返回值上,绑定的是返回类型
auto fn_rounding = std::bind<int>(my_divide, _1, _2); // returns int(x/y)
std::cout << fn_rounding(10, 3) << '\n'; // 3
MyPair ten_two{10, 2};
// 将对象ten_two绑定到函数的第一参数上,其实mypair有一个参数:this指针
auto bound_member_fn = std::bind(&MyPair::multiply, _1); // returns x.multiply()
std::cout << bound_member_fn(ten_two) << '\n'; // 20
// 将对象ten_two绑定到函数的成员变量上
auto bound_member_data = std::bind(&MyPair::a, ten_two); // returns ten_two.a
std::cout << bound_member_data() << '\n'; // 10
std::vector<int> v = { 15,66,55,5,66,33,88 };
int n = std::count_if(v.cbegin(), v.cend(), std::not1(std::bind2nd(std::less<int>(), 50)));
std::cout << "n=" << n << std::endl;//n = 4
auto fn_ = std::bind(std::less<int>(), _1, 50);
std::cout << std::count_if(v.cbegin(), v.cend(), fn_) << std::endl;//3
std::cout << std::count_if(v.cbegin(), v.cend(), std::bind(std::less<int>(), _1, 50)) << std::endl;//3
return 0;
}
迭代器适配器
逆向迭代器reverse_iterator
容器的rbegin()和rend()方法返回逆向迭代器reverse_iterator,逆向迭代器的方向与原始迭代器相反.
1.逆向迭代器适配器reverse_iterator与正常迭代器的方向正好相反:逆向迭代器的尾(头)就是正向迭代器的头(尾);逆向迭代器的加(减)运算就是正向迭代器的减(加)运算.因此逆向迭代器取值时取得是迭代器前面一格元素的值.
用于插入的迭代器insert_iterator及其辅助函数inserter
迭代器适配器insert_iterator生成用于原地插入运算的迭代器,使用insert_iterator迭代器插入元素时,就将原有位置的元素向后推.
list<int> foo = {1, 2, 3, 4, 5};
list<int> bar = {10, 20, 30, 40, 50};
list<int>::iterator it = foo.begin();
advance(it, 3);
copy(bar.begin(), bar.end(), inserter(foo, it));
insert_iterator通过重载运算符=实现上述功能:
// 适配器类insert_iterator
template<class Container>
class insert_iterator {
protected:
// 内部成员,记录底层容器和迭代器
Container *container;
typename Container::iterator iter;
public:
// 定义5个关联类型
typedef output_iterator_tag iterator_category;
insert_iterator(Container &x, typename Container::iterator i)
: container(&x), iter(i) {}
// 重载赋值运算符=
insert_iterator<Container> &
operator=(const typename Container::value_type &value) {
iter = container->insert(iter, value); // 调用底层容器的insert
++iter; // 令insert_iterator永远随其target同步移动
return *this;
}
// 辅助函数inserter
template<class Container, class Iterator>
inline insert_iterator<Container> inserter(Container &x, Iterator i) {
typedef typename Container::iterator iter;
return insert_iterator<Container>(x, iter(i));
}
输出流迭代器ostream_iterator
输出流迭代器ostream_iterator常用于封装std::cout.下面程序将容器中元素输出到std::cout中.
#include <iostream>
#include <iterator>//std::ostream_iterator
#include <vector>
#include <algorithm>
int main() {
std::vector<int> myvector;
for (int i = 1; i < 10; i++) {
myvector.push_back(i * 10);
}
std::ostream_iterator<int> out_it(std::cout, ",");
std::copy(myvector.begin(), myvector.end(), out_it);
return 0;
}
ostream_iterator重载了运算符=,*和++,其源码如下:
template<class T, class charT=char, class traits =char_traits<charT> >
class ostream_iterator : public iterator<output_iterator_tag, void, void, void, void> {
basic_ostream<charT, traits> *out_stream;
const charT *delim;
public:
typedef charT char_type,
typedef traits traits_type,
typedef basic_ostream<charT, traits> ostream_type;
ostream_iterator(ostream_type &s) : out_stream(&s), delim(0) {}
//delimite分隔符,cout是一个对象,所以可以取地址
ostream_iterator(ostream_type &s, const charT *delimiter)
: out_stream(&s), delim(delimiter) {}
ostream_iterator(const ostream_iterator<T, charT, traits> &x)
: out_stream(x.out_stream), delim(x.delim) {}
// 重载运算符=: 执行输出
ostream_iterator<T, charT, traits> &operator=(const T &value) {
//
*out_stream << value;
if (delim != 0)
*out_stream << delim;
return *this;
}
// 重载运算符*和++: 不做任何动作
ostream_iterator<T, charT, traits> &operator*() { return *this; }
ostream_iterator<T, charT, traits> &operator++() { return *this; }
ostream_iterator<T, charT, traits> &operator++(int) { return *this; }
};
输入流迭代器istream_iterator
输入流迭代器istream_iterator用于封装std::cin,下面程序从std::in中读取数据:
#include <iostream> //std::cin std::cout
#include <iterator> //std::istream_iterator
int main() {
double value1, value2;
std::cout << "please innsert two values:";
std::istream_iterator<double> eos;
std::istream_iterator<double> iit(std::cin);
if (iit != eos) value1 = *iit;
++iit;
if (iit != eos) value2 = *iit;
std::cout << value1 << "*" << value2;
return 0;
}
istream_iterator重载了运算符=,*和++,其源码如下:
template<class T, class charT=char, class traits=char_traits<charT>, class Distance=ptrdiff_t>
class istream_iterator :
public iterator<input_iterator_tag, T, Distance, const T *, const T &> {
basic_istream<charT, traits> *in_stream; // 输入流
T value; // 上一次读入的值
public:
typedef charT char_type;
typedef traits traits_type;
typedef basic_istream<charT, traits> istream_type;
istream_iterator() : in_stream(0) {} // 空迭代器,表示输入流终止
istream_iterator(istream_type &s) : in_stream(&s) { ++*this; } // 创建好迭代器后马上读入一个值
istream iterator(const istream_iterator<T, charT, traits, Distance> &x)
: in_stream(x.in_stream), value(x.value) {}
// 重载运算符++
istream_iterator<T, charT, traits, Distance> &operator++() {
if (in_stream && !(*in_stream >> value))
in_stream = 0;
return *this;
}
istream_iterator<T, charT, traits, Distance> operator++(int) {
istream_iterator<T, charT, traits, Distance> tmp = *this;
++*this;
return tmp;
}
// 重载运算符*和->
const T &operator*() const { return value; }
const T *operator->() const { return &value; }
};
tips1:当你在创建iit(cin)时,已经在开始读取了(++*this;)
下面程序使用输入流迭代器istream_iterator从std::cin中读取数据到容器中:
istream_iterator<int> iit(cin), eos;
copy(iit, eos, inserter(c, c.begin()));
其它标准库相关的话题
容器tuple
使用tuple
#include <iostream> //std::cin std::cout
#include <tuple>
using namespace std;
int main() {
// 指定初值
tuple<int, float, string> t1(41, 6.3, "nice");
cout << "size_of_tuple: " << sizeof(t1) << endl;
// 使用get<>()函数获取tuple内的元素
cout << "t1:" << get<0>(t1)<<" " << get<1>(t1)<< " " << get<2>(t1) << endl;
// 使用make_tuple函数创建tuple
auto t2 = make_tuple(22, 44, "ttt");
tuple<int, float, string> t3(3, 0.3, "nieeee");
int i1;
float f1;
string s1;
// 使用tie函数将tuple的元素绑定到变量上
tie(i1, f1, s1) = t3;
cout << "t3:" << get<0>(t3) << " " << get<1>(t3) << " " << get<2>(t3) << endl;
cout << i1 << " " << f1 << " " << s1 << endl;
// 推断 tuple 类型
// 推断出 t3 的类型为 tuple<int, float, string>
typedef decltype(t3) TupleType;
// 使用 tuple_size 获取元素个数
cout << tuple_size<TupleType>::value << endl;
// 使用 tuple_element 获取元素类型
tuple_element<1, TupleType>::type f2 = 1.0;
cout << f2 << endl;
}
**类模板tuple_size:**查询tuple成员数量。使用tuple_size需要知道tuple对象类型(确定一个对象类型使用 decltyple),tuple_size有一个public static的数据成员value,表示给定tuple中的成员数量。
auto itme = make_tuple("string", 3, 20.01);
auto sz = tuple_size<decltype(itme)>::value;//sz为3
**类模板tuple_element:**查询tuple成员的类型。tuple_element接受一个索引(从0开始)和一个tuple对象类型。tuple_element有一个public的成员type,表示给定tuple类型中指定成员的类型
auto itme = make_tuple(“string”, 3, 20.01);
tuple_element<1, decltype(itme)>::type ctype;//ctype的类型为int
tuple类源码分析
// 定义 tuple类
template<typename... Values>
class tuple;
// 特化模板参数: 空参
template<>
class tuple<> {};
// 特化模板参数
template<typename Head, typename... Tail>
class tuple<Head, Tail...> :
private tuple<Tail...> // tuple类继承自tuple类,父类比子类少了一个模板参数
{
typedef tuple<Tail...> inherited; // 父类类型
protected:
Head m_head; // 保存第一个元素的值
public:
tuple() {}
tuple(Head v, Tail... vtail) // 构造函数: 将第一个元素赋值给m_head,使用其他元素构建父类tuple
: m_head(v), inherited(vtail...) {}
Head head() { return m_head; } // 返回第一个元素值
inherited &tail() { return *this; } // 返回剩余元素组成的tuple(将当前元素强制转换为父类类型)
};
调用head函数返回的是元素m_head的值.
调用tail函数返回父类成分的起点,通过强制转换将当前tuple转换为父类tuple,丢弃了元素m_head所占内存.
type traits
类型萃取机制(type traits)获取与类有关的信息,在C++11之前和C++11中分别由不同的实现方式.
C++11之前的类型萃取机制:__type_traits
在C++11之前,类型萃取机制是由__type_traits实现的.我们每创建一个类,就要以该类为模板参数特化一个__type_traits类.
template<class type>
struct __type_traits {
typedef __false_type has_trivial_default_constructor; // 默认构造函数是否可忽略
typedef __false_type has_trivial_copy_constructor; // 拷贝构造函数是否可忽略
typedef __false_type has_trivial_assignment_operator; // 赋值函数是否可忽略
typedef __false_type has_trivial_destructor; // 析构函数是否可忽略
typedef __false_type is_POD_type; // 是否是POD(plain old data)类型
};
template<>
struct __type_traits<int> {
typedef __true_type has_trivial_default_constructor;
typedef __true_type has_trivial_copy_constructor;
typedef __true_type has_trivial_assignment_operator;
typedef __true_type has_trivial_destructor;
typedef __true_type is_POD_type;
};
template<>
struct __type_traits<double> {
typedef __true_type has_trivial_default_constructor;
typedef __true_type has_trivial_copy_constructor;
typedef __true_type has_trivial_assignment_operator;
typedef __true_type has_trivial_destructor;
typedef __true_type is_POD_type;
};
struct __true_type { /*...*/ };
struct __false_type { /*...*/ };
这种实现方式比较麻烦,因此C++11以一种新的方式引入type traits机制.
C++11中的类型萃取机制:辅助类
C++11在头文件type_traits中引入了一系列辅助类,这些辅助类能根据传入的模板参数自动进行获取该类的基本信息,实现类型萃取,并不需要我们为自己创建的类手动编写类型萃取信息.
tips1:一个类到底需不需要析构函数,只要他带着一个指针就一定要写,不带指针的话多半不用写,string就是带了一个指针,所以应该要有析构函数
#include <iostream> //std::cin std::cout
#include <tuple>
using namespace std;
template <typename T>
void type_traits_output(const T& x)
{
cout << "\ntype_traits for type: " << typeid(T).name() << endl;
cout << is_void<T>::value << endl;
cout << is_pointer<T>::value << endl;
cout << is_class<T>::value << endl;
}
class Foo
{
private:
int d1, d2;
};
int main() {
type_traits_output(Foo());
}
类型萃取机制源码分析
头文件type_traits中定义了辅助类remove_const和remove_volatile用于除去类型中的const和volatile关键字(去掉对主体无用的旁支细节).
![在这里插入图片描述](https://img-blog.csdnimg.cn/fc1366b724db42d2a961b966644886df.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1 NETiBA5oiR55qE5pi156ew5oCO5LmI5pS55LiN5LqG5ZWK,size_14,color_FFFFFF,t_70,g_se,x_16)
is_void类继承自__is_void_helper类,__is_void_helper类使用偏特化的形式判断传入的模板参数是否为void.
is_integral类继承自__is_integral_helper类,同样使用偏特化的方式判断传入的模板参数是否为整数类型
一些type traits辅助类(如is_enum、is_union和is_class等)是由编译器实现的,STL源码中找不到其实现函数.
cout
tips1:cout是一个对象
tips2:当有新的东西需要显示的时候需要操作符重载