C++函数传参详解:值传递、引用传递与指针传递的底层原理与考试应用

发布时间:2026/10/18 10:50:26
C++函数传参详解:值传递、引用传递与指针传递的底层原理与考试应用 最近在带学生准备GESP四级的时候发现一个特别有意思的现象一讲到函数参数的传参方式很多同学都能顺口背出值传递、引用传递、指针传递这几个名词但一到写代码就露馅了。尤其是引用传递和指针传递代码写出来长得像运行结果却完全不同错一次考试就丢分。这一讲咱们就把传参方式彻底讲透重点放在最容易出错的引用传参上同时把指针传参的坑一并填平结合GESP四级的实际考察方式来说清楚。GESP四级的函数部分真正拉开分数的其实不是语法考得多偏而是对传参方式底层原理的理解够不够深。值传递为什么改不了实参什么时候该用const引用指针和引用到底怎么选这些搞明白了不光应付考试后面学到结构体、链表、递归这些硬骨头时也能少踩很多坑。这篇文章适合正在准备GESP四级的学生也适合带竞赛或等级考试的辅导老师参考内容会尽量贴近考纲把理论和实操一次性说透。1. GESP四级函数部分到底在考什么1.1 传参方式是四级考纲里的核心考点GESP四级的大纲里函数与递归一直是核心考查板块而函数参数的传参方式又是这个板块里最基础、也最容易被反复出题的知识点。从历年的出题风格来看函数相关题目里至少会出现一到两道跟传参方式直接挂钩的题可能是概念选择题也可能是读程序写结果题或者程序填空题。很多同学觉得这块简单实际上在阅卷时因为传参方式理解不到位导致的失分非常常见。从考试层级的设计逻辑来看三级主要考察顺序结构、循环、数组这些编程基础能力到了四级才开始正式引入函数、结构体、指针这些真正意义上的程序设计内容。传参方式作为函数设计的核心环节自然成了区分度很高的考点。能够在题目里快速判断形参是实参的一份拷贝引用是实参的别名指针保存的是实参的地址同一道题拿满分还是丢一半分差别就在这里。1.2 形参和实参所有传参问题的起点想要把传参方式学明白第一步必须把形参和实参这两个概念彻底搞透。形参是函数定义时写在参数列表里的变量它只在函数被调用的那一刻才存在函数执行完就销毁了。实参是调用函数时真正传入的那个值或变量。比如定义void f(int x)这里的x是形参调用时写f(a)这里的a就是实参。很多同学容易混淆这两者其实可以这样理解实参是寄件人形参是收件地址。每次调用函数系统都把实参按特定规则送到形参那里而这个送的方式就是传参方式。值传递好比把包裹里的东西复印一份寄过去原稿不受影响引用传递好比把原件的存放地址告诉对方对方可以直接改原件指针传递好比把原件的存放柜编号告诉对方对方拿编号去开柜子改内容。这三种方式分别对应了不同的适用场景GESP四级要求的就是能分清它们并且会写。2. 三种传参方式的底层逻辑与区别2.1 值传递一切讨论的默认基准值传递是C里最自然的传参方式。函数定义成void func(int x)调用时实参的值会被拷贝一份赋给形参x函数体内所有的操作都是在这份拷贝上进行的实参本身完全不受影响。#include iostream using namespace std; void changeValue(int x) { x x 10; } int main() { int a 5; changeValue(a); cout a endl; // 输出 5实参没变 return 0; }这段代码的输出是5不是15。为什么因为changeValue(a)执行的时候系统把a的值5复制了一份给形参x函数里改的是x不是a。等函数执行完x销毁a还是原来的5。这个概念很多学生上课时都点头但一遇到读程序写结果的题还是会写错本质上是没有真正把拷贝这两个字刻在脑子里。值传递的优点是安全被调函数怎么折腾形参都不会影响外部变量不需要担心数据被意外修改。缺点也明显对于大对象来说每次调用都要做一次完整拷贝开销大、速度慢。在GESP四级的题目里一般不会出现特别大的对象所以不少同学觉得值传递够用了但到了后面学结构体、学递归值传递的开销问题就会暴露出来所以还是要提前建立拷贝成本这个意识。2.2 引用传递给实参起个外号引用传递的语法是在形参类型后面加一个比如void func(int x)。它的本质是形参x变成实参的一个别名两者其实是同一个变量的两个名字函数内部对x的任何修改都是在直接修改实参。#include iostream using namespace std; void changeReference(int x) { x x 10; } int main() { int a 5; changeReference(a); cout a endl; // 输出 15实参被修改了 return 0; }这里输出15。同样调用的写法几乎一样只是形参多了个结果就从改不动变成了改得动。很多学生第一次遇到这个区别时非常惊讶而这正是GESP四级喜欢出的考点给你一段代码问你主程序里的变量有没有被函数修改。能不能答对就看你能不能从函数定义的参数列表里发现那个藏在类型后面的。需要特别注意引用传递不会发生拷贝形参和实参共享同一块内存因此效率比值传递高尤其是参数是结构体或类对象时差别非常明显。同时引用传递也意味着被调函数可以修改外部实参这种副作用既是引用最大的用处也是最大的风险源。考试里常常会出现这样的陷阱函数内部改了引用参数主程序里某个变量的值悄悄变了结果题目刚好问你那个变量的最终值是多少。2.3 指针传递手动操控地址指针传递的语法是void func(int* p)调用时传入的是实参的地址比如func(a)。函数内部通过指针解引用*p来读写实参的值。#include iostream using namespace std; void changePointer(int* p) { *p *p 10; } int main() { int a 5; changePointer(a); cout a endl; // 输出 15 return 0; }指针传递和引用传递都能修改实参这是它们的共同点。区别在于指针是一个变量它保存的是地址可以重新指向别处也可能为空而引用是别名必须在定义时绑定一个存在的变量不能为空也不能重新绑定。从某个角度看引用就是不能为空、不能换绑的指针语法上更安全、更简洁。在GESP四级的题目里指针传参经常和数组联系在一起。数组名本身就是首元素的地址所以函数参数写成int arr[]和int* arr在传参层面是等价的。这个知识点在程序填空题里出现频率很高如果你知道数组传参的本质是指针传递很多填空题就能直接填出来。3. 引用传参的核心细节与实操要点3.1 什么时候必须用引用GESP四级考试里有几类场景基本是看到就必须想到引用的。第一类是需要在函数里修改实参的值比如交换、累加、更新数组元素等场景这时候只用值传递必然是错的要么用引用要么用指针。第二类是函数需要返回多个结果的时候比如要同时得到一个数的商和余数或者同时得到最大值的值和下标这时候除了返回值之外其他结果就得靠引用参数带出来。#include iostream using namespace std; // 同时计算商和余数通过引用参数带出 void divide(int dividend, int divisor, int quotient, int remainder) { quotient dividend / divisor; remainder dividend % divisor; } int main() { int q, r; divide(17, 5, q, r); cout 商: q , 余数: r endl; // 商: 3, 余数: 2 return 0; }第三类是参数本身是大型对象或结构体时为了减少拷贝开销也会用引用。虽然GESP四级的题目规模不会特别大但后面的五级、六级会考到提前养成大对象用引用的习惯只有好处。还有一种情况是递归函数里的引用。递归处理数组、链表这类需要逐层传递同一个容器的结构时如果用值传递每层递归都会拷贝一份完整容器时间和空间开销都会爆炸改成引用传递后所有递归层共享同一个容器效率天差地别。考试中的递归题目往往不会明说这里要用引用但复杂度过高时判题超时本质上就是传参方式选错了。3.2 const引用只读不写的最佳实践const引用是引用传参里一个经常被忽略、但在实际写法中非常重要的变体。格式是void func(const int x)意思是x是实参的引用但函数内部不允许修改x。这个写法既享受了引用传递的高效率又保证了实参不会被意外修改。#include iostream using namespace std; void printValue(const int x) { cout x endl; // x 10; // 编译错误const引用不允许修改 }为什么一定要强调const引用因为在实际开发中如果一个函数只负责读取参数、不需要修改参数最合理的写法就是const引用。很多学生习惯性全用值传递写起来是简单但对大对象来说浪费了拷贝时间全用引用又容易留下隐患万一函数里某行代码不小心改了实参bug非常难查。const引用恰好把这两个问题同时解决。在GESP四级考试中const引用的概念题也比较常见比如问以下哪个选项不会修改实参的值四个选项里混着值传递、普通引用、const引用和指针传递。只要记住值传递和const引用都不会修改实参普通引用和指针传递可能修改实参这类题基本就能秒答。同时还要注意一个细节const引用可以直接绑定常量比如func(5)是合法的而普通引用绑定常量会编译报错这个点偶尔会作为改错题的陷阱出现。3.3 传参方式的选择策略与考试判断技巧在实际做题和写代码时怎么快速决定用哪种传参方式我自己习惯按这个思路来先问函数需不需要修改实参不需要就选值传递或const引用需要修改再问参数类型是大对象还是基本类型小类型可以直接用引用大对象必须用引用或指针。最后问会不会有指针为空、需要重新指向的需求如果有就用指针否则一律用引用。对于GESP四级的考场上有个很实用的快速判断方法看到函数定义里形参带就是引用传递带*就是指针传递什么都不带就是值传递。题目问你调用后主程序变量是否改变只要形参是值传递答案是不变形参是引用或指针答案一般是会变。当然要注意例外比如引用参数前面加const或者指针指向的目标没被修改这些细节就要再细心一点。这里还要强调一个容易被忽略的考点函数重载与传参方式的组合。void f(int x)、void f(int x)、void f(const int x)这三个函数在C里是不能同时存在的因为调用f(a)时编译器无法区分。GESP四级偶尔会出这样的代码改错题去掉了就会报重定义或二义性错误。平时练习时多写几遍自然就有感觉了。4. GESP真题风格的函数设计实战4.1 经典考题交换两个变量的值交换两个变量的值可以说是传参方式里最经典的题目没有之一。很多学生第一次写交换函数时都会写出下面这个版本void swapWrong(int a, int b) { int temp a; a b; b temp; }调用swapWrong(x, y)之后x和y的值根本没变。为什么因为函数里交换的是形参a和b的拷贝实参压根没被碰过。正确的写法有下面几种// 写法一引用传递 void swapByRef(int a, int b) { int temp a; a b; b temp; } // 写法二指针传递 void swapByPtr(int* a, int* b) { int temp *a; *a *b; *b temp; }从代码可读性和安全性来看交换函数用引用是更好的选择调用时写swapByRef(x, y)干净利落不需要取地址符也不会出现指针为空的问题。GESP四级常常把交换函数和排序结合在一起考比如在冒泡排序或选择排序的实现里需要交换两个数组元素。如果你写的交换函数是值传递的整个排序就白做了排完序数组纹丝不动。这种错误在考场上特别隐蔽因为程序不会报错只会输出错误结果排查难度比编译错误高得多。4.2 经典考题函数返回多个结果C的return语句只能返回一个值但当题目要求函数同时得出多个结果时就需要靠传参方式来解决。典型的题目有求一个数的各位数字之和与各位数字之积、统计数组中正数和负数的个数、找出最大值和它的下标等。来看一个最常见的例子统计一个数组里大于等于某个阈值和小于该阈值的元素个数#include iostream using namespace std; void countByThreshold(int arr[], int n, int threshold, int geCount, int ltCount) { geCount 0; ltCount 0; for (int i 0; i n; i) { if (arr[i] threshold) { geCount; } else { ltCount; } } } int main() { int data[] {1, 8, 3, 9, 4, 10}; int ge, lt; countByThreshold(data, 6, 5, ge, lt); cout ge lt endl; // 输出 3 3 return 0; }这里geCount和ltCount就是靠引用带出来的第二个返回值。考试中这类题目经常出现在程序填空的最后一题出题人会把引用参数的声明留空让你填int geCount之类的内容。很多学生在这里犹豫填成int geCount结果函数内部统计完了外部变量还是0整个程序结果全错。关键是理解如果题目里要求函数更新某个外部变量并且外部要能使用更新后的值那么参数列表里就必须有引用或指针。4.3 递归与结构体中的传参陷阱递归函数的传参是一个更容易出问题的地方。以经典的求数组最大值递归为例如果用值传递把整个数组传进去每一层递归都会复制一遍数组在数据量稍大的时候程序会非常慢。更合理的做法是传数组名本质上是指针加首尾下标数组所有递归层共享一份数据。int findMax(int arr[], int l, int r) { if (l r) { return arr[l]; } int mid (l r) / 2; int leftMax findMax(arr, l, mid); int rightMax findMax(arr, mid 1, r); return leftMax rightMax ? leftMax : rightMax; }这个函数里int arr[]在编译器看来就是int* arr传的是地址没有拷贝所以效率是好的。GESP四级的递归题通常还会结合引用参数来写回溯类的程序比如走迷宫、生成排列等场景需要在一个共享容器上不断添加和撤销元素这个容器一般要用引用传递否则每层递归都会带着容器副本逻辑上就完全错了。结构体传参在四级也是常客。结构体变量可能包含多个成员值传递会把整个结构体完整复制一份如果结构体里有数组复制开销更大。考试里常见两种考法一是问以下哪种传参方式效率最高答案基本是引用或const引用二是给一段结构体传参的代码问某个成员在函数调用后被改成什么这时候你需要注意到形参是引用还是值传递。我的建议是看到结构体相关的函数先花三秒钟确认参数列表里的和const再看函数体绝大多数错误都是因为参数看漏了。5. 常见错误与排查技巧实录5.1 高频易错点盘点根据我平时带学生刷题的经验函数传参这块的错误主要集中在下面几类每一类我都见过无数遍易错点错误示范正确做法后果交换函数用值传递void swap(int a, int b)用引用或指针实参不变排序失效引用参数漏写void f(int x) 想改实参void f(int x)函数内修改无效指针传参忘记取地址f(a) 但形参是int*f(a)编译报错或乱改内存常量实参绑普通引用int f(int x)调用f(5)用const int编译错误不必要地修改实参只读函数用了普通引用用const引用数据被意外改动极难排查数组传参误以为安全以为数组传值拷贝数组传参即传地址函数内修改影响原数组这里特别要提一下数组和指针的关系。很多同学知道数组名是首地址但遇到二维数组时又乱了。二维数组做函数参数时第二维大小必须写上比如void f(int a[][5])因为编译器需要知道每行有多长才能计算地址偏移。GESP四级涉及二维数组的题目不少这个写法细节往往是程序填空题的考点。还有一个非常隐蔽的坑返回局部变量的引用。有些同学图省事写了类似int f() { int x 3; return x; }的代码。函数执行完后局部变量x的内存已被回收返回的引用指向一块失效的内存使用时会得到不确定的值程序还可能崩溃。考试里偶尔会有这类改错题看到函数返回引用、返回的却是局部变量基本可以断定是错误代码。5.2 排查思路与避坑建议如果程序运行结果不对怀疑是传参方式的问题我建议按下面这个顺序排查。第一步看函数定义参数列表里每个参数是值、引用还是指针把和*先圈出来。第二步检查调用处引用传参直接写变量名指针传参要加取地址。第三步在函数内部关键位置输出参数的值看修改是否生效。第四步如果涉及多个函数嵌套调用一层一层往上查看参数从源头传到最里层时类型有没有变化。排查时有个实用技巧在函数入口处打印参数的地址在函数内部修改后再打印一次然后回到主程序打印实参的地址和值。如果函数内外地址相同说明是引用或指针传递如果地址不同说明是值传递实参不会被修改。这个办法在Visual Studio的调试器里可视化效果更好学生也能借此直观理解引用和值的区别。在实际教学过程中我发现一个特别有效的练习方法让学生把同一个函数分别用值传递、引用传递、指针传递各写一遍然后分别观察实参变没变。三版代码放一起对比所有区别和坑基本都能暴露出来。GESP四级备考时间紧这个方法性价比很高建议你也试试。最后再分享一个小技巧考试时读程序写结果这类题读完函数定义先不要急着往下走先在参数列表上用笔标注值引用指针三个标记然后模拟调用时心里默念拷贝一份同一个变量传地址过去。这个习惯能帮你避免很多因为看漏符号造成的失分我在备考班反复强调之后学生的读程序题正确率明显提高。传参方式这个考点本质上就是考你细心程度和对底层内存模型的理解把这三句话刻在心里多刷几道真题二级到四级的函数题基本就不会再出问题了。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询