Go基础理论

特点:

简洁 快速 安全
并行 有趣 开源
内存管理,数组安全,编译迅速

可执行文件

Executable commands must always use package mainhttps://golang.org/doc/code.html#PackageNames
package main 包表示它是一个可独立运行的包,它在编译后会产生可执行文件。
https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/02.1.md#详解
除了main包之外,其它的包最后都会生成*.a文件(也就是包文件)并放置在$GOPATH/pkg/$GOOS_$GOARCH中

每一个可独立运行的Go程序,必定包含一个package main,在这个main包中必定包含一个入口函数main,而这个函数既没有参数,也没有返回值。

Go使用package(和Python的模块类似)来组织代码。main.main()函数(这个函数位于主包)是每一个独立的可运行程序的入口点。Go使用UTF-8字符串和标识符(因为UTF-8的发明者也就是Go的发明者之一),所以它天生支持多语言。

import

  • 点操作
  • 别名操作
  • _操作

变量和常量

常量使用关键字const表示,不可用:=语法定义。
var 语句定义了一个变量列表;
进一步阅读: https://blog.golang.org/gos-declaration-syntax

:=这个符号直接取代了var和type,这种形式叫做简短声明。不过它有一个限制,那就是它只能用在函数内部;
在函数外部使用则会无法编译通过,所以一般用var方式来定义全局变量。
_(下划线)是个特殊的变量名,任何赋予它的值都会被丢弃。

零值:

数值:0
布尔:false
指针:nil

指针:

& 符号会生成一个指向其作用对象的指针;
* 符号表示指针指向的底层的值;

那么到底传指针有什么好处呢?

传指针使得多个函数能操作同一个对象。
传指针比较轻量级 (8bytes),只是传内存地址,我们可以用指针传递体积大的结构体。如果用参数值传递的话, 在每次copy上面就会花费相对较多的系统开销(内存和时间)。所以当你要传递大的结构体的时候,用指针是一个明智的选择。
Go语言中channel,slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变slice的长度,则仍需要取地址传递指针)

切片

slice 只能和nil 进行==比较。

1
2
3
4
5
6
7
c := make([]int, 0)
fmt.Println(len(c), c == nil)
c = nil
fmt.Println(len(c), c == nil)
// Output:
// 0, false
// 0, true

结构体

Go没有类。然而,仍然可以在结构体类型上定义方法。
一个结构体(struct)就是一个字段的集合

一个命名为S的结构体类型将不能再包含S类型的成员

1
2
3
4
type S struct {
SS S // error: invalid recursive type S
II int
}

但是S类型的结构体可以包含*S指针类型的成员,这可以让我们创建递归的数据结构,比如链表和树结构等。

1
2
3
4
type S struct {
SS *S // ok
II int
}

匿名结构体中,
引用时可以直接访问子属性,而不需要给出完整的路径
但结构体字面值必须遵循形状类型声明时的结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type Point struct {
但结构体字面值必须遵循形状类型声明时的结构
X, Y int
}

type Circle struct {
Center Point
Radius int
}

type Wheel struct {
Circle Circle
Spokes int
}

var w Wheel
w.X = 8 // equivalent to w.Circle.Point.X = 8
w.Y = 8 // equivalent to w.Circle.Point.Y = 8
w.Radius = 5 // equivalent to w.Circle.Radius = 5
w.Spokes = 20

// 但结构体字面值必须遵循形状类型声明时的结构
w = Wheel{8, 8, 5, 20} // compile error: unknown fields
w = Wheel{X: 8, Y: 8, Radius: 5, Spokes: 20} // compile error: unknown fields

切片

1
b := a[:0]

可导出的

这个默认行为类似java中的public。
大写字母开头的变量是可导出的,也就是其它包可以读取的,是公有变量;小写字母开头的就是不可导出的,是私有变量。
大写字母开头的函数也是一样,相当于class中的带public关键词的公有函数;小写字母开头的就是有private关键词的私有函数。
http://go-tour-zh.appspot.com/basics/3

匿名字段

当匿名字段是一个struct的时候,那么这个struct所拥有的全部字段都被隐式地引入了当前定义的这个struct。
struct不仅仅能够将struct作为匿名字段、自定义类型、内置类型都可以作为匿名字段,而且可以在相应的字段上面进行函数操作

如果内层与继承层都有相同的字段,则:

  • 最外层优先访问; 例如:Bob.phone访问的是 Bob 层的字段
  • 可以通过匿名字段访问继承层;Bob.Human.phone 访问的是Human层的字段

方法

