这一章在干嘛?

把 C 里的「数据」盘一遍:类型家族(多大、怎么表示)、声明怎么写、常量怎么定义,以及最有分量的部分—— 作用域、链接属性、存储类型 。这三者共同决定一个变量「在哪儿可见、活了多久」。

3.1 基本数据类型:整型家族与浮点

3.2 声明与初始化

3.3 typedef 与常量

3.4 作用域、链接属性、存储类型

3.1 基本数据类型:整型家族与浮点

C 只直接定义了寥寥几种基本类型,其余都是它们的变化与组合:

类型典型大小要点
char1 字节本质是小整数,signed/unsigned 由实现决定
short / int / long / long long≥ 前者标准只保证下限和相对大小,具体位数看平台;limits.h 里有答案
float / double / long double4 / 8 / ≥8 字节字面量 3.14 默认是 double,想给 float 用 3.14f
指针同宽(如 8 字节)存的是地址,类型决定「解引用时读几个字节、怎么解释」

整型字面量的暗号:

10 是 int, 10L 是 long, 10U 是 unsigned, 010 是八进制, 0x10 是十六进制。溢出行为:有符号溢出是未定义,无符号溢出是回绕(模 2 N )——这是两族最重要的行为差异。

3.2 声明与初始化

声明的读法:从标识符出发,按优先级结合类型说明符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') */

最高频的坑:

局部变量不初始化就读——值是垃圾。声明即初始化(指针一律先置 NULL),能消掉一大半诡异 bug。

3.3 typedef 与常量

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 章细讲两者差异)。

3.4 作用域、链接属性、存储类型

这是本章的压轴三件套,决定一个名字「哪里可见、是否跨文件共享、活多久」:

维度取值含义
作用域代码块{} 之内可见,出了就没了
文件static 全局:本翻译单元私有
原型 / 函数只存在于函数原型参数表 / 只对 goto 标签有效
链接属性external全程序共享,跨文件同名指向同一实体(默认的全局函数/变量)
internalstatic 修饰:仅本单元可见,不同文件同名互不相干
none局部变量:无链接一说
存储类型static静态区,程序全程存活,只初始化一次
automatic栈上,进块创建出块销毁(默认)
register提示放寄存器;不可取地址
static int counter = 0;        /* 文件作用域 + internal 链接 + 静态存储 */

void tick(void)
{
    static int calls;          /* 只初始化一次,函数间保留 */
    calls++;
    int local = 0;             /* 每次进函数都重新来,栈上 */
    (void)local;
}

static 的两种用法一网打尽:

修饰全局变量/函数 → 收窄为文件私有(internal 链接,防止命名污染);修饰局部变量 → 改存静态区、寿命延长到整个程序。本质不同,记场景就行。