组织程序是处理数据的另一个方面,让程序按正确的顺序执行各个步骤。C 有许多语言特性,帮助你完成组织程序的任务。循环就是其中一个特性,循环能重复执行行为,让程序更有趣、更强大。
1. 基本运算符
1. 🧮 算术运算符
这是最基础的一类,用于数值计算。除了加减乘除,C 语言特有的"取余"和"自增自减"需要特别注意。
2. ⚖️ 关系与逻辑运算符
这两类通常配合使用,用于构建 if 或 while 等条件判断语句。
- 关系运算符 (比较大小):
==(等于) 和!=(不等于):千万不要混淆=(赋值) 和==(判断)。>,<,>=,<=
- 逻辑运算符 (判断真假):
&&(逻辑与):两边都为真,结果才为真。具有短路特性(如果左边为假,右边不再执行)。||(逻辑或):只要有一边为真,结果就为真。!(逻辑非):取反,真变假,假变真。
| 运算符 | 描述 | 示例 | 注意事项 |
|---|---|---|---|
+ |
加法 | 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; |
常见的表达式语句
- 赋值语句 :
- 表达式:
a = 10(计算出值 10 并赋给 a) - 语句:
a = 10;(执行赋值这个动作)
- 表达式:
- 算术语句 :
- 表达式:
a + b(计算出和) - 语句:
a + b;(计算了和,但没保存,无实际意义,编译器通常会警告) - 语句:
i++;(利用自增的副作用,修改 i 的值)
- 表达式:
- 函数调用语句 :
- 表达式:
printf("Hello")(返回打印的字符数) - 语句:
printf("Hello");(执行打印动作)
- 表达式:
控制语句(非表达式语句)
有些语句不是由表达式加个分号变来的,而是专门用来控制流程的,称为控制语句。
- 分支 :
if (...) {},switch (...) {} - 循环 :
for (...) {},while (...) {} - 跳转 :
return;,break;,goto;
复合语句(块)
用花括号 {} 把多个语句括起来,视为一个整体,称为复合语句
{
int a = 10;
a++;
printf("%d", a);
} // 这是一条复合语句
4. 类型转换
在 C 语言中,类型转换是连接不同数据类型的桥梁。你可以把它想象成"翻译"------告诉计算机如何用另一种语言(数据类型)去解释当前的信息。
C 语言的类型转换主要分为两类:隐式转换 (编译器自动做)和显式转换(程序员强制做)。
1. 隐式转换:编译器的"自动翻译"
这是编译器在后台悄悄完成的,目的是为了让不同类型的变量能够在一起运算。它遵循一个核心原则:向精度更高、范围更大的方向转换,以防止数据丢失。
核心机制:整型提升
这是 C 语言中最基础也最容易被忽视的规则。
-
规则 :当
char、short或枚举类型参与运算时,它们会自动 先被转换为int(如果int能存下)或unsigned int。 -
原因 :CPU 的运算器(ALU)通常是按
int的长度(如 32 位)进行工作的,直接处理 8 位或 16 位数据效率并不高。 -
后果:
char a = 0xFF; // 二进制 11111111 // 在运算时,a 会被提升为 int。 // 如果 char 是有符号的,提升后变成 0xFFFFFFFF (-1)。 // 如果 char 是无符号的,提升后变成 0x000000FF (255)。
算术转换
当不同类型的变量(如 int 和 double)进行运算时:
- 先进行整型提升(如上所述)。
- 然后遵循"就高不就低 "原则:
int会转为double,float会转为double。
2. 显式转换:程序员的"强制指令"
当你需要将高精度转为低精度,或者指针类型互转时,必须使用强制类型转换。
- 语法 :
(目标类型) 表达式 - 本质:告诉编译器"我知道会有风险,但我坚持要这样解释这块内存或数值"。
常见场景与风险
| 转换方向 | 行为规则 | 潜在风险 |
|---|---|---|
| 浮点 → 整型 | 截断(直接丢弃小数部分,非四舍五入) | 精度丢失(如 3.9 变为 3) |
| 大整型 → 小整型 | 截断(保留低字节,丢弃高字节) | 数据溢出/错误(如 int 转 char) |
| 整型 → 浮点型 | 数值映射 | 极大整数可能无法精确表示(精度误差) |
| 有符号 ↔ 无符号 | 位模式不变,解释改变 | 负数变极大正数(如 -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. 总结对比表
为了方便记忆,我为你整理了这张对比表:
| 特性 | 传值调用 | 传址调用 (指针) |
|---|---|---|
| 传递内容 | 数据的副本 | 数据的内存地址 |
| 修改影响 | 函数内修改不影响外部 | 函数内修改改变外部 |
| 性能 | 数据量大时效率低 (需拷贝) | 效率高 (只拷贝地址) |
| 安全性 | 高 (外部数据受保护) | 低 (外部数据暴露) |
| 典型应用 | 简单的数学计算、输入 | 修改多个返回值、大数组/结构体 |
💡 给你的建议
- 默认用传值 :对于
int、char、float等简单类型,直接用传值,简单又安全。 - 修改用指针 :如果你需要在函数里改变外部变量的值(比如
scanf),或者传递很大的数组/结构体,请务必使用指针(传址)。 - 数组必带长度 :写函数处理数组时,永远不要相信函数内部能自动知道数组有多长,一定要多传一个
size参数。