嵌入式八股文—C语言入门学习(二)

本章学习:运算符(七大运算符)、控制流程结构(三大结构)

一、C语言的运算符

  1. 算术运算符

    运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等。算术运算符用于对数值进行加、减、乘、除、取余等基本运算,运算结果仍为数值。在此案例下,a原始值=0;

    |--------|--------------------|-----------------------|-----------|
    | 运算符 | 名称 | 举例 | 输出结果 |
    | +(或正号) | 运算加 | +1,a=a+1; | 1,a=1 |
    | -(或负号) | 运算减法 | -2,a=a-1; | -2,a=-1 |
    | / | 除法(整除) | a=10;a/2 | a=5 |
    | % | 取模或者取余 | a=10;a%3 | a=1 |
    | ++ | 自增:初始化后自增 后增:运算后再增 | a=1;b=++a; a=2;b=a++; | b=2; b=2; |
    | -- | 自减:初始化后减 后减:运算后在减 | a=1;b=--a; a=2;b=a--; | b=0; b=2; |

  2. 赋值运算符

    赋值运算符用于把右侧表达式的值赋给左侧变量,其中复合赋值运算符是"先运算、再赋值"的简写形式,可让代码更简洁。假设 a、b 均为整型变量,常见用法如下:

    |-----|-------|-------|--------|
    | 运算符 | 名称 | 举例缩小 | 表示形式 |
    | += | 复合加赋值 | a+=b | a=a+b |
    | -= | 复合减赋值 | a-=b | a=a-b |
    | *= | 复合乘赋值 | a*=b | a=a*b |
    | /= | 复合除赋值 | a/=b | a=a/b |
    | %= | 复合模赋值 | a%=b | a=a%b |

  3. 比较(关系)运算符

    比较运算符用于判断两个值之间的大小或相等关系,运算结果为真(1)或假(0)。假设 num 为整型变量,常见用法如下:

    |-------|------|------------|------------------|
    | 关系运算符 | 含义 | 举例 | 说明 |
    | > | 大于 | num > 10 | num 大于 10 时为真 |
    | >= | 大于等于 | num >= 10 | num 大于或等于 10 时为真 |
    | < | 小于 | num < 10 | num 小于 10 时为真 |
    | <= | 小于等于 | num <= 10 | num 小于或等于 10 时为真 |
    | == | 等于 | num == 10 | num 等于 10 时为真 |
    | != | 不等于 | num != 10 | num 不等于 10 时为真 |

    注意:判断相等要用 ==,不要误写成赋值运算符 =。

  4. 逻辑运算符

    逻辑运算符用于连接一个或多个条件表达式,运算结果同样为真(1)或假(0),常用于 if、while 等需要组合判断的场景。假设 num1、num2 均为整型变量,常见用法如下:

    |-------|------|------------|--------------------------------|
    | 逻辑运算符 | 描述 | 功能 | 举例 |
    | && | 与运算符 | 两个条件都要满足 | num1 >= 10 && num2 >= 20 |
    | || | 或运算符 | 两个条件只需满足其一 | num1 >= 10 || num2 >= 20 |
    | ! | 非运算符 | 否定条件 | !(num1 >= 10)(等价于 num1 < 10) |

    注意:&& 和 || 具有短路特性,即前面的条件已经能确定结果时,后面的表达式不再执行。

  5. 位运算符

    C 语言提供一些位运算符,用来操作二进制位(bit)。位运算符的运算过程都是基于二进制的补码运算。假设参与运算的数均为整型,常见用法如下:

    |------|-------|-------------------------------------------|
    | 运算符 | 描述 | 运算规则 |
    | << | 二进制左移 | 将一个数的各二进制位全部左移指定的位数,左边的二进制位丢弃,右边补 0。 |
    | >> | 二进制右移 | 将一个数的各二进制位全部右移指定的位数,正数左补 0,负数左补 1,右边丢弃。 |
    | & | 按位与 | 两个二进制位都为 1,结果为 1,否则为 0。 |
    | | | 按位或 | 两个二进制位只要有一个为 1(包含两个都为 1 的情况),结果为 1,否则为 0。 |
    | ^ | 按位异或 | 两个二进制位一个为 0,一个为 1,结果为 1,否则为 0。 |
    | ~ | 按位取反 | 将每一个二进制位变成相反值,即 0 变成 1,1 变成 0。 |

    位运算符在MCU的控制及其普遍,需要重点掌握:以下将以案例进行说明

    最高位如果是正数就是0,负数则就是1;负数的位移运算先取反码、补码在运算位移。


    左移相当于乘法,右移就相当于除法的运算;运算规则:在一定范围内,数据每向右移动一位,相当于原数据/2。(正负数都适用)

    逻辑与&和逻辑或|主要用于特定位置的置1与置0操作;如果想要哪一位置1,就用二进制对应位或|运算,置0则用对应位的二进制0与&就可以实现对应位置的变化;异或的运算皆如此,不同的则异或结果为1,相同则就为0,可以用于检验某位结果的运算情况。

  6. 条件运算符

    复制代码
    条件运算符格式:(条件表达式)? 表达式1:表达式2
    说明:条件表达式是如果为 true (非0值),就执行表达式1,否则执行表达式2

    这种运算的表达式简单运算速度快,可以获得需要的表达式类型,此为双目运算符;案例如下所示:

    cs 复制代码
    #include<stdio.h>
    
    //获取三个数中的最大值
    int main() {
        int n1 = 23;
        int n2 = 13;
        int n3 = 33;
        //写法1:
        int numMax = (n1 > n2)? n1:n2;
        int finalMax = (numMax > n3)? numMax : n3;
        printf("三个数中最大值为%d\n",finalMax);
    
        //写法2:不推荐,可读性差
        int finalMax1 = (((n1 > n2)? n1:n2) > n3)? ((n1 > n2)? n1:n2) : n3;
        printf("三个数中最大值为%d\n",finalMax1);
    
        return 0;
    }
  7. sizof()运算符

    sizeof 运算符:sizeof(参数 ),看着字母的英文意思就是尺寸,就是计算类型的尺寸字节大小;参数可以是数据类型的关键字,也可以是变量名或某个具体的值。返回某种数据类型或某个值占用的字节数量。

