Scala基础笔记

1.课程目标

  • 安装Scala编译和运行环境
  • 熟悉Scala基本语法以及函数式编程
  • 熟练掌握Scala数据结构使用以及集合方法操作

2.Scala基础

2.1Scala概述

  • 概念:Scala是既可以面向对象也可以面向函数的编程语言,多范式的编程语言。Scala是基于JVM的编程语言。Scala能够兼容Java的语法
    • 面向对象:Scala是纯面向对象的语言。比如1+2: 1 是一个对象 + 方法 2是对象 方法的参数。在scala的世界中,每一个字符或者每一个值都是对象,每一个操作符方法操作
    • 面向函数:有两个基本点
      • 函数是一个对象。函数和字符或者数值具有同等地位。函数可以赋值给变量、可以作为方法的参数、可以作为方法的返回值。
      • 数学f(x)=>y :函数是输入数据到输出数据的映射。比如f(x)=>x*x 。x在这个函数处理过程中不能够改变。不可变变量 是安全。也是函数式编程的基石。

2.2 Scala语言特点

  • 优雅:Scala编程方式非常贴合程序员的思维。能够通过简单的代码实现复杂的逻辑。并且易读性非常强
  • 表达能力强:Scala中遍历集合一般只需要1-2代码就可以。可以节省50%以上的开发量
  • Scala是Spark、kafka等大数据框架的开发语言,Scala能够融入大数据生态圈
  • Scala是基于JVM的语言,可移植性

3.Scala编译环境和开发环境的安装

官网链接: https://www.scala-lang.org/.

  • 安装步骤

    • 下载(scala-2.11.8) https://www.scala-lang.org/download/all.html
    • mis (能够自动配置环境变量) zip(手动配置环境变量 C:\soft\scala-2.11.8\bin)
    • 验证:scala -version
    • helloword
      • //HelloWord 相当于java中类名
        //object 修饰对象
        object HelloWord{
        def main(args:Array[String]){
        println(“helloword”)
        }
        }
  • 基于IDEA Scala插件安装

    • 插件网址:http://plugins.jetbrains.com/plugin/1347-scala

    • 注意:安装scala插件最好与IDEA的版本一致

    • 安装步骤:

      • config–》plugins(file–》setting–》plugins)–》from disk
    • 创建Scalaproject

      • createproject -》scala–》IDE
    • /**
      	object : 修饰的是一个对象 跟java中class类似
      	HelloWord:名称
      	def:用来定义方法的关键词
      	args: Array[String]:输入参数
      	Unit:返回值类型
      **/
      object HelloWord {
        def main(args: Array[String]): Unit = {
          println("helloWorld")
        }
      }
      

4.Scala基础语法

4.1Scala变量

  • 声明格式

    • val/var 变量名称:数据类型=值
      
      val :修饰的是不可变变量 相当于java中final修饰的变量 (scala中推荐使用这种变量)
      var : 修饰的是可变变量
      
       //val/var 变量名称:数据类型=值
          val name:String="itcast"
          val name2="itcast"
          //name="itcast2"
          var age:Int=20
          var age2=30
          age=30
      
      注意:变量的数据类型可以省略。变量的数据类型由值的数据类型推导而来。scala中变量是强类型
      

4.2 变量的数据类型

  • Scala中数据类型跟Java中数据类型一致

    • 整型:Int Byte Shot Long Char

    • 浮点:Float Double

    • 布尔:Boolean

    • 字符串:String

    • scala中以上的数据类型全是对象 相当于java中包装类型

    • val age:Int=20
      val name:String="itcast"
      //字符串操作技巧
      //插值器
      val nameAge=s"itcast$age"
      val nameAge2=name+age
      println(nameAge)
      
      //原生态字符串
      val str=
        """sfdsdfdsfdsf
         sdfsdfd
                        fsfddsf
                          qw    qw
        """
      println(str)
      val str2=
        """
          |sfdsfd
          |sfsfdsfdsf
          |sfdsfsdfdss
        """.stripMargin
      println(str2)
      

4.3 Scala中操作符

  • 算数运算符 + - * / % 等

  • 比较运算符 > < >= <= ==

  • 逻辑运算符 && || !

  • 位运算 >> <<

  • 以上运算符跟java中运算符功能一致。以上所有的符号都是方法

  • val add=1.+(2)
    val add2=1+2
    println(add)
    

