C++ STL 体系结构与内核分析 P8-P15
分配器用于支撑容器对内存的使用
OOP(面向对象编程)和GP(泛型编程)
OOP企图把数据和操作放在一起
GP却是把数据和操作分开,操作容器时需要借用迭代器,好处是:
1)Container和Algorithm的团队可以各自闭门造车,以Iterator沟通
2)Algorithm通过Iterator确定操作范围,并通过Iterator取Container的元素
操作符重载
:: . .* :? 不能被重载(记忆方法:八个点)
** <> or &| 不能被重载
类模板:构造对象即实例化类的时候需要指定模板类型
函数模板:实参推导:定义函数模板,使用函数的时候直接传入参数,编译器可以自动识别函数参数类型从而决定函数返回类型或者调用相应的重载
成员模板:
类模板的泛化和特化及偏特化:
泛化:普通的模板编程
template<class type>
struct _type_trains{
......
}
特化:模板类绑定了某种类型,比如int或double,此时语法为template<>,<>中为空
template<>
struct _type_trains<int>{
......
}
偏特化(<>内参数个数偏特化):vector本来可以接受任意的类型作为元素,但是如果此时指定元素类型为bool,则stl利用偏特化构造一个特别的设计,这样可以使得针对boo元素类型的vector效率高或者空间小
//泛化
template<class T
class Alloc=alloc>
class vector{
......
};
//偏特化
template<class Alloc>
class vector<bool,Alloc>
{
...
};
偏特化(<>内参数范围偏特化):模板类型为T,当传入T类的指针时,构造相对应的类
template<class T>
struct iterator_train<T*>{
.......
}
或者
template<class T>
struct iterator_train<const T*>{
.......
}
分配器allocators
VC6中allocators()分配内存时调用::operator new(),归根结底是调动malloc();撤销内存时调用::operator delete,归根到底是调用free()
malloc()分配内存时,除了所需要的内存空间之外害需要附加定量的额外空间,如果所需要malloc分配的内存越大,则额外空间占比越小
allocators直接使用的示例,模板后直接加小括号()生成临时对象,没有名称,因为撤销内存时也需要指定定义的内存大小而太过麻烦,所以不直接使用allocator
int *p=allocator<int>().allocate(512,(int*)0)//分配512个整型的内存
allocator<512>().deallocate(p,512);
在G4.9版本的C++中,比较好的分配器为__pool_allco,用法如下:
vector<string,__gnu_cxx::__pool_alloc<string>>vec//__gnu_cxx为命名空间
容器之间的实现关系与分类
深度探索list(双向链表)
1.任一容器源码内都包括了将iterator类,该类使得迭代器成为一个智能指针,iterator类中包括了对操作符++,–,&,->的重载
2.前置运算符++i的重载中返回对象本身的引用,是一个左值,所以可以有++++i的操作;后置运算符i++的重载中调用了前置运算符++i的重载,返回一个新对象为初始对象的副本,是一个右值,故不可以i++++操作
3.链表的实现是环状的双向环状链表中刻意在尾端加入空白结点,以实现容器前闭后开区间特性,
4.list不支持下标访问,但是插入和删除元素的时间复杂度都是O(1)
4.list的内存空间决定于成员数据而跟成员函数无关,这个原则适合其他所有类。指针成员数据类型占win位/8字节,如果编写为64代码(为64位系统),则指针类大小为64/8=8字节。如果类中存在虚函数,无论多少个虚函数,需要另外增加win位/8字节
5.advance的作用是将迭代器移动到需要的位置,再进行删除或者插入方法适用于各种容器
G2.9版本list源码
//G2.9版本list源码
//list 类
template <class T,class Alloc=alloc>
class list{
protected:
typedef __list_node<T> list_node;
public:
typedef list node* link type;
typedef __list_iterator<T,T&,T*>iterator;
protected:
link_type node;
...
}
//结点类,包括了指向前后结点的指针和一个数据,指针类型在后续版本中被修改为初始化为指向自己本身结点类的对象
template <class T>
struct __list_node{
typedef void* void pointer;
void_point prev;
void_point next;
T data;
}
//迭代器类
template<class T,class Ref,class Ptr>
struct __list_iterator{
//五个问题即五种属性
typedef bidirectional_iterator_tag iterator_category;
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
typedef ptrdiff_t difference_type;
...
link_type node;
reference operator*() const{return {*node}.data;}
pointer operator->() const{return &(operator*());}
self& operator++()
{node = (link_type)((*node).next);return *this;}
self operator++(int)
{self tmp = *this; ++*this;return tmp;}
}
List使用
list<int> lst; // 定义一个int类型的列表a
list<int> lst(10);// 定义一个int类型的列表a,并设置初始大小为10
list<int> lst(10, 1); // 定义一个int类型的列表a,并设置初始大小为10且初始值都为1
int n[] = { 1, 2, 3, 4, 5 };
list<int> a(n, n + 5); // 将数组n的前5个元素作为列表a的初值
// 头部增加元素
lst.push_front(4);
// 末尾添加元素
lst.push_back(5);
// 任意位置插入一个元素,begin函数会产生一个迭代器start,指向链表开头,使用advance将迭代器移到需要的位置,再插入
list<int>::iterator it = lst.begin();
advance(it,2)
lst.insert(it, 2);
// 任意位置插入n个相同元素,插入3个9
lst.insert(lst.begin(), 3, 9);
迭代器设计原则和Iterator Traits的作用与设计
Iterator需要遵循的原则:
算法“提出问题”,Iterator“回答问题”,相当于算法需要知道Iterator的五个属性(associated types):
1)iterator_categoty():迭代器分类,具体是指跨度,比如c+=3,c+=5等
2)difference_type():两个迭代器之间的距离应该用什么类型来表示
3)value_type():迭代的元素的类型
4)和5)分别为reference和pointer,这两种在标准库中未出现
Iterator Traits用于分离calss iterators和non-class iterators
calss iterators:list,vector等基于类,构造的时候需要告知模板参数类型所以可以直接获取associated types
non-class iterators:指针类,