大数据笔记--scala(第二篇)

目录

一、Scala的异常处理机制

二、Scala函数

1、函数的声明

2、函数的返回值

三、Scala高阶函数

1、函数的种类

2、高阶函数

3、递归函数

四、柯里化Currying

五、Scala的类

1、概述

2、创建类

 2、类的构造

3、辅助构造器

4、单例对象

5、重写与重载

6、final的使用

7、抽象类

8、多态

9、特质trait

10、泛型

11、lazy懒值

12、caseclass-样例类

13、option类型

六、scala隐式转换机制

1、概述

2、隐式转换函数

3、隐式类

4、隐式参数


一、Scala的异常处理机制

scala中继承了java的异常机制

try catch finally

object ScalaDemo04 {
  def main(args: Array[String]): Unit = {
    try {
      throw new RuntimeException("error");
    }catch {
      case t: NullPointerException => t.printStackTrace();println("空指针异常");
      case t: Exception=>t.printStackTrace();println("其他异常");
    }finally {
      println("资源释放")
    }
  }
}

1、scala的异常处理机制同java。只不过形式上有区别。在catch中是通过case来匹配异常然后进行处理

2、scala中没有switch case。取代的是match case

3、scala的match case 有返回值。可以接

  val num=6                                     
  val result=num match{
  	case 5=>{
  	println("555")
  	"555"
  	}
  	case 6=>{
  	println("666")
  	"666"
  	}
  	case 7=>{
  	println("777")
  	"777"
  	}
  }   

4、注意:match case 只要发现有一个匹配,则剩下的case不会继续匹配

     scala中的match类似于其他语言的switch

5、scala通过import关键字导包。比如 import a._  表示导入a包下所有类

6、scala可以在任意位置导包。但注意作用域问题

7、scala实现break的continue的方式:

        注意:scala中没有break和continue语句,需要通过另外的形式来实现

        ①、导包:import util.control.Breaks._

        ②、要实现break: breakable(for(){break跳出循环效果})

        ③、要实现continue: for(){breakable(break->continue效果)}

import util.control.Breaks._
    for(i<-1.to(10)) {
      breakable(
        if (i == 8) {
          break
        } else {
          print(i+" ")  //结果 1 2 3 4 5 6 7 9 10
        }
      )
    }

二、Scala函数

1、函数的声明

        scala 函数通过 def 关键字定义,def前面可以具有修饰符,可以通过private、protected来控制其访问权限

注意:没有public,不写默认就是public的。 此外也可跟上override,final等关键字修饰。

2、函数的返回值

1)函数体中return关键字往往可以省略掉,一旦省略掉,函数将会返回整个函数体中最后一行表达式的值,这也要求整个函数体的最后一行必须是正确类型的值的表达式。

2)大部分时候scala都可以通过 =符号 来自动推断出返回值的类型,所以通常返回值类型声明可以省略。

但是注意:如果因为省略了返回值类型造成歧义,则一定要写上返回值声明。

3)如果函数体只有一行内容,则包裹函数体的大括号可以省略

4)如果定义函数时,{}前没有=,则函数的返回值一律为Unit。如果返回值类型是UNIT,则另一种写法是可以去掉返回值类型和等号,把方法体写在花括号内,而这时方法内无论返回什么,返回值都是 UNIT。                

格式:[private/protected] def 函数名(参数列表):返回值声明 = {函数体}

5) scala的函数支持默认参数机制。形式:def 函数名(形参名:类型=默认值)={}

