Groovy脚本基础全攻略(1),Android开发面试题

2 语法基础

==========

这里开始我们就来快速简单粗暴的了解一下Groovy语法,其实和Java类似,但也有些区别,下面我们一步一步来看吧,切记对比学习,这才是秘笈。

2-1 注释


Groovy的单行注释、多行注释、文档注释基本都和Java一样,没啥特殊的,不再细说。只有一种特殊的单行注释需要留意一下即可。如下:

#!/usr/bin/env groovy

println “Hello from the shebang line”

这种注释通常是用来给UNIX系统声明允许脚本运行的类型的,一般都是固定写法,没啥讲究的。

2-2 关键字


Groovy有如下一些关键字,我们些代码命名时要注意:

as、assert、break、case、catch、class、const、continue、def、default、do、else、enum、extends、false、finally、for、goto、if、implements、import、in、instanceof、interface、new、null、package、return、super、switch、this、throw、throws、trait、true、try、while

这玩意和其他语言一样,没啥特殊的,自行脑补。

2-3 标识符


对于Groovy的标示符和Java还是有些共同点和区别的,特别是引用标示符的区别,具体可以往下看。

2-3-1 普通标识符

普通标识符定义和C语言类似,只能以字母、美元符、下划线开始,不能以数字开头。如下例子:

//正确

def name

def $name

def name_type

def foo.assert

//错误

def 5type

def a+b

2-3-2 引用标识符

引用标识符出现在点后的表达式中,我们可以如下一样使用:

def map = [:]

//引用标示符中出现空格也是对的

map.“an identifier with a space and double quotes” = “ALLOWED”

//引用标示符中出现横线也是对的

map.‘with-dash-signs-and-single-quotes’ = “ALLOWED”

assert map.“an identifier with a space and double quotes” == “ALLOWED”

assert map.‘with-dash-signs-and-single-quotes’ == “ALLOWED”

当然了,Groovy的所有字符串都可以当作引用标示符定义,如下:

//如下类型字符串作为引用标识符都是对的

map.‘single quote’

map.“double quote”

map.‘’‘triple single quote’‘’

map.“”“triple double quote”“”

map./slashy string/

map. / d o l l a r s l a s h y s t r i n g / /dollar slashy string/ /dollarslashystring/

//稍微特殊的GString,也是对的

def firstname = “Homer”

map.“Simson-${firstname}” = “Homer Simson”

assert map.‘Simson-Homer’ == “Homer Simson”

2-4 字符及字符串


Groovy有java.lang.String和groovy.lang.GString两中字符串对象类型,具体如下细说。

2-4-1 单引号字符串

单引号字符串是java.lang.String类型的,不支持站位符插值操作,譬如:

def name = ‘Test Groovy!’

def body = ‘Test $name’

assert name == ‘Test Groovy!’

assert body == 'Test n a m e ′ / / 不会替换 name' //不会替换 name//不会替换name站位符

Groovy的字符串可以通过”+“直接拼接,譬如:

assert ‘ab’ == ‘a’ + ‘b’

其中涉及转义字符规则同Java,只用特殊注意”’“的转义即可。

2-4-2 三重单引号字符串

三重单引号字符串是java.lang.String类型的,不支持站位符插值操作,可以标示多行字符串,譬如:

def aMultilineString = ‘’'line one

line two

line three’‘’

三重单引号字符串允许字符串的内容在多行出现,新的行被转换为“\n”,其他所有的空白字符都被完整的按照文本原样保留;字符开头添加“/”表示字符内容不转义反斜杠“\”,只有在反斜杠接下来是一个字符u的时候才需要进行转义,因为\u表示一个unicode转义。如下:

def strippedFirstNewline = ‘’'\

line one

line two

line three

‘’’

assert !strippedFirstNewline.startsWith(‘\n’)

2-4-3 双引号字符串

双引号字符串支持站位插值操作,如果双引号字符串中不包含站位符则是java.lang.String类型的,如果双引号字符串中包含站位符则是groovy.lang.GString类型的。

对于插值占位符我们可以用${}或者$来标示,${}用于一般替代字串或者表达式,$主要用于A.B的形式中,具体如下例子:

def name = ‘Guillaume’ // a plain string

def greeting = “Hello ${name}”

assert greeting.toString() == ‘Hello Guillaume’

def sum = “The sum of 2 and 3 equals ${2 + 3}”

assert sum.toString() == ‘The sum of 2 and 3 equals 5’

def person = [name: ‘Guillaume’, age: 36]

