Skip to content

Go 语言数据类型

在 Go 编程语言中,数据类型用于声明函数和变量。

数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存。

Go 语言按类别有以下几种数据类型:

序号类型和描述
1布尔型 <br>布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。
2数字类型 <br>整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。
3字符串类型: <br>字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。
4派生类型: <br>包括:<br><br>- (a) 指针类型(Pointer)<br>- (b) 数组类型<br>- (c) 结构化类型(struct)<br>- (d) Channel 类型<br>- (e) 函数类型<br>- (f) 切片类型<br>- (g) 接口类型(interface)<br>- (h) Map 类型<br>- (g) ebed.FS 类型

数字类型

Go 也有基于架构的类型,例如:int、uint 和 uintptr。

序号类型和描述
1uint8 <br>无符号 8 位整型 (0 到 255)
2uint16 <br>无符号 16 位整型 (0 到 65535)
3uint32 <br>无符号 32 位整型 (0 到 4294967295)
4uint64 <br>无符号 64 位整型 (0 到 18446744073709551615)
5int8 <br>有符号 8 位整型 (-128 到 127)
6int16 <br>有符号 16 位整型 (-32768 到 32767)
7int32 <br>有符号 32 位整型 (-2147483648 到 2147483647)
8int64 <br>有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)

浮点型

序号类型和描述
1float32 <br>IEEE-754 32位浮点型数
2float64 <br>IEEE-754 64位浮点型数
3complex64 <br>32 位实数和虚数
4complex128 <br>64 位实数和虚数

其他数字类型

以下列出了其他更多的数字类型:

序号类型和描述
1byte <br>类似 uint8
2rune <br>类似 int32
3uint <br>32 或 64 位
4int <br>与 uint 一样大小
5uintptr <br>无符号整型,用于存放一个指针

派生类型

interface

在 Go 语言中,接口(interface) 是一种抽象类型,它定义了方法的集合。接口可以用来表示一组行为,而不关心具体实现。接口类型是 Go 语言实现多态的核心概念之一。

1. 接口类型的定义

在 Go 中,接口类型通过 typeinterface 关键字定义。接口定义了一组方法,而不包含任何方法的实现。

go
type Animal interface {
	Speak() string
}

在这个例子中,我们定义了一个 Animal 接口,它包含一个 Speak 方法。任何实现了 Speak 方法的类型都被认为是 Animal 类型。

2. 接口的实现

在 Go 语言中,一个类型不需要显式地声明自己实现了某个接口,只要该类型的方法集满足接口的方法集要求,那么该类型就自动实现了该接口。这种方式称为 结构性类型系统

go
type Dog struct{}

func (d Dog) Speak() string {
	return "Woof!"
}

type Cat struct{}

func (c Cat) Speak() string {
	return "Meow!"
}

在这个例子中,DogCat 类型都实现了 Speak 方法,因此它们都可以被看作是 Animal 类型。

3. 使用接口类型

接口类型可以用作函数参数、返回值、结构体字段等。这使得我们可以编写通用的代码,不关心具体的类型,只关注它们实现的行为(方法)。

go
func MakeAnimalSpeak(a Animal) {
	fmt.Println(a.Speak())
}

func main() {
	dog := Dog{}
	cat := Cat{}

	MakeAnimalSpeak(dog) // 输出: Woof!
	MakeAnimalSpeak(cat) // 输出: Meow!
}

在这个例子中,MakeAnimalSpeak 函数接受一个 Animal 类型的参数,可以传入任何实现了 Animal 接口的类型(如 DogCat)。

4. 空接口类型 interface{}

在 Go 语言中,interface{} 是一种特殊的接口类型,它表示“任意类型”。因为空接口没有定义任何方法,所以所有类型都满足空接口。空接口常用于表示任意类型的值,在 Go 中可以类似于其他语言的 Object 类型。

go
func PrintValue(v interface{}) {
	fmt.Println(v)
}

