Go (一) 基础部分5 -- 单元测试,协程(goroutine),管道(channel)

一、单元测试

Go自带一个轻量级的"测试框架testing"和自带的"go test"命令来实现单元测试和性能测试。

1.确保每个函数时可运行,并且运行结果是正确的。
2.确保写出来的代码性能是好的。
3.单元测试能及时的发现程序设计或实现的逻辑错误,使问题及早暴露,便于问题的定位解决。而性能测试的重点在于发现程序设计上的一些问题,让程序能够在高并发的情况下还能保持稳定。

运用测试用例的指令:

go test:运行正确时,无日志,运行错误时,会输出日志

go test -v:运行正确或者错误都会输出日志

1.1、单元测试的快速入门(判断一个函数的执行结果是否符合预期)

1.测试用例文件必须以"_test.go"结尾,文件不能命名为:_test.go,test.go,test_xxx.go;

2.测试用例文件内任何 "Test开头且首字母大写"的函数(例:TestXxx)都会被执行,文件内不需要写main函数;

3.TestXxx(t *testing.T)的形参类型必须时*testing.T;

4.出现错误时,可以使用"t.Fatalf"来格式化错误信息,并退出程序;

5."t.Logf"方法可以输出相应的日志;

6.测试用例函数,没有main函数,也正常执行了,这也是测试用例的方便之处;

7.PASS表示测试用例运行成功,FAIL表示测试用例运行失败;

8.测试单个文件"cal_test.go",一定要带上被测试的原文件:go test -v cal_test.go main.go

9.测试单个方法:go test -v -test.run(固定参数) TestAddUpper(函数名)

package mainimport ("fmt""testing"
)// 使用go test命令,能够自动执行如下形式的任何函数
// func TestXxx(*testing.T),其中Xxx可以是任何字母或字符串(第一个字母不能是[a-z])
func TestAddUpper(t *testing.T)  {res := AddUpper(10)if res != 55 {t.Fatalf("AddUpper函数执行错误,期望值:%v 返回值:%v",55,res)}t.Logf("AddUpper函数执行成功")
}func TestSudada(t *testing.T)  {fmt.Println("函数TestSudada被执行")
}

测试函数:xxx.go,文件内包含要测试的"函数",文件内不需要写main函数。

package main// xxx_test.go 内调用的函数
func AddUpper(n int) int  {res:=0for i:=0;i<=10;i++{res+=i}return res
}

"go test"命令执行错误时的返回结果

"go test"命令执行正确时的返回

1.2、单元测试-综合案例

测试用例文件:store_test.go(测试结构体的序列化和反序列化)

package mainimport ("fmt""testing"
)// 测试用例TestStore
func TestStore(t *testing.T)  {// 先创建结构体变量var monster = Monster{Name: "牛魔王",Age: 18,Skill: "蛮牛冲撞",}StoreRes:=monster.Store()if !StoreRes {fmt.Println("Store函数测试错误,返回值不为ture")}// 返回值:// === RUN   TestStore// Store方法执行成功,test.txt文件保存成功// --- PASS: TestStore (0.00s)
}// 测试用例TestReStore
func TestReStore(t *testing.T)  {// 先创建结构体变量var monster = Monster{}ReStoreRes:=monster.ReStore()if !ReStoreRes {fmt.Println("ReStore函数测试错误,返回值不为ture")}// 返回值:// === RUN   TestReStore// ReStore方法执行成功,反序列化的值为 &{牛魔王 18 蛮牛冲撞}// --- PASS: TestReStore (0.00s)
}

被测试对象:main.go(结构体和方法)

package mainimport ("encoding/json""fmt""io/ioutil"
)type Monster struct {Name stringAge intSkill string
}func (this *Monster)Store() bool {// 序列化结构体变量data, err := json.Marshal(this)if err != nil {fmt.Println("序列化失败",err)return false}// 把序列化后的数据,写入到test.txt文件内WriteFileErr :=ioutil.WriteFile("test.txt",data,0666)if WriteFileErr != nil{fmt.Println("文件保存失败",WriteFileErr)return false}fmt.Println("Store方法执行成功,test.txt文件保存成功")return true
}func (this *Monster)ReStore() bool {// 从文件中,读取序列化的数据data, ReadFileErr := ioutil.ReadFile("test.txt")if ReadFileErr != nil{fmt.Println("文件读取失败",ReadFileErr)return false}// 将读到的数据,执行反序列化err := json.Unmarshal(data, this)if err != nil{fmt.Println("反序列化失败",err)return false}fmt.Println("ReStore方法执行成功,反序列化的值为:",this)return true
}

