正则表达式

一、什么是正则表达式

正则表达式就是可以验证字符串是否符合某个规则(test),也可以把字符串中符合规则的内容捕捉到(exec/match…)

正则表达式就是一个表达式,用来查找一个有指定【特点】的表达式,规则匹配的。

正则:就是一个规则,就是用来处理字符串的一个规则

  • 匹配 :判断一个字符串是否符合我们指定的的规则 -> test , 语法:reg.test(str)
  • 捕获 :把字符串汇总符合我们正则规则的内容捕获到 ->exec , reg.exec(str)

https://regexr-cn.com/

二、正则表达式的两种创建方式

正则表达式的组成

每一个正则表达式都是由元字符 和 修饰符组成的

【元字符】: 在/ /之间具有一些意义的字符
: 转义字符,转义后面字符所代表的含义

1. 字面量创建方式

两个斜杠之间包起来的,都是用来描述规则的元字符

let reg = /\d+/; //=>\d代表0-9之间的数字

2. 基于构造函数模式创建

有两个参数:1.元字符字符串;2.修饰符字符串(什么是元字符和修饰符后面会解释)

let reg1 = new RegExp(’\d+’); //=>/\d+/;
let reg2 = new RegExp(’\d+’,‘g’) //=>/\d+/g

RegExp 是javascript中的一个内置对象。为正则表达式。

\d 在RegExp实例化的时候识别不了, 需要转义

3. 字面量方式和实例创建的方式在正则中的区别:

有个小知识点需要注意下:“\”在字符串中也有特殊含义,表示转义字符

//在字面量方式中,我们/ /之间包起来的所有的内容都是元字符,有的具有特殊的意义,大部分都是代表本身含义的普通的元字符

//需求:name是一个变量, 写一个正则, 让前面有数字开头, 后面是数字结尾

var name = 'ruanmou';
var reg = /^\d+"+name+"\d+/; // 可以吗, 不可以
console.log(reg.test('2015ruanmou2016'))  //=>false
console.log(reg.test('2015""namee"2016'))  //=>true

// 所以对于这样的需求,我们只能使用实例创建的方式了

var name = 'ruanmou';
var reg=new RegExp("^\\d+"+name+"\\d+$", "g") ;
console.log(reg.test('2015ruanmou2016'))  //=>true

总结:

1> 字面量方式中出现的一切都是元字符,所以不能进行变量值的拼接,而实例创建的方式是可以的
2> 字而量方式中直接写\d就可以,而在实例中需要把它转译成\d

三、正则表达式使用方法

reg.test(str); 匹配 : 判断一个字符串是否符合我们指定的的规则
reg.exec(str) 捕获 :把字符串汇总符合我们正则规则的内容捕获到

查看RegExp构造器里的原型方法
RegExp.prototype


str.match(reg);

四、元字符

1. 常用元字符

. 查找单个字符,除了换行(\n)和行结束符。
\w 查找单词字符(匹配字母、数字、下划线。等价于’[A-Za-z0-9_]’) [(A-Z)(a-z)(0-9)(_)]
\W 查找非单词字符
\d 查找数字
\D 查找非数字
\s 查找空白字符
\S 查找非空白字符
\b 查找单词边界
\B 查找非单词边界
\n 查找换行符 , 后者使光标下移一行
\f 查找换页符
\r 查找回车符,使光光标到行首
\t 查找制表符
\v 查找垂直制表符
\uxxx 查找以十六位进制数xxxx规定的Unicode字符
[\u4e00-\u9fa5] 所有中文字符

2. 元字符反义

反义,匹配不满足某个条件的字符串

\W 查找非单词字符,匹配任意不是字母,数字,下划线的字符
\D 查找任意非数字的字符
\S 查找非空白字符
\B 查找非单词边界,匹配任意的非单次开头或者结尾的位置
[^abc] 匹配不是a,b,c的任意字符

3. 量词元字符:

代表出现次数的量词元字符

