参数传递
函数参数就像是函数的"输入接口",它们允许我们将数据传递给函数进行处理。理解参数传递机制是编写灵活、可重用函数的关键。
参数的基本概念
什么是参数?
参数是传递给函数的值,函数可以使用这些值来完成特定的任务。参数就像是给函数的"原材料",函数根据这些原材料来工作。
<?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("小红"); // 输出:你好, 小红!
?>
默认参数的规则
- 默认参数必须放在非默认参数的后面
- 默认值必须是常量表达式,不能是变量或函数调用
<?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"); // 正确
?>
练习题
基础练习
-
计算器函数
<?php // 创建一个支持多种运算的计算器函数 // 支持参数:num1, num2, operator, precision(可选,默认2位小数) // 你的代码 // 测试 echo calculate(10, 3, '/', 4); // 应该输出:3.3333 echo calculate(10, 3, '/'); // 应该输出:3.33 ?> -
个人信息格式化
<?php // 创建函数格式化个人信息 // 参数:name, age, city, country(默认"中国") // 你的代码 echo formatPersonInfo("张三", 25, "北京"); // 包含默认国家 echo formatPersonInfo("李四", 30, "上海", "中国"); ?> -
数组处理函数
<?php // 创建函数处理数组数据 // 使用可变数量参数,返回最大值、最小值、平均值 // 你的代码 $stats = processNumbers(10, 20, 30, 40, 50); print_r($stats); ?>
进阶练习
-
用户注册验证
<?php // 创建完整的用户注册验证函数 // 参数:username, email, password, confirm_password, age(可选) // 返回验证结果数组 // 你的代码 $result = validateRegistration("testuser", "test@example.com", "password123", "password123", 25); print_r($result); ?> -
数据过滤器
<?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); ?>
实战练习
-
购物车系统
<?php // 创建购物车管理函数 // addToCart($cart, $productId, $quantity = 1, $price, $name) // removeFromCart(&$cart, $productId, $quantity = null) // calculateCartTotal($cart, $taxRate = 0.08) // 你的代码 // 测试完整的购物车流程 ?> -
配置管理器
<?php // 创建配置管理函数 // setConfig($key, $value, $isPublic = true) // getConfig($key, $default = null) // getAllPublicConfigs() // validateConfigFormat($configs) // 你的代码 // 测试配置管理功能 ?>
总结
参数传递是函数设计中的重要概念,掌握参数传递的各种特性可以让我们编写更灵活、更安全的函数。通过本章的学习,你应该能够:
- 理解值传递和引用传递的区别
- 合理使用默认参数值
- 处理可变数量的参数
- 使用类型声明提高代码质量
- 进行充分的参数验证
- 在实际项目中应用最佳实践
记住,好的参数设计可以让函数更加易用和安全。在实际开发中,要始终考虑参数的验证、默认值设置和类型安全。
下一章我们将学习函数的返回值处理,了解如何让函数返回更有用的结果。