这是自己自学C++侯捷老师的STL源码剖析所记下来的一下个人笔记,方便以后进行学习,查询,为什么要学STL?按侯捷老师的话来说就是:使用一个东西,却不明白它的道理,不高明!
标准库和STL是同样的东西吗?不是,在标准库当中百分之80左右是STL,STL分为六大部件。
一些新旧标准:
#include
#include
,但是原来的带有.h的依然可以使用std
当中原来vector
,第二个参数是allocator。以上为六大部件之间具体的联系与配合。在上方的程序当中,less
这是标准库当中的一个仿函数,然后他的作用是进行比较,比如说a < b
,然后现在利用了count_if
这个算法,其作用是找到我们指定目标的数值,现在假如需要找到小于40的值,但是仿函数少了第二参数,所以就可以利用之前适配器的功能,适配器就是把容器/算法/迭代器做出一些转换,使用bind2nd
这个转换,即可有了第二参数40;之后又进行函数的转换,加了一个not1
,以前要找小于等于40,现在要大于40。
把元素放到容器当中,容器当然是有头有尾的,所有容器都提供begin(),end()迭代器,头没有疑虑,尾巴呢?所谓容器的前闭后开区间,标准库规定,begin()要表示开始的启动,end()表示最后一个元素的下一个元素。
容器的遍历(C++11之前的写法):
Container<T> c;
...
Container<T>::iterator ite = c.begin();
for (; ite != c.end(); ++ite)
...
最新的写法(最推荐C++11)
for (auto decl : coll)
{
statement;
}
decl是一个声明,coll是一个容器,相比以前的写法,方便太多了。
不是非必要的时候不用auto,因为知道一个元素的类型还是很重要的。
大致上分为两种,序列式容器以及关联式容器(key找value,适用于查找,hashmap)。
一图看懂各个容器的结构,最后一个单链表是C++11新增的一个容器。
在标准库当中,并没有规定set和map用什么来实现,但是用红黑树(因为左右两边会自己平衡)非常好,所以各家IDE都用红黑树来做set和map。
map,每一个节点,都有key和value,set却没有明确划分。
还有就是,如果选择普通的set和map,里面的元素的key是不能重复的,但是使用multiset以及multimap的时候,里面的key是可以重复的。
哈希表很好,但是某一条链表不能太长,太长就不会太好,因为单链表是要不断进行遍历的,这样时间复杂度就会很高。
测试程序-辅助函数
long get_a_target_long()
{
long target=0;
cout << "target (0~" << RAND_MAX << "): ";
cin >> target;
return target;
}
string get_a_target_string()
{
long target=0;
char buf[10];
cout << "target (0~" << RAND_MAX << "): ";
cin >> target;
snprintf(buf, 10, "%d", target);
return string(buf);
}
int compareLongs(const void* a, const void* b)
{
return ( *(long*)a - *(long*)b );
}
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;
}
#include
#include
#include
#include //qsort, bsearch, NULL
namespace jj01
{
void test_array()
{
cout << "\ntest_array().......... \n";
array<long,ASIZE> c;
clock_t timeStart = clock();
for(long i=0; i< ASIZE; ++i) {
c[i] = rand();
}
cout << "milli-seconds : " << (clock()-timeStart) << endl; //
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;
}
}
以上函数,ASIZE = 50万,然后加入随机数,再进行排序。
可有把一些组件写在namespace当中,这上面是写了namespace jj01
,这样是为了与其他的namespace进行分开。
使用vector的时候,要想清楚他的结构,因为是只有一个尾端,所以只有push_back()
操作,没有push_front()
操作。vector是两倍增长,放一个增长一个,两倍两倍的增长,而且vector的内存是从别的地方搬过来的,需要注意的是size()
是真正存有多少个元素,capacity()
是他的真实容量大小。
记住list是双向链表,需要注意的是,除了标准库当中有sort()
,在list当中,也有sort()
操作,例如l.sort()
操作,那怎么选择呢?当某些容器提供了自己的sort()
操作肯定是用自己的会比较快一点,list和forwa_list都有自己的sort。
因为是单向链表,所以只有push_front()
。
同单向链表,其头文件包含在ext
当中。
deque,双向开口,可进可出的容器,还有需要注意的是,deque是分段连续的,说连续的只是一个假象,其实是分段连续的。
因为两端都可以,所以既可以push_back()
,又可以push_front()
。
一个deque,其实是涵盖了stack/queue的功能的,因为deque是双向进出,而stack则是单向进出的容器。因为stack/queue都是利用deque来完成的,所以其实又不把这两个容器称为容器,而是称为容器的适配器adapter。
前面的都是顺序型容器,现在这个是关联型容器。
这是由于一个红黑树高度平衡树构成的,查找的话要很快。
每一个都是由一个key和一个value组成的。
key可能重复,value可能相同。因为是multi,所以不带multi的,key就不会重复。
此容器的的基础支撑是以hashtable为结构的。
放东西进去要用insert()
命令,这个方框就是篮子,后面衍生出来的链表就是元素,可以说,篮子一定比元素多。如果元素的个数大于等于篮子,那么篮子就会扩充,至少两倍大于元素的个数,加入现在10个元素,5个篮子,当第10个元素挂在第5个篮子上的时候,内部就会开始检查,检查是篮子数小于元素的个数,然后进行扩增,直到篮子数大于元素的个数,然后再把元素重新打散,这就是散列表的由来。而且篮子拖着的元素不能太长,因为单向链表,要依次遍历过去查找。很长的话,时间效率就会很低。
与上边差不多,就是hashmap。
容器的背后需要一个东西来迟滞他对内存的使用,这个东西就是分配器allocator。在理想的情况下,我们最好是不知道这个东西,一定会存在这个东西,但是我们最好是不知道,但是首先第一个条件是不用写出来,容器就有一个默认的分配器,确实在使用接口是这样;第二个条件是这个默认的分配器要够好,当然可以自己写一个分配器。
可以看出,每一个容器的分配器的声明,都是模板,模板一定有第二参数,如红色的,默认的使用std::allocator
。
以上。
使用上方的分配器,在list当中进行演示。
list<string, allocator<string>> c1; //3140
list<string, __gnu_cxx::malloc_allocator<string>> c2; //3110
list<string, __gnu_cxx::new_allocator<string>> c3; //3156
list<string, __gnu_cxx::__pool_alloc<string>> c4; //4922
list<string, __gnu_cxx::__mt_alloc<string>> c5; //3297
list<string, __gnu_cxx::bitmap_allocator<string>> c6; //4781
switch (choice)
{
case 1 : c1.push_back(string(buf));
break;
case 2 : c2.push_back(string(buf));
break;
case 3 : c3.push_back(string(buf));
break;
case 4 : c4.push_back(string(buf));
break;
case 5 : c5.push_back(string(buf));
break;
case 6 : c6.push_back(string(buf));
break;
default:
break;
}
因为push_back()
每次都会用到内存,所以分配内存就会用到分配器。
可以直接使用allocator吗?当然可以,但没必要,因为分配器是支持容器的,我们没必要直接调用分配器去拿内存。
int* p;
allocator<int> alloc1;
p = alloc1.allocate(1);
alloc1.deallocate(p,1);
__gnu_cxx::malloc_allocator<int> alloc2;
p = alloc2.allocate(1);
alloc2.deallocate(p,1);
以上就是直接使用allocator,就是直接使用分配器,你的负担很重,你要去知道自己使用了多少个内存,这样还回去也就是还多少个内存。
面向对象OOP的概念,要有类,封装继承多态,以及虚函数之类的概念。
例如这个,标准库当中链表的设计,list这个类里面当然是要放数据本身的,面向对象就是,数据在类里,操作数据的函数也在类里面,以上就是list类的实现,体现了OOP的概念,即数据(Datas)和方法(Methods)结合。
但是泛型(GP)就不一样了,泛型是数据(Datas)和方法(Methods)分开来,比如:
在这个类里面,都没有sort()
的操作,排序的操作被单独的设置在另外一块区域,可以想象成是sort()
的内容是一块全局的区域,而OOP当中的sort()
则是局部的区域。
那GP实施这样的操作后,分开来,操作的部分要怎么样才能得到数据的本身呢?答案是迭代器,这两种操作要借助迭代器进行使用。
使用迭代器,begin()/end()
给到算法,就可以进行操作。
这样有什么好处呢?这样可以分开进行开发,容器算法团队分开开发内容。
为什么刚刚的list中的sort()
操作在类里面呢?因为链表并不是一段连续的空间,其是由指针一个个连接起来的,所以这并不是连续的,但是看全局的sort()
排序当中,其迭代器都是连续的量,但是list迭代器不是连续的量,所以这就是为什么list当中要有自己的sort()
操作了,而不能用全局的sort()
进行排序,这就是为什么有自带的排序尽量用自带的原因。
::
、.
、.*
、?:
这四个不能被重载,具体的如下:这样编译器会自动推断出未知类型的类型是什么。
特化当中,template后的<>是空的。
局部特化。
这个局部特化接受两个模板参数(其实上方特化的也可以有多个模板参数),现在呢,这个局部特化参数,两个参数只绑定了一个参数,所以叫做局部(偏)特化。
分配器给容器用的,所以分配器的效率好不好关系到容器的效率好不好。
因为分配器要取出内存,所以在经历过一系列操作的时候,在标准层面上,一系列都会回归到malloc()
上。
那么,所有的操作都会转化为malloc()
,其分配的内存长什么样子呢?如下:
在allocator的源代码当中,分配内存是使用new()
进行操作的。
回收内存的时候,使用delete()
去完成的。
G++的allocator只是以:operator new
和:operator delete
完成allocate()
和deallocate()
,没有任何特殊设计。
具体用法:
/分配512ints.
int*p = allocator<int>().allocate(512);
allocator<int>().deallocate(p, 512);
allocator
这个原来就是个类模板,现在特化后就变成类名称了,再加上个空的括号,就是一个临时对象,没有名称,也就是临时对象的实例化,有了对象才能调用成员函数。
#ifndef __SGI_STL_ALLOC_H
#define __SGI_STL_ALLOC_H
#ifndef __STL_CONFIG_H
#include
#endif
#ifndef __SGI_STL_INTERNAL_ALLOC_H
#include
#endif
#ifdef __STL_USE_NAMESPACES
using __STD::__malloc_alloc_template;
using __STD::malloc_alloc;
using __STD::simple_alloc;
using __STD::debug_alloc;
using __STD::__default_alloc_template;
using __STD::alloc;
using __STD::single_client_alloc;
#ifdef __STL_STATIC_TEMPLATE_MEMBER_BUG
using __STD::__malloc_alloc_oom_handler;
#endif /* __STL_STATIC_TEMPLATE_MEMBER_BUG */
#endif /* __STL_USE_NAMESPACES */
#endif /* __SGI_STL_ALLOC_H */
// Local Variables:
// mode:C++
// End:
GNU使用的是这个更高效的allocator。
所有的allocator最后的一步就使malloc()
,所以说,我们要减少次数,防止额外开销(cookie)越来越多,这样才更高效,在容器的状况下,可以不需要cookie。
那么,为了减少malloc()
的使用次数,GNU是怎么做的呢?
刚刚说了,在容器的状况下,可以不需要cookie,现在GNU就从这里着手,他也不要cookie,还要尽量减少malloc()
的次数。
GNU设置了16条链表,每一条链表负责的某一种特定大小的区块用链表串起来。
那么这么多链表的责任分属是怎么样的?
第0条链表负责的是8个字节这种大小,第7号链表负责的是 8 ∗ 8 = 64 8*8 = 64 8∗8=64字节这种大小,以此类推,第15条就是负责 16 ∗ 8 = 128 16*8 = 128 16∗8=128字节大小。
一条链表负责8bytes,所有容器,当需要内存的时候,都来和这个分配器要内存,容器的元素大小,会被调整到8的倍数。比如说50,就会被调整到56。调整到56后,分配器就会去看管理这个的链表下有没有挂内存呢?如果没有,才去跟malloc()
要一大块,去切割,切出来的东西用单向链表串起来,所以切出来的每一块就不带cookie。(带cookie,上下会浪费8个bytes,那么存放100万元素,可以省下800万个bytes)这个分配器非常好,但是到了最新的GNU当中的分配器后,又变回了std
的默认分配器。
list是最具代表性的,在list的源代码当中,整个data就只有一个,如下所示:
typedef __list_node<T>* link_type;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
link_type node;
node这个部分是有data的,这个源代码当中,有很多自己定义的那种类型,所以要一层一层的往上扒,这个node是link_type,发现是一个指针,指向list_node,这个node到底多大呢?一个指针4个字节,32位。
本身是一个指针,那么_list_node的定义是什么呢?
struct __list_node {
typedef void* void_pointer;
void_pointer next;
void_pointer prev;
T data;
};
因为是list,所以要有向前以及向后指的两个指针,注意这个指针是void类型的。
起点和终点就是用iterator指出来的,链表是非连续空间,所以这个itera不能是指针,离散的,所以这个迭代器要非常聪明,除了vector,arry之外,其余的容器的iterator都必须是一个class,
template<class T, class Ref, class Ptr>
struct __list_iterator {
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
typedef __list_iterator<T, Ref, Ptr> self;
iterator当中会有大量的操作符存在,因为要模拟指针,例如++,–之类的
并且至少要有5个typedef:
typedef bidirectional_iterator_tag iterator_category;
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
typedef __list_node<T>* link_type;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
self& operator++() {
node = (link_type)((*node).next);
return *this;
}
self operator++(int) {
self tmp = *this;
++*this;
return tmp;
}
self& operator--() {
node = (link_type)((*node).prev);
return *this;
}
self operator--(int) {
self tmp = *this;
--*this;
return tmp;
}
reference operator*() const { return (*node).data; }
pointer operator->() const { return &(operator*()); }
2.9到4.9有进步,指针也不再是void指针了
所有的容器,之前就讲过,一定是前闭后开的区间,即,最后一个元素的下一个元素是不属于容器的,所以这就是为什么list要设计成环状的原因,end()指向的是下面那块灰色的,最后一个元素之后的元素。
和4.9版的进行对比
还有下面这个,在2.9的时候,list node为4,到了4.9版的时候怎么变成8了呢?
4.9版中,list类有继承关系,往上找,发现list的大小就是_List_node_base中的两个指针的大小。
template<class T, class Ref, class Ptr>
struct __list_iterator {
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
typedef __list_iterator<T, Ref, Ptr> self;
typedef bidirectional_iterator_tag iterator_category;
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
很像一个array,但是他可以扩充,不能在原地扩充,
当这个容器,空间不够了,会在内存当中去找另外一块空间,这个空间数量为两倍大,是不是各个编译器都是两倍生长呢?是的
template <class T, class Alloc = alloc>
class vector {
public:
typedef T value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type* iterator;
typedef const value_type* const_iterator;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
protected:
typedef simple_alloc<value_type, Alloc> data_allocator;
iterator start;
iterator finish;
iterator end_of_storage;
iterator begin() { return start; }
const_iterator begin() const { return start; }
iterator end() { return finish; }
const_iterator end() const { return finish; }
size_type size() const { return size_type(end() - begin()); }
size_type capacity() const { return size_type(end_of_storage - begin()); }
bool empty() const { return begin() == end(); }
判断是否有空间直接比较finish与end_of_storage这两个数值是否相等。如果没有备用空间了,先把原来的大小记录下来,之后,再把扩容的大小为两倍于原来的大小进行扩容。
最上方的const size_type len = old_size != 0 ? 2 * old_size : 1;
说明了一切
typedef T value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type* iterator;
array:
value_type _M_instance[_Nm ? _Nm : 1];
,这是在定义一个数组,那么这个数组多大呢?看里面的Nm,如是0,就自动变为1,其他的就按照原先的来forward_list:
array:
还是依旧臃肿。
deque:
template <class T, class Alloc = alloc, size_t BufSiz = 0>
class deque {
public: // Basic types
typedef T value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
public: // Iterators
#ifndef __STL_NON_TYPE_TMPL_PARAM_BUG
typedef __deque_iterator<T, T&, T*, BufSiz> iterator;
typedef __deque_iterator<T, const T&, const T&, BufSiz> const_iterator;
#else /* __STL_NON_TYPE_TMPL_PARAM_BUG */
typedef __deque_iterator<T, T&, T*> iterator;
typedef __deque_iterator<T, const T&, const T*> const_iterator;
#endif /* __STL_NON_TYPE_TMPL_PARAM_BUG */
#ifdef __STL_CLASS_PARTIAL_SPECIALIZATION
typedef reverse_iterator<const_iterator> const_reverse_iterator;
typedef reverse_iterator<iterator> reverse_iterator;
#else /* __STL_CLASS_PARTIAL_SPECIALIZATION */
typedef reverse_iterator<const_iterator, value_type, const_reference,
difference_type>
const_reverse_iterator;
typedef reverse_iterator<iterator, value_type, reference, difference_type>
reverse_iterator;
#endif /* __STL_CLASS_PARTIAL_SPECIALIZATION */
protected: // Internal typedefs
typedef pointer* map_pointer;
typedef simple_alloc<value_type, Alloc> data_allocator;
typedef simple_alloc<pointer, Alloc> map_allocator;
protected: // Data members
iterator start;
iterator finish;
map_pointer map;
size_type map_size;
对应下图
T* cur;
T* first;
T* last;
map_pointer node;
template <class T, class Alloc = alloc, size_t BufSiz = 0>
inline size_t __deque_buf_size(size_t n, size_t sz)
{
return n != 0 ? n : (sz < 512 ? size_t(512 / sz) : size_t(1));
}
deque:
template <class T, class Ref, class Ptr, size_t BufSiz>
struct __deque_iterator {
typedef __deque_iterator<T, T&, T*, BufSiz> iterator;
typedef __deque_iterator<T, const T&, const T*, BufSiz> const_iterator;
static size_t buffer_size() {return __deque_buf_size(BufSiz, sizeof(T)); }
#else /* __STL_NON_TYPE_TMPL_PARAM_BUG */
template <class T, class Ref, class Ptr>
struct __deque_iterator {
typedef __deque_iterator<T, T&, T*> iterator;
typedef __deque_iterator<T, const T&, const T*> const_iterator;
static size_t buffer_size() {return __deque_buf_size(0, sizeof(T)); }
#endif
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T** map_pointer;
typedef __deque_iterator self;
T* cur;
T* first;
T* last;
map_pointer node;
deque的insert()举例
reference front() { return *start; }
reference back() {
iterator tmp = finish;
--tmp;
return *tmp;
}
const_reference front() const { return *start; }//要最前的就传回start,永远指向投
const_reference back() const {
const_iterator tmp = finish;
--tmp;//finish所指的是最后一个元素下一个的地方,所以就要减去一个
return *tmp;
}
size_type size() const { return finish - start;; }//看迭代器这个范围当中有多少个缓冲区,则元素的个数就是缓冲区的个数*元素的个数
size_type max_size() const { return size_type(-1); }
bool empty() const { return finish == start; }
reference operator*() const { return *cur; }
#ifndef __SGI_STL_NO_ARROW_OPERATOR
pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */
difference_type operator-(const self& x) const {
return difference_type(buffer_size()) * (node - x.node - 1) +
(cur - first) + (x.last - x.cur);
}
class rb_tree {
protected:
typedef void* void_pointer;
typedef __rb_tree_node_base* base_ptr;
typedef __rb_tree_node<Value> rb_tree_node;
typedef simple_alloc<rb_tree_node, Alloc> rb_tree_node_allocator;
typedef __rb_tree_color_type color_type;
public:
typedef Key key_type;
typedef Value value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef rb_tree_node* link_type;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
protected:
link_type get_node() { return rb_tree_node_allocator::allocate(); }
void put_node(link_type p) { rb_tree_node_allocator::deallocate(p); }
link_type create_node(const value_type& x) {
link_type tmp = get_node();
__STL_TRY {
construct(&tmp->value_field, x);
}
__STL_UNWIND(put_node(tmp));
return tmp;
}
link_type clone_node(link_type x) {
link_type tmp = create_node(x->value_field);
tmp->color = x->color;
tmp->left = 0;
tmp->right = 0;
return tmp;
}
void destroy_node(link_type p) {
destroy(&p->value_field);
put_node(p);
}
protected:
size_type node_count; // keeps track of size of tree
link_type header;
Compare key_compare;
protected:
size_type node_count; // keeps track of size of tree
link_type header;
Compare key_compare;
两幅图找不同
还有4.9版还是变得更加臃肿了,但是其符合OOP
template <class Key, class Compare = less<Key>, class Alloc = alloc>
#else
template <class Key, class Compare, class Alloc = alloc>
#endif
class set {
public:
// typedefs:
typedef Key key_type;
typedef Key value_type;
typedef Compare key_compare;
typedef Compare value_compare;
private:
typedef rb_tree<key_type, value_type,
identity<value_type>, key_compare, Alloc> rep_type;
rep_type t; // red-black tree representing set
public:
typedef typename rep_type::const_pointer pointer;
typedef typename rep_type::const_pointer const_pointer;
typedef typename rep_type::const_reference reference;
typedef typename rep_type::const_reference const_reference;
typedef typename rep_type::const_iterator iterator;
typedef typename rep_type::const_iterator const_iterator;
typedef typename rep_type::const_reverse_iterator reverse_iterator;
typedef typename rep_type::const_reverse_iterator const_reverse_iterator;
typedef typename rep_type::size_type size_type;
typedef typename rep_type::difference_type difference_type;
// allocation/deallocation
set() : t(Compare()) {}
explicit set(const Compare& comp) : t(comp) {}
#ifdef __STL_MEMBER_TEMPLATES
template <class InputIterator>
set(InputIterator first, InputIterator last)
: t(Compare()) { t.insert_unique(first, last); }
template <class InputIterator>
set(InputIterator first, InputIterator last, const Compare& comp)
: t(comp) { t.insert_unique(first, last); }
#else
set(const value_type* first, const value_type* last)
: t(Compare()) { t.insert_unique(first, last); }
set(const value_type* first, const value_type* last, const Compare& comp)
: t(comp) { t.insert_unique(first, last); }
set(const_iterator first, const_iterator last)
: t(Compare()) { t.insert_unique(first, last); }
set(const_iterator first, const_iterator last, const Compare& comp)
: t(comp) { t.insert_unique(first, last); }
#endif /* __STL_MEMBER_TEMPLATES */
set(const set<Key, Compare, Alloc>& x) : t(x.t) {}
set<Key, Compare, Alloc>& operator=(const set<Key, Compare, Alloc>& x) {
t = x.t;
return *this;
}
private:
typedef rb_tree<key_type, value_type,
identity<value_type>, key_compare, Alloc> rep_type;
typedef typename rep_type::const_iterator iterator;
template <class Key, class T, class Compare = less<Key>, class Alloc = alloc>
#else
template <class Key, class T, class Compare, class Alloc = alloc>
#endif
class map {
public:
// typedefs:
typedef Key key_type;
typedef T data_type;
typedef T mapped_type;
typedef pair<const Key, T> value_type;
typedef Compare key_compare;
class value_compare
: public binary_function<value_type, value_type, bool> {
friend class map<Key, T, Compare, Alloc>;
protected :
Compare comp;
value_compare(Compare c) : comp(c) {}
public:
bool operator()(const value_type& x, const value_type& y) const {
return comp(x.first, y.first);
}
};
private:
typedef rb_tree<key_type, value_type,
select1st<value_type>, key_compare, Alloc> rep_type;
rep_type t; // red-black tree representing map
public:
typedef typename rep_type::pointer pointer;
typedef typename rep_type::const_pointer const_pointer;
typedef typename rep_type::reference reference;
typedef typename rep_type::const_reference const_reference;
typedef typename rep_type::iterator iterator;
typedef typename rep_type::const_iterator const_iterator;
typedef typename rep_type::reverse_iterator reverse_iterator;
typedef typename rep_type::const_reverse_iterator const_reverse_iterator;
typedef typename rep_type::size_type size_type;
typedef typename rep_type::difference_type difference_type;
typedef typename rep_type::iterator iterator;
typedef pair<const Key, T> value_type;
typedef rb_tree<key_type, value_type,
select1st<value_type>, key_compare, Alloc> rep_type;
return pair.first
也就是select1st的功能template <class Value, class Key, class HashFcn,
class ExtractKey, class EqualKey, class Alloc = alloc>
class hashtable;
private:
hasher hash;
key_equal equals;
ExtractKey get_key;
public:
typedef Key key_type;
typedef Value value_type;
typedef HashFcn hasher;
typedef EqualKey key_equal;
vector<node*,Alloc> buckets;
size_type num_elements;
template <class Value>
struct __hashtable_node
{
__hashtable_node* next;
Value val;
};
所以在C++标准库当中,有这种设计好的hashfunc
也可以自己写,如上方的特化版本
标准库没有提供hash
hashcode算出来该落在哪个篮子上呢
例如一百个篮子,编号103,那么余数除下来就是落在3号篮子
在标准库当中看看是怎么样表现的,如下
return hash(key) % n
,所以确定落在哪个篮子都是通过取余数来判断for_each (myvec.begin(), myvec.end(), myfunc);
cout << endl; //output: 10 20 30
for_each (myvec.begin(), myvec.end(), myobj);
cout << endl; //output: 10 20 30
判断条件,while(first != last && *first != value)
即可判断
与count同理,因为关联型数据结构找的更快,所以有find和count
以下容器不带find()成员函数
因为关联型容器都会排序,所以没有sort。
因为less是比大小,所以肯定优先继承有两个操作数的binary_function
然后直接继承了三个typedef,这三个理论上是没有大小的
这样既没有增加了子类的大小,又白嫖的三个typedef,何乐不为?
STL规定每个adaptable function都应该选择适当的继承者而继承之
仿函数能否被修改?(被修改,被适配,adaptable)
如果你希望你的仿函数被修改,被适配的时候,就必须选择一个最适合的进行继承
这种关系就类似算法和迭代器,算法问问题,迭代器回答问题;这里是adapter问问题,functors回答问题,关系类似
比如上方的Operation::result_type
,问问题了,问这是什么,所以仿函数就回去找,发现自己本体没有,就去找继承的父类,发现在继承的里面有typedef Arg2 second_argument_type;
,所以说,怎么样去回答问题呢?就是仿函数的可以适配adaptable条件就是继承
所以说,你想让自己写的functors与STL水乳交融的进行融合的话,就必须选择一个适当的进行继承,也就能去适配器去修饰去改造
less
这是一个类型,我们要创建一个对象,这里直接创建了一个临时对象,这个操作非常常用,就是类型之后直接加上一个小括号greater
同理很奇怪的一件事就是,函数在被调用的时候才会指明参数,现在还没有被调用,就已经把参数绑定成40了,这是为什么呢?
(less(), 40)
这里调用,其实没有bind2nd
就是通过辅助函数创建了binder2nd
,所以真正的幕后英雄就是binder2nd
Operation op;
什么提示信息都没有,怎么能推出了这是什么?靠辅助函数推出来if (pred(*first))
bind2nd(less(), 40);
bind2nd(less(), 40)
这个跟随bind2nd的这个小括号,通过operator()这个函数进行重载,所以会执行相应的操作,在重载这里,才把op和value拿起来用,才真正的去调用op,op就是红色的less(),并且把value当做第二参数,即40,x就是count_if当做的for loopless()
的类型,这个是个临时对象,所以类型就是less
,所以这个类binder2nd这个要推类模板参数,就不太好用,就打算不直接用这个了,标准库提供一个辅助函数,就是bind2ndreturn binder2nd(op,arg2_type(x));
又是type_name直接加上小括号产生临时的对象,所以要知道小括号是函数调用还是创建对象binder2nd
这里要知道operation,现在辅助函数会帮我们去推,去写lee()
是什么东西,放到辅助函数当中,编译器就会给你直接推导出来,就把这个type作为模板参数即可,我们也不需要去推,这就是辅助函数的价值之前所说,在bind2nd当中,我们想检查的是与40有关的是整数,所以绑定的第二实参一定是整数,帮检查出来是好事,如果没检查出来,就是error,那么怎么检查出来呢?靠的是以下灰色的这些东西
arg2_type(x)
),相等不相等,这里就是在做判断,如果不能转换,这里就会检测出来member functions, _1
这里的_1是一个占位符placeholders
bind(my_divide, 10, 2)
,第一个参数是需要绑定的对象,然后2,3参数就是my_divide当中的形参的实参,所以直接return 5了auto fn invert bind (my_divide,_2,_1);
cout
auto fn_rounding bind
cout << fn_rounding(10,3) <<'\n';
注意看这两行,可以发现bind指定了一个模板参数
只会指定一个模板参数,这是为了规定他的返回类型,return_type
加了这个模板参数就是改变返回类型
如果绑定的是member function的话,有一个看不见的实参,这个实参就是this
double multiply(){return a * b};
//member function其有有個argument:this
看上方的成员函数,看起来没有形参,实际上有一个this
所以auto bound_memfn = bind(&MyPair::multiply,_1);
cout << bound_memfn(ten_two)<<'\n';
所以那里有一个_1占位符在占位这个this
也可以不用_1,直接绑定一个确定值,输出就不用指定了,如下
auto bound_memdata = bind(&MyPair::a,ten_two);
cout << bound_memdata() << '\n';
也可以绑定数据成员
auto bound_memdata2 = bind(&MyPair::b,_1);
cout << bound_memdata2(ten_two) << '\n';
利用新的适配器重写bind2nd,
auto fn_=bind(less
(),_1,50);
cout << count_if(v.cbegin(),v.cend(),fn_)<
记住,这个bind初始化之后要给一个名字,然后拿名字传入别的地方进行使用
reference operator*()const Iterator tmp=current;return *--tmp;
std::ostream iteratorout_it(std::cout, ",");
std::istream iteratoreos;//end-of-stream iterator
std::istream iteratoriit(std:cin);//stdin iterator
basic_istream* in_stream;
,这东西即为cin祖先,不一定就是cin,其包含了cin++*this;
istream_iterator<T,charT,traits,Distance> &operator++()
{
if (in_stream && !(*in_stream >> value))in_stream = 0;
return *this;
}
这个++有什么用?
如果in_stream有东西了,而且(*in_stream >> value)
等待输入一个数值放到value当中去,in_stream就是cin
也就是说,当你创建一个对象,cin就被继起来,而且++,做上方的动作
所以这里就打进来一个数值,std::istream iterator
,什么值呢?就是double
这个double就是源代码当中的T value
,所以要输入一个double
输入完毕后进行判断,看是否等于结束符号,if (iit != eos) value2 = *iit;
判断结束就进行取值
*result = *first;
这里,就已经开始return了,不需要读了使用一個東西,
卻不明白它的道理,
不高明!
勿在浮沙築高台
unordered_set custset;
unordered_setcustset(20,customer_hash_func);
,这是C++20的新标准,任意个数模板参数,上面hash_val当中,目前分解出来是3个,但是也可能是4个,8个等等,所以你不知道是多少个,所以有了这个任意多个的模板参数重载函数hash_val(c.fname,c.Iname,c.no);
应该调用第1个版本篮子个数永远大于元素的个数
刚刚说明的hash_function除了之前的两个形式,现在还有个形式3,如下
get<0>(t1)
这样的语句_type_traits::has_trivial_destructor
通过这个去问问题class ostream : virtual public ios
{
// NOTE: If fields are changed, you must fix _fake_ostream in stdstreams.C!
void do_osfx();
public:
ostream() { }
ostream(streambuf* sb, ostream* tied=NULL);
int opfx() {
if (!good()) return 0;
else { if (_tie) _tie->flush(); _IO_flockfile(_strbuf); return 1;} }
void osfx() { _IO_funlockfile(_strbuf);
if (flags() & (ios::unitbuf|ios::stdio))
do_osfx(); }
ostream& flush();
ostream& put(char c) { _strbuf->sputc(c); return *this;
ostream& operator<<(char c);
ostream& operator<<(unsigned char c) { return (*this) << (char)c; }
ostream& operator<<(signed char c) { return (*this) << (char)c; }
ostream& operator<<(const char *s);
ostream& operator<<(const unsigned char *s)
{ return (*this) << (const char*)s; }
ostream& operator<<(const signed char *s)
{ return (*this) << (const char*)s; }
ostream& operator<<(const void *p);
ostream& operator<<(int n);
ostream& operator<<(unsigned int n);
ostream& operator<<(long n);
ostream& operator<<(unsigned long n);
.....
}
class _IO_ostream_withassign : public ostream {
public:
_IO_ostream_withassign& operator=(ostream&);
_IO_ostream_withassign& operator=(_IO_ostream_withassign& rhs)
{ return operator= (static_cast<ostream&> (rhs)); }
};
extern _IO_ostream_withassign cout, cerr;
extern
说的是,这个变量可以被文件之外的东西使用,可以被外界使用,文件之外的都能看到他cout
是_IO_ostream_withassign
,那么_IO_ostream_withassign
又是什么呢?_IO_ostream_withassign
继承自ostream
,ostream
虚继承自ios
,在虚继承中,有很多重载,如果是自己的类型也想用cout输出得像下面一样重载后写进标准库c1.insert(ite,V1type(buf));
这里是一个临时对象,所以编译器知道放进去后原来的就不被使用了,所以自动调用movable的constructorM c11(c1);
就是调用传统的copy constructorM c12(std::move(c1));
就是调用move constructor,使用完,浅拷贝后,c1就废了C++标准库真是个宝库,受益匪浅