>  기사  >  백엔드 개발  >  Golang의 Struct(구조)에 대한 자세한 설명

Golang의 Struct(구조)에 대한 자세한 설명

青灯夜游
青灯夜游앞으로
2022-11-17 20:49:123040검색

Golang의 Struct(구조)에 대한 자세한 설명

Go 언어는 구조체에 대한 지원을 제공합니다. 중국어 번역은 구조라고 하며 배열과 마찬가지로 참조 유형이 아닌 복합 유형입니다. [관련 권장사항: Go 동영상 튜토리얼] struct,中文翻译称为结构体,与数组一样,属于复合类型,并非引用类型。【相关推荐:Go视频教程

Go语言的struct,与C语言中的struct或其他面向对象编程语言中的类(class)类似,可以定义字段(属性)和方法,但也有很不同的地方,需要深入学习,才能区分他们之间的区别。

注意复合类型与引用类型之间的区别,这应该也是值传递和引用传递的区别吧。

定义

使用struct关键字可以定义一个结构体,结构体中的成员,称为结构体的字段或属性。

type Member struct {
    id          int
    name, email string
    gender, age int
}

上面的代码中,我们定义了一个包含5个字段的结构体,可以看到,相同类型nameemailgenderage在同一行中定义,但比较好的编程习惯是每一行只定义一个字段,如:

type Member struct {
    id     int
    name   string
    email  string
    gender int
    age    int
}

当然,结构体也可以不包含任何字段,称为空结构体,struct{}表示一个空的结构体,注意,直接定义一个空的结构体并没有意义,但在并发编程中,channel之间的通讯,可以使用一个struct{}作为信号量。

ch := make(chan struct{})
ch <- struct{}{}

使用

上面的例子中,我们定义了Member结构体类型,接下就可以这个自定义的类型创建变量了。

直接定义变量,这个使用方式并没有为字段赋初始值,因此所有字段都会被自动赋予自已类型的零值,比如name的值为空字符串"",age的值为0。

var m1 Member//所有字段均为空值

使用字面量创建变量,这种使用方式,可以在大括号中为结构体的成员赋初始值,有两种赋初始值的方式,一种是按字段在结构体中的顺序赋值,下面代码中m2就是使用这种方式,这种方式要求所有的字段都必须赋值,因此如果字段太多,每个字段都要赋值,会很繁琐,另一种则使用字段名为指定字段赋值,如下面代码中变量m3的创建,使用这种方式,对于其他没有指定的字段,则使用该字段类型的零值作为初始化值。

var m2 = Member{1,"小明","xiaoming@163.com",1,18} // 简短变量声明方式:m2 := Member{1,"小明","xiaoming@163.com",1,18}
var m3 = Member{id:2,"name":"小红"}// 简短变量声明方式:m3 := Member{id:2,"name":"小红"}

访问字段

通过变量名,使用逗号(.),可以访问结构体类型中的字段,或为字段赋值,也可以对字段进行取址(&)操作。

fmt.Println(m2.name)//输出:小明
m3.name = "小花"
fmt.Println(m3.name)//输出:小花

age := &m3.age
*age = 20
fmt.Println(m3.age)//20

指针结构体

结构体与数组一样,都是值传递,比如当把数组或结构体作为实参传给函数的形参时,会复制一个副本,所以为了提高性能,一般不会把数组直接传递给函数,而是使用切片(引用类型)代替,而把结构体传给函数时,可以使用指针结构体

指针结构体,即一个指向结构体的指针,声明结构体变量时,在结构体类型前加*号,便声明一个指向结构体的指针,如:

注意,指针类型为引用类型,声明结构体指针时,如果未初始化,则初始值为nil,只有初始化后,才能访问字段或为字段赋值。

var m1 *Member
m1.name = "小明"//错误用法,未初始化,m1为nil

m1 = &Member{}
m1.name = "小明"//初始化后,结构体指针指向某个结构体地址,才能访问字段,为字段赋值。

另外,使用Go内置new()函数,可以分配内存来初始化结构休,并返回分配的内存指针,因为已经初始化了,所以可以直接访问字段。

var m2 = new(Member)
m2.name = "小红"

