大多数人,都低估了编程学习的难度,而高估了自己的学习能力和毅力。
当前系列: 编程语言 修改讲义


概念引入

在程序中我们经常会遇到这样一种需求:

对一段文本(字符串)进行检索,找出满足某种规则的部分,比如说找出某段文本里面所有的:

  • (单个)英文字母
  • (由单个字母组成的)英语单词
  • 以a开头或者以ing结尾的英语单词
或者进行验证,某段用户输入是不是:
  • 电话号码:以1开头的9位数字
  • email:更复杂的规则,比如包含@和.,其他由数字或字母组成……

所有这些,关键的关键,是定义规则。

这种规则,就可以用正则表达式(Regular Expression)来表示。

正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。

飞哥做一个提炼:

  • 作用:定义一种规则
  • 体现:一个字符串

几乎所有编程语言,都提供了对正则(表达式)的支持,但需要我们自己要学会编写正则表达式。

演示:使用匹配检测工具,找出所有的:a、9、ing……



单个字符,简单组合

普通字符

字母、数字、汉字、其他没有特殊定义的标点符号等

非打印字符

打印不出来的字符:

  • 缩进:\t,又称之为“制表符”
  • 换行:\n

特殊字符

又称为“元字符”,可匹配 '多种字符',常用的有:

  • \d:任意一个数字,0~9 中的任意一个 (digit)
  • \w:任意一个字母或数字或下划线,也就是 A~Z,a~z,0~9,_ 中任意一个 (word)
  • \s:包括空格、制表符、换页符等空白字符的其中任意一个(space)
  • .:小数点可以匹配除了换行符(\n)以外的任意一个字符

注意上面的字母全是小写,如果变成大写就“取反”,比如:

  • \D:所有数字的字符
  • \W:所有字母数字下划线的字符
  • \S:所有空格(制表/换行等)字符
也可以将“非打印字符”视为特殊字符

边界符号

不匹配任何字符:
  • ^:与字符串开始的地方匹配
  • $:与字符串结束的地方匹配
    @想一想@:^和$有什么用呢?
  • \b:匹配一个单词边界,也就是单词和空格之间的位置(break)

转义字符

上述特殊字符都有特殊含义(比如:^ $ .),但如果我们就需要匹配这些特殊字符呢?

用一个斜杠表示转义(即取消其特殊含义)即可,比如:\^ \$ \.


更多的选项

特殊字符有时候还不能满足我们的要求,比如说我们可能需要匹配一些自定义规则的字符:

  • 奇数数字:可以把所有满足条件的字符罗列出来,即:
    [13579]
  • 5-9之间的数字:可以用短横线(-)将起止数字连接起来,即
    [5-9]
    字母也一样,比如[a-d],或者[X-Z]
  • 不能为0的数字:可以用小尖符号表示取反,即:
    [^0]

单个字符用方括号 [ ]。多个字符就需要用竖线(|)表示"或" 关系。比如:

在这句话 this is a goverment for the people, thank you! 里面找到this和the,其正则表达式就可以是:

  • this|the
    或者用圆括号包裹可选部分
  • th(is|e)

注意

  • 方括号中使用竖线(|)没有“或”的意思
  • ^符号在圆括号中没有“取反”的意思


重复

无论普通/特殊,正则表达式的字符都可以组合使用,比如:

  • 1\d:表示以1开头,后面跟一个数字
但我想以1开头,后面跟9个或者若干个数字呢?

可以用{}指定可重复次数:

  • {n}:表达式重复n次,比如:点击测试 "\w{2}" 相当于 "\w\w"; "a{5}" 相当于 "aaaaa"
  • {m,n}:表达式至少重复m次,最多重复n次,比如:"ba{1,3}"可以匹配 "ba"或"baa"或"baaa"
  • {m,}:表达式至少重复m次,比如:"\w\d{2,}"可以匹配 "a12","_456","M12344"...

简写方式

  • ?:匹配表达式0次或者1次,相当于 {0,1},比如: "1\D?"可以匹配 "1","1+",不能匹配"11"
  • +:表达式至少出现1次,相当于 {1,},比如: "1\D+"可以匹配 "1+",不能匹配"1","1-"...
  • *:表达式不出现或出现任意次,相当于 {0,},比如: "1\D*"可以匹配 "1","1+","1++"...

多个字符

如果重复的是多个字符,可以用圆括号将其括来,后面直接跟{}或简写方式。

比如,IP地址由数字和点(.)组合而成的,最多每3个数字后面就接1个点,比如:192.168.0.10。

正则表达式可以写成:

\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}

注意“\d{1,3}\.”重复了3次:这样显得比较累赘。

我们可以把“数字+.”视为一“组”,用圆括号(())括起来,将其重复3次,如下所示:

(\d{1,3}\.){3}\d{1,3}


练习/演示:身份证

根据以下(简略)条件:

  1. 身份证号码一共有18位数字
  2. 第18位数字是校检码,除数之外,还可以是X
  3. 第7~14位数字表示:出生年、月、日

写一个正则表达式验证是查找)用户输入的是不是身份证号码。

