1. 容器之间的复合关系(composition)
如下图:
- array
- vector
- heap
- priority_queue
- heap
- list
- slist(标准库改为forward_list了)
- deque
- stack
- queue
关联性容器先不说了. 注意到这是2.9的版本, 容器之间的关系不是继承, 而是复合关系.
2. list源码探究
如下图, 从图中右边我们分析一下源码:
在
template<class T, class Alloc= alloc>
class list;
的定义中, 我们知道list
只有有一个成员变量node
,类型是link_type
, 而link_type
的类型是list_node*
的, 因此list
的大小就是8个字节(32位机子).
再看一下list_node
里面的成员变量, 其实是一个模板类__list_node<T>
, 里面有三个成员变量, 分别是data, prev, next
, 注意到prev, next
是指向void
的, 这其实不好的, 因此4.9版本改进了, 指向了本类(如__list_node
).
当我们用iterator的时候, 我们会有iterator++的操作, 如下图, 如果没有重载++的话, 当使用iterator++的时候, 指针不会智能地指向下一个node
中去, 因此我们需要重载operator++
等运算符. 这种行为其实被封装在__list_iterator
类模板里了, 这个类模板需要传入三个参数T, T&, T*
(这其实也是没必要的, 后续版本只需要传入一个), 下面我们来看看封装了iterator++
重载的函数干了啥.
如下图, 是__list_iterator
类的部分代码, 这部分代码主要typedef了5个东西, 分别是
- iterator_category
- value_type
- pointer
- reference
- different_type
(其他两个先不管, 因为这5个后面会说)
然后 类中有一个node
, 指向一个list
节点, 如下图左边的图示, 接着重载了*, ->, 前置operator++()和后置++
操作等…
下面是重载++
的详解, 主要就是说明前置和后置的区别,
其中前置返回的是self&
引用类型, 后置返回的是self
类型, 因此才会有如下结论:
++++i ⇒ ++(++i)
i++++ => (i++)++ //error, 因为返回的不是引用类型, 不能继续操作
//返回的是一个右值, 右值是不可以被修改的
同时我注意到一个优先级的问题, 比如:
self tmp = *this;
由于CPP是从右往左执行的, 具体原因主要是函数压栈的问题, 可以看C/C++函数参数为何是从右到左?
但是要注意, 压栈顺序和参数计算顺序不一样, 详见C/C++函数参数列表变量的计算顺序
我简单举个例子:
void FuncB(int a, int b)
{
cout << a << endl;
cout << b << endl;
}
void FuncA()
{
int i = 0;
FuncB(++i, i++); //2, 0
}
上面的程序就是参数计算顺序的问题, 我的编译器是2和0, 但是不同的编译器对参数的计算顺序会不一样, 按照我的理解, FuncB
先计算i++
, 然后再计算++i
, 于是便有了2和0.
下面是重载*和->
运算符的例子, 没啥好说的
接下来对比一下2.9个4.9标准库list的改变:
- 可以看到
typedef __list_iterator<T, T&, T*> itertator
里面的模板参数变成了一个_Tp
, G4.9进入__List_iterator
里面再另外typedef一个引用和一个指针. __list_node
节点也变了, 4.9中变成了_List_node
, 里面只有一个_M_data
, 并且继承于_List_node_base
, 这里面有两个指向自己的指针, 对比2.9的指向void, 发生了改变.
3. Iterator设计原则
如下图的rotate
算法, 需要传入迭代器, 里面的__rotate
还需要__iterator_category
, 这个是算法在提问迭代器的类型是什么; 接着我们看__rotate
里面的实现, 需要知道迭代器里面的different_type
和value_type
(还有两种没有出现分别是reference
和pointer
)
因此, 为了回答算法的提问, Iterator必须提供5种associated types
同时, 如果iterator不是class的话, 标准库又设计了Iterator traints来"萃取"类和指针相应的处理方式
下图的萃取机就是一个中介层, 把 指针类型T*
和const T*
分在上面, iterator则在下面.
因为iterator如果是类的话, 会有能力定义自己的associated types
, 但是指针并没有办法定义.
具体的iterator_traits实现如下:
如果I是class的话, 进入1, 如果I是指针的话, 进入2和3, 这是用偏特化来实现的.
需要注意是:
不管是T*
还是const T*
, iterator_traits
里面都是定义typedef T value_type
, 不带const的, 具体原因下图黄色部分解释了.
更具体的实现:
可以发现, 迭代器是类的 直接取类I中的即可, 如果是指针的则需要typedef新的名字.