C++标准库与内核分析第三讲(六大部件源码分析)

1.准备工作

在第二讲中主要讲到标准库中容器的源码,这里主要讲一讲标准库算法。如下
通常算法会有第二个版本,名称相同,参数不同,如下算法,第一个有两个参数,第二个有三个参数,其中第三个参数,就是允许我们用户自己传入一个准则。例如排序算法,我们就可以传入一个准则,即如何比较大小。Algorithms如果需要知道容器的某一些性质,则iterators必须进行回答,如果iterators没办法回答,则编译器编译到这一行语句的时候就会出错。
这里写图片描述
前面我们已经提到迭代器应该提供五个相关类型,由于搭配着算法,所以我们来了解在分类这件事情上是如何设计的。我们看看容器的性质,vector,array是连续空间,所以其迭代器类型为random_access,而deque虽然是分段连续,但是对外号称连续所以其迭代器类型也应为random_access.而list 为双向链表,所以其迭代器类型为bidirectional_iteration,而forword_list则为forward_iterator.至于set,multi_set,map和multi_map虽然我们看不出来,但是他们底部是以红黑书做支撑的,红黑树是双向的,所以其迭代器类型为bidirectional_iteration。其次unordered_set,unordered_multiset,unordered_map,unordered_multimap我们知道其底部是以哈希表做支撑的,他是一个链表,所以取决于这个链表是双向链表还是单向链表,这样就可以判断迭代器的类型。至于input_iterator_tag和output_iterator_tag则是输入输出流的迭代器类型,这两个很特别。
这里写图片描述

现在我们来看看istream_iterator的iterator_category..不同版本的编译器做法不一样,但是他们提供的接口必须一致。我们可以从下面的源代码中看到,istream_iterator中明确定义了iterator的类型为input_iterator。ostream_iterator类似。
这里写图片描述
这里写图片描述

2.Algorithms(算法)

我们首先看看迭代器对算法的影响。在这里有一个distance函数。他是用来计算两个元素之间的距离。我们可以看到他接受一个迭代器类型作为参数。不同的迭代器类型调用不同的distance函数。如果是连续空间,直接first-last。如果不是连续空间,则又是另一种操作。假如容器里有100万个元素,则连续空间计算距离很快,而如果不是连续空间,则first指针要走100万次,这就很慢了。
这里写图片描述
我们再看一个算法advance(前进)也是根据不同的迭代器类型,有三个子函数。如果是连续空间则直接跳到n,如果是双向链表,则可以一步步前进或后退。如果是单向则只能一步步前进。注意一个问题,我们前面讲到迭代器有5种类型,就算不算output_iterator也有四种类型,那为什么distance函数却只提供两种参数呢。这是因为迭代器的种类提供了继承关系,他是一种is-a关系,所以当我们输入的迭代器类型为forward_iterator_tag时,他会继承自input_iterator_tag.所以不管我们传入迭代器的哪一种类型,他都会落在这5正确的迭代器中,从而调用该版本的函数。这也是为什么迭代器采用对象的原因。
这里写图片描述
同样再来看看copy这个函数。从下面我们看到copy函数无所不用其极的去调用去侦测,期望找到一条效率最高的路线。
这里写图片描述
其次算法对迭代器的分类是没什么强制限制的,他有的只是一个暗示。因为算法是一个模板函数,模板的意思是可以接受不同的类型。当让刚刚已经看到了针对迭代器的不同类型,它分成一些次函数去处理。但是在其主函数里头,并没有说我只接受什么样类型的迭代器。例如 sort函数,他需要对迭代器的直接跳要叫random_access,但是他不会在接口的地方强制说要random_access类型的迭代器。所以下面的sort函数,他可以接受任意类型,他唯一能做的就是个“暗示”。如果不是random_access,在前面编译可以通过,但是当真正要开始“跳”的时候,就会编译失败。如下所示:
这里写图片描述
接下来我们看看几个算法的示例。如下张幻灯片
qsort和find不是c++标准库的算法。他是c的function。C++标准库的算法必须符合算法的形式,其次他前面一定要带着两根指针,用这样的方式跟容器做沟通。
这里写图片描述
我们看一下一个简单的算法accumulate,他有两个版本。如下,第一个版本直接对初值进行累加,第二个版本则是可以允许我们加入一个额外的原则或者一个操作opertation。
这里写图片描述
接下来看一下for_each函数。他接受一个first,last,以及一个f,这个f是什么无所谓,只要能被小括号重载就好。
这里写图片描述
下面是replace函数
这里写图片描述
再看看count函数,幻灯片右侧列举了容器是不是本身带有这些函数,如果有则应该用容器本身自带的,这样效率更高,如果没有只能用全局的,也就是在std这个命名空间里头的。为什么是那个容器呢,因为它底部是由红黑树或者哈希表做支撑,相当于一个小型数据库。
这里写图片描述
这里写图片描述
我们再来看sort,我们发现个8个容器不带sort函数,因为他们已经是排序的了,所以不需要再进行排序。
这里写图片描述
再看看二分查找,在用二分查找之前必须先对元素进行排序。
这里写图片描述

3. functors(仿函数)

顾名思义仿函数就是一个像函数的东西,所以他必须重载“()”这个操作符,其次一般来说仿函数的内容一般很短。其次为什么将下面的一些操作设计成仿函数,因为他要为算法服务。算法拿到这些东西才能去做这些动作。
这里写图片描述
接下来我们看看gnuc所独有的一些仿函数,这些仿函数我们之前在使用容器的时候讲过。容器set当中用过identity而容器map中用到selectlst。
这里写图片描述
下面就是sort函数采用不同的标准来进行排序,第一个sort采用默认值,第二个采用自定义的仿函数,第三个采用函数对象,第四个最后一个参数less ()指的是一个临时对象。其次看看代码中的黄色部分,他继承了一个类binary_function,其实还有个类叫做unary_function,一般仿函数都必须继承上面两个中的其中一个,这样才能融入到STL的体系结构里面。这样才能被后面将要讲到的adapter所修饰。
这里写图片描述
我们来看看binary_function和unary_function的源代码,他很简单,没有数据也没有函数。如下:
这里写图片描述

4. Adapters(适配器)

Adapters只是一个换肤工程,他是一个小的工程。Adapters不像其他的部件一样,单独的出现在某一块,他出现在三个地方,如下图所示。Adapter作为a,需要使用b的功能,编程中有两种技巧,继承和复合。a继承了b,此时a就拥有了b,当然就可以取用b的资源,另外一种是a内涵了b。在我们接下来要讨论的adapter中,统统都是采用的内涵的方式而不是继承的方式。adapter主要也是用来与算法合作的。因此算法也可能想要问adapter一些问题。跟迭代器类似。也是用三个typedef来回答。迭代器5个typedef来回答。如下:
这里写图片描述
我们先来看看最简单的adapter
这里写图片描述
再接着看binder2nd绑定第二实参。就是把东西记起来以备后面使用。
这里写图片描述
函数适配器not1
这里写图片描述
不过在后面binder1nd,binder2nd均被新的东西取代了,如下所示。
这里写图片描述
我们来看看新型的适配器bind。在这里使用了占位符。第二个bind中的”_1”表示绑定第一参数。”_2”表示绑定第二参数
这里写图片描述

猜你喜欢

转载自blog.csdn.net/liushao123456789/article/details/80932196