二、goroutine(协程)

一个go线程上,可以起多个协程,协程是轻量级的线程。

Go协程的特点有独立的栈空间,共享程序堆空间,调度由用户控制,协程是轻量级的线程

2.1、gorouting快速入门案例

协程的执行流程:
1.程序开始(进程/主线程开始执行);
2.go test() 开启协程:协程此时开始执;行,如果主线程退出了,那么无论协程是否执行完毕,都会退出;
3.主线程(main)执行代码逻辑;
4.主线程结束(程序退出)。

案例:在主线程中,启动一个gorouting,该协程每隔1秒输出一个"hello world"

package mainimport ("fmt""time"
)// 
func test()  {for i:=0;i<10;i++ {fmt.Println("test() hello world",i)time.Sleep(time.Second)}
}func main() {// 开启一个协程go test()for i:=0;i<10;i++ {fmt.Println("main() hello world",i)time.Sleep(time.Second)}
}// 输出结果
main() hello world 0
test() hello world 0
test() hello world 1
main() hello world 1
main() hello world 2
test() hello world 2
test() hello world 3
main() hello world 3
main() hello world 4
test() hello world 4
test() hello world 5
main() hello world 5
main() hello world 6
test() hello world 6
test() hello world 7
main() hello world 7
main() hello world 8
test() hello world 8
test() hello world 9
main() hello world 9

2.1.1、协程的快速入门小结

1.主线程是物理线程,作用在CPU上。是重量级的,非常耗费cpu资源。

2.协程是从主线程开启的,是轻量级的线程,是逻辑态,对资源消耗相对小。

3.Golang的协程机制是重要的特点,可以轻松开启上万个协程。

2.2、MPG模式

M:操作系统的主线程

P:协程执行需要的上下文

G:协程(gorouting)

2.3、Go设置运行cpu数目

为了充分利用多CPU的优势,在golang程序中,设置运行的cpu数目(go1.8版本之后,默认让程序运行在多核上,可不设置)

package mainimport ("fmt""runtime"
)func main() {// 常看当前主机的CPU核数cpuNum:=runtime.NumCPU()fmt.Println(cpuNum)// 可以自定义设置golang运行的cpu数runtime.GOMAXPROCS(5)fmt.Println("ok")
}

三、channel(管道)

在运行程序时,如何知道是否存在资源争抢的问题?

在编译程序时,增加一个参数 -race 即可。

3.1、全局锁的定义和使用

// 定义一个全局互斥锁
var lock sync.Mutexfunc main()  {// 加锁lock.Lock()代码逻辑...// 解锁lock.Unlock()
}

3.2、channel(管道)的基本介绍 - 引用类型

3.2.1、为什么要使用channel

1.channel本质就是一个数据结构-队列 (先进先出)
2.线程安全,多gorouting访问时,不需要加锁,就是说channel本身就是线程安全的 (多个协程操作同一个管道时,不会发生资源竞争问题);
3.channel是有类型的,一个string类型的channel只能存放string类型数据;

3.2.2、channel的基本语法

var 变量名 chan 数据类型

var intChan chan int (intChan用于存放int数据)

var mapChan chan map[int]string (mapChan用于存放map[int]string数据)

var perChan chan Person (perChan用于存放结构体)

var perChan chan *Person (perChan用于存放结构体指针)

说明:

channel是引用类型

channel必须初始化才能雪茹数据,即make后才能使用

3.3、channel的快速入门(管道创建,写数据,读数据)

package mainimport "fmt"func main() {// 定义intChanvar intChan chan int// 初始化intChan:初始化类型为chan,类型为int,容量为3intChan = make(chan int, 3)// 查看管道的值是什么fmt.Println(intChan)  // 得到的是一个指针地址// 向管道中写入数据(写入的数据数量,不能超过管道的容量,如果超过会报错deadlock)intChan<- 10  // "<- 10" 向管道内写入一个数据"10"num:=20intChan<-num  // "<- num" 向管道内写入一个变量的值"20"// 查看管道的长度(追加了2个数据)fmt.Println(len(intChan)) // 返回值:2// 查看管道的cap(容量)(make时设置的容量为3)fmt.Println(cap(intChan)) // 返回值:3// 从管道中取出一个值(长度会减少,容量不变)var num2 intnum2 = <-intChanfmt.Println(num2) // 返回值为:10//var num3 int//num3 = <-intChan//fmt.Println(num3) // 返回值为:20// 取出一个数据,不接收<-intChan// 管道的数据全部取完后,会报错deadlockvar num4 intnum4 = <-intChanfmt.Println(num4) // 返回值为:deadlock!
}

3.3.1、channel的注意事项

1.channel定义好了数据类型之后,只能存放指定的数据类型;

2.channel的数据存满之后,就不能在存入了;

3.channel的数据取完之后,才可以继续存入;

4.在没有使用协程的情况下,如果channel的数据取完了,再取的话,就会报错deadlock。

3.3.2、案例1:创建一个intChan,存放int类型的数据,然后取出

package mainimport "fmt"func main() {// 定义intChanvar intChan chan int// 初始化chanintChan = make(chan int, 10)// 给管道添加值intChan<-10intChan<-20intChan<-30// 从管道取值num1:=<-intChannum2:=<-intChannum3:=<-intChanfmt.Println(num1,num2,num3)
}

3.3.3、案例2:创建一个mapChan,存放map[string]string数据,然后取出

package mainimport "fmt"func main() {// 定义chanvar mapChan chan map[string]string// 初始化chanmapChan = make(chan map[string]string,10)// 给管道添加值(map类型先make)m1 := make(map[string]string,10)m1["name1"]="北京"m1["name2"]="天津"m2 := make(map[string]string,10)m2["name1"]="上海"m2["name2"]="南京"mapChan<-m1mapChan<-m2// 从管道取值m11:=<-mapChanm22:=<-mapChanfmt.Println(m11)  // map[name1:北京 name2:天津]fmt.Println(m22)  // map[name1:上海 name2:南京]
}

3.3.4、案例3:创建一个catChan,存放Cat结构体变量数据,然后取出

package mainimport "fmt"type Cat struct {Name stringAge int
}func main() {// 定义chanvar catChan chan Cat// 初始化chancatChan = make(chan Cat,10)cat1:=Cat{Name: "Tom",Age: 2,}cat2:=Cat{Name: "TTome",Age: 2,}// 给管道赋值catChan<-cat1catChan<-cat2// 管道取值cat11:=<-catChancat22:=<-catChanfmt.Println(cat11)  // 返回值:{Tom 2}fmt.Println(cat22)  // 返回值:{TTome 2}
}

3.3.5、案例4:创建一个allChan,存放任意类型的数据,然后取出

package mainimport "fmt"type Cat struct {Name stringAge int
}func main() {// 定义chanallChan := make(chan any,3)// chan赋值allChan<-"sudada"allChan<-123cat:=Cat{Name: "Tom",Age: 2,}allChan<-cat// 只想获取管道的第三个值时(需要现将前2个值推出)<-allChan<-allChan// 获取到的第三个值cat1:=<-allChanfmt.Println(cat1)  // 返回值:{Tom 2}// 查看管道内值的类型fmt.Printf("%T\n",cat1)  // 返回值:main.Cat// fmt.Println(cat1.Name)  // 直接获取结构体的值会报错:cat1.Name undefined (type any has no field or method Name)// 使用类型断言newCat:=cat1.(Cat)fmt.Println(newCat.Name) // 返回值:Tom
}

3.4、channel的遍历和关闭

3.4.1、channel的关闭 close(xxxChan)

使用内置函数close()就可以关闭channel,当channel关闭后,就不能在往里面写数据,但是仍然可以读取数据

package mainimport "fmt"func main() {// 定义并初始化chanintChan:=make(chan int,3)// 管道赋值intChan<-100intChan<-200// 管道关闭close(intChan)//intChan<-300  // 管道关闭后,在往里面写数据时,会报错:panic: send on closed channel// 管道关闭后,读取值num1:=<-intChannum2:=<-intChanfmt.Println(num1)  // 返回值:100fmt.Println(num2)  // 返回值:200
}

3.4.2、channel的遍历(for -- range)

1.在遍历时,如果channel没有关闭,则会出现deadlock的错误

2.在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完毕后退出。