5.Scala中的表达式

5.1 If表达式

  • 语法结构

    • val 变量名称=if(布尔表达式){
          业务逻辑
      }else if(布尔表达式){
          
      }else{
          
      }
      if表达式是具有返回值的
      
      
          val score=90
          val isOk=if(score>=90) "合格" else "不合格"
          val isVar=if(score>0) score else "ERROR"
          val myFeel=if(score>95) "很高兴"
      
          println(isOk)
          println(isVar)
          println(myFeel)
      
          //1.if可以返回不同的数据类型,if返回值变量的类型跟返回值数据类型有关
          //2.如果if没有返回值,缺省的返回“()”
      

5.2 块表达式

  • 概念:使用花括号括起来的表达式并且具有返回值
    格式:val 变量名称={表达式} 
    
      val h = 20
        val w = 30
        val area = {
          h * w
        }
        //周长
        val girth={
          2*(h+w)
        }
        println(area)
        println(girth)
    
    应用场景: 1.方法内部,代码内聚
    		 2.用作初始化
    

5.3 for循环

  • for(变量名称 <- 集合/数组/等){
                循环体
     }
                
       val list = new util.ArrayList[String]()
        list.add("熟悉Hadoop、spark等分布式数据处理工具")
        list.add("熟悉Hadoop,spark,storm等计算平台")
        list.add("熟悉Hadoop、hive、Spark、flink、presto等")
        list.add("熟练掌握Linux Shell、Flume、Kafka、Redis等相关技术")
    
        /**
          * for(变量名称 <- 集合/数组/等){
          * 循环体
          * }
          * 通过角标访问的形式遍历每个元素
          */
         for(index <- 0 to list.size()-1){
           println(list.get(index))
         }
         for(index<-0 until list.size()){
           println(list.get(index))
         }
          // to 是闭区间  包含  0 to 5 
         //until  是开区间  不包含  最后一位
    
  • for(变量名称 <- 集合/数组 if 条件表达式* ){
                循环体
     }
    
     val list = new util.ArrayList[String]()
        list.add("熟悉Hadoop、spark等分布式数据处理工具")
        list.add("熟悉Hadoop,spark,storm等计算平台")
        list.add("熟悉Hadoop、hive、Spark、flink、presto等")
        list.add("熟练掌握Linux Shell、Flume、Kafka、Redis等相关技术")
    
        //遍历数据并且过滤出只包含hadoop单词的数据  并且包含storm
        /**
          * for(变量名称 <- 集合/数组 if 条件表达式* ){
          * 循环体
          * }
          */
        for(index <- 0 until list.size()
            if list.get(index).contains("Hadoop") 
            if list.get(index).contains("storm")
        ){
          println(list.get(index))
        }
    
  • for(变量名称 <- 集合/数组 if 表达式;变量名称 <- 集合/数组 if 表达式){
                循环体
     }
    
     //hadoop 内容列表
        val hadoopList = new util.ArrayList[String]()
        hadoopList.add("mapreduce")
        hadoopList.add("hdfs")
        //java 内容列表
        val javaList = new util.ArrayList[String]()
        javaList.add("JavaSE")
        javaList.add("JavaWeb")
        javaList.add("javaEE")
    
        val map = new util.HashMap[String, util.List[String]]()
        map.put("hadoop", hadoopList)
        map.put("java", javaList)
    
        /**
          * hadoop
          * ----mapreduce
          * ----hdfs
          * java
          * ----JavaSE
          * ----JavaWeb
          * ----javaEE
          * 需求:遍历出所有叶子节点
          *
          * for(变量名称 <- 集合/数组 if 表达式;变量名称 <- 集合/数组 if 表达式){
          * 循环体
          * }
          * 步骤:
          *   1. 拿到map key
          *   2. 循环遍历key值 拿到key对应的list
          */
        val keys=map.keySet().toArray
        for(key<-keys;index<-0 until map.get(key).size()){
          println(map.get(key).get(index))
        }
    问题:key 是val 不变变得。for每次循环的时候都会重新创建key
    注意:凡是省略val或者var的地方,默认的都是val变量
    
  • for循环表达式 可以具有返回值。集合中的每一个元素都会进行业务逻辑处理,最终返回新的集合
    val 集合名称=for(变量<-集合/数组/表达式) yield 业务逻辑
    
     val goods = new util.ArrayList[Int]()
        goods.add(100)
        goods.add(50)
        goods.add(25)
    
        //val 集合名称=for(变量<-集合/数组/表达式) yield 业务逻辑
        //实现95折
        val newGoods = for (index <- 0 until goods.size()) yield goods.get(index) * 0.95
        for (g <- newGoods) {
          println(g)
        }
    

