C语言入门:从变量初始化、常量类型到scanf地址符的编程基础

发布时间:2026/9/5 17:43:10
C语言入门:从变量初始化、常量类型到scanf地址符的编程基础 很多初学 C 语言的同学都有这样的体验听课时觉得变量和常量很好理解“变量就是值可以变常量就是值不能变”但一到自己动手写程序问题就出现了——为什么我的程序输出是乱码为什么变量忘记赋值就是这个结果为什么保存用户输入要用符号等到后面学指针这种困惑会更强烈。其实变量与常量并不是两个需要死记硬背的名词。它们是 C 语言程序处理数据的“地基”。地基如果没有打牢后面学数组、指针、函数、结构体甚至学单片机嵌入式都会时不时被基础问题绊倒。这篇文章会从零开始把“变量与常量”这件事讲透不光告诉你定义是什么还告诉你为什么这样设计、实际写代码时怎么做、出错时怎么查。读完这篇文章你可以自己完成三件小事第一能正确写出变量的定义、初始化和赋值第二能分清普通变量、字面常量、const常量和宏常量这几种写法第三能独立完成一个包含输入、计算、输出的小程序并且知道常见报错应该去哪里排查。1. 先理解变量是什么一个带名字的“存储盒子”1.1 变量不是数学里的 x很多初学者最大的误区是把 C 语言变量和数学方程里的未知数搞混。在数学里x 3表示一个恒等关系但在 C 语言里x 3是一个动作把数值 3 保存到名叫x的内存单元里。如果你的代码写着x x 1数学老师会觉得不可思议但在 C 语言中这非常正常——它的意思是取出x当前的值加 1再放回x。所以推荐把变量理解成“带名字的存储盒子”每个盒子都有一个名字也就是变量名每个盒子只能存放指定类型的数据程序运行期间盒子里的内容可以被替换每个盒子在内存中都有唯一的地址。这个“盒子”比喻虽然简单但非常实用。后面对比“变量”和“指针”时你会发现指针就是“记录盒子地址的便利贴”而变量本身是盒子里的东西。1.2 变量定义的四个要素在 C 语言中定义一个普通变量需要写清楚四样东西// 文件路径demo_var.c #include stdio.h int main(void) { int age 20; // 类型: int名字: age初始值: 20所在位置: main函数内 double score; // 类型: double名字: score暂无初始值 char grade; // 类型: char名字: grade暂无初始值 printf(age %d\n, age); return 0; }通俗地说int、double、char是数据类型决定这个盒子的容量和存储格式age、score、grade是变量名方便人们引用盒子变量有一个存储值可以读也可以写变量还有一个地址用变量名可以取出。表面上看变量定义就是一个类型加一个名字其实真正重要的是这层理解类型决定了盒子的大小和解释方式名字决定了程序如何找到这个盒子值代表了盒子当前装的内容地址代表了盒子在内存中的物理位置。这四个角度缺一个你对变量的理解都是不完整的。1.3 为什么变量一定要有类型很多零基础读者会问为什么 Python 里写a 10不用指定类型而 C 语言非要写成int a 10;因为 C 语言是一种偏底层的编译型语言编译器在把代码变成机器指令之前必须知道要分配多少个字节的内存这块内存里的二进制数据应该如何解释对变量做运算时应该生成哪种机器指令。同样是 4 个字节的内存以int来解释可能是一个整数以float来解释可能是一个小数以指针来解释可能是一个内存地址。你告诉编译器是什么类型编译器才知道怎么处理。所以不要嫌 C 语言啰嗦。这种“啰嗦”恰恰是 C 语言能高效控制硬件的原因。后面的嵌入式开发更离不开对类型和大小的敏感。2. 常量除了不能改变还要分清三种写法变量是值可变的“盒子”常量则是程序运行期间保持不变的量。在 C 语言基础入门阶段你会遇到三种很容易混淆的常量形式字面常量、宏常量、const限定的只读变量。2.1 字面常量直接写在代码里的值直接写在代码里的数字、字符、字符串叫字面常量100 // 整型字面常量 3.14 // 浮点型字面常量 A // 字符常量 hello // 字符串字面量字面常量没有名字写在哪里就直接出现在机器指令的数据段里。例如int total 100 200;这里的100和200就是字面常量。它们不能被赋值因为 C 语言里不能写100 50——这个错误在编译阶段就会被发现。2.2 宏常量给值起的“替换名”用#define定义的宏本质上是编译前的文本替换。它发生在编译的最早阶段由预处理器把代码里的宏名字替换成后面的内容。// 文件路径demo_macro.c #define PRICE 50 #define COUNT 20 #include stdio.h int main(void) { int total PRICE * COUNT; printf(total %d\n, total); return 0; }预处理器会把PRICE和COUNT直接替换成50和20所以编译时真正看到的代码是int total 50 * 20;宏常量要注意一个问题它没有类型信息也不占变量存储空间本质上只是做文本替换。这就带来一些经典坑。比如写#define SQUARE(x) x*x传入21时会得到21*21结果是 5 而不是 9。所以带表达式的宏必须在小括号里写得足够严密。2.3 const 限定的只读变量const是 C 语言的一个关键字用来修饰变量表示这个变量一旦初始化就不能再被改写。从 C 语言的语义上讲const int x 10;是用const修饰的变量而不是传统意义上的“命名常量”。const int MAX_USER 100;表面看这和宏常量有点像都有名字值都不希望被修改。但技术上差别很大#define是预处理器文本替换不占用变量空间没有类型const修饰的是变量有类型占用内存遵循变量的作用域规则。所以实际 C 语言程序设计中更推荐用const声明不可变数据因为它有类型检查调试也更友好。用宏定义一个 50在 IDE 里跳转、调试时不如const直观。但两种在真实工程里都会用到要学会区分。为了减少混淆下面用表格做个总结写法示例本质是否占内存是否有类型适用场景字面常量100、3.14写在代码里的值按需有临时使用的直接量宏常量#define SIZE 128预编译文本替换不占变量空间无数组大小、简单映射const只读变量const int LIMIT 10;有类型的只读变量是有需要类型安全的常量很多新手以为常量就只是“不能变的量”其实在工程中选哪种写法是有讲究的。C 语言程序设计里有一条经验能用const表达的优先用const需要用于数组大小的编译期常量才会更多地依赖宏或枚举。3. 最容易出错的初始化与赋值3.1 定义、声明、初始化、赋值的关系在 C 语言里有几个术语经常混在一起但它们实际含义不同。定义告诉编译器“我要创建一个变量”例如int a;。声明告诉编译器“变量的类型和名字”在某个地方已经存在例如extern int a;。初始化定义变量时立刻给一个初始值例如int a 0;。赋值变量定义之后再改变它的值例如a 10;。这里初学者常踩的坑是只定义不初始化就当值来用。看这段代码#include stdio.h int main(void) { int sum; sum sum 10; printf(sum %d\n, sum); return 0; }sum没有初始化它里面的值本来是不确定的。你直接执行sum 10结果自然也不确定。在有些编译器上它可能恰好是 10在另一些环境里可能是一个很大的随机数。这种“时好时坏”的 bug 最让人头疼。正确的做法是在定义时就给初始值#include stdio.h int main(void) { int sum 0; // 初始化为 0 sum sum 10; // 赋值把 sum 原来的值加上 10 之后写回去 printf(sum %d\n, sum); return 0; }3.2 赋值号 “” 与比较符 “” 容易混淆零基础阶段另一个高频错误是把赋值和相等判断混用。赋值是把右边的值放到左边变量里比较是用来判断左右两边是否相等。int a 5; if (a 3) { printf(a 等于 3\n); }这段代码不会报编译错误但逻辑完全错了a 3会把 3 赋给a然后表达式的结果是 3if认为条件成立。于是明明判断 a 是否等于 3结果却把 a 改成 3再走入分支。几乎所有写过 C 语言的人都曾因为混淆“”和“”排查过半天。3.3 运行时验证这两个坑把上面两个坑集中在一个小程序里跑一遍你会更直观地看到问题// 文件路径demo_init.c #include stdio.h int main(void) { int uninit; int init_value 100; printf(uninit %d\n, uninit); printf(init_value %d\n, init_value); return 0; }用常见的 GCC 编译运行时uninit的输出可能是 0也可能是随机的残留值。而init_value的输出会稳定是 100。看到这个差异你就明白“定义 初始化”是习惯不是可选动作。4. 为什么同样是数还要分成 int、float、char4.1 类型决定了什么不少初学者会问C 语言里有了int为什么还要有float、double、char因为不同数据在内存中的编码方式不同占用空间也不同。常见概念可以这样理解int用来保存整数比如人数、次数、编号float和double用来保存小数比如价格、成绩、坐标char用来保存单个字符比如字母、数字字符、符号。每一种类型都占用一块固定大小的内存空间。虽然具体大小会随编译器和平台变化但在常见的 64 位 Linux/Windows 环境下类型常见占用典型范围以 32 位 int 为例char1 字节-128 到 127 或 0 到 255int4 字节-2147483648 到 2147483647float4 字节约 ±3.4e38 精度约 7 位double8 字节约 ±1.7e308 精度约 15 位这里特别提醒C 语言标准并没有规定int必须 4 字节。写可移植程序时不要假设int一定是 4 字节。面试和考试中常问的sizeof(int)是多少正确说法是“取决于平台在现代常见平台上是 4”。千万别背成“int 一定是 4 字节”这种错误结论。4.2 用 sizeof 亲手验证类型占用写一段代码验证类型大小是很实用的 C 语言基础练习// 文件路径demo_sizeof.c #include stdio.h int main(void) { printf(char %zu 字节\n, sizeof(char)); printf(short %zu 字节\n, sizeof(short)); printf(int %zu 字节\n, sizeof(int)); printf(float %zu 字节\n, sizeof(float)); printf(double %zu 字节\n, sizeof(double)); return 0; }sizeof是 C 语言的一个运算符不是函数。它的结果类型是size_t打印时通常用%zu。运行这段代码后你会看到每个类型在当前机器上实际占用几个字节。这一步虽然简单但能帮你把“虚的概念”和“机器现实”联系起来。4.3 类型转换规则C 语言允许不同类型的数据参加运算但会发生类型转换。转换分两类自动类型转换隐式转换编译器按一定规则自动处理强制类型转换显式转换你自己在代码里明确标注。一个最经典的坑是整数除法#include stdio.h int main(void) { int a 10; int b 3; float result1 a / b; // 结果是 3.0不是 3.333... float result2 (float)a / b; // 强制把 a 转成 float结果是 3.333333 printf(result1 %f\n, result1); printf(result2 %f\n, result2); return 0; }为什么result1是 3.0 而不是 3.33因为a / b是两个整数相除C 语言规定“整数除以整数”的结果还是整数小数部分被直接丢弃。等结果算出来变成 3 之后再转换成float存到result1已经是 3.0 了。而(float)a / b先把a从int强制转成float这时10.0f / 3会按浮点数运算结果才保留小数。这一点在写价格、平均值、比例等程序时非常重要。很多新手做“求平均分”程序时明明用了float却总输出整数原因正是没有注意整数除法规则。4.4 变量与常量在表达式中的结合再深入一点类型转换不仅发生在变量之间也发生在变量和常量之间。比如double result 5 2.5;5是int2.5是double。C 语言会先把5转换成double再运算得到7.5。这种“小类型自动向大类型转换”的规则是为了避免精度丢失。写代码时脑子里要大概知道“哪些地方会发生自动转换哪些地方需要强制转换”。5. 第一个完整小程序输入价格和数量计算总价前面讲的都是零散知识点。现在用一个完整的小程序把它们串起来。目标是让用户输入商品单价和购买数量程序输出应付总价。建议你先自己照着写一遍再运行。// 文件路径demo_total.c #include stdio.h int main(void) { double price 0.0; // 单价 int count 0; // 数量 double total 0.0; // 总价 printf(请输入商品单价); scanf(%lf, price); printf(请输入购买数量); scanf(%d, count); total price * count; printf(应付总价%.2f 元\n, total); return 0; }关键逻辑解释price、count、total都做了初始化避免“垃圾值”问题scanf里的%lf是读取double类型scanf里必须在变量前加因为scanf需要知道“把输入的数据存到哪个地址”。如果你漏写往往会导致程序崩溃或数据错乱%.2f表示输出浮点数时保留两位小数。Linux/macOS 下用 GCC 编译运行gcc demo_total.c -o demo_total ./demo_totalWindows 下如果使用 Dev-C、Code::Blocks 或 VS 等 IDE直接编译运行即可如果用命令行 MinGW则执行gcc demo_total.c -o demo_total.exe demo_total.exe运行示例请输入商品单价3.5 请输入购买数量4 应付总价14.00 元如果程序输出的是14.000000说明printf的格式没有用%.2f如果程序在输入阶段直接崩溃先检查scanf里面是不是漏了。这两个现象对应的问题都是变量与常量基础章节里最常见的知识点。6. 对变量更进一步的理解是提升 C 语言编程水平的重要环节因此我们来看看更多细节如果你认为“变量就是定义一下、赋值一下”那还不够。接下来从 C 语言程序设计的两个高频考点扩展作用域与生命周期。6.1 局部变量定义在函数内部的变量叫局部变量。它只能在函数内部使用函数结束时局部变量通常会被系统回收。#include stdio.h void func(void) { int x 10; printf(func 中的 x %d\n, x); } int main(void) { // printf(x %d\n, x); // 这里访问不到 func 里的 x return 0; }尝试在main里访问func中的局部变量x编译器会报“未定义”错误。这就是作用域限制。6.2 全局变量定义在函数外部的变量叫全局变量。它的作用域从定义处开始到源文件结束。全局变量在整个程序运行期间都存在默认初始化为 0。#include stdio.h int counter 0; // 全局变量 void add(void) { counter; } int main(void) { add(); add(); printf(counter %d\n, counter); return 0; }程序运行输出counter 2。这里add函数每次把全局变量加 1main里可以读取到修改后的值。但要注意全局变量虽然方便但不建议大量使用。因为它会让函数之间的耦合变强任何一个函数改了全局变量都可能影响其他函数的运行结果。排查 bug 时会很难定位。6.3 static 局部变量如果希望某个局部变量“虽然只在函数内部可见但生命周期延长到整个程序运行期间”可以用static修饰#include stdio.h void next(void) { static int step 0; // 只初始化一次 step; printf(step %d\n, step); } int main(void) { next(); next(); next(); return 0; }运行结果step 1 step 2 step 3普通局部变量每次进入函数都会重新创建static局部变量只在首次执行到它的定义时初始化一次之后函数退出也不会销毁下次调用会保留上一次的值。这种特性常用于统计函数调用次数、实现状态记忆等场景。6.4 变量遮蔽问题还有一个容易让新手懵的点函数里定义了和全局变量同名的变量函数内使用的是局部变量不是全局变量。#include stdio.h int value 100; // 全局变量 int main(void) { int value 200; // 局部变量遮蔽全局变量 printf(value %d\n, value); return 0; }输出结果是value 200。在main内部value指向局部变量全局变量在函数内被“遮蔽”了。这种写法在工程中容易造成误解不太建议刻意使用。给变量命名时尽量避开同名的全局变量。7. 新手常见问题排查与编译错误对照在这一节汇总 C 语言入门阶段最高频的几类问题。表格适合收藏遇到症状时直接对照。问题现象可能原因排查方式解决方案编译报variable x is uninitialized定义了变量但没有初始化查看代码中变量定义处定义时给初始值例如int x 0;编译报undeclared identifier sum变量名拼写不一致或未定义检查变量名是否写错是否在所有使用位置前定义统一变量名拼写并在使用前定义变量编译报lvalue required as left operand of assignment试图给常量或表达式赋值如10 x;查看等号左边是否是常量/表达式换成变量名例如x 10;编译报invalid conversion from int* to intscanf漏写或类型不匹配检查scanf参数是否取地址写成scanf(%d, x);程序能运行但结果是随机数局部变量未初始化查找未初始化的变量定义时初始化想算平均值却输出 0.0 或整数整数除法如avg sum / count;检查除法两边的类型改为(double)sum / count输入阶段直接崩溃scanf未写或格式串类型不匹配检查scanf参数为普通变量加double使用%lf下面挑三个做详细说明。7.1 为什么我的变量值总是随机的如果你定义变量后没有初始化就直接使用变量中保存的是该内存区域原来的内容可能是 0可能是一个残留的大数字。同一程序多次运行结果可能还不一样。排查步骤找到变量定义处看是否赋值如果定义处没有初始值补上合理默认值如果变量应该在循环里累加确认每次累加前已经置 0。7.2 “expected declaration specifiers”大概是怎么回事有时候刚学函数定义会把赋值语句写到函数外面。比如int x; x 10;如果把x 10;写到了所有函数的外部编译器会认为“函数外面不应该出现执行语句”于是报出奇怪的语法错误。当编译器报错位置和你认为的错误位置不一致时先往前看几行检查是否把执行语句放在了函数外。7.3 编译通过但输出不对应该怎么看编译通过只代表语法没错不代表逻辑正确。程序输出不对时不要盲目改代码先做两件小事在关键计算前后打印中间值比如打印price和count是否和输入一致检查运算规则尤其是整数除法、类型转换、运算符优先级。比如想输出两位小数却用了%f输出虽然不会崩溃但不是保留两位。想打印int却用了%lf也会得到混乱结果。8. 变量与常量学习过程中的最佳实践8.1 命名规范变量名要能看出含义。新手练习时用a、b、c还能接受等程序变长一个叫data的变量到底是总价格、次数还是平均值就完全看不出来了。推荐习惯变量名用英文不用拼音缩写使用小写驼峰或下划线风格例如totalPrice或total_price常量名通常全大写例如MAX_NUM、PI不要使用单个大写字母或过于相似的命名例如l和1在部分字体里很难区分。8.2 初始化与赋值分开写不如初始化一起写定义变量时立刻给初始值能避免大量的未初始化问题。如果暂时不知道值是多少也建议至少给一个“安全的默认值”例如计数器的 0、总价的 0.0。8.3 scanf 的注意事项scanf是新手输入阶段最常见的问题来源。这里强调几点普通变量的读取需要加地址符scanf(%d, num);读取double格式串用%lf不是%f不要在这个阶段研究太复杂的scanf格式化先用最简单的形式scanf对用户的输入很敏感如果第一次没读到预期内容后续输入可能卡住可以先按 CtrlC 结束程序再重新运行。8.4 变量定义放在合适的位置C99 之后C 语言允许在代码块的任意位置定义变量不必像早期标准那样只能放在块的开头。现代编译器基本都支持这一点。所以建议把变量定义放在靠近第一次使用的位置并用注释说明作用。如果是在考试或老教材环境下可能会要求集中在开头定义可按教材要求执行但实际开发中不必这样约束自己。8.5 一个变量的生命周期跟踪习惯很多初学者最怕的概念不是变量而是后面链表、树这些动态数据结构里的节点。要想后面学得顺利在基础阶段就建立一个习惯写程序前先在纸上画出每个变量“出生”和“消亡”的位置。函数开始时局部变量“出生”函数返回时局部变量“消亡”全局变量和static局部变量则在程序启动时创建、程序结束时才销毁。有了这个习惯理解“传值”和“传地址”时会轻松很多。因为函数调用时如果直接传变量函数拿到的是它的一个拷贝如果传地址函数才能通过地址修改原变量。scanf为什么需要也可以从这个角度解释。9. 基础掌握之后下一步该怎么学习变量与常量只是 C 语言程序设计的第一道门。当你能够熟练定义和初始化变量分清各种常量写法理解类型转换能写出包含输入、计算、输出的小程序时你已经迈出了最重要的一步。接下来比较自然的学习路径是运算符与表达式掌握、--、/、%的细节和优先级分支与循环用if、for、while处理逻辑数组把多个同类型变量组织在一起理解数组名和下标的关系函数学会封装代码理解传值和传地址指针从“变量的地址是什么”开始把变量、地址和内存串成一个体系字符串理解字符数组和字符串常量的区别结构体把几个相关变量组合成一种新的数据形态。在练习阶段网上流传的“翁恺C语言练习题”和各类 OJ 平台都是不错的补充材料可以用来做输入输出的专项训练。不过视频和题目只能解决“看懂了”和“会做了”真正让你理解变量与常量的是你自己动手调试每一个错误并能在出错说明和实际运行结果之间建立联系。学习 C 语言总有一个阶段需要和“不确定的变量值”“意外的类型转换”较劲。这不是坏事。因为这些看似琐碎的细节恰恰是理解计算机如何保存和处理数据的起点。把变量与常量这一课学扎实后面学再复杂的内容都有了可以依靠的地基。