我们知道,如果将结构体转给函数,只是复制结构体的副本,如果在函数内修改结构体字段值,外面的结构体并不会受影响,而如果将结构体指针传给函数,则在函数中使用指针对结构体所做的修改,都会影响到指针指向的结构体。

func main() {
    m1 := Member{}
    m2 := new(Member)
    Change(m1,m2)
    fmt.Println(m1,m2)
}

func Change(m1 Member,m2 *Member){
    m1.Name = "小明"
    m2.Name = "小红"
}

可见性

上面的例子中,我们定义结构体字段名首字母是小写的,这意味着这些字段在包外不可见

Go 언어 구조체 및 C 언어의 구조체나 다른 객체지향 프로그래밍 언어의 클래스는 유사하며 필드(속성)와 메서드를 정의할 수 있지만, 매우 다르기 때문에 차이점을 구별하기 위해서는 심층적인 연구가 필요합니다.

복합 유형과 참조 유형의 차이점에 주의하세요. 이는 값 전달과 참조 전달의 차이점이기도 합니다.

정의

구조체를 정의하려면 struct 키워드를 사용하세요. 구조의 멤버를 구조 또는 속성의 필드라고 합니다.

package member
type Member struct {
    id     int
    name   string
    email  string
    gender int
    age    int
}

package main

fun main(){
    var m = member.Member{1,"小明","xiaoming@163.com",1,18}//会引发panic错误
}

위 코드에서는 5개의 필드를 포함하는 구조를 정의합니다. nameemail, gender code> 및 동일한 유형을 볼 수 있습니다. <code>age는 같은 줄에 정의되지만 더 나은 프로그래밍 습관은 다음과 같이 각 줄에 하나의 필드만 정의하는 것입니다.

type Member struct {
    id     int
    name   string
    email  string
    gender int
    age    int
}
🎜물론 구조에는 빈 구조, struct{}는 빈 구조를 나타냅니다. 빈 구조를 직접 정의하는 것은 의미가 없지만 동시 프로그래밍에서는 struct{}를 채널 간 통신에 사용할 수 있습니다. 신호기. 🎜
type Member struct {
    Id     int    `json:"id,-"`
    Name   string `json:"name"`
    Email  string `json:"email"`
    Gender int    `json:"gender,"`
    Age    int    `json:"age"`
}

사용🎜🎜위의 예에서는 Member 구조 유형을 정의한 다음 이 사용자 정의 유형의 변수를 생성할 수 있습니다. 🎜
🎜변수를 직접 정의합니다. 이 사용 방법은 필드에 초기 값을 할당하지 않으므로 모든 필드에 자동으로 해당 유형의 0 값이 할당됩니다. code>는 빈 문자열 "" 이고 age 값은 0입니다. 🎜
Id int `json:"id" gorm:"AUTO_INCREMENT"`
🎜리터럴을 사용하여 변수를 생성합니다. 이런 방식으로 중괄호 안의 구조체 멤버에 초기값을 할당하는 방법이 있습니다. 필드별 값입니다. 구조에서 m2는 아래 코드에서 이 방법을 사용합니다. 이 방법을 사용하려면 모든 필드에 값을 할당해야 하므로 필드가 너무 많으면 각 필드에 값을 할당해야 합니다. 다른 방법은 다음 코드에서 m3 변수를 생성하는 것과 같이 필드 이름을 사용하여 필드 할당을 지정하는 것입니다. 지정되지 않은 다른 필드의 경우 필드 유형의 0 값을 초기화 값으로 사용합니다. 🎜
func setName(m Member,name string){//普通函数
    m.Name = name
}

func (m Member)setName(name string){//绑定到Member结构体的方法
    m.Name = name
}

필드 액세스

🎜쉼표 (.)를 사용하여 변수 이름을 통해 구조 유형의 필드에 액세스할 수 있습니다. , 필드에 값을 할당하거나 필드에서 주소(&) 작업을 수행합니다. 🎜
m := Member{}
m.setName("小明")
fmt.Println(m.Name)//输出为空

포인터 구조

