JavaScript 正则表达式

目录

字符串字面量

任务描述

相关知识

正则表达式的定义

创建RegExp对象的实例

使用 // 符号定义

匹配

字符串字面量

编程要求

字符类

任务描述

相关知识

字符类第一种表示

字符类第二种表示

编程要求

重复

任务描述

相关知识

重复

转义

编程要求

选择

任务描述

相关知识

选择

选择符号作用范围

编程要求

分组

任务描述

相关知识

子表达式

分组

编程要求

引用

任务描述

相关知识

编号

引用

编程要求

匹配位置

任务描述

相关知识

指定匹配位置

编程要求

修饰符

任务描述

相关知识

修饰符

不区分大小写

全局匹配

多行模式

编程要求

正则表达式的使用

任务描述

相关知识

search(a)方法

split(a)方法

replace(a,b)方法

编程要求

字符串字面量

任务描述

本关任务:使用字符串字面量定义正则表达式。

相关知识

在开发中,经常会有这样的需求:用一个式子表达有相同规律的一类字符串,比如国内的手机号码:必须是11位数字,而且只能以1315171819开头。   这样的式子在JavaScript中称为正则表达式。

正则表达式的定义

正则表达式:能够定义一类具有相同特征的字符串的式子。

JavaScript中,正则表达式有两种定义方法:

创建RegExp对象的实例

创建RegExp对象的一个实例:

var myFirstPattern = new RegExp("s$");

上面等号右侧定义了一个正则表达式,并赋值给变量myFirstPattern

使用 // 符号定义

更简单的方法是直接用//符号创建正则表达式:

var easyPattern = /s$/;

这里的easyPattern和上面的myFirstPattern含义一模一样。

匹配

上面的正则表达式表示的是这样一类字符串:以字母s结尾的字符串,比如字符串apples就符合这个要求,我们把符合要求叫做匹配。   需要注意的是,字符串和正则表达式匹配,指的是字符串的一部分(子串)和正则表达式匹配即可,比如/s/匹配的是字母s,一个单词如果含有字母s,那么就和这个正则表达式匹配。   通过方法test()可以检测字符串和正则表达式是否匹配,括号内的参数是待匹配的字符串,如果匹配返回true,否则返回false,如下:

var pattern = /s/;
pattern.test("s");//匹配,返回true
pattern.test("asa");//匹配,返回true
pattern.test("aa");//不匹配,返回false

使用正则表达式的关键是正确编写表达式,一个设计良好的正则表达式可以表示一类非常复杂的字符串。   为了实现这种复杂的表示,正则表达式提供了字符串字面量、字符类、重复、选择、分组、引用、指定匹配位置、修饰符等功能,这些将在本实训的剩余部分逐条讲述。

字符串字面量

正则表达式里面的数字和英文字母都是按照本来的含义匹配的,即aa匹配的就是字符串aa123匹配的就是数字123。这些数字和字母称为字符串字面量。

var pattern = /aa/;
pattern.test("aa");    // true
pattern.test("aabb");    // true
pattern.test("abb")    // false

一些特殊的符号,比如换行符、换页符,需要用斜杠加字母表示。比如\n匹配的是换行符,下面列出一些常用的特殊符号:  

| 字符 | 匹配 |

| ------------ | ------------ |

| \n | 换行符|

| \f | 换页符 |

| \t | 制表符 |

| \v | 垂直制表符 |

| \r | 回车符 |

编程要求

本关的编程任务是补全右侧代码片段中BeginEnd中间的代码,具体要求如下:

  • 定义正则表达式pattern

  • pattern匹配字符串js后面紧跟换行符的这样一类字符串;

function mainJs(a) {
    a = a.replace(",","\n");
	//请在此处编写代码
	/********** Begin **********/
    var pattern = /js\n/;
	/********** End **********/
    return pattern.test(a).toString();
}

字符类

任务描述

本关任务:定义两个正则表达式。

相关知识

字符类第一种表示

前一关阐述了字符串字面量的使用,一种更常见的需求是匹配一类字符:比如选择题的答案只能是ABCD中的任何一个。正则表达式用字符类来实现,字符类被放在中括号之内。[ABCD]表示一个字符类,它和A、B、C、D中的任何一个都匹配。   还可以用[A-D]表示上面的字符类,中间的-表示AD之间的所有字符。