举例1:当输入的是类型名或者间接表达

int x = sizeof(int); //通常是 4 或 8

int i; sizeof(i); //通常是 4 或 8

一般来说整型变量int 就占据4个字节、字符型 char 占据1字节、float单精度4个字节、double双精度8字节;

sizeof(3.14); //浮点数的字面量一律存储为double类型,故返回 8

sizeof 运算符的返回值,C 语言只规定是无符号整数,并没有规定具体的类型,留给系统自己去决定sizeof 到底返回什么类型。不同的系统中,返回值的类型有可能是 unsigned int ,也有可能是unsigned long ,甚至是 unsigned long long ,对应的 printf() 占位符分别是 %u 、 %lu和 %llu 。这样不利于程序的可移植性。

C 语言提供了一个解决方法,创造了一个类型别名 size_t ,用来统一表示 sizeof 的返回值类型。该别名定义在 <stddef.h>头文件里面,对应当前系统的 sizeof 的返回值类型,可能是 unsigned int ,也可能是 unsigned long 。

printf() 有专门的占位符 %zd 或 %zu ,用来处理 size_t 类型的值。

printf("%zd\n", sizeof(int));

上面代码中,不管 sizeof 返回值的类型是什么, %zd 占位符(或 %zu )都可以正确输出。

如果当前系统不支持 %zd 或 %zu ,可使用 %u (unsigned int)或 %lu (unsigned long int)代替。

以上就是本次详介绍的C语言常见的运算符类型,不过运算符也有类型的比较以及优先级;

二、C语言的控制流程

流程控制结构是用来控制程序中各`语句执行顺序`的语句,可以把语句组合成能完成一定功能的小逻辑模块。 程序设计中规定的三种流程结构,即:

顺序结构: 程序从上到下逐行地执行,中间没有任何判断和跳转。

分支结构: 根据条件,选择性地跳过执行某段代码。有if...else和switch-case两种分支语句。

循环结构:根据循环条件,重复性的执行某段代码。有for、while、do-while三种循环语句。

2.1 顺序结构(最常见的代码结构)

顺序结构,程序从上到下逐行地执行。表达式语句都是顺序执行的。并且上一行对某个变量的修改对下一行会产生影响,上一行出现错误了代码就搁置停止运算;同时注意一点就是:变量都是先定义后使用的,这点需要与python进行区分。

cs 复制代码
#include<stdio.h>
int main() {
    int a= 1;
    int b = 2;
    printf("a= %d\n", a);
    printf("b = %d\n", b);  
    
    a++;//运行完后a=2
    b = 2 * a + b;
    a = a * 10;  //对x、y的值进行修改
    printf("a = %d\n",a);
    printf("b = %d\n", y);

    return 0;

}

2.2 分支(选择)结构

分支结构很简单,就是满足条件则就跳过执行{}内的代码块、执行流程:条件表达式为真(值不为 0 )时,就执行语句块。如下代码,明显Score大于60则if()括号的结果为假,执行else语句块;

cs 复制代码
#include<stdio.h>

int main(){
    int  Score= 89;

    if(Score < 60 ){
        printf("你的成绩不及格");
    }
    else
    printf("你的成绩已经及格");
    return 0;
}

其中分支结构的格式主要分为三种:

(1)单分支结构,就只有一个if()进行判断执行,结果为真就执行语句块,假则跳过不执行;

int a=1;

if(a<0) a++; printf("a=%d\n",a);结果输出则为a=1,因为if条件不满足;

(2)双分支结构,if...else结构,条件表达式为真就执行if语句下的代码块,不满足则执行else代码块。

int a=2;

if(a>2) a++;else a--; printf("a=%d\n",a); 结果输出为:a=1;执行的是自减后的结果。

(3)多重分支结构,if()...else if()...else型,满足哪部分的条件结构就执行,都不满足就执行else的部分代码。

int age = 0;

scanf("%d", &age);

if (age < 18) printf("青年人\n");

else if(age>18&&age<45)

printf("中年人\n");

else

printf("老年人\n");

注意:

> 当条件表达式之间是"互斥"关系时(即没有交集),判断语句及执行语句间顺序无所谓。

> 当条件表达式之间是"包含"关系时,否则范围小的条件表达式将不可能被执行。

> 当if-else结构是"多选一"时,最后的else是可选的,根据需要可以省略

> 语句块只有一条执行语句时,一对{}可以省略,但建议保留

> 如果有多个 if 和 else ,没有{}的情况下, else 总是跟最接近的 if 匹配。

> 为了提供代码的可读性,建议使用大括号,明确 else 匹配哪一个 if 。
分支结构还有重要的一个语法:switch...case

第1步:根据switch中表达式的值,依次匹配各个case。如果表达式的值等于某个case中的常量值,则执行对应case中的执行语句。

第2步:执行完此case的执行语句以后,

​ 情况1:如果遇到break,则执行break并跳出当前的switch-case结构

​ 情况2:如果没有遇到break,则会继续执行当前case之后的其它case中的执行语句。--->case穿透

​ 直到遇到break关键字或执行完所有的case及default的执行语句,跳出当前switch-case结构

使用注意点:

  • case子句中的值必须是常量,不能是变量名或不确定的表达式值或范围。

  • 同一个switch语句,所有case子句中的常量值互不相同。

  • 如果没有break,程序会顺序执行到switch结尾;从使用频率说,一般switch-case结构中,都需要编写break。

  • default子句是可选的。同时,位置也是灵活的。当没有匹配的case时,执行default语句。

格式为:

switch(表达式){

case 常量值1:

语句块1;

//break;

case 常量值2:

语句块2;

//break;

case 常量值n:

语句块n;

//break;

default:

语句块n+1;

}

