美文网首页
Golang 知识点

Golang 知识点

作者: 樊海鹏 | 来源:发表于2018-10-22 14:26 被阅读0次

    变量声明

    var a
    a=100
    //或
    var b = 100
    //或
    var c int = 100
    
    // := 是声明并赋值,并且系统自动推断类型,不需要var关键字
    d := 100
    
    
    package main
    
    import "fmt"
    
    func main() {
        // 切片的一种定义方式是 用 make
        // var x = make([]float64, 5) ,切片用make
        //另外一种是通过数组切片赋值,采用[low_index:high_index]的方式获取数值切片,其中切片元素包括low_index的元素,但是不包括high_index的元素。
    
        ar := new([3]int)// 指针
        ar1 := [3]int{0, 0, 0}
        var ar2 = [3]int{0, 0, 0}
        var ar3 [3]int
        var ar4 = [3]int{0: 1, 1: 2, 2: 3}
        fp(ar)
        fp(&ar1)
        fp(&ar2)
        fp(&ar3)
        fp(&ar4)
    
        fp1(ar)
        fp1(&ar1)
        fp1(&ar2)
        fp1(&ar3)
        fp1(&ar4)
    
    }
    
    func fp(a *[3]int)  { print(*a) }
    func fp1(a *[3]int) { print(a) }
    

    与 Python不同

    任何空值(nil)或者零值(0, 0.0, "")都不能作为布尔型来直接判断。

    func main() {
        if 0 {
            fmt.Println("hello world")
        }
        if nil {
            fmt.Println("hello world")
        }
        if "" {
            fmt.Println("hello world")
        }
    }
    

    常用命令

    go build: 编译出可执行文件
    go install: go build + 把编译后的可执行文件放到GOPATH/bin目录下
    go get: git clone + go install

    何时使用指针

    指针的一大用途就是可以将变量的指针作为实参传递给函数,从而在函数内部能够直接修改实参所指向的变量值。

    Go的变量传递都是值传递。
    
    package main
    
    import (
        "fmt"
    )
    
    func change(x int) {
        x = 200
    }
    func main() {
        var x int = 100
        fmt.Println(x)
        change(x)
        fmt.Println(x)
    }
    上面的例子输出结果为
    
    100
    100
    很显然,change函数改变的仅仅是内部变量x的值,而不会改变传递进去的实参。其实,也就是说Go的函数一般关心的是输出结果,而输入参数就相当于信使跑到函数门口大叫,你们这个参数是什么值,那个是什么值,然后就跑了。你函数根本就不能修改它的值。不过如果是传递的实参是指针变量,那么函数一看,小子这次你地址我都知道了,哪里跑。那么就是下面的例子:
    
    package main
    
    import (
        "fmt"
    )
    
    func change(x *int) {
        *x = 200
    }
    func main() {
        var x int = 100
        fmt.Println(x)
        change(&x)
        fmt.Println(x)
    }
    上面的例子中,change函数的虚参为整型指针变量,所以在main中调用的时候传递的是x的地址。然后在change里面使用*x=200修改了这个x的地址的值。所以x的值就变了。这个输出是:
    
    100
    200
    

    一个函数何时该用指针类型做receiver对初学者而言一直是个头疼的问题。如果不知道该如何取舍,选择指针类型的receiver。但有些时候value receiver更加合适,比如对象是一些轻量级的不变的structs,使用value receiver会更加高效。下面是列举了一些常用的判断指导。

    如果receiver是map、func或者chan,不要使用指针

    如果receiver是slice并且该函数并不会修改此slice,不要使用指针

    如果该函数会修改receiver,此时一定要用指针

    如果receiver是struct并且包含互斥类型sync.Mutex,或者是类似的同步变量,receiver必须是指针,这样可以避免对象拷贝

    如果receiver是较大的struct或者array,使用指针则更加高效。多大才算大?假设struct内所有成员都要作为函数变量传进去,如果觉得这时数据太多,就是struct太大

    如果receiver是struct,array或者slice,并且其中某个element指向了某个可变量,则这个时候receiver选指针会使代码的意图更加明显

    如果receiver使较小的struct或者array,并且其变量都是些不变量、常量,例如time.Time,value receiver更加适合,因为value receiver可以减少需要回收的垃圾量。

    最后,如果不确定用哪个,使用指针类的receiver

    常量

    变量定义的类型推断方式 := 不能够用来定义常量

    快速声明

    Go还提供了一种同时定义多个变量或者常量的快捷方式。

    
    import (
    "fmt"
    )
    
    func main() {
        var (
            a int     = 10
            b float64 = 32.45
            c bool    = true
        )
        const (
            Pi   float64 = 3.14
            True bool    = true
        )
    
        fmt.Println(a, b, c)
        fmt.Println(Pi, True)
    }
    

    if 判断的()

    package main

    import (
    "fmt"
    )

    func main() {
    const Male = 'M'
    const Female = 'F'

    var dog_age = 10
    var dog_sex = 'M'
    
    if (dog_age == 10 && dog_sex == 'M') {
        fmt.Println("dog")
    }
    

    }

    
    但是如果你使用Go提供的格式化工具来格式化这段代码的话,Go会智能判断你的括号是否必须有,否则的话,会帮你去掉的。你可以试试。
    
    go fmt test_bracket.go
    然后你会发现,咦?!果真被去掉了。
    
    #  for 循环
    

    package main

    import (
    "fmt"
    )

    在Go里面没有提供while关键字,如果你怀念while的写法也可以这样:

    func main() {
        var i int = 1
    
        for i <= 100 {
            fmt.Println(i)
            i++
        }
    }
    或许你会问,如果我要死循环呢?是不是for true?呵呵,不用了,直接这样。
    
    for{
    
    }
    
    

    切片

    我们发现arr1的长度变为11,因为元素个数现在为11个。另外我们发现arr1的容量也变了,变为原来的两倍。

    这是因为Go在默认的情况下,如果追加的元素超过了容量大小,Go会自动地重新为切片分配容量,容量大小为原来的两倍。

    总结一下,数组和切片的区别就在于[]里面是否有数字或者...!!!!!!!!!!!

    因为数值长度是固定的,而切片是可变的。

    函数预定义

    命名返回值

    Go的函数很有趣,你甚至可以为返回值预先定义一个名称,在函数结束的时候,直接一个return就可以返回所有的预定义返回值。例如上面的例子,我们将sum作为命名返回值。

    package main
    
    import (
    "fmt"
    )
    
    func slice_sum(arr []int) (sum int) {
        sum = 0
        for _, elem := range arr {
            sum += elem
        }
        return
    }
    
    func main() {
        var arr1 = []int{1, 3, 2, 3, 2}
        var arr2 = []int{3, 2, 3, 1, 6, 4, 8, 9}
        fmt.Println(slice_sum(arr1))
        fmt.Println(slice_sum(arr2))
    }
    

    这里要注意的是,如果你定义了命名返回值,那么在函数内部你将不能再重复定义一个同样名称的变量。比如第一个例子中我们用sum := 0来定义和初始化变量sum,而在第二个例子中,我们只能用sum = 0初始化这个变量了。因为 := 表示的是定义并且初始化变量。

    异常处理

    panic & recover

    当你周末走在林荫道上,听着小歌,哼着小曲,很是惬意。突然之间,从天而降瓢泼大雨,你顿时慌张(panic)起来,
    没有带伞啊,淋着雨感冒就不好了。于是你四下张望,忽然发现自己离地铁站很近,那里有很多卖伞的,心中顿时又安定了下来(recover),于是你飞奔过去买了一把伞(defer )

    Go语言提供了关键字defer来在函数运行结束的时候运行一段代码或调用一个清理函数。上面的例子中,虽然second()函数写在first()函数前面,但是由于使用了defer标注,所以它是在main函数执行结束的时候才调用的。

    
    package main
    
    import (
        "fmt"
    )
    
    func main() {
    
        //defer一定是在函数执行结束的时候运行的。不管是正常结束还是异常终止,相当于finally
        defer func() {
            //panic用来触发异常,而recover用来终止异常并且返回传递给panic的值。(注意recover并不能处理异常,而且recover只能在defer里面使用,否则无效。)
            msg := recover()
            fmt.Println(msg)
        }()
        fmt.Println("I am walking and singing...")
        panic("It starts to rain cats and dogs")
    }
    
    

    指针

    定义
    Go函数的参数传递方式是值传递
    而指针的主要作用就是在函数内部改变传递进来变量的值

    至于使不使用结构体指针和使不使用指针的出发点是一样的,那就是你是否试图在函数内部改变传递进来的参数的值。再举个例子如下:

    在学Python的时候,经常要注意不小心修改了可变类型,如list。事实上这种可变和不可变了类型,是Python为了简化你的操作。
    
    但是在Golang中,数组和切片,你可以当做可变类型,也可以当做不可变类型,来使用,通过使用*,&指针。
    

    所谓指针其实你可以把它想像成一个箭头,这个箭头指向(存储)一个变量的地址。

    因为这个箭头本身也需要变量来存储,所以也叫做指针变量。

    new 的使用发现

    
    package main
    
    import (
        "fmt"
    )
    
    func set_value(x_ptr *int) {
    
    //指针指向的地址内容设置为100
        *x_ptr = 100
    }
    func main() {
        //开辟一块内存,用于存贮指针地址,且此指针地址的变量名为x_ptr
        x_ptr := new(int)
    
        set_value(x_ptr)
    
        //指针变量指向的地址,因为本身就是指针变量,存贮的就是指向的地址
        fmt.Println(x_ptr)
        //指针变量本身的地址
        fmt.Println(&x_ptr)
    
        //打印指针变量指向的地址内容
        fmt.Println(*x_ptr)
    }
    
    0xc084000040
    0xc084000038
    100
    

    结构体组合函数

    上面我们在main函数中计算了矩形的面积,但是我们觉得矩形的面积如果能够作为矩形结构体的“内部函数”提供会更好。这样我们就可以直接说这个矩形面积是多少,而不用另外去取宽度和长度去计算。现在我们看看结构体“内部函数”定义方法:

    package main

    import (
    "fmt"
    )

    type Rect struct {
    width, length float64
    }

    func (rect Rect) area() float64 {
    return rect.width * rect.length
    }

    func main() {
    var rect = Rect{100, 200}

    fmt.Println("Width:", rect.width, "Length:", rect.length,
        "Area:", rect.area())
    

    }
    咦?这个是什么“内部方法”,根本没有定义在Rect数据类型的内部啊?

    确实如此,我们看到,虽然main函数中的rect变量可以直接调用函数area()来获取矩形面积,但是area()函数确实没有定义在Rect结构体内部,这点和C语言的有很大不同。Go使用组合函数的方式来为结构体定义结构体方法。我们仔细看一下上面的area()函数定义。

    首先是关键字func表示这是一个函数,第二个参数是结构体类型和实例变量,第三个是函数名称,第四个是函数返回值。这里我们可以看出area()函数和普通函数定义的区别就在于area()函数多了一个结构体类型限定。这样一来Go就知道了这是一个为结构体定义的方法。

    这里需要注意一点就是定义在结构体上面的函数(function)一般叫做方法(method)

    接口和鸭子类型

    package main
    
    import (
        "fmt"
    )
    
    type Phone interface {
        call()
    }
    
    type NokiaPhone struct {
    }
    
    func (nokiaPhone NokiaPhone) call() {
        fmt.Println("I am Nokia, I can call you!")
    }
    
    type IPhone struct {
    }
    
    func (iPhone IPhone) call() {
        fmt.Println("I am iPhone, I can call you!")
    }
    
    func main() {
        var phone Phone
    
        phone = new(NokiaPhone)
        phone.call()
    
        phone = new(IPhone)
        phone.call()
    
    }
    

    以前我们说过,Go语言式静态类型语言,变量的类型在运行过程中不能改变。但是在上面的例子中,phone变量好像先定义为Phone类型,然后是NokiaPhone类型,最后成为了IPhone类型,真的是这样吗?

    在Go语言里面,一个类型A只要实现了接口X所定义的全部方法,那么A类型的变量也是X类型的变量。
    就是,你能叫,能游,你就是鸭子

    数组和指针

    package main
    import "fmt"

    func main() {
    array := [3]float64{7.0, 8.5, 9.1}
    x := Sum(&array) // Note the explicit address-of operator
    // to pass a pointer to the array
    fmt.Printf("The sum of the array is: %f", x)
    }

    func Sum(a *[3]float64) (sum float64) {
    for _, v := range a { // derefencing *a to get back to the array is not necessary!
    sum += v
    }
    return
    }

    *表示,你不用来,我去干你,我只接受你的地址,不接受你。

    &表示我告诉你的我的地址

    不用*指针的方式的话,会复制一份数组,浪费内存

    相关文章

      网友评论

          本文标题:Golang 知识点

          本文链接:https://www.haomeiwen.com/subject/trkgzftx.html