当前位置:   article > 正文

Golang笔记

Golang笔记

01 = 和 := 的区别?

= 是基本的赋值运算符,用于将右边的值赋给左边的变量。例如:

a = 10
b = "hello"
  • 1
  • 2

在上面的示例中,ab 分别被赋值为整数 10 和字符串 “hello”。

:= 是 Go 语言中的简短赋值运算符,它用于在声明变量的同时进行赋值。例如:

c := 20
d := "world"
  • 1
  • 2

在上面的示例中,cd 被声明并分别被赋值为整数 20 和字符串 “world”。

可以看到,使用 := 可以在一行代码中完成变量的声明和赋值,简化了代码的编写。但是,:= 只能在函数内部使用,不能在全局变量或常量的声明中使用。

需要注意的是,使用 := 时,如果左边的变量已经存在,会先将其初始化为零值,然后再进行赋值操作。例如:

e := 10
f := e + 10
  • 1
  • 2

在上面的示例中,e 被初始化为零值 0,然后被赋值为 10。接着,f 被初始化为零值 0,然后被赋值为 e + 10 的值,即 20。

02 指针的作用

指针指向变量的地址,在64位机器上占8个字节
【1 字节(Byte)= 8 位(bit)
1 千字节(KB,Kilobyte)= 1,024 字节(2^10 字节)】
作用

  • 取址然后取值
  • swap函数 交换变量的值
  • 指针接收器来改变结构体里面的值
package main
import "fmt"

type Counter struct {
   
    count int
}

func (c *Counter) Increment() {
   
    c.count++ // 增加 "count" 字段的值
}

