参数传递

函数参数就像是函数的"输入接口",它们允许我们将数据传递给函数进行处理。理解参数传递机制是编写灵活、可重用函数的关键。

参数的基本概念

什么是参数?

参数是传递给函数的值,函数可以使用这些值来完成特定的任务。参数就像是给函数的"原材料",函数根据这些原材料来工作。

<?php
// $name 和 $message 都是参数
function greetUser($name, $message) {
    echo "$name, $message";
}

// 调用函数时传递具体的值(实参)
greetUser("小明", "欢迎来到PHP世界!");
?>

参数 vs 实参

  • 参数(Parameters):函数定义时声明的变量
  • 实参(Arguments):调用函数时传递的实际值
<?php
// $name 是参数(在函数定义中)
function sayHello($name) {
    echo "Hello, $name";
}

// "Alice" 是实参(在函数调用中)
sayHello("Alice");
?>

值传递 vs 引用传递

PHP支持两种参数传递方式:

1. 值传递(默认方式)

值传递意味着函数接收到的是参数值的副本,函数内部对参数的修改不会影响到外部的原变量。

<?php
function modifyValue($number) {
    $number = $number * 2;  // 修改的是副本
    echo "函数内部:$number<br>";  // 输出:20
}

$originalValue = 10;
echo "原始值:$originalValue<br>";  // 输出:10

modifyValue($originalValue);

echo "函数调用后:$originalValue<br>";  // 输出:10,原值未改变
?>

值传递的特点:

  • 安全性高,不会意外修改外部变量
  • 适合传递简单数据类型
  • 对于大对象可能影响性能(需要复制数据)

2. 引用传递

引用传递意味着函数接收到的是参数值的引用,函数内部对参数的修改会影响到外部的原变量。

<?php
function modifyByReference(&$number) {  // &符号表示引用传递
    $number = $number * 2;  // 修改的是原变量
    echo "函数内部:$number<br>";  // 输出:20
}

$originalValue = 10;
echo "原始值:$originalValue<br>";  // 输出:10

modifyByReference($originalValue);

echo "函数调用后:$originalValue<br>";  // 输出:20,原值被改变
?>

引用传递的特点:

  • 性能更好,不需要复制数据
  • 可以直接修改外部变量
  • 需要谨慎使用,避免意外的副作用

默认参数值

PHP允许为参数设置默认值。如果调用函数时不传递该参数,将使用默认值。

基本语法

<?php
function greetUser($name, $greeting = "你好") {
    echo "$greeting, $name!<br>";
}

// 提供所有参数
greetUser("小明", "早上好");  // 输出:早上好, 小明!

// 省略有默认值的参数
greetUser("小红");           // 输出:你好, 小红!
?>

默认参数的规则

  1. 默认参数必须放在非默认参数的后面
  2. 默认值必须是常量表达式,不能是变量或函数调用
<?php
// 正确的默认参数设置
function createUser($username, $email, $role = "user", $isActive = true) {
    echo "用户名:$username<br>";
    echo "邮箱:$email<br>";
    echo "角色:$role<br>";
    echo "状态:" . ($isActive ? "活跃" : "未激活") . "<br>";
}

// 错误的默认参数设置
// function badFunction($default = "value", $required) { }  // 语法错误
?>

实际应用示例

<?php
// 计算商品价格(含税)
function calculatePriceWithTax($basePrice, $taxRate = 0.08) {
    $tax = $basePrice * $taxRate;
    $totalPrice = $basePrice + $tax;
    return [
        'base_price' => $basePrice,
        'tax_rate' => $taxRate,
        'tax_amount' => $tax,
        'total_price' => $totalPrice
    ];
}

// 使用默认税率
$price1 = calculatePriceWithTax(100);
echo "默认税率:" . number_format($price1['total_price'], 2) . "元<br>";

// 使用自定义税率
$price2 = calculatePriceWithTax(100, 0.13);
echo "自定义税率:" . number_format($price2['total_price'], 2) . "元<br>";

// 发送邮件函数
function sendEmail($to, $subject, $message, $priority = "normal", $cc = null) {
    echo "收件人:$to<br>";
    echo "主题:$subject<br>";
    echo "消息:$message<br>";
    echo "优先级:$priority<br>";
    if ($cc) {
        echo "抄送:$cc<br>";
    }
    echo "邮件发送成功!<br><br>";
}

// 不同优先级的邮件
sendEmail("user@example.com", "系统通知", "您的账户已激活");
sendEmail("admin@example.com", "紧急报告", "发现安全漏洞", "high", "security@company.com");
?>

可变数量参数

PHP 5.6+ 提供了 ... 操作符来处理可变数量的参数。

使用 ... 操作符

<?php
function sumAllNumbers(...$numbers) {
    $total = 0;
    foreach ($numbers as $number) {
        $total += $number;
    }
    return $total;
}

echo sumAllNumbers(1, 2, 3, 4, 5) . "<br>";  // 输出:15
echo sumAllNumbers(10, 20) . "<br>";           // 输出:30
echo sumAllNumbers(100) . "<br>";              // 输出:100
echo sumAllNumbers() . "<br>";                 // 输出:0
?>

结合固定参数使用

<?php
function logMessage($level, ...$messages) {
    $timestamp = date('Y-m-d H:i:s');
    $formattedMessage = "[$timestamp] [$level] " . implode(" ", $messages);
    echo $formattedMessage . "<br>";
    // 实际应用中这里会写入日志文件
}

logMessage("INFO", "用户登录", "用户名:admin", "IP:192.168.1.1");
logMessage("ERROR", "数据库连接失败", "错误代码:500");
logMessage("WARNING", "密码即将过期");
?>

展开数组作为参数

<?php
function formatName($firstName, $middleName, $lastName) {
    return "$firstName $middleName $lastName";
}

$nameParts = ["John", "Doe"];
echo formatName("Mr.", ...$nameParts) . "<br>";  // 输出:Mr. John Doe

function createUserInfo($name, $age, $email, $city) {
    return "姓名:$name,年龄:$age,邮箱:$email,城市:$city";
}

$userData = ["张三", 25, "zhangsan@example.com", "北京"];
echo createUserInfo(...$userData) . "<br>";
?>

类型声明

PHP支持参数类型声明,可以确保传递正确类型的数据。

基本类型声明

<?php
// 声明参数必须为整数
function addIntegers(int $a, int $b) {
    return $a + $b;
}

// 声明参数必须为浮点数
function calculateCircleArea(float $radius) {
    return pi() * $radius * $radius;
}

// 声明参数必须为字符串
function greetUser(string $name) {
    return "你好,$name!";
}

// 声明参数必须为数组
function sumArray(array $numbers) {
    return array_sum($numbers);
}

// 声明参数必须为布尔值
function toggleStatus(bool $isActive) {
    return !$isActive;
}

// 测试类型声明
echo addIntegers(5, 3) . "<br>";                    // 正确:8
// echo addIntegers(5, "hello");                  // 错误:类型不匹配

echo calculateCircleArea(5.5) . "<br>";              // 正确:95.03...
echo greetUser("小明") . "<br>";                    // 正确:你好,小明!
echo sumArray([1, 2, 3, 4]) . "<br>";              // 正确:10
echo toggleStatus(true) . "<br>";                   // 正确:false
?>

可空类型声明(PHP 7.1+)

<?php
function processUser(?string $name = null) {
    if ($name === null) {
        return "匿名用户";
    }
    return "你好,$name!";
}

echo processUser("张三") . "<br>";   // 输出:你好,张三!
echo processUser() . "<br>";         // 输出:匿名用户
echo processUser(null) . "<br>";     // 输出:匿名用户
?>

联合类型声明(PHP 8.0+)

<?php
function processId(int|string $id) {
    if (is_int($id)) {
        return "数字ID:$id";
    } else {
        return "字符串ID:$id";
    }
}

echo processId(123) . "<br";           // 输出:数字ID:123
echo processId("ABC123") . "<br>";     // 输出:字符串ID:ABC123
?>

参数验证

在实际应用中,参数验证是非常重要的环节。

基本验证

<?php
function divideNumbers($dividend, $divisor) {
    // 验证参数类型
    if (!is_numeric($dividend) || !is_numeric($divisor)) {
        throw new InvalidArgumentException("两个参数都必须是数字");
    }

    // 验证除数不为零
    if ($divisor == 0) {
        throw new InvalidArgumentException("除数不能为零");
    }

    return $dividend / $divisor;
}

// 测试验证
try {
    echo divideNumbers(10, 2) . "<br>";     // 正确:5
    echo divideNumbers(15, "3") . "<br>";    // 正确:5
    echo divideNumbers(10, 0) . "<br>";      // 错误:除数不能为零
} catch (InvalidArgumentException $e) {
    echo "错误:" . $e->getMessage() . "<br>";
}
?>

详细的参数验证

<?php
// 验证邮箱地址
function validateEmail($email) {
    // 检查是否为字符串
    if (!is_string($email)) {
        return false;
    }

    // 检查长度
    if (strlen($email) < 5 || strlen($email) > 254) {
        return false;
    }

    // 检查格式
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        return false;
    }

    return true;
}

// 验证用户注册数据
function validateUserRegistration($username, $email, $password, $age) {
    $errors = [];

    // 验证用户名
    if (!is_string($username) || strlen($username) < 3 || strlen($username) > 20) {
        $errors[] = "用户名必须是3-20个字符的字符串";
    }

    // 验证邮箱
    if (!validateEmail($email)) {
        $errors[] = "邮箱格式不正确";
    }

    // 验证密码
    if (!is_string($password) || strlen($password) < 6) {
        $errors[] = "密码必须至少6个字符";
    }

    // 验证年龄
    if (!is_int($age) || $age < 0 || $age > 150) {
        $errors[] = "年龄必须是0-150之间的整数";
    }

    return $errors;
}

// 测试验证
$testData = [
    ["abc", "user@example.com", "123456", 25],     // 正确数据
    ["a", "invalid-email", "123", -5]             // 错误数据
];

foreach ($testData as $data) {
    $errors = validateUserRegistration($data[0], $data[1], $data[2], $data[3]);
    if (empty($errors)) {
        echo "用户数据验证通过<br>";
    } else {
        echo "验证错误:<br>";
        foreach ($errors as $error) {
            echo "- $error<br>";
        }
    }
    echo "<br>";
}
?>

实际应用示例

用户管理系统

<?php
// 创建用户函数
function createUser(string $username, string $email, string $password,
                   int $age = null, string $city = "未知", array $preferences = []) {

    // 参数验证
    $errors = validateUserRegistration($username, $email, $password, $age ?? 0);
    if (!empty($errors)) {
        return ['success' => false, 'errors' => $errors];
    }

    // 处理用户偏好设置
    $defaultPreferences = [
        'theme' => 'light',
        'language' => 'zh-CN',
        'notifications' => true
    ];

    $finalPreferences = array_merge($defaultPreferences, $preferences);

    // 创建用户数据
    $user = [
        'id' => uniqid('user_'),
        'username' => $username,
        'email' => $email,
        'password_hash' => password_hash($password, PASSWORD_DEFAULT),
        'age' => $age,
        'city' => $city,
        'preferences' => $finalPreferences,
        'created_at' => date('Y-m-d H:i:s'),
        'is_active' => true
    ];

    // 模拟保存到数据库
    saveUserToDatabase($user);

    return [
        'success' => true,
        'user' => array_diff_key($user, ['password_hash' => '']) // 不返回密码哈希
    ];
}

// 更新用户偏好设置
function updateUserPreferences(string $userId, array $newPreferences) {
    // 获取当前用户数据
    $currentUser = getUserById($userId);
    if (!$currentUser) {
        return ['success' => false, 'message' => '用户不存在'];
    }

    // 合并偏好设置
    $updatedPreferences = array_merge($currentUser['preferences'], $newPreferences);

    // 更新数据库
    $success = updateUserInDatabase($userId, ['preferences' => $updatedPreferences]);

    return [
        'success' => $success,
        'preferences' => $updatedPreferences
    ];
}

