-精通正则表达式-第三版-第2章笔记

例子: 检索某台Web服务器上的页面中的重复单词(例如”this this”), 进行大规模文本编辑时, 这是一项常见的任务. 程序必须满足下面的要求:
1. 能检查多个文件, 挑出包含重复单词的行, 高亮标记每个重复单词(使用标准ANSI的转义字符序列(escape sequence)), 同时必须显示这行文字来自哪个文件.
2. 能跨行查找, 即使两个单词一个在某行末尾而另一个在下一行的开头, 也算重复单词
3. 能进行不区分大小写的查找, 例如”The the”, 重复单词之间可以出现任意数量的空白字符(空格符, 制表符, 换行符之类)
4. 能查找用 HTML tag 分隔的重复单词. HTML tag 用于标记互联网页上的文本, 例如, 粗体单词是这样表示的: “···it is very very important···”

$/ = ".\n";
    while(<>){
        next if !s/\b([a-z]+)((?:\s|<[^>]+>)+)(\1\b)/\e[7m$1\e[m$2\e[7m$3\e[m/ig;
        s/^(?:[^\e]*\n)+//mg;  # 去除为标记的行
        s/^/$ARGV: /mg;  # 在行首添加文件名
        print;
    }

该程序的主要功能依靠3个正则表达式:
「\b([a-z]+)((?:\s|<[^>]+>)+)(\1\b)」
「^(?:[^\e]*\n)+」
「^」

本章包括了一些常见的问题–验证用户的输入数据, 处理 E-mail header(电子邮件头), 把纯文本数据转换为超文本格式(HTML).在构造正则表达式时, 我会做些尽可能详细的讲解, 提供一些启示. 在这个过程中, 我们会见到一些 egrep 没有提供的结构和特性, 也会专门花很多篇幅来探讨其他重要的概念.

Perl 简单入门

现在来看一个简单的例子:

$celsius = 30;
$fahrenheit = ($celsius * 9 / 5) + 32;  # 计算华氏温度
print "$celsius C is $fahrenheit F.\n";  # 返回摄氏和华氏温度

Perl 也提供了跟其他流行的语言类似的控制结构:

$celsius = 20;
while($celsius <= 45){
    $fahrenheit = ($celsius * 9 / 5) + 32;
    print "$celsius C is $fahrenheit F.\n"
    $celsius = $celsius + 5;
}

下面我们来看在 Perl 中如何使用正则表达式.

使用正则表达式匹配文本

Perl 可以以多种方式使用正则表达式, 最简单的就是检查变量中的文本能否由某个正则表达式匹配. 下面的代码检查 $reply 中所含的字符串, 报告这个字符串是否全部由数字构成:

if($reply =~ m/^[0-9]+$/){
    print "ongly digits\n";
}
else{
    print "not only digits\n";
}

正则表达式是「^[0-9]+$」两边的 m/···/ 告诉 Perl 该对这个正则表达式进行什么操作. m 代表尝试进行”正则表达式匹配(regular expression match)”, 斜线用来标记界限. 之前的 =~ 用来连接 m/···/ 和欲搜索的字符串, 即本例中的 $reply. 此程序在其他语言中的思路有所不同.
请注意, 如果$reply 中包含任意的数字字符, $reply =~ m/[0-9]+/(相比之前的表达式, 去掉了开头的脱字符和结尾的美元符)的返回值就是 true. 两端的「^···$」保证整个 $reply 只包含数字.
现在把上面两个例子结合起来. 首先提示用户输入一个值, 接收这个输入, 用一个正则表达式来验证, 确保输入的是一个数值. 如果是, 我们就计算相应的华氏温度, 否则, 我们输入一条警报信息:

print "Enter a temperature in Celsius:\n";
$celsius = <STDIN>  # 从用户处接受一个输入
chomp($celsius);  # 去掉 $celsius 后面的换行符
if($celsius =~ m/^[0-9]+$/){
    $fahrenheit = ($celsius * 9 / 5) + 32;  # 计算华氏温度
    print "celsius C is $fahrenheit F\n";
}
else{
    print "Expecting a number, so I don't understand \"$celsius\".\n";
}

向更实用的程序前进

拓展这个例子, 容许输入负数和可能出现的小数部分.

if($celsius =~ m/^[-+]?[0-9]+(\.[0-9]*)?$/){}

成功匹配的副作用

我们再进一步, 让这个表达式能够匹配摄氏和华氏温度. 我们让用户在温度的末尾加上 C/F 来表示.
温度转换程序:

print "Enter a temperature (e.g., 32F, 100C):\n";
$input = <STDIN>;  # 接收用户输入的一行文本
chomp($input);  # 去掉文本末尾的换行符
if($input =~ m/^([-+]?[0-9]+)([CF])$/)
{
    # 如果程序运行到此, 则已经匹配. $1保存数字, $2 保存"C"或者"F"
    $InputNum = $1;  # 把数据保存到已命名变量中...
    $type = $2;  # ...保证程序清晰易懂
    if($type eq "C"){  # 'eq 测试两个字符是否相等
        # 输入为摄氏温度, 则计算华氏温度
        $celsius = $InputNum;
        $fahrenheit = ($celsius * 9 / 5) + 32;
    }else{
        $fahrenheit = $InputNum;
        $celsius = ($fahrenheit - 32) * 5 / 9;
    }  # 现在得到了两个温度值, 显示结果:
    print "%.2f C is %.2f F\n", $celsius, $fahrenheit;
}else{
    # 如果最开始的正则表达式无法匹配, 报警
    print "Expecting a number followed by \"C\" or \"F\",\n";
    print "so I don't understand \"$input\".\n";
}

错综复杂的正则表达式

在 Perl 之类的高级语言中, 正则表达式的使用与其他程序的逻辑是混合在一起的. 为了说明这一点, 我们对这个程序做三点改进: 像之前一样能够接收浮点数. 容许 f 或者 c 是小写, 容许数字和字母之间存在空格.
我们希望开发一个实际应用的程序, 所以必须容许一些除空格之外的空白字符(whitespace). 例如常见的制表符(tabs). 所以我们需要一个字符组来匹配两者「[ \t]*」
请把上面这个子表达式与「( *|\t*)」进行对比, 你能发现者其中的巨大差异吗?
「( *|\t*)」容许 或\t的匹配, 它能够匹配若干空格符以及若干制表符, 不过并不容许制表符和空格符的混合体.「[ \t]*」则可以.
在逻辑上, 「[ \t]*」与「( |\t)*」是等价的, 原因会在第4章解释, 字符组的效率通常还是会高一点.
许多流派的正则表达式提供了一种方便的办法\s.表示所有空白字符的字符组, 其中包括空格符, 制表符, 换行符和回车符, 在复杂的表达式中, \s*更易于理解.
温度转换程序–最终版本(Python)

import re

print("Enter a temperature (e.g., 32F, 100C):")
input_ = input()
result = re.match(r"^([-+]?[0-9]+(\.[0-9]*)?)\s*([CF])$", input_, re.I)
if result:
    InputNum_ = result.group(1)
    type_ = result.group(3)
    if type_ == "C" or type_ == "c":
        celsius = float(InputNum_)
        fahrenheit = celsius * 9 / 5 + 32
    else:
        fahrenheit = float(InputNum_)
        celsius = (fahrenheit - 32) * 5 / 9
    print("%.2f C is %.2f F" % (celsius, fahrenheit))
else:
    print("Expecting a number followed by \"C\" or \"F\",")
    print("so I don't understand \"%s\"." % input_)

使用正则表达式修改文本

例子: 公函生成程序
下面这个有趣的例子展示了文本替换的用途. 设想有一个公函系统, 它包含很多公函模板, 其中有一些标记, 对每一封具体的公函来说, 标记部分的值都有所不同:

Dear =FIRST=,
You have been chosen to win a brand new =TRINKET=! Free!
Could you use another =TRINKET= in the =FAMILY= household?
Yes =SUCKER=, I bet you could! Just respond by...
对特定的接收人, 变量的值分别为:
$given = "Tom";
$family = "Cruise";
$wunderprize = "100% genuine faux diamond";
准备好之后, 就可以用下面的语句"填写模板":
$letter =~ s/=FIRST=/$given/g;
$letter =~ s/=FAMILY=/$family/g;
$letter =~ s/=SUCKER=/$given $family/g;
$letter =~ s/=TRINKET=/fabulous $wunderprize/g;

例子: 修整股票价格
使用 Perl 编写的股票价格软件时遇到的问题. 我得到的价格看起来是这样”9.0500000037272”. 这里的价格显然应该是9.05, 但是因为计算机内部表示浮点的原理, Perl 有时会以没什么用的格式输出这样的结果. 我们可以使用 printf 来保证只输出两位小数, 但是此处并不适用. 如果某个价格以1/8结尾, 则应该输出3位小数(“.125”), 而不是两位.

$price =~ s/(\.\d\d[1-9]?)\d*/$1/

自动的编辑操作

% perl -p -i -e 's/sysread/read/g' file

这样简单的编辑方式是 Perl 独有的, 但这个例子告诉我们, 即使执行的是简单的任务, 作为脚本语言一部分的正则表达式的功能仍然非常强大.

处理邮件的小工具

一个文件中保存着 E-mail 信息, 我们需要生成一个用于回复的文件. 在准备过程中, 我们需要引用原始的信息, 这样就能很容易地把回复插入各个部分. 在生成回复邮件的 header 时, 我们还需要删除原始信息邮件的 header 中不需要的行.

E-mail Message 范本


From elvis Thu Feb 29 11:15 2007
Received: from elvis@localhost by tabloid.org (8.11.3) id KA8CMY
Received: from tabloid.org by gateway.net (8.12.5/2) id N8XBK
To: jfriendl@regex.info (Jeffrey Friedl)
From: elvis@tabloid.org (The King)
Date: Thu, Feb 29 2007 11:15
Message-Id: <2007022939939.KA8CMY@tabloid.org>
Subject: Be seein’ ya around
Reply-To: elvis@hh.tabloid.org
X-Mailer: Madam Zelda’s Psychic Orb [version 3.7 PL92]

Sorry I haven’t been around lately. A few years back I checked into that ole heartbreak hotel in the sky, ifyaknowwhatImean.
The Duke says “hi”.
Elvis


我们希望程序的输出结果 king.out 包括下面的内容:
To: elvis@hh.tabloid.org (The King) # Reply-To: elvis…
From: jfriendl@regex.info (Jeffrey Friedl) # To: jfreindl…
Subject: Re: Be seein’ ya around # Subject: Be…
On Thu, Feb 29 2007 11:15 The King Wrote: # Data: Thu…
Sorry I haven’t been around lately. A few years back I checked into that ole heartbreak hotel in the sky, ifyaknowwhatImean.
The Duke says “hi”.
Elvis


Python 程序:

import re

f = open("%s/message" % path, "r")
while True:
    msg = f.readline()
    if re.match(r"^\s*$", msg):
        break
    elif re.match(r"^Subject: (.*)", msg, re.I):
        result = re.match(r"^Subject: (.*)", msg, re.I)
        subject = result.group(1)
    elif re.match(r"^Date: (.*)", msg, re.I):
        result = re.match(r"^Date: (.*)", msg, re.I)
        data = result.group(1)
    elif re.match(r"^Reply-To: (\S+)", msg, re.I):
        result = re.match(r"^Reply-To: (\S+)", msg, re.I)
        reply_address = result.group(1)
    elif re.match(r"^From: (\S+) \(([^()]*)\)", msg, re.I):
        result = re.match(r"^From: (\S+) \(([^()]*)\)", msg, re.I)
        reply_address = result.group(1)
        from_name = result.group(2)
print("To: %s (%s)" % (reply_address, from_name))
print("From: jfreidl\@regex.info (Jeffrey Friedl)")
print("Subject: Re: %s" % subject)
print()
print("On %s %s wrote:" % (data, from_name))
while True:
    msg = f.readline()
    if msg:
        print(msg, end="")
    else:
        break
f.close()

真实世界的问题, 真实世界的解法
作为第一步, 在检查原始邮件之后(生成回复模板之前), 我们可以这样:

if(    not defined($reply_address)
    or not defined($from_name)
    or not defined($subject)
    or not defined($date))
{
    die "couldn't glean the required information!";
}

另外一点需要考虑的是, 程序假设 From: 这一行出现在 Reply-To: 之前. 如果 From: 出现在之后, 就会覆盖从 Reply-To 取得的 $reply_address.

用环视功能为数值添加逗号

使用一组相对较新的正则表达式特性–它们统称为”环视(lookaround)”.
环视结构不匹配任何字符, 只匹配文本中的特定位置, 这一点与单词分界符\b, 锚点^和$相似.但是, 环视比它们更加通用.
一种类型的环视叫”顺序环视(lookahead)”, 作为表达式的一部分, 顺序环视顺序查看文本, 尝试匹配子表达式, 如果能够匹配, 就返回匹配成功信息. 肯定型顺序环视(positive lookahead)用特殊的序列「(?=···)」来表示, 例如「(?=\d)」它表示如果当前位置右边的字符是数字则匹配成功.
另一种环视称为逆序环视, 它用特殊的序列「(?<=···)」表示, 例如「(?<=\d)」如果当前位置的左边有一位数字, 则匹配成功.

$pop =~ s/(?<=\d)(?=(?:(\d\d\d))+$)/,/g;
print "The US population is $pop\n";

单词分界符和否定环视
现在假设, 我们希望把这个插入逗号的正则表达式应用到很长的字符串中, 例如:

$text = "The population of 298444215 is growing";
……
$text =~ s/(?<=\d)(?=(\d\d\d)+$)/,/g;                            
print "$text\n";

很显然程序没有结果, 因为$要求字符串以3的倍数位数字结尾. 我们不能只去掉这里的$, 因为这样会从左边第一位数字之后, 右边第三位数字之前的每一个位置插入逗号–结果是”2,9,8,4,4,4,215”!
可能初看起来这问题有些棘手, 但我们可以用单词分界符\b来替换$. 就像\w一样, Perl 和其他语言都把数字, 字母和下划线当做单词的一部分. 结果, 单词分界符的意思就是, 在此位置的一侧是单词(例如数字), 另一侧不是(例如行的末尾, 或者数字后面的空格).
四种类型的环视

  • 肯定逆序环视  子表达式能够匹配左侧文本  (?<=……)
  • 否定逆序环视  子表达式不能匹配左侧文本  (?<!……)
  • 肯定顺序环视  子表达式能够匹配右侧文本  (?=……)
  • 否定顺序环视  子表达式不能匹配右侧文本  (?!……)

所以, 如果单词分界符的意思是: 一侧是\w而另一侧不是\w, 我们就能用「(?<!\w)(?=\w)」来表示单词起始分界符, 用「(?<=\w)(?!\w)」表示单词结束分界符. 把两者结合起来, 「(?<!\w)(?=\w)|(?<=\w)(?!\w)」就等价于\b. 在实践中, 如果语言本身支持\b(\b更直接, 效率也更高), 这样做有点多此一举, 但是可能的确有地方需要用到这两个单独的多选分支.
对于我们的逗号插入问题来说, 我们真正需要的是(?!\d)来标记3位数字的起始计数位置. 我们用它来取代\b或者$:

$text =~ s/(?<=\d)(?=(\d\d\d)+(?!\d))/,/g

不通过逆序环视添加逗号

$text =~ s/(\d)((\d\d\d)+\b)/$1,$2/g;

结果并非我们的期望. 得到的是类似”181,421906”的字符串. 这是因为「(\d\d\d)+」匹配的数字属于最终匹配文本, 所以不能作为”未匹配的”部分, 供/g的下一次匹配迭代使用.

Text-to-HTML转换

现在我们写一个把 Text 转换为 HTML 的小工具, 如果要处理所有的情况, 程序将非常难写.

作为正则表达式应用对象的变量都只包含一行文本. 对这个例子来说, 把我们需要转换的所有文本放在同一个字符串中比较方便.

undef $/;  # 进入"file-slurp"(文件读取)模式
$text = <>;  # 读取命令行中指定的第一个文件

1.处理特殊字符
首先我们需要确保原始文件中的’&’ ‘<’和’>’字符”不会出错”, 把它们转换为对应的 HTML 编码, 分别是’&amp’ ‘&lt’和’&gt’. 在 HTML 中这些字符有特殊的含义, 编码不正确可能会导致显示错误:

$text =~ s/&/&amp;/g;  # 保证基本的 HTML…
$text =~ s/</&lt;/g;  # …字符& < >…
$text =~ s/>/&gt;/g;  # …转换后不不错

请注意我们使用了/g来对所有的目标字符进行替换. 首先转换&是很重要的, 因为这三者的 replacement 中都有&字符.

2.分隔段落
识别段落的简单办法就是把空行作为段落之间的分隔.

$text =~ s/^$/<p>/g;

对于之前看到过的 E-mail 的例子, 我们知道每一个字符串只包含一个逻辑行. ^和$通常匹配的不是逻辑行的开头和结尾, 而是整个的字符串的开头和结束位置. 所以, 既然目标字符串中有多个逻辑行, 就需要采取不同的办法.
大多数支持正则表达式的语言提供了一个简单的办法, 即”增强的行锚点”匹配模式, 在这种模式下, ^和$会从字符串模式切换到本例中需要的逻辑行模式.在 Perl 中, 使用/m修饰符来选择此模式:

$text =~ s/^$/<p>/mg;

不过, 如果在”空行”中包含空格符或者其他空白字符, 这么做就行不通. 所以在最终的程序中,我们会使用「^\s*$」.

3.将 E- mail 地址转换为超链接形式
识别出 E-mail 地址, 然后把它转换为”mailto”链接.例如, jfriedl@oreilly.com 会被转换为
<a href="mailto:jfriedl@oreilly.com">jfriedl@oreilly.com</a>
E-mail 地址的基本形式是 username@hostname. 在思考该用怎样的表达式来匹配各个部分之前, 我们先看看这个正则表达式的具体应用环境:

$text =~ s/\b(username regex\@hostname regex)\b/<a href="mailto:$1">$1<\/a>/g;

需要注意其中两个反斜线, 第一个在正则表达式(\@)中, 另一个在 replacement 字符串的末尾.
我们先看第二个, Perl中查找替换的基本形式是 s/regex/replacement.modifier, 用斜线来分隔. 所以, 如果我们需要在某个部分中使用斜线, 就必须使用转义.

4.匹配用户名和主机名
匹配 E-mail 地址的最简单的办法是「\w+\@\w+(.\w+)+」, 用\w来匹配用户名, 以及主机名的各个部分. 不过, 实际应用起来, 我们需要考虑的更周到一些. 于是我们得到用来匹配主机名的「[-a-z0-9]+(.[a[z0-9]+)*.(com|edu|info)」.
无论使用什么正则表达式, 记住它们应用的情境都是很重要的.「[-a-z0-9]+(.[a[z0-9]+)*.(com|edu|info)」这个正则表达式本身, 可以匹配’run C:\startup.command at startup’, 但是把它置入程序运行的环境中, 我们就能确认, 它会匹配我们期望的文本, 而忽略不期望的内容.

5.把 HTTP URL 转换为链接形式
也就是说把”http://www.yahoo.com“转变为
<a href=http://www.yahoo.com/>http://www.yahoo.com/</a>

6.为什么有时候@需要转义
Perl 用@表示数组名, 而 Perl 中的字符串或正则表达式中也容许出现数组变量.
有些语言(例如Python)支持边梁插值, 但是方法各有不同.

回到单词重复问题

在本章开头我给出了一堆难懂的代码, 将其作为解法之一.
现在列在下面的程序使用了 s{regex}{replacement}modifier 的替换形式, 同时使用了/x修饰符来提高清晰程度(空间更充裕的时候, 我们使用更易懂的’next unless’替换’next if!’).

$/ = ".\n";  # ①设定特殊的"块模式"("chunk-mode"); 一块文本的终结为点号和换行符的结合体
    while(<>)  # ②
    {
        next unless s{  #③下面是正则表达式
            ### 匹配一个单词:
            \b  # 单词的开始位置
            ([a-z]+)  # 把读取的单词存储至$1 (和\1)
            ### 下面是任意多的空白字符和/或tag
            (  # 把空白保存到%2
                (?:  # 使用非捕捉型括号
                    \s  #空白字符
                    |
                    <[^>]+>  # <TAG>形式的tag
                )+  # 至少需要出现一次, 多次不受限制
            )
            ### 现在再次匹配第一个单词:
            (\1\b)  # \b保证用来避免嵌套单词的情况, 保存到$3
        }  # 正则表达式结束
        # 上面是正则表达式. 下面是 replacement 字符串, 然后是修饰符 /i /g /x
        {\e[7m$1\e[m$2\e[7m$3\e[m}/ig;  # ④
        s/^(?:[^\e]*\n)+//mg;  # ⑤去除为标记的行
        s/^/$ARGV: /mg;  # ⑥在行首添加文件名
        print;
    }

1.因为单词重复问题必须应对单词重复位于不同行的情况. 在程序中使用特殊变量$/能让<>不再返回单行文字, 而返回或多或少的一段文字, 返回的数据仍然是一个字符串, 只是这个字符串可能包含多个逻辑行.

2.你是否注意到,<>没有值赋给任何变量? 作为 while 中的使用条件时,<>能够把字符串的内容赋给一个特殊的默认变量. 该变量保存了 s/…/…/ 和 print 作用的默认字符串. 使用这些默认变量能够减少冗余代码, 但 Perl 新手不容易看明白.

3.如果没有进行任何替换, 那么替换命令之前的 next unless 会导致 Perl 中断处理当前字符串(转而开始下一个字符串). 如果在当前字符串中没有找到单词重复, 也就不必进行下一步工作.

4.ANSI 转义序列, 把两个重叠的词标记为高亮, 中间的部分则不标记.转义序列\e[7m用于标注高亮的开始,\e[m用于标注高亮的结束(在 Perl 的正则表达式中, \e用来表示 ASCII 的转义字符, 该字符表示之后的字符为 ANSI 转义序列).

5.这个字符串可能包括多个逻辑行, 不过在替换命令标记了所有的重复单词之后, 我们希望只保留那些包含转义字符的逻辑行. 正则表达式「^([^\e]*\n)+」能够找出不包含转义字符的逻辑行.

随着互联网的迅速发展,几乎所有工具软件和程序语言都支持的正则表达式也变得越来越强大和易于使用。《精通正则表达式(第3版)》是讲解正则表达式的经典之作。《精通正则表达式(第3版)》主要讲解了正则表达式的特性和流派、匹配原理、优化原则、实用诀窍以及调校措施,并详细介绍了在Perl、Java、.NET、PHP中正则表达式的用法。 《精通正则表达式(第3版)》自第1版开始着力于教会读者 "以正则表达式来思考",来让读者真正"精通"正则表达式。该版对PHP的相关内容、Java1.5和Java1.6的新特性作了可观的扩充讲解。任何有机会使用正则表达式的读者都将因《精通正则表达式(第3版)》而受益匪浅。 《精通正则表达式(第3版)》讲解正则表达式,这种工具能够提高工作效率、让生活变得更轻松。精心调校后的正则表达式只需要十多秒就能完成以前数小时才能完成的枯燥任务。如今,正则表达式已经成为众多语言及工具--Perl、PHP、Java、Python、Ruby、MysQL、VB-NET和c#(以及.NETFramework中的任何语言)--中的标准特性,依靠它,你能以之前完全不敢设想的方式进行复杂而精巧的文本处理。十年三版,再显王者风范,近30年开发经验的智慧结晶,深入理解正则表达式,彻底修炼基本功,全球第一本全面深入讲解正则表达式的经典巨著,《程序员》杂志技术主编孟岩鼎力推荐。 专家点评:《精通正则表达式》是系统学习正则表达式的唯一最权威著作。任何时候,任何地方,只要提到正则表达式著作,人们都会提到这本书。该书质量之高,声誉之盛,使得几乎没有人企图挑战它的地位,从而在正则表达式图书领域形成了独特的"一夫当关"的局面,称其为正则表达式圣经,绝对当之无愧。 --《程序员》杂志技术主编孟岩 《精通正则表达式(第3版)》包含了对PHP及其正则表达式的讲解。这一版的更新也反映了其他语言的发展,深入讲解了Sun的java.util.regex,并特别提到了Java1.4.2和Java1.5/1.6之间的众多差异。 本书的内容: ·各种语言和工具的功能比较 ·正则引擎的工作原理 ·优化(能节省大量的时间) ·准确匹配期望的文本 ·针对具体语言的节 《精通正则表达式(第3版)》,以明晰轻松的笔调向程序员深入浅出地讲解复杂的知识,并给出了现实世界中复杂问题的解决办法,读者能够立刻运用书中丰富的知识,巧妙而高效地解决各种问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值