C++标准库(STL)与泛型编程
学习网站:
cplusplus.com
cppRerference.com
学习书籍:
《STL源码剖析》
STL体系结构基础介绍
STL六大部件:容器、分配器、算法、迭代器、适配器、仿函数
迭代器:泛化的指针
分配器:分配内存
仿函数:作用类似函数,加减
适配器(变压器):
count_if
:给出条件之下符合条件的个数;
bind2nd:绑定第二参数;
not1 :否定,大于等于40;
end指向最后一个元素的下一个元素
c++11的新特性:range-based for statement、auto’
序列式的容器
array
:
vector
:尾部自动增长;
Deque
:双端队列,两端可进可出;
List
:双向链表
forward-List
:单向链表;
关联式容器(大量的查找)
Set/Multist: set中的key就是value,value就是key,set不能重复,multiset可以重复;
Map/Multimap:map节点有两个东西,里面存的都是key和value,通过key来查找value;;
set、map底层实现都是红黑树
unordered :不定序 hashtable;
separate chaining :
解决冲突:在哈希法,线性探测、拉链法;拉链法不能太长;
vector扩容的时候:扩容在别的位置找到2倍大的空间,然后拷贝过去;
Deque的底层实现:
分段连续,号称连续(但是是假象),让使用者表现的连续;每次扩充都是一个buffer;
queue和stack的底层都是deque,不支持【】,不能用iterator;
multimap也不可以用【】做insertion;
multimap<long,string>c;
c.insert(pair<long,string>(i,buf));
-------------------------------------------------------------------------------
unordered_multiset和unordered_map底层实现都是哈希表
哈希表拉链不能太长,元素个数大于等于bucket大小,bucket重新扩充,篮子一定比元素多;
使用分配器
OOP vs GP(generic programmin泛型编程)
OOP企图将data和函数放在一起;
GP确实将data和methods分开;
采用GP的方法优点:
- 容器和函数可以各自闭门造车,其可以通过迭代器来实现;
- 算法通过迭代器确定操作范围,并通过迭代器来取用容器里面的元素;
某个容器如果有自己的排序算法,一定要用自己的。
接受的comp 仿函数;
操作符重载(overloading)
类模板
特化:
template<>
偏特化
左边vector里面是long和bool时内存分配肯定不一样,数量的偏特化
右边的是范围的特化,
函数模板
成员模板(member templates)
分配器(allocators)
分配内存:时间和空间上的效率;
malloc分配的内存如右边所示:
两个重要的函数是allocate和deallocate;底层都是调用malloc和free。
VC6+的allocator只是以::operator new和::operator delete完成allocate()和deallocate(),没有任何特殊设计;
类名加上()是一个临时对象。
int *p=allocator<int>().allocate(512,(int*)0);
allocator<int>().deallocate(p,512);
总结:编译器实际上底层都是调用malloc和free来实现的,
G2.9版本的allocators
使用的分配器都是alloc;
分配内存要减少malloc额外开销,
内存上下都有一个cookie,记录着整块的大小,
容器的应用可以不需要cookie,减少cookie的内存;
0号链表代表8个字节,15号li’ai’b
深度探索list(上)
灰色节点,制造一个不属于链表的部分,end()指向灰色的节点;前开闭后开;
G2.9中
类里面只有一个变量:node(本质是一个指针,4个字节),类的大小就只有四个字节;
除了绿色节点还有两个指针(void_pointer为void *类型的)
每个元素还附加带了两个指针;
迭代器(iterator):红色的,名为typedef,三个模板参数;
typedef __list_iterator<T,T&,T*>iterator
是一个聪明的指针,是一个类;
iterator里面有大量的操作运算符重载;
5个typedef ;
前置++:没有参数,i++,i变成调用本身;把node里面的next指针取出来,当成一个值设个node自己本身。返回类型为引用;
后置++:有参数int,但是没有用;
- 先记录原来的东西;
- 进行操作,前进一个动作;
- 把原来的值传回去;
=和*都被重载过了,=是一个拷贝构造,船舰tmp并以*this为初值,不会唤起operator*,因为*this已经被解释为构造函数的参数;返回类型为原值
整数的加加
++++i---------------------++(++i);所以前置加加返回的类型是引用;
i++++;-------------------(i++)++;这是不允许的;因此后置加加不行;
*取出其中的值;
深度探索list(下)
两个版本的比较
2.9迭代器要有三参数,4.9只有一个参数
2.9指针为void*;4.9指针指向自己
2.9大小为4,4.9的大小为8.因为2.9里面数据成员有一个指针,list—>base---->list_impl多大(_M_node为两个指针,所以大小为8;)
2.9版本list只是本身自己;
4.9版本的继承一个父类,还包含了复杂的关系;
iterator设计遵循的原则
rotate函数会想要知道iterator的属性:
iterator_category
:分类
value_type
:iterator元素本身所指的东西的类型是什么;容器里面放了y一百万个元素,这个元素的类型是string。那么value type为string
difference type
:两个迭代器之间的距离用什么来表现;
未在标准库中被使用的rerference 和pointer
必须提供的5中类型typedef ;
标准库在实现链表时衡量两个链表的距离选用的标准是ptrdiff_t
;但是以后不够用时,会崩溃;
如果迭代器并不是一个class呢?如果只有指针呢,指针是退化的迭代器,迭代器是泛化的指针,如果传进去的只是一个指针,就不知道怎么调用;
用iterator traits(萃取机)用以区分类的迭代器还是普通的指针;
iterator_traits的实现;
value_type的主要目的用来声明变量,而声明一个无法被赋值的变量没什么用,所以iterator(即便的是const iterator)的value type 不该加上const;
iterator若是const int *;其type应该是int 而非const int:
vector深度探索
实现是一个数组;数组用完之后扩充,到内存的另外一个地方扩充,但是不能原地扩充;两倍的size扩充;
sizeof(vector)大小为 3个指针的大小,32位机器上的大小为12个字节;
连续空间的容器会提供中括号[]访问;
两倍增长:
先判断有没空间可用:
如果没有空间可用:2倍增长;
把原来的先拷贝到新的地方,再放新的元素,最后把insert插入的元素拷贝;
vector的使用成长存在大量的拷贝构造和析构;
vector的迭代器
value_type实际上是一个T*;利用偏特化的版本
4.9版本的vector:
大小为成员变量的大小
vector----base---->impl---->(三个指针,12个字节)
public继承:代表is a关系,是一种的关系,为了让impl类能用到 allocator的功能,理论上应该用private就可以了;
4.9版本的vector的iterator
4.9版本,大佬发言:
array、forwardlist深度探索
array没有构造函数和析构函数;
迭代器;连续空间的迭代器可以直接用指针来表示,不需要用一个class来表示;
forwardlist
deque、queue、stack深度探索(上)
deque是一个双向开口的容器;分段连续, 五个指针指向五个缓冲区;
迭代器走到每个缓冲区的边界时,都要有能力跳到下一个node;
deque里面迭代器本身的大小为16个字节;
deque本身是40个字节(16*2+4+4=40);
三个参数:参数类型、空间配置器,buffsize指每个buffer容纳的元素个数
deque的迭代器
遵循的原则:必须定义出五个类型;
随机访问,制造出分段连续的假象;
deque的insert
安插位置是距离起点还是终点比较近,选择短的一端插入。
deque、queue、stack深度探索(下)
deque怎么模拟连续空间????
后++调用前++;后–调用前–;
后加加先判断是否到达某个缓冲区的last位置,如果到了跳到下一个缓冲区;下一个缓冲区重新设置边界;
deque的+=运算符重载
先判断会不会跳跃缓冲区,算出需要跳几个缓冲区,
-=可以调用+=来实现;
[]可以用+=来实现
node存的容器是vector,扩充的时候是拷贝到中段,8个扩充到16时,是拷贝到中间,两边都有空余位置;
queue和stack
底层实现是通过deque实现的;
技术上把queue称为适配器;
stack和queue都可以选择list或queue作为底部支撑;
stack先进后出、queue先进先出;
stack和queue都不允许遍历,也不提供迭代器
stack可以使用vector作为底部支撑;
queue不可以选择vector作为底部支撑;因为c.pop()从头部删除元素会出错;
stack和queue都不可以使用set和map作为底部支撑;
红黑树深度探索
关联式容器: 红黑树和哈希表;
红黑树是一种高度平衡的二分查找树;
红黑树提供遍历操作以及iterators;不应该使用红黑树中的迭代器更改元素的指,编程层面并未阻绝这种事;
提供两种insertion方式:insert_unique和insert_equal 中;
key和data两个合起来是一个value;
五个模板参数:key的类型;value类型;value里面key怎么获取;比较大小;分配器(默认值).
三个数据域: node_count(节点个数)、header(指针指向)、key_compare(传进来的compare类);(4+4+1仿函数理论大小为0(实际上大小为1),存在内存对齐,要是4的倍数,所以是12个字节)
key就是value,没有data部分就是下面左上角的写法,identity(等同),
红黑树的使用
新版红黑树的大小为24个字节;
set、multiset深度探索
- 以红黑树作为底层,元素自动有序,排序的依据是key。
- set/multiset 元素的value和key合一,value就是key;
- set/multiset提供遍历和迭代器的操作;无法使用iterators改变元素值,因为key有其严谨的排序规则;
- set和multiset区别:set的key必须独一无二,调用的insert是红黑树提供的insert_unique(),而multiset的key可以重复,insert()调用的是红黑树的insert_equal().
set
指定三个模板参数,底层实现是红黑树;
set里面key就是value,value就是key,取出key方式是利用函数identity():
不可以改变iterators的值,拿到的是const_iterator ,因此不可以改变指向的值;
set所有的操作,都是呼叫底层的红黑树来实现的,这种意义上来说,set技术分类上来说是一个container adpator(容器适配器);
vc中不提供identity,那么如何使用红黑树的呢;
_Kfn的实现,与g2.9的实现方式其实是一样的。
map和multimap深度探索
这两个容器也是以红黑树作为底部支撑;自动排序;
提供遍历和iterators操作,++遍历可以达到排序
与set的区别就是set里面key就是value,value就是key,map里面,每个元素是一个value,value里面有key和data, key不能修改,data可以修改;
无法使用iterator改变元素的key,因为key有严谨的排列规则,但可以用来改变元素的data;因此map/multimap内部自动将user指定的key type 设置为const ,如此便可以禁止user对元素的key赋值;
select1st函数:从里面拿第一个元素就是key
迭代器就是红黑树的迭代器
pair<const Key,T>value_type
:红黑树的第二个参数,将key设置const,因此key不能改变,但是data可以改变。
select1st 的实现: 返回pair的第一个元素,即v.first;
multimap不可以使用[]作为insertion
如果不存在,会创建这个值之后插入到红黑树中去;
lower_bound():
二分查找的一种版本,试图在已经排序好的数组sorted[first,last)
中查找元素value,如果[first,last)
中有与value相等的元素(s),便返回一个iterator指向其中的第一个元素,如果没有这样的元素存在吗便返回假设该元素存在时候该出现的位置,也就是说它会返回iterator指向第一个不小于value的元素,如果value大于[first,last)的任何元素,将返回last,换句话说,lower_bound返回的是不破坏排序的已安插value的第一个适当的位置。
hashtable深度探索(上)
空间不足的时候会发生冲突:
解决办法:线性探测、平方探测、拉链法;
Separate chaining(拉链法)
链表如果太长,因为链表的搜寻是线性搜索,时间也会增大,因此需要打散;
如果元素个数大于bucket的大小,需要扩充rehash.
素数作为bucket的大小,53倍数附近的素数,53扩充到97.
HashFcn:映射的函数;得到hashcode
Extractkey:取出其中的key;
EqualKey:相等,比较key的函数;
hashtabel的data有多大:
三个仿函数(类)、buckets是一个vector、nums_elements (3+12+4=19,内存对齐,为4的倍数,因此大小为20个字节);
哈希表迭代器的设计指向某个node。设计实现有点类似deque
hashtable深度探索(下)
指定六个模板参数
元素是字符串,key也是字符串;
以下使用value就是key,key就是value;
两个字符串是两根指针指向的值,要去比指向的值,而不是指针。
EqualKey的值是true和false,但是strcmpy返回值是-1,0还有1,所以不能直接用strcmpy传进hashtable的参数中去;
最重要的是hash映射函数的设计
字符串转换为编号:
对于const char *的哈希映射函数:
for(;*s,++S) h=5*h+*s;
例如上列中s指向“abc",计算得到h为 5*(5*‘a’+‘b’)+‘c’;够乱就可以;也可以自己设计。
标准库没有提供现成c++字符串的hash映射函数;
取余运算;
unordered容器
unordered_map和unordered_set ,底层实现是哈希表;
100万元素,有多少个篮子,篮子个数一定大于100万;
序列式容器和关联式容器(哈希表和红黑树);
算法的形式
算法是一个函数模板;第二版本(可以自己设定仿函数)
算法依靠迭代器访问容器;
迭代器的分类
array:随机访问
vector: 随机访问
deque:随机访问
list:双向链表
forwardlist :单向链表
红黑树:双向迭代器
unordered_map:看bucket
各种容器的迭代器分类:
istream_iterator的迭代器分类;
ostream_iterator的迭代器分类
迭代器分类对算法的影响
distance:两个指针之间的距离,如果是随机访问(版本2直接相减),否则是版本1;用什么类型来表示距离呢,不一定是整数
问萃取机difference_type;
根据迭代器的分类调用不同的函数;
advance:当前距离跳跃n个距离;
对象来表现:因为存在继承关系,is a 是一种。因为迭代器版本之间存在继承关系;
copy:源端的起始地址,尾地址。目的端的首地址;
非常精细,考虑;
Type_Traits:拷贝赋值重不重要。
复数类的构造(不需要构造、析构、拷贝赋值,默认的不重要has trival op=())
unique_copy:只拷贝不一样独一无二的元素,一样的就不拷贝;
容器的迭代器要回答五个问题
算法源代码剖析(11个例子)
qsort和bsearch不是算法,因为不满足右边的模板。
算法accumulate
自己写仿函或者函数对象(类或者结构体重载()),来重载accumulate
算法 for_each
算法repalce,replace_if,repalce_copy
容器不带有成员函数count():array、vector、list、forward_list、deque;
关联式容器有自己比较快的方式;
容器带有成员函数count():set、multiset、map、multimap、unordered_map、unordered_set等。
算法sort
逆向排序:sort(nums.rbegin(),nums.rend())
容器带有成员函数sort():list、forwardlist;
关于reverse iterator,rbegin()、rend()
reverse_iterator:
适配器是一个很重要的东西
算法binary_search
前提是有序数组
lower_bound:不破坏原排序的第一个位置;
upper_bound:不破坏原排序的最后一个位置;
仿函数和函数对象
改变算法的某些功能;第二版本
重载小括号():
是一个class或者struct;
几个不同版本的sort;
标准库的仿函数都继承于某个类(binary_function两个操作数或者unary_function,一个操作数),指定三个模板参数;
没有data,类的大小为1(理论上是0);作为父类时候为0;
less继承于binary_function,继承函数,可以借用三个typedef;
算法要问问题,迭代器要能回答,适配器问问题,仿函数必须回答问题,
因此仿函数可适配的条件是:要回答问题的话,必须继承适当的某一个binary_function或者uanry_function:
存在多种适配器
改造器,变压器;存在的东西接口改一下;
改变什么就叫什么adpater;
a取用b的资源有两种途径:
1、a继承与b;
2、a内含于b;
stack和queue内含一个容器;改造deque的一些用法
仿函数适配器binder2nd
count_if
小于40的元素有多少
主体里面有一个辅助函数binder2nd
;
在binder2nd
源码中,调用了Operation
类的first_argument
、second_argument_type
和result_type
,这些字段都是从STL仿函数基类binary_function
继承得到的.因此我们自己写的仿函数也要继承自基类binary_function
,才能使用适配器binder2nd
进行增强.
binder2nd
适配器增强得到的仍然是一个仿函数,因此也要继承基类unary_function,
以便被其它适配器增强.
使用类模板时必须指定模板参数的取值,因此将binder2nd封装进函数bind2nd中,使用函数模板的参数推导功能,简化代码
仿函数适配器binder2nd
可以绑定二元仿函数的第二参数,生成新的仿函数.其源码如下;
// 放函数适配器binder2nd也是仿函数类,因此继承自仿函数基类unary_function
template<class Operation>
class binder2nd : public unary_function<typename Operation::first_argument type,
typename Operation::result_type> {
protected:
// 内部成员,分别记录运算和第二实参
Operation op; //红色的function object
typename Operation::second_argument_type value; //40
public:
// 构造函数,将运算和第二实参记录下来下,记录在x和y之中
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);
}
}
使用类模板时必须指定模板参数的取值,operation的类型。
因此将binder2nd封装进函数bind2nd中,使用函数模板的参数推导功能,简化代码:
typeame()是一个临时对象;
// 辅助函数
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));
}
typename
:长的提问都有typename。帮助编译器通过代码,Operation不知到是什么。
新型适配器bind
not1
继续修饰bind2nd修饰过的东西,不小于40;
pred就是bind2nd一整块,
// 仿函数适配器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);
}
bind
函数bind要和命名空间std::placeholders
中的占位符_1、_2、_3…等占位符配合使用.bind函数可以绑定:
函数和函数对象.
成员函数(绑定成员函数时占位符_1必须是该类对象的地址).
成员变量(绑定成员变量时占位符_1必须是该类对象的地址).
#include <iostream> // std::cout
#include <functional> // std::bind
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绑定到函数的第一参数上
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
return 0;
}
reverse_iterator
迭代器适配器
容器的rbegin()和rend()方法返回逆向迭代器reverse_iterator,逆向迭代器的方向与原始迭代器相反.
对逆向迭代器取值,就是将对应的正向迭代器退一位取值。
reverse_iterator
源码如下
template<class Iterator>
class reverse_iterator {
protected:
Iterator current; // 对应的正向迭代器
public:
// 逆向迭代器的5种关联类型与正向迭代器相同
typedef typename iterator_traits<Iterator>::itrator_category iterator_category;
typedef typename iterator_traits<Iterator>::value_type value_type;
// ...
typedef Iterator iterator_type; // 正向迭代器类型
typedef reverse_iterator<Iterator> self; // 逆向迭代器类型
public:
explicit reverse_iterator(iterator_type x) : current(x) {}
reverse_iterator(const self &x) : current(x.current) {}
iterator_type base() const { return current; }
// 逆向迭代器取值: 就是将迭代器视为正向迭代器,退一格再取值
reference operator*() const {
Iterator tmp = current;
return *--tmp;
}
pointer operator->() const { return &(operator*()); }
// 逆向迭代器的加运算对应正向迭代器的减运算
self &operator++() { --current;return *this; }
self &operator--() { ++current;return *this; }
self operator+(difference_type n) const { return self(current - n); }
self operator-(difference_type n) const { return self(current + n); }
};
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;
}
// 重载运算符*和++: 不做任何动作
insert_iterator<Container> &operator*() { return *this; }
insert_iterator<Container> &operator++() { return *this; }
insert_iterator<Container> &operator++(int) { 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));
}
X适配器ostream_iterator
输出流迭代器ostream_iterator常用于封装std::cout.下面程序将容器中元素输出到std::cout中.
int main() {
std::vector<int> myvector = {10, 20, 30, 40, 50, 60, 70, 80, 90};
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;
//constructor;
ostream_iterator(ostream_type &s) : out_stream(&s), delim(0) {}
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) {
//value------>cout,copy来源端的每一个元素,一个一个丢到cout里面。
*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中读取数据:
std::istream_iterator<double> eos; // 标志迭代器,通过与该迭代其比较以判断输入流是否终止
std::istream_iterator<double> iit(std::cin); // 封装std::cin的输入流迭代器
double value;
if (iit != eos)
value = *iit; // 从输入流读取数据到变量value中,相当于: std::cin >> cvalue
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; }
};
下面程序使用输入流迭代器istream_iterator从std::cin中读取数据到容器中:
//当你创建一个对象iit时,已经在读了
istream_iterator<int> iit(cin), eos;//eos作为标定:
copy(iit, eos, inserter(c, c.begin()));
一个万用的hash function
customer当成元素放在容器里面;
左边是一个成员函数(类里面对象成为函数对象,重载()),右边是一个普通的函数
不定量的模板(variadic templates)
传5个东西进来,每次取第一个作为种子,hash_combine来改种子。循环递归调用,剩下的为最终都为1时,调用第三个版本;最后一个seed就是一个hashcode;
0x9e3779b9
firstname、lastname、和编号;
新版本里面,所有基本类型,都有自己的一个hashfunction;
tuple(元组)用例
// 创建tuple
tuple<string, int, int, complex<double> > t;//大小为32个字节;不是28,未解之谜
tuple<int, float, string> t1(41, 6.3, "nico"); // 指定初值
auto t2 = make_tuple(22, 44, "stacy"); // 使用make_tuple函数创建tuple
// 使用get<>()函数获取tuple内的元素
cout << "t1:" << get<0>(t1) << "<< get<1>(t1)<<" << get<2>(t1) << endl;
get<1>(t1) = get<1>(t2); // 获取的元素是左值,可以对其赋值
// tuple可以直接进行比较
if (t1 < t2) {
cout << "t1 < t2" << endl;
} else {
cout << "t1 >= t2" << endl;
}
// 可以直接拷贝构造
t1 = t2;
cout<<"t1:"<<t1<<endl;//<<运算符重载;
// 使用tie函数将tuple的元素绑定到变量上
tuple<int, float, string> t3(77, 1.1, "more light");
int i1, float f1; string s1;
tie(i1, f1, s1) = t3;
// 推断 tuple 类型
typedef decltype(t3) TupleType; // 推断出 t3 的类型为 tuple<int, float, string>
// 使用 tuple_size 获取元素个数
cout << tuple_size<TupleType>::value << endl; // 3
// 使用 tuple_element 获取元素类型
tuple_element<1, TupleType>::type fl = 1.0; // float
tuple类源码分析
容器tuple的源码使用可变模板参数,递归调用不同模板参数的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; } // 返回第一个元素值,41
inherited &tail() { return *this; } // 返回剩余元素组成的tuple(将当前元素强制转换为父类类型)
};
type traits
类型萃取机制(type traits)获取与类有关的信息,在C++11之前和C++11中分别由不同的实现方式.
模板
默认构造函数、拷贝构造、拷贝赋值、析构函数不重要 (false);
假设所有的类型默认都是重要的;
G2.9版本
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 { /*...*/ };
使用方式:问这个问题
__type_traits<Foo>::has_trivial_destructor
C++11中的类型萃取机制:辅助类
C++11在头文件type_traits中引入了一系列辅助类,这些辅助类能根据传入的模板参数自动进行获取该类的基本信息,实现类型萃取,并不需要我们为自己创建的类手动编写类型萃取信息.
官方网站上列出了所有用于类型萃取的辅助函数:
使用案例;
typeid:丢进来的类型的名称
一个类需不需写析构函数:如果有指针,一定要写析构函数,字符串里面带有一根指针,所以一定要写析构函数。
一个类如果是一个基类的时候,需要将析构函数声明为虚函数,如果不打算作为父类,不需要是虚函数,不该把字符串当成父类。
多态(is polymorphic 1) :有虚函数;
&&:搬离构造函数,
复数不用写析构函数;所以析构函数不重要;
type traits实现
is_void :先把一些无关紧要的拿走,remove_cv remove const和volatile;
包含泛化和偏特化两种版本的实现;
头文件type_traits中定义了辅助类remove_const和remove_volatile用于除去类型中的const和volatile关键字.
// remove const
template<typename _Tp>
struct remove_const {
typedef _Tp type;
};
template<typename _Tp>
struct remove_const<_Tp const> {
typedef _Tp type;
};
// remove volatile
template<typename _Tp>
struct remove_volatile {
typedef _Tp type;
};
template<typename _Tp>
struct remove_volatile<_Tp volatile> {
typedef _Tp type;
};
is_void类继承自__is_void_helper类,__is_void_helper类使用偏特化的形式判断传入的模板参数是否为void
template<typename>
struct __is_void_helper
: public false_type {
};
template<>
struct __is_void_helper<void>
: public true_type {
};
template<typename _Tp>
struct is_void
: public __is_void_helper<typename remove_cv<_Tp>::type>::type {
};
is_integral类继承自__is_integral_helper类,同样使用偏特化的方式判断传入的模板参数是否为整数类型
template<typename>
struct __is_integral_helper : public false_type { };
template<> struct __is_integral_helper<bool> : public true_type { };
template<> struct __is_integral_helper<char> : public true_type { };
template<> struct __is_integral_helper<signed char> : public true_type { };
template<> struct __is_integral_helper<unsigned char> : public true_type { };
// ...
template<typename _Tp>
struct is_integral
: public __is_integral_helper<typename remove_cv<_Tp>::type>::type { };
一些type traits辅助类(如is_enum、is_union和is_class等)是由编译器实现的,STL源码中找不到其实现函数.
// is_enum
template<typename _Tp>
struct is_enum
: public integral_constant<bool, __is_enum(_Tp)> // __is_enum函数是由编译器实现的,STL源码中找不到其源码
{ };
// is_union
template<typename _Tp>
struct is_union
: public integral_constant<bool, __is_union(_Tp)> // __is_union函数是由编译器实现的,STL源码中找不到其源码
{ };
// is_class
template<typename _Tp>
struct is_class
: public integral_constant<bool, __is_class(_Tp)> // __is_class函数是由编译器实现的,STL源码中找不到其源码
{ };
cout
cout是一个对象,
关键字:extern,这个变量可以被文件之外的看得到
不是基本类型,需要自己写操作符重载;
正则表达式输出;
moveable元素对容器速度效能的影响
写一个moveable class
深拷贝:不但把指针拷贝,而且把指针指向的值也拷贝;
浅拷贝:move的动作;(&&)
copy版本和move版本的对比如上图左右所示;
右下角是容器的copy,和上面数据的copy不一样
move版本条件
1、move之后原来的东西不能再使用;
左下角为vector的深拷贝;
只交换了三根指针,move,原来的东西不能在使用;
basic_srting 有copy和move版本。