跳转至

02-函数与作用域:把代码封装成工具

目录页:见 C++ 小白教程目录

目标

  • 把一段重复代码封装成函数并正确调用
  • 分清“声明/定义”“形参/实参”“返回值/返回类型”
  • 彻底搞懂作用域:变量在哪能用、什么时候消失

先决知识

  • 会写 int main() 并能用 std::cin/std::cout 输入输出(见 01 章 0.1、0.4、1.1、1.2)
  • 会用 if/for/while 写基础逻辑(见 01 章 4、5)

1. 为什么需要函数:把重复劳动交给“机器”

当你的程序开始出现重复代码时,函数就是“把重复劳动装进一个按钮”:

  • 专业名称:函数(function)
  • 类比:像一台机器,你把材料放进去(参数),按下开关(调用),机器吐出结果(返回值)
  • 作用:减少重复、让代码更好读、更好改、更不容易出错
  • 规则/坑点:函数要么定义在使用之前,要么提前写声明(函数原型)

2. 函数的 4 个关键点:名字、输入、输出、实现

先看一个完整可运行的例子:写一个 max2,返回两个整数里的较大值。

#include <iostream>

int max2(int a, int b) {
    if (a > b) {
        return a;
    }
    return b;
}

int main() {
    int x = 0;
    int y = 0;
    std::cin >> x >> y;

    int ans = max2(x, y);
    std::cout << "max=" << ans << '\n';
    return 0;
}

int max2(int a, int b) { ... } 这行拆开:

  • int:返回类型(return type)
  • max2:函数名(function name)
  • (int a, int b):参数列表(parameter list)
  • ab 是形参(parameter):函数“内部用的名字”
  • xy 是实参(argument):调用时“传进去的具体值”
  • { ... }:函数体(function body):机器内部怎么加工

return 的含义:

  • 专业名称:返回语句(return statement)
  • 类比:像“出货口把成品交给调用者”
  • 作用:结束函数并把一个值带出去
  • 规则/坑点:返回类型是 int 就必须返回一个 int;漏掉 return 会导致编译错误或未定义行为(取决于情况)

3. 声明 vs 定义:先挂招牌还是先开店

你会遇到一种常见写法:main 写在上面,函数实现写在下面。此时需要“先声明”。

  • 专业名称:函数声明/函数原型(function declaration / function prototype)
  • 类比:像先挂招牌:告诉别人“这家店叫什么、提供什么服务、输入输出是什么”,实现细节以后再写
  • 作用:让编译器在看到调用时,已经知道函数的签名(signature)
  • 规则/坑点:声明要和定义一致(返回类型、参数个数、参数类型)

可运行例子(函数定义放在 main 后面):

#include <iostream>

int max2(int a, int b);

int main() {
    int x = 0;
    int y = 0;
    std::cin >> x >> y;
    std::cout << "max=" << max2(x, y) << '\n';
    return 0;
}

int max2(int a, int b) {
    if (a > b) {
        return a;
    }
    return b;
}

这里 int max2(int a, int b); 就是声明:末尾有分号 ;,没有 {}


4. 参数传递(入门版):值传递 = 传“复印件”

这章先只讲你最常用也最安全的:值传递(pass by value)。

  • 专业名称:值传递(pass by value)
  • 类比:把实参复印一份交给函数,函数改的是复印件,不会影响原件
  • 作用:简单、安全、不容易改坏外面的变量
  • 规则/坑点:如果参数很大(比如很长的字符串或大结构体),复制会有成本,后面再讲引用/指针优化

看一个“新手最容易误解”的例子:你以为函数能把外面的 x 改掉,其实改不掉。

#include <iostream>

void setToZero(int x) {
    x = 0;
}

int main() {
    int x = 123;
    setToZero(x);
    std::cout << "x=" << x << '\n';
    return 0;
}

