变量、常量与输入输出:printf 和 scanf 详解
一、本篇文章要解决什么问题
前面两篇你搭好了环境、了解了数据类型,接下来要面对的问题是:数据怎么存、怎么显示、怎么让用户输入?
这篇文章就帮你搞定三件事:
- 变量和常量到底是什么关系,const 有什么用?
printf怎么控制输出格式------宽度、小数位、对齐?scanf怎么安全地读取用户输入?那个&符号到底什么意思?
学完这篇,你就能写出真正"能和用户对话"的程序------既能接收输入,也能把结果漂漂亮亮地打印出来。
二、先用一个简单例子理解
2.1 变量就是"贴标签的盒子"
想象你去超市存包。你把包放进一个储物柜,拿到一张小票,小票上印着柜子编号。以后凭这张小票就能找到你的包。
C 语言里的变量就是这个意思:
- 储物柜 = 内存中的一块空间
- 小票上的编号 = 变量的名字(比如
age、score) - 包里的东西 = 变量存的数据
- 柜子的大小 = 变量的类型(
int占 4 字节,double占 8 字节)
2.2 printf 和 scanf 就像前台接待
printf 好比你把一张纸条递给前台,前台照着纸条上的内容大声念出来。
scanf 好比前台问你一个问题,你口头回答,前台把你的话记下来,存到指定的柜子里。
这就是 C 语言里最基本的"输入输出"模型。下面的章节会把这套流程拆开讲清楚。
三、核心知识点讲解
3.1 变量的定义与初始化
在 C 语言里用变量之前,必须先"声明"它。声明要做两件事:告诉编译器变量叫什么名字、是什么类型。
c
int age; // 声明一个 int 类型的变量,尚未初始化
age = 20; // 赋值
int score = 95; // 声明的同时初始化------推荐写法
什么叫"初始化"? 就是给变量赋第一个值。如果只声明不初始化,局部变量的值是不确定的------里面可能残留上一次用这块内存时留下的随机数据。
错误示例(不要复制运行,仅用于理解概念):
c
#include <stdio.h>
int main(void)
{
int x; // 未初始化,x 的值是随机的
// printf("x = %d\n", x); // 读取未初始化的局部变量属于未定义行为!
return 0;
}
读取未初始化局部变量的值属于未定义行为 ------C 语言标准没有规定这种情况下应该怎么做,不同编译器、不同运行环境可能表现出不同的结果(可能输出随机值、可能崩溃、也可能看起来"正常")。因此永远不要依赖未初始化变量的值。
建议 :声明变量时尽量同时初始化。如果暂时不知道赋什么值,可以先用0或其他有意义的默认值。

