1、结构体介绍

  • 为什么要有结构体?

数组只能保存同一种类型的数据,当需要记录多种不同类型的数据,并聚集在一起用来描述复杂的实体时,怎么办?

结构体就是用于解决这个问题的,结构体是由一系列具有相同类型或不同类型的数据构成的数据集合,方便容量任意类型的数据

结构体的目的就是把数据聚集在一起,以便能够更加便捷的操作这些数据

结构体是由一些列属性组成的复合数据类型,每个属性都具有名称、类型和值,结构体将属性组合在一起进行由程序进行处理

  • 结构体和类的概念

go里面没有类,go用一种特殊的方式,把结构体本身看做一个类

一个成熟的类,具备成员变量和成员函数,结构体本身就有成员变量,再给他绑定上成员函数,就可以了

  • 结构体单例绑定

如下,sayHello()用了指针的方式进行绑定,相当于给结构体绑定了函数,这个结构体等价于对象

唯一的不同点就是如果使用*绑定函数,那么这种对象就是单例的,引用的是同一个结构体

type People struct {
	name 		string
}

func (p People) toString() {
	fmt.Println(p.name)
	fmt.Printf("p的地址 %p \n", &p)
}

func (p *People) sayHello() {
	fmt.Printf("Hello %v \n", p.name)
	fmt.Printf("*P的地址 %p \n", p)
}

2、结构体的定义

结构体定义使用struct标识,需要指定其包含的属性(名和类型),使用关键字typestruct来定义一个结构体

结构体的定义格式如下

type 类型名 struct {
	字段1 类型1
	字段2 类型2
	//...
}

以上各个部分的说明如下

  • 类型名:标识自定义结构体的名称,在同一个包内不能包含重复的类型名
  • struct{}:表示结构体类型,type类型名struct{}可以被理解为将struct{}结构体定义为类型名的类型
  • 字段1、字段2......:表示结构体字段名。结构体中的字段名必须唯一
  • 类型1、类型2......:表示结构体各个字段的类型,结构体中的字段可以是任意类型:string、int、float;复合类型:map、slice、channel、struct

在定义结构体时可以为结构体指定结构体名(命名结构体),用于后续声明结构体变量使用

type struct_variable_type struct {
   member definition
   member definition
   ...
   member definition
}

例如 用于描述一个人的特征

如果单独使用变量描述应该如何描述?

没有结构体

var (
    name          string
    age           int
    gender        string
    weight        uint
    favoriteColor []string
)

使用结构体

type Person struct {
    Name          string
    Age           int
    Gender        string
    Weight        uint
    FavoriteColor []string
}

type User struct {
	ID       int
	Name     string
	Birthday string
	Addr     string
	Tel      string
	Remark   string
}

如果某几个字段类型相同,可以缩写在同一行:

type Person struct {
    Name, City string
    Age int
}

可以看出结构体就像一个容器,这个容器里面装什么自己定义,这也是结构体的特点: 自定义化的程度很高,使用灵活

一旦定义了结构体类型,则它就能用于变量的声明,语法格式如下

variable_name := struct_variable_type {value1, value2,...}

variable_name := struct_variable_type {key1: value1, key2: value2,...}

例如,定义一个名为Book的图书结构体,并打印出结构体的字段值的示例如下

package main

import "fmt"

type Book struct {
	title   string
	author  string
	subject string
	press   string
}

func main() {
	// 创建一个新的结构体
	fmt.Println(Book{"Go基础", "ssgeek", "Go语言", "Go语言教程"})
	// 也可以使用 key => value 格式
	fmt.Println(Book{title: "Go基础", author: "ssgeek", subject: "Go语言", press: "Go语言教程"})
	// 忽略的字段为 0 或 空
	fmt.Println(Book{title: "Go基础", author: "ssgeek"})
}

3、构造结构体实例

定义了struct,就表示定义了一个数据结构,或者说数据类型,也或者说定义了一个类。总而言之,定义了struct,就具备了成员属性,就可以作为一个抽象的模板,可以根据这个抽象模板生成具体的实例,也就是所谓的"对象", 也就是面向对象中的Class---> Object, 如下图

对应的结构体定义

type Car struct {
    Color string  // 颜色
    Brand string  // 品牌
    Model string  // 型号
}

var car Car   // 初始化一个Car实例, car就代表是一辆具体的车(Object)

这里的car就是一个具体的Car实例,它根据抽象的模板Car构造而出,具有具体的属性ColorBrandModel的值,虽然初始化时它的各个字段都是""值。换句话说,car是一个具体的车, 比如福特野马

struct初始化时,会做默认的赋0初始化,会给它的每个字段根据它们的数据类型赋予对应的0值。例如int类型是数值0string类型是"",引用类型是nil

