Go基础篇之一

来源:互联网 发布:数据库逻辑数据模型 编辑:程序博客网 时间:2024/05/01 22:05
1、每一个可独立运行的Go程序,必定包含一个package main,在这个main包中必定包含一个入口函数main,而这个函数既没有参数,也没有返回值。
2、Go使用package来组织代码。main.main()函数(这个函数主要位于主包)是每一个独立的可运行程序的入口点。Go使用UTF-8字符串和标识符,所以它天生就具有多语言的支持。
3、:=只能用在函数内部,在函数外部使用则会无法编译通过,所以一般用var方式来定义全局变量。
 _(下划线)是个特殊的变量名,任何赋予它的值都会被丢弃。如下例:值35赋予b,并同时丢弃34
_, b := 34, 35
4、数值类型
 直接定义好位数的类型:rune,int8, int16 和byte, uint8, uint16, uint32, uint64.其中rune是int32的别称,byte是uint8的别称。
注意:
这些类型的变量之间不允许互相赋值或操作,不然会在编译时引起编译器报错。如:
var a int8    var b int32    c := a + b    fmt.Println(c)
控制台报错:
invalid operation: a + b (mismatched types int8 and int32)
另外,尽管int的长度是32bit,但bit与int32并不可以互用
5、数组
定义: var arr [n]type
n表示数组的长度,type表示存储元素的类型。对数组的操作通过[]来进行读取或赋值。
由于长度也是数组类型的一部分,因此[3]int与[4]是不同的类型,数组也就不能改变长度。数组之间的赋值是值的赋值,即当把一个数组作为参数传入函数的时候,传入的其实是该数组的副本,而不是它的指针。

slice
slice并不是真正意义上的动态数组,而是一个引用类型slice总是指向一个底层array,slice的声明也可以像array一样,只是不需要长度。
slice和数组在声明时的区别:
声明数组时,方括号内写明了数组的长度或使用...自动计算长度,而声明slice时,方括号内
没有任何字符。
slice是引用类型,所以当引用改变其中元素的值时,其他的所有引用都会改变该值。从概念上面来说slice像一个结构体,这个结构体包含了三个元素:
一个指针,指向数组中slice指定的开始位置
长度,即slice的长度
最大长度,也就是slice开始位置到数组的最后位置的长度

append函数会改变slice所引用的数组的内容,从而影响到引用同一数组的其它slice。但当slice中没有剩余空间(及(cap - len) == 0)时,此时将动态分配新的数组空间。返回的slice数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的slice则不受影响。

6、map
num := make(map[string]int)    num["one"] = 1    fmt.Println(num["one"])
使用map过程中需要注意:
map是无序的,每次打印出来的map都会不一样,它不能通过index获取,而必须通过key获取
map的长度是不固定的,也就是和slice一样,也是一种引用类型
内置的len函数同样适用于map,返回map拥有的key的数量
map的值可以很方便的修改,通过numbers["one"] = 11可以很容易的把key为one的字典值改为11
7、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返回初始化后的(非零)值
8、if
if条件判断语句中不需要括号。
条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了。
9、goto
用goto跳转到必须在当前函数内定义的标签
func myFunc() {<span style="white-space:pre"></span>i := 0<span style="white-space:pre"></span>Here:<span style="white-space:pre"></span>println(i)<span style="white-space:pre"></span>i++<span style="white-space:pre"></span>goto Here    }
标签名是大小写敏感的。
10、传值与传指针
当我们传一个参数值到被调用函数里面时,实际上是传了这个值的一份copy,当在被调用函数中修改参数值
func add(a int) int {      <span style="white-space:pre"></span>a = a + 1      <span style="white-space:pre"></span>return a     }

main:
  x := 3      x1 := add(x)      fmt.Println("x = ", x)      fmt.Println("x1 = ", x1)
outputs:
x =  3
x1 =  4
虽然调用了add函数,并且在函数中执行a=a+1操作,但是x变量的值没有发生变化。
理由很简单:因为当我们调用add的时候,add接受的参数其实是x的copy,而不是x本身。

如果真的需要传这个x本身,该怎么办呢?
这就牵扯到了所谓的指针。我们知道,变量在内存中是存放于一定地址上的,修改变量实际是修改变量地址处的内存。只有add函数知道x变量所在的地址,才能修改x变量的值。所以我们需要将x所在地址&x传入函数,并将函数的参数类型由int改为*int,即改为指针类型,才能在函数中修改x变量的值。此时参数仍然是按copy传递的,只是copy的是一个指针。
如下例:
func add1(a *int) int {    *a = *a + 1      return *a }


