第2章 变量和类型

2.1 上一章留下的问题#

上一章说过,内存格子里存的是数字。

比如一个格子里放着:

65

按整数看,它就是数字 65

按 ASCII 字符看,它又是字母 A

那程序怎么知道该用哪种读法?

这就是变量和类型要解决的问题。


2.2 变量:给内存格子起名字#

如果每次都说“去地址 0x1000 那个格子取数据”,写程序会很麻烦。

所以 C 语言允许给一块内存起名字:

int x = 65;     // 声明一个 int 变量,初始化为 65

可以先这样看:

变量名指向一块内存位置

x 是名字。

65 是放进去的数据。

int 告诉程序:这块内存里的数据,先按整数来解释。

变量不是凭空存在的。它背后还是内存,只是程序员不用每次都直接写地址。


2.3 类型:告诉程序怎么解释这块内存#

看三行很像的代码:

int x = 65;     // int:把 65 当整数看
char c = 65;    // char:把 65 当字符看(ASCII 65 = 'A')
float f = 65;   // float:把 65 转成浮点数 65.0

它们都和数字 65 有关,但类型不一样。

先看 intchar。它们都写了 65,但程序取值时采用的读法不同。

同一个 65 在 int 和 char 中的读法不同

int x = 65;

程序把 x 当整数看。

char c = 65;

程序把 c 当字符看。按照 ASCII,65 对应 A

float f = 65;

这一行还多了一步:右边的整数常量 65 会先转换成浮点值 65.0,然后再保存到 float 变量里。

整数常量赋给 float 前会先转换成 65.0

浮点数在内存里有自己的编码方式,不是把整数 65 的那几个字节直接照搬过去。

所以类型不只是变量名旁边的装饰。更准确地说:

类型给了程序一种读法。


2.4 第一段小代码#

写一个最小实验,看一眼结果。

#include <stdio.h>

int main(void)
{
    int x = 65;             // int 变量,存整数 65
    char c = 65;            // char 变量,存同样的数字 65

    printf("%d\n", x);      // 按整数格式打印 x → 65
    printf("%c\n", c);      // 按字符格式打印 c → A

    return 0;
}

保存成 main.c

编译:

$ gcc main.c -o main

运行后大概会看到:

65
A

第一行打印 xxint,用 %d 按整数打印。

第二行打印 ccchar,用 %c 按字符打印。

这段代码不长,但它已经把上一章的问题跑出来了:

同样和 65 有关,类型不同,读法就不同。


2.5 sizeof:看看类型占多少字节#

不同类型通常会占不同大小的内存。

C 语言里可以用 sizeof 看大小:

#include <stdio.h>

int main(void)
{
    int x = 65;

    printf("char:   %zu\n", sizeof(char));     // char 占多少字节
    printf("int:    %zu\n", sizeof(int));      // int 占多少字节
    printf("float:  %zu\n", sizeof(float));    // float 占多少字节
    printf("double: %zu\n", sizeof(double));   // double 占多少字节

    printf("x address: %p\n", (void *)&x);     // 打印 x 的内存地址

    return 0;
}

常见结果类似这样:

char:   1
int:    4
float:  4
double: 8
x address: 0000005A1B7FF6CC

不同机器、编译器、系统上,结果可能略有差异。运行结果会告诉你当前环境里的大小。

这段程序会看到两个现象:

  1. 不同类型可能占不同字节数。
  2. 变量确实有地址,&x 可以拿到 x 所在的位置。

常见类型占用字节数

这里的 &x 表示“取出 x 的内存地址”。


2.6 格式化输出不是类型本身#

printf 里的 %d%c%f 很容易让人误会。

它们不是变量的类型。它们是 printf 读参数时使用的格式要求。

变量、格式符、输出结果的关系

比如:

int x = 65;             // x 是 int 类型
char c = 65;            // c 是 char 类型

printf("%d\n", x);      // %d 按整数格式打印 x
printf("%c\n", c);      // %c 按字符格式打印 c

x 的类型是 int

c 的类型是 char

%d%c 是告诉 printf:请按什么样子打印出来。

格式符和数据类型要配套。格式符对不上,轻则输出奇怪结果,重则程序行为不可预测。


2.7 不同类型适合存什么#

现在见过几个类型了:

  • char
  • int
  • float
  • double

这几个类型已经够看出差别:有的更适合整数,有的更适合小数,有的更适合字符。

char 常用来表示一个字符,也可以看成一个很小的整数。

char c = 'A';           // 用单引号括起来的字符字面量

int 常用来表示整数。

int age = 18;           // 年龄,用 int 存整数
int count = 100;        // 计数,也用 int

floatdouble 常用来表示带小数的数。

