03_变量常量与输入输出_printf与scanf详解

变量、常量与输入输出:printf 和 scanf 详解

一、本篇文章要解决什么问题

前面两篇你搭好了环境、了解了数据类型,接下来要面对的问题是:数据怎么存、怎么显示、怎么让用户输入?

这篇文章就帮你搞定三件事:

  1. 变量和常量到底是什么关系,const 有什么用?
  2. printf 怎么控制输出格式------宽度、小数位、对齐?
  3. scanf 怎么安全地读取用户输入?那个 & 符号到底什么意思?

学完这篇,你就能写出真正"能和用户对话"的程序------既能接收输入,也能把结果漂漂亮亮地打印出来。


二、先用一个简单例子理解

2.1 变量就是"贴标签的盒子"

想象你去超市存包。你把包放进一个储物柜,拿到一张小票,小票上印着柜子编号。以后凭这张小票就能找到你的包。

C 语言里的变量就是这个意思:

  • 储物柜 = 内存中的一块空间
  • 小票上的编号 = 变量的名字(比如 agescore
  • 包里的东西 = 变量存的数据
  • 柜子的大小 = 变量的类型(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 的基本用法------为什么需要 &

scanfprintf 刚好相反: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); 不加 &,但要限制长度

特别注意 %cscanf("%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 个有效字符加一个 \0
  • agescoregrade 分别存年龄、成绩和等级
  • 这些变量在声明时没有赋初值,因为马上要通过 scanf 从键盘读取

第三部分:scanf 读取输入

c 复制代码
scanf("%19s", name);
  • %19s 限制最多读取 19 个字符,留出第 20 位给字符串结束符 \0
  • name 前面没有 &,因为数组名本身就是首元素地址
c 复制代码
scanf("%d", &age);
scanf("%lf", &score);
  • ageint,用 %d,前面加 &
  • scoredouble,用 %lf(注意:printf 里 double 可以用 %f,但 scanf 里 double 必须用 %lffloat%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 可以同时用于 floatdouble,但 scanf 里必须严格区分float%fdouble%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 前面加一个空格即可解决问题。


九、本篇总结

  1. 变量使用前必须声明类型,推荐声明的同时初始化
  2. const 和 #define 都可以定义常量:宏常量常用于数组长度等场景,const 更适合有明确类型、需要类型检查的常量
  3. printf 格式控制%d 整数、%f 浮点、%c 字符、%s 字符串;.2 控制小数位,数字控制宽度,- 左对齐
  4. scanf 中普通变量必须加 & ,数组名不加;double%lffloat%f
  5. %c 前面加空格可以跳过缓冲区残留的换行符,解决"跳过输入"的问题

相关推荐
江南十四行4 小时前
并发编程(一)
java·jvm·算法
z200509304 小时前
今日算法(依旧二叉树)
算法·leetcode·职场和发展
Zxc_5 小时前
《遗传算法:从自然选择到Rastrigin函数优化,手写一个完整的进化求解器》
算法
阿Y加油吧5 小时前
两道经典动态规划题:乘积最大子数组 & 分割等和子集 复盘笔记
笔记·算法·动态规划
三品吉他手会点灯5 小时前
C语言学习笔记 - 33.数据类型 - printf函数的详细用法
c语言·开发语言·笔记·学习·算法
NashSKY6 小时前
PnP 问题:数学描述与 DLT 算法推导
算法·矩阵分解·多视图几何·射影几何
csdn_aspnet6 小时前
C++ Lomuto分区算法(Lomuto Partition Algorithm)
开发语言·c++·算法
ZPC82106 小时前
Open3D 与yolo-3d 那个更适合生成物体3d 包围盒
人工智能·算法·计算机视觉·机器人
行走的陀螺仪6 小时前
JavaScript 算法详解:10大经典算法,通俗易懂,从入门到精通
开发语言·javascript·算法