五、C 语言基础
1. C 语言 USB - CDC 串口之 printf 函数的实现
1.1 打开 USB 库中的 PRINTF.HID 宏定义 (去掉 //
)
#define PRINTF_HID //printf输出直接重定向到USB口(早期命名方式)
1.2 理解 PRINTF 的函数原型的定义
#define printf printf_hid
int printf_hid (const char *fmt, ...);
1.3 参数 fmt
说明
参数 fmt
是格式控制字符串,包含了两种类型的对象:普通字符和转换说明。
- 普通字符:在输出时,普通字符将原样不动地复制到标准输出。
示例:
printf("8051U深度入门到32位51大型实战视频\r\n");
- 转换说明:不直接输出,用于控制
printf
中参数的转换和打印。每个转换说明都由一个百分号字符(%
)开始,以转换说明符结束,从而说明输出数据的类型、宽度、精度等。
示例:
printf("8051U深度入门到32位51大型实战视频,%s\r\n", "加油");
1.4 转换说明简介
- 类型:根据不同的
fmt
字符串,函数可能需要一系列的附加参数,每个参数包含了一个要被插入的值,替换了 fmt
参数中指定的每个 %
标签。关于附加参数,既可以是变量,也可以是常量。
- 位置:
printf()
函数的普通字符和转换说明放在 ""
双引号内,附加参数放在双引号外,每个附加参数之间用逗号隔开。
- 数量:
printf()
的附加参数与转换说明符是一一对应关系,如果有 n
个转换说明符,printf()
的参数就应该有 n + 1
个。如果参数个数少于对应的转换说明符,printf()
可能会输出内存中的任意值。
1.5 标志
标志 |
含义 |
实例 |
n,m |
n 表示整数占几行,m 表示小数占几行 |
%2.3f |
- |
输出的结果左对齐 |
%-d |
空格 |
输出值为正时冠以空格,为负时冠以负号 |
|
# |
输出带有前导的数据(八进制为 0---,十六进制为 X---,- 代表数字) |
%#d |
1.6 格式字符
格式字符 |
含义 |
%d |
以十进制整数形式输出 |
%ld |
以十进制长整形输出 |
%f |
以单精度浮点型输出 |
%lf |
以双精度浮点型输出 |
%o |
以八进制整型输出整数 |
% x 或 % X |
以十六进制形式输出整数 |
%u |
以十进制无符号整形输出 |
%i |
以十进制整形输出(与 % d 无异) |
%c |
输出单个字符 |
%s |
输出字符串 |
% e 或 % E |
以指数形式输出 |
% g 或 % G |
自适应数据输出(数据够大或够小则以指数形式输出,否则以小数形式输出) |
%p |
输出地址 |
1.7 转义字符
转义字符 |
释义 |
? |
在书写连续多个问号时使用,防止它们被解析成三字字词 |
' |
用于表示字符常量 |
" |
用于表示一个字符串内部的双引号 |
\ |
用于表示一个反斜杠,防止它被解释为一个转义序列符 |
\a |
警告字符,蜂鸣 |
\b |
退格符 |
\f |
换页符 |
\n |
换行符 |
\r |
回车 |
\t |
水平制表符 (8 个空格) |
\v |
垂直制表符 |
\ddd |
ddd 表示 1 - 3 个八进制的数字。如:\120 |
\xdd |
dd 表示 2 个十六进制数字,如:\x30 |
1.8. ASCII 可显示字符(共 95 个)
二进制 |
十进制 |
十六进制 |
图形 |
0010 0000 |
32 |
20 |
(space) |
0010 0001 |
33 |
21 |
! |
0010 0010 |
34 |
22 |
" |
0010 0011 |
35 |
23 |
# |
0010 0100 |
36 |
24 |
$ |
0010 0101 |
37 |
25 |
% |
0010 0110 |
38 |
26 |
& |
0010 0111 |
39 |
27 |
' |
0010 1000 |
40 |
28 |
( |
0010 1001 |
41 |
29 |
) |
0010 1010 |
42 |
2A |
* |
0010 1011 |
43 |
2B |
+ |
0010 1100 |
44 |
2C |
, |
0010 1101 |
45 |
2D |
- |
0010 1110 |
46 |
2E |
. |
0010 1111 |
47 |
2F |
/ |
0011 0000 |
48 |
30 |
0 |
0011 0001 |
49 |
31 |
1 |
0011 0010 |
50 |
32 |
2 |
0011 0011 |
51 |
33 |
3 |
0011 0100 |
52 |
34 |
4 |
0011 0101 |
53 |
35 |
5 |
0011 0110 |
54 |
36 |
6 |
0011 0111 |
55 |
37 |
7 |
0011 1000 |
56 |
38 |
8 |
0011 1001 |
57 |
39 |
9 |
0011 1010 |
58 |
3A |
: |
0011 1011 |
59 |
3B |
; |
0011 1100 |
60 |
3C |
< |
0011 1101 |
61 |
3D |
= |
0011 1110 |
62 |
3E |
> |
0011 1111 |
63 |
3F |
? |
0100 0000 |
64 |
40 |
@ |
0100 0001 |
65 |
41 |
A |
0100 0010 |
66 |
42 |
B |
0100 0011 |
67 |
43 |
C |
0100 0100 |
68 |
44 |
D |
0100 0101 |
69 |
45 |
E |
0100 0110 |
70 |
46 |
F |
0100 0111 |
71 |
47 |
G |
0100 1000 |
72 |
48 |
H |
0100 1001 |
73 |
49 |
I |
0100 1010 |
74 |
4A |
J |
0100 1011 |
75 |
4B |
K |
0100 1100 |
76 |
4C |
L |
0100 1101 |
77 |
4D |
M |
0100 1110 |
78 |
4E |
N |
0100 1111 |
79 |
4F |
O |
0101 0000 |
80 |
50 |
P |
0101 0001 |
81 |
51 |
Q |
0101 0010 |
82 |
52 |
R |
0101 0011 |
83 |
53 |
S |
0101 0100 |
84 |
54 |
T |
0101 0101 |
85 |
55 |
U |
0101 0110 |
86 |
56 |
V |
0101 0111 |
87 |
57 |
W |
0101 1000 |
88 |
58 |
X |
0101 1001 |
89 |
59 |
Y |
0101 1010 |
90 |
5A |
Z |
0101 1011 |
91 |
5B |
[ |
0101 1100 |
92 |
5C |
\ |
0101 1101 |
93 |
5D |
] |
0101 1110 |
94 |
5E |
^ |
0101 1111 |
95 |
5F |
_ |
0110 0000 |
96 |
60 |
` |
0110 0001 |
97 |
61 |
a |
0110 0010 |
98 |
62 |
b |
0110 0011 |
99 |
63 |
c |
0110 0100 |
100 |
64 |
d |
0110 0101 |
101 |
65 |
e |
0110 0110 |
102 |
66 |
f |
0110 0111 |
103 |
67 |
g |
0110 1000 |
104 |
68 |
h |
0110 1001 |
105 |
69 |
i |
0110 1010 |
106 |
6A |
j |
2. 数的进制:2进制、10进制、16进制
十进制 |
二进制 |
八进制 |
十六进制 |
1 |
1 |
1 |
0x1 |
2 |
10 |
2 |
0x2 |
3 |
11 |
3 |
0x3 |
4 |
100 |
4 |
0x4 |
5 |
101 |
5 |
0x5 |
6 |
110 |
6 |
0x6 |
7 |
111 |
7 |
0x7 |
8 |
1000 |
10 |
0x8 |
9 |
1001 |
11 |
0x9 |
10 |
1010 |
12 |
0xA |
11 |
1011 |
13 |
0xB |
12 |
1100 |
14 |
0xC |
13 |
1101 |
15 |
0xD |
14 |
1110 |
16 |
0xE |
15 |
1111 |
17 |
0xF |
16 |
10000 |
20 |
0x10 |
17 |
10001 |
21 |
0x11 |
18 |
10010 |
22 |
0x12 |
各进制之间互相转换的方法:
(1) 十进制转二进制
采用 “除 2 取余,逆序排列” 法。将十进制数除以 2,得到商和余数,再将商继续除以 2,直到商为 0。将每次得到的余数从右到左排列,即为对应的二进制数。例如,将十进制数 10 转换为二进制: 10 ÷ 2 = 5 余 0 5 ÷ 2 = 2 余 1 2 ÷ 2 = 1 余 0 1 ÷ 2 = 0 余 1 从下往上取余数得到 1010,即十进制 10 对应的二进制是 1010。
(2) 十进制转八进制
“除 8 取余,逆序排列” 法。将十进制数除以 8,得到商和余数,再将商继续除以 8,直到商为 0。将每次得到的余数从右到左排列,就是对应的八进制数。比如,把十进制数 20 转换为八进制: 20 ÷ 8 = 2 余 4 2 ÷ 8 = 0 余 2 从下往上取余数得到 24,即十进制 20 对应的八进制是 24。
(3) 十进制转十六进制
“除 16 取余,逆序排列” 法。十进制数除以 16,得到商和余数,商再除以 16,直至商为 0。将余数从右到左排列得到十六进制数。十六进制中 10 - 15 分别用 A - F 表示。例如,十进制数 26 转换为十六进制: 26 ÷ 16 = 1 余 10(用 A 表示) 1 ÷ 16 = 0 余 1 从下往上取余数得到 1A,即十进制 26 对应的十六进制是 1A。
(4) 二进制转十进制
按位加权求和法。从右至左,将二进制数的每一位乘以 2 的相应位数次幂(幂次从 0 开始),然后将所有结果相加。例如,二进制数 1011 转换为十进制:
(5) 八进制转十进制
同样是按位加权求和法。从右至左,八进制数的每一位乘以 8 的相应位数次幂(幂次从 0 开始),再把结果相加。比如,八进制数 35 转换为十进制:
(6) 十六进制转十进制
按位加权求和。从右至左,十六进制数的每一位乘以 16 的相应位数次幂(幂次从 0 开始),相加得到十进制数。十六进制中 A - F 分别对应 10 - 15 。例如,十六进制数 2A 转换为十进制:
(7) 二进制转八进制
从右向左,每 3 位一组,不足 3 位的在左边补 0,然后将每组二进制数转换为对应的八进制数。例如,二进制数 11010 转换为八进制: 先分组为 011 010,011 对应八进制 3,010 对应八进制 2,所以结果是 32。
(8) 二进制转十六进制
从右向左,每 4 位一组,不足 4 位的在左边补 0,将每组二进制数转换为对应的十六进制数。比如,二进制数 111011 转换为十六进制: 分组为 0011 1011,0011 对应十六进制 3,1011 对应十六进制 B,结果就是 3B。
(9) 八进制转二进制
将八进制数的每一位转换为对应的 3 位二进制数。例如,八进制数 57 转换为二进制: 5 对应二进制 101,7 对应二进制 111,结果是 101111。
(10) 十六进制转二进制
把十六进制数的每一位转换为对应的 4 位二进制数。例如,十六进制数 A3 转换为二进制: A(10)对应二进制 1010,3 对应二进制 0011,结果是 10100011。
(11) 八进制转十六进制
可以先将八进制转换为二进制,再将二进制转换为十六进制。
(12) 十六进制转八进制
先把十六进制转换为二进制,再将二进制转换为八进制 。
3.数据的基本类型
想要使用64位变量,需要在程序文件里面添加申明:#pragma float64

4.C语言常用运算符
算术运算符
下表显示了 C 语言支持的所有算术运算符。假设变量 A 的值为 18,变量 B 的值为 5,则:
运算符 |
描述 |
实例 |
+ |
两个数相加 |
A+B 将得到 23 |
- |
一个数减另一个数 |
A-B 将得到 13 |
* |
两个数相乘 |
A*B 将得到 90 |
/ |
分子除以分母 |
A/B 将得到 3.6 |
% |
余数运算符,整除后的余数 |
B%A 将得到 3 |
++ |
自增运算符,整数值增加 1 |
A++ 将得到 19 |
-- |
自减运算符,整数值减少 1 |
A-- 将得到 17 |
关系运算符
关系运算(Relational Operators),用于判断条件,决定程序的流程。
下表列出了 C 语言支持的关系运算符。假设两个整型数为 operand1 = 11, operand2 = 2,则
关系运算符 |
说明 |
示例 |
== |
判断两个操作数是否相等,若相等则值为1,反之值为0 |
operand1 == operand2 的值为 0 |
!= |
判断两个操作数是否不相等,若不相等则值为1,反之值为0 |
operand1 != operand2 的值为 1 |
> |
判断第1个操作数是否大于第2个操作数,若大于则值为1,反之值为0 |
operand1 > operand2 的值为 1 |
< |
判断第1个操作数是否小于第2个操作数,若小于则值为1,反之值为0 |
operand1 < operand2 的值为 0 |
>= |
判断第1个操作数是否大于或等于第2个操作数,若大于或等于则值为1,反之值为0 |
operand1 >= operand2 的值为 1 |
<= |
判断第1个操作数是否小于或等于第2个操作数,若小于或等于则值为1,反之值为0 |
operand1 <= operand2 的值为 0 |
逻辑运算符
下表列出了 C 语言支持的逻辑运算符。假设两个整型数为 operand1 = 1, operand2 = 0,则:
逻辑运算符 |
说明 |
示例 |
&& |
如果两个操作数均为非0,则表达式的值为1,反之为0 |
operand1 && operand2 的值为 0 |
丨丨 |
如果两个操作数至少有一个为非0,则表达式的值为1,反之为0 |
operand1丨丨operand2 的值为 1 |
! |
如果操作数的值为非0,则表达式的值为0,反之亦反 |
!operand1 的值为 0 |
赋值运算符
下表列出了 C 语言支持的赋值运算符:
赋值运算符 |
说明 |
示例 |
= |
普通赋值运算符 |
val = 2 |
+= |
加并赋值操作 |
val += 2 等价于 val = val + 2 |
-= |
减并赋值操作 |
val -= 2 等价于 val = val - 2 |
*= |
乘并赋值操作 |
val *= 2 等价于 val = val * 2 |
/= |
除并赋值操作 |
val /= 2 等价于 val = val / 2 |
%= |
取余并赋值操作 |
val %= 2 等价于 val = val % 2 |
<<= |
左移并赋值操作 |
val <<= 1 等价于 val = val << 1 |
>>= |
右移并赋值操作 |
val >>= 1 等价于 val = val >> 1 |
&= |
按位与并赋值操作 |
val &= 1 等价于 val = val & 1 |
^= |
按位异或并赋值操作 |
val ^= 1 等价于 val = val ^ 1 |
|= |
按位或并赋值操作 |
val |= 1等价于 val = val | 1 |
赋值运算符支持的是C语言的基本数据类型,包括char、int和double,字符串(字符数组)不能使用赋值运算符。
位运算符
假设A=5(0000 0101),B=11(0000 1011)
运算符 |
描述 |
运算法则 |
实例 |
& |
如果同时存在于两个操作数中,二进制 AND 运算符复制一位到结果中 |
同一为一,其它为零 |
(A&B) 得到 00000001 |
| |
如果存在任一操作数中,二进制 OR 运算符复制一位到结果中 |
有一为一,皆零为零 |
(A|B) 得到 00001111 |
^ |
如果只存在于一个操作数中,二进制异或运算符复制一位到结果中 |
相同为零,不同为一 |
(A^B) 得到 00001110 |
~ |
取反,二进制将每一位 0 变为 1,1 变为 0 |
零变一,一变零 |
(~A) 得到 11111101 |
<< |
二进制左移运算符,左操作数的值向左移动右操作数指定位数 |
向高位移动两位,低位补零 |
(A<<2) 得到 00010100 |
>> |
二进制右移运算符,左操作数的值向右移动右操作数指定位数 |
向低位移动两位,高位补零 |
(A>>2) 得到 00000001 |
其他运算符
运算符 |
描述 |
Condition? X : Y |
条件运算符,如果 Condition 为真,则值为 X ,否则为 Y |
. (点) 和 -> (箭头) |
成员运算符,用于引用类、结构体和共用体成员 |
& |
取地址运算符,返回变量的存储地址 |
* |
指针运算符,* 指向一个变量,如 *a 将指向变量 a |
, |
逗号运算符会顺序执行一系列运算。整个逗号表达式的值是以逗号分隔的列表中的最后一个表达式的值 |