[ GO语言基础语法 | 青训营笔记 ]

99 阅读3分钟

👨环境配置

ubuntu16.04

go1.20.4.linux-amd64.tar.gz

vscode

  • remote -ssh插件,用于连接linux
  • Go v0.38.0插件,用于代码提示

😄详细配置过程🍻

  • 直接解压包到/usr/local

     tar -zxvf go1.20.4.linux-amd64.tar.gz -C /usr/local
    
  • 配置环境变量使得可以在命令行直接输入go而不是/usr/local/go/bin/go

     vim ~/.bashrc
     # 在最后一行添加 export PATH=$PATH:/usr/local/go/bin
     source /home/ljq/.bashrc  # 加载使其生效
    
  • 配置go env的环境

     go env -w GO111MODULE=on
     go env -w GOPROXY=https://goproxy.cn,direct
     go env -w GOSUMDB=sum.golang.google.cn
    
    • 该命令将 GO111MODULE 环境变量设置为"on",启用 Go 模块支持。
    • 设置 Go 模块的代理服务器地址,使其可以加速依赖包的下载,并解决一些境内无法访问的问题
    • 改了这个环境字段后我vscodego插件才能install all

🐺 go模块支持

这是go内置的最好的项目管理模块

go mod简单使用

  • go mod init

    创建一个.go文件前就在其目录下面go mod init以下,比如我要创建一个包含main函数的hello.go

     go mod init github.com/luopanforever/hello
    

    在当前目录下就会创建一个go.mod文件,hello.go文件内容如下

     package main
     ​
     import "fmt"
     ​
     func main() {
         fmt.Println("Hello, World!")
     }
    

    没有以来其他的库所以直接go run hello.go即可,可以看见输出.

  • go mod tidy

    此时想要调用外部包的代码,修改hello.go为如下

     package main
     ​
     import "fmt"
     ​
     import "rsc.io/quote"
     ​
     func main() {
         fmt.Println(quote.Go())
     }
    

    此时调用了其他库的包,直接go run肯定是不行 不信你试试🐇

    此时我们的mod管理工具就起作用了,直接调用go mod tidy,如果卡了则换代理go env -w GOPROXY=https://goproxy.cn,direct,此时go.mod中就会出现一行新的东西,和目录下面一个新文件go.sum,酱紫过后再次go run就可以了

go mod命令大全😄

 go mod init        initialize new module in current directory 在当前目录初始化mod
 ​
 go mod tidy //拉取缺少的模块,移除不用的模块。
 ​
 go mod download //下载依赖包
 ​
 go mod vendor //将依赖复制到vendor下
 ​
 go mod verify //校验依赖
 ​
 go list -m -json all //依赖详情
 ​
 go mod graph //打印模块依赖图
 ​
 go mod why //解释为什么需要依赖

命名要求🤙

  • 包含main函数的.go文件其package后面要跟main,其他的(不包含main函数的)就跟目录名一样就行
  • 文件名随意取

基础语法🍰

go变量与常量

1.Go语言的变量声明格式为:

 var 变量名 变量类型
 ​
 var name string
 var age int
 ​
 // 批量声明
 var (
     name string
     age int
     hight float32
 )

2.初始化格式

  • 声明初始化(显示类型)

    var name string = "luopanforever"

  • 声明初始化(类型隐式推导)

    var name = "luopanforever"

  • 声明初始化(短变量声明)> 只能在函数内部使用

    name := "string"

3.匿名变量

匿名变量用一个下划线_表示

 func foo() (int, string) {
     return 10, "Q1mi"
 }
 func main() {
     x, _ := foo()
     _, y := foo()
     fmt.Println("x=", x)
     fmt.Println("y=", y)
 }

匿名变量不占用命名空间,不会分配内存,匿名变量之间不存在重复声明。

4.常量

var换成了const常量在定义的时候必须赋值

const pi = 3.1415

const同时声明多个常量时,如果省略了值则表示和上面一行的值相同

 const (
     n1 = 100
     n2
     n3
 )

5.iota

常用于定义枚举

const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)

 const (
     n1 = iota //0
     n2        //1
     n3        //2
     n4        //3
 )

特殊使用

  • 使用_跳过某些值

     const (
         n1 = iota //0
         n2        //1
         _
         n4        //3
     )
    
  • iota声明中间插队

     const (
         n1 = iota //0
         n2 = 100  //100
         n3 = iota //2
         n4        //3
     )
     const n5 = iota //0
    

应用描述内存单位

const (
    _  = iota
    KB = 1 << (10 * iota)  // 2^10
    MB = 1 << (10 * iota)  // 2^20
    GB = 1 << (10 * iota)  // 2^30
    TB = 1 << (10 * iota)  // 2^40
    PB = 1 << (10 * iota)  // 2^50
)

6.注意事项

  • 函数外的每个语句都必须以关键字开始(var、const、func等)
  • :=不能使用在函数外。
  • _多用于占位,表示忽略值。

go基础数据结构类型

编程的时候需要用大数据的时候才需要申请大内存,这样就可以充分利用内存。

  • bool(布尔)

  • 数值类型

    • int8、int16、int32、int64、int
    • uint8、uint16、uint32、uint64、uint
    • float32, float64
    • complex64, complex128
    • byte
    • rune
  • string

1.布尔类型

 // 短类型声明
 var isExit := true
 // 或
 var isExit bool = false

2.字符串类型

字符串是 Go 中字节的集合

 var say = "hello" //单行字符串
 var tag = """ // 转义符
 ​
 var say = `hello` //原样输出
 var mLine = `line1  //多行输出
 line2
 line3
 `
 ​
 var str = "hello, 世界"

3.数字类型

取值范围

 uint8          // 所有无符号8位整数的集合  (0 to 255)
 uint16         // 所有无符号16位整数的集合 (0 to 65535)
 uint32         // 所有无符号32位整数的集合 (0 to 4294967295)
 uint64         // 所有无符号64位整数的集合 (0 to 18446744073709551615)
 ​
 int8           // 所有带符号8位整数的集合  (-128 to 127)
 int16          // 所有带符号16位整数的集合 (-32768 to 32767)
 int32          // 所有带符号32位整数的集合 (-2147483648 to 2147483647)
 int64          // 所有带符号64位整数的集合 (-9223372036854775808 to 9223372036854775807)
 ​
 float32        // 所有 IEEE-754 32 位浮点数的集合
 float64        // 所有 IEEE-754 64 位浮点数的集合
 ​
 complex64      // 浮点实数和虚数为32的所有复数的集合
 complex128     // 浮点实数和虚数为64的所有复数的集合

4.类型转换

Go对显式类型非常严格。没有自动类型提升或转换。

互加

 i := 55      // int
 j := 67.8    // float64
 sum := i + j // int类型 + float类型是不被允许的
  • 编译失败。go不允许隐式类型转换

    改正

    sum := i + int(j) // j被转到了int类型
    

赋值

 i := 10
 var j float64 = float64(i) // 没有显示转换将会报错

循环与条件判断⚪️

ifswitchfor 来进行条件判断和流程控制

forGo 中唯一可用的循环。Go没有其他语言(如 C)中存在的whiledo while循环。

for格式

 for initialisation; condition; post {
 }

if格式

 if 条件 {
   # 业务代码
 }
 ​
 if 条件 {
   # 业务代码
 } else if {
   # 业务代码
 } else {
   # 业务代码  
 }

switch格式

无需break,在 Goswitch 只要匹配中了就会中止剩余的匹配项

 age := 10
 ​
 switch age {
     case 5, 7, 10:
         fmt.Println("The age is 5 7 10")
     case 11:
         fmt.Println("The age is 11")
     default:
         fmt.Println("The age is unkown")
 }

也可以使用bool表达式

 age := 7
 ​
 switch {
     case age >= 6 && age <= 12:
         fmt.Println("It's primary school")
     case age >= 13 && age <= 15:
         fmt.Println("It's middle school")
     case age >= 16 && age <= 18:
         fmt.Println("It's high school")
     default:
         fmt.Println("The age is unkown")
 }

高级数据类型

1.数组

1)声明

声明方式

var a [3]int👈

数组中的所有元素都被自动赋值为数组类型的零值

简式声明并赋

a := [3]int{8, 18, 88}👈

  • 忽略长度 使用...代替让编译器完成操作

    a := [...]int{8, 18, 88}

2)数组是值类型

当数组赋值给一个新的变量时,该变量会得到一个原始数组的一个副本(可以按照深拷贝来理解)

如果对新变量进行更改,则不会影响原始数组

3)数组的遍历

  • 普通for循环

     for i:= 0; i < len(a); i++{}
    
  • range for循环

     for i, v := range a {}
    
    • range返回索引和该索引位置的值

4)多维数组

声明

 var a = [3][2]string{  // or a:=  or var a [3][2]string = 
     {"lion", "tiger"},
     {"cat", "dog"},
     {"pigeon", "peacock"}, 
 }

2.切片

切片是数组一个连续片段的引用,引用的这个数组被称为相关数组,通常是匿名

切片是一个长度可变的数组

1)创建切片

  • 对数组进行切片创建切片

     a := [5]int{76, 77, 78, 79, 80}
     var b []int = a[1:4]  // len 3 cap 4
    
  • 在右边空出[]来创建切片

     var c []int = []int{6, 7, 8}  // len 3 cap 3
    

2)切片的修改

切片自己不拥有任何数据。它只是底层数组的一种表示。对切片所做的任何修改都会反映在底层数组中。

 darr := [...]int{57, 89, 90, 82, 100, 78, 67, 69, 59}
 dslice := darr[2:5]
  • dslice修改darr的对应索引的值也会修改
  • c选手可以理解为切片是一个数组指针,并且其有超出索引保护

3)切片的长度和容量

切片的长度是切片中的元素数。

切片的容量是从创建切片索引开始的底层数组中元素数。

 a := [...]string{"1", "2", "3", "4", "5", "6", "7"}
 b := a[1:3]
 fmt.Printf("length of slice %d capacity %d\n", len(b), cap(b))   // len = 2  cap = 6
 fmt.Println(len(a))  // len = 7
  • len6:容量是从创建切片索引开始的底层数组中元素数

切片可以重置其长度len

b = b[:cap(b)]

  • 之后切片的lencap就相等了
  • 索引超过len是不被允许的,必须重置len

4)使用make创建一个切片

函数原型: func make([]T,len,cap)[]T

 var i []int = make([]int, 5, 5)

5)追加切片长度

函数原型: func append(s[]T,x ... T) []T

 a := []string{"1", "2", "3"}
 fmt.Println("old length", len(a), "and capacity", cap(a)) 
 a = append(a, "4")
 fmt.Println("new length", len(a), "and capacity", cap(a))
  • 当新的元素被添加到切片时,会创建一个新的数组。现有数组的元素被复制到这个新数组中,并返回这个新数组的新切片引用。现在新切片的容量旧切片的两倍

    • 由于是返回的新数组,故没有指向原来的a,则对后面新的切片的改变会不改变原数组了

6)切片的函数传递

函数的的切片形参与切片实参都是指向同一个底层数组

二维切片的创建

 pls := [][]string {
     {"C", "C++"},
     {"Java"},
     {"Go", "Rust"},
 }
 // make来创建
 matrix := make([][]int, rows)
 for i := range matrix {
     matrix[i] = make([]int, cols)
 }

3.map

1)创建map

  • 使用关键字 map 来声明

     a := map[string]int{"ljq" :001, "psy":002}
    
  • 使用make

     cMap := make(map[string]int)
    

2)添加元素到map

  • 数组形式添加

    cMap["cc"] = 003

  • 创建时指定 a := map[string]int{"ljq" :001, "psy":002}

3)不能向未初始化的map添加元素

Map在没有初始化时默认为nil

必须在添加元素之前初始化 Map

4)检查键是否存在

value, ok := map[key]

如果ok为真,则该键存在且其值存在于变量value中,否则该键不存在

5)遍历

需要注意的是map是无序的

  • range返回key-value

     for key, value := range cities{}
    

6)删除键值对

delete(map, key)

尝试删除 Map中不存在的键,不会出现运行时错误

7)线程不安全

线程不安全, 一个goroutine在对map进行写的时候,另外的 goroutine不能进行读和写操作,Go 1.6版本以后会抛出 runtime 错误信息

4.string

字符串是 Go 中的字节切片。可以通过将一组字符括在双引号中来创建字符串" "

函数

 func name(parameter) (result-list){
     //body
 }

命名返回值

 func plus(a, b int) (res int){
     res = a + b  
     return // res  可写可不写
 }

参数可变函数

 func sum(nums ...int)int{
     fmt.Println("len of nums is : ", len(nums))
     res := 0
     for _, v := range nums{
         res += v
     }
     return res
 }
  • 通过len来获取个数

匿名函数

 func(name string){
        fmt.Println(name)
     }("https://www.gotribe.cn")

没有函数名,声明了马上要传参运行

指针

go的指针作用很有限,仅为了传参时函数能够改变实参的值

使用new函数创建指针

new函数将一个类型作为参数并返回一个指针,该指针指向作为参数传递的类型的新分配的空值

函数能够返回局部变量的指针

 func hello() *int {  
     i := 5
     return &i
 }
  • Go编译器足够智能,它会在堆上分配这个变量
  • 此代码的行为在 CC++等编程语言中是未定义的,因为一旦函数返回,变量i就会超出范围。但是在 Go 的情况下,编译器会进行转义分析,并·在地址转义本地范围时在堆上进行分配。

不支持指针运算

 b := [...]int{109, 110, 111}
 p := &b
 p++
  • 上述抛出编译错误

结构体

声明一个结构体

 type StructName struct{
     FieldName type
 }
 ​
 type Student struct {
     Age     int
     Name    string
 }
 ​
 func main(){
     stu := Student{
         Age:     18,
         Name:    "name",
     }
 }

创建匿名结构体

 emp3 := struct {
     firstName string
     lastName  string
     age       int
     salary    int
 }{
     firstName: "Andreah",
     lastName:  "Nikola",
     age:       31,
     salary:    5000,
 }
  
 fmt.Println("Employee 3", emp3)

结构体的指针

 var emp8 *Employee = &Employee{
     firstName: "Sam",
     lastName:  "Anderson",
     age:       55,
     salary:    6000,
 }
  • 使用emp8.字段(*emp8).字段访问是一样的

匿名字段

 type Person struct {  
     string
     int
 }
 ​
 func main(){
     p1 := Person{
         string: "naveen",
         int:    50,
     }
     fmt.Println(p1.string)
     fmt.Println(p1.int)
 }

方法

方法(method)的声明和函数很相似, 它必须指定接收者

注意:

  • 接收者的类型只能为用关键字 type 定义的类型,例如自定义类型,结构体。
  • 同一个接收者的方法名不能重复 (没有重载),如果是结构体,方法名还不能和字段名重复。
  • 值作为接收者无法修改其值,如果有更改需求,需要使用指针类型。
 type Employee struct {  
     name     string
     salary   int
     currency string
 }
  
 func (e *Employee) displaySalary() {  
     fmt.Printf("Salary of %s is %s%d", e.name, e.currency, e.salary)
 }
 ​
 func main() {  
     emp1 := Employee {
         name:     "Sam Adolf",
         salary:   5000,
         currency: "$",
     }
     emp1.displaySalary()
 }

接口

声明与实现接口

 type interface_name interface {
    method_name1 [return_type]
    method_name2 [return_type]
    method_name3 [return_type]
    ...
    method_namen [return_type]
 }

小例子

  • 声明一个接口,里面包含一个方法

     type VowelsFinder interface {  
         FindVowels() []rune
     }
    
  • string封装为自定义类型,因为方法的接收者的类型只能是自定义类型

     type MyString string
    
  • MyString实现其接口的方法

     func (ms MyString) FindVowels() []rune {  
         var vowels []rune
         for _, rune := range ms {
             if rune == 'a' || rune == 'e' || rune == 'i' || rune == 'o' || rune == 'u' {
                 vowels = append(vowels, rune)
             }
         }
         return vowels
     }
    

上述MyString实现了VowelsFinder接口的所有方法,所以接口变量可以被MyString变量赋值

 func main(){
     name := MyString("Sam Anderson")
     var v VowelsFinder
     v = name 
     fmt.Printf("Vowels are %c", v.FindVowels())
 }

空接口

空接口里面没有任何方法,所以可以当作所有类型都实现了它,于是它可以被所有类型赋值

  • 可用在函数的形参中

     func describe(i interface{}){}
    

类型断言

 func assert(i interface{}) {  
     s, ok := i.(int) //get the underlying int value from i
     fmt.Println(s, ok)
 }
 func main() {  
     var s interface{} = 56
     assert(s)
 }

协程

Go是一种并发语言,Go 中使用 Goroutinechannel 处理并发。

如何启动一个 Goroutine

 func hello() {  
     fmt.Println("Hello world goroutine")
 }
 func main() {  
     go hello()
     fmt.Println("main function")
 }
  • 当一个新的 Goroutine 启动时,goroutine调用立即返回。与函数不同,主程不等待 Goroutine完成执行。在 Goroutine 调用之后,主程立即返回到下一行代码,并且 Goroutine 的任何返回值都将被忽略。
  • Goroutine 应该正在运行以供任何其他 Goroutines运行。如果主Goroutine 终止,则程序将终止,并且不会运行其他 Goroutine
 go hello()
 time.Sleep(1 * time.Second)
 fmt.Println("main function")
  • 这样以来协程就有足够的时间在主 Goroutine 终止之前执行

Channel

Channels可以被认为是 Goroutine 进行通信的管道。类似于水在管道中从一端流向另一端的方式,数据可以从一端发送并使用Channel从另一端接收。

声明Channel

var a chan T

  • channel的零值为nilnilChannels没有任何用处,因此必须使用make类似于mapsslices来定义Channel

     if a == nil {
         fmt.Println("channel a is nil, going to define it")
         a = make(chan int)
         fmt.Printf("Type of a is %T", a)
     }
    
  • 简短定义

     a := make(chan int)
    

channels发送和接受

 data := <- a // read from channel a  
 a <- data // write to channel a  
  • 在第一行中,箭头指向外a,因此我们从Channels读取a并将值存储到变量data中。
  • 在第二行中,箭头指向a,因此我们正在写入Channel
 func hello(done chan bool) {  
     fmt.Println("Hello world goroutine")
     time.Sleep(1 * time.Second)
     done <- true
 }
 func main() {  
     done := make(chan bool)
     go hello(done)
     <-done  // 没有使用数据  这是合法的
     fmt.Println("main function")
 }

单向channel

使用sendch关键字并将chan T改写为chan <- T

sendch chan <- T

  • 可以用于协程入口函数的参数讲双向信道转化为单向的

Buffer Channels

通过将附加容量参数传递给make指定缓冲区大小的函数来创建缓冲通道。

  • 仅当缓冲区已满时,才会阻止发送到缓冲通道。
  • 只有当缓冲区为空时,来自缓冲通道的接收才会被阻塞。
 ch := make(chan type, capacity)

select语句

select语句用于从多个send/receive通道操作中进行选择。select语句会阻塞,直到其中一个发送/接收操作准备好。

 func server1(ch chan string) {  
     time.Sleep(6 * time.Second)
     ch <- "from server1"
 }
 func server2(ch chan string) {  
     time.Sleep(3 * time.Second)
     ch <- "from server2"
  
 }
 ​
 func main() {  
     output1 := make(chan string)
     output2 := make(chan string)
     go server1(output1)
     go server2(output2)
     select {
     case s1 := <-output1:
         fmt.Println(s1)
     case s2 := <-output2:
         fmt.Println(s2)
     }
 }
  • 最终打印"from server2"

互斥锁

 import "main"
 ​
 /*
 锁的操作
   - m.Lock();
   - m.Unlock();
 */
 ​
 func main(){
     var m sync.Mutex
     
 }

文件操作

读文件

在包"io/ioutil"

 data, err := ioutil.ReadFile("test.txt")
 fmt.Println(string(data))
  • data的类型为[]uint8

写文件

 import "os"
 ​
 f, err := os.Create("test.txt") //create file
 l, err := f.WriteString("Hello World") // writing file
  • 先打开文件得到文件描述符
  • 用文件描述符读取文件,返回读取到的字节与错误信息