以下均遵循贪婪匹配:
n+ 匹配任何包含至少一个n的字符
n* 匹配任何包含0个或多个n的字符
n? 匹配任何0个或1个n的字符
n{X} 匹配X个n的序列字符串
n{X,Y} 匹配X至Y个n的序列字符串
n$ 匹配以n结尾的字符串
^n 匹配任何以n开头的字符串
?=n 正向肯定预查匹配任何后面紧接着指定字符串n的字符串
?!n 正向否定预查匹配任何其后没有紧接着指定字符串n的字符串
?<=n 反向肯定预查匹配任何前面紧跟着指定字符串n的字符串
?<!n 反向否定预查匹配任何前面没有紧接着指定字符串n的字符串非贪婪匹配:n+? n*? …

4.表示边界

^ 匹配字符串开头, 如果出现在中括号, 表示 排除 , 非
$ 匹配字符串结尾
\b 匹配一个单词的边界
\B 匹配非单词边界

\b\w{6}\b 匹配刚好6个字符的单词。

reg = /\b\w{6}\b/g;
reg.test('1234566 ffffff') //=>false
reg.test('1234566 ffffff ') //=>true

元字符^(和数字6在同一个键位上的符号)和$都匹配一个位置,这和\b有点类似。

^匹配你要用来查找的字符串的开头,$匹配结尾。

这两个代码在验证输入的内容时非常有用,比如一个网站如果要求你填写的QQ号必须为5位到12位数字时,可以使用:^\d{5,12}$。

  • ^只有在[]中的最开始才有特殊含义,表示排除,否则也是表示它的字面意思。比如
    [^0-9]匹配不包含0-9的任意字符。
    如果^在[]中的其他位置,就会只表示它的字面意思了。

比较常见的错误是[http|https],我们的本意是要匹配http或者https中的任意一个,但其实这个正则表达式和[ht|ps]是一模一样的,表示匹配h,t,p,|,s中的任意一个。

要达到上面的效果,我们可以这样写(http|https),或者https?。

^ $ 是不会占位的

5、特殊字符

具有特殊含义的字符。
\ 转义字符,转义后面字符所代表的含义
| 或 ,分支条件

  • 因为\是用来转义的,如果我们不对\转义,它就会对别人转义,所以如果要在[]中匹配\本身,我们需要使用\

  • 分支条件| ,使用分支条件一定要使用()括起来,而且分支是短路的,只要符合其中的一个,就不会继续匹配分支中的其他条件了。

  • 当我们使用|的时候,一定要使用()进行限定 , |的匹配范围是从左到右,直到结束或者遇到)为止

五、常用修饰符 i/g/m

g代表的是global全局匹配
i代表ignoreCase忽略大小写,
m代表multiline多行匹配,

六、方括号

匹配[]中的任意一个,单字符匹配

[abc] 查找方括号内的任意一个字符
[^abc] 查找任何不在方括号内的字符,^在中括号时,读非,除了abc三个字符意外的任何字符
[0-9] 查找0-9之间的数字
[a-z] 查找任何小写字母
[A-Z] 查找任何大写字母
[A-z] 查找任何字母
[^a-z] 除了a-z之间的任何一个字符

  • 在[]中用来代表一个范围,比如[0-9],[a-z],当-挨着[或者]的时候,-表示它的字面意思,比如
[-abc]会匹配-,a,b,c中的任意一个
[a-c]会匹配a b c中的任意一个
[a\-c]会匹配a,-,c这三个中的任意一个

八、分组()

把一个大正则划分成几个小的正则;
()小分组的作用

1.提升优先级
2.分组引用
3.分组捕获

(red|blue|green) 查找指定字符串(子表达式)
默认会给每一个分组一个分组号,从1开始分配,0代表整个正则表达式,我们可以在稍后通过分组号来引用前面的分组,比如\1代表引用第一个分组匹配到的内容,\n代表引用第n个分组匹配到的内容

举几个例子加深印象

\b(\w+)\b\s+\1\b,可以匹配任意的重复单次,比如go go,good good等。这个表达式首先是一个单词,也就是单词开始处和结束处之间的多于一个的字母或数字\b(\w+)\b,这个单词会被捕获到编号为1的分组中,然后是1个或几个空白符\s+,最后是分组1中捕获的内容(也就是前面匹配的那个单词)\1。
其中\1就是引用的第一个分组匹配到的内容。

1. 分组引用

