函数定义

函数定义语法

TianYuan 使用 Matlab 风格的函数定义语法:

function [output1, output2, ...] = functionName(input1, input2, ...)
    # 函数体
    # ...
    # 计算输出值
end

语法要点

  • 函数定义以 function 关键字开始
  • 输出参数用方括号 [] 括起来(单个输出可省略方括号)
  • 函数名遵循变量命名规则
  • 输入参数用圆括号 () 括起来
  • 函数体以 end 关键字结束

无输入输出的函数

最简单的函数形式:

function sayHello()
    disp("Hello, TianYuan!")
end

# 调用函数
sayHello()

单个返回值

返回单个值的函数:

基本示例

function result = square(x)
    result = x * x
end

# 使用函数
y = square(5)      # y = 25
z = square(3.5)    # z = 12.25

更多示例

# 计算圆的面积
function area = circleArea(radius)
    area = pi * radius^2
end

# 华氏度转摄氏度
function celsius = f2c(fahrenheit)
    celsius = (fahrenheit - 32) * 5/9
end

# 计算阶乘
function result = factorial(n)
    result = 1
    for i = 1:n
        result = result * i
    end
end

# 使用这些函数
A = circleArea(5)       # 78.54
temp = f2c(98.6)        # 37
fact = factorial(5)     # 120

简化写法

对于单个返回值,可以省略方括号:

# 这两种写法等价
function result = add(a, b)
    result = a + b
end

function [result] = add(a, b)
    result = a + b
end

多个返回值

函数可以返回多个值:

基本语法

function [output1, output2] = functionName(input)
    # 计算 output1 和 output2
    output1 = ...
    output2 = ...
end

# 调用时接收多个返回值
[result1, result2] = functionName(input)

示例:矩形属性

function [area, perimeter] = rectangle(width, height)
    area = width * height
    perimeter = 2 * (width + height)
end

# 使用函数
[A, P] = rectangle(5, 3)
disp("面积:" + num2str(A))        # 15
disp("周长:" + num2str(P))        # 16

示例:统计信息

function [minVal, maxVal, meanVal] = stats(data)
    minVal = min(data)
    maxVal = max(data)
    meanVal = mean(data)
end

# 使用函数
data = [3, 7, 2, 9, 5, 1, 8]
[minV, maxV, meanV] = stats(data)
disp("最小值:" + num2str(minV))    # 1
disp("最大值:" + num2str(maxV))    # 9
disp("平均值:" + num2str(meanV))   # 5

示例:求解二次方程

function [x1, x2] = solveQuadratic(a, b, c)
    # 求解 ax^2 + bx + c = 0
    discriminant = b^2 - 4*a*c
    
    if discriminant < 0
        disp("无实数解")
        x1 = nan
        x2 = nan
    else
        x1 = (-b + sqrt(discriminant)) / (2*a)
        x2 = (-b - sqrt(discriminant)) / (2*a)
    end
end

# 使用函数
[root1, root2] = solveQuadratic(1, -3, 2)
disp("根1:" + num2str(root1))  # 2
disp("根2:" + num2str(root2))  # 1

只接收部分返回值

# 只接收第一个返回值
area = rectangle(5, 3)  # 只获取面积

# 使用占位符 ~ 忽略某些返回值(如果支持)
[~, maxV, ~] = stats(data)  # 只获取最大值

多个输入参数

函数可以接受任意数量的输入参数:

基本示例

function result = add(a, b)
    result = a + b
end

function result = add3(a, b, c)
    result = a + b + c
end

function result = multiply(a, b)
    result = a * b
end

# 使用
x = add(3, 5)           # 8
y = add3(1, 2, 3)       # 6
z = multiply(4, 7)      # 28

示例:线性函数

function y = linear(x, slope, intercept)
    y = slope * x + intercept
end

# 使用
x = 1:10
y = linear(x, 2, 3)     # y = 2x + 3

匿名函数

使用 @(参数) 表达式 语法创建匿名函数(也称 lambda 函数)。匿名函数会自动捕获定义时的外部变量(闭包):

基本语法

