Golang基础知识

如何安装第三方包?

golang的函数

函数定义


在 Go 语言中,函数通过 func 关键字定义,其基本语法如下:

func functionName(parameterList) (resultList) {
    // 函数体
}
  • func 是定义函数的关键字。
  • functionName 是函数的名称。
  • parameterList 是函数参数的列表,包括参数的类型和名称。参数列表为空时,表示该函数不接受任何参数。
  • resultList 是函数返回值的列表,包括返回值的类型。如果没有返回值,则使用 void 或者省略括号。Go 语言支持命名返回值,这样可以直接在函数体中通过返回值的名称返回结果,而不需要显式声明返回语句。
  • // 函数体 是函数执行的代码块。

函数案例

无参数和返回值的函数

func sayHello() {
    fmt.Println("Hello, World!")
}

func main() {
    sayHello() // 调用函数
}

带参数的函数

func add(a int, b int) int {
    return a + b
}

func main() {
    sum := add(5, 3)
    fmt.Println("The sum is:", sum)
}

带返回值的函数

func multiply(a, b int) (result int) {
    result = a * b
    return // 命名返回值,这里也可以省略 return 语句
}

func main() {
    product := multiply(7, 8)
    fmt.Println("The product is:", product)
}

变参函数

Go 语言支持变参函数,即函数可以接受不定数量的参数。

func sum(numbers ...int) (total int) {
    for _, number := range numbers {
        total += number
    }
    return
}

func main() {
    total := sum(1, 2, 3, 4, 5)
    fmt.Println("The total is:", total)
}

定义默认值的函数

func calculate(a, b int, operation string) int {
    switch operation {
    case "add":
        return a + b
    case "subtract":
        return a - b
    default:
        return 0
    }
}

func main() {
    result := calculate(10, 5, "add")
    fmt.Println(result) 
}

匿名函数(Lambda)

Go 语言中的匿名函数也称为闭包,可以在运行时定义,通常用作回调函数或高阶函数的参数。

func printNumbers(f func(int)) {
    for i := 0; i < 5; i++ {
        f(i)
    }
}

func main() {
    printNumbers(func(i int) {
        fmt.Println(i)
    })
}

带错误返回的函数

在 Go 语言中,返回 error 类型的函数通常是那些可能会在执行过程中遇到错误情况的函数。error 是 Go 标准库中的一个接口类型,定义如下:

type error interface {
    Error() string
}

这个接口要求实现一个 Error() 方法,该方法返回一个描述错误的字符串。当函数执行时,如果遇到无法恢复的错误状态,它会返回一个实现了 error 接口的具体类型值,以及通常是一个成功的结果值。调用者可以通过检查 error 返回值来确定函数是否成功执行。

示例:带错误返回的函数

下面是一个简单的示例,展示了一个可能返回 error 的函数:

package main

import (
    "fmt"
    "os"
)

// 创建一个可能返回错误的函数
func createFile(filename string) (*os.File, error) {
    file, err := os.Create(filename)
    if err != nil {
        // 如果创建文件时出现错误,返回 nil 和错误信息
        return nil, err
    }
    // 如果成功创建文件,返回文件对象和 nil
    return file, nil
}

func main() {
    // 使用函数并处理可能的错误
    file, err := createFile("example.txt")
    if err != nil {
        fmt.Println("Error creating file:", err.Error())
    } else {
        defer file.Close() // 确保文件在使用后关闭
        fmt.Println("File created successfully.")
    }
}

在这个例子中,createFile 函数尝试创建一个新文件,并返回一个 *os.File 类型的文件对象和一个 error 类型的错误。如果在创建文件的过程中发生错误,os.Create 会返回一个非 nil 的错误对象,createFile 函数随后将这个错误对象返回给调用者。

错误处理

在 Go 语言中,错误处理是通过检查函数返回的 error 值来完成的。如果 error 值不是 nil,则表示函数执行过程中遇到了问题。调用者有责任处理这些错误,通常是通过记录日志、尝试恢复、向用户报告错误或根据错误类型执行其他逻辑。

错误处理是 Go 语言编程中的一个重要部分,它有助于编写健壮、可靠的代码。

 

特殊说明-结构体方法声明

案例

func (w WhiteCat) eat() {
	fmt.Println("白猫在吃饭,它的名字叫", w.name)
}

在Golang中,这种定义函数的方法是结构体方法的声明。结构体方法允许你为结构体类型的实例关联函数行为。这种方式在面向对象编程语言中很常见,它允许你模拟面向对象编程中的“类”和“对象”的概念,尽管Go语言本身是静态类型的、并发支持的编程语言,并不是传统意义上的面向对象语言。