let str = "book";
let reg = /^[a-zA-Z]([a-zA-Z])\1[a-zA-Z]$/; 
// => 分组引用就是通过“\数字”让其代表和对应分组出现一摸一样的内容
console.log(reg.test("look")); // true
console.log(reg.test("deep")); // true
console.log(reg.test("book")); // true
console.log(reg.test("sonn")); // false

2. 分组捕获

先总后分 先左后右 先外后里

let str = 'zhu2019zhu2020zhu2021',
    reg = /\d{1,3}?/;
console.log(reg.exec(str));
reg = /(([a-z])+)((\d)+)/g;

这种括号套括号的情况,最小的分组永远捕获到的是符合条件的最后一项,也可以理解为把量词放在小括号外的,小分组捕获到的永远是整个分组的最后一项

console.log(reg.exec(str));
//=>[“zhu2019”, “zhu”, “u”, “2019”, “9”, index: 0, input: “zhu2019zhu2020zhu2021”, groups: undefined]


// RegExp.$1是RegExp的一个属性,指的是与正则表达式匹配的第一个子匹配(以括号为标志)字符串,以此类推,RegExp.$2,RegExp.$3,..RegExp.$99总共可以有99个匹配

RegExp.$1~RegExp.$9:获取当前本次正则匹配后,第一个到第九个分组的信息

例子:
var r= /^(\d{4})-(\d{1,2})-(\d{1,2})$/;
 //正则表达式 匹配出生日期(简单匹配)    
r.exec('1985-10-15');
s1=RegExp.$1;
s2=RegExp.$2;
s3=RegExp.$3;
alert(s1+" "+s2+" "+s3)//结果为1985 10 15

九、?五大作用

  1. ?左边是非量词元字符:本身代表量词元字符。出现零到一次
  2. 问号左边是量词元字符:取消捕获时候的贪婪性
  3. (?: ) 只匹配不捕获
  4. (?=) 正向预查
  5. (?!) 负向预查
//  正负向预查
let reg = /ruanmou/;
reg.test('ruanmousss')  //=>true

// 正向预查
let reg1 = /ruanmou(?=peixun)/ // ruanmou后面必须跟培训
reg1.test('ruanmousss')  //=>false
reg1.test('ruanmoupeixun')  //=>true
reg1.test('ruanmoupeixunssss') //=>true
reg1.test('ruanmoussspeixunssss')  //=>false
// 负向预查
let reg = /ruanmou(?!peixun)/; //ruanmou后面必须不跟peixun
console.log(reg.test('ruanmoupeixun')) // false
console.log(reg.test('ruanmoupei')) // true

断言

断言和\b,^,$一样,匹配一个位置,这个位置满足一定的条件。

  • 正预测断言 , 正向预查
    (?=expr)断言一个位置,这个位置后面满足expr。比如\b\w+(?=ing\b),这个正则表达式匹配以ing结尾的单词的前半部分,比如dacing,会匹配到dac. 这个正则表达式的意思是,首先是一个单词,这个单次后面跟着ing

  • 负预测断言, 负向预查
    (?!expr)断言一个位置,这个位置后面不满足expr,。主要用去确保被匹配的字符串中不会出现一些字符,比如我们想要匹配这样一个字符串,它以hello开头,但是没有出现good,我们可以这样写
    \bhello.(?!good)\w\b

可以看一下很实用的例子
(?<=<(\w+)>).*(?=</\1>),这个可以用来匹配HTML标签中的内容,具体如下,首先是一个断言(?<=<(\w+)>),断言一个位置,这个位置前面是类似这样的标签,然后是任意的字符,接下来又是一个断言(?=</\1>),断言这个位置后面是能匹配到前面那样的标签内容,只是多了一个反斜杠,比如<\body>

十、贪婪性或懒惰

默认正则表达式是贪婪的,会尽可能多的匹配,比如字符串aabab,如果使用正则表达式a.*b去匹配的话,会匹配到整个字符串。

有时候我们需要懒惰匹配,要尽可能少的匹配,这时候我们就需要在前面提到的用于重复的元字符后面加一个?,比如*? , +? , ?? , {n,m}? , {n,}?

举例说明

(?=n)

