C语言基本笔记(2)——数据类型
整数
| 类型 | 存储大小 | 值范围 |
|---|---|---|
| char | 1 字节 | -128 到 127 或 0 到 255 |
| unsigned char | 1 字节 | 0 到 255 |
| signed char | 1 字节 | -128 到 127 |
| int | 2 或 4 字节 | -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647 |
| unsigned int | 2 或 4 字节 | 0 到 65,535 或 0 到 4,294,967,295 |
| short | 2 字节 | -32,768 到 32,767 |
| unsigned short | 2 字节 | 0 到 65,535 |
| long | 4 字节 | -2,147,483,648 到 2,147,483,647 |
| unsigned long | 4 字节 | 0 到 4,294,967,295 |
一、基本存储规则
1. 数据类型大小
-
C标准定义:
int的大小由编译器和目标平台决定,通常为:- 16位系统:2字节(如8051、部分早期MCU)
- 32/64位系统:4字节(如ARM Cortex-M、ESP32)
-
验证方法:
printf("int size: %d bytes", sizeof(int)); // 输出实际大小
2. 符号与编码
- 有符号int(signed int):
- 使用二进制补码(Two's Complement)表示负数。
- 最高位为符号位(0正1负)。
- 示例:
int8_t a = -5→ 二进制:11111011。
- 无符号int(unsigned int):
- 所有位用于表示数值(无符号位)。
- 范围:
0到2ⁿ-1(n为位数)。
二、字节顺序(Endianness)
1. 大端序(Big-Endian)
- 高位字节在前(低地址存高位)。
- 常见于网络协议(如TCP/IP)、PowerPC架构。
- 示例:
0x12345678存储顺序:12 34 56 78。
2. 小端序(Little-Endian)
- 低位字节在前(低地址存低位)。
- 常见于x86、ARM Cortex-M。
- 示例:
0x12345678存储顺序:78 56 34 12。
三、内存对齐
1. 对齐规则
-
自然对齐:变量地址必须是其类型大小的整数倍。
int(4字节)地址需为4的倍数(如0x0000、0x0004)。
-
单片机内存对齐要求:
- 某些架构(如ARM Cortex-M)强制要求对齐,否则触发硬件异常。
2. 对齐优化
- 某些架构(如ARM Cortex-M)强制要求对齐,否则触发硬件异常。
-
手动填充:通过插入占位字节保证对齐。
四、单片机开发中的实际应用
1. 多字节数据传输(如UART、SPI)
-
发送端需明确字节顺序:
uint32_t data = 0x12345678;// 按大端序发送 send_byte((data >> 24) & 0xFF); // 0x12 send_byte((data >> 16) & 0xFF); // 0x34 send_byte((data >> 8) & 0xFF); // 0x56 send_byte( data & 0xFF); // 0x78
2. 处理外设寄存器
-
直接地址访问需考虑对齐:
// 错误示例:未对齐访问(可能导致HardFault) volatile uint32_t *reg = (volatile uint32_t*)0x0003; // 地址非4字节对齐 *reg = 0x12345678; // 正确示例:对齐访问 volatile uint32_t *reg = (volatile uint32_t*)0x0004; // 地址对齐
3. 数据持久化(Flash/EEPROM)
-
写入前序列化:
uint32_t value = 0x12345678; uint8_t buffer[4];// 按小端序存储 buffer[0] = (uint8_t)(value); buffer[1] = (uint8_t)(value >> 8); buffer[2] = (uint8_t)(value >> 16); buffer[3] = (uint8_t)(value >> 24); eeprom_write(buffer, 4);
五、跨平台兼容性建议
-
使用固定大小类型:
#include <stdint.h>int32_t data1; // 明确32位有符号整数 uint16_t data2; // 明确16位无符号整数 -
显式处理字节序:
// 主机序转网络序(大端) uint32_t htonl(uint32_t hostlong) { return ((hostlong & 0xFF) << 24) | ((hostlong & 0xFF00) << 8) | ((hostlong >> 8) & 0xFF00) | ((hostlong >> 24) & 0xFF); } -
避免未定义行为:
// 错误:通过不同类型的指针访问同一内存 int a = 10; float *p = (float*)&a; // 违反严格别名规则 // 正确:使用memcpy复制字节 int a = 10; float b; memcpy(&b, &a, sizeof(int)); // 安全但需确保类型大小一致
浮点
| 类型 | 存储大小 | 值范围 | 精度 |
|---|---|---|---|
| float | 4 字节 | 1.2E-38 到 3.4E+38 | 6 位有效位 |
| double | 8 字节 | 2.3E-308 到 1.7E+308 | 15 位有效位 |
| long double | 16 字节 | 3.4E-4932 到 1.1E+4932 | 19 位有效位 |
一、IEEE 754标准核心规则
1. 浮点数组成
所有浮点数按以下三部分存储:
| 组成部分 | 作用 | 位数分配(单精度) | 位数分配(双精度) |
|---|---|---|---|
| 符号位(Sign) | 正负标志(0正1负) | 1位 | 1位 |
| 指数(Exponent) | 科学计数法的幂次(偏移编码) | 8位(范围-127~128) | 11位(范围-1023~1024) |
| 尾数(Mantissa) | 小数部分(隐含前导1) | 23位(精度约7位十进制) | 52位(精度约15位十进制) |
2. 数值计算公式
单精度(32位):
双精度(64位):
二、内存布局示例
1. 单精度浮点数(float,32位)
以数字 -6.625 为例:
-
符号位:
1(负数) -
二进制科学计数法:
-1.10101 × 2²→ 尾数部分10101,指数2 + 127 = 129(二进制10000001) -
内存结构:
| 1 | 10000001 | 10101000000000000000000 | ↑ ↑ ↑ Sign Exponent Mantissa -
十六进制表示:
0xC0D40000
2. 双精度浮点数(double,64位)
以数字 3.1415926535 为例:
- 符号位:
0(正数) - 二进制科学计数法:
1.1001001000011111101101010100010001000010110100011000 × 2¹ - 指数计算:
1 + 1023 = 1024(二进制10000000000) - 内存结构(十六进制):
0x400921FB54442D18
三、特殊值处理
| 类型 | 符号位 | 指数域 | 尾数域 | 说明 |
|---|---|---|---|---|
| 正零(+0) | 0 | 全0 | 全0 | 所有位为0 |
| 负零(-0) | 1 | 全0 | 全0 | 符号位为1 |
| 正无穷(+∞) | 0 | 全1 | 全0 | 计算结果溢出 |
| 负无穷(-∞) | 1 | 全1 | 全0 | 计算结果溢出 |
| NaN | 0或1 | 全1 | 非全0 | 无效运算(如√-1) |
四、关键开发注意事项
1. 精度陷阱
-
十进制小数无法精确表示:
float a = 0.1f; // 实际存储值约为0.10000000149 if (a == 0.1) {} // 可能判断失败!解决方案:
-
使用误差范围比较:
#define EPSILON 1e-6 if (fabs(a - 0.1) < EPSILON) {} -
避免用浮点数做精确计数(改用整数放大法)。
-
2. 字节顺序(Endianness)
浮点数同样受CPU字节序影响:
// 检测浮点数字节序
float f = 1.0f;
uint8_t *p = (uint8_t*)&f;
// 小端序存储为 0x00 0x00 0x80 0x3F
// 大端序存储为 0x3F 0x80 0x00 0x00
3. 内存对齐
-
单精度(32位):地址需4字节对齐(ARM Cortex-M要求)
-
双精度(64位):地址需8字节对齐
// 错误示例:未对齐访问导致崩溃 char buffer[10]; float *p = (float*)(buffer + 1); // 地址未对齐*p = 3.14f; // 可能触发HardFault
4. 性能优化
-
避免频繁类型转换:
int a = 100;float b = a * 0.1f; // 优于 float b = a / 10.0f; -
使用硬件浮点单元(FPU):
启用编译器的FPU支持(如ARM的-mfloat-abi=hard)。
五、实战代码:解析浮点数内存
#include <stdio.h>
// 联合体直接访问浮点数的字节
typedef union {
float f;
uint8_t bytes[4];
} FloatParser;
void print_float_bytes(float num) {
FloatParser parser;
parser.f = num;
printf("0x");
for (int i = 0; i < 4; i++) {
printf("%02X", parser.bytes[i]);
}
printf("\n");
}
int main() {
float f = -6.625f;
print_float_bytes(f); // 输出小端序:0x00 00 D4 C0 → 0xC0D40000
return 0;
}
六、总结
- IEEE 754是浮点数的通用标准,包含符号、指数、尾数三部分。
- 特殊值(如NaN、无穷)有明确编码规则,需在代码中正确处理。
- 精度问题不可避免,需用误差比较或整数放大法替代精确判断。
- 内存对齐和字节序直接影响数据可靠性,尤其在跨平台通信中。
- 性能敏感场景优先使用硬件FPU,避免软件浮点模拟。
void 类型
void 类型指定没有可用的值。它通常用于以下三种情况下:
| 序号 | 类型与描述 |
|---|---|
| 1 | 函数返回为空 C 中有各种函数都不返回值,或者您可以说它们返回空。不返回值的函数的返回类型为空。例如 void exit (int status); |
| 2 | 函数参数为空 C 中有各种函数不接受任何参数。不带参数的函数可以接受一个 void。例如 int rand(void); |
| 3 | 指针指向 void 类型为 void * 的指针代表对象的地址,而不是类型。例如,内存分配函数 *void malloc( size_t size ); 返回指向 void 的指针,可以转换为任何数据类型。 |
数据类型转换
类型转换是将一个数据类型的值转换为另一种数据类型的值。
C 语言中有两种类型转换:
-
隐式类型转换: 隐式类型转换是在表达式中自动发生的,无需进行任何明确的指令或函数调用。它通常是将一种较小的类型自动转换为较大的类型,例如,将int类型转换为long类型或float类型转换为double类型。隐式类型转换也可能会导致数据精度丢失或数据截断。
-
显式类型转换: 显式类型转换需要使用强制类型转换运算符(type casting operator),它可以将一个数据类型的值强制转换为另一种数据类型的值。强制类型转换可以使程序员在必要时对数据类型进行更精确的控制,但也可能会导致数据丢失或截断。
隐式类型转换实例:
int i = 10;
float f = 3.14;
double d = i + f; // 隐式将int类型转换为double类型
显式类型转换实例:
double d = 3.14159;
int i = (int)d; //显式将double类型转换为int类型
浙公网安备 33010602011771号