Go 学习笔记02:函数、struct、指针与 interface 基础入门

上一篇整理了 Go 中 mapslicerange 的基础用法。

这一篇继续记录我学习 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

表示参数 ab 都是 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")
}

DogCat 都实现了:

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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值