一文学会Scala【Scala一站式学习笔记】

文章目录

  • 为什么要学习Scala语言
  • 什么是Scala
  • 如何快速掌握Scala语言
  • Scala环境安装配置
    • Scala命令行
  • Scala的基本使用
    • 变量
    • 数据类型
    • 操作符
    • if 表达式
    • 语句终结符
    • 循环
    • 高级for循环
  • Scala的集合体系
    • 集合
    • Set
    • List
    • Map
    • Array
    • ArrayBuffer
    • 数组常见操作
    • Tuple
    • 总结
  • Scala中函数的使用
    • 函数的定义
    • 函数的参数
    • 特殊的函数-过程
    • lazy
  • Scala面向对象编程
    • 类-class
    • constructor
      • 主constructor
    • 对象-object
    • 伴生对象
      • apply
    • main方法
    • 接口-trait
  • Scala函数式编程
    • 什么是函数式编程
    • 函数赋值给变量
    • 匿名函数
    • 高阶函数
    • 常用高阶函数
      • map的使用:
      • flatMap
      • foreach
      • filter
      • reduceLeft
    • 案例:函数式编程
  • Scala高级特性
    • 模式匹配
      • 对变量的值进行模式匹配
      • 变量类型的模式匹配
      • case class与模式匹配
      • Option与模式匹配
    • 隐式转换
    • 案例:狗也能抓老鼠

为什么要学习Scala语言

最直接的一点就是因为我们后面要学的Spark框架需要用到Scala这门语言。但是Spark其实是同时支持Scala语言和Java语言的,为什么非要学Scala呢,使用java它难道不香吗?
这就要说第二点了:看下面的代码,使用Spark统计文件内单词出现的次数这个需求,使用java代码和scala代码的区别是有多么的明显,在代码量上来说,scala是完胜java的,所以在实际工作中开发spark代码,我们都是需要使用scala的,使用java实现函数式编程太别扭了,代码量太大,这个就是我们学习scala的最直接的原因。

image.png

image.png

什么是Scala

Scala是一门多范式的编程语言,它是一种类似Java的编程语言,它设计的初衷是为了实现可伸缩的语言、并集成面向对象编程和函数式编程的各种特性。Scala基于Java虚拟机,也就是基于JVM的一门编程语言。所有Scala代码,都需要编译为字节码,然后交由Java虚拟机来运行。
Scala和Java可以无缝相互操作,Scala可以任意调用Java代码,这个特性是非常好的

如何快速掌握Scala语言

scala语言本身是很反人类的,特别是你熟悉了例如java之类的编程语言之后再来学scala,会感觉到既爱又恨
那我们如何快速掌握Scala语言的使用呢?
首先大家在学习的过程中需要对比分析Java和Scala在使用上的区别,这样可以加深我们的理解。然后没事的时候可以尝试着用Scala代码改写你之前的Java代码。

最后的最后,交给大家一个绝招,一个终极方案,那就是多练!多练!多练。因为针对编程语言,其实就是需要掌握它的一些基本语法,大家都是有其它语言编程基础的,在这学习Scala语言,其实主要就是掌握这门语言和其它语言的一些不同之处,只要把这些东西捋顺了,那上手也是很容易的。

Scala环境安装配置

注意:由于Scala是基于Java虚拟机的,所以使用 Scala 之前必须先安装 Java,Java我们已经安装过了。那在这里我们先到官网下载Scala安装包
Scala现在有三个主要在使用的版本, 2.11,2.12,2.13。目前的话2.12使用的比较多,所以我们就使用这个版本

image.png

image.png

下载好了之后就需要安装了
安装很简单,直接解压就可以了,在这里我们把scala的安装包解压到了D盘下面的scala目录下

D:\scala\scala-2.12.11 

接下来就该配置SCALA_HOME和PATH环境变量了,和Java的操作是一样的

SCALA_HOME=D:\scala\scala-2.12.11
PATH=...%SCALA_HOME%\bin...

进入cmd命令行,验证一下,输入scala命令,可以看到下面界面就说明安装配置成功了。

C:\Users\yehua>scala
Welcome to Scala version 2.10.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.
Type in expressions to have them evaluated.
Type :help for more information.
scala>

Scala命令行

Scala命令行也称为Scala解释器(REPL),它会快速编译Scala代码为字节码,然后交给JVM来执行
这里的REPL表示:Read(取值)-> Evaluation(求值)-> Print(打印)-> Loop(循环)
在Scala命令行内,输入Scala代码,解释器会直接返回结果
如果你没有指定变量来存放计算的值,那么值默认的名称会显示为res开头的变量,而且会显示结果的数据类型

scala> 1+1
res0: Int = 2

在后面可以继续使用res0这个变量,以及它里面存放的值
例如:

scala> 5 * res0
res1: Int = 10

scala的命令行也有自动补全功能,使用起来还是比较方便的
输入res,按键盘上的tab键,下面就会列出目前以res开头的变量名称

Scala的基本使用

变量

Scala中的变量分为两种:可变 var 和 不可变 val

  • 可变var:可以随时修改var声明的变量的值
  • 不可变val:val声明的变量,值不能被修改,否则会报错: error: reassignment to val
scala> var a = 1
a: Int = 1
scala> a = 2
a: Int = 2
scala> val b = 1
b: Int = 1
scala> b = 2
<console>:8: error: reassignment to val
b = 2

注意:在实际工作中,针对一些不需要改变值的变量,通常建议使用val,这样可以不用担心值被错误的修改(等于java中的final类型)。这样可以提高系统的稳定性和健壮性!

无论声明val变量,还是声明var变量,都可以手动指定变量的类型
如果不指定,Scala会自动根据值,进行类型推断 val c = 1 等价于 val c: Int = 1

scala> val c = 1
c: Int = 1
scala> val c: Int = 1
c: Int = 1

数据类型

Scala中的数据类型可以分为两种,基本数据类型和增强版数据类型
基本数据类型有: Byte、Char、Short、Int、Long、Float、Double、Boolean
增强版数据类型有: StringOps、RichInt、RichDouble、RichChar 等。scala使用这些增强版数据类给基本数据类型增加了上百种增强的功能
例如:RichInt提供的有一个to函数, 1.to(10) ,此处Int会先隐式转换为RichInt,然后再调用其to函数

scala> 1.to(10)
res2: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7,8,9,10)

注意,to函数还可以这样写

scala> 1 to 10
res3: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7,8,9,10)

使用基本数据类型,直接就可以调用RichInt中对应的函数

scala> 1.toString()
res4: String = 1

操作符

Scala的算术操作符与Java的算术操作符没有什么区别
比如 +、-、*、/、% 等,以及 &、|、^、>>、<< 等
注意:Scala中没有提供++、–操作符
我们只能使用+和- ,比如count = 1,count++是错误的,必须写做count += 1

scala> var count = 1
count: Int = 1
scala> count++
<console>:9: error: value ++ is not a member of Int
count++
^
scala> count += 1
scala> count
res8: Int = 2

