C 基础(5) - 运算符、表达式和语句

组织程序是处理数据的另一个方面,让程序按正确的顺序执行各个步骤。C 有许多语言特性,帮助你完成组织程序的任务。循环就是其中一个特性,循环能重复执行行为,让程序更有趣、更强大。

1. 基本运算符

1. 🧮 算术运算符

这是最基础的一类,用于数值计算。除了加减乘除,C 语言特有的"取余"和"自增自减"需要特别注意。

2. ⚖️ 关系与逻辑运算符

这两类通常配合使用,用于构建 ifwhile 等条件判断语句。

  • 关系运算符 (比较大小):
    • == (等于) 和 != (不等于):千万不要混淆 = (赋值) 和 == (判断)
    • >, <, >=, <=
  • 逻辑运算符 (判断真假):
    • && (逻辑与):两边都为真,结果才为真。具有短路特性(如果左边为假,右边不再执行)。
    • || (逻辑或):只要有一边为真,结果就为真。
    • ! (逻辑非):取反,真变假,假变真。
运算符 描述 示例 注意事项
+ 加法 a + b
- 减法 a - b
* 乘法 a * b 不要漏写乘号,也不能用 x 代替
/ 除法 a / b 整数除法会截断小数 (如 5/2 结果为 2)
% 取余 (模) a % b 只能用于整数,5 % 2 结果为 1
++ 自增 ++a, a++ ++a 是先加后用,a++ 是用后加
-- 自减 --a, a-- 同上

3. 📝 赋值运算符

用于给变量赋值。除了简单的 =,C 语言还提供了一些"简写"形式,可以让代码更简洁:

  • 简单赋值a = 10;
  • 复合赋值
    • a += 1; 等价于 a = a + 1;
    • a -= 1; 等价于 a = a - 1;
    • a *= 2; 等价于 a = a * 2;
    • a /= 2; 等价于 a = a / 2;

4. 🔢 位运算符

这是 C 语言区别于其他高级语言的一大特色,允许你直接操作二进制位,常用于嵌入式开发或算法优化。

  • & (按位与):两位同为 1 则为 1。
  • | (按位或):有一位为 1 则为 1。
  • ^ (按位异或):两位不同则为 1。
  • ~ (按位取反):0 变 1,1 变 0。
  • << (左移) 和 >> (右移):将二进制位向左或向右移动。

5. ❓ 其他重要运算符

  • 条件运算符 (?:) :C 语言中唯一的三目运算符 (需要三个操作数)。
    • 语法:表达式1 ? 表达式2 : 表达式3
    • 含义:如果表达式 1 为真,则取表达式 2 的值,否则取表达式 3 的值。
  • 逗号运算符 (,) :用于将两个表达式连接起来,整个表达式的值是最右边那个表达式的值。
  • sizeof:用于计算数据类型或变量在内存中占用的字节数。

2. 核心重点:优先级与结合性

在复杂的表达式中,运算的顺序至关重要。记住**"算术 > 关系 > 逻辑 > 赋值"**这个大原则。

以下是精简后的优先级速查表(从上到下,优先级由高到低):

优先级 运算符类别 结合性 典型符号
1 (最高) 括号、下标、成员访问 左 → 右 (), [], ., ->
2 单目运算符 右 → 左 !, ~, ++, --, *, &, sizeof
3 算术 (乘除) 左 → 右 *, /, %
4 算术 (加减) 左 → 右 +, -
5 移位运算 左 → 右 <<, >>
6 关系运算 左 → 右 <, >, <=, >=
7 相等判断 左 → 右 ==, !=
8 位运算 左 → 右 &, ^, `
9 逻辑运算 左 → 右 &&, `
10 条件运算 右 → 左 ?:
11 赋值运算 右 → 左 =, +=, -=
12 (最低) 逗号 左 → 右 ,

3.表达式和语句

在 C 语言中,表达式(Expression) 和**语句(Statement)**是构建程序逻辑的两个最基本单元。虽然它们经常混在一起出现,但它们在概念上有着本质的区别。

简单来说:表达式是为了"算出一个值",而语句是为了"执行一个动作"。

1. 核心定义

🧮 什么是表达式?

表达式是由操作数 (变量、常量、函数调用)和运算符+, -, =, ++ 等)组成的序列。

  • 核心目的 :计算出一个具体的
  • 特征:每个表达式都有一个结果(哪怕结果是"无意义的")。
🚦 什么是语句?

语句是程序执行的最小独立单位,代表给计算机的一条指令。

  • 核心目的:控制程序的流程或执行某个动作。
  • 特征 :在 C 语言中,语句通常以分号 ; 结尾(复合语句除外)

2. 关键区别一览表

