第四章 函数与模块化 —— 掌握代码复用的艺术与实战技巧
在编程实践中,将特定逻辑封装成可重复调用的单元,是实现代码复用的核心思想。函数与模块化正是达成这一目标的基础工具。本章将从函数的定义方式、参数传递机制,到变量作用域与递归算法,系统性地剖析这些核心概念的底层原理与常见误区。
4.1 函数的定义与调用详解
函数的基本结构与语法

下面将分别通过 Python 和 Java 的示例,演示函数的定义(方法声明确认)及其调用过程中的关键差异。
# Python函数完整定义
def calculate_bmi(weight_kg, height_m):
"""
计算身体质量指数(BMI)
参数:
weight_kg: 体重(千克)
height_m: 身高(米)
返回:
BMI值(浮点数)
"""
if height_m <= 0:
raise ValueError("身高必须为正数")
bmi = weight_kg / (height_m ** 2)
return bmi
# 调用函数
bmi = calculate_bmi(70, 1.75)
print(f"BMI: {bmi:.2f}")
# 参数传递的深度理解
# Python中参数传递是"对象引用传递"
def modify_list(lst):
lst.append(4) # 修改传入的列表对象
lst = [1, 2, 3] # 重新绑定,不影响外部
print(f"函数内部: {lst}")
my_list = [1, 2, 3]
modify_list(my_list)
print(f"函数外部: {my_list}") # [1,2,3,4] - append生效
// Ja va方法定义
public class Calculator {
// 基本方法
public static int add(int a, int b) {
return a + b;
}
// 重载(Overloading):相同方法名,不同参数
public static int add(int a, int b, int c) {
return a + b + c;
}
public static double add(double a, double b) {
return a + b;
}
// 可变参数
public static int sum(int... numbers) {
int total = 0;
for (int num : numbers) {
total += num;
}
return total;
}
// 递归方法:阶乘
public static long factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
// 方法调用示例
public static void main(String[] args) {
System.out.println(add(5, 3)); // 8
System.out.println(add(5, 3, 2)); // 10
System.out.println(add(5.5, 3.2)); // 8.7
System.out.println(sum(1, 2, 3, 4, 5)); // 15
// 递归陷阱:栈溢出
// factorial(10000); // 可能抛出 StackOverflowError
}
}
4.2 参数的进阶特性
默认参数与关键字参数(Python)
# 默认参数
def greet(name, greeting="Hello", punctuation="!"):
print(f"{greeting}, {name}{punctuation}")
greet("Alice") # Hello, Alice!
greet("Bob", "Hi") # Hi, Bob!
greet("Charlie", punctuation="?") # Hello, Charlie?
# 默认参数的陷阱(可变对象)
def add_item(item, lst=[]): # 危险!默认参数在定义时计算
lst.append(item)
return lst
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] !不是[2]
print(add_item(3)) # [1, 2, 3]
# 正确做法
def add_item_safe(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
# 关键字参数
def create_user(name, age, email):
print(f"Name: {name}, Age: {age}, Email: {email}")
create_user(age=25, name="Alice", email="alice@example.com") # 顺序任意
# 强制关键字参数(*号后的参数必须用关键字传递)
def config(host, port, *, timeout, retry):
print(f"Host: {host}, Port: {port}, Timeout: {timeout}, Retry: {retry}")
config("localhost", 8080, timeout=30, retry=3) # 正确
# config("localhost", 8080, 30, 3) # 错误!
需要特别留意的是,当默认参数为可变对象时,其行为与预期可能不同——默认值仅在函数定义时计算一次,后续调用会共享同一个列表对象。推荐的做法是将默认值设为 None,在函数内部重新初始化。关键字参数与强制关键字参数能使函数调用更加清晰易读,尤其在参数数量较多的情况下更显优势。
4.3 变量作用域与闭包机制解析
// Ja vaScript作用域与闭包示例
function outerFunction(x) {
// 外部函数变量
let outerVar = 10;
// 内部函数(闭包)
function innerFunction(y) {
// 可以访问外部函数的变量
console.log(x + outerVar + y);
}
return innerFunction;
}
const closure = outerFunction(5);
closure(3); // 输出 18 (5+10+3)
// 经典闭包陷阱
function createCounters() {
let counters = [];
for (var i = 0; i < 3; i++) { // 使用var
counters.push(function() {
console.log(i);
});
}
return counters;
}
const counters = createCounters();
counters[0](); // 输出 3,不是0!
counters[1](); // 输出 3
counters[2](); // 输出 3
// 解决方案:使用let或IIFE
function createCountersFixed() {
let counters = [];
for (let i = 0; i < 3; i++) { // let有块级作用域
counters.push(function() {
console.log(i);
});
}
return counters;
}
闭包是 JavaScript 中一个核心且容易引发错误的概念。上述代码展示了一个经典陷阱:使用 var 声明的循环变量会共享同一个函数作用域,导致所有内部函数最终指向相同的最后一次迭代值。改用 let 声明或立即执行函数表达式(IIFE)即可有效规避。
4.4 递归深度解析与优化策略
# 递归经典案例:汉诺塔
def hanoi(n, source, target, auxiliary):
"""
汉诺塔递归解法
n: 盘子数量
source: 起始柱子
target: 目标柱子
auxiliary: 辅助柱子
"""
if n == 1:
print(f"移动盘子1从 {source} 到 {target}")
return
# 将n-1个盘子从source移动到auxiliary
hanoi(n - 1, source, auxiliary, target)
# 移动最大的盘子
print(f"移动盘子{n}从 {source} 到 {target}")
# 将n-1个盘子从auxiliary移动到target
hanoi(n - 1, auxiliary, target, source)
# 测试
hanoi(3, 'A', 'C', 'B')
# 递归优化:斐波那契数列(带备忘录)
def fibonacci_memo(n, memo={}):
if n in memo:
return memo[n]
if n <= 1:
return n
memo[n] = fibonacci_memo(n-1, memo) + fibonacci_memo(n-2, memo)
return memo[n]
# 尾递归(Python不支持尾递归优化,仅供参考)
def factorial_tail(n, accumulator=1):
if n == 0:
return accumulator
return factorial_tail(n-1, n * accumulator)
递归算法的精髓在于准确找到递推关系与终止条件。汉诺塔问题堪称经典教例,而斐波那契数列则可通过备忘录(Memoization)技术避免重复计算。尾递归写法虽然简洁,但 Python 并未对尾递归进行优化,因此深度递归时仍需警惕栈溢出的风险。
```