上一篇整理了 Go 中 map、slice 和 range 的基础用法。
这一篇继续记录我学习 Go 过程中几个比较重要的知识点:函数、结构体 struct、指针和 interface 接口。
这些内容以后写 Go 后端、项目代码时都会经常遇到,所以这一阶段主要先理解它们最基础的用法。
一、Go 函数基础
最简单的函数可以这样写:
func add(a int, b int) int {
return a + b
}
调用:
result := add(3, 5)
fmt.Println(result)
输出:
8
这里:
a int
b int
表示参数 a 和 b 都是 int 类型。
而最后这个:
int
表示这个函数最终会返回一个 int 类型的结果。
所以:
func add(a int, b int) int
可以理解为:
接收两个整数,最后返回一个整数。
二、不同返回类型
函数返回什么类型,函数后面就写什么类型。
例如返回整数:
func double(n int) int {
return n * 2
}
返回布尔值:
func isEven(n int) bool {
return n%2 == 0
}
例如:
result := isEven(8)
fmt.Println(result)
输出:
true
返回字符串:
func checkScore(score int) string {
if score >= 60 {
return "pass"
}
return "fail"
}
如果:
result := checkScore(85)
那么:
result = "pass"
因此我目前可以这样理解:
return 返回什么类型
↓
函数后面就写什么类型
三、没有返回值的函数
函数也可以没有返回值。
例如:
func sayHello(name string) {
fmt.Println("Hello", name)
}
调用:
sayHello("Tom")
输出:
Hello Tom
这里函数只是负责打印内容,没有:
return
所以函数后面也不需要写:
int
string
bool
可以简单理解成:
有返回结果,就声明返回类型;只执行操作、不返回结果,就不用写返回类型。
四、函数可以返回多个值
Go 函数还可以一次返回多个值。
例如:
func calculate(a int, b int) (int, int) {
return a + b, a - b
}
调用:
sum, diff := calculate(10, 3)
结果:
sum = 13
diff = 7
这里:
(int, int)
表示函数会返回两个整数。
这种写法在 Go 中非常常见。
例如之前 map 中判断 key 是否存在:
value, ok := m[key]
也是一次得到两个值:
value -> 对应的数据
ok -> key 是否存在
五、struct 是什么
struct 是 Go 中非常重要的数据结构。
可以先理解成:
把一组有关联的数据组合到一起。
例如定义一个学生:
type Student struct {
Name string
Age int
Score int
}
这里定义了一个 Student 类型,其中包含:
Name -> 姓名
Age -> 年龄
Score -> 分数
创建一个学生:
student := Student{
Name: "Tom",
Age: 20,
Score: 90,
}
访问字段:
fmt.Println(student.Name)
fmt.Println(student.Age)
fmt.Println(student.Score)
输出:
Tom
20
90
六、修改 struct 字段
结构体中的字段可以直接修改。
例如:
student.Score = 95
那么:
fmt.Println(student.Score)
输出:
95
所以:
student.Name
student.Age
student.Score
就是访问这个结构体内部不同字段。
七、slice 和 struct 组合
实际程序里通常不会只有一个学生,而是有多个。
这时可以把多个 struct 放进 slice:
students := []Student{
{Name: "Tom", Score: 80},
{Name: "Alice", Score: 95},
{Name: "Bob", Score: 70},
}
访问第二个学生姓名:
fmt.Println(students[1].Name)
输出:
Alice
因为:
students[1]
先取出第二个 Student,
再:
.Name
取出对应姓名。
八、range 遍历 struct slice
例如:
for _, student := range students {
if student.Score >= 80 {
fmt.Println(student.Name)
}
}
如果:
students := []Student{
{Name: "Tom", Score: 80},
{Name: "Alice", Score: 95},
{Name: "Bob", Score: 70},
}
那么输出:
Tom
Alice
因为:
Tom -> 80 >= 80
Alice -> 95 >= 80
Bob -> 70 < 80
九、struct 的方法
除了保存数据,还可以给 struct 定义方法。
例如:
func (s Student) PrintInfo() {
fmt.Println(s.Name, s.Score)
}
然后:
student := Student{
Name: "Tom",
Score: 80,
}
student.PrintInfo()
输出:
Tom 80
这里:
(s Student)
叫做接收者。
可以先理解成:
这个
PrintInfo()方法属于Student类型。
所以:
student.PrintInfo()
就是让这个学生执行自己的 PrintInfo() 方法。
十、指针基础
指针刚开始看起来比较抽象。
例如:
x := 20
p := &x
这里:
x -> 普通变量
&x -> x 的地址
p -> 保存 x 地址的变量
如果:
fmt.Println(*p)
输出:
20
其中:
*p
表示:
根据这个地址找到对应的值。
所以我目前先记住:
&x -> 取地址
*p -> 通过地址取值
十一、通过指针修改原变量
指针很重要的一个用途就是:
可以通过地址直接修改原变量。
例如:
x := 20
p := &x
*p = 50
fmt.Println(x)
最后:
50
因为:
*p = 50
实际上修改的是 p 指向的变量,也就是原来的 x。
十二、普通参数和指针参数的区别
例如普通参数:
func change(n int) {
n = 100
}
调用:
x := 10
change(x)
fmt.Println(x)
最后仍然是:
10
因为函数拿到的是一份值的副本。
但是如果改成:
func change(n *int) {
*n = 100
}
调用:
x := 10
change(&x)
fmt.Println(x)
那么输出:
100
因为传进去的是:
&x
也就是 x 的地址。
函数通过:
*n
直接修改了原变量。
因此我目前可以这样理解:
普通参数 -> 修改的是副本
指针参数 -> 可以修改原变量
十三、指针接收者
结构体的方法也可以使用指针。
例如:
type Student struct {
Name string
Score int
}
func (s *Student) AddScore(n int) {
s.Score += n
}
调用:
student := Student{
Name: "Tom",
Score: 80,
}
student.AddScore(10)
fmt.Println(student.Score)
输出:
90
这里:
(s *Student)
表示方法可以修改原来的 Student。
所以如果方法需要修改结构体本身,通常会使用指针接收者。
十四、interface 是什么
interface 是 Go 中比较重要的概念。
可以先理解成:
接口只规定一个类型“必须会做什么”。
例如:
type Speaker interface {
Speak()
}
这里表示:
只要某个类型拥有
Speak()方法,它就符合Speaker接口。
例如:
type Dog struct{}
func (d Dog) Speak() {
fmt.Println("Woof")
}
因为 Dog 有:
Speak()
所以 Dog 符合 Speaker 接口。
十五、不同类型可以实现同一个 interface
例如:
type Dog struct{}
func (d Dog) Speak() {
fmt.Println("Woof")
}
type Cat struct{}
func (c Cat) Speak() {
fmt.Println("Meow")
}
Dog 和 Cat 都实现了:
Speak()
所以它们都符合:
type Speaker interface {
Speak()
}
可以定义:
func makeSound(s Speaker) {
s.Speak()
}
然后:
makeSound(Dog{})
makeSound(Cat{})
分别输出:
Woof
Meow
这让我目前对 interface 的理解是:
接口规定能力,具体类型自己决定怎么实现。
十六、接口中的方法必须全部实现
例如:
type Animal interface {
Speak()
Eat()
}
那么一个类型想符合 Animal 接口,就必须同时拥有:
Speak()
Eat()
如果只有:
Speak()
而没有:
Eat()
那么就不符合这个接口。
所以:
interface 里要求的方法,类型必须全部实现,少一个都不行。
十七、这次学习中比较容易混淆的地方
1. 参数类型和返回类型
例如:
func checkScore(score int) string
这里:
score int -> 输入参数是整数
string -> 返回结果是字符串
一开始容易把最后的 string 也理解成参数类型。
2. 普通变量和指针
x
是变量本身。
&x
是变量地址。
*p
是根据地址取值。
这个部分目前需要通过更多代码继续巩固。
3. struct 和 interface
struct 更像是:
定义一种具体的数据类型。
而 interface 更像是:
规定一种能力。
例如:
Student
Dog
Cat
可以是 struct。
而:
Speaker
Worker
更适合作为 interface。
十八、学习总结
这一阶段主要学习了:
函数
struct
方法
指针
interface
其中我觉得最重要的几个理解是:
函数:
输入参数 -> 执行逻辑 -> 返回结果
struct:
把有关联的数据组合成一个类型
指针:
通过地址访问和修改原变量
interface:
规定一个类型需要具备哪些方法
目前这些内容还只是基础理解,后面准备继续通过实际项目和更多练习来巩固。
下一篇准备整理 goroutine、channel、WaitGroup、Mutex、defer、select。

905

被折叠的 条评论
为什么被折叠?



