跳到内容

元数据卡

  • 前置知识:方法的基本定义与调用(ch05-methods-basics)
  • 预计时间:15 分钟
  • 完成标志:能够写出带参数和返回值的方法,理解值传递

回顾

上一节你学会了定义一个简单的 showBanner() 方法。但这个方法每次打印的都一样——铁匠铺的铁砧不能同时打剑和打盾啊。

"展示个横幅有啥用?"老陈敲了敲铁砧,"你得能告诉我——给谁打、打什么、用什么材料。"

一个方法如果只能做一模一样的事,那它的用处就太有限了。你需要的是:传进去不同的东西,做出不同的东西

第三幕:传参数进去

方法接收参数,就像老陈接收材料——你把矿石扔过去,他才知道要炼什么刀。

java
public class Forge {
    public static void main(String[] args) {
        craftSword("铁剑", 3);
        craftSword("钢刀", 5);
        craftSword("秘银匕首", 2);
    }

    static void craftSword(String name, int days) {
        System.out.println("开始打造:" + name);
        System.out.println("预计需要 " + days + " 天");
        System.out.println(name + " 打造完成!");
        System.out.println("---");
    }
}
python
def craft_sword(name: str, days: int):
    print("开始打造:" + name)
    print("预计需要 " + str(days) + " 天")
    print(name + " 打造完成!")
    print("---")

def main():
    craft_sword("铁剑", 3)
    craft_sword("钢刀", 5)
    craft_sword("秘银匕首", 2)

if __name__ == "__main__":
    main()
cpp
#include <iostream>
#include <string>

using namespace std;

void craftSword(const string& name, int days) {
    cout << "开始打造:" << name << endl;
    cout << "预计需要 " << days << " 天" << endl;
    cout << name << " 打造完成!" << endl;
    cout << "---" << endl;
}

int main() {
    craftSword("铁剑", 3);
    craftSword("钢刀", 5);
    craftSword("秘银匕首", 2);
    return 0;
}

语言:Java 21 如何运行javac Forge.java && java Forge预期输出

开始打造:铁剑
预计需要 3 天
铁剑 打造完成!
---
开始打造:钢刀
预计需要 5 天
钢刀 打造完成!
---
开始打造:秘银匕首
预计需要 2 天
秘银匕首 打造完成!
---

参数的本质craftSword(String name, int days) 声明了两个"形参"(parameter)。你调用时传进去的值("铁剑"3)叫"实参"(argument)。形参就像在方法入口处声明了两个临时变量——调用时被赋值为你传进来的值。

java
// 你写的调用:
craftSword("铁剑", 3);

// 实际发生的事(编译器视角):
// String name = "铁剑";
// int days = 3;
// 然后执行 craftSword 内部的代码
python
# 你写的调用:
craft_sword("铁剑", 3)

# 实际发生的事(编译器视角):
# name = "铁剑"
# days = 3
# 然后执行 craft_sword 内部的代码
cpp
// 你写的调用:
craftSword("铁剑", 3);

// 实际发生的事(编译器视角):
// string name = "铁剑";  (或 const string& name,引用绑定)
// int days = 3;
// 然后执行 craftSword 内部的代码

C++ 差异:

老陈翻到手册的另一页:"C++ 有一个比 Java 更灵活的选择。"

cpp
#include <iostream>
void craftSword(const std::string& name, int days) {  // const & 是传引用
    std::cout << "开始打造:" << name << std::endl;
}

C++ 默认也是值传递。但多了一个选择:传引用。你在 name 前面加 const &,表示"我不复制一份,直接看你原来的——但我不改它"。Java 没有"传引用"这个选项(尽管后面会看到引用类型变量的值本身是地址——但那是"传值地址",不是真正意义上的传引用)。

第四幕:返回值——你做出东西得还给我

把矿石扔进去炼,炼完了你得把剑拿回来。

java
public class Forge {
    public static void main(String[] args) {
        String sword1 = forgeSword("铁", 3);
        String sword2 = forgeSword("钢", 5);

        System.out.println("你得到了:" + sword1);
        System.out.println("你得到了:" + sword2);
    }

