Go语言入门笔记总结(下) | 青训营

94 阅读10分钟

以下是关于Go语言入门的一些笔记总结(下):

Go语言是一种简洁高效的编程语言,适用于各种应用领域Go语言入门涵盖了基本语法、常用特性和控制结构等关键概念,以下是一份总结:

5.数据类型

数据类型是编程中的基本概念,它们定义了可以存储的数据的类型和范围。以下是对数据类型的详细解释,以及附带的代码示例。

Go语言具有多种基本数据类型,用于存储不同类型的数据。这些基本数据类型包括:

  1. 整数类型: 用于存储整数值,可以分为有符号和无符号类型。

    • intint8int16int32int64:有符号整数类型。
    • uintuint8uint16uint32uint64:无符号整数类型。
  2. 浮点数类型: 用于存储小数值。

    • float32:单精度浮点数。
    • float64:双精度浮点数。
  3. 布尔值类型: 用于存储逻辑值(真或假)。

    • bool:布尔值类型。
  4. 字符串类型: 用于存储文本数据。

    • string:字符串类型。

下面是一个示例,展示如何声明和使用基本数据类型:

package main

import "fmt"

func main() {
    var age int = 30
    var pi float64 = 3.14159
    var isTrue bool = true
    var name string = "Alice"

    fmt.Println("Age:", age)
    fmt.Println("Pi:", pi)
    fmt.Println("Is True?", isTrue)
    fmt.Println("Name:", name)
}

复合数据类型

复合数据类型是由基本数据类型组合而成的数据类型,用于存储更复杂的数据结构。这些复合数据类型包括:

  1. 数组: 用于存储相同类型的固定长度数据序列。
  2. 切片: 动态数组,允许存储可变长度的数据序列。
  3. 映射: 键值对的集合,用于存储不同类型的数据。
  4. 结构体: 允许将不同类型的字段组合在一起,用于定义自定义数据结构。
  5. 接口: 定义方法集合,用于实现多态性和抽象性。

下面是一个示例,展示如何声明和使用复合数据类型:

package main

import "fmt"

func main() {
    // 数组
    var numbers [5]int
    numbers[0] = 1
    numbers[1] = 2

    // 切片
    var colors = []string{"red", "green", "blue"}

    // 映射
    ages := map[string]int{
        "Alice": 25,
        "Bob":   30,
    }

    // 结构体
    type Person struct {
        Name string
        Age  int
    }

    person := Person{Name: "Alice", Age: 25}

    fmt.Println("Numbers:", numbers)
    fmt.Println("Colors:", colors)
    fmt.Println("Ages:", ages)
    fmt.Println("Person:", person)
}

总结

数据类型是Go语言中用于存储不同类型数据的重要概念。基本数据类型包括整数、浮点数、布尔值和字符串。复合数据类型包括数组、切片、映射、结构体和接口,它们用于存储更复杂的数据结构。

6.指针

指针是计算机编程中的一种重要概念,它用来引用和操作内存中的数据。虽然Go语言中相对少用指针,但了解它们的工作原理是很有价值的。以下是关于指针的详细解释,以及附带的代码示例。

指针的基本概念

指针是一个变量,它存储了另一个变量的内存地址。通过使用指针,可以直接访问内存中存储的值。在Go中,通过引用而不是复制数据,可以提高程序的效率。

创建指针

在Go中,使用 & 操作符获取变量的内存地址,从而创建一个指向该变量的指针。

package main

import "fmt"

func main() {
    number := 42
    pointer := &number

    fmt.Println("Number:", number)
    fmt.Println("Pointer:", pointer)
}

解引用指针

使用 * 操作符可以解引用指针,获取指针指向的值。

package main

import "fmt"

func main() {
    number := 42
    pointer := &number

    fmt.Println("Number:", *pointer) // 解引用指针获取值
}

自动垃圾回收

相对于其他编程语言,Go在内存管理方面更加便利,因为它有自动垃圾回收机制。这意味着我们不必手动释放不再需要的内存,因为Go会自动处理这些事情。这是Go语言的一大优势,减少了内存泄漏和错误的可能性。

注意事项

在使用指针时需要注意以下事项:

  • 空指针(nil):如果指针未初始化,它将是空指针,指向内存地址0。
  • 不要在函数中返回局部变量的指针:局部变量的生命周期限制在函数内部,函数结束后,指向它的指针将变得无效。
  • 避免野指针:确保指针指向有效的内存地址,否则可能导致未定义的行为。

总结

指针是一种强大的工具,允许在程序中引用和操作内存中的数据。Go语言虽然相对较少使用指针,但了解如何创建、解引用以及与指针相关的注意事项是很有价值的。自动垃圾回收机制使内存管理更为简单,但在需要时,了解指针的工作原理可以帮助我们更好地理解程序的运行过程。

7.结构体

结构体(Struct)是一种在Go语言中用于将不同类型的字段组合成自定义的数据结构的方式。结构体允许定义具有多个字段的新类型,这使得数据的组织更加灵活和可读。以下是关于结构体的详细解释,以及附带的代码示例。

定义结构体

在Go中,使用 type 关键字来定义结构体。结构体由一组字段组成,每个字段有一个名称和一个类型。

package main

import "fmt"

type Person struct {
    FirstName string
    LastName  string
    Age       int
}

func main() {
    var person1 Person
    person1.FirstName = "Alice"
    person1.LastName = "Smith"
    person1.Age = 30

    fmt.Println("First Name:", person1.FirstName)
    fmt.Println("Last Name:", person1.LastName)
    fmt.Println("Age:", person1.Age)
}

结构体实例化

可以通过结构体的字段来初始化结构体实例。

package main

import "fmt"

type Point struct {
    X int
    Y int
}

func main() {
    origin := Point{X: 0, Y: 0}
    fmt.Println("Origin:", origin)
}

结构体嵌套

结构体可以嵌套在另一个结构体中,从而创建更复杂的数据结构。

package main

import "fmt"

type Address struct {
    Street  string
    City    string
    Country string
}

type Person struct {
    FirstName string
    LastName  string
    Age       int
    Address   Address // 嵌套结构体
}

func main() {
    person := Person{
        FirstName: "Alice",
        LastName:  "Smith",
        Age:       30,
        Address: Address{
            Street:  "123 Main St",
            City:    "New York",
            Country: "USA",
        },
    }

    fmt.Println("First Name:", person.FirstName)
    fmt.Println("Address:", person.Address)
}

总结

结构体是一种强大的工具,用于创建自定义的复合数据类型。通过结构体,可以将不同类型的字段组合在一起,创建更具有结构和层次的数据类型。结构体的实例化和字段访问使得能够操作这些自定义类型的数据。结构体的嵌套使数据结构更加灵活,允许我们创建复杂的数据组织。

8.切片和映射

切片和映射是Go语言中用于处理集合数据的两种重要数据类型。它们分别提供了动态数组和键值对集合的功能。以下是关于切片和映射的详细解释,以及附带的代码示例。

切片(Slice)

切片是一种动态数组,允许在运行时添加或删除元素。切片的底层是一个数组,但其大小可以动态调整。

创建切片

可以使用 make 函数创建切片。

package main

import "fmt"

func main() {
    // 创建一个初始容量为3的切片
    numbers := make([]int, 3)
    numbers[0] = 1
    numbers[1] = 2
    numbers[2] = 3

    fmt.Println("Numbers:", numbers)
}

切片操作

切片支持类似数组的索引和截取操作。

package main

import "fmt"

func main() {
    numbers := []int{1, 2, 3, 4, 5}
    
    // 截取前三个元素
    slice1 := numbers[:3]
    fmt.Println("Slice 1:", slice1) // [1 2 3]
    
    // 截取后两个元素
    slice2 := numbers[3:]
    fmt.Println("Slice 2:", slice2) // [4 5]
}

映射(Map)

映射是一种键值对集合,也称为字典或哈希表。每个键对应一个值。

创建映射

可以使用 make 函数创建映射。

package main

import "fmt"

func main() {
    ages := make(map[string]int)
    ages["Alice"] = 25
    ages["Bob"] = 30

    fmt.Println("Ages:", ages)
}

访问映射的值

通过键来访问映射中的值。

package main

import "fmt"

func main() {
    ages := map[string]int{
        "Alice": 25,
        "Bob":   30,
    }

    fmt.Println("Alice's Age:", ages["Alice"])
}

总结

切片和映射是Go语言中用于处理集合数据的重要概念。切片是动态数组,使用 make 创建,支持切片操作。映射是键值对的集合,使用 make 创建,通过键来访问值。切片和映射提供了更灵活的数据结构,用于存储和操作集合数据。

9.并发

并发是Go语言的一个强大特性,允许同时执行多个任务,从而提高程序的性能和响应能力。Go通过goroutine实现并发,使用通道(channel)进行协程间的通信,以避免竞态条件。以下是关于并发的详细解释,以及附带的代码示例。

Goroutine

