资讯动态

C语言指针传参经典题:swap函数交换两个实型变量详解

发布时间:2026/10/20 23:12:07 来源:尧图企业网站定制
刚学C语言指针的时候几乎所有人都会遇到这么一道题编写void swap(float *px, float *py)交换两个实型变量的值然后在主函数里验证。这个题目看着基础信息量却一点都不小——函数名、参数列表、返回值类型、调用方式每个细节都对应一个考点。从我带人的经验来看很多人卡住的地方不在“交换”本身而在“为什么形参是两个指针”“主函数怎么传参才叫验证成功”这些问题上。这篇文章逐个拆开讲再附上可以直接跑的完整代码和调试心得。适合刚学完指针的初学者也适合想把这个经典点彻底搞明白的开发者。1. 先把题目拆开读float *px 到底在声明什么拿到题目第一步不是急着写代码而是把函数声明void swap(float *px, float *py)逐词拆开。这一行字符串里包含的信息足够你回答面试官关于指针和函数传参的一串追问。1.1 形参 float *px 的读法与真正含义float *px的正确读法是px 是一个指针变量它存储的是一个内存地址这个地址里存放着一个 float 类型的数据。注意px 本身存的是地址而不是浮点数本身。要拿到这个地址指向的数据必须使用间接访问运算符*也就是*px。我经常用一个类比来解释这个概念px 是门牌号*px才是房间里住的人。你想修改房间里的人必须拿着门牌号走进去改而不是把房间里的人拉出来复印一份在复印件上改。实型变量在内存中占 4 个字节存放的是 IEEE 754 单精度浮点格式的数据符号位、指数位、尾数位和整数的补码表示完全不同。很多人觉得“拷贝一份值然后再赋回去”也能实现交换但这里的关键不是数据长什么样而是函数里修改的是副本还是原件。1.2 void 返回值一个容易被忽略的考点题目要求返回类型是void这个设计是有讲究的。swap 这个函数的目的是在函数内部直接修改调用者定义的变量这种效果叫“副作用”它通过指针完成因此不需要再返回任何值。如果想把 swap 设计成有返回值的函数除非你用某种技巧把两个数打包返回否则只可能返回一个数——而“同时返回两个交换后的数”这件事本身就不是 C 语言普通函数能直接做到的。所以void在这里不是“随便写写”而是提醒你这个函数的工作方式是原地修改不是返回结果。这也顺带解释了为什么函数内部可以用return;提前结束但不能return 某个值;——类型是空的就只能空着回。2. 指针传参背后的机制为什么值传递救不了 swap理解了声明之后下一个必须想清楚的问题是为什么参数非得是float *px和float *py直接用float x, float y不行吗这个问题的答案就是整个题目的核心考点。2.1 函数参数传递时到底发生了什么C 语言的函数参数传递方式只有一个值传递。调用swap(a, b)时系统会把 a 和 b 的数值复制一份放进这个函数栈帧里的形参 x 和 y 中。从此x 和 y 就是两个全新的存储单元它们和 main 函数里的 a、b 没有任何血缘关系。你在 swap 里把 x 和 y 交换得再漂亮回到 main 函数之后a 和 b 依然纹丝不动。做个演示就特别直观。假如写出下面这个错误的版本void swap_wrong(float x, float y) { float temp x; x y; y temp; }然后在主函数里调用float a 3.14f, b 2.71f; swap_wrong(a, b); printf(a %f, b %f\n, a, b);输出结果依然会是a 3.14, b 2.71。如果在 swap_wrong 内部加一行 printf你会发现函数里 x 和 y 确实悄悄交换了但这仅仅是“复印件”之间的交换。这就是值传递的本质——你对形参做的所有操作都不会反馈到实参上。2.2 地址传递究竟传递了什么当函数参数换成float *px后调用时写成swap(a, b)。这里的a是取变量 a 的地址形参 px 拿到的是 a 的门牌号。此时px 变量本身的存储单元依然是复制的但 px 里保存的“门牌号”却和 a 是同一个地址。通过*px函数可以直接找到那个地址修改里面的数据。这就是间接寻址也是指针传参能“改到外面”的原因。这里有一个特别容易误解的细节指针变量本身在函数间传递时也是值传递也就是说形参 px 和实参 a 不是同一个变量你如果写px py只是让 px 这个指针变量指向 py 指向的位置并没有改变任何 float 变量本身。只有带着*的写操作比如*px *py才能真正落到实参变量的内存上。把下面的对比记住基本就出不了大问题传递方式函数内修改形参是否影响实参本质原因传值 float xx 5.0f不影响形参是实参的副本传指针 float *px*px 5.0f影响*px 访问的是实参的内存地址传指针 float *pxpx 其他指针不影响修改的是指针形参自己不是它指向的数据3. 完整代码与逐行解读直接能跑的验证程序理解归理解代码还是要一行一行落到实处。下面就是我建议初学者完整敲一遍的参考实现。3.1 完整参考代码#include stdio.h void swap(float *px, float *py); int main(void) { float a 3.14f, b 2.71f; printf(交换前a %f, b %f\n, a, b); swap(a, b); printf(交换后a %f, b %f\n, a, b); return 0; } void swap(float *px, float *py) { float temp; temp *px; *px *py; *py temp; }把代码放进任意一个 C 语言编译环境GCC、Clang、VS 或者在线编译器都可以编译运行你会看到交换前a 3.140000, b 2.710000 交换后a 2.710000, b 3.140000到这一步题目要求的主函数验证就算完成了。3.2 函数体三行代码的逐句拆解float temp;这行声明了一个临时变量它的类型必须是 float不是指针。为什么因为我们要拿来暂存*px的值——也就是一个实型数据本身。temp *px; // 第一步把 px 指向的地址里的 float 值复制到 temp 中 *px *py; // 第二步把 py 指向的地址里的 float 值写入 px 指向的地址 *py temp; // 第三步把 temp 中保存的原 *px 值写入 py 指向的地址这三步就是经典的“三杯水交换法”两杯水要互换必须有一个空杯做中转。这里 temp 就是空杯。如果你省掉 temp直接写*px *py; *py *px;那么第一步执行完px 指向的地址已经被覆盖成原来的*py值原来的*px数据已经丢了。第二步执行时*py再拿到的还是原来的*py结果就是两个变量都变成了原来 py 的值交换失败。这种“覆盖后丢失”的错误是初学指针时最经典的翻车案例。3.3 主函数验证到底在验证什么很多人觉得验证就是把程序跑一遍看到“交换前后”两行输出就成了。其实验证的核心是“比较”交换后的 a 是否等于交换前的 b交换后的 b 是否等于交换前的 a。如果只是肉眼扫一眼输出万一你选的数不够明显比如 1.03 和 1.02用%.2f打印出来几乎看不出变化验证就失去了意义。更稳妥的做法是把验证写进代码里让程序自己判断float old_a a; float old_b b; swap(a, b); if (a old_b b old_a) { printf(验证成功a %f, b %f\n, a, b); } else { printf(验证失败\n); }这里直接用做 float 比较是安全的因为 a 的值是从 b 原样拷贝过来的不是经过复杂运算产生的新浮点结果不涉及精度误差问题。这个验证方式比单纯打印更严谨能让你一眼看出 swap 到底有没有生效。4. 现场排错实录新手最常见的五种翻车方式代码本身不长但新手写起来翻车姿势多到超出想象。下面这些错误我见过太多次每一个都是真实调试现场的血泪经验。4.1 错误一调用时忘记写 直接 swap(a, b)这是出现频率最高的问题。函数要求的是指针你传了一个 float 变量进去。编译器会报警告或者错误有些编译器会提示makes pointer from integer without a cast。如果你强行编译运行程序大概率崩溃——因为函数拿到 a 的数值后会把 3.14 的浮点编码当成一个内存地址去访问这个地址十有八九是非法地址直接段错误。修正方式只有一个swap(a, b)。取地址运算符是这一刻的主角。4.2 错误二把临时变量声明成了指针有人知道要定义临时变量但写成了void swap(float *px, float *py) { float *temp; *temp *px; // ... }temp 被声明成指针却没有被初始化。它根本不知道指向哪里*temp *px就是在向一个随机地址写入数据。这类野指针问题在 C 语言里极其危险轻则数据被莫名篡改重则程序直接崩溃。正确写法是float temp;——你需要的只是一个装 float 数据的盒子不是一个门牌号。4.3 错误三把 *px 和 px 混为一谈在函数体内写px py而不是*px *py这是概念性错误。px py的意思是让 px 这个指针变量存储 py 保存的地址结果只是两个指针指向了同一个位置float 变量的内存内容完全没动。运行之后main 函数里的 a、b 还是原来的值你还会百思不得其解明明函数执行了为什么没效果排查技巧在 swap 里临时打印px、py、*px、*py这四个值。打印*px能看到数据本身打印px能看到地址。如果你发现函数执行前后*px的值没变而 px 的地址变了那基本就是写了px py这类代码。4.4 错误四只用一个 printf 看结果且数据选得不直观有人主函数写成printf(交换后a %f, b %f\n, a, b);但没有在主函数里写“交换前”的输出或者两个数选得特别接近比如 1.01 和 1.02。这样即使交换成功你肉眼也未必能分辨。建议选差异明显的数值做验证而且一定要在调用前后都打印。前面提到的 if 判断验证法也建议直接加进去。4.5 错误五想用位运算或加减法交换 float整数变量可以用异或交换这个技巧不少同学从书上见过。于是有人想当然地用异或去交换 float*px *px ^ *py; *py *px ^ *py; *px *px ^ *py;这在 float 上是行不通的。异或是按二进制位运算float 的内存布局是 IEEE 754 标准符号位、指数位、尾数位都不在参与异或运算后保证能变回合法浮点数。同样加减法交换也存在浮点精度风险比如大数加小数后可能丢失精度。所以在 swap 的标准实现里老老实实用 float temp 中转是最可靠、最不易出错的方案。5. 扩展思路从交换两个数到操作数组元素这道题的边界只是“两个实型变量”但真正读懂它的人会立刻想到它在真实程序里的泛化场景数组元素交换、排序算法、多值返回等。5.1 把 swap 用到数组中排序的基础就有了如果有一个 float 数组float arr[5] {5.0f, 3.0f, 4.0f, 1.0f, 2.0f};想交换下标 i 和下标 j 的两个元素只需要这样调用swap(arr[i], arr[j]);因为arr[i]是一个 float 类型的左值对它取地址得到的类型正好是float *和 swap 的参数完全匹配。冒泡排序、选择排序里最核心的“交换元素”动作底层就是这样一个 swap 函数。很多排序算法的完整代码中单独写一个 swap 辅助函数就是因为这个操作太通用、太常用了。// 以冒泡排序为例的简单片段 for (int i 0; i 4; i) { for (int j 0; j 4 - i; j) { if (arr[j] arr[j 1]) { swap(arr[j], arr[j 1]); } } }5.2 指针移动时的隐蔽陷阱别把地址和数据搞混在数组中用指针遍历并交换连续元素时有个特别容易犯的错。假设你写循环的时候图省事让一个指针p从数组头开始走每次交换swap(p, p 1)后执行p 2这里的p 1不是地址加 1 字节而是加一个float类型的宽度也就是 4 字节。如果不理解这一点交换对象会错位数据越跳越乱。另外继续强调一次写成swap(arr[i], arr[i 1])是错的arr[i]是数据本身不是地址。写成swap(arr[i], arr[i 1])才是正解。调错的时候错误可能只表现为“数组数据没变化”但排查起来会非常耗时间。5.3 给 swap 加一道防线空指针检查工程实践中函数接收外部传入的指针时第一件事通常是检查指针是否为空。swap 也可以加void swap(float *px, float *py) { if (px NULL || py NULL) { return; } float temp *px; *px *py; *py temp; }这样即使调用方误传了空指针函数也只是静默返回不会造成段错误。不过需要说明教学题目中往往不要求也不建议额外加这种检查因为题目隐含的假设是“传入的指针一定是有效实参的地址”。但从工程代码的安全性角度看这是一个值得养成的习惯。你也可以思考一个问题加了检查之后如果同时有人传了px pyswap 会怎样答案是函数正常运行temp *px后*px *py和*py temp都是写同一个地址数据不变这也是一个边界情况的讨论。最后再分享一点我的个人体会这类基础题目光看懂不算会一定要亲手编译运行一遍再故意把代码改成错误版本观察错误现象。有一次我给 A 同学演示故意把a, b改成a, b程序直接崩溃那一刻他对“指针参数需要取地址”的理解比看十遍书都深刻。多出几次错指针这点事就真正长在身上了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