1. 关键字与数据类型.md 8.7 KB

1 关键字与数据类型

1.1 完整概念讲解

C 语言的数据类型系统是所有后续知识的基础。理解关键字、类型大小、符号性以及类型转换规则,是写出正确、可移植代码的前提。

关键字全表

C90(ANSI C)定义了 32 个关键字:

auto        break       case        char        const       continue
default     do          double      else        enum        extern
float       for         goto        if          inline      int
long        register    return      short       signed      sizeof
static      struct      switch      typedef     union       unsigned
void        volatile    while

C99 新增:

_Alignas    _Alignof    _Atomic    _Bool    _Complex    _Generic
_Imaginary  _Noreturn   _Static_assert  _Thread_local

C11 新增:

_Alignas    _Alignof    _Atomic    _Bool    _Complex    _Generic
_Imaginary  _Noreturn   _Static_assert  _Thread_local

基本数据类型与 sizeof

类型 最小大小 典型大小(64 位系统)
char 1 字节 1 字节
short 2 字节 2 字节
int 2 字节 4 字节
long 4 字节 4 或 8 字节
long long 8 字节 8 字节
float 4 字节 4 字节
double 8 字节 8 字节
long double 8 字节 16 字节

有符号与无符号

  • 默认类型为 signedchar 除外,其符号性由编译器决定)
  • 无符号数永远不会为负,但溢出时回绕到最大值
  • 混合使用有符号与无符号进行比较是常见 bug 来源

整型提升(Integer Promotion)

在表达式中,比 int 小的类型(charshort)会被隐式提升为 int。这是 C 语言设计的一个重要规则。

隐式类型转换

当不同类型混合运算时,遵循usual arithmetic conversions:较小类型向较大类型转换,有符号向无符号转换(当无符号类型秩 ≥ 有符号类型时)。

固定宽度类型 <stdint.h>

C99 引入 <stdint.h>,提供平台无关的固定宽度整数类型:

  • int8_t / uint8_t:精确 8 位
  • int16_t / uint16_t:精确 16 位
  • int32_t / uint32_t:精确 32 位
  • int64_t / uint64_t:精确 64 位
  • intmax_t / uintmax_t:最大宽度
  • intptr_t / uintptr_t:指针可安全存储的整数

1.2 核心 API/语法

sizeof 运算符

sizeof(type)      // 返回类型所占字节数
sizeof expression  // 返回表达式结果类型的大小

固定宽度类型头文件

#include <stdint.h>
#include <inttypes.h>   // 提供 PRId64 等格式化宏

类型限定符

const     // 只读
volatile  // 禁止编译器优化(硬件寄存器、信号处理)
restrict  // C99,指针别名优化(仅用于指针)

1.3 代码示例

示例 1:关键字与数据类型大小

#include <stdio.h>

int main(void) {
    printf("=== C 基本数据类型大小 ===\n");
    printf("char:      %zu 字节\n", sizeof(char));
    printf("short:     %zu 字节\n", sizeof(short));
    printf("int:       %zu 字节\n", sizeof(int));
    printf("long:      %zu 字节\n", sizeof(long));
    printf("long long: %zu 字节\n", sizeof(long long));
    printf("float:     %zu 字节\n", sizeof(float));
    printf("double:    %zu 字节\n", sizeof(double));
    printf("long double: %zu 字节\n", sizeof(long double));

    printf("\n=== 有符号与无符号范围 ===\n");
    printf("signed char:   [%d, %d]\n",
           (int)((signed char)-128),
           (int)((signed char)127));
    printf("unsigned char: [0, %u]\n",
           (unsigned int)((unsigned char)255));
    printf("signed int:    [%d, %d]\n", INT_MIN, INT_MAX);
    printf("unsigned int:  [0, %u]\n", UINT_MAX);

    return 0;
}

编译命令:

gcc -std=c99 -Wall -Wextra -o data_types data_types.c
./data_types

示例 2:整型提升与隐式转换

#include <stdio.h>

int main(void)
{
    const char a = 100;
    const char b = 50;
    const char c = a + b; /* 整型提升:a+b 计算时先提升为 int,结果截断回 char */

    printf("char a = %d, char b = %d\n", a, b);
    printf("a + b (int) = %d\n", a + b);
    printf("c = a + b (char) = %d  (溢出!\n", c);

    /* 有符号与无符号混合比较 */
    const int signed_val = -1;
    const unsigned int unsigned_val = 1;

    printf("\n signed -1 < unsigned 1 ? %s\n",
           signed_val < unsigned_val ? "Yes" : "No");
    /* 结果是 No!因为 有符号向无符号转换, 即 -1 被转换为 unsigned 的 UINT_MAX */

    return 0;
}

