返回

Go学习笔记02-Go语言基础

编译命令

go run

go run命令直接编译和执行源码中的main函数,但是并不会留下任何可执行文件(可执行文件被放在临时文件中执行,执行结束后将被自动删除)。go run命令后可以添加参数。

来到HelloGo.go文件的目录下,执行如下命令:

1
go run HelloGo.go

go build

go build命令会将源码编译为可执行文件,默认将编译该目录下的所有的源码。也可以在命令后添加多个文件名,go build命令将编译这些源码,输出可执行文件。

同样来到HelloGo.go文件的目录下,执行如下命令,其中-o选项用于指定生成的可执行文件的文件名:

1
go build -o HelloGo HelloGo.go

或者

1
go build HelloGo.go

都将在当前目录下生成一个HelloGo的可执行文件。

基本语法

变量的声明与初始化

var是Go语言中声明变量的关键字。Go语言在声明变量时,会自动把变量对应的内存区域 进行初始化操作,每个变量会被初始化为其类型的默认值。即变量一经声明,则被初始化为其类型的默认值。变量声明样式如下所示:

1
var name T

在Go语言中,每一个声明的变量都必须被使用,否则会编译不通过。即变量一经声明,则必须使用

变量初始化样式:

1
var name T = 表达式

类型推导

Go语言提供了类型推导的语法糖,可精简变量初始化为以下样式:

1
name := 表达式

类型推导的语法糖省略了声明变量的关键字var和类型属性T

除了类型推导的语法糖特性,Go语言还提供了多重赋值匿名变量的语法糖特性。

多重赋值

多重赋值特性可以轻松实现变量变换任务,不需要借助第三方变量。如下所示:

1
2
3
a := 1
b := 2
b, a =a, b

匿名变量

通过在不需要的变量声明的地方使用'_‘来代替变量名,我们就可以忽略部分不需要的左值。匿名变量不占用命名空间不会分配内存匿名变量与匿名变量之间也不会因为多次声明而无法使用。具体例子如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package main

import "fmt"

// 返回一个人的姓和名
func getName() (string, string){
	return "王", "小二" 			  // Go语言支持函数多返回值
}

func main()  {
	surname,_ := getName() 			// 使用匿名变量
	_, personalName := getName()	// 使用匿名变量
	
	fmt.Printf("My surname is %v and my personal name is %v", surname, personalName)
}

原生数据类型

基本数据类型:整型、浮点数、布尔型、字符串型等。

整型

整型中主要有两大类,分别是:

  • 按照整型的长度划分:int8int16int32int64
  • 按照有无符号划分:uint8uint16uint32uint64

除此之外,Go语言还提供了平台自匹配长度的int类型和uint类型。

整型类型之间可以相互转换,高长度类型向低长度类型转换时仅保留高长度类型的低位值。

浮点型

浮点型主要有两种:

  • float32:单精度,存储占用4个字节,也即4*8=32位,其中1位用来符号,8位用来指数,剩下的23位表示尾数
  • float64:双精度,存储占用8个字节,也即8*8=64位,其中1位用来符号,11位用来指数,剩下的52位表示尾数

float32float64之间可以进行类型转换,但需要注意精度损失。

布尔型

truefalse。不能与整型进行强转,也无法参与数值运算。

字符串型

在Go语言中,字符串是基本类型,它基于UTF-8编码实现。在遍历字符串型,我们需要区分byterune

分别以byterune方式遍历字符串:

1
2
3
f := "Golang编程"
fmt.Printf("byte len of f is %v\n", len(f))
fmt.Printf("rune len of f is %v\n", utf8.RuneCountInString(f))

上述例子的输出为:

1
2
byte len of f is 12
rune len of f is 8

第一种方式,统计的是字节的长度。由于中文字符在UTF-8中占用了3个字节,所以使用len方法获得的中文字符长度为6个字节。

第二种方式,统计的是字符的长度。

在本质上,byterune的底层类型分别为uint8int32。由于int32能够表达更多的值,可以更容易处理Unicode字符,所以rune能够处理一切的字符,而byte仅仅局限与处理ASCII字符。

指针

在C/C++语言中,指针直接操作内存的特性使得C/C++具备极高的性能,开发人员通过它直接操作和管理大块内存数据。但与此同时,指针偏移、运算和内存释放可能引发的错误也让指针编程饱受诟病。

