Go 入门笔记

Go入门笔记。有很多东西还没有涉及。仅供参考。

求Go大佬指点。


安装(Sublime Text 3)

  • 官网下载最新版Go并安装。同时安装好Sublime Text 3与Sublime插件Package Control。

  • 使用Sublime插件Package Control安装插件Golang Build和GoSublime。

  • .go文件存储源代码。注意每个文件夹内的Go文件是自动相互关联的,即所有文件会被视作一个整体,编译产生的可执行程序名称为文件夹名称。用Sublime打开后可以Ctrl + Shift + B并选择编译选项。

  • GoSublime选项支持打开一个终端。用go help查看Go语言的终端命令。注意Sublime进行Go语言程序的运行都不能输入。


基本情况

1
2
3
4
5
package main
import "fmt"
func main() {
fmt.Printf("hello, world\n")
}

Hello World程序范例。

  • 空格不区别,文末回车不区别,左大括号强制不换行

  • 使用import "xxx"引入模块,可以一次引入多个模块形如import("xxx","yyy","zzz")引用没有使用到的模块、声明没有使用的变量、废弃的返回值会导致编译报错。Go语言也支持直接从Github或者网络获取模块。

  • 区分大小写,变量名只能以下划线和字母开头,不能以数字开头,不能包含空格(同C++)。变量名不能包含下述字符:

    ~ ! @ # $ % ^ & * ( ) ; - : " ' < > , . ? / { } [ ] + = /

  • //进行单行注释,/**/进行多行注释。Go语言的许多语法与C和C++几乎完全一致


输入输出

  • 输入输出包含在fmt模块中。

Println

输出并换行。逗号连接多个参数等价于空格作为分隔符输出。

Print

输出。逗号连接多个参数等价于空格作为分隔符输出。

Printf

输出。使用方式和C++几乎完全一致。对于格式化字符串,新增%t表示布尔类型(输出truefalse),%v表示通用类型(包括自定义结构体),%+v输出自定义结构体时会输出属性名称,%#v输出自定义结构体新建时的Go语法,%T输出类型,形如%[n]v指示输出第n个提供的参数(从1开始),%q输出未经转义的字符串。

Scanln

输入一行。Scanln(&a,&b,&c,...)直接将输入以空格作为分隔符赋值给各个变量。

Scan

输入。Scan(&a,&b,&c,...)直接将输入以空格或回车作为分隔符赋值给各个变量。

Scanf

输入。使用方式和C++几乎完全一致。可以用形如%5s表示输入5个字符,用%v以空格作为分隔符输入通用类型的各个参数。


数据类型

  • 变量声明var NAME TYPE。或者数组形如var NAME [n]TYPE。变量总是会被默认初始化为0。常量定义形如const NAME TYPE = xxx。省略TYPE会导致推断。

  • 区别于=进行赋值,用:=表示声明并赋值。类似于Python支持多个变量的赋值与声明,相应地支持一个函数的多返回值。

  • 各种不同的声明方式:

1
2
3
4
5
6
7
var (
a,b int32
c,d float32
x,y = 100,"abc"
z = make(map[string]int32)
A = []int32{1,2,3,4,5}
)
  • 用形如A{3,3.5,"aaa",7}方式新建一个结构体对象。

  • iota是一个从0开始的自增量:

1
2
3
4
5
6
var (
a = iota
b
c
d
)

就会得到a,b,c,d分别是0,1,2,3

int类型

支持^ ! * / % << >> & &^ + - | ^ == != < <= > >= && || = += -= *= /= %=和C++类似(运算符优先级从左到右)。其中高优先级的^是单目运算符表示C++中的~,而优先级的则表示异或。a &^ b表示a&(~b)++--只能作为独立语句,不能再作为表达式的一部分。

支持(u)int__的定义方式,__可以是8163264。注意类型之间不能自动转换,单独的声明(u)int类型根据机器字长而变化,是不可移植的。

float类型

支持float32float64

complex类型

支持complex64complex128

Slice类型和数组

形如A := []int32{1,2,3}声明一个数组。多维数组只有第一维可以省略。

也可以使用A := make([]TYPE,CUR_SIZE,MAX_SIZE)来声明一个长度为MAX_SIZE的切片A,其中前CUR_SIZE个元素被初始化了。

Go语言中的数组是一个值!与此相对,切片是一个引用,这与C++和Python都恰好相反。

类似于Python可以使用a[l:r]可以获得一个子列表包含a[l,r)的元素,即一个切片(Slice)。可以省略lr甚至两个都省略,表示一直延伸到两端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
import "fmt"
func main() {
var a [3]int32 = [3]int32{1,2,3}
d := a
b := a[0:2]
c := a[1:3]
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
b[1] = 4
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
}

