
第4章 字符串操作与正则表达式
(视频讲解:1小时56分钟)
在Web编程中,字符串总是会被大量地生成和处理。正确地使用和处理字符串,对于PHP程序员来说越来越重要。本章从最简单的字符串定义一直引导读者到复杂的正则表达式,希望广大读者能够通过本章的学习,了解和掌握PHP字符串,达到举一反三的目的,为了解和学习其他的字符串处理技术奠定良好的基础。
学习摘要:
字符串的定义
字符串操作
正则表达式
正则表达式应用
4.1 字符串的定义方法

视频讲解
字符串,顾名思义,就是将一堆字符串联在一起。字符串最简单的定义方法是使用英文单引号('')或双引号("")包含字符。另外,还可以使用定界符指定字符串。
4.1.1 使用单引号或双引号定义字符串
字符串通常以串的整体作为操作对象,一般用双引号或者单引号标识一个字符串。单引号和双引号在使用上有一定区别。
下面分别使用双引号和单引号来定义一个字符串。例如:

运行结果如下:

从上面的结果中可以看出,对于定义的普通字符串看不出两者之间的区别。而通过对变量的处理,即可轻松地理解两者之间的区别。例如:

运行结果如下:

从以上代码中可以看出,双引号中的内容是经过PHP的语法分析器解析过的,任何变量在双引号中都会被转换为它的值进行输出显示;而单引号的内容是“所见即所得”的,无论有无变量,都被当作普通字符串进行原样输出。
说明
单引号字符串和双引号字符串在PHP中的处理是不相同的。双引号字符串中的内容可以被解释并且被替换,而单引号字符串中的内容则被作为普通字符进行处理。
4.1.2 使用定界符定义字符串
定界符(<<<)是从PHP 4.0开始支持的。定界符用于定义格式化的大文本,格式化是指文本中的格式将被保留,所以文本中不需要使用转义字符。在使用时后接一个标识符,然后是字符串,最后是同样的标识符结束字符串。定界符的语法格式如下:

其中str为指定的标识符,标识符读者可以自己设定,切记要前后保持一致。
使用定界符定义字符串的方式与双引号没什么区别,包含的变量也被替换成实际数值,例如:

运行结果如下:

注意
结束标识符必须单独另起一行,并且不允许有空格。在标识符前后有其他符号或字符,也会发生错误。
4.2 字符串操作

视频讲解
字符串的操作在PHP编程中占有重要的地位,几乎所有的输入与输出都用到字符串。尤其是在PHP项目开发过程中,为了实现某项功能,经常需要对某些字符串进行特殊处理,如获取字符串的长度、截取字符串、替换字符串等。本节将对PHP常用的字符串操作技术进行详细讲解,并通过具体的实例加深对字符串函数的理解。
4.2.1 去除字符串首尾空格和特殊字符
用户在输入数据时,可能会无意中输入多余的空格,或在一些情况下,字符串前后不允许出现空格和特殊字符,此时就需要去除字符串中的空格和特殊字符。例如,图4.1中“HELLO”这个字符串前后都有一个空格。可以使用PHP中提供的trim()函数去除字符串左右两边的空格和特殊字符,也可以使用ltrim()函数去除字符串左边的空格和特殊字符,或使用rtrim()函数去除字符串右边的空格和特殊字符。

图4.1 前后包含空格的字符串
1.trim()函数
trim()函数用于去除字符串首尾处的空白字符(或者其他字符)。语法格式如下:

参数及返回值说明如下。
str:操作的字符串。
charlist:为可选参数,一般要列出所有希望过滤的字符,也可以使用“..”列出一个字符范围。如果不设置该参数,则所有的可选字符都将被删除。trim()如果不指定charlist参数,trim()函数将去除表4.1中的字符。
返回值:过滤后的字符串。
表4.1 不指定charlist参数trim()函数去除的字符

注意
除了以上默认的过滤字符列表外,也可以在charlist参数中提供要过滤的特殊字符。
【例4.01】 明日学院网站中有搜索课程和社区的功能,当在输入框中输入关键词并单击“搜索”按钮时,程序会先处理用户输入的关键词,将关键词左右的空格去除。使用trim()函数实现该功能,代码如下:(实例位置:资源包\源码\04\4.01)

运行结果如图4.2所示。

图4.2 trim()去除左右空格
2.ltrim()函数
ltrim()函数用于去除字符串左边的空格或者指定字符串。ltrim()函数参数与trim()函数相同。语法格式如下:

例如,使用ltrim()函数去除字符串左边的空格及特殊字符(:@_@,代码如下:

运行结果如下:

3.rtrim()函数
rtrim()函数用于去除字符串右边的空格或指定字符。语法格式如下:

例如,使用rtrim()函数去除字符串右边的空格及特殊字符@_@:),代码如下:

运行结果如下:

4.2.2 获取字符串的长度
在PHP中常见的计算字符串长度的函数有strlen()和mb_strlen()。当字符全是英文字符时,两者功能是一样的。但是,当字符串中包含中文时,所占字节有所不同。先来了解一下英文和中文所占字节情况。
数字、英文、小数点、下画线和空格占1字节,一个汉字可能会占2~4字节,具体占几字节取决于采用的是什么编码。汉字在GBK/GB 2312编码中占2字节,在UTF-8/Unicode中一般占用3字节(或2~4字节)。本书中所有文件均使用UTF-8编码,即一个汉字占3字节,如图4.3所示。

图4.3 汉字和英文所占字节个数
下面讲解如何使用strlen()和mb_strlen()函数获取指定字符串的长度。
1.strlen()函数
strlen()函数主要用于获取指定字符串的长度。语法格式如下:

参数及返回值说明如下。
str:需要计算长度的字符串。
返回值:成功则返回字符串str的长度;如果str为空,则返回0。
例如,使用strlen()函数来获取指定字符串的长度,代码如下:

在上述代码中“明日学院官方网站:”均为中文字符,每个占3字节,共占用27字节。“www. mingrisoft.com”均为英文字符,每个占1字节,共占用18字节。运行结果如下:

2.mb_strlen()函数
由于strlen()无法正确处理中文字符串,它得到的只是字符串所占的字节数,可以采用mb_strlen()函数较好地解决这个问题。
mb_strlen()函数主要用于获取指定字符串的长度。语法格式如下:

参数及返回值说明如下。
str:需要计算长度的字符串。
encoding:字符编码。如果省略,则使用内部字符编码。
返回值:返回具有encoding编码的字符串$str包含的字符数。多字节的字符被计为1。如果给定的encoding无效则返回false。
mb_strlen()函数的用法和strlen()类似,只不过它有第二个可选参数用于指定字符编码。例如,得到UTF-8的字符串$str长度,可以用mb_strlen($str,'UTF-8')。如果省略第二个参数,则会使用PHP的内部编码。内部编码可以通过mb_internal_encoding()函数得到。
注意
mb_strlen()并不是PHP核心函数,使用前需要确保在php.ini中加载了php_mbstring.dll,即确保extension=php_mbstring.dll这一行存在并且没有被注释掉,否则会出现未定义函数的问题。
【例4.02】 明日学院网站注册页面中,用户注册时输入的用户名必须为3~18位中英文字符,既可以是全中文,也可以是全英文或者中英文混合。使用mr_strlen()函数实现该功能,代码如下:(实例位置:资源包\源码\04\4.02)

运行结果如图4.4所示。

图4.4 判断用户名是否满足条件
4.2.3 截取字符串
PHP对字符串截取可以采用内置函数substr()和mb_substr()实现。通常使用substr()函数截取英文字符,mb_substr()函数截取中文或中英文混合字符。
1.substr()函数
substr()函数的语法格式如下:

参数及返回值说明如下。
str:指定字符串对象。
start:指定开始截取字符串的位置。如果参数start为负数,则从字符串的末尾开始截取。
length:可选参数,指定截取字符的个数,如果length为负数,则表示取到倒数第length个字符。
返回值:返回提取的子字符串,或者在失败时返回false。
注意
本函数中,参数start的指定位置是从0开始计算的,即字符串中的第一个字符表示为0,如图4.5所示。

图4.5 start开始位置
使用substr()函数截取字符串中指定长度的字符,代码如下:

运行结果如下:

由于在UTF-8编码下,一个汉字占3字节,所以在使用substr()函数时,可能出现截取汉字不完整的情况。例如,使用substr()函数截取字符串“Hi明日科技”,代码如下:

上述代码中,start为0,length为7,即从第一个位置开始,截取7字节,如图4.6所示。由于在第7个字符位置,汉字“日”没有被截取完成,将会出现汉字乱码,运行结果如图4.7所示。

图4.6 substr()函数截取

图4.7 substr()函数截取汉字乱码
2.mb_substr()函数
针对substr()函数截取汉字乱码问题,可以使用mb_substr()函数来解决。mb_substr()函数语法格式如下:

参数及返回值说明如下。
str:从该string中提取子字符串。
start:str中要截取的第一个字符的位置。
length:可选参数,指定截取字符的个数,如果length为负数,则表示取到倒数第length个字符。
encoding:字符编码。如果省略,则使用内部字符编码。
返回值:根据start和length参数返回str中指定的部分。
【例4.03】 在明日学院网站“最新动态”专栏中,显示所有最新课程标题的列表。为了保持整个页面的合理布局,需要对一些超长标题进行部分显示,使用mb_substr()函数截取超长文本的部分字符串,剩余的部分用“…”代替。代码如下:(实例位置:资源包\源码\04\4.03)

运行结果如图4.8所示。

图4.8 mb_substr()函数截取字符串
4.2.4 检索字符串
在PHP中,提供了很多应用于字符串查找的函数,最常用的有strstr()函数和strpos()函数。
1.strstr()函数
获取一个指定字符串在另一个字符串中首次出现的位置到后者末尾的子字符串。语法格式如下:

参数及返回值说明如下。
haystack:指定从该字符串中进行搜索。
needle:指定搜索的对象。如果needle不是一个字符串,那么它将被转化为整型并且作为字符的序号来使用。
before_needle:可选参数,默认为false。若为true,strstr()将返回needle在haystack中的位置之前的部分。
返回值:返回haystack字符串从needle第一次出现的位置开始到haystack结尾的字符串。
例如,获取“Hi明日科技”字符串中“明日”以后的内容。代码如下:

strstr()函数实现方式如图4.9所示,运行结果如下:

图4.9 strstr()函数实现方式

注意
本函数区分字母的大小写,如不区分大小写,可以使用stristr()函数。
【例4.04】 使用strstr()函数,根据邮箱地址,获取邮箱用户名和服务器名,代码如下:(实例位置:资源包\源码\04\4.04)

运行结果如图4.10所示。

图4.10 strstr()函数获取用户名和服务器名
说明
strrchr()函数与其正好相反,该函数是从字符串倒序的位置开始检索子字符串的。
2.strpos()函数
查找字符串首次出现的位置,返回首次出现的数字位置。语法格式如下:

参数及返回值说明如下。
haystack:必要参数,指定从该字符串中进行搜索。
needle:必要参数,指定搜索的对象。如果needle不是一个字符串,那么它将被转化为整型并且作为字符的序号来使用。
offset:可选参数,默认为0。如果提供了此参数,搜索会从字符串该字符数的起始位置开始统计。
返回值:返回needle存在于haystack字符串起始的位置。同时注意字符串位置是从0开始,而不是从1开始的。如果没找到needle,将返回false。
注意
本函数区分字母的大小写,如不区分大小写,可以使用stripos()函数。
例如,获取“Hi明日科技”字符串中“明日”以后的内容。代码如下:

strpos()函数实现方式如图4.11所示,运行结果如下:

图4.11 strpos()函数实现方式

说明
strrpos()函数与其正好相反,该函数是计算指定字符串在目标字符串中最后一次出现的位置。strrpos()函数也区分大小写,如不区分大小写,可以使用strripos()函数。
4.2.5 替换字符串
通过字符串的替换技术可以实现对指定字符串中的指定字符进行替换。字符串的替换技术可以通过str_replace()函数和substr_replace()函数实现。
1.str_replace()函数
使用新的子字符串替换原始字符串中被指定要替换的字符串。语法格式如下:

将所有在参数subject中出现的参数search以参数replace取代,参数&count表示取代字符串执行的次数。本函数区分大小写。
参数及返回值说明如下。
search:必要参数,要搜索的值,可以使用数组来提供多个值。
replace:必要参数,指定替换的值。
subject:必要参数,要被搜索和替换的字符串或数组。
count:可选参数,如果被指定,它的值将被设置为替换发生的次数。
返回值:替换后的字符串或者数组。
例如,将文本中的指定字符串“某某”替换为“**”,并且输出替换后的结果,代码如下:

运行结果如下:

注意
str_replace()函数在执行替换操作时区分大小写,如果不需要对大小写加以区分,可以使用str_ireplace()函数。
2.substr_replace()函数
对指定字符串中的部分字符串进行替换。语法格式如下:

参数及返回值说明如下。
string:指定要操作的原始字符串,可以是字符串或数组。
replacement:指定替换后的新字符串。
start:指定替换字符串开始的位置。正数表示替换从字符串的第start位置开始;负数表示替换从字符串的倒数第start位置开始;0表示替换从字符串中的第一个字符开始。
length:可选参数,指定返回的字符串长度。默认值是整个字符串。正数表示被替换的子字符串的长度;负数表示待替换的子字符串结尾处距离字符串末端的字符个数;0表示将replacement插入到string的start位置处。
返回值:返回结果字符串。如果string是个数组,那么也将返回一个数组。
注意
如果参数start设置为负数,而参数length数值小于或等于start数值,那么length的值自动为0。
【例4.05】 明日学院网站举办抽奖活动,活动结束后将获奖用户姓名和手机号公布在网站上,为保护用户隐私,将获奖用户的手机号中间4位用“****”替换,代码如下:(实例位置:资源包\源码\04\4.05)

运行结果如图4.12所示。

图4.12 substr_replace()函数替换手机号
4.2.6 分割、合成字符串
在PHP中,提供了分割和合成字符串的函数,它们都与数组相关。数组就是一组数据的集合,把一系列数据组织起来,形成一个可操作的整体。数组的知识会在第5章讲解,先来了解一下如何分割和合成字符串。
1.分割字符串
explode()函数按照指定的规则对一个字符串进行分割,返回值为数组。语法格式如下:

参数及返回值说明如下。
delimiter:边界上的分隔符。
string:指定将要被进行分割的字符串。
limit:可选参数,如果设置了limit,则返回的数组包含最多limit个元素,而最后的元素将包含$string的剩余部分。
返回值:该函数返回由字符串组成的数组,每个元素都是string的一个子串,它们被字符串delimiter作为边界点分割出来。
2.合成字符串
implode()函数可以将数组的内容组合成一个新字符串。语法格式如下:

参数及返回值说明如下。
glue:指定分隔符。
pieces:指定要被合并的数组。
返回值:返回一个字符串,其内容为由glue分割开的数组的值。
例如,使用implode()函数将数组中的内容以@为分隔符进行连接,从而组合成一个新的字符串,代码如下:

运行结果如下:

说明
implode()函数和explode()函数是两个相对的函数,一个用于合成,一个用于分割。
4.3 正则表达式

视频讲解
4.3.1 正则表达式简介
在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。换句话说,正则表达式就是记录文本规则的代码。对于接触过DOS的读者来说,如果想匹配当前文件夹下所有的文本文件,可以输入dir *.txt命令,按Enter键后所有.txt文件将会被列出来。这里的*.txt即可理解为一个简单的正则表达式。
4.3.2 行定位符
行定位符用来描述字串的边界。“^”表示行的开始;“$”表示行的结尾。例如:

该表达式表示要匹配字串tm的开始位置是行头,如tm equal Tomorrow Moon就可以匹配,而Tomorrow Moon equal tm则不匹配。但如果使用:

后者可以匹配而前者不能匹配。如果要匹配的字串可以出现在字符串的任意部分,那么可以直接写成:

这样两个字符串就都可以匹配了。
4.3.3 元字符
现在我们已经知道几个很有用的元字符了,如“^”“$”。正则表达式中还有更多的元字符,下面来看看更多的例子:

匹配以字母mr开头的单词,先是从某个单词开始处(\b),然后匹配字母mr,接着是任意数量的字母或数字(\w*),最后单词结束处(\b)。该表达式可以匹配mrsoft、mrbook、mr123456等。更多常用元字符如表4.2所示。
表4.2 常用元字符

4.3.4 限定符
在上面的例子中,使用(\w*)匹配任意数量的字母或数字。如果想匹配特定数量的数字,该如何表示呢?正则表达式提供了限定符(指定数量的字符)来实现该功能。如匹配8位QQ号可用如下表示式:

常用的限定符如表4.3所示。
表4.3 常用限定符

4.3.5 字符类
正则表达式查找数字和字母是很简单的,因为已经有了对应这些字符集合的元字符(如\d和\w),但是如果要匹配没有预定义元字符的字符集合(如元音字母a,e,i,o,u),应该怎么办?
很简单,只需要在方括号中列出它们即可,像[aeiou]就匹配任何一个英文元音字母,[.?!]匹配标点符号(.或?或!)。也可以轻松地指定一个字符范围,像[0-9]代表的含意与\d就是完全一致的:一位数字;同理[a-z0-9A-Z_]也完全等同于\w(如果只考虑英文的话)。
4.3.6 排除字符
上面的例子是匹配符合命名规则的变量。现在反过来,匹配不符合命名规则的变量,正则表达式提供了“^”字符。这个元字符在4.3.2节中出现过,表示行的开始。而这里将会放到方括号中,表示排除的意思。例如:

该表达式匹配的就是不以字母开头的变量名。
4.3.7 选择字符
试想一下,如何匹配身份证号码?首先需要了解一下身份证号码的规则。身份证号码长度为15位或者18位。如果为15位时,则全为数字;如果为18位时,前17位为数字,最后一位是校验位,可能为数字或字符X。
在上面的描述中,包含着条件选择的逻辑,这就需要使用选择字符(|)来实现。该字符可以理解为“或”,匹配身份证的表达式可以写成如下方式:

该表达式的意思是匹配15位数字,或者18位数字,或者17位数字和最后一位。最后一位可以是数字或者是X或者是x。
4.3.8 转义字符
正则表达式中的转义字符(\)和PHP中的大同小异,都是将特殊字符(如“.”“?”“\”等)变为普通的字符。举一个IP地址的实例,用正则表达式匹配诸如127.0.0.1这样格式的IP地址。如果直接使用点字符,格式如下:

这显然不对,因为“.”可以匹配一个任意字符。这时,不仅是127.0.0.1这样的IP,连127101011这样的字串也会被匹配出来。所以在使用“.”时,需要使用转义字符(\)。修改后上面的正则表达式格式如下:

说明
括号在正则表达式中也算是一个元字符。
4.3.9 分组
通过4.3.8节中的例子,相信读者已经对小括号的作用有了一定的了解。小括号字符的第一个作用就是可以改变限定符的作用范围,如“|”“*”“^”等。来看下面的一个表达式:

这个表达式的意思是匹配单词thirth或fourth,如果不使用小括号,那么就变成了匹配单词thir和fourth了。
小括号的第二个作用是分组,也就是子表达式。如(\.[0-9]{1,3}){3},就是对分组(\.[0-9]{1,3})进行重复操作。
4.4 正则表达式在PHP中的应用

视频讲解
PHP中提供了两套支持正则表达式的函数库,即PCRE函数库和POSIX函数库。PCRE函数库在执行效率上要略优于POSIX函数库,所以这里只讲解PCRE函数库中的函数。PCRE函数库中常用函数如表4.4所示。
表4.4 PCRE函数库中常用函数

下面讲解如何使用PHP中最常用的preg_match()函数。
preg_match()函数用于执行匹配正则表达式,函数语法如下:

参数及返回值说明如下。
pattern:要搜索的模式,字符串类型。
subject:输入字符串。
matches:可选参数,如果提供了参数matches,它将被填充为搜索结果。$matches[0]将包含完整模式匹配到的文本,$matches[1]将包含第一个捕获子组匹配到的文本,以此类推。
返回值:返回pattern的匹配次数。它的值将是0次(不匹配)或1次,因为preg_match()在第一次匹配后将会停止搜索。如果发生错误preg_match()返回false。
【例4.06】 在明日学院网站注册页面中,需要对用户输入的手机号码格式进行检测,以避免用户手误导致注册失败。使用preg_match()函数实现该功能,代码如下:(实例位置:资源包\源码\04\4.06)

运行结果如图4.13所示。

图4.13 preg_match()函数检测手机号码格式
多学两招
preg_match_all()函数用于执行一个全局正则表达式匹配。它会一直搜索subject直到到达结尾。
4.5 小结
本章主要对常用的字符串操作技术进行了详细讲解,其中去除字符串首尾空格、获取字符串的长度、截取字符串和字符串的查找与替换等都是需要重点掌握的技术。此外,还介绍了正则表达式的基础知识。这些内容也是作为一个PHP程序员必须熟悉和掌握的知识。相信通过本章的学习,读者能够举一反三,对所学知识灵活运用,从而开发实用的PHP程序。
4.6 实战
4.6.1 “…”代替多余字符
实例位置:资源包\源码\04\实战\01
模拟淘宝详情页,当商品名称多于30字时,截取前20个字符,剩余用“…”代替。运行结果如图4.14所示。

图4.14 实例运行结果
4.6.2 判断车牌号归属地
实例位置:资源包\源码\04\实战\02
将“津A·12345”“沪A·23456”“京A·34567”这3张号牌放到数组中,然后在遍历数组的过程中完成对每张号牌归属地的判断,运行结果如图4.15所示。

图4.15 实例运行结果
4.6.3 检测邮箱格式
实例位置:资源包\源码\04\实战\03
模拟明日学院网站登录页面,实现检测邮箱格式的功能,运行结果如图4.16所示。

图4.16 实例运行结果