Go语言限制了指针类型的偏移和运算能力,使得指针类型具备了指针高效访问的特性,但又不会发生指针偏移,避免了非法修改敏感数据的问题。同时Go语言中提供的自动垃圾回收机制,也减少了指针占用内存回收的复杂性。

在Go语言中,指针包含以下三个概念:

  • 指针地址
  • 指针类型
  • 指针取值

在程序运行过程中,每一个变量的值都保存在内存中,变量对应的内存有其特定的地址。假设某一个变量的类型为T,在Go语言中,我们可以通过取址符号&获取该变量对应内存的地址,生成该变量对应的指针。此时**,指针的值即变量的内存地址**,指针类型即*T,称为T的指针类型,*代表指针。

Go语言也提供根据指针获取变量值的取指操作*,通过取值操作*可以获取指针对应变量的值和对变量进行赋值操作。具体代码如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func main() {
	str := "Golang is Good!"
	// 获取 str 的指针
	strPrt := &str
	fmt.Printf("str type is %T, value is %v, address is %p\n", str, str, &str)
	fmt.Printf("strPtr type is %T, and value is %v\n", strPrt, strPrt)
	
	// 获取指针对应变量的值
	newStr := *strPrt 
	fmt.Printf("newStr type is %T, value is %v, and address is %p\n", newStr, newStr, &newStr)
	
	// 通过指针对变量进行赋值
	*strPrt = "Java is Good too!" 
	fmt.Printf("newStr type is %T, value is %v, and address is %p\n", newStr, newStr, &newStr)
	fmt.Printf("str type is %T, value is %v, address is %p\n", str, str, &str)
}

输出的结果为:

1
2
3
4
5
str type is string, value is Golang is Good!, address is 0xc00004a250
strPtr type is *string, and value is 0xc00004a250
newStr type is string, value is Golang is Good!, and address is 0xc00004a280
newStr type is string, value is Golang is Good!, and address is 0xc00004a280
str type is string, value is Java is Good too!, address is 0xc00004a250

在上述代码中,我们通过strPtr指针获取str的值赋予给newStr变量。

可以观察到strnewStr是两个不同的变量,它们对应的内存不一样,赋值过程中发生了值拷贝

值拷贝会创建新的内存空间,然后将原有变量的值复制到新的内存空间中,形成两个独立的变量。通过指针修改str变量的值并不会影响到newStr,因为这两个变量对应的内存地址不一样。

除了使用&对变量进行取址操作创建指针,还可以使用new函数直接分配内存,并返回指向内存的指针,此时内存中的值会被初始化为类型的默认值。如下例所示:

1
2
3
4
// 通过new函数创建一个*string指针
str := new(string)
// 通过指针对变量进行赋值
*str = "Golang is Good!"

在Go语言的flag包中,命令行参数一般以指返回。

常量

变量的值在运行时可变,而常量的值在声明之后不允许变化。通过const关键字可以声明常量。如下例所示:

1
const name T = 表达式

类型推导

Go语言的类型推导省略常量声明时的类型T同时声明多个常量。如下例所示:

1
2
3
4
5
6
7
// 省略类型T
const name = 表达式
// 同时声明多个常量
const (
	name1 = 表达式1
	name2 = 表达式2
)

类型别名

Go语言提供了类型别名的语法特性。类型别名本质上与原类型是属于同一个类型的,它相当于原类型T的一个别称。定义一个类型别名的样式如下:

1
type name = T

类型定义

类型定义会创建一个新的类型,新建的类型将具备原类型T的特性类型定义的样式如下:

1
type name T

通过一个例子理解类型别名和类型定义之间的区别:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
type aliasInt = int // 定义一个类型别名
type myInt int // 定义一个新的类型

func main()  {
    
   var alias aliasInt
   fmt.Printf("alias value is %v, type is %T\n", alias, alias)

   var myint myInt
   fmt.Printf("myint value is %v, type is %T\n", myint, myint)
 }

输出结果为:

1
2
alias value is 0, type is int
myint value is 0, type is main.myInt

从输出结果中,我们可以看到通过类型别名aliasInt声明的alias变量还是int型,而重新定义的myInt属于新的类型,但是通过它声明的变量myintalias一样都为0。

分支与循环控制

