Accelerated C++学习笔记9—<编写泛型函数>

第八章 编写泛型函数

1、泛型函数:指在使用之前我们都不知道它的参数或返回类型是什么。语言支持泛型函数。泛型函数的语言特征被称做模板函数。模板参数让我们可以按照共同的行为特征来编写程序,即使在定义模板的时候我们并不知道与模板参数相对应的特定的类型
2、未知类型的中值
<span style="font-family:KaiTi_GB2312;">template<class T>  //模板头
T median(vector<T> v)
typedef typename vector<T>::size_type vec_sz;

vec_sz size = v.size();
if(size == 0)
throw domain_error("median of an empty vector");

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

vec_sz mid = size/2;

return size % 2 == 0 ? (v[mid] + v[mid-1]) / 2 : v[mid];
</span>

程序说明:
1)模板头告知系统环境我们定义的是一个模板函数,而且这个函数将会有一个类型参数。T命名了一个类型。
2)在vec_sz的定义中,typename的使用告知系统环境vector<T>::size_type是一个类型名

3、模板实例化
1)只有模板被实例化了,系统环境才能证实,模板代码能被用于指定的类型
2)为了对一个模板进行实例化,定义了模板的源文件和头文件必须要是可访问的。

4、泛型函数和类型
<span style="font-family:KaiTi_GB2312;">template<class T>
T max(const T& left, const T& right)
{
	return left > right ? left : right;
}</span>

程序说明:
如果我们传递两个分别为int类型和double类型的参数给这个函数,那么系统环境就无法推断出应该把哪一个参数转换成另一个参数类型,故在编译期间这个调用将不能通过。

5、数据结构的独立性
1)标准库使用迭代器来让我们对任何容器的任何连续的部分调用find
find(c.begin(), c.end(), val)这样做的目的可以只搜索容器的一部分而不是需要彻底地搜索整个容器;还有一个好处是我们可以访问到具有特殊意义的、而不存在于容器中的元素。
而不使用c.find(val)
或者find(c,val)
2)顺序只读访问
形式一:
<span style="font-family:KaiTi_GB2312;">template <class In, class X> In find(In begin, In end, const X& x)
{
	while(begin != end && *begin != x)
		++begin;
	return begin;
}</span>

形式二:
<span style="font-family:KaiTi_GB2312;">template <class In, class X> In find(In begin, In end, const X& x)
{
	if(begin == end|| *begin != x)
		return begin;
	begin++;
	return find(begin,end,x);
}</span>

3)顺序只写访问
<span style="font-family:KaiTi_GB2312;">template <class In, class Out>
Out copy(In begin, In end, Out, dest)
{
	while(begin != out)
		*dest++ = *begin++;
	return dest;
}</span>

注意:输出迭代器不能在对*it的两个赋值运算之间执行超过一次的++it的操作,同时我们也不能在没有对it进行递增的情况下对it进行多次赋值。back_inserter

4)顺序读-写访问
<span style="font-family:KaiTi_GB2312;">template <class For, class X>
void replace(For beg, For end, const X& x, const X& y)
{
	while(beg != end)
	{
		if(*beg == x)
			*beg = y;
		++beg;
	}
}</span>

注意:正向迭代器要支持*it(对于读和写); ++it 和 it++  (但不支持--it或者it--);it == j 和 it != j (在这里,j的类型和it一样);it -> member(作为(*it).member的一个替代名);所有的标准库容器都满足正向迭代器的要求。所有标准库容器都满足正向迭代器的要求。

5)可逆访问
<span style="font-family:KaiTi_GB2312;">template <class Bi> 
void reverse(Bi begin, Bi end)
{
	while(begin != end)
		--end;
	if(begin != end)
		swap(*begin++, *end);
}</span>
注意:一个类型满足了一个正向迭代器的所有要求并且还支持--(前缀和后缀),那我们就把它称作双向迭代器。所有的标准库容器都支持双向迭代器。

