什么是阶乘
阶乘(Factorial)是一个数学运算,用感叹号表示,比如n!。它指的是从1一直乘到n的所有整数的乘积。
举个具体的例子:
-
4! = 4 × 3 × 2 × 1 = 24
-
6! = 6 × 5 × 4 × 3 × 2 × 1 = 720
需要记住的两个关键点:
-
阶乘只针对正整数(0和正整数)
-
0! 的结果是1,这是数学上的约定
个人经验:很多初学者会忽略0! = 1这个边界情况,导致写函数时处理不了输入0的场景。后面写递归版本时会看到,递归的终止条件$n <= 1正是为了覆盖0和1两种情况。
两种实现方式的对比
| 方式 | 原理 | 适用场景 |
|---|---|---|
| 循环(迭代) | 用for或while依次相乘 | 大多数常规场景,容易理解 |
| 递归 | 函数调用自身,逐层返回 | 数学概念清晰,但要注意调用深度 |
示例一:使用for循环计算阶乘(递减方式)
代码号学习编程中,通常从递减循环开始理解阶乘。
<?php
$num = 4;
$factorial = 1;
for ($x = $num; $x >= 1; $x--) {
$factorial = $factorial * $x;
}
echo "数字 {$num} 的阶乘结果是 {$factorial}";
?>
输出:数字 4 的阶乘结果是 24
为什么初始值设为1:因为乘法的单位元是1,如果设为0那么所有乘积都会变成0。这个细节新手比较容易忽略。
示例二:使用for循环计算阶乘(递增方式)
两种循环方向都可以,关键在于乘法的交换律。
<?php
$num = 5;
$factorial = 1;
for ($i = 1; $i <= $num; $i++) {
$factorial = $factorial * $i;
}
echo "5! = {$factorial}"; // 输出 120
?>
个人经验:我自己更习惯用递增方式,因为顺着自然数顺序读起来更直观。但从执行效率来看两者没有差别,看个人编码习惯。
示例三:通过表单动态计算阶乘
下面这个代码号学习编程的例子展示了如何在网页中接收用户输入并返回结果。
<html>
<head>
<title>阶乘计算器 - 代码号学习编程</title>
</head>
<body>
<form method="post">
请输入一个非负整数:<br>
<input type="number" name="number" id="number" min="0">
<input type="submit" name="submit" value="计算阶乘">
</form>
<?php
if ($_POST) {
$factorial = 1;
$number = $_POST['number'];
// 输入有效性检查
if ($number < 0) {
echo "阶乘只对非负整数有定义,请输入0或正整数。";
} elseif ($number == 0) {
echo "0! = 1";
} else {
for ($i = 1; $i <= $number; $i++) {
$factorial = $factorial * $i;
}
echo "{$number}! = {$factorial}";
}
}
?>
</body>
</html>
示例四:使用递归方法计算阶乘
递归是一种函数调用自身的编程技巧,在数学定义清晰的场景下写出来很简洁。
<?php
function factorial($n) {
if ($n <= 1) {
return 1;
} else {
return $n * factorial($n - 1);
}
}
echo "6! = " . factorial(6); // 输出 720
?>
递归的调用过程(以factorial(4)为例):
factorial(4) = 4 * factorial(3)
factorial(3) = 3 * factorial(2)
factorial(2) = 2 * factorial(1)
factorial(1) = 1
往回计算:2×1=2 → 3×2=6 → 4×6=24
本节课程知识要点
| 知识点 | 说明 |
|---|---|
| 阶乘定义域 | n ≥ 0 的整数,0! = 1 是边界条件 |
| 累积器模式 | 用变量存储中间计算结果,初始值视运算类型而定(乘法用1,加法用0) |
| 递归基线条件 | 必须设置终止条件($n<=1),否则会无限循环导致栈溢出 |
| 递归调用栈 | 每层调用占用内存,过深的递归(如n>1000)可能耗尽PHP的递归限制 |
递归和循环的选择建议
这是项目开发中经常需要考虑的问题。我说一下自己的看法:
用循环的情况:
-
数值较大(比如超过1000)
-
代码维护者可能不熟悉递归概念
-
性能敏感的场景(递归有函数调用开销)
用递归的情况:
-
数学定义本身就是递归形式(比如阶乘、斐波那契数列)
-
代码需要保持数学上的简洁表达
-
处理树形数据结构(这个后面会接触到)
坦白说,对于阶乘这种简单问题,我通常直接用循环。递归虽然写法优雅,但在PHP中多了一层函数调用开销,而且一旦输入的数值大了还可能碰到递归深度限制(默认通常是256层)。
常见错误与排查
错误1:阶乘结果超出整数范围
-
PHP中整数超过平台较大值(64位系统约9.22×10^18)会自动转为浮点数,超过20!就已经超出这个范围了
-
解决:需要大数运算时可以用BCMath扩展的
bcmul()函数
错误2:输入0时报错
-
原因:循环条件
$i<=$number在number=0时不成立,但number=0时不成立,但factorial保持了初始值1,逻辑上其实正确。但如果代码里有其他特殊处理可能会出问题 -
解决:显式处理0的情况,让代码意图更清楚
错误3:递归忘记写终止条件
-
后果:函数无限调用自己,最终报
Maximum function nesting level错误
个人经验分享
当初刚学递归的时候,我总在想“为什么函数能调用自己”,感觉挺反直觉的。后来用断点调试一步步看调用过程才慢慢理解。我的建议是:如果你觉得递归难懂,可以先熟练循环版本,递归不着急硬啃。在工作中,很多团队甚至规定尽量不用递归,因为可读性和调试便利性的问题。
另外写阶乘程序时,边界条件(0和负数)的处理能体现一个开发者的严谨程度。面试中问阶乘,面试官往往不是看你写不写得出来,而是看你有没有考虑到这些特殊情况。