一、C 标准库

C标准

真正的C标准应该包含两部分内容,一部分用于描述C标准语法,另一个部分则描述C标准库,
一个平台如果说要完整的支持C语言的话,不仅仅要实现C编译器(解释C语法),还要实现C标准库,这样才算符合C标准

各个平台在实现C标准时,大体上可能会存在两种情况:

  • 超额符合
  • 缩水符合

(1)超额符合

所谓超额符合就是,不仅实现了C编译器,实现了C标准库,而且人家还额外的多提供了很多自己平台的C库函数,
比如Linuxglibc,这个是GNULinux开发的C库,这个C库大于C标准库,或者叫标准库C的超集,
glibc除了提供了基本的C标准库函数外,还提供了Linux平台独有的C库函数,甚至包括了LinuxOS API

(2)缩水符合

出现这种情况最多的是单片机开发平台,由于单片机本身的计算机资源的有限,运行完整C库会很吃力,
所以很多单片机平台(IDE)往往只提供了C编译器,只能对基本的C标准语法进行解释,但是并没有提供完整的C标准库

不过有些单片机平台(比如stm32)可能会提供“微缩”版的C标准库,以适应单片机资源有限的情况

当然,有关C标准语法我们需要再说一说,大多数平台在实现编译器时,除了支持C标准语法外,
往往都会加一些属于自己的特有语法,像这些C语法只能由该平台的编译器识别,其它平台的编译器是无法识别的,
如果我们希望我们C代码能够在不同的平台都能编译通过,建议写代码时,尽量使用C标准语法

当然这个说法也不是绝对的,比如进行单片机开发时,我们往往会使用一些很特别的关键字,
这些关键字是只能由该单片机编译器才能识别的语法,不过此时我们还必须要使用这些关键字,要不然代码就没法写了


二、C 标准库组成

C标准库由很多子库函数组成,这些子库可能是以如下这些方式来构成C标准库的

  • (1)每个子库都被独立的做成了静态库或者动态库,然后集合到一起就构成C标准库
  • (2)所有的子库函数全被打包做成了一个静态库或者动态库,这个库就C标准库
  • (3)将某些子库函数打包做成静态库或者动态库,然后这些库合在一起就组成了C标准

第三情况比较常见:
比如在Linux下,将标准io子库、字符串子库等的scanfprintfmallocgmtimestrcpy等常用函数,
统统被打包为了libc.so,而不怎么常用的数学库函数,则被单独的打包为了libm.so

为了方便我们使用哪些常用函数,gcc编译时会自动链接libc.so,而使用不常用的数学库函数时,
gcc必须手动指定-lm来链接libm.so,有关这一点,我们在前面的课程中已经多次提及


三、子库

1. 标准 IO 库

作用:

提供标准IO函数,实现输入输出操作,IOin/out)就是输入输出的意思

头文件:

stdio.h

函数举例
printfscanfgetcharfopenfclosefprintffscanfsprintffwritefreadperror等函数
都是由这个标准io库这个子库来提供的,有关常用的标准IO函数,我们在第九章会详细介绍


2. ctype库

作用:
提供字符操作函数,专门用于字符进行处理

头文件:

ctype.h

函数举例

`isalnum`:判断字符是否为字母或者数字,比如`' '`(空格),这个字符既不是字母也不是数字
`isalpha`:判断字符是否为字母,比如`'a'`就为字母
`isdigit`:判断字符是否为数字,比如`'1'`就为数字
`iscntrl`:判断字符是否为控制字符,比如回车、换行、`D`删除、退格等字符就控制字符。
`islower`:判断是否为小写字母
`toupper`:将小写字母转为大写字母
`...`

字符操作函数用的比较少,所以大家不是很熟悉,而且字符操作函数也比较简单,不需要记性专门的学习,
需要使用时,大家百度一下即可


3. 字符串处理函数库

作用:

提供字符串处理的函数

头文件:

sting.h

函数举例

`strcat`:把某个字符串接到另一个字符串的后面
`strchr`:在字符串中,找出第一次出现某字符的位置
`strcmp`:对比两个字符串是否相等
`strcpy`:将字符串复制到一个字符串数组空间中
`strlen`:统计字符串中字符的个数
`strstr`:在字符串中,找出第一次出现某字符串的位置
`strerror`:将错误号转换为字符串,用于提示函数出了什么错
`...`

字符串处理函数的使用频率还是比较高的,有关常用字符串处理函数,我们会在第六章《数组、字符串》中介绍


4. 数学库