func main() {
   
    myCounter := &Counter{
   } // 创建一个新的 "Counter" 实例。"&Counter{}" 表示我们直接获得了一个指向新实例的指针。
    fmt.Println("Initial count:", myCounter.count) // 打印初始的计数(默认为0)
    myCounter.Increment() // 调用 "Increment" 方法来增加计数
    fmt.Println("Count after incrementing:", myCounter.count) // 打印增加后的计数
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

将输出

Initial count: 0
Count after incrementing: 1
  • 1
  • 2

03 Go 允许多个返回值吗?

可以。通常函数除了一般返回值还会返回一个error。

04 Go 有异常类型吗?

有,Go用error类型代替了try…catch. 也可以用errors.New()来定义自己的异常

 _, err := funcDemo()
if err != nil {
   
    fmt.Println(err)
    return
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

05 什么是协程(Goroutine)

协程是用户态轻量级线程
在这里插入图片描述
协程是线程调度的基本单位
与传统的线程相比,协程更加轻量级,因为它们在内存使用和上下文切换方面更加高效
在这里插入图片描述
通常在函数前加上go关键字就能实现并发。一个Goroutine会以一个很小的栈启动2KB或4KB,当遇到栈空间不足时,栈会自动伸缩, 因此可以轻易实现成千上万个goroutine同时启动。

06 ❤ 如何高效地拼接字符串

strings.Join ≈ strings.Builder(没有变量拷贝) > bytes.Buffer > “+” > fmt.Sprintf (要用反射获取值)

在 Go 语言中,strings.Builder 类型被设计用来高效地构建字符串。当提到 “strings.Builder(没有变量拷贝)” 这个概念时,主要是指在使用 strings.Builder 时,你可以避免在字符串拼接或修改过程中发生不必要的内存拷贝,从而提高性能。

通常,在编程中构建或修改字符串时,每次操作都可能涉及到创建字符串的新副本。这是因为字符串在很多语言中(包括Go)是不可变的,意味着一旦创建,它的内容就不能被改变。因此,任何修改操作(如拼接、插入、删除等)都会生成新的字符串,这可能涉及到复制原始字符串及附加内容到新的内存位置。

然而,strings.Builder 使用了不同的方法。它在内部维护一个字节切片(byte slice),用于存储和修改字符串数据。这种方式的优点是:

避免拷贝:当你向 strings.Builder 添加内容时,它只是将新数据追加到内部的字节切片上,而不是创建整个字符串的新副本。这减少了内存的使用和拷贝操作,尤其是在构建大型字符串时。

07 什么是 rune 类型

ASCII 码只需要 7 bit 就可以完整地表示,但只能表示英文字母在内的128个字符,为了表示世界上大部分的文字系统,发明了 Unicode, 它是ASCII的超集,包含世界上书写系统中存在的所有字符,并为每个代码分配一个标准编号(称为Unicode CodePoint),在 Go 语言中称之为 rune,是 int32 类型的别名。

Go 语言中,字符串的底层表示是 byte (8 bit) 序列,而非 rune (32 bit) 序列。

sample := "我爱GO"
runeSamp := []rune(sample)
runeSamp[0] = '你'
fmt.Println(string(runeSamp))  // "你爱GO"
fmt.Println(len(runeSamp))  // 4 输出的是字节长度,而不是字符数
  • 1
  • 2
  • 3
  • 4
  • 5

比如,我们有一个字符串 "Go语言"。在 UTF-8 编码中(这是 Go 语言使用的编码格式),英文字符 “Go” 中的每个字母都只占一个字节,但是 “语” 和 “言” 这两个中文字符每个都占多个字节。

不使用 rune 的情况:

s := "Go语言"
for i := 0; i < len(s); i++ {
   
    fmt.Println(s[i])
}
  • 1
  • 2
  • 3
  • 4
  • 5

这段代码将按字节遍历字符串,因此对于 “语” 和 “言” 这样的多字节字符,它会将其拆分成单独的字节,并且打印出看起来无意义的数字(字节的值)。
具体输出将是:

‘G’ 的字节值
‘o’ 的字节值
“语” 的第一个字节的值
“语” 的第二个字节的值
“语” 的第三个字节的值
“言” 的第一个字节的值
“言” 的第二个字节的值
“言” 的第三个字节的值

使用 rune 的情况:

s := "Go语言"
for _, r := range s {
   
    fmt.Println(string(r))
}
  • 1
  • 2
  • 3
  • 4
  • 5

具体输出将是:

‘G’
‘o’
‘语’
‘言’

在这段代码中,我们使用了 range 循环来遍历字符串,它会自动处理字符串中的 rune。这样,即使是像 “语” 或 “言” 这样的多字节字符,也会被正确识别并作为一个整体处理。因此,每次迭代都会打印出一个完整的字符,无论它是由一个字节还是多个字节组成。

通过使用 rune,你可以确保无论字符占用多少字节,都能正确地处理每个字符,这对于编写能够处理各种语言的国际化软件来说是非常重要的。

08 如何判断 map 中是否包含某个 key ?

package main
import "fmt"
func main() {
   
    // 创建一个 map
    myMap := make(map[string]int)

    // 向 map 中添加键值对
    myMap["apple"] = 1
    myMap["orange"] = 2

    // 检查 key 是否存在
    value, exists := myMap["apple"]

    if exists {
   
        fmt.Println("The key exists with value:", value)
    } else {
   
        fmt.Println("The key does not exist.")
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

09 Go 支持默认参数或可选参数吗?

不支持
不过,有几种方法可以模拟这种行为:
1、使用变长参数

package main

import "fmt"

func printMessage(message string, optionalParts ...string) {
   
    fullMessage := message
    for _, part := range optionalParts {
   
        fullMessage += " " + part
    }

    // 如果没有额外的参数传入,optionalParts 将是一个空切片
    if len(optionalParts) == 0 {
   
        // 处理没有可选参数的情况,比如设定一个默认值
        fullMessage += " default part"
    }

    fmt.Println(fullMessage)
}

func main() {
   
    printMessage("hello")                   // 使用默认值
    printMessage("hello", "optional part")  // 不使用默认值
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

2、使用函数重载的方式(通过多个函数): Go 不支持传统的函数重载,但你可以通过创建多个函数来模拟这一点,每个函数有不同数量的参数。

package main
import "fmt"

// 没有额外参数的版本,提供默认值
func printWithDefault() {
   
    fmt.Println("Printing with default value")
}

// 接受一个参数的版本
func printWithOneArgument(arg1 string) {
   
    fmt.Println("Printing with argument:", arg1)
}

func main() {
   
    printWithDefault()
    printWithOneArgument("GoLand")
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

3、使用结构体和函数方法: 创建一个结构体来保存参数,并为该结构体创建方法。你可以在创建结构体实例时设置默认值。

package main

import "fmt"

type Params struct {
   
    Arg1 string
    Arg2 int
}

func NewParams() *Params {
   
    // 设置默认值
    return &Params{
   
        Arg1: "default string",
        Arg2: 42,
    }
}

func (p *Params) DoSomething() {
   
    fmt.Printf("Doing something with Arg1: %s and Arg2: %d\n", p.Arg1, p.Arg2)
}

func main() {
   
    params := NewParams()
    params.DoSomething()

    // 修改参数
    params.Arg1 = "another string"
    params.DoSomething()
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

10 defer 的执行顺序–后进先出

在 Go 语言中,defer 语句用于安排一个函数调用在当前函数执行结束后进行,无论当前函数是因为遇到 return 语句结束、还是到达函数体末尾结束、抑或是因为 panic 而结束。这在处理资源清理、文件关闭、解锁以及一些其他“清理”工作时特别有用。

package main
import (
    "fmt"
    "os"
)

func main() {
   
    // 尝试打开一个文件
    file, err := os.Open("example.txt")
    
    // 如果打开文件时出现错误,返回错误
    if err != nil {
   
        fmt.Println("Error opening file: ", err)
        return
    }
    
    // 使用 defer 来确保文件会被关闭
    // 这个语句意味着 "在这个函数的最后,执行 'file.Close()' "
    defer file.Close()

    // 我们可以读取文件并执行其他操作
    data := make([]byte, 100)
    _, err = file.Read(data)
    if err != nil {
   
        // 如果在读取文件时遇到错误,我们依然可以确保文件会被关闭,因为我们已经使用了 defer。
        fmt.Println("Error reading file: ", err)
        return
    }

    fmt.Println("File data:", string(data))
    // 当 main 函数结束时,defer 语句会自动触发并关闭文件。
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

关于 defer 的执行顺序,重要的一点是,如果一个函数内部有多个 defer 调用,它们会以 LIFO(后进先出)的顺序执行(类似栈,最后一个 defer 语句最先执行)。下面是一个具体的例子来解释这个概念:

package main

import "fmt"

func main() {
   
    fmt.Println("start")

    defer fmt.Println("deferred 1")
    defer fmt.Println("deferred 2")
    defer fmt.Println("deferred 3")

    fmt.Println("end")
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

程序的输出将会是

start
end
deferred 3
deferred 2
deferred 1
  • 1
  • 2
  • 3
  • 4
  • 5

12 Go 语言 tag 的用处?

1 数据序列化和反序列化

序列化指的是将复杂的数据结构(如对象、数据列表等)转换成一种标准格式(如JSON、XML等),这样就可以方便地在不同的系统之间传输或存储

package main
import (
	"encoding/json"
	"fmt"
)

// 定义一个结构体
type Person struct {
   
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`
	Age       int    `json:"age"`
}

func main() {
   
	// 创建一个 Person 结构体实例
	person := Person{
   
		FirstName: "John",
		LastName:  "Doe",
		Age:       30,
	}

	// 将 Person 结构体序列化为 JSON 格式
	jsonData, err := json.Marshal(person)
	if err != nil {
   
		fmt.Println("JSON serialization error:", err)
		return
	}

	// 打印 JSON 数据
	fmt.Println("JSON Data:", string(jsonData))

	// 将 JSON 数据反序列化为 Person 结构体
	var newPerson Person
	err = json.Unmarshal(jsonData, &newPerson)
	if err != nil {
   
		fmt.Println("JSON deserialization error:", err)
		return
	}

	// 打印反序列化后的 Person 结构体
	fmt.Println("Deserialized Person:", newPerson)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

输出

JSON Data: {
   "first_name":"John","last_name":"Doe","age":30}
Deserialized Person: {
   John Doe 30}
  • 1
  • 2
  • 3
  • 4

2 验证和表单处理

type User struct {
   
    Username string `json:"username" validate:"required,min=4"`
    Password string `json:"password" validate:"required,min=8"`
}
  • 1
  • 2
  • 3
  • 4
  • 5

在上面的示例中,标签 validate 用于指定验证规则,例如要求 Username 和 Password 字段不能为空,且密码长度至少为 8 个字符。

3 ORM(对象关系映射)

type Product struct {
   
    ID       uint   `gorm:"primary_key"`
    Name     string `gorm:"size:255"`
    Price    float64
    Category string
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在上面的示例中,GORM 标签用于定义 Product 结构体字段与数据库表列之间的映射关系。

13 如何获取一个结构体的所有tag?

使用Go语言的反射(reflection)机制

package main
import (
	"fmt"
	"reflect"
)

type Person struct {
   
	Name     string `json:"name"`
	Age      int    `json:"age"`
	Location string `json:"location"`
}

func main() {
   
	p := Person{
   
		Name:     "John",
		Age:      30,
		Location
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/445455
推荐阅读
相关标签
  

闽ICP备14008679号