PHP 从入门到精通(第4章):字符串与 heredoc/nowdoc
🎯 开场:为什么你的程序需要"字符串"?
上 一章我们学了变量和基本数据类型,你已经能让程序记住数字、存个开关状态。但有个大问题——程序如何处理文字?
举个例子:小明的购物清单程序,上一章只能存 ["苹果", "香蕉", "牛奶"] 这种列表,但没法把"用户姓名""收货地址""订单备注"这些杂七杂八的信息组合在一起。
更实际的问题是:你想让程序自动生成一封邮件,里面包含用户的名字、订单号、快递单号——这些由用户名字+固定文字+数字拼接起来的内容,PHP 怎么做到?
这一章我们就来解决这个问题。学完你能:
- 用单引号、双引号、写多行字符串(heredoc/nowdoc)
- 把变量塞进字符串里(字符串插值)
- 熟练使用 10+ 个字符串处理函数
🧱 基础:4 个核心概念,从零理解字符串
4.1 字符串是什么?说白了就是"一串字符"
生活类比:想象你有一张便利贴,上面写满了字——这就是字符串。它可以是一个字,可以是\n\n
\n\n
\n\n一句话,可以是一篇文章。
为什么要用:程序不可能只处理数字。用户名、地址、邮件内容、网页 HTML、JSON 数据——全是字符串。
怎么用:
<?php
// 用双引号包裹
$name = "小明";
// 用单引号包裹
$city = '北京';
// 多行字符串?用 heredoc
$intro = <<<EOT
大家好,我叫小明
来自北京
喜欢编程
EOT;
解释一下:
- $name = "小明"; 用双引号包住一串文字
- $city = '北京'; 用单引号包住一串文字
- $intro = <<<EOT ... EOT; 这是 heredoc 语法,适合写多行大段文字
4.2 单引号 vs 双引号:选哪个?
生活类比:单引号像是"原样照抄"——里面写啥就显示啥。双引号像是"翻译员"——会帮你解析里面的变量和转义字符。
为什么要搞两种:有时候你不想让程序替换变量(比如你真的想显示 $name 这个符号,而不是它的值),这时候单引号就派上用场了。
怎么用:
<?php
$drink = "咖啡";
echo "小明喜欢喝 $drink"; // 输出:小明喜欢喝 咖啡(双引号会解析变量)
echo '小明喜欢喝 $drink'; // 输出:小明喜欢喝 $drink(单引号原样输出)
echo "第二行\n第三行"; // 双引号:\n 会被解析为换行符
echo '第二行\n第三行'; // 单引号:\n 原样输出
重要区别:
| 特性 | 双引号 "" |
单引号 '' |
|---|---|---|
解析变量 $var |
✅ 会替换成值 | ❌ 原样输出 |
处理 \n \t 等转义符 |
✅ 会转换成换行/tab | ❌ 原样输出 |
| 处理速度 | 稍慢(需要解析) | 稍快(直接原样) |
⚠️ 注意:如果你输出的是不含变量的纯文字,用单引号更快;如果需要插入变量或转义符,用双引号。
4.3 heredoc 和 nowdoc:写大段文字的利器
生活类比:heredoc 就像你给快递员留了一张大纸条,上面写满了多行信息,你用 <<<EOT 和 EOT 把纸条的内容框起来。nowdoc 则是把这张纸条锁在保险箱里,里面的东西完全原样不动。
为什么要用:写邮件内容、写 HTML 模板、写 SQL 语句——这些都有很多行,用双引号拼字符串会疯掉。
heredoc 基本语法:
<?php
$email_content = <<<EOT
尊敬的客户:
您的订单号是 #20240115,已于今日发货。
预计 3 天内送达。
感谢您选择我们的服务!
EOT;
echo $email_content;
输出:
尊敬的客户:
您的订单号是 #20240115,已于今日发货。
预计 3 天内送达。
感谢您选择我们的服务!
nowdoc 基本语法(适合内容里有单引号/双引号的情况):
<?php
$code_snippet = <<<'EOT'
echo "Hello, $name"; // 这里 $name 不会被解析
echo 'Welcome to PHP'; // 单引号也原样输出
EOT;
echo $code_snippet;
关键区别:
// heredoc:<<<EOT(不带引号)—— 会解析变量
$heredoc = <<<EOT
你好,$name
EOT;
// nowdoc:<<<'EOT'(带单引号)—— 不解析变量
$nowdoc = <<<'EOT'
你好,$name
EOT;
4.4 字符串插值:把变量塞进字符串里
生活类比:字符串插值就像是"填空题"。你在试卷上写"我叫,今年岁",然后把答案填进去。插值就是 PHP 帮你自动填这些空。
为什么要用:手动拼接字符串容易出错,而且代码看起来很乱:
// ❌ 传统拼接:很难读
echo "你好," . $name . ",你的订单" . $order_id . "已发货";
// ✅ 字符串插值:清晰多了
echo "你好,$name,你的订单$order_id已发货";
怎么用:
<?php
$name = "小明";
$order_id = "20240115";
$amount = 99.8;
// 最简单:直接用双引号包住变量
echo "订单用户:$name\n";
// 稍微复杂:变量和文字混在一起
echo "订单号:$order_id,总价:$amount 元\n";
// 数组元素怎么插值?用大括号包裹
$items = ["苹果", "香蕉"];
echo "购买的商品:{$items[0]} 和 {$items[1]}\n";
// 对象的属性也行
class Order {
public $id = "ORD001";
}
$order = new Order();
echo "订单ID:{$order->id}\n";
输出:
订单用户:小明
订单号:20240115,总价:99.8 元
购买的商品:苹果 和 香蕉
订单ID:ORD001
🔥 实战:3 个项目,从入门到独立完成
📦 项目 1:温度单位转换器(5 分钟)
场景:小明的妈妈在美国,她发来微信说"明天纽约气温 68 华氏度"。小明想知道这摄氏度是多少度。
<?php
// 华氏度转摄氏度的温度转换器
$fahrenheit = 68;
$celsius = ($fahrenheit - 32) * 5 / 9;
// 用插值输出,保留 1 位小数
echo "华氏度 {$fahrenheit}°F = 摄氏度 " . round($celsius, 1) . "°C\n";
// 反过来也试试:摄氏度转华氏度
$celsius2 = 20;
$fahrenheit2 = $celsius2 * 9 / 5 + 32;
echo "摄氏度 {$celsius2}°C = 华氏度 " . round($fahrenheit2, 1) . "°F\n";
预期输出:
华氏度 68°F = 摄氏度 20°C
摄氏度 20°C = 华氏度 68°F
一句话解释:用 round() 函数保留小数,四舍五入更美观。
📊 项目 2:CSV 日志分析器(15 分钟)
场景:小明在服务器上有一个访问日志文件 access_log.csv,每一行是:时间,IP地址,访问页面,状态码。他想分析哪些页面最受欢迎。
先准备测试数据:
<?php
// 创建模拟日志文件(实际使用时读取真实文件)
$log_content = <<<'EOT'
2024-01-15 10:00:00,192.168.1.100,/index.html,200
2024-01-15 10:05:00,192.168.1.101,/products.php,200
2024-01-15 10:10:00,192.168.1.100,/index.html,200
2024-01-15 10:15:00,192.168.1.102,/about.html,200
2024-01-15 10:20:00,192.168.1.100,/products.php,200
2024-01-15 10:25:00,192.168.1.103,/index.html,200
2024-01-15 10:30:00,192.168.1.101,/contact.php,404
EOT;
// 保存到临时文件
file_put_contents("access_log.csv", $log_content);
echo "=== 网站访问日志分析 ===\n\n";
// 读取文件
$lines = file("access_log.csv", FILE_IGNORE_NEW_LINES);
// 统计每个页面的访问次数
$page_views = [];
$error_pages = [];
foreach ($lines as $line) {
// 用逗号分割一行数据
$parts = explode(",", $line);
$datetime = $parts[0];
$ip = $parts[1];
$page = $parts[2];
$status = intval($parts[3]);
// 统计页面访问量
if (!isset($page_views[$page])) {
$page_views[$page] = 0;
}
$page_views[$page]++;
// 记录错误页面
if ($status >= 400) {
if (!isset($error_pages[$page])) {
$error_pages[$page] = 0;
}
$error_pages[$page]++;
}
}
// 按访问量排序(从高到低)
arsort($page_views);
echo "📈 页面访问排名:\n";
foreach ($page_views as $page => $count) {
echo " $page : $count 次访问\n";
}
if (count($error_pages) > 0) {
echo "\n⚠️ 需要检查的页面:\n";
foreach ($error_pages as $page => $count) {
echo " $page : $count 次错误\n";
}
} else {
echo "\n✅ 没有发现错误页面!\n";
}
预期输出:
=== 网站访问日志分析 ===
📈 页面访问排名:
/index.html : 3 次访问
/products.php : 2 次访问
/about.html : 1 次访问
/contact.php : 1 次访问
⚠️ 需要检查的页面:
/contact.php : 1 次错误
一句话解释:用 file() 读取文件,用 explode() 按逗号分割每一行,用 arsort() 对数组降序排列。
🛠️ 项目 3:个人简历生成器(15 分钟)
场景:小明要投简历,想用 PHP 生成一份格式化好的简历文档,方便他复制粘贴到各大招聘网站。
<?php
// 个人简历生成器
$resume = <<<'EOT'
╔══════════════════════════════════════════════════════════════╗
║ 个 人 简 历 ║
╚══════════════════════════════════════════════════════════════╝
EOT;
// 用 nowdoc 保持内容原样输出
$template = <<<'TEMPLATE'
基本信息
--------
姓名:%s
年龄:%d
手机:%s
邮箱:%s
教育背景
--------
%s
工作经历
--------
%s
技能特长
--------
%s
TEMPLATE;
// 模拟填入的数据(实际可以改成从表单或数据库读取)
$name = "张小明";
$age = 26;
$phone = "138-0013-8000";
$email = "xiaoming@example.com";
$education = <<<'EDU'
2016-2020 北京大学 计算机科学与技术 本科
EDU;
$experience = <<<'EXP'
2020-至今 字节跳动 后端开发工程师
- 负责用户增长系统后端开发
- 优化接口响应时间从 200ms 降至 50ms
- 主导数据管道重构,日处理数据量 5000W+
2019-2020 实习 美团 后端开发实习生
- 开发订单系统 RESTful API
- 完成交易模块单元测试,覆盖率 85%
EXP;
$skills = <<<'SKILLS'
- 编程语言:PHP / Python / Go
- 数据库:MySQL / Redis / MongoDB
- 框架:Laravel / Django / Gin
- 工具:Git / Docker / Kubernetes
SKILLS;
// 用 sprintf 填充模板
$resume_content = sprintf(
$template,
$name, $age, $phone, $email,
trim($education),
trim($experience),
trim($skills)
);
$resume .= "\n" . $resume_content;
// 添加结尾
$resume .= <<<'END'
---
生成时间:%s
TEMPLATE;
$resume = sprintf($resume, date("Y-m-d H:i:s"));
// 输出简历
echo $resume;
// 可以保存到文件
file_put_contents("resume.txt", $resume);
echo "\n\n简历已保存到 resume.txt\n";
预期输出:
╔══════════════════════════════════════════════════════════════╗
║ 个 人 简 历 ║
╚══════════════════════════════════════════════════════════════╝
基本信息
--------
姓名:张小明
年龄:26
手机:138-0013-8000
邮箱:xiaoming@example.com
教育背景
--------
2016-2020 北京大学 计算机科学与技术 本科
工作经历
--------
2020-至今 字节跳动 后端开发工程师
- 负责用户增长系统后端开发
- 优化接口响应时间从 200ms 降至 50ms
- 主导数据管道重构,日处理数据量 5000W+
2019-2020 实习 美团 后端开发实习生
- 开发订单系统 RESTful API
- 完成交易模块单元测试,覆盖率 85%
技能特长
--------
- 编程语言:PHP / Python / Go
- 数据库:MySQL / Redis / MongoDB
- 框架:Laravel / Django / Gin
- 工具:Git / Docker / Kubernetes
---
生成时间:2024-01-15 10:30:00
简历已保存到 resume.txt
一句话解释:用 sprintf() 把数据填充到模板里,用 heredoc/nowdoc 保持格式原样。
💪 进阶:5 个新手必踩的坑 + 2 个技巧
❌ 坑 1:单引号里写 $name 以为会输出变量值
<?php
$name = "小明";
// ❌ 错误:单引号不解析变量
echo '你好,$name'; // 输出:你好,$name
// ✅ 正确:用双引号
echo "你好,$name"; // 输出:你好,小明
// ✅ 正确:字符串拼接
echo '你好,' . $name; // 输出:你好,小明
❌ 坑 2:中文乱码——编码问题导致乱码
<?php
// ❌ 错误:没有指定编码,Windows 默认会用 GBK
$content = "你好世界";
// ✅ 正确:在输出前设置编码
header('Content-Type: text/html; charset=utf-8');
echo $content;
// ✅ 正确:文件保存为 UTF-8 编码(编辑器里设置)
❌ 坑 3:字符串拼接用 . 还是 +=?
<?php
// ❌ 错误:字符串不支持 += 操作
$text = "Hello";
$text += " World"; // 这里会变成 0(数值相加)
// ✅ 正确:用 . 拼接
$text = "Hello";
$text = $text . " World"; // Hello World
// ✅ 正确: .= 简写形式
$text = "Hello";
$text .= " World"; // Hello World
❌ 坑 4:heredoc 缩进导致解析失败
<?php
// ❌ 错误:结束标记 EOT 必须单独一行,且不能有缩进
$text = <<<EOT
你好世界
EOT; // 这行有缩进,会报错!
// ✅ 正确:EOT 单独一行,不能有空格或 tab
$text = <<<EOT
你好世界
EOT;
❌ 坑 5:数组元素插值忘加大括号
<?php
$foods = ["苹果", "香蕉", "橘子"];
// ❌ 错误:PHP 会把 $foods[0] 解析成 $foods 变量,后面跟 [0]
echo "我最喜欢吃 $foods[0]"; // 报错或输出奇怪
// ✅ 正确:用大括号包裹
echo "我最喜欢吃 {$foods[0]}"; // 输出:我最喜欢吃 苹果
// ✅ 正确:先把值取出来
$favorite = $foods[0];
echo "我最喜欢吃 $favorite"; // 输出:我最喜欢吃 苹果
🚀 性能小贴士:静态字符串用单引号
<?php
// 大量循环时,优先用单引号
// 单引号不解析变量,解析速度更快
$start = microtime(true);
for ($i = 0; $i < 100000; $i++) {
// ❌ 双引号:每次都要检查是否有变量
$a = "hello world";
}
$time1 = microtime(true) - $start;
$start = microtime(true);
for ($i = 0; $i < 100000; $i++) {
// ✅ 单引号:纯静态,解析更快
$a = 'hello world';
}
$time2 = microtime(true) - $start;
echo "双引号耗时:{$time1}秒\n";
echo "单引号耗时:{$time2}秒\n";
🔍 调试技巧:用 var_dump() 和 print_r() 查看变量
<?php
$user = [
"name" => "小明",
"age" => 26,
"skills" => ["PHP", "Python"]
];
// print_r 更美观,适合看数组结构
echo "print_r 输出:\n";
print_r($user);
// var_dump 更详细,包含类型和长度
echo "\nvar_dump 输出:\n";
var_dump($user);
输出:
print_r 输出:
Array
(
[name] => 小明
[age] => 26
[skills] => Array
(
[0] => PHP
[1] => Python
)
)
var_dump 输出:
array(3) {
["name"]=>
string(6) "小明"
["age"]=>
int(26)
["skills"]=>
array(2) {
[0]=>
string(3) "PHP"
[1]>
string(6) "Python"
}
}
✏️ 练习题 + 作业题
📝 练习 1(2 分钟):改个名字
- 输入:
$greeting = "你好,{$name}!"其中$name = "小红" - 预期输出:
你好,小红! - 提示:变量插值用双引号包住
📝 练习 2(2 分钟):判断单双引号区别
- 输入:
$a = "PHP";
echo '$a 是世界上最好的语言';
- 预期输出:
$a 是世界上最好的语言 - 提示:单引号里的变量不会被解析
📝 练习 3(3 分钟):用 heredoc 写一首诗
- 输入:用 heredoc 语法写一首四句诗
- 预期输出:格式保持原样的四行诗
- 提示:结束标记要单独一行,且前面不能有空格
📝 练习 4(5 分钟):把两个变量拼接成完整路径
- 输入:
$dir = "/var/www/html"; $file = "index.php"; - 预期输出:
完整路径是:/var/www/html/index.php - 提示:用
.拼接,或者用$dir . '/' . $file
📝 练习 5(5 分钟):分析报错并修复
- 输入:
$items = ["苹果", "香蕉"];
echo "我买了 $items[0] 和 $items[1]";
- 问题:这段代码会报错,说
$items未定义 - 提示:数组元素在字符串里要加大括号
{$items[0]}
📝 作业:做一个「留言板内容格式化工具」
需求描述:用户输入一条留言,需要格式化输出成统一的展示格式。
功能点:
1. 读取用户的昵称、留言时间、留言内容
2. 用 heredoc 生成带边框的展示格式
3. 把内容保存到 messages.txt 文件(每条留言用分隔线隔开)
加分项:
1. 检测并高亮留言中的敏感词(用 str_replace 把敏感词替换成 ***)
2. 支持查看历史留言(读取并显示 messages.txt 内容)
验收标准:
- 能运行不报错
- 输出格式和预期一致
- 代码有适当注释
提交方式:把代码复制到 评论区,我会挑选 3 位同学的代码做点评!
📚 总结 + 资源
本文学到的 3 个核心点:
- ✅ 单引号和双引号有本质区别——双引号会解析变量和转义符,单引号更快但原样输出
- ✅ heredoc/nowdoc 是写多行字符串的神器,适合模板、邮件、SQL 等场景
- ✅ 字符串插值让代码更清晰,但数组元素记得加 {}
延伸学习资源:
- 📖 PHP 官方文档:字符串函数 — 收录了所有字符串函数
- 📖 《PHP 核心技术与最佳实践》第 3 章 — 详细讲解了字符串的内部实现
- 🎬 B站:PHP 字符串处理实战 — 视频教程,20 分钟上手
互动钩子:你在实际项目中遇到过字符串乱码问题吗?是怎么解决的?评论区聊聊你的经历,老粉优先回复!
📌 下章预告:学会了字符串处理,下一章我们要学习程序的「耳朵和嘴巴」——输入输出与错误处理。用户怎么把数据交给程序?程序出错了怎么友好地提示用户?答案都在下一章!

评论(0)