本文共 3421 字,大约阅读时间需要 11 分钟。
话不多说,就是总结几个问题
1.什么是迭代器
(1)迭代器可以看做一种智能指针,他会重载*和->运算符。
(2)但是它并不一定具备指针的所有运算符:p++,p--,p+n,p-n,p[n],p1-p2,p1<p2,p1!=p2
有可能它只提供一部分,比如:p++,p--,p1!=p2
(3)STL强数据容器与算法分开,而迭代器又作为胶着剂将其撮合在一起。
2.为什么每一种容器都要开发其专属迭代器
(1)不同容器的迭代器,能实现的操作本身就和容器息息相关。
(2)如果把迭代器放在容器的外面,可能会暴露出过多的容器的内部实现细节。
3.迭代器的相应型别
(1)在使用迭代器时,我们希望通过访问迭代器这个类就能访问到的与迭代器息息相关的一些类,
比如说,我们在使用迭代器时,很可能访问迭代器所指向的类型,这个型别。
再比如,我们在求取两个迭代器的距离时,描述这个距离的变量的类型。指针毕竟是指针,距离毕竟不是直接的int或unsigned int
而迭代器毕竟也不是直接的指针。
(2)迭代器的5中相应型别
①value type
②difference type
③reference type
④pointer type
⑤iterator_category
4.内嵌型别与iterator_traits的作用
(1)为什么要用内嵌型别
在3.迭代器的相应型别中,我说了,相应型别就是我们希望访问迭代器这个类就希望访问到的与迭代器息息相关的类。
那么问题是,如何访问呢?
比如,有一个函数func
template<class Iterator>
value_type func(Iterator iter){}
这里,模板参数Iterator表示迭代器的类型,这个函数的返回值时迭代器所指向的类型value_type但是,这根本不合法,
因为value_type根本没有声明过,编译器根本不知道value_type是个什么东西
如果改为:
template<class Iterator, class value_type>
value_type func(Iterator iter){}
这样倒是合法了,可是,这个value_type类,与Iterator这个类有什么关系呢?表示不出来value_type是Iterator实例所指向的类
template<class Iterator<value_type>>
value_type func(Iterator iter){}
这样还是不行
但是我们可以这样
template<class T>
class Iterator{
typedef T value_type
........
};
然后
template<class Iterator>
typename Iterator::value_type
func(Iterator iter){}
其实内嵌型别就是内部类,只是本来在类里面定义一个class,这里由于是由模板参数T来推断的类名,所以直接就typedef T value_type
表示,在class Iterator里面定义了一个类叫做value_type,而这个类的定义和模板参数T的定义是一样的。到时候编译期根据传进来的模板参数自己去找。
(2)为什么要使用iterator_traits
首先我们来看看,iterator_traits是什么,
template<class Iterator>
struct iterator_traits{
typedef typename Iterator::value_type value_type
............
};
感觉就是多加了一层间接性,但是这样又把取得Iterator的内嵌型别的过程放入了另一个模板类中,这个时候可以用到偏特化特性。
偏特化特性是什么,下面会介绍。有什么好处呢?
之前的内嵌型别解决了访问模板类Iterator的相关型别访问问题,但是如果我需要将我的这个函数func的参数范围从Iterator这个类似于指针的类类型,
扩展到包含系统原生指针类型怎么办呢?毕竟原生指针类型不是模板类,也就没有了内嵌型别一说。
这个时候,采用iterator_traits类,再来访问指针类型的相关型别,利用偏特化特性,也能使得原始指针类型也访问到相关型别了。
这就是iterator_traits的作用
5.偏特化
泛化、特化、偏特化这三个是对应的概念。
泛化就是说泛型
特化就是说,具体到某一种类型
偏特化就是说,相对于泛型的任意一种型别的更进一步的限制,但是它仍然没有具体到某一种类型
比如,
template<T> func(T t),泛化,T表示任意类型
template<T> func(char c)特化,char表示字符型
template<T> func(T* pt)偏特化,表示T为原生指针
6.平凡与非平凡函数
non-trival表示有专门的定义,而不是合成的默认的。
trival表示,没有专门定义的
比如int、long或者某个类的默认构造函数就是trival的,对于这种,在进行大规模删除的时候,系统可以不做处理。
但是如果某个类定义了属于自己的析构函数什么的,就是non-trivial的了,大规模删除还是要一个一个老老实实调用其析构函数。
7.type_traits编程技巧
type_traits是对于iterator_traits的推广。
通过定义一个type_traits<T>类,type_traits可以编译时判断出,T类别的构造器、拷贝函数、析构函数是trival还是non-trivial的,从而分情况进行处理。
提升效率。
2016/06/26补充:
8.奇妙的C++构造函数
为了理解什么是trival与non-trival的,做了几个实验。感觉确实有不一样的。
重点就是要理解,什么叫没有专门定义的。
1.什么是trivial函数
我们先来看以下的栗子:
int i1;
int i2();
int i3 = int();
int i4(6);
cout << i1 << endl << i2 << endl << i3 << endl << i4 << endl;
编译运行,直接上图:
i1是一个整数,只是未经初始化
i2是一个指针,指向一个函数
i3是一个整数,值为0
i4是一个整数,值为6
为什么说,i2是一个指针呢?
因为int i2();就是一个函数声明,函数名是i2,返回类型为int
同时可以通过
cout << (i2 == 5) << endl;
验证,出错信息为:
假设有类A,那么在C++中,调用默认构造函数就是
A a;
假设构造函数是A(int, double)
调用这个构造函数就是
A a(1, 1.5);
值得注意的是
A a = A(1, 1.5);
首先调用构造函数A(int, double)生成一个对象,然后再调用拷贝构造函数,将生成的对象用于初始化a
那么下面回答正题,trivial的构造函数会发生什么?
A a;
这就是一个默认的构造函数,那么发生的就是,只是将a的内存空间,分配给你使用了,但是对于a里面的内容,并没有做任何处理。
所以a里面现在可能是任何值。
所以trivial的函数就是,实际上除了将这个空间分配给你使用(trivial构造函数),或者收回系统(trivial析构函数)以外,对空间里面的内容根本不管。
2.有哪些函数使得trivial的
①原始数据类型的构造和析构函数(局部自动类型,非static或者全局)
②系统为某个类合成的默认构造函数
③虽然类定义了自己的构造函数,但是对成员并没有构造函数列表的
其中②、③是对于其成员来说的。
3.trivial函数有什么好处
如果要以一句话来回答的话,就是:提高效率。
因为对于空间里面的内容实际上什么都没有干,所以效率很高。
如果你对某个动态分配内存的原始类型调用destroy,那么其实你会发现,调用以后值根本就没有变,这就是因为
原始类型的析构函数是trivial的,调用之后,并没有什么卵用。
当然这其实是牺牲了一部分安全性的。
C/C++里面有多少bug是因为未经初始化的指针,去指向改变,简直就是灾难。