6) scala的函数支持变长参数机制。可类比于java的可变参数机制。  def f9(num:Int,a:String*)={
注意:变长参数必须位于参数列表的最后。

object ScalaDemo05 {
  def main(args: Array[String]): Unit = {
    println(p0("1.txt"))
    println(p1(10))
    println(p2(3))
    p3("hello")
  }
  // 返回文件名的后缀
  def p0(name:String)={
    name.split("\\.")(1)
  }
  def p1(num:Int)={
    0 to num
  }
  //默认参数
  def p2(a:Int,b:Int=10)={
    a+b
  }
  //可变参数
  def p3(a:String*)={
    for(i<-a){
      println(i)
    }
  }
}

三、Scala高阶函数

1、函数的种类

①、成员函数

 函数被使用在类的内部,作为类的一份子,称为类的成员函数

object ScalaDemo06 {
  def main(args: Array[String]): Unit = {
    val p = new Student()
    p.eat()
    p.study()
  }
  // eat() 和study()属于 类Student的成员函数
  class Student{
    def eat(): Unit ={
      println("吃饭")
    }
    def study(): Unit ={
      println("学习")
    }
  }
}

②、本地函数

函数内嵌的函数称为本地函数,这样的函数外界无法访问

/**
 * 本地函数
 */
object ScalaDemo07 {
  def main(args: Array[String]): Unit = {
    val p = new Student()
    p.eat("肉")
  }
  class Student{
    def eat(food:String): Unit ={
      //cook函数内嵌在eat函数里面,这样的函数称之为本地函数
      def cook(food:String)={
        "做熟了的"+food
      }
      println("吃"+cook(food))
    }
  }
}

③、函数值(匿名函数):最直观的特点就是没有函数名;方法体{}的连接是=>

匿名和函数的作用:把匿名函数作为参数进行赋值;把匿名函数作为参数进行传递

  def f1(a: Int, b: Int) = {a + b}
  //等价于上式的函数
  (a: Int, b: Int) => {a + b}
  //如果函数体只有一行代码,可以省去大括号
  (a: Int, b: Int) =>a + b
  //如果函数的参数类型可以被推测,则可以省略类型值
  //(a, b) => a + b
  //如果函数参数列表只有一个参数,小括号有可以省略
  //a: Int => a + 1;

  //可以将f1()函数赋值给f2常量
  val f2=f1(_,_);
  //可以将f1()函数赋值给f3变量,f3可以更改此函数
  var f3=f1(_,_);
  f3=(a:Int,b:Int)=>a*b;
  //也可以这样写
  val f4=(c:Int,d:Int)=>{c+d}
  //注意,下面的写法是将f1的函数值复制给f5,而不是函数赋值给f5 
  val f5=f1(2,3)

④、高阶函数

高阶函数(Higher-Order Function)就是操作其他函数的函数。

object ScalaDemo09 {
  def main(args: Array[String]): Unit = {
    f1("hello","world",(a:String,b:String)=>{a+b})
  }
  def f1(a: String,b: String,f:(String,String)=>String){
    println(f(a,b))

  }
}

2、高阶函数

Scala 中允许使用高阶函数, 高阶函数可以使用其他函数作为参数,或者使用函数作为输出结果。

object ScalaDemo09 {
  //定义了compute函数,a,b和f函数  三个参数。其中f函数未做实现。
  //我们的目的是对传入的a和b参数利用 f函数做运算,但是具体是什么运算需要由用户自己来指定。
  def compute(a:Int,b:Int,f:(Int,Int)=>Int):Int={
    f(a,b)
  }
  def main(args: Array[String]): Unit = {
    val f1=(a:Int,b:Int)=>{a+b}
    val f2=(a:Int,b:Int)=>{a*b}
    val f3=(a:Int,b:Int)=>{a-b}
    val f4=(a:Int,b:Int)=>{a/b}
    //由下式可以看出,scala中,函数可以当做参数进行传递和调用
    val result=compute(2,3,f1)
    println(result)
    //下式等价于上式
    //val result=compute(2,3,(a,b)=>{a+b})
  }
}
/**
 * 将用户处理后的字符串结果进行打印输出
 */
object ScalaDemo10 {

  def handleString(a:String,f:(String)=>String): Unit ={
    println("处理完后的字符串:"+f(a))
  }

  def main(args: Array[String]): Unit = {
    val a="hello scala"
    handleString(a,a=>a)
    handleString(a,(a)=>{a.substring(6)})
    handleString(a,(a)=>{a.concat("1706")})
  }
}

占位符:占位符指的是scala中的下划线_ ,可以用它当作一个或多个参数来使用。

使用_占位符的前提要求每个参数在函数仅出现一次。

使用下划线时,如果类型可以自动推断出,则不用声明类型。如果无法自动推断类型,则在下划线后自己来显示声明类型即可。

object ScalaDemo11 {
  def compute(a:Int,b:Int,f:(Int,Int)=>Int)={
    f(a,b)
  }
  def handleString(a:String,f:(String)=>String){
    println("处理完后的字符串为:"+ f(a))
  }

  def main(args: Array[String]): Unit = {
    val message="hello scala"
    //这样用占位符会出错
    //handleString(message,(_)=>{_.substring(6)})
    //应该为下面的写法
    handleString(message,{_.substring(6)})

    //如果函数体只有一行代码,则还可以将大括号去掉
    handleString(message,_.substring(6))

    //compute的代码可简化
    compute(2,3,_+_)
    compute(2,3,_*_)
    compute(2,3,_-_)

    //此外
    //    val f1=(a:Int,b:Int)=>{a+b}
    //    等价于下式:
    //    val f1=(_:Int)+(_:Int)
    //    compute(2, 3, f1)

    //比如
    val list=List(1,3,5,7,9)
    list.foreach {x => print(x)}
    list.foreach {_ => print(_)}
    list.foreach{print(_)}
    list.foreach{x=>x*2}
    list.foreach{_*2}
  }

}

3、递归函数

用递归方式实现斐波那契数列:

scala 递归函数的返回值类型必须显示声明,不能使用自动推断
scala 递归函数结束条件的返回值时,必须加return 关键字返回

/**
 * 用递归方式实现斐波那契数列
 * 给定一个斐波那契数列:
 *    1 2 3 5 8 13 21 ...
 * 目的:编写一个递归函数f1(n:Int),然后获取第n项的数字
 * 写递归函数的技巧,掌握两要素:
 *    ①找到结束条件:f(0)=1  f(1)=2
 *    ②找到项与项之间的函数关系:f(n)=f(n-1)+f(n-2)
 */
object ScalaDemo12 {
  def f1(n:Int):Int={
    if(n==0) return 0
    if(n==1) 1
    else f1(n-1)+f1(n-2)
  }

  def main(args: Array[String]): Unit = {
    val a =f1(11)
    println(a)
  }

}
object ScalaDemo04 {
  def main(args: Array[String]): Unit = {
    //给定一个数列
    //2 3 4 9 16 81 ...
    //要求编写递归函数f2(n:Int),判断第n项的数字是多少
    //结束条件:f(0)=2  f(1)=3
    //函数关系:f(n)=f(n-2)*f(n-2)
    def f2(n:Int):Int={
      if(n==0)return 2
      if(n==1) 3
      else f2(n-2)*f2(n-2)
    }
    f2(7)
    println(f2(7))


    //2 3 4 9 8 27 16 81 ...
    //0 1 2 3 4 5  6  7
    //要求编写递归函数f3(n:Int),判断第n项的数字是多少
    //结束条件: f(0)=2  f(1)=3
    //函数关系: 如果n是偶数项, f(n)=2*f(n-2)
    //如果n是偶数项,f(n)=3*f(n-2)

    def f3(n:Int):Int={
      if(n==0)return 2
      if(n==1)return 3
      if(n%2==0) 2*f3(n-2)
      else 3*f3(n-2)
    }
    println(f3(9))


    //编写递归函数计算x的n次方,其中n是整数,要考虑等n是0,正偶数,正奇数,负数这几种情况。
    //比如当x=2时,此函数要算出 2^4,2^3,2^0,2^(-1)对应的值
    //mi(x:Int,n:Int):Double={    }
    //mi(2,10)=1024
    //mi(2,-1)=0.5
    def mi(x:Double,n:Int):Double={
      if(n==0)return 1
      else if(n>0&&n%2==0) mi(x,n/2)*mi(x,n/2)
      else if(n>0&&n%2==1) x*mi(x,n-1)
      else 1/mi(x,-n)
    }
    println(mi(2,-1))

  }
}

四、柯里化Currying

柯里化(Currying)技术 Christopher Strachey 以逻辑学家 Haskell Curry 命名的(尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的)。它是把接受多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术。

示例一:

object ScalaDemo01 {
  def main(args: Array[String]): Unit = {
    //首先我们定义一个函数:
    def f1(a:Int,b:Int)={a+b}
    //现在我们把这个函数变一下形:
    def f2(a:Int)(b:Int)={a+b}
    //那么我们应用的时候,应该这样使用:f2(2)(3),最后结果一样都是5,这种方式就叫柯里化
    val r1 =f2(2)(3)
    /**
     * 柯里化实质上会演变成这样一个函数:
     * 接受一个参数a,返回一个匿名函数,
     * 该匿名函数又接收一个参数b,函数体为a+b
     */
    // 延迟处理思想
    def f3(a:Int)=(b:Int)=>a+b
    val f4=f3(2)
    f4(3)   //答案为5
    println(f4(3))
  }

}

示例二:

object ScalaDemo02 {
  def f1(a:Int,b:Int,c:Int)={a+b+c}
  def f2(a:Int)(b:Int)(c:Int)={a+b+c}
  def f3(a:Int)(b:Int,c:Int)={a+b+c}
  def f4(a:Int,b:Int)(c:Int)={a+b+c}

  def main(args: Array[String]): Unit = {
    // f1和f2的函数的体现的是传入三个数,马上的到结果
    f1(1,2,3)
    f2(1)(2)(3)
    // f3函数则可以体现:延迟执行的思想以及固定易变因素的思想
    val r1=f3(1)(2,3)
    println(r1)
  }
}

案例三:

object ScalaDemo03 {
  def f1(a:Int,b:Int,f:(Int,Int)=>Int)={f(a,b)}
  def f2(a:Int)(b:Int)(f:(Int,Int)=>Int)={f(a,b)}
  def f3(a:Int,b:Int)(f:(Int,Int)=>Int)={f(a,b)}
  def f4(a:Int)(b:Int,f:(Int,Int)=>Int)={f(a,b)}

  def main(args: Array[String]): Unit = {
    //调用f3
    f3(2,3){(a,b)=>a+b}
    //可简化,我们发现这和scala中很多函数的形式很相近,比如:for(x<-1 to 10){print(_)}
    //所以,柯里化的另外一个作用是让用户灵活的自义定自建控制结构
    f3(2,3){_+_}
    f3(2,3){_*_}
  }
}

五、Scala的类

1、概述

1)scala中的类和java中基本类似。

