记录Go语言的基础语法和基本知识。
Go语言是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。
go// helloWorld.go
package main
import {
"fmt"
}
func main() {
fmt.Println("Hello World!\n")
}
注意,Go语言使用包来组织代码,一个包可以提供一些类型和方法来给其他包使用。
Go语言的唯一程序执行入口,只在main包里的main函数。
声明变量语法:
govar i int // 仅声明
var j int = 1 // 声明并且赋值
var k = 1 // 声明并且赋值 类型支持自动推导
l := 1 // 声明并且赋值
注意Go语言的类型声明在变量名后面。
简单类型:
字符串:
在Go中,字符串类型变量一般使用UTF-8编码,并且使用uint8数组来保存字符,英文一个字符占8个字节,中文一般情况下占3个字节。所以在中英文混合的字符串中,如果打印字符串中中文的任意一个位置,大概率打出来也是乱码。这种情况下,如果需要打印其中的中文字符,需要将其转换为rune数组:
gostr2 := "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来存储,因此可以正确处理中文。
数组、切片:
声明数组:
gopackage 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一样灵活的数组,可以使用切片:
gopackage 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个重要的部分:长度、容量、指向这个切片的指针。对于切片来说,无论是长度还是容量都可以随时扩展。
gopackage 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。
gopackage 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]
}
指针:
对,就是那个指针。
gopackage main
import "fmt"
func main() {
str := "golang"
var pointer *string = &str // 获取指针 指针类型就是原类型前面加星号 获取一个变量的指针使用&
*pointer = "hello" // 在指针前加星号 等效这个指针指向的变量
fmt.Println(pointer) // 0xc000026070
fmt.Println(str) // hello
}
函数参数会用指针。如果参数类型不是指针类型,那么实际传进去的是参数的副本,不影响参数本身;反之传入了指针的话,就能对这个变量直接进行修改。
if-else:
goage := 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关键字
gopackage 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:
gosum := 0
for i := 0; i < 10; i++ {
if sum > 50 {
break
}
sum += i
}
Go还支持直接对数组,切片和字典进行遍历:
gonums := []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
Go里面的函数支持多个返回值。一个简单的函数定义如下:
gofunc test(i int, j int) (int, int) { // 先是参数 后是返回值 返回值只有一个的时候可以不要括号 也可以没有返回值
return i + j, i - j
}
return也可以配合返回值这么写:
gofunc test2 (i int) (a int){ // 直接在返回处定义一个变量 在函数内对这个变量进行操作 直接返回
a = i
return
}
异常处理:
Go的异常处理和其他语言有比较大的差异,反而和c语言可能有点像。
gopackage 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。
gopackage 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。
gopackage 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函数来使程序恢复运行。
Go语言中没有特别明确的类的概念,可以通过结构体的方式来近似的实现类的功能。
gopackage 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) classMethod
和func (class Class) classMethod
两者并不等效。除了之前函数里说过的,传入的是指针和传入的是值本身的区别以外,前者只会被实例化的类的指针变量调用,后者只会被实例化的类的变量调用。只是Go语言做了优化,对于可寻址的被实例化的类的变量,依然可以调用前者,只是传入的仍然是指针。
对于上例来说,classTest.classMethodTest("1")
实际执行的是*(classTest).classMethodTest("1")
。
这两种写法的函数(暂且叫作指针方法和值方法),大部分情况下都是指针方法更加常用,只有在类不算大且不对类的成员进行修改的情况下,可以创建值方法。
类还可以通过new函数来实例化:classTest2 = new(ClassTest)
,只是此时的实例内部的成员变量全部都是默认值。
接口:
定义了一组方法的集合,就能叫接口。这个接口不需要继承,直接实现就行。
gopackage 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接口的所有方法。
接口和实例可以互相强制转换。
空接口:
如果一个接口里没有任何的方法,那么这个接口可以代表任意类型。
gofunc 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]]
}
一般来说,一个文件夹即可作为一个包,包内的方法,变量,类,方法互相可见。如果想让一个方法/类对包外也可见,需要将其首字母大写。如果首字母小写,它就是一个类似private的状态。包之间互相调用也需要import。
本文作者:御坂19327号
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!