go语言语法重点
LYT
首页
分类
标签
项目
留言
友链
关于

go语言语法重点

2023年7月24日17时36分
NaN年NaN月NaN日NaN时NaN分
golang
golang
浏览量:
总浏览量:
0

Go 语言范围(Range)

  Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。

遍历切片

  使用range遍历切片时,需要传入两个值:key,value,这两个值分别代表了循环中的索引值元素值具体使用方式如下:

切片变量定义

// 结构体类型 type Person struct { name string age int } // 结构体切片变量 var ps []Person

使用value 和 key

  value和key可以换成其他名字,但是变量的顺序必须是key->value。

func Range1() { create(&ps) // range访问这个切片变量,key代表切片的索引,value代表切片的值 for key, value := range ps { fmt.Println(key, value, "###") } }

运行结果

0 {14} ### 1 {44} ### 2 {54} ### 3 {64} ### 4 {84} ### 6 {22} ### 7 {26} ###

只使用key

  只使用key可以省略value,也可以使用 _ 接收迭代器的值。

// 只使用key func Range2() { create(&ps) for key := range ps { fmt.Println(key) } } // 使用 _ 接收迭代器的值 func Range2(){ create(&ps) for key,_ := range ps { fmt.Println(key) } }

运行结果

0 1 2 3 4 6 7

只使用value

  此时不能省略key,需要用_接受迭代器的值。

// 只使用value func Range3() { create(&ps) for _, value := range ps { fmt.Println(value) } }

运行结果

{14} {44} {54} {64} {84} {21} {22} {26}

遍历map

  遍历map变量,key和value对应的就是map元素的键值对。

func Range4() { m := map[string]int{} m["一"] = 1 m["二"] = 2 m["三"] = 3 m["四"] = 4 for key := range m { fmt.Println(key) } }

运行结果

因为map里的元素是无序的,运行结果也是无序的。

Go 语言Map(集合)

  Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,遍历 Map 时返回的键值对的顺序是不确定的。

定义 Map

  可以使用内建函数 make 或使用 map 关键字来定义 Map:

/* 使用 make 函数 */ map_variable := make(map[KeyType]ValueType, initialCapacity)

访问map元素

  访问map元素的方式和访问数组、切片的方式类似,只不过把下表换成key

// 获取键值对 v1 := m["apple"] v2, ok := m["pear"] // 如果键不存在,ok 的值为 false,v2 的值为该类型的零值

删除map元素

// 删除键值对 delete(m, "banana")

Go语言接口(interface)

  Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。

实现接口

接口包

package Interface import "fmt" type Json interface { Read() Write() } type Admin struct { Name string } func (a Admin) Read() { fmt.Println(a.Name, "已阅读") } func (a Admin) Write() { fmt.Println(a.Name, "已写入") }

主函数

package main import ( "go-enhance/Interface" ) func main() { var a Interface.Admin = Interface.Admin{Name: "Lee"} var json Interface.Json = a json.Read() }

运行结果

Lee 已阅读

空接口

  空接口没有任何约束,任何类型的变量都可以实现空接口

package main import "fmt" type A interface{} func main() { var a A str := "你好,golang" a = str //str实现a接口 fmt.Println(a) }

  空接口可以直接当作一个类型

package main import "fmt" type A interface{} func main() { var a A str := "你好,golang" a = str //str实现a接口 fmt.Println(a) }

  空接口可以当作函数的参数

package main import "fmt" func goPrint(i interface{}) { fmt.Println(i) } func main() { a := "hello golang" goPrint(a) }

  切片、map类型的元素也可以是任意类型

package main import "fmt" func main() { var a = make(map[string]interface{}) a["zhi"] = "hello" a["wo"] = 12 a["mo"] = true a["hello"] = 2.2 fmt.Println(a) }

类型断言

  可以判断空interface的类型

package main import "fmt" func main() { var a interface{} = "hello" v, ok := a.(string) if ok { fmt.Println(a, "是一个string类型,值为", v) } else { fmt.Println("断言失败") } }

错误处理

  Go 语言通过内置的错误接口提供了非常简单的错误处理机制。error 类型是一个接口类型,这是它的定义:

type error interface { Error() string }

  我们可以在编码中通过实现 error 接口类型来生成错误信息。函数通常在最后的返回值中返回错误信息。使用 errors.New 可返回一个错误信息:

result, err:= Sqrt(-1) if err != nil { fmt.Println(err) }

Go 并发

  Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。

goroutine 语法格式:

go 函数名( 参数列表 )

  Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。

进程

  进程(Process) 就是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位,进程是一个动态概念,是程序在执行过程中分配和管理资源的基本单位,每一一个进程都有一个自己的地址空间。一个进程至少有5种基本状态,它们是:初始态,执行态,等待状态,就绪状态,终止状态。通俗的讲进程就是-一个正在执行的程序。

线程

  是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单位一个进程可以创建多个线程,同一个进程中的多个线程可以并发执行,一个程序要运行的话至少有一个进程。

关于并行和并发

  • 并发:多个线程同时竞争一一个位置,竞争到的才可以执行,每一个时间段只有一个线程在执行。

  • 并行:多个线程可以同时执行,每一一个时间段,可以有多个线程同时执行。通俗的讲多线程程序在单核CPU上面运行就是并发,多线程程序在多核CUP上运行就是并行,如果线程数大于CPU核数,则多线程程序在多个CPU上面运行既有并行又有并发

Golang中的协程(goroutine) 以及主线程

  golang中的主线程: ( 可以理解为线程/也可以理解为进程),在一个Golang程序的主线程上可以起多个协程。Golang 中多协程可以实现并行或者并发。协程:可以理解为用户级线程,这是对内核透明的,也就是系统并不知道有协程的存在,是完全由用户自己的程序进行调度的。Golang 的-大特色就是从语言层面原生支持协程,在函数或者方法前面加go关键字就可创建一个协程。可以说Golang中的协程就是goroutine。

示例代码

package main import ( "fmt" "time" ) func test() { for i := 0; i < 10; i++ { fmt.Println("你好golang-test") time.Sleep(time.Millisecond * 50) } } func main() { go test() //开启一个协程 用户级别 占用更小的内存 for i := 0; i < 10; i++ { fmt.Println("你好golang-main") time.Sleep(time.Millisecond * 50) } }

运行结果

  由运行结果可以看出,协程和主线程是同时运行的,运行顺序和代码当中的顺序没有联系了。

你好golang-main 你好golang-test 你好golang-test 你好golang-main 你好golang-test 你好golang-main 你好golang-main 你好golang-test 你好golang-test 你好golang-main 你好golang-main 你好golang-test 你好golang-test 你好golang-main 你好golang-main 你好golang-test 你好golang-main 你好golang-test 你好golang-test 你好golang-main

  如果主线程的任务执行的速度比协程的快,那么在主线程任务结束以后,协程就算没有完成也要停止执行。我们可以用sync.WaitGroup在主线程中等待协程的完成。

package main import ( "fmt" "sync" "time" ) var wg sync.WaitGroup func test() { for i := 0; i < 10; i++ { fmt.Println("你好golang-test") time.Sleep(time.Millisecond * 100) } wg.Done() //协程计数器-1 } func main() { wg.Add(1) //协程计数器+1 go test() //开启一个协程 用户级别 占用更小的内存 for i := 0; i < 10; i++ { fmt.Println("你好golang-main") time.Sleep(time.Millisecond * 50) } wg.Wait() fmt.Println("主线程退出") }

统计素数

顺序执行的方式

package main import ( "fmt" "time" ) func main() { start := time.Now().Unix() for i := 2; i < 120000; i++ { var flag = true for j := 2; j < i; j++ { if i%j == 0 { flag = false break } } if flag { // fmt.Println(i, "是素数") } } end := time.Now().Unix() fmt.Println("耗时", end-start) }

运行结果

耗时 4

使用协程执行的方式

package main import ( "fmt" "sync" "time" ) // 1 协程 统计 1-30000 // 2 协程 统计 30001-60000 // 3 协程 统计 60001-90000 // 4 协程 统计 90001-120000 // start:(n-1)*30000+1 end:n*30000 var wg sync.WaitGroup func test(n int) { for i := (n-1)*30000 + 1; i <= n*30000; i++ { if i > 1 { var flag = true for j := 2; j < i; j++ { if i%j == 0 { flag = false break } } if flag { // fmt.Println(i, "是素数") } } } wg.Done() } func main() { start := time.Now().Unix() for i := 1; i <= 4; i++ { wg.Add(1) go test(i) } wg.Wait() end := time.Now().Unix() fmt.Println("耗时", end-start) }

运行结果

耗时 2

go通道channel

  channel是一种类型, 一种引用类型。声明管道类型的格式如下:

var 变量 chan 元素类型 举几个例子: var ch1 chan int var ch2 chan bool var ch3 chan []int

创建channel

  声明的管道后需要使用make函数初始化之后才能使用。创建channel的格式如下:

make(chan元素类型,容量)

channel操作

  管道有发送(send)、接收(receive) 和关闭(close) 三种操作。发送和接收都使用<-符号。现在我们先使用以下语句定义一个管道:

ch := make(chan int, 3)

  发送(将数据放在管道内),将一个值发送到管道中。

ch<-10//把10发送到ch中
接收(从管道内取值)

从一个管道中接收值。

x :=<- ch //从th中接收值并赋值给变量 x<-ch //从ch中接收值,忽略结果

管道的一些基础操作

package main import "fmt" func main() { // 1.创建channel ch := make(chan int, 3) // 2.给管道里面存储数据 ch <- 10 ch <- 21 ch <- 32 // 3.获取管道里面的类型 a := <-ch <-ch b := <-ch fmt.Println(a, b) // 4.管道的长度和容量 fmt.Println("值", ch, "容量", cap(ch), "长度", len(ch)) }
管道的类型

  管道是引用类型,如果管道之间赋值,那么就是共用一个内存地址。

package main import "fmt" func main() { // 5.管道的类型 ch1 := make(chan int, 4) ch1 <- 32 ch1 <- 52 ch1 <- 35 ch2 := ch1 ch2 <- 88 <-ch1 <-ch1 <-ch1 d := <-ch1 fmt.Println(d) }
管道阻塞

  当管道里面没有数据的时候,再进行取数据会出现阻塞。

package main import "fmt" func main() { 管道阻塞 ch3 := make(chan int, 2) ch3 <- 24 ch3 <- 24 }

关闭管道

  循环遍历管道数据,使用for range遍历通道, 当通道被关闭的时候就会退出for range,如果没有关闭管道就会报个错误fatal error: a11。使用for循环遍历管道时可以不关闭管道

package main import "fmt" func main() { var ch1 = make(chan int, 10) for i := 0; i < 10; i++ { ch1 <- i } close(ch1) //关闭管道 // for range 循环遍历管道的值 for v := range ch1 { fmt.Println(v) } }

Goroutine 结合Channel管道

需求1:定义两个方法,-一个方法给管道里面写数据,一个给管道里面读取数据。要求同步进行。 1、开启一个fn1的的协程给向管道inChan中写入100条数据 2、开启一个fn2的协程读取inChan中写入的数据 3、注意:fn1和fn2同时操作一个管道 4、主线程必须等待操作完成后才可以退出

package main import ( "fmt" "sync" "time" ) var wg sync.WaitGroup func fn1(ch1 chan int) { for i := 0; i < 10; i++ { ch1 <- i fmt.Printf("写入数据%v成功\n", i) time.Sleep(time.Millisecond * 50) } close(ch1) wg.Done() } func fn2(ch1 chan int) { for v := range ch1 { fmt.Printf("读取数据%v成功\n", v) time.Sleep(time.Millisecond * 50) } wg.Done() } func main() { var ch = make(chan int, 10) wg.Add(2) go fn1(ch) go fn2(ch) wg.Wait() fmt.Println("退出") }

运行结果

  运行结果是并行的

写入数据0成功 读取数据0成功 写入数据1成功 写入数据2成功 读取数据1成功 读取数据2成功 写入数据3成功 写入数据4成功 读取数据3成功 写入数据5成功 读取数据4成功 读取数据5成功 写入数据6成功 写入数据7成功 读取数据6成功 读取数据7成功 写入数据8成功 写入数据9成功 读取数据8成功 读取数据9成功 退出

需求2:使用协程和管道结合的方式统计素数

  首先,在putNum函数中,它向intChan通道中放入了从2到119999的数字。然后它关闭了intChan通道,并通过wg.Done()告知main函数它执行完毕。接下来,在primeNum函数中,它从intChan通道中取出数字,并判断是否为素数。如果是素数,就将它放入primeChan通道中。最后,它通exitChan通道告知main函数它执行完毕。然后,在printPrime函数中,它从primeChan通道中取出数字并打印。但是在这段给出的代码中,这部分被注释掉了。在main函数中,它首先获取了当前时间,并创建了intChanprimeChanexitChan通道。然后,它通过wg.Add(1)增加了wg的计数器,启动了一个putNum的协程。接着,它通过一个循环启动了16个primeNum的协程。然后,增加了一个printPrime的协程和一个判断值是否满了的协程。最后,通过wg.Wait()等待所有协程执行完毕。最后,通过time.Now().Unix()获取当前时间,并计算出代码的执行时间。需要注意的是,在给出的代码中,printPrime函数的打印部分被注释掉了,所以不会有输出。如果要打印出素数,需要取消注释相应的代码。

package main import ( "fmt" "sync" "time" ) // 向intChan放入 1-120000个数 func putNum(intChan chan int) { for i := 2; i < 120000; i++ { intChan <- i } close(intChan) wg.Done() } // 从intChan取出数据,并判断是否为素数,如果是,就把得到的素数放在primeChan func primeNum(intChan chan int, primeChan chan int, exitChan chan bool) { for num := range intChan { flag := true for i := 2; i < num; i++ { if num%i == 0 { flag = false break } } if flag { primeChan <- num } } exitChan <- true wg.Done() } // 打印函数,从管道取出素数并打印 func printPrime(primeChan chan int) { // for v := range primeChan { // fmt.Println(v) // } wg.Done() } var wg sync.WaitGroup func main() { start := time.Now().Unix() intChan := make(chan int, 1000) primeChan := make(chan int, 60000) exitChan := make(chan bool, 16) //标识primeChan wg.Add(1) go putNum(intChan) for i := 0; i < 16; i++ { wg.Add(1) go primeNum(intChan, primeChan, exitChan) } // 打印素数的协程 wg.Add(1) go printPrime(primeChan) // 判断值是否满了 wg.Add(1) // go judgeChan(primeChan, exitChan) go func() { for i := 0; i < 16; i++ { <-exitChan } close(primeChan) wg.Done() }() wg.Wait() // 关闭primeChan通道 end := time.Now().Unix() fmt.Println("执行完毕,执行时间:", end-start) }

运行结果:

执行完毕,执行时间: 1

单向管道

package main import "fmt" func main() { // 在默认情况下,管道是双向的 ch1 := make(chan int, 2) ch1 <- 10 ch1 <- 12 m1 := <-ch1 m2 := <-ch1 fmt.Println(m1, m2) // 管道声明为只写 ch2 := make(chan<- int, 2) ch2 <- 10 ch2 <- 12 // <-ch2 报错invalid operation: cannot receive from send-only // 管道声明为只读 ch3 := make(<-chan int, 2) // ch3 <- 24 invalid operation: cannot send to receive-only }

文件操作

读文件

os.Open

  以e, err := os.Open(xxx)的方式读取一个文件,这种方式只能读取。

package main import ( "fmt" "io" "os" ) func main() { // 只读的方式打开当前目录下的文件 file, err := os.Open("./a.json") if err != nil { fmt.Println(err) return } defer file.Close() // 操作文件,读取文件里面的内容 var strSlice []byte var tempSlice = make([]byte, 128) for { n, err := file.Read(tempSlice) if err == io.EOF { fmt.Println("读取完毕") break } if err != nil { fmt.Println("读取失败") return } strSlice = append(strSlice, tempSlice[:n]...) } fmt.Println(string(strSlice)) }

bufio.NewReader

  以bufio.NewReader的方式读取文件

package main import ( "bufio" "fmt" "io" "os" ) func main() { file, err := os.Open("./a.json") if err != nil { fmt.Println(err) } defer file.Close() var fileStr string reader := bufio.NewReader(file) for { str, err := reader.ReadString('\n') //表示一次读一行 if err == io.EOF { break } if err != nil { fmt.Println(err) return } fileStr += str } fmt.Println(fileStr) }

ReadFile

  前两种方式都是通过的方式来读取文件,ReadFile却是封装好的包,开箱即用,代码要简洁得多。

package main import ( "fmt" "os" ) func main() { byteStr, err := os.ReadFile("./a.json") if err != nil { fmt.Println(err) return } fmt.Println(string(byteStr)) }

写文件

os.OpenFile()函数能够以指定模式打开文件,从而实现文件写入相关功能。

func OpenFile(name string, flag int, perm FileMode) (*File, error)
  • name:文件名字

  • flag:打开文件的模式

| 模式 | 含义 | |:------------:|:----:| | OS.Cj_WRONLY | 只写 | | os.O_ CREATE | 创建文件 | | Os.O_ RDONLY | 只读 | | os.O_ RDWR | 读写 | | os.O_ TRUNC | 清空 | | os.O_ APPEND | 追加 |

  • perm:文件权限,一个八进制数。(读)04,w(写)02,X(执行)01。

通过WriteString写入

示例代码:

package main import ( "fmt" "os" ) func main() { file, err := os.OpenFile("./a.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { fmt.Println(err) return } defer file.Close() // 写入文件 for i := 0; i < 10; i++ { file.WriteString("直接写入字符串数据\r\n") } }

通过Write写入

  通过这种方式写入的时候需要把字符串转换成byte切片类型。

package main import ( "bufio" "fmt" "os" ) func main() { file, err := os.OpenFile("./a.txt", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666) if err != nil { fmt.Println(err) return } defer file.Close() writer := bufio.NewWriter(file) // 写入文件 for i := 0; i < 10; i++ { writer.Write([]byte("你好golang\r\n")) } writer.Flush() }