C语言是简洁的强大的,当然也有很多坑。C语言也是有点业界良心的,至少它实现了2个最最常用的算法:快速排序和二分查找。

我们知道,对于C语言标准库 qsort和 bsearch:

a. 它是“泛型”的,可以对任何类型进行排序或二分。

b. 我们使用时必须自定义一个比较函数当作函数指针传入。

c语言要实现泛型,基本上就只有 void指针提供的弱爆了的泛型机制,容易出错。

这篇文章中,我实现了 标准库qsort和bsearch函数,最基本的正确性和泛型当然要保证了。

在这里,不涉及优化(写标准库实现的那帮人恨不得用汇编实现),只展现算法的运行原理和泛型的实现机制。

1.C语言标准库qsort源码实现。我先呈上完整实现,然后具体剖析。

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h> void swap(const void* a, const void* b, int size)
{
assert(a != NULL && b != NULL);
char tmp = ;
int i = ;
while (size > ) {
tmp = *((char*)a + i);
*((char*)a + i) = *((char*)b + i);
*((char*)b + i) = tmp;
++i;
--size;
}
} void Qsort(void* base, int left, int right, int size, int (*cmp)(const void* a, const void* b))
{
assert(base != NULL && size >= && cmp != NULL); /* left may be < 0 because of the last - 1 */
if (left >= right) return;
char* pleft = (char*)base + left * size;
char* pkey = (char*)base + (left + (right - left) / ) * size;
swap(pleft, pkey, size);
int last = left;
char* plast = (char*)base + last * size;
for (int i = left + ; i <= right; ++i) {
char* pi = (char*)base + i * size;
if (cmp(pi, pleft) < ) {
++last;
plast = (char*)base + last * size;
swap(pi, plast, size);
}
}
swap(pleft, plast, size);
Qsort(base, left, last - , size, cmp);
Qsort(base, last + , right, size, cmp);
} int cmp_string(const void* a, const void* b)
{
assert(a != NULL && b != NULL);
const char** lhs = (const char**)a;
const char** rhs = (const char**)b;
return strcmp(*lhs, *rhs);
} int cmp_int(const void* a, const void* b)
{
assert(a != NULL && b != NULL);
const int* lhs = (const int*)a;
const int* rhs = (const int*)b;
if (*lhs < *rhs) {
return -;
} else if (*lhs == *rhs) {
return ;
} else {
return ;
}
} int main(int argc, char* argv[])
{
int a[] = {-, , , , , , , , , };
int len1 = sizeof(a) / sizeof(a[]);
fprintf(stdout, "before sort:\n");
for (int i = ; i < len1; ++i) {
fprintf(stdout, "%d ", a[i]);
}
fprintf(stdout, "\n"); Qsort(a, , len1 - , sizeof(a[]), cmp_int);
fprintf(stdout, "after sort:\n");
for (int i = ; i < len1; ++i) {
fprintf(stdout, "%d ", a[i]);
}
fprintf(stdout, "\n"); const char* b[] = {"what", "chenwei", "skyline", "wel", "dmr"};
int len2 = sizeof(b) / sizeof(b[]);
fprintf(stdout, "before sort:\n");
for (int i = ; i < len2; ++i) {
fprintf(stdout, "%s-->", b[i]);
}
fprintf(stdout, "\n"); Qsort(b, , len2 - , sizeof(b[]), cmp_string);
fprintf(stdout, "after sort:\n");
for (int i = ; i < len2; ++i) {
fprintf(stdout, "%s-->", b[i]);
}
fprintf(stdout, "\n");
}

qsort基本实现在K&R里有非常详细的描述,我这里重点解释的是 swap函数,这个函数是实现泛型的基石。

首先对qsort函数原型说明几点:

a.Qsort函数原型里面没有标准库qsort的 len, 而是使用 left 和 right 来指明每次待排序的区间,用两个值来表示一个区间非常直观且优雅。

b.对于数据类型大小,我没有使用 size_t 无符号类型。

C语言中无符号类型虽然可以对数组提供负向越界保证和2倍空间,但是由于坑爹的类型提升规则滋生了N多的bug,我是尽量少用这个。

然后就是 swap函数.

首先我们知道 数据元素都是以 比特位 形式暂存在 内存中的,后简化为以 字节 形式暂存在内存中。

我们平常的一个 swap 函数,如果交换的数据是 int 型, 我们就是:

void swap(int* a , int* b) {
int tmp = *a;
*a = *b;
*b = tmp;
}