assert “$person.name is $person.age years old” == ‘Guillaume is 36 years old’

特别注意,$只对A.B等有效,如果表达式包含括号(像方法调用)、大括号、闭包等符号则是无效的。譬如:

def number = 3.14

shouldFail(MissingPropertyException) {

println “$number.toString()”

}

//该代码运行抛出groovy.lang.MissingPropertyException异常,因为Groovy认为去寻找number的名为toString的属性,所以异常

注意,在表达式中访问属性前必须保证属性已经定义好(值为空也可以),如果使用了未定义的属性会抛出groovy.lang.MissingPropertyException异常。 GString还支持延迟运算,譬如在GString中使用闭包,闭包在调用GString的toString()方法时被延迟执行;闭包中可以有0或1个参数,若指定一个参数,则参数会被传入一个Writer对象,我们可以利用这个Writer对象来写入字符,若没有参数,闭包返回值的toString()方法被调用。譬如:

//无参数闭包

def sParameterLessClosure = “1 + 2 == ${-> 3}”

assert sParameterLessClosure == ‘1 + 2 == 3’

//一个参数闭包

def sOneParamClosure = “1 + 2 == ${ w -> w << 3}”

assert sOneParamClosure == ‘1 + 2 == 3’

上面了解了GString的推迟运算特性,下面我们再来看一个牛逼的特性,如下:

def number = 1

def eagerGString = “value == ${number}”

def lazyGString = “value == ${ -> number }”

assert eagerGString == “value == 1”

assert lazyGString == “value == 1”

number = 2

assert eagerGString == “value == 1”

assert lazyGString == “value == 2”

可以看见,eagerGString是普通的双引号插值站位替换,lazyGString是双引号闭包插值替换,我们可以发现在number变为2以后他们的运算结果就有了差异。可以明显推理到结论,一个普通插值表达式值替换实际是在GString创建的时刻,一个包含闭包的表达式由于延迟运算调运toString()方法,所以会产生一个新的字符串值。

当然了,GString和String即使字符串一样他们的HashCode也不会一样,譬如:

assert “one: ${1}”.hashCode() != “one: 1”.hashCode()

由于相同字符串的String与GString的HashCode不同,所以我们一定要避免使用GString作为MAP的key,譬如:

def key = “a”

def m = [“${key}”: “letter ${key}”]

assert m[“a”] == null //由于key的HashCode不同,所以取不到

其中涉及转义字符规则同Java,只用特殊注意””“的转义即可。

2-4-4 多重双引号字符串

多重双引号字符串也支持站位插值操作,我们要特别注意在多重双引号字符串中的单引号和双引号转换问题。譬如:

def name = ‘Groovy’

def template = “”"

Dear Mr ${name},

You’re the winner of the lottery!

Yours sincerly,

Dave

“”"

assert template.toString().contains(‘Groovy’)

2-4-5 斜线字符串

斜线字符串其实和双引号字符串很类似,通常用在正则表达式中,下面我们看几个例子,如下:

//普通使用

def fooPattern = /.foo./

assert fooPattern == ‘.foo.’ //含转义字符使用

def escapeSlash = /The character / is a forward slash/

assert escapeSlash == ‘The character / is a forward slash’ //多行支持

def multilineSlashy = /one

two

three/

assert multilineSlashy.contains(‘\n’) //含站位符使用支持

def color = ‘blue’

def interpolatedSlashy = /a ${color} car/

assert interpolatedSlashy == ‘a blue car’

特别注意,一个空的斜线字符串会被Groovy解析器解析为一注释。

2-4-6 字符Characters

不像Java,Groovy没有明确的Characters。但是我们可以有如下三种不同的方式来将字符串作为字符处理,譬如:

char c1 = ‘A’

assert c1 instanceof Character

def c2 = ‘B’ as char

assert c2 instanceof Character

def c3 = (char)‘C’

assert c3 instanceof Character

2-5 数字Numbers


Groovy支持各种类型的整型和数值类型,通常支持Java支持的那些,下面我们仔细来说说。

2-5-1 整型

Groovy像Java一样支持如下一些整型,byte、char、short、int、long、java.lang.BigInteger。我们在使用中可以像下面例子一样:

// primitive types

byte b = 1

char c = 2

short s = 3

int i = 4

long l = 5

// infinite precision

BigInteger bi = 6

int xInt = 077

assert xInt == 63

int xInt = 0x77

assert xInt == 119

int xInt = 0b10101111

