Essential C++中文版(Chap1--Chap3)

    刚接触C++,有了一定C语言的基础,为了做C++的界面开发,只好硬着头皮学习C++,了解一下也并非坏处~网上好些人在谈论E-C++和清华老谭的书谁适合入门,其实我觉着都挺好的,最好能够放在一起看,章节安排差不多的,老谭的书例子更加完整,E-C++更侧重于循序渐进,告诉读者C++语言如何去完成一件事,如实现泛型,实现Iterator class等。

    这份笔记差不多就是E-C++全书了,网上只有扫描版本的,我用OCR软件识别更改了一下,边更改边学习,效果不错(虽然有点浪费时间。。。)而且我觉着放在博客上以后看起来会方便许多,方便自己方便大家吧~~~~ ---- C++菜鸟一个

Chap-1: C++编程基础

    类(class)是用户自定的数据类型,class的定义一般来说分为两部分:头文件和源代码,头文件用来声明class锁提供的各种操作行为,代码则包含这些操作行为的实现内容。

Tips:使用类必须要在程序中包含头文件,如“输入/输出 程序库”名为iostream,其中包含了相关的整套classes,用于支持对终端机和文件的输入与输出。如cout(see out)对象,和cin(终端的输入)对象,output运算符<<可以将数据导入cout,而>>可以将数据导入到对象。string class包含在头文件中,用法如:string user_name,cin >> user_name

/* Chap-1-Code-1:Hello-Bye.cpp */
#include 
#include 
using namespace std;

