float.h
float.h定义了浮点数类型 float、double、long double
的一些宏,规定了这些类型的范围和精度。
FLT_ROUNDS
宏FLT_ROUNDS表示当前浮点数加法的四舍五入方向。
它有以下可能的值。
-1:不确定。
0:向零舍入。
1:向最近的整数舍入。
2:向正无穷方向舍入。
3:向负无穷方向舍入。
(2)FLT_RADIX
宏FLT_RADIX表示科学计数法的指数部分的底(base),一般总是2。
(3)浮点数类型的最大值
FLT_MAX
DBL_MAX
LDBL_MAX
(4)浮点数类型的最小正值
FLT_MIN
DBL_MIN
LDBL_MIN
(5)两个同类型浮点数之间可表示的最小差值(最小精度)
FLT_EPSILON
DBL_EPSILON
LDBL_EPSILON
(6)DECIMAL_DIG
宏DECIMAL_DIG表示十进制有效位数。
(7)FLT_EVAL_METHOD
宏FLT_EVAL_METHOD表示浮点数运算时的类型转换。
它可能有以下值。
-1:不确定。
0:在当前类型中运算。
1:float ...
errno.h
errno 变量
errno.h声明了一个 int 类型的 errno
变量,用来存储错误码(正整数)。
如果这个变量有非零值,表示已经执行的程序发生了错误。
12345678910int x = -1;errno = 0;int y = sqrt(x);if (errno != 0) { fprintf(stderr, "sqrt error; program terminated.\n"); exit(EXIT_FAILURE);}
上面示例中,计算一个负值的平方根是不允许的,会导致errno不等于0。
如果要检查某个函数是否发生错误,必须在即将调用该函数之前,将errno的值置为0,防止其他函数改变errno的值。
宏
变量errno的值通常是两个宏EDOM或ERANGE。这两个宏都定义在errno.h。它们表示调用数学函数时,可能发生的两种错误。
定义域错误(EDOM):传递给函数的一个参数超出了函数的定义域。例如,负数传入sqrt()作为参数。
取值范围错误(ERANGE):函数的返回值太大,无法用返回类型表示。 ...
ctype.h
ctype.h头文件定义了一系列字符处理函数的原型。
字符测试函数
这些函数用来判断字符是否属于某种类型。
isalnum():是否为字母数字
isalpha():是否为字母
isdigit():是否为数字
isxdigit():是否为十六进制数字符
islower():是否为小写字母
isupper():是否为大写字母
isblank():是否为标准的空白字符(包含空格、水平制表符或换行符)
isspace():是否为空白字符(空格、换行符、换页符、回车符、垂直制表符、水平制表符等)
iscntrl():是否为控制字符,比如 Ctrl + B
isprint():是否为可打印字符
isgraph():是否为空格以外的任意可打印字符
ispunct():是否为标点符号(除了空格、字母、数字以外的可打印字符)
它们接受一个待测试的字符作为参数。注意,参数类型为int,而不是char,因为它们允许
EOF 作为参数。
如果参数字符属于指定类型,就返回一个非零整数(通常是1,表示为真),否则返回0(表示为伪)。
下面是一个例子,用户输入一个字符,程序判断是否为英文字母。
...
assert.h
assert()
assert.h头文件定义了宏assert(),用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行。这个宏常常被称为“断言”。
1assert(PI > 3);
上面代码在程序运行到这一行语句时,验证变量PI是否大于3。如果确实大于3,程序继续运行,否则就会终止运行,并且给出报错信息提示。
assert()宏接受一个表达式作为参数。如果该表达式为真(返回值非零),assert()不会产生任何作用,程序继续运行。如果该表达式为假(返回值为零),assert()就会报错,在标准错误流stderr中写入一条错误信息,显示没有通过的表达式,以及包含这个表达式的文件名和行号。最后,调用abort()函数终止程序(abort()函数的原型在stdlib.h头文件中)。
12z = x * x - y * y;assert(z >= 0);
上面的assert()语句类似于下面的代码。
1234if (z < 0) { puts("z less than 0"); abort();}
如 ...
多字节字符
本章介绍 C 语言如何处理非英语字符。
Unicode 简介
C 语言诞生时,只考虑了英语字符,使用7位的 ASCII 码表示所有字符。ASCII
码的范围是0到127,也就是100多个字符,所以char类型只占用一个字节。
但是,如果处理非英语字符,一个字节就不够了,单单是中文,就至少有几万个字符,字符集就势必使用多个字节表示。
最初,不同国家有自己的字符编码方式,这样不便于多种字符的混用。因此,后来就逐渐统一到
Unicode 编码,将所有字符放入一个字符集。
Unicode 为每个字符提供一个号码,称为码点(code
point),其中0到127的部分,跟 ASCII
码是重合的。通常使用“U+十六进制码点”表示一个字符,比如U+0041表示字母A。
Unicode 编码目前一共包含了100多万个字符,码点范围是 U+0000 到
U+10FFFF。完整表达整个 Unicode
字符集,至少需要三个字节。但是,并不是所有文档都需要那么多字符,比如对于
ASCII
码就够用的英语文档,如果每个字符使用三个字节表示,就会比单字节表示的文件体积大出三倍。
为了适应不同的使用 ...
命令行环境
命令行参数
C 语言程序可以从命令行接收参数。
1$ ./foo hello world
上面示例中,程序foo接收了两个命令行参数hello和world。
程序内部怎么拿到命令行参数呢?C
语言会把命令行输入的内容,放在一个数组里面。main()函数的参数可以接收到这个数组。
1234567#include <stdio.h>int main(int argc, char* argv[]) { for (int i = 0; i < argc; i++) { printf("arg %d: %s\n", i, argv[i]); }}
上面示例中,main()函数有两个参数argc(argument
count)和argv(argument
variable)。这两个参数的名字可以任意取,但是一般来说,约定俗成就是使用这两个词。
第一个参数argc是命令行参数的数量,由于程序名也被计算在内,所以严格地说argc是参数数量
+ 1。
第二个参数argv是一个数组,保存了所有的命令行输 ...
多文件项目
简介
一个软件项目往往包含多个源码文件,编译时需要将这些文件一起编译,生成一个可执行文件。
假定一个项目有两个源码文件foo.c和bar.c,其中foo.c是主文件,bar.c是库文件。所谓“主文件”,就是包含了main()函数的项目入口文件,里面会引用库文件定义的各种函数。
123456// File foo.c#include <stdio.h>int main(void) { printf("%d\n", add(2, 3)); // 5!}
上面代码中,主文件foo.c调用了函数add(),这个函数是在库文件bar.c里面定义的。
12345// File bar.cint add(int x, int y) { return x + y;}
现在,将这两个文件一起编译。
1234$ gcc -o foo foo.c bar.c# 更省事的写法$ gcc -o foo *.c
上面命令中,gcc
的-o参数指定生成的二进制可执行文件的文件名,本例是foo。
这个命令运行后,编译器会发 ...
变量说明符
C
语言允许声明变量的时候,加上一些特定的说明符(specifier),为编译器提供变量行为的额外信息。它的主要作用是帮助编译器优化代码,有时会对程序行为产生影响。
const
const说明符表示变量是只读的,不得被修改。
12const double PI = 3.14159;PI = 3; // 报错
上面示例里面的const,表示变量PI的值不应改变。如果改变的话,编译器会报错。
对于数组,const表示数组成员不能修改。
12const int arr[] = {1, 2, 3, 4};arr[0] = 5; // 报错
上面示例中,const使得数组arr的成员无法修改。
对于指针变量,const有两种写法,含义是不一样的。如果const在*前面,表示指针指向的值不可修改。
1234// const 表示指向的值 *x 不能修改int const * x// 或者const int * x
下面示例中,对x指向的值进行修改导致报错。
1234int p = 1const int* x = &p;(*x)++; // 报错
如果 ...
文件操作
本章介绍 C 语言如何操作文件。
文件指针
C 语言提供了一个 FILE
数据结构,记录了操作一个文件所需要的信息。该结构定义在头文件stdio.h,所有文件操作函数都要通过这个数据结构,获取文件信息。
开始操作一个文件之前,就要定义一个指向该文件的 FILE
指针,相当于获取一块内存区域,用来保存文件信息。
1FILE* fp;
上面示例定义了一个 FILE 指针fp。
下面是一个读取文件的完整示例。
123456789101112131415161718#include <stdio.h>int main(void) { FILE* fp; char c; fp = fopen("hello.txt", "r"); if (fp == NULL) { return -1; } c = fgetc(fp); printf("%c\n", c); fclose(fp); return 0;}
上面示例中,新建文件指针fp以后,依次使用了下 ...
I/O 函数
C 语言提供了一些函数,用于与外部设备通信,称为输入输出函数,简称 I/O
函数。输入(import)指的是获取外部数据,输出(export)指的是向外部传递数据。
缓存和字节流
严格地说,输入输出函数并不是直接与外部设备通信,而是通过缓存(buffer)进行间接通信。这个小节介绍缓存是什么。
普通文件一般都保存在磁盘上面,跟 CPU
相比,磁盘读取或写入数据是一个很慢的操作。所以,程序直接读写磁盘是不可行的,可能每执行一行命令,都必须等半天。C
语言的解决方案,就是只要打开一个文件,就在内存里面为这个文件设置一个缓存区。
程序向文件写入数据时,程序先把数据放入缓存,等到缓存满了,再把里面的数据会一次性写入磁盘文件。这时,缓存区就空了,程序再把新的数据放入缓存,重复整个过程。
程序从文件读取数据时,文件先把一部分数据放到缓存里面,然后程序从缓存获取数据,等到缓存空了,磁盘文件再把新的数据放入缓存,重复整个过程。
内存的读写速度比磁盘快得多,缓存的设计减少了读写磁盘的次数,大大提高了程序的执行效率。另外,一次性移动大块数据,要比多次移动小块数据快得多。
这种读写模式,对于程 ...