《c++ primer》学习笔记——泛型算法

generic algorithm

一,      概述

1,标准库没有为每种容器类型都定义是吸纳某些特定操作的成员函数,而是定义了一组泛型算法;自定义的容器类型只要与标准库兼容,也可以使用这些算法。

2,每个泛型算法的实现独立于容器,且不依赖于容器存储的元素类型;

3,算法往往需要通过两个迭代器遍历一段元素来实现其功能;

a)        迭代器支持自增操作符,相等和不等操作符;

b)        第二个迭代器为超出末端迭代器,第一个迭代器必须能够通过自增到达第二个迭代器;

4,泛型算法本身不执行容器操作:

a)        基于迭代器及其操作实现,不急于容器的操作;

b)        即算法从不修改基础容器的大小,即可能会改变存储在容器中的值,可能会移动元素,但是不会直接添加或者删除元素。

c)        如果需要实现更多的操作,可以使用插入器(也是一种迭代器),但是算法本身不这么做;

5,泛型算法需包含<algorithm>头文件,泛化的算术算法必须包含<numeric>头文件。

二,      初窥算法

1,只读算法:例如findaccumulatefind_first_of等;

int sum = accumulate(vec.begin(), vec.end, 42);        //第三个参数为起累加始值

a)        accumulate算法对容器一无所知:

                        i.              必须传递一个起始值告知起始值类型;

                      ii.              容器内的元素类型必须和起始值类型匹配,或者可以转换;

b)        标记迭代器范围的两个实参类型必须精确匹配:

                        i.              第一个迭代器必须能够通过不断自增到达第二个迭代器;

                      ii.              如果算法带有两队迭代器参数,则每对迭代器中的实参类型必须精确匹配,但不要求两对之间的类型匹配,即元素可以存储在不同类型的序列中,只要两个元素的序列可以比较即可。

 

2,写容器元素的算法:小心!必须确保算法所写的序列至少足以存储要写入的元素。

a)        三种类型的写入算法:

                        i.              直接将数据写入输入序列;

                      ii.              带有额外的迭代器参数指定写入目标;

                    iii.              将指定数目的元素写入某序列:这种算法不检查目标的大小是否足以存储要写入的元素,容易造成灾难性后果!

b)        确保算法有足够的元素存储输出数据的一种方法是使用插入迭代器

                        i.              插入迭代器是可以给基础容器添加元素的迭代器;

                      ii.              需要包含<iterator>头文件;

                    iii.              是一种迭代器适配器,使用一个对象作为实参,并生成一个适应其实参行为的新对象;

                     iv.              back_inserter生成一个绑定在该容器上的插入迭代器,在试图通过该迭代器给元素赋值时,赋值运算符将调用push_back添加一个具有指定值的元素。

c)        写入到目标迭代器的算法:

                        i.              该算法向目标迭代器写入未知个数的元素,如copy算法:

vector<int>  ivec;

copy( ilst.begin(), ilst.end(), back_inserter(ivec));

d)        各个算法的_copy版本:有的操作在对序列做操作后如果不想改变原序列,就会定义一个复制版本,将新生成的序列存储到目标序列中;如:replacereplace_copy

replace( ilst.begin(), ilst.end(), 0, 42);

replace_copy( ilst.begin(), ilst.end(), back_inserter(ivec), 0, 42 );

 

3,对容器元素重新排序的算法

a)        通过一个例子说明:统计一段话中使用了多少个由6个或以上字母组成的单词,同一单词不重复计数。实现步骤如下:

                        i.              去掉所有重复的单词;

                      ii.              按单词的长度排序;

                    iii.              统计长度等于或者超过6个字符的单词数;

                     iv.              程序如下:

bool isShorter(const string & s1, const string & s2 ){

       return  s1.size() < s2.size();                                       //长度比较函数

}

bool GT6 (const string & s ){ return  s.size() >= 6; }       //判断长度函数

int main(){                                                                          //主函数

       vector<string> words;

       string next_word;

       while (cin >> next_word){ word.push_back(next_word); }

       //对数组排序

       sort ( words.begin(), words.end() );

       //删除重复的元素

       vector<string>::iterator end_unique =

unique(words.begin(), words.end() );

                     words.erase(end_unique, words.end() );

                     //

                     stable_sort( words.begin(), words.end(), isShorter ); //需要一个谓词

                     vector<string>::size_type wc =

                            count_if (words.begin(), words.end(), GT6 );      //需要一个谓词

                     cout<< … …

                     return 0;

}

b)        unique的使用

                        i.              “删除”相邻的重复元素,重新排列输入范围内的元素,返回一个迭代器,表示无重复的值范围的结束;

                      ii.              但此时words的大小并没有改变!只是顺序变了,除了被新排序的元素覆盖的元素外,剩下的元素还在;算法不能删除元素!

                    iii.              要用算法返回的迭代器配合容器操作erase来删除多余的元素;

c)        使用容器操作删除元素

d)        定义需要的谓词

                        i.              一些算法的某个重载版本使用时可能需要一个配套的实用函数,即谓词,如count_ifstable_sort

                      ii.              传递给算法的谓词的性参数目是有要求的,如count_if的谓词只能接受1个形参,而stable_sort的谓词则需要接受2个形参;

 

三,      再谈迭代器

1,包含<iterator>头文件!

2,  插入迭代器

a)        插入器是一种迭代器适配器,带有一个容器参数,并生成一个迭代器,用于在指定容器中插入元素;

b)        back_inserter使用push_back,只有一个形参;

vector<int>  ivec;

copy( ilst.begin(), ilst.end(), back_inserter(ivec));

      

c)        front_inserter使用push_front,只有一个形参;

                        i.              只有当容器提供push_frontpush_back操作时,才能使用上述迭代器适配器,在vector上就不能使用push_f

 

d)        inserter将产生在指定位置时先插入的迭代器

                        i.              有两个实参:所关联的容器和只是其实插入位置的迭代器;

                      ii.              总是在它的迭代器实参所标明的位置前面插入新元素;

                    iii.              front_inserter的区别front_inserter始终在首元素之前插入,而即使给inserter传递了ivec.begin()作为迭代器实参,也不能实现front_inserter的操作,因为在插入第一个元素后,它就不再是首元素了;

即用front_inserter插入将导致元素反向排列,而用inserter则不会;

 

3,  iostream迭代器

iostream迭代器的构造函数

istream_iterator<T> in(strm);

创建从输入流strm中读取T类型对象的istream_iterator对象

istream_iterator<T> in;

istream_iterator的超出末端迭代器

ostream_iterator<T> in(strm);

创建将T类型的对象写到输出流strmostream_iterator对象

ostream_iterator<T> in(strm, delim);

同上,在写入过程中将delim作为元素的间隔符。delim是以空字符结束的字符数组

a)        流迭代器的定义和创建

                        i.              流迭代器都是类模板:任何已定义>>操作符操作符的类型都可以定义istream_iterator,任何已定义<<操作符的类型都可以定义ostream_iterator

                      ii.              创建流迭代器时,必须指定迭代器所读写的对象类型;

                    iii.              创建ostream_iterator必须和特定的流绑定在一起;ostream_iterator不提供超出末端迭代器。

                     iv.              创建istream_iterator时,可以直接绑定到一个流上,也可以创建时不提供实参,则迭代器指向超出末端位置。

 

istream_iterator的操作

it1 == it2

比较是否相等,迭代器读取的必须是相同的类型。

it1 != it2

如果两个迭代器都是end值,则相等;

对于两个都不指向end的迭代器,如果他们使用同一个输入流构造,则他们也相等;

*it

返回流中读取的值

it->mem

返回从流中读取的对象的mem成员

++it

通过使用元素类型提供的>>操作符从输入流中读取下一个元素值,使迭代器向前移动;

前缀版本返回移动后对迭代器的引用,后缀版本返回原值。

it++

 

b)        istream_iterator对象上的操作

                        i.              用法1

istream_iterator<int> in_iter(cin);

istream_iterator<int> eof;

while ( in_iter != eof) vec.push_back(*in_iter++);

eof迭代器定义为空的istream_iterator对象,用作结束迭代器。绑在流上的迭代器遇到文件结束或者某个错误时,将等于结束迭代器的值;

                      ii.              用法2

istream_iterator<int> in_iter(cin);

istream_iterator<int> eof;

vector<int> vec( in_iter, eof );

用一对标记元素范围的迭代器构造vec对象。效果是读cin,知道到达文件结束或者输入的不是int型数值为止,读的元素用于构造vec对象。

 

c)        ostream_iterator对象和ostream_iterator对象的使用

                        i.              用法1

ostream_iterator<string> out_iter(cout, “/n”);

istream_iterator<string> in_iter(cin), eof;

while ( in_iter != eof ) *out_iter++ = *in_iter++;