Go语言的分支控制与其他语言相似,但是更为简略,简单的表达样式如下:

1
2
3
4
5
6
7
if expression1 {
	branch1
} else if expression2 {
	branch2
} else {
	branch3
}

Go语言中规定与if匹配的{必须与if和表达式位于同一行,同样的,else也必须与上一个分支的}位于同一行,否则会发生编译错误。表达式两边可以省略()

Go语言还提供了switch语句对大量的值和表达式进行判断。

  • 为了避免人为错误,switch中的每一个case都是独立的代码块,不需要通过break跳出switch选择体。
  • 如果需要继续执行接下来的case判断,需要添加fallthrough关键字对上下两个case进行连接。
  • 除了支持数值常量,Go语言的switch还能对字符串 、表达式等复杂情况进行处理。

一个简单的例子如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 根据人名分配工作
name := "小红"
switch name {
    case "小明":
    fmt.Println("扫地")
    case "小红":
    fmt.Println("擦黑板")
    case "小刚":
    fmt.Println("倒垃圾")
    default:
    fmt.Println("没人干活")
}

在上面的例子中,每一个case都是字符串样式,且无需通过break控制跳出。

如果我们需要在case中判断表达式,在这种情况下switch后面不需要指定判断变量,这种形式就和if-else类似。如下例所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 根据分数判断成绩程度
score := 90
switch {
    case score < 100 && score >= 90:
    fmt.Println("优秀")
    case score < 90 && score >= 80:
    fmt.Println("良好")
    case score < 80 && score >= 60:
    fmt.Println("及格")
    case score < 60:
    fmt.Println("不及格")
    default:
    fmt.Println("分数错误")
}

Go语言的循环体仅提供了for关键字,没有其他语言中提供的while或者do-while形式。基本样式如下:

1
2
3
for init; condition; end {
	循环体代码
}

这其中,初始语句、条件表达式、结束语句都可以不写。如果三者都缺省,这将变成一个无限循环语句,可以通过break关键字跳出循环体,或者使用continue关键字继续下一个循环。

Go中常用的容器

  • 当我们在程序中操作大量同类型变量时,为了方便数据的存储和操作,我们需要借助容器的力量。
  • Go语言中以标准库的方式提供了常用的容器实现,主要有固定大小的数组可以动态扩容的切片双向列表以及**key-value方式存储的字典**等。

数组

数组是一段存储固定类型固定长度的连续内存空间,它的大小在声明时就已经固定。数组的声明样式如下所示:

1
var name [size]T
  • size必须在静态编译时就确定其大小,不能动态指定
  • T表示数组成员的类型,可为任意类型

在Go语言,可以在声明时使用初始化列表对数组进行初始化,也可以通过下标对数据成员进行访问和赋值。如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var classMates1 [3]string
// 通过下标为数组成员赋值
classMates1[0] = "小明"
classMates1[1] = "小红"
classMates1[2] = "小李"
fmt.Println(classMates1)
// 通过下标访问数组成员
fmt.Println("The No.1 student is " + classMates1[0])
// 通过初始化列表声明并初始化数组
classMates2  := [...]string{"小明", "小红", "小李"}
fmt.Println(classMates2)

输出结果为:

1
2
3
[小明 小红 小李]
The No.1 student is 小明
[小明 小红 小李]
  • 使用初始化列表初始化数组时,需要注意[]内的数组大小需要和{}内的数组成员的数量一致。
  • 上述例子中,我们使用了...让编译器为我们根据{}内成员的数量确定数组的大小。

除此之外,我们还可以使用指针操作数组。如下例所示:

1
2
3
4
5
classMates3 := new([3]string)
classMates3[0] = "小明"
classMates3[1] = "小红"
classMates3[2] = "小李"
fmt.Println(*classMates3)

输出结果为:

1
[小明 小红 小李]
  • 在上述代码中,我们通过new函数申请了[3]string的内存空间并初始化,返回其对应的指针
  • 需要注意的是,该指针无法支持偏移和运算,这是Go语言对指针类型的限制。
  • 我们可以通过指针直接操作数组,这与C语言中的指针功能无异。

newmake的区别:

  1. make 只能用来分配及初始化类型为 slice、map、chan 的数据。new 可以分配任意类型的数据;
  2. new 分配返回的是指针,即类型 *Type。make 返回引用,即 Type;
  3. new 分配的空间被清零。make 分配空间后,会进行初始化;

