当前位置: 首页 > news >正文

Day4 函数

不需要知道手机是怎么制造的,也能熟练地使用它;不需要了解软件的内部实现,也能正常运行它。

函数,就是编程世界里的"手机"和"软件"。

以"比较两个数大小"为例:如果没有函数,每次需要比较大小时,都得在main函数里重新写一遍逻辑;而有了函数,只需要把这段逻辑封装起来,之后无论在哪里需要比较大小,直接调用这个函数即可,不必关心它内部是怎么实现的。

这就是函数的核心价值:封装细节,暴露接口,让代码可以被复用。

二、函数声明与定义分离

在 C++ 中,函数有三个相关概念,职责各不相同:

  • 函数声明:告诉编译器"这个函数存在",让编译器知道它的名称、参数和返回值类型
  • 函数定义:真正实现函数的功能,包含具体的执行逻辑
  • 函数调用:在需要的地方使用这个函数

三者的关系可以类比为:声明是"提前打招呼",定义是"真正做事",调用是"发出指令"。

下面用一段代码来展示:

#include <iostream> using namespace std; int add(int a, int b); // 声明:放在头部或 .h 文件中,告知编译器函数的存在 int main() { int result = add(3, 5); // 调用:直接使用,不需要关心内部实现 cout << "结果为:" << result << endl; return 0; } int add(int a, int b) { // 定义:具体实现加法逻辑 return a + b; }

💡为什么要分离?在大型项目中,声明通常放在.h头文件里,定义放在.cpp源文件里。这样其他文件只需引入头文件就能使用函数,而不必关心实现细节——和第一节的"手机"类比一脉相承。

三、参数的三种传递方式

值传递:将变量的值复制一份传给函数,函数操作的是副本,原变量不受影响。
引用传递:传递的是变量的别名,函数内部操作的就是原变量本身,修改立即生效。
指针传递:传递变量的内存地址,通过解引用*p间接修改原变量。效果类似引用传递,但语法更繁琐,C++ 中优先使用引用。


下面用swap(交换两数)这个经典场景来对比三种方式的实际效果:

#include <iostream> using namespace std; // ❌ 值传递:交换的只是副本,外部 a、b 不变 void swap_wrong(int a, int b) { int t = a; a = b; b = t; } // ✅ 引用传递:直接操作原变量,交换成功 void swap_right(int& a, int& b) { int t = a; a = b; b = t; } // 了解即可:指针传递,效果同引用,但写法繁琐 void swap_ptr(int* a, int* b) { int t = *a; *a = *b; *b = t; } int main() { int x = 3, y = 5; swap_wrong(x, y); cout << "值传递后:x=" << x << " y=" << y << endl; // x=3, y=5(未变) swap_right(x, y); cout << "引用传递后:x=" << x << " y=" << y << endl; // x=5, y=3(成功) return 0; }
四、默认参数(了解即可)

讲两个要点,教材常漏的细节:

  • 默认参数必须从右往左设置,不能跳着来
  • 声明和定义分开时,默认参数只写在声明里,定义里不写(否则编译报错)
void greet(std::string name, std::string prefix = "你好,"); // 定义里不再写默认值 void greet(std::string name, std::string prefix) { std::cout << prefix << name << "\n"; }

五、函数重载

C++ 允许定义多个同名函数,只要它们的参数类型或数量不同,编译器就能在调用时自动区分。这种机制叫做函数重载。

注意:返回值类型不同不能构成重载。

下面用print函数演示三个重载版本:

#include <iostream> #include <string> using namespace std; void print(int x) { cout << "整数:" << x << endl; } void print(double x) { cout << "浮点数:" << x << endl; } void print(string x) { cout << "字符串:" << x << endl; } int main() { print(42); // 调用 print(int) print(3.14); // 调用 print(double) print("hello"); // 调用 print(string) return 0; }

🤔思考题:为什么返回值不能用于区分重载?

假设存在这样两个函数:

int func(); double func();

当你写下func();时,编译器该调用哪个?它无从判断——因为你没有使用返回值,或者返回值被赋给了一个可以隐式转换的变量。这种调用歧义是返回值无法参与重载决议的根本原因。


