原则
点操作 有时候会看到如下的方式导入包 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>
## 定义函数
```go
func 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+n
return//定义了返回值变量,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等都是引用类型,传的是地址_
```go
func fun(i string){
i+="b"
}
func fun2(i *string){
*i+="b"
}
func main(){
i:="a"
fun(i)//基本类型传递的是值,如果函数中对值发生了变化,这里的i是不会变的
fmt.Println(i)//a
n :="a"
fun2(&n)//这里传递的是n的地址,
fmt.Println(n) //ab
}
将函数作为参数传递
package main
import (
"encoding/json"
"fmt"
)
type Student struct {
Name string
Age int
IsMan bool
Score 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 main
import "fmt"
type Options struct {
StringOption1 string
StringOption2 string
IntOption1 int
IntOption2 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:=0
for _,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+=n
newn :=strconv.FormatInt(int64(i),10)
str+=newn
fmt.Println(str)
/*
hello11
hello1112
hello111213
*/
return i
}
}
func main() {
f:=run()
fmt.Println(f(1)) //11
fmt.Println(f(1))//12
fmt.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+n
fmt.Println("计算完成")
return res
}
func main() {
res := sum1(10,20)
fmt.Println(res)
}
/*
计算完成
n= 20
i= 10
32
*/
使用陷阱
func f1() (t int){
defer func() {
t++
}()
return 0
}
func f2() (r int){
t:=5
defer func() {
t++
}()
return t
}
func f3() (r int){
defer func(r int) {
r++
}(r)
return r
}
func main() {
res1 :=f1()//1
res2 :=f2()//5
res3 :=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 int
defer func(r *int) {
*r++
}(&r)
return r
}
res3 :=f3()//0,
例子3
package main
import "fmt"
type A struct {
A1 int64
}
func main() {
fmt.Printf("%d\n",test1().A1) // 100
fmt.Printf("%d\n",test2().A1) // 200
}
func test1()(A){
var a A
defer func() {
a.A1 = 200
}()
a.A1 = 100
return a
}
func test2()(*A){
var a A
defer func() {
a.A1 = 200
}()
a.A1 = 100
return &a
}
例子4
package main
import "fmt"
type A struct {
A1 int64
}
func main() {
fmt.Printf("%d\n",test1().A1) // 100
fmt.Printf("%d\n",test2().A1) // 200
fmt.Printf("%d\n",test3().A1) // 200
}
func test1()(A){
var a A
defer func() {
a.A1 = 200
}()
a.A1 = 100
return a
}
func test2()(*A){
var a A
defer func() {
a.A1 = 200
}()
a.A1 = 100
return &a
}
func test3()(a A){
defer func() {
a.A1 = 200 // 这里的a是对返回值栈区的闭包引用(地址一样)
}()
a.A1 = 100
return a
}