一、流程控制
Go语言的流程控制包括3大类:条件判断、循环控制和无条件跳转。
1、if
Go语言的if条件判断语句中不需要括号,具体如下:
if x > 10 {
fmt.Println("x is greater than 10")
}
else {
fmt.Println("x is less than 10")
}
上面这些跟其它脚本语言相比没什么出奇的。Go语言的if有一个强大的地方就是条件判断语句里面允许声明一个变量,其作用域只能在该条件逻辑块内:
//计算获取值x,然后根据x返回的大小,判断是否大于10
if x := computedValue(); x > 10 {
fmt.Println("x is greater than 10")
}
else{
fmt.Println("x is less than 10")
}
//这个地方如果调用x就会出错,因为x仅仅是条件里的变量
fmt.Println(x)
2、goto
这是一条在各种语言中都存在争议的语句,在Go语言里也不例外。用goto只能跳转到当前函数内定义的标签处:
func myFunc(){
i := 0
Here: //同其它语言也是以冒号结束作为标签
println(i)
i++
goto Here //跳转到Here去
}
标签名是大小写敏感的。
3、for
Go语言中的for不同于C语言,其既可以用来循环读取数据,又可以当作while来控制逻辑,还能迭代操作,其语法如下:
for expression1; expression2; expression3 {
//...
}
expression1和expression3是变量声明或者函数调用返回值之类的,expression2是条件判断表达式,expression1在循环开始前调用,expression3在每轮循环结束之时调用。举例说明如下:
package main
import "fmt"
func main(){
sum := 0;
for index:=0; index<10; index++ {
sum += index
}
fmt.Println("sum is equal to ", sum)
}
在Go语言中,for的表达式1和3是可以忽略的,那么就会简化成while形式了,具体如下:
sum := 1
for sum < 1000 {
sum += sum
}
在循环中,一般都会有两个重要的操作即break和continue,break跳出当前循环,continue跳过本次循环。这都很稀松平常,干货来了,当循环嵌套过深时,break可以配合标签使用,即跳转至标签所指定的位置,以此来跳到多重循环中的外层循环。
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 {
fmr.Println("map's val:", v)
}
4、switch
switch与C语言类似,语法如下:
switch sExpr {
case expr1:
some instructions
case expr2:
some other instructions
case expr3:
some other instructions
default:
other code
}
sExpr和expr1、expr2、expr3的类型必须一致。Go语言的switch非常灵活,表达式不必是常量或整数,执行过程从上至下,直到找到匹配项;而如果switch没有表达式,它会匹配true。Go语言里面switch默认相当于每个case最后自动带有break,但是如果在case语句后使用fallthrough会强制执行后面的case代码。
integer := 5
switch integer {
case 4:
fmt.Println("The integer was <= 4")
fallthrough
case 5:
fmt.Println("The integer was <= 5")
fallthrough
case 6:
fmt.Println("The integer was <= 6")
default:
fmt.Println("default case")
}
上面程序输出结果如下:
The integer was <= 5
The integer was <= 6
default case
二、 函数
函数是Go语言里面的核心设计,其通过关键字func来声明,具体格式如下:
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
//逻辑代码
//返回多个值
return value1, value2
}
函数相邻参数类型一致,则前一个参数可以省略类型,参数变量名在类型前,如果没有返回值的话可以忽略输出参数声明部分即output,输出参数可以只给出类型,也可以给出变量名,给出变量名后return时可以省略返回的变量列表,但是不建议这么做,这种情况程序可读性很差。
package main
package "fmt"
func max(a, b int) int {
if a > b {
return a
}
return b
}
1、多值返回
Go语言比C语言先进的特性在于提供了多值返回,这点同Lua,举例如下:
package main
import "fmt"
//返回A+B和A*B
func SumAndProduct(A, B int) (int, int) {
return A+B, A*B
}
func main() {
x := 3
y := 4
xPLUSy, xTIMESy := SumAndProduct(x, y)
fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)
fmt.Printf("%d + %d = %d\n", x, y, xTIMESy)
}
2、变参
Go语言函数支持变参,具体语法如下:
func myfunc(arg ...int) {}
arg ...int表示函数接受类型为int的不定数量的参数。在函数体中,变量arg是一个slice。
for _, n := range arg {
fmt.Printf("And the number is: %d\n", n)
}
3、传值与传指针
这点与C语言一致,在此对于传指针说明以下几点:
(1)传指针使得多个函数能操作同一个对象。
(2)传指针比较轻量级即只传内存地址。这在传递体积比较大的结构体时,可以节省传值时copy的系统开销(虽然也需要copy地址)。
(3)Go语言中string、slice、map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。需要注意的是:若函数需要改变slice的长度,则仍需要取地址传递指针。
4、defer
Go语言中有种不错的设计,即延迟(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
}
上面这段代码如果采用defer则会优雅得多:
func ReadWrite() bool {
file.Open("file")
defer file.Close()
if failureX {
return false
}
if failureY {
return false
}
return true
}
在defer后指定的函数会在函数推出前调用,所以非常优雅。如果有很多调用defer,则defer采用后进先出模式,所以如下代码会输出4 3 2 1 0:
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
5、第一类值
在Go语言中函数也是一种变量,这点与Lua中的函数类似,是作为第一类值的。我们可以通过type来定义它,其类型就是所有拥有相同的参数,相同的返回值:
type typename func(input1 inputType1, input2 inoutType2 [, ...]) (result1 resultType1 [, ...])
函数作为类型的好处就是可以把函数当做值来传递:
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)
}
函数当做值和类型在我们写一些通用接口的时候非常有用,通过上面例子我们看到testInt这个类型是一个函数类型,两个filter函数的参数和返回值与testInt类型是一样的,但是我们可以实现很多种的逻辑,这样使得我们的程序变得非常灵活。
6、Panic和Recover
Go语言没有像Java那样的异常处理机制,即不能抛出异常,而是使用了panic和recover机制。
(1)Panic
Panic是一个可以中断原有控制流程的内建函数。当函数F调用panic,则函数F的执行被中断,但是F中的延迟函数defer会正常执行,然后F返回到调用它的地方。在调用的地方,F的行为就像调用了panic。这一过程继续向上直到发生panic的goroutine中所有调用的函数返回,此时程序退出。
(2)Recover
Recover是一个内建函数,其能让产生异常的goroutine恢复过来。recover仅在延迟函数中有效。在正常执行过程中调用recover会返回nil,并且没有其它任何效果。在goroutine发生异常时,调用recover可以捕获到panic的输入值,并且恢复正常的执行。
下面函数演示如何使用panic:
var user = os.Getenv("USER")
func inti() {
if user == "" {
panic("no value for $USER")
}
}
下面函数检查作为其参数的函数在执行时是否会产生panic:
func throwsPanic(f func()) (b bool) {
defer func() {
if x := recover(); x != nil {
b = true
}
} ()
f() //执行函数f,如果f中出现了panic,那么就可以恢复回来
return
}
7、main函数和init函数
Go语言里面有两个保留的函数:init函数(能够应用于所有的package)和main函数(只能应用于package main)。这两个函数定时不能有任何参数和返回值。Go语言程序会自动调用init()和main(),前面是可选的,但后者是package main必须的。
程序的初始化和执行都起始于main包。如果main包还导入了其它的包,那么编译时就会将它们依次导入,有时候一个包被多个包同时导入,那么它只会被导入一次。当一个包被导入时,如果该包还导入了其它包,则会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的话),依次类推。等所有被导入的包都加载完毕了,就会开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执行main函数。
8、import
在Go语言代码中一般用import来导入包文件:
imort(
"fmt"
)
然后在代码中就可以如下方式来调用包中函数:
fmt.Println("hello world")
fmt是Go语言的标准库,其实是去goroot下加载该模块。Go语言的import还支持如下两种方式来加载自己写的模块:
(1)相对路径
import "./model" //当前文件同一目录的model目录
(2)绝对路径
import "shorturl/model" //加载gopath/src/shorturl/model模块
除了上面几种import方式外,还有一些特殊的import形式:
(1)点操作
import(
. "fmt"
)
用点操作导入包后,调用包中函数时可以省略前缀的包名,也就是前面的调用可以省略成Println("hello world")。
(2)别名操作
别名操作就是可以将包命名为另一个容易记忆的名字:
import(
f "fmt"
)
这样前面调用也可以这样了f.Println("hello world")。
(3)_操作
import(
"database/sql"
_ "github.com/ziutek/mysql/godir"
)
_操作其实是引入该包,不直接使用包里的函数,而是调用了该包里面的init函数。
阅读(448) | 评论(0) | 转发(0) |