Golang快速入门(2) | 青训营

134 阅读13分钟

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的元素
  • 切片截取,读取切片的一部分,或根据数组、切片创建一个新的切片。

    切片中的指针指向一个底层数组的起始位置,也可以理解为切片截取是左闭右开。如下图所示:image-20230819225227672

请看下面的代码示例:

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 的结构体,它有三个字段:FirstNameLastNameAge,分别表示人的名字和年龄。

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结构体方法的用途

结构体方法的主要用途包括:

  1. 封装数据与行为:结构体方法允许将数据和与其相关的操作封装在一起,提高了代码的可读性和可维护性。

  2. 自定义类型的操作:您可以为自定义类型添加自定义操作,例如计算面积、获取属性等。

  3. 实现接口:结构体方法是实现 Go 接口的一种方式,它们允许结构体满足特定接口的要求。

  4. 避免直接操作结构体字段:通过提供方法,您可以控制对结构体字段的访问和修改,从而提高代码的安全性。

  5. 模块化代码:将操作与数据绑定在一起有助于代码的模块化,使代码更易于组织和维护。