2)scala中的类同样通过class来进行声明

3)scala中的类同样可以具有成员变量和成员方法

4)scala中的类同样可以通过new关键字来创建出对象

2、创建类

成员属性和成员方法默认都是public的 需要私有可以写private 需要保护可以写protected

class Person {
  //创建一个类,并定义类里的两个成员变量name和age。以及一个成员方法 eat()
  //需要注意的是:scala中变量(var)声明时需要指定初始值,
  var name:String="";
  var age:Int=0;
  def eat(): Unit ={
    println("吃饭")
  }

}

当成员变量或成员方法是私有属性时,外部将不能直接访问,这个同java一样

 2、类的构造

和java不同,scala中的类不需要明确声明一个构造器,而是直接将构造参数通过构造参数列表声明为类的一部分。

而直接写在类的体中的既不是类的成员变量也不是成员函数的部分,会自动收集为构造函数的体。

object ScalaDemo06 {
  //scala中的类不需要明确声明一个构造器,
  //而是直接将构造参数通过构造参数列表声明为类的一部分
  class Person(var1:String,var2:Int){
    var name=var1;
    var age=var2;

    //而直接写在类的体中的既不是类的成员变量也不是成员函数的部分,
    //会自动收集为构造函数的体。
    println("……")
    println("***")
  }

