3. C语言基础回顾(上):数据类型、运算符、流程控制、循环

各位同学,欢迎来到第三章。我知道很多人一听到「回顾基础」就觉得无聊,恨不得直接跳到项目实战。但别急,我做了十几年嵌入式,见过太多人因为基础不牢,在调试时浪费大量时间。今天这堂课,咱们把C语言最核心的几个零件拆开看看。

说白了,嵌入式开发就是跟硬件打交道。而C语言,就是我们指挥硬件的「遥控器」。你想想看,如果遥控器上的按键你都搞不清楚,那怎么控制设备?

3.1 数据类型:给变量找个合适的「家」

在嵌入式世界里,内存是稀缺资源。一块STM32芯片的RAM可能只有几十KB,比一张老照片还小。所以,选对数据类型特别重要。

我个人习惯,在定义变量前先问自己三个问题:这个数会不会是负数?最大能到多少?需要小数吗?

数据类型 占用字节 取值范围 典型用途
char 1 -128 ~ 127 存储字符、小范围状态
unsigned char 1 0 ~ 255 传感器原始数据(如ADC值)
int 4 -21亿 ~ 21亿 普通计数器、循环变量
unsigned int 4 0 ~ 42亿 时间戳、大范围计数
float 4 ±3.4e-38 ~ ±3.4e38 温度、电压等小数数据
⚠ 避坑指南: 我曾经在做一个温控项目时,用int存储温度值,结果发现负数温度被截断了。后来排查了半天,才发现是数据类型没选对。记住:涉及负数就用signed,涉及小数就用float。

这里有个小技巧:在嵌入式开发中,我建议多用unsigned char来存储传感器数据。为什么?因为很多传感器的输出范围就是0-255,用char刚好,省内存。

3.2 运算符:C语言的「计算器」

运算符这东西,看着简单,但用不好容易出bug。我把它分成三类来讲。

3.2.1 算术运算符

加减乘除大家都懂,但有个坑——整数除法。你试试5 / 2,结果是2,不是2.5。因为两个整数相除,结果还是整数,小数部分直接丢掉。

int a = 5, b = 2;
float c;
c = a / b;      // c = 2.0,不是2.5!
c = (float)a / b;  // 这样才是2.5
💡 我的经验: 做除法时,如果期望得到小数,记得先把其中一个操作数转成float。这个坑我踩过不止一次。

3.2.2 关系运算符与逻辑运算符

关系运算符就是><==这些。逻辑运算符是&&(与)、||(或)、!(非)。

嗯,这里要注意一个经典错误:把==写成=。比如:

if (x = 5) {  // 这是赋值,不是判断!
    // 这里的代码永远会执行
}

为什么会这样?因为x = 5这个表达式的结果是5,而5在C语言中被视为「真」。所以条件永远成立。我个人习惯把常量写在左边:if (5 == x),这样万一写成=,编译器会报错。

3.2.3 位运算符:嵌入式开发的「独门武器」

位运算在嵌入式里太常用了。说白了,就是直接操作二进制位。常用的有:

  • & 按位与:清零某位
  • | 按位或:置位某位
  • ^ 按位异或:翻转某位
  • ~ 按位取反:全部取反
  • << 左移:相当于乘以2
  • >> 右移:相当于除以2
// 假设我们要设置GPIO端口的第3位为1
unsigned char port = 0b00000000;
port |= (1 << 3);  // port变成0b00001000

// 清零第3位
port &= ~(1 << 3); // port变回0b00000000
🔑 核心要点: 位运算比算术运算快得多。在嵌入式系统中,能用位运算解决的问题,尽量别用乘除法。

3.3 流程控制:让程序「有脑子」

程序不是一直顺序执行的,它需要根据条件做决策。这就是流程控制的作用。

3.3.1 if/else:最常用的判断

if/else的语法很简单,但嵌套多了容易乱。我见过有人写了七八层if嵌套,那代码简直没法看。

if (温度 > 50) {
    // 开启风扇
} else if (温度 > 30) {
    // 开启散热片
} else {
    // 正常状态,什么都不做
}
⚠ 避坑指南: 我曾经在写一个多条件判断时,把else if写成了if,结果导致多个条件同时成立时,程序执行了不该执行的代码。记住:互斥的条件用else if,独立的条件用多个if。

3.3.2 switch:多分支的「高速公路」

当判断条件很多时,switch比if/else更清晰。比如根据按键值执行不同操作:

switch (key_value) {
    case 0x01:
        // 启动
        break;
    case 0x02:
        // 停止
        break;
    case 0x03:
        // 复位
        break;
    default:
        // 无效按键
        break;
}

注意每个case后面都要加break,不然会「穿透」到下一个case。不过有时候,穿透也是一种技巧。比如多个case执行相同操作时:

switch (mode) {
    case 1:
    case 2:
        // 模式1和2执行相同操作
        break;
    case 3:
        // 模式3单独处理
        break;
}

3.4 循环:让程序「不知疲倦」

嵌入式程序大部分时间都在循环。比如不断读取传感器数据、不断刷新显示屏。

3.4.1 for循环:适合「知道次数」的场景

for循环的结构是:初始化、条件判断、更新。比如让LED闪烁10次:

for (int i = 0; i < 10; i++) {
    LED_ON();
    delay(500);
    LED_OFF();
    delay(500);
}

我个人习惯把循环变量定义在for内部,这样出了循环就释放了,省内存。

3.4.2 while循环:适合「不知道次数」的场景

while循环只判断条件,条件为真就一直执行。比如等待按键松开:

while (按键按下) {
    // 等待,什么都不做
}
// 按键松开了,继续执行

还有一种do...while,它至少执行一次。我用的不多,但在某些场景下很实用,比如先发送一次数据,再检查是否成功。

💡 我的经验: 写循环时,一定要确保循环能结束。我见过有人写while(1)然后忘了加退出条件,程序就卡死了。如果确实需要无限循环,记得在里面加个break条件。

3.5 实战小例子:温控风扇

好了,理论讲完了,咱们来个综合例子。假设我们要做一个温控风扇:温度超过30度开启,低于25度关闭。

#include <stdio.h>

int main() {
    float temperature;
    int fan_status = 0;  // 0:关闭, 1:开启
    
    while (1) {
        // 读取温度(这里用模拟值)
        temperature = 28.5;
        
        // 控制逻辑
        if (temperature > 30.0) {
            fan_status = 1;
        } else if (temperature < 25.0) {
            fan_status = 0;
        }
        // 25~30度之间保持原状态,避免频繁开关
        
        // 输出状态
        if (fan_status) {
            printf("风扇开启\n");
        } else {
            printf("风扇关闭\n");
        }
        
        // 延时1秒
        // delay(1000);
    }
    
    return 0;
}

这个例子用到了数据类型(float、int)、运算符(比较)、流程控制(if/else)、循环(while)。你看,基础知识点串起来就能解决实际问题。

📌 课后思考: 如果温度在25~30度之间来回波动,风扇会频繁开关吗?怎么优化?提示:可以用「滞回控制」——开启温度设为30度,关闭温度设为25度,中间留个缓冲区。

好了,这一章就到这里。下一章咱们继续回顾C语言的函数、数组和指针——这些才是嵌入式开发的「硬菜」。记得动手敲代码,光看是学不会的。