assert xInt == 175

2-5-2 浮点型

Groovy像Java一样支持如下一些浮点型,float、double、java.lang.BigDecimal。我们在使用中可以像下面例子一样:

// primitive types

float f = 1.234

double d = 2.345

// infinite precision

BigDecimal bd = 3.456

assert 1e3 == 1_000.0

assert 2E4 == 20_000.0

assert 3e+1 == 30.0

assert 4E-2 == 0.04

2-6 Booleans类型


Boolean类型没啥解释的,和其他语言一样,就两个值,如下:

def myBooleanVariable = true

boolean untypedBooleanVar = false

booleanField = true

比较简单,没啥特例,自行脑补。

2-7 Lists类型


Groovy同样支持java.util.List类型,在Groovy中同样允许向列表中增加或者删除对象,允许在运行时改变列表的大小,保存在列表中的对象不受类型的限制;此外还可以通过超出列表范围的数来索引列表。如下例子:

//使用动态List

def numbers = [1, 2, 3]

assert numbers instanceof List

assert numbers.size() == 3

//List中存储任意类型

def heterogeneous = [1, “a”, true]

//判断List默认类型

def arrayList = [1, 2, 3]

assert arrayList instanceof java.util.ArrayList

//使用as强转类型

def linkedList = [2, 3, 4] as LinkedList

assert linkedList instanceof java.util.LinkedList

//定义指定类型List

LinkedList otherLinked = [3, 4, 5]

assert otherLinked instanceof java.util.LinkedList

//定义List使用

def letters = [‘a’, ‘b’, ‘c’, ‘d’]

//判断item值

assert letters[0] == ‘a’

assert letters[1] == ‘b’

//负数下标则从右向左index

assert letters[-1] == ‘d’

assert letters[-2] == ‘c’

//指定item赋值判断

letters[2] = ‘C’

assert letters[2] == ‘C’

//给List追加item

letters << ‘e’

assert letters[ 4] == ‘e’

assert letters[-1] == ‘e’

//获取一段List子集

assert letters[1, 3] == [‘b’, ‘d’]

assert letters[2…4] == [‘C’, ‘d’, ‘e’]

//多维List支持

def multi = [[0, 1], [2, 3]]

assert multi[1][0] == 2

2-8 Arrays类型


Groovy中数组和Java类似,具体如下:

//定义初始化String数组

String[] arrStr = [‘Ananas’, ‘Banana’, ‘Kiwi’]

assert arrStr instanceof String[]

assert !(arrStr instanceof List)

//使用def定义初始化int数组

def numArr = [1, 2, 3] as int[]

assert numArr instanceof int[]

assert numArr.size() == 3

//声明定义多维数组指明宽度

def matrix3 = new Integer[3][3]

assert matrix3.size() == 3

//声明多维数组不指定宽度

Integer[][] matrix2

matrix2 = [[1, 2], [3, 4]]

assert matrix2 instanceof Integer[][]

//数组的元素使用及赋值操作

String[] names = [‘Cédric’, ‘Guillaume’, ‘Jochen’, ‘Paul’]

assert names[0] == ‘Cédric’

names[2] = ‘Blackdrag’

assert names[2] == ‘Blackdrag’

2-9 Maps类型


Map是“键-值”对的集合,在Groovy中键key不一定是String,可以是任何对象(实际上Groovy中的Map就是java.util.Linke dHashMap)。如下:

//定义一个Map

