不需要知道手机是怎么制造的也能熟练地使用它不需要了解软件的内部实现也能正常运行它。函数就是编程世界里的手机和软件。以比较两个数大小为例如果没有函数每次需要比较大小时都得在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; // x3, y5未变 swap_right(x, y); cout 引用传递后x x y y endl; // x5, y3成功 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值传递修改无效初学者最常踩的坑。以为传进去改了外面就变了——实则函数操作的是副本。cppvoid 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返回局部变量的引用函数执行结束后其栈帧会被销毁局部变量随之消失。若返回该变量的引用调用方拿到的是一个指向已释放内存的悬空引用访问它是未定义行为——可能崩溃也可能得到随机值比崩溃更难排查。cppint 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 → 除零错误操作取消九、小结明日预告函数让代码有了结构下一天给函数传更复杂的数据——数组和字符串。
Day4 函数
发布时间:2026/5/25 12:54:36
不需要知道手机是怎么制造的也能熟练地使用它不需要了解软件的内部实现也能正常运行它。函数就是编程世界里的手机和软件。以比较两个数大小为例如果没有函数每次需要比较大小时都得在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; // x3, y5未变 swap_right(x, y); cout 引用传递后x x y y endl; // x5, y3成功 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值传递修改无效初学者最常踩的坑。以为传进去改了外面就变了——实则函数操作的是副本。cppvoid 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返回局部变量的引用函数执行结束后其栈帧会被销毁局部变量随之消失。若返回该变量的引用调用方拿到的是一个指向已释放内存的悬空引用访问它是未定义行为——可能崩溃也可能得到随机值比崩溃更难排查。cppint 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 → 除零错误操作取消九、小结明日预告函数让代码有了结构下一天给函数传更复杂的数据——数组和字符串。