2024年安卓最新Dart语言之从入门到放弃(万字长文),带你快速通过字节跳动面试

尾声

以薪资待遇为基础,以发展为最终目标,要在高薪资的地方,谋求最好的发展!

下面是有几位Android行业大佬对应上方技术点整理的一些进阶资料。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

final finalList1 = [1, 2];
final finalList2 = [1, 2];
print(identical(finalList1, finalList2)); //identical用于检查两个引用是否指向同一个对象

const constList1 = [1, 2];
const constList2 = [1, 2];
print(identical(constList1, constList2));

上面代码的运行结果是false和ture,也印证了上面所说的。

  • const需要是编译时常量

final DateTime finalDateTime = DateTime.now();
// const DateTime constDateTime = DateTime.now();//DateTime.now() 是运行期计算出来的值

小总结

本小节简单说明了在Dart中变量的一些细节问题,如有补充或者错误可直接在评论区说出来,不要给我面子😂。

内置类型

概述

在Dart中,内置类型有以下几种:Numbers 数值、Strings 字符串、Booleans 布尔值、Lists 列表(数组)、Sets 集合、Maps 集合、Runes 符号字符、Symbols 标识符

num, int, double

int和double大家都很熟悉,int还是整数值,double是64-bit双精度浮点数。这里需要着重看一下numint和double是num的子类。剩下的就没什么好说的了,其他的使用方法和Java基本一样,只是可以直接定义num类型的数据,剩下的大家直接看下面的代码应该就明白了:

int i = 1; //整数值
double d = 1.0; //double 64-bit (双精度) 浮点数
int bitLength = i.bitLength;
print(‘bitLength: ${bitLength}’); //bitLength判断int值需要多少bit位
double maxFinite = double.maxFinite;
print(‘maxFinite: ${maxFinite}’); //maxFinitedouble的最大值
//int和double都是num的子类
num n1 = 1;
num n2 = 1.0;
//支持十进制、十六进制
int i1 = 0xfff;
//科学计数法
double d1 = 1.2e2; //120.0
//转换
//String->int
int i2 = int.parse(‘1’);
double d2 = 1; //当double的值为int值时,int自动转成double
print(‘d2: ${d2}’);
// int i2 = int.tryParse(‘1.0’);//返回null

String

  • Dart 字符串是 UTF-16 编码的字符序列,可以使用单引号或者双引号来创建字符串

main(){
String a = ‘zhujiang’;
String b = “zhujiang”;
}

  • 可以使用三个单引号或者双引号创建多行字符串对象

String c = “dd”
“sssdffg”
“vftgt”;
String d = ‘’‘ssss
fffffffgg
grrrr’‘’;

  • 可以使用 r 前缀创建”原始raw”字符串。

String e = ‘’‘ssss
fff\nffffgg
grrrr’‘’;
String f = r’‘‘ssss
fff\nffffgg
grrrr’’';
print(e);
print(f);

这里需要说明一下,如果使用r前缀,即是原始字符串,\n换行会直接打印出来而不会实现换行,下面是执行结果:

lib/2-type.dart: Warning: Interpreting this as package URI, ‘package:darttest/2-type.dart’.
ssss
fff
ffffgg
grrrr
ssss
fff\nffffgg
grrrr

  • 可以在字符串中使用表达式: ${expression},如果表达式是一个标识符,可以省略 {},如果表达式的结果为一个对象,则 Dart 会调用对象的 toString() 函数来获取一个字符串。

print(“object:$c”);

这里既然已经说到String了,那就再说一下StringBuffer吧,使用方法和Java一样,但是可以省略new关键字,还可以进行链式调用:

StringBuffer stringBuffer = StringBuffer();
stringBuffer…write(“sss”)…write(“ssss”);

bool

这里的bool相当于Java中boolean,这里只需要记着bool对象未初始化的默认值是null(上一篇文章中提到过)

List

  • Dart中可以直接打印list包括list的元素,List也是对象(java中直接打印list结果是地址值)。

print(List)

这里会直接输出List。

  • Dart中List的下标索引和java一样从0开始

  • 和java一样支持泛型。

  • 有增删改查,支持倒序,自带排序、洗牌,可使用+将两个List合并