6)随机访问
<span style="font-family:KaiTi_GB2312;">template <class Ran, class X>
bool binary_search(Ran begin, Ran end, const X& x)
{
	while(begin < end)
	{
		//查找区间的中点
		Ran mid = begin + (end - begin) / 2;

		//看区间的哪一部分含有x;只往下查找这部分
		if(x < *mid)
			end = mid;
		else if(*mid < x)
			begin = mid + 1;
		//如果我们在这里得到了待查找的值,那么*mid == x,完成查找
		else return true;
	}

	return false;
}</span>

注意:sort要求有随机访问迭代器,向量和字符串迭代器都是随机访问迭代器,而链表迭代器则不死这样的迭代器,它只支持双向迭代器,为什么呢?
因为链表是为了快速的插入和删除而被优化,因此我们无法快速地定位到表中的任意元素,定位链表中元素的唯一办法是按顺序查看每一个元素。

7)迭代器区间和越界值
算法要用两个参数来指定区间,第一个参数指向区间的第一个元素,第二个参数则指向了紧位于区间最后的元素后面的那个位置。
注意:第二个参数必须要指向紧位于区间最后的元素后面的那个位置,而不是用一个直接指向最后的元素的迭代器来标记区间终点
1)如果区间根本没有元素,那我们就找不到一个最后的元素来标记终点;
2)如果一个迭代器(这个迭代器指向位于最后的元素之后的位置)来标记区间终点,那我们就可以仅仅为了判定相等或不相等免去比较迭代器;
3)如果我们用开头和紧位于末尾元素之后的位置来定义一个区间,那我们就能以一种自然的方式来表示“区间之外”,每一个容器
6、输入输出迭代器
如果所有的标准容器都不要求输入输出迭代器和正向迭代器之间要有差别,那么为什么还要有这两种种类呢?
一个原因是并不是所有的迭代器都是与容器相关联的。
7、用迭代器来提高适应性
以前的split返回一个vector<string>类型的向量,但是我们用户不一定想要一个向量,相反我们可能需要得到一个list<string>类型的链表或者是其他种类的容器。
测试主函数:
<span style="font-family:KaiTi_GB2312;">// lesson8_1.cpp : 定义控制台应用程序的入口点。
//功能:简单测试下split,我们改写split
//时间:2014.5.29

#include "stdafx.h"
#include <iterator>
#include <iostream>
#include <string>
#include "split.h"


using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
	string s;
	while(getline(cin,s))
		split(s, ostream_iterator<string>(cout, "\n"));

	return 0;
}
</span>
程序说明:
这个函数用split来把输入行分割成独立的单词并把这些单词写到标准输出,我们通过传递一个ostream_iterator类型的迭代器给split从而把单词写到cout,这里的迭代器会被连接到cout,当split赋值给*os的时候,实际上它是在写数据到cout中。

头文件:
<span style="font-family:KaiTi_GB2312;">#pragma once

#include <algorithm>
#include <cctype>
#include <string>

using namespace std;

inline bool space(char c)
{
	return !isspace(c);
}

inline bool not_space(char c)
{
	return !isspace(c);
}

template<class Out>
void split(const string& str, Out os)
{
	typedef string::const_iterator iter;

	iter i = str.begin();
	while(i != str.end())
	{
		//忽略前端的空白
		i = find_if(i, str.end(), not_space);

		//找到下一个单词的结尾
		iter j = find_if(i, str.end(),not_space);

		//复制在[i,j)中的字符
		if(i != str.end())
			*os++ = string(i,j); //输出我们刚刚找到的单词,子表达式*os指示了os被连接到的容器的当前位置,因为我们把值string(i,j)赋给位于这个位置的元素,完成之后对os加1

		i = j;
	}
}</span>

8、小结
迭代器:C++标准库的主要贡献是确立了一个算法设计思想,算法能够用迭代器作为算法与容器之间的粘合剂从而获得数据结构的独立性,此外,算法所用到的迭代器都要求有某些操作,我们能以这些操作为基础而分解算法,这就意味着我们可以把一个容器和能够使用这个容器的算法匹配起来。
                                                                                   ——To_捭阖_youth

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值