基本类型、对象、声明与初始化
本节目标
查询 C23 基本类型、对象与声明、初始化、const、类型范围和定宽整数的核心规则。
本章把常被混用的词拆开:类型描述一组值和操作,对象是一块有类型的存储,声明把名字及其含义告诉编译器,初始化为对象建立初始值。查询某条规则时,先判断问题属于哪一层。
基本类型家族
C 最基础的一组类型概念可以先按用途记忆:
| 家族 | 常见类型 | 查询重点 |
|---|---|---|
| 布尔 | bool | C23 可直接使用 bool;旧代码常见 <stdbool.h> |
| 字符 | char、signed char、unsigned char | char 是否有符号由实现决定;三者是不同类型 |
| 整数 | short、int、long、long long 及其无符号版本 | 标准规定最小范围和相对宽度,不固定每种类型的字节数 |
| 浮点 | float、double、long 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_MIN、INT_MAX。<float.h>提供浮点范围与精度,如DBL_MAX、DBL_DIG。- 除可变长度数组外,
sizeof的操作数通常不会被求值;不要借它隐藏需要执行的副作用。
比较跨平台结果时,应记录“范围”和“位宽”而不是把某台机器的 sizeof(int) == 4 写成语言保证。
定宽整数
<stdint.h> 在实现能够提供对应宽度时定义 int8_t、int32_t、uint64_t 等精确宽度类型;这些名字不是所有实现都必须提供。至少宽度类型 int_least32_t 和快速类型 int_fast32_t 解决的是不同约束。
常量和格式说明符也要配套:
int32_t value = INT32_C(2026);
printf("%" PRId32 "\n", value);
不要假定 int32_t 一定等同于 int,也不要固定使用 %d。需要与文件格式、网络协议或硬件寄存器对齐时,才优先选择精确宽度类型;普通计数可先选择自然的 int 或 size_t。
#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 char 或 unsigned char |
假定所有平台 int 都是 32 位 | 查询 <limits.h>,协议字段用可用的定宽类型 |
| 有符号整数溢出 | 先做边界检查;有符号溢出是未定义行为 |
| 把无符号回绕当成通用错误恢复 | 无符号运算按模回绕,但仍可能是业务缺陷 |
把 const 当成编译期常量 | 分开判断“不可通过该左值修改”和“常量表达式” |
遇到类型问题时,按“对象实际类型 → 表达式转换后的类型 → API 期望类型 → 输出格式”逐层核对,比只盯着声明的一行更可靠。
如果编译命令或完整程序结构还不熟悉,先回到入门导览与第一个程序。