Scala--函数式编程

本文介绍了Scala中的各种集合类型,包括Array、Set、Tuple、Map等,并详细讲解了可变与不可变集合的区别及使用方法。此外,还探讨了函数式编程的基本概念,如函数定义、高阶函数、柯里化函数等,并提供了自定义实现Map、reduce等算子的示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Scala–Collections

  • 在介绍函数式编程之前需要介绍下 Scala 的集合,以及 可变集合(mutable)、不可变集合(immutable)
  • Scala 中的迭代的集合包括:Seq、Set、Map

Scala 集合

  • 集合中的可变集合(mutable):可以修改集合长度的集合。

Scala--mutable

  • 集合中的不变集合(immutable):不可以修改集合长度的集合,集合的长度是一开始初始化就确定好的。

Scala--immutable

1. Array

1.1 不变数组–Array
  • 定义:1) Array( 数 据 内 容 ) 2 ) n e w A r r a y [ 数据内容) 2) new Array [ )2newArray[数据类型] ($数据条数)
  • 更新数据: 数 组 对 象 . u p d a t e ( 数组对象.update( .update(索引,$数据)
  • Array转成一个String: 数 组 对 象 . m k S t r i n g ( 数组对象.mkString( .mkString(分隔符)
  • 两个数组相加:$数组对象1 ++ $数组对象2
  • union: 数 组 1. u n i o n ( 数组1.union( 1.union(集合2)
  • 取第一个数据:$数组.head
  • 数组的长度:
    • $数组.length
    • $数组.size
  • 数组中的最大、最小、总和: 数 组 . m a x 、 数组.max、 .max数组.min、$数组.sum
  • 数组反向:$数组.reverse
1.2 可变数组–ArrayBuffer
  • 定义:cala.collection.mutable.ArrayBuffer [$数据类型] ()
  • 插入数据:1) 数 组 . i n s e r t ( 数组.insert( .insert(索引, 数 据 ) 2 ) 数据) 2) )2数组 += 数 据 3 ) 数据 3) 3数组 ++= Array($数据)
  • ArrayBuffer 转换成 Array:$可变数组.toArray
  • 其他的都和不变数据使用差不多
package com.xk.bigdata.scala.collection

object ArrayApp {

  def main(args: Array[String]): Unit = {
    val array1 = new Array[String](3)
    val array2 = Array("hadoop2", "spark2", "flink2")
    // 更新数据
    array1.update(0, "hadoop1")
    array1.update(1, "spark1")
    array1.update(2, "flink1")
    // mkString
    println(array1.mkString(","))
    // ++ array 相加
    (array1 ++ array2).foreach(println)
    array1.foreach(println)
    // union
    array1.union(array2).foreach(println)
    // 取第一个数据
    println(array1.head)
    // 数组的长度
    println(array1.length)
    println(array1.size)
    // 数组中的最大、最小、总和
    val array3 = Array(1, 2, 3, 4, 5, 6)
    println(array3.max)
    println(array3.min)
    println(array3.sum)
    // 数组反向
    array1.reverse.foreach(println)

    val arrayBuffer = scala.collection.mutable.ArrayBuffer[String]("hadoop", "spark", "flink")
    arrayBuffer.insert(0, "hbase")
    arrayBuffer += "spark1"
    arrayBuffer ++= Array("hadoop1")
    arrayBuffer.update(0, "hbase1")
    println(arrayBuffer.toArray)
    println(arrayBuffer)
  }

}

2. Set

  • Set 特点就是:1)无序 2)去重
  • head 返回集合第一个元素
  • tail 返回一个集合,包含除了第一元素之外的其他元素
  • isEmpty 在集合为空时返回true
  • 使用 Set.& 方法或 Set.intersect 方法来查看两个集合的交集元素
2.1 不变集合-Set
  • 定义:1)Set($数据)
  • 其他都和Array差不多
2.2 可变集合-Set
  • 定义:scala.collection.mutable.Set($数据)
  • 添加数据:1) 集 合 . a d d ( 集合.add( .add(数据) 2)$集合 += 数 据 3 ) 数据 3) 3集合 ++= Set($数据)
  • 删除数据:1) 集 合 . r e m o v e ( 集合.remove( .remove(数据) 2)$集合 -= 数 据 3 ) 数据 3) 3集合 --= Set(数据)
