go语言struct

一、基本说明

结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。

二、结构体声明

type 结构体名称 struct{
	field type
	field type
}

例子:

type Dog struct {
	Name  string
	Age   int
	Color string
}

注:在创建一个结构体变量后,如果没有给字段赋值,都应该对应一个零值(默认值),布尔类型为false,数值类型为0,字符串为"",指针、slice和map的零值时nil,即没有分配空间。

三、结构体变量和访问结构体字段

  1. 方式一:直接声明
var dog Dog
  1. 方式二:{}
package main

import (
	"fmt"
)

type Dog struct {
	Name  string
	Age   int
	Color string
}

func main() {
	dog := Dog{"来福", 3, "白色"}
	fmt.Printf("dog=%v\n", dog)
}

输出结果:

dog={来福 3 白色}
  1. 方式三:new()
package main

import (
	"fmt"
)

type Dog struct {
	Name  string
	Age   int
	Color string
}

func main() {
	dog := new(Dog)
	// dog是一个指针,因此标准的给字段赋值应该是(*dog).Name = "来福" (*dog).Age = 3 (*dog).Color = "白色"
	// go设计者为了程序员使用方便,底层会对dog.Name = "来福"进行处理,会给dog加上取值运算(*dog).Name = "来福"
	dog.Name = "来福"  // 等价于(*dog).Name = "来福"
	dog.Age = 3      // 等价于(*dog).Age = 3
	dog.Color = "白色" // 等价于(*dog).Color = "白色"
	fmt.Printf("dog=%v\n", *dog)
}

输出结果:

dog={来福 3 白色}
  1. 方式四:
package main

import (
	"fmt"
)

type Dog struct {
	Name  string
	Age   int
	Color string
}

func main() {
	dog := &Dog{"来福", 3, "白色"}
	fmt.Printf("dog=%v\n", *dog)
}

输出结果:

dog={来福 3 白色}
  1. 方式五:创建struct实例指定字段值
package main

import (
	"fmt"
)

type Person struct {
	Name string
	Age  int
}

func main() {
	person := Person{
		Name: "Tom",
		Age:  18,
	}

	personPor := &Person{
		Name: "Jack",
		Age:  20,
	}
	fmt.Println("person=", person)
	fmt.Println("personPor=", *personPor)
}

输出结果:

person= {Tom 18}
personPor= {Jack 20}

说明:

  • 第三种方式和第四种方式返回的是结构体指针
  • 结构体指针访问字段的标准方式应该是:(*结构体指针).字段名,比如(*dog).Name = "来福"
  • 但是go做了一个简化,也支持结构体指针.字段名,比如:dog.Name = "来福",更加符合程序员使用习惯,go编译器底层对dog.Name做了转化(*dog).Name

四、结构体和结构体变量的区别

  1. 结构体是自定义的数据类型,代表一类事物
  2. 结构体变量(实例)是具体的,实际的,代表一个具体变量

五、结构体在内存中的示意图

image

六、注意细节

  1. 结构体所有字段在内存中是连续分布的
package main

import (
	"fmt"
)

type Point struct {
	x, y int
}

type Rect struct {
	LeftUp, RightDown Point
}

func main() {
	rect := Rect{Point{10, 20}, Point{30, 40}}
	fmt.Printf("rect.LeftUp.x地址=%v\n,rect.LeftUp.y地址=%v\n,rect.RightDown.x地址=%v\n,rect.RightDown.y地址=%v\n",
		&rect.LeftUp.x, &rect.LeftUp.y, &rect.RightDown.x, &rect.RightDown.y)
}

输出结果:

rect.LeftUp.x地址=0xc0000124e0
,rect.LeftUp.y地址=0xc0000124e8
,rect.RightDown.x地址=0xc0000124f0
,rect.RightDown.y地址=0xc0000124f8
  1. 结构体是用户单独定义的类型,和其他类型进行转换时需要有完全相同的字段(名字、个数、顺序和类型)
package main

import (
	"fmt"
)

type A struct {
	Num  int
	Num2 int
}

type B struct {
	Num  int
	Num2 int
}

func main() {
	var a A
	var b B
	a = A(b) // 可以转换,但是有要求,就是结构体字段要完全一样(包括:名字,类型,个数,顺序)
	fmt.Println("a=", a, "b=", b)
}

输出结果:

a= {0 0} b= {0 0}
  1. 结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以强转
package main

import (
	"fmt"
)

type integer int

func main() {
	var i integer
	var j int
	j = int(i)
	fmt.Println("i=", i, "j=", j)
}

输出结果:

i= 0 j= 0
  1. struct的每个字段上,可以写上一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列化和反序列化
package main

import (
	"encoding/json"
	"fmt"
)

type Dog struct {
	Name  string `json:"name"` // `json:"name"`就是struct tag
	Age   int    `json:"age"`
	Color string `json:"color"`
}

