到目前为止,你的所有C++代码可能都挤在 main 这个“房间”里。对于小程序来说,这没问题。但想象一下,当你的程序需要处理成百上千行代码时,main 会变得多么混乱不堪,无法维护!

现在,我们要学习如何“组织”代码。我们将使用C++中最强大的组织工具:函数 (Functions)

函数,就是您可以重复使用的“代码积木”。

一个简单的比喻:“餐厅经理”与“专业厨师”

  • main 函数是“餐厅经理”。
  • “经理”的工作是协调一切(比如“接待客人”、“下单”、“上菜”),但他不应该亲自下厨。
  • 函数”就是“经理”雇佣的“专业厨师”(比如 bakeBread() 厨师 或 makeSalad() 厨师)。

当“经理”main 需要面包时,他不会自己去和面、揉面、烤制(在main里写一大堆代码),他只需要“调用”(Call)他的“面包厨师”:
bakeBread();

这种方式让 main(经理)的工作保持简洁,并且,如果餐厅的5个不同地方都需要面包,经理不需要重复5次烤面包的流程,他只需要**“调用”5次** bakeBread() 厨师就行了。

这就是函数的魔力:“不要重复你自己” (Don’t Repeat Yourself - DRY)

在本教程中,你将学会:

  • 什么是函数:为什么它是“代码复用”的基石。
  • 函数的“生命三部曲”:定义 (Definition)、调用 (Call) 和声明 (Declaration)。
  • 函数的“输入”:如何通过参数 (Parameters) 把数据“递”给函数。
  • 函数的“输出”:如何通过 return 语句把结果“递”回给调用者。
  • void 的含义:“只干活,不汇报”的函数。
  • 新手的“头号噩梦”:为什么函数没能修改我的变量?(“传值”的“影印本”陷阱)。
  • “X光透视”:用调试器**“跳进”**函数内部,看清它的每一步。

前置知识说明:

  • ✔️ 你需要知道如何声明变量(存储数据的“盒子”)。
  • ✔️ 你需要知道如何编译和运行C++程序(“烤蛋糕”的比喻)。

第一部分:函数的“解剖图”

让我们来“解剖”一个典型的“专业厨师”(函数):

int add(int num1, int num2)
{
int sum = num1 + num2;
return sum;
}

  • int (在最前面): 这是“返回类型” (Return Type)。
    • 翻译: “这位‘厨师’在工作完成后,会递还给你一个什么类型的东西?”
    • 在这里,int 意味着他会递还给你一个“整数盒子”。
  • add : 这是“函数名” (Function Name)。
    • 翻译: “这位‘厨师’的名字叫 add。”
  • (int num1, int num2) : 这是“参数列表” (Parameter List)。
    • 翻译: “这位‘厨师’开始工作前,你必须递给他两个‘整数盒子’,他把它们分别命名为 num1num2。”
  • { ... } : 这是“函数体” (Function Body)。
    • 翻译: “这是这位‘厨师’的**‘独家菜谱’**。”
  • return sum; : 这是“返回语句” (Return Statement)。
    • 翻译: “厨师把 sum 盒子里装着的结果,正式递还给‘经理’。”

第二部分:“实战演练”——雇佣你的“厨师”

类型1:“只干活,不汇报” (void 函数)

void 是一个关键词,意思是“虚空”或“”。
当“返回类型”是 void 时,意味着这位“厨师”不会递还给你任何东西。他只是默默地完成工作。

function1.cpp

#include <iostream>
using namespace std;

// --- “厨师”的“菜谱” (函数定义) ---
// 返回类型是 void,参数列表是空的 ()
void printWelcomeMessage() {
    cout << "---------------------" << endl;
    cout << " 欢迎来到C++的世界! " << endl;
    cout << "---------------------" << endl;
}

// --- “经理”的主程序 ---
int main() {
    cout << "经理:准备开始..." << endl;
    
    // --- “经理”"调用"(Call)“厨师” ---
    printWelcomeMessage();
    
    cout << "经理:工作完成。" << endl;
    
    return 0;
}

“手把手”终端模拟:

PS C:\MyCode> .\function1.exe
经理:准备开始...
---------------------
 欢迎来到C++的世界! 
---------------------
经理:工作完成。