现在我们不知道元素是什么类型,那我们怎么做到泛型呢? 首先 传入的两个指针肯定是 void 指针,如何确定 void所指元素类型呢? 其实我们这里是不需要知道 元素类型的。

我们这里的目的是什么? 我们就是要 交换两个元素的 字节序列,我们每次交换一个字节,直到交换完为止,这样就达到目的了,这时我们就需要第三个参数:待排元素的所占字节数。

我们知道C语言中结构体是可以直接复制的,比如:

struct test {
int a;
char b;
};
struct test a, b;
a = b;

C语言是怎样支持这种直接复制呢? 其实最终就是 两个元素的字节序列的复制。注意到,如果struct里面有指针,那么这个结构体是引用语义的,两个元素的指针成员指向同一内存。

我们这里的swap函数其实就是 手工模拟了 类似结构体复制 的整个过程。我们每次交换一个 字节,总共交换的次数就是 元素的sizeof大小。

注意两点:

a. 数据的字节序列是有 大端小端之分的,我的这个实现是不能 跨大端小端的。如果排序过程都始终都在同一机器中,那么无需担心。

b. 数据的字节序列可能是有 内存对齐的,主要是在结构体之中。 所以我的这个实现受 机器 内存对齐规则的影响,但是 排序发生在同一机器中的话,这个是不会影响正确性的。

总而言之,代码实现受机器底层 数据大小端表示和 内存对齐策略 的影响。但是实际上 一个排序过程是不可能 一部分进行在这台机,另一部分在另外一台机器上进行的(仅考虑单机),所以我的这个实现是可以很好工作的。

Qsort的快排思想就很简单了,我们最需要注意的就是 每次对 交换元素的首字节地址进行更新,我们都是经由char*转换,因为char*所指元素正好1字节,正好模拟每次一字节的swap.

2.C语言标准库bsearch源码实现

void* Bsearch(void* base, int len, int size, const void* key, int (*cmp)(const void* a, const void* b))
{
assert(base != NULL && len >= && size >= && cmp != NULL);
int low = ;
int high = len - ;
while (low <= high) {
int mid = low + (high - low) / ;
char* pmid = (char*)base + mid * size;
if (cmp(pmid, key) < ) {
low = mid + ;
} else if (cmp(pmid, key) > ) {
high = mid - ;
} else {
return pmid;
} }
return NULL;
} int cmp_int(const void* a, const void* b)
{
assert(a != NULL && b != NULL);
const int* lhs = (const int*)a;
const int* rhs = (const int*)b;
if (*lhs < *rhs) {
return -;
} else if (*lhs == *rhs) {
return ;
} else {
return ;
}
} int cmp_string(const void* a, const void* b)
{
assert(a != NULL && b != NULL);
const char** lhs = (const char**)a;
const char** rhs = (const char**)b;
return strcmp(*lhs, *rhs);
} int main(int argc, char* argv[])
{
int a[] = {-, , , , , , , , , };
int len1 = sizeof(a) / sizeof(a[]); int tmp = ;
int* res1 = (int*)Bsearch(a, len1, sizeof(a[]), &tmp, cmp_int);
if (res1 != NULL) {
fprintf(stdout, "found it\n");
} else {
fprintf(stdout, "Not found\n");
} const char* str[] = {"chenwei", "dmr", "skyline", "wel", "what"};
int len2 = sizeof(str) / sizeof(str[]);
const char* p = "chenwei";
char* res2 = (char*)Bsearch(str, len2, sizeof(str[]), &p, cmp_string);
if (res2 != NULL) {
fprintf(stdout, "found it\n");
} else {
fprintf(stdout, "Not found\n");
}
return ;
}

欢迎大家批评指正,共同学习。转载请注明出处,谢谢。

