Go 语言结构 – 高级篇(函数、变量作用域、数组、指针、结构体、切片、Range、Map)
Go 语言函数
函数是基本的代码块,用于执行一个任务。
Go 语言最少有个 main() 函数。
你可以通过函数来划分不同功能,逻辑上每个函数执行的是指定的任务。
函数声明告诉了编译器函数的名称,返回类型,和参数。
Go 语言标准库提供了多种可动用的内置的函数。例如,len() 函数可以接受不同类型参数并返回该类型的长度。如果我们传入的是字符串则返回字符串的长度,如果传入的是数组,则返回数组中包含的元素个数。
函数定义
Go 语言函数定义格式如下:
func function_name( [parameter list] ) [return_types] { 函数体 }
函数定义解析:
- func:函数由 func 开始声明
- function_name:函数名称,参数列表和返回值类型构成了函数签名。
- parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
- return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
- 函数体:函数定义的代码集合。
以下实例为 max() 函数的代码,该函数传入两个整型参数 num1 和 num2,并返回这两个参数的最大值:
/* 函数返回两个数的最大值 */ func max(num1, num2 int) int { /* 声明局部变量 */ var result int if (num1 > num2) { result = num1 } else { result = num2 } return result }
函数调用
当创建函数时,你定义了函数需要做什么,通过调用该函数来执行指定任务。
调用函数,向函数传递参数,并返回值,例如:
package main import "fmt" func main() { /* 定义局部变量 */ var a int = 100 var b int = 200 var ret int /* 调用函数并返回最大值 */ ret = max(a, b) fmt.Printf( "最大值是 : %d\n", ret ) } /* 函数返回两个数的最大值 */ func max(num1, num2 int) int { /* 定义局部变量 */ var result int if (num1 > num2) { result = num1 } else { result = num2 } return result }
以上实例在 main() 函数中调用 max()函数,执行结果为:
最大值是: 200
函数返回多个值
Go 函数可以返回多个值,例如:
package main import "fmt" func swap(x, y string) (string, string) { return y, x } func main() { a, b := swap("Google", "Runoob") fmt.Println(a, b) }
以上实例执行结果为:
Runoob Google
函数参数
函数如果使用参数,该变量可称为函数的形参。
形参就像定义在函数体内的局部变量。
调用函数,可以通过两种方式来传递参数:
传递类型 | 描述 |
---|---|
值传递 | 值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。 |
引用传递 | 引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。 |
默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。
函数用法
函数用法 | 描述 |
---|---|
函数作为另外一个函数的实参 | 函数定义后可作为另外一个函数的实参数传入 |
闭包 | 闭包是匿名函数,可在动态编程中使用 |
方法 | 方法就是一个包含了接受者的函数 |
Go 语言变量作用域
作用域为已声明标识符所表示的常量、类型、变量、函数或包在源代码中的作用范围。
Go 语言中变量可以在三个地方声明:
- 函数内定义的变量称为局部变量
- 函数外定义的变量称为全局变量
- 函数定义中的变量称为形式参数
接下来让我们具体了解局部变量、全局变量和形式参数。
局部变量
在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量。
全局变量
在函数体外声明的变量称之为全局变量,全局变量可以在整个包甚至外部包(被导出后)使用。
Go 语言程序中全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑。实例如下:
package main import "fmt" /* 声明全局变量 */ var g int = 20 func main() { /* 声明局部变量 */ var g int = 10 fmt.Printf ("结果: g = %d\n", g) }
以上实例执行输出结果为:
结果: g = 10
形式参数
形式参数会作为函数的局部变量来使用。
初始化局部和全局变量
不同类型的局部和全局变量默认值为:
数据类型 | 初始化默认值 |
---|---|
int | 0 |
float32 | 0 |
pointer | nil |
Go 语言数组
Go 语言提供了数组类型的数据结构。
数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整型、字符串或者自定义类型。
相对于去声明 number0, number1, …, number99 的变量,使用数组形式 numbers[0], numbers[1] …, numbers[99] 更加方便且易于扩展。
数组元素可以通过索引(位置)来读取(或者修改),索引从 0 开始,第一个元素索引为 0,第二个索引为 1,以此类推。
声明数组
Go 语言数组声明需要指定元素类型及元素个数,语法格式如下:
var variable_name [SIZE] variable_type
以上为一维数组的定义方式。例如以下定义了数组 balance 长度为 10 类型为 float32:
var balance [10] float32
初始化数组
以下演示了数组初始化:
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
我们也可以通过字面量在声明数组的同时快速初始化数组:
balance := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
如果数组长度不确定,可以使用 … 代替数组的长度,编译器会根据元素个数自行推断数组的长度:
var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0} 或 balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
如果设置了数组的长度,我们还可以通过指定下标来初始化元素:
// 将索引为 1 和 3 的元素初始化 balance := [5]float32{1:2.0,3:7.0}
初始化数组中 {} 中的元素个数不能大于 [] 中的数字。
如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小:
balance[4] = 50.0
访问数组元素
数组元素可以通过索引(位置)来读取。格式为数组名后加中括号,中括号中为索引的值。
Go 语言多维数组
Go 语言支持多维数组,以下为常用的多维数组声明方式:
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type
以下实例声明了三维的整型数组:
var threedim [5][10][4]int
二维数组
二维数组是最简单的多维数组,二维数组本质上是由一维数组组成的。二维数组定义方式如下:
var arrayName [ x ][ y ] variable_type
二维数组中的元素可通过 arrayName[ i ][ j ] 来访问。
package main import "fmt" func main() { // Step 1: 创建数组 values := [][]int{} // Step 2: 使用 appped() 函数向空的二维数组添加两行一维数组 row1 := []int{1, 2, 3} row2 := []int{4, 5, 6} values = append(values, row1) values = append(values, row2) // Step 3: 显示两行数据 fmt.Println("Row 1") fmt.Println(values[0]) fmt.Println("Row 2") fmt.Println(values[1]) // Step 4: 访问第一个元素 fmt.Println("第一个元素为:") fmt.Println(values[0][0]) }
以上实例运行输出结果为:
Row 1 [1 2 3] Row 2 [4 5 6] 第一个元素为: 1
初始化二维数组
多维数组可通过大括号来初始值。以下实例为一个 3 行 4 列的二维数组:
a := [3][4]int{ {0, 1, 2, 3} , /* 第一行索引为 0 */ {4, 5, 6, 7} , /* 第二行索引为 1 */ {8, 9, 10, 11}, /* 第三行索引为 2 */ }
注意:以上代码中倒数第二行的 } 必须要有逗号,因为最后一行的 } 不能单独一行,也可以写成这样:
a := [3][4]int{ {0, 1, 2, 3} , /* 第一行索引为 0 */ {4, 5, 6, 7} , /* 第二行索引为 1 */ {8, 9, 10, 11}} /* 第三行索引为 2 */
访问二维数组
二维数组通过指定坐标来访问。如数组中的行索引与列索引,例如:
val := a[2][3] 或 var value int = a[2][3]
Go 语言向函数传递数组
如果你想向函数传递数组参数,你需要在函数定义时,声明形参为数组,我们可以通过以下两种方式来声明:
方式一:形参设定数组大小
void myFunction(param [10]int) { ... }
方式二:形参未设定数组大小
void myFunction(param []int) { ... }
func getAverage(arr []int, size int) float32 { var i int var avg, sum float32 for i = 0; i < size; ++i { sum += arr[i] } avg = sum / size return avg; }
Go 语言指针
我们都知道,变量是一种使用方便的占位符,用于引用计算机内存地址。
Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。
以下实例演示了变量在内存中地址:
package main import "fmt" func main() { var a int = 10 fmt.Printf("变量的地址: %x\n", &a) }
执行以上代码输出结果为:
变量的地址: 20818a220
什么是指针
一个指针变量指向了一个值的内存地址。
类似于变量和常量,在使用指针前你需要声明指针。指针声明格式如下:
var var_name *var-type
var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。以下是有效的指针声明:
var ip *int /* 指向整型*/ var fp *float32 /* 指向浮点型 */
如何使用指针
指针使用流程:
- 定义指针变量。
- 为指针变量赋值。
- 访问指针变量中指向地址的值。
在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。
Go 空指针
当一个指针被定义后没有分配到任何变量时,它的值为 nil。
nil 指针也称为空指针。
nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。
一个指针变量通常缩写为 ptr。
空指针判断:
if(ptr != nil) /* ptr 不是空指针 */ if(ptr == nil) /* ptr 是空指针 */
Go 语言结构体
Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。
结构体表示一项记录,比如保存图书馆的书籍记录,每本书有以下属性:
- Title :标题
- Author : 作者
- Subject:学科
- ID:书籍ID
定义结构体
结构体定义需要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体中有一个或多个成员。type 语句设定了结构体的名称。结构体的格式如下:
type struct_variable_type struct { member definition member definition ... member definition }
一旦定义了结构体类型,它就能用于变量的声明,语法格式如下:
variable_name := structure_variable_type {value1, value2...valuen} 或 variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}
package main import "fmt" type Books struct { title string author string subject string book_id int } func main() { // 创建一个新的结构体 fmt.Println(Books{"Go 语言", "www.runoob.com", "Go 语言教程", 6495407}) // 也可以使用 key => value 格式 fmt.Println(Books{title: "Go 语言", author: "www.runoob.com", subject: "Go 语言教程", book_id: 6495407}) // 忽略的字段为 0 或 空 fmt.Println(Books{title: "Go 语言", author: "www.runoob.com"}) }
输出结果为:
{Go 语言 www.runoob.com Go 语言教程 6495407} {Go 语言 www.runoob.com Go 语言教程 6495407} {Go 语言 www.runoob.com 0}
访问结构体成员
如果要访问结构体成员,需要使用点号 . 操作符,格式为:
结构体.成员名
结构体作为函数参数
你可以像其他数据类型一样将结构体类型作为参数传递给函数。
package main import "fmt" type Books struct { title string author string subject string book_id int } func main() { var Book1 Books /* 声明 Book1 为 Books 类型 */ var Book2 Books /* 声明 Book2 为 Books 类型 */ /* book 1 描述 */ Book1.title = "Go 语言" Book1.author = "www.runoob.com" Book1.subject = "Go 语言教程" Book1.book_id = 6495407 /* book 2 描述 */ Book2.title = "Python 教程" Book2.author = "www.runoob.com" Book2.subject = "Python 语言教程" Book2.book_id = 6495700 /* 打印 Book1 信息 */ printBook(Book1) /* 打印 Book2 信息 */ printBook(Book2) } func printBook( book Books ) { fmt.Printf( "Book title : %s\n", book.title) fmt.Printf( "Book author : %s\n", book.author) fmt.Printf( "Book subject : %s\n", book.subject) fmt.Printf( "Book book_id : %d\n", book.book_id) }
以上实例执行运行结果为:
Book title : Go 语言 Book author : www.runoob.com Book subject : Go 语言教程 Book book_id : 6495407 Book title : Python 教程 Book author : www.runoob.com Book subject : Python 语言教程 Book book_id : 6495700
结构体指针
你可以定义指向结构体的指针类似于其他指针变量,格式如下:
var struct_pointer *Books
以上定义的指针变量可以存储结构体变量的地址。查看结构体变量地址,可以将 & 符号放置于结构体变量前:
struct_pointer = &Book1
使用结构体指针访问结构体成员,使用 “.” 操作符:
struct_pointer.title
Go 语言切片(Slice)
Go 语言切片是对数组的抽象。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
定义切片
你可以声明一个未指定大小的数组来定义切片:
var identifier []type
切片不需要说明长度。
或使用 make() 函数来创建切片:
var slice1 []type = make([]type, len) 也可以简写为 slice1 := make([]type, len)
也可以指定容量,其中 capacity 为可选参数。
make([]T, length, capacity)
这里 len 是数组的长度并且也是切片的初始长度。
切片初始化
s :=[] int {1,2,3}
直接初始化切片,[] 表示是切片类型,{1,2,3} 初始化值依次是 1,2,3,其 cap=len=3。
s := arr[:]
初始化切片 s,是数组 arr 的引用。
s := arr[startIndex:endIndex]
将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片。
s := arr[startIndex:]
默认 endIndex 时将表示一直到arr的最后一个元素。
s := arr[:endIndex]
默认 startIndex 时将表示从 arr 的第一个元素开始。
s1 := s[startIndex:endIndex]
通过切片 s 初始化切片 s1。
s :=make([]int,len,cap)
通过内置函数 make() 初始化切片s,[]int 标识为其元素类型为 int 的切片。
len() 和 cap() 函数
切片是可索引的,并且可以由 len() 方法获取长度。
切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。
package main import "fmt" func main() { var numbers = make([]int,3,5) printSlice(numbers) } func printSlice(x []int){ fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x) }
以上实例运行输出结果为:
len=3 cap=5 slice=[0 0 0]
空(nil)切片
一个切片在未初始化之前默认为 nil,长度为 0,实例如下:
package main import "fmt" func main() { var numbers []int printSlice(numbers) if(numbers == nil){ fmt.Printf("切片是空的") } } func printSlice(x []int){ fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x) }
以上实例运行输出结果为:
len=0 cap=0 slice=[] 切片是空的
切片截取
可以通过设置下限及上限来设置截取切片 [lower-bound:upper-bound],实例如下:
package main import "fmt" func main() { /* 创建切片 */ numbers := []int{0,1,2,3,4,5,6,7,8} printSlice(numbers) /* 打印原始切片 */ fmt.Println("numbers ==", numbers) /* 打印子切片从索引1(包含) 到索引4(不包含)*/ fmt.Println("numbers[1:4] ==", numbers[1:4]) /* 默认下限为 0*/ fmt.Println("numbers[:3] ==", numbers[:3]) /* 默认上限为 len(s)*/ fmt.Println("numbers[4:] ==", numbers[4:]) numbers1 := make([]int,0,5) printSlice(numbers1) /* 打印子切片从索引 0(包含) 到索引 2(不包含) */ number2 := numbers[:2] printSlice(number2) /* 打印子切片从索引 2(包含) 到索引 5(不包含) */ number3 := numbers[2:5] printSlice(number3) } func printSlice(x []int){ fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x) }
执行以上代码输出结果为:
len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8] numbers == [0 1 2 3 4 5 6 7 8] numbers[1:4] == [1 2 3] numbers[:3] == [0 1 2] numbers[4:] == [4 5 6 7 8] len=0 cap=5 slice=[] len=2 cap=9 slice=[0 1] len=3 cap=7 slice=[2 3 4]
append() 和 copy() 函数
如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。
下面的代码描述了从拷贝切片的 copy 方法和向切片追加新元素的 append 方法。
package main import "fmt" func main() { var numbers []int printSlice(numbers) /* 允许追加空切片 */ numbers = append(numbers, 0) printSlice(numbers) /* 向切片添加一个元素 */ numbers = append(numbers, 1) printSlice(numbers) /* 同时添加多个元素 */ numbers = append(numbers, 2,3,4) printSlice(numbers) /* 创建切片 numbers1 是之前切片的两倍容量*/ numbers1 := make([]int, len(numbers), (cap(numbers))*2) /* 拷贝 numbers 的内容到 numbers1 */ copy(numbers1, numbers) printSlice(numbers1) } func printSlice(x []int){ fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x) }
以上代码执行输出结果为:
len=0 cap=0 slice=[] len=1 cap=1 slice=[0] len=2 cap=2 slice=[0 1] len=5 cap=6 slice=[0 1 2 3 4] len=5 cap=12 slice=[0 1 2 3 4]
Go 语言范围(Range)
Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。
package main import "fmt" func main() { //这是我们使用range去求一个slice的和。使用数组跟这个很类似 nums := []int{2, 3, 4} sum := 0 for _, num := range nums { sum += num } fmt.Println("sum:", sum) //在数组上使用range将传入index和值两个变量。上面那个例子我们不需要使用该元素的序号,所以我们使用空白符"_"省略了。有时侯我们确实需要知道它的索引。 for i, num := range nums { if num == 3 { fmt.Println("index:", i) } } //range也可以用在map的键值对上。 kvs := map[string]string{"a": "apple", "b": "banana"} for k, v := range kvs { fmt.Printf("%s -> %s\n", k, v) } //range也可以用来枚举Unicode字符串。第一个参数是字符的索引,第二个是字符(Unicode的值)本身。 for i, c := range "go" { fmt.Println(i, c) } }
以上实例运行输出结果为:
sum: 9 index: 1 a -> apple b -> banana 0 103 1 111
Go 语言Map(集合)
Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。
定义 Map
可以使用内建函数 make 也可以使用 map 关键字来定义 Map:
/* 声明变量,默认 map 是 nil */ var map_variable map[key_data_type]value_data_type /* 使用 make 函数 */ map_variable := make(map[key_data_type]value_data_type)
如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对。
package main import "fmt" func main() { var countryCapitalMap map[string]string /*创建集合 */ countryCapitalMap = make(map[string]string) /* map插入key - value对,各个国家对应的首都 */ countryCapitalMap [ "France" ] = "巴黎" countryCapitalMap [ "Italy" ] = "罗马" countryCapitalMap [ "Japan" ] = "东京" countryCapitalMap [ "India " ] = "新德里" /*使用键输出地图值 */ for country := range countryCapitalMap { fmt.Println(country, "首都是", countryCapitalMap [country]) } /*查看元素在集合中是否存在 */ capital, ok := countryCapitalMap [ "American" ] /*如果确定是真实的,则存在,否则不存在 */ /* fmt.Println(capital) */ /* fmt.Println(ok) */ if (ok) { fmt.Println("American 的首都是", capital) } else { fmt.Println("American 的首都不存在") } }
以上实例运行结果为:
France 首都是 巴黎 Italy 首都是 罗马 Japan 首都是 东京 India 首都是 新德里 American 的首都不存在
delete() 函数
delete() 函数用于删除集合的元素, 参数为 map 和其对应的 key。
package main import "fmt" func main() { /* 创建map */ countryCapitalMap := map[string]string{"France": "Paris", "Italy": "Rome", "Japan": "Tokyo", "India": "New delhi"} fmt.Println("原始地图") /* 打印地图 */ for country := range countryCapitalMap { fmt.Println(country, "首都是", countryCapitalMap [ country ]) } /*删除元素*/ delete(countryCapitalMap, "France") fmt.Println("法国条目被删除") fmt.Println("删除元素后地图") /*打印地图*/ for country := range countryCapitalMap { fmt.Println(country, "首都是", countryCapitalMap [ country ]) } }
以上实例运行结果为:
原始地图 India 首都是 New delhi France 首都是 Paris Italy 首都是 Rome Japan 首都是 Tokyo 法国条目被删除 删除元素后地图 Italy 首都是 Rome Japan 首都是 Tokyo India 首都是 New delhi