var lowerCharPattern = /[a-z]/;    // 匹配任意小写字母
var upperCharPattern = /[A-Z]/;    // 匹配任意大写字母
var numberPattern = /[0-9]/;    // 匹配任意数字
var mixPattern = /[a-zA-Z0-9]/;    // 匹配大小写字母,数字

在中括号最前面加上^符号表示反向匹配:匹配和中括号内的正则表达式不匹配的所有字符,比如:

var notNumberPattern = /[^0-9]/;
notNumberPattern.test("123");    // false
notNumberPattern.test("ahc");    // true

[^0-9]匹配的是任意非数字的字符。其中^符号一定要在中括号内排在最前面。

字符类第二种表示

字符类还有一种较为简单的表示方法,比如\d[0-9]的含义相同:表示任意的数字。下面用表格列出:  

| 字符 | 匹配 | 等价于 |

| ------------ | ------------ | ------------ |

| \w | 大小写字符或数字 | [a-zA-Z0-9] |

|\W | 非字母,非数字 | [^a-zA-Z0-9] |

| \d | 数字 | [0-9] |

| \D | 非数字 | [^0-9] |

上面的内容都是单独的一个字面量或者字符类,对于稍微复杂一点的,比如一个数字后面紧跟着一个小写字母,该怎么表示呢?来看一个例子:  

//表示数字后面紧跟着一个小写字母
var pattern = /[0-9][a-z]/;
pattern.test("1a");//true
pattern.test("11a");//true
pattern.test("a1");//false

从上面可以看出,表示数字的正则表达式[0-9]和表示小写字母的正则表达式[a-z]直接连接起来,即表示数字后面紧跟小写字母。其他的字符串字面量或者字符类也可以用这种方式。

编程要求

本关的编程任务是补全右侧代码片段中BeginEnd中间的代码,具体要求如下:

  • 定义正则表达式pattern1pattern2

  • pattern1用来匹配一个英文字母后面紧跟一个数字;

  • pattern2用来匹配字母A后面紧跟一个非数字的字符;

function mainJs(a) {
	//请在此处编写代码
	/********** Begin **********/
    var pattern1 = /[a-zA-Z][0-9]/;
    var pattern2 = /[A][\D]/;
    
	/********** End **********/
    return pattern1.test(a).toString()+","+pattern2.test(a).toString();
}

重复

任务描述

本关任务:完成一个正则表达式的编写。

相关知识

通过前面的学习,我们可以编写一些简单正则表达式,比如两个连续的数字可以表示为[0-9][0-9],但是,这种方法有一个问题:比如手机号码的正则表达式,用11个连续的[0-9]写起来容易错,且不具有可读性。正则表达式用重复解决了这个问题。

重复

重复表示指定的字符或者字符串(本关可以简单理解为前面紧邻的字符)可以连续出现多次。比如匹配含有100个字母a的字符串,在这个字符串中,a连续出现100次,用正则表达式表示为:

var pattern = /a{100}/;//匹配100个连续的字母a组成的字符串

有多种表示重复的方法:  

  • {a,b}中的ab都是数字,表示前面的字符至少出现a次,最多出现b次;
    var pattern = /at{1,2}/;//表示a后面最少一个t,最多两个t
    pattern.test("at");//true
    pattern.test("att");//true
    pattern.test("am");//false
  • {a,}表示前面的字符至少出现a次,最多不限制;
    var pattern = /[0-9]{4,}/;//匹配最少四个数字
    pattern.test("1234");//true
    pattern.test("1");//false
  • {a}表示前面的字符出现a次;
    var pattern = /[a-z]{1}/;//匹配单个小写字母
    pattern.test("r");//true
    pattern.test("12R");//false
  • ?,表示前面的字符出现一次或者不出现,等价于{0,1}
    var pattern = /A[0-9]?A/;//匹配两个A之间有0或1个数字
    pattern.test("AA");//true
    pattern.test("A0A");//true
    pattern.test("A01A");//false
  • +,表示前面的字符至少出现一次,等价于{1,}
    var pattern = /js+/;    // 匹配j后面最少一个s
    pattern.test("jsjs");    // true
    pattern.test("java");    // false
  • *,表示前面的字符至少出现0次,等价于{0,}
    var pattern = /A[0-9]*B/;    // 匹配A和B之间为空或者只有数字
    pattern.test("AB");    // true
    pattern.test("A1B");    // true
    pattern.test("AaB");    // false

    需要特别注意的是,至少出现0次,就是说这个字符可以不出现,比如正则表达式[0-9]{0,}和字符串hello是匹配的,这里特别容易出错。

    总结一下重复匹配的用法,如下:   | 表达式 | 匹配 | 等价于| | ------------ | ------------ | ------------ | | {a,b} |至少出现a次,最多出现b次 | | |{a,} | 至少出现a次 | | | {a} | 出现a次 | {a,a} | | + | 最少出现一次 | {1,} | | ? | 出现一次或者不出现 | {0,1} | |* | 至少出现0次 | {0,} |

    转义

    相信细心的读者已经发现了一个问题:对于?+等表示特殊含义的字符,如何实现字面量匹配呢?就是说如何匹配他们本来的含义呢?   在JavaScript中,使用\实现特殊符号的普通化,又叫做转义:

var pattern1 = new RegExp("\?");//匹配一个问号
var pattern2 = /\+{4}/;//匹配四个加号

编程要求

本关的编程任务是补全右侧代码片段中BeginEnd中间的代码,具体要求如下:

  • 定义正则表达式pattern1pattern2pattern3pattern4

  • pattern1匹配这样一类字符串:?至少出现一次;

  • pattern2匹配这样一类字符串:+只出现三次;

  • pattern3匹配这样一类字符串:{}最少出现一次,最多出现两次;

  • pattern4匹配这样一类字符串:\出现一次或者不出现;

function mainJs(a) {
	//请在此处编写代码
	/********** Begin **********/
    var pattern1 = /\?{1,}/;
    var pattern2 = /\+{3,3}/;
    var pattern3 = /\{\}{1,2}/;
    var pattern4 = /\\{0,1}/;
	/********** End **********/
    return pattern1.test(a).toString()+","+pattern2.test(a).toString()+","+pattern3.test(a).toString()+","+pattern4.test(a).toString();
}

选择

任务描述

本关任务:完成身份证号,某地邮政编码以及三位数电话区号的正则表达式吧!

相关知识

考虑一下身份证号码的匹配:最后一位是数字或者X,即二选一的关系,正则表达式使用选择实现这种匹配。

选择

选择使用符号|来实现,比如0|1表示匹配0或者1\d|a表示匹配数字或者字母a。   第一关我们讲过:所谓匹配指的是匹配字符串的某一个子串。但是涉及到选择的时候情况就会有些复杂,比如正则表达式[0-9]|[a-z]匹配的是字符串1ABCa中的子串1还是a

  看起来似乎都可以。实际上JavaScript会先挑选左边的子正则表达式[0-9]进行匹配,匹配成功后立即结束,所以匹配上的子串是1。   下面是一些选择的例子:

var pattern1 = /[ABCD]|[abcd]/;//匹配ABCD中任意一个或者abcd中任意一个
pattern1.test("A");//true
pattern1.test("Ab");//true,且匹配的是A
var pattern2 = /\d|\+/;//匹配数字或者+号
pattern2.test("1");//true
pattern2.test("+");//true
选择符号作用范围

关于选择,还有一个问题需要明确:|作用到整个式子中的,即整个的正则表达式会被选择符号一分为二,一个字符串和该表达式匹配,要么匹配左侧的整个子正则表达式,要么匹配右侧的整个子正则表达式,如:

var pattern = /0|1ABC/;
pattern.test("0");//true,和左侧的整个子正则表达式0匹配
pattern.test("1");//false,和右侧的整个子正则表达式1ABC不匹配

如果想要限制|符号的作用范围,需要将目标作用范围用圆括号包含在内,如:

var pattern = /(0|1)ABC/;//选择符号仅仅作用在0和1上,而不是像上面的例子一样作用在整个正则表达式中
pattern.test("0");//false,注意这里和上面的例子不同
pattern.test("0ABC");//true
pattern.test("1ABC");//true

编程要求

本关的编程任务是补全右侧代码片段中BeginEnd中间的代码,具体要求如下:

  • 定义正则表达式pattern1pattern2pattern3

  • pattern1用来匹配一段含有身份证号码的字符串,身份证号的规律是:17位数字,加上1位数字或者大写字母X

  • pattern2用来匹配一段含有某地的邮编的字符串,邮编的规律是:23或者24再加4位数字;

  • pattern3用来匹配一段含有三位数电话区号的字符串,三位数电话区号只有10个,它们分别是010020021022023024025027028029

