飞翔飞翔
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
  • 学习路径
  • 第1章 编程基础概念

    • 冯·诺依曼体系结构
    • 数据在计算机中的表示
    • 编程语言的层次
    • C语言的起源与发展
    • C99标准的主要改进
    • 开发环境搭建
    • 第一个C程序
    • 编译与运行流程
    • 可移植性风险的三级体系
  • 第2章 数据类型与运算

    • 字符集与标识符
    • 关键字
    • 注释
    • char 类型
    • short 与 int
    • long 与 long long
    • 有符号与无符号
    • 取值范围与 limits.h
    • float 与 double
    • long double
    • _Bool 类型
    • 变量声明与定义
    • 常量
    • 转义序列
    • 算术运算符
    • 赋值运算符
    • 自增自减运算符
    • 关系与判等运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 逗号运算符
    • 运算符优先级
    • 隐式类型转换
    • 显式类型转换
  • 第3章 控制流

    • 表达式语句与空语句
    • 复合语句
    • if 语句
    • switch 语句
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
    • goto 语句
    • return 语句
  • 第4章 函数与模块化编程

    • 函数定义
    • 函数声明与原型
    • main 函数
    • 函数调用机制
    • 传值调用
    • 数组参数
    • 作用域
    • 存储期
    • 链接属性
    • static 与 extern
    • 递归
    • 头文件与源文件
    • 头文件保护
    • include 规则
  • 第5章 数组与字符串

    • 一维数组声明与初始化
    • 数组的存储模型
    • 数组访问与越界
    • 数组操作
    • 二维数组
    • 变长数组 VLA
    • 字符串基础
    • 字符串输入输出
    • 字符串处理函数
    • 字符串与数字转换
  • 第6章 指针

    • 指针的概念
    • 指针的声明与使用
    • 指针运算
    • const 与指针
    • 数组名与指针
    • 指针遍历数组
    • 指针与多维数组
    • 指针作为函数参数
    • 函数返回指针
    • 函数指针
    • 二级指针
    • 复杂声明解析
  • 第7章 结构体、联合体与枚举

    • 结构体定义与声明
    • 结构体初始化
    • 结构体成员访问
    • 结构体嵌套
    • 结构体指针
    • 结构体与函数
    • 联合体
    • 联合体与类型双关
    • 枚举类型
    • 位域
    • 内存对齐与填充
  • 第8章 动态内存管理

    • malloc 与 free
    • calloc 与 realloc
    • 内存泄漏
    • 悬垂指针
    • 内存分配策略
    • 自定义内存池
    • Valgrind 与内存检测
    • 内存碎片
    • 内存对齐分配
    • 常见内存错误
  • 第9章 文件输入输出

    • 文件打开与关闭
    • 文本读写
    • 格式化输入输出
    • 二进制读写
    • 文件定位
    • 错误处理
    • 标准流
    • 临时文件
    • 文件操作示例
  • 第10章 预处理器

    • 预处理器基础
    • 宏定义
    • 带参数的宏
    • 条件编译
    • 头文件包含
    • 预定义宏
    • 宏的高级技巧
    • 预处理器陷阱
    • 编译器特定扩展
  • 第11章 标准库概览

    • 标准库概述
    • assert.h
    • ctype.h
    • errno.h
    • float.h
    • limits.h
    • locale.h
    • math.h
    • setjmp.h
    • signal.h
    • stdarg.h
    • stddef.h
    • stdlib.h
  • 第12章 进阶主题

    • 内联函数
    • 变长数组 VLA
    • 复数类型
    • 布尔类型
    • stdint 与 inttypes
    • 灵活数组成员
    • 匿名结构体与联合体
    • 静态断言
    • 线程支持
    • 原子操作

赋值运算符

赋值运算符将右侧表达式的值存入左侧变量。C 语言除了简单的 = 赋值外,还提供 +=、-=、*=、/=、%= 等复合赋值运算符,它们将运算和赋值合并为一步。赋值表达式本身也有值——赋值后左侧变量的值——这一特性让连续赋值成为可能,但也可能写出晦涩的代码。

简单赋值

= 将右侧值赋给左侧变量:

int x;
x = 10;                 /* x 的值变为 10 */

