《GO Web 编程》 GO基础

长孙深
2023-12-01

读书笔记源于:GO web 编程

GO环境配置

GO目录结构

src 存放源代码(比如:.go .c .h .s等)
pkg 编译后生成的文件(比如:.a)
bin 编译后生成的可执行文件(为了方便,可以把此目录加入到 $PATH 变量中)

GO 命令

go build

如果是main包,当你执行go build之后,它就会在当前目录下生成一个可执行文件。如果不是main包则无反应

go clean

这个命令是用来移除当前源码包里面编译生成的文件

go fmt

go fmt <文件名>.go : 用于格式化代码

使用go fmt命令,更多时候是用gofmt,而且需要参数-w,否则格式化结果不会写入文件。gofmt -w src,可以格式化整个项目。

go get

实际上分成了两步操作:第一步是下载源码包,第二步是执行go install。

go get github.com/astaxie/beedb 可以下载远程包

go get -u 参数可以自动更新包

为了go get 能正常工作,你必须确保安装了合适的源码管理工具

go install

这个命令在内部实际上分成了两步操作:第一步是生成结果文件(可执行文件或者.a包),第二步会把编译好的结果移到 G O P A T H / p k g 或 者 GOPATH/pkg或者 GOPATH/pkgGOPATH/bin。

go test

执行这个命令,会自动读取源码目录下面名为*_test.go的文件,生成并运行测试用的可执行文件。

go doc

如何查看相应package的文档呢? 例如builtin包,那么执行go doc builtin 如果是http包,那么执行go doc net/http 。查看某一个包里面的函数,那么执行godoc fmt Printf 。也可以查看相应的代码,执行godoc -src fmt Printf。

通过命令在命令行执行 godoc -http=:端口号 比如godoc -http=:8080。然后在浏览器中打开127.0.0.1:8080,你将会看到一个golang.org的本地copy版本,通过它你可以查询pkg文档等其它内容。如果你设置了GOPATH,在pkg分类下,不但会列出标准包的文档,还会列出你本地GOPATH中所有项目的相关文档,这对于经常被墙的用户来说是一个不错的选择。

其他命令

go fix 用来修复以前老版本的代码到新版本,例如go1之前老版本的代码转化到go1
go version 查看go当前的版本
go env 查看当前go的环境变量
go list 列出当前全部安装的package
go run 编译并运行Go程序
以上这些工具还有很多参数没有一一介绍,用户可以使用go help 命令获取更详细的帮助信息。

GO 开发工具配置(vim)

vim的使用参考:vim 学习

我的Linux机器是腾讯云提供的主机,安装的是Ubuntu16.04

vim 的配置主要有两个地方,一个是 /etc/vim/文件夹下(有一个vimrc文件,这个文件名不是.开头的),这是全局配置。一个是个人主目录下( ~/.vimrc(需要自己创建文件), ~/.viminfo),这是用户配置。

使用命令go get -u github.com/nsf/gocode 下载自动补全工具(安装教程)执行如下命令:

~ cd $GOPATH/src/github.com/nsf/gocode/vim
~ ./update.bash(这个脚本是在主目录下创建.vim文件夹存放插件)
~ gocode set propose-builtins true(是否自动提示Go的内置函数、类型和常量,默认为false,不提示。)

