# assert.h

assert.h头文件定义了宏assert(),用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行。这个宏常常被称为“断言”。

使用 assert() 有几个好处:它不仅能自动标识文件和出问题的行号,还有一种无需更改代码就能开启或关闭assert()的机制。如果已经确认程序没有问题,不需要再做断言, 就在#include <assert.h>语句的前面,定义一个宏 NDEBUG。

assert(PI > 3);
z = x * x - y * y;
assert(z >= 0);
1
2
3

assert()的缺点是,因为引入了额外的检查,增加了程序的运行时间。

C11 引入了静态断言static_assert(),用于在编译阶段进行断言判断。

static_assert(constant-expression, string-literal);
1

static_assert()接受两个参数,第一个参数constant-expression是一个常量表达式,第二个参数string-literal是一个提示字符串。如果第一个参数的值为false,会产生一条编译错误,第二个参数就是错误提示信息。

static_assert(sizeof(int) == 4, "64-bit code generation is not supported.");
1

static_assert()只在编译阶段运行,无法获得变量的值。如果对变量进行静态断言,就会导致编译错误。 static_assert()的好处是,尽量在编译阶段发现错误,避免运行时再报错,节省开发时间。 static_assert()不会生成可执行代码,所以不会造成任何运行时的性能损失。

# ctypes.h

定义了一系列字符处理函数的原型。

// 参数类型为int,而不是char,因为它们允许 EOF 作为参数。
isalnum():是否为字母数字
isalpha():是否为字母
isdigit():是否为数字
isxdigit():是否为十六进制数字符
islower():是否为小写字母
isupper():是否为大写字母
isblank():是否为标准的空白字符(包含空格、水平制表符或换行符)
isspace():是否为空白字符(空格、换行符、换页符、回车符、垂直制表符、水平制表符等)
iscntrl():是否为控制字符,比如 Ctrl + B
isprint():是否为可打印字符
isgraph():是否为空格以外的任意可打印字符
ispunct():是否为标点符号(除了空格、字母、数字以外的可打印字符)
tolower():如果参数是大写字符,返回小写字符,否则返回原始参数。
toupper():如果参数是小写字符,返回大写字符,否则返回原始参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 下面是一个例子,用户输入一个字符,程序判断是否为英文字母。

#include <stdio.h>
#include <ctype.h>