6.Scala中函数

  • 函数两个基本点:

    • 函数是一个对象 val 变量名称=函数

    • 函数是一个映射关系 (输入数据列表)=>输出数据

    • 格式:

      • val 变量名称=(输入数据列表)=>{输出数据}
        
        //val 变量名称=(输入数据列表)=>输出数据
            //使用:val 返回值名称=变量名称(参数列表)
            //求平方函数
            val square=(x:Int)=>x*x
            val result=square(5)
            println(result)
        注意:函数的输入参数都是默认都是val修饰的
        //val 变量名称=(输入数据列表)=>输出数据
            //使用:val 返回值名称=变量名称(参数列表)
            //求平方函数
            val square=(x:Int)=>x*x
            val result=square(5)
            println(result)
            println(square.toString()) //function1
        
            //求面积的函数
            val area=(h:Int,w:Int)=>h*w
            println(area(10,20))
            println(area.toString())  //function2
        
        	//求立方
            val cube:Function3[Int,Int,Int,Int]=(h:Int,w:Int,len:Int)=>h*w*len
            println(cube(1,2,3))
        	等同于
        val cube:(Int,Int,Int)=>Int =(h:Int,w:Int,len:Int)=>h*w*len
            println(cube(1,2,3))
        
        注意:函数都是继承自FunctionN 其中N代表就是参数的个数  N的最大值22。
        
         //val 变量名称=(输入数据列表)=>输出数据
            //使用:val 返回值名称=变量名称(参数列表)
            //求平方函数
            val square: Int=>Int =(x:Int)=>x*x
            val result=square(5)
            println(result)
            println(square.toString()) //function1
        
            //求面积的函数
            val area:(Int,Int)=>Int =(h:Int,w:Int)=>h*w
            println(area(10,20))
            println(area.toString())  //function2
            //求立方
            val cube:Function3[Int,Int,Int,Int]=(h,w,len)=>h*w*len
            println(cube(1,2,3))
        
        

7.Scala中方法定义

  • 格式:def 方法名称(方法参数):返回值数据类型={
    	方法体
    }
    /**
        * 格式:def 方法名称(方法参数):返回值数据类型={
        * 方法体
        * }
        * 1. 如果方法体非常简单,可以省略{}
        * 2. 方法的最后一个表达式就算方法的返回值
        * 3. 方法的返回值类型是可以省略的
        * 注意:如果方法内部调用其自身则返回值类型不能省略(递归调用)
        * @param args
        */
      def add(a: Int, b: Int) = a + b
       //阶乘 5*4*3*2*1
      def jiecheng(x:Int):Int={
        if(x<=1) 1
        else x*jiecheng(x-1)
      }
    
      def main(args: Array[String]): Unit = {
        val result=add(1,2)
        println(result)
        println(jiecheng(5))  //120
      }
    

8.方法和函数的区别

  • 函数是一个对象,拥有方法

  • 方法只能作为对象的成员存在

  • 方法转换成函数(提升方法级别)

    • 格式: val 变量名称= 方法名称 _  //将方法通过 _ 转换成函数
      
      	//将sum转换成function
          //格式: val 变量名称= 方法名称 _
          val sumFun=sum _
          println(sumFun.toString()) //function3
          println(sumFun(1,2,3))
      

9.Scala中数据结构

  • Scala学习5中数据结构
    • Array(数组)
    • List(列表)
    • Tuple(元组)
    • Map (映射)
    • Set(集)
  • 以上数据结构Array、List、Map 、Set又可变和不可变之分
    • 说明
      • 不可变与java中String类型数据不可变是一致
      • 可变一般指的长度可变
    • 可变集合放在scala.collection.mutable包中
    • 不可变的集合放在scala.collection.immutable包中
  • 以上数据结构 主要从增、删、改、查、遍历五个操作说明

9.1 不可变Array