六、内联函数inline

普通函数调用时,程序需要跳转到函数地址、保存现场、执行、再返回。对于极短的函数,这个"来回跳转"的开销反而比函数本身的逻辑还贵。

inline的作用就是建议编译器:把函数体直接展开在调用处,省去跳转开销。

#include <iostream> using namespace std; inline int square(int x) { return x * x; } int main() { cout << square(4) << endl; // 编译器可能展开为:cout << 4 * 4 << endl; return 0; }

调用square(4)时,编译器可以直接把代码替换成4 * 4,完全省掉函数调用的开销。

适用场景与注意事项:

  • inline适合逻辑极简的函数(1-3 行),如取绝对值、取最大值、简单的 getter
  • 函数体过长时,编译器通常会忽略inline建议,仍按普通函数处理
  • 开启-O2优化后,编译器会自动判断并内联合适的函数,手动写inline更多是一种语义提示,告诉读者"这是一个轻量函数"
  • inline函数通常定义在头文件中,因为每个调用它的编译单元都需要看到完整的函数体
⚠️ 坑1:值传递修改无效

初学者最常踩的坑。以为传进去改了,外面就变了——实则函数操作的是副本。

cpp

void swap_wrong(int a, int b) { int t = a; a = b; b = t; // 只交换了副本,外部毫无变化 } int main() { int x = 3, y = 5; swap_wrong(x, y); cout << x << " " << y << endl; // 仍然输出:3 5 }

解决:改用引用传递int& a, int& b


⚠️ 坑2:返回局部变量的引用

函数执行结束后,其栈帧会被销毁,局部变量随之消失。若返回该变量的引用,调用方拿到的是一个指向已释放内存的悬空引用,访问它是未定义行为——可能崩溃,也可能得到随机值,比崩溃更难排查。

cpp

int& danger() { int x = 42; return x; // ❌ x 在函数返回后已被销毁 } int main() { int& ref = danger(); cout << ref << endl; // 未定义行为,结果不可预期 }

解决:返回值而非引用,或将变量声明为static(生命周期延长至程序结束),或使用堆上分配的对象。


⚠️ 坑3:默认参数重复声明

默认参数只能写一次。声明和定义分离时,默认参数写在声明处,定义处不再重复,否则编译器会报"重复指定默认参数"错误。

cpp

// ✅ 正确:默认参数写在声明处 void greet(string name, string msg = "你好"); // 声明(.h 文件) void greet(string name, string msg) { // 定义(.cpp 文件),不再写默认值 cout << msg << "," << name << endl; } // ❌ 错误:定义处重复写默认参数 void greet(string name, string msg = "你好") { // 编译报错 cout << msg << "," << name << endl; }

八、实战练习

综合前面所学,实现一个简单计算器。要求包含四个运算函数,并通过引用参数处理除零异常,而不是直接返回错误值。

设计思路:

  • add/subtract/multiply直接返回结果
  • divide引用参数bool& ok返回是否成功,避免除以零崩溃
  • main函数中逐一调用,演示正常与异常两种情况

cpp

#include <iostream> #include <string> using namespace std; double add(double a, double b) { return a + b; } double subtract(double a, double b) { return a - b; } double multiply(double a, double b) { return a * b; } // ok 为引用参数:成功时置 true,除零时置 false double divide(double a, double b, bool& ok) { if (b == 0) { ok = false; return 0; } ok = true; return a / b; } int main() { double x = 10, y = 3; cout << "加法:" << x << " + " << y << " = " << add(x, y) << endl; cout << "减法:" << x << " - " << y << " = " << subtract(x, y) << endl; cout << "乘法:" << x << " * " << y << " = " << multiply(x, y) << endl; bool ok; double result = divide(x, y, ok); if (ok) { cout << "除法:" << x << " / " << y << " = " << result << endl; } // 演示除零情况 result = divide(x, 0, ok); if (!ok) { cout << "除法:" << x << " / 0 → 除零错误,操作取消" << endl; } return 0; }

输出结果:

加法:10 + 3 = 13 减法:10 - 3 = 7 乘法:10 * 3 = 30 除法:10 / 3 = 3.33333 除法:10 / 0 → 除零错误,操作取消
九、小结+明日预告

函数让代码有了结构,下一天给函数传更复杂的数据——数组和字符串。

http://www.jsqmd.com/news/883715/

相关文章:

  • 抖音下载器终极指南:如何快速下载抖音视频和直播回放
  • 马鞍山6月雨季来临,房屋漏水怎么办?卫生间免砸砖防水、外墙、屋面+地下室渗漏。权威防水公司靠谱TOP5推荐(2026年6月本地最新深度调研) - 企业资讯
  • Python Android打包终极指南:5步将Python应用变为Android APK
  • 江苏省丹阳寄快递省钱攻略|本地人私藏靠谱低价寄件渠道,跨省寄件轻松省下一笔钱 - 时讯资讯
  • 2026惠州防水深度测评!破解沿海湿热漏水通病,四大卫生间补漏品牌甄选 - 资讯焦点
  • 如何解锁网易云音乐加密文件?ncmdump让你轻松拥有通用音频格式
  • 2026-05-25 Systematic review of foundation models for structured electronic health records
  • 融合教育影子教师证交钱前怎么判断机构是否正规 - 最新教育培训热点
  • 如何轻松定制COM3D2角色:终极玩家指南与实时编辑器秘籍
  • 2026年Hermes Agent/OpenClaw如何集成?阿里云高可用安装及Token Plan配置
  • Proteus例程导入方法
  • 北京公司注册找谁家?2026最新梯队推荐 + 避坑指南 - 博客湾
  • 扩散模型diffusion
  • 龙岩6月雨季来临,房屋漏水怎么办?卫生间免砸砖防水、外墙、屋面+地下室渗漏。权威防水公司靠谱TOP5推荐(2026年6月本地最新深度调研) - 企业资讯
  • 3步实现浏览器端HTML转Word文档:html-docx-js实战指南
  • 哔哩下载姬DownKyi完整指南:快速获取B站高清视频的终极方案
  • 【AI代码审查新纪元】:DeepSeek为何比GitHub Copilot Code Review准确率高42%?
  • Whisper-WebUI:一站式语音转字幕解决方案在Mac上的完美部署指南
  • 亳州6月雨季来临,房屋漏水怎么办?卫生间免砸砖防水、外墙、屋面+地下室渗漏。权威防水公司靠谱TOP5推荐(2026年6月本地最新深度调研) - 企业资讯
  • 在SCnet上部署70b int4的模型
  • FM广播高精度预加重模块设计:解决传统电路缺陷,提升音质与信噪比
  • 终极3步驱动清理:如何用DriverStore Explorer释放Windows性能
  • 告别短信验证码:在uni-app中集成阿里云一键登录的完整配置与优化心得
  • 为什么你的DeepSeek总生成无效边界值?揭秘LLM测试生成中的3层语义断层与2种对齐方案
  • 分子对接的困境与突围:为什么AutoDock-Vina能成为药物发现的加速引擎?
  • 手把手教你用PE镜像修复麒麟系统磁盘异常(Boot From Harddisk故障保姆级教程)
  • 淮北6月雨季来临,房屋漏水怎么办?卫生间免砸砖防水、外墙、屋面+地下室渗漏。权威防水公司靠谱TOP5推荐(2026年6月本地最新深度调研) - 企业资讯
  • 阜阳6月雨季来临,房屋漏水怎么办?卫生间免砸砖防水、外墙、屋面+地下室渗漏。权威防水公司靠谱TOP5推荐(2026年6月本地最新深度调研) - 企业资讯
  • 衢州6月雨季来临,房屋漏水怎么办?卫生间免砸砖防水、外墙、屋面+地下室渗漏。权威防水公司靠谱TOP5推荐(2026年6月本地最新深度调研) - 企业资讯
  • 如何在Windows系统上完美运行Android应用:WSABuilds终极解决方案指南