切片

  • 切片是对数组的一个连续片段的引用,它是一个容量可变的序列
  • 我们可以简单将切片理解为动态数组,它的内部结构包括底层数组指针、大小和容量,它通过指针引用底层数组,把对数据的读写操作限定在指定的区域内。

切片的结构体由三部分组成:

  • array:指向底层存储数据数组的指针
  • len:指当前切片的长度,即成员数量
  • cap:指当前切片的容量,它总是大于等于len

从原生数组中生成切片

我们可以从原有数组中生成一个切片,生成的切片指针即指向原数组。生成的样式如下:

1
slice := source[begin:end]
  • source表示生成切片的原有数组
  • begin表示切片的开始位置,end表示切片的结束位置,
  • 不包含end索引指向的原有数组成员,即左闭右开。

具体例子如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
sourceArray := [...]int{1,2,3}
slice := sourceArray[0:1]

fmt.Printf("slice value is %v\n", slice)
fmt.Printf("slice len is %v\n", len(slice))
fmt.Printf("slice cap is %v\n", cap(slice))

slice[0] = 4
fmt.Printf("slice value is %v\n", slice)
fmt.Printf("sourceArray value is %v\n", sourceArray)

输出的结果为:

1
2
3
4
5
slice value is [1]
slice len is 1
slice cap is 3
slice value is [4]
sourceArray value is [4 2 3]
  • 因为切片作为指向原有数组的引用,对切片修改就是对原数组进行修改

动态创建切片

通过make函数动态创建切片,在创建过程中指定切片的长度和容量。样式如下所示:

1
make([]T, size, cap)
  • T即切片中的成员类型
  • size为当前切片具备的长度
  • cap为当前切片预分配的长度,即切片的容量

例子如下所示:

1
2
3
4
slice = make([]int, 2, 4)
fmt.Printf("slice value is %v\n", slice)
fmt.Printf("slice len is %v\n", len(slice))
fmt.Printf("slice cap is %v\n", cap(slice))

输出的结果为:

1
2
3
slice value is [0 0]
slice len is 2
slice cap is 4

从上述输出可以看出make函数创建的新切片中的成员都被初始化为类型的初始值。

声明新的切片

直接声明新的切片类似于数组的初始化,但是不需要指定其大小,否则就变成了数组。样式如下所示:

1
var name []T

此时声明的切片并没有分配内存,我们可以在声明切片的同时对其进行初始化,如下例所示:

1
2
3
4
ex := []int{1, 2, 3}
fmt.Printf("ex value is %v\n", ex)
fmt.Printf("ex len is %v\n", len(ex))
fmt.Printf("ex cap is %v\n", cap(ex))

输出的结果为:

1
2
3
ex value is [1 2 3]
ex len is 3
ex cap is 3
  • 此时声明的切片大小和容量都为3

向切片添加元素

  • Go语言中提供了append内建函数用于动态向切片添加元素,它将返回新的切片。
  • 如果当前切片的容量可以容纳更多的元素,即len小于cap,添加操作将在切片指向的原有数组上进行,这将会覆盖掉原有数组的值。
  • 如果当前切片的容量不足以容纳更多的元素,那么切片将会进行扩容
  • 扩容的具体过程为:申请一个新的连续内存空间,空间大小一般为原有容量的两倍,然后将原来数组中的数据复制到新的数组中,同时将切片中的指针指向新的数组,最后将新的元素添加到新的数组中。

通过下例演示切片的动态扩容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
arr1 := [...]int{1,2,3,4}
arr2 := [...]int{1,2,3,4}

sli1 := arr1[0:2] // 长度为2,容量为4
sli2 := arr2[2:4] // 长度为2,容量为2

fmt.Printf("sli1 pointer is %p, len is %v, cap is %v, value is %v\n", &sli1, len(sli1), cap(sli1), sli1)
fmt.Printf("sli2 pointer is %p, len is %v, cap is %v, value is %v\n", &sli2, len(sli2), cap(sli2), sli2)

newSli1 := append(sli1, 5)
fmt.Printf("newSli1 pointer is %p, len is %v, cap is %v, value is %v\n", &newSli1, len(newSli1), cap(newSli1), newSli1)
fmt.Printf("source arr1 become %v\n", arr1)

