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
语言的解决方案,就是只要打开一个文件,就在内存里面为这个文件设置一个缓存区。
程序向文件写入数据时,程序先把数据放入缓存,等到缓存满了,再把里面的数据会一次性写入磁盘文件。这时,缓存区就空了,程序再把新的数据放入缓存,重复整个过程。
程序从文件读取数据时,文件先把一部分数据放到缓存里面,然后程序从缓存获取数据,等到缓存空了,磁盘文件再把新的数据放入缓存,重复整个过程。
内存的读写速度比磁盘快得多,缓存的设计减少了读写磁盘的次数,大大提高了程序的执行效率。另外,一次性移动大块数据,要比多次移动小块数据快得多。
这种读写模式,对于程 ...
预处理器(Preprocessor)
简介
C
语言编译器在编译程序之前,会先使用预处理器(preprocessor)处理代码。
预处理器首先会清理代码,进行删除注释、多行的语句合成一个逻辑行等等。然后,执行#开头的预处理指令。本章介绍
C 语言的预处理指令。
预处理指令可以出现在程序的任何地方,但是习惯上,往往放在代码的开头部分。
每个预处理指令都以#开头,放在一行的行首,指令前面可以有空白字符(比如空格或制表符)。#和指令的其余部分之间也可以有空格,但是为了兼容老的编译器,一般不留空格。
所有预处理指令都是一行的,除非在行尾使用反斜杠,将其折行。指令结尾处不需要分号。
define
#define是最常见的预处理指令,用来将指定的词替换成另一个词。它的参数分成两个部分,第一个参数就是要被替换的部分,其余参数是替换后的内容。每条替换规则,称为一个宏(macro)。
1#define MAX 100
上面示例中,#define指定将源码里面的MAX,全部替换成100。MAX就称为一个宏。
宏的名称不允许有空格,而且必须遵守 C
语言的变量命名规则,只能使用字母、数字与下划线(_),且 ...
Enum 类型
如果一种数据类型的取值只有少数几种可能,并且每种取值都有自己的含义,为了提高代码的可读性,可以将它们定义为
Enum 类型,中文名为枚举。
12345enum colors {RED, GREEN, BLUE};printf("%d\n", RED); // 0printf("%d\n", GREEN); // 1printf("%d\n", BLUE); // 2
上面示例中,假定程序里面需要三种颜色,就可以使用enum命令,把这三种颜色定义成一种枚举类型colors,它只有三种取值可能RED、GREEN、BLUE。这时,这三个名字自动成为整数常量,编译器默认将它们的值设为数字0、1、2。相比之下,RED要比0的可读性好了许多。
注意,Enum
内部的常量名,遵守标识符的命名规范,但是通常都使用大写。
使用时,可以将变量声明为 Enum 类型。
1enum colors color;
上面代码将变量color声明为enum colors类型。这个变量的值就是常量RED、GREEN、 ...
