C标准
C语言标准
C89
C89标准,也被称作ANSI C,是C语言编程语言的第一个国际标准。它在1989年由美国国家标准协会(ANSI)发布,标准编号为ANSI X3.159-1989。C89标准定义了C语言的基本语法、数据类型、运算符、控制结构、函数、存储类别以及预处理指令等内容。
C89标准主要包括以下内容:
- 基本数据类型:包括
char
、short
、int
、long
以及浮点类型float
和double
等。 - 运算符和表达式:包括算术运算符、逻辑运算符、关系运算符和位运算符等。
- 控制结构:如
if
、while
、for
和switch
语句等。 - 函数:包括函数的声明、定义和调用等。
- 存储类别:如
auto
、register
、static
和extern
等。 - 预处理指令:例如
#define
和#include
等。
C89标准在1990年被国际标准组织ISO(International Organization for Standardization)和国际电工委员会(IEC)采纳为国际标准,命名为ISO/IEC 9899:1990,也被称为C语言的国际标准。尽管C89标准已经有些年头,但它仍然是许多编译器所支持的标准,并且在一些嵌入式系统和旧的软件项目中仍然被广泛使用。
然而,C89标准也存在一些限制和不足,例如不支持长标识符、不支持可变参数函数、不支持单行注释等。为了解决这些问题并扩展C语言的功能,后来的标准如C99和C11对C89进行了扩充和更新。C99标准引入了更多的特性,如支持长标识符、支持单行注释、引入了新的数据类型(如_Complex
和_Bool
)以及新的库函数等。C11标准则进一步扩展了C语言的功能,包括对多线程的支持、新的原子操作以及对Unicode的支持等。
C99
--std=c99
C99标准,也被称为ISO/IEC 9899:1999,是C语言编程语言的第二个国际标准,于1999年发布。它是在C89标准的基础上进行了扩展和增强,增加了许多新的特性和功能。C99标准得到了广泛的接受和应用,成为现代C语言编程的重要基础。
C99标准的主要改进和增强包括:
- 增加restrict指针
C99引入了指针类型限定符restrict
,它用于告诉编译器两个指针不会指向同一块内存区域,从而帮助编译器进行优化。
// 受限指针
// 开优化之后,编译器会认为两个指针不会指向同一块内存区域
// 不开优化,没有区别。都返回结果:10
int add_fun(int *a, int *b)
{
*a = 2;
*b = 5;
return *a + *b;
}
int add_fun2(int * restrict a, int * restrict b)
{
*a = 2;
*b = 5;
return *a + *b;
}
int main(int argc, char *argv[])
{
int x = 0;
int *a = &x;
int *b = &x;
printf("r = %d\n", add_fun(a, b));
printf("r = %d\n", add_fun2(a, b));
return 0;
}
// gcc c99.c --std=c99 -O2
// 执行
// r = 10
// r = 7
- inline 内联函数
- 新增数据类型
类型 | 描述 | 说明 |
---|---|---|
_Bool | 布尔类型 | 新增stdbool.h 头文件,定义了bool ,true ,false |
_Complex | 复数类型 | 新增complex.h 头文件,定义了_Complex ,I |
long long int | 64位整数 | 新增stdint.h 头文件,定义了int8_t ,uint32_t ,int64_t 等 |
long double | 扩展精度浮点数 | 新增float.h 头文件,定义了`FLT_EVAL |
- 可变长数组 只有局部数组可变长。这里的可变长数组指的是数组的长度可以由运行时决定,而不是在编译时确定。 注意:可变长数组在生存期内是不变的。并非是动态的,可变的只是数组的长度。
#include <stdio.h>
void reverse(int *data, int len)
{
int temp[len];
// 实际中肯定不会这么写,只是为了测试
for(int i = 0; i < len; i++)
{
temp[i] = data[len - i - 1];
}
for(int i = 0; i < len; i++)
{
data[i] = temp[i];
}
}
void print_array(int *data, int len)
{
for(int i = 0; i < len; i++)
{
printf("%d ", data[i]);
}
printf("\n");
}
int main(int argc, char *argv[])
{
int a[] = {1,2,3,4,5};
print_array(a, 5);
reverse(a, 5);
print_array(a, 5);
return 0;
}
//单行注释 有些编译器即使C89也支持了,实测gcc的--std=c89不可以。
预处理程序的修改
- 变元列表
#include <stdio.h>
#define DBG(...) printf(__VA_ARGS__)
int main(int argc, char *argv[])
{
int a = 10;
int *p = &a;
*p = 10;
DBG("only test\n");
DBG("a = %d\n", a);
return 0;
}
- 复合赋值 结构体赋值
struct _student
{
char name[16];
int id;
};
struct _student student = {"robot", 97};
struct _student temp;
temp = student;
temp = (struct _student){"robot", 97};
数组不可以直接赋值,编译器报错。
int a[5] = {1,2,3,4,5};
int data[5] = {0};
/*
data = (int []){10,20,30,40,50};
ata = a;
*/
// 指针可以,但似乎和直接定义一个数组没区别啊
int *p = NULL;
p = (int []){1,2,3,4,5}; // 这有什么意义吗?
int p2[] = {1,2,3,4,5};
- for循环 可以在for循环中定义一个或多个变量,作用域仅为for循环体。
for(int i = 0; i < 10; i++)
{
// ...
}
- 柔性数组成员 结构体中的最后一个成员允许是未知大小的数组(可变大小的数组),称为柔性数组成员。但结构体柔性数组成员前至少要有一个其他成员。sizeof该结构体,返回的时候不包括柔性数组成员的大小。
// 若在c89某些编译器上,不支持该功能,你又想用
// 那么可以data[1],然后计算结构体的时候,在某些特定场合计算大小,可能需要sizeof(struct _apdu)-1而已
#include <stdint.h>
#include <stdio.h>
struct _apdu
{
uint8_t cla;
uint8_t ins;
uint8_t p1;
uint8_t p2;
uint8_t lc;
uint8_t data[]; // uint8_t data[0];
};
int main(int argc, char *argv[])
{
printf("len = %ld\n", sizeof(struct _apdu));
return 0;
}
// len = 5
- 指定的初始化符 成员初始化
int data[10] = {[3]=5, 1, [8]=9};
// data[3] = 5, data[4] = 1, data[8] = 9, 其他为0
int data[10] = {
[1] = 1,
[2] = 2,
[3 ... 5] = 4,
[8] = 8,
[9] = 9,
};
// data:0 1 2 4 4 4 0 0 8 9
struct _apdu apdu = {.ins=0x84, .lc=0x10};
// apdu: 00 84 00 00 10
printf和scanf增强 支持了
ll
,支持long long int
类型。 支持了hh
,用于char
型变元新增头文件
- __func__预定义符
- 其他特性
- 放宽限制:数据块嵌套层数,条件语句嵌套层数,函数调用参数个数,结构体成员个数等
- 返回值:非空类型的函数,必须使用带有返回值的return语句
- 扩展的整数类型,如
int16_t
,int_least16_t
,int_fast32_t
,uintmax_t
等 - 整数类型提升规则修改,如
long long int
高于int
等
C11
C11标准,全称为ISO/IEC 9899:2011,也被称为C1X,是C语言的第三个国际标准,于2011年12月发布。它是在C99标准的基础上进行了进一步的扩展和增强,增加了一些新的特性和功能。C11标准的目标是提供更高的性能和更好的可移植性,同时保持对早期C语言标准的兼容性。
C11标准的主要改进和增强包括:
- 对齐处理(Alignment)的标准化:C11引入了对齐处理的标准化,包括
_Alignas
标志符、alignof
运算符、aligned_alloc
函数以及<stdalign.h>
头文件。这些特性使得程序员能够更精确地控制数据的对齐方式,从而提高程序的性能和可移植性。 _Noreturn
函数标记:C11引入了_Noreturn
函数标记,用于标识一个函数不会返回。这与GCC的__attribute__((noreturn))
类似,可以帮助编译器进行更好的优化。_Generic
关键字:C11引入了_Generic
关键字,用于实现泛型编程。它允许程序员根据表达式的类型选择不同的函数或操作,增强了C语言的灵活性。- 多线程(Multithreading)支持:C11增加了对多线程的支持,包括
_Thread_local
存储类型标识符、<threads.h>
头文件(里面包含了线程的创建和管理函数)以及原子操作相关的类型和函数(如<stdatomic.h>
头文件中的_Atomic
类型修饰符)。这些特性使得C语言能够更方便地编写并发程序。 - 增强的Unicode支持:C11增加了对Unicode的支持,包括对UTF-8和UTF-16编码的处理以及新的Unicode字符分类函数。这使得C语言能够更好地处理国际化文本。
- 更多的浮点处理宏和函数:C11增加了一些新的浮点处理宏和函数,用于处理浮点数和复数运算。这些特性提高了C语言在数值计算领域的适用性。
- 匿名结构体/联合体支持:C11将GCC早已支持的匿名结构体/联合体特性引入标准。这允许程序员在结构体或联合体中定义匿名的成员,从而简化代码结构。
C11标准在现代C语言编程中得到了广泛关注和应用。尽管在实际项目中,由于编译器和平台的支持情况,C99或更早的标准仍然可能被使用,但C11标准提供的新特性和功能无疑为C语言编程带来了更多的便利和灵活性。当前,许多主流编译器如GCC、Clang、Intel C++ Compiler等都支持C11标准。
C语言标准库
编号 | 头文件 | 功能说明 | C89 | C99 | C11 | C17 | C2x |
---|---|---|---|---|---|---|---|
1 | <assert.h> | 宏定义和assert函数,用于调试目的 | |||||
2 | <complex.h> | 复数类型定义和函数 | 否 | 新增 | |||
3 | <ctype.h> | 字符处理函数,如判断字符类型、大小写转换等 | |||||
4 | <errno.h> | 错误码宏定义,用于检测库函数调用中的错误 | |||||
5 | <fenv.h> | 浮点环境函数,控制浮点数的异常和舍入模式 | 否 | 新增 | |||
6 | <float.h> | 浮点类型属性,如浮点数的范围、精度等 | |||||
7 | <inttypes.h> | 整数类型格式转换宏,用于printf和scanf等函数 | 否 | 新增 | |||
8 | <iso646.h> | 替代运算符宏,如and 、or 、not 等 | 否 | 新增 | |||
9 | <limits.h> | 各种数据类型属性,如整数的最大值和最小值 | |||||
10 | <locale.h> | 本地化函数和宏,用于处理不同地域的语言和文化习惯 | |||||
11 | <math.h> | 数学函数和宏,如三角函数、指数函数等 | |||||
12 | <setjmp.h> | 非局部跳转函数,用于setjmp和longjmp | |||||
13 | <signal.h> | 信号处理函数,用于处理程序运行时接收到的信号 | |||||
14 | <stdarg.h> | 可变参数宏和类型,用于处理函数中的可变参数列表 | |||||
15 | <stdbool.h> | 布尔类型定义,包括bool、true和false | 否 | 新增 | |||
16 | <stddef.h> | 通用类型定义和宏,如NULL、ptrdiff_t等 | |||||
17 | <stdint.h> | 固定宽度整数类型定义,如int8_t、uint32_t等 | 否 | 新增 | |||
18 | <stdio.h> | 输入输出函数,如printf、scanf、fopen等 | |||||
19 | <stdlib.h> | 通用函数,如内存分配、随机数生成、程序终止等 | |||||
20 | <string.h> | 字符串处理函数,如复制、连接、比较等 | |||||
21 | <tgmath.h> | 类型泛型数学函数,提供统一接口用于处理不同类型的数学运算 | 否 | 新增 | |||
22 | <time.h> | 时间处理函数,如获取当前时间、格式化时间等 | |||||
23 | <wchar.h> | 宽字符处理函数,用于处理多字节字符集和宽字符集 | C95 | 新增 | |||
24 | <wctype.h> | 宽字符分类函数,如判断宽字符的类型 | C95 | 新增 |
assert
assert.h
头文件定义了两个宏:assert
和NDEBUG
。
assert
宏用于在程序中添加断言,如果断言失败,则终止程序并输出错误信息。NDEBUG
宏用于控制是否启用断言。如果定义了NDEBUG
,则不会启用断言;否则会启用断言。
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <float.h>
double division(double numerator, double denom)
{
// assert
// true: 继续执行
// false: 打印错误信息
assert(!(denom <= DBL_EPSILON)); // denom == 0则报错
return numerator / denom;
}
int main() {
double result = 0;
result = division(5, 2);
printf("result = %f\n",result);
result = division(10, 0);
printf("result = %f\n",result);
return 0;
}
complex
C99标准
开始支持complex.h
,定义了一系列用于处理复数的函数和宏。
函数名 | 声明 | 描述 |
---|---|---|
cabs | double cabs(double complex z); | 计算复数z的模(绝对值)。 |
cacos | double complex cacos(double complex z); | 计算复数z的反余弦值。 |
casin | double complex casin(double complex z); | 计算复数z的反正弦值。 |
catan | double complex catan(double complex z); | 计算复数z的反正切值。 |
catanh | double complex catanh(double complex z); | 计算复数z的反双曲正切值。 |
ccos | double complex ccos(double complex z); | 计算复数z的余弦值。 |
ccosh | double complex ccosh(double complex z); | 计算复数z的双曲余弦值。 |
cexp | double complex cexp(double complex z); | 计算复数z的指数函数值。 |
clog | double complex clog(double complex z); | 计算复数z的自然对数。 |
conj | double complex conj(double complex z); | 获取复数z的共轭。 |
cproj | double complex cproj(double complex z); | 计算复数z在Riemann球上的投影。 |
creal | double creal(double complex z); | 获取复数z的实部。 |
cimag | double cimag(double complex z); | 获取复数z的虚部。 |
cpow | double complex cpow(double complex x, double complex y); | 计算复数x的y次幂。 |
csin | double complex csin(double complex z); | 计算复数z的正弦值。 |
csinh | double complex csinh(double complex z); | 计算复数z的双曲正弦值。 |
csqrt | double complex csqrt(double complex z); | 计算复数z的平方根。 |
ctan | double complex ctan(double complex z); | 计算复数z的正切值。 |
ctanh | double complex ctanh(double complex z); | 计算复数z的双曲正切值。 |
ctype
包含了用于测试和映射字符的函数
函数名 | 功能描述 |
---|---|
isalnum() | 检查字符是否为字母或数字 |
isalpha() | 检查字符是否为字母 |
isascii() | 检查字符是否为ASCII字符 |
iscntrl() | 检查字符是否为控制字符 |
isdigit() | 检查字符是否为数字 |
isgraph() | 检查字符是否为可打印字符(不包括空格) |
islower() | 检查字符是否为小写字母 |
isprint() | 检查字符是否为可打印字符(包括空格) |
ispunct() | 检查字符是否为标点符号 |
isspace() | 检查字符是否为空白字符(如空格、制表符、换行符等) |
isupper() | 检查字符是否为大写字母 |
isxdigit() | 检查字符是否为十六进制数字 |
tolower() | 将大写字母转换为小写字母 |
toupper() | 将小写字母转换为大写字母 |
errno
提供了全局变量errno
,当系统调用或库函数发生错误时,这个变量通常会被设置为特定的错误码。
errno
通常是一个 int
类型的变量,可以被程序读取和修改。
同时,该头文件也定义了一系列的宏,这些宏通常用于表示系统调用或库函数在发生错误时设置的错误码。
宏名称 | 描述 |
---|---|
EDOM | 表示数学函数的参数超出其定义域时的错误。例如,尝试对负数求平方根时。 |
ERANGE | 表示数学函数的结果超出其返回类型能够表示的范围时的错误。例如,整数溢出。 |
EILSEQ | 表示不合法的字符序列错误,通常与多字节字符编码有关。 |
EINVAL | 表示无效的参数传递给函数时的错误。 |
ENOMEM | 表示内存分配失败,因为没有足够的可用内存。 |
EACCES | 表示权限不足,无法访问文件或资源。 |
ENOENT | 表示文件或目录不存在。 |
EIO | 表示输入/输出错误,通常与硬件故障或驱动程序问题有关。 |
EBADF | 表示无效的文件描述符。 |
EXDEV | 表示跨设备链接错误,例如尝试在不同文件系统的文件之间创建硬链接。 |
示例:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <math.h>
int main() {
double number = -1;
double result = 0;
// errno 中的全局变量
errno = 0;
result = sqrt(number);
if(errno != 0)
{
printf("#error\n");
}
else
{
printf("result = %f\n", result);
}
if(errno == EDOM)
{
printf("Domain Error!\n");
}
if(errno == ERANGE)
{
printf("Range Error!\n");
}
return 0;
}
math
函数名 | 声明 | 描述 |
---|---|---|
acos | double acos(double x); | 计算x的反余弦值,结果以弧度表示。 |
asin | double asin(double x); | 计算x的反正弦值,结果以弧度表示。 |
atan | double atan(double x); | 计算x的反正切值,结果以弧度表示。 |
atan2 | double atan2(double y, double x); | 计算从x轴到点(x, y)的向量之间的角度,结果以弧度表示。 |
ceil | double ceil(double x); | 返回不小于x的最小整数。 |
cos | double cos(double x); | 计算x的余弦值,x以弧度为单位。 |
cosh | double cosh(double x); | 计算x的双曲余弦值。 |
exp | double exp(double x); | 计算e的x次方。 |
fabs | double fabs(double x); | 返回x的绝对值。 |
floor | double floor(double x); | 返回不大于x的最大整数。 |
fmod | double fmod(double x, double y); | 计算x除以y的余数。 |
frexp | double frexp(double value, int *exp); | 将浮点数分解为尾数和指数。 |
hypot | double hypot(double x, double y); | 计算直角三角形的斜边长度。 |
ldexp | double ldexp(double x, int exp); | 返回x乘以2的exp次方的值。 |
log | double log(double x); | 计算x的自然对数(以e为底)。 |
log10 | double log10(double x); | 计算x的常用对数(以10为底)。 |
modf | double modf(double value, double *ipart); | 将浮点数分解为整数部分和小数部分。 |
pow | double pow(double base, double exponent); | 计算base的exponent次方。 |
sin | double sin(double x); | 计算x的正弦值,x以弧度为单位。 |
sinh | double sinh(double x); | 计算x的双曲正弦值。 |
sqrt | double sqrt(double x); | 计算x的平方根。 |
tan | double tan(double x); | 计算x的正切值,x以弧度为单位。 |
tanh | double tanh(double x); | 计算x的双曲正切值。 |
根据需要链接数学库(如-lm
)。对于涉及到浮点数的运算,要注意浮点数的精度和舍入行为可能导致的误差,以及避免除以零等可能导致程序崩溃的情况。
#include <stdint.h>
#include <stdio.h>
#include <math.h>
int main(void)
{
double a = 2;
double result = 0;
printf("%f\n",cos(3.14));
// result = pow(a,5);
// printf("%f\n",result);
return 0;
}
那么为什么只有pow(a,5)
的时候需要连接数学库,而cos(3.14)
不需要呢?
gcc -S test.c
cat test.s | grep 'cos'
cat test.s | grep 'pow'
通过汇编代码分析,只找到了pow,没有发现cos。因为传递的参数是常量,编译器可以优化。就直接用数值替代了。根本没有调用cos函数。
setjmp
主要作用是实现一种非局部跳转机制,允许程序在深层嵌套的函数调用中直接返回到某个特定的setjmp
点,而不是按照常规的函数调用和返回顺序执行。
函数 | 作用 |
---|---|
int setjmp(jmp_buf env) | 这个宏用于设置跳转点。它保存当前的程序上下文到env 参数中,并返回0。如果setjmp 是直接调用的,它将返回0;但如果在longjmp 调用之后调用,它将返回非0值。 |
void longjmp(jmp_buf env, int val) | 这个函数用于实现非局部跳转。它使用env 参数中保存的上下文信息,将程序的控制流跳转到最近一次调用setjmp 的地方。同时,它还将val 作为setjmp 的返回值。 |
jmp_buf
这是一个数据类型,通常是一个数组或结构体,用于保存程序执行时的上下文信息,如程序计数器、栈指针等。
这些信息在setjmp
调用时被保存,供longjmp
在跳转时使用。
示例:
/**
该示例模拟异常抛出的场景
*/
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
jmp_buf jmp_env;
double fun_div0(double data, double div)
{
if(div == 0)
{
// ------------------------------
// 即便这里填写0,实际上传的是1。但传其他值正常。
// void longjmp(jmp_buf env, int val)
// val == 0 ? setjmp(env) == 1 : setjmp(env) == value
// ------------------------------
// longjmp(jmp_env, 0);
// longjmp(jmp_env, -1);
longjmp(jmp_env, 5);
}
return data/div;
}
int main()
{
double result = 0;
int ret = 0;
// 保存现场
ret = setjmp(jmp_env);
printf("ret = %d\n", ret);
if(ret == 0)
{
// 如果 setjmp 返回 0,说明没有发生跳转
result = fun_div0(5, 2);
printf("1. result = %0.2f\n",result);
result = fun_div0(5, 0);
printf("2. result = %0.2f\n",result);
}
else
{
printf("#error: Dividing by zero is an illegal operation.\n");
exit(-1);
}
printf("----- main exit -----\n");
return 0;
}
signal
signal.h
提供了信号(即异常情况)的处理工具。以下是这个头文件中定义的一些主要函数和宏:
函数
函数名 | 描述 |
---|---|
int signal(int signum, sig_t handler) | 设置信号处理函数。当指定的信号signum 发生时,调用handler 函数。 |
int raise(int sig) | 该函数会生成信号sig |
sig_t
是一个函数指针类型,用于指向信号处理函数。
sig_atomic_t
是一个数据类型,在信号处理程序中作为变量使用,它保证即使在存在异步信号的情况下,对该变量的访问也是原子的。
宏
宏名 | 描述 |
---|---|
SIGABRT | 异常中止信号,可能由于调用了abort() 方法。 |
SIGFPE | 浮点异常信号,如除以零或溢出。 |
SIGILL | 非法指令信号,如执行了无效的机器代码。 |
SIGINT | 中断信号,通常由用户按下Ctrl+C产生。 |
SIGSEGV | 无效内存引用信号,如访问未分配的内存。 |
SIGTERM | 终止信号,要求程序正常退出。 |
SIG_DFL | 默认的信号处理程序宏,表示使用系统默认的信号处理方式。 |
SIG_ERR | 表示信号错误,当信号相关函数执行失败时返回此值。 |
SIG_IGN | 忽略信号的处理程序宏,表示忽略指定的信号。 |
注意:具体的信号种类和处理方式可能因操作系统的不同而有所差异,因此在使用时需要参考特定系统的文档。
示例:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h> // [posix] sleep()
// 信号处理函数
void signal_handler(int signum) {
if (signum == SIGINT) {
printf("Caught SIGINT signal, exiting...\n");
exit(signum); // 退出程序
}
}
int main() {
// 注册SIGINT信号的处理函数
if (signal(SIGINT, signal_handler) == SIG_ERR) {
perror("signal");
exit(EXIT_FAILURE);
}
printf("During the program's operation, you can press Ctrl+C to send a SIGINT signal.\n");
while (1) {
// 无限循环,直到接收到SIGINT信号
sleep(1);
}
return 0;
}
stdlib
分类 | 函数 | 函数定义 | 描述 |
---|---|---|---|
内存管理 | malloc | void *malloc(size_t size); | 分配指定大小的内存,并返回指向它的指针。 |
calloc | void *calloc(size_t num, size_t size); | 分配足够数量的内存块以容纳指定数量的对象,每个对象的大小由 size 指定,并初始化所有字节为 0。 | |
realloc | void *realloc(void *ptr, size_t newsize); | 更改之前分配的内存块的大小。 | |
free | void free(void *ptr); | 释放之前通过 malloc 、calloc 或 realloc 分配的内存。 | |
随机数生成 | rand | int rand(void); | 返回一个伪随机整数。 |
srand | void srand(unsigned int seed); | 设置随机数生成器的种子。 | |
字符串转换 | atof | double atof(const char *nptr); | 将字符串转换为浮点数。 |
atoi | int atoi(const char *nptr); | 将字符串转换为整数。 | |
atol | long int atol(const char *nptr); | 将字符串转换为长整数。 | |
strtod | double strtod(const char *nptr, char **endptr); | 将字符串转换为双精度浮点数,并返回转换后的值。 | |
strtol | long int strtol(const char *nptr, char **endptr, int base); | 将字符串转换为长整数,并返回转换后的值。 | |
strtoll | long long int strtoll(const char *nptr, char **endptr, int base); | 将字符串转换为长长整数,并返回转换后的值。 | |
程序控制 | exit | void exit(int status); | 终止程序执行,并返回一个状态码给操作系统。 |
abort | void abort(void); | 导致程序异常终止。 | |
system | int system(const char *string); | 执行一个 shell 命令,并返回命令的退出状态。 | |
搜索和排序 | bsearch | void *bsearch(const void *key, const void *base, size_t num, size_t size, int (*compar)(const void *, const void *)); | 在数组中执行二分查找,并返回指向找到的元素的指针。 |
qsort | void qsort(void *base, size_t num, size_t size, int (*compar)(const void *, const void *)); | 对数组进行快速排序。 |
string
函数 | 描述 |
---|---|
strcpy(char *dest, const char *src) | 将字符串src 复制到dest 中。 |
strncpy(char *dest, const char *src, size_t n) | 将字符串src 的前n 个字符复制到dest 中。 |
strcat(char *dest, const char *src) | 将字符串src 追加到dest 的末尾。 |
strncat(char *dest, const char *src, size_t n) | 将字符串src 的前n 个字符追加到dest 的末尾。 |
strcmp(const char *s1, const char *s2) | 比较两个字符串s1 和s2 。 |
strncmp(const char *s1, const char *s2, size_t n) | 比较两个字符串s1 和s2 的前n 个字符。 |
strlen(const char *s) | 返回字符串s 的长度(不包括结尾的空字符\0 )。 |
strcspn(const char *s1, const char *s2) | 返回s1 中第一个与s2 中任何字符匹配的字符之前的字符数。 |
strspn(const char *s1, const char *s2) | 返回s1 中第一个不在s2 中的字符之前的字符数。 |
strchr(const char *s, int c) | 在字符串s 中查找字符c 的第一次出现。 |
strrchr(const char *s, int c) | 在字符串s 中查找字符c 的最后一次出现。 |
strstr(const char *s1, const char *s2) | 在字符串s1 中查找子字符串s2 的第一次出现。 |
memchr(const void *s, int c, size_t n) | 在内存块s 的前n 个字节中查找字符c 的第一次出现。 |
memcmp(const void *s1, const void *s2, size_t n) | 比较内存块s1 和s2 的前n 个字节。 |
memcpy(void *dest, const void *src, size_t n) | 将内存块src 的前n 个字节复制到dest 中。 |
memset(void *s, int c, size_t n) | 将内存块s 的前n 个字节设置为字符c 。 |
strtok(char *str, const char *delim) | 根据指定的分隔符delim 来分割字符串str 。 |
需要注意的是,当使用这些函数时,需要确保字符串和内存块的大小足够,以避免缓冲区溢出等安全问题。
同时,对于某些函数(如strcpy
和strcat
),建议使用更安全的版本(如strncpy
和strncat
),以防止潜在的缓冲区溢出问题。
time
函数名称 | 描述 |
---|---|
time_t time(time_t *tloc) | 返回当前时间(自1970年1月1日以来的秒数),如果tloc不是NULL,则也将其设置为当前时间。 |
double difftime(time_t time2, time_t time1) | 返回time2和time1之间的时间差(以秒为单位)。 |
struct tm *localtime(const time_t *timer) | 将time_t类型的时间转换为本地时间,并返回一个指向tm结构体的指针。 |
struct tm *gmtime(const time_t *timer) | 将time_t类型的时间转换为UTC时间,并返回一个指向tm结构体的指针。 |
char *asctime(const struct tm *timeptr) | 将tm结构体转换为字符串形式的日期和时间。 |
char *ctime(const time_t *timer) | 将time_t类型的时间转换为本地时间,并以字符串形式返回。 |
size_t strftime(char *s, size_t maxsize, const char *format, const struct tm *timeptr) | 根据指定的格式,将tm结构体转换为字符串。 |
time_t mktime(struct tm *timeptr) | 将tm结构体转换为time_t类型的时间(秒数)。 |
clock_t clock(void) | 返回程序启动到调用时处理器所使用的时间(毫秒数)。 |
示例:
#include <stdint.h>
#include <stdio.h>
#include <time.h>
void delay_soft_ms(uint32_t ms)
{
volatile uint32_t i = 0;
while(ms--)
{
for(i = 0; i < 0x80000; i++)
{
;
}
}
}
int main(int argc, char *argv[])
{
time_t t0, t1;
time_t t;
struct tm *info;
struct tm tminfo;
time_t t2;
char buf[80];
clock_t clock_start, clock_end;
double cpu_time_used = 0;
printf("----- 当前时间 -----\n");
t = time(NULL);
printf("the count of timer is : %ld\n", (long)t);
printf("ctime : %s", ctime(&t));
printf("----- UTC时间和本地时间 -----\n");
info = gmtime(&t);
printf("gmtime : %s",asctime(info));
info = localtime(&t);
printf("localtime : %s",asctime(info));
printf("----- 指定格式显示 -----\n");
strftime(buf, 80, "%Y-%m-%d %H:%M:%S", info);
printf("Local date and time: %s\n", buf);
printf("----- 计算耗时 -----\n");
time(&t0);
delay_soft_ms(2000);
time(&t1);
printf("the difftime is %0.2f\n", difftime(t1,t0));
printf("----- 手动配置时间 -----\n");
tminfo.tm_year = 2023-1900; // 年份是从1900年开始计算的
tminfo.tm_mon = 0; // 月份是从0开始的
tminfo.tm_mday = 1; // 一个月中的第几天
tminfo.tm_hour = 0;
tminfo.tm_min = 0;
tminfo.tm_sec = 0;
tminfo.tm_isdst = -1; // 让库函数自己去计算夏令时
t2 = mktime(&tminfo);
printf("The time represented by tminfo is %ld\n", (long)t2);
printf("ctime : %s", ctime(&t2));
printf("----- clock -----\n");
clock_start = clock();
delay_soft_ms(2000);
clock_end = clock();
cpu_time_used = ((double) (clock_end - clock_start)) / CLOCKS_PER_SEC;
printf("cpu time used: %.6f seconds\n", cpu_time_used);
return 0;
}