substr("abc", 1, -1) 返回空字符串,因为起始位置1得子串"bc"(长2),负长度-1要求从原串末尾省略1字符,但2 + (-1) = 1 < 起始偏移量1,触发边界修正为0,故返回空。

直接说结论:用 substr() 截字符串,关键不是“怎么写”,而是“起始位置和长度怎么算对”——尤其当出现负数时,行为容易反直觉。
为什么 substr("abc", 1, -1) 返回空字符串?
因为 length 为负数时,它表示“从字符串末尾往前省略多少个字符”,但前提是:先按 start 定位起始点,再在这个子串上做“末尾截断”。
而 "abc" 长度是 3,start = 1 后剩下子串是 "bc"(2 个字符),此时 length = -1 意味着“从 "bc" 末尾省略 1 个字符”,结果是 "b" ——但等等,实际运行却是空字符串?
这是因为:当 start 为正、length 为负,且 start + length 时(本例中 1 + (-1) = 0),PHP 认为“截取范围无效”,直接返回空字符串(PHP 8.0+ 行为,旧版可能返回 <code>false)。
-
substr("hello", 2, -1)→"ll"(合法:2 + (-1) = 1 > 0) -
substr("hi", 1, -1)→""(非法:1 + (-1) = 0) -
substr("x", 0, -1)→""(合法起点,但省略全部 → 空)
substr() 的 start 为负数时,到底从哪开始?
负数 start 是从整个原字符串末尾倒数,不是从当前已截取部分倒数。比如:
立即学习“PHP免费学习笔记(深入)”;
-
substr("abcdef", -2)→"ef"(倒数第 2 个字符开始,到末尾) -
substr("abcdef", -3, 1)→"d"(倒数第 3 个是"d",取 1 个) -
substr("abcdef", -4, -1)→"cd"(从倒数第 4 个"c"开始;然后从整个字符串末尾省略 1 个 → 原串去掉末尾"f",剩下"abcde";再从"c"开始取到"e")
注意:start 超出范围(如 -10 对于长度为 6 的字符串)会返回空字符串,不会报错。
什么时候必须换用 mb_substr()?
只要字符串含中文、日文、emoji 或其他 UTF-8 多字节字符,就该换。因为 substr() 按字节操作,"你好" 是 6 字节,substr("你好", 0, 2) 会切出两个乱码字节,浏览器显示为 。
- 启用
mbstring扩展(检查phpinfo()中是否有mbstring support => enabled) - 显式传入编码:如
mb_substr($str, 0, 5, 'utf-8');不传可能依赖默认编码,不稳定 -
mb_substr()的start和length都是按“字符”计数,不是字节,所以mb_substr("?Hello", 0, 2)稳定返回"?H"
别指望 mb_internal_encoding('utf-8') 全局设置能兜底 —— 函数参数优先级更高,漏写编码参数等于白换。
substr() 和 substr_replace() 别混用
名字像,但功能完全不同:substr() 是只读提取,substr_replace() 是原地替换或插入。
-
substr("abcde", 1, 2)→"bc"(纯取) -
substr_replace("abcde", "X", 1, 2)→"aXde"(把"bc"换成"X") -
substr_replace("abc", "XY", 1, 0)→"aXYbc"(在位置 1 插入,length=0是关键)
常见误用:想删掉某段却写了 substr(...),结果只是取出来没赋值;或者想取子串却调了 substr_replace(),白跑一遭还改了原串。
最易被忽略的点:负数 length 在 substr() 和 substr_replace() 中语义一致(都指“从末尾省略”),但 substr_replace() 的 offset 为负时,替换起点仍按原字符串算,不是按替换后字符串算 —— 这点一旦嵌套使用,极易出错。



