// 批量发送通知函数
function sendNotifications(array $userIds, string $message,
                          string $priority = 'normal', ?DateTime $sendTime = null) {
    $results = [];

    foreach ($userIds as $userId) {
        try {
            $user = getUserById($userId);
            if (!$user) {
                $results[$userId] = ['success' => false, 'message' => '用户不存在'];
                continue;
            }

            // 检查用户通知偏好
            if (!$user['preferences']['notifications']) {
                $results[$userId] = ['success' => false, 'message' => '用户禁用了通知'];
                continue;
            }

            // 发送通知(这里简化为记录)
            $notification = [
                'user_id' => $userId,
                'message' => $message,
                'priority' => $priority,
                'send_time' => $sendTime ?? new DateTime(),
                'status' => 'sent'
            ];

            $results[$userId] = ['success' => true, 'notification' => $notification];

        } catch (Exception $e) {
            $results[$userId] = ['success' => false, 'message' => $e->getMessage()];
        }
    }

    return $results;
}

// 模拟的辅助函数(实际项目中这些会连接真实数据库)
function saveUserToDatabase($user) {
    // 模拟保存操作
    return true;
}

function getUserById($userId) {
    // 模拟获取用户
    return [
        'id' => $userId,
        'preferences' => ['notifications' => true, 'theme' => 'light']
    ];
}

function updateUserInDatabase($userId, $data) {
    // 模拟更新操作
    return true;
}

// 测试用户管理系统
echo "=== 用户管理系统测试 ===<br>";

// 创建用户
$result1 = createUser("张三", "zhangsan@example.com", "password123", 25, "北京");
if ($result1['success']) {
    echo "用户创建成功:{$result1['user']['username']}<br>";
} else {
    echo "用户创建失败:<br>";
    foreach ($result1['errors'] as $error) {
        echo "- $error<br>";
    }
}

// 批量发送通知
$userIds = ['user_1', 'user_2', 'user_3'];
$notifications = sendNotifications($userIds, "系统维护通知:今晚10点进行系统升级", "high");

echo "<br>通知发送结果:<br>";
foreach ($notifications as $userId => $result) {
    echo "用户 $userId: " . ($result['success'] ? '成功' : '失败 - ' . $result['message']) . "<br>";
}
?>

数据处理管道

<?php
// 数据处理管道 - 接收多个处理函数和初始数据
function processDataPipeline($initialData, ...$processors) {
    $data = $initialData;
    $pipelineLog = [];

    foreach ($processors as $index => $processor) {
        if (!is_callable($processor)) {
            $pipelineLog[] = "步骤 $index: 处理器不是有效的函数";
            continue;
        }

        try {
            $beforeData = $data;
            $data = $processor($data);
            $pipelineLog[] = "步骤 $index: 处理成功";
        } catch (Exception $e) {
            $pipelineLog[] = "步骤 $index: 处理失败 - " . $e->getMessage();
            break; // 处理失败时停止管道
        }
    }

    return [
        'final_data' => $data,
        'pipeline_log' => $pipelineLog
    ];
}

// 定义一些数据处理函数
function trimData($data) {
    if (is_array($data)) {
        return array_map('trim', $data);
    }
    return trim($data);
}

function sanitizeEmail($data) {
    if (is_array($data)) {
        return array_map(function($email) {
            return filter_var($email, FILTER_SANITIZE_EMAIL);
        }, $data);
    }
    return filter_var($data, FILTER_SANITIZE_EMAIL);
}

function validateData($data) {
    $errors = [];
    if (is_array($data)) {
        foreach ($data as $value) {
            if (empty($value)) {
                $errors[] = "发现空值";
            }
        }
    } else {
        if (empty($data)) {
            $errors[] = "数据为空";
        }
    }

    if (!empty($errors)) {
        throw new Exception(implode(', ', $errors));
    }

    return $data;
}

function transformToUpper($data) {
    if (is_array($data)) {
        return array_map('strtoupper', $data);
    }
    return strtoupper($data);
}