func main() {
	PrintValue(42)          // int
	PrintValue("Hello")     // string
	PrintValue(3.14)        // float64
	PrintValue([]int&#123;1, 2&#125;) // slice
}

在这个例子中,PrintValue 函数接受 interface{} 类型的参数,因此它可以接收任何类型的值。

5. 类型断言(Type Assertion)

当使用空接口(或任何接口)时,我们可能需要将接口类型的值还原为具体的类型。这可以通过 类型断言 实现。

go
value, ok := interfaceValue.(ConcreteType)
  • value:转换后的值。
  • ok:布尔值,表示转换是否成功。如果 okfalse,则转换失败。

类型断言

go
func Describe(i interface{}) {
	switch v := i.(type) {
	case int:
		fmt.Printf("This is an int: %d\n", v)
	case string:
		fmt.Printf("This is a string: %s\n", v)
	default:
		fmt.Printf("Unknown type\n")
	}
}

func main() {
	Describe(42)
	Describe("Hello")
}

在这个例子中,Describe 函数通过类型断言检查传入的空接口参数 i 的具体类型,并根据不同的类型执行不同的操作。

6. 类型断言 vs 类型转换

类型断言用于将接口类型还原为具体的类型,前提是接口实际保存的值符合该具体类型。类型转换则是用于兼容类型的转换,如 intfloat64

7. 接口的零值

接口的零值是 nil。一个接口值在未被赋值时默认为 nil

go
var a Animal
fmt.Println(a == nil) // 输出: true

如果一个接口类型的变量没有保存任何具体值或类型信息,则它的值为 nil

8. 接口组合(嵌套接口)

接口可以组合(嵌套)其他接口,使得一个接口可以包含多个方法集。

go
type Walker interface {
	Walk()
}

type Flyer interface {
	Fly()
}

type Bird interface {
	Walker
	Flyer
}

在这个例子中,Bird 接口组合了 WalkerFlyer 接口,因此任何实现了 WalkFly 方法的类型都满足 Bird 接口。

9. 接口和结构体组合

接口和结构体组合是面向接口编程的常见模式。我们可以将接口作为结构体的字段,来实现依赖注入和解耦合。

go
type Walker interface {
	Walk()
}

type Flyer interface {
	Fly()
}

type Bird interface {
	Walker
	Flyer
}

在这个例子中,Person 结构体依赖于 Animal 接口,而不是具体的 DogCat 类型,这样可以方便地替换为其他 Animal 实现。

10. 接口的动态性

在 Go 中,接口的实际类型和值可以在运行时动态变化,因此接口的底层实现类型可以在程序运行过程中被替换。

go
var a Animal
a = Dog{}
fmt.Println(a.Speak()) // 输出: Woof!
a = Cat{}
fmt.Println(a.Speak()) // 输出: Meow!

embed.FS

embed.FS 是 Go 语言在 1.16 版本引入的 embed 包中的一种数据类型,用于将多个文件或整个目录嵌入到 Go 程序的二进制文件中。通过 embed.FS,可以将文件作为虚拟文件系统(Filesystem)的一部分访问,使得在部署时可以将静态资源(如配置文件、HTML 模板、图片等)直接包含在二进制文件中,避免对外部文件的依赖。

embed.FS 的使用

要使用 embed.FS,需要以下几个步骤:

  1. 导入 embedembed 是 Go 标准库的一部分,因此无需安装,只需 import "embed" 即可。
  2. 声明 embed.FS 类型的变量:在变量声明上方使用 //go:embed 指令,指定要嵌入的文件或目录。
  3. 访问嵌入的文件:可以使用 embed.FS 提供的方法,如 ReadFileOpenReadDir 等来读取文件内容。

代码示例

假设目录结构如下:

myapp/
├── main.go
└── templates/
    ├── index.html
    └── about.html
示例代码

main.go 中嵌入 templates 目录下的所有文件,并读取其中的内容:

go
package main

import (
	"embed"
	"fmt"
	"io/fs"
)

//go:embed templates/*
var templateFiles embed.FS

func main() {
	// 读取单个文件
	content, err := templateFiles.ReadFile("templates/index.html")
	if err != nil {
		panic(err)
	}
	fmt.Println("Content of index.html:")
	fmt.Println(string(content))

	// 遍历目录中的所有文件
	fmt.Println("All files in the templates directory:")
	fs.WalkDir(templateFiles, "templates", func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		fmt.Println(" -", path)
		return nil
	})
}

在这里:

  • //go:embed templates/*:这行指令嵌入 templates 目录下的所有文件。
  • var templateFiles embed.FS:声明一个 embed.FS 类型的变量,存储 templates 目录中的文件。
  • ReadFile:读取 index.html 文件的内容。
  • WalkDir:遍历 templates 目录中所有嵌入的文件和子目录。

主要功能和方法

embed.FS 实现了 fs.FS 接口,因此可以使用 fs.FS 提供的各种文件系统操作方法来访问嵌入的文件。

1. ReadFile 方法
  • 功能:读取文件内容,返回字节切片([]byte)。
  • 参数:文件路径(相对路径)。
  • 返回:文件内容和错误(如果文件不存在或无法读取)。

示例:

go
content, err := templateFiles.ReadFile("templates/index.html")
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(content))
2. Open 方法
  • 功能:打开文件,返回一个 fs.File 接口,可以用来读取文件内容。
  • 参数:文件路径。
  • 返回fs.File 接口实例,用于进一步读取文件内容。

示例:

go
file, err := templateFiles.Open("templates/about.html")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

