编写规范

语言风格

Google 风格:

微软风格:

注意事项

C 语言由于其特性,不建议作为日常使用语言,如果有底层开发需求,去使用 C++ or Rust。

除非你是嵌入式开发或者性能紧张场景,如果这样建议你看看下面文章,恐怕这类场景对于安全性要求也是很高的:

还有一些好习惯推荐:

语言要求

main

任何 C 程序必须从 main 函数开始

int main(int argc, char* argv[]){
} // 函数入口强制定义argc表示参数个数,包括自己。argv表示每个命令字符串

同时 main 不一定是第一个函数, 在一些调试检测需求下, 以下是有可能会出现的:

编译预处理

宏定义

// 替换
#define a 10
#define b 20+a
b * 2 = 20 + 10 * 2 // 要达到先加后乘的效果应该是把b定义为(20+a)
  
// 字符串定义
#define STR(s) #s 

STR(hello)      → "hello"
STR(123 + 456)  → "123 + 456"

// 拼接
#define CAT(x, y) x##y 

CAT(hello, world)  → helloworld
CAT(var, 123)      → var123

注意点:

常用预处理指令

#include <> #include "" // 一个先找库,一个先找当前目录
#if / #ifdef / #ifndef / #elif / #else / #endif // 根据条件来选择是否编译该段代码
#error       // 编译时直接报错并终止
#warning     // 编译时输出警告但不终止
#pragma once // 只允许引用一次,用来避免重复引用
#pragma pack // 控制结构体内存对齐
_Static_assert(sizeof(int) == 4, "int must be 4 bytes"); // C11 引入,编译期断言

类型

基础类型

整型

因为 C 语言历史发展的缘故,它的整形数据结构大小不同平台不一致,下表:

类型标准最小宽度 / 最小范围32 位 ILP32(典型 x86 Linux/Win32)64 位 LP64(Linux/macOS x86_64, aarch64)64 位 LLP64(Windows x64)
char至少 8 位;范围为 signed charunsigned char1 字节;-128~1270~2551 字节;-128~1270~2551 字节;-128~1270~255
signed char≥ 8 位;至少 -127 ~ +1271 字节;通常 -128~1271 字节;通常 -128~1271 字节;通常 -128~127
unsigned char≥ 8 位;至少 0 ~ 2551 字节;0~2551 字节;0~2551 字节;0~255
short≥ 16 位;至少 -32767 ~ +327672 字节;-32768~327672 字节;-32768~327672 字节;-32768~32767
unsigned short≥ 16 位;至少 0 ~ 655352 字节;0~655352 字节;0~655352 字节;0~65535
int≥ 16 位;至少 -32767 ~ +327674 字节;-2 147 483 648~2 147 483 6474 字节;-2 147 483 648~2 147 483 6474 字节;-2 147 483 648~2 147 483 647
unsigned int≥ 16 位;至少 0 ~ 65 5354 字节;0~4 294 967 2954 字节;0~4 294 967 2954 字节;0~4 294 967 295
long≥ 32 位;至少 -2 147 483 647 ~ +2 147 483 6474 字节;-2 147 483 648~2 147 483 6478 字节;-9 223 372 036 854 775 808~9 223…8074 字节;-2 147 483 648~2 147 483 647
unsigned long≥ 32 位;至少 0 ~ 4 294 967 2954 字节;0~4 294 967 2958 字节;0~18 446 744 073 709 551 6154 字节;0~4 294 967 295
long long≥ 64 位;至少 -9 223 372 036 854 775 807 ~ +…8078 字节;-9 223 372 036 854 775 808~9 223…8078 字节;-9 223 372 036 854 775 808~9 223…8078 字节;-9 223 372 036 854 775 808~9 223…807
unsigned long long≥ 64 位;至少 0 ~ 18 446 744 073 709 551 6158 字节;0~18 446 744 073 709 551 6158 字节;0~18 446 744 073 709 551 6158 字节;0~18 446 744 073 709 551 615