% 单参数匿名函数
f = @(x) x^2 + 1
y = f(3)       # 10

# 多参数匿名函数
g = @(x, y) x^2 + y^2
z = g(3, 4)    # 25

# 无参数匿名函数
pi_func = @() 3.14159265358979
val = pi_func()  # 3.14159...

闭包:捕获外部变量

% 匿名函数在创建时捕获外部变量的当前值
a = 2
b = 3
linear = @(x) a * x + b   # 捕获 a=2, b=3

y1 = linear(5)   # 2*5+3 = 13

# 即使之后修改 a/b,捕获的值不变
a = 10
y2 = linear(5)   # 仍然是 13(快照值)

作为参数传递

% 将函数传给另一个函数(高阶函数)
function result = applyFunc(f, x)
    result = f(x)
end

double_it = @(x) x * 2
square_it = @(x) x^2

r1 = applyFunc(double_it, 5)  # 10
r2 = applyFunc(square_it, 5)  # 25

# 牛顿法的函数句柄传递
root = newtonMethod(@myFunc, @myFuncDerivative, 1.0, 1e-6, 100)

函数句柄与已命名函数

使用 @name 创建一个指向已命名函数(内建函数或用户自定义函数)的句柄。句柄可以存入变量、传入其他函数,或直接调用:

# 指向内建函数
f = @sin
disp(f(pi / 2))    # 1.0
disp(f(0))         # 0.0

# 指向用户自定义函数
function y = square(x)
    y = x * x
end

g = @square
disp(g(5))         # 25

# 将函数句柄传入高阶函数
function result = apply_twice(f, x)
    result = f(f(x))
end

disp(apply_twice(@square, 3))   # square(square(3)) = square(9) = 81

# 与匿名函数等价写法对比:
h = @(x) x * x       # 效果与 @square 相同
disp(h(5))           # 25

nargin / nargout

在函数体内,nargin 表示实际传入的参数数量,nargout 表示调用方请求的返回值数量。常用于实现可选参数:

基本用法

function result = myFunc(a, b, c)
    disp("nargin = " + num2str(nargin))
    if nargin < 2
        b = 10    # 默认值
    end
    if nargin < 3
        c = 100   # 默认值
    end
    result = a + b + c
end

r1 = myFunc(1)          # nargin=1, result=111
r2 = myFunc(1, 2)       # nargin=2, result=103
r3 = myFunc(1, 2, 3)    # nargin=3, result=6

根据 nargout 决定计算量

function [mn, mx, avg] = analyze(data)
    mn = min(data)
    if nargout >= 2
        mx = max(data)  # 只在需要时才计算
    end
    if nargout >= 3
        avg = mean(data)
    end
end

data = [1 2 3 4 5]
mn = analyze(data)           # 只计算 min
[mn, mx] = analyze(data)     # 计算 min 和 max
[mn, mx, av] = analyze(data) # 全部计算

在顶层脚本中,narginnargout 的值为 -1(表示不在函数调用上下文中)。

变量作用域

局部变量

在函数内部定义的变量是局部变量,只在函数内部可见:

function result = myFunc(x)
    temp = x * 2      # temp 是局部变量
    result = temp + 1
end

# 在函数外部无法访问 temp
y = myFunc(5)         # y = 11
# disp(temp)          # 错误:temp 未定义

参数作用域

函数参数在函数内部作为局部变量:

function result = modify(x)
    x = x + 10        # 修改局部副本
    result = x
end

value = 5
result = modify(value)
disp(value)           # 仍然是 5(原值未改变)
disp(result)          # 15

全局变量访问

函数可以访问外部作用域的变量(闭包特性):

global_var = 100

function result = useGlobal(x)
    result = x + global_var  # 可以读取外部变量
end

y = useGlobal(10)     # 110

递归函数

函数可以调用自身:

阶乘(递归版)

function result = factorial(n)
    if n <= 1
        result = 1
    else
        result = n * factorial(n - 1)
    end
end

# 使用
fact5 = factorial(5)   # 120
fact10 = factorial(10) # 3628800