作用:

提供数学函数

头文件:

math.h

函数举例

`abs`:求整数的绝对值
`fabs`:求浮点数的绝对值
`cos`:计算余弦值
`sin`:计算正选值
`sqrt`:开方
`log`:求对数
`log10`:求`log10x`
`pow`:计算某个数的`n`次幂
`...`

在实际的C应用开发中,数学库函数使用的比较少,因此我们不会单独的介绍这些函数,
大家用到时自己百度或则查C标准库函数手册即可


5. 基础工具函数库

作用:

提供基础的工具函数,比如内存管理、字符串转换、随机数、进程环境等

头文件:

stdlib.h

函数举例

(1)字符串转换函数:

`atof`:将字符串转成浮点数,比如将"234.56"转为浮点数234.56
`atoi`:将字符串转成整形数,比如将"234"转为整形数234
`atol`:将字符串转成长整形数
`strtod`、`strtol`、`strtoul`

这几个函数稍微有点麻烦,这里就先不介绍了

以上字符串转换函数,会在第六章《数组、字符串》中,和字符串处理函数一块介绍

(2)随机数函数

`rand`:产生随机数
`srand`:设置产生随机数的种子

这两个函数很简单,大家请自行百度用法。

(3)内存管理函数

`malloc`:从堆内存中分配变量空间
`calloc`:calloc与malloc最大的区别是,calloc会清零开辟的变量空间
`realloc`:重新开辟空间,常用于扩充变量空间
`aligned_alloc`:分配对齐的变量空间
`free`:释放开辟堆变量空间

我们最常用的是mallocfree函数,至于其它的calloc等函数,用不的并不多,
不过我们在《Linux系统编程、网络编程》的第四章会大致的介绍

(4)进程环境相关的函数

`abort`:进程给自己发送一个信号,将自己异常终止
`exit`:在程序的任何函数中调用`exit`时,程序会正常终止

main函数中执行return,也能正常终止,但是在子函数中return,只是返回调用的上一级函数,
exit则不然,不管在什么地方调用exit,进程(程序)都将会正常终止

mainreturnexit是有关系的,有关这一点,就请大家看《Linux系统编程、网络编程》的内容

`atexit`:注册进程退出处理函数,进程(程序)在终止时,会调用退出处理函数进行扫尾处理。
`system`:开辟一个子进程,然后执行另一个新程序
`getenv`:获取某个环境变量
`setenv`:设置环境变量
`...`

以上这些函数都是与进程环境相关的C标准库函数,我们将会在《Linux系统编程、网络编程》的第四章-进程环境 中详细的介绍

6. 其它子库——时间函数、信号处理、线程函数

(1)时间函数
作用:

提供用于获取系统(OS)时间的函数

头文件:

time.h

函数举例

`gmtime`:将一个总秒数的时间,转成年月日分秒,放到一个结构体中,供我们使用“操作系统”基本都是以秒来累计时间的,
这总秒数是从某时间点开始到现在的累计,这个总秒数被换算为年月日分秒后,加上那一点的年月日分秒,即可得到公元纪年的时间

`asctime`:将结构体中的年月日分秒,转为一个字符串形式的时间
`ctime`:直接将累计的总秒数,直接转为字符串形式的时间,比如将总秒数`5443434234`转为`"June 1(st), 2018"`
`mktime`、
`localtime`
...

以上与时间有关的函数,我们在《Linux系统编程、网络编程》的第三章-Linux系统信息中会详细介绍

(2)信号处理函数
作用:

用于处理信号

头文件:

signal.h

函数举例

`signal`:捕获信号,并处理信号
`raise`:给自身发送信号

C标准库只提供了以上两个与信号有关的函数,有关信号处理函数、以及信号,
我们会在《Linux系统编程、网络编程》的第六章—信号中详细介绍,那个时候我们会介绍很多与信号相关的函数,
只不过介绍的函数都是LinuxOS API,其中有两个OS API也叫signalraise,用法都是一致的

所以当你学会了《Linux系统编程、网络编程》的第6章,你自然也就会使用c标准库所提供的signalraise这两个函数了

(3)线程函数
作用:

实现多线程

头文件:

thread.h() # C11

函数举例

thrd_create:创建线程
thrd_detach:通知操作系统,当线程结束时由操作系统负责释放线程所占用的计算机资源
thrd_exit:此函数用于结束当前线程
...

