在C语言中实现泛型编程

0x00 泛型编程概述

  • 泛型编程是一个很是常见的编程方式。主要目的是实现静态联编,使得函数能够接受不一样类型的参数,而且在编译的时候肯定正确的类型。
  • 不少语言都对泛型编程提供了支持,好比在C++中可使用函数模版和类模版来实现泛型编程;在Java、Objective-C或者C#等单根继承的语言中,也可使用相似java.lang.Object、NSObject等类型进行编程。在具备类型推断功能(好比Swift)的编程语言中,更是能够直接使用泛型编程。
  • 不过C语言是高级语言编程的基础语言,那如何在C语言中实现泛型编程,确实是一个问题。首先C语言不支持函数重载,不支持模版类型,因此实现起来确实比较困难。

0x01 泛型指针(void *)简介

  • void *是C语言中的一种类型,你们都知道在大多数编程语言中,void类型都表明所谓的空类型,好比一个函数的返回一个空类型void ,这是很常见的用法。
注意:返回值为 void 并非没有返回值,而是表明返回空类型,这就是你仍然能够在这些函数中使用 return语句的缘由。只有一些语言的构造函数和析构函数才没有返回值,在这些函数中,不可使用 return语句,他们是有显著的不一样的,Objective-C是一门独特的语言,它的类的初始化方法是一个普通方法,返回值是 instancetype(当前类的指针类型)类型。
  • void *可能就稍微不为人知一些,void *在C语言中能够表示人任意类型的指针。毕竟对于内存单元的地址而言,所谓它存储的数据类型,只是每次取出的字节数不一样而已,这些内存单元的地址自己并无什么不一样。下面会更好的体现这句话的含义。
  • void *的大小和普通类型的指针同样,老是一个字,具体的大小因机器的字长而异,例如对于32位机器是4个字节,对于64位机器是8个字节。
我没有考证过16位的8086机器上指针的大小,由于8086的地址是20位的,这个有兴趣的话能够回去试一试。

我的认为指针的大小仍然是16位,由于20位是物理地址,而物理地址是由段地址和偏移地址计算出的,在汇编以后C语言的指针可能只是变成相对于段地址的偏移地址,毕竟对于8086而言数据通常老是在DS段中,而代码通常老是在CS段中。(斜体字表明还没有考证的说法)java

  • 在C语言中,其余普通类型的指针能够自动转换为void *类型,而void *类型通常只能强制转换为其余普通类型的指针,不然会出现警告或错误。
  • 有一个特别大的坑就是关于所谓void *指向数组的状况,这里直接上代码解释了。
void Swap(void *array, int x, int y, int mallocsize) {
    void *temp = malloc(mallocsize);
    memcpy(temp, array+mallocsize*x, mallocsize);
    memcpy(array+mallocsize*x, array+mallocsize*y, mallocsize);
    memcpy(array+mallocsize*y, temp, mallocsize);
    free(temp);
}
  • 这是一个比较经典的交换函数,借助的是临时变量temp,可是这个函数是泛型的,对于memcpy的使用稍后会介绍。须要注意的是,array指向一个数组的话,不能直接用&array[x]或者array+x得到指向第x个元素的地址,由于void *类型默认的指针偏移量是1,和char *是相同的,这对于绝大多数类型来讲都会出现错误。因此在使用的时候必须知道该泛型类型原来所占的长度,咱们须要一个名为mallocsizeint类型形参来告诉咱们这个值,在计算指针偏移的时候乘以它。这就至关于C++编程中的模版类型定义或者Java中的泛型参数了。
  • 同时要注意对于void *类型的指针,任什么时候候都不能够对其进行解引用运算(或者在课堂上老师习惯叫作“取内容”?),缘由是显然的:void类型的变量并不合法。因此若是想进行解引用运算,必须先将其转换为普通类型的指针。用于数组的时候还须要注意解引用运算符的优先级是高于加法的,因此要加括号,好比这样:
int a = *(array + mallocsize * x);
  • 这句代码完美的体现了C语言编程的丑陋。

0x02 sizeof运算符简介

  • sizeof运算符相信学过C语言的朋友都不会陌生,可是sizeof是一个运算符估计就没多少人知道了,返回的类型是size_t类型。sizeof运算符返回某个类型所占用的空间大小。这里只说一点就是,若是对一个指针类型或者数组名(实际上数组名就是指针常量嘛)求sizeof的话,返回结果老是一个字(见上面所述)。而对一个结构体类型求sizeof,并非简单的将结构体中各个类型的sizeof求和获得,而是要涉及到内存对齐问题,这里很少作介绍了,详细了解能够访问:如何理解 struct 的内存对齐? - 知乎

0x03 memcpy函数简介

  • memcpy是一个常常和void *配合使用的函数,其函数原型为:
void * memcpy(void *, const void *, size_t);
  • 所属的头文件为string.h,你们也看出来了,这个函数自己就是以void *类型做为参数和返回值,其实也很好理解,就是一个赋值的过程,进行内存拷贝。把第二形参指向的内存拷贝到第一形参,拷贝的字节数由第三形参指定。固然了第三个参数通常经过sizeof运算符求出,这里就不举例子了。返回值我没有研究过,也没用过,若是有知道的朋友能够评论区交流。

