第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 有关,但类型不一样。
先看 int 和 char。它们都写了 65,但程序取值时采用的读法不同。
int x = 65;
程序把 x 当整数看。
char c = 65;
程序把 c 当字符看。按照 ASCII,65 对应 A。
float f = 65;
这一行还多了一步:右边的整数常量 65 会先转换成浮点值 65.0,然后再保存到 float 变量里。
浮点数在内存里有自己的编码方式,不是把整数 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
第一行打印 x。x 是 int,用 %d 按整数打印。
第二行打印 c。c 是 char,用 %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
不同机器、编译器、系统上,结果可能略有差异。运行结果会告诉你当前环境里的大小。
这段程序会看到两个现象:
- 不同类型可能占不同字节数。
- 变量确实有地址,
&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 不同类型适合存什么#
现在见过几个类型了:
charintfloatdouble
这几个类型已经够看出差别:有的更适合整数,有的更适合小数,有的更适合字符。
char 常用来表示一个字符,也可以看成一个很小的整数。
char c = 'A'; // 用单引号括起来的字符字面量
int 常用来表示整数。
int age = 18; // 年龄,用 int 存整数
int count = 100; // 计数,也用 int
float 和 double 常用来表示带小数的数。
float score = 86.5f; // 单精度小数,后缀 f 表示 float
double distance = 12.75; // 双精度小数,默认就是 double
float 后面的 f 是在告诉 C:这是一个 float 小数,不是默认的 double 小数。
这张表可以放在手边:
| 类型 | 先怎么理解 | 例子 |
|---|---|---|
char | 一个字符,或者一个很小的整数 | 'A' |
int | 整数 | 65、1001 |
float | 单精度小数 | 86.5f |
double | 更常用的小数类型 | 3.14159 |
选类型时,先看数据本身是什么。
年龄、人数、次数,通常是整数。
分数、距离、温度,常常需要小数。
一个字母,可以用 char。
类型不是装饰。它会影响这块内存占多大、怎么解释、怎么打印。
2.8 声明和初始化#
写变量时,常见有两步:
int x; // 声明:告诉 C 有一个叫 x 的 int 变量
x = 65; // 赋值:把 65 放进 x 对应的那块内存
第一行叫声明:告诉 C,有一个叫 x 的 int 变量。
第二行叫赋值:把 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 的地址告诉 scanf。scanf 需要知道"把读到的数字写到内存的哪一格",所以必须给地址。printf 只是读取变量的值,不需要地址;scanf 要往里写,必须给地址。
从第 1 章的"格子编号"就能理解:&x 就是 x 那一格的门牌号。
2.10 一条记录里的不同数据#
一条学生信息里可能会同时有:
| 字段 | 值 |
|---|---|
id | 1001 |
score | 86.5 |
name | Alice |
看这些数据本身:
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 |
下一章的问题#
现在已经知道:
- 变量给内存位置起名字。
- 类型告诉程序怎么解释那块内存。
接下来会遇到一个新问题:
- 如果
score >= 60,打印pass。 - 否则,打印
fail。
程序怎么根据条件走不同的路?
程序需要根据条件选择不同路径,也需要重复处理一批输入。判断和循环就是这两个需求对应的基本工具。