涉及的关键知识点:

  • \d:数字
  • {}:指定重复次数
  • ^$:确保对整个字符串进行验证,而不是在字符串中查找
  • []:末尾可以是数字,也可以是X;年份从1900到2100中19/20/21的问题
  • |:解决月份01-12问题:0后面可以跟1-9,1后面只能跟0-2


贪婪与非贪婪

默认取最大长度的匹配结果:贪婪模式。

比如这句话:

this is a goverment, of the people,for the people, by the people!

里我想取this is a goverment,(以逗号结束)

用正则表达式:

.*,

得到的结果却是:

this is a goverment, of the people,for the people,

因为贪婪模式将government后面的,也视为可匹配“.”。如何解决这个问题呢?

  • 方案一:用[^,]代替.
    [^,]*,
  • 非贪婪模式,在截至字符前面加一个问号(?)即可
    .*?,

?的意思可理解为:到此为止,找到一个满足条件的就行!

非贪婪模式在找出引号括号等(文本中可能出现多个)界定中内容的时候非常有用。比如:找出本讲义前面若干段中()中的数据

([\s\S]*?)

@想一想@:用.代替[\s\S]和行不行?为什么?


零宽断言

上面的正则表达式取出的内容包括了圆括号,能不能只要圆括号里面的内容,不要圆括号本身呢?
  • 去掉结尾
    (?=))
  • 去掉开头
    (?<=()

这就是所谓的零宽断言(什么鬼名字?^_^)注意其语法特点:

  • 要有一个圆括号把不包含的字符括起来。这里注意区分全角的()和半角的()。
  • 然后,?=用在末尾,?<=用在开头,这是有次序的

完整的写法:

(?<=()[\s\S]*?(?=))

意外:a开头之后的单词

可能你首先想到的正则是这样的:

(?<=a)\w+

但用文本

aside along apple banana

测试,你会发现anana也别匹配进来了。怎么办?

你想到了前面学的\b,于是你可能会使用:

\b(?<=a)\w+
但这是不对的,正确的写法是:
(?<=\ba)\w+
结论:零宽断言前后都不要再有其他表达式!


分组语法

比如我们想找出一段文字

好好学习天天向上

中重复的叠声词(好好,天天)

\S\S

只能找出所有的两个汉字,“学习”和“向上”不能满足要求。这就必须使用分组的语法

捕获和引用

正则表达式中,用圆括号(())括起来一部分内容,这被称之为:捕获

捕获的内容还可以被正则表达式使用,引用的方式是反斜杠+数字,这杯称之为:引用

所以,上述要求正确的写法应该是:

(\S)\1

其中:

  • (\S)是第一个分组;
  • \1代表该正则表达式中第一个分组捕获的内容(是规则!)。如果表达式中有多个分组,可以依次用\2、\3……表示
比如说我们要找出“高高兴兴”“快快乐乐”“开开心心”这样的双叠音词,怎么办?
(\S)\1(\S)\2

注意:\1和\2的区别。

命名

还可以给捕获命名,比如:
(?<name>\S)\k<name>
注意:
  • 分组中用?<name>指定捕获名称
  • 后文中使用\k<name>引用


作业

  1. 用正则表达式找出这段话
    I'm going to learn programing e.g. Java, C#, in 2021 when I am 23 years old. At that time, things will be different.里面的:
    1. 数字,比如:2、0、1……(答案:\d)
    2. 数字“串”,比如:2021、22
    3. 单词,比如:going、to、At(大小写都行)……不包含I'm、e.g.、C#(带有标点符号)和2021(数字)
    4. 字母个数等于4个、大于4个、小于4个的单词
    5. 以t开头的单词
    6. 以I或者i开头的“字段”(单词,以及单词简写比如I'm)
    7. 以大写字母开头的单词,比如Java、At,C#不是单词,还是不包含
    8. 以ing结尾的单词
    9. 以ing结尾的单词中不包含ing的部分
  2. 继续完成身份证号码验证
  3. 假设有下面这一段文本:【难】
    <h3 
        script  ="onclick:function(){}">
    	概念引入
    </h3>
    <img src= '/picture.jpg' alt='源栈logo' />
    <p style="color:red;">
    	在程序中我们经常会遇到这样一种<strong>需求</strong>:当name="飞哥"时 </p>
    写出正则表达式,能匹配得到:
    1. 相邻>和<之间的内容,比如:概念引入、在程序中我们经常会遇到这样一种
    2. 相邻<和>之间的内容,比如:<p style="color:red;">、<img src='/picture.jpg' alt='源栈logo' />、</p>
    3. 对2得到的内容再进行匹配,得到
      1. <后面的一个单词,比如:p、img(/p不要)
      2. ="前面的单词,比如:style、src、alt
学习笔记
源栈学历
今天学习不努力,明天努力找工作

作业

觉得很 ,不要忘记分享哟!

任何问题,都可以直接加 QQ群:273534701

在当前系列 编程语言 中继续学习:

多快好省!前端后端,线上线下,名师精讲

  • 先学习,后付费;
  • 不满意,不要钱。
  • 编程培训班,我就选源栈

更多了解 加:

QQ群:273534701

答疑解惑,远程debug……

B站 源栈-小九 的直播间

写代码要保持微笑 (๑•̀ㅂ•́)و✧

公众号:源栈一起帮

二维码