int main(void) {
  char ch = getchar();

  if (isalpha(ch))
    printf("it is an alpha character.\n");
  else
    printf("it is not an alpha character.\n");

    // 将字符转为大写
    ch = toupper(ch);

  return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# errno.h

C 标准库的 errno.h 头文件定义了正整数变量 errno,它是通过系统调用设置的,在错误事件中的某些库函数表明了什么发生了错误。该宏扩展为类型为 int 的可更改的左值,因此它可以被一个程序读取和修改。

在程序启动时,errno 设置为零,C 标准库中的特定函数修改它的值为一些非零值以表示某些类型的错误。您也可以在适当的时候修改它的值或重置为零。

int x = -1;

errno = 0;

int y = sqrt(x);

if (errno != 0) {
  fprintf(stderr, "sqrt error; program terminated.\n");
  exit(EXIT_FAILURE);
}

if (errno != 0) {
    printf("%s\n",strerror(errno));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# float.h

C 标准库的 float.h 头文件包含了一组与浮点值相关的依赖于平台的常量。这些常量是由 ANSI C 提出的,这让程序更具有可移植性。

这些宏定义了最大的浮点值

FLT_MAX

DBL_MAX

LDBL_MAX

这些宏定义了最小的浮点值

FLT_MIN

DBL_MIN

LDBL_MIN

浮点数范围

两个浮点数只要进行比较时,它们的差值,只需要在-EPS ~ EPS的精度范围内,那么便认定两个数相等。

浮点数本身存储的时候,在计算不尽的时候,会采用“四舍五入”或者其他策略。

DBL_EPSILON 和 FLT_EPSILON 分别是双精度浮点数和单精度浮点数的精度。

FLT_EPSILON

DBL_EPSILON

#include <stdio.h>
#include <float.h>

int main()
{
   printf("The maximum value of float = %.10e\n", FLT_MAX);
   printf("The minimum value of float = %.10e\n", FLT_MIN);

   printf("The number of digits in the number = %.10e\n", FLT_MANT_DIG);

  // 比较两个浮点数是否相等。
   if (fabs(a - b) < FLT_EPSILON) {

   }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# inttypes.h

为 stdint.h 定义的四类整数类型,提供了printf()和scanf()的占位符,提供整数输入的各种进制转换的宏。

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int main(void) {
  int_least16_t x = 3490;
  printf("The value is %" PRIdLEAST16 "!\n", x);
}
1
2
3
4
5
6
7
8

下面是其它的原始占位符对应的占位符。

%i:PRIin PRIiLEASTn PRIiFASTn PRIiMAX
%o:PRIon PRIoLEASTn PRIoFASTn PRIoMAX
%u:PRIun PRIuLEASTn PRIuFASTn PRIuMAX
%x:PRIxn PRIxLEASTn PRIxFASTn PRIxMAX
%X:PRIXn PRIXLEASTn PRIXFASTn PRIXMAX

PRIxPTR // uintptr_t
PRIuPTR // ptrdiff_t

scanf()的占位符规则也与之类似。

%d:SCNdn SCNdLEASTn SCNdFASTn SCNdMAX
%i:SCNin SCNiLEASTn SCNiFASTn SCNiMAX
%o:SCNon SCNoLEASTn SCNoFASTn SCNoMAX
%u:SCNun SCNuLEASTn SCNuFASTn SCNuMAX
%x:SCNxn SCNxLEASTn SCNxFASTn SCNxMAX


#include <stdio.h>
#include <inttypes.h>

int main() {
    int a = 5;
    printf("a=%"PRIu64"\n", a);
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# limits.h

提供了用来定义各种整数类型(包括字符类型)取值范围的宏。

CHAR_BIT:每个字符包含的二进制位数。
SCHAR_MIN:signed char 类型的最小值。
SCHAR_MAX:signed char 类型的最大值。
UCHAR_MAX:unsiged char 类型的最大值。
CHAR_MIN:char 类型的最小值。
CHAR_MAX:char 类型的最大值。
MB_LEN_MAX:多字节字符最多包含的字节数。
SHRT_MIN:short int 类型的最小值。
SHRT_MAX:short int 类型的最大值。
USHRT_MAX:unsigned short int 类型的最大值。
INT_MIN:int 类型的最小值。
INT_MAX:int 类型的最大值。
UINT_MAX:unsigned int 类型的最大值。
LONG_MIN:long int 类型的最小值。
LONG_MAX:long int 类型的最大值。
ULONG_MAX:unsigned long int 类型的最大值。
LLONG_MIN:long long int 类型的最小值。
LLONG_MAX:long long int 类型的最大值。
ULLONG_MAX:unsigned long long int 类型的最大值。

#if INT_MAX < 100000
  #error int type is too small
#endif

// 可以使用limit.h里面的宏,为类型别名选择正确的底层类型。
// 如果整数类型的最大值(INT_MAX)不小于100000,那么类型别名Quantity指向int,否则就指向long int。
#if INT_MAX >= 100000
  typedef int Quantity;
#else
  typedef long int Quantity;
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# locale.h

程序的本地化设置,主要影响以下的行为:

数字格式 货币格式 字符集 日期和时间格式

它设置了以下几个宏。

LC_COLLATE  //影响字符串比较函数strcoll()和strxfrm()。
LC_CTYPE //影响字符处理函数的行为。
LC_MONETARY //影响货币格式。
LC_NUMERIC  //影响printf()的数字格式。
LC_TIME //影响时间格式strftime()和wcsftime()。
LC_ALL  //将以上所有类别设置为给定的语言环境。
1
2
3
4
5
6
// 设置当前的地区
char* setlocale(int category, const char* locale);
//第一个参数表示影响范围,如果值为前面五个表示类别的宏之一,则只影响该宏对应的类别,如果值为
// LC_ALL,则影响所有类别。第二个参数通常只为"C"(正常模式)或""(本地模式)。

//任意程序开始时,都隐含下面的调用。

setlocale(LC_ALL, "C");
//下面的语句将格式本地化。
setlocale(LC_ALL, "");
//第二个参数也可以设为当前系统支持的某种格式。
setlocale(LC_ALL, "en_US.UTF-8");
// setlocale()的返回值是一个字符串指针,表示已经设置好的格式。如果调用失败,则返回空指针 NULL。
char *loc;

loc = setlocale(LC_ALL, NULL);

// 输出 Starting locale: C
printf("Starting locale: %s\n", loc);

loc = setlocale(LC_ALL, "");

// 输出 Native locale: Chinese (Simplified)_China.936
printf("Native locale: %s\n", loc);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

zh-Hans:中文(简体) zh-CN:中文(中国)

打印系统当前的属性值:

#include <stdio.h>
#include <locale.h>
#include <string.h>

int main ()
{
    setlocale (LC_ALL,"zh_CN");
    struct lconv * lc;
    lc=localeconv();
    printf ("decimal_point: %s\n",lc->decimal_point);
    printf ("thousands_sep: %s\n",lc->thousands_sep);
    printf ("grouping: %s\n",lc->grouping);
    printf ("int_curr_symbol: %s\n",lc->int_curr_symbol);
    printf ("currency_symbol: %s\n",lc->currency_symbol);
    printf ("mon_decimal_point: %s\n",lc->mon_decimal_point);
    printf ("mon_thousands_sep: %s\n",lc->mon_thousands_sep);
    printf ("mon_grouping: %s\n",lc->mon_grouping);
    printf ("positive_sign: %s\n",lc->positive_sign);
    printf ("negative_sign: %s\n",lc->negative_sign);
    printf ("frac_digits: %d\n",lc->frac_digits);
    printf ("p_cs_precedes: %d\n",lc->p_cs_precedes);
    printf ("n_cs_precedes: %d\n",lc->n_cs_precedes);
    printf ("p_sep_by_space: %d\n",lc->p_sep_by_space);
    printf ("n_sep_by_space: %d\n",lc->n_sep_by_space);
    printf ("p_sign_posn: %d\n",lc->p_sign_posn);
    printf ("n_sign_posn: %d\n",lc->n_sign_posn);
    printf ("int_frac_digits: %d\n",lc->int_frac_digits);
    printf ("int_p_cs_precedes: %d\n",lc->int_p_cs_precedes);
    printf ("int_n_cs_precedes: %d\n",lc->int_n_cs_precedes);
    printf ("int_p_sep_by_space: %d\n",lc->int_p_sep_by_space);
    printf ("int_n_sep_by_space: %d\n",lc->int_n_sep_by_space);
    printf ("int_p_sign_posn: %d\n",lc->int_p_sign_posn);
    printf ("int_n_sign_posn: %d\n",lc->int_n_sign_posn);

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# signal.h

# 简介

signal.h提供了信号(即异常情况)的处理工具。所谓“信号”(signal),可以理解成系统与程序之间的 短消息,主要用来表示运行时错误,或者发生了异常事件。

头文件signal.h定义了一系列宏,表示不同的信号。

  • SIGABRT:异常中止(可能由于调用了 abort() 方法)。
  • SIGFPE:算术运算发生了错误(可能是除以 0 或者溢出)。
  • SIGILL:无效指令。
  • SIGINT:中断。
  • SIGSEGV:无效内存访问。
  • SIGTERM:终止请求。

POSIX

  • SIGQUIT:退出
  • SIGKILL:kill,不能被捕获或者忽略

上面每个宏的值都是一个正整数常量。

# signal()

头文件signal.h还定义了一个signal()函数,用来指定某种信号的处理函数。

signal(SIGINT, handler);

signal()接受两个参数,第一个参数是某种信号的宏,第二个参数是处理这个信号的函数指针handler。

信号处理函数handler接受一个 int 类型的参数,表示信号类型。它的原型如下。

void (*func)(int);

handler函数体内部可以根据这个整数,判断到底接受到了哪种信号,因为多个信号可以共用同一个处理函数。一旦处理函数执行完成,程序会从信号发生点恢复执行。但是,如果遇到 SIGABRT 信号,处理函数执行完成,系统会让程序中止。

当系统向程序发送信号时,程序可以忽略信号,即不指定处理函数。

signal()的返回值是前一个处理函数的指针,常常把它保存在变量之中,当新的处理函数执行完,再恢复以前的处理函数。

void (*orig_handler)(int);
orig_handler = signal(SIGINT, handler);
// SIGINT 信号发生之后
signal(SIGINT, orig_handler);
1
2
3
4

上面示例中,signal()为信号SIGINT指定了新的处理函数handler,把原来的处理函数保存在变量orig_handler里面。等到handler这个函数用过之后,再恢复原来的处理函数。

# 信号相关的宏

signal.h还提供了信号相关的宏。

(1)SIG_DFL

SIG_DFL 表示默认的处理函数。

signal(SIGINT, SIG_DFL);

上面示例中,SIGINT 的处理函数是默认处理函数,由当前实现决定。

(2)SIG_IGN

SIG_IGN 表示忽略该信号。

signal(SIGINT, SIG_IGN);

上面示例表示不对 SIGINT 信号进行处理。由于程序运行时按下 Ctrl + c 是发出 SIGINT 信号,所以使用该语句后,程序无法用 Ctrl + c 终止。

(3)SIG_ERR

SIG_ERR 是信号处理函数发生错误时,signal()的返回值。

if (signal(SIGINT, handler) == SIG_ERR) {
  perror("signal(SIGINT, handler) failed");
  // ...
}
1
2
3
4

上面示例可以判断handler处理 SIGINT 时,是否发生错误。

# raise()

raise()函数用来在程序中发出信号。

int raise(int sig);

它接受一个信号值作为参数,表示发出该信号。它的返回值是一个整数,可以用来判断信号发出是否成功,0 表示成功,非 0 表示失败。

void handler(int sig) {
  printf("Handler called for signal %d\n", sig);
}

signal(SIGINT, handler);
raise(SIGINT);
1
2
3
4
5
6

上面示例中,raise()触发 SIGINT 信号,导致 handler 函数执行。

# stdint

固定宽度的整数类型

stdint.h 定义了一些固定宽度的整数类型别名,主要有下面三类。

宽度完全确定的整数 intN_t,比如 int32_t。

宽度不小于某个大小的整数 int_leastN_t,比如 int_least8_t。

宽度不小于某个大小、并且处理速度尽可能快的整数 int_fastN_t,比如 int_fast64_t。

上面所有类型都是有符号的,类型名前面可以加一个前缀u,表示无符号类型,比如 uint16_t。

  • int8_t(可选) uint8_t(可选)
  • int16_t(可选) uint16_t(可选)
  • int32_t(可选) uint32_t(可选)
  • int64_t(可选) uint64_t(可选)

uintptr_t 指针,intptr_t 可以安全地在 void* 和 整数之间转换。

固定宽度的整数常量

以下两个类型表示当前系统可用的最大宽度整数。

intmax_t,uintmax_t

固定宽度的整数极限值

下面一些宏代表了固定宽度的整数最大值和最小值。

INT8_MAX INT8_MIN UINT8_MAX
INT16_MAX INT16_MIN UINT16_MAX
INT32_MAX INT32_MIN UINT32_MAX
INT64_MAX INT64_MIN UINT64_MAX
INT_LEAST8_MAX INT_LEAST8_MIN UINT_LEAST8_MAX
INT_LEAST16_MAX INT_LEAST16_MIN UINT_LEAST16_MAX
INT_LEAST32_MAX INT_LEAST32_MIN UINT_LEAST32_MAX
INT_LEAST64_MAX INT_LEAST64_MIN UINT_LEAST64_MAX
INT_FAST8_MAX INT_FAST8_MIN UINT_FAST8_MAX
INT_FAST16_MAX INT_FAST16_MIN UINT_FAST16_MAX
INT_FAST32_MAX INT_FAST32_MIN UINT_FAST32_MAX
INT_FAST64_MAX INT_FAST64_MIN UINT_FAST64_MAX
INTMAX_MAX INTMAX_MIN UINTMAX_MAX
// 注意,所有无符号整数类型的最小值都为0,所以没有对应的宏。
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# stddef.h

ptrdiff_t // uintptr_t 的差。
size_t    // sizeof 的结果
wchar_t   // 宽字符类型

#define NULL
#define offsetof(type, member-designator)
1
2
3
4
5
6