go理论知识记录(入门2)
go理论知识记录(入门2)
来源于刘丹冰老师的课程
type关键字
1. 定义类型别名
使用 type
关键字可以为现有类型定义别名。
语法:
type 新类型名 原类型
示例:
type myInt int
myInt
是int
类型的别名,但它是一个全新的类型,不能直接与int
类型混用。
2. 使用自定义类型
定义别名后,可以像使用原类型一样使用新类型。
示例:
var a myInt = 10
fmt.Printf("%d\n", a) // 输出: 10
3. 定义结构体
结构体是一种复合数据类型,用于将多个字段组合在一起。
语法:
type 结构体名 struct {
字段1 类型1
字段2 类型2
...
}
示例:
type book struct {
title string
auth string
}
4. 使用结构体
定义结构体后,可以创建结构体变量并访问其字段。
示例:
var book1 book
book1.title = "Golang"
book1.auth = "zhangsan"
fmt.Printf("%v\n", book1) // 输出: {Golang zhangsan}
5. 完整示例代码
以下是完整的示例代码:
package main
import "fmt"
// 给 int 类型起别名
type myInt int
// 声明一个结构体类型
type book struct {
title string
auth string
}
func main() {
// 使用自定义类型
var a myInt = 10
fmt.Printf("%d\n", a) // 输出: 10
// 使用结构体
var book1 book
book1.title = "Golang"
book1.auth = "zhangsan"
fmt.Printf("%v\n", book1) // 输出: {Golang zhangsan}
}
6. 输出结果
运行上述代码后,输出如下:
10
{Golang zhangsan}
7. 注意事项
-
类型别名与原始类型:
- 类型别名是一个全新的类型,不能直接与原始类型混用。
- 如果需要转换,必须显式进行类型转换。
示例:
var a myInt = 10 var b int = int(a) // 显式类型转换
-
结构体字段访问:
- 结构体的字段通过
.
操作符访问。 - 如果结构体字段未初始化,会使用其类型的零值。
- 结构体的字段通过
-
值接收者 vs 指针接收者:
- 值接收者(
func (this Hero)
)会创建对象的副本,修改不会影响原始对象。 - 指针接收者(
func (this *Hero)
)直接操作原始对象,修改会生效。
- 值接收者(
结构体的注意点
1. 公有与私有:
- Go 语言通过首字母大小写控制访问权限。
- 大写字母开头的字段或方法可以在包外访问。
- 小写字母开头的字段或方法只能在包内访问。
2. 指针接收者的使用:
- 如果需要在方法中修改结构体的字段,必须使用指针接收者。
- 否则,修改只会作用于方法的副本,不会影响原始对象。
3. 方法命名规范:
- 方法名应清晰表达其功能。
- 遵循 Go 语言的命名规范,使用驼峰式命名法。
好的!以下是关于 Go语言中的继承 的学习笔记片段,重点围绕你提供的代码,解释 Go 语言如何通过 结构体嵌入 实现类似继承的功能。
继承
Go 语言本身并没有继承,但通过 结构体嵌入 和 方法提升,可以实现类似继承的效果。
1. 结构体嵌入
在代码中,Hero
结构体嵌入了 Human
结构体:
type Hero struct {
Human // 嵌入 Human 结构体
level int
}
- 嵌入 意味着
Hero
结构体继承了Human
的所有字段和方法。 Hero
可以直接访问Human
的字段和方法,就像访问自己的字段和方法一样。
2. 方法提升
嵌入的结构体的方法会被 自动提升 到外层结构体中。例如:
func (this *Human) Eat() {
fmt.Println("human eat……")
}
- 由于
Human
被嵌入到Hero
中,Hero
实例可以直接调用Eat
方法:
h := Hero{Human{"lucifer", "male"}, 80}
h.Eat() // 输出:human eat……
3. 方法重写
Go 语言允许外层结构体 重写 嵌入结构体的方法。例如:
func (this *Hero) Eat() {
fmt.Println("hero eat……")
}
- 当
Hero
重写了Eat
方法后,调用h.Eat()
时会优先调用Hero
的Eat
方法,而不是Human
的Eat
方法:
h.Eat() // 输出:hero eat……
4. 访问嵌入结构体的方法
即使外层结构体重写了方法,仍然可以通过 显式指定嵌入结构体 来调用原始方法。例如:
h.Human.Eat() // 输出:human eat……
5. 初始化嵌入结构体
在初始化时,可以直接为嵌入结构体赋值。例如:
h := Hero{Human{"lucifer", "male"}, 80}
- 这里为
Hero
的Human
部分初始化了name
和sex
字段。
6. 代码示例分析
func main() {
h := Hero{Human{"lucifer", "male"}, 80} // 初始化 Hero
h.doi() // 调用 Human 的 doi 方法
h.Eat() // 调用 Hero 重写的 Eat 方法
h.Fly() // 调用 Hero 重写的 Fly 方法
}
h.doi()
:调用的是Human
的doi
方法,因为Hero
没有重写它。h.Eat()
和h.Fly()
:调用的是Hero
重写后的方法。
注意点
- Go 语言通过 结构体嵌入 实现类似继承的功能。
- 嵌入结构体的字段和方法会被 自动提升 到外层结构体。
- 外层结构体可以 重写 嵌入结构体的方法,实现多态。
- 通过显式指定嵌入结构体,可以调用原始方法。
以下是关于 Go 语言多态 的学习笔记片段,基于你提供的代码,详细解释了多态的实现原理和代码分析。
Go 语言中的多态
多态是面向对象编程的重要特性之一,它允许 父类指针指向子类实例,并通过统一的接口调用不同子类的方法。Go 语言通过 接口 实现了多态。
1. 多态的实现条件
在你的代码中,多态的实现需要满足以下条件:
- 定义接口:
AnimalIF
接口定义了Sleep
、GetColor
和GetType
方法。 - 实现接口:
Cat
和Dog
结构体实现了AnimalIF
接口的所有方法。 - 父类指针指向子类实例:在
main
函数中,AnimalIF
类型的变量(父类指针)分别指向了Cat
和Dog
的实例。
2. 代码分析
定义接口
type AnimalIF interface {
Sleep()
GetColor() string // 获取动物颜色
GetType() string // 获取动物种类
}
AnimalIF
接口定义了三个方法:Sleep
、GetColor
和GetType
。- 任何实现了这三个方法的类型都可以被认为是
AnimalIF
接口的实现。
实现接口
Cat
和 Dog
结构体分别实现了 AnimalIF
接口的所有方法。
Cat
结构体
type Cat struct {
color string
}
func (this *Cat) Sleep() {
fmt.Println("cat is sleeping")
}
func (this *Cat) GetColor() string {
return this.color
}
func (this *Cat) GetType() string {
return "Cat v1.0"
}
Cat
实现了Sleep
、GetColor
和GetType
方法。
Dog
结构体
type Dog struct {
color string
}
func (this *Dog) Sleep() {
fmt.Println("dog is sleeping")
}
func (this *Dog) GetColor() string {
return this.color
}
func (this *Dog) GetType() string {
return "Dog v1.0"
}
Dog
也实现了Sleep
、GetColor
和GetType
方法。
使用多态
在 main
函数中,通过 AnimalIF
接口类型的变量调用不同子类的方法:
func main() {
var animal AnimalIF // 接口数据类型,父类的指针
// 父指针指向 Cat 实例
animal = &Cat{"green"}
animal.Sleep() // 调用 Cat 的 Sleep 方法
fmt.Println("Cat color:", animal.GetColor())
fmt.Println("Cat type:", animal.GetType())
// 父指针指向 Dog 实例
animal = &Dog{"yellow"}
animal.Sleep() // 调用 Dog 的 Sleep 方法
fmt.Println("Dog color:", animal.GetColor())
fmt.Println("Dog type:", animal.GetType())
}
animal = &Cat{"green"}
:AnimalIF
类型的变量animal
指向Cat
实例。animal = &Dog{"yellow"}
:AnimalIF
类型的变量animal
指向Dog
实例。- 通过
animal.Sleep()
调用方法时,实际调用的是Cat
或Dog
的Sleep
方法,具体取决于animal
指向的实例。
3. 运行结果
运行代码后,输出如下:
cat is sleeping
Cat color: green
Cat type: Cat v1.0
dog is sleeping
Dog color: yellow
Dog type: Dog v1.0
注意点
- Go 语言通过 接口 实现多态。
- 多态的条件:
- 定义接口。
- 子类实现接口的所有方法。
- 父类指针指向子类实例。
- 多态提高了代码的复用性、扩展性和灵活性。
以下是关于 Go 语言中的 interface{}
的学习笔记片段,基于你提供的代码,详细解释了 interface{}
的使用方法和相关特性。
interface{}
interface{}
是 Go 语言中的 空接口,它可以存储任意类型的值。因为所有类型都至少实现了零个方法,所以所有类型都满足空接口。
1. 特点
- 万能数据类型:
interface{}
可以存储任何类型的值,包括基本类型(如int
、string
)和复杂类型(如结构体、切片、映射等)。 - 类型断言:通过 类型断言,可以从
interface{}
中提取具体的类型和值。
2. 代码分析
定义函数接受 interface{}
参数
func myFunc(arg interface{}) {
fmt.Println("myFunc is called")
}
myFunc
函数的参数类型是interface{}
,因此它可以接受任意类型的参数。
类型断言
在 myFunc
函数中,使用 类型断言 判断 arg
的具体类型:
value, ok := arg.(string)
if !ok {
fmt.Println("arg is not string type")
} else {
fmt.Println("arg is string type, value is", value)
}
arg.(string)
:尝试将arg
断言为string
类型。value
:如果断言成功,value
是arg
的具体值。ok
:如果断言成功,ok
为true
;否则为false
。
调用 myFunc
函数
在 main
函数中,分别传入不同类型的参数:
func main() {
book := Book{"golang"}
myFunc(book) // 传入结构体
myFunc(100) // 传入整数
myFunc("200") // 传入字符串
}
myFunc(book)
:传入Book
结构体实例。myFunc(100)
:传入整数100
。myFunc("200")
:传入字符串"200"
。
3. 运行结果
运行代码后,输出如下:
myFunc is called
arg is not string type
myFunc is called
arg is not string type
myFunc is called
arg is string type, value is 200
4. 类型断言的详细说明
类型断言的语法:
value, ok := arg.(Type)
arg
:interface{}
类型的变量。Type
:目标类型(如string
、int
等)。value
:如果断言成功,value
是arg
的具体值;否则为Type
的零值。ok
:如果断言成功,ok
为true
;否则为false
。
5. 使用 switch
进行类型判断
除了 if-else
,还可以使用 switch
语句对 interface{}
进行类型判断:
func myFunc(arg interface{}) {
fmt.Println("myFunc is called")
switch v := arg.(type) {
case string:
fmt.Println("arg is string, value is", v)
case int:
fmt.Println("arg is int, value is", v)
case Book:
fmt.Println("arg is Book, auth is", v.auth)
default:
fmt.Println("arg is unknown type")
}
}
arg.(type)
:只能在switch
语句中使用,用于判断arg
的具体类型。v
:arg
的具体值。
5. 注意点
interface{}
是 Go 语言中的万能数据类型,可以存储任意类型的值。- 通过 类型断言 或
switch
类型判断,可以从interface{}
中提取具体的类型和值。 - 空接口在需要处理多种数据类型的场景中非常有用,例如 JSON 解析、泛型编程等。
好的!以下是关于 Go 语言中的 pair
概念 的学习笔记片段,基于你提供的代码,详细解释了 pair
的作用和实现原理。
pair
在 Go 语言中,pair
是一个常见的概念,用于描述变量或接口的内部结构。具体来说,pair
由两部分组成:
- 类型信息(Type):表示变量的静态类型。
- 值信息(Value):表示变量的实际值。
这种 pair
的概念在 Go 语言的接口实现中尤为重要,因为接口变量实际上存储了一个 pair
,其中包含 动态类型 和 动态值。
1. 普通变量的 pair
对于普通变量,pair
的结构是固定的:
- 类型信息:变量的静态类型。
- 值信息:变量的实际值。
示例代码
var a string
a = "abcde" // pair<static type: string, value: "abcde">
- 变量
a
的pair
是<string, "abcde">
。
2. 接口变量的 pair
对于接口变量,pair
的结构是动态的:
- 类型信息:接口的动态类型(即实际存储的值的类型)。
- 值信息:接口的动态值(即实际存储的值)。
示例代码
var allType interface{}
allType = a // pair<dynamic type: string, value: "abcde">
- 变量
allType
的pair
是<string, "abcde">
。 - 通过类型断言,可以从接口中提取出具体的类型和值:
str, _ := allType.(string) fmt.Println(str) // 输出:abcde
3. 接口的类型断言
类型断言用于从接口中提取具体的类型和值。它的语法是:
value, ok := interfaceVar.(Type)
interfaceVar
:接口变量。Type
:目标类型。value
:如果断言成功,value
是接口的动态值。ok
:如果断言成功,ok
为true
;否则为false
。
示例代码
str, ok := allType.(string)
if ok {
fmt.Println("动态类型是 string,值是", str)
} else {
fmt.Println("动态类型不是 string")
}
4. 接口的 pair
在类型转换中的应用
在 Go 语言中,接口的 pair
可以用于实现类型转换。例如,将 io.Reader
接口转换为 io.Writer
接口。
示例代码
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
fmt.Println("open file error", err)
return
}
var r io.Reader
r = tty // pair<dynamic type: *os.File, value: "/dev/tty">
var w io.Writer
w = r.(io.Writer) // pair<dynamic type: *os.File, value: "/dev/tty">
w.Write([]byte("hello test!\n"))
r
的pair
是<*os.File, "/dev/tty">
。- 通过类型断言,将
r
转换为io.Writer
接口,w
的pair
仍然是<*os.File, "/dev/tty">
。 - 调用
w.Write
方法时,实际调用的是*os.File
的Write
方法。
5. 注意点
pair
是 Go 语言中描述变量或接口内部结构的概念,包含类型信息和值信息。- 对于普通变量,
pair
是静态的;对于接口变量,pair
是动态的。 - 通过 类型断言,可以从接口中提取具体的类型和值。
- 接口的
pair
在类型转换和方法调用中起着关键作用。
完整代码示例
以下是结合 pair
概念的完整代码示例:
示例 1:普通变量和接口的 pair
package main
import "fmt"
func main() {
var a string
a = "abcde" // pair<static type: string, value: "abcde">
var allType interface{}
allType = a // pair<dynamic type: string, value: "abcde">
str, _ := allType.(string)
fmt.Println(str) // 输出:abcde
}
示例 2:接口的类型转换
package main
import (
"fmt"
"io"
"os"
)
func main() {
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
fmt.Println("open file error", err)
return
}
var r io.Reader
r = tty // pair<dynamic type: *os.File, value: "/dev/tty">
var w io.Writer
w = r.(io.Writer) // pair<dynamic type: *os.File, value: "/dev/tty">
w.Write([]byte("hello test!\n"))
}
以下是关于 Go 语言中的 reflect
包 的学习笔记片段,基于你提供的代码,详细解释了反射的使用方法和相关特性。
反射(reflect
包)
反射是 Go 语言中一种强大的机制,允许程序在运行时动态地获取变量的类型信息和值信息,并对其进行操作。reflect
包提供了实现反射的核心功能。
1. 反射的基本概念
- 类型信息(Type):表示变量的静态类型。
- 值信息(Value):表示变量的实际值。
- 反射的核心函数:
reflect.TypeOf(arg)
:获取arg
的类型信息。reflect.ValueOf(arg)
:获取arg
的值信息。
2. 反射的基本用法
示例代码
func reflectNum(arg interface{}) {
fmt.Println("type:", reflect.TypeOf(arg))
fmt.Println("value:", reflect.ValueOf(arg))
}
reflect.TypeOf(arg)
:返回arg
的类型信息。reflect.ValueOf(arg)
:返回arg
的值信息。
3. 反射操作结构体
定义结构体
type User struct {
Id int
Name string
Age int
}
func (this *User) Call() {
fmt.Println(this.Name)
}
User
结构体包含三个字段:Id
、Name
和Age
。Call
方法是指针接收者方法。
反射获取结构体字段和方法
func DoFiledAndMethod(input interface{}) {
// 获取 type
InputType := reflect.TypeOf(input)
fmt.Println("InputType is : ", InputType)
// 获取 value
InputValue := reflect.ValueOf(input)
fmt.Println("InputValue is : ", InputValue)
// 如果 input 是指针类型,获取其指向的实际类型
if InputType.Kind() == reflect.Ptr {
InputType = InputType.Elem() // 获取指针指向的实际类型
InputValue = InputValue.Elem() // 获取指针指向的实际值
}
// 通过 type 获取里面的字段
for i := 0; i < InputType.NumField(); i++ {
field := InputType.Field(i)
value := InputValue.Field(i).Interface()
fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
}
fmt.Println("=======================")
// 通过 type 获取里面的方法
for i := 0; i < InputType.NumMethod(); i++ {
m := InputType.Method(i)
fmt.Printf("%s: %v\n", m.Name, m.Type)
}
}
InputType.Elem()
:获取指针指向的实际类型。InputValue.Elem()
:获取指针指向的实际值。InputType.NumField()
:获取结构体的字段数量。InputType.Field(i)
:获取第i
个字段的信息。InputValue.Field(i).Interface()
:获取第i
个字段的值。InputType.NumMethod()
:获取结构体的方法数量。InputType.Method(i)
:获取第i
个方法的信息。
4. 反射的注意事项
-
指针类型的处理:
- 如果
input
是指针类型,需要使用InputType.Elem()
获取指针指向的实际类型。 - 否则,
NumField
和Field
方法会报错。
- 如果
-
方法获取的限制:
- 如果方法是指针接收者(例如
func (this *User) Call()
),则必须传递指针给reflect
包才能获取到方法。 - 如果方法是值接收者(例如
func (this User) Call()
),则可以传递值或指针。
- 如果方法是指针接收者(例如
-
性能开销:
- 反射操作比直接操作变量更慢,因此在性能敏感的场景中应谨慎使用。
结构体标签与 JSON 转换
在 Go 语言中,结构体标签是一种元数据,用于为结构体字段附加额外的信息。最常见的用途是 控制 JSON 编码和解码的行为。
1. 结构体标签的基本语法
结构体标签是写在结构体字段后面的字符串,用反引号(`
)包裹。标签的格式通常是 key:"value"
,多个键值对之间用空格分隔。
示例
type Movie struct {
Title string `json:"title"`
Year int `json:"year"`
Price int `json:"rmb"`
Actors []string `json:"actors"`
}
json:"title"
:表示在 JSON 编码和解码时,Title
字段对应的 JSON 键名为"title"
。json:"year"
:表示在 JSON 编码和解码时,Year
字段对应的 JSON 键名为"year"
。json:"rmb"
:表示在 JSON 编码和解码时,Price
字段对应的 JSON 键名为"rmb"
。json:"actors"
:表示在 JSON 编码和解码时,Actors
字段对应的 JSON 键名为"actors"
。
2. JSON 编码(结构体 → JSON)
使用 encoding/json
包中的 json.Marshal
函数,可以将结构体编码为 JSON 字符串。
示例代码
movie := Movie{"喜剧之王", 2000, 10, []string{"xingye", "lucifer"}}
jsonStr, err := json.Marshal(movie)
if err != nil {
fmt.Println("json marshal error", err)
return
}
fmt.Printf("jsonStr = %s\n", jsonStr)
输出结果
jsonStr = {"title":"喜剧之王","year":2000,"rmb":10,"actors":["xingye","lucifer"]}
关键点
json.Marshal
会根据结构体标签将字段名映射为 JSON 键名。- 如果字段没有标签,默认使用字段名作为 JSON 键名。
3. JSON 解码(JSON → 结构体)
使用 encoding/json
包中的 json.Unmarshal
函数,可以将 JSON 字符串解码为结构体。
示例代码
myMovie := Movie{}
err = json.Unmarshal(jsonStr, &myMovie)
if err != nil {
fmt.Println("json unmarshal error", err)
}
fmt.Printf("%v\n", myMovie)
输出结果
{喜剧之王 2000 10 [xingye lucifer]}
关键点
json.Unmarshal
会根据结构体标签将 JSON 键名映射为结构体字段名。- 如果 JSON 键名与结构体标签不匹配,该字段会被忽略。
4. 结构体标签的高级用法
忽略字段
如果希望某个字段在 JSON 编码时被忽略,可以使用 json:"-"
标签。
示例
type Movie struct {
Title string `json:"title"`
Year int `json:"year"`
Price int `json:"-"`
Actors []string `json:"actors"`
}
Price
字段在 JSON 编码时会被忽略。
输出结果
jsonStr = {"title":"喜剧之王","year":2000,"actors":["xingye","lucifer"]}
空值处理
如果希望某个字段在 JSON 编码时忽略空值,可以使用 omitempty
选项。
示例
type Movie struct {
Title string `json:"title"`
Year int `json:"year,omitempty"`
Price int `json:"rmb,omitempty"`
Actors []string `json:"actors,omitempty"`
}
- 如果
Year
、Price
或Actors
字段为零值(如0
、nil
),则在 JSON 编码时会被忽略。
输出结果
jsonStr = {"title":"喜剧之王"}
5. 完整代码示例
以下是结合结构体标签和 JSON 转换的完整代码:
package main
import (
"encoding/json"
"fmt"
)
// 结构体标签
type Movie struct {
Title string `json:"title"`
Year int `json:"year"`
Price int `json:"rmb"`
Actors []string `json:"actors"`
}
func main() {
movie := Movie{"喜剧之王", 2000, 10, []string{"xingye", "lucifer"}}
// 编码:结构体 ==> JSON
jsonStr, err := json.Marshal(movie)
if err != nil {
fmt.Println("json marshal error", err)
return
}
fmt.Printf("jsonStr = %s\n", jsonStr)
// 解码:JSON ==> 结构体
myMovie := Movie{}
err = json.Unmarshal(jsonStr, &myMovie)
if err != nil {
fmt.Println("json unmarshal error", err)
}
fmt.Printf("%v\n", myMovie)
}
6. 运行结果
运行代码后,输出如下:
jsonStr = {"title":"喜剧之王","year":2000,"rmb":10,"actors":["xingye","lucifer"]}
{喜剧之王 2000 10 [xingye lucifer]}