在标准库 limits.h 中可以看到相应类型的最小值和最大值,是宏定义。

long long 是 C99 才开始有的。

由于各个平台规定不一,故而写 C 语言更推荐引用 <stdint.h> 库,使用 uintx_t、intx_t。

另外 C 语言有布尔类型,内建为 _Bool,可以用 stdbool.h 引入(烂尾了)。

浮点型

类型存储大小值范围精度
float4 字节1.2E-38 到 3.4E+386 位有效数字
double8 字节2.3E-308 到 1.7E+30815 位有效数字
long double16 字节3.4E-4932 到 1.1E+493219 位有效数字

字符和宽字符

char 类型,默认为 8 bit,带符号。

unsigned char 类型,默认为 8 bit,不带符号。

wchar_t 类型,默认为 16 bit,通过 <wchar.h><stddef.h> 引入,主要用于宽字符支持。

指针

指针,就是一个存放内存地址的变量。形式为 type*,这里的 type 同样可以是指针,因为指针的值也是存放在内存里的。

type 的区别取决于你想如何解释这个地址上的变量。

实际上在 C 语言中几乎所有东西都能转化为指针,甚至函数也是。

void

void 类型表示没有可用的值。它通常用于以下三种情况下:

字面量

字符字面量中,单引号当成字符,双引号当成字符串,L"" 为宽字符字面量。

整数字面量中,从 C99 起拥有 LL/ULL 的整数后缀。而且可以使用科学计数法。

浮点数字面量中,浮点数默认被当成 double 类型的,带上 f 或者 F 后缀,比如 2.3f,可以修改字面量为 foalt。同理 l 和 L 可以修改字面量为 long double

转义字符和规定符

转义字符:

写法含义说明
\\反斜杠字符 \本来 \ 是转义起始,所以要写成 \\
\'单引号 '用在字符/字符串中
\"双引号 "字符串字面量里写 " 必须转义
\n换行(LF,line feed)在 Unix/Linux/macOS 里就是换行
\r回车(CR,carriage return)Windows 文本换行常是 \r\n
\t水平制表(Tab)Tab
\v垂直制表现在很少用
\b退格(backspace)光标向左退一格
\f换页(form feed)古老打印机/终端控制符
\a响铃(bell)老终端会“滴”一声,现代一般没反应
\?问号 ?避免被老式“三字母序列”影响(几乎不用)
\ooo八进制数字反斜杠 + 1 3 位八进制数字(0–7),最多三位
\xhh...十六位进制数字直到遇到非十六进制字符为止的那串数字组成的值
\uXXXX16 位 Unicode 码点(4 位十六进制)基本用不上
\UXXXXXXXX32 位 Unicode 码点(8 位十六进制)基本用不上

规定符:

格式适用函数含义 / 说明对应类型
%d / %i
%ld
%lld
printf/scanf十进制有符号整数int
long int
long long int
%u
%lu
%llu
printf/scanf十进制无符号整数unsigned int
unsigned long int
unsigned long long int
%x / %X
%lx / %lX
%llx / %llX
printf/scanf无符号整数的十六进制表示(小写/大写)unsigned int
unsigned long int
unsigned long long int
%o
%lo
%llo
printf/scanf无符号整数的八进制表示unsigned int
unsigned long int
unsigned long long int
%fprintf/scanf小数形式的单精度浮点数float
%lfprintf/scanf小数形式的双精度浮点数,在 printf 中与 %f 等价(同样读取 doubledouble
%e / %Eprintf/scanf指数形式的浮点数(如 1.23e+10double
%g / %Gprintf/scanf%e%f 中输出“更短”的形式double
%cprintf/scanf单个字符int / char
%sprintf/scanf字符串(以 '\0' 结尾)char *
%pprintf打印指针的地址void *
%pscanf读入指针值void ** 或具体类型的 T **
%%printf输出一个 % 字符本身

printf 精度:

格式说明含义示例
%nd宽度至少为 n 的十进制整数,右对齐,左侧不足时补空格printf("%5d", 42); 输出 " 42"(宽度5)
%-ns宽度至少为 n 的字符串,左对齐,右侧不足时补空格printf("%-10s", "hi"); 输出 "hi "(宽度10)
%m.nf总宽度至少为 m,小数部分保留 n 位,右对齐;不足时左侧补空格printf("%5.2f", 3.14159); 输出 " 3.14"(总宽5,2位小数)
%0nd宽度至少为 n 的整数,右对齐,左侧不足时用 0 填充printf("%05d", 7); 输出 "00007"(宽度5,0填充)
%#x输出带 0x 前缀的十六进制无符号整数(%#X 则前缀 0Xprintf("%#x", 255); 输出 "0xff"
%*d宽度由一个额外的 int 实参指定,效果等同 %ndn 由参数动态给出printf("%*d", 5, 42); 输出 " 42"(宽度为第一个参数5)

scanf 精度:

格式说明含义示例
%nd最多读取 n 位数字构成整数,存入对应变量,多余字符留在输入缓冲区scanf("%4d", &a); 若输入 123456a 得到 1234,剩余 56 留在缓冲区
%*d读取一个整数,但不赋值(抑制赋值),常用于跳过数据scanf("%*d %d", &x); 输入 100 200,跳过 100x 得到 200
%d%*c%d读取整数 → 读取一个字符并丢弃(%*c)→ 再读取一个整数,常用于跳过分隔符scanf("%d%*c%d", &a, &b); 输入 1+2a=1,丢弃 +b=2

复合类型结构

数组

数组分为一维数组和多维数组,一维数组的定义为 type name[length],其含义为存储在内存中连续存放的 length 个大小等同于 type 的变量们, 总空间大小为内存字节数 = 元素个数 * sizeof(类型)

多维数组格式为 type name[length1][length2]...[lengthN],在内存中仍是一维排序。

数组的第一个元素下标为 0,所有的方括号范围为 [0, 长度-1]

一维初始化方式:

int a[4]={1,2,3,4};     // 每个位置的值最开始就表示好了
int a[4]={1,2,3};       // 没给初始值的默认为0
int a[]={1,2,3,4}       // 长度为初始化的长度
char fuck[] = "fjskljf" // 结尾默认加0,因为这是字符串
int a[12];              // 初始化:如果没有初始化,那么内存中是什么是不确定的

多维数组初始化方式(以二维为例):

// 以下所有格式把行数去掉依旧等价,可省略行数, 但不可省略列数。
int a[2][3]={1,2,3,4,5,6};
int a[2][3]={{1,2,3},{4,5,6}};
int a[2][3]={1,2,3,4};         // 未初始化的为0即{1,2,3,4,0,0}
int a[2][3]={{1,2},{3,4}};     // 为{1,2,0,3,4,0}

结构体

结构体就是把若干个变量变量打包成一个更大的变量类型,格式如下:

// 定义
struct Point { 
    int x;
    int y;
}; // 注意这里要加上分号

// 定义的同时声明变量
struct Person { 
    char name[20];
    int age;
    struct Point; // 可嵌套
    struct { // 可匿名
      int a;
      int b;
    };
    struct Person* p; // 可自引用
    // struct Person p; // 错误,因为 Person 中又定义一个 Person,那么没法确认其大小
} p1, p2;

// 定义变量
struct Person p; 
struct Person *pp = &p;

// 使用变量
p.age = 10;
pp->age = 4; // 等价于 (*pp).y
p.a = 1; // 可自由使用任何匿名复合体

内存布局同数组类似,但是大小计算方式则不同,拿下例说明:

struct S {
    char c;   // 1 字节
    int  i;   // 通常 4 字节
};

在常见 32/64 位 ABI 中,sizeof(struct S) 通常是 8,而不是 5,因为中间会插入填充字节,使 int 按 4 字节对齐。

可通过位域来节省空间,常用于单片机这种需要节省空间的硬件上,下例:

struct Flags {
    unsigned int a : 1;   // 1 位
    unsigned int b : 3;   // 3 位
    unsigned int c : 4;   // 4 位
};

有些编译器还支持 #pragma pack 或属性来改变对齐方式:

#pragma pack(push, 1)
struct Packed {
    char c;
    int  i;
};
#pragma pack(pop)

初始化方式:

// 顺序初始化,即按成员声明顺序
struct Person p = {"Alice", 20};

// 指定初始化
struct Person p = {
    .age = 20,
    .name = "Alice",
};

// 临时构造
func((struct Point){3, 4});  // 作为参数

联合体

空间大小为所有变量中空间最大的变量,以最后赋值的值来作为其内容的结构体。语法与结构体一致,下面只展示基本写法。

// 定义
union Data {
    int   i;
    float f;
    void *p;
};

// 初始化
union Data d;
union Data d = {10};
union Data d = {.f = 3.14f};

// 使用变量
d.i = 42;

枚举类型

与前两个类似,不再做说明。

enum Color {
    RED, // 默认为0,逐次+1
    GREEN,
    BLUE
};

enum ErrorCode {
    ERR_OK      = 0,
    ERR_IO      = 1,
    ERR_TIMEOUT = 2,
    ERR_FATAL   = 100
};

enum Weekday {
    MON = 1,
    TUE,        // 2
    WED,        // 3
    THU = 10,
    FRI,        // 11
    SAT,        // 12
    SUN         // 13
};

enum Color color;

color = RED;

if (color == GREEN) {
    /* ... */
}

enum Color color = BLUE; // C99

复合变量体与 typedef

定义复合变量体变量由于要反复写明其复合变量类型,十分麻烦,我们可以用 typedef 给它取别名

// 取别名
typedef struct Point {
    int x, y;
} Point;

// 使用匿名函数进一步简化
typedef struct {
    int x, y;
} Point; 

Point p;   // 不用再写 struct

类型转化

分为隐式类型转化和显式类型转化,很好理解,不写了。

注意截断和溢出,C 编译器默认不检查这些东西,有一些安全选项开启后可以检查a。

运算符与表达式

下表显示了 C 语言支持的所有运算符。假设变量 A 的值为 10,变量 B 的值为 20。

运算符优先级:

类别运算符结合性
后缀() [] -> . ++ --从左到右
一元+ - ! ~ ++ -- (type) * & sizeof从右到左
乘除* / %从左到右
加减+ -从左到右
移位<< >>从左到右
关系< <= > >=从左到右
相等== !=从左到右
位与 AND&从左到右
位异或 XOR^从左到右
位或 OR|从左到右
逻辑与 AND&&从左到右
逻辑或 OR||从左到右
条件?:从右到左
赋值= += -= *= /= %= >>= <<= &= ^= |=从右到左
逗号,从左到右

算术运算符

运算符描述实例
+把两个操作数相加A + B 将得到 30
-从第一个操作数中减去第二个操作数A - B 将得到 −10
*把两个操作数相乘A * B 将得到 200
/分子除以分母,如果是整数向下取整B / A 将得到 2
%取模运算符,整除后的余数。注意:浮点型不能用,要用 math.hfmod()fmodl()B % A 将得到 0
++自增运算符,整数值增加 1A++ 将得到 11
--自减运算符,整数值减少 1A-- 将得到 9

关系运算符

运算符描述实例
==检查两个操作数的值是否相等,如果相等则条件为真。(A == B) 为假。
!=检查两个操作数的值是否相等,如果不相等则条件为真。(A != B) 为真。
>检查左操作数的值是否大于右操作数的值,如果是则条件为真。(A > B) 为假。
<检查左操作数的值是否小于右操作数的值,如果是则条件为真。(A < B) 为真。
>=检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。(A >= B) 为假。
<=检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。(A <= B) 为真。

逻辑运算符

运算符描述实例
&&逻辑与运算符。如果两个操作数都非零,则条件为真。(A && B) 为假。
||逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。(A || B) 为真。
!逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。!(A && B) 为真。

位运算符

运算符描述实例
&按位与:对每一位执行逻辑与操作,两个相应位都为 1 则结果为 1,否则为 0。运算规则:0&0=0; 0&1=0; 1&0=0; 1&1=1;(A & B) 将得到 12,即为 0000 1100
|按位或:对每一位执行逻辑或操作,两个相应位都为 0 则结果为 0,否则为 1。运算规则:0|0=0; 0|1=1; 1|0=1; 1|1=1;(A | B) 将得到 61,即为 0011 1101
^按位异或:对每一位执行逻辑异或操作,两个相应位值相同则结果为 0,否则为 1。运算规则:0^0=0; 0^1=1; 1^0=1; 1^1=0;(A ^ B) 将得到 49,即为 0011 0001
~按位取反:将每一位的 0 变为 1,1 变为 0。运算规则:~1=-2; ~0=-1;(~A) 将得到 −61,即为 1100 0011,一个有符号二进制数的补码形式。
<<左移:将操作数的所有位向左移动指定的位数。左移 n 位相当于乘以 2 的 n 次方。左边丢弃,右边补 0。A << 2 将得到 240,即为 1111 0000
>>右移:将操作数的所有位向右移动指定的位数。右移 n 位相当于除以 2 的 n 次方。正数左补 0,负数左补 1,右边丢弃。A >> 2 将得到 15,即为 0000 1111

赋值运算符

运算符描述实例
=简单赋值,把右边操作数的值赋给左边操作数C = A + B 将把 A + B 的值赋给 C
+=加且赋值,C += A 相当于 C = C + AC += A
-=减且赋值,C -= A 相当于 C = C - AC -= A
*=乘且赋值,C *= A 相当于 C = C * AC *= A
/=除且赋值,C /= A 相当于 C = C / AC /= A
%=求模且赋值,C %= A 相当于 C = C % AC %= A
<<=左移且赋值,C <<= 2 等同于 C = C << 2C <<= 2
>>=右移且赋值,C >>= 2 等同于 C = C >> 2C >>= 2
&=按位与且赋值,C &= 2 等同于 C = C & 2C &= 2
^=按位异或且赋值,C ^= 2 等同于 C = C ^ 2C ^= 2
|=按位或且赋值,C |= 2 等同于 C = C | 2C |= 2

杂项运算符:sizeof 与三元

运算符描述实例
sizeof()返回变量或类型的大小。sizeof(a) 将返回 4,其中 aint
&返回变量的地址。&a; 将给出变量的实际地址。
*解引用,指向一个变量。*a; 将指向一个变量。
? :三元条件表达式如果条件为真 ? 则值为 X : 否则值为 Y

控制流


// 条件判断
if(){
  
}else if(){
  
}else{
  
}

// 多重条件判断
switch:
case x:
  xxx;
  break; // 如果不带 break,则会继续朝向下一个 case 作比较
default:
  xxx;

for (a;b;c){ // 除了表达式 b 都可省略
  
}

// 先判断再执行的循环 
while(){
  continue; // 跳过一次循环
  break;    // 跳出循环
}

// 先执行再判断的循环
do{
  
}while()
  
// 直接跳转
goto label
label:
  xxx;

函数

类型为 返回值类型 函数名(函数参数类型 函数参数名, ...){...}

变量函数特殊属性

作用域(scope)

作用域指的是变量或者函数的可见范围。

链接属性(linkage)

链接属性指的是跨源文件时,不同文件中同个名字是否指向同一个实体。C 里有三种链接属性:

生存期(lifetime / storage duration)

生存期 = 变量在内存中实际存在的时间段。主要有三类:

相应对应二进制文件中的常量段,栈,堆

关键字

常见关键字:

常用库函数

math.h

函数函数原型(简化)作用示例
fabsdouble fabs(double x);绝对值(浮点)fabs(-3.5) → 3.5
sqrtdouble sqrt(double x);开平方sqrt(9.0) → 3.0
sin / cosdouble sin(double x);正弦 / 余弦(弧度)sin(3.14159/2)
powdouble pow(double x, double y);幂运算,x 的 y 次方pow(2.0, 3.0) → 8.0
expdouble exp(double x);e 的 x 次方exp(1.0) ≈ 2.71828
log / log10double log(double x);自然对数 / 以 10 为底对数log(2.71828) ≈ 1

ctype.h

函数函数原型作用示例
isdigitint isdigit(int c);是否数字字符 '0'..'9'isdigit('3') → 非0
isalphaint isalpha(int c);是否字母字符 A..Z,a..zisalpha('A') → 非0
isupperint isupper(int c);是否大写字母isupper('A') → 非0
islowerint islower(int c);是否小写字母islower('a') → 非0
isspaceint isspace(int c);是否空白(空格、换行等)isspace(' ') → 非0
tolowerint tolower(int c);转为小写(若是字母)tolower('A') → 'a'
toupperint toupper(int c);转为大写(若是字母)toupper('a') → 'A'

stdio.h

标准输入输出

函数原型作用简例
printfint printf(const char *format, ...);格式化输出到标准输出printf("x=%d\n", x);
fprintfint fprintf(FILE *stream, const char *format, ...);格式化输出到指定文件流fprintf(fp, "x=%d\n", x);
sprintfint sprintf(char *str, const char *format, ...);格式化输出到字符串(不安全)sprintf(buf, "%d", x);
snprintfint snprintf(char *str, size_t size, const char *format, ...);限长格式化到字符串(推荐)snprintf(buf, 16, "%.2f", f);
scanfint scanf(const char *format, ...);从标准输入按格式读scanf("%d", &x);
fscanfint fscanf(FILE *stream, const char *format, ...);从文件流按格式读fscanf(fp, "%s", s);
sscanfint sscanf(const char *str, const char *format, ...);从字符串按格式解析sscanf(buf, "%d", &x);
putsint puts(const char *s);输出字符串并加换行puts("Hello");
fputsint fputs(const char *s, FILE *stream);输出字符串到文件fputs("Hi", fp);
putcharint putchar(int c);输出一个字符putchar('A');
fputcint fputc(int c, FILE *stream);向文件写一个字符fputc('A', fp);
getcharint getchar(void);从标准输入读一个字符int c = getchar();
fgetcint fgetc(FILE *stream);从文件读一个字符int c = fgetc(fp);
ungetcint ungetc(int c, FILE *stream);把字符退回输入流ungetc(c, fp);

文件处理

函数原型作用简例
fopenFILE *fopen(const char *path, const char *mode);打开文件FILE *fp = fopen("a.txt","r");
fcloseint fclose(FILE *stream);关闭文件fclose(fp);
fflushint fflush(FILE *stream);刷新输出缓冲fflush(stdout);
freadsize_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);二进制读块fread(buf,1,100,fp);
fwritesize_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);二进制写块fwrite(buf,1,n,fp);
fseekint fseek(FILE *stream, long offset, int whence);移动文件位置fseek(fp, 0, SEEK_SET);
ftelllong ftell(FILE *stream);当前文件位置long pos = ftell(fp);
rewindvoid rewind(FILE *stream);回到文件开头rewind(fp);
removeint remove(const char *path);删除文件remove("a.txt");
renameint rename(const char *old, const char *new);重命名文件rename("a.txt","b.txt");
tmpfileFILE *tmpfile(void);创建临时文件FILE *fp = tmpfile();

