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. Python语言编写脚本时,对日期控件的处理方式

    对日期控件,日期控件的输入控一般是不能手动输入的:把readonly属性去掉就好 其实很简单,我们不去搞时间日期空间,我们把它当成一个普通的input框处理就好了! 但是,很多此类型input框都是禁 ...

  2. Sql Server 查询今天,昨天,近七天....数据

    今天数据: 昨天数据: 7天内数据: 30天内数据: 本月数据: 本年数据: 查询今天是今年的第几天: select datepart(dayofyear,getDate()) 查询今天是本月的第几天 ...

  3. C/C++基础知识:函数指针和指针函数的基本概念

    [函数指针] 在程序运行中,函数代码是程序的算法指令部分,它们和数组一样也占用存储空间,都有相应的地址.可以使用指针变量指向数组的首地址,也可以使用指针变量指向函数代码的首地址,指向函数代码首地址的指 ...

  4. Github使用技巧总结

    <config> PyCharm与GitHub配置使用总结 <readme> 在github的readme添加图片 github readme写法 GitHub上README. ...

  5. 科技庄园(背包dp)---对于蒟蒻来说死了一大片的奇题

    题目描述: Life种了一块田,里面种了一些桃树. Life对PFT说:“我给你一定的时间去摘桃,你必须在规定的时间之内回到我面前,否则你摘的桃都要归我吃!” PFT思考了一会,最终答应了! 由于PF ...

  6. 有关nmap的5个常用的扫描指令

    [以下IP可替换成需要被测试的IP网段] 1.ping扫描:扫描192.168.0.0/24网段上有哪些主机是存活的: nmap -sP 192.168.0.0/24   2.端口扫描:扫描192.1 ...

  7. k短路模板

    https://acm.taifua.com/archives/jsk31445.html 链接: https://nanti.jisuanke.com/t/31445 #include <io ...

  8. Erasing and Winning UVA - 11491 贪心

    题目:题目链接 思路:不难发现,要使整体尽量大,应先满足高位尽量大,按这个思路优先满足高位即可 AC代码: #include <iostream> #include <cstdio& ...

  9. 使用像AdminLTE的前端框架,树形导航菜单实现方式都有哪些?

    之前用easyui等富前端框架开发的时候都是使用封装好的县城的插件,现在使用最新的类似AdminLTE似的前段框架实现树形菜单都用什么方式? 后台拼接html然后前端用JS append方法添加还是直 ...

  10. HDU 3376 费用流 Matrix Again

    题意: 给出一个n × n的矩阵,每个格子中有一个数字代表权值,找出从左上角出发到右下角的两条不相交的路径(起点和终点除外),使得两条路径权值之和最大. 分析: 如果n比较小的话是可以DP的,但是现在 ...