3、C语言基础回顾(上):数据类型、运算符与表达式、流程控制语句(if、switch、for、while)、数组与字符串

好,咱们开始第三讲。说实话,C语言是嵌入式的根。你想想看,不管是51、STM32还是ARM Linux,底层跑的都是C。我见过不少新手,一上来就追新语言,结果连个指针都搞不明白,写出来的代码跑起来各种玄学问题。嗯,这一章咱们把基础夯实,后面写驱动、调协议栈才不慌。

3.1 数据类型——选对类型,省一半内存

C语言的数据类型,说白了就是告诉编译器:我要用多大的一块地方,以及怎么解释这块地方里的二进制数。

我个人习惯,在嵌入式开发里,能用int就别用long,能用uint8_t就别用int。为什么?因为MCU的RAM和Flash都很金贵。我曾经在一个项目里,为了省几个字节,把一个全局变量从int改成uint8_t,结果整个系统多跑了两个功能模块——这就是抠细节的价值。

类型 大小(常见32位机) 取值范围 典型用途
char 1字节 -128 ~ 127 或 0~255 字符、小范围状态
int 4字节 -2^31 ~ 2^31-1 常规整数、循环计数
float 4字节 ±3.4e-38 ~ ±3.4e38 传感器数据、小数运算
double 8字节 ±1.7e-308 ~ ±1.7e308 高精度计算(MCU慎用)
注意: 在8位单片机(如51)上,int只有2字节!跨平台开发时,我建议直接用 stdint.h 里的 uint8_tuint16_tuint32_t,这样代码移植起来不头疼。

3.2 运算符与表达式——别让优先级坑了你

运算符这块,我踩过的坑比走过的路还多。尤其是优先级问题,你想想看:

int a = 5;
int b = a++ * 2 + 1;
// 结果是多少?

很多人会算错。实际上,a++ 是先取值后自增,所以 b = 5 * 2 + 1 = 11,然后 a 变成6。我曾经在调试一个电机控制程序时,就因为 ++* 的优先级搞混,导致电机转了几圈就卡死——从那以后,我养成了一个习惯:拿不准优先级,就加括号

常用的运算符分类:

  • 算术运算符:+、-、*、/、%、++、--
  • 关系运算符:>、<、>=、<=、==、!=
  • 逻辑运算符:&&、||、!
  • 位运算符:&、|、^、~、<<、>>(嵌入式里用得最多!)
  • 赋值运算符:=、+=、-=、*= 等
小技巧: 在嵌入式里,判断一个数是不是2的幂,可以用 if (x & (x-1) == 0)。我经常用这个来校验配置参数是否合法。

3.3 流程控制语句——程序的分岔路口

3.3.1 if-else 与 switch-case

if-else 是最直观的条件判断。但要注意,不要嵌套太深。我见过有人写了7层if嵌套,那代码简直像一团乱麻。我个人习惯,超过3层就考虑用switch或者重构。

// 好的写法
if (temp > 100) {
    overheat_alarm();
} else if (temp > 80) {
    fan_on();
} else {
    fan_off();
}

// 不好的写法(嵌套太深)
if (mode == 1) {
    if (flag) {
        if (count > 10) {
            // 太深了!
        }
    }
}

switch-case 适合多分支定值判断。注意每个case后面要加 break,不然会“穿透”。我曾经在写按键扫描时,忘了加break,结果按一下键,触发了三个功能——用户差点以为产品坏了。

3.3.2 for 与 while——循环的两种姿势

for循环适合已知循环次数的场景。比如遍历数组:

int arr[10] = {0};
for (int i = 0; i < 10; i++) {
    arr[i] = i * 2;
}

while循环适合条件驱动的场景。比如等待外设就绪:

while (!(UART->SR & (1 << 5)));  // 等待发送完成标志位
// 注意:这种空循环要加超时保护,否则可能死锁

嗯,这里要注意:千万不要在嵌入式里用死循环不加退出条件。我有个同事,写了个 while(1) 等着中断来修改标志位,结果中断没配置好,程序直接卡死——整个产线都停了。

核心原则: 循环一定要有明确的退出条件,或者加看门狗喂狗机制。这是嵌入式开发的铁律。

3.4 数组与字符串——数据的集装箱

数组,就是同一类型数据的连续存储空间。在嵌入式里,数组常用于存放采样数据、查表、缓冲区等。

// 一维数组
uint8_t adc_values[100];  // 存放100次ADC采样值

// 二维数组(常用于矩阵、图像处理)
int matrix[3][3] = {
    {1, 0, 0},
    {0, 1, 0},
    {0, 0, 1}
};

字符串在C语言里,其实就是以 '\0' 结尾的字符数组。很多新手容易犯一个错:忘记给字符串结尾留位置

char str[6] = "hello";  // 实际需要6个字节:h e l l o \0
// 如果写成 char str[5] = "hello"; 就会越界!

我曾在处理AT指令解析时,因为字符串缓冲区少算了1个字节的结束符,导致解析出来的数据总是多一个乱码。排查了整整一个下午,最后发现是 strlensizeof 搞混了。

避坑指南: 使用 strcpystrcat 等字符串函数时,一定要确保目标缓冲区足够大。我建议用 strncpystrncat 这类带长度限制的函数,更安全。

好了,这一章的内容就到这里。数据类型、运算符、流程控制、数组和字符串,这些都是C语言的骨架。你把这些吃透了,后面学指针、结构体、链表就会轻松很多。下一章咱们继续聊C语言的高级话题——函数、指针和内存管理,到时候我会分享一些调试技巧,保证让你少走弯路。