method是附属在一个给定的类型上的,他的语法和函数的声明语法几乎一样,只是在func后面增加了一个receiver(也就是method所依从的主体)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Rectangle struct {
width, height float64
}

type Circle struct {
radius float64
}

func (r Rectangle) area() float64 {
return r.width*r.height
}

func (c Circle) area() float64 {
return c.radius * c.radius * math.Pi
}

在使用method的时候重要注意几点:

  • 虽然method的名字一模一样,但是如果接收者不一样,那么method就不一样
  • method里面可以访问接收者的字段
  • 调用method通过.访问,就像struct里面访问字段一样

指针作为Receiver会对实例对象的内容发生操作,而普通类型作为Receiver仅仅是以副本作为操作对象,并不对原实例对象发生操作。

那是不是method只能作用在struct上面呢?当然不是咯,他可以定义在任何你自定义的类型、内置类型、struct等各种类型上面。这里你是不是有点迷糊了,什么叫自定义类型,自定义类型不就是struct嘛,不是这样的哦,struct只是自定义类型里面一种比较特殊的类型而已,还有其他自定义类型申明,可以通过如下这样的申明来实现。
type typeName typeLiteral

1
2
3
4
5
6
7
8
9
type ages int
type money float32
type months map[string]int
m := months {
"January":31,
"February":28,
...
"December":31,
}

函数作为值、类型

在Go中函数也是一种变量,我们可以通过type来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型
type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])
函数作为类型到底有什么好处呢?那就是可以把这个类型的函数当做值来传递

因此在Go语言里,我们为一些简单的数值、字符串、slice、map来定义一些附加行为很方便。
方法可以被声明到任意类型,只要不是一个指针或者一个interface。
https://yar999.gitbooks.io/gopl-zh/content/ch6/ch6-01.html

此外,为了避免歧义,在声明方法时,如果一个类型名本身是一个指针的话,是不允许其出现在接收器中的,比如下面这个例子:
https://yar999.gitbooks.io/gopl-zh/content/ch6/ch6-02.html

1
2
type P *int
func (P) f() { /* ... */ } // compile error: invalid receiver type

面向对象

  • 如果一个method的receiver是*T,你可以在一个T类型的实例变量V上面调用这个method,而不需要&V去调用这个method
  • 如果一个method的receiver是T,你可以在一个*T类型的变量P上面调用这个method,而不需要 *P去调用这个method

Go里面的面向对象是如此的简单,没有任何的私有、公有关键字,通过大小写来实现(大写开头的为公有,小写开头的为私有),方法也同样适用这个原则。

接口

interface在我们需要存储任意类型的数值的时候相当有用,因为他可以存储任意类型的数值。
神奇之处:在java中,必须现有interface,然后才有其继承类;而在go中,可以先有类,然后才有接口(当然,先有接口再有类更是可以的);https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/02.6.md
简单的说,interface是一组method 签名的组合,我们通过interface来定义对象的一组行为。

并行

  • 必须使用make来创建channel
  • goroutine是Go并行设计的核心。goroutine说到底其实就是协程,但是它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。
  • 我们可以看到go关键字很方便的就实现了并发编程。 上面的多个goroutine运行在同一个进程里面,共享内存数据,不过设计上我们要遵循:不要通过共享来通信,而要通过通信来共享。

    如何理解 Golang 中“不要通过共享内存来通信,而应该通过通信来共享内存”? - 知乎

  • runtime.Gosched()表示让CPU把时间片让给别人,下次某个时候继续恢复执行该goroutine。
  • make和new的区别 https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/02.2.md#makenew操作

    make用于内建类型(map、slice 和channel)的内存分配。new用于各种类型的内存分配。

    1. new返回指针。
      new(T)分配了零值填充的T类型的内存空间,并且返回其地址,即一个*T类型的值。用Go的术语说,它返回了一个指针,指向新分配的类型T的零值。
    2. make返回初始化后的(非零)值。
      make只能创建slice、map和channel,并且返回一个有初始值(非零)的T类型,而不是*T。 在这些项目被初始化之前,slice为nil。
  • range和close

正则

注意:所有字符都是UTF-8处理。

字符串的处理可以使用strings包来进行搜索(Contains, Index)、替换(Replace)和解析(Split、Join)等操作

反射

[包]

当创建一个包,一般要用短小的包名,但也不能太短导致难以理解。
包名一般采用单数的形式。标准库的bytes、errors和strings使用了复数形式,这是为了避免和预定义的类型冲突,同样还有go/types是为了避免和type关键字冲突。
https://yar999.gitbooks.io/gopl-zh/content/ch10/ch10-06.html