作用:读cin,并将读入的值写到cout中不同的行中;

 

d)        再累类型上使用istream_iterator

                        i.              提供了>>操作符的任何类型都可以创建istream_iterator对象。

                      ii.              例子如下:

istream_iterator<Sales_item> item_iter(cin), eof;

Sales_item sum;

sum = *item_iter++;

while (item_iter) {

       if (item_iter->same_isbn(sum)) sum = sum + *item_iter;

       else { cout << sum << endl;  sum = *item_iter; }

       ++item_iter;

}

cout << sum << endl;

 

e)        迭代器的限制:

                        i.              不能从ostream_iterator中读入,也不能写到istream_iterator对象中;

                      ii.              一段给ostream_iterator赋值,就不能修改了,每个不同值都只能正好输出一次;

                    iii.              ostream_iterator没有->操作符。

 

f)         与算法一起使用流迭代器

                        i.              例子1:从输入读取一些数,再不重复的输出到标准输出

istream_iterator<int> cin_it(cin);

istream_iterator<int> end_of_stream;

vector<int> vec( cin_it, end_of_stream );

sort ( vec.begin(), vec.end() );

ostream_iterator<int> output ( cout, “ “ );

unique_copy ( vec.begin(), vec.end(), output );

 

4,  反向迭代器reverse_iterator

a)        反向迭代器是一种反向遍历容器的迭代器:

                        i.              ++运算符将访问前一个元素,反之亦然;

                      ii.              容器定了beginend成员,也定义了rbeginrend成员,后者就是反向迭代器;

b)        例子1:逆序输出元素:

vector<int>::reverse_iterator r_iter;

for (r_iter = vec.rbegin();  r_iter != vec.rend();  ++r_iter )

       cout << *r_iter << endl;

c)        例子2:将序排列vector,只需向sort传递一对反向迭代器:

sort( vec.rbegin(), vec.rend() );

 

d)        反向迭代器需要使用自减操作符,所以流迭代器不能创建反向迭代器,因为流不能被反向遍历;

 

e)        反向迭代器与其他迭代器之间的关系:

如图显示的对象直观的结束了普通迭代器与反向迭代器之间的关系:

                        i.              设计普通迭代器与反向迭代器之间的关系是为了适应左闭合范围,以使[ line.rbegin(), rcomma )( rcomma.base(), line.end() ]标记的是line中的相同元素。

                      ii.              程序如下(假设有一个名为linestring对象,存储以逗号分隔的单词列表,我们希望输出line中的第一个单词):

string::iterator comma = find( line.begin(), line.end(), ‘,’ );

cout << string(line.begin(), comma) << endl;

//输出:FIRST

string::reverse_iterator  rcomma = find( line.rbegin(), line.rend(), ‘,’ );

cout << string( line.rbegin(), rcomma ) << endl;

//输出:TSAL  这是错误的!原因从上图可以看出,正确的方法如下:

cout << string( rcomma.base(), line.end() ) << endl;

 

5,  const迭代器

a)        如果我们不希望使用迭代器来修改容器中的元素,那么可以定义为const_iterator类型;

b)        但是,用于算法中表示范围的两个迭代器必须具有完全一样的类型,所以,如果另一个迭代器不是const类型,那么即使不希望改变元素,也不能定义成const类型;

c)        类似lst.begin()lis.end()返回的迭代器的类型依赖于所指向的容器存放的元素的类型,存放的是const对象,则返回const_iterator类型的迭代器,反之亦然。

 

6,  五种迭代器

迭代器种类(按算法要求的迭代器操作分类)

输入迭代器

读,不能写;只支持自增运算

输出迭代器

写,不能读;只支持自增运算

前向迭代器

读和写;只支持自增运算

双向迭代器

读和写;支持自增和自减运算

随机访问迭代器

读和写;支持完整的迭代器算术运算

a)        输入迭代器

                        i.              可读,不保证能写;

                      ii.              支持==!=操作符,自增操作符(++),解引用(*)操作符,箭头操作符(->)(对迭代器进行解应用来获取所关联的对象的成员)。

                    iii.              一旦自增了,就无法检查之前的元素;

                     iv.              istream_iterator即为此类型,findaccumulate算法;

 

b)       输出迭代器

                        i.              可写,不保证可读;

                      ii.              支持++操作符;

                    iii.              支持解引用(*),此操作符只能出现在赋值运算的左操作数上,用来对该迭代器所指向的元素做写入操作;

                     iv.              可以要求每个迭代器的值必须正好写入一次;??

                       v.              输出迭代器一般用作算法的第三个实参,用于标注开始写入的位置;

                     vi.              copy算法;ostream_iterator为输出迭代器。

 