0x04 C语言中实现泛型编程

  • 说了这么多,还没提到泛型编程。不过前面也提的差很少了,整体思想就是使用void *类型看成泛型指针,而后再辅以相似于mallocsize的参数指定所占内存大小,所占内存大小经过sizeof运算符求得,若是须要进行赋值的话,利用memcpy函数完成,下面就直接给一个例子出来,是泛型的快速排序算法,说明这些问题:
#ifndef Compare_h
#define Compare_h

#include <stdio.h>
#include "JCB.h"

int IsGreater(void *x, void *y);
int IsGreaterOrEqual(void *x, void *y);
int IsSmaller(void *x, void *y);
int IsSmallerOrEqual(void *x, void *y);

#endif /* Compare_h */
//
//  Compare.c
//  Job-Dispatcher
//
//  Created by 路伟饶 on 2017/11/16.
//  Copyright © 2017年 路伟饶. All rights reserved.
//

#include "Compare.h"

int IsGreater(void *x, void *y) {
    return *(int *)x > *(int *)y;
}
int IsGreaterOrEqual(void *x, void *y) {
    return *(int *)x >= *(int *)y;
}
int IsSmaller(void *x, void *y) {
    return *(int *)x < *(int *)y;
}
int IsSmallerOrEqual(void *x, void *y) {
    return *(int *)x <= *(int *)y;
}
//
//  QuickSort.h
//  Job-Dispatcher
//
//  Created by 路伟饶 on 2017/11/16.
//  Copyright © 2017年 路伟饶. All rights reserved.
//

#ifndef QuickSort_h
#define QuickSort_h

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Compare.h"

void QuickSort(void *array, int left, int right, int mallocsize);

#endif /* QuickSort_h */
//
//  QuickSort.c
//  Job-Dispatcher
//
//  Created by 路伟饶 on 2017/11/16.
//  Copyright © 2017年 路伟饶. All rights reserved.
//

#include "QuickSort.h"

void Swap(void *array, int x, int y, int mallocsize) {
    void *temp = malloc(mallocsize);
    memcpy(temp, array+mallocsize*x, mallocsize);
    memcpy(array+mallocsize*x, array+mallocsize*y, mallocsize);
    memcpy(array+mallocsize*y, temp, mallocsize);
    free(temp);
}

int QuickSortSelectCenter(int l, int r) {
    return (l+r)/2;
}
int QuickSortPartition(void *array, int l, int r, int mallocsize) {
    int left = l;
    int right = r;
    void *temp = malloc(mallocsize);
    memcpy(temp, array+mallocsize*right, mallocsize);
    while (left < right) {
        while ( IsSmallerOrEqual(array+mallocsize*left, temp) && left < right) {
            left ++;
        }
        if (left < right) {
            memcpy(array+mallocsize*right, array+mallocsize*left, mallocsize);
            right--;
        }
        while ( IsGreaterOrEqual(array+mallocsize*right, temp) && left < right) {
            right--;
        }
        if (left < right) {
            memcpy(array+mallocsize*left, array+mallocsize*right, mallocsize);
            left ++;
        }
    }
    memcpy(array+mallocsize*left, temp, mallocsize);
    return left;
}

void QuickSort(void *array, int left, int right, int mallocsize) {
    if (left>=right) {
        return;
    }
    int center = QuickSortSelectCenter(left, right);
    Swap(array, center, right, mallocsize);
    center = QuickSortPartition(array, left, right, mallocsize);
    QuickSort(array, left, center-1, mallocsize);
    QuickSort(array, center+1, right, mallocsize);
}
  • 这里留了一个悬念,明明能够直接比较的,为何还要这么麻烦使用好多函数完成,也就是关于Compare.h的用处的问题,下面会揭晓答案。

0x05 泛型的协议问题

  • 刚刚那个问题就涉及到了一个泛型的协议问题,我这里是借用了Objective-C 中的一个概念去阐述。就像刚刚那个问题,既然个人快速排序是泛型的,那么怎么保证明际传入泛型参数必定是可比较的呢?举个例子,显然intfloatdouble是能够进行比较的,char使用ASCII编码方案的比较咱们也理解,String类型甚至也是能够比较的。可是若是在其余语言中,对象之间如何进行比较呢?这就是个问题了。在C++中咱们能够进行运算符重载,这样就仍旧可使用比较运算符,借助运算符重载函数来完成。不过对于Java、Objective-C这种语言该怎么办?并且若是传入的泛型参数没有实现对应的运算符重载函数怎么办?这时候就要引入一个协议的概念,简单来讲就是,若是某个类型想要做为排序泛型函数的泛型参数,那你必须实现可比较的协议。这个协议在Swift语言中就称为Comparable,这样的话在编译的时候,编译器才知道这个泛型参数是能够进行比较的,这样才能完成咱们的操做,不然的话就会出现错误。这就是泛型中的协议问题。

0x06 总结

  • C语言的泛型编程以void *做为泛型类型,本质上是泛型指针。
  • C语言的泛型编程须要知道一个泛型类型变量所占的内存大小,这个能够经过sizeof求得并传入泛型函数。
  • C语言的泛型编程中要注意数组的偏移问题,void *的默认偏移是1,对于绝大多数类型来讲都是错误的,须要自行编程转换。
  • C语言的泛型编程中使用memcpy函数进行泛型变量的拷贝和赋值。
  • C语言的泛型编程中也须要注意协议问题,可是C中就只能自行编写函数进行定义了,在其余语言中可使用现成的接口或者协议。
相关文章
相关标签/搜索