在上面代码片段中

  • func 是Go语言中定义函数的关键字。
  • (w WhiteCat) 表示这是一个结构体方法,WhiteCat 是接收者(receiver)的类型,w 是接收者的名称。这意味着这个方法与WhiteCat类型的每个实例相关联。
  • eat 是这个方法的名称,它表示这个方法是WhiteCat类型的一个行为或动作。

WhiteCat类型的一个实例调用eat方法时,它会执行方法体中的代码,即打印出一条消息,告诉别人这只白猫的名字。

完整的代码如下:

package main

import "fmt"

// Duck 定义一个猫接口
type Cat interface {
	eat()
	run()
}

// WhiteCat 定义一个白猫结构体
type WhiteCat struct {
	name string
	age  int
	sex  string
}

// BlackCat 定义一个黑猫结构体
type BlackCat struct {
	name string
	age  int
	sex  string
}

// 让白猫和黑猫实现接口中的所有方法,就叫实现该接口
// 让白猫实现 Cat 接口
func (w WhiteCat) eat() {
	fmt.Println("白猫在吃饭,它的名字叫", w.name)
}

func (w WhiteCat) run() {
	fmt.Println("白猫在走路,它的名字叫", w.name)
}

// 让黑猫实现 Cat 接口
func (b BlackCat) eat() {
	fmt.Println("黑猫在喝汤,它的名字叫", b.name)
}

func (b BlackCat) run() {
	fmt.Println("黑猫在散步,它的名字叫", b.name)
}

func main() {
	var cat Cat
	cat = WhiteCat{"小白", 5, "雄性"} // 把我的对象赋值给一个接口类型,就可以实现多态的效果
	fmt.Println(cat)

	// cat 现在他是一个接口,它只能取方法,不能取出属性了。
	cat.eat()
	cat.run()
}

输出内容是:
{小白 5 雄性}
白猫在吃饭,它的名字叫 小白
白猫在走路,它的名字叫 小白

类型和接口类型

在 Go 语言中,类型和接口类型之间存在着紧密的联系,但它们是两个不同的概念。下面我将详细解释它们之间的关系:

类型(Type)

在 Go 语言中,"类型"(Type)是指一组具有相同名称和属性的数据的集合。类型可以是基本数据类型(如 int, float64, bool, string 等),复合数据类型(如结构体 struct),切片 slice,映射 map,通道 channel,函数 func,接口 interface 、指针类型等。

每种类型都有其特定的操作和行为。例如,int 类型可以进行整数运算,string 类型可以进行字符串拼接和搜索操作,而结构体可以包含多种不同类型的字段,形成复杂的数据结构。

基本数据类型

Go 语言的基本数据类型包括:

  • bool:布尔类型,表示 true 或 false
  • string:字符串类型,表示文本。
  • intint8int16int32int64:整数类型,有不同的位宽。
  • uintuint8uint16uint32uint64:无符号整数类型。
  • byte:字节类型,是 int8 的别名,表示 8 位整数。
  • rune: rune 类型,是 int32 的别名,表示 Unicode 码点。
  • float32float64:浮点数类型。
  • complex64complex128:复数类型。

