天天看点

C和C++中的一点泛化技巧

 首先讲回调机制。回调机制,C中用函数指针,C++中用函数对象。

考虑一个比较大小的函数,当然,这个例子比较傻

C中:

int cmp(void* a,void* b,int (*fun)(const void*,const void*)) 

{

return fun(a,b);

}

我希望cmp可以用于任意类型的比较。第三个参数传函数指针,这就是C语言的回调机制。真正做事的就是用fun函数。为了完成比较任务,自己写用于具体类型比较的代码,比如:

int f(const void* a,const void* b)

{

if(*(int*)a>*(int*)b) return 1;

else

return *(int*)a<*(int*)b?(-1):0;

}

这样调用:

int a=6,b=3;

cmp((void*)&a,(void*)&b,f);

如此就实现了任意类型的比较,当然我们自己为任意其它类型写比较函数。这种技巧可以在C库函数qsort看到。原型是:

void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );

第四个参数就是传一个用于比较的函数指针。以实现泛化的比较。

再看C++:

template <class T,class op>

int cmp(const T& a,const T& b,op f) 

{

return f(a,b);

}

C++中我们还是可以像C哪样写个用于回调的类似函数:

int f(int a,int b)

{

if(a>b) return 1;

else

return a<b?(-1):0;

}

这样调用:

int a=6,b=3;

comp(a,b,f);

模板机制会将第三个参数翻译成函数指针。不过C++不鼓励这样。因为C++有自己的机制--函数对象。

函数对象是提供了operator()的一个类。它是一个类,但用法却类型函数。

class op

{

public:

int operator() (int a,int b)

{

if(a>b) return 1;

else

return a<b?(-1):0;

}

};

op就是一个函数对象了。完成和C语言中函数f一样的功能,即比较。

C和C++都是强类型语言。可以看出来,C和C++中为了解决不同类型(实现通用类型)的比较,都使用了一定的语言上的技巧。C++是用模板。C语言是用void*的通用性。void*可以强制类型转化为任意类型的指针。

上面实现是回调机制相关的。C++与C谁优谁劣呢?这让我们进入下一个讨论主题:默认参数。

为了更加的通用,通过带默认参数的方式就可以少写一个参数了。

先看C。C语言本身不提供默认参数支持,要实现又需要一些技巧。还是上面例子,改一下。

int cmp(void* a,void* b,int (*fun)(const void*,const void*)) 

{

if(fun==NULL)  fun=f;

return fun(a,b);

}

函数f由默认提供。这样当我们是比较int型大小,就可以把第三个参数直接写0了.这个技巧就可以实现默认参数了。很多C库函数中可以看到这个技巧的应用,比如说

int setvbuf( FILE *stream, char *buffer, int mode, size_t size );

如果第二个参数传NULL,就默认由系统帮我们申请和管理缓冲区了。

再看C++:

C++中一方面提供函数的默认参数,我们可以直接写

int cmp(void* a,void* b,int (*fun)(const void*,const void*)=f)

{

return fun(a,b);

}

这样调用int a=6,b=3;

cmp(&a,&b);   //可以省略第三个参数,相比C语言,连传NULL指针都不必了

C++对默认参数的支持比C进了一大步。关键还不是这里。C++中,类模板也可以提供默认的模板参数。这种模板默认参数的使用使得C++模板变得威力无穷。但也同时是太过灵活的模板,使C++变得难以控制。下面看个实际例子:

void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );

template<class RanIt, class Pred> void sort(RanIt first, RanIt last, Pred pr);

STL的sort函数与C语言的qsort是一个很好的对比。sort传二个iterator就够了。而qsort却要传一大堆的参数.

sort通过默认参数减掉了用于比较的函数。再通过iterator traits技巧及模板推演又减掉了类型参数。

qsort没这些技巧,一个参数都不能少。这样一比较上面我提出的问题就很明白了:通过语言技巧的完美利用,C++可以写得相当优美,而C丑陋得多。默认参数让C++威力无穷!还有,C语言虽然利用void*通用性实现了一定程度的泛化,但却失去了强类型检验的部分好处。如果使用都不洽当使用,会使问题变得糟糕很多。而C++在泛化的同时没有太多失去类型安全检查。事物都有二面性。C++的模板机制也带来了很多问题。模板很C++成了一问很复杂的语言。光是语法就极难驾驭。而有些C++吹捧的狂热分子使问题再严重。iterator_traits,模板的模板参数,泛型编程,模板元编程.....C++走向极端。

关于这些语言上的是是非非,我不再深入去研究。模板好不好?自己在使用中去慢慢体会。

前面讲了回调机制,默认参数,下面我来谈第三个问题,最后一个问题,泛化技巧的实现本质。

C在泛化上的本质,就是利用void*可以向任意类型转换。C++在泛化的本质上,就是模板机制。C++的模板,会为所有类型都生成一个函数。相当于把用于一种类型比较的代码Ctrl+c Ctrl+v了一遍。当然,编译器在上述过程有些优化。模板没有增加运行时开销,但增加程序的代码段的长度。相比之下C的泛化能力弱多了。void*失去了类型安全的优势。所能做的也有限。回调机制增加了灵活性,增加了少量的运行切换开销。不带来其它损失。

大致就是这些了。我刚接触C语言,还在学习中。还是我以前说过的,多少优秀的语言也无法阻止垃圾的程序员写出糟糕的代码。C++我驾驭不了,现学习C语言中....