C 只直接定义了寥寥几种基本类型,其余都是它们的变化与组合:
| 类型 | 典型大小 | 要点 |
|---|---|---|
| char | 1 字节 | 本质是小整数,signed/unsigned 由实现决定 |
| short / int / long / long long | ≥ 前者 | 标准只保证下限和相对大小,具体位数看平台;limits.h 里有答案 |
| float / double / long double | 4 / 8 / ≥8 字节 | 字面量 3.14 默认是 double,想给 float 用 3.14f |
| 指针 | 同宽(如 8 字节) | 存的是地址,类型决定「解引用时读几个字节、怎么解释」 |
10 是 int,10L 是 long,10U 是 unsigned,010 是八进制,0x10 是十六进制。溢出行为:有符号溢出是未定义,无符号溢出是回绕(模 2N)——这是两族最重要的行为差异。声明的读法:从标识符出发,按优先级结合类型说明符。int a; 是「a 是 int」,int a[10]; 是「a 是数组」,int *p; 是「*p 是 int,所以 p 是指向 int 的指针」。声明只分配「描述」,定义才分配内存——但对变量而言,带初始化或不带 extern 的声明同时也是定义:
int a = 10; /* 定义并初始化 */
int b; /* 定义:静态存储的 b 被零初始化,栈上的 b 是随机值! */
extern int c; /* 纯声明:告诉编译器 c 在别处定义 */
int arr[5] = {1, 2}; /* 不完整初始化:剩余元素全部补 0 */
char msg[] = "hi"; /* 数组大小自动算:3 字节(含结尾 '\0') */typedef 给已有类型起别名,语法和声明一模一样,只是把变量名换成新类型名:
typedef char *String; /* String 就是 char* */ typedef struct Node Node; /* 前置声明常用,配自引用结构 */ const int limit = 100; /* limit 不可修改(值常量) */ int const *p1; /* 指向 const int 的指针:*p1 不能写,p1 能改 */ int *const p2 = &a; /* const 指针:p2 不能改,*p2 能写 */ const int *const p3 = &a; /* 都不能改 */
记法:const 在 * 左边修饰「被指向的东西」,在 * 右边修饰「指针本身」。另外 #define 也能定义常量,但它是文本替换、没有类型、不进符号表——能用 const 就优先 const(第 14 章细讲两者差异)。
这是本章的压轴三件套,决定一个名字「哪里可见、是否跨文件共享、活多久」:
| 维度 | 取值 | 含义 |
|---|---|---|
| 作用域 | 代码块 | {} 之内可见,出了就没了 |
| 文件 | static 全局:本翻译单元私有 | |
| 原型 / 函数 | 只存在于函数原型参数表 / 只对 goto 标签有效 | |
| 链接属性 | external | 全程序共享,跨文件同名指向同一实体(默认的全局函数/变量) |
| internal | static 修饰:仅本单元可见,不同文件同名互不相干 | |
| none | 局部变量:无链接一说 | |
| 存储类型 | static | 静态区,程序全程存活,只初始化一次 |
| automatic | 栈上,进块创建出块销毁(默认) | |
| register | 提示放寄存器;不可取地址 |
static int counter = 0; /* 文件作用域 + internal 链接 + 静态存储 */
void tick(void)
{
static int calls; /* 只初始化一次,函数间保留 */
calls++;
int local = 0; /* 每次进函数都重新来,栈上 */
(void)local;
}