C语言在 stdlib.h
头文件中提供了 qsort
函数,其原型如下:
void qsort( void *ptr, size_t count, size_t size,
int (*comp)(const void *, const void *) );
ptr
- pointer to the array to sort
count
- number of elements in the array
size
- size of each element in the array in bytes
comp
- comparison function which returns a negative integer value if the first argument is less than the second, a positive integer value if the first argument is greater than the second and zero if the arguments are equivalent.
但是C中所有的变量都需要有一个确定的类型,函数的形参也需要有一个确定的类型。 qsort
“似乎”可以绕开这个限制,对 int
double
char
甚至自己定义的 struct
进行比较。换言之, qsort
具有了泛型的效果。
泛型程序设计(generic programming)是程序设计语言的一种风格或范式。**泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。**各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。Ada、Delphi、Eiffel、Java、C#、F#、Swift 和 Visual Basic .NET 称之为泛型(generics);ML、Scala 和 Haskell 称之为参数多态(parametric polymorphism);C++ 和 D称之为模板。具有广泛影响的1994年版的《Design Patterns》一书称之为参数化类型(parameterized type)。 ——维基百科
泛型可以使函数接受不同类型的变量,不用再对每种类型的变量都定义一个函数。C语言中并没有原生提供泛型。 qsort
却实现了类似泛型的效果——这便是使用 void *
曲线救国的结果。
void
类型指针在各类C语言教程上,介绍指针时都会提及各种指针变量的类型。除了常见的 int *
double *
,还会提到 void *
。任何类型的指针都可以显式转换为 void *
, void *
可以显式转换为任何类型的指针。借助这一特性,我们可以让函数形参为 void *
来间接接受不同类型的参数。
比如:
//ptrdiff_t 用于储存指针间的距离
//定义在头文件 stddef.h 中
#include <stddef.h>
ptrdiff_t Foo(void* a, void* b)
{
return (a - b);
}
这样一个简单的函数可以计算任意两个指针之间的距离,无论它们是什么类型的指针。
int I1 = 123, I2 = 456;
double D1 = 114.514, D2 = 1919.810;
ptrdiff_t re1 = Foo(&I1, &I2);
ptrdiff_t re2 = Foo(&D1, &D2);
re1
的结果可能与 &D1-&D2
不同,这是因为 &D1-&D2
计算的是 D1
所在的地址与 D2
所在的地址差了多少个 sizeof(int)
,但 re1
返回的单位直接是byte。 re2
同理。
这就发现了一个问题——从 Foo
函数内部的角度看, Foo
只知道传入了一个 void *
类型的指针,并不知道这个指针实际指向的变量类型。
int *
类型的指针经过类型转换并进入 Foo
函数内部时,指针的类型变为了 void *
。此时, int *
所携带的信息,比如指向的变量类型、指针长度等等都丧失了。