Golang入门教程(十四)结构体和类详解

golang中并没有明确的面向对象的说法,实在要扯上的话,可以将struct比作其它语言中的class。

类声明

1
2
3
4
5
type Book struct {
    Title  string
    Author string
    intro  string
}

  这样就声明了一个类,其中没有public、protected、private的的声明。golang用另外一种做法来实现属性的访问权限:属性的开头字母是大写的则在其它包中可以被访问,否则只能在本包中访问。类的声明和方法亦是如此。

类方法声明

1
2
3
4
5
6
7
8
9
10
11
// 类方法声明-传递值对象
func (b Book) B1() {
    b.Title = "Book001"
    b.Author = "ErWan"
}
 
// 类方法声明-传递指针对象
func (b *Book) B2() {
    b.Title = "Book002"
    b.Author = "Tinywan"
}

  和其它语言不一样,golang声明方法和普通方法一致,只是在func后增加了b Book这样的声明。加和没有加*的区别在于一个是传递指针对象(加*),一个是传递值对象。

传递指针和对象的区别

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
package main
 
import "fmt"
 
// 类声明
type Book struct {
    Title    string
    Author   string
    DateTime string
}
 
// 类方法声明-传递值对象
func (b Book) B1() {
    b.Title = "Book001"
    b.Author = "ErWan"
}
 
// 类方法声明-传递指针对象
func (b *Book) B2() {
    b.Title = "Book002"
    b.Author = "Tinywan"
}
 
func main() {
    /*声明一个 Book 类型的变量 b ,并调用 B1() 和 B2()*/
    b := Book{"Def-Book", "Def-Author", "Def-DateTime"}
 
    fmt.Println("B1 调用前:", b.Title, b.Author, b.DateTime)
    b.B1()
    fmt.Println("B1 调用后:", b.Title)
 
    fmt.Println("------------------\r\n")
    fmt.Println("B2 调用前:", b.Title)
    b.B2()
    fmt.Println("B2 调用后:", b.Title)
 
}

 执行结果:

B1() 的接收者是值类型 Book, B2() 的接收者是值类型 *Book , 两个方法内都是改变Name值。

小结:

  1、接收者可以看作是函数的第一个参数,即这样的: func B1(b Book), func B2(b *Book)。 go不是面向对象的语言,所以用那种看起来像面向对象的语法来理解可能有偏差。

  2、当调用 b.B1() 时相当于 B1(b) ,实参和行参都是类型 Book,可以接受。此时在B1()中的b只是 "Def-Book" 的值拷贝,所以B1()的修改影响不到" Def-Book"。

  3、当调用 b.B2() => B2(b1),这是将 Book 类型传给了 *Book 类型,go可能会取 b 的地址传进去: B2(&b)。所以 B2() 的修改可以影响 b 。

 

 例如在一个beego 框架中我们要修改一个action的值,是这么定义的(为了修改内部结构的值。而不是传递一下)

1
2
3
4
5
func (this *InputController) InputGet(){      // Get 方式接受
    name := this.GetString("name")
     // 不使用模版,直接用 this.Ctx.WriteString 输出字符串
    this.Ctx.WriteString(name)
}

值类型不可以改变值,而指针类型则是可以的

匿名结构体

1
2
3
4
5
p := struct {
    Name string
    Gender string
    Age uint8
}{"Robert", "Male", 33}

匿名结构体最大的用处是在内部临时创建一个结构以封装数据,而不必正式为其声明相关规则。 

实例化对象

实例化对象有好几种方式:

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
package main
 
// 类声明
type Person struct {
    Name    string
    Age     int
    Doc     []string // slice切片
}
 
// 类方法声明-传递值对象
func (p *Person) P1() {
    p.Name = "Tinywan"
    p.Age = 24
}
 
// 类方法声明-传递指针对象
func (p *Person) P2() {
    p.Name = "Tinyaiai"
    p.Age = 22
}
 
func main() {
 
    // 实例化对象 实例化的时候可以初始化属性值,如果没有指明则默认为系统默认值
    p1 := &Person{}
    p1.Name = "ShaoBo Wan"
    p1.Age = 20
 
    p2 := &Person{Name:"HaHa"}
 
    p3 := new(Person)
    p3.Name = "New Name"
 
    p4 := Person{}
    p4.Name = "Name 001"
    p4.Age = 26
 
    p5 := Person{Name:"Name 002",Age:28}
 
    // 使用中如果包含数组(动态数组 slice切片),结构体的实例化需要加上类型如上如果intro的类型是[]string
    p6 := &Person{
        "zhangsan",
        25,
        []string{"lisi", "wangwu"},
    }
}

注意,最后一个实例化

1
2
3
4
5
p6 := &Person{
    "zhangsan",
    25,
    []string{"lisi", "wangwu"},
}

小结:

  1、使用中如果包含数组,结构体的实例化需要加上类型如上如果Doc的类型是[]string。  

  2、实例化的时候可以初始化属性值,如果没有指明则默认为系统默认值。

  3、加&符号和new的是指针对象,没有的则是值对象,这点和php、java不一致,在传递对象的时候要根据实际情况来决定是要传递指针还是值

  4、当对象比较小的时候传递指针并不划算。

 继承

确切的说golang中叫做组合(composition)

1、先初始化为空再赋值

1
2
3
// 先初始化为空再赋值
s1 := &Student{}
s1.schoole = "QiHua"

2、直接赋值

1
2
3
4
5
6
7
8
9
// 直接赋值
s2 := &Student{
    Persons: Persons{
        Name:"Tinywan",
        Age:24,
        Doc:[]string{"H1","h2"},
    },
    schoole:"BeiJin Schoole",
}

3、完整代码

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
48
package main
 
import (
    "fmt"
)
 
// 类声明
type Persons struct {
    Name    string
    Age     int
    Doc     []string // slice切片
}
 
// 获取Name
func (p *Persons) getName() {
    fmt.Println("Name is ",p.Name)
}
 
type Student struct {
    // Student 属性中声明了 Persons,表示组合了Persons 的属性和方法(属性和方法都会被继承)
    Persons
    Name string
    schoole string
}
 
func main() {
 
    // 先初始化为空再赋值
    s1 := &Student{}
    // 当访问Name的时候默认为ProsePoem的 Name,如果需要访问Persons 的 Name 属性可以使用 s1.Persons.Name 来访问方法同理。
    s1.Name = "Tinywan"
    s1.Persons.Name = "Persons Name"
    s1.schoole = "QiHua"
    fmt.Println("s1 = ",s1)
    fmt.Println("\r\n")
 
    // 直接赋值
    s2 := &Student{
        Persons: Persons{
            Name:"Tinywan",
            Age:24,
            Doc:[]string{"s2-Doc","s2-Doc"},
        },
        Name:"ErWan",
        schoole:"BeiJin-Schoole",
    }
    fmt.Println("s2 = ",s2)
}

方法的继承和属性一致

  

参考

1、https://segmentfault.com/a/1190000012325027

2、 

 

posted @   Tinywan  阅读(1755)  评论(0编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示