Go语言基础
Go是一门类似C的编译型语言,但是它的编译速度非常快。这门语言的关键字总共也就二十五个,比英文字母还少一个,这对于我们的学习来说就简单了很多。先让我们看一眼这些关键字都长什么样:
下面列举了 Go 代码中会使用到的 25 个关键字或保留字:
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
Go程序设计的一些规则
Go之所以会那么简洁,是因为它有一些默认的行为:
大写字母开头的变量是可导出的,也就是其它包可以读取的,是公有变量;小写字母开头的就是不可导出的,是私有变量。
大写字母开头的函数也是一样,相当于class中的带public关键词的公有函数;小写字母开头的就是有private关键词的私有函数。
1. 变量、常量、Go内置类型
1.1 变量
Go语言里面定义变量有多种方式。
使用var
关键字是Go最基本的定义变量方式,与C语言不同的是Go把变量类型放在变量名后面:
var valName type
定义多个变量
var vname1, vname2, vname3 type
定义变量并初始化值
var vName type = value
同时初始化多个变量
var vname1, vname2, vname3 type = v1, v2, v3
你是不是觉得上面这样的定义有点繁琐?没关系,因为Go语言的设计者也发现了,有一种写法可以让它变得简单一点。我们可以直接忽略类型声明,那么上面的代码变成这样了:
var vname1, vname2, vname3 = v1, v2, v3
你觉得上面的还是有些繁琐?好吧,我也觉得。让我们继续简化:
vname1, vname2, vname3 := v1, v2, v3
:=
这个符号直接取代了var
和type
,这种形式叫做简短声明。不过它有一个限制,那就是它只能用在函数内部 ;在函数外部使用则会无法编译通过,所以一般用var
方式来定义全局变量。
_
(下划线)是个特殊的变量名,任何赋予它的值都会被丢弃。在这个例子中,我们将值2
赋予b
,并同时丢弃1
:
_ , b := 1 , 2
Go对于已声明但未使用的变量会在编译阶段报错 ,比如下面的代码就会产生一个错误:声明了i
但未使用:
package main
func main ( ) {
var i int
}
1.2 常量
所谓常量,也就是在程序编译阶段就确定下来的值,而程序在运行时无法改变该值。在Go程序中,常量可定义为数值、布尔值或字符串等类型。
它的语法如下:
const constantName = value
const Pi float32 = 3.1415926
下面是一些常量声明的例子:
const PI = 3.1415926
const MaxThread = 10
const Prefix = "so_"
1.3 内置基础类型
Go 语言按类别有以下几种数据类型:
布尔型 --> 在Go中,布尔值的类型为bool
,值只可以是常量 true 或者 false。一个简单的例子:var b bool = true;
整数型 --> 整型 int 和浮点型 float,Go 语言支持整型和浮点型数字,并且原生支持复数,其中位的运算采用补码;
字符串 --> 字符串就是一串固定长度的字符连接起来的字符序列。Go的字符串是由单个字节连接起来的。Go语言的字符串的字节使用UTF-8编码标识Unicode文本;
派生型 -->
(a) 指针类型(Pointer)
(b) 数组类型
© 结构化类型(struct)
(d) 联合体类型 (union)
(e) 函数类型
(f ) 切片类型
(g) 接口类型(interface)
(h) Map 类型
(i) Channel 类型
Boolean
var isActive bool
var enabled, disabled = true , false
func test ( ) {
var available bool
valid := false
available = true
}
数值类型
整数类型有无符号和带符号两种。Go同时支持int
和uint
,这两种类型的长度相同,但具体长度取决于不同编译器的实现。Go里面也有直接定义好位数的类型:rune
, int8
, int16
, int32
, int64
和byte
, uint8
, uint16
, uint32
, uint64
。其中rune
是int32
的别称,byte
是uint8
的别称。
需要注意的一点是,这些类型的变量之间不允许互相赋值或操作,不然会在编译时引起编译器报错。
如下的代码会产生错误:invalid operation: a + b (mismatched types int8 and int32)
var a int8
var b int32
c:=a + b
另外,尽管int的长度是32 bit, 但int 与 int32并不可以互用。
浮点数的类型有float32
和float64
两种(没有float
类型),默认是float64
。
这就是全部吗?No!Go还支持复数。它的默认类型是complex128
(64位实数+64位虚数)。如果需要小一些的,也有complex64
(32位实数+32位虚数)。复数的形式为RE + IMi
,其中RE
是实数部分,IM
是虚数部分,而最后的i
是虚数单位。下面是一个使用复数的例子:
var c complex64 = 5 + 5i
fmt. Printf ( "Value is: %v" , c)
字符串
Go中的字符串都是采用UTF-8
字符集编码,字符串是用一对双引号(""
)或反引号(
)括起来定义,它的类型是 string
。
func test ( a, b int ) {
str := "hello world"
m := "haha"
result := str + m
println ( result)
multStr := `hello
world`
println ( multStr)
}
在Go中字符串是不可变的,例如下面的代码编译时会报错:cannot assign to s[0]
var s string = "hello"
s[ 0 ] = 'k'
如果你想要修改一个字符串怎么办呢:
s := "hello"
c := [ ] byte ( s)
c[ 0 ] = 'c'
s1 := string ( c)
println ( s1)
Go中可以使用 + 操作符来连接两个字符串:
str := "hello world"
m := "haha"
result := str + m
println ( result)
如果要声明一个多行的字符串怎么办?可以通过`` 来声明:
multStr := `hello
world` go
println ( multStr)
括起的字符串为Raw字符串,即字符串在代码中的形式就是打印时的形式,它没有字符转义,换行也将原样输出。例如本例中会输出:
hello
world
错误类型
Go内置有一个error
类型,专门用来处理错误信息,Go的package
里面还专门有一个包errors
来处理错误:
func error ( ) {
e := errors. New ( "this is a error demo" )
if e != nil {
println ( e)
}
}
1.4 iota枚举
这个关键字用来声明enum
的时候采用,它默认开始值是0,const中每增加一行加1:
package main
const (
x = iota
y
z
w
)
const (
h, i, j = iota , iota , iota
)
const (
l = iota
m = "B"
n = iota
o, p, q = iota , iota , iota
r = iota
)
func main ( ) {
println ( x, y, z, w)
println ( h, i, j)
println ( l, m, n, o, p, q, r)
}
结果:
0 1 2 3
0 0 0
0 B 2 3 3 3 4
1.5 array,slice,map
array
array就是数组,定义方式如下:
var arr [ n] type
在[n]type
中,n
表示数组的长度,type
表示存储元素的类型。对数组的操作和其它语言类似,都是通过[]
来进行读取或赋值:
var arr [ 10 ] int
arr[ 0 ] = 1
arr[ 1 ] = 2
由于长度也是数组类型的一部分,因此[3]int
与[4]int
是不同的类型,数组也就不能改变长度 。数组之间的赋值是值的赋值,即当把一个数组作为参数传入函数的时候,传入的其实是该数组的副本,而不是它的指针。如果要使用指针,那么就需要用到后面介绍的slice
类型了。
数组可以使用另一种:=
来声明:
a := [ 3 ] int { 1 , 2 , 3 }
b := [ 10 ] int { 1 , 2 , 3 }
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 } }
注意: [2][4] int表示的是一个int型数组,数组内有两个数组,每个数组有四个元素组成。
slice
知道python数组的就知道slice,跟python的实现是一样的。
slice有一些简便的操作
slice
的默认开始位置是0,ar[:n]
等价于ar[0:n]
slice
的第二个序列默认是数组的长度,ar[n:]
等价于ar[n:len(ar)]
如果从一个数组里面直接获取slice
,可以这样ar[:]
,因为默认第一个序列是0,第二个是数组的长度,即等价于ar[0:len(ar)]
下面有一些关于slice的示例:
var array = [ 10 ] byte { 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' }
var aSlice, bSlice [ ] byte
aSlice = array[ : 3 ]
aSlice = array[ 5 : ]
aSlice = array[ : ]
aSlice = array[ 3 : 7 ]
bSlice = aSlice[ 1 : 3 ]
bSlice = aSlice[ : 3 ]
bSlice = aSlice[ 0 : 5 ]
bSlice = aSlice[ : ]
重要:slice
是引用类型,所以当引用改变其中元素的值时,其它的所有引用都会改变该值,例如上面的aSlice
和bSlice
,如果修改了aSlice
中元素的值,那么bSlice
相对应的值也会改变。
对于slice
有几个有用的内置函数:
len
获取slice
的长度
cap
获取slice
的最大容量
append
向slice
里面追加一个或者多个元素,然后返回一个和slice
一样类型的slice
copy
函数copy
从源slice
的src
中复制元素到目标dst
,并且返回复制的元素的个数
var array = [ 10 ] int { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 }
var slice = array[ 1 : 7 ]
slice2 := make ( [ ] int , 5 , 10 )
slice3 := [ ] int { 1 , 2 , 3 , 4 , 5 , 6 }
slice5 := slice3[ : 4 ]
println ( slice5)
for i, v := range slice3 {
println ( i, v)
}
var len = len ( slice2)
var cap = cap ( slice)
println ( "len(slice2) =" , len )
println ( "cap(slice) =" , cap )
slice4 := append ( slice2, 6 , 7 , 8 )
slice4 = append ( slice4, slice3... )
println ( slice4)
copy ( slice2, slice3)
println ( slice2, slice3)
map
map就是Java中的Map,python中的字典。它的格式为 map[keyType]valueType
。
fruit := map [ string ] int { "apple" : 5 , "orange" : 7 , "pineapple" : 3 }
println ( fruit)
var appleCount = fruit[ "apple" ]
println ( appleCount)
使用map过程中需要注意的几点:
map
是无序的,每次打印出来的map
都会不一样,它不能通过index
获取,而必须通过key
获取
map
的长度是不固定的,也就是和slice
一样,也是一种引用类型
内置的len
函数同样适用于map
,返回map
拥有的key
的数量
map
的值可以很方便的修改,通过numbers["one"]=11
可以很容易的把key为one
的字典值改为11
map
和其他基本型别不同,它不是thread-safe,在多个go-routine存取时,必须使用mutex lock机制
map
的初始化可以通过key:val
的方式初始化值,同时map
内置有判断是否存在key
的方式
1.6 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
。本质来讲,导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被初始化。例如,一个slice
,是一个包含指向数据(内部array
)的指针、长度和容量的三项描述符;在这些项目被初始化之前,slice
为nil
。对于slice
、map
和channel
来说,make
初始化了内部的数据结构,填充适当的值。
make
返回初始化后的(非零)值。
对于不同的数据类型,零值的意义是完全不一样的。比如,对于bool类型,零值为false;int的零值为0;string的零值是空字符串:
b := new ( bool )
println ( * b)
i := new ( int )
println ( * i)
s := new ( string )
println ( * s)
输出:
false
0
注意:上面最后string的输出是空值。
1.7 零值
关于“零值”,所指并非是空值,而是一种“变量未填充前”的默认值,通常为0。 此处罗列 部分类型 的 “零值”
int 0
int8 0
int32 0
int64 0
uint 0x0
rune 0
byte 0x0
float32 0
float64 0
bool false
string ""
1.8 一些技巧
1.分组声明
在Go语言中,同时声明多个常量、变量,或者导入多个包时,可采用分组的方式进行声明。
例如下面的代码:
import "log"
import "net/http"
import "strings"
const a = 3
const b = 2
const c = 4
var str = "aa"
var prefix = "abc_"
可以写成如下分组形式:
import (
"log"
"net/http"
"strings"
)
const (
a = 2 ,
b = 2 ,
c = 4
)
var (
str = "aa"
prefix = "abc_"
)
Go程序设计的一些规则
Go之所以会那么简洁,是因为它有一些默认的行为:
大写字母开头的变量是可导出的,也就是其它包可以读取的,是公有变量;小写字母开头的就是不可导出的,是私有变量。
大写字母开头的函数也是一样,相当于class
中的带public
关键词的公有函数;小写字母开头的就是有private
关键词的私有函数。
2. 流程和函数
Go中流程控制分三大类:条件判断,循环控制和无条件跳转。
2.1 流程
if
Go里面if
条件判断语句中不需要括号,如下代码所示 :
if x > 80 {
println ( "better" )
} else {
println ( "good" )
}
Go的if
还有一个强大的地方就是条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了,如下所示
if countScore := getCountScore ( ) ; countScore >= 80 {
println ( "better" )
} else if countScore >= 60 {
println ( "good" )
} else {
println ( "e......" )
}
println ( countScore)
goto
Go有goto
语句——请明智地使用它。用goto
跳转到必须在当前函数内定义的标签。例如假设这样一个循环:
a := 4
b := 5
if a > b {
println ( a * b)
} else {
Ding:
a = a+ 12
b = a + ( 32 / 4 )
if a < b {
goto Ding
}
}
注意:标签名是大小写敏感的
for
Go里面的for除了基本的循环外,还可以读取slice和map的数据:
sum := 0
for i:= 0 ; i< 10 ; i++ {
sum += i
}
for
配合range
可以用于读取slice
和map
的数据:
fruit := map [ string ] int { "apple" : 5 , "orange" : 7 , "pineapple" : 3 }
for k, v := range fruit{
println ( k, v)
}
小插曲:
"_"的使用
由于 Go 支持 “多值返回”, 而对于“声明而未被调用”的变量, 编译器会报错, 在这种情况下, 可以使用_
来丢弃不需要的返回值 例如
fruit := map[string]int{"apple":5,"orange":7,"pineapple":3}
for _,v := range fruit{
println(v)
}
"_"相当于占位符的作用,这个位置必须要有一个值来接收,但是这个值又没有用,可以用 “ _”来占着位置。
switch
跟别的语言中的switch别无他致:
var sex byte
switch sex {
case 0 :
println ( "男生" )
case 1 :
println ( "女生" )
default :
println ( "纳尼。。。。。" )
}
2.2 函数
函数是Go里面的核心设计,它通过关键字func
来声明,它的格式如下:
func funcName ( input1 type1, input2 type2) ( output1 type1, output2 type2) {
return value1, value2
}
上面的代码我们看出:
关键字func
用来声明一个函数funcName
函数可以有一个或者多个参数,每个参数后面带有类型,通过,
分隔
函数可以返回多个值
上面返回值声明了两个变量output1
和output2
,如果你不想声明也可以,直接就两个类型
如果只有一个返回值且不声明返回值变量,那么你可以省略 包括返回值 的括号
如果没有返回值,那么就直接省略最后的返回信息
如果有返回值, 那么必须在函数的外层添加return语句
来看一个最简单的函数:
package main
func add ( a, b int ) map [ ] {
var resultMap = map [ string ] int { }
resultMap[ "add" ] = a + b
resultMap[ "multi" ] = a * b
resultMap[ "subtract" ] = a - b
resultMap[ "division" ] = a / b
return resultMap
}
func main ( ) {
a := 3
b := 4
count := add ( a, b)
println ( count)
}
多个返回值:
Go中函数可以有多个返回值,这个比java强悍100倍:
package main
func add ( a, b int ) ( int , int ) {
return a+ b, a- b
}
func main ( ) {
a := 3
b := 4
add, sub := add ( a, b)
println ( add, sub)
}
可变参数:
跟Java中差不多吧:
func myFunc ( arg ... int ) { }
defer
defer是golang的一个特色功能,被称为“延迟调用函数”。当外部函数返回后执行defer。类似于其他语言的 try… catch … finally… 中的finally,当然差别还是明显的。
释放占用资源:
func test ( ) error {
file, err := os. Open ( "path" )
if err != nil {
return err
}
defer file. Close ( )
return nil
}
异常处理:
func test2 ( ) {
defer func ( ) {
if err := recover ( ) ; err != nil {
println ( err)
}
} ( )
file, err := os.
请发表评论