Go语言的语法跟C、Java有许多不同的地方的,除去变量、循环等易懂的语法,我对Go语言比较特有的语法做一点梳理,并上手实践敲敲代码。
Go语言的特点
-
高性能、高并发:Go 语言通过 Goroutines 和 Channels 的并发模型实现了轻量级的多线程编程,使得处理并发任务非常高效。
-
语法简单、学习曲线平缓:Go 语言的设计目标之一是简洁易学,它摒弃了一些复杂的语法,使得代码更易读、易维护,并且学习门槛较低。
-
丰富的标准库:Go 语言提供了一个强大而丰富的标准库,涵盖了网络、文件处理、加密、并发等领域,这些标准库可以极大地简化开发任务。
-
完善的工具链:Go 语言提供了一套完善的工具链,包括代码格式化、构建工具、测试工具等,使得开发、测试、部署过程更加便捷。
-
静态链接:Go 语言的程序可以静态链接,这意味着可以将所有的依赖库打包到一个可执行文件中,简化了部署和分发过程。
-
快速编译:Go 语言的编译速度非常快,这对于开发效率是一个重要的优势。
-
跨平台:Go 语言支持跨平台编译,可以在不同的操作系统上编译运行,无需修改代码,提高了代码的可移植性。
-
垃圾回收:Go 语言拥有自动垃圾回收机制,使得内存管理更加简单和安全,避免了常见的内存泄漏问题。
init函数
go语言中init函数用于包(package)的初始化,该函数是go语言的一个重要特性。init函数具有以下特征:
- init函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等
- 每个包可以拥有多个init函数
- 包的每个源文件也可以拥有多个init函数
- 同一个包中多个init函数的执行顺序go语言没有明确的定义(说明)
- init函数不能被其他函数调用,而是在main函数执行之前,自动被调用
但值得注意的是,init函数与main函数具有以下异同点,
相同点:两个函数在定义时不能有任何的参数和返回值,且Go程序自动调用。
不同点:
- init可以应用于任意包中,且可以重复定义多个。
- main函数只能用于main包中,且只能定义一个。
数组
- 数组:是同一种数据类型的固定长度的序列。
- 数组定义:var a [len]int,比如:var a [5]int,数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变。
- 长度是数组类型的一部分,因此,var a[5] int和var a[10]int是不同的类型。
- 数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1。
1.数组基本语法
var arrayName [length]dataType
其中:
- arrayName是数组的名称
- length是数组的长度(即包含的元素个数)
- dataType是数组中元素的数据类型。
2.数组初始化
Go语言提供了更简洁的数组初始化方式,可以在创建数组时直接指定元素的值:
var arr0 [5]int = [5]int{1, 2, 3}
var arr2 = [...]int{1, 2, 3, 4, 5, 6}
var str = [5]string{3: "hello world", 4: "tom"}
func main() {
a := [3]int{1, 2} // 未初始化元素值为 0。
c := [5]int{2: 100, 4: 200} // 使用引号初始化元素。
d := [...]struct {
name string
age uint8
}{
{"user2", 20}, // 别忘了最后一行的逗号。
}
fmt.Println(arr0, arr1, arr2, str)
fmt.Println(a, b, c, d)
}
要打印整个数组,可以使用fmt.Println函数,它会将数组的所有元素打印出来。
多维数组
在Go中,多维数组是指由多个一维数组组成的数组。多维数组可以用于表示矩阵、图像等数据结构。
初始化一个二维数组:
var arr0 [5][3]int
var arr1 [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}
访问一个二维数组:
func main() {
a := [2][3]int{{1, 2, 3}, {4, 5, 6}}
b := [...][2]int{{1, 1}, {2, 2}, {3, 3}} // 第 2 纬度不能用 "..."
fmt.Println(a, b)
}
切片
Go 语言切片是对数组的抽象。切片是对底层数组的一个视图,它包含了指向底层数组的指针、切片的长度和容量信息。由于切片是对数组的引用,因此修改切片中的元素会影响原始数组,反之亦然。切片支持自动扩容,当切片的容量不够时,Go会自动为其分配更大的底层数组,并将原有的数据复制到新的数组中。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
Go语言中的数组是值类型,当将一个数组赋值给另一个数组时,会将整个数组内容复制一份。这可能导致在处理大型数组时出现性能问题。为了避免这种问题,可以使用切片(slice),切片是对数组的引用,它更灵活、更高效。
创建切片的各种方式
1.通过切片字面量创建切片:在初始化时没有指定长度,只有一个空的方括号【】,表示创建一个切片。切片的长度是根据初始值的个数来确定如创建一个包含3个整数的切片
slice := []int{10, 20, 30}
2.用make()方法来创建:通过make函数,我们可以方便地创建指定类型和长度的切片,初始值都是该类型的零值
var slice1 []type = make([]type, len)
也可以简写为
slice1 := make([]type, len)
3.用数组的方式创建切片:
- 初始化切片 s,是数组 arr 的引用。
- 将 arr 中从下标 startIndex到 endIndex-1 下的元素创建为一个新的切片。
- 默认 endIndex 时将表示一直到arr的最后一个元素。
- 默认 startIndex 时将表示从 arr 的第一个元素开始。
intArray := [5]int{1, 2, 3, 4, 5}
在初始化时,使用了固定长度的方括号[5],表示创建一个长度为5的整数数组。数组的长度在创建时就确定,无法更改。
遍历切片
func main() {
data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
slice := data[:]
for index, value := range slice {
fmt.Printf("inde : %v , value : %v\n", index, value)
}
}
切片的优点
- 动态长度:切片的长度可以根据需要动态增加或缩减,相比于数组的静态长度,切片在处理不确定长度的数据时更为方便。
- 函数传参:切片在函数参数传递时,不会像数组一样复制整个数据,而是复制指向底层数组的指针、长度和容量信息,这样可以避免大量数据的复制,节省内存和提高性能。
- 灵活的使用方式:切片支持切割、追加、复制和连接等操作,使得切片在各种场景下都有广泛的应用。
Map
map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用。映射是一种数据结构,用于存储一系列无序的键值对。映射功能强大的地方是,能够基于键快速检索数据。键就像索引一样,指向与该键关联的值。
Map的定义
可以使用内建函数 make 或使用 map 关键字来定义 Map:
1.使用 make 函数
map_variable := make(map[KeyType]ValueType, initialCapacity)
2.使用 map关键字
m := make(map[string]int)
3.创建一个初始容量为 10 的 Map
m := make(map[string]int, 10)
其中 KeyType 是键的类型,ValueType 是值的类型,initialCapacity 是可选的参数,用于指定 Map 的初始容量。Map 的容量是指 Map 中可以保存的键值对的数量,当 Map 中的键值对数量达到容量时,Map 会自动扩容。如果不指定 initialCapacity,Go 语言会根据实际情况选择一个合适的值。
map的零值
map在初始化完的零值是nil,也就是没有引用任何哈希表。当使用一个不存在的key去获取对应的value时,会返回一个value类型的零值。有时需要知道元素是否真的在map中,例如,如果元素类型是一个数字,我们可能需要区分一个已经存在的0,和不存在而返回零值的0,我们来这样测试:
age, ok := ages["hello"]
if !ok { /* "hello" is not a key in this map; age == 0. */ }
在这种场景下,map的下标语法将产生两个值;第二个是一个布尔值,用于报告元素是否真的存在。布尔变量一般命名为ok,特别适合马上用于if条件判断部分。
Map的基本使用
map的使用查询用法和常见的其他语言差不多。通过m[key]的形式进行获取对应的value值。如果key不存在,则获取到的是一个空行。
func main() {
scoreMap := make(map[string]int, 8)
scoreMap["张三"] = 90
scoreMap["小明"] = 100
fmt.Println(scoreMap)
fmt.Println(scoreMap["小明"])
fmt.Printf("type of a:%T\n", scoreMap)
}
输出结果:
100
type of a:map[string]int
判断某个键是否存在
在这种场景下,map的下标语法将产生两个值;第二个是一个布尔值,用于报告元素是否真的存在。布尔变量一般命名为ok,特别适合马上用于if条件判断部分。
value, ok := map[key]
func main() {
scoreMap := make(map[string]int)
scoreMap["张三"] = 90
scoreMap["小明"] = 100
// 如果key存在ok为true,v为对应的值;不存在ok为false,v为值类型的零值
v, ok := scoreMap["张三"]
if ok {
fmt.Println(v)
} else {
fmt.Println("没有查到到此人")
}
}
如果key存在ok为true,v为对应的值;不存在ok为false,v为值类型的零值
map遍历
遍历元素是对map使用的一种形式,在Go语言中,同样使用range进行遍历最为方便。同样,你也可以选择使用传统的key,value对的形式。
以下代码实例使用for range遍历map。
func main() {
scoreMap := make(map[string]int)
scoreMap["张三"] = 90
scoreMap["小明"] = 100
scoreMap["王五"] = 60
for k, v := range scoreMap {
fmt.Println(k, v)
}
}
当使用range遍历字符串、数组、切片、map或通道时,会返回两个值:第一个是索引,第二个是对应位置的值。如果你不需要索引,可以使用下划线” _“ 来忽略它。
写在最后:
还有更多的语法细节无法一一展示,需要我们在实际应用时多去学习、利用。