C语言赋值运算符详解:从=到==,零基础避坑指南
2026/9/1 20:17:50 网站建设 项目流程

C语言里几乎每个程序都离不开=这个符号。很多零基础同学第一次看到int a = 10;,会下意识把=读成“等于”。但从 C 语言的角度看,它更准确的名字是赋值运算符:把右边的值放进左边变量对应的内存单元里。这个区别如果一开始不搞清楚,后面会踩两个坑:一是看代码时理解偏差,二是写出if (a = 1)这种永远不会按预期工作的 bug。

本次教程就专注讲透赋值运算符这一件事,服务人群是真正的零基础读者。我们会从“什么是变量”开始,讲清楚=+=-=*=/=%=这些运算符的作用和区别,再讲最容易混淆的===,最后用两个完整可运行的 C 语言程序做实战验证。读完你不仅能看懂赋值相关的代码,还能自己动手编译运行,排查典型的编译错误。

先给一个判断:这段内容需要你有一台可以写代码的电脑,装好任意一个 C 语言编译环境。Windows 用户可以用 Dev-C++ 或 VS Code,Linux / macOS 用户直接使用gcc。如果你还没有环境,可以直接跳到第 8 章,那里有一份简化版环境准备流程,大概 10 分钟就能跑起第一个程序。

1. 本章知识点速览