输出还是 x=123,因为函数里拿到的是一份复印件。要想真正修改外面的变量,需要引用/指针(放到第 04 篇讲)。


5. 作用域(scope):变量能活在哪些地方

你写的每个变量,都有“能用的范围”。这就是作用域。

  • 专业名称:作用域(scope)
  • 类比:变量像“房间里的物品”,你出了房间就看不见、也用不了
  • 作用:控制名字可见范围,避免到处乱用、互相撞名
  • 规则/坑点:C++ 里最常见的是块作用域:一对 {} 就是一个范围

5.1 块作用域:{} 里的变量,外面不能用

#include <iostream>

int main() {
    if (true) {
        int inside = 10;
        std::cout << "inside=" << inside << '\n';
    }

    return 0;
}

如果你在 if 外面写 std::cout << inside;,会编译失败:名字在外面不可见。

5.2 for 的计数器作用域

#include <iostream>

int main() {
    for (int i = 0; i < 3; i++) {
        std::cout << i << '\n';
    }
    return 0;
}

i 只在 for 的小括号和循环体里可见。循环结束后 i 就“消失”了。

5.3 变量生命周期(lifetime):什么时候创建、什么时候销毁

  • 专业名称:生命周期(lifetime)
  • 类比:变量像“临时工”,进入代码块上班,离开代码块下班
  • 作用:理解为什么变量会“消失”,也能理解后面指针为什么会危险
  • 规则/坑点:局部变量(local variable)离开作用域就销毁,再去用它会出大问题(后面会专门讲)

常见坑

  • 错误:在调用之前没有声明/定义函数
    结果:编译器找不到函数
    正确:把函数定义写在 main 前,或写函数声明(见第 3 节)

  • 错误:int 函数少写了 return
    结果:编译错误或行为不确定
    正确:保证所有路径都有 return(或重构逻辑)

  • 错误:想在函数里改外面的变量,但用了值传递
    结果:外面的变量不变
    正确:需要引用/指针(第 04 篇再做)

  • 错误:把变量定义在 {} 里,出了 {} 还想用
    结果:编译失败
    正确:把变量定义到需要使用的共同外层作用域


小练习

练习 1:写 3 个小工具函数

  • 题目:实现 max2min2absInt
  • 输入/输出:自行在 main 里读入两个整数并输出三个结果
  • 约束:只用 ifreturn,先别用三目运算符
  • 提示:absInt(x):如果 x<0 返回 -x,否则返回 x
  • 目标:练函数定义、返回值、调用

练习 2:求和函数

  • 题目:实现 int sum1ToN(int n),返回 1+2+...+n
  • 输入/输出:输入 n,输出 sum=...
  • 约束:n 为正整数
  • 提示:函数里用 for/while 都行
  • 目标:练“函数里写循环”

练习 3:判断素数函数(复用上一章思路)

  • 题目:实现 bool isPrime(int n)
  • 输入/输出:输入 n,输出 prime=1prime=0
  • 约束:n >= 2
  • 提示:先用最朴素的试除写法即可;想优化可以做到 i*i <= n
  • 目标:练返回 bool、函数逻辑分支

练习 4:做一个“计算器”程序(综合)

  • 题目:写一个循环菜单:1 加 2 减 3 乘 4 除 0 退出
  • 输入/输出:用户先输入选择,再输入两个数,输出结果
  • 约束:除法要处理除数为 0(提示错误并继续循环)
  • 提示:把加减乘除分别写成 4 个函数;main 只负责菜单与调度
  • 目标:练“把大程序拆成小函数”,感受可读性提升

小结

你现在应该掌握:

  • 函数的基本结构:返回类型、函数名、参数列表、函数体
  • return 的意义与“必须返回”的规则
  • 声明/定义的区别,以及为什么需要函数原型
  • 作用域与生命周期:变量能用的范围、什么时候消失

下一篇会把“一组数据”放进数组和字符串里,让你能处理更真实的输入。