图3-1 变量与内存关系图:帮助初学者建立"变量 = 命名内存"的核心概念。
3.2 常量------"不能改的变量"
有些数据在程序运行期间不应该被修改。比如圆周率 π = 3.14159,一天的秒数 = 86400。这些数据适合定义为常量。
C 语言有两种方式定义常量:
方式一:const 关键字
c
const double PI = 3.14159;
PI = 3.14; // 编译错误!const 变量不能被修改
方式二:#define 宏常量
c
#define PI 3.14159
#define SECONDS_PER_DAY 86400
const 和 #define 的区别:
const定义的常量有类型,编译器可以做类型检查,可以取地址#define是预处理替换,不占内存空间,但没有类型信息- 宏常量(
#define)常用于定义数组长度、编译期开关等场景;const更适合表示有明确类型、希望受到编译器类型检查的常量。初学阶段两者都会用到,关键是理解它们各自的工作方式
3.3 printf 的基本用法
printf 是 C 语言里最常用的输出函数,来自 stdio.h。它的基本格式是:
c
printf("格式字符串", 变量1, 变量2, ...);
格式字符串里用 % 开头的格式占位符来表示"这里要被后面的变量替换"。
最常用的格式占位符:
| 占位符 | 对应类型 | 含义 |
|---|---|---|
%d |
int |
有符号十进制整数 |
%u |
unsigned int |
无符号十进制整数 |
%lld |
long long |
长长整型 |
%f |
float / double |
浮点数,默认 6 位小数 |
%lf |
double |
浮点数(printf 中 %f 也兼容 double) |
%c |
char |
单个字符 |
%s |
char[] / char* |
字符串 |
%p |
指针 | 内存地址(十六进制) |
%% |
--- | 输出一个 % 符号本身 |
代码示例------各种占位符一次看全:
c
#include <stdio.h>
int main(void)
{
int age = 20;
double height = 175.5;
char grade = 'A';
char name[] = "Tom";
printf("姓名:%s\n", name);
printf("年龄:%d 岁\n", age);
printf("身高:%.1f cm\n", height);
printf("成绩等级:%c\n", grade);
printf("完成度:%d%%\n", 100); // 输出 100%,%% 表示一个 %
return 0;
}
运行结果:
text
姓名:Tom
年龄:20 岁
身高:175.5 cm
成绩等级:A
完成度:100%
3.4 printf 格式控制:宽度、小数位、对齐
很多时候你不仅需要输出数据,还需要控制输出的格式。printf 的占位符可以加修饰符来精确控制。
控制小数位数:
c
double pi = 3.14159265;
printf("%.2f\n", pi); // 输出 3.14(保留 2 位小数)
printf("%.5f\n", pi); // 输出 3.14159(保留 5 位小数)
控制输出宽度(常用于对齐表格):
c
printf("%5d\n", 42); // 输出 " 42"(总宽度 5,右对齐,前面补空格)
printf("%-5d\n", 42); // 输出 "42 "(加负号,左对齐)
printf("%05d\n", 42); // 输出 "00042"(宽度 5,前面补 0)
综合演示------打印对齐表格:
c
#include <stdio.h>
int main(void)
{
printf("%-10s %5s %8s\n", "姓名", "年龄", "成绩");
printf("-------------------------\n");
printf("%-10s %5d %8.1f\n", "Tom", 20, 92.5);
printf("%-10s %5d %8.1f\n", "Alice", 22, 88.0);
printf("%-10s %5d %8.1f\n", "Bob", 19, 76.5);
return 0;
}
运行结果:
text
姓名 年龄 成绩
-------------------------
Tom 20 92.5
Alice 22 88.0
Bob 19 76.5
%-10s:字符串左对齐,占 10 个字符宽%5d:整数右对齐,占 5 个字符宽%8.1f:浮点数右对齐,占 8 个字符宽,保留 1 位小数

图3-2 printf 格式控制效果图:让读者直观看到每种格式控制符对输出的影响。
3.5 scanf 的基本用法------为什么需要 &
scanf 和 printf 刚好相反:printf 把数据从程序里送出去,scanf 把数据从键盘读进来。
c
int age;
scanf("%d", &age); // 注意:age 前面必须加 &
为什么 scanf 的变量前要加 &?
回顾上一篇讲指针时的概念:& 是"取地址"运算符。scanf 需要知道"把读到的数据存到哪里 ",所以你必须把变量的地址传给 scanf,而不是把变量的值传给它。
用图书馆的类比:scanf 是"送书员",你告诉它柜子的编号(地址),它才能把书放进正确的柜子里。如果你只告诉它"柜子里现在有什么",送书员根本不知道该把新书放哪。
c
// 正确:把 age 的地址传给 scanf
scanf("%d", &age);
// 错误:传的是 age 的值(20),scanf 把这个值当成地址来写------会崩溃
scanf("%d", age);
各种类型的 scanf 写法:
| 数据类型 | scanf 占位符 | 示例 |
|---|---|---|
int |
%d |
scanf("%d", &age); |
double |
%lf |
scanf("%lf", &score); |
float |
%f |
scanf("%f", &height); |
char |
%c |
scanf(" %c", &grade); 注意 %c 前加空格 |
char[](字符串) |
%s |
scanf("%19s", name); 不加 &,但要限制长度 |
特别注意
%c:scanf("%c", &ch)会读取缓冲区中残留的换行符。解决办法是在%c前面加一个空格:scanf(" %c", &ch)。这个空格会跳过所有空白字符(包括换行符)。

图3-3 scanf 读取流程图:帮读者理解 scanf 的工作机制和缓冲区的存在。
3.6 输入缓冲区的初步认识
当你在键盘上打字并按下回车时,你输入的所有字符(包括最后的回车 \n)会先被放进一个叫输入缓冲区 的临时区域,scanf 再从这个缓冲区里按格式读取数据。
这就产生了一个经典问题------残留的换行符:
c
#include <stdio.h>
int main(void)
{
int age;
char grade;
printf("请输入年龄:");
scanf("%d", &age); // 输入 20,然后按回车
// 缓冲区里还剩一个 '\n'
printf("请输入等级:");
scanf("%c", &grade); // grade 读到了那个残留的 '\n'!
// 程序不等你输入,直接跳过去了
printf("年龄:%d,等级:%c\n", age, grade);
return 0;
}
运行结果(用户没有机会输入等级):
text
请输入年龄:20
请输入等级:年龄:20,等级:
解决方案:
c
scanf(" %c", &grade); // %c 前加空格,跳过残留的空白字符
这个问题非常常见,很多初学者会卡在这里。你只要知道"%c 前面加空格"这个技巧,就不会被坑了。

图3-4 变量、常量、宏对比表:让读者分清三种"存数据"方式的区别和适用场景。
四、完整代码示例
下面这个程序把变量定义、常量、printf 格式化、scanf 输入这些知识点串在一起,模拟一个简单的"学生信息录入与显示"场景:
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#define MAX_NAME_LEN 20
#define PASS_SCORE 60
int main(void)
{
// 变量声明与初始化
char name[MAX_NAME_LEN];
int age;
double score;
char grade;
// 用户输入
printf("===== 学生信息录入 =====\n");
printf("请输入姓名:");
scanf("%19s", name); // 限制输入长度,防止溢出
printf("请输入年龄:");
scanf("%d", &age);
printf("请输入成绩:");
scanf("%lf", &score); // double 用 %lf
printf("请输入等级(A/B/C/D):");
scanf(" %c", &grade); // %c 前加空格,跳过残留换行符
// 输出结果
printf("\n===== 学生信息卡 =====\n");
printf("%-12s: %s\n", "姓名", name);
printf("%-12s: %d 岁\n", "年龄", age);
printf("%-12s: %.1f 分\n", "成绩", score);
printf("%-12s: %c\n", "等级", grade);
// 判断是否及格
if (score >= PASS_SCORE)
{
printf("%-12s: 已及格(>= %d 分)\n", "状态", PASS_SCORE);
}
else
{
printf("%-12s: 未及格(< %d 分)\n", "状态", PASS_SCORE);
}
return 0;
}
五、运行结果
text
===== 学生信息录入 =====
请输入姓名:Tom
请输入年龄:20
请输入成绩:85.5
请输入等级(A/B/C/D):A
===== 学生信息卡 =====
姓名 : Tom
年龄 : 20 岁
成绩 : 85.5 分
等级 : A
状态 : 已及格(>= 60 分)
六、代码逐行解析
第一部分:预处理和常量定义
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#define MAX_NAME_LEN 20
#define PASS_SCORE 60
- 第一行屏蔽 Visual Studio 的 scanf 安全警告
- 第二行引入标准输入输出头文件
- 第三、四行用
#define定义了两个宏常量:名字最大长度和及格分数线。好处:以后如果要改及格线为 70 分,只需改这一个地方,不用在代码里到处找"60"
第二部分:变量声明
c
char name[MAX_NAME_LEN];
int age;
double score;
char grade;
name[20]是一个字符数组,能存 19 个有效字符加一个\0age、score、grade分别存年龄、成绩和等级- 这些变量在声明时没有赋初值,因为马上要通过
scanf从键盘读取
第三部分:scanf 读取输入
c
scanf("%19s", name);
%19s限制最多读取 19 个字符,留出第 20 位给字符串结束符\0name前面没有&,因为数组名本身就是首元素地址
c
scanf("%d", &age);
scanf("%lf", &score);
age是int,用%d,前面加&score是double,用%lf(注意:printf 里double可以用%f,但 scanf 里double必须用%lf,float用%f)
c
scanf(" %c", &grade);
%c前面的空格是关键------它跳过输入缓冲区里残留的换行符,确保能正确读到用户输入的等级字符
第四部分:printf 格式化输出
c
printf("%-12s: %s\n", "姓名", name);
%-12s表示"字符串左对齐,占 12 个字符宽度"。冒号后面的内容会对齐排列- 后面的
%s输出实际的名字字符串
第五部分:const / #define 常量的实际应用
c
if (score >= PASS_SCORE)
不使用魔术数字 60,而是用宏常量 PASS_SCORE。这是好的编程习惯------代码更可读,修改也更方便。
七、初学者常见错误
错误1:scanf 中 int/double/char 变量忘记加 &
c
// 错误写法
int age;
scanf("%d", age); // 编译可能不报错,但运行时崩溃
// 正确写法
int age;
scanf("%d", &age);
原因 :scanf 需要变量的地址才能把读到的数据存进去。不加 & 就是把 age 的值当成地址,这是典型的段错误来源。
错误2:scanf 读取 double 时用了 %f
c
// 错误写法
double score;
scanf("%f", &score); // double 应该用 %lf
// 正确写法
double score;
scanf("%lf", &score);
printf 里 %f 可以同时用于 float 和 double,但 scanf 里必须严格区分 :float 用 %f,double 用 %lf。这是初学者最容易搞混的一个细节。
错误3:scanf 读取 %c 时跳过了用户输入
c
// 错误写法(前有 scanf("%d") 输入后,缓冲区残留 \n)
scanf("%c", &ch); // ch 读到了残留的 \n
// 正确写法
scanf(" %c", &ch); // %c 前加空格,跳过空白字符
错误4:用 scanf 读取带空格的字符串
c
char name[20];
scanf("%s", name); // 用户输入 "Zhang San",只读到 "Zhang"
// 如果需要读取含空格的字符串,用 fgets(后续文章会讲)
%s 遇到空格、Tab、换行都会停止。如果你的输入可能包含空格(比如"Zhang San"),scanf("%s") 不合适。这在第 8 篇字符串文章会详细展开。
错误5:printf 格式占位符与参数类型不匹配
c
int a = 10;
printf("%f\n", a); // 用 %f 打印 int------结果不可预测
long long b = 123456789LL;
printf("%d\n", b); // 用 %d 打印 long long------可能只输出部分数据
格式占位符必须和参数类型严格对应。 搞错了编译器不一定报错,但输出结果会出错。
八、练习题
练习题1:个人信息卡片
写一个程序,让用户输入以下信息,然后按对齐格式输出一张"个人信息卡片":
- 姓名(字符串,最多 19 个字符)
- 年龄(整数)
- 身高(浮点数,单位 cm,保留 1 位小数)
- 体重(浮点数,单位 kg,保留 1 位小数)
要求输出时标签左对齐、占 8 个字符宽,值右对齐。输出格式参考:
text
姓名 Tom
年龄 20
身高 175.5
体重 68.3
练习题2:圆的周长和面积
定义常量 PI = 3.14159。让用户输入圆的半径(浮点数),程序输出圆的周长和面积,各保留 2 位小数。
公式:周长 = 2 × π × r,面积 = π × r²
提示:注意
scanf读取double要用%lf。
练习题3:输入缓冲区问题修复
下面的程序有 bug------用户输入学号后,程序会跳过字母等级的选择,直接结束。找出原因并修复:
c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
int studentID;
char grade;
printf("请输入学号:");
scanf("%d", &studentID);
printf("请输入等级(A/B/C/D):");
scanf("%c", &grade); // 这里有问题!
printf("学号:%d,等级:%c\n", studentID, grade);
return 0;
}
提示:输入学号后按回车,缓冲区里残留了一个
\n。%c会读到这个换行符,导致程序不等你输入等级就结束了。在%c前面加一个空格即可解决问题。
九、本篇总结
- 变量使用前必须声明类型,推荐声明的同时初始化
- const 和 #define 都可以定义常量:宏常量常用于数组长度等场景,const 更适合有明确类型、需要类型检查的常量
- printf 格式控制 :
%d整数、%f浮点、%c字符、%s字符串;.2控制小数位,数字控制宽度,-左对齐 - scanf 中普通变量必须加
&,数组名不加;double用%lf,float用%f %c前面加空格可以跳过缓冲区残留的换行符,解决"跳过输入"的问题