函数的弹性设计

函数的弹性设计

前言:
      实现函数的泛化及功能的弹性收缩是代码重用的技术基础也是编程人员应该有的编程思想,C++在语言范畴上也有很多支持其的机制。我们初窥编程时,当针对一个数据进行特定处理时,我们根据处理的流程在main函数的相应位置添相应的处理,当下一次需要对一个相同类型的另一数据处理时,我们在相应的置添加同样的代码用于处理,随着处理次数的增加,我们选择将这个功能抽象出来,这样我们就可以设计出一个对特定数据实现特定操作的函数,用于调用,从使代码更简洁,更易于理解。

      随着程序设计的深入,我们常常面对例如对另一种数据类型进行相同的处理或对多种数据进行类似的处理,我们也希望将此类问题抽象出来实现进一步的泛化,前者用模板实现后者用重载实现,事实上模板只需要在定义函数时说明一下并只需提供一个函数体,调用方法与调用其他函数一样;重载因为对数据的处理不完全相同,如针对一组输出操作,但有可能是输出一个字符串也有可能输出一数列的前两项,这样我们针对处理的参数类型和参数个数实现多个函数体。

 

以下是对一个函数的泛化过程:
1.函数最初声明
  void display(const vector<int> &vec)
  {
       cout<<...
  }
2.提供默认参数值
  设想这样一种情况,我们希望实现这样一种效果,我们默认将数据输出到屏幕

上,但我们希望可以选择将数据输出到文件中;
  void display(const vector<int> &vec, ostream &os=cout)
  {
      os<<...
  }
3.提供指针类型参数
  设想这样一种情况,我们希望可以选择是否将数据输出,也许我们程序中存在
  void display(const vector<int> &vec, ostream *os=0)
  {
      if(os)
        os<<...
  }
  比较2和3,当我们需要有选择的开启和关闭某个功能时传递指针参数,因为指

针可以不指向任何对象,但引用必须指向确定的对象;
4.模板函数
  设想这样一种情况我们希望可以选择输出字符型或整型或字符串型等等;
  template <typename elemtype>
  void display(const elemtype &, const ostream &os=cout)
  {
     os<<...
  }

5.函数重载
  设想这样一种情况,我们希望在一次调用中选择输出字符型或字符串型,或字

符串的一部分,这样需要设定不同类型的参数和参数个数;
  void display(char ch);
  void display(const string& );
  void display(const string& ,int, int);
    比较4和5,模板和函数重载代表程序设计的不同思想,模板体现程序设计的泛化,将有关类型的参数抽取出来,最终用一个函数体实现; 函数重载是将一个函数功能的弹性扩展,是一个函数能够实现多个功能,这样看来模板也是函数功能的弹性扩展,使一个函数可以操作各种类型的数据;
    我认为模板是将不同类型的数据进行相同的处理,对应一份函数体; 重载对数据的处理流程不同,对应多份函数体;二者的实现机制和效率及安全性也有不同之处,简单的说就是模板在编译时确定效率和安全性更高,重载在运行时确定;
    具体编程时我们要综合考虑函数泛化和功能的弹性级别,合理的选择模板和重载;
6.Pointer to Functions
    个人认为这里没有合适的中文翻译,有很多有关函数指针和指针函数的区别的解释,我认为英文更显得一目了然,首先是一个指针并且这个指针指向一个函数。那么Pointer to Functions和函数的弹性有什么联系呢?
    设想这样一种情况,这display()函数体中需要输出一个等差数列的前n项和;
    void display(ostream &os=cout)
    {
         os<<sum_equal_diff(n);
    }
    下一次我们需要输出一个等比数列前n项和,我们需要在定义一个函数;
    void display(ostream &os=cout)
    {
         os<<sum_equal_radio(n);
    }
    以后我们可能面对Fibonacci数列等等,因此我们希望在函数体内调用函数级别上的一个抽象概括(将调用函数参数化),这里我们就用到了我们的法宝,Pointer

to Functions;
我们的方式是把将要在函数体中调用的函数作为函数参数传递进来;
编程小技巧:
当我们要泛化某个操作时,我们可以将该操作抽象为一个函数并将其参数化;
  
  void display(int (sum_ptr)(int), ostream &os=cout)
  {
       oc<<sum_ptr(n);
  }
  具体调用的时候,我们可以定义一数组,内放Pointer to Functions,这样我们可以利用数组的索引技巧,将其用循环实现;


  int (*sum_ptr)(int)=0;

  int (*sum_array[])(int)={sum_dengcha,sun_dengbi,sum_fibonacci};
  for(int ix=0; ix!=3; ++ix)
  {
      sum_ptr=sum_array[ix];
      display(sum_ptr(n));
  }
  更进一步,我们可以利用枚举类型将整数和字符串关联起来,实现下标的辅助记忆,以更容易用Pointer to Functions调用函数,
  如enum ns_index{
    index_dengcha,index_dengbi,index_fibonacci};
  这样当我们获取等比的求和函数时就可以;
    sum_ptr=sum_array[index_dengbi];

总结:
    代码冗余不一定就应该赶尽杀绝,适当的代码冗余有利于函数的维护,因为软

件产品在整个生命周期内都有可能面对维护和修改,尤其由别人完成维护工作时

,可理解性就显的尤其重要了。
    Pointer to Functions将函数体内的一部分代码抽象出来实现函数嵌套调用的

弹性扩展,但我们的代码也变的更加晦涩,虽然通过枚举使其调用变得稍微直观

了一些但阅读起来仍很艰难。
    故当我们对某一个功能模块用函数实现时,我们需要综合运用默认参数,模板

,重载及Pointer to Functions,实现一个合适的弹性级别,并给出好的代码注

释,将函数功能,代码冗余及泛化程度达到一个黄金分割点的结合。

     
  
   

posted @ 2012-03-27 16:04  liuhao2638  阅读(439)  评论(0编辑  收藏  举报