  def main(args: Array[String]): Unit = {
    //当调用构造方法时,会打印 …… 和***
    val p=new Person("tom",23)
  }
}

3、辅助构造器

有些时候 一个类里需要多个构造器。scala里主构造器之外的构造器被称为辅助构造器

1)Scala的辅助构造器定义开始于 def this()

2)Scala里每个辅助构造器的第一个动作都是调用同类的构造器。

object ScalaDemo07 {
  //scala支持辅助构造器,
  class Person(var1:String,var2:Int){
    var name=var1;
    var age=var2;

    //Scala的辅助构造器定义开始于 def this() 
    //Scala里每个辅助构造器的第一个动作都是调用同类的构造器。
    def this(var1:String){
      this(var1:String,0)
    }
    //Scala的辅助构造器定义开始于 def this() 
    def this(var2:Int){
      this("",var2:Int)
    }

  }
  def main(args: Array[String]): Unit = {
    val p1=new Person("tom")
    val p2=new Person(23)

  }

}

示例:

class Person(v1:String,v2:Int){
    private var name=v1
    private var age=v2
    def this(v1:String){
      this(v1:String,0)
    }
    def this(v2:Int){
      this("",v2)
    }
    def setName(name:String)={this.name=name}
    def getName()={name}
    def setAge(age:Int)={this.age=age}
    def getAge()={age}
    def eat()={println("eat")}
    def say()={println("say")}
    println("&&&&&&")
}

