如果有人和你说, 如果不将字符串转换为数字, 你要如何判断字符串是否由全数字组成? 把字符串拆成 char 数组, 然后放入一个循环, 来判断每个 char 是否为数字? 那你要如何判断手机号是否合法? IP 是否合法呢? 把字符串拆成 char 数组总不是个办法啊, 是否有更好的解决办法? 有的, 正则表达式就是正则表达式是什么? 可以这么说, 它是一种字符串语法, 可以形容字符串的格式本文就来介绍正则表达式, 就像我在其他博客中讲过的, 我不喜欢把所有规则列出来, 规则在网上一搜一大把我要讲的是正则表达式究竟能够做什么, 怎么使用正则表达式
维基百科中, 正则表达式的解释为: 正则表达式(英语: Regular Expressionregex 或 regexp, 缩写为 RE), 也译为正规表示法常规表示法, 在计算机科学中, 是指一个用来描述或者匹配一系列符合某个句法规则的字符串的单个字符串没错, 正则表达式也是一个字符串, 只不过这个字符串能够用来判断另一个字符串是否满足一定规则, 如是否由全数字组成, 是否是合法的手机号码, 是否是合法的 IP 只要掌握了正则表达式, 你就相当于掌握了一种描述正则表达式的语法, 当然这种语法不是用来和人交流的, 而是和机器交流的, 你只要打出 ^((\w)(\w)\1\2)+$ 机器就能告诉你一个字符串是否长成 ababab... 这样下面我来讲解正则表达式我不会列出所有语法, 因为网上很多, 如果你懒得找, 这里有个网址 正则表达式手册, 这里有全部的语法, 如果你看不懂, 或者记不得了, 打开看一下就可以了
我会以 Lesson 的形式, 先介绍实例, 然后详细分析实例, 因为正则表达式的规则确实很难记, 但是能达到的效果是很好记的, 只要你看过有人实现了利用正则表达式来判断一个字符串是否由全数字组成, 那么你就再也不会忘了, 下次再有类似的要求的时候, 你至少知道用正则表达式来做会很简单, 那么剩下的就是翻出正则表达式的手册, 研究一阵子, 自然就出来了, 因为虽然规则难记, 但是很好理解
Lesson1 判断字符串是否由全字符组成
该题的想法很简单, 从头到尾, 全是数字就可以了打开手册, 看到表示开头的字符是 ^, 表示结尾的字符是 $, 表示数字的字符是 \ d, 然后看到想要匹配多个字符, 有两个选择,+ 和 *,+ 是至少出现一次,* 是 0 次或者多次,^\d+$ 这就完成了! 好, 让我们来试一试在 c# 中, 正则表达式相关的类是 System.Text.RegularExpressions.Regex, 其中我用到的方法有 Match,Replace, 和 IsMatch, 分别是匹配, 替换和判断是否匹配的便捷方法用法也很简单, 看下我的例子就会明白了
- internal static class RegexExtension
- {// 将匹配信息转换为字符串信息
- public static string Convert2String(this Match match, string enter)
- {
- StringBuilder builder = new StringBuilder();
- builder.AppendFormat("匹配的字符串为:{0}, 是否匹配成功 {1}, 匹配到的字符串为{2}, 匹配的位置为{3}, 匹配的长度为{4}, 一共匹配到{5} 个结果",
- enter, match.Success, match.Value, match.Index, match.Length, match.Groups.Count);
- return builder.ToString();
- }
- }
- public static void Lesson1(){
- Console.WriteLine("全数字的判断:");
- string[] enters = {"123123123", "123a123", "a123123", "","0 "};
- foreach (var enter in enters){
- Console.WriteLine(
- Regex.Match(enter, @"^\d+$").Convert2String(enter));
- }
- }
Regex.Match 方法会将 enter 与规则进行匹配, 然后将结果转换为字符串, 例子中只有 123123123 会匹配成功, 其他都是失败, 即使 0 也会失败, 是因为有空格, 而 \ d 只会匹配数字是不是很简单? 下面来看第二课
Lesson2 判断号码是否为合法的座机或者手机号码
判断号码是否为固话或者手机号, 固话是 7-8 位, 手机为 11 位, 这时就需要 2 种情况都可以匹配, 先看固话, 全是数字已经会了, 是 ^\d+$ , 只要加上个数的限制就可以了, 查看手册, 发现{m,n} 描述有几个字符, m 表示最少出现的次数, n 表示最多出现的次数那么 + 字符就应该是和{1,} 一样, 不填 n 表示不限制最大次数, 那么 m 不填就表示不限制最小次数然后是手机, 手机为 11 位, 且由 1 开头, 且都为数字, 那么就很简单了:^1\d{10}$ 如何把他们拼起来? 字符 | 可以办到, 下面是代码
- public static void Lesson2(){
- Console.WriteLine("是否为合法的电话号码, 电话号码的规则分两部分:");
- Console.WriteLine("固话为 7-8 位, 手机为 11 位, 且由 1 开头, 且都为数字");
- string landPhoneRule = @"^\d{7,8}$";
- string handPhoneRule = @"^1\d{10}$";
- // 规则合并
- string rule = string.Format("{0}|{1}", landPhoneRule, handPhoneRule);
- string[] enters ={
- "1234567", //7 位数字, 合法
- "12345678", //8 位数字, 合法
- "13888888888", //11 位以 1 开头的数字, 合法
- "23888888888", //11 位以 2 开头的数字, 非法
- "0123456789", //10 位数字, 非法
- "1388888888a",// 带有字符, 非法
- "10111111111"//11 数字, 合法
- };
- foreach (var enter in enters){
- Console.WriteLine(Regex.Match(enter, rule));
- }
- Console.WriteLine("现改变手机的规则, 改为: 手机要以数字 1 开头, 且第二位和第三位不能有数字 0, 其他不变");
- handPhoneRule = @"^1[1-9]{2}\d{8}$";
- rule = string.Format("{0}|{1}", landPhoneRule, handPhoneRule);
- foreach (var enter in enters){
- Console.WriteLine(Regex.Match(enter, rule));
- }
- }
我们看到 10111111111 也能匹配, 显然这不是个手机号, 因此在后面我更改了规则, 添加了第二位和第三位不是 0 的限制这时 \d 不满足条件了, 查看手册, 发现 [] 字符, 可以在里面添加候选字符, 例如 [123] 指匹配 123, 也可以用 - 来添加范围, 如 [0-9] 和 \d 是一样的那么更改后的手机号码匹配的规则就变成了 ^1[1-9]{2}\d{8}$ 下面是第三课
Lesson3 判断 IP 是否合法
这一题, 我将 IP 的判断稍微简化了些, 规则是必须是 ***.***.***.*** , 其中每项的字符数最少为 1 位, 最多为 3 位, 且 255>=***>= 0, 第一项不能为 0\00\000 这一题的判断要复杂的多, 您也能看到正则表达式的一个短板, 那就是不能获取字符的意义, 后面我会解释这一点看条件, 基本的语法我前面都介绍了, 该规则可以拆分为两部分, 第一项和后三项第一项, 255>=***>= 0, 且不能为 0\00\000 我在思考这道题的时候, 先按照正向的思路想, 即描述什么样的满足条件, 情况非常多, 01,001,011,1-249,250-255, 一共这么多种情况, 正则表达式是没有办法忽略的, 如果是数字, if (001 == 1)是成立的, 但是正则表达式办不到, 你只能 (0[2]1)|1 来描述 1 和 001 都满足条件, 这就是我前面说的, 无法获取字符本身的意义, 你只能每一位的描述一个字符串, 第一位是什么, 第二位不是什么, 而无法通过意义来形容字符串我通过正向的列举每种情况, 表达式是这样的:
(0{2}[1-9]|0[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|0[1-9]|[1-9]\d|[1-9])
其意义大概就是把 01,001,011,1-249,250-255 的全部情况都列举了出来, 这么做没什么错, 但是太长了, 有没有什么简便做法? 正向思考不行, 那么我们来反向试一下: 第一项是 1-3 个数字, 不能全是 0, 不能大于 255, 也就是不能是 ^0{1,3},2[6-9]\d,25[6-9],[3-9]\d{2}, 只要不是前面的条件, 就可以如何形容不满足的条件? [^]可以, 但是只能指明一个字符可以看到手册的下面有几个?<! 这样的字符, 他们表示附近是否满足条件, 举个例子:?! 加一起表示正向否定的预查找,
Windows(?!95|98|NT|2000)
能匹配 Windows3.1 中的 Windows, 但不能匹配 Windows2000 中的 Windows, 还有其他的这里我用?<! , 表示反向否定查找, 即(?<!123)456 表示能匹配 23456, 但是不能匹配前面带 123 的 456, 如 123456 就不满足条件这个正向和反向就是前面和后面, 肯定就是匹配, 否定就是排除我的第一项的表达式是:^(\d{1,3})(?<!^0{1,3}|2[6-9]\d|25[6-9]|[3-9]\d{2}), 第二项的前面如果是上述几种情况, 则第二项永远不会匹配接下来是后面三项, 后面的三项都是 .***,255>=***>=0, 语句是(.[01]?\d?\d|2[0-4]\d|25[0-5]){3}$, 加起来就是 ^(\d{1,3})(?<!^(0{1,3}|2[6-9]\d|25[6-9]|[3-9]\d{2}))(.[01]?\d?\d|2[0-4]\d|25[0-5]){3}$ 代码如下:
- public static void Lesson3(){
- Console.WriteLine("题目是判断一个 IP 是否合法, 规则如下:");
- Console.WriteLine("格式必须是 ***.***.***.***");
- Console.WriteLine("其中第一组数字必须大于 1, 每组数字都要小于等于 255");
- // 复杂版
- string rule = @"^(\d{1,3})(?<!^(0{1,3}|2[6-9]\d|25[6-9]|[3-9]\d{2}))(.[01]?\d?\d|2[0-4]\d|25[0-5]){3}$";
- string[] enters =
- {
- "255.255.255.255", // 合法
- "21.1.1.1", // 合法
- "256.0.0.0", // 非法
- "300.2.2.250", // 非法
- "10.1.1.99", // 合法
- "00.1.1.009",// 非法
- "100.1.1.1"// 合法
- };
- foreach (var enter in enters){
- Console.WriteLine(Regex.Match(enter, rule).Convert2String(enter));
- }
- }
这里要吐槽下, 如果能转成数字, 判断 IP 是否合法, 只要把数字截出来, 判断>=0&& <=255 就可以了, 这里也可能有更简便的做法, 笔者一时没想出来, 欢迎有好办法的同学留言给我
Lesson4 函数替换
这个题目是我学习正则表达式的起源当时我在重构代码, 发现有一个方法写的多余我们管这个多余的方法叫 MA, 应该换成 MB(另一个方法),MA 接受 2 个变量, MB 接受一个变量 VS 提供了自动更换函数名字的功能, 但是对于函数变量的变化就无能为力了当时全工程有接近 50 个 MA 的调用, 都要替换成 MB, 如果手动一下一下的删除多余的参数, 那实在是太恶心了 VS 查找界面提供了替换功能, 我看到搜索选项里面有正则表达式一项, 是否能够使用正则表达式来自动替换? 于是就开始研究起正则表达式, 是 1 天还是 2 天, 磕磕绊绊, 研究出了表达式, 然后一试, 确实好使! 怕匹配错, 就点击替换, 一个一个的换, 中间多次调整表达式, 最终在没有手动更改的情况下, 全部替换成功, 当时真的很高兴, 感觉正则表达式很神奇因此我就把这个情景转换为第四课的内容, 前面讲的都是匹配, 这一课就来看看如何进行替换下面是题目的要求, 上面的一组 Console.WriteLine 是原语句, 下面的 4 个 Console.WriteLine 是替换后的样子就是用 MB 替换 MA, 并保留 MA 的第一个参数给 MB, 舍弃第二个参数每条语句都毫无意义, 只是用来替换
- // 下面的语句没有任何的实际意义, 只是模拟 想要替换的语句的具体使用
- Console.WriteLine(MA("a", "b")),
- Console.WriteLine("a" + MA("a", GetType().ToString()));
- Console.WriteLine(MA("a", GetType().ToString()));
- Console.WriteLine("a" + MA("a", "a".Substring(1)) + "b");
- // 替换成如下的样子
- Console.WriteLine(MB("a"));
- Console.WriteLine("a" + MB("a"));
- Console.WriteLine(MB("a"));
- Console.WriteLine("a" + MB("a") + "b");
- // 这两个方法没有任何意义, 只是用 MA 模拟原函数, MB 模拟想要被替换的函数
- public string MA(string a, string b) { return null; }
- public string MB(string a) { return null; }
先来匹配, 分析一下要替换的 4 条语句: 开始的部分都是函数名 MA, 内容是 () 内的部分, 只要找到适当的 (和), 就可以了但事实是无法确定到底哪个) 才是合适的, 能看到后的语句后面有一个), 有的是两个, 有的是三个这里括号要精确匹配, 不能匹配多了, 不然语句就错误了一个)的情况是带个 ", 其他的至少有 2 个), 因此我们可以分两种情况, 第一种是"), 第二种是多个括号的前两个这里有一个问题, 如何只匹配前两个? 在正则表达式中, 有两种匹配方式, 一种是贪心, 一种是非贪心贪心的意思是能匹配几个就匹配几个, 比如 a+, 在匹配 aaaaaab 的时候, 会匹配全部的 a 字母, 结果是 aaaaaa 而如果加入 a+?,? 的本来意思是 0 个或 1 个, 但是在这里, 表示最多匹配 1 个, 结果就是 a 还有一个问题就是, 正则表达式中,()是有特殊意义的, 如果放弃其特殊意义, 只是想匹配括号, 就要用转义副 \, 这个用过 ASC|| 码的都应该能明白则匹配 MA 函数的表达式为: MA\((.+),\s?.+?(\)\))|""\) 注意, c# 中双引号要这样 @"""", 前面用 @, 然后用两个 "" 表示一个双引号读别人的正则表达式会有些费劲, 那么我的建议是你可以先不看我的结果, 只要明白题意, 一边查看手册, 一边自己练习一点一点的试, 很快就能做出来, 这时 你就明白大概了, 再来看拿自己的表达式与别人的对比, 很有可能你做的比我简略!
可以看到在 \ s? 之后, 我用.+? 来尽量少的匹配字符, 直到遇到))或者 ")就停止匹配, 如果只用.+, 就会过多的匹配括号 光有正则表达式还不够, 还要能替换替换的难点在如何保留第一个变量正则表达式提供了提取括号里匹配到则值得机制, 如 (\w)\1(\w)\2, 能够匹配 aabb, 第一个 \ w 匹配到了 a, 由于在括号中, 因此被记录了下来, 可通过 \ 1 来获得第一个括号中的内容, 以此类推在替换时, C#(其他语言不清楚), 可利用 $1 来获得第一个括号中匹配到的内容则替换的语句是 MB($1) 注意, 这里替换的语句不需要转义, 因为替换不需要匹配, 只要将字符原封不动替换就可以了下面是代码:
- public static void Lesson4(){
- Console.WriteLine("第四课, 替换函数");
- string rule = @"MA\((.+?),\s?.+?(\)\)|""\))";
- string[] enters =
- {
- @"Console.WriteLine(MA(""a"",""b""))",
- @"Console.WriteLine(""a""+ MA(""a"", GetType().ToString()));",
- @"Console.WriteLine(MA(""a"", GetType().ToString()));",
- @"Console.WriteLine(""a""+ MA(""a"",""a"".Substring(1)) +""b"");"
- };
- string replacement = @"MB($1)";
- foreach (var enter in enters)
- {
- Console.WriteLine(Regex.Match(enter, rule).Convert2String(enter));
- Console.WriteLine("替换后由 {0} 变为{1}", enter, Regex.Replace(enter, rule, replacement));
- }
- }
正则表达式看起来很难, 但如果您把这 4 课都做一遍, 就已经基本掌握了正则表达式了, 日常的应用是没有问题的语法有些难记, 我的建议是先不管语法, 要用就随时翻手册欢迎各位同学在评论区与我互动
来源: https://www.cnblogs.com/jazzpop/p/8615803.html