顿悟时刻: main 函数的执行流在 printWelcomeMessage(); 这一行,“”到了函数内部,执行完函数里的三行 cout 后,又“”了回来,继续执行 main 的下一行。

类型2:“给数据,不汇报” (带参数的 void 函数)

现在,“经理”main 想让“厨师”打印出指定的年龄。

function2.cpp

#include <iostream>
using namespace std;

// --- “厨师”的“菜谱” ---
// 这次,"参数列表"里要求一个 int 盒子,厨师叫它 "age"
void printUserAge(int age) {
    cout << "厨师报告:我收到了 " << age << endl;
    cout << "您的年龄是: " << age << endl;
}

int main() {
    int myAge = 25;
    
    // "调用"厨师,并把 25 "递"给他
    printUserAge(myAge);
    
    // 你也可以直接"递"一个数字
    printUserAge(80);
    
    return 0;
}
  • “行内预警”:参数 (Parameter) vs 实参 (Argument)
    • int age:是参数 (Parameter)。这是“厨师”在他的“菜谱”里给输入起的名字(形参)。
    • myAge80:是实参 (Argument)。这是“经理”在调用时,实际递过去的“东西”(实参)。
类型3:“给数据,也汇报” (带 return 的函数)

这是最强大的函数。“经理”递给“厨师”两个数字,“厨师”把“”递回来。

function3.cpp

#include <iostream>
using namespace std;

// --- “厨师”的“菜谱” ---
// 返回类型是 int,意味着他“承诺”会递回一个整数
int add(int num1, int num2) {
    int sum = num1 + num2;
    // “厨师”通过 return 语句“递回”结果
    return sum;
}

int main() {
    int a = 10;
    int b = 20;
    
    // 1. “经理”调用厨师,并“等待”他递回结果
    // 2. 厨师递回了 30
    // 3. “经理”用“=”号,把 30 存入自己的“盒子” total
    int total = add(a, b);
    
    cout << "经理报告:总和是 " << total << endl; // 打印 30
    
    return 0;
}

第三部分:新手的“头号噩梦”——“影印本”陷阱

你可能会想:“如果‘厨师’修改了他收到的‘盒子’,‘经理’的‘盒子’会变吗?”
让我们来试试…

trap.cpp

#include <iostream>
using namespace std;

// 这个“厨师”试图把收到的盒子里的值改成 999
void tryToChange(int originalBox) {
    originalBox = 999;
    cout << "厨师说:我的盒子现在是 " << originalBox << endl;
}

int main() {
    int managersBox = 10;
    
    cout << "经理说:我的盒子现在是 " << managersBox << endl;
    
    // 经理把“10”递给了厨师
    tryToChange(managersBox);
    
    // 厨师干完活回来了...
    cout << "经理说:我的盒子 *还是* " << managersBox << endl;
    
    return 0;
}

“手把手”终端模拟:

PS C:\MyCode> .\trap.exe
经理说:我的盒子现在是 10
厨师说:我的盒子现在是 999
经理说:我的盒子 *还是* 10

顿悟时刻:

  • “经理”的 managersBox 没有被改变
  • 为什么?
    • 在C++中,默认情况下,当“经理”main 调用 tryToChange(managersBox) 时,C++不会把“经理”的**“原版盒子”**递过去。
    • 相反,C++ 制作了一份“影印本” (originalBox),然后把这份“影印本”递给了“厨师”。
    • “厨师”从头到尾都在涂改那份“影印本”,“经理”的“原版盒子”根本没动过!
    • 这被称为“按值传递” (Pass-by-Value),它是C++中保护数据的一种重要机制。

第四部分:函数“声明” (The Prototype)

到目前为止,我们都把“厨师的菜谱”(函数定义)写在了 main(经理办公室)的前面

如果你把它写在 main后面,会发生什么?

// ...
int main() {
    int total = add(10, 20); // 经理:“我要调用 add 厨师!”
    return 0;
}

// “菜谱”在后面
int add(int num1, int num2) {
    return num1 + num2;
}

编译大爆炸!

  • 翻译: 编译器(“建筑检查员”)从上到下读取文件。当它读到 main 里的 add(10, 20); 时,它会“尖叫”:error: 'add' was not declared in this scope (“add 是个啥玩意?我没听说过!”)

解决方案:“函数原型” (Prototype)
你需要在 main 之前,给编译器一个“预告”或“名片”,这被称为“函数声明” (Declaration)。

