嵌入式day3
一、在VS的MSVC平台下调用scanf函数的细节问题
1. 安全函数警告
- MSVC默认开启安全函数警告,使用
scanf
会提示改用安全版本scanf_s
。 scanf_s
是MSVC的“方言”,不属于C语言标准库,不建议使用。
2. 解决方案
- 在源文件开头第一行添加宏定义以禁用警告:
#define _CRT_SECURE_NO_WARNINGS
注意:必须放在代码真正意义上的第一行,不可后置。
二、基础数据类型
C语言基本数据类型分为两大类:整型和浮点型。
(一)整型
1. 传统整型(4类)
- 类型:
short
、int
、long
、long long
。 - 大小规定:
- C标准未明确固定长度,仅规定最小长度和大小关系:
short
、int
≥ 2字节,long
≥ 4字节,long long
≥ 8字节;- 大小关系:
short ≤ int ≤ long ≤ long long
。
- 具体长度由编译器和平台决定,属于实现定义行为(非未定义行为,平台需明确文档说明)。
- 特别注意:
int
在普通机器上通常为4字节,嵌入式设备中可能为2字节。
- C标准未明确固定长度,仅规定最小长度和大小关系:
2. 有符号与无符号整型
- 分类:
- 有符号整型:默认类型(如
short
、int
、long
、long long
),取值范围含负数,如4字节signed int
为[-2³¹, 2³¹ - 1]
。 - 无符号整型:需显式加
unsigned
关键字(如unsigned int
),取值范围为非负数,如4字节unsigned int
为[0, 2³² - 1]
。
- 有符号整型:默认类型(如
- 默认规则:
short
、int
、long
、long long
直接使用时默认有符号,C标准明确规定,不可改变。
3. char类型
- 固定长度:C标准规定
char
必须为1字节,区别于其他整型。 - 符号性:
- 标准未规定默认符号性,部分平台为有符号(如MSVC,多数现代平台),部分为无符号。
- 建议显式声明符号性:
signed char
(有符号)、unsigned char
(无符号)。
- 用途:
- 表示字符(对应ASCII码表),存储范围有限;
- 表示1字节小整数,符号性需明确。
4. 整型变量取值范围
- 有符号数(1字节):
[-128, 127]
; - 无符号数(1字节):
[0, 255]
。 - 存储特性:取值范围为环形(溢出时循环),如:
- 最大值 + 1 = 最小值(如
127 + 1 = -128
); - 最小值 - 1 = 最大值(如
-128 - 1 = 127
)。
- 最大值 + 1 = 最小值(如
(二)浮点型
- 类型:
float
、double
、long double
。 - 存储标准:遵循IEEE 754标准(二进制科学计数法)。
- 特点:
- 范围大:同4字节空间,
float
表示范围远大于int
; - 精度有限:仅保证有限有效数字内的准确性,本质不精确,适用于对精度要求不高的场景(如科学计算、工程计算)。
- 范围大:同4字节空间,
- 示例:
float a = 0.1f;
double b = 0.1;
printf("%d\n", a == b); // 输出0(false,精度不同)
三、无符号整数使用建议
- 混合运算风险:有符号与无符号数混合运算时,C会自动将有符号数转换为无符号数,可能引发逻辑错误(如负数转为极大正数)。
- 非必要不使用:同等长度下,有无符号数取值范围不同,转换易导致bug。
- 避免混用:必须使用时,避免与有符号数混合,防止自动类型转换引发问题。
- 适用场景:
- 非负数场景:数组长度、字符串长度、数据结构容量、内存大小、内存地址值(地址值必为非负数,适合无符号数)。
- 示例:
unsigned num = 10;
while (num--) { /* 循环体 */ } // 结束时num为0,后缀--执行后变为UINT_MAX(32位无符号最大值)
四、关键总结
- 整型默认符号性:
short
、int
、long
、long long
默认有符号,char
符号性依平台而定,建议显式声明。 - 浮点数特性:范围大但精度有限,适用于非精确计算场景。
- 无符号数原则:仅在明确非负数场景使用,避免与有符号数混用。
五、类型别名(重点)
1. 语法与定义
- 关键字:
typedef
,用于为已存在的类型定义别名,使同一类型拥有两个名称。 - 语法格式:
typedef 现有类型名 别名;
- 示例:
typedef int Integer; // 为int定义别名Integer
Integer a = 10; // 等价于int a = 10;
2. 使用优点
优点 | 说明与示例 |
---|---|
提升代码可读性 | 别名可更清晰描述类型功能,例如链表结点结构体Node 在链式栈中可起别名StackFrame (栈帧),明确其功能定位。 |
提升扩展性与维护性 | 通过别名统一管理数据类型,便于修改。例如定义ElementType 作为链表元素类型,需存储int 时typedef int ElementType; ,需存储double 时仅修改别名定义即可,无需修改所有使用处。 |
增强跨平台性 | 应对不同平台数据类型长度差异。例如定义BigInteger 表示大整数,在int 为4字节的平台用typedef int BigInteger; ,在int 为2字节的平台用typedef long BigInteger; (long 最小4字节,符合跨平台需求)。 |
3. 使用建议
- 跨平台场景首选:若代码需在不同平台(如PC与嵌入式设备)运行,用别名替代直接使用基本类型(如
int
),避免依赖平台特定长度。 - 工程实践常用:在嵌入式开发、Linux编程中,广泛使用类型别名替代基本整型(如
size_t
替代unsigned int
)。
4. C标准库提供的类型别名
类型别名 | 说明 | 头文件 | 示例与用法 |
---|---|---|---|
size_t | 无符号整数,长度与平台位数相关(32位平台为32位,64位平台为64位),用于表示内存大小、数组索引等非负数场景。 | 隐含(部分需<stddef.h> ) | 打印时用%zu (十进制)、%zo (八进制)、%zx (十六进制):size_t num = 100; printf("%zu", num); |
int8_t /uint8_t | 精确位数的有符号/无符号整数(8位、16位、32位等),平台无关。 | <stdint.h> | uint16_t port = 8080; (明确16位无符号整数) |
ssize_t | 有符号版本的size_t ,长度与平台位数相关,类Unix平台(如Linux)常用。 | <stddef.h> | 用于可能返回负数的数组操作(如strlen 返回size_t ,read 系统调用返回ssize_t )。 |
5. 底层原理(以size_t
为例)
- 通过预处理指令适配平台:
#ifdef WIN64
typedef unsigned __int64 size_t; // 64位Windows
#else
typedef unsigned int size_t; // 32位平台
#endif
六、sizeof运算符(重点)
1. 定义与功能
- 作用:计算类型或变量所占内存空间的字节数,结果为无符号整数(
size_t
类型),值恒为非负数(非零)。 - 语法形式:
sizeof(类型名) // 如sizeof(int)
sizeof 变量名 // 如sizeof(arr)
2. 经典应用场景:求数组长度
- 公式:
数组长度 = sizeof(数组名) / sizeof(数组元素类型)
- 代码示例:
int arr[] = {1, 2, 3, 4};
int len = sizeof(arr) / sizeof(arr[0]); // len = 4
- 原理:
sizeof(arr)
:获取整个数组的字节大小(如int
数组占4 * 4 = 16
字节)。sizeof(arr[0])
:获取单个元素的字节大小(int
为4字节)。- 相除得到元素个数(
16 / 4 = 4
)。
3. 注意事项
- 数组与指针区别:
- 数组名在
sizeof
中表示整个数组,而非指针(仅在作为函数参数时退化为指针)。 - 禁止在函数中传递数组参数:未掌握指针前,避免将数组作为参数传递,防止
sizeof
失效(此时数组退化为指针,长度计算错误)。
- 数组名在
- 空数组禁止:C语言不允许空数组,
arr[0]
必然存在,确保sizeof(arr[0])
合法。 - 替代固定值:需使用类型长度时,优先用
sizeof
而非硬编码(如#define ARR_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
)。
七、关键总结
- 类型别名核心价值:通过
typedef
提高可读性、扩展性和跨平台性,标准库提供的_t
后缀别名(如size_t
)是工程实践的首选。 - sizeof核心用途:动态计算类型/变量字节大小,尤其用于求数组长度(公式
sizeof(arr)/sizeof(arr[0])
),避免硬编码和平台依赖。 - 最佳实践:
- 跨平台代码中用
int32_t
/uint8_t
等精确位数别名,而非直接使用int
/unsigned char
。 - 永远用
sizeof
计算数组长度,禁止手动编写固定数值。
- 跨平台代码中用
八、表达式的主要作用和副作用(重点)
- 主要作用:表达式最重要的特点是一定会计算出一个结果,这个结果就是表达式的主要作用。例如,
1 + 1
的主要作用是结果2
,变量a
作为表达式,其主要作用是a
的取值。当小表达式出现在大表达式中时,小表达式在大表达式中起到的作用就是主要作用,分析大表达式时,关注每个小表达式的主要作用即可。 - 副作用:除计算结果外,表达式的其他功能都是副作用。表达式不一定有副作用,常见副作用有:
- 赋值(最常见):所有赋值运算符(
=
、+=
、-=
等)组成的表达式都有此副作用。如int a; a = 10;
,该表达式主要作用是结果10
,副作用是将变量a
的值设置为10
。 - IO操作(也很常见):例如
printf
函数调用表达式,主要作用是函数返回值,副作用是将内存中的数据格式化输出到屏幕上。
- 赋值(最常见):所有赋值运算符(
- 总结:一个表达式的主要作用和副作用(如果有)同时存在且相互独立。例如,
a + b
主要作用是计算出的a + b
的结果,无副作用;a <<= 1
主要作用是计算出的a
左移1位的结果,副作用是将a
的值修改为左移1位后的值。在while((ch = getchar())!='\n')
循环中,getchar()
主要作用是返回读取的字符,副作用是读键盘输入IO操作;ch = getchar()
主要作用是返回读取的字符,副作用是将读到的字符赋值给ch
;(ch = getchar())!='\n'
主要作用是返回布尔值判断是否为换行符,由!=
组成的大表达式自身无副作用,但组成它的小表达式有副作用,该循环在读到换行符时结束。
九、运算符的优先级和结合性(重点)
- 优先级:当表达式中有多个运算符时,优先级决定运算符的计算顺序。例如,
a + b * c
中,乘法优先级高,先算乘法,再算加法。一些重要规律需记住:- 一元运算符的优先级一定高于二元运算符。
- 在所有二元运算符中,算术运算符的优先级更高。
- 赋值运算符的优先级往往最低,只有逗号运算符的优先级比它还低(逗号运算符很少用)。
- 所有运算符中
()
优先级最高。当不清楚运算符优先级时,可在需要先计算的部分加()
,增强代码可读性。
- 结合性:结合性决定多个相同优先级的运算符组成表达式时的运算顺序,分为左结合和右结合。左结合性意味着相同优先级的运算符从左到右计算,右结合性则从右到左计算。例如,
a + b - c
中,加减运算优先级一致,是左结合的,从左往右计算;int a, b, c; a = b = c = 0;
中,=
赋值号是右结合性的,从右往左计算,等价于a = (b = (c = 0));
。 - 示例:
- 优先级示例:求数组区间
[a, b]
的中间索引,简单做法(a + b)/2
在int
可能是2个字节的情况下易出问题,C语言中惯用法是(b - a)/2 + a
或((b - a) >> 1) + a
,最常见写法是(b - a >> 1) + a
,因为+-
高于>>
,里面的()
多余,外面的()
不能去掉,否则会先算+
。此需求在数组算法、数据结构(如归并排序、二分查找)中常见。 - 结合性示例:对于结构体
Student
有age
成员,s1
是结构体对象,s1.age++
等价于(s1.age)++
,.
和后缀++
优先级相同,左结合,从左往右计算;若p
是指向s1
的指针,p->age++
等价于(p->age)++
,->
和后缀++
优先级相同,左结合,从左往右计算。
- 优先级示例:求数组区间
十、自增自减运算符
- 两种形式:自增自减运算符分为前缀形式和后缀形式,后缀形式运算优先级更高。
- 前缀自增自减:先进行自增自减,然后返回自增自减的结果。如
--a
,主要作用是返回a
自减1后的结果,副作用是a
的值减少1。 - 后缀自增自减:直接返回自增自减前的结果,再进行自增自减。如
a++
,主要作用是直接返回a
,副作用是a
的值增加1 。
- 前缀自增自减:先进行自增自减,然后返回自增自减的结果。如
- 指针操作应用:自增自减符号在指针操作中很有用。例如,指针
p
指向数组首元素,对于表达式*p++
,后缀++
优先级更高,等价于*(p++)
。p++
主要作用是返回指针p
原本的值,副作用是让p
指针指向数组的下一个元素;*(p++)
主要作用是返回当前指针p
指向的元素的取值,p++
的副作用仍然会生效,利用*p++
及循环可实现指针遍历数组。 - 使用建议(重要):
- 在
for
循环中使用自增自减符号是最常见、清晰、稳定准确不出错的用法。 - 自增自减运算符尽量不要用于连接表达式,尽量单独成行,避免因主要作用不同产生歧义。
- 如果一定要将自增自减符号写在表达式中,严禁被自增自减的变量在同一个表达式中出现多次,否则会导致未定义行为,如
a+++--a;
在C语言中是错误的。
- 在
十一、取余运算符的特点
- 操作数要求:取余/取模运算符要求操作数必须是整数,不能是浮点数。
- 结果符号性:取余的结果除了0(整除)外,其余结果的符号性总和被除数保持一致。例如,
-100 % 3 = -1
,100 % 3 = 1
。
十二、短路逻辑运算符
C语言的逻辑运算符“或”(||
)、“与”(&&
)都是短路设计的:
&&
(逻辑与):如果第一个条件为假,后面的就不再执行了,结果一定是假。||
(逻辑或):如果第一个条件为真,后面的就不再执行了,结果一定是真。
这种短路设计符合现实场景,语义清晰,可避免不必要的运算,提高效率,还能提升程序的安全性。