斐波那契数列

function result = fibonacci(n)
    if n <= 2
        result = 1
    else
        result = fibonacci(n-1) + fibonacci(n-2)
    end
end

# 使用
for i = 1:10
    disp("fib(" + num2str(i) + ") = " + num2str(fibonacci(i)))
end

最大公约数(欧几里得算法)

function result = gcd(a, b)
    if b == 0
        result = a
    else
        result = gcd(b, mod(a, b))
    end
end

# 使用
g = gcd(48, 18)        # 6
disp("GCD: " + num2str(g))

实用函数示例

判断素数

function result = isPrime(n)
    if n < 2
        result = 0
        return
    end
    
    for i = 2:sqrt(n)
        if mod(n, i) == 0
            result = 0
            return
        end
    end
    
    result = 1
end

# 使用
if isPrime(17)
    disp("17 是素数")
end

向量归一化

function normalized = normalize(vec)
    magnitude = sqrt(sum(vec .^ 2))
    normalized = vec / magnitude
end

# 使用
v = [3, 4]
v_norm = normalize(v)  # [0.6, 0.8]
disp("归一化后的向量:")
disp(v_norm)

计算两点距离

function d = distance(x1, y1, x2, y2)
    d = sqrt((x2 - x1)^2 + (y2 - y1)^2)
end

# 使用
dist = distance(0, 0, 3, 4)  # 5
disp("距离:" + num2str(dist))

线性插值

function y = lerp(a, b, t)
    # 在 a 和 b 之间线性插值,t ∈ [0, 1]
    y = a + (b - a) * t
end

# 使用
y = lerp(0, 10, 0.5)    # 5 (中点)
y = lerp(0, 10, 0.25)   # 2.5

矩阵转换

function B = rotateMatrix90(A)
    # 将矩阵顺时针旋转90度
    B = A'
    B = fliplr(B)
end

# 使用
A = [1 2 3; 4 5 6; 7 8 9]
B = rotateMatrix90(A)
disp("旋转后的矩阵:")
disp(B)

高级示例:牛顿法求根

function root = newtonMethod(f, df, x0, tolerance, maxIter)
    # f: 函数(传入函数句柄或使用字符串)
    # df: 导数
    # x0: 初始猜测
    # tolerance: 容差
    # maxIter: 最大迭代次数
    
    x = x0
    
    for iter = 1:maxIter
        fx = f(x)
        dfx = df(x)
        
        if abs(fx) < tolerance
            root = x
            disp("收敛于 " + num2str(iter) + " 次迭代")
            return
        end
        
        x = x - fx / dfx
    end
    
    root = x
    disp("达到最大迭代次数")
end

# 定义要求根的函数和导数
function y = myFunc(x)
    y = x^2 - 2  # 求 sqrt(2)
end

function y = myFuncDerivative(x)
    y = 2*x
end

# 使用牛顿法
root = newtonMethod(@myFunc, @myFuncDerivative, 1.0, 1e-6, 100)
disp("sqrt(2) ≈ " + num2str(root))

函数最佳实践

1. 使用描述性的函数名

# 好的命名
function area = calculateCircleArea(radius)
function [min, max] = findMinMax(data)

# 避免的命名
function a = f(r)
function [x, y] = calc(d)

2. 添加注释说明

function [area, perimeter] = rectangle(width, height)
    # 计算矩形的面积和周长
    # 输入:
    #   width - 矩形宽度
    #   height - 矩形高度
    # 输出:
    #   area - 面积
    #   perimeter - 周长
    
    area = width * height
    perimeter = 2 * (width + height)
end

3. 参数验证

function result = safeDivide(a, b)
    # 安全除法,检查除数
    if b == 0
        disp("错误:除数不能为零")
        result = nan
    else
        result = a / b
    end
end

4. 单一职责原则

每个函数应该只做一件事,并把它做好:

# 好的设计:功能单一
function area = calculateArea(radius)
    area = pi * radius^2
end

function circumference = calculateCircumference(radius)
    circumference = 2 * pi * radius
end

# 而不是把所有功能塞在一个函数里