Golang快速入门(2)| 青训营
1.数组
1.1. 声明数组
Golang中声明数组的基本语法:var 数组名 [长度]元素类型
例如,声明一个包含5个整数的数组:
var arr [5]int
1.2. 初始化数组
Golang数组的初始化有几种方式:
a. 使用索引逐个初始化元素:
arr := [5]int{1, 2, 3, 4, 5}
b. 自动推导数组长度:
arr := [...]int{1, 2, 3, 4, 5} // Go 自动计算数组长度为5
c. 指定索引位置初始化:
arr := [5]int{0: 1, 3: 4}
// 这将创建一个长度为5的数组,索引0和3分别初始化为1和4,其他元素默认初始化为0
1.3. 访问数组元素
数组的元素可以通过索引访问,索引从0开始,以数组名加方括号的方式表示。例如:
arr := [5]int{10, 20, 30, 40, 50}
fmt.Println(arr[0]) // 输出 10
fmt.Println(arr[3]) // 输出 40
1.4. 数组的长度和容量
Golang中,数组的长度是固定的,不可更改。要获取数组的长度,可以使用内置的 len 函数。容量与长度相同,因为数组的长度是不可变的。例如:
arr := [5]int{1, 2, 3, 4, 5}
length := len(arr) // 获取数组长度,结果为 5
1.5. 多维数组
Go 支持多维数组,您可以创建二维、三维或更多维度的数组。多维数组的声明和初始化方式类似,只需在声明时指定每个维度的大小即可。例如:
var matrix [3][3]int // 声明一个3x3的二维整数数组
matrix[0][0] = 1
// ...
1.6. 数组的特性
下面是一些关于 Golang数组的重要特性:
- 数组是值类型:将一个数组赋值给另一个数组会复制数组的内容,而不是引用。
- 数组在函数间传递时是值传递,这意味着传递的是整个数组的副本,而不是指向数组的指针。
- 数组的长度是其类型的一部分。
[5]int和[10]int是两种不同的数组类型,不能互相赋值。 - 数组在内存中是连续存储的,这有助于提高访问速度。
2.切片
2.1. 切片的声明和初始化
切片的基本声明和初始化方式如下:
var numbers []int // 声明一个整数切片
numbers = []int{1, 2, 3} // 初始化切片
2.2. 切片的操作
切片支持许多操作,如添加、删除元素,以及切片截取等。
- 使用
append函数添加元素到切片:
numbers = append(numbers, 4, 5) // 添加元素到切片末尾
- 使用切片索引删除元素:
index := 1
numbers = append(numbers[:index], numbers[index+1:]...) // 删除索引为1的元素
-
切片截取,读取切片的一部分,或根据数组、切片创建一个新的切片。
切片中的指针指向一个底层数组的起始位置,也可以理解为切片截取是左闭右开。如下图所示:
请看下面的代码示例:
package main
import "fmt"
func main() {
// 声明一个整数数组
arr := []int{10, 20, 30, 40, 50, 60}
// 对数组进行切片操作
slice := arr[0:3]
// 输出切片的值
fmt.Println(slice) // 输出 [10 20 30]
}
在这个示例中,我们声明了一个整数数组 arr,然后通过切片操作 arr[0:3] 创建了一个切片 slice,其中包含了数组的前三个元素 {10, 20, 30}。最后,我们使用 fmt.Println 打印切片的值,输出为 [10 20 30]。
2.3. 切片的长度和容量
切片具有长度和容量的概念。长度是切片中的实际元素数量,而容量是底层数组中可以容纳的元素数量。使用 len 函数获取切片长度,使用 cap 函数获取切片容量。
numbers := []int{1, 2, 3, 4, 5}
length := len(numbers) // 长度为5
capacity := cap(numbers) // 容量也为5,因为切片是从数组创建的
2.4. 切片的底层数组
切片实际上是对底层数组的引用,所以多个切片可以共享相同的底层数组。这意味着对其中一个切片的修改可能会影响其他切片。
2.5. 使用 make 创建切片
make 函数可以用来创建一个指定类型的切片,同时也可以指定切片的长度和容量。这在需要预分配内存的情况下很有用。
slice := make([]int, 0, 10) // 创建长度为0,容量为10的整数切片
2.6. 切片与数组的区别
- 数组的长度是固定的,切片长度可以动态增长。
- 数组是值类型,赋值会复制整个数组,切片是引用类型,赋值会共享底层数组。
- 切片不需要预先定义长度,在需要时会自动扩展。
3.map
Golang中的map类似于python的字典,都是用于存储键-值对的数据结构,但是pyhton的dict是动态结构,golang的map初始化时要指定键-值类型,其次golang map还有容量.
3.1. 声明和初始化
map 的声明和初始化方式如下:
var m map[keyType]valueType // 声明一个键类型为 keyType,值类型为 valueType 的 map
例如:
var scores map[string]int // 声明一个字符串到整数的 map
scores = make(map[string]int) // 初始化 map
您也可以在声明时使用 make 函数进行初始化:
scores := make(map[string]int) // 声明并初始化 map
3.2. 添加和访问元素
可以使用键来添加和访问 map 中的元素:
scores["Alice"] = 95 // 添加一个键为 "Alice",值为 95 的元素到 map
fmt.Println(scores["Alice"]) // 输出 95
3.3. 删除元素
可以使用 delete 函数删除 map 中的元素:
delete(scores, "Alice") // 删除键为 "Alice" 的元素
3.4. 判断键是否存在
可以使用一个特殊的多重赋值形式,判断 map 中是否存在指定的键:
score, exists := scores["Alice"]
if exists {
fmt.Println("Alice's score:", score)
} else {
fmt.Println("Alice's score not found")
}
3.5. map 的迭代
您可以使用 for 循环迭代 map 中的键-值对:
for key, value := range scores {
fmt.Printf("%s: %d\n", key, value)
}
3.6. map 的特性
map是引用类型:当将一个map分配给另一个变量时,它们共享相同的底层数据结构。对其中一个map的修改会影响另一个。map中的键是唯一的:每个键在map中只能出现一次。map是无序的:map中的键-值对没有固定的顺序。map的大小是动态的:map可以根据需要动态增长。map中的键必须支持相等运算符:例如,字符串、整数、浮点数、指针等类型都可以作为键。
4.函数
Golang中的函数类似于c语言的函数,但是Golang的函数更加强大。
4.1. 声明和定义函数
Golang 函数的基本声明和定义方式如下:
func functionName(parameters) returnType {
// 函数体
// 执行任务和操作
return result // 可选的返回值
}
functionName是函数的名称。parameters是函数的参数列表,包含参数名和参数类型。returnType是函数的返回值类型。- 函数体内包含要执行的代码。
return语句用于返回函数的结果(可选)。
例如:
func add(x, y int) int {
result := x + y
return result
}
4.2. 函数的调用
要调用一个函数,只需使用函数名和传递给函数的参数列表。
sum := add(10, 20) // 调用 add 函数,并传递参数 10 和 20
4.3. 多返回值
Golang 函数支持多返回值。这对于同时返回多个值非常有用。
func swap(x, y int) (int, int) {
return y, x
}
a, b := 5, 10
a, b = swap(a, b) // 调用 swap 函数并交换 a 和 b 的值
4.4. 隐式返回值
func swap(x, y int) (a, b int) {
a = y
b = x
return // 不需要显式使用 return a, b
}
4.5. 匿名函数
在 Golang 中,您还可以创建匿名函数,即没有函数名的函数,通常用于实现闭包和在其他函数内部使用。
func main() {
add := func(x, y int) int {
return x + y
}
result := add(3, 4) // 调用匿名函数
}
4.6. 函数作为参数和返回值
在 Go 中,函数可以作为参数传递给其他函数,也可以作为另一个函数的返回值。
func operation(x, y int, op func(int, int) int) int {
return op(x, y)
}
func add(x, y int) int {
return x + y
}
result := operation(10, 20, add) // 调用 operation 函数并传递 add 函数作为参数
4.7. 可变参数函数
Go 支持可变参数函数,这允许您传递任意数量的参数。
func sum(numbers ...int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}
result := sum(1, 2, 3, 4, 5) // 调用 sum 函数并传递多个参数
4.8. 递归函数
递归是在函数内部调用自身的过程。Go 支持递归函数。
func factorial(n int) int {
if n <= 1 {
return 1
}
return n * factorial(n-1)
}
result := factorial(5) // 计算 5 的阶乘
5. 指针
在Golang中指针的使用并不像c语言那样6,Golang中指针常用在传递大型数据结构,以避免复制数据,提高性能,还有在函数内部修改函数外部变量的值。
5.1. 声明指针
要声明一个指针,可以使用 * 符号,后跟变量的类型。例如:
var x int
var ptr *int
在这个示例中,ptr 是一个整数类型的指针,可以用来存储 x 的内存地址。
5.2. 取地址操作符 &
要获取一个变量的地址,可以使用取地址操作符 &。例如:
x := 42
ptr := &x // ptr 现在包含了变量 x 的内存地址
5.3. 解引用操作符 *
要访问指针指向的变量的值,可以使用解引用操作符 *。例如:
x := 42
ptr := &x
value := *ptr // value 现在包含了 ptr 指向的变量 x 的值
5.4. 指针作为函数参数
指针常常用于将变量的引用传递给函数,从而在函数内部修改变量的值。这种传递方式被称为传递指针或者按引用传递。
func modifyValue(ptr *int) {
*ptr = 100
}
x := 42
ptr := &x
modifyValue(ptr) // 传递 x 的地址给函数
fmt.Println(x) // 输出 100,因为在函数内部修改了 x 的值
5.5. 指针的零值
未初始化的指针的零值是 nil,表示它不指向任何有效的内存地址。
var ptr *int // ptr 是一个 nil 指针
5.6. new 函数
Go 语言提供了 new 函数,用于动态分配内存并返回一个指向新分配内存的指针。例如:
ptr := new(int) // 分配一个整数的内存,并返回一个指向它的指针
*ptr = 42 // 通过指针设置值
6.结构体
我认为,结构体就是声明模型。Golang通过结构体来实现其他语言的面向对象。
6.1. 声明结构体
要声明一个结构体,需要使用 type 关键字,后跟结构体的名称和字段列表。字段列表由字段名称和字段类型组成,通常以大写字母开头,表示字段是导出的(可在其他包中访问)。
type Person struct {
FirstName string
LastName string
Age int
}
在这个示例中,我们声明了一个名为 Person 的结构体,它有三个字段:FirstName、LastName 和 Age,分别表示人的名字和年龄。
6.2. 创建结构体实例
要创建一个结构体的实例,可以使用结构体的名称和字段值来初始化实例。
person := Person{
FirstName: "John",
LastName: "Doe",
Age: 30,
}
6.3. 访问结构体字段
可以使用点操作符 . 来访问结构体的字段。
fmt.Println(person.FirstName) // 输出 "John"
fmt.Println(person.Age) // 输出 30
6.4. 匿名结构体
Go 支持匿名结构体,这些结构体没有结构体名称,通常用于创建临时的数据结构。
book := struct {
Title string
Author string
}{
Title: "The Go Programming Language",
Author: "Alan A. A. Donovan",
}
6.5. 结构体嵌套
结构体可以嵌套在其他结构体中,以创建更复杂的数据结构。
type Address struct {
Street string
City string
ZipCode string
}
type Person struct {
FirstName string
LastName string
Age int
Address Address // 嵌套的 Address 结构体
}
6.6. 结构体标签(Tags)
结构体字段可以附加标签,标签是字符串文本,可以在运行时通过反射访问。标签通常用于描述字段的元数据信息,如序列化和反序列化等。
type Person struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Age int `json:"age"`
}
6.7. 结构体指针
结构体指针在 Go 语言中是一种特殊的指针类型,它用于指向结构体的实例,允许您通过指针修改结构体的字段值。
声明结构体指针:var ptr *Mystruct
6.8. 创建结构体指针
可以通过取地址操作符 & 来创建一个结构体实例的指针。这个指针指向结构体的内存地址。
type Person struct {
Name string
Age int
}
func main() {
person := &Person{
Name: "John",
Age: 30,
}
}
6.9. 使用结构体指针访问
可以使用解引用操作符 * 来访问结构体指针指向的实际结构体,然后访问其字段。
func main() {
person := &Person{
Name: "Alice",
Age: 25,
}
fmt.Println((*person).Name) // 输出 "Alice"
fmt.Println(person.Age) // 输出 25,语法糖,等同于 (*person).Age
}
6.10. 修改结构体字段值
结构体指针允许您在函数中直接修改原始结构体的字段值。
func modifyPerson(p *Person) {
p.Name = "Bob"
p.Age = 27
}
func main() {
person := &Person{
Name: "Alice",
Age: 25,
}
modifyPerson(person)
fmt.Println(person.Name, person.Age) // 输出 "Bob 27"
}
6.11. 结构体方法
要在结构体上声明方法,需要使用 func 关键字,紧接着是接收者。接收者可以是结构体的值或结构体的指针。接收者的类型决定了方法是与结构体值还是结构体指针关联的。
6.11.1 值接收者
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}
在上面的示例中,Area 方法是一个值接收者方法,它与 Circle 结构体值关联。这意味着在方法内部对 c 的修改不会影响原始 Circle 结构体的值。
6.11.2 指针接收者
type Rectangle struct {
Width float64
Height float64
}
func (r *Rectangle) Area() float64 {
return r.Width * r.Height
}
在这个示例中,Area 方法是一个指针接收者方法,它与 Rectangle 结构体指针关联。这允许在方法内部修改原始结构体的字段。
6.11.3调用结构体方法
结构体方法可以使用点操作符 . 来调用,与调用结构体的字段类似。
circle := Circle{Radius: 5.0}
area := circle.Area()
rectangle := &Rectangle{Width: 3.0, Height: 4.0}
area := rectangle.Area()
6.11.4结构体方法的用途
结构体方法的主要用途包括:
-
封装数据与行为:结构体方法允许将数据和与其相关的操作封装在一起,提高了代码的可读性和可维护性。
-
自定义类型的操作:您可以为自定义类型添加自定义操作,例如计算面积、获取属性等。
-
实现接口:结构体方法是实现 Go 接口的一种方式,它们允许结构体满足特定接口的要求。
-
避免直接操作结构体字段:通过提供方法,您可以控制对结构体字段的访问和修改,从而提高代码的安全性。
-
模块化代码:将操作与数据绑定在一起有助于代码的模块化,使代码更易于组织和维护。