if 表达式

在Scala中,if表达式是有返回值的,就是if或者else中最后一行语句返回的值,这一点和java中的if是不一样的,java中的if表达式是没有返回值的
例如: val age = 20; if (age > 18) 1 else 0

scala> val age = 20
age: Int = 20
scala> if(age > 18) 1 else 0
res9: Int = 1

在这因为if表达式是有返回值的,所以可以将if表达式赋予一个变量

scala> val res = if(age > 18) 1 else 0
res: Int = 1

由于if表达式是有值的,而if和else子句的值的类型可能还不一样,此时if表达式的值是什么类型呢?
注意:Scala会自动进行推断,取两个类型的公共父类型

例如,if(age > 18) 1 else 0,表达式的类型是Int,因为1和0都是Int
例如,if(age > 18) “old” else 0,此时if和else的值分别是String和Int,则表达式的值是Any类型,Any是String和Int的公共父类型

scala> if(age > 18) 1 else 0
res12: Int = 1
scala> if(age > 18) "old" else 0
res13: Any = old

如果if后面没有跟else,则默认else的值是Unit,也可以用()表示,类似于java中的void或者null
例如,val age = 12; if(age > 18) “old”。此时就相当于if(age > 18) “old” else ()。
此时表达式的值是Any

scala> if(age > 18) "old" else ()
res17: Any = ()

如果想在scala REPL中执行多行代码,该如何操作?
使用 :paste 和 ctrl+D 的方式
:paste 表示代码块的开始
ctrl+D 表示代码块的结束

语句终结符

Scala默认不需要语句终结符,它将每一行作为一个语句
如果一行要放多条语句,则前面的语句必须使用语句终结符
语句终结符和Java中的一样,就是我们平时使用的分号

scala> val age = 20; if(age > 18) 1 else 0
age: Int = 20
res0: Int = 1

循环

  • print和println
    在讲循环之前,先来看一下打印命令print和println
    print打印时不会加换行符,而println打印时会加一个换行符,这个特性和Java中的打印语句的特性是一样的
  • for循环
    for循环本身的特性就没什么好说的了,直接上案例,主要注意一下scala中的for和java中的for在语法层面的区别
scala> :paste
// Entering paste mode (ctrl-D to finish)
val n = 10
for(i <- 1 to n)
println(i)
// Exiting paste mode, now interpreting.
1
2
3
4
5
6
7
8
9
10
n: Int = 10

这里面的to可以换成until

scala> :paste
// Entering paste mode (ctrl-D to finish)
val n = 10
for(i <- 1 until 10)
println(i)
// Exiting paste mode, now interpreting.
1
2
3
4
5
6
7
8

对比两次执行的结果发现
1 to 10 可以获取1~10之间的所有数字
1 until 10可以获取1~9之间的所有数字

所以在这需要注意了,to 和 until 其实都是函数,一个是闭区间,一个是开区间,具体用哪个就要看你的需求了

for循环针对字符串还可以用

scala> for(c <- "hello scala") println(c)
h
e
l
l
o
s
c
a
l
a

注意:在这里我在for循环后面没有使用花括号,都省略了,主要是因为for循环的循环体代码就只有一行,如果有多行,就需要使用花括号了,否则,最终执行的结果就不是我们想要的

  • while循环
    while循环,它的用法和java中的while也是很像的,主要看一下语法层面的区别
scala> :paste
// Entering paste mode (ctrl-D to finish)
var n = 10
while(n>0){
println(n)
n -= 1
}
// Exiting paste mode, now interpreting.
10
9
8
7
6
5
4
3
2
1
n: Int = 0

高级for循环

最后来看一下高级for循环的用法
if守卫
if守卫模式,假设我们想要获取1~10之间的所有偶数,使用普通的for循环,需要把每一个数字都循环出来,然后判断是否是偶数。
如果在for循环里面使用if守卫,可以在循环的时候就执行一定的逻辑,判断数值是否是偶数。

scala> for(i <- 1 to 10 if i % 2 == 0) println(i)
2
4
6
8
10

这是if守卫模式的用法
for推导式
for推导式,一个典型例子是构造集合

我们在使用for循环迭代数字的时候,可以使用yield指定一个规则,对迭代出来的数字进行处理,并且创建一个新的集合。

scala> for(i <- 1 to 10) yield i *2
res16: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 4, 6, 8, 10, 12,14,16,18,20)

Scala的集合体系

Scala中的集合体系,集合在工作中属于经常使用的数据结构

首先看一下整个集合体系结构,这个结构与Java的集合体系非常相似

image.png

集合的顶层接口是Iterable,Iterable接口下面还有一些子接口, Set、Seq、Map
这几个子接口下面有具体的实现类

  • set下面有HashSet、LinkedHashSet、SortedSet等等
  • seq下面有List、Buffer、Range等等
  • Map下面有HashMap、SortedMap、LinkedHashMap等等
  • 其中Buffer下面还有两个常用的,ArrayBuffer、ListBuffer

这个集合体系的时候,还会关联讲到 Array和Tuple 这两个数据结构

集合

Scala中的集合是分成可变和不可变两类集合的

  • 其中可变集合就是说,集合的元素可以动态修改
  • 而不可变集合就是说,集合的元素在初始化之后,就无法修改了

可变集合:在 scala.collection.mutable 这个包下面
不可变集合:在 scala.collection.immutable 这个包下面
我们在创建集合的时候,如果不指定具体的包名,默认会使用不可变集合

Set

Set代表一个没有重复元素的集合
这个集合的特性和Java中Set集合的特性基本一样
Set集合分为可变的和不可变的集合,默认情况下使用的是不可变集合
Set可以直接使用,并且不需要使用new关键字,来看一下

scala> val s = Set(1,2,3)
s: scala.collection.immutable.Set[Int] = Set(1, 2, 3)

本来Set是一个接口,但是却可以创建对象,更神奇的是竟然还不需要使用new关键字,这就有点颠覆我们的认知了

注意了,在学习Scala的时候,可以拿Java来进行对比,加深理解,但是不要全部拿Java里面的知识点来硬套,因为它们两个有些地方还是不一样的。

image.png

只要前面带有object的,可以直接创建对象,并且不需要使用new关键字所以set可以直接使用。

注意:默认情况下直接创建的set集合是一个不可变集合,在这可以看到是在immutable包里面
的,不可变集合中的元素一经初始化,就不能改变了,所以初始化后再向里面添加元素就报错了。


scala> val s = Set(1,2,3)
s: scala.collection.immutable.Set[Int] = Set(1, 2, 3)
scala> s += 4
<console>:9: error: value += is not a member of scala.collection.immutable.Set
s += 4

但是注意,我使用s + 4 这种操作是可以的, 因为 s + 4 返回的是一个新的集合了,相当于在之前的集合的基础上,创建一个新的集合,新的集合包含之前集合的元素和我们新增的4这个元素这个大家需要能够区分开。如果想要创建一个可变的set集合,可以使用mutable包下面的set集合,显式指定包名

