编译后运行
gcc -std=c11 -Wall -Wextra values.c -o values.exe
./values.exe请在第一课使用的 UCRT64 终端、values.c 所在目录执行。
C foundations / Lesson 02
上一课的程序每次都打印固定的文字。这一课开始使用变量:给数据起名字、说明它属于什么类型,再用正确的格式输出。学完后,你就能为“采样次数”“电压值”和“状态字符”写出清晰的 C 代码。
学习类型时,最有用的检验方式是让程序输出真实结果,而不是背一张字节数表。完成本课后,你应当能独立做到:
int、double、char 变量,用有含义的名字描述它们。= 在赋值语句里的作用,预测变量重新赋值后的输出。printf 的格式占位符与实参类型匹配,并看懂一个格式不匹配警告。sizeof 查看当前平台的类型大小,知道为什么不能把本机数字当成所有 STM32 的规则。一个能打印采样次数、电压和状态的 values.c;一份记录自己电脑上 sizeof(int) 的结果,以及一次“改值 → 重新编译 → 对照输出”的实验记录。
上一课的 printf("Hello, world!\n"); 无论运行多少次,都会输出同一行文字。嵌入式程序却要面对会变化的数据:今天测到多少伏、已经采样几次、当前是什么状态。我们需要在程序中给这些数据一个名字。
int sample_count = 3;
可以先把它读作:“创建一个名叫 sample_count 的整数变量,一开始放入数值 3。”这只是理解用的模型;编译器可能把值放在寄存器或优化掉某些存储,不能凭这一行推断固定的 RAM 地址。
3 改为 5,重新编译并运行后,屏幕上哪部分会变化?带着预测继续往下读。在 C 中,变量使用前要先声明。初学时尽量在声明的同时给出初值,这叫初始化。下面一行可以拆成四部分:
int sample_count = 3;
intsample_countsample_count 与 Sample_Count 不是同一个名字。= 33 放进刚声明的变量。这里的 = 表示初始化时指定值,不是数学上“左右两边永远相等”。;printf 一样,漏写分号会得到编译诊断。名字可以包含字母、数字和下划线,但不能以数字开头,也不能用 int、return 这样的 C 关键字。用 voltage 比用 x 更能表达用途;本课统一采用小写单词加下划线。
int sample_count;,紧接着就输出它,并不能指望自动得到 0。先给值,再使用;编译器没有发出警告也不等于这种写法安全。values.c在第一课的文件夹中新建 values.c,输入下面的完整程序。先预测三行输出,再编译运行。第一课的编译环境可以直接复用,不需要重新安装。
#include <stdio.h>
int main(void)
{
int sample_count = 3;
double voltage = 3.3;
char grade = 'A';
sample_count = sample_count + 1;
printf("samples=%d\n", sample_count);
printf("voltage=%.1f V\n", voltage);
printf("grade=%c\n", grade);
return 0;
}
gcc -std=c11 -Wall -Wextra values.c -o values.exe
./values.exe请在第一课使用的 UCRT64 终端、values.c 所在目录执行。
gcc -std=c11 -Wall -Wextra values.c -o values
./values如果提示找不到源文件,先用 pwd 和 ls 检查当前目录。
samples=4
voltage=3.3 V
grade=A
第一行不是 3,因为输出前执行了 sample_count = sample_count + 1;。第二行的 %.1f 把数值显示为一位小数;它没有把变量改成字符串。第三行用 %c 显示字符。显示成 3.3 不意味着二进制浮点数能精确表示所有十进制小数。
类型决定了数据能表达什么,以及哪些操作和输出格式适合它。本课先用足够支撑练习的四种类型;不要把电脑上的字节数或最大值硬背成 C 语言在所有板卡上的规定。
| 类型 | 本课用法 | 示例 | 输出格式 |
|---|---|---|---|
int | 可正可负的整数,例如计数差值。 | int count = 3; | %d |
unsigned int | 从 0 开始的无符号整数;减法边界要特别留意。 | unsigned int ticks = 3U; | %u |
double | 带小数的近似数值,例如电压。 | double voltage = 3.3; | %f |
char | 一个字符值,例如状态代号。 | char grade = 'A'; | %c |
'A' 使用单引号,表示字符常量;"A" 使用双引号,表示字符串,两者不能随意互换。C 里还有 float,它与 double 都用于浮点值;两者的范围与精度通常有差异,具体以当前实现为准。这一课先用 double,下一步再按目标平台认识取舍。
int 多宽、char 是否按有符号值解释,不能只凭电脑上的结果推断。在后面的硬件接口与通信课程里,我们会用 <stdint.h> 中的定宽整数类型来表达明确的数据宽度。printf 必须知道你给了什么类型格式字符串里的占位符像一份“取值说明书”。例如 printf("samples=%d\n", sample_count); 会用后面的整数替换 %d。占位符与实参的数量、类型都要对上。
| 占位符 | 本课对应数据 | 读作 |
|---|---|---|
%d | int | 按有符号十进制整数输出 |
%u | unsigned int | 按无符号十进制整数输出 |
%f / %.1f | double | 输出浮点值;后者只显示一位小数 |
%c | char 值调用时经过整数提升(常见平台上为 int) | 按字符输出 |
%zu | sizeof 返回的 size_t | 按无符号十进制输出大小 |
如果把 double voltage 交给 %d,不是“只是显示得难看”:可变参数函数无法按正确类型取值,这种格式不匹配会导致未定义行为。把编译器的格式警告当作需要修复的问题,而不是让错误程序继续运行。
%.1f 只控制打印时保留一位小数。double voltage 仍按该平台的浮点格式保存;许多十进制小数无法用有限的二进制位精确表示。更深入的浮点误差留到运算课中实验。
声明时给初值叫初始化;声明之后再写 sample_count = ...; 是赋值。C 的 = 会先计算右侧表达式,再把结果赋给左侧变量,所以这段代码是成立的:
int sample_count = 3;
sample_count = sample_count + 1;
printf("%d\n", sample_count);
第二行读取旧值 3,加 1 后把 4 写回,因此输出是 4。数学等号不能这样理解;判断两个值是否相等要用另一个运算符,放到后面的条件课学习。
本课把变量写在 main 的花括号内,所以这些名字在这个函数体内可用,离开它就不能直接使用。先掌握“声明之后才能用、使用之前先有值”这两条规则,不必立刻研究全局变量。
输出 5。先读取旧值 3,再加 2,最后把结果写回 sample_count。
sizeof 测量,而不是猜类型大小sizeof(类型) 给出该类型在当前 C 实现中占多少字节。结果的类型是 size_t,打印时使用 %zu。把下面三行加入 values.c 的 return 0; 前,重新编译并运行:
printf("int: %zu byte(s)\n", sizeof(int));
printf("double: %zu byte(s)\n", sizeof(double));
printf("char: %zu byte(s)\n", sizeof(char));
你会看见三个数字;本课不预填 int 和 double 的数字,因为它们依平台而定。sizeof(char) 按 C 的定义是 1 个字节,但“一个字节有几位”也由实现定义,不应把所有处理器都想成同一份内存布局。
int 恰好占几个字节”不能替代协议或芯片手册。| 现象 | 原因线索 | 处理方式 |
|---|---|---|
出现 format '%d' expects ... 一类警告 | printf 格式与后面的变量类型不匹配。 | 逐一核对 %d、%f、%c 与实参;修好后重新编译。 |
| 提示变量未声明 | 名字拼错、大小写不同,或在声明之前使用。 | 对照声明处的拼写和所在花括号。 |
| 提示重定义 | 在同一作用域内又写了一次同名类型声明。 | 如果只想改值,写 sample_count = 4;,不要重复 int。 |
| 输出还是旧数值 | 只保存了源文件,没重新编译;或运行了另一个文件夹中的程序。 | 按“保存 → 编译 → 运行”的顺序重做,并核对当前目录。 |
sizeof(int) 与别人的结果不同 | 目标平台、编译器或 ABI 可能不同。 | 记录本机结果,继续用程序与目标板资料核对;不要改代码去凑教程数字。 |
编译器警告很有用,但并不能证明程序所有路径都正确。特别是局部变量,先初始化再读取;格式占位符要与值匹配。预测和运行结果也要互相核对。
每一题先写下预测,然后只改一处、保存、重新编译并运行。答案用于核对思路;更重要的是你能解释程序为什么这样输出。
把主例程中的 sample_count = sample_count + 1; 改成 sample_count = sample_count + 2;。在运行前写下第一行输出,并解释右侧先用的是哪个值。
第一行变为 samples=5。右侧先用原来的 3,算出 5,再赋给左侧变量。若仍看到 4,检查是否重新编译。
不改变 double voltage = 3.3;,只改输出格式,让第二行显示 voltage=3.30 V。指出你改动的是变量的值,还是打印规则。
printf("voltage=%.2f V\n", voltage);改动的是格式字符串:%.2f 显示两位小数。voltage 的类型和值都没因这条语句而改变。
运行本课的 sizeof 三行代码,记录本机 int、double、char 的结果。随后解释为什么不能把 int 的本机结果直接写成 STM32 通信帧的字段长度。
你应当写出自己机器上的实际数字,而不是照抄一个固定答案。sizeof(char) 应为 1 字节;其他类型大小由当前实现决定。通信帧的字段宽度来自协议设计或硬件资料,代码需据此选择明确的类型。
int 变量,说明变量名、类型和初值各是什么。%d、%.1f、%c 和 %zu 分别对应什么值。sizeof 取得本机结果,并说出为什么目标板的数据宽度要另行核对。变量已经能保存数据。接下来的表达式与运算课会让它们参与计算,并检查整数除法、运算顺序和类型变化。现在先把本课三个练习做完,带着自己的输出记录再前进。
sizeof 与 size_t类型的实际大小、浮点实现和工具链警告随平台变化。操作具体板卡前,以所用编译器、芯片手册及通信协议为准。