维度 表达式 语句
核心任务 求值 (计算结果) 执行 (控制流程/动作)
标志性符号 无特定结尾 通常以分号 ; 结尾
能否独立运行 不能(除非加上分号变成语句) 能,是程序的基本组成单元
包含关系 语句通常包含表达式 语句是表达式的"容器"或控制者
例子 a + b, x = 5, printf("hi") a + b;, if(x>0){...}, return 0;
常见的表达式语句
  1. 赋值语句
    • 表达式:a = 10 (计算出值 10 并赋给 a)
    • 语句:a = 10; (执行赋值这个动作)
  2. 算术语句
    • 表达式:a + b (计算出和)
    • 语句:a + b; (计算了和,但没保存,无实际意义,编译器通常会警告)
    • 语句:i++; (利用自增的副作用,修改 i 的值)
  3. 函数调用语句
    • 表达式:printf("Hello") (返回打印的字符数)
    • 语句:printf("Hello"); (执行打印动作)
控制语句(非表达式语句)

有些语句不是由表达式加个分号变来的,而是专门用来控制流程的,称为控制语句

  • 分支if (...) {}, switch (...) {}
  • 循环for (...) {}, while (...) {}
  • 跳转return;, break;, goto;
复合语句(块)

用花括号 {} 把多个语句括起来,视为一个整体,称为复合语句

复制代码
{
    int a = 10;
    a++;
    printf("%d", a);
} // 这是一条复合语句

4. 类型转换

在 C 语言中,类型转换是连接不同数据类型的桥梁。你可以把它想象成"翻译"------告诉计算机如何用另一种语言(数据类型)去解释当前的信息。

C 语言的类型转换主要分为两类:隐式转换 (编译器自动做)和显式转换(程序员强制做)。

1. 隐式转换:编译器的"自动翻译"

这是编译器在后台悄悄完成的,目的是为了让不同类型的变量能够在一起运算。它遵循一个核心原则:向精度更高、范围更大的方向转换,以防止数据丢失。

核心机制:整型提升

这是 C 语言中最基础也最容易被忽视的规则。

  • 规则 :当 charshort 或枚举类型参与运算时,它们会自动 先被转换为 int(如果 int 能存下)或 unsigned int

  • 原因 :CPU 的运算器(ALU)通常是按 int 的长度(如 32 位)进行工作的,直接处理 8 位或 16 位数据效率并不高。

  • 后果:

    复制代码
    char a = 0xFF; // 二进制 11111111
    // 在运算时,a 会被提升为 int。
    // 如果 char 是有符号的,提升后变成 0xFFFFFFFF (-1)。
    // 如果 char 是无符号的,提升后变成 0x000000FF (255)。
算术转换

当不同类型的变量(如 intdouble)进行运算时:

  1. 先进行整型提升(如上所述)。
  2. 然后遵循"就高不就低 "原则:int 会转为 doublefloat 会转为 double

2. 显式转换:程序员的"强制指令"

当你需要将高精度转为低精度,或者指针类型互转时,必须使用强制类型转换。

  • 语法(目标类型) 表达式
  • 本质:告诉编译器"我知道会有风险,但我坚持要这样解释这块内存或数值"。