//常用方法 增删改查,排序,洗牌,复制子列表
var list4 = [];
//增
list4.add(1);
print(‘add 1 : l i s t 4 ′ ) ; / / 删 l i s t 4. r e m o v e ( 5 ) ; p r i n t ( ′ r e m o v e 5 : {list4}'); //删 list4.remove(5); print('remove 5 : list4);//list4.remove(5);print(remove5:{list4}’);
list4.removeAt(2);
print(‘remove at 0 : l i s t 4 ′ ) ; / / 改 l i s t 4 [ 4 ] = 5 ; p r i n t ( ′ u p d a t e l i s t 4 [ 4 ] t o 5 : {list4}'); //改 list4[4] = 5; print('update list4[4] to 5 : list4);//list4[4]=5;print(updatelist4[4]to5:list4}’);
//range
list4.fillRange(0, 3, 9);
print(‘fillRange update list4[0]-list4[2] to 9 :KaTeX parse error: Expected 'EOF', got '}' at position 6: list4}̲'); Iterable ge…getRange}’);
//查
var contains = list4.contains(5);
print(‘list4 contains 5 : c o n t a i n s ′ ) ; v a r i n d e x O f = l i s t 4. i n d e x O f ( 1 ) ; p r i n t ( ′ l i s t 4 i n d e x O f 1 : {contains}'); var indexOf = list4.indexOf(1); print('list4 indexOf 1 : contains);varindexOf=list4.indexOf(1);print(list4indexOf1:{indexOf}’);
int indexWhere = list4.indexWhere((test) => test == 5);
print(‘list4 indexWhere 5 : i n d e x W h e r e ′ ) ; / / 排序 l i s t 4. s o r t ( ) ; p r i n t ( ′ l i s t 4 s o r t : {indexWhere}'); //排序 list4.sort(); print('list4 sort : indexWhere);//排序list4.sort();print(list4sort:{list4}’);
//洗牌
list4.shuffle();
print(‘list4 shuffle : l i s t 4 ′ ) ; / / 复制子列表 v a r l i s t 5 = l i s t 4. s u b l i s t ( 1 ) ; p r i n t ( ′ s u b l i s t ( 1 ) l i s t 5 : {list4}'); //复制子列表 var list5 = list4.sublist(1); print('sublist(1) list5 : list4);//复制子列表varlist5=list4.sublist(1);print(sublist(1)list5:{list5}’);
//操作符
var list6 = [8, 9];
print(‘list6 : l i s t 6 ′ ) ; v a r l i s t 7 = l i s t 5 + l i s t 6 ; p r i n t ( ′ l i s t 5 + l i s t 6 : {list6}'); var list7 = list5 + list6; print('list5 + list6 : list6);varlist7=list5+list6;print(list5+list6:{list7}’);

这里的代码比较长,但是很好理解,就是一个集合的增删改查和一些比较常用的方法,大家可以自己手写一下加深理解。

Map

map这里就不多说了,和Java类似

Set

Set这里其实也和Java差不多,但是有几个地方需要说明一下:

  • set1.difference(set2):返回set1集合里有但set2里没有的元素集合
  • set1.intersection(set2):返回set1和set2的交集
  • set1.union(set2):返回set1和set2的并集
  • set1.retainAll():set1只保留某些元素(要保留的元素要在原set中存在)

这几个方法非常好用,简单写一下伪代码大家看一下吧:

var difference12 = set1.difference(set2);
var difference21 = set2.difference(set1);
print(‘set1 difference set2 : d i f f e r e n c e 12 ′ ) ; / / 返回 s e t 1 集合里有但 s e t 2 里没有的元素集合 p r i n t ( ′ s e t 2 d i f f e r e n c e s e t 1 : {difference12}'); //返回set1集合里有但set2里没有的元素集合 print('set2 difference set1 : difference12);//返回set1集合里有但set2里没有的元素集合print(set2differenceset1:{difference21}’); //返回set2集合里有但set1里没有的元素集合
var intersection = set1.intersection(set2);
print(‘set1 set2交集 : i n t e r s e c t i o n ′ ) ; / / 返回 s e t 1 和 s e t 2 的交集 v a r u n i o n = s e t 1. u n i o n ( s e t 2 ) ; p r i n t ( ′ s e t 1 s e t 2 并集 : {intersection}'); //返回set1和set2的交集 var union = set1.union(set2); print('set1 set2并集 : intersection);//返回set1set2的交集varunion=set1.union(set2);print(set1set2并集:{union}’); //返回set1和set2的并集
set2.retainAll([‘aa’, ‘flutter’]); //只保留(要保留的元素要在原set中存在)
print(‘set2只保留aa flutter 😒{set2}’);

Runes

