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

eE 表示 以 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 标准并未规定作何处理,由编译系统自己决定。与其他整形变量的处理方式不同。

可以用以下方法测定(说明 gccsigned 处理的):

#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]

二维数组形式上是矩阵形式,但在内存中线性存储的:

内存地址第二维度第一维度
2000a[0][0]第 0 行元素
2004a[0][1]
2008a[0][2]
2012a[0][3]
2016a[1][0]第 1 行元素
2020a[1][1]
2024a[1][2]
2028a[1][3]
2032a[2][0]第 2 行元素
2036a[2][1]
2040a[2][2]
2044a[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;
posted @ 2020-09-06 21:33  KerShaw  阅读(952)  评论(0编辑  收藏  举报