C语言标准库 qsort bsearch 源码实现的更多相关文章

  1. python 协程库gevent学习--源码学习(一)

    总算还是要来梳理一下这几天深入研究之后学习到的东西了. 这几天一直在看以前跟jd对接的项目写的那个gevent代码.为了查错,基本上深入浅出了一次gevent几个重要部件的实现和其工作的原理. 这里用 ...

  2. python语言线程标准库threading.local源码解读

    本段源码可以学习的地方: 1. 考虑到效率问题,可以通过上下文的机制,在属性被访问的时候临时构建: 2. 可以重写一些魔术方法,比如 __new__ 方法,在调用 object.__new__(cls ...

  3. requests库核心API源码分析

    requests库是python爬虫使用频率最高的库,在网络请求中发挥着重要的作用,这边文章浅析requests的API源码. 该库文件结构如图: 提供的核心接口在__init__文件中,如下: fr ...

  4. 第三方库Mantle的源码解析

    Mantle是一个用于简化Cocoa或Cocoa Touch程序中model层的第三方库.通常我们的应该中都会定义大量的model来表示各种数据结构,而这些model的初始化和编码解码都需要写大量的代 ...

  5. 2018-08-27 使用JDT核心库解析JDK源码后初步分析API命名

    源自术语词典API项目 · Issue #85 · program-in-chinese/overview, 打算先用早先的代码提取JDK API中的类/方法/参数名, 看看有哪些词需要翻译. 源码在 ...

  6. AndroidStudio导入第三方开源库 --文件夹源码

    1 在已打开的项目中  File-New-ImportModule 选择开源项目中的 库所在文件夹比如 library文件夹 然后导入. 2 File-Project  Sructure  在Modu ...

  7. qsort.c源码

    /* 版权所有(C) 1991-2019 自由软件资金会. 该文件属于是GUN C语言函数库,由Douglas C. Schmidt(schmidt@ics.uci.edu)所写. GUN C语言函数 ...

  8. Mysql依赖库Boost的源码安装,linux下boost库的安装

      boost‘准标准库’安装过程.安装的是boost_1_60_0. (1)首先去下载最新的boost代码包,网址www.boost.org. (2)进入到自己的目录,解压: bzip2 -d bo ...

  9. 如何快糙好猛的使用Shiqi.Yu老师的公开人脸检测库(附源码)

    前言 本次编写所用的库为于仕祺老师免费提供的人脸检测库.真心好用,识别率和识别速度完全不是Opencv自带的程序能够比拟的.将其配合Opencv的EigenFace算法,基本上可以形成一个小型的毕业设 ...

随机推荐

  1. C09 指针

    目录 指针相关概念 指针变量 null指针 指针的算术运算 指针数组 指向指针的指针 传递指针给函数 从函数返回指针 指针相关概念 变量 如果在程序中定义了一个变量,在对程序进行编译时,系统就会为这个 ...

  2. PhoneGap+JQuery Mobile移动应用开发学习笔记

    最近一直在学习使用PhoneGap+JQuery Mobile的开发框架开发Android应用,抛开这个框架的运行效率不说,暂且将使用中遇到的问题进行一下整理. 1.JS文件引用顺序 也许在进行web ...

  3. iOS跳转到各种系统设置界面

    定位服务 定位服务有很多APP都有,如果用户关闭了定位,那么,我们在APP里面可以提示用户打开定位服务.点击到设置界面设置,直接跳到定位服务设置界面.代码如下: //定位服务设置界面 NSURL *u ...

  4. 当c++遇上音乐

    运用到的函数 #include <windows.h> Beep( f, t ); Sleep( t ); eep() 函数可以让蜂鸣器发出频率为f赫兹,音长大约为 2t 毫秒的音.(注意 ...

  5. 根据参数优化nginx的服务性能

    一.优化nginx服务的worker进程数 在高并发.高访问量的Web服务场景,需要事先启动好更多的nginx进程,以保证快速响应并处理大量并发用户的请求. 1).优化nginx进程对应的配置 优化n ...

  6. Python 成长之路

    Python roadmap python 基础 ... 内置常用函数.三元运算.递归 迭代器和生成器 模块和常用的模块 面向对象 对向对象进阶 网络编程 并发编程 ... 数据库 MySQL pym ...

  7. LeetCode(220) Contains Duplicate III

    题目 Given an array of integers, find out whether there are two distinct indices i and j in the array ...

  8. Letters CodeForces - 978C (二分)

    Time limit4000 ms Memory limit262144 kB There are nn dormitories in Berland State University, they a ...

  9. HDU 4781 Assignment For Princess 构造

    题意: 构造一个\(N(10 \leq N \leq 80)\)个顶点\(M(N+3 \leq M \leq \frac{N^2} {7})\)条边的有向图,要满足如下条件: 每条边有一个\([1,M ...

  10. myeclipse中hibernate生成映射文件

    在hibernate中,每个数据表对应的其实是一个实体类,每个实体类有一个对应的hbm.xml配置文件匹配,myeclipse中有个MyEclipse Database Explorer视图,它提供了 ...