常见场景与风险
转换方向 行为规则 潜在风险
浮点 → 整型 截断(直接丢弃小数部分,非四舍五入) 精度丢失(如 3.9 变为 3
大整型 → 小整型 截断(保留低字节,丢弃高字节) 数据溢出/错误(如 intchar
整型 → 浮点型 数值映射 极大整数可能无法精确表示(精度误差)
有符号 ↔ 无符号 位模式不变,解释改变 负数变极大正数(如 -1 变为 4294967295
指针强转的危险

你可以将任意类型的指针强转为 void* 或其他类型指针,但这非常危险。

  • 对齐问题 :将 char* 强转为 int* 并解引用,如果地址不是 4 字节对齐的,可能会导致程序崩溃。
  • 内存误读 :将 int* 当作 double* 读取,会因为读取字节数不同(4字节 vs 8字节)而读到垃圾数据。

5. 带参数的函数

在 C 语言中,带参数的函数 是实现代码复用和模块化的核心。你可以把函数想象成一个"加工厂",而参数就是送进工厂的"原材料"。

根据你传入的"原材料"不同,工厂的处理方式(是复制一份处理,还是直接处理原件)也会不同。

1. 基础定义:形参与实参

在深入之前,我们需要统一两个术语,这非常重要:

  • 形式参数(形参):定义函数时括号里的变量。它是"占位符",只在函数内部有效。

  • 实际参数(实参):调用函数时传入的具体数值或变量。它是"真材实料"

    // 定义:x 和 y 是形参
    int Add(int x, int y) {
    return x + y;
    }

    // 调用:a 和 b 是实参
    int a = 10, b = 20;
    Add(a, b);

2. 核心机制:两种参数传递方式

这是 C 语言函数参数中最关键的部分。C 语言主要支持两种传递逻辑:传值传引用(通过指针)

📄 传值调用

这是 C 语言默认的方式。

  • 原理 :把实参的副本(拷贝)传给形参。

  • 特点:形参和实参是两个独立的内存空间。

  • 后果 :在函数内部修改形参,不会影响外部的实参。

    void swap_val(int a, int b) {
    int temp = a;
    a = b; // 修改的是副本
    b = temp; // 外部变量不受影响
    }

    int main() {
    int x = 10, y = 20;
    swap_val(x, y);
    // 结果:x 还是 10,y 还是 20
    return 0;
    }

🔗 传址调用 (模拟传引用)

C 语言没有 C++ 那样的原生"引用传递"(&),而是通过指针来实现。

  • 原理 :把实参的内存地址传给形参。

  • 特点:形参(指针)指向了实参的内存地址。

  • 后果 :在函数内部通过指针解引用(*)修改数据,会直接改变外部的实参。

    void swap_addr(int *a, int *b) {
    int temp = *a;
    *a = *b; // 通过地址修改原内存中的值
    *b = temp;
    }

    int main() {
    int x = 10, y = 20;
    swap_addr(&x, &y); // 传入地址
    // 结果:x 变成了 20,y 变成了 10
    return 0;
    }

3. 特殊参数形式

除了基本的整数或浮点数,C 语言的函数参数还有几种特殊的形态。

数组作为参数

当你把数组传给函数时,数组名会自动退化为指向数组第一个元素的指针

  • 误区 :在函数内部用 sizeof(arr) 无法得到数组的总大小(只能得到指针大小)。

  • 最佳实践:必须额外传递一个参数来记录数组的长度。

    // arr[] 本质上等同于 int *arr
    void print_array(int arr[], int size) {
    for(int i=0; i<size; i++){
    printf("%d ", arr[i]);
    }
    }

结构体作为参数
  • 传值void func(struct Student s)。会拷贝整个结构体,如果结构体很大,会消耗较多时间和内存。
  • 传址(推荐)void func(struct Student *s)。只传递地址,效率高,且能修改结构体内容。
可变参数

类似于 printf 函数,参数个数不固定。

  • 实现 :需要使用 <stdarg.h> 头文件中的宏(va_list, va_start, va_arg, va_end)。
  • 注意:函数至少需要一个固定的参数作为起点。

4. 总结对比表

为了方便记忆,我为你整理了这张对比表:

特性 传值调用 传址调用 (指针)
传递内容 数据的副本 数据的内存地址
修改影响 函数内修改不影响外部 函数内修改改变外部
性能 数据量大时效率低 (需拷贝) 效率高 (只拷贝地址)
安全性 高 (外部数据受保护) 低 (外部数据暴露)
典型应用 简单的数学计算、输入 修改多个返回值、大数组/结构体

💡 给你的建议

  1. 默认用传值 :对于 intcharfloat 等简单类型,直接用传值,简单又安全。
  2. 修改用指针 :如果你需要在函数里改变外部变量的值(比如 scanf),或者传递很大的数组/结构体,请务必使用指针(传址)。
  3. 数组必带长度 :写函数处理数组时,永远不要相信函数内部能自动知道数组有多长,一定要多传一个 size 参数
相关推荐
一只废狗狗狗狗狗狗狗狗狗2 小时前
c语言速通复习
c语言·开发语言
计算机安禾2 小时前
【数据结构与算法】第31篇:排序概述与插入排序
c语言·开发语言·数据结构·学习·算法·重构·排序算法
草莓熊Lotso2 小时前
MySQL 事务管理全解:从 ACID 特性、隔离级别到 MVCC 底层原理
linux·运维·服务器·c语言·数据库·c++·mysql
浅时光_c2 小时前
9 循环语句
c语言·开发语言
l1t2 小时前
DeepSeek辅助编写的dmp转schema和csv文件c语言程序
c语言·开发语言·windows
DREW_Smile3 小时前
自定义类型:联合体和枚举
c语言·开发语言
算法鑫探3 小时前
显示器插座最短连线算法(蓝桥杯十六届C组编程题第二题)
c语言·数据结构·算法·排序算法·新人首发
青桔柠薯片3 小时前
I²C 总线协议学习总结:从开漏逻辑到读写事务的工程视角
c语言·开发语言·学习
计算机安禾4 小时前
【数据结构与算法】第32篇:交换排序(一):冒泡排序
c语言·数据结构·c++·算法·链表·排序算法·visual studio code