前言
"打牢基础,万事不愁" .C++的基础语法的学习."学以致用,边学边用",编程是实践性很强的技术,在运用中理解,总结.
引入
STL(标准模板库)的学习.以<C++ Prime Plus> 6th Edition(以下称"本书")内容理解 .算法是STL中的一个重要内容.这部分内容比较重要,采用逐句解读来学习
算法概述
本书P713:STL包含很多处理容器的非成员函数,前面已经介绍过其中的一些:sort( )、copy( )、find( )、random_shuffle( )、set_union( )、 set_intersection( )、set_difference( )和transform( )。可能已经注意到,它们的总体设计是相同的,都使用迭代器来标识要处理的数据区间和结果的放置位置。有些函数还接受一个函数对象参数,并使用它来处理数据。(黑体部分是原话)
----解读:
1>STL算法的定义:处理容器的非成员函数.对于"算法",广义的概念很宽,比如处理数据的函数就可以被认为是算法.但STL中算法的概念有所区别
算法概念的分析:每个容器内都定义了迭代器类iterator,并让iterator继承同一个接口,方便使用.每个容器都有同名函数,如size(),可以被每个容器对象所调用.但他们不在"算法"的定义范围之内(虽然他们实际上也是算法).他强调的是处理容器的非成员函数.
2>总体设计相同.迭代器标识处理的数据区间---迭代器做参数,和结果的放置位置---也是迭代器做参数.还接受函数对象参数----使用处理容器元素的函数指针
/*伪代码*/
/*以迭代器对象做参数,函数对象做参数OneFun*/
ResultType no_member(InputIterator it1,OutputIterator ot1,OneFun of);
本书: 对于算法函数设计,有两个主要的通用部分。首先,它们都使用模板来提供泛型;其次,它们都使用迭代器来提供访问容器中数据的通用表示。因此,copy( )函数可用于将double值存储在数组中的容器、将 string值存储在链表中的容器,也可用于将用户定义的对象存储在树结构中(如set所使用的)的容器。因为指针是一种特殊的迭代器,因此诸如copy( )等STL函数可用于常规数组。(黑体部分是原话)
----解读:
1>使用模板提供泛型,算法为了处理容器内元素而产生,必然需要模板提供泛型(在函数对象中样,模板类除了提供泛型,还可以使用容器类元素类型的对象,指针等作为其属性).算法用的是模板函数
2>迭代器提供访问容器中的数据的通用表示.迭代器是对容器内元素的间接访问,操作容器元素是通过访问迭代器对象而进行
以上两点是对模板和迭代器概念的应用.---他们本来就是干这个的.
统一的容器设计使得不同类型的容器之间具有明显关系。例如,可以使用copy( )将常规数组中的值复制到vector对象中,将vector对象中的值复制到list对象中,将list对象中的值复制到set对象中。可以用==来比较不同类型的容器,如deque和vector。之所以能够这样做,是因为容器重载的==运算符使用迭代器来比较内容,因此如果deque对象和vector对象的内容相同,并且排列顺序也相同,则它们是相等的。
----解读:
用不同形状的篮子来比喻"容器":两个篮子一个圆一个方,共同点:都可以用来装物品.他们之间采用同样的方式(迭代器)访问数据.因此可以在两个篮子里传递同样的物品(模板保证相同类型)
算法组
本书:STL将算法库分成4组: 非修改式序列操作; 修改式序列操作; 排序和相关操作; 通用数字运算。 前3组在头文件algorithm(以前为algo.h)中描述,第4组是专用于数值数据的,有自己的头文件,称为numeric(以前它们也位于algol.h 中)。
非修改式序列操作对区间中的每个元素进行操作。这些操作不修改容器的内容。例如,find( )和for_each( )就属于这一类。
修改式序列操作也对区间中的每个元素进行操作。然而,顾名思义,它们可以修改容器的内容。可以修改值,也可以修改值的排列顺序。transform( )、random_shuffle( )和copy( )属于这一类。
排序和相关操作包括多个排序函数(包括sort( ))和其他各种函数,包括集合操作。
数字操作包括将区间的内容累积、计算两个容器的内部乘积、计算小计、计算相邻对象差的函数。通常,这些都是数组的操作特性,因此vector是最有可能使用这些操作的容器。
----解读:算法分了4个组,了解
算法的通用特征
本书: 正如您多次看到的,STL函数使用迭代器和迭代器区间。从函数原型可知有关迭代器的假设。例如,copy( )函数的原型如下
//书上代码
template<class InputIterator,class OutputIterator>
OutputIterator copy(InputIterator first,InputIterator last,OutputIterator result):
因为标识符InputIterator和OutputIterator都是模板参数,所以它们就像T和U一样。然而,STL文档使用模板参数名称来表示参数模型的概念。因此上述声明告诉我们,区间参数必须是输入迭代器或更高级别的迭代器,而指示结果存储位置的迭代器必须是输出迭代器或更高级别的迭代器。
----解读:copy()是函数原型,非伪代码(此前输出迭代器,输入迭代器只是个概念存在于书上.这里作为模板参数InputIterator和OutputIterator出现了).以此可以做出一些推导.
===================================内容分割线==============================
以下内容为推导内容,不保证完全准确
输入迭代器InputIterator的定义:可以向前移动指针,可以访问数据,不能改变数据,代码如下
class InputIterator{
public:
virtual void operator++();
virtual void operator*() const; //能取值不能修改
}
输出迭代器OutputIterator
class OutputIterator{
public:
virtual void operator++();
virtual void operator*();
}
随机迭代器RandomIterator拥有输入迭代器功能,所以他们有以下关系
class RandomIterator:public OutputIterator{
public:
void operator[](int num); //随机读取功能
etc; //其他
}
中间有双向迭代器,所以还可能有class Two_wayIterator继承OutputIterator的关系存在
容器(动态数组)vector拥有随机迭代器,所以在vector的定义大概是这样:
template<class T>
class vector{
class iterator:public RandomIterator{ //继承RandomIterator
迭代器内容;
}
}
迭代器的实现,是用继承来实现阶梯式需求的一个典型例子
===================================内容分割线==============================
本书:对算法进行分类的方式之一是按结果放置的位置进行分类。有些算法就地完成工作,有些则创建拷贝。例如,在sort( )函数完成时,结果被存放在原始数据的位置上,因此,sort( )是就地算法(in-place algorithm);而copy( )函数将结果发送到另一个位置,所以它是复制算法(copying algorithm)。transform( )函数可以以这两种方式完成工作。 与copy( )相似,它使用输出迭代器指示结果的存储位置;与copy( )不同的是,transform( )允许输出迭代器指向输入区间,因此它可以用计算结果覆盖原来的值。
----解读:根据结果位置,算法分为就地算法和复制算法两种.transform()同属于就地算法和复制算法.
本书:有些算法有两个版本:就地版本和复制版本。STL的约定是,复制版本的名称将以_copy结尾。复制版本将接受一个额外的输出迭代器参数,该参数指定结果的放置位置。例如,函数replace( )的原型如下:
//本书代码
template<class ForwardIterator,class T>
void replace(ForwardIterator first,ForwardIterator last,const T& old_value,const T& new_value);
它将所有的old_value替换为new_value,这是就地发生的。由于这种算法同时读写容器元素,因此迭代器类型必须是ForwardIterator或更高级别的。
复制版本的原型如下:
//本书代码
template<class InputIterator,class OutputIterator,class T>
OutputIterator replace_copy(InputIterator first,InputIterator last,
OutputIterator result,
const T& old_value,const T& new_value);
在这里,结果被复制到result指定的新位置,因此对于指定区间而言,只读输入迭代器足够了。
注意,replace_copy( )的返回类型为OutputIterator。对于复制算法,统一的约定是:返回一个迭代器,该迭代器指向复制的最后一个值后面的一个位置。
----解读: 就地算法和复制算法,作者写过一篇代码集:如何编写返回值受限的函数-CSDN博客,内容相似,可以参考.就地算法相当于形参用指针或引用,传入地址或引用修改原值;复制算法不能修改原值,返回值通道使用了形参通道.本书中的replace_copy函数,返回值通道就用到了形参中的result接收返回值,而返回值的类型是迭代器OutputIterator,指向复制的最后一个值后面的一个位置,这是复制算法的一种约定,上面红色字部分.
replace的就地版本和复制版本来实现了两种算法:就地算法和复制算法
本书:另一个常见的变体是:有些函数有这样的版本,即根据将函数应用于容器元素得到的结果来执行操作。这些版本的名称通常以_if结尾。例如,如果将函数用于旧值时,返回的值为true,则replace_if( )将把旧值替换为新的值。下面是该函数的原型:
//本书代码
template<class ForwardIterator,class Predicate class T>
void replace_if(ForwardIterator first,ForwardIterator last,
Predicate pred,const T& new_value);
如前所述,谓词是返回bool值的一元函数。还有一个replace_copy_if( )版本,您不难知道其作用和原型。
----解读:Predicate,谓词类型.函数符的类型.这里有两个问题:一是书上没有传个谓词函数符的参数进去看效果.二是没有源码,不太好去猜.copy版本的replace_if,形参多一个OutputIterator迭代器,用于接收返回值结果.
本书:与InputIterator一样,Predicate也是模板参数名称,可以为T或U。然 而,STL选择用Predicate来提醒用户,实参应模拟Predicate概念。同样,STL使用诸如Generator和BinaryPredicate等术语来指示必须模拟其他函数对象概念的参数。请记住,虽然文档可指出迭代器或函数符需求,但编译器不会对此进行检查。如果您使用了错误的迭代器,则编译器试图实例化模板时,将显示大量的错误消息。
小结
算法和函数符一样,是为了处理容器数据而产生.
容器里有些成员函数,是属于所有容器共同的约定,由每个容器独自完成的.例如每个容器都有名叫size()的函数,获得当前容器内元素个数.算法是非成员函数,在容器外完成的对容器元素的访问和修改等操作.
算法核心是使用模板函数表达泛型,迭代器间接访问容器元素.
算法的通用特征是对算法函数的格式进行了一定的"约束",比如就地算法和复制算法.复制算法以"_copy"结尾,他的形参比就地算法多一个,用于返回值的迭代器;
用"_if"结果的,有一个谓词形参