float score = 86.5f;        // 单精度小数,后缀 f 表示 float
double distance = 12.75;    // 双精度小数,默认就是 double

float 后面的 f 是在告诉 C:这是一个 float 小数,不是默认的 double 小数。

这张表可以放在手边:

类型先怎么理解例子
char一个字符,或者一个很小的整数'A'
int整数651001
float单精度小数86.5f
double更常用的小数类型3.14159

选类型时,先看数据本身是什么。

年龄、人数、次数,通常是整数。

分数、距离、温度,常常需要小数。

一个字母,可以用 char

类型不是装饰。它会影响这块内存占多大、怎么解释、怎么打印。


2.8 声明和初始化#

写变量时,常见有两步:

int x;      // 声明:告诉 C 有一个叫 x 的 int 变量
x = 65;     // 赋值:把 65 放进 x 对应的那块内存

第一行叫声明:告诉 C,有一个叫 xint 变量。

第二行叫赋值:把 65 放进去。

也可以合在一起写:

int x = 65;     // 声明并初始化:一步完成

这叫初始化:变量刚创建时,就给它一个值。

写变量时有一个很稳的习惯:

变量创建出来,尽快给它一个明确的值。

因为一个没有初始化的局部变量,里面可能是旧的、不可预测的内容。比如:

int x;                  // 声明但没有初始化,x 里的值是未知的
printf("%d\n", x);      // 打印未知值,结果不可预测——这是错误写法

这段代码不是正常写法。它提醒你:变量不是自动“干净”的盒子。你没有放进去一个可靠的值,就不要直接读它。


2.9 看一眼地址#

变量有名字,也有位置。

int x = 65;

printf("%p\n", (void *)&x);     // &x 取出 x 的地址,%p 按地址格式打印

&x 可以读成“x 的地址”。

输出通常长得像这样:

0000005A1B7FF6CC

每次运行、每台机器都可能不一样。地址不是让你背的,它只是说明:

变量背后确实对应内存里的某个位置。

这一章只用它说明变量确实有位置,不展开地址运算。

地址和 & 的用途#

这一章用 &x 拿到了变量的地址。这个 & 用来取得变量在内存中的位置。

scanf("%d", &score) 这样的调用里,&score 就是把 score 的地址告诉 scanfscanf 需要知道"把读到的数字写到内存的哪一格",所以必须给地址。printf 只是读取变量的值,不需要地址;scanf 要往里写,必须给地址。

从第 1 章的"格子编号"就能理解:&x 就是 x 那一格的门牌号。


2.10 一条记录里的不同数据#

一条学生信息里可能会同时有:

字段
id1001
score86.5
nameAlice

看这些数据本身:

  • id 更像整数。
  • score 可能需要小数。
  • name 是一串字符。

一条信息里常常会放着不同类型的数据。变量和类型不是孤立语法,它们会直接影响记录该怎么组织。


2.11 常见误区#

误区 1:以为变量名就是内存地址。

变量名是给人和编译器看的名字。真正运行时,变量会放在某个地址上;每次运行的地址还可能不同。

误区 2:以为 char 只能存字母。

char 本质上也是一个很小的整数类型。char c = 65; 可以按字符打印成 A,也可以按整数打印成 65

误区 3:格式符写错也没关系。

printf 的格式符要和数据类型匹配。用 %d 打印 float,或者用 %f 打印 int,输出就不可信。


2.12 自己试试看#

Q1:把 65 改成 66

int x = 66;     // 换成 66,ASCII 66 对应字符 'B'
char c = 66;    // char 也换成 66

再运行,看看第二行是不是变成了 B

Q2:把 char c = 65; 改成 char c = 'A';

再分别用 %c%d 打印它。

你会看到:字符也可以看成数字。

Q3:运行 sizeof 程序。

记录你的机器上这些类型占多少字节:

类型我的机器上占多少字节
char
int
float
double

Q4:连续运行两次打印地址的程序。

看看地址是不是完全一样。

如果不一样,不要紧。操作系统每次运行程序时,可能把变量放到不同位置。

Q5:给下面这些数据选一个合适的类型。

不用追求唯一答案,先说出你的理由。

数据你会先选什么类型
年龄 18
分数 86.5
字母 A
计数器 count

下一章的问题#

现在已经知道:

  • 变量给内存位置起名字。
  • 类型告诉程序怎么解释那块内存。

接下来会遇到一个新问题:

  1. 如果 score >= 60,打印 pass
  2. 否则,打印 fail

程序怎么根据条件走不同的路?

程序需要根据条件选择不同路径,也需要重复处理一批输入。判断和循环就是这两个需求对应的基本工具。