声明格式:
	//根据数组的长度创建数组
  val 名称=new Array[数据类型](长度)
   //根据初始化数据创建数组
  val 名称=Array[数据类型](初始化数据,以逗号隔开)

注意:不可变数组一旦声明 长度不可变,数据可以改变

 // val 名称=new Array[数据类型](长度)
   val strArr=new Array[String](3)
    //给数组数据赋值
    strArr(0)="hadoop"
    strArr(1)="spark"
    strArr(2)="scala"
    strArr(2)="storm" //重新赋值修改数据
    //遍历数组strArr1
    for(str<-strArr){
      println(str)
    }
    println("-----------------")
    // hadoop,spark,scala
    //  val 名称=Array[数据类型](初始化数据,以逗号隔开)
     val strArr2=Array[String]("hadoop","spark","scala")

    //遍历数组strArr2
    for(str<-strArr2){
      println(str)
    }

注意:strArr(2):实际上是方法调用 apply方法

9.2 可变数组ArrayBuffer (长度和数据都可变)

  • 声明格式:
    	//根据数组的长度创建数组
      val 名称=new ArrayBuffer [数据类型](长度)
       //根据初始化数据创建数组
      val 名称=ArrayBuffer[数据类型](初始化数据,以逗号隔开)
    
     /**
          * //根据数组的长度创建数组
          * val 名称=new ArrayBuffer [数据类型](长度)
          * //根据初始化数据创建数组
          * val 名称=ArrayBuffer[数据类型](初始化数据,以逗号隔开)
          */
        val strArrBuffer=new ArrayBuffer[String]
        //hadoop scala spark
        val strArrBuffer2=ArrayBuffer[String]("hadoop","scala","spark")
    
        /**
          * 增加操作
          *   +=  添加一个或者多个
          *   ++= 添加一个Array
          */
        strArrBuffer2+="mapreduce"
        strArrBuffer2+=("html","css")
        strArrBuffer2++=Array("java","javaEE")
    
        /**
          * 删除操作
          *   -=  删除一个或者多个数据
          *   --= 删除一个Array
          */
        strArrBuffer2-="java"
        strArrBuffer2-=("html","mapreduce")
        strArrBuffer2--=Array("hadoop","scala")
    
        /**
          * 修改
          */
        strArrBuffer2(0)="storm"
    
        //遍历
        for(str<-strArrBuffer2){
          println(str)
        }
    
  • 问题:数组的优点查询速度非常快,存储线性存储。缺点:增删相对较慢。多线程情况下数组不安全的。

9.3 list 列表

  • list:以链表形式存在的集合

  • 不可变list

    • 数据和长度都是不可改变,相当java String,对list的任何操作都会产生新的list

    • 格式: val 名称=List[数据类型](初始化数据,以逗号隔开)
      补充:val 名称=item1::item2::tiem3::Nil 
       //val 名称=List[数据类型](初始化数据,以逗号隔开)
          //hadoop scala spark
          val hadoopList = List[String]("hadoop", "scala", "spark")
      
          /**
            * 增加
            * 头部增加一条数据: “ :: ”  " +: "
            * 尾部添加一条数据: “ :+ ”
            * 增加list :
            * 头部: “ ::: ” " ++:"
            * 尾部添加: " ++ "
            */
          val stormList = "storm" :: hadoopList
          val kafkaList = "kafka" +: stormList
          val flinkList = kafkaList :+ "flink"
      
          val solorList=List("solor","flume"):::flinkList
          val redisList=List("redis")++:solorList
          val aiList=redisList++List("AI")
      
          //删除
          val dropList=aiList.drop(2)
          val dorpList2=dropList.dropRight(3)
      
          // 修改数据
          val zkList=dorpList2.updated(0,"zookeeper")
          //访问数据
          println(zkList(2))
          //遍历
          for (item <- zkList) {
            println(item)
          }
      
  • ListBuffer 可变list :长度可变

    • 格式:
      	val 名称=new ListBuffer[数据类型](长度)
          val 名称=ListBuffer[数据类型](初始化数据)
       // val 名称=ListBuffer[数据类型](初始化数据)
          val listBuffer=ListBuffer[String]("hadoop","scala","spark")
          //增加
          listBuffer+="java"
          listBuffer+=("javaEE","Flume")
          listBuffer++=List("redis","kafka")
          //删除操作
          listBuffer-="Flume"
          listBuffer-=("hadoop","scala")
          //修改操作
          val list=listBuffer.updated(0,"flink")
          //查询
          println(listBuffer(0))
          //遍历
          for(item <- list){
            println(item)
          }
      
      
    • 问题:空列表

      • val 名称=item1::item2::tiem3::Nil
    • 问题2: “storm” :: hadoopList 说明 :: 实际上list的方法,但是看起来像字符串的方法

      • 凡是以“:”结尾的方法,其作用于“:”右侧
    • 问题3:List[Int],Array[Int] 这两种集合都能装Int类型的数据,需求,如果同时需要装多种数据类型的数据?