// 使用 io.ReadAll 读取文件内容
content, _ := io.ReadAll(file)
fmt.Println(string(content))
3. ReadDir 方法
  • 功能:读取目录中的文件和子目录列表。
  • 参数:目录路径。
  • 返回[]fs.DirEntry 列表,每个 DirEntry 表示一个文件或子目录。

示例:

go
entries, err := templateFiles.ReadDir("templates")
if err != nil {
    log.Fatal(err)
}

for _, entry := range entries {
    fmt.Println(entry.Name(), entry.IsDir())
}

4. WalkDir 函数

WalkDirio/fs 包中的函数,它可以遍历 embed.FS 文件系统中的所有文件和子目录。

  • 功能:遍历指定目录及其子目录中的所有文件。
  • 参数:虚拟文件系统(如 embed.FS)、目录路径和处理函数。
  • 返回:返回错误(如果有)。

示例:

go
fs.WalkDir(templateFiles, "templates", func(path string, d fs.DirEntry, err error) error {
    if err != nil {
        return err
    }
    fmt.Println("Found file:", path)
    return nil
})

embed.FS 的用法示例

以下是几个常见的使用场景:

1. 嵌入 HTML 模板

使用 embed.FS 嵌入 HTML 模板并通过 html/template 渲染:

go
package main

import (
	"embed"
	"html/template"
	"net/http"
)

//go:embed templates/*
var templates embed.FS

func main() {
	tmpl := template.Must(template.ParseFS(templates, "templates/*.html"))

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		tmpl.ExecuteTemplate(w, "index.html", map[string]string{
			"Title": "Welcome",
			"Body":  "Hello, embedded templates!",
		})
	})

	http.ListenAndServe(":8080", nil)
}
在这个示例中,将 `templates` 目录下的 HTML 文件嵌入并使用 `html/template` 渲染。

2. 嵌入静态文件

可以将静态资源(如 CSS、JS、图片等)嵌入到 embed.FS 中并使用 http.FileServer 提供静态文件服务:

go
package main

import (
	"embed"
	"net/http"
)

//go:embed static/*
var staticFiles embed.FS

func main() {
	// 使用 http.FileServer 提供嵌入的静态文件服务
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticFiles))))

	http.ListenAndServe(":8080", nil)
}

在这个示例中,staticFiles 目录下的文件可以通过 URL http://localhost:8080/static/filename 访问。

3. 嵌入多种文件类型

embed.FS 支持通配符,可以一次性嵌入多个类型的文件(如 .html.css 等),然后根据文件类型和路径做不同的处理。

go
package main

import (
	"embed"
	"fmt"
	"io/fs"
)

//go:embed files/*.html files/*.css
var files embed.FS

值/引用 类型

所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值:

4.4.2_fig4.1

当使用等号 = 将一个变量的值赋值给另一个变量时,如:j = i,实际上是在内存中将 i 的值进行了拷贝:

4.4.2_fig4.2

你可以通过 &i 来获取变量 i 的内存地址,例如:0xf840000040(每次的地址都可能不一样)。

值类型变量的值存储在堆中。

内存地址会根据机器的不同而有所不同,甚至相同的程序在不同的机器上执行后也会有不同的内存地址。因为每台机器可能有不同的存储器布局,并且位置分配也可能不同。

更复杂的数据通常会需要使用多个字,这些数据一般使用引用类型保存。

一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。

4.4.2_fig4.3

这个内存地址称之为指针,这个指针实际上也被存在另外的某一个值中。

同一个引用类型的指针指向的多个字可以是在连续的内存地址中(内存布局是连续的),这也是计算效率最高的一种存储形式;也可以将这些字分散存放在内存中,每个字都指示了下一个字所在的内存地址。

当使用赋值语句 r2 = r1 时,只有引用(地址)被复制。

如果 r1 的值被改变了,那么这个值的所有引用都会指向被修改后的内容,在这个例子中,r2 也会受到影响。

1. 值类型

值类型在赋值或传递时,会创建原始数据的副本。修改副本不会影响原始数据。以下是 Go 中的值类型:

  • 基本类型intfloatboolstring
  • 结构体struct
  • 数组array

示例

go
package main

import "fmt"

func main() {
    a := 5
    b := a // b 是 a 的副本
    b = 10 // 修改 b,不会影响 a
    fmt.Println(a) // 输出 5
    fmt.Println(b) // 输出 10
}

在这个例子中,ba 的副本,修改 b 不会影响 a

值类型特征

  • 当赋值或传递时,会复制数据本身。
  • 函数传参时会复制参数值,函数内的修改不会影响外部变量。

2. 引用类型

引用类型在赋值或传递时,会复制值的引用(即地址)。修改引用指向的值会影响原始数据。以下是 Go 中的引用类型:

  • 切片slice
  • 映射map
  • 通道chan
  • 接口interface
  • 指针pointer