main:
x := 3x2 := add1(&x)    fmt.Println("x+1 = ", x2)    fmt.Println("x = ", x)
outputs:
x+1 =  4
x =  4
这样,我们就达到了修改x的目的。那么到底传指针有什么好处呢?
传指针使得多个函数能操作同一个对象
传指针比较轻量级(8bytes),只是传内存地址,我们可以用指针传递体积大的结构体。如果用参数值传递的话,在每次copy上面就会花费相对较多的系统开销(内存和时间)。所以当你要传递大的结构体的时候,用指针是一个明智的选择。
Go语言中string,slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变slice的长度,则仍需要取地址传递指针)
11、函数作为值、类型
在Go中函数也是一种变量,我们可以通过type来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型。
例:
func isEven(integer int) bool {    if integer%2 == 0 {            return true      }          return false     }func isOdd(integer int) bool {    if integer%2 == 0 {            return false      }          return true     }     func filter(slice []int, f testInt) []int {    var result []int        for _, value := range slice {            if f(value) {               result = append(result, value)              }      }      return result  }

main:
sli := []int{1, 2, 3, 4, 5, 7}fmt.Println("sli = ", sli)    odd := filter(sli, isOdd) //函数当做值来传递了    fmt.Println("Odd elements of sli are: ", odd)    even := filter(sli, isEven)    fmt.Println("Even elements of sli are: ", even)
outputs:
sli =  [1 2 3 4 5 7]
Odd elements of sli are:  [1 3 5 7]
Even elements of sli are:  [2 4]
12、Panic和Recover
Go没有像Java那样的一场机制,它不能抛出异常,而是使用了panic和recover机制。一定要记住,你应当把它作为最后的手段来使用,也就是说,你的代码中应当没有,或者很少有panic的东西。
Panic
是一个內建函数,可以中断原有的控制流程,进入一个panic流程中。当函数F调用panic,函数F的执行被中断,但是F中的延迟函数会正常执行,然后F返回到调用它的地方。在调用的地方,F的行为就像调用了panic。这一过程继续向上,知道发生panic的goroutine中所有调用的函数返回,此时程序退出。panic可以直接调用panic产生,也可以由运行时错误产生,例如访问越界的数组。
Recover
是一个內建的函数,可以让进入panic的流程中的goroutine恢复过来。recover仅在延迟函数中有效。在正常的执行过程中,调用recover会返回nil,并且没有其它任何效果。如果当前的goroutine进入panic,调用recover可以捕获到panic的输入值,并且恢复正常的执行。
13、main函数和init函数
Go里面有两个保留的函数:init函数(能够应用于所有的package)和main函数(只能应用于package main)。这两个函数在定义时不能有任何的参数和返回值。虽然一个package里面可以写任意多个init函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个package中每个文件只写一个init函数。
Go程序会自动调用init()和main(),所以你不需要在任何地方调用这两个函数。每个package中的init函数都是可选的,但package main就必须包含一个main函数。程序的初始化和执行都起始于main包。如果main包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次。当一个包被导入时,如果该包还导入了其他的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的话),然后执行main包中的init函数(如果存在的话),最后执行main函数。如下图:

  • main函数引入包初始化流程图

14、import
import支持如下两种方式来加载自己写的模块:
1、相对路径
import "./model"        //当前文件同一目录的model目录,但是不建议这种方式来import
2、绝对路径
import "shorturl/model"     //加载gopath/src/shorturl/model模块
还有一些特殊的import
1、点操作
import(<span style="white-space:pre"></span>."fmt"    )

这个点操作的含义就是这个包导入之后在你调用这个包的函数时,你可以省略前缀的包名,也就是fmt.Println("hello worle")可以省略的写成Println("hello world")
2、别名操作
顾名思义我们可以把包命名成另一个我们用起来容易记忆的名字
import(
  • f "fmt"
)
调用包函数时前缀变成了我们的前缀,即f.Println("hello world")
3、 _操作
import(<span style="white-space:pre"></span>_ "github.com/ziutek/mymysql/godrv"    )


_操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数。
15、Go程序设计的一些规则
Go之所以会那么简洁,是因为它有一些默认的行为:
大写字母开头的变量是可导出的,也就是其他包可以读取的,是公用变量;小写字母开头的就是不可导出的,是私有变量。
大写字母开头的函数也是一样,相当于class中的带public关键词的公有函数;小写字母开头的就是有private关键词的私有函数。





0 0