会发现a,b,c都改变了而d没有改变。

append(A,x,y,...): 在A切片后面追加x(CUR_SIZE后面追加,超过MAX_SIZE后会重新分配空间),返回值是新的切片,注意原切片不变(可以视作是一对L,R),而数组改变了。

string类型

必须用双引号定义。本质是一个byte数组。byte类型的本质是uint8,可以说两者等价。

单引号定义得到字符类型,是一个rune类型。rune类型的本质是int32,可以说两者等价,但是rune被用于表示字符。

用英文的`符号可以定义原生字符串。

字符串的默认值是"",而不是nil

支持!= == < > + +=

字符串本身是不可直接修改的,需要强行转换为[]byte[]rune

map类型

通过M := make(map[KEY_TYPE]VAL_TYPE)声明,基本使用方法和map几乎一致。KEY_TYPE需要定义==!=map自动按照关键字顺序排序。默认返回值是0

delete(M,key)来删除一个元素。

迭代期间的增删是安全,但是并发程序同时读写或写写map会导致报错。


结构语法

结构语法和C++几乎完全相同,iffor等的实现不需要大括号。

if

if的基本用法例如:

1
2
3
4
5
6
7
if EXPRESSION {
OPERATION
} else EXPRESSION {
OPERATION
} else {
OPERATION
}

switch

1
2
3
4
5
6
switch EXPRESSION {
case SOMETHING: OPERATION
case SOMETHING: OPERATION
...
deffault: OPERATION
}

与C++不同的是去除了每个case后面需要break的要求。相反用fallthrough来跳过默认的break

for

for的基本用法例如:

1
2
3
for i := 0; i < n; i++ {
fmt.Println(i)
}

for完全取代了while,例如:

1
2
3
for EXPRESSION {
OPERATION
}

甚至可以表示while(true),例如:

1
2
3
for {
OPERATION
}

可以用for-range来访问一个数组(切片)。range得到的对象是一个二元组i,A[i]。注意如果定义了i却不使用是会编译错误的,用_代替可以避免报错。例如数组求和:

1
2
3
4
s := int32(0)
for _,a := range A {
s += a
}

这里用range访问数组得到的是数组A中元素的复制,在循环体中改变A不会影响a的值。

goto, break, continue

和C++一致。不过配合goto LABEL的标签,可以指定break LABELcontinue LABEL来指定跳出/重新循环的层级。

函数

1
2
3
4
func FUNCTION(ARGUMENTS) RETURN_TYPE {
OPERATION
return SOMETHING
}

也支持隐式返回值: 给返回值命名NAME RETURN_TYPE。然后在程序操作过程中修改NAME后最后直接return

一个简单的多返回值函数例如:

1
2
3
4
5
6
7
8
9
package main
import "fmt"
func Norm(x int32, y int32) (int32,int32) {
return y,-x
}
func main() {
x,y := Norm(3,5)
fmt.Println(x,y)
}

多变量赋值的时候是现在计算完所有的右值,然后进行赋值操作。

ARGUMENTS填入...可以表示任意数量参数。使用range来访问:

1
2
3
4
5
6
7
8
9
10
11
12
package main
import "fmt"
func Sum(A ... int32) int32 {
s := int32(0)
for _,a := range A {
s += a
}
return s
}
func main() {
fmt.Println(Sum(2,4,6,8,10))
}

也可以使用一个匿名函数(闭包):

1
2
3
4
5
6
7
8
9
10
11
12
package main
import "fmt"
func main() {
fmt.Println(
func (A ... int32) int32 {
s := int32(0)
for _,a := range A{
s += a
}
return s
}(2,4,6,8,10))
}

匿名函数也可以作为值传递:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main
import "fmt"
func main() {
Sum :=
func (A ... int32) int32 {
s := int32(0)
for _,a := range A{
s += a
}
return s
}
fmt.Println(Sum(2,4,6,8,10))
}

defer FUNCTION关键字表示延迟调用,在函数结束的时候(返回返回值以后)再调用FUNCTION

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
import "fmt"
func Fin() {
fmt.Println("Accumulated!")
}
func Sum(A ... int32) int32 {
s := int32(0)
defer Fin()
for _,a := range A{
s += a
}
return s
}
func main() {
fmt.Println(Sum(2,4,6,8,10))
}

结构体

一个结构体可以嵌入其他结构体,直接获得其他结构体的成员。

1
2
3
4
5
6
7
8
type NAME struct {
NAME TYPE
NAME TYPE
...
OTHER_STRUCTS
OTHER_STRUCTS
...
}

结构体的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main
import "fmt"
type Point struct {
x int32
y int32
}
func (p *Point) Norm() {
p.x,p.y = p.y,-p.x
}
func main() {
a := Point{3,5}
a.Norm()
fmt.Println(a)
}

这里的(p *Point)类似于Python的self。Go语言不支持p->x这种写法,然而可以直接用指针调用成员函数,即p.x(*p).x等价。

1
type NEW_TYPE TYPE

类似于C++的typedef

接口

1
2
3
4
5
type NAME interface {
FUNCTION(ARGUMENTS) RETURN_TYPE
FUNCTION(ARGUMENTS) RETURN_TYPE
...
}

类似于C++中的通用类型(template)。即对于多个不同的STRUCT,各自支持一个或几个意义相近而实现不同的函数FUNCTION,那么就可以用接口来实现通用类型。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main
import "fmt"
type A struct {
}
type B struct {
}
func (a A) F() int32 {
return 1
}
func (b B) F() int32 {
return 2
}
type AorB interface {
F() int32
}
func main() {
var a A
var b B
var aorb AorB = a
fmt.Println(aorb.F())
aorb = b
fmt.Println(aorb.F())
}

这样AorB接口既可以作为一个B的对象又可以作为一个A的对象。注意要实现一个接口必须要求AB都定义了AorB中所描述的函数,而且接口函数必须传入值本身而不是指针,本例中必须定义func (a A) F()而不是func (a *A) F()。接口对于实现一个通用类型函数非常有用。

异常处理

error类型是一个接口类型,只需要包含Error()函数,返回一条错误信息字符串。

可以用errors.New(string)返回一个error对象。也可以使用fmt.Errorf(string)

panic(SOMETHING)立即中断当前函数流程,执行延迟调用,在延迟调用中配合recover()可以将SOMETHING作为返回值。

error当没有发生异常时为nil

并发

goroutine

相对于普通函数调用F(),将其写为go F()就会启动一个新的goroutine运行目标函数,其比普通的线程更加高效(内存消耗更少,创建与销毁的开销更小,切换开销更小)。

注意函数运行时并不会等待goroutine,所以往往配合人工制造的等待time.Sleep()

channel

A := make(chan TYPE)来声明一个通道。然后goroutine之间就可以使用通道来互相传递信息。

A <- SOMETHING向通道内输入信息。

<-A将通道内的信息以值的形式输出,使用方法例如SOMETHING = <-A。输出会导致堵塞: 如果当前通道内没有内容则会一直等待直到有内容输出或通道关闭。

close(A)来关闭一个通道。

select语句相当于通道处理的switch语句,例如:

1
2
3
4
5
6
select {
case SOMETHING: OPERATION
case SOMETHING: OPERATION
...
default: OPERATION
}

这里SOMETHING往往是一个通道的输入输出动作。如果当前缓冲区内没有滞留的输入输出,就会执行default,如果没有写default则会堵塞直到出现一个通道输入输出操作并执行。如果当前缓冲区内已经有多个滞留的输入输出,则会随机执行一个。与无条件for循环配合可以实现持续监听。

模块

BIF

new(): 新建一个对象。

len(x): 返回一个对象的长度或是包含的元素个数。

append(): 数组、切片追加元素。

copy(A,B): 将B拷贝到A,拷贝的实际长度是两者长度的最小值。

imag()real(): 复数的实部和虚部。

delete(): 删除map元素。

close(): 关闭channel

panic()recover(): 异常处理。

os

OpenFile(PATH,TYPE,PERM): 其中TYPE包括只读O_RDONLY,只写O_WRONLY,可读可写O_RDWR,创建O_CREATE。而如果文件不存在,PERM指定新建文件的权限,一般为0777表示可读可写。返回一个*os.File对象和一个error信息。

一个使用的例子是:

1
file,_ := os.OpenFile("/a.txt",os.O_WRONLY|os.O_CREATE,0777)

File.Read([]byte)读取文件内容,返回读取字节数和error信息。File.Write()写入文件。File.Close()关闭。

getwd(): 返回当前工作目录绝对路径PATHerror信息。

Chdir(PATH): 将工作目录改变到PATH返回error信息。

strings

Contains(A,B): B是不是A的子串。

ContainsAny(A,B): AB是否有相同字符。

ContainsRune(A,r): r是否是A的字符。

Index(A,B): 返回BA第一次出现的位置或-1。

IndexAny(A,B): 返回B中某个字符在A第一次出现的位置或-1。

IndexFunc(A,r,F): 返回在A第一次出现的满足F(r)true的字符或-1。

Count(A,B): 求BA中出现的次数。

HasPrefix(A,B): B是不是A的前缀。

HasSuffix(A,B): B是不是A的后缀。

ToUpper(A)/ToLower(A): 转换大小写后返回字符串。

Split(A,TOKEN): 将A按照TOKEN分割,去掉TOKEN返回一个字符串数组。

SplitAfter(A,TOKEN): 将A按照TOKEN分割,TOKEN分到前一个字符串里返回一个字符串数组。

Join(A[],TOKEN): 将A[]TOKEN为分隔符连接返回一个字符串。

Replace(A,B,C,n): 将A中的前n个子串B替换为C返回一个字符串。

strconv

ParseBool(STRING): 将STRING转换为bool。其中1tTTRUEtrueTrue都会转换为真,类似的转换为假,其他值得到错误。

ParseFloat(STRING,SIZE): 将STRING转换为floatSIZE指定大小(32或64)。

ParseInt(STRING,BASE,SIZE): 将STRING转换为intBASE指定进制,SIZE指定大小。

Atoi(STRING): 将STRING转换为int。一般使用这个而不是ParseInt()

FormatInt(INT,BASE): 将int转换为STRINGBASE指定进制。

Itoa(INT): 将int转换为STRING。一般使用这个而不是FormatInt()

io/ioutil

Reader()是一个接口。这里的TYPE只需要自定义Read函数。同理有Writer()接口和Closer()接口。

var a bytes.Buffer建立一个缓冲区。支持a.Write(SOMETHING)将数据写入a,已经a.Read()读入、a.ReadByte()逐字节读入和a.ReadRune()逐4字节读入返回一个字符。

ioutil包中是许多io的接口:

Discard是一个io.Write对象,其Write()函数什么也不做。

ReadAll(Reader): 读取所有数据([]byte)。

ReadFile(STRING): 读取字符串对应的文件中的所有数据。

WriteFile(STRING,DATA,PERM)是一个io.Write对象,清空文件重写,如果文件不存在就创建。DATA是一个[]byte

fmt

Println()Print()Printf()Scanln()Scan()Scanf()

Errorf(): 输出到标准错误流。

Fprint(writer,...)Fprintf(writer,...): 输出到writer,只要是一个io.Writer对象。

time

time.Sleep(T): 睡眠。其中T是一个时间类型,常用的有time.Secondtime.Millisecond。例如: time.Sleep(300 * time.Millisecond)来睡眠300ms。

time.After(T): 返回一个通道,睡眠时间T后这个通道会输出一个消息。

time.Tick(T): 返回一个通道,每隔T时间这个通道就会输出一个消息。

sync

WaitGroup类型。var a sync.WaitGroup用于新建一个计数器。每次可以用a.Add(x)使计数器增加x,用a.Done()使计数器减1。a.Wait()会堵塞直至计数器为0。

Mutex类型。var a sync.Mutex用于新建一个锁。a.Lock()a.Unlock()用于加锁和解锁。注意传递锁的时候需要用指针,否则复制新锁,是没有意义的。不支持重复锁定(即一个goroutine在持有同一把锁的时候再次申请这把锁)。

runtime

Gosched(): 让当前goroutine暂停,等待下次调度的时候恢复执行。

Goexit(): 执行defer语句后退出当前goroutine

NumCPU(): 当前系统的CPU核数量。

GOMAXPROCS(x): 设置最大可同时使用的CPU核数目为(x)。

reflect

注意reflect速度较慢。

TypeOf(x): 获得x的类型。x.Name()是这个变量类型(可以是自己定义的类型),x.Kind()表示这个变量类型的本质(底层实现)。

ValueOf(x): 获得x的值,返回一个reflect.value对象。value.Interface.(TYPE)可以强制转换为TYPE。如果x是一个指针用value.Elem()可以得到*x的值。

value.CanSet(x): 表示是否可以更改。

net/http

1
2
3
4
5
6
7
import "net/http"
func handler(w http.ResponseWriter, r *http.Request) {
OPERATION
}

http.HandleFunc("/", handler)
http.ListenAndServe(":8080",nil)

来实现一个服务器。OPERATION例如:

1
fmt.Fprintf(w,"Hello World\n")


扫描二维码即可在手机上查看这篇文章,或者转发二维码来分享这篇文章:


文章作者: Magolor
文章链接: https://magolor.cn/2019/07/27/2019-07-27-blog-01/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Magolor
扫描二维码在手机上查看或转发二维码以分享Magolor的博客