var str="hello world,all students";
var patt1=/ll(?=o)/g;
var patt2=/ll/g;
str.match(patt1)  //["ll"]
str.match(patt2)  //["ll","ll"]

总结: (?=o) 起到了 辅助定位作用…

var str = "12345678";
var patt = /\B(?=(...)+$)/g;
console.log(str.replace(patt,","));
// 12,345,678
// https://i.getshell.cn/2018/03/15/js-%E6%AD%A3%E5%88%99%E9%87%8F%E8%AF%8D-n%E7%94%A8%E6%B3%95/

默认情况下正则捕获的时候,是按照当前正则所匹配的最长结果来获取的

解决贪婪性:把后面的量词元字符去掉或者在量词元字符后面加?;

let str = 'sss2010ddd1855';
let reg = /\d+/;
console.log(reg.exec(str)); //=>["2010", index: 3, input: "sss2010ddd1855", groups: undefined]  2010中2,20,201都符合/\d+/这个条件但是却匹配到了2010,这就是正则匹配的贪婪性
// 解决方案一
reg = /\d/
console.log(reg.exec(str)); //=>["2", index: 3, input: "sss2010ddd1855", groups: undefined];
// 解决方法二
reg = /\d+?/g
conosle.log(reg.exec(str)); //=>["2", index: 3, input: "sss2010ddd1855", groups: undefined]

正则捕获的懒惰性

每执行一次 exec 只能捕获到一个符合正则规则的,但是默认情况下执行多少次,获取的结果永远都是第一个匹配到的,其余的捕获不到,即正则捕获的懒惰性:默认只捕获一个

  • 懒惰性的原因:默认情况下 lastIndex 的值不会被修改,每一次都是从字符串开始位置找,所以找到的永远是第一个符合规则的(任何捕获方法都是这样)
  • 解决正则的懒惰性用全局匹配 g

RegExp对象属性

global 查看RegExp对象是否具有g标志
ignoreCase 查看RegExp对象是否具有i标志
multiline 查看RegExp对象是否具有m标志
source 查看正则表达式源文本
lastIndex 一个整数标志开始下一次匹配的字符位置(一般与exec一起使用)

var str = 'The best things in life are fre';
var pat1=new RegExp("be");

pat1.global  //false
pat1.ignoreCase //false
pat1.multiline  //false 
pat1.source  //be
pat1.lastIndex  //0

十一、RegExp对象方法

正则 RegExp.prototype 上的方法

compile 编译正则表达式。 (就是改变这个正则表达式)
exec 检索字符串中指定的值。返回找到的值,并确定其位置。
test 检索字符串中指定的值。返回 true 或 false。

compile() 方法用于改变 RegExp。
compile() 既可以改变检索模式,也可以添加或删除第二个参数。

var str = 'The best things in life are fre';

var pat1=new RegExp("be");
//更换前
var t1 = pat1.exec(str)
    console.log(t1)
pat1.compile("ng");
//更换后
var t2 =pat1.exec(str)
console.log(t2)

1. test 方法

返回 Boolean,查找对应的字符串中是否存在模式

var str = "1a1b1c";
var reg = new RegExp("1.", "");  //.代表任意一个字符
reg.test(str) // true

var str = "hello world!";
var result = /^hello/.test(str);
console.log(result); // true

test()继承正则表达式的lastIndex属性,表达式在匹配全局标志g的时候须注意。

function testDemo(){ 
    var r, re; // 声明变量。 
    var s = "I"; 
    re = /I/ig; // 创建正则表达式模式。 
    document.write(re.test(s) + "<br/>"); // 返回 Boolean 结果。 
    document.write(re.test(s) + "<br/>"); 
    document.write(re.test(s)); 
} 
testDemo(); 

输出结果:
true
false
true
当第二次调用test()的时候,lastIndex指向下一次匹配所在位置1,所以第二次匹配不成功,lastIndex重新指向0,等于第三次又重新匹配。

2. exec 方法

exec 查找并返回当前的匹配结果,并以数组的形式返回。
rgExp.exec(str)

参数

  • rgExp
    必选项。包含正则表达式模式和可用标志的正则表达式对象。
  • str
    必选项。要在其中执行查找的 String 对象或字符串文字。
  • 返回数组包含:
    input:整个被查找的字符串的值;
    index:匹配结果所在的位置(位);
    arr:结果值,arr[0]全匹配结果,arr[1,2…]为表达式内()的子匹配,由左至右为1,2…。

注意:在匹配后,rgExp 的 lastIndex 属性被设置为匹配文本的最后一个字符的下一个位置。lastIndex并不在返回对象的属性中,而是正则表达式对象的属性。

例子1:不含子表达式的正则表达式exec方法循环应用

!function RegExpTest(){ 
    var src="http://ruanmou.blog.163.com/blog/I love you!"; 
    // 注意g将全文匹配,不加将永远只返回第一个匹配。 
    var re = /\w+/g; 
    var arr; 
    // exec使arr返回匹配的第一个,while循环一次将使re在g作用寻找下一个匹配。 
    while((arr = re.exec(src)) !=null){ 
        document.write(arr.index + "-" + re.lastIndex + ":" + arr + "<br/>"); 
        for(key in arr){ 
            document.write(key + "=>" + arr[key] + "<br/>"); 
        } 
        document.write("<br/>"); 
    } 
}() 

exec默认只返回匹配结果的第一个值,比如上例如果不用while循环,将只返回’http’(尽管后面的ruanmou等都符合表达式),无论re表达式用不用全局标记g。但是如果为正则表达式设置了全局标记g,exec从上次匹配结束的位置开始查找。如果没有设置全局标志,exec依然从字符串的起始位置开始搜索。利用这个特点可以反复调用exec遍历所有匹配,等价于match具有g标志。当然,如果正则表达式忘记用g,而又用循环(比如:while、for等),exec将每次都循环第一个,造成死循环。

如果正则表达式中包含子表达式,那么输出结果将包含子匹配项
例子2:包含子表达式的正则表达式exec方法应用

!function execDemo(){ 
    var r, re; // 声明变量。 
    var s = "The rain in Spain falls mainly in the plain"; 
    re = /[\w]*(ai)n/ig; 
    r = re.exec(s); 
    document.write(r + "<br/>");
    for(key in r){ 
        document.write(key + "-" + r[key] + "<br/>"); 
    } 
}() 

十二、字符串方法

search 检索正则表达式相匹配的值
match 查找所有符合正则匹配条件的结果
replace 替换与正则表达式匹配的字符串
split 把字符串分割成数组(注: 用字表达式分割的话会保留子表达式)

1. replace

基于replace捕获:即把捕获到内容进行替换,第二个参数可以传递回调函函数,可以把正则表达式每次捕获到的内容传递给回调函数,进行相应的处理。然后把回调函数每次执行的返回值拿来替换捕获到的内容。

let time = '123-456-789';
time = time.replace(/-/g,'');
console.log(time)  //=> '123456789'

2. match

使用正则表达式模式对字符串执行查找,并将包含查找的结果作为数组返回。
stringObj.match(rgExp)

@params:正则对象(如果参数是非正则表达式对象,会默认 new RegExp(obj))
@return:数组(如果参数为空返回[’’];)

@如果传的正则表达式是全局捕获,则返回大正则每次匹配的内容,不会返回小分组匹配的内容
@如果不是全局捕获,返回第一次与大正则匹配的内容及其分组匹配的内容

let str = 'asc1021dad201';
str.match(/\d+/);  //=>["1021", index: 3, input: "asc1021dad201", groups: undefined]
let str = 'asc1021dad201';
str.match(/\d+/g); //=>["1021", "201"]
// match多次匹配的情况下,match只能把大正则匹配的内容捕获到,小分组匹配的信息无法获取
let str = 'abc123adc456';
str.match(/[a-z](\d+)/g);  //=>["c123", "c456"]

正则表达式中的分组捕获

// => 身份证号码
let str = "620421196608034816";
let reg = /^(\d{6})(\d{4})(\d{2})(\d{2})\d{2}(\d)(?:\d|X)$/;

console.log(reg.exec(str)); // => ["620421199410102456", "620421", "1994", "10", "10", "5", index: 0, input: "620421199410102456", groups: undefined]