4、单例对象

1、scala中的类(class)不能定义静态成员(或静态方法),取而代之的是定义单例对象

2、单例对象需要通过Object关键字声明

3、一个单例对象可以单独存在,也可以绑定到一个类上,绑定时object和class必须在同一个文件,名字一致

4、单例对象当中的所有方法,都可以不需要创建对象而直接通过object单例对象的名字直接来调用,用起来感觉像一个静态方法一样

5、一个单例对象和某个类写在同一个源文件中且共享同一个名字时,他们就产生了一个绑定的关系

6、此时单例对象称为该类的伴生对象。类称为该对象的伴生类

7、类和它的伴生对象可以互相访问其私有成员

8、伴生的方式可以就可以为类增加静态成员

9、单例对象不能new,因此没有构造参数

10、可以把单例对象当作java中可能会用到的静态方法工具类

11、比如作为程序入口的main方法必须是静态的,所以main方法必须处在一个单例对象中,而不能写在一个类中

12、单例对象在第一次被访问的时候才会初始化

//类Person
class Person{
  //此私有变量,伴生类可以访问
  private val namespace="1706"
}

//Person的单利对象,也即伴生类Person,
object Person{
  def showTime(){
    println(System.currentTimeMillis())
  }
  def shownamespace(){
    val p=new Person
    //可以访问类的私有变量
    println(p.namespace)
  }  
}
 def main(args: Array[String]): Unit = {
    Person.showTime();
    Person.shownamespace()
  }

5、重写与重载

1)重写是指覆盖父类中的方法来在子类中做其他事项,scala支持继承

override def 父类方法名 参数列表 返回值 方法体

2)重载是指在同一个类中提供方法名相同但是参数不同的方法和java中基本一致

3)在重写的时候,如果是一个普通方法,则需要加override关键字,如果是一个抽象方法,则不需要加。

 重写eat()方法:

6、final的使用

可以用在成员变量、成员方法、类本身上

作用和java中相同

7、抽象类

scala中同样支持抽象类的使用,抽象类的内部可以包含抽象方法和非抽象方法。

抽象类不允许被实例化,抽象类主要是用来被继承的

 抽象方法没有方法体

8、多态

多态:动态绑定,指"在执行期间判断所引用对象的实际类型,根据其实际类型调用其相应的方法"

object Demo13{
  def main(args: Array[String]): Unit = {
    val t1:Teacher=new TeacherChen("chen",32)
    val t2:Teacher=new TeacherLiu("liu",32)
  }
}

9、特质trait

1)可以类比java中的接口,但是又和接口非常不一样,特质相当于java中的接口,java中称为类实现了接口,scala中称为混入了特质。

2)和java中的接口不同的是,scala中的特质可以包含具有方法体的方法。

和抽象类不同的地方在于,scala的类只能单继承,但是可以多混入,利用这种方式可以实现类似c语言中多继承的特性。

3)在类中可以通过extends 或 with 关键字来让类混入特质,如果类没有明确继承父类,extends关键字没有被占用就可以使用extends。

4)但是如已经使用了extends显示的继承了父类,再向混入特质就要用with关键字了。

一个类的声明中只能有一个 extends,但是可以有多个with

 

 

10、泛型

基本和java中相同,不同的是,泛型是用方括号引起来的,java用<>

val arr = Array[String]();      

11、lazy懒值