示例

go
package main

import "fmt"

func main() {
    a := []int&#123;1, 2, 3&#125;
    b := a        // b 是 a 的引用
    b[0] = 100    // 修改 b,会影响 a
    fmt.Println(a) // 输出 [100 2 3]
    fmt.Println(b) // 输出 [100 2 3]
}

在这个例子中,ba 的引用,修改 b 的内容也会反映在 a 上。

引用类型特征

  • 赋值或传递时复制的是引用(即地址),不会复制数据本身。
  • 函数传参时修改引用类型会影响外部变量的值。

3. 指针类型

指针也是一种引用类型,它存储另一个变量的地址。通过指针可以间接修改变量的值。

示例

go
package main

import "fmt"

func main() {
    a := 5
    b := &a // b 是指向 a 的指针
    *b = 10 // 通过指针修改 a 的值
    fmt.Println(a) // 输出 10
}

数据类中转换

在Go语言中,类型转换是一种将一种数据类型转换为另一种数据类型的操作。Go是一种强类型语言,这意味着不同类型之间不能自动转换,必须通过显式的类型转换操作来实现。这种特性可以提高代码的安全性,但也要求开发者对类型转换有更多的理解和掌控。

基本类型转换

Go语言使用类型名作为转换函数,将一个类型转换为另一个类型的语法如下:

go
Type(value)

其中 Type 是目标类型,value 是要转换的值。

示例:整型和浮点型转换

go
package main

import "fmt"

func main() {
    var intVal int = 42
    var floatVal float64 = float64(intVal) // 将 int 转换为 float64
    fmt.Println(floatVal)                  // 输出:42.0

    var floatVal2 float64 = 3.14
    var intVal2 int = int(floatVal2)       // 将 float64 转换为 int,精度会丢失
    fmt.Println(intVal2)                   // 输出:3
}

在这个例子中,intVal 转换为 float64,结果是 42.0。相反,当 float64 转换为 int 时,小数部分会被截断。

字符串和其他类型之间的转换

1. 整数与字符串之间的转换

Go语言提供了 strconv 包来处理字符串和数字之间的转换。

整数转字符串:Itoa
go
import "strconv"

var num int = 123
var str string = strconv.Itoa(num) // 将整数转为字符串
fmt.Println(str)                   // 输出:"123"
字符串转整数:Atoi
go
var str string = "456"
	var num, err = strconv.Atoi(str) // 将字符串转为整数
if err != nil {
    fmt.Println("转换出错:", err)
} else {
    fmt.Println(num) // 输出:456
}

2. 浮点数与字符串之间的转换

浮点数转字符串:FormatFloat
go
var floatVal float64 = 3.14159
var str = strconv.FormatFloat(floatVal, 'f', 2, 64) // 'f' 表示十进制,2 表示保留小数点后两位
fmt.Println(str)                                    // 输出:"3.14"
字符串转浮点数: ParseFloat
go
var str string = "2.718"
var floatVal, err = strconv.ParseFloat(str, 64) // 64 表示转换为 float64 类型
if err != nil {
    fmt.Println("转换出错:", err)
} else {
    fmt.Println(floatVal) // 输出:2.718
}

布尔值与字符串之间的转换

布尔值转字符串:FormatBool
go
var b bool = true
var str = strconv.FormatBool(b)
fmt.Println(str) // 输出:"true"
字符串转布尔值:ParseBool
go
var str string = "true"
var b, err = strconv.ParseBool(str)
if err != nil {
    fmt.Println("转换出错:", err)
} else {
    fmt.Println(b) // 输出:true
}

类型别名转换

在Go语言中,可以使用类型别名,即使两个类型是基于相同的基础类型,仍然需要显式转换。

go
type MyInt int
var a int = 10
var b MyInt = MyInt(a) // 需要显式转换
fmt.Println(b)         // 输出:10

接口类型之间的转换

Go语言的接口类型支持多种类型的实现,因此接口类型之间的转换需要通过类型断言来实现。

类型断言

类型断言用于从接口类型获取具体类型的值,语法为:

go
value, ok := interfaceValue.(ConcreteType)
  • value 是转换后的值,ok 是布尔值,表示转换是否成功。

类型断言失败

如果不使用 ok 检查而直接转换失败,程序会 panic

go
var s = i.(string) // 运行时将产生 panic,因为 i 不是 string

指针类型转换

在Go语言中,不同的指针类型之间不能直接转换,除非通过 unsafe 包的 unsafe.Pointer。然而,直接操作指针通常是不推荐的,因为可能导致不安全的内存访问。

评论区

欢迎留言、补充或勘误。

xiaoba.blog