STL - 设计一个泛型算法

1、需求描述v-1

需求:用户给予一个整数vector,我们必须返回一个新的vector,其中内含原vector之中小于10的所有数据。

根据这个需求可以很容易的设计一个如下的函数 v1_less_than_10():

using vecInt = vector<int>;

vecInt v1_less_than_10(const vecInt& vec)
{
	vecInt nVec;
	for (size_t ix = 0; ix < vec.size(); ix++)
	{
		if (vec[ix] < 10)
			nVec.push_back(vec[ix]);
	}
	return nVec;
}

v1_less_than_10():函数是最简单直接的一种实现方式,但是存在一个一个问题:假如用户下次想返回一个小于11的所有数据组成的vector,那就要重新建立一个新的函数,比如:less_than_11,那显然不够通用化!

2、需求描述v-2

需求:可以让用户指定某个上限值。

根据这个需求,我们可以在函数中添加表示上限值的一个参数,函数设计如下所示:

vecInt v2_less_than(const vecInt& vec, int less_than_val)
{
	vecInt nVec;
	for (size_t ix = 0; ix < vec.size(); ix++)
	{
		if (vec[ix] < less_than_val)
			nVec.push_back(vec[ix]);
	}
	return nVec;
}

函数增加一个参数 less_than_val,新的vector中的数值都小于less_than_val,稍微通用化了一些,但是这个函数只能用来处理 < 的情况,而不能处理 > 的情况。

3、需求描述v-3:函数作为参数

需求:可以让用户指定某个操作,比如 大于、小于等。

根据需求描述,有一个解法,以函数调用取代比较运算符。加入第三个参数pred,用它来指定一个函数指针,其参数列表有两个函数,返回值为bool。形如: bool (*pred)(int , int);  新的函数定义如下所示:

/*
vecInt v3_filter(const vecInt& vec,
	int less_than_val,
	bool(*pred)(int, int));
*/

//也可以采用下面的形式(推荐):
using Func = bool(*)(int, int);	//c++11
vecInt v3_filter(const vecInt& vec, int less_than_val, Func func);

vecInt v3_filter(const vecInt& vec, int less_than_val, Func func)
{
	vecInt nVec;
	for (size_t ix = 0; ix < vec.size(); ix++)
	{
		if (func(vec[ix], less_than_val))
			nVec.push_back(vec[ix]);
	}
	return nVec;
}

v3_filter(),新添加了一个参数,这个参数是一个函数指针,所以在调用它之前需要有相关的函数传参给它,定义如下两个函数:

//此时,需要先自定义Func类型的关系比较函数:有两个参数、必须返回bool
bool less_than(int v1, int v2)	// < 操作符:v1<v2则返回true,否则返回false
{
	return v1 < v2 ? true : false;
}

bool greater_than(int v1, int v2)	// > 操作符:v1>v2则返回true,否则返回false
{
	return v1 > v2 ? true : false;
}

v3_filter()函数调用方式如下所示:

//函数调用
void play01_2()
{
	vecInt vec{ 6,10,8,4,10,7,10 };
	int num = 10;

	vecInt nVec = v3_filter(vec, num, less_than);

	vecInt nVec2 = v3_filter(vec, num, greater_than);

}

4、需求描述v-4:Function Object和Binder Adapter

需求:使用标准库中的function object实现提取小于某个值的vector。

在v3_filter()中,我们使用的for循环来寻找满足条件的数据元素,其实也可以用STL中的算法find_if()来进行查找

 find_if():
原型:template <class InputIterator, class UnaryPredicate>
     InputIterator find_if(InputIterator first, InputIterator last, UnaryPredicate pred);
功能:Find element in range
返回:Returns an iterator to the first element in the range[first, last) for which pred returns true.
     If no such element is found, the function returns last.

需要注意的是,pred是一个一元函数(unary function,只能接收一个参数)。而上面v-3版本的 Func 函数类型是自己定义的,有两个参数,不满足一元函数的要求。那能不能用STL自带的函数呢?

当然是可以的。但是在STL中不称为函数,而是函数对象(function object)

4.1 Function Object

所谓函数对象:是某种class的实例对象, 这类class对象对function call运算符做了重载操作,如此以来可是function object被当成一般函数来用。

function object实现了我们原本可能以独立函数加以定义的事务,那又何必呢?主要是为了效率,我们完全可以另call运算符成为inline函数,从未消除“通过函数指针来调用函数”时需付出的额外的代价。

STL事先定义了一组function object,分为算术运算符(Arithmetic)、关系运算符(Comparison)、逻辑运算符(Logical),如果要使用标准库中的function object,需要包含头文件<functional>。(C++98)

标准库中的大小比较,比较常见的有less<type>、greater<type>,下面以less为例:

less:函数对象要求必须有两个参数,并且会返回一个bool值。
原型定义:
template <class T> struct less : binary_function <T, T, bool> 
{
    bool operator() (const T& x, const T& y) const 
    { return x<y; }
};

用法:Objects of this class can be used on standard algorithms such as sort, merge or lower_bound.
      比如:sort(first_iterator, last_iterator, less<int>());
      less<int>()会产生一个未命名的class template object,传给sort,然后sort会利用这个对象进行数值比较。

经过分析,发现上述的less并不那么符合我们的需求,因为,less期望外界传入两个值,如果第一个小于第二个就返回true,而本例中每个元素都必须和用户所指定的数值进行比较,而正常情况下,我们并不能指定less的某一个参数

针对上述情况,可以将less<type>转化为一个一元(unary)运算符。这可以通过“将其第二个参数绑定(bind)至用户指定的数值”完成。这样一来,less<type>便会将每个元素拿出来一一与用户指定的数值进行比较

可以通过标准库提供的所谓adapter(适配器)实现上述功能。

4.2 Binder Adapter

 adapter(适配器):所谓binder adapter(绑定适配器)会将function object的参数绑定至某特定值,使binary(二元)function object 转换为unary(一元)function object。

STL提供了两个binder adapter:bind1dt会将指定值绑定至第一操作数,bind2nd则将指定值绑定至第二操作数。

函数实现:

//函数实现
vecInt v4_filter(const vecInt& vec, int val, less<int>& lt)
{
	vecInt nVec;
	vecInt::const_iterator iter = vec.begin();
	while ((iter = find_if(iter, 
				vec.end(), 
				bind2nd(lt, val))) != vec.end())
	{
		nVec.push_back(*iter);
		iter++;	//遍历寻找下一个
	}
	return nVec;
}

可以看到在find_if()的第三个参数调用了bind2nd()函数。

函数调用:

//函数调用
void play01_3()
{
	vecInt vec{ 6,10,8,4,10,7,10 };
	int num = 10;

	vecInt nVec = v4_filter(vec, num, less<int>());
}

5、需求描述v-5: Function Template

需求:消除filter()与vector元素类型,以及filter()与vector容器类型的依赖关系,使filter()更加泛型化;

为了消除它和元素类型间的依赖性,我们将filter()改为function template,并将元素类型加入到template的声明中;

为了消除它和容器类型间的依赖性,可以传入一对iteartor[first,last),并在参数列表中增加另一个iterator,用以指定从何处开始复制元素。

函数实现:

//函数实现
template<typename InputIterator, typename OutputIterator, typename ElemType, typename Comp>
OutputIterator v5_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;
}

函数调用:

//函数调用
void play01_4()
{
	int num = 10;
	const int elem_size = 8;

	int ia[elem_size] = { 6,10,8,4,10,7,10 };
	vecInt vec1(ia, ia+elem_size);

	//下面这个容器用来存储过滤效果
	int ia2[elem_size];
	vecInt vec2(elem_size);		//初始化了大小

	cout << "filtering int array for value less than 8:\n";
	v5_filter(ia, ia+elem_size, ia2, num, less<int>());

	cout << "\nfiltering int vector for value less than 8:\n";
	v5_filter(vec1.begin(), vec1.end(), vec2.begin(), num, less<int>());

	shoeVec(vec2);
}

运行结果如下所示:

可以看到最终打印新的vector时,里面有几个元素为0的数据,这是因为在定义新的vectror:vec2时,指定了初始化时的大小,有可能填不满vec2,这样就会造成后面几个元素没有数据。 

6、需求描述v-6: Function Template和Insertion Adapter

需求:返回任意大小的vector,用以存储小于某个特定值的数据(个数不定、长度不定)

在上面的play01_4()演示中,定义了一个新的vector:vec2用以存储返回的数据,在初始化时指明了该容器的容量。

而在v5_filter()的实现中,将源端(容器)中每一个符合条件的元素一一赋值(assign)至目的端(容器),这种形式下,目的端的容器必须足够大,以储存被赋值进来的每个元素

但是,v5_filter()没办法知道每次对at递增至后,at是否仍指向一个有效的容器位置。“确保at所指目的端容器空间够大”这是程序员的责任。那意味着,对于那些采用赋值(assigned)操作的算法,我们必须总是传入某个固定大小的容器至上述算法吗?这样也不符合STL的精神!

标准库提供了三个所谓的insertion adapter,这些 adapter让我们得以避免使用容器的assgnment运算符

6.1 Insertion Adapter

back_inserter: 会以容器的push_back()函数取代assignment运算符。对于vector来说,这是比较适合的inserter.传入back_inserter的参数,应该就是容器本身:

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

inserter: 会以容器的inserter函数取代assignment运算符。inserter()接受两个参数:一个是容器,另一个是iterator,指向容器内的插入操作起点:

vector<string> svec_res;   
unique_copy(ivec.begin(), ivec.end(), inserter(svec_res, svec_res.end()));

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

list<int> ilist_clone;   
unique_copy(ilist.begin(), ilist.end(), front_inserter(ilist_clone));

要使用上述适配器,必须包含头文件<iterator>。但是这些适配器不能用在array上,因为array不支持插入操作

函数实现部分仍然是v5_filter(),只是调用方式有些不同,函数调用:

//函数调用
void play01_5()
{
	int num = 10;
	const int elem_size = 8;

	int ia[elem_size] = { 6,10,8,4,10,7,10,9 };
	vecInt vec1(ia, ia + elem_size);

	//下面这个容器用来存储过滤效果
	int ia2[elem_size];
	vecInt vec2;		//未初始化大小

	cout << "filtering int array for value less than 8:\n";
	v5_filter(ia, ia + elem_size, ia2, num, less<int>());

	cout << "\nfiltering int vector for value less than 8:\n";
	v5_filter(vec1.begin(), vec1.end(), back_inserter(vec2), num, less<int>());

	shoeVec(vec2);
}

 上述函数调用部分,使用了back_inserter()适配器。

运行结果:

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值