知识点说明
赋值运算符=,把右侧表达式的值存入左侧变量
复合赋值运算符+=-=*=/=%=<<=>>=&=、`
赋值表达式a = 10本身是一个表达式,它的值就是赋值后变量的值
右结合性a = b = c = 10从右往左依次赋值
优先级赋值运算符优先级极低,仅高于逗号运算符
易错点=是赋值,==是相等比较,两者含义完全不同
经典考点赋值表达式可以出现在条件判断中,如if (n = 0)
推荐练习平台洛谷、PTA、GESP 等在线评测系统

2. 赋值运算符的本质

赋值运算符在 C 语言里的作用是向变量写入数据

int a = 10;这个语句,读法可以是“声明一个 int 类型的变量 a,并把 10 赋值给它”。这里的=不是一个数学等号,而是一个操作指令。数学里的=表示左右两侧相等,信息是对等的;C 语言里的=表示数据流动方向,信息从右边流到左边。所以a = 10合法,10 = a完全不合法。

理解这一点,需要先理解 C 语言中的“变量”。变量可以理解成一个带名字的容器,这个容器有固定的大小,也有固定的类型。int类型通常占 4 字节,能保存一个整数。赋值运算符做的事情,就是把一个值放进这个容器中:

int age; // 声明变量 age,此时里面是未初始化的垃圾值 age = 18; // 通过赋值运算符把 18 放入变量 age

第一行是“声明”,第二行是“赋值”。很多教程会把这两步合并成int age = 18;,但你要清楚,这里其实发生了两件事:先声明变量,再在声明时对它进行初始化赋值。初学者常忽略“未初始化变量里可能是垃圾值”这一点,直接使用age去参与运算,结果会输出一串莫名其妙的数字。

变量被赋值之后,可以再次赋值,同一时刻变量里只保存最后一次写入的值:

int a = 1; a = 2; a = 3; // 此时 a 的值是 3,之前写入的 1 和 2 已经被覆盖

赋值运算的目标位置必须是一个可以修改的内存单元,在 C 语言术语里叫“左值”。变量是左值,数组元素arr[0]是左值。常量、表达式结果、函数返回值不是左值,所以不能写在=左边。这些概念现在不用全记,先记住一条:=左边必须是变量,右边可以是常量、变量或表达式。

3. 基本赋值运算符

基本赋值运算符就是单独的=。它的完整工作方式是:先计算右边表达式的值,然后把结果存入左侧变量。

看这个程序:

#include <stdio.h> int main(void) { int a; int b = 5; a = 10; printf("第一次赋值: a = %d\n", a); a = b; printf("第二次赋值: a = %d, b = %d\n", a, b); a = 1 + 2; printf("第三次赋值: a = %d\n", a); return 0; }

运行输出:

第一次赋值: a = 10 第二次赋值: a = 5, b = 5 第三次赋值: a = 3

三段赋值分别演示了:直接赋常量、把另一个变量的值复制过来、赋一个表达式的结果。其中a = b表示把 b 里存的 5 复制一份给 a,a 和 b 是两个独立变量,以后修改 a 不会影响 b,反之亦然。

如果搞反了方向,把变量写在右边、常量写在左边,编译器会直接报错。例如:

int a = 1; 10 = a; // 编译错误:左值必须是可修改的变量

这类错误的提示通常是lvalue required as left operand of assignment,翻译过来就是:赋值运算符左边需要一个左值,而10不是左值。看到这条报错,第一反应就应该检查=左边的对象是否写错。

另一个常见误用是把初始化理解成赋值。int x = 5;是初始化,发生在变量创建时;x = 5;是赋值,发生在变量创建之后。两者语法上有时看起来一样,但语义不同。考试和面试中常出现“以下哪些说法正确”的题目,重点考察的就是初始化与赋值的区分。

4. 赋值表达式的值与右结合性

C 语言里有一个与其他高级语言不太一样的特性:赋值操作本身也是一个表达式,有值

a = 10这个表达式,在执行完赋值后,整个表达式的值就是 10。因此你可以把它放在任何需要值的位置,甚至放在条件判断里。

#include <stdio.h> int main(void) { int a, b, c; a = b = c = 10; printf("a = %d, b = %d, c = %d\n", a, b, c); printf("赋值表达式的值: %d\n", a = 20); return 0; }

运行输出:

a = 10, b = 10, c = 10 赋值表达式的值: 20

这里涉及赋值运算符的右结合性。大多数运算符是左结合的,比如1 - 2 - 3等价于(1 - 2) - 3,从左向右算。但赋值运算符不同,a = b = c = 10要从右往左解析,等价于:

c = 10; b = c; a = b;

为什么要设计成右结合?因为连续赋值的语义是:先给最右侧变量赋初始值,再把同一个值依次传递到左边。如果从左往右算,a = b = c = 10会变成先给 a 赋值,但那时 b 和 c 还没赋值,逻辑就乱了。

理解了“赋值表达式有值”的特性,你就能看懂很多 C 语言经典代码。比如:

while ((ch = getchar()) != EOF) { putchar(ch); }

这段代码的含义是:调用getchar()读取一个字符,把返回值赋给变量ch,然后判断这个赋值表达式的值是否不等于EOF。如果不等,就继续循环。这里同时用到了赋值表达式的值和!=比较运算符。需要注意,=的优先级比!=低,所以必须加外层的圆括号。如果不加括号,会先比较getchar() != EOF,再把比较结果赋值给ch,逻辑完全改变。

这个特性也是后面大量 bug 的源头。因为赋值表达式有值,所以if (a = 1)是合法的 C 语言代码,它先把 1 赋给 a,然后判断表达式的值,也就是 1,在 C 语言中非 0 即真,条件永远成立。这不是编译器 bug,而是语言本身的设计。初学者最需要练就的本能,就是区分什么时候该写=,什么时候该写==

5. 复合赋值运算符

复合赋值运算符是一类简写形式。a += b等价于a = a + b。类似地,a -= b等价于a = a - ba *= b等价于a = a * ba /= b等价于a = a / ba %= b等价于a = a % b

运算符示例等价写法
+=a += ba = a + b
-=a -= ba = a - b
*=a *= ba = a * b
/=a /= ba = a / b
%=a %= ba = a % b
<<=a <<= ba = a << b
>>=a >>= ba = a >> b
&=a &= ba = a & b
`=`a |= b
^=a ^= ba = a ^ b

位运算相关的复合赋值(<<=>>=&=|=^=)在嵌入式开发中非常常见,单片机寄存器配置几乎天天用到。零基础阶段可以先知道它们的存在,等学到位运算后再深入。

看一个完整示例:

#include <stdio.h> int main(void) { int x = 10; x += 5; printf("x += 5 之后: x = %d\n", x); x -= 3; printf("x -= 3 之后: x = %d\n", x); x *= 2; printf("x *= 2 之后: x = %d\n", x); x /= 4; printf("x /= 4 之后: x = %d\n", x); x %= 3; printf("x %%= 3 之后: x = %d\n", x); return 0; }

运行输出:

x += 5 之后: x = 15 x -= 3 之后: x = 12 x *= 2 之后: x = 24 x /= 4 之后: x = 6 x %= 3 之后: x = 0

很多初学者认为复合赋值只是简写,没有其他区别。但从语义层面看,两者有一个微妙差异:a += b中,左侧变量a只被求值一次;而a = a + b中,左侧可能被求值多次。当左侧是复杂表达式时,这种差异会体现出来。

举个例子,arr[i++] += 1i只会自增一次;而arr[i++] = arr[i++] + 1i自增了两次,并且这种写法在同一个表达式中对i执行了多次自增,属于未定义行为,不同编译器可能给出不同结果。所以实际工程中,不推荐把自增操作混在赋值表达式里。考试如果遇到这类题,要能看出+=只求值一次这个特性。

6. 赋值运算符的优先级

C 语言运算符很多,从高到低大致可以分成这些等级:

优先级分类运算符
最高括号、数组下标、成员访问()[]->.
单目运算!~++--+-*&(类型)sizeof
算术运算*/%
算术运算+-
移位运算<<>>
关系运算<<=>>=
相等比较==!=
位与运算&
异或运算^
位或运算`
逻辑与&&
逻辑或||
条件运算?:
很低赋值运算=+=-=*=/=%=<<=>>=&=`
最低逗号运算,

赋值运算符的优先级非常低,只比逗号运算符高。这意味着在一行复杂代码中,赋值操作往往是在最后执行的。例如:

a = b * c + d;

先计算b * c,再计算b * c + d,最后把结果赋值给a。所以这行代码不需要加括号。

但下面这段就有问题:

a = b << 2 + 1;

很多人认为它是(b << 2) + 1,实际不是。因为+的优先级高于<<,它等价于b << (2 + 1),即对b左移 3 位。初学者在写位运算和算术运算混排的代码时,建议加上括号,即使优先级你已经背熟,括号也能让阅读者少猜一次。

优先级与结合性是两个维度。赋值运算符本身优先级低,同时又具有右结合性。a = b = c = 10先执行最右边的赋值,再逐步向左传递。这个特性前面已经演示过,这里再强调:当赋值运算符与比较运算符混用时,必须留意优先级带来的执行顺序变化。

7. 最容易混淆的===

如果是零基础自学者,这个点大概率会踩坑。

=是赋值运算符,负责把右侧的值写入左侧变量;==是比较运算符,负责判断左右两侧的值是否相等,如果相等表达式结果为真(非 0),否则为假(0)。

先看一个典型的逻辑错误:

#include <stdio.h> int main(void) { int n = 5; if (n = 0) { printf("条件成立\n"); } else { printf("条件不成立\n"); } printf("n 现在的值: %d\n", n); return 0; }

这段代码的逻辑意图可能是“如果 n 等于 0”,但写成了n = 0。执行过程是:先把 0 赋给 n,此时 n 变为 0,然后判断赋值表达式的值,也就是 0,C 语言中 0 为假,所以输出“条件不成立”,并且 n 被意外修改成了 0。

如果写if (n = 1),则条件永远成立,因为赋值表达式的值是 1,非 0 为真。这种 bug 很难排查,因为程序能编译、能运行,只是结果不符合预期。很多项目里的严重故障,源头就是少写了一个=

解决这个问题有两种常见手段。

第一种,写代码时把常量放在比较运算符左边:

if (0 == n)

这样如果漏写一个=,变成if (0 = n),编译器会直接报错,因为0不是左值,不能接收赋值。这种写法在部分团队中流行,但现代编译器对if (n = 0)这种写法通常也会给出警告,所以是否采用取决于团队规范。

第二种,学会看编译器警告。用gcc编译时加上-Wall选项:

gcc -Wall test.c -o test

编译器可能会提示suggest parentheses around assignment used as truth value,意思是“建议在作为真值使用的赋值表达式周围加括号”。看到这个警告,就要检查是不是把==写成了=

===的混淆不只出现在if中,还会出现在whilefor的条件部分。典型正确用法是读取输入直到文件结束:

#include <stdio.h> int main(void) { int ch; printf("请输入若干字符,按 Ctrl+D(Windows 下 Ctrl+Z)结束:\n"); while ((ch = getchar()) != EOF) { putchar(ch); } return 0; }

这里的关键是(ch = getchar())外层的括号。getchar()的返回值赋给ch之后,整个表达式的值是读到的字符,然后与EOF比较。如果漏掉外层括号,由于赋值运算符优先级低于!=,实际会变成ch = (getchar() != EOF),也就是把比较结果的真假值赋给ch,程序逻辑完全错误。

8. 开发环境准备与编译运行

学 C 语言只需要一个文本编辑器加一个编译器。推荐组合如下:

平台推荐方式说明
WindowsDev-C++ 或 VS Code + MinGWDev-C++ 开箱即用,适合零基础
Linuxgcc系统通常自带,或通过包管理器安装
macOSclang 或 gcc安装 Xcode Command Line Tools 即可
在线编译ideone、洛谷在线 IDE不需要安装本地环境,适合快速测试

如果不想折腾本地环境,可以直接在浏览器里打开洛谷的在线 IDE 或 ideone.com,粘贴代码,选择 C 语言,点击运行。这种方式对零基础最友好,完全绕开安装配置问题。

如果你选择 Linux 下的本地编译,常见流程是:

# 创建一个 C 源文件 vim hello.c # 在 hello.c 中写好代码后,编译生成可执行文件 hello gcc hello.c -o hello # 运行 ./hello

Windows 下用 Dev-C++ 的同学,只需要新建源文件,写代码,然后点击“编译运行”按钮。程序会在弹出的控制台窗口中显示输出。

下面是一个带赋值运算的完整测试程序,你可以直接复制到你的环境里运行:

#include <stdio.h> int main(void) { int a = 3; int b = 4; int c; c = a + b; printf("c = a + b = %d\n", c); c += 10; printf("c += 10 之后: %d\n", c); c -= 2; printf("c -= 2 之后: %d\n", c); c *= 3; printf("c *= 3 之后: %d\n", c); c /= 5; printf("c /= 5 之后: %d\n", c); c %= 7; printf("c %%= 7 之后: %d\n", c); return 0; }

预期输出:

c = a + b = 7 c += 10 之后: 17 c -= 2 之后: 15 c *= 3 之后: 45 c /= 5 之后: 9 c %= 7 之后: 2

如果编译过程中提示找不到stdio.hgcc: command not found,说明编译器没有正确安装。这时候不要继续往下写代码,先解决环境问题。Windows 用户直接安装 Dev-C++,Linux 用户执行sudo apt install gcc(Debian/Ubuntu 系)或sudo yum install gcc(CentOS/RHEL 系)。macOS 用户执行xcode-select --install

9. 实战:计算日期是当年的第几天

这里用一个经典练习题来综合验证赋值运算符的使用,题目是:输入年月日,计算并输出这一天是该年的第几天。

例如输入2025 7 20,输出应该是 2025 年的第 201 天。

先分析思路。1 月到 6 月一共多少天,需要累加;再加上 7 月的 20 天。注意闰年判断:如果月份大于 2,且年份是闰年,要在总天数基础上加 1。闰年条件为:能被 4 整除但不能被 100 整除,或者能被 400 整除。

第一种写法用数组存储每个月天数:

#include <stdio.h> int main(void) { int year, month, day; int days = 0; int daysPerMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; printf("请输入年月日,例如 2025 7 20:"); scanf("%d%d%d", &year, &month, &day); for (int i = 0; i < month - 1; i++) { days += daysPerMonth[i]; } if (month > 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)) { days += 1; } days += day; printf("%d年%d月%d日是这一年的第 %d 天\n", year, month, day, days); return 0; }

这里用到了多个赋值相关知识点:

  • int days = 0;初始化计数器。
  • days += daysPerMonth[i];循环累加前几个月的天数。
  • days += 1;闰年修正。
  • days += day;加上当前月份的日。

+=在计数器累加场景中非常常用。如果写成days = days + daysPerMonth[i]功能一样,但+=更简洁,也能避免左侧写重复。

第二种写法不用数组,用switch的穿透特性:

#include <stdio.h> int main(void) { int year, month, day; int days = 0; printf("请输入年月日,例如 2025 7 20:"); scanf("%d%d%d", &year, &month, &day); switch (month - 1) { case 11: days += 30; case 10: days += 31; case 9: days += 30; case 8: days += 31; case 7: days += 31; case 6: days += 30; case 5: days += 31; case 4: days += 30; case 3: days += 31; case 2: days += 28; case 1: days += 31; default: break; } if (month > 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)) { days += 1; } days += day; printf("%d年%d月%d日是这一年的第 %d 天\n", year, month, day, days); return 0; }

两种代码运行结果相同。第二种方法利用switch的 case 穿透,从比当前月份小 1 的 case 开始,一路向下累加。这里的days += 31写了很多遍,你可以感受到复合赋值在重复计数场景的便利。缺点是代码可读性略差,不熟悉穿透特性的读者可能看不懂。学习阶段建议先掌握数组方法,它能帮你巩固循环和数组两个重要知识点。

验证几个输入:

  • 2025 1 1,输出第 1 天。
  • 2025 3 1,输出第 60 天(31 + 28 + 1)。
  • 2024 3 1,输出第 61 天(2024 是闰年,2 月有 29 天)。

10. 常见错误与调试排查

编译和运行阶段最常见的几类问题,可以对照下面这张表来排查:

问题现象可能原因排查方式解决方案
编译报错lvalue required=左边写了常量或表达式检查赋值方向把常量放到右边,变量放到左边
编译报错undeclared identifier变量未声明就使用检查变量名拼写和声明位置在使用前先声明变量
输出是随机大数字变量未初始化就参与运算检查变量声明后是否有赋值声明时初始化,如int a = 0;
if条件永远成立或永远不成立==误写成=-Wall编译看警告把条件修正为if (n == 0)
输入后程序没有任何输出scanf格式占位符与输入类型不匹配检查%d是否写错确保scanf("%d", &n)格式正确
除法结果总是 0两个整数相除,如1 / 2检查操作数类型使用浮点类型或强制转换
运算结果与预期不符运算符优先级理解错误拆开表达式逐步验证加括号明确优先级

一个比较实用的调试技巧:在关键位置用printf打印变量当前值。比如怀疑赋值没有生效,就加一行:

printf("debug: a = %d\n", a);

观察输出是否在预期位置发生了变化。对于初学者来说,printf是最简单的断点工具。

11. 最佳实践与下一步学习建议

C 语言赋值运算符本身不难,但把它融入工程习惯,需要建立几条规范。

第一,变量声明时尽量初始化。int count = 0;永远好过先写int count;,后面再找机会赋值。未初始化的局部变量值不确定,使用它是未定义行为,程序可能在你的电脑上正常,换一个编译环境就出问题。

第二,条件判断中用==,不要用=。写出if (n = 1)这种代码,编译器不一定报错,但逻辑一定不对。看到编译警告时不要无视,尤其是涉及赋值作为条件判断的警告。

第三,累加、累乘、位操作场景优先使用复合赋值运算符。total += price;total = total + price;更简洁,也更容易体现“更新原变量”的意图。在嵌入式 C 语言中,|=用于置位、&= ~用于清位,是寄存器配置的固定操作。

第四,不要在一行代码里做太多事情。比如a = b = c = 10虽然合法,但可读性一般。初学阶段建议分开写,等熟练之后再读连续赋值代码。

第五,碰到优先级不确定的表达式,直接加括号。括号不会影响性能,但能避免很多误读。

学完赋值运算符,下一步可以按这个顺序继续推进:分支结构(ifswitch)-> 循环结构(whilefor)-> 数组 -> 函数 -> 指针 -> 结构体 -> 文件操作。赋值运算符会贯穿后面所有章节,因为只要程序要保存计算结果,就离不开赋值。

练习方面,建议在洛谷或 PTA 上从入门题开始。优先尝试与数学计算相关的题目,比如计算两数之和、判断闰年、交换两个变量、分段函数求值。每道题都要做到能独立写出完整代码并编译通过,不要只看别人的代码。你可以在练习中刻意使用较短的复合赋值写法,但前提是自己能解释每一行的执行过程。

如果希望跟着课程系统学习,可以关注面向零基础的 C 语言网课,比如翁恺老师的 C 语言课程。视频课适合建立整体框架,但这篇文章的内容适合你反复看、动手敲。遇到编译错误不可怕,每一个报错都是你理解 C 语言底层规则的机会。

赋值运算符只是 C 语言运算符家族里的一个成员,后面还有算术运算符、关系运算符、逻辑运算符、位运算符等着你去认识。把这个基础打牢,后面的路会顺很多。如果这篇文章对你有帮助,建议收藏备用。下一篇可以继续聊if分支或者运算符优先级总表,看你的学习进度决定。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询