来源: C Primer Plus
1 初识C语言
1.6 语言标准
| 1978 | 1989 | 1990 | 1994 | 2011 |
|---|---|---|---|---|
| K&R C | ANSI C (C89) | ISO C (C90) | C99 | C11 |
1.8 编程机制
源代码文件(source code file): 文件名=基本名(basename).扩展名(extension)
C编程: 源代码(by编译) -> 中间代码(by链接: + 库代码&启动代码) -> 可执行代码
| 编译平台 | 编译工具 | 编译命令 |
|---|---|---|
| UNIX | cc XXX.c | |
| GNU | GCC | gcc -std=c99/c1x/c11 XXX.c |
| LLVM | Clang | clang -std=c99/c1x/c11 XXX.c |
| Linux | GCC | gcc XXX.c |
2 C语言概述
2.2 结构解释
1 | /* C程序结构 */ |
C语言元素: 关键字 标识符 运算符 数据
C语言语句(6种): 标号语句 复合语句 表达式语句 选择语句 迭代语句 跳转语句
1 | /* 一个简单的程序 */ |
- 头文件: 通常指C程序顶部的信息集合,经常包含编译器创建最终可执行程序要用到的信息
- 如定义常量或指明函数名及其如何使用,但函数的实际代码在一个预编译代码的库文件中
- 函数: C程序包含一个或多个函数, 他们是C程序的基本模块; 函数由函数头和函数体组成
- 函数头: 包含函数名、传入函数的信息类型和函数的返回类型
- 函数头: 通过函数名后面的圆括号()可识别函数, 圆括号里可能为空也可能有参数
- 函数体: 被花括号括起来, 由一系列语句、声明组成
- main()函数:
int和void是ANSI C定义main()的一部分, ANSI C之前的编译器需省略void
| int main(void) | 标准形式写法, 移植到不同编译器不会出什么问题 |
| main() | 此形式C90勉强接受, C99和C11标准不允许这样写 |
| void main() | 此形式一些编译器允许这样写, 但所有标准都未认可 |
- 声明: 为变量指定变量名, 并标识该变量中储存的数据类型; C语言中所有变量须先声明才能使用
- C99之前的C语言要求把变量声明在块的顶部, 其他语句不能出现在任何声明之前
- C99和C11遵循C++惯例, 可把声明放在块中的任何位置, 当然变量使用前还是一定要先声明
- 赋值: C语言通过赋值运算符而不是赋值语句完成赋值操作, C语言没有所谓”赋值语句”
- printf()函数:
\n让计算机另起一行,%d告诉计算机以何种形式、在何处输出打印num值- 参数意指传递给函数的信息
- 实参(实际参数, actual argument)是传递给函数的特定值;
- 形参(形式参数)是函数中用于储存值的变量;
- 转义序列(escape sequence)用于代表难以表示或无法输入的字符
- 参数意指传递给函数的信息
- return语句: 有返回值的C函数要有return语句; return语句是一种跳转语句
- 命名: 可以用小写字母、大写字母、数字、下划线来命名; 第一个字符不能是数字; 区分大小写; 注意代码可读性
- C99和C11根据通用字符名(UCN)机制添加了扩展字符集, 包含了除英文字母以外的部分字符
- C99和C11允许更长的标识符名, 但编译器只识别前63个字符; 外部标识符只允许使用31个字符
- C90只允许6个字符, 旧式编译器只允许使用8个字符
2.6 多个函数
1 | /* two_func.c -- 一个文件中包含两个函数 */ |
- 函数声明(function declaration):
void butler();- 早期C语言的函数声明, 只需指定返回类型, 不用描述参数
- C90 C99 C11标准都承认旧版本形式, 但它会逐步被淘汰
- 函数原型(prototype):
void butler(void);- C90新增了函数原型, 指明了函数的属性: 函数返回值, 函数描述参数
- 标准include文件(包含文件)为标准库函数提供了函数原型, 如stdio.h文件包含了printf()的函数原型
- 函数执行: 无论在什么地方所有C程序都从main()开始执行, 但惯例把main()放开头, 因为它提供了程序的基本框架
2.8 关键字和保留标识符
| auto | break | case | char | const | continue | default | do | double | else | enum |
| estern | float | for | goto | if | inline | int | long | register | restrict | return |
| short | signed | sizeof | static | struct | switch | typeof | union | unsigned | void | volatile |
| while | _Alignas | _Aligof | _Atomic | _Bool | _Complex | _Generic | _Imaginary | _Noreturn | _Static_assert | _Thred_local |
- 粗体 表示C90标准新增的关键字; 斜体 表示C99标准新增的关键字; 粗斜体 表示C11标准新增的关键字
3 数据和C
3.1 示例程序
1 | #include <stdio.h> |
- 浮点数类型float可以储存带小数的数字,以便处理更大范围的数据
- scanf()读取键盘的输入:
%f说明要读取的是浮点数&weight告诉scanf()把输入的值赋给weight变量,即用&表明找到weight变量的地点
- printf()进行信息的输出
%.2f中的.2用于精确控制输出,即指定输出的浮点数只显示小数点后两位- printf()语句把输出发送到一个叫作缓冲区(buffer)得到中间存储区域
在缓冲区满或遇到换行字符或需要输入时, 缓冲区中的内容再不断被发送到屏幕上 - 刷新缓冲区: 从缓冲区把数据发送到屏幕或文件
3.2 变量和常量数据
- 数据: 承载信息的数字和字符
- 常量(constant): 数据类型在程序使用之前已经预设好了,在整个程序运行过程中没有变化的数据
- 变量(variable): 数据类型在程序运行期间可能改变或被赋值的数据
3.3 数据: 数据类型关键字
| 位(bit) | 可以储存0或1 |
| 字节(byte) | char类型占用的位(bit)数, 通常1字节均为8位 |
| 字(word) | 设计计算机时给定的自然存储单位 |
int(加signed、unsigned、short、long、long long) 表示基本的整数类型char指定字母和其它字符(# $ % * ...),以及较小的整数floatdoublelong double表示带小数点的数_Bool表示布尔值(trueorfalse)_Complex表示复数_Imaginary表示虚数
整数类型: 没有小数部分, 计算机中整数以二进制数字储存
浮点数类型: 有小数部分, 同数学中的实数, 计算机中浮点数分开为小数部分和指数部分表示和储存
3.4 C语言基本数据类型
1 | #include <stdio.h> |
sizeof: C语言的内置运算符, 以字节为单位给出指定对象(类型/特定量)的大小- 运算对象若是类型: 圆括号必不可少,应写成
sizeof(char)形式 - 运算对象是特定量: 圆括号可有可无,可写成
sizeof name或sizeof 6.28或sizeof(6.28)
- 运算对象若是类型: 圆括号必不可少,应写成
%zd: C99和C11提供%zd转换说明匹配sizeof的返回类型(size_t类型)%zd: 一些不支持C99和C11的编译器可用%u或%lu代替%zd
3.4.1 int类型
- 声明: 为变量赋于名称并分配内存空间
- 初始化: 为变量赋一个初始值
- 进制表示: 通常假定整形常量为十进制,
0前缀表示八进制,0x或0X前缀表示十六进制 - 整形常量/整形字面量:
| 类型 | 十六进制 | 八进制 | 十进制 |
|---|---|---|---|
| char | \0x41 | \0101 | N.A. |
| int | 0x41 | 0101 | 65 |
| unsigned int | 0x41u | 0101u | 65u |
| long | 0x41L | 0101L | 65L |
| unsigned long | 0x41UL | 0101UL | 65UL |
| long long | 0x41LL | 0101LL | 65LL |
| unsigned long long | 0x41ULL | 0101ULL | 65ULL |
- 整型显示:
printf()转换说明%d十进制显示;%(#)o八进制(前缀)显示;%(#)x十六进制(前缀)显示 - 转换说明: printf()函数中的转换说明决定了数据的显示方式, 而不是数据的储存方式
- 转换说明: printf()函数中转换说明的数量应与待打印值(int类型的变量/常量,值为int类型的表达式)的数量相等
- 转换说明: C虽然允许使用大写或小写的多常量后缀, 但是在转换说明中只能用小写
3.4.3 char类型
- 表示: 字符以数值形式储存, 计算机在内存中用数值编码来表示字符(C支持常用的ASCII码及其他编码)
- 类型: C90标准中C语言允许在关键词char前使用signed和unsigned表示有/无符号类型
- 初始化: 可使用数字代码值或字符常量来赋值
- 变量名: 没有引号的(单个)字符
- 字符常量(character constant): 计算机系统使用数值编码的的符号表示,表示为单引号括起来的字符
- 字符串: 用双括号括起来的(单个)字符
- 字符串: 无论普通字符还是转义序列, 只要是双引号括起来颚的字符合集, 就无须用单引号括起来
打印字符
1
2
3
4
5
6
7
8
9#include <stdio.h>
int main()
{
char ch;
printf("Please enter a character.\n");
scanf("%c", &ch);
printf("The code for %c is %d.\n", ch, ch);
return 0;
}非打印字符
- ASCII码表示:
char beep = 7; - 转义序列表示:
char nerf = '\n';
- ASCII码表示:
转义序列(escape sequence)
| 转义序列 | 含义 |
|---|---|
| \a | 警报(ANSI C) |
| \b | 退格 |
| \f | 换页(把活跃位置移至下一页的开始处)(打印机上) |
| \n | 换行(把活跃位置移至下一行的开始处) |
| \r | 回车(把活跃位置移至当前行的开始处) |
| \t | 水平制表符(把活跃位置移至下一个水平制表点)(打印机上) |
| \v | 垂直制表符(把活跃位置移至下一个垂直制表点) |
| \\ | 反斜杠() |
| \’ | 单引号 |
| \” | 双引号 |
| \? | 问号 |
| \0oo | 八进制值(oo必须是有效的八进制数, 即每个o可表示0~7中的一个数) |
| \xhh | 十六进制值(hh必须是有效的十六进制数, 即每个h可表示0~f中的一个数) |
C标准规定警报字符不得改变活跃位置(active position), 即显示设备中下一个字符将出现的位置
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int main()
{
float salary;
printf("\aEnter your desired monthly salary:");
printf(" $_______\b\b\b\b\b\b\b");
scanf("%f", &salary);
printf("\n\t$%.2f a month is $%.2f a year.", salary, salary * 12.0);
printf("\rGee!\n");
return 0;
}
3.4.4 _Bool 类型
C99标准添加了_Bool类型,用于表示布尔值,即逻辑值true和false,原则上仅占用1位存储空间
C语言用值1表示true,值0表示false,所以_Bool类型实际也是一种整数类型
3.4.5 可移植类型: stdint.h和inttypes.h
1 | #include <stdio.h> |
- 精确宽度整数类型(exact-width integer type)
- 最小宽度类型(minimum width type): 保证所表示类型一定是至少有指定宽度的最小整数类型
- 最快最小宽度类型(fast minimum width type)
- 最大的无符号整数类型
3.4.6 浮点数类型: float、double、long double
- 浮点型常量: 默认情况编译器假定是
double类型- 加上
f或F后缀覆盖为float类型 - 加上
l或L覆盖为long double类型
- 加上
- 十六进制表示浮点型常量(C99新增):
- 在十六进制数前加上十六进制前缀(
0x或0X), 用p或P分别代替e和E - 用2的幂代替10的幂(即p计数法)
- 在十六进制数前加上十六进制前缀(
1 | #include <stdio.h> |
- 浮点值的上溢(overflow)和下溢(underflow)
- 上溢: 当计算导致数字过大, 超过当前类型能表达的范围
- C语言规定给其赋一个表示无穷大的特定值
- printf()显示该值为inf或infinity(或有无穷意义的其他内容)
- 下溢: 计算过程中损失了原本末尾有效位上的数字
- 低于正常的(subnormal)浮点值: 损失了类型全精度的浮点值
- 上溢: 当计算导致数字过大, 超过当前类型能表达的范围
1 | #include <stdio.h> |
3.4.6 复数和虚数类型
- 复数类型和虚数类型的支持
- C99标准支持复数类型和虚数类型, 但有所保留
- 一些独立实现(如嵌入式处理器的实现)就不需要使用复数和虚数(如VCR芯片不需要复数)
- 一般虚数类型都是可选项, C11标准把整个复数软件包都作为可选项
- 复数类型
float_Complex: 包含两个float类型的多值, 分别表示复数的实部和虚部double_Complexlong double_Complex
- 虚数类型
float_Imaginarydouble_Imaginarylong double_Imaginary
- complex.h: 若包含该头文件,便可用
complex代替_Complex,Imaginary代替_Imaginary,I代替-1的平方根
3.4.7 其他类型(由基本类型衍生): 数组、指针、结构、联合
4 字符串和格式化输入/输出
4.1 前导程序
1 | /* talkback.c —— 演示与用户交互 */ |
4.2 字符串简介
数组(array): 同类型数据元素的有序序列
字符串(character string): 一个或多个字符的序列 e.g., "I am your father!"
双引号" "不是字符串的一部分,仅用来告知编译器它扩起来的是字符串,正如单引号’’标识单个字符
C语言没有专门用于储存字符串的变量类型,字符串都被储存在char类型的数组中 e.g., char name[40];
| 空字符 | |||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| I | a | m | y | o | u | r | f | a | t | h | e | r | ! | \0 |
C语言用空字符(null character)标记字符串的结束,即字符串一定以空字符结束,占用数组最后一个储存单元
有40个储存单元的字符串最多能储存39个字符,故字符串的数组容量必须至少比待存储字符串的字符数多1
空字符不是数字0,而是非打印字符,其ASCII码值是(或等价于)0
1 | /* sizeof运算符、strlen()函数返回的数据类型d的转换说明: %u或%lu(早期),%zd(C99、C11新增) */ |
- sizeof运算符: 以字节为单位给出对象的大小(字符串数组总的字节大小)
- strlen()函数: 给出字符串中的字符长度(字符串数组中字符信息的字节大小,不包括空字符)
4.3 常量和C预处理器
符号常量(symbolic constant): 符号常量名命名规则与变量相同,但习惯使用大写表示,有时表示为带c_或k_前缀
明示常量(manifest constant): C标准没有此概念,仅提到过#define最简单的用法是定义一个“明示常量”
编译时替换(compile-time substitution): 预处理器可用来定义常量、字符和字符串常量等,格式#define NAME value
const 限定符: const关键字用于限定一个变量为只读,如const int MONTHS = 12;,用起来比#define更灵活
4.4 printf()和scanf()
4.4.1 printf()函数
printf()函数使用的是值,无论是变量、常量还是表达式的值
printf()函数使用%符号来标识转换说明
转换说明(conversion specification): printf()函数打印数据的指令(须与待打印数据的类型相匹配)
转换说明修饰符: 在%和转换字符之间插入修饰符可修饰基本的转换说明(插入多个应注意顺序、不是所有组合都行)
| 转换说明 | 输出 |
|---|---|
| %a | 浮点数、十六进制数、p计数法(C99/C11) |
| %A | 浮点数、十六进制数、p计数法(C99/C11) |
| %c | 单个字符 |
| %d | 有符号的十进制整数 |
| %e | 浮点数、e计数法 |
| %E | 浮点数、e计数法 |
| %f | 浮点数、十进制计数法 |
| %g | 根据值的不同,自动选择%f或%e;%e格式用于指数小于-4、大于或等于精度时 |
| %G | 根据值的不同,自动选择%f或%e;%e格式用于指数小于-4、大于或等于精度时 |
| %i | 有符号十进制整数(与%d相同) |
| %o | 指针 |
| %p | 字符串 |
| %s | 无符号八进制整数 |
| %u | 无符号十进制整数 |
| %x | 无符号十六进制整数,使用十六进制数0f |
| %X | 无符号十六进制整数,使用十六进制数0F |
| %% | 打印一个百分号 |
| 修饰符 | 含义 |
|---|---|
| (标记) - |
待打印项左对称,即从字段的左侧开始打印该项 示例: "%-20s" |
| (标记) + |
有符号值若为正,则在值前面显示加号 有符号值若为负,则在值前显示减号 示例: "%+6.2f" |
| (标记) 空格 |
有符号值若为正,则在值前面显示前导空格(不显示任何符号) 若为负,则在值前面显示减号 +标记覆盖一个空格 </br> 示例:"% 6.2f" |
| (标记) # |
把结果转换为另一种形式: 若是 #o格式,则以0开始 若是 %x或%X格式,则以0x或0X开始 对所有的浮点格式, #保证了即使后面没有任何数字,也打印一个小数点字符 对 %g和%G格式,#防止结果后面的0被删除 示例: "%#o"、"%#8.0f"、"%+#10.3e" |
| (标记) 0 |
对于数值格式,用前导0代替空格填充字段宽度 对于整数格式,若出现 -标记或指定精度,则忽略该标记 示例: "%010d"、"%0.83f" |
| 数字 | 最小字段宽度:若该字段不能容纳待打印的数字或字符串,系统会使用更宽的字段 示例: "%4d" |
| .数字 | 精度: 对于 %e、%E、%f转换,表示小数点右边数字的位数 对于 %g、G,表示有效数字最大位数 对于%s转换,表示待打印字符的最大数量 对于整形转换,表示待打印数字的最小位数 如有必要,使用前导 0来达到这个位数 只使用 .表示其后跟随一个0,所以%.f和%.0f相同 示例: "%5.2f"打印一个浮点数,字段宽度为5字符,其中小数点后有两位数字 |
| h | 和整形转换说明一起使用,表示short int或unsigned short int类型的值 示例: "%hu"、"%hx"、"%6.4hd" |
| hh | 和整形转换说明一起使用,表示signed char或unsigned char类型的值 示例: "%hhu"、"%hhx"、"%6.4hhd" |
| j | 和整形转换说明一起使用,表示intmax_t或uintmax_t类型的值(这些类型定义在stdint.h中) 示例: "%jd"、"%8jx" |
| l | 和整形转换说明一起使用,表示long int或unsigned long int类型的值 示例: "%ld"、"%8lu" |
| ll | 和整形转换说明一起使用,表示long long int或unsigned long long int类型的值(C99) 示例: "%lld"、"%8llu" |
| L | 和浮点转换说明一起使用,表示long double类型的值 示例: "%Lf"、"%10.4Le" |
| t | 和整形转换说明一起使用,表示ptrdiff_t类型的值,ptrdiff_t是两个指针差值的类型(C99) 示例: "%td"、"%12ti" |
| z | 和整形转换说明一起使用,表示size_t类型的值,size_t是sizeof返回的类型(C99) 示例: "%zd"、"%12zd" |
| 整数类型/type | 十进制/dec | 八进制/octal | 十六进制/hex | |
|---|---|---|---|---|
(signed) short (int) |
%hd | %(#)ho | %(#)hx | 有符号类型 |
(signed) int |
%d | %(#)o | %(#)x | 有符号类型 |
(signed) long (int) |
%ld | %(#)lo | %(#)lx | 有符号类型 |
(signed) long long (int) |
%lld | %(#)llo | %(#)llx | 有符号类型 |
unsigned short (int) |
%hu | 无符号类型 | ||
unsigned (int) |
%u | 无符号类型 | ||
unsigned long (int) |
%lu | 无符号类型 | ||
unsigned long long (int) |
%llu | 无符号类型 | ||
(signed)/unsigned char |
||||
(signed) _Bool |
| 浮点数类型/type | 十进制/dec | 指数/e计数法 | 十六进制/hex | |
|---|---|---|---|---|
float |
%f | %e | %a或%A | 至少能表达6位有效数字 |
double |
%f | %e | %a或%A | 至少能表达10位有效数字 |
long double |
%Lf | %Le | %La | 至少能表达10位有效数字 |
4.4.2 参数传递
1 | ... |
这是一种常见的参数传递方式,但是printf()函数读错了字节
首先,程序把传入的值放入被称为栈/stack的内存区域,计算机根据变量类型而不是转换说明把这些值放入栈中
然后,控制转到printf()函数,函数根据转换说明而不是变量类型从栈中读取值
结果,所读取值为n1的前半部分、n1的后半部分、n2的前半部分、n2的后半部分
4.4.3 scanf()函数
scanf()把输入的字符串转换成整数、浮点数、字符、字符串
printf()把整数、浮点数、字符、字符串转换成显示在屏幕上的文本
scanf()和printf()都使用格式字符串和参数列表,两个函数的主要区别在参数列表中
- printf()函数的参数列表使用变量、常量、表达式
- scanf()函数的参数列表使用指向变量的指针
- 若用scanf()函数读取基本变量类型的值,在变量名前加上一个
& - 若用scanf()函数把字符串读入字符数组中,不要使用
&
- 若用scanf()函数读取基本变量类型的值,在变量名前加上一个
1 | /* input.c —— 何时使用& */ |
| 转换说明 | 含义 |
|---|---|
| %c | 把输入解释成字符 |
| %d | 把输入解释成有符号十进制整数 |
| %e %f %g %a | 把输入解释成浮点数 (C99标准新增%a) |
| %E %F %G %A | 把输入解释成浮点数 (C99标准新增%A) |
| %i | 把输入解释成有符号十进制整数 |
| %o | 把输入解释成有符号八进制整数 |
| %p | 把输入解释成指针 (地址) |
| %s | 把输入解释成字符串: 从第1个非空白字符开始到下一个空白字符之前的所有字符都是输入 |
| %u | 把输入解释成无符号十进制整数 |
| %x %X | 把输入解释成有符号十六进制整数 |
| 修饰符 | 含义 |
|---|---|
| * | 抑制赋值 ; 示例: %*d |
| 数字 | 最大字段宽度: 输入达到最大字段宽度处,或第1次遇到空白字符时停止 ; 示例: %10s |
| hh | 把整数作为signed char或等价于unsigned char类型读取 ; 示例: %hhd %hhu |
| ll | 把整数作为long long或unsigned long long类型读取 (C99) ; 示例: %lld %llu |
| h l L | "%hd"、"%hi"表明把对应的值储存为short int类型 "%ho"、"%hx"、"%hu"表明把对应的值储存为unsigned short int类型 "ld"、"li"表明把对应的值储存为long类型 "%lo"、"%lx"、"%lu"表明把对应的值储存为unsigned long类型 "%le"、"%lf"、"%lg"表明把对应的值储存为double类型 在e、f、g前面使用L而不是l,表明把对应的值储存为 long double类型 若无修饰符,d、i、o、x表明对应的值应被储存为 int类型,f、g表明把对应的值储存为float类型 |
| j | 在整型转换说明后面时,表明使用intmax_t或uintmax_t类型 (C99) ; 示例: %jd %ju |
| z | 在整型转换说明后面时,表明使用sizeof的返回类型 (C99) ; 示例: %zd %zo |
| t | 在整型转换说明后面时,表明使用表示两个指针差值的类型 (C99) ; 示例: %td %tx |