表示符文的意思,用于在字符串中表示Unicode字符。使用String.fromCharCodes显示字符图形。如果非4个数值,需要把编码值放到大括号中。

Runes runes = new Runes('\u{1f605} \u6211‘);
var str1 = String.fromCharCodes(runes);
print(str1);

直接这样写就可以了,下面看一下打印效果:

😅 我

直接可以打印出表情,有表情需求的可以直接使用Runes。

Symbol

Symbol标识符,以前主要是反射使用,但是现在mirrors模块已经被移除,所以没啥大用了,大家只要知道有这么一个内置类型就可以了。

方法

方法这一块很重要,写代码离不开方法啊,继续开车,抓稳了!

定义

  • 可在函数内定义

void main(){
void test(){
}
}

和Java不同,Dart中的函数可以定义在函数内部(和Java的匿名内部类别搞混了)

  • 定义函数时可省略类型(不建议)

main(){
test(){
// return null;
}
}

上面的函数可以写成下面这样,Dart中函数是Function类型的对象。所有的函数都返回一个值。如果没有指定返回值,则 默认把语句 return null; 作为函数的最后一个语句执行。

  • 支持缩写语法 => (Kotlin是用=来实现)

main(){
print(test1());
}
test()=> “zhujiang”;
String test1(){
return “zhujiang”;
}

上面代码中的test()和test1()效果是一样的,“=>”相当于大括号和return

可选参数

既然是函数,那么肯定要传参,我在经过Java构造方法的摧残之后,看这个功能的时候泪流满面😭。为什么会这样呢?下面先来看一个咱们的Java类的构造函数:

public class Test {

private String name;
private int age;
private String address;

public Test(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}

public Test(String name, String address) {
this.name = name;
this.address = address;
}

public Test(String name, int age) {
this.name = name;
this.age = age;
}

public Test(String name) {
this.name = name;
}
}

一个非常简单的构造方法,只是想要传参数形式多一点,竟然需要写这么多重载方法。。。那么下面咱们来看一下拿Dart写的同样功能的代码:

class Test{
String name;
int age;
String address;
Test(this.name, {this.age, this.address});
}

What?竟然可以这样?是的,可以这样。下面来详细说一下函数的可选参数。

可选命名参数

其实上面构造方法我使用的就是可选命名参数,写的时候其实很简单,只需要加上大括号就行。下面是使用方法:

main() {
print(add(a: 1, b: 3));
}

int add({int a = 1, int b}) {
return a + b;
}

上面的add方法调用的时候可以不填参数,也可以填任何一个参数,也可以将参数都填上。这里要注意:

  • 如果a不填的话,默认值就是1,如果b不填,默认值就是null。
  • 传参数的时候一定要记得写a:,不然会报错。
可选位置参数

和上面的可选命名参数不同,上面的函数如果想调用,必须命名再加上冒号才能使用,可选位置参数就不需要了。这里和上面一样,默认值可加可不加:

int add2(int a,[int b = 2,int c = 3]) {
return a + b;
}

下面是上面add2()方法的几种调用方式:

print(add2(1));
print(add2(1,2));
print(add2(1,1,3));

注意

上面的可选命名参数和可选位置参数,可使用list或map作为默认值,但必须是const。

int add3({List a = const [1,2,3], int b = 2}) {
return b;
}

匿名函数

可赋值给变量,通过变量调用。可在其他函数中直接调用或传递给其他函数。匿名函数分为无参匿名函数和有参匿名函数

  • 无参匿名函数

var printFun = () => print(“无参匿名函数”);

  • 有参匿名函数

var printFun2 = (name) => print(“有参匿名函数 $name”);
printFun2(“sss”);

这里还有一个小的知识点可以通过()调用,不推荐。

(() =>print(“***可以通过()调用,不推”))();

闭包

这里感觉没什么好说的,大家直接看代码应该就可以理解:

Function makeAddFunc(int x) {
x++;
return (int y) => x + y;
}
var makeAddFun = makeAddFunc(11);
print(makeAddFun(10));

就是创建一个方法,返回的也是一个方法,可以再继续调用。

函数别名

typedef给函数起一个别名,使用比较方便。例如定义一个方法的回调,直接使用别名定义。没返回值,则只要参数匹配就行了,如果定义了返回值,则返回值不一样会报错。

typedef Fun1(int a, int b);
typedef Fun2<T, K>(T a, K b);
int add(int a, int b) {
print(‘a + b’);
return a + b;
}
class Demo1 {
Demo1(int f(int a, int b), int x, int y) {
var sum = f(x, y);
print(“sum1 = $sum”);
}
}
class Demo2 {
Demo2(Fun1 f, int x, int y) {
var sum = f(x, y);
print(“sum2 = $sum”);
}
}
class Demo3 {
Demo3(Fun2<int, int> f, int x, int y) {
var sum = f(x, y);
print(“sum3 = $sum”);
}
}

上面代码就是定义函数别名的方法,下面是调用方法:

Fun1 fun1 = add(11, 12);

操作符、流程控制语句、异常

这一小节看着东西应该挺多,其实很简单,好多都和Java基本一致。

操作符

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上面这幅图中就是Dart中的操作符,标为黑色的操作符和Java中使用基本一样,这里也就不过多赘述,咱们来仔细看看标红Java中没有的操作符:

  • 后缀操作: ?.

如果写过Kotlin的话,可以跳过这段了,这里的问号点和Kotlin中基本一致,都是为了判空而出现的,那么下面来看一下使用方式吧:

main() {
String a;
print(a.length != null ? a.length : ‘’);
}

上面就是咱们平时写的代码,一个三目表达式来进行判空。但是在Dart中可以不这样写,可以直接使用问号点。

main() {
String a;
//print(a.length != null ? a.length : '');
print(a?.length);
}

是不是很方便?以后判空可以直接使用**“?.”**了

  • 除(取整):~/

这个操作符其实很简单,只是对数字进行取整,下面是实例代码:

print(1/2);
print(1~/2);

上面代码的输出值是0.5和0

  • 类型操作:as

这个和Kotlin中的as也基本一样,是对数据进行类型转换,很简单,直接看代码:

num n = 1;
int n2 = n as int;

  • 类型操作: is

num n1 = 1.0;
if(n1 is int){
print(“int”);
}else{
print(“double”);
}

  • 类型操作:is!

从名称上就可以知道和上面的is正好相反,就不写代码验证了。

  • 判空:??

这个上面也写出来了,意思就是判空,下面直接看一下使用方法吧:

bool aaa;
aaa = aaa ?? false;

  • 级联:

这个其实就是链式调用,之前的文章中写过,当时的例子是StringBuffer:

StringBuffer stringBuffer = StringBuffer();
stringBuffer…write(“sss”)…write(“ssss”);

操作符就说到这里吧,掌握好操作符在写代码的时候会事半功倍的,希望大家都能掌握好。

流程控制语句

这个,怎么说,稍微提一下吧,这个是基础的基础,Dart中的流程控制语句和Java基本一样,直接进行使用就行。

不是不想写,if语句、for循环、while循环、brake、continue、switch、case,没了,这就是Dart的流程控制语句,如出一辙。跳过。

异常

Dart 提供了 Exception 和 Error 类型, 以及一些子类型。还可以定义自己的异常类型。但是,Dart 代码可以抛出任何非 null 对象为异常,不仅仅是实现了 Exception 或者 Error 的对象。

下面这幅图是Dart中的Exception

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

下面这幅图是Dart中的Error

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

抛出异常
  • 这个和Java中写法一样,所有的 Dart 异常是非检查异常。 方法不一定声明了他们所抛出的异常, 并且你不要求捕获任何异常。

// 抛出Exception 对象
// throw new FormatException(‘格式异常’);

// 抛出Error 对象
// throw new OutOfMemoryError();

  • Dart 代码可以抛出任何非 null 对象为异常,不仅仅是实现了 Exception 或者 Error 的对象。

// 抛出任意非null对象
// throw ‘这是一个异常’;

捕获异常
  • 所有的 Dart 异常是非检查异常。 方法不一定声明了他们所抛出的异常, 并且你不要求捕获任何异常。可以使用on 或者 catch 来声明捕获语句,也可以 同时使用。使用 on 来指定异常类型,使用 catch 来 捕获异常对象。

try {
throw new OutOfMemoryError();
} on OutOfMemoryError {
print(‘没有内存了’);
}

  • catch() 可以带有一个或者两个参数, 第一个参数为抛出的异常对象, 第二个为堆栈信息 (一个 StackTrace 对象)。

try {
throw new OutOfMemoryError();
} catch (e, s) {
print(‘Exception details: $e’);
print(‘Stack Trace: $s’);
} finally {
print(‘end’);
}

  • 可以使用rethrow把捕获的异常重新抛出。

try {
throw new OutOfMemoryError();
} catch (e, s) {
print(‘Exception details: $e’);
print(‘Stack Trace: $s’);
rethrow;
}

类的点点滴滴

前几小节分别讲解了Dart中的变量、内置类型、函数(方法)、操作符、流程控制语句和异常,对Dart的基本语法已经有了很多的了解,那么接来说一下Dart中的类。

构造函数

前几篇文章中在讲函数(方法)的一篇中提到过,这里再说一下吧,首先来看一下Java中构造函数的写法:

class Point {
double x;
double y;

Point(int x, int y) {
this.x = x;
this.y = y;
}
}

下面是dart中的建议写法:

class Point {
num x;
num y;
Point(this.x, this.y);
}

命名构造函数

使用命名构造函数可以为一个类实现多个构造函数, 或者使用命名构造函数来更清晰的表明你的意图。

class Point {
num x;
num y;

Point(this.x, this.y);

//命名构造函数
Point.fromJson(Map json) {
x = json[‘x’];
y = json[‘y’];
}
}

重定向构造函数

一个重定向构造函数是没有代码的,在构造函数声明后,使用 冒号调用其他构造函数。

class Point {
num x;
num y;

Point(this.x, this.y);

//重定向构造函数,使用冒号调用其他构造函数
Point.alongXAxis(num x) : this(x, 0);
}

初始化列表

在构造函数体执行之前可以初始化实例参数。 使用逗号分隔初始化表达式。初始化列表非常适合用来设置 final 变量的值。

import ‘dart:math’;

class Point {
//final变量不能被修改,必须被构造函数初始化
final num x;
final num y;
final num distanceFromOrigin;

//初始化列表
Point(x, y)
: x = x,
y = y,
distanceFromOrigin = sqrt(x * x + y * y);
}

调用超类构造函数

首先来建立一个超类(父类),下面的代码都继承自此类:

class Parent {
int x;
int y;

//父类命名构造函数不会传递
Parent.fromJson(x, y)
: x = x,
y = y {
print(‘父类命名构造函数’);
}
}

  • 超类命名构造函数不会传递,如果希望使用超类中定义的命名构造函数创建子类,则必须在子类中实现该构造函数。

class Child extends Parent {
int x;
int y;

Child.fromJson(x, y)
x = x,
y = y,
print(‘子类命名构造函数’);
}
}
  • 如果超类没有默认构造函数, 则你需要手动的调用超类的其他构造函数,调用超类构造函数的参数无法访问 this。

