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 * 类型的指针,并不知道这个指针实际指向的变量类型。

转换与退化

Untitled

int * 类型的指针经过类型转换并进入 Foo 函数内部时,指针的类型变为了 void * 。此时, int * 所携带的信息,比如指向的变量类型、指针长度等等都丧失了。