func main() {
	dog := Dog{"来福", 3, "白色"}
	// 将dog变量序列化为json字符串
	// json.Marshal函数中使用反射
	jsonStr, err := json.Marshal(dog)
	if err != nil {
		fmt.Println("json转换错误")
	}
	fmt.Println("dog=", string(jsonStr))
}

输出结果:

dog= {"name":"来福","age":3,"color":"白色"}

七、方法

golang中的方法是作用在指定的数据类型上的(即和指定的数据类型绑定)因此自定义类型,都可以有方法,而不仅仅是struct。


1.方法的声明(定义)
func (recevier type) methodName(参数列表)(返回值列表){
	方法体
	return 返回值
}
  • 参数列表,表示方法输入
  • recevier type:表示这个方法和type这个类型进行绑定,或者说该方法作用于type类型
  • recevier type:可以是结构体,也可以说是自定义类型。
  • recevier就是type类型的一个变量(实例)。
  • 参数列表表示方法的输入
  • 返回值列表,表示返回的值,可以是多个。
  • 方法主体,表示为了实现某一功能代码块
  • return语句不是必须的

例子:

package main

import (
	"fmt"
)

type Dog struct {
	Name  string
	Age   int
	Color string
}

// 给Dog类型绑定一个方法
func (d Dog) Eat(food string) {
	fmt.Printf("%v正在吃%v\n", d.Name, food)
}

func main() {
	d := Dog{
		Name:  "小花",
		Age:   3,
		Color: "花色",
	}
	// 调用方法
	d.Eat("骨头")
}

输出结果:

小花正在吃骨头

总结:

  • Eat方法和Dog类型绑定
  • Eat方法只能通过Dog类型来调用,既不能直接调用,也不能通过其他类型来调用。
  • func (d Dog) Eat(food string) {...},方法的调用和传参机制和函数基本一样,不一样的地方是方法的调用时,会将调用方法的变量,当做实参也传递给方法。

2. 方法的注意事项和细节讨论
  • 结构体是值类型,在方法的调用中遵守值类型的传递机制,是值拷贝传递方式
  • 若程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理
package main

import (
	"fmt"
)

type Dog struct {
	Name  string
	Age   int
	Color string
}

// 给Dog类型绑定一个方法
func (d *Dog) test() {
	d.Name = "旺财"
}

func main() {
	d := Dog{
		Name:  "小花",
		Age:   3,
		Color: "花色",
	}
	// 编译器底层做了优化,(&d).test()等价d.test()
	// 因为编译器会自动加上&
	// (&d).test()
	d.test()
	fmt.Println("d=", d)
}

输出结果:

d= {旺财 3 花色}
  • Golang中的方法是作用在数据类型上(即和指定的数据类型绑定),因此自定义数据类型,都可以有方法。而不仅仅是struct。
package main

import (
	"fmt"
)

type interge int

func (i interge) change() {
	fmt.Println("i=", i)
}
func main() {
	var i interge = 2
	i.change()

}

输出结果:

i= 2
  • 方法的访问范围控制的规则,和函数一样,方法名首字母小写,只能在本包访问,方法的首字母大写,可以在本包和其他包访问

  • 如果一个变量实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出

package main

import (
	"fmt"
)

type Person struct {
	Name string
	Age  int
}

func (p Person) String() string {
	str := fmt.Sprintf("Name=[%v],Age=[%v]", p.Name, p.Age)
	return str
}

func main() {
	person := Person{
		Name: "Tom",
		Age:  18,
	}
	fmt.Println("person=", person)
	fmt.Println("&person=", &person)
}

输出结果:

person= Name=[Tom],Age=[18]
&person= Name=[Tom],Age=[18]

3.方法和函数的区别
  • 调用方式不一样
    函数的调用方式:函数名(实参列表)
    方法的调用方式:变量.方法名(实参列表)
  • 对于普通函数,接收者是值类型时,不能将指针类型的数据直接传递,反之亦然
  • 对于方法(如strtuct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反之同样也可以>对于方法(如strtuct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反之同样也可以
package main

import (
	"fmt"
)

type Person struct {
	Name string
	Age  int
}

func (p Person) test() {
	p.Name = "Merry"

}

func (p *Person) test01() {
	p.Name = "Jack"
}

func main() {
	person := Person{
		Name: "Tom",
		Age:  18,
	}
	person.test()
	fmt.Println("name=", person.Name)
	(&person).test() // 从形式上是传入地址,但本质任然是值拷贝
	fmt.Println("name=", person.Name)
	person.test01() // 等价于(&person).test01() 从形式上是传入值类型,但本质任然是地址拷贝
	fmt.Println("name=", person.Name)
	(&person).test01()
	fmt.Println("name=", person.Name)
}

输出结果:

name= Tom
name= Tom
name= Jack
name= Jack

对上面代码总结:


不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法是和那个类型绑定的
如果是和值类绑定型,比如: (p Person) test(),则是值拷贝
如果是和地址类型绑定,比如:func (p *Person) test01(),则是地址拷贝


posted @ 2021-05-24 23:33  若雨蚂蚱  阅读(115)  评论(0编辑  收藏  举报