def colors = [red: ‘#FF0000’, green: ‘#00FF00’, blue: ‘#0000FF’]

//获取一些指定key的value进行判断操作

assert colors[‘red’] == ‘#FF0000’

assert colors.green == ‘#00FF00’

//给指定key的对赋值value操作与判断

colors[‘pink’] = ‘#FF00FF’

colors.yellow = ‘#FFFF00’

assert colors.pink == ‘#FF00FF’

assert colors[‘yellow’] == ‘#FFFF00’

//判断Map的类型

assert colors instanceof java.util.LinkedHashMap

//访问Map中不存在的key为null

assert colors.unknown == null

//定义key类型为数字的Map

def numbers = [1: ‘one’, 2: ‘two’]

assert numbers[1] == ‘one’

对于Map需要特别注意一种情况,如下:

//把一个定义的变量作为Map的key,访问Map的该key是失败的

def key = ‘name’

def person = [key: ‘Guillaume’]

assert !person.containsKey(‘name’)

assert person.containsKey(‘key’)

//把一个定义的变量作为Map的key的正确写法—添加括弧,访问Map的该key是成功的

person = [(key): ‘Guillaume’]

assert person.containsKey(‘name’)

assert !person.containsKey(‘key’)

【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流

3 运算符

=========

关于Groovy的运算符介绍类似于上面一样,我们重点突出与Java的不同点,相同点自行脑补。

Groovy支持**次方运算符,如下:

assert 2 ** 3 == 8

def f = 3

f **= 2

assert f == 9

Groovy非运算符如下:

assert (!true) == false

assert (!‘foo’) == false

assert (!‘’) == true

Groovy支持?.安全占位符,这个运算符主要用于避免空指针异常,譬如:

def person = Person.find { it.id == 123 }

def name = person?.name

assert name == null

Groovy支持.@直接域访问操作符,因为Groovy自动支持属性getter方法,但有时候我们有一个自己写的特殊getter方法,当不想调用这个特殊的getter方法则可以用直接域访问操作符。如下:

class User {

public final String name

User(String name) { this.name = name}

String getName() { “Name: $name” }

}

def user = new User(‘Bob’)

assert user.name == ‘Name: Bob’

assert user.@name == ‘Bob’

Groovy支持.&方法指针操作符,因为闭包可以被作为一个方法的参数,如果想让一个方法作为另一个方法的参数则可以将一个方法当成一个闭包作为另一个方法的参数。如下:

def list = [‘a’,‘b’,‘c’]

//常规写法

list.each{

println it

}

String printName(name){

println name

}

//方法指针操作符写法

list.each(this.&printName)

Groovy支持将?:三目运算符简化为二目,如下:

displayName = user.name ? user.name : ‘Anonymous’

displayName = user.name ?: ‘Anonymous’

Groovy支持*.展开运算符,一个集合使用展开运算符可以得到一个元素为原集合各个元素执行后面指定方法所得值的集合,如下:

cars = [

new Car(make: ‘Peugeot’, model: ‘508’),

null,

new Car(make: ‘Renault’, model: ‘Clio’)]

assert cars*.make == [‘Peugeot’, null, ‘Renault’]

assert null*.make == null

关于Groovy的其他运算符就不多说,类比Java吧。

【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流

4 程序结构

==========

这里主要讨论Groovy的代码组成结构,具体如下细则。

4-1 包名


包名的定义和作用及含义完全和Java一样,不再介绍,如下:

// defining a package named com.yoursite

package com.yoursite

4-2 Imports引入


常规的imports导包操作和Java一样,如下:

//例1:

import groovy.xml.MarkupBuilder

// using the imported class to create an object

def xml = new MarkupBuilder()

assert xml != null

//例2:

import groovy.xml.*

def markupBuilder = new MarkupBuilder()

assert markupBuilder != null

assert new StreamingMarkupBuilder() != null

//例3:

import static Boolean.FALSE

assert !FALSE

//例4:特殊的,相当于用as取别名

import static Calendar.getInstance as now

assert now().class == Calendar.getInstance().class

不过要特别注意,Groovy与Java类似,已经帮我们默认导入了一些常用的包,所以在我们使用这些包的类时就不用再像上面那样导入了,如下是自动导入的包列表:

import java.lang.*

import java.util.*

import java.io.*

import java.net.*

import groovy.lang.*

import groovy.util.*

import java.math.BigInteger

import java.math.BigDecimal

4-3 脚本与类(脚本的实质)


相对于传统的Java类,一个包含main方法的Groovy类可以如下书写:

class Main {

static void main(String… args) {

println ‘Groovy world!’

}

}

和Java一样,程序会从这个类的main方法开始执行,这是Groovy代码的一种写法,实际上执行Groovy代码完全可以不需要类或main方法,所以更简单的写法如下:

println ‘Groovy world!’

上面这两中写法其实是一样的,具体我们可以通过如下命令进行编译为class文件:

groovyc demo.groovy //编译Groovy源码为class

我们使用反编译工具可以查看到这个demo.groovy类源码如下:

import org.codehaus.groovy.runtime.InvokerHelper

class Main extends Script {

def run() {

println ‘Groovy world!’

}

static void main(String[] args) {

InvokerHelper.runScript(Main, args)

}

}

可以看见,上面我们写的groovy文件编译后的class其实是Java类,该类从Script类派生而来(查阅API);可以发现,每个脚本都会生成一个static main方法,我们执行groovy脚本的实质其实是执行的这个Java类的main方法,脚本源码里所有代码都被放到了run方法中,脚本中定义的方法(该例暂无)都会被定义在Main类中。

通过上面可以发现,Groovy的实质就是Java的class,也就是说他一定会和Java一样存在变量作用域!对哦,前面我们解释变量时竟然没说到这个东东,这里说下吧。看下面例子:

//单个Groovy源码文件,运行会报错找不到num变量

def num = 1

def printNum(){

println num

}

//单个Groovy源码文件,运行会报错找不到num变量

int num = 1

def printNum(){

println num

}

//单个Groovy源码文件,运行OK成功

num = 1

def printNum(){

println num

}

上面的例子可以发现,我们如果想要在Groovy的方法中使用Groovy的变量则不能有修饰符。然而,如果我们想在B.groovy文件访问A.groovy文件的num变量咋办呢,我们可以使用Field注解,具体操作如下:

import groovy.transform.Field;

@Field num = 1

哈哈,这就是Groovy的变量作用域了,如果你想知道上面这些写法为啥出错,很简单,自己动手整成Java源码相信你一定可以看懂为啥鸟。

【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流

5 闭包

========

Groovy的闭包(closure)是一个非常重要的概念,闭包是可以用作方法参数的代码块,Groovy的闭包更象是一个代码块或者方法指针,代码在某处被定义然后在其后的调用处执行。

5-1 语法


定义一个闭包:

{ [closureParameters -> ] statements }

//[closureparameters -> ]是可选的逗号分隔的参数列表,参数类似于方法的参数列表,这些参数可以是类型化或非类型化的。

如下给出几个有效的闭包定义例子:

//最基本的闭包

{ item++ }

//使用->将参数与代码分离

{ -> item++ }

//使用隐含参数it(后面有介绍)

{ println it }

//使用明确的参数it替代

{ it -> println it }

//使用显示的名为参数

{ name -> println name }

//接受两个参数的闭包

{ String x, int y ->

println “hey ${x} the value is ${y}”

} //包含一个参数多个语句的闭包

{ reader ->

def line = reader.readLine()

line.trim()

}

闭包对象:

一个闭包其实就是一个groovy.lang.Closure类型的实例,如下:

//定义一个Closure类型的闭包

def listener = { e -> println “Clicked on $e.source” }

assert listener instanceof Closure

//定义直接指定为Closure类型的闭包

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

写在最后

由于本文罗列的知识点是根据我自身总结出来的,并且由于本人水平有限,无法全部提及,欢迎大神们能补充~

将来我会对上面的知识点一个一个深入学习,也希望有童鞋跟我一起学习,一起进阶。

提升架构认知不是一蹴而就的,它离不开刻意学习和思考。

**这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家,**梳理了多年的架构经验,筹备近1个月最新录制的,相信这份视频能给你带来不一样的启发、收获。

最近还在整理并复习一些Android基础知识点,有问题希望大家够指出,谢谢。

希望读到这的您能转发分享和关注一下我,以后还会更新技术干货,谢谢您的支持!

转发+点赞+关注,第一时间获取最新知识点

Android架构师之路很漫长,一起共勉吧!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

//定义一个Closure类型的闭包

def listener = { e -> println “Clicked on $e.source” }

assert listener instanceof Closure

//定义直接指定为Closure类型的闭包

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-N7KlCG78-1712540490572)]

[外链图片转存中…(img-UkL5EhJP-1712540490573)]

[外链图片转存中…(img-88JAKedp-1712540490573)]

[外链图片转存中…(img-yYEQ1dyv-1712540490573)]

[外链图片转存中…(img-huOWCQG8-1712540490574)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

写在最后

由于本文罗列的知识点是根据我自身总结出来的,并且由于本人水平有限,无法全部提及,欢迎大神们能补充~

将来我会对上面的知识点一个一个深入学习,也希望有童鞋跟我一起学习,一起进阶。

提升架构认知不是一蹴而就的,它离不开刻意学习和思考。

**这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家,**梳理了多年的架构经验,筹备近1个月最新录制的,相信这份视频能给你带来不一样的启发、收获。

[外链图片转存中…(img-cibmurSr-1712540490574)]

[外链图片转存中…(img-LDbHqpa4-1712540490574)]

最近还在整理并复习一些Android基础知识点,有问题希望大家够指出,谢谢。

希望读到这的您能转发分享和关注一下我,以后还会更新技术干货,谢谢您的支持!

转发+点赞+关注,第一时间获取最新知识点

Android架构师之路很漫长,一起共勉吧!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 29
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值