9.4 Tuple(元组)

  • 概念:tuple就是使用()括起来的不同数据类型的集合
    格式:val 名称=(不同数据类型的数据)
    注意:tuple是不可变的 (长度和数据都不可变)
    //tuple应用场景
      // 接受具有多个返回值方法的返回值
      def getArrayList():Tuple2[Array[String],List[Int]]={
        val arr=Array("hadoop","spark")
        val list=List(30,60)
        (arr,list)
      }
    
      def main(args: Array[String]): Unit = {
        //val 名称=(不同数据类型的数据)
        val t=("hadoop",100,99.9,true)
        //访问 通过角标 从1开始
        println(t._1)
        println(t._2)
        println(t._3)
        println(t._4)
        println(t.getClass) //Tuple4
        println("---------")
        val (arr,list)=getArrayList()
        for (a<-arr){
          println(a)	
        }
        println("---------")
        for(l<-list){
          println(l)
        }
      }
    说明:Tuple类型:都是TupleN N<=22
    

9.5 Map(映射)

  • 不可变Map

    • 格式:val 名称=Map[key数据类型,value数据类型](key->value *)
      格式:val 名称=Map[key数据类型,value数据类型]((key,value) *)
      
      val tuple2="name"->"dddf"  //("name","dddf")
          //格式:val 名称=Map[key数据类型,value数据类型](key->value *)
          val personMap=Map("name"->"itcast","age"->10,"sex"->"man")
          val personMap2=Map(
            ("name","itcast")
          )
          //查询map中元素
          println(personMap.get("name").get)  // Some 有值    None 没有
          println(personMap.get("salary"))
          println(personMap.getOrElse("salary","10K"))
          println("---------------------------")
          //增加
          // +
          val salaryMap=personMap+("salary"->"10K","dept"->"Java")
          println(salaryMap)
          println("---------------------------")
          //删除  -
          val ageMap=salaryMap-("age","sex")
          println("ageMap:"+ageMap)
          println("---------------------------")
          val newSalaryMap=ageMap+("salary"->"20K")
          println("newSalaryMap:"+newSalaryMap)
          println("---------------------------")
      
    •     //遍历map
          for((k,v)<-personMap2){
            println(k+"\t"+v)
          }
          //第二种遍历方式
          for(t<-personMap){
            println(t._1+"\t"+t._2)
          }
      
      
  • 可变Map

    • 导包 scala.collection.mutable

    • 格式:val 名称=mutable.Map[key数据类型,value数据类型](key->value *)
      格式:val 名称=mutable.Map[key数据类型,value数据类型]((key,value) *)
      
          //val 名称=mutable.Map[key数据类型,value数据类型](key->value *)
          val map=mutable.Map("hadoop"->30,"scala"->20,"spark"->50)
          //增加  +=
          map+=("java"->10,"hadoop"->15)
          //删除 -=
          map-=("spark","scala")
      
          //遍历操作
          for((k,v)<- map) println(k+"\t"+v)
                      
      