🎜구조체는 배열과 마찬가지로 값으로 전달됩니다. 예를 들어 배열이나 구조가 실제 매개변수로 형식 매개변수에 전달되는 경우입니다. function 은 복사본을 만들기 때문에 성능 향상을 위해 일반적으로 배열이 함수에 직접 전달되지 않고 대신 슬라이스(참조 유형)가 사용됩니다. 구조를 함수에 전달할 때 포인터 구조 code>. 🎜🎜포인터 구조, 즉 구조에 대한 포인터입니다. 구조 변수를 선언할 때 구조 유형 앞에 * 기호를 추가하여 다음과 같이 구조에 대한 포인터를 선언합니다. 🎜
🎜포인터 유형은 다음과 같습니다. 참조 유형, 구조체 포인터를 선언할 때 초기화되지 않은 경우 초기 값은 초기화 후에만 필드에 액세스하거나 값을 할당할 수 있습니다. 🎜
func (m *Member)setName(name string){/将Member改为*Member
    m.Name = name
}

m := Member{}
m.setName("小明")
fmt.Println(m.Name)//小明
🎜또한 Go에 내장된 new() 함수를 사용하면 메모리를 할당하여 구조체를 초기화하고 할당된 메모리 포인터를 반환할 수 있으므로 해당 필드에 직접 접근할 수 있습니다. 🎜
type Animal struct {
    Name   string  //名称
    Color  string  //颜色
    Height float32 //身高
    Weight float32 //体重
    Age    int     //年龄
}
//奔跑
func (a Animal)Run() {
    fmt.Println(a.Name + "is running")
}
//吃东西
func (a Animal)Eat() {
    fmt.Println(a.Name + "is eating")
}

type Cat struct {
    a Animal
}

func main() {
    var c = Cat{
	    a: Animal{
            Name:   "猫猫",
            Color:  "橙色",
            Weight: 10,
            Height: 30,
            Age:    5,
        },
    }
    fmt.Println(c.a.Name)
    c.a.Run()
}
🎜구조체를 함수로 옮기면 구조의 복사본만 생성된다는 것을 알고 있습니다. 함수 내의 구조 필드 값을 수정하면 외부 구조는 영향을 받지 않으며 구조 포인터를 전달하면 됩니다. 함수에 포인터를 사용하여 구조를 수정하면 포인터가 가리키는 구조에 영향을 미칩니다. 🎜
type Lion struct {
	Animal //匿名字段
}

func main(){
    var lion = Lion{
        Animal{
            Name:  "小狮子",
            Color: "灰色",
        },
    }
    lion.Run()
    fmt.Println(lion.Name)
}

Visibility🎜🎜위 예에서는 구조 필드 이름의 첫 글자를 소문자로 정의했습니다. 이는 해당 필드가 패키지 외부에서 표시되지 않음을 의미합니다. 이므로 다른 패키지에서는 접근이 불가능하며, 해당 패키지 내에서만 접근이 허용됩니다. 🎜🎜다음 예에서는 멤버 패키지에서 Member를 선언한 다음 메인 패키지에서 변수를 생성합니다. 그러나 구조체의 필드는 패키지 외부에서 볼 수 없으므로 필드에 초기 값을 할당할 수 없습니다. 필드나 인덱스로 할당할 수 없습니다. 할당하면 패닉 오류가 발생합니다. 🎜rrreee🎜 따라서 한 패키지의 다른 패키지에 있는 구조체의 필드에 액세스하려면 대문자로 시작하는 변수, 즉 다음과 같이 내보낸 변수여야 합니다. 🎜rrreee🎜Tags🎜🎜When 구조 필드를 정의하면 필드 이름과 데이터 유형 외에도 백틱을 사용하여 구조 필드에 대한 메타 정보를 선언할 수 있으며, 이 메타 정보는 컴파일 단계에서 필드와 연결하는 데 사용됩니다. 예를 들어, 위 예의 구조를 다음과 같이 수정합니다.🎜
type Member struct {
    Id     int    `json:"id,-"`
    Name   string `json:"name"`
    Email  string `json:"email"`
    Gender int    `json:"gender,"`
    Age    int    `json:"age"`
}

上面例子演示的是使用encoding/json包编码或解码结构体时使用的Tag信息。

Tag由反引号括起来的一系列用空格分隔的key:"value"键值对组成,如:

Id int `json:"id" gorm:"AUTO_INCREMENT"`

特性

下面总结几点结构体的相关特性:

值传递

结构体与数组一样,是复合类型,无论是作为实参传递给函数时,还是赋值给其他变量,都是值传递,即复一个副本。