C标准库从2011c11标准的推出时间)后才开始提供C线程库,老版本的编译器可能都不支持C标准库的C线程库,
但是这些编译器可能会提供其它的C线程库,比如gcc编译器提供的是posix线程库,头文件为pthread.h

posix线程库的线程函数为:

pthread_create:功能同thrd_create
pthread_detach:功能同thrd_detach
pthread_exit:功能同thrd_exit
...

首字母p就是posix的意思,posix的线程函数与thrd_createC标准库的线程函数几乎差不多

我们虽然没有讲C标准库所提供的C线程库,但是我们在《Linux系统编程、网络编程》的第八章C线程中,
会详细的介绍posix c线程函数,所以大家只要把posix c线程函数搞定了,C标准库的C线程函数非常简单


四、C 标准库头文件

C 标准库提供了很多的头文件,从c89c11标准,一直都有不断的添加新的头文件,
其中很多的头文件是大家所不熟悉的,主要是因为这些头文件在C应用开发中用的很少,或者几乎用不到,
我们既然时介绍c标准库的组成,那么我们就有必要了解下,C标准库到底提供了哪些.h文件

几乎所有C标准库的头文件,在编译器的安装目中都能找到,如果你知道这些头文件中放的是写啥内容,
大家可以自己去打开看看,当然死扣这些头文件,没有意义

比如在CodeblocksMingW目录下的include目录下,就能找到C标准库中几乎所有的头文件,
当然由于编译器的版本问题,有些c11标准新出的头文件,在里面可能是找不到的

大体上,我们可以将C标准库的头文件划分为两类

  • 一类是功能性头文件
  • 另一类是辅助性头文件

1. 功能性头文件

stdio.hctype.hsting.hmath.hstdlib.htime.hsignal.h

这些头文件,每一个对应着我们前面所介绍的子库,是我们调用库函数时必须要包含的头文件,
属于会被经常用到的C标准库头文件

这些头文件的内容:

库函数会用的宏定义、结构体类型定义、内联函数定义、typdef类型等外、以及库函数声明


2. 辅助性头文件

(1)这些头文件的特点

  • (a)在编程中用的并不频繁,而且其中有少数的头文件,只有C标准库自己会用到,在我们的C应用程序中一般用不到的
  • (b)这些头文件中放的主要是各类宏定义、结构体类型定义,很少放函数声明,
    因为库函数的声明都放在stdio.hctype.hsting.h等功能性头文件中

(2)举例
(a)比较常用的辅助性头文件

`assert.h`:程序调试所用的“断言”宏,就定义在了这个.h中,有关断言,我们第8章再介绍
`errno.h`:函数报错时所用到的各种错误号,就定义坐在了这个.h中,有关函数报错,我们在第8章会介绍
`stdarg.h`:可变参数要用的头文件,我们会在第8章介绍。
`stdbool.h()`:C99标准开始,布尔类型,也就是如果你想使用true、flase来表示真假时,就需要使用这个头文件
bool flag = true;
if(flag)
{
    ...
}

有关bool型,实际用的并不多,因为一般非0为真,0为假就已经很好用了,所以很少有人会经常使用bool型,
而老的编译器也不支持bool型,因为这个是c99才开始支持

(b)其它辅助性头文件
这些头文件我们几乎用不到,不过我们这里还是大概的了解一下,做到心中基本有数即可。

complex.h(C99):与复数运算有关
fenv.h(C99):浮点数环境
float.h:浮点数类型的极限
inttypes.h(C99):整数类型的格式转换
iso646.h(C95):符号的替代写法
limits.h:基本类型的大小
locale.h:本地化工具
stdatomic.h(C11):原子类型
stdint.h(C99):定宽整数类型
stdnoreturn.h:(C11)noreturn 便利宏
tgmath.h(C99):泛型数学(包装 math.h 和 complex.h 的宏)
uchar.h(C11):UTF-16 和 UTF-32 字符工具
wchar.h(C95):扩展多字节和宽字符工具
wctype.h(C95):用来确定包含于宽字符数据中的类型的函数
stdalign.h(C11):定义了alignas和alignof,这两个宏与对齐有关,目前了解即可

setjmp.h:非局部跳转(长跳转)
goto类似,不过goto只能用于函数内部跳转,共同属于局部条转

非局部跳转则是用于实现函数之间的跳转长跳转,长跳转往往会带来一些负面效应,就连goto我们都建议尽量不要使用,
所以长跳转更是不会用到,这里仅仅了解下即可

stddef.h:定义了某些特殊的宏以及类型,比如后面第七章要讲的offsetof宏,就定义在了这个.h