[golang]语言基础Go基础流程和函数stru

Go 基础

内置基础类型

数值类型

runeint8int16int32int64byteuint8uint16uint32uint64,其中 runeint32 的别称,byteuint8 的别称。
浮点数 的类型有 float32float64 两种(没有 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 只能创建 slicemapchannel,并且返回一个有初始值(非零)的 T 类型,而不是 *T。本质来讲,导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被初始化。例如,一个 slice,是一个包含指向数据(内部 array)的指针、长度和容量的三项描述符;在这些项目被初始化之前,slicenil。对于 slicemapchannel来说,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。这一过程继续向上,直到发生 panicgoroutine 中所有调用的函数返回,此时程序退出。可以直接调用 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 函数。
image.png

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)
}
复制代码