元数据卡
- 前置知识:方法的基本定义与调用(ch05-methods-basics)
- 预计时间:15 分钟
- 完成标志:能够写出带参数和返回值的方法,理解值传递
回顾
上一节你学会了定义一个简单的 showBanner() 方法。但这个方法每次打印的都一样——铁匠铺的铁砧不能同时打剑和打盾啊。
"展示个横幅有啥用?"老陈敲了敲铁砧,"你得能告诉我——给谁打、打什么、用什么材料。"
一个方法如果只能做一模一样的事,那它的用处就太有限了。你需要的是:传进去不同的东西,做出不同的东西。
第三幕:传参数进去
方法接收参数,就像老陈接收材料——你把矿石扔过去,他才知道要炼什么刀。
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("---");
}
}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()#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)。形参就像在方法入口处声明了两个临时变量——调用时被赋值为你传进来的值。
// 你写的调用:
craftSword("铁剑", 3);
// 实际发生的事(编译器视角):
// String name = "铁剑";
// int days = 3;
// 然后执行 craftSword 内部的代码# 你写的调用:
craft_sword("铁剑", 3)
# 实际发生的事(编译器视角):
# name = "铁剑"
# days = 3
# 然后执行 craft_sword 内部的代码// 你写的调用:
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 没有"传引用"这个选项(尽管后面会看到引用类型变量的值本身是地址——但那是"传值地址",不是真正意义上的传引用)。
第四幕:返回值——你做出东西得还给我
把矿石扔进去炼,炼完了你得把剑拿回来。
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; // ← 返回成果
}
}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()#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 做了两件事:
- 计算出结果并返回给调用者
- 立即结束方法——后面的代码不会执行
static String checkAge(int age) {
if (age < 18) {
return "未成年,不能领取冒险执照";
// System.out.println("这行永远不会执行"); ← 编译器会报错
}
return "欢迎领取冒险执照";
}def check_age(age: int) -> str:
if age < 18:
return "未成年,不能领取冒险执照"
# print("这行永远不会执行")
return "欢迎领取冒险执照"string checkAge(int age) {
if (age < 18) {
return "未成年,不能领取冒险执照";
// cout << "这行永远不会执行" << endl; ← 编译器会报 warning
}
return "欢迎领取冒险执照";
}Python 差异:
阿花凑过来看了看:"Python 的返回类型只是提示,不强制。"
pythondef forge_sword(material: str, quality: int) -> str: result = f"{'精良' if quality >= 4 else '普通'}{material}剑" return resultPython 用
-> str标记返回类型(3.5+),但这只是类型提示——你不写也照样跑。Java 的String是编译期强制检查的,你要是声明了String却没return,编译器直接拒绝编译。
第七幕:值传递的真相
有一个很隐蔽的知识,你必须在现在就遇到——否则以后 debug 时你会疯掉。
Java 只有值传递(pass-by-value)。你传给方法的任何东西,都是它的副本。
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);
}
}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()#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 = 10x 没有被方法改掉。因为 num 是 x 的值的一个复制品,它们在内存里的位置不同。
"那数组和对象呢?"你问。
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; // 改的是数组里的元素
}
}def change_array(nums):
nums[0] = 99 # 改的是数组里的元素
def main():
arr = [1, 2, 3]
print("调用前:", arr)
change_array(arr)
print("调用后:", arr) # 变了!
if __name__ == "__main__":
main()#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]"数组被改了!"你喊到,"这不是引用传递吗?"
不是。 来,看这个:
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));
}
}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()#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++ 给了你第三种选择——真正的引用传递:
cppvoid reassignArray(int*& nums) { // 指针的引用 nums = new int[3]{100, 200, 300}; } // 这么调用后,外面的指针确实被改了但这是 C++ 独有的能力,Java 和 Python 都不支持。
旅人笔记
- 形参(parameter)是方法定义时的"空位",实参(argument)是调用时传入的具体值
- 方法可以有多个参数,用逗号分隔
return结束方法并返回结果;void表示不返回任何东西- Java 只有值传递——传的是副本,方法内改不了原始变量
- 引用类型传的是"地址值的副本"——可以改对象内容,但无法让外部引用指向一个新对象
→ 下一步
你已经掌握了参数和返回值。但有个新问题出现了:同样是"打造",你想同时支持"打剑"和"打盾牌"——难道要分别叫 forgeSword 和 forgeShield 两个方法吗?
下一节 方法重载,老陈会教你用同一个名字干不同的活。