解决问题的过程就是数据抽象的过程。对于一个函数来说,它的任务是为了完成某个特定的工作,通常我们称为功能模块。注意是特定。通常模块化是有效的,它可以更好的提高效率。那么抽象模块化可能就相当于意味着使工作不再变得特定。它将变得更加通用(也就是进一步抽象)。打个例子,这里有一个数组求和的代码,以便思考,可以运用到其他地方(例子会越来越抽象):
int sum(int * p,int n)
{
int Sum = 0;
for (int i = 0; i < n; ++i)
{
Sum += *p;
++p;
}
return Sum;
}
简单的是为这一数组求和。但是想想有什么思想能把例子更抽象化,可以适应很多类型,所以很容易想到模板:
template<class T>
T sum(T * data, int n)
{
T result = 0;
for (int i = 0; i < n; ++i)
result += data[i];
return result;
}
这里抽象了类型,也就是说只要这个T类,它只要满足以下三种操作,那么便可以使用这个求和函数:
1.以0作为对象
2.支持 [ ] 操作符
3.重载了 += 操作符
仔细想想,这个函数还是不够灵活....
这里sum函数知道3件事情:
1.它把一组数加在一起
2.它所加的数是整数,小数等(内置类型-int double float unsigned int等)
3.它所加的整数是一种特殊的方式存储了起来(数组)
那么当然,可以重写这个sum函数。
可以先分离迭代方式。
要从sum函数中移走的第一个东西 ---- 添加的元素是放在数组中,我们可以试着摆脱这种限制
可以用类来抽象这种依赖:(所以也可以容易的写出)
tempalate<class T>
class iterator
{
public:
//构造函数有两个参数,第一个元素的地址,序列的长度
iterator(T * p, int c) :data(p), len(c) {}
~iterator() {}
//如果序列中还有元素存在,则成员valid返回一直非零值
int valid() const { return len > 0; }
//取出下一个元素。只有序列中有值的情况,才会调用此函数
int next() {
--len;
return *data++;
}
iterator(const iterator&) {}
iterator& operator = (const iterator&) {}
private:
T * data;
int len;
};
那么相应的sum求和函数可以进一步简化:
template<class T>
T sum(iterator<T> ir)
{
T result = 0;
while (ir.valid())
result += ir.next();
return result;
}
虽然看起来sum对于它要添加的值所知道的唯一事情,就是参数是iterator类型,sum内部逻辑并不怎么依赖于iterator类,它只使用valid()跟next()操作。那么有什么办法使sum运用到那些并不是以数组形式存储的序列上?
所需要的是用来反映不同数据结构的不同迭代器,到现在为止,我们只能访问数组中的值。假如数据保存在链表中,或者保存在文件中该怎么办?有没有办法进一步抽象化?我们可以进一步抽象这个类(可以表示许多不同迭代器类中的任何一个):
template<class T>
class Iterator {
public:
virtual int valid() const = 0;
virtual T next() = 0;
virtual ~Iterator() {}
};
然后这里:可以认为数组类型为Array_iterator<T>是一种iterator<T>,这里用了继承
//可以认为数组访问 ----Array_iterator<T>是一种iterator<T>
template<class T> class Array_Iterator :public Iterator<T>
{
public:
Array_Iterator(T * p, int c) :data(p), len(c) {}
int valid() const { return len > 0; }
T next() {
--len;
return *data++;
}
private:
T * data;
int len;
};
然后我们再一次修改sum函数:(这里说明下,因为用到动态绑定,所以这里形参要传引用的方式)
template<class T>
T sum(iterator<T>& ir)
{
T result = 0;
while (ir.valid())
result += ir.next();
return result;
}
那么我们可以这样:
int main()
{
//求和就变成这样
int x[10];
for (int i = 0; i < 10; ++i)
x[i] = i;
//与前面的例子扩展相比,这种开销可能是昂贵的。,因为有虚函数
//能不能弥补动态绑定带来的效率问题?
//取消动态绑定???
//那就意味着在编译时候,我们需要知道集合的类型
Array_Iterator<int> it(x, 10);
cout << sum(it) << endl;
return 0;
}
到了这一步,我们是否还要把动态绑定的效率问题考虑进去??网上有些人说不用(在这里,我们先不纠结是否考虑,默认是考虑),但是也是一种思想,这里给出了解决方案。
首先我们可以取消动态绑定--这意味着在编译的时候要知道这个集合的类型 也就是知道Array_iterator类型
我们可以再一次利用模板的特性:
这里让模板接受两个参数
这里Iter --- Array_iterator,让编译器在编译的时候就确定Array_iterator类型
template<class Iter, class T> T sum(T& result,Iter ir)
{
while (ir.valid())
result += ir.next();
return result;
}
当然为了说明这种方法的灵活性,可以利用sum对一个从istream中生成的,数量不限的数字集合来求和。
首先需要一个当作迭代器的类。当然,这个类跟之前Array_iterator的类有着明显的差别。这里称为Reader.
template<class T> class Reader
{
public:
Reader(istream& is) :i(is) { advance(); }
int valid() const { return status; }
T next() {
T result = 0;
advance();
return result;
}
private:
istream& i;
int status;
T data;
void advance()
{
i >> data;
status = i != 0;
}
};
注意这里不同于以往,没有使用继承,因为动态绑定被取消了,编译器在编译的时候就已经确定了类型,所以动态绑定的存在变得不再有意义。
int main()
{
cout << "输入数字:" << endl;
double r = 0;
sum(r, Reader<double>(cin));
return 0;
}
当然这种设计忽略了很多细节,比如可以把sum的参数改成迭代器,然后抽象迭代器,让迭代器提供统一的数据访问接口。