newSli2 := append(sli2, 5)
fmt.Printf("newSli2 pointer is %p, len is %v, cap is %v, value is %v\n", &newSli2, len(newSli2), cap(newSli2), newSli2)
fmt.Printf("source arr2 become %v\n", arr2)

上例的输出结果为:

1
2
3
4
5
6
sli1 pointer is 0xc000004078, len is 2, cap is 4, value is [1 2]
sli2 pointer is 0xc000004090, len is 2, cap is 2, value is [3 4]
newSli1 pointer is 0xc0000040d8, len is 3, cap is 4, value is [1 2 5]
source arr1 become [1 2 5 4]
newSli2 pointer is 0xc000004108, len is 3, cap is 4, value is [3 4 5]
source arr2 become [1 2 3 4]
  • 通过上面的例子,我们可以发现,容量足够的sli1直接将append添加的新元素覆盖到原有数组arr1中。而容量不够的sli2进行了扩容操作,申请了新的底层数组,不在原数组的基础上进行操作。在实际使用的过程要记住这两种的区别。

如果原有数组可以添加新的元素,但切片自身的容量已经饱和,此时进行append操作,同样会进行扩容,申请新的内存空间。如下例所示:

1
2
3
4
5
6
7
8
arr3 := [...]int{1,2,3,4}
sli3 := arr3[0:2:2] // 长度为2,容量为2

fmt.Printf("sli3 pointer is %p, len is %v, cap is %v, value is %v\n", &sli3, len(sli3), cap(sli3), sli3)

newSli3 := append(sli3,5)
fmt.Printf("newSli3 pointer is %p, len is %v, cap is %v, value is %v\n", &newSli3, len(newSli3), cap(newSli3), newSli3)
fmt.Printf("source arr3 become %v\n", arr3)

对应的输出结果为:

1
2
3
sli3 pointer is 0xc000004138, len is 2, cap is 2, value is [1 2]
newSli3 pointer is 0xc000004168, len is 3, cap is 4, value is [1 2 5]
source arr3 become [1 2 3 4]
  • 在上述代码中,我们指定了创建切片的第三个参数cap。这里的cap不是切片容量,切片容量是cap-begin

为了方便切片的数据快速复制到另一个切片中,Go语言提供了内建的copy函数。它的使用样式如下:

1
copy(destSli, srcSli []T)

它的返回结果为实际发生复制的元素个数。

列表

Go语言中的列表即双向链表,它适合于存储需要经常进行元素插入和删除操作的元素集合

列表的初始化样式如下所示:

1
2
3
4
// 方法一
var name list.List
// 方法二
name := list.New()
  • 方法一直接声明初始化列表,方法二使用container/list包中的New函数初始化列表,返回列表对应的指针。
  • 可以注意到,例表没有限制其内保存成员的类型,即任意类型的成员都可以同时存在列表中。

演示列表的插入、删除和遍历操作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
tmpList := list.New()
// 尾插
for i := 1; i <= 10; i++ {
    tmpList.PushBack(i)
}
// 头插
first := tmpList.PushFront(0)
// 删除
tmpList.Remove(first)
// 遍历
for l := tmpList.Front(); l != nil; l = l.Next() {
    fmt.Print(l.Value, " ")
}

字典

Go语言中的字典用于存储键值对,其中每一个键都会映射到一个值。其内部通过散列表的方式实现。定义的样式如下所示:

1
name := make(map[keyType]valueType)

通过一个简单的例子演示map使用方式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
classMates1 := make(map[int]string)

// 添加映射关系
classMates1[0] = "小明"
classMates1[1] = "小红"
classMates1[2] = "小张"

fmt.Printf("id %v is %v\n", 1, classMates1[1])

// 在声明时初始化数据
classMates2 := map[int]string{
	0 : "小明",
	1 : "小红",
	2 : "小张",
}

fmt.Printf("id %v is %v\n", 3, classMates2[3])
  • 如上代码所示,我们可以使用make函数为map分配内存空间后,再为map一一添加键值对映射关系。
  • 也可以直接在声明时通过类JSON格式添加键值对映射关系 。
  • map中可以通过键直接查询对应的值,如果不存在这样的键,将会返回值类型的默认值。