复合数据类型

  • 结构体(struct:可以包含多个不同类型的字段,用于表示复杂的数据结构。
  • 数组(array:固定长度的序列,所有元素类型相同。
  • 切片(slice:动态长度的序列,基于数组,但可以改变大小。
  • 映射(map:键值对集合,每个键唯一对应一个值。
  • 通道(channel:用于在不同的 Goroutine 之间传递数据的通信机制。
  • 接口(interface:定义了一组方法的集合,任何实现了这些方法的类型都实现了该接口。

函数类型

Go 语言中的函数也是类型的一种。函数类型由其参数列表和返回值列表决定。

指针类型

每种基本类型都有一个对应的指针类型,表示对该类型的引用。例如,*int 是指向 int 类型的指针。

类型转换

在 Go 语言中,可以使用类型转换来将一个类型的值转换为另一个类型的值。类型转换的语法是 T(表达式),其中 T 是目标类型,而 表达式 是要转换的值。

类型断言

类型断言用于接口类型,用于检查和转换接口变量中存储的具体类型。语法是 接口名.(目标类型)

类型是 Go 语言中的核心概念之一,理解不同类型的行为和操作对于编写高效、可靠的 Go 代码至关重要。

 

接口类型(Interface Type)

接口类型是一种特殊类型的类型,它定义了一组方法的签名,但不包含具体的实现。接口类型的主要作用是提供了一种方式,使得不同类型的对象可以以统一的方式处理。如果一个类型实现了接口中声明的所有方法,那么这个类型就实现了该接口。

接口的定义

接口通过 interface 关键字定义,其基本语法如下:

type InterfaceName interface {
    Method1(Parameters) ReturnType1
    Method2(Parameters) ReturnType2
    // ...
}
  • InterfaceName 是接口的名称。
  • Method1Method2, ... 是接口中定义的方法签名,包括方法名、参数列表和返回类型。

接口的特性

  1. 类型断言:接口类型的变量可以存储任何实现了接口声明方法的值。要访问接口变量中存储的具体值,需要使用类型断言。
  2. 空接口:如果一个接口没有声明任何方法,它被称为空接口,用 interface{} 表示。任何类型的值都可以赋给空接口类型的变量。
  3. 类型实现接口:如果一个类型实现了接口中的所有方法,那么这个类型就实现了该接口。在 Go 中,类型实现接口是隐式的,不需要显式声明。
  4. 接口的多重实现:一个类型可以实现多个接口。

类型和接口类型的联系

  1. 实现(Implementation):任何具体类型(如结构体、数组、切片等)都可以实现一个或多个接口。当一个类型拥有了接口声明的所有方法时,它就实现了该接口。这种实现是隐式的,不需要显式声明。
  2. 赋值(Assignment):在 Go 中,如果一个类型 T 实现了接口 I,那么 T 类型的变量可以被赋值给 I 类型的变量。这是因为 T 已经具备了 I 接口所需的所有方法。
  3. 多态性(Polymorphism):接口类型使得我们可以编写更加通用的代码。通过接口,我们可以编写处理未知类型的函数,只要这些类型实现了相应的接口。这种多态性是 Go 语言强大的特性之一。
  4. 类型断言(Type Assertion):当我们有一个接口类型的变量时,我们可以使用类型断言来检查和转换它所持有的具体类型。这是接口和具体类型之间联系的一个重要方面,它允许我们在运行时确定和访问接口变量中存储的具体类型。

具体示例1:

假设我们有一个接口 Reader,它定义了一个 Read 方法:

type Reader interface {
    Read(p []byte) (n int, err error)
}

现在,我们有两个不同的类型 FileStringReader,它们都实现了 Read 方法:

type File struct{}

func (f *File) Read(p []byte) (n int, err error) {
    // 实现文件读取的代码
}

type StringReader struct {
    s string
}

func (sr *StringReader) Read(p []byte) (n int, err error) {
    // 实现字符串读取的代码
}

在这个例子中,FileStringReader 都实现了 Reader 接口,因为它们都有 Read 方法。现在我们可以创建一个 Reader 类型的变量,并将其分别赋值为 FileStringReader 类型的实例:

var reader Reader

file := &File{}
reader = file // file 实现了 Reader 接口,所以可以赋值给 Reader 类型的变量

stringReader := &StringReader{}
reader = stringReader // stringReader 也实现了 Reader 接口,同样可以赋值给 Reader 类型的变量

通过这种方式,我们可以编写通用的函数来处理任何实现了 Reader 接口的类型,而不需要关心具体的实现细节。这就是类型和接口类型之间的联系,以及它们如何共同工作以支持多态性和类型安全。

具体示例2:见上方函数部分中的“特殊说明-结构体方法声明”

总结来看,接口类型的定义和实现,可以类似实现面向对象编程中的“类”和“对象”的概念,定义一个接口就好比定义一个类,后面对接口的实现,就好比创建了一个对象。

具体示例3:error处理

在Go语言中,error是一个内置的接口类型,它定义了错误处理的基本方法。error接口有一个方法:

type error interface {
    Error() string
}

任何实现了Error() string方法的类型都可以被用作错误值。当一个函数返回error类型时,它通常

是返回了一个实现了error接口的值,这样调用者就可以通过调用Error()方法来获取错误的描述性信息。

下面是一个简单的例子,展示了如何自定义一个错误类型,并确保它实现了error接口:

package main

import (
    "fmt"
)

// MyError 是一个自定义的错误类型,实现了 error 接口
type MyError struct {
    Message string
}

// 实现 error 接口的 Error() 方法
func (e MyError) Error() string {
    return e.Message
}

func main() {
    // 创建一个自定义的错误实例
    myErr := MyError{Message: "Custom error occurred"}

    // 打印错误信息
    fmt.Println(myErr.Error()) // 输出: Custom error occurred
}

在这个例子中,MyError是一个结构体,它包含一个Message字段用于存储错误信息。MyError类型通过实现Error() string方法来满足error接口的要求。这样,当我们创建MyError的实例并调用Error()方法时,它将返回存储在Message字段中的错误信息。

在实际编程中,通常会使用标准库中提供的错误类型,如ioutil包中的io.EOF(文件结束错误)或者os包中的os.ErrNotExist(文件或目录不存在错误),这些错误类型都已经实现了error接口。自定义错误类型通常用于封装更具体的业务逻辑错误信息。