int main()
{
        string user_name;

        cout << "Please enter your name:";
        cin >> user_name;
        cout << '\n'
             << "Hello, "
             << user_name
             << "...and goodbye!\n";

        return 0;
}
/* Linux下编译:g++ -Wall Hello-Bye.cpp Hello-Bye */

    所谓命名空间(namespace)是一种将程序库名称封装起来的方法.通过这种方法,可以避免和应用程序发生命名冲突的问题(所谓命名冲突是指在应用程序内两个不同的实体具有相同名称,导致程序无法区分两者.命名冲突发生时,程序必须等到该命名冲突获得决议之后,才得以继续执行。命名空间像是在众多名称的可见范围之间竖起的一道道围墙.若要在程序中使用string class以及cin,cout这两个iostream类对象,我们不仅得含人头文件,还得让命名空间std内的名称曝光.而所谓的using directive:usinging namespace std:便是让命名空间中的名称曝光的最简单方法.

Chap-2: 面向过程的编程风格

1.referrence:不同于pointer,无法被设为0,一定要代表某个对象。int &refer = referred :refer代表了referred

2.生存空间

3.内存管理:

new - new Type (initial_value)  int *pi = new int (1024);申请数组 int *pia = new int [array_size], pi为申请到的首地址

delete - delete pi; delete [] pia;  如果要删除数组中的所有对象,必须在数组指针和delete表达式之间,加上空的下表运算符[]

4.默认参数:关于默认参数值的提供,有两个不甚直观的规则,第一个规则是,默认值的决议操作由最右边开始进行.如果我们为某个参数提供了默认值,那么这个参数右侧的所有参数都必须也具有默认参数值才行,下面这样实属非法:
//错误:没有为vec提供默认值
void display( ostream &os = cout, const vector &vec);
第二个规则是,默认值只能够指定一次,可以在函数声明处,亦可以在函数定义处,但不能够在两个地方都指定.那么,我们应该在何处指定参数的默认值呢?
 通常,函数声明会被置于头文件,每个打算使用该函数的文件,都会含入对应的头文件。函数的定义通常被置于程序代码文件,此文件只被编译一次,当我们想要使用此函散时,会将它链接( link)到我们的程序中来.也就是说,头文件可为函数带来更高的可见度(visibility)。所以上述函数定义的声明为:

void display(const vector &, ostream &os = cout);

5.函数重载机制:

参见http://www.cnblogs.com/skynet/archive/2010/09/05/1818636.html

6.定义并使用Template Functions(模板函数)

7.函数指针

Chap-3: 泛型编程风格

    STL(Standard Template Library)主要由两种组件构成:一是容器(container),包括vector,list,set,map等类,另一种组件是用以操作这些容器类的所谓泛型算法(generic algorithm),包括find(),sort(),replace(),merge().等。
    vector和list这两种容器是所谓的序列式容器(sequential container).序列式容器会依次维护第一个元素、第二个元素……直到最后一个元素,我们在序列式容器身上主要进行所谓的迭代(iterate)操作.map和set这两种容器属于关联式容器(associative cornainer).关联式容器可以 让我们快速寻找容器中的元素值。
    所谓map乃是一对对的keylvalue组合。key用于搜寻,value用来表示我们要存储或取出的数据.例如,电话号码即可轻易以map表示,电话用户名称便是key,而value与电话号码产生关联。所谓set,其中仅含有key。我们对它进行查询操作,为的是要判断某值是否存在于其中.如果我们想要建立一组索引表,用来记录出现于新闻,故事中出现的宇,我们可能会希望将一些中性字眼,如the,an,but排除掉.在放行某个宇,让它进入索引表之前,我们先查询excludecLword这么一个set,如果这个字存在其中,我们便忽略它,不再计较;反之则将此宇加入索引表.
    所谓泛型算法,提供了许多可施行于容器类及数组型别上的操作行为.这些算法之所以被称为泛型(generic),因为它们和它们想要操作的元素型别无关。举个例子,它们和元素型别究竟是int、double或string全然无关。它们同样也和容器型别彼此独立(不论容器是vectar、list或array)。 泛型算法系通过function templaLe技术,达成“与操作对象之型别相互独立”的目的,而达成“与容器无关”的诀窍,就是不要直接在容器身上进行操作。取代方式是,藉由一对iterators(first:last),标示我们欲进行选代的元素区间。如果first等于last,算法便只作用于first所指元素,如果first不等于last,算法便会首先作用于first所指元素身上,并将first前进一个位置,然后再作用于当前的first所指元素身上,如此持续进行,直到first等于last为止.

3.1  指针的算术运算( The Arithmetic of Pointers)

    假设我们需要完成以下工作。给定一个存储整数的Vector,以及一个整数值。如果此值存在于vector内,我们必须返回一个指针指向该值;反之则返回0,表示此值并不在vedor内.以下便是我的做法:

int *find(const vector &vec, int value)
{
	for (int ix = 0; ix < vec.size(), ++ix)
	{
		if (vec[ix] == value)
		{
			return &vec[ix];
		}
	}
	
	return 0;
}

    测试过这个函数之后,其结果的确满足我们的需求.接下来我们又获得一个新任务:想办法让这个函数不仅可以处理整数,也可以处理任何型别——前提是该型别定义有equality(相等)运算符,这个任务其实就是要求我们将泛型算法find()以functiontemplate的形式呈现:

template 
elemType *find(const vector &vec, const elemType &value)
{
	for (int ix = 0; ix < vec.size(), ++ix)
	{
		if (vec[ix] == value)
		{
			return &vec[ix];
		}
	}
	
	return 0;			
}

    再次测试这个函数,其执行结果同样符合我们的需求。下一个任务,便是要让这个函数同时可以处理vector与array内的任意型别元素----当然该型别的equality运算符皆已定义.首先映人脑海的第一个想法便是将此函数重载(overload),一份用来处理vector,另一份用以处理array。
    对于本例我们并不建议使用重载方式来解决。稍经思考之后我们发现,只要写一份函数,就可以同时处理vector和array的元素。指向array启始处的指针,使我们得以开始对anay进行读取操作,接下来我们必须设法告诉应该在何处停止对arny的读取。解法之一是:增加一个参数,用来表示array的容量.以下便是采用此浃完成的find(),声明如下:

template 
elemType *find(const elemType *array, int size, const elemType &value);

解法之二则是传人另一个地址,指示array读取操作的终点。我们将此值称为“哨兵”.

template 
elemType *find(cosnt elemType *array, const elemType *sentinel, const elemType &value);

    上述函数完成了我们所设定的两个子任务中的第一个:我们已经完成tind(}的实现,而且不论数组元素的型别如何,我们都可以存取数组中的每个元素,接下来,我们该如何调用find呢?下列程序代码会用到先前所说的指针算术运算:

int ia[8] = {0,1,2,3,4,5,6,7};
doubel da[6] = {1.1,2.2,3.3,4.4,5.5,6.6};
string sa[4] = {"first", "second", "third", "fourth"};

int *pi = find(ia, ia+8, ia[3]);
double *pi = find(da, da+8, da[3]);
string *ps = find(sa, sa+4, sa[3]);

     我们传人第二个地址,标示出数组最后一个元素的下一个地址,这合法吗?是的,不过倘若我们企图对此地址进行读取或写人操作,那就不合法.如果我们仅仅只是将该地址拿来和其它元素的地址进行比较,那就完全不会有任何问题.数组最后一个元素的下一个地址,扮演着我们所悦的哨兵角色,用以指示我们的选代操作何时完成.
    应该如何达成第二个子任务呢?这个任务是说,不论vector的元素类型为何,皆能一一存取vector内的每个元素.要知道,vcctor和array相同,都是以一块连续内存存储其所有元素,所以我们可以使用和array一样的处理方式,将一组用来表示“启始地址,结束地址”的参数传人find.但是,切记:vcctor可以是空的,array则否,例如以下写法:   

vector svec;
find(&svec[0], &svec[svec.size()], search_value);

定义了一个空的、可用来存储string元素的vector.下述对find()的调用操作,如果svec为空,便会产生一个执行期错误: 比较保险的方法是,先确定svec不为空:通常我们会将“取用第一个元索的地址”的操作封装为函数,像这样:

template 
inline elemType *begin(const vector &vec)
{
	return vec.empty() ? 0 : &vec[0];	
}

    其对应函数end()会返回0或是vector最后元素的下一个地址,采用这种方式,我们便有了安全的,放诸四海皆准的方式:使findf)应用于任意vector之上:

find(begin(svec), end(svec), search_value);

    这同时也解决了我们最原始的任务,我们已实现出find,只需这一份find便可同时处理vector或array.烃过测试之后,证明这个find运行无误,现在让我们延伸find的功能,令它也能支持标准程序库所提供的list类,说实在的,这又是一个难题.
    list也是一个容器,不同的是,list的元素以一组指针相互链接(linked):前向(forward)指针用来寻址下一个元素,回向(backward)指针用来寻址上一个元素.因此,指针的算术运算并不适用于list,因为指针的算术运算必须首先假设所有元素都存储在连续空间里,然后才能根据当前的指针,加上元素大小之后,指向下一个元素.这是先前find的实现之中最基本的假设,不幸的是,当我们想要取得list的下一个元素时,这个假设并不成立,首先浮起的念头,便是再多写一份find()函数,使其有能力处理list对象.于是我们宣布.
array,vcctor,list的指针行为大相径庭,以至于无法以一个共通的语法来取得其下一元素.
  这样的说法对错相杂.对的部分是,它们的底层指针运行方式,就标准语法而言的确是大不相同.错的的部分则是,我们不需要提供另一个find()函数来支持list.事实上,除了参数表之外,find的实现内容一点也不需要改变,
    解决这个问题的办法是,在底层指针的行为之上提供一层抽象化机制,取代程序原本的“指针直接操作”方式.我们把底层指针的处理通通置于此抽象层中,让用户不需直接面对指针的操作,这个技巧使得我们只需提供一份find函数,便可以处理标准程序库所提供的所有容器类. 

3.2.了解泛型指针iterators

    很显然,我们一定会问,这一层抽象化机制如何实现呢?是的,我们需要一组对象,可提供有如内建运算符(++,*,==,!=) 一般的运算符,并允许我们只为这些运算符提供一份实现码即可.我们可以利用c++的类机制来完成目的。接下来我要设计一组iterator classes,让我们得以使用“和指针相同的语法”来进行程序的撰写。举个例子,如果first和last都是list的iterators,我们可以这样来写:

while(first != last)  //first和last皆为iterator class objects
{
    cout << *first << ' ';
    ++first;
}

    这就好像把first和last当做指针一样,唯一的差别在于其dereference(提领*)运算符,inequality(不等!=)运算符、increment(递增.++)运算符乃是由iierator classes内相关的inline函数提供,对list iterator而言,其递增函数会沿着list的指针前进到下一个元素,对vector iierator而言,其递增函数前进至下一个元素的方武,是将当前的地址加上一个元素的大小,第四章中我们会检视如何实现iterator classes,包括如何为特定的运算符提供实现内容,本节我们先看看如何定义并使用标准容器的iterators。
    如何取得iterators呢?每个标准容器都提供有一个名为begin的操作函数,可返回一个iterator,指向第一个元素.另一个名为end的操作函数会返回一个iterator,指向最后一个元素的下一位置,因此,不论此刻如何定义iteraLor对象,以下都是对iterator进行赋值(assign)、比较(compare)、递增(increment)、提领(dereference)操作:

for (iter = svec.begin(); iter != svec.end(); ++iter)
{
    cout << *iter << ' ';
}

    vector svec;   

    以下是标准程序库中的iterator语法
    //iter指向一个vector,后者的元素型别为string
    //iter 一开始指向svec的第一个元素
    vactor::iterator iter = svec.begin()
    此处iter被定义为一个iterator,指向一个vector,后者的元素型别为string.其初值指向svec的第一个元素,式中的双冒号::表示此iterator乃是位于string iterator定义式内的嵌套(nested)型别,如果你读了第四章,并实现过自己的iterator class,当更有体会,目前我们还只停
留在iterator的运用而已,至于面对const vccton
    const vector cs_vec;

    我们使用const_iterator来进行遍历操作:
    vector:: const_iterator iter  = cs_vec. begin(),
    const- iterator免许我们读取vector的元素,但不允许任何写入操作.
    现在我要重新设计find,让它同时支持两种形式:一对指针,或是一对指向某种容器的iterators:

template 
IteratorType
find(IteratorType first, IteratorType last, const elemType &value)
{
	for(; first != last; ++first)
	{
		if (value == *firs)
		{
			return first;	
		}	
	}	
	
	return last;
}	

    使用翻修过的find来处理array、vector和list:

const int asize = 8;
int ia[asize] = {0,1,2,3,4,5,6,7};

//以ia的8个元素作为list和vector的初值
vector ivec(ia, ia+asize);
list ilist(ia, ia+asize);