// 测试数据处理管道
echo "=== 数据处理管道测试 ===<br>";

$testData = ["  user@example.com  ", "  admin@test.com  ", "  support@company.com  "];

// 处理数据
$result = processDataPipeline(
    $testData,
    'trimData',           // 去除空白
    'sanitizeEmail',      // 清理邮箱格式
    'validateData',       // 验证数据
    'transformToUpper'    // 转换为大写
);

echo "原始数据:" . implode(', ', $testData) . "<br>";
echo "处理后数据:" . implode(', ', $result['final_data']) . "<br>";
echo "处理日志:<br>";
foreach ($result['pipeline_log'] as $log) {
    echo "- $log<br>";
}

// 另一个示例:数字处理管道
function addVat($prices, $vatRate = 0.21) {
    return array_map(function($price) use ($vatRate) {
        return $price * (1 + $vatRate);
    }, $prices);
}

function formatCurrency($amounts, $currency = '¥') {
    return array_map(function($amount) use ($currency) {
        return $currency . number_format($amount, 2);
    }, $amounts);
}

$prices = [100, 250, 75.50, 420];

echo "<br>=== 价格处理管道 ===<br>";
$priceResult = processDataPipeline(
    $prices,
    function($p) { return addVat($p, 0.13); },  // 加13%税
    'formatCurrency'                           // 格式化为货币
);

echo "原始价格:" . implode(', ', $prices) . "<br>";
echo "含税价格:" . implode(', ', $priceResult['final_data']) . "<br>";
?>

最佳实践

1. 合理使用默认参数

<?php
// 好的做法:为可选参数提供合理的默认值
function connectDatabase($host = 'localhost', $port = 3306, $timeout = 30) {
    return "连接到 $host:$port,超时时间:$timeout秒";
}

// 不好的做法:所有参数都没有默认值
function connectDatabaseBad($host, $port, $timeout) {
    return "连接到 $host:$port,超时时间:$timeout秒";
}
?>

2. 参数验证要充分

<?php
// 好的做法:充分的参数验证
function createUser($name, $email, $age) {
    // 类型检查
    if (!is_string($name) || !is_string($email) || !is_int($age)) {
        throw new InvalidArgumentException('参数类型错误');
    }

    // 值检查
    if (strlen($name) < 2) {
        throw new InvalidArgumentException('姓名至少2个字符');
    }

    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        throw new InvalidArgumentException('邮箱格式错误');
    }

    if ($age < 0 || $age > 150) {
        throw new InvalidArgumentException('年龄必须在0-150之间');
    }

    // 继续处理...
}
?>

3. 避免过多参数

<?php
// 不好的做法:参数过多
function createUserBad($name, $email, $age, $city, $country, $phone, $address, $postalCode) {
    // 太多参数,容易出错
}

// 好的做法:使用数组或对象
function createUser($userData) {
    $required = ['name', 'email', 'age'];
    foreach ($required as $field) {
        if (empty($userData[$field])) {
            throw new InvalidArgumentException("缺少必填字段:$field");
        }
    }

    // 设置默认值
    $userData = array_merge([
        'city' => '未知',
        'country' => '中国',
        'isActive' => true
    ], $userData);

    // 继续处理...
}

// 使用方式
createUser([
    'name' => '张三',
    'email' => 'zhangsan@example.com',
    'age' => 25,
    'city' => '北京'
]);
?>

4. 使用类型声明

<?php
// 好的做法:使用类型声明
function calculateDiscount(float $price, int $percentage, bool $isVip = false): float {
    if ($percentage < 0 || $percentage > 100) {
        throw new InvalidArgumentException('折扣百分比必须在0-100之间');
    }

    if ($isVip) {
        $percentage += 5; // VIP用户额外5%折扣
    }

    return $price * (1 - $percentage / 100);
}
?>

常见错误和解决方案

1. 参数数量不匹配

<?php
function greet($name, $message) {
    echo "$name: $message";
}

// 错误:参数不足
// greet("张三");  // 警告:缺少参数2