console.log(str.match(reg)); // => ["620421199410102456", "620421", "1994", "10", "10", "5", index: 0, input: "620421199410102456", groups: undefined]
// =>第一项:大正则匹配结果
// =>其余项:每一个小分组单独匹配捕获的结果
// =>如果设置了分组(改变优先级),但是捕获的时候不需要单独捕获,可以基于?:来处理

!function MatchDemo(){
    // 声明变量。
    var r, re; 
    var s = "The rain in Spain falls mainly in the plain";
    // 创建正则表达式模式。
    re = /(a)in/ig; 
    // 尝试去匹配搜索字符串。
    r = s.match(re); 
    // 返回的数组包含了所有 "ain" 出现的四个匹配,r[0]、r[1]、r[2]、r[3]。
    //但没有子匹配项a。
    document.write(r); 
}()

输出结果:ain,ain,ain,ain

match() 方法将检索字符串 stringObject,以找到一个或多个与 regexp 匹配的文本。这个方法的行为在很大程度上有赖于 regexp 是否具有标志 g。

3. split

@params:正则对象
@return:基于正则捕获到的所有字符分割后的数组

// 基于split进行捕获:即可以选定多个字符进行替换
let str = 'dadw#frr%dd#frgg%3223s';
let ary = str.split(/[#%]/g);
console.log(ary); //=>["dadw", "frr", "dd", "frgg", "3223s"];

4. search

@params:正则对象,如果传入的是一个非正则表达式对象 obj,则会隐式的基于 new RegExp(obj)进行转换
@return:第一次匹配成功对应的索引,否则返回-1;

// 基于search捕获
let str = "dadA1452";
console.log(str.search(/[^a-zA-Z]/g));  //=>4

相关面试题

1.正则表达式实现aabb的形式变成bbaa
2.给10000000000三位打点 变成 10.000.000.000
3.字符串去重 aaaaaaaaaaaaaaaaaaaaaabbbbbbbbcccccccccc变成abc
4.把the-first-name转换成小驼峰式theFirstName

答案:
1.正则表达式实现aabb的形式变成bbaa
var reg = /(\w)\1(\w)\2/g;
方法一:
var str = “aabb”;
str.replace(reg,"$2$2$1 1 " ) ; / / 1"); // 1");//可以引用reg中的子表达式的内容 KaTeX parse error: Undefined control sequence: \w at position 20: …一个 var reg = /(\̲w̲)\1(\w)\2/g; 方法…,$1,KaTeX parse error: Expected '}', got 'EOF' at end of input: 2){ // 正则表达式匹配的全局结果“aabb”
$1表示第一个子表达式匹配的内容
$2表示第二个子表达式匹配的内容
return $2+$2+$1+$1;

});
2. 将数值变成英文计数式:10000000—>100.000.000
var reg = /(?=(\B)(\d{3})+$)/g;
var str = “100000000”;
str.replace(reg,".");

3.字符串去重 aaaaaaaaaaaaaaaaaaaaaabbbbbbbbcccccccccc变成abc
var str = ‘aaaaaaaaabbbbbccc’;
var reg = /(\w)\1*/g;
str.replace(reg,"$1"); //“abc”

4.把the-first-name转换成小驼峰式theFirstName
  <!-- 
        正则表达式的用途: 过滤字符串的(找出符合我们要求的字符串)
        希望让the-first-name这个字符串 转换成小驼峰形式 theFirstName
        解析:   1. 找到特定的子字符串 (-f, -n)
                 2. 替换字符串

子表达式的作用: 拿取正则表达式匹配出来的子字符串中的子字符串
     -->
var reg = /-(([a-z]))/g;
var str = “the-first-name”;
var newStr = str.replace(reg, function ($, $1, $2, $3) {
             return $1.toUpperCase();
})

邮箱

验证邮箱的正则(简版)

//左边:数字、字母、下环线、…-
//11447092650.qq.com
//peixun@ruanmou.cn
//laney@163.com

var reg = /1+@[0-9a-zA-Z]+(.[a-zA-Z]{2,4}){1,2}$/;
reg.test(‘laney@163.com’)


  1. \w_. ↩︎

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ེ夜雨微澜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值