一、Go基础
①“调用”就是向被调用函数的参数赋值,执行被调用函数语句,并使用其返回值。
这样,函数就封装了可以被反复调用的一些语句,根据需要赋值得到不同的结果。
② min,max := 0, 100
var s string
min,max通过赋值引申声明为int类型,s用var明确声明为string类型。
③Go使用字节而不是字符作为字符串的单元。因为Go的字符采用UTF-8编码,是不等长的。
英文字母数字是1个字节,希腊字母要2个字节,汉字等大写字符集,使用3个以上的字节
才能表示。所以字符串取其最小单位。
④计算结果的类型与操作数类型相同。 3/2 == 1.
⑤Go里面switch默认相当于每个case最后带有break,匹配成功后不会
自动向下执行其他case,
而是跳出整个switch, 但是可以使用fallthrough强制执行后面的case代码
⑥这样,我们就达到了修改x的目的。那么到底传指针有什么好处呢?
1.传指针使得多个函数能操作同一个对象。
2.传指针比较轻量级 (8bytes),只是传内存地址,我们可以用指针传递体积大的结构体。如果用参数值传递的话,
在每次copy上面就会花费相对较多的系统开销(内存和时间)。
所以当你要传递大的结构体的时候,用指针是一个明智的选择。
3.Go语言中channel,slice,map这三种类型的实现机制类似指针,
所以可以直接传递,而不用取地址后传递指针。
(注:若函数需改变slice的长度,则仍需要取地址传递指针)
⑦Go语言中有种不错的设计,即延迟(defer)语句,你可以在函数中添加多个defer语句。
当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回。
特别是当你在进行一些打开资源的操作时,遇到错误需要提前返回,在返回前你需要关闭相应的资源,
不然很容易造成资源泄露等问题。
在defer后指定的函数会在函数退出前调用。
二、流程和函数
函数作为值、类型
在Go中函数也是一种变量,我们可以通过type来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型
type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])
函数作为类型到底有什么好处呢?那就是可以把这个类型的函数当做值来传递,请看下面的例子
package main
import"fmt"
type testInt func(int) bool// 声明了一个函数类型
func isOdd(integer int) bool {
if integer%2 == 0 {
returnfalse
}
returntrue
}
func isEven(integer int) bool {
if integer%2 == 0 {
returntrue
}
returnfalse
}
// 声明的函数类型在这个地方当做了一个参数
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("Oddelements of slice are: ", odd)
even := filter(slice, isEven) // 函数当做值来传递了
fmt.Println("Evenelements of slice are: ", even)
}
函数当做值和类型在我们写一些通用接口的时候非常有用,通过上面例子我们看到testInt这个类型是一个函数类型,然后两个filter函数的参数和返回值与testInt类型是一样的,但是我们可以实现很多种的逻辑,这样使得我们的程序变得非常的灵活。
Panic和Recover
Go没有像Java那样的异常机制,它不能抛出异常,而是使用了panic和recover机制。一定要记住,你应当把它作为最后的手段来使用,也就是说,你的代码中应当没有,或者很少有panic的东西。这是个强大的工具,请明智地使用它。那么,我们应该如何使用它呢?
Panic
是一个内建函数,可以中断原有的控制流程,进入一个令人恐慌的流程中。当函数F调用panic,函数F的执行被中断,但是F中的延迟函数会正常执行,然后F返回到调用它的地方。在调用的地方,F的行为就像调用了panic。这一过程继续向上,直到发生panic的goroutine中所有调用的函数返回,此时程序退出。恐慌可以直接调用panic产生。也可以由运行时错误产生,例如访问越界的数组。
Recover
是一个内建的函数,可以让进入令人恐慌的流程中的goroutine恢复过来。recover仅在延迟函数中有效。在正常的执行过程中,调用recover会返回nil,并且没有其它任何效果。如果当前的goroutine陷入恐慌,调用recover可以捕获到panic的输入值,并且恢复正常的执行。
下面这个函数演示了如何在过程中使用panic
var user = os.Getenv("USER")
func init() {
if user == "" {
panic("no value for $USER")
}
}
下面这个函数检查作为其参数的函数在执行时是否会产生panic:
func throwsPanic(f func()) (b bool) {
deferfunc() {
if x := recover(); x != nil {
b = true
}
}()
f() //执行函数f,如果f中出现了panic,那么就可以恢复回来
return
}
main函数和init函数
Go里面有两个保留的函数:init函数(能够应用于所有的package)和main函数(只能应用于package main)。这两个函数在定义时不能有任何的参数和返回值。虽然一个package里面可以写任意多个init函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个package中每个文件只写一个init函数。
Go程序会自动调用init()和main(),所以你不需要在任何地方调用这两个函数。每个package中的init函数都是可选的,但package main就必须包含一个main函数。
程序的初始化和执行都起始于main包。如果main包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多次)。当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的话),依次类推。等所有被导入的包都加载完毕了,就会开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执行main函数。下图详细地解释了整个执行过程:
图2.6 main函数引入包初始化流程图
import
我们在写Go代码的时候经常用到import这个命令用来导入包文件,而我们经常看到的方式参考如下:
import( "fmt")
然后我们代码里面可以通过如下的方式调用
fmt.Println("hello world")
上面这个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函数。