// 解决方案:提供默认参数
function greetFixed($name, $message = "你好") {
    echo "$name: $message";
}

greetFixed("张三");  // 正确
?>

2. 引用传递错误

<?php
function modifyArray(&$array) {
    $array[] = "新元素";
}

// 错误:传递字面量
// modifyArray([1, 2, 3]);  // 致命错误

// 解决方案:先创建变量
$myArray = [1, 2, 3];
modifyArray($myArray);  // 正确
?>

3. 类型声明冲突

<?php
function processNumber(int $number) {
    return $number * 2;
}

// 错误:传递字符串会抛出异常
// processNumber("hello");

// 解决方案:进行类型转换或验证
function processNumberSafe($number) {
    if (!is_numeric($number)) {
        throw new InvalidArgumentException("必须是数字");
    }

    $number = (int)$number;
    return $number * 2;
}

processNumberSafe("5");  // 正确
?>

练习题

基础练习

  1. 计算器函数

    <?php
    // 创建一个支持多种运算的计算器函数
    // 支持参数:num1, num2, operator, precision(可选,默认2位小数)
    
    // 你的代码
    
    // 测试
    echo calculate(10, 3, '/', 4);  // 应该输出:3.3333
    echo calculate(10, 3, '/');     // 应该输出:3.33
    ?>
    
  2. 个人信息格式化

    <?php
    // 创建函数格式化个人信息
    // 参数:name, age, city, country(默认"中国")
    
    // 你的代码
    
    echo formatPersonInfo("张三", 25, "北京");                    // 包含默认国家
    echo formatPersonInfo("李四", 30, "上海", "中国");
    ?>
    
  3. 数组处理函数

    <?php
    // 创建函数处理数组数据
    // 使用可变数量参数,返回最大值、最小值、平均值
    
    // 你的代码
    
    $stats = processNumbers(10, 20, 30, 40, 50);
    print_r($stats);
    ?>
    

进阶练习

  1. 用户注册验证

    <?php
    // 创建完整的用户注册验证函数
    // 参数:username, email, password, confirm_password, age(可选)
    // 返回验证结果数组
    
    // 你的代码
    
    $result = validateRegistration("testuser", "test@example.com", "password123", "password123", 25);
    print_r($result);
    ?>
    
  2. 数据过滤器

    <?php
    // 创建通用数据过滤器
    // 参数:data数组,rules数组(包含验证规则)
    // 返回过滤后的数据和错误信息
    
    // 你的代码
    
    $data = ['name' => '张三', 'email' => 'zhangsan@example.com', 'age' => '25'];
    $rules = [
        'name' => 'required|string|min:3',
        'email' => 'required|email',
        'age' => 'required|int|min:0'
    ];
    
    $result = filterData($data, $rules);
    print_r($result);
    ?>
    

实战练习

  1. 购物车系统

    <?php
    // 创建购物车管理函数
    // addToCart($cart, $productId, $quantity = 1, $price, $name)
    // removeFromCart(&$cart, $productId, $quantity = null)
    // calculateCartTotal($cart, $taxRate = 0.08)
    
    // 你的代码
    
    // 测试完整的购物车流程
    ?>
    
  2. 配置管理器

    <?php
    // 创建配置管理函数
    // setConfig($key, $value, $isPublic = true)
    // getConfig($key, $default = null)
    // getAllPublicConfigs()
    // validateConfigFormat($configs)
    
    // 你的代码
    
    // 测试配置管理功能
    ?>
    

总结

参数传递是函数设计中的重要概念,掌握参数传递的各种特性可以让我们编写更灵活、更安全的函数。通过本章的学习,你应该能够:

  1. 理解值传递和引用传递的区别
  2. 合理使用默认参数值
  3. 处理可变数量的参数
  4. 使用类型声明提高代码质量
  5. 进行充分的参数验证
  6. 在实际项目中应用最佳实践

记住,好的参数设计可以让函数更加易用和安全。在实际开发中,要始终考虑参数的验证、默认值设置和类型安全。

下一章我们将学习函数的返回值处理,了解如何让函数返回更有用的结果。