int a, b, c;
a = b = c = 0;          /* 连续赋值:从右到左 */
/* 等价于:a = (b = (c = 0)); */
/* c = 0 的值是 0,赋给 b;b = 0 的值是 0,赋给 a */

赋值表达式的值就是赋值后左侧变量的值。这一特性使得连续赋值合法,但也容易写出难以理解的代码:

if (x = 5)              /* 将 5 赋给 x,表达式值为 5(真) */
    printf("x is 5\n"); /* 总是执行! */

/* 正确做法 */
if (x == 5)             /* 比较 x 是否等于 5 */
    printf("x is 5\n");

= 和 == 的混淆是 C 语言中最常见的错误之一。开启编译器警告(-Wall)可以捕获大部分这类问题:

gcc -Wall -Wextra program.c
/* 警告:suggest parentheses around assignment used as truth value */

复合赋值运算符

复合赋值运算符将二元运算和赋值合并:

运算符等价形式示例
+=a = a + ba += 5
-=a = a - ba -= 3
*=a = a * ba *= 2
/=a = a / ba /= 4
%=a = a % ba %= 10
&=a = a & ba &= 0xFF
|=a = a | ba |= 0x10
^=a = a ^ ba ^= mask
<<=a = a << ba <<= 2
>>=a = a >> ba >>= 1
int sum = 0;
sum += 10;              /* sum = sum + 10;sum 变为 10 */
sum -= 3;               /* sum = 7 */
sum *= 2;               /* sum = 14 */
sum /= 4;               /* sum = 3(整数除法) */
sum %= 2;               /* sum = 1 */

复合赋值的好处:

  • 代码更简洁
  • 左侧表达式只计算一次(对复杂表达式有性能优势)
/* 简单变量无区别 */
arr[i] = arr[i] + 5;
arr[i] += 5;            /* 等价 */

/* 复杂表达式有区别 */
get_matrix()[row][col] = get_matrix()[row][col] + 5;
/* 两次函数调用 */

get_matrix()[row][col] += 5;
/* 只计算一次左侧地址 */

赋值与类型转换

赋值时,右侧值会自动转换为左侧变量的类型:

int i;
double d = 3.14;
i = d;                  /* double 转为 int,小数部分截断:i = 3 */

float f;
f = i;                  /* int 转为 float:f = 3.0 */

unsigned int u;
u = -5;                 /* 有符号转为无符号:u 变成很大的正数 */

窄化转换(将大范围类型赋给小范围类型)可能丢失信息,编译器通常会警告:

double pi = 3.14159;
int approx = pi;        /* 警告:隐式转换可能丢失数据 */

/* 显式转换消除警告 */
int approx2 = (int)pi;  /* 明确意图:我知道会截断 */

连续赋值与可读性

连续赋值可以初始化多个变量:

int x = 0, y = 0, z = 0;    /* 推荐:清晰 */

int a, b, c;
a = b = c = 0;              /* 可行,但稍难读 */

在 for 循环中,赋值表达式有时用于简洁的初始化:

int i, j;
for (i = 0, j = 10; i < j; i++, j--) {
    /* i 从 0 递增,j 从 10 递减 */
}

注意:逗号运算符 , 与连续赋值不同。a = b = c = 0 是赋值运算符的右结合,i = 0, j = 10 是逗号运算符。

常见错误

混淆 = 和 ==:

if (x = 0)              /* 将 0 赋给 x,表达式值为 0(假) */
    printf("Never\n"); /* 不会执行 */

if (x == 0)             /* 比较 x 是否等于 0 */
    printf("Maybe\n");   /* 取决于 x 的值 */

在不该赋值的地方赋值:

const int max = 100;
max = 200;              /* 编译错误:不能修改 const */

int arr[5];
arr = NULL;             /* 编译错误:数组名不是可修改的左值 */

复合赋值的副作用:

int i = 5;
int result = (i += 3) + i;  /* 未指定行为!i 在同一表达式中被修改两次 */
                            /* 结果可能是 16 或 18,取决于求值顺序 */

/* 安全做法 */
int i = 5;
i += 3;
int result = i + i;         /* 明确:16 */

赋值给不兼容的指针:

int *p;
float f = 3.14f;
p = &f;                 /* 警告:类型不兼容 */
上一页
算术运算符
下一页
自增自减运算符