scala> val s = scala.collection.mutable.Set(1,2,3)
s: scala.collection.mutable.Set[Int] = Set(1, 2, 3)
scala> s += 4
res34: s.type = Set(1, 2, 3, 4)

Set常用子类有: HashSet、LinkedHashSet、SortedSet

  • HashSet:这个集合的特点是:集合中的元素不重复、无序
  • LinkedHashSet:这个集合的特点是:集合中的元素不重复、有序,它会用一个链表维护插入顺序,可以保证集合中元素是有序的
  • SortedSet:这个集合的特点是:集合中的元素不重复、有序,它会自动根据元素来进行排序

先看HashSet

image.png

HashSet集合分为可变和不可变之分, immutable 包下面的是不可变的,后期无法新增元素。
在这里可以使用new关键字,也可以不使用,因为HashSet既是class,又是object,但是包名需要指定,否则无法识别

scala> val s = new scala.collection.mutable.HashSet[Int]()
s: scala.collection.mutable.HashSet[Int] = Set()
scala> s +=1
res35: s.type = Set(1)
scala> s +=2
res36: s.type = Set(1, 2)
scala> s +=5
res38: s.type = Set(1, 5, 2)

List

List属于Seq接口的子接口,List代表一个不可变的列表

image.png

创建一个list

scala> val l = List(1, 2, 3, 4)
l: List[Int] = List(1, 2, 3, 4)

注意:为什么有的地方需要写类的全路径,而有的不需要呢?
由于immutable包是默认导入的,所以不需要导包,但是也会有个别虽然在immutable包下面的,但是不写全路径还是报错,原谅它把,反正你都带全路径肯定是没有问题的,后期我们会使用idea来开发,也不需要考虑包名的问题,不过在这为了演示起来更加清晰,就使用scala的命令行了。
针对List有 head 、 tail 以及 :: 这几个操作
先演示一下 head、tail 操作

scala> l.head
res49: Int = 1
scala> l.tail
res51: List[Int] = List(2, 3, 4)
  • head:表示获取List中的第一个元素
  • tail:表示获取List中第一个元素之后的所有元素

那其实head和tail就可以获取list中的所有元素了
通过 :: 操作符,可以将head和tail的结果合并成一个List

scala> l.head :: l.tail
res52: List[Int] = List(1, 2, 3, 4)

针对List中的元素进行迭代和前面讲的Set集合的迭代是一样的

scala> val l = List(1, 2, 3, 4)
l: List[Int] = List(1, 2, 3, 4)
scala> for(i <- l) println(i)
1
2
3
4

在这里List是不可变的列表,在实际工作中使用的时候会很不方便,因为我们很多场景下都是需要向列表中动态添加元素,这个时候该怎么办呢?
Scala还提供的有一个ListBuffer
ListBuffer:可以支持动态增加或者移除元素

scala> val lb = scala.collection.mutable.ListBuffer[Int]()
lb: scala.collection.mutable.ListBuffer[Int] = ListBuffer()
scala> lb +=1
res56: lb.type = ListBuffer(1)
scala> lb +=2
res57: lb.type = ListBuffer(1, 2)
scala> lb +=5
res58: lb.type = ListBuffer(1, 2, 5)
scala> lb -=5
res59: lb.type = ListBuffer(1, 2)

ListBuffer也可以直接使用for循环迭代

scala> for(i <- lb) println(i)
1
2

Map

Map是一种可迭代的键值对(key/value)结构

创建一个不可变的Map