可以采用以下方式查询某个键是否存在于map中:

1
mate,ok := classMates2[1]

如果键存在于map中,布尔型ok将会是truemate为对应的值。

容器遍历

下例通过for-range遍历数组、切片、字典

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 数组的遍历
nums := [...]int{1, 2, 3, 4, 5, 6, 7, 8}

for k, v := range nums {
    // k为下标,v为对应的值
    fmt.Println(k, v, " ")
}

fmt.Println()

// 切片的遍历
slis := []int{1, 2, 3, 4, 5, 6, 7, 8}
for k, v := range slis {
    // k为下标,v为对应的值
    fmt.Println(k, v, " ")
}

fmt.Println()

// 字典的遍历
tmpMap := map[int]string{
    0: "小明",
    1: "小红",
    2: "小张",
}

for k, v := range tmpMap {
    // k为键值,v为对应值
    fmt.Println(k, v, " ")
}
  • 可以将不需要的键或值改为匿名变量
  • 列表的遍历比较特殊,需要配合Front函数获取列表的头元素,再使用其Next函数依次往下遍历,具体代码可见上面列表部分的举例。

特别注意:在for-range遍历过程中,键和值是值拷贝,对它们修改不会影响容器内成员变化。

函数与接口

函数声明和参数传递

Go语言中函数声明包括函数名、参数列表和返回参数列表。具体样式如下所示:

1
2
3
func name(params)(return params) {
	function body
}
  • 函数以func作为标识,函数名可以由字母、数字、下划线组成,但第一位不能是数字
  • 在同一包内,函数名不可重名
  • 一个函数如果希望被包外代码访问,函数名的首字母需要为大写
  • 参数列表中的每个参数由参数变量名和参数类型组成,它们将作为函数的局部变量被使用
  • 在参数列表中,多个参数之间通过逗号分隔
  • 如果相邻的参数的类型相同,那么可以只在最后参数列表最后写上参数类型

Go语言中函数不仅支持多返回值,还支持对返回值进行命名,此时返回参数列表与参数列表类似,如下例所示:

1
2
3
4
5
func div(dividend, divisor int)(quotient, remainder int) {
	quotient = dividend/divisor
	remainder = dividend%divisor
	return
}
  • 在上面的正整数除法的函数中,我们对返回值分别命名为quotientremainder,于是可以直接在函数体内对它们进行赋值。
  • 需要注意的是,在使用命名返回值的函数中,在函数结束前我们需要显式使用return语句进行返回
  • 命名返回值和非命名返回值不能混合使用,两种形式只能二选一,否则会出现编译错误

Go语言中函数参数的传递方式是值传递,但可以在参数中使用指针或者引用来减少复制时产生的性能损耗。

匿名函数

  • 匿名函数是一种没有函数名,只有函数体的函数,即定义即使用
  • 匿名函数只有在被调用的时候才会被初始化
  • 匿名函数一般被当作一种类型赋值给函数类型的变量,经常被用作回调函数

Go语言的匿名函数的声明样式如下所示:

1
2
3
func(params)(return params){
	function body
}

我们可以在匿名函数声明之后,在其后加上调用的参数列表,即可对匿名函数进行调用,如下例所示:

1
2
3
func (name string) {
	fmt.Println("My name is ",name)
}("wyatt")

还可以将匿名函数赋值给函数类型的变量,用于多次调用或者求值,如下例所示:

1
2
3
4
5
currentTime := func() {
	fmt.Println(time.Now())
}
// 调用匿名函数
currentTime()

匿名函数一个比较常用的场景是用作回调函数。如下例所示,定义一个函数proc():它接受string和匿名函数的参数输入,然后使用匿名函数对string进行处理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
func proc(input string, processor func(str string)) {
	// 调用匿名函数
	processor(input)
}

func main() {

	proc("王小二", func(str string) {
		for _, v := range str {
			fmt.Printf("%c\n", v)
		}
	})
}
  • 匿名函数作为参数传入函数proc(),赋值给函数类型参数processor
  • 在函数proc()中作为回调函数用于对传入的字符串进行处理
  • 可以根据自己的需要传递不同的匿名函数实现对字符串不同的处理操作
最后更新于 Mar 24, 2022 11:31 UTC
Built with Hugo
Theme Stack designed by Jimmy