编译命令:

gcc -std=c99 -Wall -Wextra -o promotion promotion.c
./promotion

示例 3:固定宽度类型 <stdint.h>

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int main(void) {
    int8_t   i8  = -128;
    uint8_t  u8  = 255;
    int16_t  i16 = -32768;
    uint16_t u16 = 65535;
    int32_t  i32 = -2147483648;
    uint32_t u32 = 4294967295U;
    int64_t  i64 = -9223372036854775807LL - 1;
    uint64_t u64 = 18446744073709551615ULL;

    printf("=== 固定宽度整数类型 ===\n");
    printf("int8_t:   %" PRId8 "   (size: %zu)\n", i8, sizeof(i8));
    printf("uint8_t:  %" PRIu8 "   (size: %zu)\n", u8, sizeof(u8));
    printf("int16_t:  %" PRId16 " (size: %zu)\n", i16, sizeof(i16));
    printf("uint16_t: %" PRIu16 " (size: %zu)\n", u16, sizeof(u16));
    printf("int32_t:  %" PRId32 " (size: %zu)\n", i32, sizeof(i32));
    printf("uint32_t: %" PRIu32 " (size: %zu)\n", u32, sizeof(u32));
    printf("int64_t:  %" PRId64 " (size: %zu)\n", i64, sizeof(i64));
    printf("uint64_t: %" PRIu64 " (size: %zu)\n", u64, sizeof(u64));

    /* uintptr_t:存储指针的整数类型 */
    int x = 42;
    uintptr_t ptr_as_int = (uintptr_t)&x;
    printf("\nuintptr_t can hold a pointer: %p -> %" PRIuPTR "\n",
           (void *)&x, ptr_as_int);

    return 0;
}

编译命令:

gcc -std=c99 -Wall -Wextra -o stdint stdint_demo.c
./stdint

1.4 注意事项与易错点

  1. char 的符号性不确定char 在某些编译器上是有符号的,在另一些上是无符号的。如果需要明确符号性,使用 signed charunsigned char
  2. sizeof 不是函数sizeof 是运算符,对表达式求值时不执行表达式(除了 VLA)。
  3. 无符号数的陷阱

    unsigned int len = 0;
    if (len - 1 > 0) { ... }  // 永远为真!因为 0-1 回绕到 UINT_MAX
    
  4. 整型提升的隐蔽影响

    char c = 0xFF;
    unsigned char uc = 0xFF;
    printf("%d\n", c);   // -1(有符号提升)
    printf("%d\n", uc);  // 255(无符号提升)
    
  5. long long 至少 64 位:C 标准保证 long long 不小于 64 位,但 long 在不同平台上可能是 4 或 8 字节。

  6. <stdint.h> 不是所有平台都有:嵌入式平台可能不完整,检查编译器文档。


1.5 面试要点

Q1:charsigned charunsigned char 三者有什么区别?

A:char 的符号性由编译器/平台决定,可能等同于 signed charunsigned charsigned char 明确有符号(-128 到 127),unsigned char 明确无符号(0 到 255)。需要精确控制符号性时,显式使用 signedunsigned 修饰。

Q2:sizeof 的结果在不同平台上可能不同吗?

A:是的。int 在 16 位系统上可能是 2 字节,在 32/64 位系统上通常是 4 字节。long 在 Windows 64 位上是 4 字节,在 Linux 64 位上是 8 字节。使用 <stdint.h> 的固定宽度类型可以避免这种移植性问题。

Q3:什么是整型提升?为什么要这样设计?

A:C 语言在表达式计算时,charshort 会被隐式转换为 int(如果 int 能容纳所有值)。这是 C 语言早期设计决定——当时 CPU 运算以字(word)为单位最高效,提升到 int 可以统一运算宽度,简化编译器和硬件设计。

Q4:无符号数参与混合运算时会发生什么?

A:遵循 usual arithmetic conversions:如果无符号类型的秩 ≥ 有符号类型,有符号值会被转换为无符号。例如 int(-1) < unsigned int(1) 结果为 false,因为 -1 被转换为 UINT_MAX,远大于 1。

Q5:为什么要使用 <stdint.h> 而不是 intlong

A:intlong 在不同平台上的大小不固定。嵌入式、网络协议、文件格式等场景需要精确的位宽保证。int32_tuint64_t 等保证了精确的位宽,使代码可移植且意图明确。