int *pia = find(ia, ia+asize, 1024);

vector::iterator it;
if = find(ivec.begin(),ivec.end(),1024);
	
list::iterator iter;
iter = find(ilist.begin(),ilist.end(),1024);

    find的实现内容使用了底部元素所属型别的equality(相等)运算符.如果底部元素所属型别井未提供这个运算符,或如果用户希望赋予equality运算符不同的意义,这个findl)的弹性便嫌不足了.如何才能增加其弹性?解法之一便是传人一个函数指针,取代原本固定使用的equality运算符.第二种解法则是运用所谓的function object(这是一种特殊的class).我们会在第四章看到如何设计function objects.

3.所有容器的共通操作

下列为所有容器类(以及string类)的共通操作:
●equality(==)和inequality(I_)运算符,返回true或false
●assignment(=)运算符,将某个容器复制给另一个容器。
●empty()会在容器无任何元素时返回trua,否则返回false。
●size()传用容器内当前言有的元素数目。
●clear()删除所有元素.

 每个容器都提供begin和end()两个函数,分别返回iterator,指向容器的第一个元素和最后一个元素的下一个位置:
●begin返回一个iterator.指向容器的第一个元素。
●end()返回一个iterator,指向容器的最后一个元素的下一个位置.
 通常我们在容器身上进行的迭代操作都是始于begin()而终于end.所有容器都提供insert用以安插元素,以及提供erase用以删除元素。
●insert将单一或某个范围内的元素安插到容器内.
●erase将容器内的单一元素或某个范围内的元素删除.

4.使用序列式容器(Sequential Containers)

    序列式容器用来维护一组排列有序、型别相同的元素,其中有第一、第二……依此类推,乃至最后一个元素.vector和list是两个最主要的序列式容器。vector以一块连续内存来存放元素.对vector进行随机存取(random access)——例如先取其第5个元素,再取其第17个元素,然后再取其第9个元素——颇有效率;vecior内的每个元素都被存储在自己的某个固定位置上.如果将元素安插至任意位置,而此位置不是vector的尾端,效率就很低,因为安插位置右端的每个元素都必须被复制一份,依次向右位移。同样,删除vector内最后一个元素以外的任意元素同样缺乏效率.   

    list系以双向链接(double-linked,而非连续内存)来存储内容,因此可以执行前进或后退操作。list中的每个元素都包含3个字段:value,back指针(指向前一个元素)、front指针(指向下一个元素).在list的任意位置进行元素的安插或删除操作,都颇具效率,因为list本身只需适当设定backI指针和front指针即可.但是如果要对list进行随机存取操作,效率不彰.例如要依次存取其第5个元素,第17个元素,第9个元素,都必须遍历介于其中的所有元素。
    第三种序列式容器是趼谓的deque(读作deck).deque的行为和vector颇为相似——都以连续内存存储元素.和vector不同的是,deque对于最前端元素的安插和删除操作效率更高:末端元素亦同.如果我们需要在容器最前端安插元素,并执行末端删除操作,那么deque便很理想.标准程序库的queue便是以deque实现完成的,也就是说,以deque作为底部,存储元素.
    要使用序列式容器,首先必须舍^相关的头文件,也就是以下三者之一:
    #include
    #include
    #include
定义序列式容器对象的方式有5种:
1_产生空的容器:
    list< string>slist,
    vector< int>ivec,
2.产生特定大小的容器。每个元素都以其默认值作为初值(还记得吗,int和double这类语言内建的算术型别,其默认值为0)。
    list  ilist(1024):
    vector< string>svec(  32);
3.产生特定大小的容器,并为每个元素指定初值:
    vector.c int>  ivec( 10, -1);
    list< string)slist(  16, "unassigned" );
4. 通过一对iterators产生容器。这对iterators用来标示一整组作为初值的元素区间:
    int ia[8] = {0,1,2,3,4,5,6,7};    vector< int> fib(ia,ia+8);
5.根据某个容器产生出新容器。复制原容器内的元素,作为新容器的初值。
    list slist;

    list slist2(slist)

5.使用泛型算法

    欲使用泛型算法,首先得含人对应的algorithm头文件:
    #include
    让我们以vector来存储数列,以此练习泛型算法的运用,如果给定的值已存在于数列之中,is_elems必须返回true:否则返回flase。下面为4种可能被我们采用的泛型搜寻算法:
1.find()用于搜寻无序(unordered)集合中是否存在某值。搜寻范围由itcrators(first.last)标出.如果找到目标,find()会返回一个iteratar指向该值,否则返回一个iterator指向last.
2.binary_search()用于已序(sorted)集合的搜寻。如果搜寻到目标,就返回true:否则返回false.binary_search比find()更有效率。  (译注:因为find()属于iinear saarch,效率较binary search差)
3.count返回数值相符的元素数目。
4.search()比较某个容器内是否存在某个子序列。例如给定数序{3,5,7,2},如果搜寻子序列 {5,7,2},则search会返回一个iterator,指向子序列起始处。如果子序列不存在,就返回一个iterator指向容器末尾。
   由于我们的vector必定以递增顺序存储其值,因此,binary_search是我们的最佳选择;  调用binary_search()之前,必须确定数列中存有足够数量的元素,也就是说.elem必须在此数列之内。方法之一就是拿elem和数列最大元素做比较,如果elem比较大,我们就扩展数列,直到其最大元素值大于或等于elem. 有个方法可以帮助我们取得数列最大元素值——泛型算法max_element。将一对iterators传给max_element().它会返回该区间内的最大值。binary_search要求,其施行对象必须经过排序(sorted),这个责任由程序员承担。如果我们不确定是否已排序,可以将容器先复制一份,然后,先对新容器执行排序操作,再调用binary_search。

6.如何设计一个泛型算法   

  下面是我们的新任务.用户给予一个整数vector.我们必须返回一个新的vector,其中内含原vector之中小于10的所有数值.一个快速但缺乏弹性的解法是;

vector less_than_10(const vector &vec)
{
	vector nvec;
	for(int ix = 0; ix < vec.size(); ++ix)
	{
		if (vec[ix] < 10)
		{
			nvec.push_back(vec[ix]); //push_back在vector类中作用为在vector尾部加入一个数据	
		}	                         //在vector类中:void push_back(const _Ty&_X){insert(end(), _X); 
	}
	return nvec;
}

  如果用户想找到所有小于11的元素,我们就得建立一个新函数,要不就得将此函数通用化,让用户得以指定某个上限值,像这样:

vector less_than(const vector &vec, int less_than_val);

  下一个任务难度颇高。我们必须允许用户指定不同的比较操作,如大于、小于等等.如何才能将“比较操作”参数化呢?
  有一个解法:以函数调用来取代less-than运算符。加入第三个参数pred,用它来指定一个函数指针,其参数表有两个整数,返回值为bool。至此,less_than的名称己不再适当,让我们称它为filterl吧:

vector filter(const vector &vec,
		  int filter_value,
		  bool (*pred)(int, int));

  站在用户的角度来考虑,为方便起见,我们同时定义了许多可传给filter的关系(relational)比较函数:

bool less_than(int v1, int v2)
	{return v1 < v2 ? true : false;}

bool greater_than(int v1, int v2)
	{return v1 > v2 ? true : false;}

  调用filterl}时,用户亦可传入上述函数,或其它自行定义的关系比较函数,唯一一个限制就是,这些函数必须返回bool,而且参数表中只接受两个整数.以下是filter的使用方式:

vector big_vec;
int value;
vector lt_10 = filter(big_vec, value, less_than);

  接下来最后的工作,便是实现出filterf):

vector filter_ver1(const vector &vec,
                        int filter_value,
                        bool (*pred)(int, int))
{
	vector nvec;
	for (int ix = 0; ix < vec.size(); ++ix)
	{
		if (pred(vec[ix], filter_value))
			nvec.push_back(vec[ix]);	
	}	
	
	return nvec;
}

  这个filter()使用for循环走访每个元素。现在让我们以泛型算法find_if来取代for循环的运用。我将find_if反复施行于数列身上,找出符合条件的每一个元素——所谓“条件”则由用户指定的函数指针定义之。这要怎样才能做到呢?
  让我们从“找出每个等于10的元素”开始吧,泛型算法find接受3个参数:两个iterators标示出检测范围,第三个参数是我们想要寻找的数值。以下程序代码中,count_occurs()说明如何在“不对任一元素进行两次以上的柱视”前提下,反复地在容器身上施行find。

int count_occurs(const vector &vec, int val)
{
	vector::const_iterator iter = vec.begin();
	int occur_count = 0;
	while((iter = find(iter, vec.end(), val)) != vec.end())
	{
		++occur_count;
		++iter;
	}
	
	return occur_count;
}

  我们在while循环之内将find的返回值设给iter。findl返回一个iterator,指向元素值为val的元素.如果没有找到任何符合条件的元素,就返回一个等同于vec .end的iterator.一旦iter等同于vec.end(),循环即终止。
Functlon Objects
  在重新实现filter以便支持find_if()之前,让我们先看看标准程序库预先定义好的许多Function objects。所谓function objects,是某种class的实体对象,这类cjasses对function call运算符进行了重载操作,如此一来,可使function object被当成一般函数来使用.function objecr实现出我们原本可能以独立函数加以定义的事物.但又何必如此呢?主要是为了效率.我们可以令call运算符成为inline,因而消除“通过函数指针来调用函数”时需付出的额外代价。
  标准程序库事先定义了一组function objects分为算术运算(arithmeric)、关系(relational),逻辑运算(logical)三大类.以下列表中的type在实际运用时会铍替换为内建型别或class型别:
●6个算术运算:plus,minus< type>,negate,multiplies< type).divides,modules
●6 个关系:  less,less_equal, greater< type>,  greater_equal.equal_to,    not_equa l_to< type>
●3个逻辑运算:分别对应于&&,||,!运算符,logical_and,logical_or< type>,logical_not
  欲使用事先定义的function objects.首先得含入相关头文件:#include
  举个例子,默认情形下.sort()会使用底部元索的型别所供应的Iess-than运算符,将元素递增排序,如果我们传人greater_than function object,元素就会以递减方式排序:

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

  其中的:greater() 会产生一个匿名的class template object.传给sort.
  现在,我以另外数种略加变化的方式来显示Fibonacci数列;每个元素和自身相加、和自身相乘,被加到对应的Pell数列……等等.做法之一是使用泛型算法transform(),并搭配plus和multiplies
  我们必须传给transform(): (1) 一对iterators,标示出欲转换的元素范围:(2) 一个iterator,其所指元素将应用于转换操作之上;(3)一个iterator,其所指位置(及其后面的空间)用来存放转换结果;(4)一个function object,表现出我们想要施行的转换操作.以下便是将Pell数列加到Fibonacci数列的写法:

transform(fib.begin(), fib.end(), //欲转换的元素范围
          pell.begin(),           //所指元素将应用于转换操作上
          fib_plus_pell.begin();  //所指位置(及后继空何)用来存放转换结果
          plus());           //想要实施的转换操作

Function Object Adapters
  上述的function objects并不那么恰好符合find_if的需求.举个例子,function objectless期望外界传入两个值,如果第一个值小于第二个值就返回true.本倒中,每个元素都必须和用户所指定的数值进行比较.理想情形下,我们需耍做的就是将less转化为一个一元(uanry)运算符.这可通过“将其第二个参数绑定(bind)至用户指定的数值”而达成.这么一来,less便会将每个元素拿出来一一与用户指定的数值比较.真的可以做到这样吗?是的.标准程序库提供的所谓adapter(配接器)便是应此而生.
  function object adapter会对function object进行修改操作。所谓binder adapter(绑定配接器)会将function object的参数绑定至某特定值身上,使binary(二元)function object转化为unary(一元)funcrion object.这正是我们需要的,标准程序库提供了两个binder adapter:bindlst会将指定值绑定至第一操作数,bind2nd则将指定值绑定至第二操作数。以下是修改后的filter,使用bind2nd adapter.

vector filter(const vector &vec,
                   int val, less <)
{
	vector nvec;
	vector::const_iterator iter = vec.begin();
	//bin2nd(less, val)会把val绑定于less的第二个参数身上
	//于是less会将每个元素拿来和val比较
	while ((iter = find_if(iter, vec.end(), bind2nd(lt, val))) 
	        != vec.end())
	{
		nvec.push_back(*iter);
		iter++;	
	}
	
	return nvec;	        
}

  接下来如何消除filter()与vector元素型别的相依关联,以及filter与vecror容器类型的相依关联,以使filter更加泛型化呢?为了消除它和元素型别间的相依性,我们将filter改为funclion template,并将元素型别加入template的声明中。为了消除它和容器类型间的相依性,我们传人一对iterators[first,last],并在参数表中增加另一个iterator,用以指定从何处开始复制元素,以下便是新的实现内容:

template
OutputIterator
filter(InputIterator first, InputIterator last,
       OutputIterator at, const ElemType &val, Comp pred)
{
	while((first = find_if(first, last,
	               bind2nd(pred, val))) != last)	
	{
		cout << "found value:" << *first << endl;
		*at++ = *first++;		
	}
	return at;
}

  现在,我们的函数和元素型别无法,也和比较操作无关,更和容器型别无关,简单的说,我们已经将最初的函数转化为一个泛型算法了。

7.使用Map

  map被定义为一对(pair)数值,其中的key通常是个字符串,扮演索引的角色,另一个数值是value。字典便是map的一个不错的实体,如果要撰写一个能对文章内每个字的出现次数加以分析的程序,可以建立一份map.,带有string key和int value(前者表现单字,后者表示出现次数):
    #include
    #include
    map  words;
    输入key/value的最简单方式是:
    words['vermeer"] = 1;
    对字数统计程序而盲,我们可以采用以下方式;
    string tword;
    while (cin>>tword)
    words[ tword]++;
    其中的表达式:
    words[tword]
会取出与tword相应的value。如果tword不在map内,它便会因此被置人map内,并获得默认值0.稍后出现的increment运算符会将其值累加1.
    以下的for循环会打印出所有单字及其出现次数:
    map< string,int>::iterator  it=words _begin()j
    for(; it != words.end(); ++it)

        cout << "key: · <first

               << "value:·<second<

  map对象有一个名为first的mtmber,对应于key,本例之中便是单字字符串,另有一个名为second的member,对应于value,本例之中便是单字的出现次数,欲查询map内是否存在某个key,有3种方法,最直观的做法就是把key当索引使用:
    int count=Of
    if(!(count=words[ "venneer"]  ))
        //vermeer并不存于words map内
    这种写法的缺点是,如果我们用来索引的那个key并不存在于map内,则那个key会自动被加入map中,而其相应的value会被设为所属型别的默认值。例如,假设- vermeer”不在wordsmap内,上述搜寻方式会将它置人map内,而其value将是0。
    第二种map查询法是利用map的find函数(不要和泛型算法find搞混了).我们将key传人find并调用之:
    words. find("vermeer“);
    如果key已置于其中,find会返回一个iterator,指向key/value形成的一个pair(译注:pair class是标准程序库的一员),反之则返回end:
    int count = 0,
    map:: iterator  it;
    it = words.find(  "verrneer"  ) ;
    if ( it != words;.end() )
      count = it->second;
    第三种map查询法是利用map的count函数。count会返回某特定项目在map内的个数:
    int count = 0;
    string search_word(  'vermeer"  ) ;
    if   (  words . count (  search_word  )      //  ok,它存在
       count  =  words [  searct_word  ] :
    任何一个key值在map内最多只会有一份。如果我们需要存储多份相同的key值,就必须使用multimap.

8.使用Set   

  Set由一群keys组合而成。如果我们想知道某值是否存在于某个集合内,就可以使用sel。例如,在graph traversal(图形走访)算法中,我们可以使用set存储每个走访过的节点(node)。在移至下一节点前,我们可以先查询set,判断该节点是否已经走访过了.
  以前一节的宇数统计程序为例,它可能不想统计一般中性单词的出现次数,为达此目的,我们定义一个用来记录“排除单词”的set,元素型别为string:
    #include
    #include
    set word_exclusion;

  在程序将某个字置入map之前,应该先检查它是否存在于word_exclusion set中:
    while{cin>>tword)
    {
        if  (word_exclusion.count(tword))
            //如果tword涵盖于“排除字集”内,
            //就跳过此次迭代的剩余部分
        contlnue:
            //ok:一旦抵达此处,表示tword并不属于“排除字集”
        words[tword]+十;
    )
    其中的continue语句会跳离本次迭代,此例中,如果tword位于word_exclusion set内,那么以下句子就不会被执行:words[tword]++;
    对于任何key值,set只能存储一份,如果要存储多份相同的key值,必须使用multiset,本书并不讨论multiset,请参阅[LIPPMAN98] 6.15节,其中有详尽的讨论和范例.
    默认情形下,set元素皆依据其所属型别默认的less-than运算符进行排列.例如,如果给予:
    int ia[10] = {1,3,5,8,5,3,1.5.8,1};
    vector vec(  ia,  ia+10);
    set    iset = ( vec.begin(), vec.end());iset的元素将是{1,3,5,8}.
    如果要为set加人单一元素,可使用单一参数的insert():
    iset.insert(ival);
    如果要为set加人某个范围的元素,可使用双参数的insert:
    iset.insert(vec.begin(), vec.end());
    在sec身上进行迭代,其形式正如你所预期:
    set::iterator it=iset.begin():
    for(; it != iset .end(); ++it)
        cout << *it << ' ';     

    cout t'endl;
    泛型算法中有不少和set相关的算法:set_intersaction(),set_union(),set_difference(), set_symmetric_difference()。

9.如何使用Iterator Inserters

  回到先前3.6节对filter()的实现内容,我们将 来源端(容器)之中每一个符合条件的元素一一赋值( assign)至目的端(容器)中:

while((first=
           find_if(  first, last, bind2nd(pred, val))) != last)
               *at++ = *first++;  

  这种形式之下,目的端的容器必须有足够大的容量,存储被赋值进来的每个元素.filter没有办法知道每次对at累加之后,at是否仍指向一个有效的容器位置.“确保at所指之目的端容器具有足够大的空间”是程序员的责任。在3.6节的测试程序中,我们设定目的端容器的大小,使它等于来源端容器的大小,藉此方式来确保以上条件:

int ia[elem_size] = {12,8,43,0,6,21,3,7};
    vector ivec(ia, ia+elem_size);
    int  ia2 [elem_size];
    vector  ivec2 (elem_size) :    

  这个解法的问题在于,大部分情形下,目的端容器的容量显然太大了.另一种解法是先定义一个空容器,而后每当有元素被安插进来时,才加以扩展。不幸的是,filter目前的实现法是将元素赋值至某个已存在的容器位置上,如果我们重新以安插方式实现filter,那对我们目前已有的应用程序会产生什么影响呢?此外,我们叉应该提供什么样的安插操作呢?
  所有“会对元素进行复制行为”的泛型算法,例如copy,copy_backwards,remove_copy(),replace_copy,unique_copy,皆和filter的实现极为相似.每个算法都接受一个iterator,标示出复制的起始位置.每当复制一个元素时,其值舍被赋值(assigned).iterator则会递增至下个位置.我们必须保证在每一次复制操作中,目的端容器都具有够大的容量以存储这些被赋值进来的元素.既然有了这些算法,我们在不需要重新写一个。
  这意谓着我们必须总是传入某个固定大小的容器至上述算法吗?这绝对不符合STL的精神.标准程序库提供了3个所谓的insertion adapters,这些adapter让我们得以避免使用容器的assignment运算符:
●back_inserter会以容器的push_back函数取代assignment运算符.对vector来说,这是比较适合的inserter.派给back_inserter的引数,应该就是容器本身:

vector result_vec;
unique_copy(ivec. begin(), ivec. end(), back_inserter(result_vec));

●inserter会以容器的insert函数取代assignment运算符.inserter接受两个参数一个是容器,一个是iterator,指向容器内的安插操作起始点

●front_inserter()会以容器的push_front函数取代assignment运算符,这个inserter只适用于list和deque:

 list ilist_clone;
    copy(ilist.begin(), ilist. end()), front_inserter(ilist_clone));   

  欲使用上述3种adapters.首先必须含人iterator头文件:#include

10 .使用iostream Iterators

  想象我们的新任务如下:从标准输人装置读取一串string元素,将它们存进vector内,并进行排序,最后再将这些字符串写回标准输出设备。一般的解法看起来像这样:

#include 
#include 
#include 
#include 
using namespace std;
int  main()
{
    string word:
    vector  text;
    //ok:让我们依次读人每个单宇,直至完成
    while (cin >> word)
        text.push_back(word);
    //ok:加以排序
    sort(text.begin(), text.end());
    //ok:输出到标准输出装置上
    for (int ix = 0; ix < text.size(); ++ix)
        cout << text[ix] << ' ';    
}

  标准程序库定义有供输入及输出用的iosrream iterator类,称为istream_iterator和ostream_iterator,分别支持单一型别的元素读取和写人。使用这两个iterator classes之前,先得
含人iterator头文件:#include
  现在让我们看看如何利用istream_iterator从标准输入装置中读取字符串。就像所有的iterators -样,我们需要一对iterators:first和last,用来标示元素范围.以下定义式:
    istream_iterator   is(cin);

为我们提供了一个first iterator,它将is定义为一个“连结至标准输人装置”的istream_iterator。我们还需要一个last iterator.表示“欲读取之最后元素的下一位置”.对标准输入装置而言,end-of-file即代表last。这该如何表示呢?只要在定义istream_iterator时不为它指定istream对象,它便代表了end-of-flle。例如:
    istrearrLiterator eof;
  我们应该如何使用这对iterators呢?下面的例子中,我将它们,以及存储字符串元素的vector,一起传给泛型算法copy.由于不知道为vector保留多少空间,所以我选用back_inserter:
    copy( is, eof, back_inserter( text));
  现在我还需要一个ostream_ itarator,表示字符串元素的输出位置.一旦不再有任何元素需要输出,我就停止输出操作.以下程序代码将os定义为一个“连结至标准输出设备的
ostream_ iterator.此标准输出设备供我们输出型别为string的元素.
    ostream_ iterator os(cout, " ");

  上述第二个参数可以是C-style字符串,也可以是字符串常量,它用来表示各个元素被输出时的分隔符。默认情形下输出的各个元素井无任何分隔符.本例我选择在各输出字符串之间以空白加以分隔.以下便是可能的运用方式:
    copy(text.begin), text.end(), os);
    copy会将存储在text中的每个元素一一写到由os所表示的ostream上头.每个元素皆以空格符分隔开未,以下是完整的程序:

#include 
#include 
#include 
#include 
#include 
using namespace std;
int  main()
{
	istream_iterator is(cin);
	istream_iterator eof;
	
	vector text;
	copy(is, eof, back_inserter(text));
	
	sort(text.begin(), text.end());
	
	ostream_iterator os(cout, " ");
	copy(text.begin(), text.end(), os);
}

   然而,常常,我们并不是要从标准输人设备中读数据,也不是要写到标准输出设备中击,而是希望从文件中读取,写到文件中去,只需将istream iterator绑定至ifstream object,将ostream iterator绑定至ofstream object即可:

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
int  main()
{
	ifstream in_file("input_file.txt");
	ofstream out_file("output_file.txt");
	
	if (!in_file || !out_file)
	{
		cerr << "open wrong!\n";
		return -1;
	}	
	
	istream_iterator is(in_file);
	istream_iterator eof;	
	
	vector text;
	copy(is, eof, back_inserter(text));
	
	sort(text.begin(), text.end());
	
	ostream_iterator os(out_file, " ");
	copy(text.begin(), text.end(), os);
}

你可能感兴趣的:(C/C++)