    static String forgeSword(String material, int quality) {
        // 根据材料和品质组合名字
        String result = quality >= 4
            ? "精良" + material + "剑"
            : "普通" + material + "剑";
        return result;  // ← 返回成果
    }
}
python
def forge_sword(material: str, quality: int) -> str:
    # 根据材料和品质组合名字
    result = "精良" + material + "剑" if quality >= 4 else "普通" + material + "剑"
    return result

def main():
    sword1 = forge_sword("铁", 3)
    sword2 = forge_sword("钢", 5)
    print("你得到了:" + sword1)
    print("你得到了:" + sword2)

if __name__ == "__main__":
    main()
cpp
#include <iostream>
#include <string>

using namespace std;

string forgeSword(const string& material, int quality) {
    // 根据材料和品质组合名字
    string result = quality >= 4
        ? "精良" + material + "剑"
        : "普通" + material + "剑";
    return result;  // ← 返回成果
}

int main() {
    string sword1 = forgeSword("铁", 3);
    string sword2 = forgeSword("钢", 5);

    cout << "你得到了:" << sword1 << endl;
    cout << "你得到了:" << sword2 << endl;
    return 0;
}

如何运行javac Forge.java && java Forge预期输出

你得到了:普通铁剑
你得到了:精良钢剑

对比看看变化:

之前之后
声明static void craftSword(...)static String forgeSword(...)
调用craftSword(...);String x = forgeSword(...);
内部只打印,无 return计算后 return result

void 变成了 String——"这个方法运行完毕后,会还给你一个 String 类型的值"。然后在方法体内,你需要 return 那个值。

return 做了两件事:

  1. 计算出结果并返回给调用者
  2. 立即结束方法——后面的代码不会执行
java
static String checkAge(int age) {
    if (age < 18) {
        return "未成年,不能领取冒险执照";
        // System.out.println("这行永远不会执行"); ← 编译器会报错
    }
    return "欢迎领取冒险执照";
}
python
def check_age(age: int) -> str:
    if age < 18:
        return "未成年,不能领取冒险执照"
        # print("这行永远不会执行")
    return "欢迎领取冒险执照"
cpp
string checkAge(int age) {
    if (age < 18) {
        return "未成年,不能领取冒险执照";
        // cout << "这行永远不会执行" << endl; ← 编译器会报 warning
    }
    return "欢迎领取冒险执照";
}

Python 差异:

阿花凑过来看了看:"Python 的返回类型只是提示,不强制。"

python
def forge_sword(material: str, quality: int) -> str:
    result = f"{'精良' if quality >= 4 else '普通'}{material}剑"
    return result

Python 用 -> str 标记返回类型(3.5+),但这只是类型提示——你不写也照样跑。Java 的 String编译期强制检查的,你要是声明了 String 却没 return,编译器直接拒绝编译。

第七幕:值传递的真相

有一个很隐蔽的知识,你必须在现在就遇到——否则以后 debug 时你会疯掉。

Java 只有值传递(pass-by-value)。你传给方法的任何东西,都是它的副本。

java
public class ValuePass {
    public static void main(String[] args) {
        int x = 10;
        System.out.println("调用前: x = " + x);
        changeValue(x);
        System.out.println("调用后: x = " + x);  // 还是 10!
    }

    static void changeValue(int num) {
        num = 100;  // 改的是副本
        System.out.println("方法内: num = " + num);
    }
}
python
def change_value(num):
    num = 100  # 改的是副本
    print("方法内: num =", num)

def main():
    x = 10
    print("调用前: x =", x)
    change_value(x)
    print("调用后: x =", x)  # 还是 10!

if __name__ == "__main__":
    main()
cpp
#include <iostream>

using namespace std;

void changeValue(int num) {
    num = 100;  // 改的是副本
    cout << "方法内: num = " << num << endl;
}

int main() {
    int x = 10;
    cout << "调用前: x = " << x << endl;
    changeValue(x);
    cout << "调用后: x = " << x << endl;  // 还是 10!
    return 0;
}

预期输出

调用前: x = 10
方法内: num = 100
调用后: x = 10

x 没有被方法改掉。因为 numx 的值的一个复制品,它们在内存里的位置不同。

"那数组和对象呢?"你问。

java
import java.util.Arrays;