cs 复制代码
#include<stdio.h>

int main() {
    int grade = 1;
    switch (grade) {
        case 0:
            printf("zero\n");
            break;
        case 1:
            printf("one\n");
            break;
        case 2:
            printf("two\n");
            break;
        case 3:
            printf("three\n");
            break;
        default:
            printf("other\n");
    }
    return 0;
}

结果输出为:one;

小结: 凡是使用switch-case的结构都可以转换为if-else结构。反之,不成立。

开发经验:如果可以使用switch-case,又可以使用if-else,建议使用switch-case。因为效率稍高。

细节对比: if-else语句优势, if语句的条件可以用于范围的判断,也可以用于等值的判断,使用范围更广。switch语句的条件是一个常量值,只能判断某个变量或表达式的结果是否等于某个常量值,使用场景较狭窄。switch语句优势:当条件是判断某个变量或表达式是否等于某个固定的常量值时,使用if和switch都可以,习惯上使用switch更多。因为效率稍高。当条件是区间范围的判断时,只能使用if语句。使用switch可以利用穿透性,同时执行多个分支,而if...else没有穿透性。

2.3 循环结构

循环结构四要素: 初始化部分,循环条件部分,循环体部分,迭代部分;下面将详细介绍。

(1)for循环结构

语法格式:

for (①初始化部分; ②循环条件部分; ④迭代部分){

③循环体部分;}

for(;;)中的两个;不能多也不能少

①初始化部分,用于初始化循环变量,只执行一次。可以声明多个变量,但必须是同一个类型,用逗号分隔;

②循环条件部分,只要为 true ,就会不断执行循环体;当值为false时,退出循环

④迭代部分,每轮循环结束后执行,使得循环变量发生变化。可以有多个变量更新,用逗号分隔。

cs 复制代码
#include<stdio.h>
//经典题目:输出所有的水仙花数,所谓水仙花数是指一个3位数,其各个位上数字立方和等于其本身。
//例如: 153 = 1*1*1 + 3*3*3 + 5*5*5

int main() {
    //定义统计变量,初始化值是0
    int count = 0;
    //获取三位数,用for循环实现
    for (int x = 100; x < 1000; x++) {
        //获取三位数的个位,十位,百位
        int ge = x % 10;
        int shi = x / 10 % 10;
        int bai = x / 100;
        //判断这个三位数是否是水仙花数,如果是,统计变量++
        if ((ge * ge * ge + shi * shi * shi + bai * bai * bai) == x) {
            printf("水仙花数:%d\n", x);
            count++;
        }
    }
    //输出统计结果就可以了
    printf("水仙花数共有%d个", count);

    return 0;
}

说明:我们可以在循环中使用break。一旦执行break,就跳出当前循环结构。

小结:如何结束一个循环结构?

结束情况1:循环结构中的循环条件部分返回false

结束情况2:循环结构中执行了break。

如果一个循环结构不能结束,那就是一个死循环!我们开发中要避免出现死循环。

(2)while()循环与do....while()循环

语法格式:

①初始化部分

while(②循环条件部分){

③循环体部分;

④迭代部分;

}

while(循环条件部分)中循环条件为非零值,表示true、真;为零值,表示false、伪。

注意不要忘记声明④迭代部分。否则,循环将不能结束,变成死循环。

for循环和while循环`可以相互转换`。二者没有性能上的差别。实际开发中,根据具体结构的情况,选择哪个格式更合适、美观。

for循环与while循环的区别:初始化条件部分的作用域不同。

do-while 结构是 while 的变体,它会先执行一次循环体,然后再判断是否满足条件。如果满足的话,就继续执行循环体,否则跳出循环。

语法格式:

①初始化部分;

do{

③循环体部分

④迭代部分

}while(②循环条件部分);

说明:

do{}while();最后有一个分号

do-while结构的循环体语句是至少会执行一次,这个和for和while是不一样的

循环的三个结构for、while、do-while三者是可以相互转换的。

cs 复制代码
#include<stdio.h>

int main() {
    //while循环:
    int num1 = 10;
    while (num1 > 10) {
        printf("hello:while\n");
        num1--;
    }

    //do-while循环:
    int num2 = 10;
    do {
        printf("hello:do-while\n");
        num2--;
    } while (num2 > 10);

    return 0;
}

总结:三种循环结构都具有四个要素:(1) 循环变量的初始化条件;(2) 循环条件;(3)循环体语句块;(4)循环变量的修改的迭代表达式.

从循环次数角度分析:

do-while循环至少执行一次循环体语句

for和while循环先判断循环条件语句是否成立,然后决定是否执行循环体。

如何选择:(1)遍历有明显的循环次数(范围)的需求,选择for循环

(2)遍历没有明显的循环次数(范围)的需求,选择while循环

(3)如果循环体语句块至少执行一次,可以考虑使用do-while循环

本质上:三种循环之间完全可以互相转换,都能实现循环的功能

|----------|-------------|---------------------|----------------|
| 关键字 | 适用范围 | 循环结构中的作用 | 相同点 |
| break | switch-case | - | - |
| break | 循环结构 | 一旦执行,就结束(或跳出)当前循环结构 | 此关键字的后面,不能声明语句 |
| continue | 循环结构 | 一旦执行,就结束(或跳出)当前循环结构 | 此关键字的后面,不能声明语句 |

三、总结

本文围绕 C 语言的两大基础主题展开:一是运算符,二是控制流程结构。运算符部分系统梳理了算术运算符、赋值运算符、比较(关系)运算符、逻辑运算符、位运算符、条件运算符以及 sizeof() 运算符,并结合表格与代码示例说明了各自的写法、运算规则和常见易错点,例如判断相等要用 == 而非 =、逻辑运算符具有短路特性、位运算基于补码进行等。

控制流程部分则从程序设计的三种基本结构出发,依次讲解了顺序结构、分支结构和循环结构。分支结构重点对比了 if-else 与 switch-case 的适用场景和穿透特性;循环结构则围绕 for、while、do-while 三种写法,归纳了循环四要素、循环结束的两种方式以及如何根据是否有明确循环次数来选择合适的循环语句,最后用表格对比了 break 与 continue 的作用范围。

整体来看,运算符决定了"数据怎么算",控制流程决定了"代码怎么走",二者共同构成了 C 语言程序的基本骨架。建议读者在理解语法规则的基础上,多动手编写和调试示例代码,尤其注意边界条件、死循环和类型转换等细节,为后续学习函数、数组、指针等内容打好基础。

相关推荐
Nerd(BUG版)32 分钟前
KMP学习day5--Kotlin Flow异步流基础
开发语言·学习·kotlin
叼烟扛炮1 小时前
Linux 学习笔记(六十一):网络编程毕业设计——Reactor 反应堆模式
linux·网络·笔记·学习·课程设计
叼烟扛炮3 小时前
Linux 学习笔记(五十九):epoll 的生死抉择——LT 与 ET 模式
linux·运维·服务器·网络·笔记·学习·php
水云桐程序员9 小时前
从零开发软件的步骤,软件开发从零到完成应该怎么做?
笔记·学习·软件构建·个人开发
David猪大卫10 小时前
【Linux修炼】环境变量、进程地址空间
linux·c语言·经验分享·笔记·学习·ubuntu·centos
叼烟扛炮11 小时前
Linux 学习笔记(五十六):IO 多路复用初探——select 的“三宗罪”
linux·服务器·数据库·笔记·学习·php
叼烟扛炮17 小时前
Linux 学习笔记(五十八):IO 多路复用封神——epoll 底层原理揭秘
linux·运维·服务器·网络·笔记·学习·php
Zwawa18 小时前
两根线怎么挂住一堆设备:I2C 协议原理
c语言·单片机·嵌入式
欧西18 小时前
零成本做一颗芯片:我用 OpenLane 把 SHA-256 从 Verilog 做到了 GDS
开源·嵌入式