C 程序设计(第四版)谭浩强 著
C 程序设计(第四版)谭浩强 著
Keywords (37)
const auto static extern register
short int long float double signed unsigned _bool char enum struct union
switch case default if else for while do continue break
void return sizeof typedef
goto restrict volatile inline _Complex _Imaginary
ASCII 码对照表
ASCII(American Standard Code for Information Interchange,美国信息互换标准代码,ASCII)是基于拉丁字母的一套电脑编码系统。它主要用于显示现代英语和其他西欧语言。它是现今最通用的单字节编码系统,并等同于国际标准 ISO / IEC 646。
ASCII 第一次以规范标准的型态发表是在 1967 年,最后一次更新则是在 1986 年,至今为止共定义了 128 个字符,其中 33 个字符无法显示(这是以现今操作系统为依归,但在 DOS 模式下可显示出一些诸如笑脸、扑克牌花式等 8-bit 符号),且这 33 个字符多数都已是陈废的控制字符,控制字符的用途主要是用来操控已经处理过的文字,在 33 个字符之外的是 95 个可显示的字符,包含用键盘敲下空白键所产生的空白字符也算 1 个可显示字符(显示为空白)。
二进制 | 十进制 | 十六进制 | 缩写 | 名称/意义 | 二进制 | 十进制 | 十六进制 | 缩写 | 名称/意义 |
---|---|---|---|---|---|---|---|---|---|
0000 0000 | 0 | 00 | NUL | 空字符(Null) | 0001 0001 | 17 | 11 | DC1 | 设备控制一(XON 启用软件速度控制) |
0000 0001 | 1 | 01 | SOH | 标题开始 | 0001 0010 | 18 | 12 | DC2 | 设备控制二 |
0000 0010 | 2 | 02 | STX | 本文开始 | 0001 0011 | 19 | 13 | DC3 | 设备控制三(XOFF 停用软件速度控制) |
0000 0011 | 3 | 03 | ETX | 本文结束 | 0001 0100 | 20 | 14 | DC4 | 设备控制四 |
0000 0100 | 4 | 04 | EOT | 传输结束 | 0001 0101 | 21 | 15 | NAK | 确认失败回应 |
0000 0101 | 5 | 05 | ENQ | 请求 | 0001 0110 | 22 | 16 | SYN | 同步用暂停 |
0000 0110 | 6 | 06 | ACK | 确认回应 | 0001 0111 | 23 | 17 | ETB | 区块传输结束 |
0000 0111 | 7 | 07 | BEL | 响铃 | 0001 1000 | 24 | 18 | CAN | 取消 |
0000 1000 | 8 | 08 | BS | 退格 | 0001 1001 | 25 | 19 | EM | 连接介质中断 |
0000 1001 | 9 | 09 | HT | 水平定位符号 | 0001 1010 | 26 | 1A | SUB | 替换 |
0000 1010 | 10 | 0A | LF | 换行键 | 0001 1011 | 27 | 1B | ESC | 跳出 |
0000 1011 | 11 | 0B | VT | 垂直定位符号 | 0001 1100 | 28 | 1C | FS | 文件分割符 |
0000 1100 | 12 | 0C | FF | 换页键 | 0001 1101 | 29 | 1D | GS | 组群分隔符 |
0000 1101 | 13 | 0D | CR | 归位键 | 0001 1110 | 30 | 1E | RS | 记录分隔符 |
0000 1110 | 14 | 0E | SO | 取消变换(Shift out) | 0001 1111 | 31 | 1F | US | 单元分隔符 |
0000 1111 | 15 | 0F | SI | 启用变换(Shift in) | 0111 1111 | 127 | 7F | DEL | 删除 |
0001 0000 | 16 | 10 | DLE | 跳出数据通讯 |
二进制 | 十进制 | 十六进制 | 图形 | 二进制 | 十进制 | 十六进制 | 图形 | 二进制 | 十进制 | 十六进制 | 图形 |
---|---|---|---|---|---|---|---|---|---|---|---|
0010 0000 | 32 | 20 | (空格) | 0100 0000 | 64 | 40 | @ | 0110 0000 | 96 | 60 | ` |
0010 0001 | 33 | 21 | ! | 0100 0001 | 65 | 41 | A | 0110 0001 | 97 | 61 | a |
0010 0010 | 34 | 22 | " | 0100 0010 | 66 | 42 | B | 0110 0010 | 98 | 62 | b |
0010 0011 | 35 | 23 | # | 0100 0011 | 67 | 43 | C | 0110 0011 | 99 | 63 | c |
0010 0100 | 36 | 24 | $ | 0100 0100 | 68 | 44 | D | 0110 0100 | 100 | 64 | d |
0010 0101 | 37 | 25 | % | 0100 0101 | 69 | 45 | E | 0110 0101 | 101 | 65 | e |
0010 0110 | 38 | 26 | & | 0100 0110 | 70 | 46 | F | 0110 0110 | 102 | 66 | f |
0010 0111 | 39 | 27 | ' | 0100 0111 | 71 | 47 | G | 0110 0111 | 103 | 67 | g |
0010 1000 | 40 | 28 | ( | 0100 1000 | 72 | 48 | H | 0110 1000 | 104 | 68 | h |
0010 1001 | 41 | 29 | ) | 0100 1001 | 73 | 49 | I | 0110 1001 | 105 | 69 | i |
0010 1010 | 42 | 2A | * | 0100 1010 | 74 | 4A | J | 0110 1010 | 106 | 6A | j |
0010 1011 | 43 | 2B | + | 0100 1011 | 75 | 4B | K | 0110 1011 | 107 | 6B | k |
0010 1100 | 44 | 2C | , | 0100 1100 | 76 | 4C | L | 0110 1100 | 108 | 6C | l |
0010 1101 | 45 | 2D | - | 0100 1101 | 77 | 4D | M | 0110 1101 | 109 | 6D | m |
0010 1110 | 46 | 2E | . | 0100 1110 | 78 | 4E | N | 0110 1110 | 110 | 6E | n |
0010 1111 | 47 | 2F | / | 0100 1111 | 79 | 4F | O | 0110 1111 | 111 | 6F | o |
0011 0000 | 48 | 30 | 0 | 0101 0000 | 80 | 50 | P | 0111 0000 | 112 | 70 | p |
0011 0001 | 49 | 31 | 1 | 0101 0001 | 81 | 51 | Q | 0111 0001 | 113 | 71 | q |
0011 0010 | 50 | 32 | 2 | 0101 0010 | 82 | 52 | R | 0111 0010 | 114 | 72 | r |
0011 0011 | 51 | 33 | 3 | 0101 0011 | 83 | 53 | S | 0111 0011 | 115 | 73 | s |
0011 0100 | 52 | 34 | 4 | 0101 0100 | 84 | 54 | T | 0111 0100 | 116 | 74 | t |
0011 0101 | 53 | 35 | 5 | 0101 0101 | 85 | 55 | U | 0111 0101 | 117 | 75 | u |
0011 0110 | 54 | 36 | 6 | 0101 0110 | 86 | 56 | V | 0111 0110 | 118 | 76 | v |
0011 0111 | 55 | 37 | 7 | 0101 0111 | 87 | 57 | W | 0111 0111 | 119 | 77 | w |
0011 1000 | 56 | 38 | 8 | 0101 1000 | 88 | 58 | X | 0111 1000 | 120 | 78 | x |
0011 1001 | 57 | 39 | 9 | 0101 1001 | 89 | 59 | Y | 0111 1001 | 121 | 79 | y |
0011 1010 | 58 | 3A | : | 0101 1010 | 90 | 5A | Z | 0111 1010 | 122 | 7A | z |
0011 1011 | 59 | 3B | ; | 0101 1011 | 91 | 5B | [ | 0111 1011 | 123 | 7B | { |
0011 1100 | 60 | 3C | < | 0101 1100 | 92 | 5C | \ | 0111 1100 | 124 | 7C | | |
0011 1101 | 61 | 3D | = | 0101 1101 | 93 | 5D | ] | 0111 1101 | 125 | 7D | } |
0011 1110 | 62 | 3E | > | 0101 1110 | 94 | 5E | ^ | 0111 1110 | 126 | 7E | ~ |
0011 1111 | 63 | 3F | ? | 0101 1111 | 95 | 5F | _ |
Identifiers
在计算机高级语言中,用来对
变量
、符号常量名
、函数
、数组
、类型
等命名的有效字符序列统称为 标识符(identifier)。
C 语言规定:
- 标识符只能由
字母
、数字
和下划线
3 中字符组成 - 第一个字符必须为
字母
或下划线
- 编译系统将
大写字母
和小写字母
认为是两个不同
的字符,即:大小写敏感 - 不同系统对标识符的字符数有不同的规定,一般允许
7
个字符
一般而言,变量名用小写字母表示,与人们的日常习惯一致,以增强可读性。
Constants
在程序运行过程中,其值不能被改变的量称为常量。
Integer
如 1000
, 12345
, 0
, -345
Floating Number
如 123.456
, 0.345
, -56.79
, 0.0
, 12.0
, 12.34e3
, -346.87e-25
, 0.145E-25
e
和 E
表示 以 10 为底的指数
,之前必须 有数字
,后面必须 为整数
。
Char
如 'a'
, 'Z'
, '3'
, '?'
, '#'
必须用 单撇号
括起来,且只能是 一个字符
;如果使用 双撇号
则可以包含多个字符,但就变成了 字符串
了。
内存中以 ASCII 代码的 二进制形式
存储;如 'a' 的 ASCII 代码为 97,则 内存
中存储为 97 的二进制
。
C 语言未指定使用哪种字符集,字符集由编译系统决定。
C 语言规定,基本字符集中每个字符必须用一个字节表示;空字符 '\0' 也占一个字节
,它所有二进制位都是 0;
中小型计算机系统大都采用 ASCII
字符集,ASCII 是 American Standard Code for Information Interchang
(美国标准信息交换代码)的缩写。
0 ~ 9
的 ASCII 为:48 ~ 57
A ~ Z
的 ASCII 为:65 ~ 90
a ~ z
的 ASCII 为:97 ~ 122
(与对应的大写字母相差 32
)
String
如 "Boy"
, "123"
,必须用双撇号(字符串常量)将若干字符括起来,单撇号(普通字符常量)只能包含一个字符,双撇号内可包含字符串。
Symbol(即:不带参数的宏定义)
如 #define PI 3.1416
,好处为 见名知意
,一改全改
;不占内存,编译时会将所有的 PI 全部替换为 3.1416
Escape Character(表)
C 允许用一种以
\
开头的字符序列,意思是将\
后面的字符转换成另外的意义。
这是一种在屏幕上无法显示的控制字符
,在程序中也无法用一个一般形式的字符来表示,所以只能采用这样的特殊形式来表示。
转义字符 | 字符值 | 输出结果 |
---|---|---|
\' | 一个单撇号(') | 具有此八进制码的字符 |
\" | 一个双撇号(") | 输出此字符 |
\? | 一个双撇号(?) | 输出此字符 |
\\ | 一个反斜线(\) | 输出此字符 |
\a | 警告(alert) | 产生声音或视觉信号 |
\b | 退格(backspace) | 将当前位置后退一个字符 |
\f | 换页(form feed) | 将当前位置移到下一页的开头 |
\n | 换行 | 将当前位置移到下一行的开头 |
\r | 回车(carriage return) | 将当前位置移到本行的开头 |
\t | 水平制表符 | 将当前位置移到下一个 tab 位置 |
\v | 垂直制表符 | 将当前位置移到下一个垂直制表对齐点 |
\o、\oo 或 \ooo 其中 o 代表一个八进制数字 |
与该八进制码对应的 ASCII 字符 | 与该八进制码对应的字符 '\033' 代表 ASCII 代码为 27 的字符,即 ESC 控制符 '\0' 或 '\000' 代表 ASCII 为 0 的控制符,即 “空操作” 字符,常用在字符串中 |
\xh[h...] 其中 h 代表一个十六进制数字 |
与该十六进制码对应的 ASCII 字符 | 与该十六进制码对应的字符 '\x1B' 代表 ASCII 代码为 27 的字符,即 ESC 控制符 |
Variables
变量代表一个有名字的、具有特定属性的一个存储单元
- 有名字,以便在程序中被引用
- 有类型,以便编译系统确定需要分配多大的存储单元
- 程序运行期间变量的值可以被改变
- 必须先定义,后使用
- 对程序编译连接时,由编译系统给每一个变量名分配对应的内存地址
- 从变量中取值,实际上是通过变量名找到相应的内存地址,从该存储单元中读取数据
Constant Variables(C99)
如:const float pi = 3.1415926;
,有类型、占内存、值不可变;可替代符号常量,使用更方便。
Data Type(表)
整形类型 | 浮点类型 | 派生类型 | 其他类型 |
---|---|---|---|
基本整形(int) | 单精度浮点型(float) | 指针类型(*) | 空类型(void) |
短整形(short int) | 双精度浮点型(double) | 数组类型([])\(\color{red}{\mathrm{字符串}}\) | |
长整形(long int) | 长双精度浮点型(long double) | 结构体类型(struct) | |
双长整形(long long int)\(\color{red}{\mathrm{C99}}\) | 复数浮点型(float_complex, double_comple, long long_comple)\(\color{red}{\mathrm{C99}}\) | 共用体类型(union) | |
字符型(char) | 函数类型 | ||
布尔型(bool) \(\color{red}{\mathrm{C99}}\) | |||
枚举类型(enum) |
Integer(表)
用补码的形式存放在内存,减法也可以当作加法来实现,提高 CPU 运算效率
整数的补码等于源码:
5 的源码:0 0 0 0 0 0 0 0 | 0 0 0 0 0 1 0 1
5 的补码:0 0 0 0 0 0 0 0 | 0 0 0 0 0 1 0 1
负数的补码等于源码按位取反后加 1:
5 的源码:0 0 0 0 0 0 0 0 | 0 0 0 0 0 1 0 1
5 的反码:1 1 1 1 1 1 1 1 | 1 1 1 1 1 0 1 0
5 的补码: \(\color{red}{\mathrm{1}}\) 1 1 1 1 1 1 1 | 1 1 1 1 1 0 1 1
有符号
:补码的第 1 位为符号位,0 为正,1 为负
无符号
:存储单元中全部二进位都用作存放数值本身
类型 | 字节数 | 取值范围 |
---|---|---|
[signed] int | 2 | -32768 ~ 32767,即 \(-2^{15}\) ~ \((2^{15} - 1)\) |
4 | -2147483648 ~ 2147483648,即 \(-2^{31}\) ~ \((2^{31} - 1)\) | |
unsigned int | 2 | 0 ~ 65535,即 0 ~ \((2^{16} - 1)\) |
4 | 0 ~ 4294967295,即 0 ~ \((2^{32} - 1)\) | |
[signed] short [int] | 2 | -32768 ~ 32767,即 \(-2^{15}\) ~ \((2^{15} - 1)\) |
unsigned short [int] | 2 | 0 ~ 65535,即 0 ~ \((2^{16} - 1)\) |
[signed] long [int] | 4 | -2147483648 ~ 2147483648,即 \(-2^{31}\) ~ \((2^{31} - 1)\) |
unsigned long [int] | 4 | 0 ~ 4294967295,即 0 ~ \((2^{32} - 1)\) |
[signed] long long [int] | 8 | -9223372036854775808 ~ 9223372036854775808,即 \(-2^{63}\) ~ \((2^{63} - 1)\) |
unsigned long long [int] | 8 | 0 ~ 18446744073709551615,即 0 ~ \((2^{64} - 1)\) |
Floating(表)
实数是以指数形式存放在内存的;因为小数点的位置浮动的同时,改变指数的值就可以保证其值不变,所以实数的指数形式称为
浮点数
如:\(3.14159 \times 10^{0}\),\(0.314159 \times 10^{1}\),\(0.00314159 \times 10^{2}\),\(31.4159 \times 10^{-1}\),\(314.159 \times 10^{-2}\) 等,都代表同一个值
小数点前面的数字为 0,后面的第一个数字不为 0 的表示形式称为 规范化的指数形式
,如:\(0.314159 \times 10^{1}\)
程序以指数形式(%e)输出的时候必然以规范化的形式输出,如:0.314159e001
C 语言中,浮点运算时,全部自动转换为 double 类型,然后再进行运算
+(数符)| .314159(小数) | 1(指数,表示 10^1)
,这是用十进制表示的;实际中存储时,计算机用二进制表示小数,用 2 的次幂表示指数
由于用二进制形式表示一个实数,以及存储单元是有限的,因此不可能得到完全精确的值,只能存储成有限的精度
小数部分占的位越多,有效数字越多,精度越高;指数部分占位越多,能表示的数字范围愈大
e.g. float 变量能存储的 最小整数为:1.2 \(\times\) \(10^{-38}\),不能存放绝对值小于此值的数,所以 float 的范围为:\(\begin{cases} -3.4 \times 10^{38} \text{ ~ } -1.2 \times 10^{-38} \\ 0 \\ 1.2 \times 10^{-38} \text{ ~ } 3.4 \times 10^{38} \end{cases}\)
类型 | 字节数 | 有效数字 | 数值范围 |
---|---|---|---|
float | 4 | 6 | 0 以及 1.2 \(\times\) \(10^{-38}\) ~ 3.4 \(\times\) \(10^{38}\) |
double | 8 | 15 | 0 以及 2.3 \(\times\) \(10^{-308}\) ~ 1.7 \(\times\) \(10^{308}\) |
long double | 8 | 15 | 0 以及 2.3 \(\times\) \(10^{-308}\) ~ 1.7 \(\times\) \(10^{308}\) |
16 | 19 | 0 以及 3.4 \(\times\) \(10^{-4932}\) ~ 1.1 \(\times\) \(10^{4932}\) |
Char(表)
用补码的形式存放在内存
如果将一个负整数赋值给有符号字符型变量是合法的,但它不代表一个字符,而作为一个字节整形变量存储负整数:
signed char c = -6;
printf("%d\n", c); // output -6
printf("%u\n", c); // output 4294967290
类型 | 字节数 | 取值范围 |
---|---|---|
signed char | 1 | -128 ~ 127,即 \(-2^{7}\) ~ \((2^{7} - 1)\) |
unsigned char | 1 | 0 ~ 255,即 0 ~ \((2^{8} - 1)\) |
在定义 char 时,如果既不加 signed 也不加 unsigned,C 标准并未规定作何处理,由编译系统自己决定。与其他整形变量的处理方式不同。
可以用以下方法测定(说明 gcc 按 signed 处理的):
#include <stdio.h>
int main()
{
char c1 = 255;
printf("%d\n", c1); // output -1
unsigned char c2 = 255;
printf("%d\n", c2); // output 255
return 0;
}
Operators and Expressions(表)
优先级 | 运算符 | 含义 | 运算对象个数 | 结合方向 | 举例 |
---|---|---|---|---|---|
1 | ( ) | 圆括号 | - | \(\to\) | (a + 1) |
1 | [ ] | 下标 | - | \(\to\) | a[0] |
1 | -> | 指向结构体成员 | - | \(\to\) | |
1 | . | 结构体成员 | - | \(\to\) | |
2 | ! | 逻辑非 | 1 | \(\color{red}{\gets}\) | !a || a > b _Bool a = !0, b = !'b'; #include <stdbool> bool a = true, b = false; |
2 | ~ | 按位取反 | 1 | \(\color{red}{\gets}\) | |
2 | ++ | 自增 | 1 | \(\color{red}{\gets}\) | i++; |
2 | -- | 自减 | 1 | \(\color{red}{\gets}\) | j--; |
2 | - | 负号 | 1 | \(\color{red}{\gets}\) | -3.14159 |
2 | (类型) | 类型转换 | 1 | \(\color{red}{\gets}\) | (float)(5 % 3) |
2 | * | 指针 | 1 | \(\color{red}{\gets}\) | int *p = NULL; |
2 | & | 取地址 | 1 | \(\color{red}{\gets}\) | int a = 123; p = &a; printf("%d", *p); // 123 |
2 | sizeof | 长度(字节数) | 1 | \(\color{red}{\gets}\) | short s = 1; int i = 2; long l = 3; long long ll = 4; printf("%lu, %lu, %lu, %lu\n", sizeof(s), sizeof(i), sizeof(l), sizeof(ll)); // 2, 4, 8, 8 printf("%d, %d, %ld, %lld\n", s, i, l, ll); // 1, 2, 3, 4 printf("%u, %u, %lu, %llu\n", s, i, l, ll); // 1, 2, 3, 4 float f = 1.0; double d = 2.0; long double ld = 3.0; printf("%lu, %lu, %lu\n", sizeof(f), sizeof(d), sizeof(ld)); // 4, 8, 16 printf("%e, %e, %Le", f, d, ld); // 1.000000e+00, 2.000000e+00, 3.000000e+00 |
3 | * | 乘法 | 2 | \(\to\) | sizeof(1) // 4 sizeof(1 * 1.0) // 8 sizeof(1 * 1.0f) // 4 sizeof(1 * 1.0d) // 8 sizeof(1 * 1.0l // 16 sizeof(1 * 1.0F * 1.0D * 1.0L) // 16 常量也有类型: 整形:小于 2147483648 作为 int 处理,大于 2147483647 作为 long int 处理 实数:默认 double,如 3.14;结尾可加 f,F,d,D,l,L 表示不同的精度 |
3 | / | 除法 | 2 | \(\to\) | 两个实数相除,结果为 double; 两个整数相除结果仍是整数 5 / 3 结果为 1,舍去小数部分 如果除数和被除数中有一个为负数,则舍入的方向是不固定的 如 -5 / 3,结果可能为 -1,也可能为 -2 多数 C 编译系统采取 “向 0 取整” 的方法, 即:5 / 3 = 1,-5 / 3 = -1,取整后向 0 靠拢 |
3 | % | 求余 | 2 | \(\to\) | 参加运算的对象必须为整数,其结果也为整数 如: 8 % 3 的结果为 2 % 以外的运算符的操作数可以是任何算数类型 |
4 | + | 加法 | 2 | \(\to\) | 'A' + 32 = 65 + 32 = 97 = 'a' |
4 | - | 减法 | 2 | \(\to\) | 3.14159 - 1 |
5 | << | 左移 | 2 | \(\to\) | |
5 | >> | 右移 | 2 | \(\to\) | |
6 | < <= > >= | 关系运算符 | 2 | \(\to\) | c > a + b a = b > c f = a > b > c |
7 | == | 关系运算符 | 2 | \(\to\) | a == b < c a > b == c |
7 | != | 关系运算符 | 2 | \(\to\) | a != b |
8 | & | 按位与 | 2 | \(\to\) | |
9 | ^ | 按位异或 | 2 | \(\to\) | |
10 | | | 按位或 | 2 | \(\to\) | |
11 | && | 逻辑与 | 2 | \(\to\) | a > b && x > y 'a' && 3.14f if(0 为 False; 非 0 为 True) |
12 | || | 逻辑或 | 2 | \(\to\) | a == b || x == y a || b || c 中,只要 a 为 1,就不需要判断 b 和 c |
13 | ? : | 条件运算符 | 3 | \(\color{red}{\gets}\) | max = a > b ? a : b max = a > b ? a : b + 1 a > b ? max = a : max = b a > b ? printf("%d", a) : printf("%d", b) |
14 | = += -= *= /= %= >>= <<= &= ^= |= |
赋值运算符 | 2 | \(\color{red}{\gets}\) | a = 3 * 5 a = b = c = 5 a = 5 + (c = 6) a = (b = 4) + (c = 6) a = (b = 10) / (c = 2) a = (b = 3 * 4) a += a -= a * a printf("%d", a = b) if((a = b) > 0) max = a; int a = 3; float f = 3.56; char c = 'a'; int a, b, c = 5; int a = 3, b = 3, c = 3; 整形 = 浮点型,只保留整数部分 浮点型 = 整数,数值不变,但以浮点数形式存储,如 23 变为 23.0 float = double,先将 double 转换 float,只取 6 ~ 7 位有效数字 double 数值大小不能超过 float 的数值范围 如: float f = 123.456789e100 指数为 100,超过了 float 数据的最大范围 i = 'A',结果 i = 65 short = int,只将低字节原封不动底送到被赋值的变量,即发生 “截断” 如: char c = 289,结果变为 33: 289: 0 0 0 0 0 0 0 1 | 0 0 1 0 0 0 0 1 33:0 0 1 0 0 0 0 1 |
15 | , | 逗号运算符 (顺序求值运算符) |
- | \(\to\) | for(i = 0, j = 100; i < j; i++, j--) |
C Statement
; // 空语句,可作为流程跳转点(goto);或作为循环语句中的循环体,循环体是空语句,表示什么也不做
i = i + 1 // 表达式
a = 3 // 表达式
i = i + 1; // 语句,一个表达式的最后加一个分号就可以变为语句
i++; // 语句
x + y; // 语句
a = 3; // 语句
if() {...;} else if() {...;} else {...;}
for([,];;[,]) {...;}
while() {...;}
do {...;} while();
continue;
break;
return 0;
goto
switch(integer type expression) {
case 'A' : ; break;
case 'b' : ; break;
case 97 : ; break;
default: ;
}
printf, scanf, putchar, getchar
格式字符 | 说明 |
---|---|
o | 八进制 unsigned; 无前导符号 0 |
d, i | 十进制 signed |
u, lu, Lu | 十进制 unsigned |
x, X | 十六进制 unsigned; 无前导符号 0x; x: a ~ f; X: A ~ F |
ho, hd, hi, hu, hx, hX | short int |
lo, ld, li, lu, lx, lX | long int |
Lo, Ld, Li, Lu, Lx, LX | long long int |
c | 单个字符 |
s | 字符串 |
f | f: float、double |
e, E | 指数形式; 1.2e+02; 1.2E+02 |
g, G | 选用 %f 或 %e 格式中宽度较短的格式,忽略无意义的 0; g 和 G 指数时对应 e 和 E |
Lf, Le, LE, Lg, LG | long double |
-m.nf, m.nf, -mf, mf, -m.ns, m.ns, -ms, ms | -: 左对齐; m: 数据宽度; n: 小数点位数或预截取的字符串长度; e, E, g, G 同样适用 |
printf("%d", 1);
scanf("%s %s", &a, &b);
putchar('B');
putchar(79); // 79 是 'O' 的 ASCII 码
putchar('Y');
putchar('\n'); // output: BOY + 换行
getchar(); // 从屏幕接收一个字符,多个字符需要调用多次;
printf("%c", getchar());
printf("测试中文\n");
printf("%s", "输出中文");
Arrays
一维数组
// 定义
int a[10]; // 10 个整形元素,下标从 0 开始
int a[3 + 5];
int a[2 * n]; // 只用在非 main 的函数中
static int a[2 * n]; // 错误定义,不合法; 静态存储模式,不可使用变量定义数组
// 初始化
int a[5] = {0, 1, 2, 3, 4};
int a[ ] = {0, 1, 2, 3, 4}; // 由于数组个数已定,定义时可省略数组长度
int a[10] = {0, 1, 2, 3, 4}; // 此时,定义时的数组长度不可省略
int a[5] = {0, 0, 0, 0, 0};
int a[5] = {0}; // 等同于上面的初始化方式,未赋值的元素自动设为 0
// 引用
a[0] = a[5] + a[7] - a[2 * 3];
二维数组
// 定义
float a[3][4];
float a[3][4], b[5][10];
float a[2][3][4]; // 也可以定义三维数组,乃至 n 维
// 初始化
int a[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
int a[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; // 不推荐,容易混乱
int a[ ][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; // 等价于上面的
int a[3][4] = {{1}, {3}, {5}}; // 只对各行第 1 列元素赋值,其余元素为 0
int a[3][4] = {{1}, {0, 6}}; // 其余元素为 0
int a[3][4] = {{1}, {0, 6}, {0, 0, 11}}; // 其余元素为 0
int a[3][4] = {{1}, {}, {9}}; // 其余元素为 0
int a[ ][4] = {{1}, {}, {9}}; // 其余元素为 0,等价于上面的
// 引用
a[2 - 1][2 * 2 - 1];
b[1][2] = a[2][3] / 2;
// 对于 a[3][4], 相当于一个一维数组中每个元素中又嵌套了一个一维数组
a[0] ---- a[0][0] a[0][1] a[0][2] a[0][3]
a[1] ---- a[1][0] a[1][1] a[1][2] a[1][3]
a[2] ---- a[2][0] a[2][1] a[2][2] a[2][3]
二维数组形式上是矩阵形式,但在内存中线性存储的:
内存地址 | 第二维度 | 第一维度 |
---|---|---|
2000 | a[0][0] | 第 0 行元素 |
2004 | a[0][1] | |
2008 | a[0][2] | |
2012 | a[0][3] | |
2016 | a[1][0] | 第 1 行元素 |
2020 | a[1][1] | |
2024 | a[1][2] | |
2028 | a[1][3] | |
2032 | a[2][0] | 第 2 行元素 |
2036 | a[2][1] | |
2040 | a[2][2] | |
2044 | a[2][3] |
字符数组
// \0 为结束标志,系统可自动添加,也可手动添加,算一个字节
char c[5] = {'C', 'h', 'i', 'n', 'a'};
char c[6] = {'C', 'h', 'i', 'n', 'a', '\0'};
int i[6] = {'C', 'h', 'i', 'n', 'a', '\0'}; // 合法,但是浪费空间
char c[ ] = {'C', 'h', 'i', 'n', 'a', '\0'};
char c[] = {"China"};
char c[] = "China";
char c[] = "China\0";
char c[] = "Ch\0ina\0"; // 打印输出 Ch,遇到第一个 '\0' 就停止输出
// 输入 How are you? 回车;多个字符串用空格分隔;每个数组中的空余元素全部补 \0;数组名是数组起始地址,无需 &str1 的形式;
char str1[5], str[2], str[3];
scanf("%s%s%s", str1, str2, str3);
printf("%o", str1); // 以八进制无符号整形输出数组的起始地址,会收到 warning
#include <string.h>
gets(str); // 等价于 scanf("%s", str); // 一次只能操作一个字符串
puts(str); // 等价于 printf("%s", str); // 一次只能操作一个字符串
strcat(str1, str2); // 连接字符串,str1 的长度需要足够大,以容下和合并后的字符串
strcpy(str1, str2); // 拷贝 str2 到 str1 中;str1 长度需容下 str2;str1 位置需为数组名; str2 位置可为数组名也可以是字符串常量
strncpy(str1, str2, n); // 将 str2 中最前面 n 个字符复制到 str1 中取代 str1 中原有的最前面的 n 个字符; n <= strlen(str1);
strcmp(str1, str2); // 从左到右,逐个字符按 ASCII 码值大小比较,直到出现不同字符或遇到 '\0' 为止;返回值为 0,正整数,负整数
strlen(str); // 测字符串的实际长度,不包含 '\0'
strlwr(str); // 转小写
strupr(str); // 转大写
Function
Definition, Declaration and Invoke
// definition
void print_str() {}
void print_str(void) {}
int max(int x, int y) {}
// need to declaration first before invoke it; 否则就将函数定义在调用之前,可以省略声明,但不推荐
// 声明用的是函数首部,也就是函数原型 (function prototype),然后加了一个分号
void print_str();
void print_str(void);
int max(int x, int y);
// invoke function
print_str();
c = max(a, b);
m = max(a, max(b, c));
c = 2 * max(a, b);
printf("%d", max(a, b));
f(f();); // 递归调用
max(a[0], b[3]);
float average(float array[]) {} // 形参数组大小无意义,接收的是实参数组的首地址
float average(float array[][4]) {} // 第二维长度不能省略
average(array);
变量的储存方式
内存中供用户使用的存储空间有三种:
程序区
、静态存储区
和动态存储区
存放在 CPU 中的寄存器的变量已不常用void func(register int a) { register int b;}
,现在的编译器会自动识别使用频繁的变量
静态存储区
全局变量
(函数外定义的变量)和静态局部变量
存储在静态存储区;
- 在整个程序运行期间不释放存储空间
- 只在编译时赋初值一次;如果不赋初值,编译时自动赋初值 0 或 '\0'
int A; int main() {} // A 在函数外,为全局变量
void func() { static int i = 3; print("%d", i++); } // 连续调用 3 次, 输出 3 4 5;函数调用结束仍存在,但其他函数无法引用
动态存储区
不加
static
关键字定义的局部变量
存储在动态存储区,包括函数的形参、函数中定义的变量、函数中程序块 { } 中定义的变量
- 函数调用完,即刻释放空间
- 每调用一次函数,就赋一次初值;如果不赋初值,那么它的值是不确定的
void func(int a) { [auto] int b, c = 3; { int d; } } // 调用完该函数 a, b, c, d 全部释放
作用域
- 函数中定义的变量只在函数范围内有效
- 函数中的语句块 { } 内定义的变量只在该语句块范围内有效
- 全局变量的作用域是从定义处开始,到本程序文件结束
文件内扩展全局变量作用域
int main() {
extern [int] A, B, C; // 把外部变量 A,B,C 的作用域扩展到从此处开始,类型 int 可省略,因为毕竟只是声明,而非定义
}
int A, B, C
文件之间扩展全局变量作用域(先从本文件查找,没有再去其他文件查找,都没有就报错)
// file1.c
int A;
int main() {}
// file2.c
extern A;
void print_str() { printf("%d", A); } // 把 file1 文件中已定义的外部变量的作用域扩展到本文件
全局变量作用域限制在本文件内
static int A; // A 只能在本文件内使用;其他文件即使用 extern A 也无法使用
int main() {}
内部函数和外部函数
static int func(int a, int b) {} // 内部函数,只能在本文件内被使用
// file1.c
int main() { [extern] void func(); }
// file2.c
[extern] void func() {} // 外部函数,可被其他文件调用;extern 可省略
Pointer
引用和定义
int i;
scanf("%d", &i);
printf("%d", i);
int a = 10, b = 100;
int * p1, * p2;
p1 = &a; p2 = &b;
printf("%d, %d", *p1, *p2); // 输出指针变量指向的地址的内容
printf("%o, %o", p1, p2); // 8 进制格式输出指针变量指向的地址
指针变量作为函数参数
if(a < b) { swap(p1, p2); } // 主调函数可以引用被调函数中的变化
void swap(int * p1, int * p2) { // *p1 和 *p2 值互换
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
数组元素的指针
int a[5] = {1, 3, 5, 7, 9};
int * p = &a[0]; // 等价于下面的方式
int * p = a; // 数组名实际上代表的就是数组首元素的地址
引用数组元素时,指针的运算
p = p + 1;
p = p + 1;
p += 1;
p -= 1;
p++; p--; ++p; --p;
p2 - p1; // 两个数组元素之间的相对位置,e.g. (2020 - 2012) / 4 = 2
p1 + p2; // 无意义
a[i] == *(a + i) == *(p + i)
&a[i] == (a + i) == (p + i)
scanf("%d", a + i);
for(p = a; p < (a + 10); p++) { scanf("%d", p); } // 速度较快,p 值可变,a 值无法改变,所以 a++ 不可行
p[i] 被处理成 *(p + i),但必须确认 p 的当前值;如:p 指向 a[3],那么 p[2] 不等于 a[2],而等于 a[3 + 2] // 慎用
用数组名作函数的参数
void func(int x, int y); func(a[1], a[2]) // 值传递
func(int arr[]); 等价于 func(int * arr); // 地址传递
void func(int arr[]) { arr += 3; } // 形参的数组名被当作指针变量处理,所以可以改变其值
int a[10] ; f(a); int f(int x[]) { printf("%d %d %d %d\n", *x, *x++, x[1], x[0]); } // 2 1 2 1
int a[10] ; f(a); int f(int * x ) { printf("%d %d %d %d\n", *x, *x++, x[1], x[0]); } // 2 1 2 1
int a[10], * p = a; f(p); int f(int * x ) { printf("%d %d %d %d\n", *x, *x++, x[1], x[0]); } // 2 1 2 1
int a[10], * p = a; f(p); int f(int x[]) { printf("%d %d %d %d\n", *x, *x++, x[1], x[0]); } // 2 1 2 1
printf("%d %d %d %d\n", x[0], x[1], *++x, *x); // 从左到右计算,++x 在左边,x[1] 溢出;输出 2 xxxxxxx(不确定的数) 2 1
二维数组元素的地址
int a[3][4] = {{1, 3, 5, 7}, {9, 11, 13, 15}, {17, 19, 21, 23}};
a
---> --------------------------------- ------------------ ------------------ ------------------ ------------------
| a[0] = 2000 = *(a+0) | = | 1 = *(*(a+0)+0) | 3 = *(*(a+0)+1) | 5 = *(*(a+0)+2) | 7 = *(*(a+0)+3) |
--------------------------------- ------------------ ------------------ ------------------ ------------------
| a[1] = 2016 = 2000+4x4 = *(a+1) | = | 9 = *(*(a+1)+0) | 11 = *(*(a+1)+1) | 13 = *(*(a+1)+2) | 15 = *(*(a+1)+3) |
--------------------------------- ------------------ ------------------ ------------------ ------------------
| a[2] = 2032 = 2016+4x4 = *(a+2) | = | 17 = *(*(a+2)+0) | 19 = *(*(a+2)+1) | 21 = *(*(a+2)+2) | 23 = *(*(a+2)+3) |
--------------------------------- ------------------ ------------------ ------------------ ------------------
printf("%d, %d\n", a , *a ); // -1016031232, -1016031232
printf("%d, %d\n", a[0] , *(a + 0) ); // -1016031232, -1016031232
printf("%d, %d\n", &a[0] , &a[0][0] ); // -1016031232, -1016031232
printf("%d, %d\n", a[0][0] , **a ); // 1, 1
printf("%d, %d\n", a[1] , a + 1 ); // -1016031216, -1016031216
printf("%d, %d\n", &a[1][0], *(a + 1) + 0 ); // -1016031216, -1016031216
printf("%d, %d\n", a[2] , *(a + 2) ); // -1016031200, -1016031200
printf("%d, %d\n", &a[2] , a + 2 ); // -1016031200, -1016031200
printf("%d, %d\n", a[1][0] , *(*(a + 1) + 0) ); // 9, 9
printf("%d, %d\n", *a[2] , *(*(a + 2) + 0) ); // 17, 17
指向二维数组元素的指针变量
int a[5] = {1, 3, 5, 7, 9};
int * p = a; // 一维数组名可以直接赋值给 p, p 是指向一维数组元素 int 类型的指针
int a[3][4] = {{1, 3, 5, 7}, {9, 11, 13, 15}, {17, 19, 21, 23}};
int * p = a[0]; // 二维数组要想指向元素,则需要使用 a[0], 相当于 *a; p + 1 代表移动 4 个字节(int)
a[1][2] = &a[0][0] + (i * m + j) = a[0] + (1 * 4 + 2) = *(p + 6); // 此时可以使用 i * m + j 的形式引用数组中第二维度的元素
a[2][3] = &a[0][0] + (i * m + j) = a[0] + (2 * 4 + 3) = *(p + 11); // m 为二维数组的列数
指向一维数组的指针(二维数组第二维度的一维数组)
int (*p)[4] = a; // 二维数组如果想使用像一维数组的 p = a 的形式,则需要将 p 指向一维数组的类型,不能是元素的 int 类型
a[1][2] = *(*(p + 1) + 2); // 而在引用数组第二维度的元素时,需要使用 *(*(p + i) + j) 的方式,不能使用 i * m + j 的方式
a[2][3] = *(*(p + 2) + 3); // 因为此时 p + 1 或 a + 1 代表的是移动 16 (4 个 int 元素)个字节,而不是一个 int 类型的 4 个字节
指向数组的指针作函数参数
int a[3][4] = {{1, 3, 5, 7}, {9, 11, 13, 15}, {17, 19, 21, 23}};
func1(int * p) { *(p + i * m + j) }
func1(*a);
func2(int (*p)[4]) { *(*(p + i) + j) }
func2(a);
字符串的引用方式
char string[] = "I love China!"; // 长度 14,包括一个结束符 '\0'
printf("%s", string);
printf("%c", string[7]);
char * string = "I love China!"; // Clang 对字符串按字符数组处理,这种方式没有数组名,只能通过指针变量名来操作
string = "I am a student."; // 可以改变 string 的指向
将字符串 a 复制到字符串 b
char a[] = "I am a student.", b[20];
for(int i = 0; *(a + i) != '\0'; i++) { *(b + i) = *(a + i) }
*(b + i) = '\0';
char a[] = "I am a student.", b[20], * p1 = a, * p2 = b;
for(; *p1 != '\0'; p1++, p2++) { *p2 = *p1 }
*p2 = '\0';
void copy(char * from, char * to) { while(*to++=*from++); }
// 不能直接用字符串初始化指针变量 to,因为字符串是常量,初始化了之后,to 指向的地址的内容就不能被修改了
// 例如: char *to = "You are a student.";
char arr_to[] = "You are a student.", *to = arr_to;
char *from = "I am a teacher.";
copy(from, to);
printf("%s -> %s\n", from, to); // I am a teacher. -> I am a teacher.
可变格式输出
char * format;
format = "a = %d, b = %f \n";
printf(format, a, b);
char format[] = "a = %d, b = %f \n";
char format[];
format = "a = %d, b = %f \n"; // 非法,还是用字符指针更方便,可以随时修改
用函数指针变量调用函数
int func1(int x, int y) {}
int func2(int x, int y) {}
int func1(int x, int y);
int func2(int x, int y);
int a = 10, b = 100, c;
c = func1(a, b); // 通过函数名调用
int (* p)(int, int); // 定义指向函数的指针变量 p,() 不能省略,如果是 int * p(int, int) 就变成函数 p 的返回值是 int 指针类型了
if() { p = func1 } // 通过指针调用
if() { p = func2 }
c = (*p)(a, b) // 根据不同情况,调用不同函数,但是调用的语句不变
用指向函数的指针做函数参数(回调函数)
int func(int x, int y, int (* p)(int, int)) { return (*p)(x, y); }
int max(int x, int y) {}
int min(int x, int y) {}
int add(int x, int y) {}
int max(int x, int y); // 函数声明不能省
int min(int x, int y);
int add(int x, int y);
if() { func(a, b, max); } // p = max; 的过程隐藏在了 func 调用时的参数传递中
else if() { func(a, b, min); }
else if() { func(a, b, add); }
指针数组(类比于:于二维数组第一维度的数组)
图书馆有若干本书,想把书名放在一个数组中,然后对这些书名排序和查询。按一般方法,字符串本身就是一个字符数组,因此可以设计一个二维数组。但是在定义二维数组的时候需要定义列数,也就是说二维数组中每一行中包含的元素个数(即列数)相等。而实际上各个书名的字符串长度一般是不相等的。如按最长的字符串来定义列数,则会浪费许多的内存单元。所以,用指针数组来实现更为合适,操作起来也更加灵活。
char * name[] = {"Follow me", "BASIC", "Great Wall", "FORTRAN", "Computer design"};
char * p = *(name + 1);
printf("%s", p); // BASIC
指向指针数据的指针(类比于:指向二维数组第二维度的一维数组的指针)
char * name[] = {"Follow me", "BASIC", "Great Wall", "FORTRAN", "Computer design"};
二维数组时,可以定义 char (*p)[4] = name; 但是指针数组中的元素长度不同,所以就不能使用这种方式了,可以使用 char * * p = name;
char * * p = name + 1;
printf("%s", *p); // BASIC; %s 格式可以自动取出指针指向的地址中的内容
printf("%d", *p); // name[1] 的值,它是一个指向 "BASIC" 的地址
int a[5] = {1, 3, 5, 7, 9};
int * num[5] = {&a[0], &a[1], &a[2], &a[3], &a[4]};
int * * p = num;
printf("%d ", **p); // 1
指针数组作 main 函数的形参
#include <stdio.h>
int main(int argc, char * argc[]) {
while(argc-->1) {
printf("%s%c", *++argv, (argc > 1) ? '' : '\n');
}
return 0;
}
$ ./echo Computer and C language 回车
输出: Computer and C language
注:文件名本身(本例中的 "./echo")也被传入,即 argv[0]
argc : argument count 缩写
argv : argument vector 缩写
argc 和 argv 可以使用其他名代替
内存的动态分配
全局变量或静态局部变量被分配在内存中的静态存储区,非静态局部变量被分配在内存的动态存储区,这个存储区称为 “栈”(stack)
C 语言允许建立内存动态分配区域,以存放一些临时数据,这些数据不必在程序的声明部分定义,也无需等到函数结束时才释放,而是需要时随时开辟,不需要时随时释放。这些数据被临时存放在一个特别的区域,称为 “堆”(heap)
由于未在声明部分定义它们为变量或数组,因此不能通过变量名或数组名去引用这些数据,只能通过指针来引用
void * malloc(unsigned int size); // C99
在内存的堆中分配一个长度为 size 的连续空间;void * 表示返回一个没有类型的指针,指向分配区域的第一个字节的地址;失败返回 NULL
void * calloc(unsigned n, unsigned size); // C99
在内存的堆中分配 n 个长度为 size 的连续空间,即动态数组;成功返回指向分配区域的起始位置的指针,失败返回 NULL
void free(void * p);
释放指针变量 p 所指向的动态空间,使这部分空间可重新被其他变量使用;p 应是最近一次调用 calloc 或 malloc 函数时的返回值;free 无返回值
void * realloc(void * p, unsigned int size); // C99
如果已经使用 malloc 或 calloc 函数获得了空间,想改变其大小,可以用 realloc 函数重新分配;
realloc 函数将 p 指向的动态空间的大小改变为 size,p 值不变;失败返回 NULL
以上 4 个函数声明在 stdlib.h 头文件中
以前 C 版本提供的 malloc 和 calloc 得到的是指向字符型数据的指针:
char * malloc(unsigned int size);
int * pt = (int *)malloc(100); // 使用时可能需要强制转换
C99 已经将 malloc, calloc realloc 函数的基本类型定为 void * 类型,称为无类型指针(typeless pointer),在将它赋值给另一个指针变量时由系统对它进行类型转换,使之适合于被赋值的变量的类型。
int a = 3;
int * p1 = &a;
char * p2;
void * p3;
p3 = (void *)p1;
p2 = (char *)p3;
printf("%d", *p1); // 合法
p3 = &a;
printf("%d", *p3); // 错误,p3 为 void * 类型,赋值后 p3 得到 a 的纯地址,但不指向 a,不能使用 *p3 输出 a 的值
printf("%d", *(int *)p3); // 合法,指向了 int * 类型了
int * p = (int *)malloc(5 * sizeof(int)); // (int *) 强制转换可以省略,系统会自动转换
for(int i = 0; i < 5; i++) {
scanf("%d", p + i);
printf("%d", p[i]);
}
指向结构体变量的指针
struct Student {
long num;
char name[20];
char sex;
float score;
};
struct Student stu_1;
struct Student * p = &stu_1;
stu_1.num = 10101;
strcpy(stu_1.name, "LiLin"); // 字符串函数给 stu_1.name 赋值
stu_1.sex = 'M';
stu_1.score = 89.5;
// 三种方式引用结构体的成员
printf("%ld %s %c %5.1f \n", stu_1.num, stu_1.name, stu_1.sex, stu_1.score); // 10101 LiLin M 89.5
printf("%ld %s %c %5.1f \n", (*p).num, (*p).name, (*p).sex, (*p).score); // 10101 LiLin M 89.5
printf("%ld %s %c %5.1f \n", p->num, p->name, p->sex, p->score); // 10101 LiLin M 89.5
指向结构体数组的指针
指向结构体对象的指针,也可以指向结构体数组中的元素
struct Student {
int num;
char name[20];
char sex;
int age;
};
struct Student stu[3] = {{10101, "LiLin", 'M', 18}, {10102, "ZhangFan", 'M', 19}, {10103, "WangMin", 'F', 20}};
struct Student * p = NULL;
printf("No. Name sex age\n");
for(p = stu; p < stu + 3; p++) {
printf("%5d %-15s %2c %6d \n", p->num, p->name, p->sex, p->age);
}
// output:
No. Name sex age
10101 LiLin M 18
10102 ZhangFan M 19
10103 WangMin F 20
p 是一个指向结构体的指针,不应该用它来指向结构体的成员,如:
p = stu[1].name; // 编译时会给出警告信息
p = (struct Student *)stu[1].name; // 可以使用强制类型转换
强转后,p 的值指向 stu[1] 元素的 name 成员的起始地址,可以用 printf("%s", p); 输出 stu[1] 中成员 name 的值
但是 p 仍保持原来的类型,如果执行 p + 1, 则会输出 stu[2].name 的值,即 p 的值增加了结构体 Student 的长度
用指向结构体的指针或结构体数组作为函数参数
用结构体成员和结果体变量作为函数的参数都是值传递,效率不高;推荐使用指向结构体的指针或结构体数组作为函数的参数,为址传递。
#define N 2
struct Student f0(struct Student stu[]) {
return *(stu); // or stu[0]
}
struct Student * f1(struct Student * p) {
return &p[1]; // or p++
}
struct Student {
int num_a;
int num_b;
};
struct Student stu[N] = {{2, 4}, {3, 6}}, * p = stu;
struct Student f0(struct Student stu[]);
struct Student * f1(struct Student * p);
struct Student * f2(struct Student * p);
struct Student stu_0 = f0(p);
struct Student * stu_1 = f1(stu);
struct Student * stu_2 = f1(&stu[0]);
struct Student * stu_3 = f1(&stu[1]);
struct Student * stu_4 = f1(&stu[2]); // 不报错,但是已经溢出了
printf("%d %d\n", stu_0.num_a, stu_0.num_b); // output: 2 4
printf("%d %d\n", stu_1->num_a, stu_1->num_b); // output:3 6
printf("%d %d\n", (*stu_1).num_a, (*stu_1).num_b); // output:3 6
printf("%d %d\n", stu_2->num_a, stu_2->num_b); // output:3 6
printf("%d %d\n", stu_3->num_a, stu_3->num_b); // output:-174219776 32767 溢出,输出不确定的数
printf("%d %d\n", stu_4->num_a, stu_4->num_b); // output:1802865920 -74099725 溢出,输出不确定的数
Structure、Union、Enumeration、typedef
Structure
// 结构体定义、结构体变量定义、结构体赋初值(赋初值时:未赋值的其他成员给予相应类型的初值:0,'\0', NULL)
struct Data { int m; int d; int y; };
struct Data { int m; int d; int y; } brithday;
struct Data { int m; int d; int y; } brithday = {12, 31, 1990};
struct Data { int m; int d; int y; } brithday = {};
struct Data brithday;
struct Data brithday = {12, 31, 1990};
struct Data brithday = {};
struct Data brithday = {.d = 31}; // C99
struct { int m; int d; int y; } brithday; // 无名结构体,之后无法再次使用此结构体类型定义新的结构体变量
struct { int m; int d; int y; } brithday = {};
struct { int m; int d; int y; } brithday = {12};
struct { int m; int d; int y; } brithday = {12, 31, 1990};
struct Student { int num; char name[20]; struct Data brithday; } stu1, stu2;
struct Student { int num; char name[20]; struct Data brithday; } stu = {};
struct Student { int num; char name[20]; struct Data brithday; } stu = {10101};
struct Student { int num; char name[20]; struct Data brithday; } stu = {.name = "Xiao Ming"};
struct Student { int num; char name[20]; struct Data brithday; } stu = {10101, "Xiao Ming", 12, 31, 1999};
struct Student stu; stu = {}; // 间接赋初值
struct Student stu = {}; // 直接赋初值
struct Student stu = {.name = "Xiao Ming"}; // C99
struct Student stu = {10101};
struct Student stu = {10101, "Xiao Ming", 12, 31, 1999};
struct Student stu = {.brithday.d = 31}; // C99
// 给成员赋值之前必须要进行赋初值
stu.num = 10102;
stu.brithday.d = 31;
strcpy(stu.name, "Xiao Fang");
// 引用结构体成员
printf("%lu\n", sizeof(stu)); // 4 的倍数个字节
printf("%d\n", stu.num);
printf("%s\n", stu.name);
printf("%d\n", stu.brithday.d);
// 结构体变量相互赋值
stu2 = stu1;
// 结构体数组
struct Person { char name[20]; int count; } leader[3] = {"Li", 0, "Zhang", 0, "Sun", 0};
struct Person { char name[20]; int count; } leader[3] = { {"Li", 0}, {"Zhang", 0}, {"Sun", 0} };
Union
共用体(Union)可以用同一段内存单元存放不同类型的变量,每次只能使用一个变量;变量之间可能占用的字节不同,但是起始地址相同
union Data { int i; char ch; float f; }; union Data a, b, c;
union Data { int i; char ch; float f; } a, b, c;
union { int i; char ch; float f; } a, b, c;
union Data a = {};
union Data a = {16}; // 最多只能给一个成员赋初值
union Data a = {.ch = 'j'}; // C99
a.ch = 'a'; a.f = 1.5; a.i = 40; // 起作用的只有最后一次的赋值,上一次的值会被覆盖
printf("%d", a.i);
printf("%c", a.ch);
printf("%f", a.f);
a = b; // 同类型的共用体之间可以相互赋值
结构体是求同;共用体是存异;结构体和共用体结合可以 “求同存异
”。
struct {
int num;
char name[20];
union {
int class;
char position[10];
} category;
} person[2] = { {10001, "Xiao Ming", 3}, {10002, "Teacher Zhang", {.position = "Zhi Wu"} } };
printf("%d", person[0].category.class); // 3
printf("%s", person[1].category.position); // Zhi Wu
Enumeration
enum {sun, mon, tue, wed, thu, fri, sat} workday, weekend;
enum {sun, mon, tue, wed, thu, fri, sat} workday, weekend = fri;
enum {sun, mon, tue, wed, thu, fri, sat} workday = wed, weekend = fri;
enum Weekday {sun, mon, tue, wed, thu, fri, sat}; // 0,1,2,3,4,5,6
enum Weekday {sun = 4, mon = 1, tue, wed, thu, fri, sat}; // 4,1,2,3,4,5,6
enum Weekday {sun = 7, mon = 1, tue, wed, thu, fri, sat}; // 7,1,2,3,4,5,6
enum Weekday workday, weekend;
workday = mon;
weekend = sun;
weekend = 'a'; // 居然不报错 ?
if(workday == mon) ...
if(workday > sun)...
typedef
简单地用一个新的类型名代替原有类型名
typedef int Integer; // 适应使用 FORTRAN 的人的习惯
Integer i, j; // int i, j;
typedef float Real; // 适应使用 FORTRAN 的人的习惯
Real a, b; // fload a, b;
typedef int Count; // 一目了然是用于计数的
Count i, j;
命名一个简单的类型代替复杂的类型表示方法
// 一些看起来比较复杂的类型
float * [] // 指针数组
float (*) [] // 指向 5 个元素的一维数组的指针
double * (double *) // 定义函数,函数的参数和返回值都是指向 double 类型的指针
double (*) () // 指向函数的指针,函数返回值 double 类型
int * ( * (*)[10] )(void) // 指向包含 10 个元素的一维数组的指针,数组元素为函数指针,函数无参数,函数返回 int 指针
typedef struct { int month; int day; int year; } Date;
Date birthday; // 定义结构体变量,不要写成 struct Date birthday;
Date *p; // 定义结构体指针变量 p 指向此结构体类型的数据
typedef int Num[100];
Num a; // 定义 a 为整形数组名,它有 100 个元素
typedef char * String;
String p, s[10]; // 定义 p 为字符指针变量,s 为字符指针数组
typedef int ( * Pointer )(); // 声明 Pointer 为指向函数的指针,该函数返回 int
Pointer p1, p2; // p1, p2 为 Pointer 类型的指针变量
typedef int Arr[10]; // 比 int a[10], b[10], c[10], d[10]; 简单很多
Arr a, b, d, d;
#define Count int; // 预编译时处理,只做简单的字符串替换
typedef int Count; // 编译阶段处理,不是简单的做字符串替换
typedef int Integer; // 便于在不同系统之间移植
typedef long Integer;
#include <xxx.h> // 常用的类型可以使用 typedef 声明,放在同一个文件,再一起 include 进来
Bitwise
参加位运算的对象只能是
整型
或字符型
的数据,不能是实型
数据
可以和=
一起形成复合赋值语句&=, |=, ^=, >>=, <<=
short 和 int 进行位运算,系统会将二者右对齐,然后:如果 short 为正,则左侧 16 位补满 0,如果负数则补满 1
https://www.jianshu.com/p/4071090051b6
Bitwise And &
参加按位 "与" 的两个相应的二进制位都为 1,则结果为 1,否则为 0
0 0 0 0 0 1 1 1 = 7
(&) 0 0 0 0 0 1 0 1 = 5
------------------------
0 0 0 0 0 1 0 1 = 5
如果参加 & 运算的是负数,则以补码形式表示为二进制数,然后按位进行 "与" 运算
清零:
0 0 1 0 1 0 1 1
(&) 1 0 0 1 0 1 0 0
------------------------
0 0 0 0 0 0 0 0
取一个数中某些指定位(例如:取低位字节和高位字节):
0 0 1 0 1 1 0 0 | 1 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 | 1 0 1 0 1 1 0 0
(&) 0 0 0 0 0 0 0 0 | 1 1 1 1 1 1 1 1 (&) 1 1 1 1 1 1 1 1 | 0 0 0 0 0 0 0 0
----------------------------------------- -----------------------------------------
0 0 0 0 0 0 0 0 | 1 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 | 0 0 0 0 0 0 0 0
要想将哪一位保留下来,就与一个数进行 & 运算,此数在该位取 1,例如:保留左面第 3,4,5,7,8 位:
0 1 0 1 0 1 0 0 = 84
(&) 0 0 1 1 1 0 1 1 = 59
------------------------
0 0 0 1 0 0 0 0 = 16
Bitwise Or |
按位 "或" 的运算规则是: 两个对应的二进制位中只要有一个位 1,则该位的结果值为 1
0 0 1 1 0 0 0 0
(|) 0 0 0 0 1 1 1 1
------------------------
0 0 1 1 1 1 1 1
按位 "或" 常用来对一个数据的某些位定值为 1
Exclusive Or ^
异或运算符 "^" 也称 XOR 运算符。它的规则是:若参加运算的两个二进制位异号,则得 1,若同号,则结果为 0
0 0 1 1 1 0 0 1 = 57
(^) 0 0 1 0 1 0 1 0 = 42
------------------------
0 0 0 1 0 0 1 1 = 19
使特定位翻转,例如:低 4 位翻转,前 4 位不变:
0 1 1 1 1 0 1 0
(^) 0 0 0 0 1 1 1 1
------------------------
0 1 1 1 0 1 0 1
与 0 相 ^,保留原值:
0 0 0 0 1 0 1 0
(^) 0 0 0 0 0 0 0 0
------------------------
0 0 0 0 1 0 1 0
交换两个值,不用临时变量;例如:a = 3, b = 4; 交换 a 和 b 得值:
a = a^b;
b = a^b;
a = a^b;
a = 0 0 0 0 0 0 1 1 = 3
(^) b = 0 0 0 0 0 1 0 0 = 4
-------------------------------
a = 0 0 0 0 0 1 1 1 = 7
(^) b = 0 0 0 0 0 1 0 0 = 4
-------------------------------
b = 0 0 0 0 0 0 1 1 = 3
(^) a = 0 0 0 0 0 1 1 1 = 7
-------------------------------
a = 0 0 0 0 0 1 0 0 = 4
推导过程为(a ^ 0 = a; a ^ a = 0):
b = a ^ b ^ b = a
a = a ^ b ^ b ^ a ^ b = a ^ a ^ b ^ b ^ b = b
Bitwise Not ~
(~) 0 0 0 0 0 0 0 0 | 0 0 0 1 0 1 0 1 = 21
----------------------------------------------
1 1 1 1 1 1 1 1 | 1 1 1 0 1 0 1 0 = 65514
若一个整数 a 为 16 位,想使最低一位为 0,可以用:a = a & 65534,即:
0 0 0 0 0 0 0 0 | 0 0 1 1 1 1 0 1 = 61
(&) 1 1 1 1 1 1 1 1 | 1 1 1 1 1 1 1 0 = 65534
----------------------------------------------
0 0 0 0 0 0 0 0 | 0 0 1 1 1 1 0 0 = 60
但是如果 a 为 32 位,就不能用 a = a & 65534,需要用 a = a & 4294967294 了,这种方法不容易记,可以改用 a = a & ~1
~1 = 4294967294 = 1 1 1 1 1 1 1 1 | 1 1 1 1 1 1 1 1 | 1 1 1 1 1 1 1 1 | 1 1 1 1 1 1 1 0
Left Shift and Right Shift <<
,>>
a<<1; 二进制数左移 1 位,相当于乘 2,前提是被舍弃得高位中不包含 1;右边补 0,左边溢出舍弃
a>>1; 二进制数右移 1 位,相当于除 2;右边溢出舍弃;正数左边补 0,负数左边可能补 0 ("逻辑右移"),也可能补 1("算术右移")
SHL("逻辑左移"):"shift logical left"
SHR("逻辑右移"):"shift logical right"
SAL("算术左移"):"shift arithmetic left"
SAR("算术右移"):"shift arithmetic right"
ROL("循环左移"):"rotate left"
ROR("循环右移"):"rotate right"
RCL("带进位的循环左移"):"rotate left through carry"
RCR("带进位的循环右移"):"rotate right through carry"
循环移位:
a: $$$$$$$$ | $$$%%%%%
c: %%%%%$$$ | $$$$$$$$
b = a << (sizeof(short) * 8 - 5); // rotate left
c = a >> 5; // rotate right
c = c | b;
Bit Field
内存中信息的存储一般以字节为单位;实际应用中,有时存储一个信息不必用一个或多个字节;例如,“真” 或 “假” 用 0 或 1 表示,只需 1 个二进制位即可。
如何向一个字节中的一个或几个二进制位赋值和改变它的值呢? 可以用以下两种方法:
short data = a a b b b b b b | c c c c d d d d
如果想将 c 段的 4 个二进制位的值变为 12(1100), n = 12
15 0 0 0 0 0 0 0 0 | 0 0 0 0 1 1 1 1
15<<4 0 0 0 0 0 0 0 0 | 1 1 1 1 0 0 0 0
~(15<<4) 1 1 1 1 1 1 1 1 | 0 0 0 0 1 1 1 1
data a a b b b b b b | c c c c d d d d
data & ~(15<<4) a a b b b b b b | 0 0 0 0 d d d d
n = 12 0 0 0 0 0 0 0 0 | 0 0 0 0 1 1 0 0
15 0 0 0 0 0 0 0 0 | 0 0 0 0 1 1 1 1
n & 15 0 0 0 0 0 0 0 0 | 0 0 0 0 1 1 0 0
(n & 15)<<4 0 0 0 0 0 0 0 0 | 1 1 0 0 0 0 0 0
data & ~(15<<4) a a b b b b b b | 0 0 0 0 d d d d
(n & 15)<<4 0 0 0 0 0 0 0 0 | 1 1 0 0 0 0 0 0
a a b b b b b b | 1 1 0 0 d d d d = data & ~(15<<4) | (n & 15)<<4 = data
以上方法给一个字节中的某几个二进制位赋值太麻烦了,可以使用位段(位域)结构体的方法:
struct Packed_data {
unsigned a : 2;
int b : 6;
unsigned c : 4;
int d : 4;
short i;
} data; // 共占 4 个字节
a a b b b b b b | c c c c d d d d | i i i i i i i i | i i i i i i i i
也可以使各个位段不恰好占一个字节:
struct Packed_data {
unsigned a : 2;
int b : 3;
unsigned c : 4;
short i;
} data; // 共占 4 个字节, C 后面的 7 个二进制位闲置
a a b b b c c c | c - - - - - - - | i i i i i i i i | i i i i i i i i
可以直接对位段进行操作(需注意每个位段允许的最大范围),系统会自动地转换成整型数:
data.a = 2;
data.b = 7;
data.c = 9
data.a + 5 / data.b
系统至少为位段组分配一个存储单元(即一个机器字,n 个字节,因编译系统而异),可以指定某一段从下一个存储单元开始存放:
unsigned a : 1;
unsigned b : 2;
unsigned 0; // 表示本存储单元不再存放数据
unsigned c : 3;
一个位段必须存储在同一个存储单元中,不能跨两个单元。如果第 1 个单元空间不能容下下一个位段,则该空间不用,从下一个单元开始存放
可以定义无名位段:
unsigned a : 1;
unsigned 2; // 这两个空间不使用
unsigned c : 3;
unsigned d : 4;
位段长度不能大于 `存储单元` 的长度,也不能定义位段数组
位段中的数据可以使用格式化输出: %d %u %o %x 等等
位运算加速技巧
1. 如果乘上一个2的倍数数值,可以改用左移运算(Left Shift) 加速 300%
x = x * 2;
x = x * 64;
//改为:
x = x << 1; // 2 = 21
x = x << 6; // 64 = 26
2. 如果除上一个 2 的倍数数值,可以改用右移运算加速 350%
x = x / 2;
x = x / 64;
//改为:
x = x >> 1;// 2 = 21
x = x >> 6;// 64 = 26
3. 数值转整数加速 10%
x = int(1.232)
//改为:
x = 1.232 >> 0;
4. 交换两个数值(swap),使用 XOR 可以加速20%
t = a;
a = b;
b = t;
//equals:
a = a^b;
b = a^b;
a = a^b;
5. 正负号转换,可以加速 300%
i = -i;
//改为
i = ~i + 1; // NOT 写法
//或
i = (i ^ -1) + 1; // XOR 写法
6. 取余数,如果除数为 2 的倍数,可利用 AND 运算加速 600%
x = 131 % 4;
//equals:
x = 131 & (4 - 1);
7. 利用 AND 运算检查整数是否为 2 的倍数,可以加速 600%
isEven = (i % 2) == 0;
//equals:
isEven = (i & 1) == 0;
8. 加速 Math.abs 600% 的写法1,写法 2 又比写法 1 加速 20%
//写法 1
i = x < 0 ? -x : x;
//写法 2
i = (x ^ (x >> 31)) - (x >> 31);
//写法 3
i = x^(~(x >> 31) + 1) + (x >> 31);
9. 比较两数值相乘之后是否拥有相同的符号,加速 35%
eqSign = a * b > 0;
//equals:
eqSign = a ^ b > 0;
10. RGB 色彩分离
var 24bitColor:uint = 0xff00cc;
var r:uint = 24bitColor >> 16;
var g:uint = 24bitColor >> 8 & 0xFF;
var b:uint = 24bitColor & 0xFF;
11. RGB 色彩合并
var r:uint = 0xff;
var g:uint = 0x00;
var b:uint = 0xcc;
var 24bitColor:uint = r << 16 | g << 8 | b;
预处理指令
宏定义
不带参数的宏定义
#define array_size 1000 // 见名知意,一改全改
int array[array_size];
#define array_size 500
#define G 9.8 // 宏定义有效范围为该指令行起到本源文件结束
int main() {}
#undef G // 提前终止宏定义的作用域
func() {}
#define R 3.0 // 可以层层置换
#define PI 3.1415926
#define L 2 * PI * R
#define S PI * R * R
带参数的宏定义
#define S(a, b) a * b
area = S(3, 2); // 替换后为 area = 3 * 2
#define PI 3.1415926
#define S(r) PI * (r) * (r) // 对于 S(r), S 和 (r) 之间不能有空格
double a = 3.6;
area = S(a);
area = 3.1415926 * (a) * (a); // 替换后
area = S(a + b);
area = 3.1415926 * (a + b) * (a + b); // 替换后;参数加 () 可以避免错误
#define CHAIR "CHINA"
printf("%s", CHAIR); // 替换后为 printf("%s", "CHINA");
// 调用函数只可得到一个返回值,而用宏可以设法得到几个结果
#define PI 3.1415926
#define CIRCLE(R, L, S, V) L = 2 * PI * R; S = PI * R * R; V = 4.0 / 3.0 * PI * R * R * R
double r = 3.0, l, s, v;
CIRCLE(r, l, s, v);
l = 2 * 3.1415926 * r; s = 3.1415926 * r * r; v = 4.0 / 3.0 * 3.1415926 * r * r * r; // 替换后
// 宏替换不占运行时间,只占预处理时间;函数调用占用运行时间(分配单元、保留现场、值传递、返回)
#define MAX(x, y) (x) > (y) ? (x) : (y)
t = MAX(a + b, c + d);
t = (a + b) > (c + d) ? (a + b) : (c + d); // 替换后
int max(int x, int y) { return x > y ? x : y; }
t = max(a + b, c + d);
//
#define PR printf
#define NL "\n"
#define D "%d"
#define D1 D NL
#define D2 D D NL
#define D3 D D D NL
#define D4 D D D D NL
#define S "%s"
int a = 1, b = 2, c = 3, d = 4;
char string[] = "CHINA";
PR(D1, a); // 1
PR(D2, a, b); // 1 2
PR(D3, a, b, c); // 1 2 3
PR(D4, a, b, c, d); // 1 2 3 4
PR(S, string); // CHINA
#include
// format.h
#define PR printf // 用宏定义简化程序,将格式输出语句事先用宏定义好
#define NL "\n"
#define D "%d"
#define D1 D NL
#define D2 D D NL
#define D3 D D D NL
#define D4 D D D D NL
#define S "%s"
// file1.c
#include <stdio.h> // 到存放 C 库函数头文件的目录查找
#include "format.h" // 先在当前目录查找,找不到再去 C 库函数头文件目录查找
#include "C:\file2.h" // 也可以添加目录
int main() {
int a = 1, b = 2, c = 3, d = 4;
char string[] = "CHINA";
PR(D1, a); // 1
PR(D2, a, b); // 1 2
PR(D3, a, b, c); // 1 2 3
PR(D4, a, b, c, d); // 1 2 3 4
PR(S, string); // CHINA
return 0;
}
Condition Compiling
# if 表达式
# ifdef 宏名
# ifndef 宏名
...
# else
...
# endif
Input & Output of Files
为了简化用户对输入输出设备的操作,使用户不必区区分各种输入输出设备之间的区别,操作系统把各种设备都统一作为文件来处理。例如:终端键盘是输入文件,显示屏和打印机是输出文件。
Text files & Binary files
数据在内存是以二进制形式存储的,如果不加转换地输出到外存,就是 二进制文件
。
如果需要在外存上以 ASCII 代码形式存储(文本文件
),则需要在存储前进行转换;每一个字节放一个字符的 ASCII 代码。
字符一律以 ASCII 形式存储;数值型既可以用 ASCII 形式存储,也可以用二进制形式存储。
假设有整数 10000,用 ASCII 码形式存储需要 5 个字节(每个字符占一个字节,且需花费时间转换);而用二进制形式存储,只占用 4 个字节。
File Buffer & File Pointer
系统会自动地在内存区为程序中的每一个正在使用的文件开辟一个 文件缓冲区
。
从内存向磁盘输出数据,必须先送到缓冲区,装满缓冲区后才一起发送到磁盘。
从磁盘向内存输入数据,则一次从磁盘文件输入一批数据到缓冲区,并将其充满。
每个正在使用的文件在内存中都有一个相应的 文件信息区
,是一个结构体(由系统声明,取名 FILE,在 stdio.h 中),用来保存文件的有关信息:
typedef struct {
short level; // 缓冲区 “满” 或 “空” 的程度
unsigned flags; // 文件状体标志
char fd; // 文件描述符
unsigned char hold; // 如缓冲区无内容不读取字符
short bsize; // 缓冲区的大小
unsigned char * buffer; // 数据缓冲区的位置
unsigned char * curp; // 指针当前的指向
unsigned istemp; // 临时文件指示器
short token; // 用于有效性检查
} FILE;
FILE * fp; // 文件指针,指向文件的信息区
// 打开文件 - 建立相应的文件信息区和文件缓冲区; #include <stdlib.h> to use exit(0)
if((fp = fopen("file1", "r")) == NULL) {
printf("cannot open this file\n");
exit(0);
}
// 关闭文件 - 释放相应的文件信息区和文件缓冲区
fclose(fp); // 成功返回 0;失败返回 EOF(-1)
文件使用方式 | 含义 | 动作 | 文件类型 | 游标位置 | 文件存在情况 |
---|---|---|---|---|---|
"r" | 只读 | 打开 | 文本 | 文件首 | 不存在则报错 |
"w" | 只写 | 创建 | 文本 | 文件首 | [先删除] 建立新文件 |
"a" | 追加 | 打开 | 文本 | 文件尾 | 不存在则报错 |
"rb" | 只读 | 打开 | 二进制 | 文件首 | 不存在则报错 |
"wb" | 只写 | 创建 | 二进制 | 文件首 | [先删除] 建立新文件 |
"ab" | 追加 | 打开 | 二进制 | 文件尾 | 不存在则报错 |
"r+" | 读写 | 打开 | 文本 | 文件首 | 不存在则报错 |
"w+" | 读写 | 创建 | 文本 | 文件首 | [先删除] 建立新文件 |
"a+" | 读写 | 打开 | 文本 | 文件尾 | 不存在则报错 |
"rb+" | 读写 | 打开 | 二进制 | 文件首 | 不存在则报错 |
"wb+" | 读写 | 创建 | 二进制 | 文件首 | [先删除] 建立新文件 |
"ab+" | 读写 | 打开 | 二进制 | 文件尾 | 不存在则报错 |
程序开始运行时,系统自动打开 3 个标准流文件 —— stdin
, stdout
, stderr
:
char arr[50];
fgets(arr, 50, stdin); // 键盘输入 "Hello World!"
fputs(arr, stdout); // 屏幕输出 "Hello World!"
fputs(arr, stderr); // 屏幕输出 "Hello World!"
fprintf(stderr, "%s\n", "Invalid input");
File Reading & File Writing
// 读写字符
fgetc(fp); // 读一个字符,成功,返回所读字符;失败,返回文件结束标志 EOF(-1)
while(!feof(fp)) {} // 如果未遇到输入文件的结束标志 ...
fputc(ch, fp); // 写一个字符,成功,返回所写字符;失败,返回 EOF(-1)
// 读写字符串
fgets(str, n, fp) // 读取 n - 1 长度的字符串, 包括 '\n',后面添加 '\0'; 成功,返回 str 地址;失败,返回 NULL; 遇 '\n' 则停读
fputs(str, fp) // 写 str -> fp;成功,返回 0;失败,返回 EOF(-1);str 可以是:"xxx"、str[]或字符型指针;'\0' 不输出
// ASCII 码方式操作数据,格式化读写;内存与磁盘频繁交换数据时,最好不用
int i = 3; float f = 4.5f;
fprintf(fp, "%d, %6.2f", i, f);
fscanf(fp, "%d, %f", &i, &f);
// 二进制方式操作数据
fread(buffer, size, count, fp); // size 字节数;count 要读写多少个数据项;
fwrite(buffer, size, count, fp); // 两个函数成功,则返回 count 的值
struct Student_type {
char name[10];
int num;
int age;
char addr[30];
} stud[40];
for(i = 0; i < 40; i++) { fread (&stud[i], sizeof(struct Student_type), 1, fp); } // 读
for(i = 0; i < 40; i++) { fwrite(&stud[i], sizeof(struct Student_type), 1, fp); } // 写
// 用 rewind 函数使游标返回到文件头
while(!feof(fp)) {...};
rewind(fp);
while(!feof(fp)) {...};
fclose(fp);
// 用 fseek 函数改变游标位置,一般用于二进制文件,offset 位移量为 long 型数据
fseek(fp, offset, position);
fseek(fp, 100L, 0);
fseek(fp, 100L, SEEK_SET); // 文件头部开始右移 100 个字节
fseek(fp, 50L, 1);
fseek(fp, 50L, SEEK_CUR); // 文件当前游标位置开始右移 50 个字节
fseek(fp, -10L, 2);
fseek(fp, -10L, SEEK_END); // 文件尾部开始左移 10 个字节
// 用 ftell 函数获取当前游标的位移量(相对于文件开头的位移量)
long i = ftell(fp);
if(i == -1L) printf("error\n");
Coding
Integer To Binary
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int integer_to_binary(char input[], int byte_limits) {
_Bool p_n_flag = (input[0] == 45);
unsigned long long number = 0;
// string to number
for(int i = strlen(input) - 1, j = 0; i >= 0; i--, j++) {
//printf("%c - %d\n", input[i], input[i]);
if((input[i] > 57 || (input[i] < 48 && input[i] != 45)) || (input[i] == 45 && i != 0)) {
//printf("%s\n", "Invalid input");
fprintf(stderr, "%s\n", "Invalid input");
return -1;
}
if(i == 0 && input[i] == 45) { break; } // '-'
if(input[i] == 48) { continue; } // '0'
if(j == 0) { number += (input[i] - 48); continue; }
unsigned long long tmp = input[i] - 48;
for(int k = 0; k < j; k++) {
tmp *= 10;
}
number += tmp;
}
// check byte limits
if(byte_limits < 0 || byte_limits > sizeof(long long)) {
fprintf(stderr, "%s %ld\n", "The value of byte_limits is limited to 0 ~", sizeof(long long));
return -1;
}
// get byte
int byte = byte_limits != 0 ? byte_limits : sizeof(long long);
// get & check number range
long long x = 2;
for(int j = 1; j < byte * 8 - 1; j++) { x *= 2; }
long long min = -x;
long long max = x - 1;
unsigned long long mmax = 2 * x - 1;
if((p_n_flag && number > llabs(min)) || (!p_n_flag && number > mmax)) {
fprintf(stderr, "Please enter a number in the range of %Ld ~ %Ld ~ %Lu\n", min, max, mmax);
return -1;
}
// Add minus sign
number = p_n_flag ? -number : number;
// Define variables
int len = byte * 8, i = 0;
int * const binary = (int*) malloc(byte<<6);
// Use a positive number as a reference to convert binary numbers
unsigned long long opt_num = p_n_flag ? max + number + 1 : number;
//printf("%Lu\n", opt_num);
// start to convert binary numbers
while(opt_num) {
if(opt_num % 2 == 1) {
binary[i++] = 1;
} else {
binary[i++] = 0;
}
opt_num /= 2;
}
// if it is a negative number, add 1 to the highest binary bit
if(p_n_flag) { binary[len - 1] = 1; }
// output the result
printf("%c%Ld : | ", p_n_flag && number == 0 ? '-' : '\0', number);
for(int k = len - 1; k >= 0; k--){
printf("%d",binary[k]);
if(k % 8 == 0) {
printf(" | ");
}
}
return 0;
}
int main(){
return integer_to_binary("-32768", 2); // 以字符串的格式输入一个整形类型的数,第二个参数为输出的字节数
}
c hello world
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdbool.h>
#include <stdlib.h>
#define N 2
int main () {
printf("Hello world!\n");
return 0;
}
c++ hello world
#include <iostream>
std::cout << "Hello world!" << std::endl;