Goroutine是Go语言中的轻量级线程,允许您在同一个程序中同时执行多个任务。通过使用go关键字,可以启动一个新的goroutine。

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("Main goroutine started")

	// 启动一个新的goroutine
	go printNumbers()

	// 主goroutine继续执行其他任务
	for i := 0; i < 3; i++ {
		fmt.Println("Main:", i)
		time.Sleep(time.Millisecond * 500)
	}

	fmt.Println("Main goroutine finished")
}

func printNumbers() {
	for i := 0; i < 3; i++ {
		fmt.Println("PrintNumbers:", i)
		time.Sleep(time.Millisecond * 500)
	}
}

通道(Channel)

通道是goroutine之间进行通信的机制,用于在不同goroutine之间传递数据。通道可以避免竞态条件,确保数据在并发环境中的安全传递。

创建和使用通道

可以使用make函数创建一个通道。

package main

import (
	"fmt"
	"time"
)

func main() {
	messageChannel := make(chan string)

	go sendMessage(messageChannel)

	message := <-messageChannel // 从通道中接收数据
	fmt.Println("Received message:", message)
}

func sendMessage(ch chan string) {
	time.Sleep(time.Second)
	ch <- "Hello, Go Concurrency!" // 将数据发送到通道
}

总结

并发是Go语言的一个重要特性,通过goroutine实现多任务的同时执行,提高程序的性能和响应能力。使用通道进行协程间的通信,可以避免竞态条件,确保数据在并发环境中的安全传递。并发是编写高效、并发安全的程序的关键要素之一,Go语言在这方面提供了简单而强大的工具。

10.错误处理

错误处理是编程中不可或缺的一部分,用于识别和处理在程序执行过程中可能发生的问题。Go语言采用一种特殊的错误处理机制,鼓励使用多返回值来处理错误情况。以下是关于错误处理的详细解释,以及附带的代码示例。

错误处理机制

在Go语言中,函数通常返回两个值:正常的结果和一个错误。错误通常用error类型表示,如果函数执行成功,错误为nil,否则为非nil的错误值。

返回多个值

package main

import (
	"errors"
	"fmt"
)

func divide(a, b float64) (float64, error) {
	if b == 0 {
		return 0, errors.New("division by zero")
	}
	return a / b, nil
}

func main() {
	result, err := divide(10, 2)
	if err != nil {
		fmt.Println("Error:", err)
	} else {
		fmt.Println("Result:", result)
	}
}

自定义错误类型

还可以自定义error类型,以便更好地描述错误情况。

package main

import (
	"fmt"
)

type CustomError struct {
	Message string
}

func (e CustomError) Error() string {
	return e.Message
}

func process(data int) error {
	if data < 0 {
		return CustomError{Message: "Negative data not allowed"}
	}
	return nil
}

func main() {
	err := process(-10)
	if err != nil {
		fmt.Println("Error:", err)
	} else {
		fmt.Println("Data processed successfully")
	}
}

总结

错误处理是编写健壮程序的重要组成部分。Go语言鼓励使用多返回值来处理错误,将正常结果与错误信息分开。通过检查错误是否为nil,可以判断是否发生了错误。自定义错误类型可以使错误信息更具有描述性,帮助开发人员更好地理解问题所在。正确的错误处理可以提高程序的可靠性和可维护性。

11.defer

defer 是Go语言中的一个关键字,用于延迟执行函数调用,这些调用会在包含它们的函数执行完毕后才被执行。defer通常用于确保在函数退出之前进行清理工作,例如释放资源、关闭文件等。以下是关于defer的详细解释,以及附带的代码示例。

延迟执行函数调用

使用defer关键字可以将函数调用推迟到包含defer语句的函数结束之前执行。

package main

import "fmt"

func main() {
	defer fmt.Println("This will be printed last")
	fmt.Println("This will be printed first")
}

用途示例:资源释放

defer通常用于确保资源在函数退出时得到释放,以避免资源泄漏。

package main

import (
	"fmt"
	"os"
)

func main() {
	file, err := os.Open("example.txt")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer file.Close() // 确保在函数退出时关闭文件

	// 读取文件内容并处理
}

延迟执行顺序

如果函数包含多个defer语句,它们将按照后进先出(LIFO)的顺序执行。

package main

import "fmt"

func main() {
	defer fmt.Println("First")
	defer fmt.Println("Second")
	defer fmt.Println("Third")
}

注意事项

  • defer语句执行时,函数的参数会被计算并传递给它,但实际的函数调用不会立即执行。
  • defer语句中的变量会在defer语句被执行时求值,而不是在实际函数调用时。

总结

defer关键字用于延迟执行函数调用,通常用于确保在函数退出之前进行清理工作。它在资源管理、错误处理等方面非常有用。通过使用defer,可以更轻松地确保资源被释放,代码更加清晰且容易维护。