正常情况下通过val 和 var定义的量都会直接分配空间,即使这个量要在很久以后才使用,这样就会造成内存空间白白被占用。

这种情况下可以加上lazy关键字,延后变量/常量赋值的位置。这样直到后续真正用到这个量时才真正开辟空间赋值,减少了内存的浪费。

object ScalaDemo10 {
  def main(args: Array[String]): Unit = {
    val name = "zhang"//直接分配空间 即使一时半会用不到
    lazy val name1 = "zhang"//并不会立即分配空间 直到后续用到了才会分配
  }

}

12、caseclass-样例类

1)只要在声明类时 在class关键字前加上case关键字 这个类就成为了样例类

样例类必须要显式的声明一个主构造器

2)当样例类声明一个主构造器后,会默认隐式的声明一个空构造器

3)样例类默认实现序列化接口,如果是普通类要继承 Serializable

4)样例类默认自动覆盖 toString 方法,便于打印测试

5)样例类不需要new可以直接生成对象

\

13、option类型

在Scala中用来表示一个结果,它可能有值,也可能没有值,它有两个子Option

通过getOrElse(默认值)来操作Option,如果返回None,则返回值为默认值

object ScalaDemo12 {
  def f1(a:Int,b:Int):Option[Int]={
    if(b!=0){
      Some(a/b)
    }else{
      None
    }
  }

  def main(args: Array[String]): Unit = {
    //表示如果有正确结果,返回正确结果,没有则返回指定的默认值
    val Result=f1(4,2).getOrElse(0)
    println(Result)
  }

}

六、scala隐式转换机制

1、概述

scala implicit关键字详解(隐式转换函数、隐式类、隐式参数、隐式值),implicit是scala中的一个关键字,关于它有着丰富的用法,使得scala更灵活和容易扩展

2、隐式转换函数

implicit def int2str(x:Int):String = x.toString

这段代码声明了一个函数int2str,它与正常函数唯一的区别在于前面多出的implicit关键字。这里的implicit就是它字面的含义——隐式,它告诉编译器,这个函数是一个隐式转换函数,能够把Int类型的值转换成String类型的值。

这种隐式转换的意义在于,如果在进行一个对Int类型的操作时不合法,编译器会在当前作用域寻找合适的隐式转换,来尝试使这种操作合法。

需要注意:

1.对于隐式转换函数,编译器最关心的是它的类型签名,即它将哪一种类型转换到另一种类型,也就是说它应该接受只一个参数,对于接受多参数的隐式函数来说就没有隐式转换的功能了。

implicit def int2str(x:Int):String = x.toString // 正确

implicit def int2str(x:Int,y:Int):String = x.toString // 错误

2.不能存在二义性,即同一个作用域不能定义两个相同类型的隐式转换函数,这样编译器将无法决定使用哪个转换

implicit def int2str(x:Int):String = x.toString

implicit def anotherInt2str(x:Int):A = x.toString

object ScalaDemo13 {
  val v1=100
  //一种方式:做显式的类型转换,形式固定,to指定类型
  val v2:String=v1.toString()
  //另一种方式:做隐式的类型转换
  //可以定义一个隐式的转换函数,完成隐式的类型转换
  //implicit 关键字,用于定义隐式方法,以及隐式类和隐式参数
  implicit def f1(x:Int)={x.toString()}
  val v3:String=v1

}

3、隐式类

scala也支持隐式类的声明,在隐式类中定义的方法都是隐式转换方法。如下:

4、隐式参数

在当前作用域里存在一个Adder[Int]类型的隐式值implicit val a

在调用f2时,编译器可以找到implicit标记过的a,所以我们不必传递隐式参数而是直接调用f2(2,3)。而如果你想要传递隐式参数的话,你也可以自定义一个传给它

trait Adder[T]{
    def add(x:T,y:T):T
  }
  def f1(x:Int,y:Int)(implicit adder: Adder[Int]): Unit ={
    adder.add(x,y)
  }
  implicit val a= new Adder[Int] {
    def add(x:Int,y:Int)={x+y}
  }
  f1(2,3)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

是小先生

知识是无价的,白嫖也可以的。

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

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

打赏作者

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

抵扣说明:

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

余额充值