没有继承

Go语言是支持面向对象编程的,但却没有继承的概念,在结构体中,可以通过组合其他结构体来构建更复杂的结构体。

结构体不能包含自己

一个结构体,并没有包含自身,比如Member中的字段不能是Member类型,但却可能是*Member。

方法

在Go语言中,将函数绑定到具体的类型中,则称该函数是该类型的方法,其定义的方式是在func与函数名称之间加上具体类型变量,这个类型变量称为方法接收器,如:

注意,并不是只有结构体才能绑定方法,任何类型都可以绑定方法,只是我们这里介绍将方法绑定到结构体中。

func setName(m Member,name string){//普通函数
    m.Name = name
}

func (m Member)setName(name string){//绑定到Member结构体的方法
    m.Name = name
}

从上面的例子中,我们可以看出,通过方法接收器可以访问结构体的字段,这类似其他编程语言中的this关键词,但在Go语言中,只是一个变量名而已,我们可以任意命名方法接收器

调用结构体的方法,与调用字段一样:

m := Member{}
m.setName("小明")
fmt.Println(m.Name)//输出为空

上面的代码中,我们会很奇怪,不是调用setName()方法设置了字段Name的值了吗?为什么还是输出为空呢?

这是因为,结构体是值传递,当我们调用setName时,方法接收器接收到是只是结构体变量的一个副本,通过副本对值进行修复,并不会影响调用者,因此,我们可以将方法接收器定义为指针变量,就可达到修改结构体的目的了。

func (m *Member)setName(name string){/将Member改为*Member
    m.Name = name
}

m := Member{}
m.setName("小明")
fmt.Println(m.Name)//小明

方法和字段一样,如果首字母为小写,则只允许在包内可见,在其他包中是无法访问的,因此,如果要在其他包中访问setName,则应该将方法名改为SetName

组合

我们知道,结构体中并没有继承的概念,其实,在Go语言中也没有继承的概念,Go语言的编程哲学里,推荐使用组合的方式来达到代码复用效果。

什么是组合

组合,可以理解为定义一个结构体中,其字段可以是其他的结构体,这样,不同的结构体就可以共用相同的字段。

注意,在记得我们前面提过的,结构体不能包含自身,但可能包含指向自身的结构体指针。

例如,我们定义了一个名为Animal表示动物,如果我们想定义一个结构体表示猫,如:

type Animal struct {
    Name   string  //名称
    Color  string  //颜色
    Height float32 //身高
    Weight float32 //体重
    Age    int     //年龄
}
//奔跑
func (a Animal)Run() {
    fmt.Println(a.Name + "is running")
}
//吃东西
func (a Animal)Eat() {
    fmt.Println(a.Name + "is eating")
}

type Cat struct {
    a Animal
}

func main() {
    var c = Cat{
	    a: Animal{
            Name:   "猫猫",
            Color:  "橙色",
            Weight: 10,
            Height: 30,
            Age:    5,
        },
    }
    fmt.Println(c.a.Name)
    c.a.Run()
}

可以看到,我们定义Cat结构体时,可以把Animal结构体作为Cat的字段。

匿名字段

上面的例子,我们看到,把Animal结构体作为Cat的字段时,其变量名为a,所以我们访问Animal的方法时,语法为c.a.Run(),这种通过叶子属性访问某个字段类型所带的方法和字段用法非常繁琐。

Go语言支持直接将类型作为结构体的字段,而不需要取变量名,这种字段叫匿名字段,如:

type Lion struct {
	Animal //匿名字段
}

func main(){
    var lion = Lion{
        Animal{
            Name:  "小狮子",
            Color: "灰色",
        },
    }
    lion.Run()
    fmt.Println(lion.Name)
}

通过上面例子,可以看到,通过匿名字段组合其他类型,而后访问匿名字段类型所带的方法和字段时,不需要使用叶子属性,非常方便。

小结

在Go语言编程中,结构体大概算是使用得最多的数据类型了,通过定义不同字段和方法的结构体,抽象组合不同的结构体,这大概便是Go语言中对面向对象编程了。

原文地址:https://juejin.cn/post/6844903814168838151

更多编程相关知识,请访问:编程视频!!

위 내용은 Golang의 Struct(구조)에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제