跳到主要内容

基本类型、对象、声明与初始化

本节目标

查询 C23 基本类型、对象与声明、初始化、const、类型范围和定宽整数的核心规则。

本章把常被混用的词拆开:类型描述一组值和操作,对象是一块有类型的存储,声明把名字及其含义告诉编译器,初始化为对象建立初始值。查询某条规则时,先判断问题属于哪一层。

基本类型家族

C 最基础的一组类型概念可以先按用途记忆:

家族常见类型查询重点
布尔boolC23 可直接使用 bool;旧代码常见 <stdbool.h>
字符charsigned charunsigned charchar 是否有符号由实现决定;三者是不同类型
整数shortintlonglong long 及其无符号版本标准规定最小范围和相对宽度,不固定每种类型的字节数
浮点floatdoublelong double精度和范围查询 <float.h>
空类型void表示无值;是不能完成的不完整类型,不能定义 void 对象

sizeof(char) 永远是 1,但一个 C 字节含多少位由 CHAR_BIT 给出,不应在可移植代码中直接假定为 8。

对象、名字与声明

extern int total; // 声明 total;定义可能在别处
int total = 0; // 定义对象,同时完成初始化
double average(int n); // 声明函数

“变量”是日常说法;标准语境里的对象强调存储、类型和生命周期。一个声明不一定创建对象:extern int total; 通常只承诺这个名字对应某个 int 对象,而带初始化器的 int total = 0; 是定义。

标识符要在其作用域内唯一地表达意图。类型写在声明中,编译器据此检查表达式、调用约定和格式化输入输出是否匹配。

初始化

int count = 0;
double ratio = 1.0;
int values[4] = {1, 2}; // 其余元素初始化为 0

定义对象时优先立即初始化。未显式初始化的静态存储期对象会按规则初始化为零值;未初始化的自动对象,其对象表示不确定,读取它可能触发未定义行为,不能把“当前碰巧是 0”当成规则。

花括号初始化适合数组、结构体和联合体。对数组和结构体,未显式列出的元素或成员按默认初始化规则处理;联合体一次只初始化所选成员。初始化发生在对象生命周期开始时,后续的 count = 3; 是赋值,不是再次初始化。

const 对象

const size_t capacity = 64;

const 表示不能通过该限定类型的左值修改对象。它有助于表达接口意图,但不自动等同于编译期常量,也不保证对象一定放在只读存储区。

指针声明要分清限定的是谁:

const int *read_only_view; // 不能通过该指针修改所指 int
int *const fixed_pointer = 0; // 指针自身不能改指向

通过强制转换去修改一个原本就定义为 const 的对象会导致未定义行为。

大小与范围

  • sizeof(T)sizeof expression 的结果类型是 size_t,用 %zu 输出。
  • <limits.h> 提供整数范围和 CHAR_BIT,如 INT_MININT_MAX
  • <float.h> 提供浮点范围与精度,如 DBL_MAXDBL_DIG
  • 除可变长度数组外,sizeof 的操作数通常不会被求值;不要借它隐藏需要执行的副作用。

比较跨平台结果时,应记录“范围”和“位宽”而不是把某台机器的 sizeof(int) == 4 写成语言保证。

定宽整数

<stdint.h> 在实现能够提供对应宽度时定义 int8_tint32_tuint64_t 等精确宽度类型;这些名字不是所有实现都必须提供。至少宽度类型 int_least32_t 和快速类型 int_fast32_t 解决的是不同约束。

常量和格式说明符也要配套:

int32_t value = INT32_C(2026);
printf("%" PRId32 "\n", value);

不要假定 int32_t 一定等同于 int,也不要固定使用 %d。需要与文件格式、网络协议或硬件寄存器对齐时,才优先选择精确宽度类型;普通计数可先选择自然的 intsize_t

type_report.c
#include <inttypes.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>

int main(void) {
int count = 42;
double ratio = 0.75;
char grade = 'A';
const size_t capacity = 64;
int32_t exact = INT32_C(2026);

printf("count=%d\n", count);
printf("ratio=%.2f\n", ratio);
printf("grade=%c\n", grade);
printf("capacity=%zu\n", capacity);
printf("exact=%" PRId32 "\n", exact);
printf("int-width=%zu\n", sizeof(int) * CHAR_BIT);
return 0;
}

常见陷阱

陷阱规则或替代做法
读取未初始化自动对象定义时初始化;不要依赖调试环境留下的位模式
printf 类型与格式不匹配size_t%zu;定宽整数用 <inttypes.h>
假定 char 总是有符号或无符号需要明确语义时写 signed charunsigned char
假定所有平台 int 都是 32 位查询 <limits.h>,协议字段用可用的定宽类型
有符号整数溢出先做边界检查;有符号溢出是未定义行为
把无符号回绕当成通用错误恢复无符号运算按模回绕,但仍可能是业务缺陷
const 当成编译期常量分开判断“不可通过该左值修改”和“常量表达式”

遇到类型问题时,按“对象实际类型 → 表达式转换后的类型 → API 期望类型 → 输出格式”逐层核对,比只盯着声明的一行更可靠。

如果编译命令或完整程序结构还不熟悉,先回到入门导览与第一个程序