public class ValuePassRef {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        System.out.println("调用前: " + Arrays.toString(arr));
        changeArray(arr);
        System.out.println("调用后: " + Arrays.toString(arr));  // 变了!
    }

    static void changeArray(int[] nums) {
        nums[0] = 99;  // 改的是数组里的元素
    }
}
python
def change_array(nums):
    nums[0] = 99  # 改的是数组里的元素

def main():
    arr = [1, 2, 3]
    print("调用前:", arr)
    change_array(arr)
    print("调用后:", arr)  # 变了!

if __name__ == "__main__":
    main()
cpp
#include <iostream>
#include <vector>

using namespace std;

void changeArray(vector<int>& nums) {  // C++ 传引用
    nums[0] = 99;
}

int main() {
    vector<int> arr = {1, 2, 3};
    cout << "调用前: ";
    for (int n : arr) cout << n << " ";
    cout << endl;
    changeArray(arr);
    cout << "调用后: ";
    for (int n : arr) cout << n << " ";
    cout << endl;
    return 0;
}

预期输出

调用前: [1, 2, 3]
调用后: [99, 2, 3]

"数组被改了!"你喊到,"这不是引用传递吗?"

不是。 来,看这个:

java
public class ValuePassRef2 {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        System.out.println("调用前: " + Arrays.toString(arr));
        reassignArray(arr);
        System.out.println("调用后: " + Arrays.toString(arr));  // 还是 [1, 2, 3]!
    }

    static void reassignArray(int[] nums) {
        nums = new int[]{100, 200, 300};  // 让副本指向新数组
        System.out.println("方法内: " + Arrays.toString(nums));
    }
}
python
def reassign_array(nums):
    nums = [100, 200, 300]  # 让副本指向新数组
    print("方法内:", nums)

def main():
    arr = [1, 2, 3]
    print("调用前:", arr)
    reassign_array(arr)
    print("调用后:", arr)  # 还是 [1, 2, 3]!

if __name__ == "__main__":
    main()
cpp
#include <iostream>
#include <vector>

using namespace std;

// C++ 传值:vector 会被完整复制
void reassignArray(vector<int> nums) {
    nums = {100, 200, 300};
    cout << "方法内: ";
    for (int n : nums) cout << n << " ";
    cout << endl;
}

int main() {
    vector<int> arr = {1, 2, 3};
    cout << "调用前: ";
    for (int n : arr) cout << n << " ";
    cout << endl;
    reassignArray(arr);
    cout << "调用后: ";
    for (int n : arr) cout << n << " ";
    cout << endl;
    return 0;
}

预期输出

调用前: [1, 2, 3]
方法内: [100, 200, 300]
调用后: [1, 2, 3]

看到没?你可以改数组内容(因为副本指向同一个数组对象),但不能让方法的 nums 指向一个新数组来改变 main 里的 arr。因为 nums 本身是 arr地址值的副本

用老陈的话说:"你拿着地图的副本,可以找到那座山,在山里挖矿。但你换一张地图,原来的地图不会跟着变。"

Python 差异:

阿花确认道:"Python 在这点上和你一样——也是值传递。"完全一样。你做了赋值操作 nums = [100, 200, 300],也只是让局部变量指向另一个对象。

C++ 差异:

老陈敲了敲手册:"C++ 额外给了你一个 Java 和 Python 都没有的选项。" C++ 给了你第三种选择——真正的引用传递

cpp
void reassignArray(int*& nums) {  // 指针的引用
    nums = new int[3]{100, 200, 300};
}
// 这么调用后,外面的指针确实被改了

但这是 C++ 独有的能力,Java 和 Python 都不支持。

旅人笔记

  • 形参(parameter)是方法定义时的"空位",实参(argument)是调用时传入的具体值
  • 方法可以有多个参数,用逗号分隔
  • return 结束方法并返回结果;void 表示不返回任何东西
  • Java 只有值传递——传的是副本,方法内改不了原始变量
  • 引用类型传的是"地址值的副本"——可以改对象内容,但无法让外部引用指向一个新对象

下一步

你已经掌握了参数和返回值。但有个新问题出现了:同样是"打造",你想同时支持"打剑"和"打盾牌"——难道要分别叫 forgeSwordforgeShield 两个方法吗?

下一节 方法重载,老陈会教你用同一个名字干不同的活。

Built with VitePress | Software Systems Atlas