function mainJs(a) {
	//请在此处编写代码
	/********** Begin **********/
    var pattern1 = /[0-9]{17}([0-9]|X)/;
    var pattern2 = /(23|24)[0-9]{4,4}/;
    var pattern3 = /(010|020|021|022|023|024|025|027|028|029)/;
    
	/********** End **********/
    return pattern1.test(a).toString()+","+pattern2.test(a).toString()+","+pattern3.test(a).toString();
}

分组

任务描述

本关任务:练习使用分组创建正则表达式。

相关知识

子表达式

我们把一个完整的正则表达式的一部分叫做子正则表达式,或者子表达式,比如:  

var pattern = /[0-9]?a{12,15}/;
var subPattern = /[0-9]?/;

subPatternpattern的前面一部分,称为pattern的子表达式。

通过分组获得子表达式,通过引用操作子表达式。

分组

前面讲过的正则表达式的写法中,重复只能作用在紧邻符号的前面一个字符上,比如:  

var pattern = /hello{3}/;

pattern表达的意思是字母o必须重复三遍,而不是单词hello必须重复三遍。   如果要表达单词hello必须重复三遍的意思,我们需要用到分组。   同样,要改变选择符号|的作用范围,也必须用到分组。   分组的符号是(),括号中是单独的项构成的子表达式,将这些子表达式看成一个整体进行操作,比如:

var pattern = /(hello){2}/;    // 匹配字符串hellohello
pattern.test("shellohellos");    // 返回true
pattern.test("helloo");    // 返回false

用括号分组后,可以像使用单独的项一样使用子表达式,即可以+?等符号操作它:  

var pattern1 = /((hello){2})+/;    // hellohello至少出现一次
var pattern2 = /(he|she)?/;    // he或者she出现一次或不出现
console.log(pattern1.test("hello"));    // 输出false
console.log(pattern1.test("hellohello"));    // 输出true
console.log(pattern2.test("he"));    // 输出true
console.log(pattern2.test("it"));    // 输出true

编程要求

本关的编程任务是补全右侧代码片段中beginend中间的代码,具体要求如下:

  • 创建正则表达式pattern1,匹配这样的字符串:?+需要连续出现至少两次;

  • 创建正则表达式pattern2,匹配这样的字符串:数字+问号+数字,或者数字+加号+数字,比如1?11+4asd0+3asdfa;

function mainJs(a) {
	//请在此处编写代码
	/********** Begin **********/
    var pattern1 = /(\?\+){2,}/;
    var pattern2 = /[0-9](\?|\+)[0-9]/;
	/********** End **********/
    return pattern1.test(a).toString()+","+pattern2.test(a).toString();
}

引用

任务描述

本关任务:在创建正则表达式时使用引用。

相关知识

编号

什么时候需要用到引用?   比如需要匹配这样一类字符串:以数字开头,中间是若干个字母,以数字结尾,并且开头的数字和结尾的数字相同,这个时候用前面所有介绍过的方法都不可行,无法确保开头的数字和结尾的数字相同。   所谓引用,就是在后面使用和前面完全相同的子表达式。我们把所有的带圆括号的子表达式编个号,从1开始。比如:

var pattern = /(A|B)(\d{5})not([0-9])/;//匹配字母A或者B后面紧跟5个数字,后面再紧跟字符串not,后面再紧跟1个数字

其中(A|B)编号为1(\d{5})编号为2([0-9])编号为3,中间的not不在圆括号内,不参与编号。

  一个小问题:如果子表达式里面嵌套另外一层子表达式,引用时如何编号?简单来说,以子表达式前面的左括号的个数为准:

var pattern = /(play(ed|ing)){1,}/;

ed|ing前面有两个左括号,所以编号为2

引用

后面可以用\1引用编号为1的子表达式,依次类推,比如:

var pattern = /(A|B)(\d{5})not([0-9])\1\2/;

pattern在最后引用了第一个和第二个子表达式。  

注意:这里的引用是对与子表达式匹配的字符串的引用,而不是简单的对子表达式的引用。例如:

var pattern = /([0-9])AA\1/;

 pattern不等价于正则表达式([0-9])AA[0-9],而是指字符串AA后面的数字必须和前面的相同,即形如1AA1这样的字符串。   再看一个例子:

var pattern = /([a-z])([a-z])([a-z])\3\2\1/;
pattern.test("abccba");    // 返回true
pattern.test("123eduude456");    // 返回true
pattern.test("abcdefg");    // 返回false

在上面的正则表达式pattern里面,我们先引用第三个子表达式,表示此处的字符串必须和第三个子表达式相同,然后引用第二个子表达式,最后引用的是第一个子表达式,所以pattern匹配一个回文串,即顺序读取和倒序读取结果相同的字符串。

编程要求

本关的编程任务是补全右侧代码片段中beginend中间的代码,具体要求如下:

  • 创建正则表达式pattern1,匹配这样的字符串:三个数字加一个非数字字符再加三个数字,字符两侧的数字相同,如123A123234,234

  • 创建正则表达式pattern2,匹配这样的字符串:大写字母+数字+大写字母+数字+大写字母+数字,三个数字相同,如A2B2C2F9D9K9

function mainJs(a) {
	//请在此处编写代码
	/********** Begin **********/
    var pattern1 = /([0-9])([0-9])([0-9])([^0-9])\1\2\3/;
    var pattern2 = /([A-Z])([0-9])([A-Z])\2([A-Z])\2/;
	/********** End **********/
    return pattern1.test(a).toString()+","+pattern2.test(a).toString();
}

匹配位置

任务描述

本关任务:利用正则表达式匹配以单词js开头的文本。

相关知识

考虑这样一种情况,判断一个字符串是否为合法的JavaScript变量名,变量名必须以字母、$或者_开头,这个时候要用到正则表达式中指定匹配位置的功能。

指定匹配位置

^用来匹配字符串的开头,比如:

var startPattern = /^[0-9]/;    // 匹配以数字开头的字符串
console.log(startPattern.test("1aa"));    // true
console.log(startPattern.test("a11"));    // false

注意,^符号在中括号的外面!不要与[^0-9]混淆,后者匹配非数字字符。   $用来匹配字符串的结尾,比如:

var endPattern = /ing$/;    // 匹配以ing结尾的字符串
console.log(endPattern.test("playing"));    // true
console.log(endPattern.test("going first"));    // false

\b用来匹配单词的边界,那么什么是单词的边界?

图片中蓝色的线指示了单词的边界,实际上就是英文字母和非英文字母(如空格符)之间的界限。  

var boundaryPattern = /\bOK\b/;    // 匹配单词OK
console.log(boundaryPattern.test("OK"));    // true
console.log(boundaryPattern.test("rewa OK de"));    // true
console.log(boundaryPattern.test("sa,OK"));    // true
console.log(boundaryPattern.test("OKNot"));    // false

简单来说,\b表示英文字母非英文字母之间的界限,这个界限不占用字符的位置。   \B用来匹配非单词的边界,与上面的\b相反。

var pattern = /\Bed/;//ed左侧不能是单词的边界
console.log(pattern.test("played"));//true
console.log(pattern.test("edu"));//false

编程要求

本关的编程任务是补全右侧代码片段中BeginEnd中间的代码,具体要求如下:

  • 定义正则表达式pattern

  • pattern匹配以单词js开头的一段文本:这段文本以js开头,且这个js是一个单词,比如js is goodjs's history符合要求,而js2jsjs不符合要求;

function mainJs(a) {
	//请在此处编写代码
    /********** Begin **********/
    var pattern = /^js\b/;
	/********** End **********/
     return pattern.test(a).toString();
}

修饰符

任务描述

本关任务:找到文本中的所有指定的单词。

相关知识

修饰符

修饰符用来描述一些整体的匹配规则,有igm三种。

修饰符需要放在//符号之后,如果通过新建RegExp对象定义正则表达式,则修饰符作为第二个参数。比如:

var pattern1 = /^java/m;
var pattern2 = new RegExp("^java","m");
不区分大小写

i表示整个的匹配过程中不考虑单词的大小写。如:

var pattern = /^edU/i;
console.log(pattern.test("edu"));    // 输出true
console.log(pattern.test("Edu"));    // 输出true
console.log(pattern.test("EDUCoder"));    // 输出true
全局匹配

g表示执行全局匹配,即找出所有满足匹配的子字符串。比如,已知match()函数返回由匹配结果组成的数组,如果没有匹配到返回null。   不用g修饰时:

var pattern = /[a-z]/;//匹配小写字母
console.log("a1b2c3".match(pattern));//输出["a", index: 0, input: "a1b2c3"]

显然,只匹配到了第一个小写字母a。   用g修饰时:

var pattern = /[a-z]/g;//全局匹配小写字母
console.log("a1b2c3".match(pattern));//输出["a", "b", "c"]

三个小写字母都被匹配到了,这就是所谓的全局匹配。

多行模式

有的时候,需要匹配的字符串很长,分为很多行(即中间有换行符号)。

m在多行模式中执行匹配,即:符号^不仅匹配整个字符串的开头,还匹配每一行的开头,&不仅匹配整个字符串的结尾,还匹配每一行的结尾。

var pattern = /[0-9]$/m;//多行匹配以数字结尾的字符串
var string = "1\nhello";//字符串在两行,中间的\n是换行符
console.log(pattern.test(string));//输出true

如果没有m修饰,将会输出false,因为这种情况下$不会和换行符\n匹配。

编程要求

本关的编程任务是补全右侧代码片段中BeginEnd中间的代码,具体要求如下:

  • 定义正则表达式pattern

  • pattern能够匹配一段英文中所有的单词shell,不区分大小写,该段英文在一行中;

  • 所谓单词,指前后紧跟的字符都不是英文字母的字符串;

function mainJs(a) {
	//请在此处编写代码
    /********** Begin **********/
    var pattern = /\bshell\b/ig;
	/********** End **********/
    return a.match(pattern);
}

正则表达式的使用

任务描述

本关任务:对成绩单进行数据的清洗。

相关知识

本关将介绍字符串中有关正则表达式的方法。

字符串比较常用的方法有search()replace()match()split(),这些方法的调用者都是字符串对象,参数中都有正则表达式。

search(a)方法

参数:a为正则表达式。 功能:返回字符串中与该正则表达式匹配的第一个子串的起始位置,无匹配返回-1

var pattern = /[0-9]/;
var string = "a3b2c1";
console.log(string.search(pattern));//输出1
split(a)方法

参数:a是字符串或者正则表达式; 功能:以a为分隔符分隔原来的字符串; 返回值:分割后形成的子字符串数组。

console.log("a1b2c3d".split(/[0-9]/));//以数字为分隔符,输出["a", "b", "c", "d"]
replace(a,b)方法

参数:a是正则表达式,b是字符串; 功能:用b替换掉第一个a匹配的子串,如果a中有修饰符g,替换掉所有子串; 返回值:被替换后的字符串。

var pattern1 = /[0-9]/;
var pattern2 = /[0-9]/g;
var string = "a1b2c3";
console.log(string.replace(pattern1,"A"));//部分替换,输出aAb2c3
console.log(string.replace(pattern2,"A"));//全部数字被替换,输出aAbAcA

复杂的情况:b还可以是子表达式$1$2等,$1等会先被替换为与它匹配的子串。比如:

var pattern = /([0-9])[A-Z]/g;
var string = "1A,2B,3C,1";
console.log(string.replace(pattern,"$1"));//输出1,2,3,1

上面的$1指的是与子表达式[0-9]匹配的子字符串,比如第一个匹配1A$1指的是1,第二个匹配2B$1指的是2,依次类推。

string中与pattern匹配的有1A2B3C,这其中与子表达式$1匹配的分别是123,所以$1会相继被替换为123,然后再用123去分别替换1A2B3C

编程要求

本关的编程任务是补全右侧代码片段中BeginEnd中间的代码,具体要求如下:

  • 一段成绩单是类似这样的字符串:Sivan:99,Kathy:100,Eray:8,,即:人名+冒号+成绩+逗号,成绩范围是0100,人名是英文;

  • 使用replace()方法删掉其中的分数,保留其他所有字符串,比如上面的最终结果应该是Sivan:,Kathy:,Eray:,

function mainJs(a) {
	//请在此处编写代码
    /********** Begin **********/
    var pattern = /[0-9]/g;
    return a.replace(pattern, "");
	/********** End **********/
}

  • 11
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值