var car Car
fmt.Printf("%+v", car) // {Color: Brand: Model:}

也可以在声明的时候直接为成员变量赋值,通常把这个过程称之为构造结构体的实例, 语法如下:

// 使用{k:v, k:v}这种方式来为结构体的成员赋值
TypeName{filed1: value1, filed2: value2, ...}

// 为了书写美观, 通常都把每一个k:v 独立写在一行,比如:
TypeName{
    file1: value1,
    file2: value2,
    ...
}

一个具体的例子,比如要通过Car构造一个具体的汽车: 福特野马

// 为了书写的简洁,采用简单声明的方式, 就像 a := 10 <--->  var a int; a = 10
//
var car Car
fmt.Printf("%+v\n", car)

car = Car{           // 当然也可以不然声明car的类型, 直接使用简短声明: car := Car{...}
    Color: "yellow", // 黄色
    Brand: "ford",   // 福特
    Model: "yema",   // 
}
fmt.Printf("%+v\n", car) // {Color:yellow Brand:ford Model:yema}

// 注意,上面最后一个逗号","不能省略,Go会报错,这个逗号有助于去扩展这个结构

4、声明与初始化

声明结构体变量只需要定义变量类型为结构体名,变量中的每个属性被初始化为对应类型的零值

也可声明结构体指针变量,此时变量被初始化为nil

遵循所有类型声明语法: var struct_name struct_type

使用结构体创建的变量叫做对应结构体的实例或者对象

  • 只声明不初始化

比如下面初始化一个person的实例

// 只声明
var person Person
var me User
fmt.Printf("%T\n", me)  // main.User

可以看到声明后的结构体的所有属性都是初始值

var不加等号:初始化零值(对数值类型来说,零值是0;对字符串来说,零值是空字符串;对布尔类型,零值是false

var person Person
fmt.Printf("%+v\n", person)
// {Name: Age:0 Gender: Weight:0 FavoriteColor:[]}
var me3 User = User{}
fmt.Println(me3)  // {0  0001-01-01 00:00:00 +0000 UTC   }
  • 声明并初始化

    使用字面量初始化结构体值对象

var person Person = Person{
    Name:          "andy",
    Age:           66,
    Gender:        "male",
    Weight:        120,
    FavoriteColor: []string{"red", "blue"},
}
fmt.Printf("%+v\n", person)
// {Name:andy Age:66 Gender:male Weight:120 FavoriteColor:[red blue]}
fmt.Printf("%p\n", &person.Name)  // 0xc0000ce080  地址相邻
fmt.Printf("%p\n", &person.Age)  // 0xc0000ce090  地址相邻

var me2 User = User{
	1,
	"geek",
	time.Now().Format("2006-01-02"),
	"北京市",
	"15588888888",
	"这是备注2",
}
fmt.Printf("%v\n", me2)  // {1 geek 2021-08-10 北京市 155888888888 这是备注2}

可以在函数外部使用,可以用来声明初始化全局变量

注意,上面最后一个逗号","不能省略,Go会报错,这个逗号有助于去扩展这个结构体

  • 使用短变量声明
// 变量 := 结构体{赋值}
func TestPerson32(t *testing.T) {
	p3 := Person3{
		Name: "ssgeek",
		Age:  24,
	}
	fmt.Println(p3)
}
  • 通过属性名
// 通过属性名指定,可以省略、无序
	var me4 User = User{ID:1, Name: "geek", Birthday: time.Now().Format("2006-01-02"), Addr: "北京市", Tel: "15588888888"}
	fmt.Println(me4)
  • 使用new函数进行初始化结构体指针对象
var me5 *User = new(User)
fmt.Printf("%T, %#v, %#v\n", me5, me5, *me5)
  • 使用指针初始化
var me6 *User
fmt.Printf("%T\n", me6)  // *main.User
fmt.Printf("%#v\n", me6)  // (*main.User)(nil)
var me7 = &User{ID: 2, Name: "geek"}
fmt.Printf("%T\n", me7)  // *main.User

5、属性的访问和修改

通过结构体对象名.属性名的方式来访问和修改对象的属性值

可以通过结构体指针对象的点操作直接对对象的属性值进行访问和修改

// 语法:结构体.成员名
fmt.Println((&person).Name)  // andy
fmt.Println(person.Name)  // andy
person.Age = 20
fmt.Println(person.Age)  // 20

golang中,访问结构体成员需要使用点号操作符,点号操作符也被称为选择器selector

func main() {
	p1 := Person{
		Name: "geek",
		Age:  9000,
	}
	fmt.Println(p1.Name, p1.Age)
	p1.Age += 1
	fmt.Println(p1.Name, p1.Age)
}

See you ~