文章目录
关键字
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
变量声明
var关键字:(任意情况都可以使用,一般用来定义全局变量)
var vname1, vname2, vname3 type = v1, v2, v3 //type 可省略
简短声明:(仅能用于函数中)
vname1, vname2, vname3 := v1, v2, v3 //编译器自动推导出相应的类型
_
(下划线)是个特殊的变量名,任何赋予它的值都会被丢弃。
作用:一般用于接收函数返回得到的后续不使用的值
// 变量较多时,可以分组声明
import(
"fmt"
"os"
)
const(
i = 100
pi = 3.1415
prefix = "Go_"
)
var(
i int
pi float32
prefix string
)
基础数据类型
//常量
const cname type = c1 //type可省略
//Boolean
var active bool = true
//int, int8(byte), int16, int32(rune), int64, uint, uint8, uint16, uint32, uint64
//complex32, complex64
var c complex64 = 5+5i
//string (常量不可修改)
var s string = "hello"
s[0] = 'c' //编译出错
错误类型
Go内置有一个error
类型,专门用来处理错误信息,Go的package
里面还专门有一个包errors
来处理错误:
err := errors.New("emit macho dwarf: elf header corrupted")
if err != nil {
fmt.Print(err)
}
iota枚举
默认开始值是0,const中每增加一行加1:
package main
import (
"fmt"
)
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
)
func main() {
fmt.Println(a, b, c, d, e, f, g, h, i, j, x, y, z, w, v)
}
array
【注意】数组之间赋值为值传递
var arr [n]type //声明一个长度为n得类型为type的数组
// 声明及初始化
a := [3]int{1, 2, 3} // 声明了一个长度为3的int数组
b := [10]int{1, 2, 3} // 声明了一个长度为10的int数组,其中前三个元素初始化为1、2、3,其它默认为0
c := [...]int{4, 5, 6} // 可以省略长度而采用`...`的方式,Go会自动根据元素个数来计算长度
// 二维数组
// 声明了一个二维数组,该数组以两个数组作为元素,其中每个数组中又有4个int类型的元素
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}}
slice
【注意】slice为引用类型
// 和声明array一样,只是少了长度
var fslice []int
// 声明及初始化
slice := []byte {'a', 'b', 'c', 'd'}
slice
可以从一个数组或一个已经存在的slice
中再次声明。slice
通过array[i:j]
来获取,其中i
是数组的开始位置,j
是结束位置,但不包含array[j]
,它的长度是j-i
。
// 声明一个含有10个元素类型为byte的数组
var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
// 声明两个含有byte的slice
var a, b []byte
// a指向数组的第3个元素开始,并到第五个元素结束,
a = ar[2:5]
//现在a含有的元素: ar[2]、ar[3]和ar[4]
// b是数组ar的另一个slice
b = ar[3:5]
// b的元素是:ar[3]和ar[4]
注意
slice
和数组在声明时的区别:声明数组时,方括号内写明了数组的长度或使用...
自动计算长度,而声明slice
时,方括号内没有任何字符。
slice
是引用类型,所以当引用改变其中元素的值时,其它的所有引用都会改变该值。
对于slice
有几个有用的内置函数:
len
获取slice
的长度cap
获取slice
的最大容量append
向slice
里面追加一个或者多个元素,然后返回一个增加元素后的slice
copy
函数从源slice
的src
中复制元素到目标dst
,并且返回复制的元素的个数
注:append
函数会改变slice
所引用的数组的内容,从而影响到引用同一数组的其它slice
。 但当slice
中没有剩余空间(即(cap-len) == 0
)时,此时将动态分配新的数组空间。返回的slice
数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的slice
则不受影响。
package main
import "fmt"
func main() {
array := [...]int{1,2,3,4,5,6,7}
s := array[2:4]
a := append(s, 6)
fmt.Println(array, s, a)
}
// 输出结果
// PS E:\mygo\src> go run slice.go
// [1 2 3 4 6 6 7] [3 4] [3 4 6]
从Go1.2开始slice支持了三个参数的slice,之前我们一直采用这种方式在slice或者array基础上来获取一个slice
var array [10]int
slice := array[2:4]
这个例子里面slice的容量是8,新版本里面可以指定这个容量
slice = array[2:4:7]
上面这个的容量就是7-2
,即5。这样这个产生的新的slice就没办法访问最后的三个元素。
如果slice是这样的形式array[:i:j]
,即第一个参数为空,默认值就是0。
map
【注意】map也是引用类型
map
也就是Python中字典的概念,它的格式为map[keyType]valueType
// 声明一个key是字符串,值为int的字典,这种方式的声明需要在使用之前使用make初始化
var numbers map[string]int
// 另一种map的声明方式
numbers = make(map[string]int)
numbers["one"] = 1 //赋值
numbers["ten"] = 10 //赋值
numbers["three"] = 3
fmt.Println("第三个数字是: ", numbers["three"]) // 读取数据
// 打印出来如:第三个数字是: 3
通过delete
删除map
的元素:
// 初始化一个字典
rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 }
// map有两个返回值,第二个返回值,如果不存在key,那么ok为false,如果存在ok为true
csharpRating, ok := rating["C#"]
if ok {
fmt.Println("C# is in the map and its rating is ", csharpRating)
} else {
fmt.Println("We have no rating associated with C# in the map")
}
delete(rating, "C") // 删除key为C的元素
上面说过了,map
也是一种引用类型,如果两个map
同时指向一个底层,那么一个改变,另一个也相应的改变:
m := make(map[string]string)
m["Hello"] = "Bonjour"
m1 := m
m1["Hello"] = "Salut" // 现在m["hello"]的值已经是Salut了
make、new操作
make
用于内建类型(map
、slice
和channel
)的内存分配。new
用于各种类型的内存分配。
内建函数new
本质上说跟其它语言中的同名函数功能一样:new(T)
分配了零值填充的T
类型的内存空间,并且返回其地址,即一个*T
类型的值。用Go的术语说,它返回了一个指针,指向新分配的类型T
的零值。有一点非常重要:
new
返回指针。
内建函数make(T, args)
与new(T)
有着不同的功能,make只能创建slice
、map
和channel
,并且返回一个有初始值(非零)的T
类型,而不是*T
,args
包括了len
长度和cap
容量。本质来讲,导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被初始化。例如,一个slice
,是一个包含指向数据(内部array
)的指针、长度和容量的三项描述符;在这些项目被初始化之前,slice
为nil
。对于slice
、map
和channel
来说,make
初始化了内部的数据结构,填充适当的值。
make
返回初始化后的(非零)值。
区别:
package main
import "fmt"
func main() {
var p *[]int = new([]int) //声明一个指向slice的指针
(*p)[0] = 1
fmt.Println((*p)[0], (*p)[1])
var s []int = make([]int, 3) //使用make来初始化slice,len=3
s[0] = 2
fmt.Println(s[0], s[1])
}
// 输出结果
// PS E:\mygo\src> go run slice.go
// panic: runtime error: index out of range
// goroutine 1 [running]:
// main.main()
// E:/mygo/src/slice.go:16 +0x11
// exit status 2
原因:
利用new
来分配slice
结构,但是结构中的应该指向底层数组的ptr
指针为空,故不能向这个slice
里面存取数据;利用make
来分配slice
结构,此时结构中应该只想底层数组的ptr
指针已经指向了某个底层数组。这个底层数组已经分配了,所以可以使用。
func
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
//这里是处理逻辑代码
//返回多个值
return value1, value2
}
变参
func myfunc(arg ...int) {}
// arg ...int告诉Go这个函数接受不定数量的参数。注意,这些参数的类型全部是int。在函数体中,变量arg是一个int的slice
函数作为参数
package main
import "fmt"
type testInt func(int) bool // 声明了一个函数类型
func isOdd(integer int) bool {
if integer%2 == 0 {
return false
}
return true
}
func isEven(integer int) bool {
if integer%2 == 0 {
return true
}
return false
}
// 声明的函数类型在这个地方当做了一个参数
func filter(slice []int, f testInt) []int {
var result []int
for _, value := range slice {
if f(value) {
result = append(result, value)
}
}
return result
}
func main(){
slice := []int {1, 2, 3, 4, 5, 7}
fmt.Println("slice = ", slice)
odd := filter(slice, isOdd) // 函数当做值来传递了
fmt.Println("Odd elements of slice are: ", odd)
even := filter(slice, isEven) // 函数当做值来传递了
fmt.Println("Even elements of slice are: ", even)
}
函数内部常用语法:defer,panic,recover
defer–延迟语句
可以在函数中添加多个defer语句,当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回。 (有些类似于析构函数)
例子:
func ReadWrite() bool {
file.Open("file")
// 做一些工作
if failureX {
file.Close()
return false
}
if failureY {
file.Close()
return false
}
file.Close()
return true
}
我们看到上面有很多重复的代码,Go的defer
有效解决了这个问题。使用它后,不但代码量减少了很多,而且程序变得更优雅。在defer
后指定的函数会在函数退出前调用。
func ReadWrite() bool {
file.Open("file")
defer file.Close()
if failureX {
return false
}
if failureY {
return false
}
return true
}
panic
是一个内建函数,可以中断原有的控制流程,进入一个panic
状态中。当函数F
调用panic
,函数F的执行被中断,但是F
中的延迟函数会正常执行,然后F返回到调用它的地方。在调用的地方,F
的行为就像调用了panic
。这一过程继续向上,直到发生panic
的goroutine
中所有调用的函数返回,此时程序退出。panic
可以直接调用panic
产生。也可以由运行时错误产生,例如访问越界的数组。
下面这个函数演示了如何在过程中使用panic
var user = os.Getenv("USER")
func init() {
if user == "" {
panic("no value for $USER")
}
}
recover
是一个内建的函数,可以让进入panic
状态的goroutine
恢复过来。recover
仅在延迟函数中有效。在正常的执行过程中,调用recover
会返回nil
,并且没有其它任何效果。如果当前的goroutine
陷入panic
状态,调用recover
可以捕获到panic
的输入值,并且恢复正常的执行。
下面这个函数检查作为其参数的函数在执行时是否会产生panic
:
func throwsPanic(f func()) (b bool) {
defer func() {
if x := recover(); x != nil {
b = true
}
}()
f() //执行函数f,如果f中出现了panic,那么就可以恢复回来
return
}
main函数和init函数
init
函数(能够应用于所有的package
)和main
函数(只能应用于package main
)。这两个函数在定义时不能有任何的参数和返回值
import
fmt是Go语言的标准库,其实是去GOROOT
环境变量指定目录下去加载该模块,当然Go的import还支持如下两种方式来加载自己写的模块:
-
相对路径
import “./model” //当前文件同一目录的model目录,但是不建议这种方式来import
-
绝对路径
import “shorturl/model” //加载gopath/src/shorturl/model模块
上面展示了一些import常用的几种方式,但是还有一些特殊的import,让很多新手很费解,下面我们来一一讲解一下到底是怎么一回事
-
点操作
我们有时候会看到如下的方式导入包
import( . "fmt" )
这个点操作的含义就是这个包导入之后在你调用这个包的函数时,你可以省略前缀的包名,也就是前面你调用的fmt.Println(“hello world”)可以省略的写成Println(“hello world”)
-
别名操作
别名操作顾名思义我们可以把包命名成另一个我们用起来容易记忆的名字
import( f "fmt" )
别名操作的话调用包函数时前缀变成了我们的前缀,即f.Println(“hello world”)
-
_操作
这个操作经常是让很多人费解的一个操作符,请看下面这个import
import ( "database/sql" _ "github.com/ziutek/mymysql/godrv" )
_操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数。