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. 对齐优化
  • 手动填充:通过插入占位字节保证对齐。

四、单片机开发中的实际应用

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);
    

五、跨平台兼容性建议

  1. 使用固定大小类型

    #include <stdint.h>int32_t  data1;  // 明确32位有符号整数
    uint16_t data2;  // 明确16位无符号整数
    
  2. 显式处理字节序

    // 主机序转网络序(大端)
    uint32_t htonl(uint32_t hostlong) 
    {    
    return ((hostlong & 0xFF) << 24) |           
           ((hostlong & 0xFF00) << 8) |               
           ((hostlong >> 8) & 0xFF00) |           
           ((hostlong >> 24) & 0xFF);
    }
    
  3. 避免未定义行为

    // 错误:通过不同类型的指针访问同一内存
    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位):

\[(-1)^{\text{Sign}} \times (1 + \text{Mantissa}) \times 2^{\text{Exponent} - 127} \]

双精度(64位):

\[(-1)^{\text{Sign}} \times (1 + \text{Mantissa}) \times 2^{\text{Exponent} - 1023} \]


二、内存布局示例

1. 单精度浮点数(float,32位)

以数字 -6.625 为例:

  1. 符号位1(负数)

  2. 二进制科学计数法-1.10101 × 2² → 尾数部分 10101,指数 2 + 127 = 129(二进制 10000001

  3. 内存结构

    | 1 | 10000001 | 10101000000000000000000 |
      ↑     ↑             ↑
    Sign Exponent      Mantissa
    
  4. 十六进制表示0xC0D40000


2. 双精度浮点数(double,64位)

以数字 3.1415926535 为例:

  1. 符号位0(正数)
  2. 二进制科学计数法1.1001001000011111101101010100010001000010110100011000 × 2¹
  3. 指数计算1 + 1023 = 1024(二进制 10000000000
  4. 内存结构(十六进制):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;

}


六、总结

  1. IEEE 754是浮点数的通用标准,包含符号、指数、尾数三部分。
  2. 特殊值(如NaN、无穷)有明确编码规则,需在代码中正确处理。
  3. 精度问题不可避免,需用误差比较或整数放大法替代精确判断。
  4. 内存对齐和字节序直接影响数据可靠性,尤其在跨平台通信中。
  5. 性能敏感场景优先使用硬件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类型
posted @ 2025-04-11 19:19  故渊Y  阅读(138)  评论(0)    收藏  举报