然后在~/.vimrc文件中加入filetype plugin on即可,这个命令是让脚本生效。没有这个命令在执行自动补全时可能会报option ‘omnifunc’ is notset的错误。(这里真是傻了,以为要执行source vimrc,其实不用,source是用于执行bashrc文件变更时用的,如果用source执行这个vimrc文件会报错filetype: command not found

~/.vimrc 文件下加入如下几行,即可实现括号的自动补全

inoremap ( ()<ESC>i 
inoremap [ []<ESC>i  
inoremap { {}<ESC>i  
inoremap < <><ESC>i

GO语言基础

变量

var variableName type
var vname1, vname2, vname3 type
var variableName type = value
var vname1, vname2, vname3 type= v1, v2, v3
var vname1, vname2, vname3 = v1, v2, v3
vname1, vname2, vname3 := v1, v2, v3

:=这个符号直接取代了var和type,这种形式叫做简短声明。不过它有一个限制,那就是它只能用在函数内部;在函数外部使用则会无法编译通过,所以一般用var方式来定义全局变量。

_(下划线)是个特殊的变量名,任何赋予它的值都会被丢弃。

Go对于已声明但未使用的变量会在编译阶段报错

常量

const Pi float32 = 3.1415926
const prefix = "astaxie_"

内置基础类型

bool
int uint(长度取决于编译器的实现)
rune, int8, int16, int32, int64和byte, uint8, uint16,uint32, uint64。其中rune是int32的别称,byte是uint8的别称。
这些类型的变量之间不允许互相赋值或操作,不然会在编译时引起编译器报错。int 与 int32并不可以互用。
浮点数的类型有float32和float64两种(没有float类型),默认是float64。
复数
string 在Go中字符串是不可变的.Go中可以使用+操作符来连接两个字符串,字符串也可以使用切片操作。

string类型的变量包含两个字段,Data中存放的是字符串实际存放位置 的地址,string的不可变性就是指 Data字段指向的位置是一个不可修改的内存区域。但string变量本身的 Data和Len字段的值是可以改变的。

s := "hello"
c := []byte(s) // 将字符串 s 转换为 []byte 类型
c[0] = 'c'
s2 := string(c) // 再转换回 string 类型
fmt.Printf("%s\n", s2)

m := `hello
world` (Raw字符串)

Go内置有一个error类型,专门用来处理错误信息,Go的package里面还专门有一个包errors来处理错误

技巧

primes := [4]int{2,,3,5,7} //数组

const(
i = 100
pi = 3.1415
prefix = "Go_"
)	//分组声明

除非被显式设置为其它值或iota,每个const分组的第一个常量被默认设置为它的0值,第二及后续的常量被默认设置为它前面那个常量的值,如果前面那个常量的值是iota,则它也被设置为iota。

Go里面有一个关键字iota,这个关键字用来声明enum的时候采用,它默认开始值是0,每调用一次加1:
const(
x = iota // x == 0
y = iota // y == 1
z = iota // z == 2
w // 常量声明省略值时,默认和之前一个值的字面相同。这里隐式地说w = iota,因此w == 3。其实上面y和z可同样不用
) 
const v = iota // 每遇到一个const关键字,iota就会重置,此时v == 0

array、slice、map

当把一个数组作为参数传入函数的时候,传入的其实是该数组的副本,而不是它的指针

var arr [10]int // 声明了一个int类型的数组
b := [10]int{1, 2, 3} // 声明了一个长度为10的int数组,其中前三个元素初始化为1、2、3,其它默认为0
c := [...]int{4, 5, 6} // 可以省略长度而采用`...`的方式,Go会自动根据元素个数来计算长度
doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}}
easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}

在Go里面的动态数组叫slice,是一个引用类型,指向一个底层array,声明slice时,方括号内没有任何字符。

var fslice []int
slice := []byte {'a', 'b', 'c', 'd'}

var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
var a, b []byte
a = ar[2:5]

当引用改变其中元素的值时,其它的所有引用都会改变该值

    arr := [5]int{2,3,4,3,7}
    testSlice := arr[:]
    arr[0] = 90
    fmt.Printf("%v", testSlice[0]) //输出结果值是90

从底层理解,一个slice可以看出一个包含指针,长度,最大容量共3个成员的结构体。

对于slice有几个有用的内置函数:
len 获取slice的长度
cap 获取slice的最大容量
append 向slice里面追加一个或者多个元素,然后返回一个和slice一样类型的slice
copy 函数copy从源slice的src中复制元素到目标dst,并且返回复制的元素的个数
注:append函数会改变slice所引用的数组的内容,从而影响到引用同一数组的其它slice。 但当slice中没有剩余空间(即(cap-len) == 0)时,此时将动态分配新的数组空间。返回的slice数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的slice则不受影响。

map是无序的,每次打印出来的map都会不一样,是一种引用类型,如果两个map同时指向一个底层,那么一个改变,另一个也相应的改变

var numbers map[string] int
numbers := make(map[string]int)

// map有两个返回值,第二个返回值,如果不存在key,那么ok为false,如果存在ok为true
csharpRating, ok := rating["C#"]

delete(rating, "C") // 删除key为C的元素

m1 := m //m和m1的改变会互相影响,因为是引用类型

make,new操作

new(T) 为一个 T 类型新值分配空间并将此空间初始化为 T 的零值,返回的是新值的地址,也就是 T 类型的指针 *T,该指针指向 T 的新分配的零值。

make 只能用于 slice,map,channel 三种类型,make(T, args) 返回的是初始化之后的 T 类型的值,这个新值并不是 T 类型的零值,也不是指针 *T,是经过初始化之后的 T 的引用。

slice 的零值是 nil,使用 make 之后 slice 是一个初始化的 slice,即 slice 的长度、容量、底层指向的 array 都被 make 完成初始化,此时 slice 内容被类型 int 的零值填充,形式是 [0 0 0],map 和 channel 也是类似的。

参考:理解 Go make 和 new 的区别

流程控制

// 计算获取值x,然后根据x返回的大小,判断是否大于10。
if x := computedValue(); x > 10 {
fmt.Println("x is greater than 10")
} else {
fmt.Println("x is less than 10")
} /
/这个地方如果这样调用就编译出错了,因为x是条件里面的变量
fmt.Println(x)

for index:=0; index < 10 ; index++ {
sum += index
}

//for配合range可以用于读取slice和map的数据:
for k,v:=range map {
fmt.Println("map's key:",k)
fmt.Println("map's val:",v)
} 
//由于 Go 支持 “多值返回”, 而对于“声明而未被调用”的变量, 编译器会报错, 在这种情况下, 可以使用_来丢弃不需要的返回值 例如
for _, v := range map{
fmt.Println("map's val:", v)
}

i := 10
switch i {
case 1:
fmt.Println("i is equal to 1")
case 2, 3, 4:
fmt.Println("i is equal to 2, 3 or 4")
case 10:
fmt.Println("i is equal to 10")
default:
fmt.Println("All I know is that i is an integer")
}
//Go里面switch默认相当于每个case最后带有break,匹配成功后不会自动向下执行其他case,而是跳出整个switch, 但是可以使用fallthrough强制执行后面的case代码。

函数

func max(a, b int) int {
if a > b {
return a
} 
return b
}

//变参,arg是一个slice
func myfunc(arg ...int) {
	for _, n := range arg {
		fmt.Printf("And the number is: %d\n", n)
	}
}

defer

Go语言中有种不错的设计,即延迟(defer)语句,你可以在函数中添加多个defer语句。当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回。特别是当你在进行一些打开资源的操作时,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题。

func ReadWrite() bool {
file.Open("file")
// 做一些工作
if failureX {
file.Close()
return false
} i
f failureY {
file.Close()
return false
} f
ile.Close()
return true
}

func ReadWrite() bool {
file.Open("file")
defer file.Close()
if failureX {
return false
} i
f failureY {
return false
} r
eturn true
}

type testInt func(int) bool,可以把这个类型的函数当做值来传递
Panic和Recover
main函数和init函数

//可以省略前缀的包名
import(
. "fmt"
)
//别名操作
import(
f "fmt"
)
//_操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数。
import (
"database/sql"
_ "github.com/ziutek/mymysql/godrv"
)

struct类型

type person struct {
	name string
	age int
}

var P person
P := person{"Tom", 25}
P := person{age:24, name:"Tom"}

当匿名字段是一个struct的时候,那么这个struct所拥有的全部字段都被隐式地引入了当前定义的这个struct,就像访问自己所有用的字段一样。不仅仅是struct字段哦,所有的内置类型和自定义类型都是可以作为匿名字段的。

type Human struct {
name string
age int
weight int
} 
type Student struct {
Human // 匿名字段,那么默认Student就包含了Human的所有字段
speciality string
}

//也可以整体赋值
mark.Human = Human{"Marcus", 55, 220}

GO使用最外层的优先访问,允许我们去重载通过匿名字段继承的一些字段,当然如果我们想访问重载后对应匿名类型里面的字段,可以通过匿名字段名来访问。

面向对象

func (r ReceiverType) funcName(parameters) (results)

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

method也是可以继承的。如果匿名字段实现了一个method,那么包含这个匿名字段的struct也能调用该method。

interface(接口)

一个对象可以实现任意多个interface,任意的类型都实现了空interface

如果我们定义了一个interface的变量,那么这个变量里面可以存实现这个interface的任意类型的对象。

空interface在我们需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数值。它有点类似于C语言的void*类型。

interface的变量可以持有任意实现该interface类型的对象,这给我们编写函数(包括method)提供了一些额外的思考,我们是可以通过定义interface参数,让函数接受各种类型的参数。fmt.Println就是可以接受任何实现了String方法的参数。

Go语言里面有一个语法,可以直接判断是否是该类型的变量: value, ok = element.(T),这里value就是变量的值,ok是一个bool类型,element是interface变量,T是断言的类型。如果element里面确实存储了T类型的数值,那么ok返回true,否则返回false。

如果一个interface1作为interface2的一个嵌入字段,那么interface2隐式的包含了interface1里面的method。

反射机制

并发

go say("world") 就是开一个新的Goroutines执行

设计上我们要遵循:不要通过共享来通信,而要通过通信来共享

runtime.Gosched()表示让CPU把时间片让给别人,下次某个时候继续恢复执行该goroutine。

channels 来做通讯(包括超时,缓冲等…)

 类似资料: