2024-04-14
Go & 后端
00

目录

1 HelloWorld
2 变量与内置数据类型
3 流程控制
4 函数
5 结构体、方法、接口
6 包,模块

记录Go语言的基础语法和基本知识。

Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。

1 HelloWorld

go
// helloWorld.go package main import { "fmt" } func main() { fmt.Println("Hello World!\n") }

注意,Go语言使用包来组织代码,一个包可以提供一些类型和方法来给其他包使用。

Go语言的唯一程序执行入口,只在main包里的main函数。

2 变量与内置数据类型

声明变量语法

go
var i int // 仅声明 var j int = 1 // 声明并且赋值 var k = 1 // 声明并且赋值 类型支持自动推导 l := 1 // 声明并且赋值

注意Go语言的类型声明在变量名后面。


简单类型

  • 空值:nil
  • 整型:int,uint8,uint16,uint32,int8,int16,int32...
  • 浮点型:float32,float64
  • 字节型:byte(等效uint8)
  • 字符串类型:string
  • 布尔型:boolean

字符串

在Go中,字符串类型变量一般使用UTF-8编码,并且使用uint8数组来保存字符,英文一个字符占8个字节,中文一般情况下占3个字节。所以在中英文混合的字符串中,如果打印字符串中中文的任意一个位置,大概率打出来也是乱码。这种情况下,如果需要打印其中的中文字符,需要将其转换为rune数组:

go
str2 := "Go语言" runeArr := []rune(str2) fmt.Println(reflect.TypeOf(runeArr[2]).Kind()) // 查看类型 int32 fmt.Println(runeArr[2], string(runeArr[2])) // 35821 语 fmt.Println("len(runeArr):", len(runeArr)) // len(runeArr): 4

转换为rune数组后,字符串中的每个字符都会被以int32来存储,因此可以正确处理中文。


数组、切片:

声明数组:

go
package main import "fmt" func main() { var array = [5]int{} // 声明一维int数组 var array2 = [5][2]int{} // 声明二维int数组 fmt.Println(array) // [0 0 0 0 0] fmt.Println(array2) // [[0 0] [0 0] [0 0] [0 0] [0 0]] var array3 = [5]int{1, 2, 3, 4, 5} // 声明并且赋值 fmt.Println(array3) // [1 2 3 4 5] array3[1] = 10 // 根据下标修改值 fmt.Println(array3) // [1 10 3 4 5] }

数组是定长的,如果想要像python的list一样灵活的数组,可以使用切片:

go
package main import "fmt" func main() { slice := make([]int, 5) // 声明一个长度为5的切片 slice2 := make([]int, 5, 10) // 声明一个长度为5 容量为10的切片 fmt.Println(slice) // [0 0 0 0 0] fmt.Println(slice2) // [0 0 0 0 0] // len函数访问长度 cap函数访问容量 fmt.Println(len(slice), cap(slice)) // 5 5 fmt.Println(len(slice2), cap(slice2)) // 5 5 }

一个切片有3个重要的部分:长度、容量、指向这个切片的指针。对于切片来说,无论是长度还是容量都可以随时扩展。

go
package main import "fmt" func main() { slice2 := make([]int, 5, 10) // 声明切片 slice2 = append(slice2, 1, 2, 3, 4, 5, 6, 7) // 为切片添加元素 fmt.Println(slice2) // [0 0 0 0 0 1 2 3 4 5 6 7] fmt.Println(len(slice2), cap(slice2)) // 12 20 sub1 := slice2[3:] // 为切片进行切片 sub2 := slice2[4:6] // 同上 combined := append(sub1, sub2...) // 合并切片 切片后面加... 是解包的写法 fmt.Println(combined) // [0 0 1 2 3 4 5 6 7 0 1] }

字典:

各方面同python的dict。

go
package main import "fmt" func main() { map1 := map[string]string{} // 声明 []里面是键类型 外面是值类型 fmt.Println(map1) // map[] map2 := map[string]int{ // 声明并且赋值 "test": 1, "test2": 2, } fmt.Println(map2) // map[test:1 test2:2] map2["test2"] = 3 // 赋值/修改值 fmt.Println(map2) // map[test:1 test2:3] }

指针:

对,就是那个指针。

go
package main import "fmt" func main() { str := "golang" var pointer *string = &str // 获取指针 指针类型就是原类型前面加星号 获取一个变量的指针使用& *pointer = "hello" // 在指针前加星号 等效这个指针指向的变量 fmt.Println(pointer) // 0xc000026070 fmt.Println(str) // hello }

函数参数会用指针。如果参数类型不是指针类型,那么实际传进去的是参数的副本,不影响参数本身;反之传入了指针的话,就能对这个变量直接进行修改。

3 流程控制

if-else:

go
age := 18 if age < 18 { fmt.Printf("Kid") } else { fmt.Printf("Adult") } // 可以简写为: if age := 18; age < 18 { // 赋值语句和判断语句在一起也没问题 fmt.Printf("Kid") } else { fmt.Printf("Adult") }

switch:

不需要break。如果需要向下执行下面的分支的话,可以加fallthrough关键字

go
package main import "fmt" func main() { a := 1 switch a { case 1: { // 这个冒号后面的大括号可以省略 fmt.Println("1") // 1 // 如果在这里加fallthrough关键字 那么下面分支都会被执行 } case 2: { fmt.Println("2") } default: { fmt.Println("default") } } }

for:

go
sum := 0 for i := 0; i < 10; i++ { if sum > 50 { break } sum += i }

Go还支持直接对数组,切片和字典进行遍历:

go
nums := []int{10, 20, 30, 40} for i, num := range nums { // 这个遍历方式好评 fmt.Println(i, num) } // 0 10 // 1 20 // 2 30 // 3 40 m2 := map[string]string{ "Sam": "Male", "Alice": "Female", } for key, value := range m2 { fmt.Println(key, value) } // Sam Male // Alice Female

4 函数

Go里面的函数支持多个返回值。一个简单的函数定义如下:

go
func test(i int, j int) (int, int) { // 先是参数 后是返回值 返回值只有一个的时候可以不要括号 也可以没有返回值 return i + j, i - j }

return也可以配合返回值这么写:

go
func test2 (i int) (a int){ // 直接在返回处定义一个变量 在函数内对这个变量进行操作 直接返回 a = i return }

异常处理:

Go的异常处理和其他语言有比较大的差异,反而和c语言可能有点像。

go
package main import ( "errors" "fmt" ) func main() { if j, e := test("1"); e != nil { // 如果函数运行正常 那么e就是nil 如果被赋值了 那就表示有异常 fmt.Println(e) } else { fmt.Println(j) } } func test(i string) (j string, e error) { // 返回值里除了正常返回的值 还有返回的异常 if len(i) < 2 { e = errors.New("string is too short") // 返回异常 return } j = i + "test" return // 正常返回 }

这种错误属于那种“可以被预估的”异常,还有一种不可预估的异常,会直接中断Go程序的运行,这被称为Panic。

go
package main import "fmt" func main() { fmt.Println(get(5)) // 数组越界 } func get(i int) int { array := [3]int{1, 2, 3} return array[i] } // 报错: // panic: runtime error: index out of range [5] with length 3 // goroutine 1 [running]: // main.get(...) // C:/Users/Misaka19327/OneDrive/Projects/GoLandProjects/Test/src/test2.go:11 // main.main() // C:/Users/Misaka19327/OneDrive/Projects/GoLandProjects/Test/src/test2.go:6 +0x18

与try-catch类似的,Go有defer和recover两个关键字用来处理Panic。

go
package main import "fmt" func main() { fmt.Println(get(5)) fmt.Println("Finished!") } func get(i int) (result int) { defer func() { // 异常处理函数 if r := recover(); r != nil { // r即为报错信息 fmt.Println(r) result = -1 // 直接返回-1 不继续执行 } }() // 这个函数声明之后必须马上调用 array := [3]int{1, 2, 3} return array[i] } // 运行结果: // runtime error: index out of range [5] with length 3 // -1 // Finished!

在程序运行中,如果触发了Panic,程序控制权会直接移交给defer函数,defer函数中可以使用recover函数来使程序恢复运行。

5 结构体、方法、接口

Go语言中没有特别明确的类的概念,可以通过结构体的方式来近似的实现类的功能。

go
package main import "fmt" type ClassTest struct { // 结构体当类用 property1 string property2 string } func (classTest *ClassTest) classMethodTest(param1 string) (result string) { // 类方法 其实它就是传入了一个结构体的指针来近似的实现类方法的效果 result = param1 + classTest.property1 return } func main() { classTest := &ClassTest{ // 实例化一个类(结构体) property1: "111", property2: "222", } classTest.property2 = "333" // 修改类(结构体)的成员变量 fmt.Println(classTest.property2) result := classTest.classMethodTest("1") // 调用方法 fmt.Println(result) } // 结果: // 333 // 1111

提示

注意,func (class *Class) classMethodfunc (class Class) classMethod两者并不等效。除了之前函数里说过的,传入的是指针和传入的是值本身的区别以外,前者只会被实例化的类的指针变量调用,后者只会被实例化的类的变量调用。只是Go语言做了优化,对于可寻址的被实例化的类的变量,依然可以调用前者,只是传入的仍然是指针。

对于上例来说,classTest.classMethodTest("1")实际执行的是*(classTest).classMethodTest("1")

这两种写法的函数(暂且叫作指针方法和值方法),大部分情况下都是指针方法更加常用,只有在类不算大且不对类的成员进行修改的情况下,可以创建值方法。

类还可以通过new函数来实例化:classTest2 = new(ClassTest),只是此时的实例内部的成员变量全部都是默认值。


接口:

定义了一组方法的集合,就能叫接口。这个接口不需要继承,直接实现就行。

go
package main import "fmt" type interfaceTest interface { // 定义接口 下面是两个方法和返回值 getAttr1() string getAttr2() string } type classFromInterface struct { // 定义类 实现接口 attr1 string attr2 string } func (c *classFromInterface) getAttr1() string { // 实现方法1 return c.attr1 } func (c *classFromInterface) getAttr2() string { // 实现方法2 return c.attr2 } func getClassFromInterface(attr1 string, attr2 string) (result *classFromInterface) { // 类的构造方法 返回一个指针 result = &classFromInterface{ attr2: attr2, attr1: attr1, } return } func main() { var class interfaceTest = getClassFromInterface("111", "222") // 获取类指针 fmt.Println(class.getAttr2()) // 调用接口方法 fmt.Println(class.getAttr1()) // 调用接口方法 }

提示

注意,如果不创建并且使用一个类,Go语言并不会检查这个类是否全部实现了一个接口。就上例来说,想检查这一点可以通过类似var _ interfaceTest = (*classFromInterface)(nil)来让Go在编译期就检查是否全部实现了接口。

这条语句的意思是,将空值转换为*classFromInterface类型,再转换为interfaceTest接口,如果转换失败,说明classFromInterface类并没有实现interfaceTest接口的所有方法。

接口和实例可以互相强制转换。


空接口:

如果一个接口里没有任何的方法,那么这个接口可以代表任意类型。

go
func main() { m := make(map[string]interface{}) m["name"] = "Tom" m["age"] = 18 m["scores"] = [3]int{98, 99, 85} fmt.Println(m) // map[age:18 name:Tom scores:[98 99 85]] }

6 包,模块

一般来说,一个文件夹即可作为一个包,包内的方法,变量,类,方法互相可见。如果想让一个方法/类对包外也可见,需要将其首字母大写。如果首字母小写,它就是一个类似private的状态。包之间互相调用也需要import。

本文作者:御坂19327号

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!