package com.xk.bigdata.scala.collection

object SetApp {

  def main(args: Array[String]): Unit = {

    val set1 = Set("spark", "spark", "spark1")
    val set2 = Seq(1, 23, 4, 64, 34)
    // 取最大、最下、总和数据
    println(set2.max)
    println(set2.min)
    println(set2.sum)

    val set3 = scala.collection.mutable.Set(1, 23, 34)
    // 添加数据
    set3.add(2)
    set3 += 4
    set3 ++= Set(3, 5, 7)
    // 删除数据
    set3.remove(1)
    set3 -= 2
    set3 --= Set(3, 5, 7)
    println(set3)
    // head
    println(set3.head)
    // tail
    println(set3.tail)
    // 判断是否为空
    println(set3.isEmpty)
    // 两个集合交集
    val set4 = Set(1,2,3,4)
    println(set3.intersect(set4))
    println(set3.&(set4))
  }

}

3. Tuple

  • 元组:目前 Scala 支持的元组最大长度为 22。对于更大长度你可以使用集合,或者扩展元组。
  • 定义:1) ( 数 据 ) 2 ) 可 以 根 据 T u p l e 长 度 来 进 行 创 建 , 如 果 需 要 创 建 一 个 两 个 长 度 的 T u p l e , 则 T u p l e 2 ( 数据) 2)可以根据Tuple长度来进行创建,如果需要创建一个两个长度的Tuple,则 Tuple2( )2TupleTuple,Tuple2(数据1, 数 据 2 ) 3 ) 数据2) 3) 2)3数据1 -> $数据2
  • 得到里面的数据:KaTeX parse error: Expected group after '_' at position 9: Tuple对象._̲索引(从1开始)
  • Tuple 迭代
    • 通过每个元素的索引得到数据
    • 通过把 Tuple 转换成迭代器
package com.xk.bigdata.scala.collection

object TupleApp {

  def main(args: Array[String]): Unit = {

    val tuple1 = (1, 2, "spark", "hadoop")
    val tuple2 = Tuple2(1, "spark")
    // 得到 Tuple 里面的第一条数据
    println(tuple1._1)
    val tuple3 = 0 -> "zero"
    println(tuple3)
    // Tuple 迭代
    // 1. 通过每个元素的索引得到数据
    for (i <- 0 until (tuple3.productArity)) {
      println(tuple3.productElement(i))
    }
    // 2. 通过把 Tuple 转换成迭代器
    for (els <- tuple3.productIterator) {
      println(els)
    }
  }

}

4. Map

  • Map是一种可迭代的键值对(key/value)结构。
  • 所有的值都可以通过键来获取。
  • Map 中的键都是唯一的。
  • 我们经常使用:HashMap、SortedMap