c)        前向迭代器

                        i.              只能以一个方向遍历序列;

                      ii.              支持输入迭代器和输出迭代器的所有操作,支持对同一个元素的多次读写;

                    iii.              需要复制向前迭代器来记录序列中的一个位置,以便返回此处;

                     iv.              replace算法。

 

d)        双向迭代器

                        i.              从两个方向读写容器;

                      ii.              支持前向迭代器的所有操作,还有前置和后置的--操作;

                    iii.              所有标准库容器提供的迭代器都至少达到双向迭代器的要求;

                     iv.              reverse算法;

                       v.              mapsetlist提供双向迭代器。

 

e)        随机访问迭代器

                        i.              访问容器任意位置;

                      ii.              支持关系操作符,与整数值之间的算术运算操作符,两个迭代器之间的减法操作符(返回距离),下标操作符(iter[n]相当于*(iter+n);

                    iii.              vectordequestring和内置数组元素的指针都是随机访问迭代器;

                     iv.              sort算法。

 

f)         除了输出迭代器:在需要低级类别迭代器的地方,可以使用任意一种更高级的迭代器。

注:向算法传递无效的迭代器类别所引起的错误,无法保证会在编译时被捕捉到!

 

四,      泛型算法的结构

1,算法的形参模式:算法最基本的性质是需要使用的迭代器总类。

a)        alg( beg, end, other parms );

b)        alg( beg, end, dest, other parms );

                        i.              算法假定无论需要写入多少元素都是安全的;

                      ii.              调用算法是,必须确保输出容器有足够大的容量存储输出数据,这正是通常要使用插入迭代器或者ostream_iterator来调用这些算法的原因。

c)        alg( beg, end, beg2, otherparms );

                        i.              算法假定以beg2开始的范围至少与begend指定的范围一样大。

d)        alg( beg, end, beg2, end2, otherparms );

2,算法的命名规范:算法使用一组相同的命名和重载函数的规范。

a)        两种基本模式:测试输入范围内元素的算法,对输入范围内元素重新排序的算法。

b)        测试输入的元素的算法:区别带有一个值或一个为此函数参数的算法版本

                        i.              通常要用到标准关系操作符:==<;

                      ii.              其中大部分算法提供第二个版本,允许程序员提供谓词取代操作符;

                    iii.              如果算法的两种版本性参数不同,则通过重载函数实现第二个版本:

sort (beg, end );                //

sort ( beg, end, comp );    //通过重载实现,comp为谓词

                     iv.              如果算法的两种版本形参数相同,则通过增加名字后缀_if来区别:

find ( beg, end, val );

find_if ( beg, end, pred );  //通过名字后缀来区分,pred为谓词

 

c)        对输入元素重新排序的算法:区别是否实现复制的算法版本

                        i.              无论算法是否检查它的元素值,都可能重新排列输入范围内的元素;

                      ii.              默认情况下,算法将重新排列后的元素写回其输入的范围;

                    iii.              如果要将排列后的元素写到输出目标,则用名字后缀_copy加以区分:

reverse ( beg, end );

reverse_copy ( beg, end, dest);

 

五,      容器特有的算法

list容器的特有操作

lst.merge (lst2)

lst2的元素合并到lst中;

两个list对象都必须排序;

合并后来lst2为空,返回void类型。

lst.merge (lst2, comp)

lst.remove ( val )

调用lst.erase删除所有等于指定值或使指定的为此函数返回非零值的元素;

返回void类型。

lst.remove_if ( unaryPred )

lst.reverse ()

反向排列

lst.sort ()

排序

lst.splice ( iter, lst2 )

lst2中的元素一道lstiter指向的元素前,并删除lst2中移除的元素;

第一个版本:移动所有元素,lst2lst不能是同一个list对象;

第二个版本:移动iter2指向的元素,必须是lst2中的元素,lst2lst可以是同一个list对象;

第三个版本:移动范围内的元素,必须是一个有效的范围,可以是lst中的元素,但iter指向的元素不能再该范围内;

lst.splice ( iter, lst2, iter2 )

lst.splice ( iter, beg, end )

lst.unique ()

调用erase删除同一个值的连续副本

lst.unique ( binaryPred )

 

你可能感兴趣的:(C++,算法,String,iterator,存储,iostream)