1.5 字符串:单据文本的加工车间 本节摘要:字符串是月台上流转最广的物料——表单内容、URL 参数、日志行、响应片段全是字符串。本节先分清三种定界写法与插值规则,再过一遍拆、查、换、拼的高频函数,重点讲清多字节中文在字节与字符之间的差异,最后以"用户昵称清洗与截断"实战收尾。本节承上(1.4 的数组元素多数是字符串)启下(1.7 表单文本的校验与转义)。 先分清三种写法 PHP 里写字符串有三种常见定界方式,行为差异必须门儿清: 经验法则:纯文字、不含变量用单引号(少一层解析,意图也明确);需要插值用双引号,复杂表达式用花括号包住: 。拼接大段 HTML 时 heredoc 比一长串点号清爽得多。
本节摘要:字符串是月台上流转最广的物料——表单内容、URL 参数、日志行、响应片段全是字符串。本节先分清三种定界写法与插值规则,再过一遍拆、查、换、拼的高频函数,重点讲清多字节中文在字节与字符之间的差异,最后以"用户昵称清洗与截断"实战收尾。本节承上(1.4 的数组元素多数是字符串)启下(1.7 表单文本的校验与转义)。
PHP 里写字符串有三种常见定界方式,行为差异必须门儿清:
<?php $item = '行李箱'; $s1 = '一件$item'; // 单引号:原样输出,不做插值 echo $s1, "\n"; // 一件$item $s2 = "一件$item"; // 双引号:解析变量与转义符 echo $s2, "\n"; // 一件行李箱 $s3 = <<<TXT 托运单:{$item}x1 备注:易碎品,轻拿轻放 TXT; // heredoc:同双引号规则,适合多行 echo $s3, "\n";
经验法则:纯文字、不含变量用单引号(少一层解析,意图也明确);需要插值用双引号,复杂表达式用花括号包住:"共{$count}件"。拼接大段 HTML 时 heredoc 比一长串点号清爽得多。
围绕一个示例串演示,注释即输出:
<?php $line = ' G102,南苑,虹桥,08:30 '; // 查与取 echo strpos($line, '虹桥'), "\n"; // 位置(字节偏移),找不到返回 false echo substr($line, 2, 4), "\n"; // G102(按下标截取) // 换与修 echo trim($line), "\n"; // 去首尾空白:G102,南苑,虹桥,08:30 echo str_replace('南苑', '西站', $line), "\n"; // 拆与拼 $parts = explode(',', trim($line)); // 按分隔符拆成数组 print_r($parts); // [G102, 南苑, 虹桥, 08:30] echo implode(' | ', $parts), "\n"; // 反向拼回字符串 // 格式化 echo sprintf('%s 次列车 %s 发车,票价 %.2f 元', $parts[0], $parts[3], 55.5), "\n"; // G102 次列车 08:30 发车,票价 55.50 元
sprintf 值得多花两条命令练熟:%s 填字符串、%d 填整数、%.2f 控小数位,模板与数据分离,比一长串点号拼接好读得多。
这是本节的重心。PHP 的字符串本质是字节序列,strlen 数的是字节;一个 UTF-8 汉字占三个字节,于是出现了"函数说有六、肉眼只有两"的错位。凡涉及中文,一律换 mb_ 前缀的多字节函数:
<?php $nick = '月台客'; var_dump(strlen($nick)); // int(9):三个汉字共九字节 var_dump(mb_strlen($nick)); // int(3):三个字符 var_dump(substr($nick, 0, 4)); // 可能切出半个汉字,输出乱码 var_dump(mb_substr($nick, 0, 2)); // 月台:按字符切,安全
乱码问题还要守住另外两端:文件本身以 UTF-8 无签名编码保存,响应头声明 Content-Type: text/html; charset=utf-8(2.3 节展开)。三端一致(源码端、处理端、输出端),中文问题基本绝迹;任何一端不一致,都会以"页面上问号或火星文"的形式收场。大小写转换同理:strtolower 对中文无意义,对带音标的西文也不完整,需要时用 mb_strtolower。
把本节函数组装成一条完整的加工线。场景:旅客注册填写昵称,要求去掉首尾空白、压掉连续空格、过滤敏感标记、超长按字符截断,并保证截断后仍可安全输出到 HTML:
<?php function cleanNickname(string $raw, int $maxChars = 12): string { $nick = trim($raw); // 去首尾空白 $nick = preg_replace('/\s+/u', ' ', $nick); // 连续空白压成一个空格 $nick = strip_tags($nick); // 拍掉内嵌的 HTML 标签 if (mb_strlen($nick) > $maxChars) { $nick = mb_substr($nick, 0, $maxChars) . '…'; } return $nick; } echo cleanNickname(' 张 三 <b>速达</b>旅客们请注意' ), "\n"; // 张 三 速达旅客们请注… (按字符截断,无乱码)
走一遍流程解读:trim 收边、正则压空格、strip_tags 拆掉伪装成昵称的标签、超长按 mb_ 截断补省略号。输出到页面时还要过一道 htmlspecialchars(1.7 节专门讲),那是出站前的最后一道安检,与本节的入库前清洗分工不同、缺一不可。变式:若产品要求昵称唯一,清洗后还需按规范化结果查重,规范化函数定下来就别再改,否则历史数据对不上。
变式思路:如果昵称要参与唯一性判断(一个昵称只能一位旅客使用),清洗函数之外还需要一道"规范化"——统一全半角、统一大小写规则后再比对,而且规范化规则一旦上线就不能再改,否则历史数据之间对不上号。这类"格式资产"的设计决策,比函数本身更值得在动手前想清楚。另外补一个"字符与显示宽度"的边界提醒:字符数对了,显示宽度未必对——等宽终端里一个汉字通常占两个英文字符的位置,做命令行表格、对齐输出时要用"显示宽度"而非"字符数"来补空格,否则表格永远对不齐。这类细节不影响正确性,但决定交付物的体面程度。另外提醒一处性能分寸:preg_replace 与多字节函数都比基础函数慢,但它们工作在"每请求个位次数"的量级,完全不必为省这点开销牺牲正确性——真正要计较的是 2.5 节那种循环千次的场景。
为什么 strlen 统计中文数量不对?
strlen统计字节数,UTF-8 下一个汉字占三个字节,所以结果约是字符数的三倍。统计字符请用mb_strlen,截取请用mb_substr,并把多字节扩展确认在已加载清单里(1.1 节的php -m)。
mb_strlen、mb_substr 替换字节版函数,三端编码统一 UTF-8。下一节转向文件与目录:字符串要落成日志与配置,就得经过站内仓库的手。