错误检查

函数原型作用
perrorvoid perror(const char *s);打印最近错误原因(结合 errno
feofint feof(FILE *stream);是否到达 EOF
ferrorint ferror(FILE *stream);是否发生 I/O 错误
clearerrvoid clearerr(FILE *stream);清除 EOF/错误标志

stdlib.h

动态内存

函数原型作用示例
mallocvoid *malloc(size_t size);申请未初始化内存int *p = malloc(10*sizeof *p);
callocvoid *calloc(size_t nmemb, size_t size);申请并清零 nmemb 个元素int *p = calloc(10,sizeof *p);
reallocvoid *realloc(void *ptr, size_t size);调整已分配内存大小p = realloc(p,20*sizeof *p);
freevoid free(void *ptr);释放动态内存free(p); p=NULL;

字符串到数字

函数原型作用示例
atoiint atoi(const char *nptr);字符串 → intint x = atoi("123");
atollong atol(const char *nptr);字符串 → long
atofdouble atof(const char *nptr);字符串 → doubledouble d = atof("3.14");
strtollong strtol(const char *nptr, char **endptr, int base);字符串 → long,支持进制,能返回错误位置long x = strtol("0xff",NULL,0);
strtoulunsigned long strtoul(const char *nptr, char **endptr, int base);字符串 → unsigned long
strtoddouble strtod(const char *nptr, char **endptr);字符串 → double

随机数

函数原型作用示例
randint rand(void);返回 [0, RAND_MAX]int x = rand()%(n-m+1)+m;
srandvoid srand(unsigned int seed);设置随机种子srand(time(NULL));

程序控制 / 环境

函数原型作用
exitvoid exit(int status);正常结束程序
abortvoid abort(void);异常终止
atexitint atexit(void (*func)(void));注册进程退出时调用的函数
systemint system(const char *command);调用系统命令

排序与查找

函数原型作用简例
qsortvoid qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));通用快速排序qsort(a,n,sizeof *a, cmp);
bsearchvoid *bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));有序数组二分查找bsearch(&key,a,n,sizeof *a,cmp);

