对Golang的一些记录和想法

这两天认认真真的看了一遍Golang的官方Tour https://tour.golang.org 和GolangBot上的教程 https://golangbot.com/learn-golang-series/ (二者均在墙外),将一些值得注意的点记录一下。

注:GolangBot教程这里有中译版:https://blog.csdn.net/u011304970/article/details/74970942

1. 可以省略的分号

golang语句终止的判断方式为分号或者换行。当一行中只有一条语句时可以省略分号。同时,你仍然可以在一行中写多条语句并用分号隔开。这对习惯了C/C++语法的人和代码的可读性不会造成任何损害并提升了效率。

2. 方便的多变量同时赋值方式

golang中允许这样的赋值方式

a, b = 1, 2

留意编译器是计算出所有=右侧值,然后一一写入左侧,这样的特性在

a, b = b, a

这种语句中保证了a和b的值互换而不是一一从左至右或者从右至左计算使得最后a和b的值相同。

3. 更舒服的变量声明方式

golang中声明变量使用var name type的形式,如:

var a,b int
var c,d int = 2,3

将变量类型放置于变量名之后使得指针变量的可读性更高。
go还定义了:=让编译器根据初始值自己去判断变量类型,如:

e := 4
f := "this is a string"

声明变量时,如果没有为变量赋初始值,golang保证了你它们一定会被初始化为该类型的0值而非未知值

函数时采用func functionname(parametername type) returntype { }形式,如:

func add(x int, y int) int {
    return x+y
} 

4. 函数可以有多个返回值

golang中函数可以有多个返回值,很实用。如

func swap(x,y int) (int, int) {
    return y,x
}

调用时:

var a,b int=1,2
a,b = swap(a,b);

当然,这个swap还可以直接用

a,b = b,a

5. 超-强类型语言

说go是超-强类型语言是因为go不仅是强类型,而且没有隐式的类型转换和类型提升。如下的代码在golang中会报错:

var a int = 2
var b float64 = 1.5
fmt.Println(a + b)

我们需要将a显式转换为float64才可以运行。

6. {}是必需的,而(condition)中()是可以省略的

我们知道c/c++中if (condition) 只需要执行一条语句的时候,可以省略代码块{},在golang中{}是必需的,反而(condition)中()不是必需的。这无疑省略了真正可以省略的,保留了增加了代码可读性的部分,值得举双手支持。

7. 一切循环只剩下for

C中的while循环明显是多余的,因为while (condition)完全可以用for (;condition;)来替代。在golang中直接取缔了while循环,可以直接用for condition来达到while的效果。至于while(1)这样的无限循环,直接一个for {}即可。

8. break和continue支持标签

写过c语言的都知道c有个很难受的地方是无法指定break/continue跳出几层循环,使得只能用一个变量做flag的方式变通解决。golang无疑与其他现代语言一样解决了这个问题。golang也支持标签方式,这也是汇编中的goto唯一的也是最棒的用途了。实例:

LOOPI:
    for i := 0; i < 5; i++ {
        for j := 0; j < 10; j++ {
            if j > 6 {
                break LOOPI //终止的是外层i循环,而不是内层j
            }
            fmt.Println(J)
        }
    }

9. 被精简了的指针

在指针变量的声明过程中,就可以清晰的看到golang变量声明方式的恰当之处:

var p *int;

很清晰的声明了p为一个*int,也就是指向int的指针类型。

Golang的指针能做的事情很少,golang没有指针运算,所以p++这种东西是不存在的。确切的说,golang的指针只是一个为函数参数传址的工具。毫不客气的说,这也正是指针真正该干的活。

10. Golang中的数组

数组的声明如下;

var a [10]int
var b [3]int{12, 78, 50}
c := [3]int{12, 78, 50}

这种方式非常清晰的表达了”定义a为一个容量为10的int数组“。同样,如果没有为数组赋初始值,golang保证了你它们一定会被初始化为该类型的0值而非未知值。

数组的长度是数组类型的属性。[3]int和[10]int是声明时就确定的,数组长度不可变化。数组长度可以用golang内置函数len(arrayname)来获取。

Go中数组是值类型而非和C相同的指针类型。当数组变量被赋值时,将会获得原数组的拷贝。

如:

var a [3]int
b := [3]int{1,2,3}
a = b
b[0] = 10
fmt.Println(a)
fmt.Println(b)

以上程序输出结果为

[1 2 3]
[10 2 3]

同理可知,将数组变量名作为参数传入函数,并不会对原数组进行修改。这一点使得golang更好理解,换句话说,Golang中的数组就是个数组,函数中要修改一样传指针,不再是一个让新手一头雾水的特殊的指针使用方法。

11. 动态数组:切片

比起C99标准中大部分人对其敬而远之的可变长数组,Golang用一种十分合理的方式引入了数组的切片。动态数组(切片)本身并不存储任何数据,而是用一个静态数组定义,但是当需要的时候,能够增长其长度。一个切片有两个属性:len(长度)和cap(容量)。切片的长度是指切片中元素的个数。切片的容量是指从切片的起始元素开始到其底层数组中的最后一个元素的个数。内置函数append可以在切片后追加元素。

我们可以这样理解切片的实现方式,将其理解为一个结构体:

type slice struct {  
    Length        int
    Capacity      int
    ZerothElement *byte
}

我们使用实例解释:

a := []int{1,2,3}  //a是一个len=3,cap=3的int型动态数组
fmt.Println(len(a)) 
fmt.Println(cap(a))
a = append(a,4,5)  //追加4和5到切片尾部
fmt.Println(a)
fmt.Println(len(a))
fmt.Println(cap(a))

输出结果

3
3
[1 2 3 4 5]
5
6

可以看到,当一个切片的容量不够的时候,容量被翻倍了。在golang中具体的实现机制为:创建一个两倍len的数组,将其拷贝过来,将切片底层数组指向新的,再将原数组垃圾回收。Golang机制在容量小于1000时每次增加一倍,大于时每次增加25%。

请注意:根据切片的实现方式,我们可以想到切片作为函数参数时的表现会等同与传指针。

12. 内置的Map类型

MAP在golang中内置,用法基本差不多,有一点需要特别注意:map[key]当key不存在的时候,并不会报错,而是返回value类型的0值。要判断一个key是否存在于map中,可以用如下写法:

value, ok := map[key] 

如果 ok 是 true,则键存在,value 被赋值为对应的值。如果 ok 为 false,则表示键不存在。


title: 对Golang的一些记录和想法
time: 2018-06-28 21:41
tags: Golang
category: Study 

标签: Golang

精彩评论
  1. R-Stalker R-Stalker

    我只能讲,我用到golang的时候会回来看,现在看了记不住

  2. R.Stalker R.Stalker

    看完文档,写了点工程,现在回头看,果然都说到了点子上。
    所以,憋问,问就是吊!

  3. R-Stalker R-Stalker

    吊!!!!我滴孩来!!!切片讲的真好!!!

发表评论: