6.1 字符串基础

字符串是PHP编程中最基本也是最常用的数据类型之一。理解字符串的基础知识是掌握PHP字符串处理的第一步。本节将详细介绍字符串的创建、表示、访问、修改等基本操作。

字符串的定义和概念

什么是字符串?

字符串是由零个或多个字符组成的有限序列。在PHP中,字符串可以包含:

  • 字母(大小写英文字母、中文字符等)
  • 数字(0-9)
  • 特殊符号(!@#$%^&*()等)
  • 空格和空白字符
  • 控制字符(换行符、制表符等)

字符串在内存中的表示

PHP将字符串存储为字节序列,这意味着:

<?php
// 不同编码的字符串
$ascii_string = "Hello";        // ASCII编码,每个字符占1字节
$utf8_chinese = "你好";         // UTF-8编码,中文字符通常占3字节
$emoji = "😊";                  // UTF-8编码,emoji通常占4字节

// 查看字符串的字节长度
echo strlen($ascii_string) . "\n";      // 输出: 5
echo strlen($utf8_chinese) . "\n";      // 输出: 6 (2个中文字符×3字节)
echo strlen($emoji) . "\n";             // 输出: 4

// 查看字符数量(需要使用多字节函数)
echo mb_strlen($utf8_chinese, 'UTF-8') . "\n";  // 输出: 2
?>

字符串的创建方式

PHP提供了多种创建字符串的方式,每种方式都有其特点和适用场景。

1. 单引号字符串

单引号是最简单的字符串定义方式:

<?php
// 基本单引号字符串
$simple = 'Hello World';
$chinese = '你好,PHP编程';
$empty = '';

// 单引号中的特殊字符处理
$text = 'This is a \n test';  // \n 不会被解析为换行符
$quote = 'It\'s a test';      // 使用反斜杠转义单引号
$backslash = 'Path\\to\\file'; // 转义反斜杠

echo $simple . "\n";
echo $text . "\n";           // 输出: This is a \n test
echo $quote . "\n";          // 输出: It's a test
?>

单引号的特点:

  • 变量和转义字符(除了\和')不会被解析
  • 处理速度较快
  • 适合包含纯文本内容

2. 双引号字符串

双引号字符串支持变量解析和转义字符:

<?php
$name = "张三";
$age = 25;

// 变量解析
$greeting = "你好,$name!";           // 输出: 你好,张三!
$info = "姓名:{$name},年龄:{$age}岁"; // 输出: 姓名:张三,年龄:25岁

// 转义字符解析
$multiline = "第一行\n第二行\t制表符";
$quote = "他说:\"PHP很有趣!\"";
$dollar = "价格:\$100";

echo $greeting . "\n";
echo $info . "\n";
echo $multiline . "\n";
echo $quote . "\n";
echo $dollar . "\n";
?>

双引号的特点:

  • 支持变量解析(简单变量和数组元素)
  • 支持转义字符(\n, \t, \r, \, $, "等)
  • 处理速度比单引号稍慢
  • 适合包含变量和需要转义的文本

3. Heredoc语法

Heredoc用于创建包含大量文本的字符串:

<?php
$name = "李四";

// Heredoc语法
$html = <<<HTML
<!DOCTYPE html>
<html>
<head>
    <title>欢迎页面</title>
</head>
<body>
    <h1>欢迎,{$name}!</h1>
    <p>这是一个使用Heredoc创建的HTML模板。</p>
</body>
</html>
HTML;

echo $html;

// 不包含变量的Heredoc
$text = <<<TEXT
这是一个长文本,
可以跨越多行,
不需要担心引号问题。
"双引号"和'单引号'都可以直接使用。
TEXT;

echo $text;
?>

4. Nowdoc语法

Nowdoc类似于单引号,不解析变量和转义字符:

<?php
$name = "王五";

// Nowdoc语法
$template = <<<'TEMPLATE'
用户模板
---------
姓名:{$name}  // 这里的变量不会被解析
邮箱:user@example.com
"引号"不会被转义
TEMPLATE;

echo $template;
?>

字符串的访问和操作

访问字符串中的字符

PHP提供了多种访问字符串中单个字符的方法:

<?php
$string = "Hello PHP";

// 方法1:使用花括号(PHP 7.4+已废弃)
$char1 = $string{0};     // 'H'
$char2 = $string{6};     // 'P'

// 方法2:使用方括号(推荐)
$char3 = $string[0];     // 'H'
$char4 = $string[6];     // 'P'

// 获取字符串长度并访问最后一个字符
$length = strlen($string);
$last_char = $string[$length - 1]; // 'P'

echo "第一个字符: {$char3}\n";
echo "第七个字符: {$char4}\n";
echo "最后一个字符: {$last_char}\n";

// 遍历字符串中的每个字符
for ($i = 0; $i < $length; $i++) {
    echo "位置{$i}: {$string[$i]}\n";
}
?>

修改字符串中的字符

可以通过索引直接修改字符串中的字符:

<?php
$string = "Hello World";

// 修改单个字符
$string[0] = 'J';        // 变为 "Jello World"
$string[6] = 'P';        // 变为 "Jello Porld"

echo $string . "\n";

// 批量修改示例:首字母大写
function capitalizeFirst($str) {
    if (empty($str)) return $str;
    $str[0] = strtoupper($str[0]);
    return $str;
}

echo capitalizeFirst("hello") . "\n";  // "Hello"
echo capitalizeFirst("world") . "\n";  // "World"
?>

字符串连接

基本连接操作

<?php
// 使用点号连接
$first_name = "张";
$last_name = "三";
$full_name = $first_name . $last_name;
echo $full_name . "\n";  // "张三"

// 连接多个字符串
$greeting = "你好," . $first_name . $last_name . "!";
echo $greeting . "\n";  // "你好,张三!"

// 连接不同类型的值
$age = 25;
$message = "我今年" . $age . "岁了";
echo $message . "\n";   // "我今年25岁了"
?>

使用连接赋值运算符

<?php
$text = "Hello";
$text .= " ";        // 等同于 $text = $text . " ";
$text .= "World";
$text .= "!";

echo $text . "\n";   // "Hello World!"

// 构建HTML示例
$html = "<div>";
$html .= "<h1>标题</h1>";
$html .= "<p>段落内容</p>";
$html .= "</div>";

echo $html . "\n";
?>

复杂字符串构建

<?php
// 构建SQL查询示例
function buildSelectQuery($table, $columns = [], $where = '') {
    $query = "SELECT ";

    if (empty($columns)) {
        $query .= "*";
    } else {
        $query .= implode(", ", $columns);
    }

    $query .= " FROM {$table}";

    if (!empty($where)) {
        $query .= " WHERE {$where}";
    }

    return $query;
}

echo buildSelectQuery("users", ["id", "name", "email"], "age > 18") . "\n";
// 输出: SELECT id, name, email FROM users WHERE age > 18
?>

字符串比较

相等性比较

<?php
// 使用 == 比较(值相等)
$str1 = "hello";
$str2 = "hello";
$str3 = "Hello";  // 大小写不同

var_dump($str1 == $str2);  // bool(true)
var_dump($str1 == $str3);  // bool(false)

// 使用 === 比较(值和类型都相等)
var_dump($str1 === $str2); // bool(true)
var_dump($str1 === "123"); // bool(false)

// 注意:PHP的字符串和数字比较
$number_str = "123";
$number = 123;

var_dump($number_str == $number);  // bool(true) - 类型转换
var_dump($number_str === $number); // bool(false) - 类型不同
?>

大小写敏感比较

<?php
// strcmp() 函数 - 大小写敏感
$result1 = strcmp("Apple", "apple");   // 返回负数
$result2 = strcmp("apple", "Apple");   // 返回正数
$result3 = strcmp("hello", "hello");   // 返回0

echo "Apple vs apple: " . $result1 . "\n";
echo "apple vs Apple: " . $result2 . "\n";
echo "hello vs hello: " . $result3 . "\n";

// 比较结果说明
// 返回值 < 0: 第一个字符串小于第二个字符串
// 返回值 > 0: 第一个字符串大于第二个字符串
// 返回值 = 0: 两个字符串相等
?>

大小写不敏感比较

<?php
// strcasecmp() 函数 - 大小写不敏感
$result1 = strcasecmp("Apple", "apple");   // 返回0
$result2 = strcasecmp("Hello", "HELLO");    // 返回0
$result3 = strcasecmp("PHP", "Python");     // 返回非0

echo "strcasecmp结果:\n";
var_dump($result1);  // int(0)
var_dump($result2);  // int(0)
var_dump($result3);  // int(-1)

// 实际应用:验证用户输入(不区分大小写)
$input = "ADMIN";
$correct_username = "admin";

if (strcasecmp($input, $correct_username) === 0) {
    echo "用户名正确!\n";
} else {
    echo "用户名错误!\n";
}
?>

自然排序比较

<?php
// strcmp vs strnatcmp
$files1 = ["file1.txt", "file10.txt", "file2.txt"];
$files2 = ["file1.txt", "file10.txt", "file2.txt"];

// 普通比较
sort($files1);
echo "普通排序: " . implode(", ", $files1) . "\n";
// 输出: file1.txt, file10.txt, file2.txt

// 自然排序
usort($files2, "strnatcmp");
echo "自然排序: " . implode(", ", $files2) . "\n";
// 输出: file1.txt, file2.txt, file10.txt
?>

特殊字符和转义序列

常用转义字符

<?php
// 在双引号字符串中的转义字符
$text = "换行符:\n制表符:\t回车符:\r";
echo $text . "\n";

// 引号转义
$single = "单引号:\'";
$double = "双引号:\"";
$backslash = "反斜杠:\\";

echo $single . "\n";
echo $double . "\n";
echo $backslash . "\n";

// 美元符号转义
$variable = "变量名使用:\$name 而不是变量的值";
echo $variable . "\n";
?>

八进制和十六进制字符

<?php
// 八进制表示
$octal_a = "\101";  // 'A' 的八进制表示
$octal_newline = "\012";  // 换行符的八进制表示

// 十六进制表示
$hex_a = "\x41";   // 'A' 的十六进制表示
$hex_newline = "\x0A";  // 换行符的十六进制表示

echo "八进制A: {$octal_a}\n";
echo "十六进制A: {$hex_a}\n";
?>

Unicode字符

<?php
// Unicode字符表示(PHP 7.0+)
$unicode1 = "\u{4F60}";    // '你'
$unicode2 = "\u{597D}";    // '好'
$emoji = "\u{1F603}";      // 😊

echo "Unicode字符: {$unicode1}{$unicode2}\n";
echo "Emoji: {$emoji}\n";

// 实际应用:生成特殊符号
$symbols = [
    'check' => "\u{2713}",   // ✓
    'cross' => "\u{2717}",   // ✗
    'star' => "\u{2605}",    // ★
];

echo "状态: " . $symbols['check'] . " 完成\n";
?>

中文字符串处理

UTF-8编码的重要性

处理中文字符串时,必须注意编码问题:

<?php
// 设置内部编码为UTF-8
mb_internal_encoding('UTF-8');

// 中文文本
$chinese_text = "你好,世界!这是一个中文字符串测试。";

// 错误的长度计算(按字节)
echo "字节长度: " . strlen($chinese_text) . "\n";  // 输出字节数

// 正确的字符数量计算
echo "字符数量: " . mb_strlen($chinese_text, 'UTF-8') . "\n";

// 截取子字符串
$substring = mb_substr($chinese_text, 0, 5, 'UTF-8');
echo "前5个字符: {$substring}\n";

// 获取单个字符
$first_char = mb_substr($chinese_text, 0, 1, 'UTF-8');
echo "第一个字符: {$first_char}\n";
?>

中文字符串操作最佳实践

<?php
class ChineseStringHelper {
    /**
     * 安全地获取中文字符串长度
     */
    public static function length($str) {
        return mb_strlen($str, 'UTF-8');
    }

    /**
     * 安全地截取中文字符串
     */
    public static function substring($str, $start, $length = null) {
        return mb_substr($str, $start, $length, 'UTF-8');
    }

    /**
     * 按字符分割中文字符串
     */
    public static function split($str) {
        $length = mb_strlen($str, 'UTF-8');
        $chars = [];

        for ($i = 0; $i < $length; $i++) {
            $chars[] = mb_substr($str, $i, 1, 'UTF-8');
        }

        return $chars;
    }

    /**
     * 检查是否包含中文字符
     */
    public static function containsChinese($str) {
        return preg_match('/[\x{4e00}-\x{9fa5}]/u', $str) > 0;
    }
}

// 使用示例
$text = "PHP编程很有趣!";

echo "字符串长度: " . ChineseStringHelper::length($text) . "\n";
echo "前3个字符: " . ChineseStringHelper::substring($text, 0, 3) . "\n";

$chars = ChineseStringHelper::split($text);
echo "字符数组: " . implode(", ", $chars) . "\n";

echo "包含中文: " . (ChineseStringHelper::containsChinese($text) ? "是" : "否") . "\n";
?>

常见错误和解决方案

1. 编码问题

错误示例:

<?php
// 错误:没有指定编码
$chinese = "你好";
echo strlen($chinese);  // 可能输出错误的结果
?>

正确做法:

<?php
// 正确:使用多字节函数并指定编码
$chinese = "你好";
echo mb_strlen($chinese, 'UTF-8');
?>

2. 字符串越界访问

错误示例:

<?php
$str = "hello";
echo $str[10];  // 访问不存在的字符,会产生Notice
?>

正确做法:

<?php
$str = "hello";
$length = strlen($str);
if ($length > 10) {
    echo $str[10];
} else {
    echo "索引超出范围";
}
?>

3. 混淆比较运算符

错误示例:

<?php
$str1 = "123";
$str2 = 123;

if ($str1 == $str2) {  // 可能不是你想要的结果
    echo "相等";  // 会输出,因为类型转换
}
?>

正确做法:

<?php
$str1 = "123";
$str2 = 123;

if ($str1 === $str2) {  // 严格比较
    echo "完全相等";
} else {
    echo "类型或值不同";
}
?>

实际应用示例

用户输入处理

<?php
class InputProcessor {
    /**
     * 清理和验证用户名
     */
    public static function processUsername($username) {
        // 去除首尾空格
        $username = trim($username);

        // 检查长度
        $length = mb_strlen($username, 'UTF-8');
        if ($length < 3 || $length > 20) {
            return ['success' => false, 'message' => '用户名长度必须在3-20个字符之间'];
        }

        // 检查是否包含特殊字符
        if (preg_match('/[^a-zA-Z0-9\x{4e00}-\x{9fa5}_]/u', $username)) {
            return ['success' => false, 'message' => '用户名只能包含字母、数字、中文和下划线'];
        }

        // 转换为安全格式
        $safe_username = htmlspecialchars($username, ENT_QUOTES, 'UTF-8');

        return ['success' => true, 'username' => $safe_username];
    }
}

// 测试
$result1 = InputProcessor::processUsername(" 张三123 ");
print_r($result1);

$result2 = InputProcessor::processUsername("ab");
print_r($result2);

$result3 = InputProcessor::processUsername("张三@#");
print_r($result3);
?>

简单的模板引擎

<?php
class SimpleTemplate {
    private $template;

    public function __construct($template) {
        $this->template = $template;
    }

    /**
     * 渲染模板
     */
    public function render($variables = []) {
        $result = $this->template;

        foreach ($variables as $key => $value) {
            // 简单的变量替换 {变量名}
            $result = str_replace('{' . $key . '}', $value, $result);
        }

        return $result;
    }

    /**
     * 渲染并输出
     */
    public function display($variables = []) {
        echo $this->render($variables);
    }
}

// 使用示例
$template = new SimpleTemplate(<<<TEMPLATE
<div class="user-profile">
    <h1>{name}</h1>
    <p>邮箱:{email}</p>
    <p>年龄:{age}岁</p>
    <p>注册时间:{register_date}</p>
</div>
TEMPLATE);

$user_data = [
    'name' => '张三',
    'email' => 'zhangsan@example.com',
    'age' => 25,
    'register_date' => date('Y-m-d')
];

$template->display($user_data);
?>

本节练习

基础练习

  1. 使用四种不同的方式(单引号、双引号、Heredoc、Nowdoc)创建相同的字符串
  2. 编写一个函数,将字符串的首字母大写,其余字母小写
  3. 创建一个包含中文、英文、数字和特殊符号的字符串,并计算其字符数量

进阶练习

  1. 编写一个函数,检查字符串是否为回文(正读反读都一样)
  2. 实现一个简单的字符串加密函数(如凯撒密码)
  3. 创建一个函数,统计字符串中每个字符出现的次数

实战练习

  1. 完善InputProcessor类,添加邮箱和手机号验证功能
  2. 扩展SimpleTemplate类,支持条件渲染和循环
  3. 创建一个中文字符串处理工具类,包含常用方法

总结

本节我们学习了PHP字符串的基础知识,包括:

  • 字符串的概念和内存表示
  • 四种字符串创建方式及其特点
  • 字符串的访问、修改和连接操作
  • 字符串比较的各种方法
  • 特殊字符和转义序列的使用
  • 中文字符串处理的注意事项
  • 常见错误和解决方案

掌握这些基础知识是学习高级字符串处理技巧的前提。在下一节中,我们将学习PHP提供的强大字符串处理函数,这些函数将大大提高我们处理文本的能力。

💡 学习建议:多动手练习每个示例,特别注意中文字符串处理中的编码问题。在实际开发中,建议总是使用UTF-8编码和多字节字符串函数来处理中文内容。