从零开始学习嵌入式P3----C语言表达式、字符的输入输出
书接上文,今天从变量的定义和初始化开始,学习表达式、类型转换、常用运算符及运算符优先级,最后介绍单个字符的输入输出函数
getchar和putchar。默认在 Linux + GCC 环境下运行。
本篇目标
学完这一篇,我们应该能够:
- 按规则定义、初始化和修改变量;
- 理解表达式的值与类型;
- 区分隐式类型转换和显式类型转换;
- 正确使用算术、赋值、逗号及
sizeof运算符; - 看懂运算符优先级并主动使用括号消除歧义;
- 使用
getchar和putchar完成单个字符的输入与输出。
一、变量的定义与使用
变量是在程序运行期间值可以改变的数据对象。变量在内存中拥有对应的存储空间,我们通过变量名访问这块空间,并按照变量声明的类型解释其中的数据。
1. 先定义,再使用
变量定义的基本格式如下:
数据类型 变量名;例如:
shortnum;unsignedshortnum;intcount;unsignedintid;longtotal;unsignedlongaddress;floattemperature;doublevoltage;charcommand;unsignedcharregister_value;应为布尔不是C语言自带的关键字,使用bool时需要使用<stdbool.h>库调用:
#include<stdbool.h>bool really;2. 变量名的规则
C 语言标识符只能由字母、数字和下划线组成,并且不能以数字开头。
inta1_;// 合法int_a1;// 合法,但不建议int1a;// 错误:不能以数字开头intreturn;// 错误:不能与 C 语言关键字重名虽然某些库函数名在特定作用域中可能可以被重新声明或遮蔽,但这样做会让代码难以理解,也可能引发编译问题,因此不要把变量命名为printf、getchar等库函数名。
变量名最好做到见名知意。常见命名风格包括:
intMaxScore;// 驼峰命名intint8_t;// 下划线命名,在跨平台开发中很常见不论选什么,保持一致和能看懂最重要。
3. 初始化和赋值
定义变量时为它设置初始值,称为初始化:
intcount=0;floattemperature=25.0f;charcommand='A';而变量定义完成后,再使用赋值运算符=修改它的值,则称为赋值:
intcount;floattemperature;charcommand;//定义变量count=0;temperature=25.0f;command='A';这两种写法看起来相似,但发生的时机不同:
inta=10;// 定义并初始化a=20;// 给已经存在的变量赋值二、什么是表达式
表达式是由运算符连接常量、变量或其他表达式后形成的式子。例如:
10+20a*b count=count+1一个表达式通常同时具有两个重要属性:
- 类型:决定结果按哪种数据类型解释;
- 值:执行表达式后得到的结果。
由于C语言中具有多种数据存储类型,表达式中容易出现多种类型的数据。这会改变式子的输出结果。
例如:
10/4// 输出类型是 int,值是 210.0/4// 输出类型是 double,值是 2.5同样是除法,因为参与运算的数据类型不同,结果也不同。这也是 C 语言初学阶段非常容易出错的地方。
三、混合类型运算与类型转换
当运算符两侧的数据类型不一致时,编译器通常需要先把它们转换为适合共同运算的类型。
1. 常见的隐式转换
当表达式中出现像上文中的不同数据类型经行计算的情况:
2.5/2;C语言就会默认采用隐形转换机制经行转换。
隐式类型转换由编译器自动完成,常见情况包括:
- 当
char和short参与整数运算时,通常先将他们转换为int; - 当运算两侧出现浮点类型时,整数会转换为对应的浮点类型;
- 当
float与double混合运算时,float会转换为double; - 同等级的有符号和无符号整数混合运算时,转换为无符号类型;
实际的整数转换还会受到类型等级和目标平台表示范围的影响,尤其是有符号数和无符号数混合时,要格外谨慎数据格式。
2. 显式类型转换
有时也会人为经行数据类型的转换。
程序员主动写出的类型转换也称强制类型转换,基本格式为:
(目标类型)表达式例如:
intcount=10;doubleresult=(double)count/4;这里先把count转换成double,再参与除法,所以结果为2.5。
类型转换只影响表达式求值时采用的类型,不会自动改变原变量的声明类型:
doublevalue=5.27;intint_part=(int)value;// integer_part 为 5,但value 仍然是 5.27四、不同数据类型的赋值
看图片中的核心代码:
#include<stdio.h>intmain(void){doubled=0;d=10/4;printf("d = %f\n",d);return0;}很多人会认为d的输出结果是double类型,值为2.5。实际输出却是:
d = 2.000000原因在于,赋值之前会先计算等号右侧的表达式。10和4都是int类型,因此执行的是整数除法,小数部分直接被舍弃,先得到整数2,之后这个2才被转换为double并赋给d。
10 / 4 -> 整数除法得到 2 -> 转换为 double -> 2.000000如果想输出double结果,只要让除法中的任意一个操作数在运算前变成浮点类型即可:
d=10.0/4;d=10/4.0;d=(double)10/4;这三种写法的结果都是2.5。
五、算术运算符
C 语言常见的算术运算符如下:
| 运算符 | 含义 | 示例 |
|---|---|---|
+ | 加法 | a + b |
- | 减法 | a - b |
* | 乘法 | a * b |
/ | 除法 | a / b |
% | 取余 | a % b |
++ | 自增 | a++、++a |
-- | 自减 | a--、--a |
1. 除法与取余
两个整数相除时仍然得到整数,会直接省略小数点后面的部分:
inta=10/4;// 2intb=-10/4;// -2%用来计算整数除法的余数,不能对浮点数使用:
inta=10%4;// 22. 前置与后置自增
++和--可以粗略理解为:
a++;//a = a + 1a--;//a = a - 1++和--只能作用于可修改的变量,不能作用于常量。且a ++与++ a与不同的含义。
inti=5;inta=i++;// 先使用 i 的旧值完成当前表达式,再令 i 加 1:a = 5,i = 6intj=5;intb=++j;// 先令 j 加 1,再使用新值完成表达式:j = 6,b = 6如果只是单独写一条自增语句,i++和++i都会让i增加 1:
i++;++j;六、赋值运算符
常用赋值运算符包括:
= -= //a -= 1意为:a=a-1 *= //a *= 1意为:a=a*1 /= //a /= 1意为:a=a/1 %= //a %= 1意为:a=a%1复合赋值可以让代码更紧凑。
1. 左值与右值
简单理解:
- 左值表示一个可以被定位的对象,可修改变量能够放在赋值号左边;
- 右值用于提供参与计算或赋值的值,变量、常量和表达式都可以出现在右侧。
inta=0;intb=10;a=100;// 正确a=b;// 正确a=b+100;// 正确// 100 = a; // 错误:常量不能作为赋值目标// a + b = 0; // 错误:表达式不能作为赋值目标2. 混合类型赋值
不同类型之间赋值时可能发生转换:
- 整数赋给浮点变量:整数转换为浮点类型;
- 浮点数赋给整数变量:小数部分被舍弃;
- 小范围整数赋给大范围整数:通常进行符号扩展或补 0;
- 大范围整数赋给小范围整数:可能丢失高位数据;
- 有符号与无符号整数互相赋值:按目标类型规则转换,结果可能与直觉不同。
例如:
inta=3.99;// a 得到 3doubleb=10;// b 得到 10.0unsignedcharc=355;// 可能得到 99在常见 8 位unsigned char平台中,355 超出 0-255 的范围,转换结果相当于对 256 取模,因此得到 99。对于不能由目标类型表示的有符号整数转换,具体结果可能由编译器实现决定。
七、符号位
代码:
#include<stdio.h>intmain(void){unsignedinta=5;if(a-10>0){printf("Yes\n");}else{printf("No\n");}return0;}从数学上看,5 - 10等于-5,条件应该为假。但这段程序通常会输出:
Yes原因是a为unsigned int。表达式a - 10求值时,整数常量10会转换为unsigned int,整个减法执行无符号运算。
无符号整数不能表示负数。当结果越过 0 时,会按照模 2ⁿ 的规则回绕。假设unsigned int为 32 位:
5U - 10U = 4294967291U这个值当然大于 0,所以程序进入if分支。
如果本来就需要表示负数,则应从一开始选择有符号类型:
inta=5;if(a-10>0){printf("Yes\n");}else{printf("No\n");}这个例子说明,类型不仅决定变量能保存什么,也会直接改变表达式的计算方式。
八、逗号运算符与sizeof
1. 逗号运算符
逗号表达式从左到右依次计算各个子表达式,并把最后一个子表达式的值作为整体结果:
inta=1;intb=2;intresult=(a+10,b+20);//以最后一个式子为整个表达式的结果// result 的值为 22逗号运算符的优先级很低,所以通常需要加括号。日常代码不宜用逗号表达式,会降低可读性。
2.sizeof运算符
sizeof用来获取类型或对象占用的存储空间,结果以字节为单位:
#include<stdio.h>intmain(void){inta=0;printf("sizeof(int) = %zu\n",sizeof(int));printf("sizeof(a) = %zu\n",sizeof(a));return0;}对类型使用sizeof时必须加括号;对变量或普通表达式使用时括号可以省略,但为了统一和易读,通常保留。
sizeof(int);// 正确sizeofa;// 正确sizeof(a);// 正确九、运算符优先级
当一个表达式中出现多个运算符时,C 语言会按照优先级和结合性决定求值结构。常见运算符从高到低的大致顺序:
| 优先级 | 类别 | 常见运算符 |
|---|---|---|
| 高 | 括号和后缀 | ()、后置++、后置-- |
| 单目 | 前置++、前置--、!、~、sizeof、强制类型转换 | |
| 乘除取余 | *、/、% | |
| 加减 | +、- | |
| 移位 | <<、>> | |
| 关系与相等 | <、<=、>、>=、==、!= | |
| 按位运算 | &、^、` | |
| 逻辑运算 | &&、` | |
| 条件运算 | ?: | |
| 赋值 | =、+=、-=、*=、/=、%= | |
| 低 | 逗号 | , |
不建议仅靠背诵优先级来编写复杂表达式。存在歧义时主动使用括号,既能确保计算顺序,也能让读者快速看懂意图:
intresult=(a+b)*c;十、常用输入输出函数概览
C 语言的标准输入输出函数声明在<stdio.h>中:
#include<stdio.h>按处理对象可以粗略分为:
| 类别 | 常见函数 | 用途 |
|---|---|---|
| 单个字符输入输出 | getchar、putchar | 一次读取或输出一个字符 |
| 格式化输入输出 | scanf、printf | 按指定格式处理数据 |
| 字符串输入输出 | fgets、puts | 读取或输出字符串 |
十一、putchar输出字符
putchar的作用是在标准输出中写出一个字符,基本用法为:
putchar(字符或字符编码表达式);例如:
#include<stdio.h>intmain(void){putchar('A');putchar('\n');return0;}putchar每次只输出一个字符。调用成功时,它返回所输出字符对应的无符号字符值并以int类型表示;失败时返回EOF。
虽然putchar一次只能输出一个字符,但可以连续调用来输出一段文字:
#include<stdio.h>intmain(void){putchar('h');putchar('e');putchar('l');putchar('l');putchar('o');putchar('\n');return0;}也可以利用字符数组和循环实现,不过数组与循环将在后续内容中进一步学习。
十二、getchar输入字符
getchar从标准输入读取一个字符:
intch=getchar();要注意,它的返回类型是int,而不是char。
在普通终端中,标准输入通常按行缓冲。用户输入字符后,往往还需要按下回车,程序才能读取到这一行数据。
小写字母转换为大写字母
利用 ASCII 中大小写字母编码之间的固定差值,可以把输入的小写字母转为大写:
#include<stdio.h>intmain(void){intch=getchar();putchar(ch);putchar('\n');return0;}小结
这一篇学习了变量的定义、初始化与赋值,理解了表达式具有类型和值,并重点分析了整数除法、显式类型转换以及有符号与无符号混合运算。随后认识了算术、赋值、逗号和sizeof运算符,最后使用getchar和putchar完成了单个字符的输入与输出。
嵌入式程序经常直接处理寄存器、计数值和原始字节,类型选择或转换顺序稍有不同,表达式的结果就可能完全改变。看到一条表达式时,不仅要计算它的数学结果,还要先确认每个操作数是什么类型。
因为篇幅原因,本篇内容只讲到字符的输入输出,其他输入输出只能留到下一篇讲。