原则
点操作 有时候会看到如下的方式导入包 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()函数就会被执行,但有些时候并非真的需 要使用这些包,仅仅是希望它的init()函数被执行而已。这个时候就可以使用操作引用该包了。 即使用_操作引用包是无法通过包名来调用包中的导出函数,而是只是为了简单的调用其init函数()。
<a name="uaJsD"></a>## 定义函数```gofunc name(参数 类型,...) 返回值类型{}例1:func sum(i int,n int )int{fmt.Println(i+n)return i+n}例2:定义返回值变量func sum(i int,n int )(m int){fmt.Println(i+n)m = i+nreturn//定义了返回值变量,return后就不用接语句了}
函数的返回值
- go支持多个返回值,用逗号隔开即可 ```go func sum_sub(i int,n int )int{ sum := i+n sub := i-n return sum,sub }
func main(){ sum,sub :=sumsub(1,2) //如果只想要其中一个,另一个用‘’占位即可 sum,_ :=sum_sub(1,2) }
<a name="ELcTV"></a>## 传参- **_值类型:_**_基本数据类型int系列, float系列, bool, string 、数组和结构体struct_- **_引用类型:_**_指针、slice切片、map、管道chan, interface等都是引用类型,传的是地址_```gofunc fun(i string){i+="b"}func fun2(i *string){*i+="b"}func main(){i:="a"fun(i)//基本类型传递的是值,如果函数中对值发生了变化,这里的i是不会变的fmt.Println(i)//an :="a"fun2(&n)//这里传递的是n的地址,fmt.Println(n) //ab}
将函数作为参数传递
package mainimport ("encoding/json""fmt")type Student struct {Name stringAge intIsMan boolScore float64}// 定义一个接收一个Student类型参数的函数类型type handle func(str Student) string// exec函数,接收handle类型的参数func exec(f handle) string{s :=Student{"jw",12,true,100}return f(s)}func fun(s Student) string{data,_ :=json.Marshal(s)return string(data)}func main() {//带名函数stu_1 :=exec(fun)// 匿名函数作为参数直接传递给exec函数stu_2 :=exec(func(s Student) string{data,_ :=json.Marshal(s)return string(data)})fmt.Println(stu_1)fmt.Println(stu_2)}
一种高可用的设计模式
package mainimport "fmt"type Options struct {StringOption1 stringStringOption2 stringIntOption1 intIntOption2 int}type Option func(opt *Options)func InitOption(opt ...Option){options := &Options{}for _,opt :=range opt{opt(options)}fmt.Printf("%v",*options)}func WithStringOption1(str string)Option{return func(opt *Options) {opt.StringOption1=str}}func WithStringOption2(str string)Option{return func(opt *Options) {opt.StringOption2=str}}func WithIntOption1(intvar int)Option{return func(opt *Options) {opt.IntOption1=intvar}}func WithIntOption2(intvar int)Option{return func(opt *Options) {opt.IntOption2=intvar}}func main() {InitOption(WithStringOption1("aaa"),WithStringOption2("bbb"),WithIntOption1(111),WithIntOption2(222),)}
不定参
//arr相当于切片func test_sum(arr ...int) int{sum:=0for _,v :=range arr{sum+=v}return sum}func main() {sum:=test_sum(1,2,3)fmt.Println(sum)}
数组陷阱
语法错误:注意:[]int是切片,而不是数组,[3]int才是数组类型func array(arr []int){fmt.Println(arr)}func main() {arr := [3]int{1,2,3}array(arr)}array(arr [4]int),这样定义也是错的,长度不一致,编译器会认为它们数据类型不一样array(arr [...]int),这样定义也是错的,必须明确指定数组长度
匿名函数
//全局匿名函数var(Fun1 = func (i int,n int) int{return i*n})func main() {方式一:sum :=func (i int,n int) int{return i+n}(1,2)fmt.Println(sum)//3方式二:sub := func (i int,n int) int{return i-n}res:=sub(1,2)fmt.Println(res)//-1方式三:全局匿名函数res1:=Fun1(2,3)fmt.Println(res1)//6}
闭包
//闭包func run() func(int) int{i:=10//外层包的数据只会初始化一次str:="hello"return func(n int) int {i+=nnewn :=strconv.FormatInt(int64(i),10)str+=newnfmt.Println(str)/*hello11hello1112hello111213*/return i}}func main() {f:=run()fmt.Println(f(1)) //11fmt.Println(f(1))//12fmt.Println(f(1))//13}
函数体内可修改全局变量
var name = "张三"//不能使用 name :="张三" 这样去定义func test1(){name = "李四"//修改值,对全局变量有影响name := "李四"//重新定义name,对全局没影响,且语法正确fmt.Println(name)}func main() {test1()fmt.Println(name)//李四 李四}//将test1中的name="李四"改为,name :="李四"//李四//张三
函数的执行顺序
import ("fmt"u"函数/utlis")var age = test()func test()int{fmt.Println("test函数执行了")return 1}//init函数会在main函数之前执行,通常用于初始化func init() {fmt.Println("init函数执行了")}func main(){fmt.Println("main函数执行了")fmt.Println(age)fmt.Println(u.Name)}/*utlis包中的init函数执行了test函数执行了init函数执行了main函数执行了1张三*/
defer
func sum1(i int,n int) int{//defer语句,暂时不执行,而是当所在函数执行完之后再执行,会将defer后的语句压入到栈中//当函数执行完后,会以LIFO的方式出栈并开始执行,通常用于收尾工作defer fmt.Println("i=",i)defer fmt.Println("n=",n)i++//注意在defer之后对值发生变化,对已经压入站的defer语句没影响n++res:=i+nfmt.Println("计算完成")return res}func main() {res := sum1(10,20)fmt.Println(res)}/*计算完成n= 20i= 1032*/
使用陷阱
func f1() (t int){defer func() {t++}()return 0}func f2() (r int){t:=5defer func() {t++}()return t}func f3() (r int){defer func(r int) {r++}(r)return r}func main() {res1 :=f1()//1res2 :=f2()//5res3 :=f3()//0}
解析:f1
- 带名返回值,t被赋值为零值:0
- return 0,复制0到返回值栈区,返回值t被赋值为0
- defer ,由于匿名函数是对t的闭包引用(地址一样),t++后,t=1,因此函数返回值被修改为1
解析:f2
- 带名返回值,r被赋值为零值:0
- 定义局部变量t,变并初始化为5
- return t,复制t的值到返回值栈区,返回值r被赋值为5
- defer t++,由于匿名函数是对t的闭包引用,t++后,t=6,此时返回值栈区变量为r,且值是5,所以函数返回值为5
解析:f3
- 带名返回值,r被赋值为零值:0
- return r = return 0,复制0到返回值栈区,返回值r被赋值为0
- defer r++ ,此时匿名函数是带参数的,参数为r,此时会复制f3中的r值:0传给匿名函数,经过r++后,匿名函数中的r值修改为1,注意此时f3中与匿名函数中的变量r的地址是不一样的,因此函数返回0
例子1
func f3() (r int){defer func(r *int) {*r++}(&r)//传地址return r}res3 :=f3()//1,
例子2
func f3() (int){var r intdefer func(r *int) {*r++}(&r)return r}res3 :=f3()//0,
例子3
package mainimport "fmt"type A struct {A1 int64}func main() {fmt.Printf("%d\n",test1().A1) // 100fmt.Printf("%d\n",test2().A1) // 200}func test1()(A){var a Adefer func() {a.A1 = 200}()a.A1 = 100return a}func test2()(*A){var a Adefer func() {a.A1 = 200}()a.A1 = 100return &a}
例子4
package mainimport "fmt"type A struct {A1 int64}func main() {fmt.Printf("%d\n",test1().A1) // 100fmt.Printf("%d\n",test2().A1) // 200fmt.Printf("%d\n",test3().A1) // 200}func test1()(A){var a Adefer func() {a.A1 = 200}()a.A1 = 100return a}func test2()(*A){var a Adefer func() {a.A1 = 200}()a.A1 = 100return &a}func test3()(a A){defer func() {a.A1 = 200 // 这里的a是对返回值栈区的闭包引用(地址一样)}()a.A1 = 100return a}
