go语言基础
Go程序是通过package来组织的,package告诉我们当前文件属于哪个包,如果包名是main则告诉我们它是一个可独立运行的包,它在编译后会产生可执行文件。其它的包最后都会生成*.a文件并放置在
GOPATH/pkg/$GOOS_$GOARCH中(以Mac为例就是$GOPATH/pkg/darwin_amd64)。
每一个可独立运行的Go程序,必定包含一个package main,在这个main包中必定包含一个入口函数main,而这个函数既没有参数,也没有返回值。
Go基础
:=这个符号直接取代了var和type,这种形式叫做简短声明。不过只能用在函数内部;在函数外部使用则会无法编译通过,所以一般用var方式来定义全局变量。
_(下划线)是个特殊的变量名,任何赋予它的值都会被丢弃。
数值类型
- 整数类型有无符号和带符号两种。Go同时支持int和uint,这两种类型的长度相同,但具体长度取决于不同编译器的实现。Go里面也有直接定义好位数的类型:rune, int8, int16, int32, int64和byte, uint8, uint16, uint32, uint64。其中rune是int32的别称,byte是uint8的别称。
- 需要注意的一点是,这些类型的变量之间不允许互相赋值或操作,不然会在编译时引起编译器报错。
- 另外,尽管int的长度是32 bit, 但int 与 int32并不可以互用。
- 浮点数的类型有float32和float64两种(没有float类型),默认是float64。
- Go还支持复数。它的默认类型是complex128(64位实数+64位虚数,也有complex64(32位实数+32位虚数)。复数的形式为RE + IMi
字符串
-
在Go中字符串是不可变的
-
字符串虽不能更改,但可进行切片操作
s := "hello" s = "c" + s[1:] //
-
可以通过`来声明一个多行的字符串:
m := `hello world` //` 括起的字符串为Raw字符串,即字符串在代码中的形式就是打印时的形式,它没有字符转义,换行也将原样输出。例如本例中会输出: //hello // // world
分组声明
在Go语言中,同时声明多个常量、变量,或者导入多个包时,可采用分组的方式进行声明。
iota枚举
Go里面有一个关键字iota,这个关键字用来声明enum的时候采用,它默认开始值是0,const中每增加一行加1:
const (
x = iota // x == 0
y = iota // y == 1
z = iota // z == 2
w // 常量声明省略值时,默认和之前一个值的字面相同。这里隐式地说w = iota,因此w == 3。其实上面y和z可同样不用"= iota"
)
const v = iota // 每遇到一个const关键字,iota就会重置,此时v == 0
const (
h, i, j = iota, iota, iota //h=0,i=0,j=0 iota在同一行值相同
)
const (
a = iota //a=0
b = "B"
c = iota //c=2
d, e, f = iota, iota, iota //d=3,e=3,f=3
g = iota //g = 4
)
Go程序设计的一些规则
- 大写字母开头的变量和函数是可导出的,也就是其它包可以读取的,是公有变量;小写字母开头的就是不可导出的,是私有变量。
array
数组的声明
var a [5]int
b := [5]int{1, 2, 3, 4, 5}
var twoD [2][3]int
数组之间的赋值是值的赋值,即当把一个数组作为参数传入函数的时候,传入的其实是该数组的副本,而不是它的指针。
slice
-
声明
//要创建一个长度非零的空slice,需要使用内建的方法 make。 //这里我们创建了一个长度为3的 string 类型 slice(初始化为零值) s := make([]string, 3) //我们可以在一行代码中声明并初始化一个 slice 变量 t := []string{"g", "h", "i"}
-
slice并不是真正意义上的动态数组,而是一个引用类型。slice总是指向一个底层array,slice的声明也可以像array一样,只是不需要长度。var fslice []int
-
slice是引用类型,所以当引用改变其中元素的值时,其它的所有引用都会改变该值。
map
-
声明
//要创建一个空 map,需要使用内建的 //make:make(map[key-type]val-type). m := make(map[string]int) //你也可以通过这个语法在同一行申明和初始化一个新的map。 n := map[string]int{"foo": 1, "bar": 2}
-
map是无序的,每次打印出来的map都会不一样,它不能通过index获取,而必须通过key获取。
-
map的长度是不固定的,也就是和slice一样,也是一种引用类型,如果两个map同时指向一个底层,那么一个改变,另一个也相应的改变
-
map和其他基本型别不同,它不是thread-safe,在多个go-routine存取时,必须使用mutex lock机制
-
当从一个 map 中取值时,可选的第二返回值指示这个键是在这个 map 中。这可以用来消除键不存在和键有零值,像 0 或者 "" 而产生的歧义。
value, prs := m["k2"] fmt.Println(value)//如果k2不存在,打印0值 fmt.Println("prs:", prs)//打印k2是否在m中
make、new操作
- 内建函数new本质上说跟其它语言中的同名函数功能一样:new(T)分配了零值填充的T类型的内存空间,并且返回其地址,即返回了一个指针,指向新分配的类型T的零值。
- 内建函数make(T, args)只能创建slice、map和channel,并且返回一个有初始值(非零)的T类型,而不是*T。本质来讲,导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被初始化。例如,一个slice,是一个包含指向数据(内部array)的指针、长度和容量的三项描述符;在这些项目被初始化之前,slice为nil。对于slice、map和channel来说,make初始化了内部的数据结构,填充适当的值。
go指针
-
传值与传指针
当我们传一个参数值到被调用函数里面时,实际上是传了这个值的一份copy,当在被调用函数中修改参数值的时候,调用函数中相应实参不会发生任何变化,因为数值变化只作用在copy上。如果真的需要传这个x本身,传递指针,此时参数仍然是按copy传递的,只是copy的是一个指针。
-
传指针有什么好处呢?
- 传指针使得多个函数能操作同一个对象。
- 传指针比较轻量级 (8bytes),只是传内存地址,我们可以用指针传递体积大的结构体。如果用参数值传递的话, 在每次copy上面就会花费相对较多的系统开销(内存和时间)。所以当你要传递大的结构体的时候,用指针是一个明智的选择。
-
Go语言中channel,slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变slice的长度,则仍需要取地址传递指针)
-
Go 自动处理方法调用时的值和指针之间的转化。你可以使用指针来调用方法来避免在方法调用时产生一个拷贝,或者让方法能够改变接受的数据。