STL

STL 组件概述

容器、迭代器、算法。


序列式容器:vector、deque、list,元素排列顺序和置入顺序一致。

关联式容器:set、multiset、map、multimap,他们的插入位置与插入次序无关。关联式容器 自动排序。

序列式容器(Sequence Containers)

vector以一个dynamic array加以管理,在尾部添加或者删除元素均非常快速,但是在中部或者头部比较费时。push_back(),size()所有序列式容器都提供这个成员函数。可以通过下标操作符[]来存取vector内的元素。

deque 是double-ended deque的缩写。也是一个dynamic array,可以向两端发展。push_front()向前安插,vector没有这个函数。支持下标[]操作。

list 是由双向链表(doubly linked list)实作而成,这意味这list内的每一个元素都以一部分内存指示其前趋元素或者后继元素。不支持随机存储(即没有提供[]下标操作),优势是在任何位置上安插或者删除动作都很迅速。

关联式容器(Associative Containers)关联式容器依据特定准则,自动为其元素排序,默认以operator<进行比较。通常关联式容器由二叉树实作出来。在二叉树中,每个元素都有一个父节点和两个子节点,左子树的所有元素都比自己小,右子树的所有元素比自己大。关联式容器的差别在于元素的类别以及处理元素的方式。

set:内部元素依据其值自动排序,每个元素值只能出现一次,不允许重复。

multiset:与set相同,不过他允许重复。

map:都是”实值/键值“所形成的一个对组(key/value pairs),每个元素有一个键,是排序准则的基础,每个键只能出现一次,不允许重复。

multimap: 和map一样,但是允许重复。也就是说multimap可以包括多个键值相同的元素。multimap可被当做字典。

set<int> coll;
	coll.insert(3);
	coll.insert(1);
	coll.insert(5);
	coll.insert(4);
	coll.insert(1);
	coll.insert(6);
	coll.insert(2);
	set<int>::iterator pos;
	for(pos=coll.begin();pos!=coll.end();++pos)
		cout << *pos << " ";
	cout << endl;
	multimap<int,string> intStringMMap;
	intStringMMap.insert(make_pair(1,"I"));
	intStringMMap.insert(make_pair(1,"have"));
	intStringMMap.insert(make_pair(2,"a"));
	intStringMMap.insert(make_pair(3,"dream"));
	intStringMMap.insert(make_pair(3,"."));
	multimap<int,string>::iterator itor;
	for(itor = intStringMMap.begin(); itor != intStringMMap.end(); ++itor)
	{
//		cout << itor->first << " ";
//		cout << itor->second << " ";
		cout << (*itor).second<< " ";
	}
	cout <<endl;
	
	multimap<string,int> stringIntMMap;
	stringIntMMap.insert(make_pair("gfafasdf",2));
	stringIntMMap.insert(make_pair("gdgfsdfgs",1));
	stringIntMMap.insert(make_pair("hdhfsdfgs",3));
	multimap<string,int>::iterator itor;
	for(itor=stringIntMMap.begin(); itor!=stringIntMMap.end(); ++itor)
	{
		cout << itor->second << endl;
	}
	cout << endl;

容器配接器( Container Adapters ):除了容器类别,C++标准库还提供了容器适配器,他包括

stacks : stack容器对元素采用LIFO(后进先出)管理策略。

Queues :对元素采用FIFO(先进先出)的管理策略。也就是普通的缓冲区。

Priority Queues :容器中的元素可以拥有不通的优先权。所谓优先权,是基于程序员提供的排序准则(缺省使用operator<)定义。Priority Queue 的效果相当于一个buffer:"下一个元素永远是Queue中优先级最高的元素"。如果同时有多个元素具备最高优先权,则其次序无明确定义。

迭代器(Iterators): Operator *、Operator ++、Operator -- 与指针相同,也可以使用operator-> ,但是operator==和operator != 判断两个迭代器是否指向同一个位置。operator将其所指元素的位置赋值过去。

begin() 返回是第一个元素的位置。end() 返回一个迭代器,指向容器结束点。最后一个元素之后,"逾尾"迭代器。begin()和end()形成一个前闭后开区间。优势:为遍历元素提供循环结束时机,只要未达到end(),循环就可以继续进行。不必对空区间采取特殊处理,空区间就是begin()=end()。

任何一个容器都定义两种迭代器:

container::const_iterator 以”读写“模式遍历元素 

container::iterator 以"只读"模式遍历元素。

list<char>::interator pos;  ++pos, 前置式递增比pos++后置式递增效率高,后置式需要一个额外的临时对象,因为他必须存放迭代器原来位置并将他返回,所以最好使用++pos。

算法(algorithms)

STL 提供了一些标准算法,如搜寻、排序、拷贝等,算法并非容器类型的成员函数,而是一种搭配迭代器使用的全局函数,这么做有一个优势就是所有算法只需要做出一份,可以对所有容器进行操作,而不需要为每一个容器量身定做。注意:这里所阐述的并非面向对象思维模式(OOP paradigm),而是泛型函数式编程思维模式(generic functional programming paradigm)。在面向对象概念里,数据和操作结合为一体,而这里被明确划分开来,再透过特定的接口彼此互动。当然这样付出的代价:首先有失直观,其次某些数据和算法不兼容。因此要深入学习STL的概念并了解缺陷,才能取利而弊害。

迭代器 之 配接器(Iterator Adapters)

1. Insert iterators (安插型迭代器)

back_inserter 安插于容器最尾端,在容器尾部插入元素,内部调用push_back(),所以可以使用的只有vector、deque、list三个容器,

front_inserter 安插于容器最前端, 在内部调用push_front(),将元素安插于容器前端,内部调用push_front(),所以只有deque、list使用。

inserter(General inserters 一般性安插),内部调用的insert函数,所有容器提供insert()函数,因此所有函数都可以使用。

         list<int> coll1;
	for(int i=1; i<=9; ++i)
	{
		coll1.push_back(i);
	}
	coll1.push_back(0);
	vector<int> coll2;
	copy(coll1.begin(),coll1.end(),back_inserter(coll2));
	vector<int>::iterator itor;
	for(itor=coll2.begin(); itor!=coll2.end(); ++itor)
	{
		cout << *itor << "  ";
	}
	cout << endl;


	deque<int> coll3;
	copy(coll1.begin(),coll1.end(),front_inserter(coll3));
	deque<int>::iterator itor1;
	for(itor1=coll3.begin(); itor1!=coll3.end(); ++itor1)
	{
		cout << *itor1 << "   ";
	}
	cout << endl;

	set<int> coll4;
	copy(coll1.begin(),coll1.end(),inserter(coll4,coll4.begin()));
	set<int>::iterator itor2;
	for(itor2=coll4.begin(); itor2!=coll4.end(); ++itor2)
	{
		cout << *itor2 << "  ";
	}
	cout << endl;
<img src="https://img-blog.csdn.net/20150815205954665?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />

2. Stream Iterators(流迭代器)

读写stream的迭代器。

<span style="white-space:pre">	</span>vector<string> coll;
	copy(istream_iterator<string>(cin),
			istream_iterator<string>(),
			back_inserter(coll)); //把输入的参数传递到coll
	sort(coll.begin(),coll.end());//排序
	unique_copy(coll.begin(),coll.end(),
			ostream_iterator<string>(cout,"\n"));

程序输入不知道如何结束。

3. Reverse Iterator(逆向迭代器)

倒转筋脉似地以逆向方式进行所有操作。它将increment(递增)运算转换为decrement(递减)运算。反之亦然。所有容器都可以透过成员函数rbegin()和rend()产生reverse iterators。

        vector<int> coll1;
	for(int i=1; i<=9; ++i)
	{
		coll1.push_back(i);
	}
	copy(coll1.rbegin(),coll1.rend(),
			ostream_iterator<int>(cout," "));
	cout << endl;
//coll1.rbegin() 返回coll的逆向迭代器
//<span style="font-family: Arial, Helvetica, sans-serif;">coll1.rend() 逆向"逾尾"位置,指的是容器内第一个元素的前一个位置</span>

更易型算法( manipulating algorithms )

移除算法 ( remove ):只是把后面的元素向前移了,返回一个新的结尾。如果要真正修改,还需要配合distance()和erase函数

        list<int> coll1;
	for(inti=1; i<=6; ++i)
	{
		coll1.push_back(i);
		coll1.push_front(i);
	}
	copy(coll1.begin(),coll1.end(),
			ostream_iterator<int>(cout," "));
	cout << endl;
	list<int>::end = remove(coll1.begin(),coll1.end(),3);
	copy(coll1.begin(),end,ostream_iterator(cout," "));
	cout << endl;

	cout << "number of remove elements:"
			<< distance(end,coll1.end()) << endl;

	coll1.erase(end,coll1.end());

	copy(coll1.begin(),coll1.end(),
			ostream_iterator<int>(cout," "));
	cout << endl;

如果两个迭代器都是随机存取迭代器,可以采用operator-直接计算其距离。

如果要彻底删除元素,可以:

coll1.erase(remove(coll1.begin(),coll1.end(),3),coll1.end());
那么为何算法自己部调用erase(),这正是STL为了获取灵活性二付出的代价,透过"以迭代器"为接口,STL将数据结构和算法分离开来。而迭代器只是容器的一个位置的抽象概念而已,一般说来迭代器对自己所属的容器一无所知。任何“以迭代器访问容器元素”的算法,都不得(无法)透过迭代器调用容器类别所提供的任何成员函数。这样设计的结果是苏昂操作的对象不一定是“容器内的全部元素”所形成的区间,可以是一部分元素,甚至是一个“未提供成员函数erase()”的容器(如array),所以为了达成算法的最大弹性,不要求“迭代器必须了解容器细节”是有一定道理的。

注意,通常并不必要删除元素,通常以逻辑终点来取代容器的实际终点就足够了。

更易型算法和关联式容器

更易型算法(移除remove、重排resort、修改modify)用于关联式容器身上会出问题。关联式容器不能被当做操作目标,原因很简单,如果更易型算法用于关联式容器,会改变某位置上的值,进而破坏其已序特征,那就推翻了关联式容器的基本原则,容器内的元素总是根据某个排列准则自动排序,因此为了保证这个原则,关联式容器的所有迭代器均被声明为指向常量,如果你更易关联式容器中的元素,会导致编译错误。

那么如何从关联式容器中删除元素,原因很简单,调用成员函数。关联式容器提供了多种不同的erase()成员函数,返回值为被删除的元素个数。
如果高效是你的最高目标,你应该永远优先选用成员函数。但是一旦改变容器,你就得考量成员函数的合理性,而此时通用算法可能不需要修改代码。

仿函数(functors,Function Objects)

仿函数是泛型编程强大威力和纯粹抽象概念的又一例证。你可以说任何东西,只要行为像函数,它就是个函数,因此,如果你定义了一个对象,行为像函数,它就可以被当函数使用。所谓函数行为,是指可以“使用小括号传递参数,籍以调用某个东西”。没错,在C++中,只需要定义operator(),并给予合适的参数型别。

<span style="white-space:pre">	</span>class PrintInt
<span style="white-space:pre">	</span>{
<span style="white-space:pre">	</span>public:
<span style="white-space:pre">		</span>void operator()(int elem)const
<span style="white-space:pre">		</span>{
<span style="white-space:pre">			</span>cout << elem << "  ";
<span style="white-space:pre">		</span>}
<span style="white-space:pre">	</span>};


<span style="white-space:pre">	</span>int main()
<span style="white-space:pre">	</span>{
<span style="white-space:pre">		</span>vector<int> coll;
<span style="white-space:pre">		</span>for(int i=1; i<=9; ++i)
<span style="white-space:pre">		</span>{
<span style="white-space:pre">			</span>coll.push_back(i);
<span style="white-space:pre">		</span>}
<span style="white-space:pre">		</span>for_each(coll.begin(),coll.end(),PrintInt());
<span style="white-space:pre">		</span>cout << endl;
<span style="white-space:pre">		</span>return 0;
<span style="white-space:pre">	</span>}
表达式PrintInt()产生此类别的一个临时对象,当做for_each()算法的一个参数。

仿函数优势:

1、仿函数是"smart functions"(智能型函数)

“行为类似指针”的对象,我们称为“smart pointers”。“行为类似函数”的对象我们称之为“smart functions”,因此他们的能力可以超越operator()。仿函数可拥有成员函数和成员变量,这意味着仿函数拥有状态。

2、每个仿函数都有自己的型别

仿函数定义的每一个函数行为都有其自己的型别。我们便可以将函数行为当做template模板来运用。

3、仿函数通常比一般函数速度快

就template概念而言,由于更多细节在编译期就已确定,所以通常可能进行更好的最佳化。所以传入一个仿函数(而非一般函数),可能获得更好的性能。

template <int theValue>
void add(int& elem)
{
	elem += theValue;
}
int main()
{

	vector<int> coll;
	for(int i=1; i<=9; ++i)
	{
		coll.push_back(i);
	}
	for_each(coll.begin(),coll.end(),add<10>);
	copy(coll.begin(),coll.end(),
			ostream_iterator<int>(cout,"  "));
	cout << endl;
	return 0;
}



预先定义的仿函数

C++标准程序库包含一些预先定义的仿函数,涵盖许多基础运算。有了它们,很多时候你就不必费心自己去写仿函数。一个典型的例子是作为排序准则的仿函数,operator<之缺省排序准则乃是less<>,所以声明的

set<int> coll;会被扩展为set<int,less<int> >coll;

int main()
{
	set<int,greater<int> > coll1;
	deque<int> coll2;
	for(int i=1; i<=9; ++i)
	{
		coll1.insert(i);
	}
	// transform all elements into coll2 by multiplying 10
	transform(coll1.begin(),coll1.end(), //source
			back_inserter(coll2),		//destination
			bind2nd(multiplies<int>(),10));//operation

	// replace value equal to 70 with 42
	replace_if(coll2.begin(),coll2.end(), //range
			bind2nd(equal_to<int>(),70),  //replace criterion
			42);					//new value

	// remove all elements with values less than 50
	coll2.erase(remove_if(coll2.begin(),coll2.end(), // range
			bind2nd(less<int>,50)),			// remove criterion
			coll2.end());

	return 0;
}

这里使用了配接器bind2nd,使得进行multiples<int>运算时,以源集群的元素作为第一参数,10作为第二参数。

配接器bind2nd的工作方式如下:transform()期待他自己的第四个参数是个能接纳单一参数的表达式,然而我们却希望先把该元素乘以10,再传给transfor()。所以我们必须构建一个表达式,接受两个参数,并以数值10作为第二个参数,以此产生一个“只需要单个参数”的表达式。bind2nd()正好胜任这项工作。他会把表达式保存起来,把第二个参数当做内部数值也保存起来。

此种方式的程序编写,导致函数的组合。有趣的是这些所有仿函数通常都声明为inline。还有一些仿函数。某些仿函数可用来调用群集内每一个元素的成员函数:

for_each(coll.begin(),coll.end(),
               mem_fun_ref(&Person::save));
仿函数mem_fun_ref 用来调用它所作用的元素的某个成员函数。


STL的设计原则是效率优先,安全次之。错误检查相当花时间,所以C++几乎没有。C++标准库指出,对于STL的任何运用,如果违反规则,将导致未定义的行为。所以使用STL必须满足:1、迭代器务必合法有效。2、一个迭代器的“逾尾”位置,它不指向任何对象,因此operator*或者operator->不适用于任何end()和rend()。3、区间必须合法。4、如果涉及的区间不止一个,第二个区间及后继各区间必须拥有“至少和第一个区间一样多”的元素。5、覆盖(overwritten)动作中的目标区间必须拥有足够元素,负责就必须采用insert iterators(插入型迭代器)。

一个带有警戒能力的STL版本,名为“STLport”,几乎适用于任何平台。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值