string.h

字符串函数(以 '\0' 结尾)

函数原型作用
strlensize_t strlen(const char *s);返回字符串长度(不含 '\0'
strcpychar *strcpy(char *dest, const char *src);拷贝字符串到 dest
strncpychar *strncpy(char *dest, const char *src, size_t n);最多拷贝 n 字符
strcatchar *strcat(char *dest, const char *src);把 src 接到 dest 末尾
strncatchar *strncat(char *dest, const char *src, size_t n);最多连接 n 字符
strcmpint strcmp(const char *s1, const char *s2);比较两个字符串
strncmpint strncmp(const char *s1, const char *s2, size_t n);比较前 n 个字符
strchrchar *strchr(const char *s, int c);查找首次出现的字符 c
strrchrchar *strrchr(const char *s, int c);查找最后一次出现的字符 c
strstrchar *strstr(const char *haystack, const char *needle);查找子串
strtokchar *strtok(char *str, const char *delim);分割字符串(非线程安全,慎用)

内存函数(任意字节块)

函数原型作用
memsetvoid *memset(void *s, int c, size_t n);用字节 c 填充前 n 字节
memcpyvoid *memcpy(void *dest, const void *src, size_t n);拷贝 n 字节(不允许重叠)
memmovevoid *memmove(void *dest, const void *src, size_t n);拷贝 n 字节(允许重叠)
memcmpint memcmp(const void *s1, const void *s2, size_t n);比较前 n 字节
memchrvoid *memchr(const void *s, int c, size_t n);在前 n 字节中查找字符 c

assert.h

assertassert.h 中,检测参数是否为假,如果为假,则使用 abort() 退出程序,是运行时断言,同 _Static_assert 编译时断言不同。