#include <iostream>
using namespace std;

// --- “名片” (函数声明/原型) ---
// 告诉编译器:“嘿,别慌,后面会有一个叫 add 的厨师”
// “他需要2个int,并会返回1个int。相信我。”
// 注意,这里只需要“签名”,不需要“菜谱”{...},并且以分号 ; 结尾
int add(int num1, int num2);

// --- “经理”的主程序 ---
int main() {
    // 编译器看到这里,想起了“名片”,于是它不报错了
    int total = add(10, 20); 
    cout << "总和是: " << total << endl;
    return 0;
}

// --- “厨师”的“菜谱” (函数定义) ---
// “菜谱”的“签名”必须和“名片” *完全* 匹配
int add(int num1, int num2) {
    return num1 + num2;
}

第五部分:“X光透视”——“跳进”函数

你还是不理解数据是如何“跳”来“跳”去的?
让我们用“X光眼镜”(调试器)来亲眼看看

“X光”实战(基于 function3.cppadd 程序)
  1. 设置“暂停点”(断点):

    • 动作: 在VS Code中,把你的鼠标移动到代码的第16行int total = add(a, b); 那一行)的行号左边
    • 点击那个小红点,设置一个断点
  2. 启动“子弹时间”(F5):

    • 动作: 按下 F5 键。
    • 你会看到:
      1. 程序开始运行…
      2. 程序“冻结”在第16行(黄色高亮)。
      3. 在左侧“变量”窗口中,你看到 a: 10b: 20total 还是“垃圾值”。
  3. “跳进”函数(F11):

    • 动作: 按下 F11(在VS Code中叫“Step Into”,单步调试进入)。不要按 F10
    • 你会看到:
      1. 高亮条瞬间“跳”到了第8行int add(...))!你**“跳进”**了“厨师”的“厨房”里。
      2. 在左侧“变量”窗口中,main 的变量消失了,取而代之的是函数内部的变量:
        • num1: 10 (它从 a 拿到了“影印本”)
        • num2: 20 (它从 b 拿到了“影印本”)
        • sum: [垃圾值]
  4. “慢放”一步(F10):

    • 动作: 按下 F10(“Step Over”,单步跳过)。
    • 你会看到:
      1. 高亮条移动到 return sum; 这一行。
      2. 在“变量”窗口中,sum 的值变成了 30
  5. “跳出”函数(Shift + F11 或 F10):

    • 动作: 再按一次 F10(或者 Shift+F11,“Step Out”)。
    • 你会看到:
      1. 高亮条**瞬间“跳回”**了 main 函数的第16行。
      2. “变量”窗口重新显示 main 的“盒子”
      3. total 的值从“垃圾值”更新为了 30
    • 顿悟时刻: 你亲眼见证了 return 30 这个动作,是如何把数据“隔空”传递回 main 并存入 total 盒子的。

动手试试!(终极挑战:你的第一个“工具箱”)

现在,你来当一次“工具”的制造者。你需要编写两个“专业厨师”:

#include <iostream>
using namespace std;

// --- TODO 1: “名片” (原型) ---
// 在这里为你的两个函数写“名片”
// 1. 一个叫 calculateArea, 需要2个int, 返回1个int
// 2. 一个叫 findMax, 需要2个int, 返回1个int


int main() {
    int width = 10;
    int height = 5;
    
    // 调用你的“面积厨师”
    int area = calculateArea(width, height);
    cout << "10x5 的面积是: " << area << endl; // 应该打印 50
    
    // 调用你的“比较厨师”
    int maxVal = findMax(width, height);
    cout << "10 和 5 之间,最大的是: " << maxVal << endl; // 应该打印 10
    
    return 0;
}

// --- TODO 2: “菜谱” (定义) ---
// 在这里写你的两个函数的“菜谱” (函数体)

// int calculateArea( ... ) {
//     // 提示: 面积 = 宽 * 高
//     // ...
//     // return ...
// }

// int findMax( ... ) {
//     // 提示: 你需要一个 if-else 语句
//     // if (num1 > num2) { ... } else { ... }
//     // ...
//     // return ...
// }

这个挑战同时检验了你对 if-elsefunctions 的理解。欢迎在评论区分享你的“工具箱”代码!

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