scala> val ages = Map("jack"->30,"tom"->25,"jessic"->23)
ages: scala.collection.immutable.Map[String,Int] = Map(jack -> 30, tom -> 25,
scala> ages("jack")
res100: Int = 30

创建一个可变的Map

scala> val ages = scala.collection.mutable.Map("jack"->30,"tom"->25,"jessic"-
ages: scala.collection.mutable.Map[String,Int] = Map(jessic -> 23, jack -> 30
scala> ages("jack")
res101: Int = 30

还有一种创建Map的简易方式,这种方式创建的是不可变Map

scala> val ages = Map(("jack",30),("tom",25),("jessic"->23))
ages: scala.collection.immutable.Map[String,Int] = Map(jack -> 30, tom -> 25,

更新map中的元素

scala> ages("jack") = 31
scala> ages
res104: scala.collection.mutable.Map[String,Int] = Map(jessic -> 23, jack ->

增加多个元素

scala> ages += ("hehe" -> 35, "haha" -> 40)
res105: ages.type = Map(hehe -> 35, jessic -> 23, jack -> 31, tom -> 25

移除元素

scala> ages -= "hehe"
res106: ages.type = Map(jessic -> 23, jack -> 31, tom -> 25, haha -> 40)

遍历
遍历map的entrySet

scala> for ((key, value) <- ages) println(key + " " + value)
jessic 23
jack 31
tom 25
haha 40

最后看一下Map的几个子类
HashMap、SortedMap和LinkedHashMap

  • HashMap:是一个按照key的hash值进行排列存储的map
  • SortedMap:可以自动对Map中的key进行排序【有序的map】
  • LinkedHashMap:可以记住插入的key-value的顺序

HashMap分为可变和不可变的,没有什么特殊之处
在这主要演示一下SortedMap和LinkedHashMap

Array

Scala中Array的含义与Java中的数组类似,长度不可变
由于Scala和Java都是运行在JVM中,双方可以互相调用,因此Scala数组的底层实际上就是Java数组。数组初始化后,长度就固定下来了,而且元素全部根据其类型进行初始化

scala> val a = new Array[Int](10)
a: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
scala> a(0)
res65: Int = 0
scala> a(0)=1
scala> a(0)
res67: Int = 1

也可以直接使用Array()创建数组,元素类型自动推断

scala> val a = Array("hello", "world")
scala> a(0)
res68: String = hello
scala> val a1 = Array("hello", 30)
a1: Array[Any] = Array(hello, 30)

如果想使用一个长度可变的数组,就需要使用到ArrayBuffer了

ArrayBuffer

Scala中ArrayBuffer与Java中的ArrayList类似,长度可变
ArrayBuffer:添加元素、移除元素
如果不想每次都使用全限定名,则可以预先导入ArrayBuffer类

scala> import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.ArrayBuffer
  • 初始化

使用ArrayBuffer()的方式可以创建一个空的ArrayBuffer
注意:也支持直接创建并且初始化ArrayBuffer(1,2,3,4)

scala> val b = new ArrayBuffer[Int]()
b: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer()
  • 添加元素
    使用+=操作符,可以添加一个元素,或者多个元素
    b += 1 或者 b += (2, 3, 4, 5)
scala> b += 1
res69: b.type = ArrayBuffer(1)
scala> b += (2, 3, 4, 5)
res70: b.type = ArrayBuffer(1, 2, 3, 4, 5)

使用insert()函数可以在指定位置插入元素,但是这种操作效率很低,因为需要移动指定位置后的所有元素向3号角标的位置添加一个元素 30

scala> b.insert(3,30)
scala> b
res72: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3, 30, 4
  • 移除元素

使用 remove() 函数可以移除指定位置的元素

注意:Array与ArrayBuffer可以互相进行转换

  • b.toArray:ArrayBuffer转Array
  • a.toBuffer:Array转ArrayBuffer

数组常见操作

  • 遍历Array和ArrayBuffer的两种方式
    由于Array和ArrayBuffer都是有角标的,所以在迭代数组中元素的时候除了可以使用前面迭代集合的
    方式还可以使用角标迭代
scala> val b=ArrayBuffer(1, 2, 3, 4, 5)
b: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2, 3, 4, 5)
scala> for(i <- b) println(i)
1
2
3
4
5
scala> for(i <- 0 until b.length ) println(b(i))
1
2
3
4
5
  • 求和、求最大值
scala> val a = Array(3, 2, 1, 4, 5)
a: Array[Int] = Array(3, 2, 1, 4, 5)
scala> val sum = a.sum
sum: Int = 15
scala> val max = a.max
max: Int = 5
  • 数组排序
scala> scala.util.Sorting.quickSort(a) 
scala> a
res99: Array[Int] = Array(1, 2, 3, 4, 5)

Tuple

Tuple:称之为元组,它与Array类似,都是不可变的,但与数组不同的是元组可以包含不同类型的元素Tuple中的元素角标从 1 开始

注意:目前 Scala 支持的元组最大长度为 22 ,对于更大长度可以使用集合或数组

scala> val t = (1, 3.14, "hehe")
t: (Int, Double, String) = (1,3.14,hehe)
scala> t._1
res117: Int = 1
scala> t._3
res118: String = hehe

总结

前面讲了很多集合体系中的数据结构,有的是可变的,有的是不可变的,有的是既是可变的又是不可变的,听起来有点乱,在这里我们总结一下
可变集合: LinkedHashSet、ListBuffer、ArrayBuffer、LinkedHashMap
不可变集合: List、SortedMap
可变+不可变集合: Set、HashSet、SortedSet、Map、HashMap
还有两个编外人员:
Array、Tuple
Array:长度不可变,里面的元素可变
Tuple:长度不可变,里面的元素也不可变

Scala中函数的使用

函数的定义

在Scala中定义函数需要使用 def 关键字,函数包括函数名、参数、函数体。Scala要求必须给出函数所有参数的类型,但是函数返回值的类型不是必须的,因为Scala可以自己根据函数体中的表达式推断出返回值类型。函数中最后一行代码的返回值就是整个函数的返回值,不需要使用return,这一点与Java不同,java中函数的返回值是必须要使用return的
下面来实现一个单行函数和多行函数

  • 单行函数
scala> def sayHello(name: String) = print("Hello, " + name)
sayHello: (name: String)Unit
scala> sayHello("Scala")
Hello, Scala
  • 多行函数
scala> :paste
// Entering paste mode (ctrl-D to finish)
def sayHello(name: String, age: Int) = {println("My name is "+name+",age is "+age)age
}
// Exiting paste mode, now interpreting.
sayHello: (name: String, age: Int)Int
scala> sayHello("Scala",18)
My name is Scala,age is 18
res120: Int = 18

函数的参数

  • 默认参数
    在Scala中,有时候我们调用某些函数时,不希望给出参数的具体值,而是希望使用参数自身默认的值,此时就需要在定义函数时使用默认参数。如果给出的参数不够,则会从左往右依次应用参数。
scala> def sayHello(fName: String, mName: String = "mid", lName: String = "last")
sayHello: (fName: String, mName: String, lName: String)String
scala> sayHello("zhang","san")
res122: String = zhang san last
  • 带名参数
    在调用函数时,也可以不按照函数定义的参数顺序来传递参数,而是使用带名参数的方式来传递。
scala> def sayHello(fName: String, mName: String = "mid", lName: String = "last")
sayHello: (fName: String, mName: String, lName: String)String
scala> sayHello(fName = "Mick", lName = "Tom", mName = "Jack")
res127: String = Mick Jack Tom
  • 可变参数
    在Scala中,有时我们需要将函数定义为参数个数可变的形式,则此时可以使用变长参数来定义函数。
scala> :paste
// Entering paste mode (ctrl-D to finish)
def sum(nums: Int*) = {var res = 0for (num <- nums) res += numres
}
// Exiting paste mode, now interpreting.
sum: (nums: Int*)Int
scala> sum(1,2,3,4,5)
res129: Int = 15

特殊的函数-过程

在Scala中,定义函数时,如果函数体直接在花括号里面而没有使用=连接,则函数的返回值类型就是Unit,这样的函数称之为过程
过程通常用于不需要返回值的函数
过程还有一种写法,就是将函数的返回值类型显式定义为Unit
比较一下这四种写法的区别

def sayHello(name: String) = "Hello, " + name
def sayHello(name: String): String = "Hello, " + name
def sayHello(name: String) { "Hello, " + name }
def sayHello(name: String): Unit = "Hello, " + name

前面两种写法的效果是一样的,都是函数
后面两种写法的效果是一样的,都是过程

lazy

Scala提供了lazy特性,如果将一个变量声明为lazy,则只有在第一次使用该变量时,变量对应的表达式才会发生计算
什么场景下需要使用lazy特性呢?
这种特性对于特别耗时的操作特别有用,比如打开文件这个操作。

scala> import scala.io.Source._
import scala.io.Source._
scala> lazy val lines = fromFile("D://test.txt").mkString
lines: String = <lazy>

即使D://test.txt文件不存在,代码也不会报错,只有变量使用时才会报错,这就是lazy这个特性。

Scala面向对象编程

在这里我们主要学习Scala中的类、对象和接口
注意:
Scala中类和java中的类基本是类似的
Scala中的对象时需要定义的,而java中的对象是通过class new出来的
Scala中的接口是trait,java中的接口是interface

类-class

首先看一下类
Scala中定义类和Java一样,都是使用 class 关键字
和Java一样,使用new关键字创建对象
那下面来看一个具体案例
定义Person类,

class Person{
var name = "scala"
def sayHello(){println("Hello,"+name)
}
def getName= name
}

注意:如果在定义方法的时候指定了(),那么在调用的时候()可写可不写,如果在定义方法的时候没指定(),则调用方法时肯定不能带()

scala> val p = new Person()
p: Person = Person@23b8d9f3
scala> p.sayHello()
Hello,scala
scala> p.sayHello
Hello,scala
scala> println(p.getName)
scala
scala> println(p.getName())

constructor

类创建好了,下面我们来看一下类中的构造函数,
Scala类中的构造函数可以分为主构造函数和辅助构造函数
这两种构造函数有什么区别呢?
主constructor:类似Java的默认构造函数 this()
辅助constructor:类似Java的重载构造函数 this(name,age)

主constructor

那先来看一下主构造函数
Scala的主constructor是与类名放在一起的,与Java不同,Java中的构造函数是写在类内部的
注意:在类中,没有定义在任何方法或者是代码块之中的代码就是主constructor的代码

先定义一个Student类

class Student(val name:String,val age:Int){
println("your name is " + name + ", your age is " + age)
}

创建对象

scala> new Student("zs",19)
your name is zs, your age is 19
res8: Student = Student@3134153d

在创建对象的时候,类中的println语句执行了,说明这个语句属于主构造函数中的代码,主constructor中还可以通过使用默认参数,来给参数设置默认值

class Student(val name:String = "jack",val age:Int = 20){
println("your name is " + name + ", your age is " + age)
}

创建对象

scala> new Student()
your name is jack, your age is 20
res10: Student = Student@7ddd84b5
scala> new Student("tom",18)
your name is tom, your age is 18
res11: Student = Student@a09303

Scala中,可以给类定义多个辅助constructor,类似于java中的构造函数重载。辅助constructor之间可以互相调用,但是第一行必须调用主constructor
来看一个案例:
给学生指定姓名和年龄信息

class Student {
var name = "jack"
var age = 10
def this(name: String) {
this()
this.name = name
}
def this(name: String, age: Int) {
this(name)
this.age = age
}
}

执行

scala> val s = new Student("tom")
s: Student = Student@1a538ed8
scala> val s = new Student("mick",30)
s: Student = Student@319642db

对象-object

我们学习的scala中的class和java中的class是类似的,下面来看一个java中没有的内容,叫Object
那大家可能有疑问了,Java中也有object,通过class就可以创建object
但是注意了,在scala中,我们可以直接定义一个object,就像定义class一样。
object:相当于class的单个实例,通常在里面放一些静态的field或者method
object不能定义带参数的constructor,只有空参的constructor
第一次调用object的方法时,会执行object的constructor,也就是执行object内部不在任何方法中的代码,因为它只有空参的构造函数。但是注意,object的constructor的代码只会在他第一次被调用时执行一次,以后再次调用就不会再执行了object通常用于作为单例模式的实现,或者放class的一些静态成员,比如工具方法。object可以直接使用,不能new创建一个object,使用object关键字

object Person {var age = 1println("this Person object!")def getAge = age
}

执行,直接通过Object的名称调用属性或者方法即可,类似于Java中的静态类

伴生对象

前面学习了class和object,那下面再来看一个特殊的概念,伴生对象
如果有一个class,还有一个与class同名的object,那么就称这个object是class的 伴生对象 ,class是object的 伴生类

注意:伴生类和伴生对象必须存放在一个.scala文件之中
伴生类和伴生对象最大特点在于可以互相访问private field

创建一个Person object和Person class

object Person {
private val fdNum= 1def getFdNum = fdNum
}class Person(val name: String, val age: Int) {def sayHello = println("Hi, " + name + ",you are " + age + " years old!" +
}

执行

scala> new Person("tom",20).sayHello
Hi, tom,you are 20 years old!, and you have 1 friend.
scala> Person.fdNum
<console>:9: error: value fdNum is not a member of object Person
Person.fdNum
^
scala> Person.getFdNum
res26: Int = 1

apply

apply是object中非常重要的一个特殊方法,通常在伴生对象中实现apply方法,并在其中实现构造伴生类对象的功能
在创建对象的时候,就不需要使用new Class的方式,而是使用Class()的方式,隐式调用伴生对象的
apply方法,这样会让对象创建更加简洁

例如:Array的伴生对象的apply方法就实现了接收可变数量的参数,以及会创建一个Array对象
val a = Array(1, 2, 3, 4, 5)

image.png

从Array object的源码中可以看出来,它里面就是在apply方法内部使用new Array创建的对象。下面我们来自己定义一个伴生类和伴生对象

class Person(val name: String){println("my name is,"+name)
}
object Person {def apply(name: String) = {println("apply exec...")new Person(name)}
}

执行

scala> new Person("tom")
my name is,tom
res29: Person = Person@63917fe1
scala> Person("tom")
apply exec...
my name is,tom
res30: Person = Person@35e74e08

注意:在这里new Person(“zhang”) 等于 Person(“zhang”),都是获取Person的对象
只不过Person(“zhang”)是用的object中apply方法, 而new Person(“zhang”)是直接基于class创建的。这两种方式肯定Person(“zhang”)这种方式使用起来更加方便简洁

main方法

接下来看一下scala中的main方法和Java一样,在Scala中如果要运行一个应用程序,必须有一个main方法,作为入口
Scala中的main方法必须定义在object中,格式为 def main(args: Array[String])
这就需要在编辑器中操作了,我们可以使用eclipse或者idea,但是eclipse对scala的支持不太好,所以建议使用idea。

首先确认一下idea中是否集成了scala语言插件
打开idea,点击 configure–>plugins

image.png

确认scala的插件是否已经安装

image.png

接着创建maven项目

image.png

点击开启自动引入依赖

到这还没完,因为此时我们是无法创建scala代码的,这个项目中也没有集成scala的sdk,只有java的

image.png

接下来就需要给这个项目添加scala的sdk了

image.png

image.png

image.png

image.png

添加成功以后是这样的

image.png

此时查看项目的依赖,发现scala的sdk已经添加进来了。

image.png

最终点击ok即可

以后再创建想要创建scala的maven项目,只需要进入到这个界面确认项目中是否有scala的依赖,没有的话直接点击右边的加号按钮添加即可

image.png

image.png

idea的scala开发环境配置好了,但是我一般还是喜欢再增加一些配置
目前项目的src目录下有一个java目录,这个目录表示是放java代码的,当然了你在里面写scala代码肯定是没有问题的。只是针对我这种稍微有点强迫症的用起来就有点别扭了。
在实际工作中可能我们一个项目既需要使用到java代码,也需要使用到scala代码,所以最好还是建议把java代码和scala代码分开存放,这样比较清晰

这样创建的scala目录是有问题的,你有没有发现这个目录的颜色和java目录的颜色都不一样因为你在这直接创建的scala目录是一个普通的目录,而java那个目录是一个source根目录。
所以我们也需要把scala目录变为source根目录。这样操作

image.png

这样操作之后,就发现scala的目录颜色就正常了
image.png

下面就可以正式开始写代码了,scala相关的代码就放到scala目录中
现在scala目录下创建包: com.imooc.demo
然后创建一个scala的object
先选中Scala Class

image.png

image.png

Scala 开发HelloWorld程序

package com.imooc.demo
/**
* Created by xuwei
*/
object mainDemo {
def main(args: Array[String]): Unit = {
println("hello scala!")}
}

执行代码,右键run即可

这就是Scala中main方法的用法,注意:main方法只能定义在object中,不能定义在class中

接口-trait

scala中的接口,这个接口也是比较特殊的
Scala中的接口称为trait,trait类似于Java中的interface
在triat中可以定义抽象方法

类可以使用 extends 关键字继承trait,无论继承类还是trait统一都是使用 extends 这个关键字。
类继承trait后,必须实现trait中的抽象方法,实现时不需要使用 override 关键字。
scala不支持对类进行多继承,但是支持对trait进行多重继承,使用 with 关键字即可
下面我们就来看一个接口多继承的案例

object PersonDemo {
def main(args: Array[String]): Unit = {
val p1 = new Person("tom")
val p2 = new Person("jack")
p1.sayHello(p2.name)
p1.makeFriends(p2)
}
}
trait HelloTrait { def sayHello(name: String)}
trait MakeFriendsTrait { def makeFriends(p: Person)}
class Person(val name: String) extends HelloTrait with MakeFriendsTrait {
def sayHello(name: String) = println("Hello, " + name)
def makeFriends(p: Person) = println("Hello, my name is " + name + ", your
}

Scala函数式编程

什么是函数式编程

Scala是一门既面向对象,又面向过程的语言。因此在Scala中有非常好的面向对象的特性,可以使用Scala来基于面向对象的思想开发大型复杂的系统和工程;而且Scala也面向过程,因此Scala中有函数的概念。在Scala中,函数与类、对象一样,都是一等公民,所以说scala的面向过程其实就重在针对函数的编程了,所以称之为函数式编程

函数赋值给变量

Scala中的函数是一等公民,可以独立定义,独立存在,而且可以直接将函数作为值赋值给变量。
Scala的语法规定,将函数赋值给变量时,必须在函数后面加上空格和下划线。
来看一个案例:将函数赋值给变量

scala> def sayHello(name: String) { println("Hello, " + name) }
sayHello: (name: String)Unit
scala> val sayHelloFunc = sayHello _
sayHelloFunc: String => Unit = <function1>
scala> sayHelloFunc("scala")
Hello, scala

匿名函数

Scala中的函数也可以不需要命名,这种函数称为匿名函数
匿名函数的语法格式:(参数名: 参数类型) => 函数体
(参数名: 参数类型) :是函数的参数列表
可以将匿名函数直接赋值给某个变量

看一个例子

scala> val sayHelloFunc = (name: String) => println("Hello, " + name)
sayHelloFunc: String => Unit = <function1>
scala> val sayHelloFunc = (name: String) => {println("Hello, " + name)}
sayHelloFunc: String => Unit = <function1>

注意:如果函数体有多行代码,则需要添加{}

高阶函数

由于函数是一等公民,所以说我们可以直接将某个函数作为参数传入其它函数。
这个功能是极其强大的,也是Java这种面向对象的编程语言所不具备的
这个功能在实际工作中是经常需要用到的。
接收其它函数作为当前函数的参数,当前这个函数也被称作高阶函数 (higher-order function)
看一个例子:
先定义一个匿名函数,赋值给变量sayHelloFunc

val sayHelloFunc = (name: String) => println("Hello, " + name)

再定义一个高阶函数,这个高阶函数的参数会接收一个函数
参数: (String) => Unit 表示这个函数接收一个字符串,没有返回值

def greeting(func: (String) => Unit, name: String) { func(name) } 

使用:

scala> greeting(sayHelloFunc, "scala")
Hello, scala

或者还可以这样用,直接把匿名函数的定义传过来也是可以

scala> greeting((name: String) => println("Hello, " + name),"scala")
Hello, scala

高阶函数可以自动推断出它里面函数的参数类型,对于只有一个参数的函数,还可以省去小括号

# 先定义一个高阶函数
def greeting(func: (String) => Unit, name: String) { func(name) }
# 使用高阶函数:完整写法
greeting((name: String) => println("Hello, " + name), "scala")
# 使用高阶函数:高阶函数可以自动推断出参数类型,而不需要写明类型
greeting((name) => println("Hello, " + name), "scala")
# 使用高阶函数:对于只有一个参数的函数,还可以省去其小括号
greeting(name => println("Hello, " + name), "scala")

常用高阶函数

刚才是我们自己实现的高阶函数,其实我们在工作中自己定义高阶函数的场景不多,大部分场景都是去使用已有的高阶函数。下面我们来看几个常见的高阶函数

  • map:对传入的每个元素都进行处理,返回一个元素
  • flatMap:对传入的每个元素都进行处理,返回一个或者多个元素
  • foreach:对传入的每个元素都进行处理,但是没有返回值
  • filter:对传入的每个元素都进行条件判断,如果返回true,则保留该元素,否则过滤掉该元素
  • reduceLeft:从左侧元素开始,进行reduce操作

map

map的使用:

scala> Array(1, 2, 3, 4, 5).map(num=>{num * 2})
res38: Array[Int] = Array(2, 4, 6, 8, 10)

可以简写为:

scala> Array(1, 2, 3, 4, 5).map(_ * 2)
res40: Array[Int] = Array(2, 4, 6, 8, 10)

flatMap

flagMap的使用

scala> Array("hello you","hello me").flatMap(line=>line.split(" "))
res53: Array[String] = Array(hello, you, hello, me)

可以简写为:

scala> Array("hello you","hello me").flatMap(_.split(" "))
res54: Array[String] = Array(hello, you, hello, me)

foreach

foreach的使用

scala> Array(1, 2, 3, 4, 5).map(_ * 2).foreach(num=>println(num))
2
4
6
8
10

可以简写为:

scala> Array(1, 2, 3, 4, 5).map(_ * 2).foreach(println(_))
2
4
6
8
10

filter

filter的使用

scala> Array(1, 2, 3, 4, 5).filter(num=>num % 2 == 0)
res46: Array[Int] = Array(2, 4)

可以简写为:

scala> Array(1, 2, 3, 4, 5).filter(_ % 2 == 0)
res47: Array[Int] = Array(2, 4)

reduceLeft

reduceLeft的使用
表示先对元素1和元素2进行处理,然后将结果与元素3处理,再将结果与元素4处理,依次类推
spark中有一个reduce函数,和这个函数的效果一致

scala> Array(1, 2, 3, 4, 5).reduceLeft((t1,t2)=>t1+t2)
res50: Int = 15

这个操作操作就相当于 1 + 2 + 3 + 4 + 5

可以简写为:

scala> Array(1, 2, 3, 4, 5).reduceLeft( _ + _)
res49: Int = 15

案例:函数式编程

统计多个文本内的单词总数
使用scala的io包读取文本文件内的数据

val lines01 = scala.io.Source.fromFile("D://a.txt").mkString
val lines02 = scala.io.Source.fromFile("D://b.txt").mkString

注意:下面这一行是核心代码,使用了链式调用的函数式编程

lines.flatMap(_.split( " ")).map((_, 1)).map(_._2).reduceLeft(_ + _) 

lines.flatMap(.split( " ")) :表示对每一行数据使用空格进行切割,返回每一个单词
.map((
, 1)) :针对每一个单词,都转成tuple类型,tuple中的第1个元素是这个单词,第2个元素表示单词出现的次数1
.map(_.2) :迭代每一个tuple,获取tuple中的第2个元素
.reduceLeft(
+ _) :对前面获取到的元素进行累加求和

Scala高级特性

模式匹配

模式匹配是Scala中非常有特色,非常强大的一种功能。
模式匹配,其实类似于Java中的 switch case 语法,即对一个值进行条件判断,然后针对不同的条件,进行不同的处理
不过Scala没有Java中的 switch case 语法,但是,Scala提供了更加强大的 match case 语法,就是这个模式匹配
Java的 switch case 仅能匹配变量的值,而Scala的 match case 可以匹配各种情况,比如:变量的类型、集合的元素,有值没值

对变量的值进行模式匹配

match case语法格式:变量 match { case 值 => 代码 }
如果值为下划线,则代表了不满足以上所有情况下的默认处理

def demo1(day: Int) {
day match {
case 1 => println("Monday")
case 2 => println("Tuesday")
case 3 => println("Wednesday")
case _ => println("none")
}
}

执行

scala> demo1(1)
Monday
scala> demo1(4)
none

注意:match case中,只要一个case分支满足并处理了,就不会继续判断下一个case分支了,这一点与Java不同,java的switch case需要用break停止向下执行

变量类型的模式匹配

Scala的模式匹配一个强大之处就在于,可以直接匹配类型,而不是值!!!这点是java的switch case绝对做不到的
语法格式:变量 match { case 变量: 类型 => 代码 }
典型的一个应用场景就是针对异常的处理

import java.io._
def processException(e: Exception) {
e match {
case e1: IllegalArgumentException => println("IllegalArgumentException "
case e2: FileNotFoundException => println("FileNotFoundException " + e2)
case e3: IOException => println("IOException " + e3)
case _: Exception => println("Exception " )
}
}

执行

scala> processException(new Exception())
Exception

在try-catch异常中的应用

try {
val lines02 = scala.io.Source.fromFile("D://test02.txt").mkString
} catch {
case ex: FileNotFoundException => println("no file")
case ex: IOException => println("io exception")
case ex: Exception => println("exception")
}

case class与模式匹配

Scala中提供了一种特殊的类,用 case class 进行声明,中文可以称为样例类
case class 其实有点类似于Java中的JavaBean的概念。即只定义field,会由Scala在编译时自动提供get和set方法,但是没有其它的method
case class 的主构造函数接收的参数通常不需要使用var或val修饰,Scala自动就会使用val修饰(但是如果你自己使用var修饰,那么还是会按照var来,在这用哪个区别都不大)
Scala自动为 case class 定义了伴生对象,也就是object,并且定义了apply()方法,该方法接收主构造函数中相同的参数,并返回 case class 对象
下面来看一个案例:,先定义三个class

class Person
case class Teacher(name: String, sub: String) extends Person
case class Student(name: String, cla: String) extends Person

再创建一个函数

def check(p: Person) {
p match {
case Teacher(name, sub) => println("Teacher, name is " + name + ", sub is
case Student(name, cla) => println("Student, name is " + name + ", cla is
case _ => println("none")
}
}

执行

scala> check(new Student("tom","class1"))
Student, name is tom, cla is class1
scala> check(new Person())
none

Option与模式匹配

Scala有一种特殊的数据类型,叫做Option。
Option有两种值,一种是Some,表示有值,一种是None,表示没有值
Option通常会用于模式匹配中,用于判断某个变量是有值还是没有值,这比null来的更加简洁明了
看这个案例

val ages = Map("jack" -> 18, "tom" -> 30, "jessic" -> 27)
def getAge(name: String) {
val age = ages.get(name)
age match {
case Some(age) => println("your age is " + age)
case None => println("none")
}
}

执行

scala> getAge("jack")
your age is 18
scala> getAge("hehe")
none

隐式转换

Scala的隐式转换,允许手动指定将某种类型的对象转换成其它类型的对象。
Scala的隐式转换,最核心的就是定义隐式转换函数,即implicit conversion function 。
隐式转换函数与普通函数唯一的语法区别是要以implicit开头而且最好要定义函数返回类型。
隐式转换非常强大的一个功能,就是可以在不知不觉中加强现有类型的功能。也就是说,我们可以为某个普通类定义一个加强类,并定义对应的隐式转换函数,这样我们在使用加强类里面的方法的时候,Scala会自动进行隐式转换,把普通类转换为加强类,然后再调用加强类中的方法
Scala默认会自动使用两种隐式转换
1:源类型,或者目标类型的伴生对象里面的隐式转换函数
2:当前程序作用域内可以用唯一标识符表示的隐式转换函数

def check(p: Person) {p match {case Teacher(name, sub) => println("Teacher, name is " + name + ", sub iscase Student(name, cla) => println("Student, name is " + name + ", cla iscase _ => println("none")
}scala> check(new Student("tom","class1"))
Student, name is tom, cla is class1
scala> check(new Person())val ages = Map("jack" -> 18, "tom" -> 30, "jessic" -> 27)
def getAge(name: String) {val age = ages.get(name)age match {case Some(age) => println("your age is " + age)case None => println("none")}
}scala> getAge("jack")
your age is 18
scala> getAge("hehe")
none

如果隐式转换函数不在上述两种情况下的话,那么就必须手动使用import引入对应的隐式转换函数。
通常建议,仅仅在需要进行隐式转换的地方,比如某个函数内,用import导入隐式转换函数,这样可以缩小隐式转换函数的作用域,避免不需要的隐式转换。

案例:狗也能抓老鼠

通过隐式转换实现,狗也具备猫抓老鼠的功能

class cat(val name: String){def catchMouse(){println(name+" catch mouse")}
}
class dog(val name: String)implicit def object2Cat (obj: Object): cat = {if (obj.getClass == classOf[dog]) {val dog = obj.asInstanceOf[dog]new cat(dog.name)}else Nil
}

执行

scala> val d = new dog("d1")
d: dog = dog@7f0e0db3
scala> d.catchMouse()
d1 catch mouse

大部分的场景是我们只需要使用import。导入对应的隐式转换函数就可以了,在这个案例中我们是自己手工实现了一个隐私转换函数,因为他们都在一个作用域内,所以就不需要import了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/187110.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

手握“发展密钥”,TCL科技或迎价值重估?

在高度竞争且快速变化的泛半导体产业&#xff0c;每一次周期性或结构性的变化&#xff0c;都会对企业经营策略带来深远的影响。 2023年前三季度&#xff0c;泛半导体产业迎来结构性复苏。其中&#xff0c;主流显示领域供需关系趋向健康化&#xff0c;半导体显示行业整体上量价…

【Node.js入门】1.1Node.js 简介

Node.js入门之—1.1Node.js 简介 文章目录 Node.js入门之—1.1Node.js 简介什么是 Node.js错误说法 Node.js 的特点跨平台三方类库自带http服务器非阻塞I/O事件驱动单线程 Node.js 的应用场合适合用Node.js的场合不适合用Node.js的场合弥补Node.js不足的解决方案 什么是 Node.j…

获取AAC音频的ADTS固定头部信息

文章目录 前言一、AAC音频中的ADTS二、解析ADTS信息1.标准文档中介绍2.解析3.采样率索引和值4.下载AAC标准文档 前言 调试嵌入式设备中播放aac音频的过程中&#xff0c;了解了aac音频格式&#xff0c;记录在此&#xff0c;防止遗忘。 一、AAC音频中的ADTS ADTS&#xff08;Audi…

2.【自动驾驶与机器人中的SLAM技术】左乘模型推导ESKF

目录 1. 证明题 证明&#xff1a;若某个高斯随机变量为零均值&#xff0c;协方差为对角线矩阵且大小相同&#xff08;各向同性&#xff09;&#xff0c;那么在乘任意旋转矩阵以后&#xff0c;其均值仍为零&#xff0c;且协方差不变&#xff1b; 2. 代码实现运动方程将F矩阵…

Webpack--动态 import 原理及源码分析

前言 在平时的开发中&#xff0c;我们经常使用 import()实现代码分割和懒加载。在低版本的浏览器中并不支持动态 import()&#xff0c;那 webpack 是如何实现 import() polyfill 的&#xff1f; 原理分析 我们先来看看下面的 demo function component() {const btn docume…

【ARM Coresight OpenOCD 系列 1 -- OpenOCD 介绍】

请阅读【ARM Coresight SoC-400/SoC-600 专栏导读】 文章目录 1.1 OpenOCD 介绍1.1.1 OpenOCD 支持的JTAG 适配器1.1.2 OpenOCD 支持的调试设备1.1.3 OpenOCD 支持的 Flash 驱动 1.2 OpenOCD 安装与使用1.2.1 OpenOCD 代码获取及安装1.2.2 OpenOCD 使用1.2.3 OpenOCD 启用 GDB…

Ubuntu 22.04 安装水星无线 USB 网卡

我的 USB 网卡是水星 Mercury 的&#xff0c; 在 Ubuntu 22.04 下面没有自动识别。 没有无线网卡的时候只能用有线接到路由器上&#xff0c;非常不方便。 寻思着把无线网卡驱动装好。折腾了几个小时装好了驱动。 1.检查网卡类型 & 安装驱动 使用 lsusb 看到的不一定是准确…

ChatGPT:something went wrong

今天下午不知什么原因&#xff0c;ChatGPT无法使用。我原来在使用ChatGPT for chrome&#xff0c;返回了一个答案&#xff0c;后来在网页端无法使用&#xff0c;以为是这个chrome插件泄露API KEY导致的。注销账号&#xff0c;删除API KEY后&#xff0c;wrong问题仍然存在。 我…

Centos配置邮件发送

在CentOS Linux上配置邮件发送 在这个指南中&#xff0c;我们将讨论如何配置CentOS Linux系统以通过外部邮件服务器发送电子邮件&#xff0c;使用自己的邮件账户进行发送。 第一步&#xff1a;开启SMTP授权码 首先&#xff0c;我们以QQ邮箱为例&#xff0c;需要开启SMTP授权…

类直径树上贪心

http://cplusoj.com/d/senior/p/SS231109C 场上想到枚举点&#xff0c;然后最大值为高&#xff0c;然后可以求最大值。但是感觉计数会重 计数其实不会重&#xff0c;如图中&#xff0c;红色线段显然比蓝色线段优 所以我们枚举3叉点时没错的 #include<bits/stdc.h> usin…

使用ffmpeg 压缩视频

我有一批1080p的视频,在网上播放占用空间太大,需要进行压缩以后再上传,下面是记录一下ffmpeg命令的使用情况 原视频大小:288mb --压缩加修改分辨率 640p ffmpeg -y -i C4995.mp4 -vcodec libx264 -crf 18 -s vga C4995\C4995_2.MP4 -y: 强制覆盖 -i :输入文件 -vcodec lib…

2、鸿蒙开发工具首次运行时开发环境配置

请务必在第一次运行时配置好开发环境&#xff0c;如果取消了配置&#xff0c;后续再配置会比较麻烦 1、点击工具图标运行 2、在欢迎页中点击“Agree” 3、默认“Do not import setting”&#xff0c;点击“OK” 3、此片设置Nodejs和Ohpm的安装&#xff0c;其中&#xff0c; …

计算机网络学习笔记(五):运输层(待更新)

目录 5.1 概述 5.1.1 TCP协议的应用场景 5.1.2 UDP协议的应用场景 5.2 三大关系 5.2.1 传输层协议和应用层协议之间的关系 5.3 用户数据报协议UDP(User Datagram Protocol) 5.3.1 UDP的特点 5.3.2 UDP的首部 5.4 传输控制协议TCP(Transmission Control Protocol) 5.…

系列一、Shiro概述

一、概述 Shiro是一款主流的Java安全框架&#xff0c;不依赖任何容器&#xff0c;可以运行在JavaSE 和 JavaEE项目中&#xff0c;它的主要作用是对访问系统的用户进行身份认证、授权、会话管理、加密等操作。 一句话&#xff1a;Shiro是一个用来解决安全管理的系统框架&#x…

MVCC中的可见性算法

在之前的文章 MVCC详解-CSDN博客中我们已经介绍过了MVCC的原理&#xff08;read viewundo log&#xff09;&#xff0c;今天来详细的说一下readview的匹配规则&#xff08;可见性算法&#xff09; 隔离级别在RC&#xff0c;RR的前提下 Read View是如何保证可见性判断的呢&#…

一篇带你精通php

华子目录 什么是phpphp发展史平台支持和数据库支持网站静态网站和动态网站的区别静态网站动态网站的特点 关键名词解析服务器概念IP的概念域名DNS端口 web程序的访问流程静态网站访问流程动态网站访问流程 php标记脚本标记标准标记&#xff08;常用&#xff09; php注释 什么是…

[LeetCode]-225. 用队列实现栈

目录 225. 用队列实现栈 题目 ​思路 代码 225. 用队列实现栈 225. 用队列实现栈 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/implement-stack-using-queues/description/ 题目 请你仅使用两个队列实现一个后入先出&#xff08;LIFO&#xff0…

【Servlet】 三

本文主要介绍了基于serlvet的表白墙项目的编写. (附完整代码) 一.JS基础 作为后端开发,对于前端的要求是能前端代码能看懂七七八八 . JS是一个动态弱类型的编程语言 1. let/war定义变量 (推荐使用let) 2.querySelector是浏览器提供api , 能够获取到页面的元素的 (js的目的就…

原语:串并转换器

串并转换器OSERDESE2 可被Select IO IP核调用。 IP配置方法&#xff1a;&#xff08;待续&#xff09; OSERDESE2允许DDR功能 参考&#xff1a; FPGA原语学习与整理第二弹&#xff0c;OSERDESE2串并转换器 - 知乎 (zhihu.com) 正点原子。 ISERDESE2原语和OSERDESE2原语是串…

阿里云99元的主机到底怎么样?

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 在云栖大会上&#xff0c;阿里云推出了一款绝对超级超值的99元云服务器&#xff0c;并号称是11月销量王。什么?云栖大会11月2号结束的&#xff0c;你就号称11月销量王&#xff0c;这是未卜先知啊。…