9.6 Set(集)

  • Set特点:

    • 无序 (输出顺序与输入顺序无关) 唯一(不能存在重复元素)
  • 不可变Set

    • 格式:val 名称=Set[数据类型](初始化数据)
      
       //val 名称=Set[数据类型](初始化数据)
          val set=Set[Int](1,2,2,4,10,6,8,9)
      
          //增加 +
         val newSet=set+(11,12)
          //删除 -
          val newSet2=newSet-(1,2)
      
          //遍历
          for(s<-newSet2) println(s)
      
          val set2=Set[Int](3,4,6,5,7)
          println("----------------")
          //两个set的并集  ++
          val bingSet=set++set2
          println(bingSet)
          println("----------------")
          //交集 “&”
          val jiaoSet=set & set2
          println(jiaoSet) //4,6
          //差集操作 &~
          val chaSet=set &~ set2
          println(chaSet)  //1,2,10,8,9
      
  • 可变Set

    • 导包 scala.collection.mutable

    • 格式:val 名称=mutable.Set[数据类型](初始化数据)
      
       //val 名称=mutable.Set[数据类型](初始化数据)
          val set = mutable.Set("hadoop", "scala", "spark")
          //增加  +=
          //删除 -=
          set += "java"
          set += ("javaee", "flume")
          set -= "scala"
          set -= ("spark", "hadoop")
          //更新
          set.update("kafka",true)
          set.update("redis",false)
      
          //遍历
          for (s <- set) {
            println(s)
          }
      

9.7 符号操作

  • 对于不可变的集合:+ - ++ +: :: :+ ::: ++: :++
  • 对于可变集合:+= ++= -= --=
  • 连个set的操作符号: ++(并集) &~(差集) &(交集)

10.集合API操作

  • 以Array数组为例操作API

  • 单个Array的操作

    • val arr=Array(1,3,5,10,6,8,3,3)
      //求和
      println(arr.sum)
      //最大值
      println(arr.max)
      //最小值
      println(arr.min)
      //去重复操作
      println(arr.distinct.toBuffer)
      //排序 默认升序
      println(arr.sorted.toBuffer)
      //倒序操作
      println(arr.sorted.reverse.toBuffer)
      
  • 两个Array的操作

    • val arr=Array(1,3,5,10,6,8,3,3)
      val strArr=Array("hadoop","spark","scala","flume")
      //拉链操作 zip
      val zipArray=strArr.zip(arr)
      println(zipArray.toBuffer)
      println("--------------------------------")
      //zipAll
      val zipAllArray=strArr.zipAll(arr,"itcast",100)
      println(zipAllArray.toBuffer)
      //扁平化
       val arrArray=Array(
            Array(1,2,3),
            Array(4,5,6),
            Array(7,8,9)
      )
          //把二维数组压扁成一维数组
      println(arrArray.flatten.toBuffer)
      
  • Array高阶操作

    • //高级操作:把函数当做方法参数
      val printFun = (str: String) => println(str)
      arr.foreach(printFun)
      println("----------------")
      arr.foreach((str: String) => println(str))
      println("----------------")
      // map操作  将集合中每个元素都进行函数操作 转换成型的集合
      val mapArray = arr.map((x: String) => x + "_itcast")
      println(mapArray.toBuffer)
      println("----------------")
      //过滤操作 过滤出包含h字符的元素
      val filterArray = arr.filter((x: String) => x.contains("h"))
      println(filterArray.toBuffer)
      println("----------------")
      //将数组中的数据进行分割  按照空格分割 合并成一个数组
      val array2 = Array[String]("hadoop mapreduce hdfs", "hive hadoop mapreduce")
      val flatMapArray = array2.flatMap((x: String) => x.split(" "))
      println(flatMapArray.toBuffer)
        
      //groupBy操作 分组操作
      val groupByMap = flatMapArray.groupBy((x: String) => x)
      println(groupByMap)
      println("----------------")
        
      val intArray = Array(1, 3, 5, 7, 8)
      //x:初始化变量  0 相当于 临时变量 sum
      //y: 是数组中每个元素
      val result: Int = intArray.reduce((x: Int, y: Int) => x + y)
      println(result)
      println("----------------")
      //map集合中高阶操作
      val map = Map[String, Array[Tuple2[String, Int]]](
        "hadoop" -> Array(
          ("hadoop", 10),
          ("hadoop", 20),
          ("hadoop", 30)
        ),
        "spark" -> Array(
          ("spark", 10),
          ("spark", 30)
        ),
        "java" -> Array(
          ("java", 100),
          ("java", 150),
          ("java", 180)
        )
      )
      /**
        * 需求:hadoop->60  spark ->40 java ->430
        */
        val resultMap= map.mapValues((arr:Array[Tuple2[String, Int]])=>{
        val intArray=arr.map((t:Tuple2[String, Int])=>t._2)
        intArray.sum
      })
      println(resultMap)
      
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值