golang]语言基础(golang编译原理)
Go 基础
内置基础类型
数值类型
rune
,int8
,int16
,int32
,int64
和 byte
,uint8
,uint16
,uint32
,uint64
,其中 rune
是 int32
的别称,byte
是 uint8
的别称。
浮点数 的类型有 float32
和 float64
两种(没有 float
类型)。
复数 的类型有 complex128
(64 位实数 + 64 位虚数)和 complex64
(32 位实数 + 32 位虚数)。复数的形式为 RE + IMi
,其中 RE
是实数部分,IM
是虚数部分,而最后的 i
是虚数单位。
var c complex64 = 5 + 5i // output: (5+5i) fmt.Printf("Value is: %v", c) 复制代码
string
在 Go 中字符串是不可变的,但如果真的想要修改怎么办呢?
s := "hello" c := []byte(s) // 将字符串 s 转换为 []byte 类型 c[0] = 'c' s2 := string(c) // 再转换回 string 类型 fmt.Printf("%s\n", s2) 复制代码
数组
数组之间的赋值是值的赋值,即当把一个数组作为参数传入函数的时候,传入的其实是该数组的副本,而不是它的指针。
var variable_name [SIZE] variable_type var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0} balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0} balance := [5]float32{1:2.0,3:7.0} 复制代码
多维数组
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type 复制代码
func main() { // Step 1:创建数组 values := [][]int{} // Step 2:使用 appped() 函数向空的二维数组添加两行一维数组 row1 := []int{1, 2, 3} row2 := []int{4, 5, 6} values = append(values, row1) values = append(values, row2) // Step 3:显示两行数据 fmt.Println("Row 1") fmt.Println(values[0]) fmt.Println("Row 2") fmt.Println(values[1]) // Step 4:访问第一个元素 fmt.Println("第一个元素为:") fmt.Println(values[0][0]) } 复制代码
slice
slice
是引用类型,所以当引用改变其中元素的值时,其它的所有引用都会改变该值。
slice
是一个结构体,这个结构体包含了三个元素:
引用数组指针地址;
切片的目前使用长度;
切片的容量;
// 默认是 nil var identifier []type s := make([]int, len, cap) y := s[low:high:max] 复制代码
func main() { s := []int{1, 2, 3} a := s[0:1] s[0] = 888 fmt.Printf("a: %p\n", a) fmt.Println("a", a, len(a), cap(a)) fmt.Printf("s: %p\n", s) fmt.Println("s", s, len(s), cap(s)) s = append(s, 4) fmt.Println("扩容后") s[0] = 666 fmt.Printf("a: %p\n", a) fmt.Println("a", a, len(a), cap(a)) fmt.Printf("s: %p\n", s) fmt.Println("s", s, len(s), cap(s)) } 复制代码
a: 0xc00011a000 a [888] 1 3 s: 0xc00011a000 s [888 2 3] 3 3 扩容后 a: 0xc00011a000 a [888] 1 3 s: 0xc000120000 s [666 2 3 4] 4 6 复制代码
func main() { var ( arr = [3]int{1, 2, 3} slice = []int{1, 2, 3} ) changeArr(&arr) changeSlice(slice) fmt.Println("arr", arr) fmt.Println("slice", slice) } func changeArr(arr *[3]int) { arr[0] = 100 } func changeSlice(slice []int) { slice[0] = 100 } 复制代码
arr [100 2 3] slice [100 2 3] 复制代码
map
// 默认是 nil,nil map 不能用来存放键值对 var map_variable map[key_data_type]value_data_type map_variable := make(map[key_data_type]value_data_type) 复制代码
make & new
make
只能创建 slice
、map
和 channel
,并且返回一个有初始值(非零)的 T
类型,而不是 *T
。本质来讲,导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被初始化。例如,一个 slice
,是一个包含指向数据(内部 array
)的指针、长度和容量的三项描述符;在这些项目被初始化之前,slice
为 nil
。对于 slice
、map
和 channel
来说,make
初始化了内部的数据结构,填充适当的值。
内建函数 new
本质上说跟其它语言中的同名函数功能一样,new(T)
分配了零值填充 T
类型的内存空间,并且返回其地址,即一个 *T
类型的值。用 Go 的术语说,它返回了一个指针,指向新分配的类型 T
的零值。
iota
常量中的数据类型只可以是布尔型、数值型(整数型、浮点型和复数)和字符串型。
const ( a = "abc" b = len(a) c = unsafe.Sizeof(a) ) const ( a = iota // 0 b // 1 c // 2 d = "ha" // 独立值,iota += 1 e // "ha",iota += 1 f = 100 // iota += 1 g // 100,iota +=1 h = iota // 7,恢复计数 i // 8 ) const ( h, i, j = iota, iota, iota // h = 0、i = 0、j = 0,因为 iota 在同一行 ) 复制代码
error
err := errors.New("emit macho dwarf: elf header corrupted") if err != nil { fmt.Print(err) } 复制代码
type User struct { username string password string } func (p *User) init(username string, password string) (*User, string) { if "" == username || "" == password { return p, p.Error() } p.username = username p.password = password return p, "" } func (p *User) Error() string { return "Usernam or password shouldn't be empty!" } func main() { var user User user1, _ := user.init("", "") fmt.Println(user1) } 复制代码
Usernam or password shouldn't be empty! 复制代码
流程和函数
流程控制
switch
func main() { switch { case false: fmt.Println("1、case 条件语句为 false") fallthrough case true: fmt.Println("2、case 条件语句为 true") fallthrough case false: fmt.Println("3、case 条件语句为 false") fallthrough case true: fmt.Println("4、case 条件语句为 true") case false: fmt.Println("5、case 条件语句为 false") fallthrough default: fmt.Println("6、默认 case") } } 复制代码
2、case 条件语句为 true 3、case 条件语句为 false 4、case 条件语句为 true 复制代码
for
sum := 1 for sum < 1000 { sum += sum } 复制代码
break / continue
func main() { // 不使用标记 fmt.Println("---- break ----") for i := 1; i <= 3; i++ { fmt.Printf("i: %d\n", i) for i2 := 11; i2 <= 13; i2++ { fmt.Printf("i2: %d\n", i2) break } } // 使用标记 fmt.Println("---- break label ----") re: for i := 1; i <= 3; i++ { fmt.Printf("i: %d\n", i) for i2 := 11; i2 <= 13; i2++ { fmt.Printf("i2: %d\n", i2) break / continue re } } } 复制代码
goto
func main() { /* 定义局部变量 */ var a int = 10 /* 循环 */ LOOP: for a < 20 { if a == 15 { /* 跳过迭代 */ a = a + 1 goto LOOP } fmt.Printf("a的值为 : %d\n", a) a++ } } 复制代码
a的值为 : 10 a的值为 : 11 a的值为 : 12 a的值为 : 13 a的值为 : 14 a的值为 : 16 a的值为 : 17 a的值为 : 18 a的值为 : 19 复制代码
函数
变参
// 变量 arg 是一个 int 的 slice func myfunc(arg ...int) {} 复制代码
函数作为实参
在 Go 中函数也是一种变量,可以通过 type
来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型。
func main() { /* 声明函数变量 */ getSquareRoot := func(x float64) float64 { return math.Sqrt(x) } /* 使用函数 */ fmt.Println(getSquareRoot(9)) } 复制代码
2 复制代码
type testInt func(int) bool // 声明了一个函数类型 func isOdd(integer int) bool { if integer%2 == 0 { return false } return true } func isEven(integer int) bool { if integer%2 == 0 { return true } return false } // 声明的函数类型在这个地方当做了一个参数 func filter(slice []int, f testInt) []int { var result []int for _, value := range slice { if f(value) { result = append(result, value) } } return result } func main() { slice := []int{1, 2, 3, 4, 5, 7} fmt.Println("slice = ", slice) odd := filter(slice, isOdd) // 函数当做值来传递了 fmt.Println("Odd elements of slice are: ", odd) even := filter(slice, isEven) // 函数当做值来传递了 fmt.Println("Even elements of slice are: ", even) } 复制代码
slice = [1 2 3 4 5 7] Odd elements of slice are: [1 3 5 7] Even elements of slice are: [2 4] 复制代码
闭包
Go 语言支持匿名函数,可作为闭包。匿名函数是一个“内联”语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。
func getSequence() func() int { i := 0 return func() int { i += 1 return i } } func main() { /* nextNumber 为一个函数,函数 i 为 0 */ nextNumber := getSequence() /* 调用 nextNumber 函数,i 变量自增 1 并返回 */ fmt.Println(nextNumber()) fmt.Println(nextNumber()) fmt.Println(nextNumber()) /* 创建新的函数 nextNumber1,并查看结果 */ nextNumber1 := getSequence() fmt.Println(nextNumber1()) fmt.Println(nextNumber1()) } 复制代码
1 2 3 1 2 复制代码
func add(x1, x2 int) func(int, int) (int, int, int) { i := 0 return func(x3, x4 int) (int, int, int) { i += 1 return i, x1 + x2, x3 + x4 } } func main() { add_func := add(1, 2) fmt.Println(add_func(1, 1)) fmt.Println(add_func(0, 0)) fmt.Println(add_func(2, 2)) } 复制代码
1 3 2 2 3 0 3 3 4 复制代码
defer
当函数执行到最后时,defer
语句会按照逆序执行。
panic & recover
panic 是一个内建函数,可以中断原有的控制流程,进入一个 panic
状态中。当函数 F
调用 panic
,函数 F
的执行被中断,但是 F
中的延迟函数会正常执行,然后 F
返回到调用它的地方。在调用的地方,F
的行为就像调用了 panic
。这一过程继续向上,直到发生 panic
的 goroutine
中所有调用的函数返回,此时程序退出。可以直接调用 panic
产生,也可以由运行时错误产生,例如访问越界的数组。
recover 是一个内建函数,可以让进入 panic
状态的 goroutine
恢复过来。recover
仅在延迟函数中有效。在正常的执行过程中,调用 recover
会返回 nil
,并且没有其它任何效果。如果当前的 goroutine
陷入 panic
状态,调用 recover
可以捕获到 panic
的输入值,并且恢复正常的执行。
func throwsPanic(f func()) (b bool) { defer func() { if x := recover(); x != nil { b = true } }() f() // 执行函数 f,如果 f 中出现了 panic,那么就可以恢复回来 return } 复制代码
main 函数 & init 函数
Go 里面有两个保留的函数:init
函数(能够应用于所有的 package
)和 main
函数(只能应用于 main package
),这两个函数在定义时不能有任何的参数和返回值。虽然一个 package
里面可以写任意多个 init
函数,但强烈建议在一个 package
中每个文件只写一个 init
函数。
Go 程序会自动调用 init()
和 main()
,所以不需要在任何地方调用这两个函数。
程序的初始化和执行都起始于 main
包,如果 main
包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到 fmt
包,但它只会被导入一次,因为没有必要导入多次)。当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行 init
函数(如果有的话),依次类推。等所有被导入的包都加载完毕了,就会开始对 main
包中的包级常量和变量进行初始化,然后执行 main
包中的 init
函数(如果存在的话),最后执行 main
函数。
import
Go 程序是通过 package
来组织的。
包名与文件名没有直接关系;
包名与文件夹名没有直接关系;
同一个文件夹下的文件只能有一个包名,否则编译报错;
只有包名为
main
的源码文件可以包含main
函数;一个可执行程序有且仅有一个
main
包;
// 点操作,调用的时候只需要 Println(),而不需要 fmt.Println() // fmt 是 Go 语言的标准库,其实是去 GOROOT 环境变量指定目录下去加载该模块 import . "fmt" // _ 操作,引入该包,而不直接使用包里面的函数,而是调用了该包里面的 init 函数 import _ "github.com/ziutek/mymysql/godrv" // 相对路径,当前文件同一目录的 model 目录,但是不建议这种方式来 import import "./model" // 绝对路径,加载 gopath/src/shorturl/model 模块 import "shorturl/model" 复制代码
struct
方法
Go 语言中同时有函数和方法。一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。
type Foo struct { name string } func (f *Foo) PointerMethod() { fmt.Println("pointer method on", f.name) } func (f Foo) ValueMethod() { fmt.Println("value method on", f.name) } func NewFoo() Foo { // 返回一个右值 return Foo{name: "right value struct"} } func main() { f1 := Foo{name: "value struct"} f1.PointerMethod() // 编译器会自动插入取地址符,变为 (&f1).PointerMethod() f1.ValueMethod() f2 := &Foo{name: "pointer struct"} f2.PointerMethod() f2.ValueMethod() // 编译器会自动解引用,变为 (*f2).PointerMethod() NewFoo().ValueMethod() NewFoo().PointerMethod() // Error!!! } 复制代码
# command-line-arguments .\main.go:33:10: cannot call pointer method on NewFoo() .\main.go:33:10: cannot take the address of NewFoo() 复制代码
看来编译器首先试着给 NewFoo()
返回的右值调用 pointer method
,出错;然后试图给其插入取地址符,未果,就只能报错了。
可以被寻址的是左值,既可以出现在赋值号左边也可以出现在右边;不可以被寻址的即为右值,比如函数返回值、字面值、常量值等等,只能出现在赋值号右边。
匿名字段、继承、重写
type Skills []string type Human struct { name string age int weight int } func (h *Human) SayHi() { fmt.Printf("Hi, I am %s\n", h.name) } type Student struct { Human // 匿名字段,struct Skills // 匿名字段,自定义的类型 string slice int // 内置类型作为匿名字段 speciality string age int } func (s *Student) SayHi() { fmt.Printf("Hi, I Student am %s\n", s.name) } func main() { jane := Student{Human: Human{"Jane", 35, 100}, speciality: "Biology"} jane.Human.name = "Jane1" jane.Human.age = 23 jane.age = 22 fmt.Println("Her name is ", jane.Human.name) fmt.Println("Her Human.age is ", jane.Human.age) fmt.Println("Her age is ", jane.age) fmt.Println("Her speciality is ", jane.speciality) jane.Skills = []string{"anatomy"} fmt.Println("Her skills are ", jane.Skills) jane.Skills = append(jane.Skills, "physics", "golang") fmt.Println("Her skills now are ", jane.Skills) jane.int = 3 fmt.Println("Her preferred number is", jane.int) jane.Human.SayHi() jane.SayHi() } 复制代码
Her name is Jane1 Her Human.age is 23 Her age is 22 Her speciality is Biology Her skills are [anatomy] Her skills now are [anatomy physics golang] Her preferred number is 3 Hi, I am Jane1 复制代码
interface
type Phone interface { call() string } type Android struct { brand string } type IPhone struct { version string } func (android Android) call() string { return "I am Android " + android.brand } func (iPhone IPhone) call() string { return "I am iPhone " + iPhone.version } func printCall(p Phone) { fmt.Println(p.call() + ", I can call you!") } func main() { var vivo = Android{brand: "Vivo"} var hw = Android{"HuaWei"} i7 := IPhone{"7 Plus"} ix := IPhone{"X"} printCall(vivo) printCall(hw) printCall(i7) printCall(ix) }
作者:giao_magic
链接:https://juejin.cn/post/7035536153777864711