class Child extends Parent {
int x;
int y;
//若超类没有默认构造函数, 需要手动调用超类其他构造函数

最后

简历首选内推方式,速度快,效率高啊!然后可以在拉钩,boss,脉脉,大街上看看。简历上写道熟悉什么技术就一定要去熟悉它,不然被问到不会很尴尬!做过什么项目,即使项目体量不大,但也一定要熟悉实现原理!不是你负责的部分,也可以看看同事是怎么实现的,换你来做你会怎么做?做过什么,会什么是广度问题,取决于项目内容。但做过什么,达到怎样一个境界,这是深度问题,和个人学习能力和解决问题的态度有关了。大公司看深度,小公司看广度。大公司面试你会的,小公司面试他们用到的你会不会,也就是岗位匹配度。

选定你想去的几家公司后,先去一些小的公司练练,学习下面试技巧,总结下,也算是熟悉下面试氛围,平时和同事或者产品PK时可以讲得头头是道,思路清晰至极,到了现场真的不一样,怎么描述你所做的一切,这绝对是个学术性问题!

面试过程一定要有礼貌!即使你觉得面试官不尊重你,经常打断你的讲解,或者你觉得他不如你,问的问题缺乏专业水平,你也一定要尊重他,谁叫现在是他选择你,等你拿到offer后就是你选择他了。

金九银十面试季,跳槽季,整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

就是岗位匹配度。

选定你想去的几家公司后,先去一些小的公司练练,学习下面试技巧,总结下,也算是熟悉下面试氛围,平时和同事或者产品PK时可以讲得头头是道,思路清晰至极,到了现场真的不一样,怎么描述你所做的一切,这绝对是个学术性问题!

面试过程一定要有礼貌!即使你觉得面试官不尊重你,经常打断你的讲解,或者你觉得他不如你,问的问题缺乏专业水平,你也一定要尊重他,谁叫现在是他选择你,等你拿到offer后就是你选择他了。

金九银十面试季,跳槽季,整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-BjrBPFXV-1715815276652)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值