4.1 不变集合-Map
  • 定义:Map($key -> $value)
  • 得到Map里面数据: M a p 对 象 . g e t O r E l s e ( Map对象.getOrElse( Map.getOrElse(Key, $默认值(如果没有这个Key,则返回默认值))
  • 得到Map里面的所有的Key:$Map对象.keys
  • 得到Map里面的所有的Value:$Map对象.values
  • 遍历Map:
    • 使用keys
    • 使用keySet
4.2 可变集合-Map
  • 定义:scala.collection.mutable.Map [$Key数据类型, $Value数据类型] ()
  • 插入数据:
    • M a p 对 象 . p u t ( Map对象.put( Map.put(Key,$Value)
    • $Map对象 += $Key -> $Value
    • M a p 对 象 + + = M a p ( Map对象 ++= Map( Map++=Map(Key -> $Value)
package com.xk.bigdata.scala.collection

object MapApp {

  def main(args: Array[String]): Unit = {

    val map1 = Map("hadoop" -> 21, "spark" -> 22)
    // 得到 Map 里面的数据
    println(map1.getOrElse("hadoop", 0))
    println(map1.getOrElse("hadoop1", 0))
    // 得到 Map 里面的 所有的key
    map1.keys.foreach(println)
    // 得到Map里面所有的Value
    map1.values.foreach(println)
    // 遍历Map
    for (key <- map1.keys) {
      println(s"key==============>$key")
      val value = map1.getOrElse(key, 0)
      println(s"value=============>$value")
    }
    for (key <- map1.keySet) {
      println(s"key==============>$key")
      val value = map1.getOrElse(key, 0)
      println(s"value=============>$value")
    }

    val map2 = scala.collection.mutable.Map[String, Int]()
    // 插入数据
    map2.put("hbase", 20)
    map2 += "flink" -> 21
    map2 ++= Map("kafka" -> 25)
    println(map2)
  }

}

5. Queue(队列)

  • 队列实现了一种数据结构, 该结构允许以先进先出(FIFO)的方式插入和检索元素。
  • 添加元素: 队 列 对 象 . e n q u e u e ( 队列对象.enqueue( .enqueue(队列数据…)
  • 取出数据:$队列对象.dequeue(),取出队列中的第一条数据,先进先出
  • 取出第一条数据:$队列对象.head,第一条数据不会被取出
  • 取出除第一条以外的数据:$队列对象.tail
  • 队列同样分成可变队列以及不可变队列
5.1 不可变队列
  • 定义:scala.collection.immutable.Queue [ 数 据 类 型 ] ( 数据类型] ( ](队列数据)
5.2 可变队列
  • 定义:new mutable.Queue [$数据类型] ()
package com.xk.bigdata.scala.collection

import scala.collection.mutable

object QueueApp {

  def main(args: Array[String]): Unit = {

    val queue1 = scala.collection.immutable.Queue[Int](2, 3, 4, 5, 5)
    println(queue1)
    val queue2 = new mutable.Queue[Int]()
    // 添加元素
    queue2.enqueue(2, 3, 4, 5, 6)
    println(queue2)
    // 取出数据
    queue2.dequeue()
    println(queue2)
    // 取第一个元素
    queue2.head
    println(queue2)
    // 取出除第一条以外的数据
    println(queue2.tail)
  }

}

6. List

  • Scala 列表类似于数组,它们所有元素的类型都相同,但是它们也有所不同:列表是不可变的,值一旦被定义了就不能改变,其次列表 具有递归的结构(也就是链接表结构)而数组不是。
  • Nil 为一个空列表
  • head 返回列表第一个元素
  • tail 返回一个列表,包含除了第一元素之外的其他元素
  • isEmpty 在列表为空时返回true
  • 除最后一个元素以外的其他元素:init
  • List.reverse 用于将列表的顺序反转
  • 删除列表数据:drop
  • 遍历
    • 直接使用加强的 For 循环
    • 把 List 转换成可迭代,然后进行迭代
6.1 不可变列表
  • 定义:List($列表数据)
6.2 可变列表-ListBuffer
  • 定义:scala.collection.mutable.ListBuffer [$数据类型]
  • 添加数据:
    • $List对象 += $单个列表数据
    • $List对象 ++= $列表数据
package com.xk.bigdata.scala.collection

object ListApp {

  def main(args: Array[String]): Unit = {

    val list1 = List("spark", "hadoop", "flink", "kafka")
    // 返回列表第一个元素
    println(list1.head)
    // 返回一个列表,包含除了第一元素之外的其他元素
    println(list1.tail)
    // 判断是否为空
    println(list1.isEmpty)
    // 除最后一个元素以外的其他元素
    println(list1.init)
    // 用于将列表的顺序反转
    println(list1.reverse)
    // 删除列表数据
    println(list1.drop(1))
    // 直接使用加强的 For 循环
    for (els <- list1) {
      println(els)
    }
    // 把 List 转换成可迭代,然后进行迭代
    val iterator = list1.iterator
    while (iterator.hasNext) {
      println(iterator.next())
    }

    val listBuffer = scala.collection.mutable.ListBuffer[String]()
    listBuffer += "hadoop"
    listBuffer ++= List("spark", "flink")
    println(listBuffer)
  }

}

7. 练习

  • 使用之前学习的知识,使用递归方法实现一个累加功能。
  • 使用递归方法的时候,递归方法必须手动定义返回值类型。
package com.xk.bigdata.scala.collection

object RecursAddApp {

  def main(args: Array[String]): Unit = {
    println(sum(1, 2, 3, 4, 5))
  }

  def sum(nums: Int*): Int = {
    if (nums.length == 0) {
      0
    } else {
      nums.head + sum(nums.tail: _*)
    }
  }

}

8. 函数式编程

8.1 函数和方法
  • 函数和方法的区别:
    • 方法不能作为单独的表达式而存在(参数为空的方法除外),而函数可以。
    • 函数必须要有参数列表,而方法可以没有参数列表。
    • 方法名是方法调用,而函数名只是代表函数对象本身。
    • 在需要函数的地方,如果传递一个方法,会自动进行ETA展开(把方法转换为函数)。
    • 传名参数本质上是个方法。
  • 函数的定义:
    • val/var 函数名称 = (输入参数列表) => {函数体}
    • val/var 函数名称:(输入参数类型) => 返回值类型 = (输入参数的引用) => {函数体}
  • 高阶函数:参数为函数的函数。
  • 常用的高阶函数:
    • map
    • reduce
    • filter
    • foreach
  • 柯里化函数:指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数。
package com.xk.bigdata.scala.function

object FunctionBasic {

  def main(args: Array[String]): Unit = {

    // 定义一个函数
    // 定义方式一: val/var 函数名称 = (输入参数列表) => {函数体}
    val f1 = (a: Int, b: Int) => a + b
    println(f1(1, 2))

    // 定义一个相加的方法
    def add(a: Int, b: Int): Int = a + b

    // 把方法赋予给一个变量
    val addFun = add _
    println(addFun(2, 4))
    // 定义方式二: val/var 函数名称:(输入参数类型) => 返回值类型 = (输入参数的引用) => {函数体}
    val f2: (Int, Int) => Int = add
    println(f2(4, 5))

    // 高阶函数
    def f3(op: ((Int, Int) => Int)) = {
      op(2, 3)
    }

    println(f3((a, b) => {
      a + b
    }))

    // 柯里化函数
    def f4(a: Int, b: Int)(op: ((Int, Int) => Int)) = {
      op(a, b)
    }

    println(f4(4, 20)((a, b) => {
      a + b
    }))

  }

}
8.2 自定义实现Map
  • map 的作用会把集合里面的所有元素,全部做同样的操作,返回一个集合。
package com.xk.bigdata.scala.function

/**
 * 自定义实现常用函数 :Map
 * 使用 map 算子把 array 中的数据全部 * 2
 */
object CustomMapFun {

  def customMap(array: Array[Int])(op: (Int) => Int): Array[Int] = {
    for (els <- array) yield op(els)
  }

  def main(args: Array[String]): Unit = {
    val array = Array(1, 2, 3, 4, 5)
    // 使用 map 算子把 array 中的数据全部 * 2
    array.map(_ * 2).foreach(println)
    customMap(array)(_ * 2).foreach(println)
  }
}
8.3 自定义实现reduce
  • reduce 是把集合中两两相近的元素进行同一个操作,最终计算出一个结果。
package com.xk.bigdata.scala.function

/**
 * 自定义实现常用函数 :reduce
 * 使用 reduce 把 array 中的数据全部相加一起
 */
object CustomReduceFun {

  def customReduce(array: Array[Int])(op: (Int, Int) => Int): Int = {
    var last = array(0)
    for (i <- 1 until (array.length)) {
      last = op(last, array(i))
    }
    last
  }

  def main(args: Array[String]): Unit = {
    val array = Array(1, 2, 3, 4, 5)
    // 使用 reduce 把 array 中的数据全部相加一起
    println(array.reduce((a, b) => {
      println(s"a:${a}=====>b:$b")
      a + b
    }))
    println(customReduce(array)((a, b) => {
      println(s"a:${a}=====>b:$b")
      a + b
    }))
  }

}
8.4 自定义实现filter
  • filter 是把集合中不合符条件的数据去除
package com.xk.bigdata.scala.function

/**
 * 自定义实现常用函数 :filter
 * 使用 filter 算子把 array 中大于3的数据全部取出来
 */
object CustomFilterFun {

  def customFilter(array: Array[Int])(op: (Int) => Boolean): Array[Int] = {
    for (els <- array if op(els)) yield els
  }

  def main(args: Array[String]): Unit = {
    val array = Array(1, 2, 3, 4, 5)
    // 使用 filter 算子把 array 中大于3的数据全部取出来
    array.filter(_ > 3).foreach(println)
    customFilter(array)(_ > 3).foreach(println)
  }

}
8.5 自定义实现foreach
  • foreach 是对集合中每个元素做同一个操作,返回值为 Unit
package com.xk.bigdata.scala.function

/**
 * 自定义实现常用函数 :foreach
 * 使用 foreach 算子把 array 中的数据全部打印出来
 */
object CustomForeachFun {

  def customForeach(array: Array[Int])(op: (Int) => Unit): Unit = {
    for (els <- array) op(els)
  }

  def main(args: Array[String]): Unit = {
    val array = Array(1, 2, 3, 4, 5)
    // 使用 foreach 算子把 array 中的数据全部打印出来
    array.foreach(println)
    customForeach(array)(println)
  }

}
8.6 其他常用算子的使用
  • flatten:压平
  • flatMap:压平+ Map,可以理解等于 flatten 算子 + map 算子
  • groupBy:按照某个字段分组
  • mapValues:对Map里所有的value施加一个map函数,返回一个新的Map
  • sortBy:按照某个字段排序
  • take:得到前几条数据
package com.xk.bigdata.scala.function

object OtherFun {

  def main(args: Array[String]): Unit = {

    val array = Array("spark,hbase", "spark,hadoop", "spark,hadoop", "kafka")
    // flatten : 压平函数
    array.flatten.foreach(println)
    // flatMap 算子 : 需要把 array 里面的数据按照 逗号 分隔符分割
    // 使用 flatten + map 算子
    array.map(_.split(",")).flatten.foreach(println)
    // 使用 flatMap 算子
    val words = array.flatMap(_.split(","))
    // groupBy 按照某个字段分组: 把上面压扁的函数按照 key 进行排序
    // scala.collection.immutable.Map[String,Array[(String, Int)]] = Map(hadoop -> Array((hadoop,1)), spark -> Array((spark,1), (spark,1)), hbase -> Array((hbase,1)))
    val groupBy = words.map((_, 1)).groupBy(_._1)
    // mapValues 对 value 进行操作 : 计算出 value 中的所有数据数量
    // sortBy 按照 value 进行从大到小排序
    // take: 得到前两条数据
    groupBy.mapValues(_.size).toArray.sortBy(-_._2).take(2).foreach(println)
  }

}

9. 使用 Scala 实现 WC

  • 数据
hadoop,spark,hadoop,flink
spark,hbase,kafka
hadoop
package com.xk.bigdata.scala.wc

import scala.io.Source

object WcApp {

  def main(args: Array[String]): Unit = {
    val list = Source.fromFile("data/wc.txt").getLines().toList
    list.flatMap(_.split(","))
      .map((_,1))
      .groupBy(_._1)
      .mapValues(_.size)
      .foreach(println)
  }

}

10. 模式匹配

  • 如果代码中有多个 if else if 的时候,Java 中可以使用 switch case 来解决,Scala 模式匹配对应了 Java 中的 switch case。
  • 在模式匹配最后一个匹配项写 case _ 是指的不满足以上条件的,执行后面的代码。
  • 在 Scala 中模式匹配最常用的场景就是在捕获异常的时候。
package com.xk.bigdata.scala.pattern

import scala.io.Source

object PatternApp {

  def main(args: Array[String]): Unit = {

    // 普通模式匹配
    judgeGrade1("A")
    judgeGrade1("B")
    judgeGrade1("E")

    // 模式匹配中增加 if 条件
    judgeGrade2("A", "hadoop")
    judgeGrade2("E", "spark")

    // 模式匹配中匹配数组里面的数据
    judgeFriend1(Array("spark"))
    judgeFriend1(Array("spark", "hadoop"))
    judgeFriend1(Array("flink", "hadoop"))
    judgeFriend1(Array("flink", "hadoop", "spark"))

    // 模式匹配 List 里面的数据
    judgeFriend2(List("spark"))
    judgeFriend2(List("spark", "hadoop"))
    judgeFriend2(List("flink", "hadoop"))
    judgeFriend2(List("flink", "hadoop", "spark"))

    // 模式匹配数据类型
    judgeDataType("data")
    judgeDataType(1)
    judgeDataType(Map("spark" -> 21))
    judgeDataType(List("Spark"))
    judgeDataType(1L)

    // 模式匹配判断 class
    judgeClass(Person("spark"))
    judgeClass(Student(21))
    judgeClass(1)

    // 模式匹配在 try catch 中的使用
    tryCatch()

  }

  /**
   * 普通模式匹配
   */
  def judgeGrade1(grade: String): Unit = {
    grade match {
      case "A" => println("很好!!")
      case "B" => println("还不错!!")
      case "C" => println("需要加油了!!")
      case _ => println("其他分数!!")
    }
  }

  /**
   * 模式匹配中增加 if 条件
   */
  def judgeGrade2(grade: String, name: String): Unit = {
    grade match {
      case "A" => println("很好!!")
      case "B" => println("还不错!!")
      case _ if (name == "spark") => println(s"$name 还可以!!")
      case _ => println("其他的!!")
    }
  }

  /**
   * 模式匹配中匹配数组里面的数据
   */
  def judgeFriend1(array: Array[String]): Unit = {
    array match {
      case Array("spark") => println("Hi spark")
      case Array("spark", _*) => println("Hi spark and friends")
      case Array(x, y) => println(s"Hi ,$x==>$y")
      case _ => println("大家好")
    }
  }

  /**
   * 模式匹配 List 里面的数据
   */
  def judgeFriend2(list: List[String]): Unit = {
    list match {
      case "spark" :: Nil => println("Hi spark")
      case "spark" :: tail => println("Hi spark and friends")
      case List(x, y) => println(s"Hi ,$x==>$y")
      case _ => println("大家好")
    }
  }

  /**
   * 模式匹配数据类型
   */
  def judgeDataType(data: Any): Unit = {
    data match {
      case i: Int => println("Int")
      case s: String => println("String")
      case map: Map[_, _] => println("map")
      case list: List[_] => println("list")
      case _ => println("其他类型")
    }
  }

  def judgeClass(obj: Any): Unit = {
    obj match {
      case Person(name) => println(s"Person=======>$name")
      case Student(age) => println(s"Student=========>$age")
      case _ => println("other...")
    }
  }

  case class Person(name: String)

  case class Student(age: Int)

  /**
   * try catch
   */
  def tryCatch(): Unit = {
    try {
      1 / 0
      Source.fromFile("data/demo.txt")
    } catch {
      case e: ArithmeticException => println(s"计算异常:${e.getMessage}")
      case e: Exception => println(s"${e.getMessage}")
    }

  }
}

11. 偏函数

  • 偏函数:被包在花括号里面的没有match的一组case语句
  • 定义:PartialFunction[-A, +B]
    • A:代表的输入参数类型
    • B:代表的输出参数类型
package com.xk.bigdata.scala.pattern

object PartitalFunctionApp {

  def main(args: Array[String]): Unit = {

    println(judgeGrade("A"))
    println(judgeGrade("B"))
    println(judgeGrade("E"))
    
    // 需求:对集合中的数值类型 * 10
    // 第一步:判断集合里面的数据是否是 Int 类型
    // 第二步:把数据转换成 Int 类型
    // 第三步:把数据 * 10
    val list = List(1, 2, 3, 4, 5, "hadoop", "spark")
    // 第一个版本
    val pair1 = new PartialFunction[Any, Int] {
      override def isDefinedAt(x: Any): Boolean = {
        x.isInstanceOf[Int]
      }

      override def apply(v1: Any): Int = {
        v1.asInstanceOf[Int] * 10
      }
    }
    list.collect(pair1).foreach(println)

    // 第二个版本
    val pair2: PartialFunction[Any, Int] = {
      case i: Int => i * 10
    }
    list.collect(pair2).foreach(println)

    // 第三版本
    list.collect({
      case i: Int => i * 10
    }).foreach(println)

    // 第四版本
    list.collect {
      case i: Int => i * 10
    }.foreach(println)
  }

  def judgeGrade: PartialFunction[String, String] = {
    case "A" => "很好!!"
    case "B" => "还不错!!"
    case "C" => "需要加油了!!"
    case _ => "其他分数!!"
  }
  
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值