package mainimport "fmt"func main() {// 定义并初始化chanintChan:=make(chan int,100)// 管道赋值for i:=1;i<=100;i++{intChan<-i}// 关闭管道(如果不关闭会报错deadlock)close(intChan)// for-range遍历管道for v:=  range intChan {fmt.Println(v)  // 返回值:1...100}
}

3.5、gorouting和chnnel结合使用的案例

1.开启一个writeData协程,向管道intChan写入10个整数;

2.开启一个readData协程,从管道intChan内读取10个整数;

3.writeData和readData操作的是同一个管道;

4.主线程需要等待writeData和readData协程都完成工作才退出;

流程图解:

代码实现:

package mainimport ("fmt""time"
)func writeData(intChan chan int)  {for i:=0;i<10;i++{// 写之前等1秒,模拟一边写一边读的场景time.Sleep(time.Second)// 管道内放入数据intChan<-ifmt.Println("writeData: ",i)}// 关闭管道(不在写数据之后,就把管道关闭,但还可以继续读管道的数据)close(intChan)
}func readData(intChan chan int, exitChan chan bool)  {for {// 读之前等1秒,模拟一边写一边读的场景time.Sleep(time.Second)// 管道内取值,并判断是否取值成功v, ok := <-intChanif !ok {break}fmt.Println("readData: ",v)}// intChan管道内的所有值全部都取出后,往exitChan写一个数据,并关闭管道exitChan<-trueclose(exitChan)
}func main() {// 创建2个管道intChan := make(chan int,10)exitChan := make(chan bool,1)// 调用协程go writeData(intChan)go readData(intChan,exitChan)// 主进程读取exitChan管道内的数据,取到值之后再退出,否则就一直等待for {// 管道内取值,并判断是否取值成功_, ok := <-exitChanif ok {break}}
}// 返回值:
//writeData:  0
//readData:  0
//writeData:  1
//readData:  1
//writeData:  2
//readData:  2
//writeData:  3
//readData:  3
//writeData:  4
//readData:  4
//writeData:  5
//readData:  5
//writeData:  6
//readData:  6
//writeData:  7
//readData:  7
//writeData:  8
//readData:  8
//writeData:  9
//readData:  9

3.6、阻塞

编译器在运行时,发现一个管道只有写,而没有读,就会阻塞。(如果有(缓慢的)读取管道内的数据时,就不会阻塞)

举例:管道的容量为10,但是放入管道的数据量超过了10,或者一直没有取出管道内的数据,就会阻塞,报错deadlock;

案例1:一直往管道内写数据,当写入的数据量,超过管道的容量时,就会阻塞,报错deadlock

package mainimport ("fmt"
)func writeData(intChan chan int)  {for i:=0;i<10;i++{// 写之前等1秒,模拟一边写一边读的场景//time.Sleep(time.Second)// 管道内放入数据intChan<-ifmt.Println("writeData: ",i)}// 关闭管道(不在写数据之后,就把管道关闭,但还可以继续读管道的数据)close(intChan)
}func readData(intChan chan int, exitChan chan bool)  {for {// 读之前等1秒,模拟一边写一边读的场景//time.Sleep(time.Second)// 管道内取值,并判断是否取值成功v, ok := <-intChanif !ok {break}fmt.Println("readData: ",v)}// intChan管道内的所有值全部都取出后,往exitChan写一个数据,并关闭管道exitChan<-trueclose(exitChan)
}func main() {// 创建2个管道intChan := make(chan int,5)exitChan := make(chan bool,1)// 调用协程go writeData(intChan)//go readData(intChan,exitChan)// 主进程读取exitChan管道内的数据,取到值之后再退出,否则就一直等待for {// 管道内取值,并判断是否取值成功_, ok := <-exitChanif ok {break}}
}// 返回值: fatal error: all goroutines are asleep - deadlock!

案例2:在往管道内写数据时,如果如果有协程在(缓慢的)去读/取(消费)管道内的数据,那么就不会阻塞

package mainimport ("fmt""time"
)func writeData(intChan chan int)  {for i:=0;i<10;i++{// 写之前等1秒,模拟一边写一边读的场景//time.Sleep(time.Second)// 管道内放入数据intChan<-ifmt.Println("writeData: ",i)}// 关闭管道(不在写数据之后,就把管道关闭,但还可以继续读管道的数据)close(intChan)
}func readData(intChan chan int, exitChan chan bool)  {for {// 读之前等1秒,模拟一边写一边读的场景time.Sleep(time.Second)// 管道内取值,并判断是否取值成功v, ok := <-intChanif !ok {break}fmt.Println("readData: ",v)}// intChan管道内的所有值全部都取出后,往exitChan写一个数据,并关闭管道exitChan<-trueclose(exitChan)
}func main() {// 创建2个管道intChan := make(chan int,5)exitChan := make(chan bool,1)// 调用协程go writeData(intChan)go readData(intChan,exitChan)// 主进程读取exitChan管道内的数据,取到值之后再退出,否则就一直等待for {// 管道内取值,并判断是否取值成功_, ok := <-exitChanif ok {break}}
}// 返回值:
writeData:  0
writeData:  1
writeData:  2
writeData:  3
writeData:  4
readData:  0
writeData:  5
readData:  1
writeData:  6
writeData:  7
readData:  2
readData:  3
writeData:  8
readData:  4
writeData:  9
readData:  5
readData:  6
readData:  7
readData:  8
readData:  9

3.7、协程求素数的实现

统计1-1000个数字中,哪些是素数?

package mainimport ("fmt"
)// 往管道内放入1000个数
func putNum(intChan chan int) {for i := 0; i < 100; i++ {intChan <- i}// 关闭intChanclose(intChan)
}// 从管道intChan内取数据,判断是否为素数,把素数放入primeChan。取完后往exitChan写入一个true
func putPrimeNum(intChan chan int, primeChan chan int, exitChan chan bool) {var flag boolfor {// 从管道intChan内取数据num,ok:=<-intChan// 管道intChan内的数据取完就退出if !ok {break}// 假设是素数flag = true// 判断是否为素数for i:=2;i<num;i++{if num %i == 0 {  // 为0说明不是素数flag = falsebreak}}if flag {// 把素数放入管道primeChanprimeChan<-num}}// 向退出的管道exitChan写入一个true即可fmt.Println("协程putPrimeNum取不到数据,退出")exitChan<-true
}func main() {// 存放整数的chanvar intChan chan intintChan = make(chan int, 100)// 存放素数的chanvar primeChan chan intprimeChan = make(chan int, 200)// 标识退出的chanvar exitChan chan boolexitChan = make(chan bool, 4)// 开启协程,向intChan放入1000个数go putNum(intChan)// 开启协程,从intChan里面取数据,并判断是否为素数(如果是就放入primeChan)for i:=0;i<4;i++{go putPrimeNum(intChan,primeChan,exitChan)}// 退出主线程之前,先判断exitChan管道内的值是否都会truego func(){for i:=0;i<4;i++{<-exitChan}// 如果从exitChan管道内取出了4个true,就可以退出主线程了close(primeChan)}()// 遍历primeChan,把值取出for{num,ok:=<-primeChanif !ok {break}fmt.Println("素数: ",num)}fmt.Println("主线程退出")// 返回值:// 素数:  。。。// 协程putPrimeNum取不到数据,退出// 协程putPrimeNum取不到数据,退出// 协程putPrimeNum取不到数据,退出// 协程putPrimeNum取不到数据,退出// 主线程退出
}

3.8、channel的使用细节和注意事项

1.channel可以定义为只读或者只写模式(应用场景:把一个可读可写的管道传入一个函数,函数内对管道做下封装,只允许读/写)

只读模式,只写模式的代码定义:

package mainimport "fmt"func main() {// 定义一个"只写"的管道var intChanWrite chan<- intintChanWrite = make(chan<- int,3)intChanWrite<-10// "只写"的管道的不能取数据// num:=<-intChan// 定义一个"只读"的管道var intChanRead <-chan intintChanRead = make(<-chan int,3)num:=<-intChanReadfmt.Println(num)  // 因为管道内没有数据,这里取不到值// "只读"的管道的不能写数据//intChanRead<-10
}

2.使用select可以解决从管道内取数据阻塞的问题

package mainimport ("fmt"
)func main() {// 定义管道并存放值intChan := make(chan int,10)for i:=0;i<10;i++{intChan<-i}stringChan := make(chan string,5)for i:=0;i<5;i++{stringChan<-"hello"+fmt.Sprintf("%d",i)}// 传统的方式在遍历管道时,如果不关闭管道会deadlock(阻塞)// 实际开发中,不好确认什么时候关闭管道,此时使用select(解决从管道内取数据时,阻塞的问题)for  {select {// 如果管道intChan没有关闭,同时又取不到值时,不会deadlock(阻塞)会去下一个case取值case v:= <-intChan:fmt.Println("从intChan管道内取到的值",v)// 如果管道stringChan没有关闭,同时又取不到值时,不会deadlock(阻塞)会去下一个case取值case v:= <-stringChan:fmt.Println("从stringChan管道内取到的值",v)// 默认逻辑default:fmt.Println("管道内取不到值了。。。")return}}
}

3.协程中使用recover,解决协程中出现panic,导致程序崩溃的问题

package mainimport ("fmt""time"
)func test()  {// defer + recover 捕获当前函数抛出的panic错误defer func() {err:=recover()if err != nil {fmt.Println("test函数发生错误",err)}}()// 函数的代码逻辑(这里故意写的错误代码,触发报错)var testmap map[int]stringtestmap[0]="sudada"
}func main() {go test()// 正常的代码执行(不加recover时,协程报错,整个程序就退出了。加上recover后,协程报错了不影响正常代码的执行)for  {fmt.Println("main")time.Sleep(time.Second)}// 返回值:// main// test函数发生错误 assignment to entry in nil map// main
}

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

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

相关文章

程序员副业之无人直播助眠

介绍和概览 大家好&#xff0c;我是小黑&#xff0c;本文给大家介绍一个比较轻松简单的副业&#xff0c;无人直播助眠副业。 这个项目的核心就是通过直播一些助眠素材来赚钱。比如你可以放一些舒缓的雨声之类的&#xff0c;吸引观众进来。然后&#xff0c;咱们可以挂个小程序…

spring boot 集成邮件发送功能

一、首先到QQ邮箱申请开启POP3、SMTP协议 二、安装依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId></dependency><dependency><groupId>org.springframew…

探索生成式AI:自动化、问题解决与创新力

目录 自动化和效率&#xff1a;生成式AI的颠覆力量 解谜大师生成式AI&#xff1a;如何理解和解决问题 创新与创造力的启迪&#xff1a;生成式AI的无限潜能 自动化和效率&#xff1a;生成式AI的颠覆力量 1. 神奇的代码生成器&#xff1a;生成式AI可以帮助开发人员像魔术一样快…

TemporalKit的纯手动安装

最近在用本地SD安装temporalkit插件 本地安装插件最常见的问题就是&#xff0c;GitCommandError:… 原因就是&#xff0c;没有科学上网&#xff0c;而且即使搭了ladder&#xff0c;在SD的“从网址上安装”或是“插件安装”都不行&#xff0c;都不行&#xff01;&#xff01;&am…

【JAVA】OPENGL+TIFF格式图片,不同阈值旋转效果

有些科学研究领域会用到一些TIFF格式图片&#xff0c;由于是多张图片相互渐变&#xff0c;看起来比较有意思&#xff1a; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.logging.*;/*** 可以自已定义日志打印格式…

窗体控件(表格和控制器)

DataGridView 控件 DataGridView控件是C#中的一个Windows Forms控件&#xff0c;用于在应用程序中显示和编辑表格形式的数据。 先拖出四个label控件和四个TextBox控件和一个ComboBox和一个Button按钮&#xff0c;下面是一个DataGridView控件 准备一个Student类 namespace _窗…

八大算法排序@堆排序(C语言版本)

目录 堆排序大堆排序概念算法思想建堆建堆核心算法建堆的代码 排序代码实现 小堆排序代码实现时间复杂度空间复杂度 特性总结 堆排序 堆排序借用的是堆的特性来实现排序功能的。大堆需要满足父节点大于子节点&#xff0c;因此堆顶是整个数组中的最大元素。小堆则相反&#xff0…

码农的周末日常---2024/1/6

上周总结 按照规划进行开发&#xff0c;处事不惊&#xff0c;稳稳前行 2024.1.6 天气晴 温度适宜 AM 睡觉前不建议做决定是真的&#xff0c;昨天想着睡到中午&#xff0c;今天九点多醒了&#xff0c;得了&#xff0c;不想睡了 日常三连吧&#xff0c;…

【mars3d】new mars3d.layer.GeoJsonLayer({实现多孔面遮罩mask: true,

【mars3d】new mars3d.layer.GeoJsonLayer({实现多孔面遮罩 官网测试示例&#xff1a; 1.功能示例(Vue版) | Mars3D三维可视化平台 | 火星科技 测试代码&#xff1a; export function showDraw(isFlyTo) { removeLayer() const geoJsonLayer new mars3d.layer.GeoJsonLaye…

神经网络-搭建小实战和Sequential的使用

CIFAR-10 model structure 通过已知参数&#xff08;高、宽、dilation1、kernel_size&#xff09;推断stride和padding的大小 网络 import torch from torch import nnclass Tudui(nn.Module):def __init__(self):super(Tudui, self).__init__()self.conv1 nn.Conv2d(in_chan…

TSConfig 配置(tsconfig.json)

详细总结一下TSConfig 的相关配置项。个人笔记&#xff0c;仅供参考&#xff0c;欢迎批评指正&#xff01; 另外&#xff0c;如果想了解更多ts相关知识&#xff0c;可以参考我的其他笔记&#xff1a; vue3ts开发干货笔记ts相关笔记&#xff08;基础必看&#xff09;ts相关笔记…

LeetCode-有效的字母异位词(242)

题目描述&#xff1a; 给定两个字符串 s 和 t &#xff0c;编写一个函数来判断 t 是否是 s 的字母异位词。 注意&#xff1a;若 s 和 t 中每个字符出现的次数都相同&#xff0c;则称 s 和 t 互为字母异位词。 思路&#xff1a; 这题还是比较简单的&#xff0c;首先将两个字符…

盖子的c++小课堂——第二十三讲:背包问题

前言 又是一次漫长的更新&#xff08;我真不是故意的aaaaaaaaaaaaaaa&#xff09;&#xff0c;先不多说了&#xff0c;直接给我~坐下~说错了说错了&#xff0c;直接开始~ 背包问题----动态规划 背包问题&#xff08;knapsack problem&#xff09; 动态规划&#xff08;dyna…

基于python的leetcode算法介绍之动态规划

文章目录 零 算法介绍一 例题介绍 使用最小花费爬楼梯问题分析 Leetcode例题与思路[118. 杨辉三角](https://leetcode.cn/problems/pascals-triangle/)解题思路题解 [53. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/)解题思路题解 [96. 不同的二叉搜索树](h…

企业出海数据合规:GDPR中的个人数据与非个人数据之区分

GDPR仅适用于个人数据&#xff0c;这意味着非个人数据不在其适用范围内。因此&#xff0c;个人数据的定义是一个至关重要的因素&#xff0c;因为它决定了处理数据的实体是否要遵守该法规对数据控制者规定的各种义务。尽管如此&#xff0c;什么是个人数据仍然是当前数据保护制度…

uniapp微信小程序投票系统实战 (SpringBoot2+vue3.2+element plus ) -小程序首页实现

锋哥原创的uniapp微信小程序投票系统实战&#xff1a; uniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )_哔哩哔哩_bilibiliuniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )共计21条视频…

Docker 镜像以及镜像分层

Docker 镜像以及镜像分层 1 什么是镜像2 Docker镜像加载原理2.1 UnionFs&#xff1a;联合文件系统2.2 Docker镜像加载原理2.3 Docker镜像的特点 3 镜像的分层结构4 可写的容器层 1 什么是镜像 镜像是一种轻量级、可执行的独立软件包&#xff0c;用来打包软件运行环境和基于运行…

数据处理四 基于图像hash进行数据整理(删除重复图片、基于模版查找图片)

一、背景知识 1.1 什么是hash Hash&#xff0c;一般翻译做“散列”&#xff0c;也有直接音译为“哈希”的&#xff0c;基本原理就是把任意长度的输入&#xff0c;通过Hash算法变成固定长度的输出。这个映射的规则就是对应的Hash算法&#xff0c;而原始数据映射后的二进制串就…

程序员必知!命令模式的实战应用与案例分析

命令模式是一种行为设计模式&#xff0c;它将请求封装为对象以实现客户端参数化、请求排队、日志记录及撤销操作&#xff0c;旨在解耦调用者与操作实现者&#xff0c;以智能家居为例&#xff0c;用户通过界面发送命令对象&#xff0c;设备作为接收者执行相应操作&#xff0c;无…