Golang 結構體

前言

結構體是一種聚合的數據類型,是由零個或多個任意類型的值聚合成的實體。每一個值稱爲結構體的成員。git

用結構體的經典案例:學校的學生信息,每一個學生信息包含一個惟一的學生學號、學生的名字、學生的性別、家庭住址等等。全部的這些信息都須要綁定到一個實體中,能夠做爲一個總體單元被複制,做爲函數的參數或返回值,或者是被存儲到數組中,等等。數組

結構體也是值類型,所以能夠經過 new 函數來建立。數據結構

組成結構體類型的那些數據稱爲字段(fields)。字段有如下特性:app

  • 字段擁有本身的類型和值。
  • 字段名必須惟一。
  • 字段的類型也能夠是結構體,甚至是字段所在結構體的類型。

關於 Go 語言的類(class)函數

Go 語言中沒有「類」的概念,也不支持「類」的繼承等面向對象的概念。Go 語言的結構體與「類」都是複合結構體,但 Go 語言中結構體的內嵌配合接口比面向對象具備更高的擴展性和靈活性。佈局

Go 語言不只認爲結構體能擁有方法,且每種自定義類型也能夠擁有本身的方法。網站

結構體的定義

使用關鍵字 type 能夠將各類基本類型定義爲自定義類型,基本類型包括整型、字符串、布爾等。結構體是一種複合的基本類型,經過 type 定義爲自定義類型後,使結構體更便於使用。指針

結構體的定義格式以下:code

type 結構體類型名 struct {
    字段1 字段1類型
    字段2 字段2類型
    …
}

其中:對象

一、結構體類型名:標識自定義結構體的名稱,在同一個包內不能重複。

二、字段1:表示結構體字段名。結構體中的字段名必須惟一。

三、字段1類型:表示結構體字段的具體類型。

舉個例子,咱們定義一個 Student(學生)結構體,代碼以下:

type Student struct{
    id      int
    name    string
    age     int
    gender  int // 0 表示女生,1 表示男生
    addr    string
}

在這裏,Student 的地位等價於 int、byte、bool、string 等類型

一般一行對應一個結構體成員,成員的名字在前,類型在後

不過若是相鄰的成員類型若是相同的話能夠被合併到一行:

type Student struct{
    id          int
    name        string
    age, gender int
    addr        string
}

這樣咱們就擁有了一個 Student 的自定義類型,它有 id、name、age等字段。

這樣咱們使用這個 Student 結構體就可以很方便的在程序中表示和存儲學生信息了。

遞歸結構體

結構體類型能夠經過引用自身來定義。這在定義鏈表或二叉樹的元素(一般叫節點)時特別有用,此時節點包含指向臨近節點的連接(地址)。

以下所示,鏈表中的 su,樹中的 rile 分別是指向別的節點的指針。

鏈表

type Node struct {
    data float64
    su *Node
}

雙向鏈表

type Node struct {
    pr *Node
    data float64
    su *Node
}

二叉樹

type Tree struct {
    le *Tree
    data float64
    ri *Tree
}

結構體的實例化

結構體的定義只是一種內存佈局的描述,只有當結構體實例化時,纔會真正地分配內存,所以必須在定義結構體並實例化後才能使用結構體的字段。

實例化就是根據結構體定義的格式建立一份與格式一致的內存區域,結構體實例與實例間的內存是徹底獨立的。

Go語言能夠經過多種方式實例化結構體,根據實際須要能夠選用不一樣的寫法。

基本的實例化形式

結構體自己也是一種類型,咱們能夠像聲明內置類型同樣使用 var 關鍵字聲明結構體類型。

基本實例化格式以下:

var 結構體實例 結構體類型

對 Student 進行實例化,代碼以下:

type Student struct{
    id      int
    name    string
    age     int
    gender  int // 0 表示女生,1 表示男生
    addr    string
}

func main() {
    var stu1 Student
    stu1.id = 120100
    stu1.name = "Conan"
    stu1.age = 18
    stu1.gender = 1
    fmt.Println("stu1 = ", stu1)  // stu1 =  {120100 Conan 18 1 }
}

注意:沒有賦值的字段默認爲該字段類型的零值,此時 addr = ""

咱們能夠經過 點 "." 的方式來訪問結構體的成員變量,如 stu1.name,結構體成員變量的賦值方法與普通變量一致。

建立指針類型的結構體

Go 語言中,還可使用 new 關鍵字對類型(包括結構體、整型、浮點數、字符串等)進行實例化,結構體在實例化後會造成指針類型的結構體。

使用 new 的格式以下:

變量名 := new(類型)

Go 語言讓咱們能夠像訪問普通結構體同樣使用 點"." 來訪問結構體指針的成員,例如:

type Student struct{
    id      int
    name    string
    age     int
    gender  int // 0 表示女生,1 表示男生
    addr    string
}

func main() {
    stu2 := new(Student)
    stu2.id = 120101
    stu2.name = "Kidd"
    stu2.age = 23
    stu2.gender = 1
    fmt.Println("stu2 = ", stu2)  // stu2 =  &{120101 Kidd 23 1 }
}

通過 new 實例化的結構體實例在成員賦值上與基本實例化的寫法一致。

注意:在 Go 語言中,訪問結構體指針的成員變量時能夠繼續使用 點".",這是由於 Go 語言爲了方便開發者訪問結構體指針的成員變量,使用了語法糖(Syntactic sugar)技術,將 stu2.name 形式轉換爲 (*stu2).name。

取結構體的地址實例化

在 Go 語言中,對結構體進行 & 取地址操做時,視爲對該類型進行一次 new 的實例化操做,取地址格式以下:

變量名 := &結構體類型{}

取地址實例化是最普遍的一種結構體實例化方式,具體代碼以下:

type Student struct{
    id      int
    name    string
    age     int
    gender  int // 0 表示女生,1 表示男生
    addr    string
}

func main() {
	stu3 := &Student{}
	stu3.id = 120102
	stu3.name = "Lan"
	stu3.age = 18
	stu3.gender = 0
	fmt.Println("stu3 = ", stu3)  // stu3 =  &{120102 Lan 18 0 }
}

結構體的初始化

結構體在實例化時能夠直接對成員變量進行初始化,初始化有兩種形式分別是以字段「鍵值對」形式和多個值的列表形式。

鍵值對形式的初始化適合選擇性填充字段較多的結構體,多個值的列表形式適合填充字段較少的結構體。

特別地,還有一種初始化匿名結構體。

使用「鍵值對」初始化結構體

結構體可使用「鍵值對」(Key value pair)初始化字段,每一個「鍵」(Key)對應結構體中的一個字段,鍵的「值」(Value)對應字段須要初始化的值。

鍵值對的填充是可選的,不須要初始化的字段能夠不填入初始化列表中。

結構體實例化後字段的默認值是字段類型的零值,例如 ,數值爲 0、字符串爲 ""(空字符串)、布爾爲 false、指針爲 nil 等。

鍵值對初始化的格式以下:

變量名 := 結構體類型名{
    字段1: 字段1的值,
    字段2: 字段2的值,
    ...
}

注意:

一、字段名只能出現一次。

二、鍵值之間以 : 分隔,鍵值對之間以 , 分隔。

使用鍵值對形式初始化結構體的代碼以下:

stu4 := Student{
	id:     120103,
	name:   "Gin",
	age:    25,
	gender: 1,
	addr:   "unknown",
}
fmt.Println("stu4 = ", stu4) // stu4 =  {120103 Gin 25 1 unknown}

使用多個值的列表初始化結構體

Go語言能夠在「鍵值對」初始化的基礎上忽略「鍵」,也就是說,可使用多個值的列表初始化結構體的字段。

多個值使用逗號分隔初始化結構體,例如:

變量名 := 結構體類型名{
    字段1的值,
    字段2的值,
    ...
}

注意:

一、必須初始化結構體的全部字段

二、每個初始值的填充順序必須與字段在結構體中的聲明順序一致。

三、鍵值對與值列表的初始化形式不能混用。

使用多個值列表初始化結構體的代碼以下:

stu5 := Student{
	120104,
	"Kogorou",
	38,
	1,
	"毛利偵探事務所",
}
fmt.Println("stu5 = ", stu5) // stu5 =  {120104 Kogorou 38 1 毛利偵探事務所}

初始化匿名結構體

匿名結構體沒有類型名稱,無須經過 type 關鍵字定義就能夠直接使用。

例如:

package main

import (
    "fmt"
)

func main() {
    var user struct{name string; age int}
    user.name = "Conan"
    user.age = 18
    fmt.Println("user = ", user)  // user =  {Conan 18}
}

結構體的賦值與比較

結構體的賦值

當使用 = 對結構體賦值時,更改其中一個結構體的值不會影響另外的值:

package main

import (
	"fmt"
)

type Student struct {
	id     int
	name   string
	age    int
	gender int // 0 表示女生,1 表示男生
	addr   string
}

func main() {
	var stu1 Student
	stu1.id = 120100
	stu1.name = "Conan"
	stu1.age = 18
	stu1.gender = 1

	stu6 := stu1
	fmt.Println("stu1 = ", stu1) // stu1 =  {120100 Conan 18 1 }
	fmt.Println("stu6 = ", stu6) // stu6 =  {120100 Conan 18 1 }

	stu6.name = "柯南"
	fmt.Println("stu1 = ", stu1) // stu1 =  {120100 Conan 18 1 }
	fmt.Println("stu6 = ", stu6) // stu6 =  {120100 柯南 18 1 }
}

結構體的比較

若是結構體的所有成員都是能夠比較的,那麼結構體也是能夠比較的;若是結構體中存在不可比較的成員變量,好比說切片、map等,那麼結構體就不能比較。這時若是強行用 ==、!= 來進行判斷的話,程序會直接報錯,咱們能夠用 DeepEqual 來進行深度比較。

若是結構體的所有成員都是能夠比較的,那麼兩個結構體將可使用 == 或 != 運算符進行比較。

相等比較運算符 == 將比較兩個結構體的每一個成員,所以下面兩個比較的表達式是等價的:

type Student struct {
	id   int
	name string
}

func main() {
	var stu1 Student
	stu1.id = 120100
	stu1.name = "Conan"

	stu6 := stu1
	stu6.name = "柯南"

	fmt.Println(stu1.id == stu6.id && stu1.name == stu6.name) // "false"
	fmt.Println(stu1 == stu6)                                 // "false"
}

可比較的結構體類型和其餘可比較的類型同樣,能夠用於 map 的 key 類型。

結構體數組和切片

如今咱們有一個需求:用結構體存儲多個學生的信息。

咱們就能夠定義結構體數組來存儲,而後經過循環的方式,將結構體數組中的每一項進行輸出:

package main

import "fmt"

type student struct {
	id   int
	name string
	score  int
}

func main() {
	// 結構體數組
	students := [3]student{
		{101, "conan", 88},
		{102, "kidd", 78},
		{103, "lan", 98},
	}
	// 打印結構體數組的每一項
	for index, stu := range students {
		fmt.Println(index, stu.name)
	}
}

用結構體切片存儲同理。

練習1:計算以上學生成績的總分。

package main

import "fmt"

type student struct {
	id    int
	name  string
	score int
}

func main() {
	// 結構體數組
	students := [3]student{
		{101, "conan", 88},
		{102, "kidd", 78},
		{103, "lan", 98},
	}
    // 計算以上學生成績的總分
	sum := students[0].score
	for i, stuLen := 1, len(students); i < stuLen; i++ {
		sum += students[i].score
	}
	fmt.Println("總分是:", sum)
}

練習2:輸出以上學生成績中最高分。

package main

import "fmt"

type student struct {
	id    int
	name  string
	score int
}

func main() {
	// 結構體數組
	students := [3]student{
		{101, "conan", 88},
		{102, "kidd", 78},
		{103, "lan", 98},
	}

	// 輸出以上學生成績中最高分
	maxScore := students[0].score
	for i, stuLen := 1, len(students); i < stuLen; i++ {
		if maxScore < students[i].score {
			maxScore = students[i].score
		}
	}
	fmt.Println("最高分是:", maxScore)
}

結構體做爲 map 的 value

結構體做爲 map 的 value 示例以下:

package main

import "fmt"

type student struct {
	id    int
	name  string
	score int
}

func main0801() {
	// 結構體數組
	students := [3]student{
		{101, "conan", 88},
		{102, "kidd", 78},
		{103, "lan", 98},
	}
	// 打印結構體數組的每一項
	for index, stu := range students {
		fmt.Println(index, stu.name)
	}
	fmt.Println(students)
	// 計算以上學生成績的總分
	sum := students[0].score
	for i, stuLen := 1, len(students); i < stuLen; i++ {
		sum += students[i].score
	}
	fmt.Println("總分是:", sum)

	// 輸出以上學生成績中最高分
	maxScore := students[0].score
	for i, stuLen := 1, len(students); i < stuLen; i++ {
		if maxScore < students[i].score {
			maxScore = students[i].score
		}
	}
	fmt.Println("最高分是:", maxScore)
}

func main() {
	// 定義 map
	m := make(map[int]student)
	m[101] = student{101, "conan", 88}
	m[102] = student{102, "kidd", 78}
	m[103] = student{103, "lan", 98}
	fmt.Println(m) // map[101:{101 conan 88} 102:{102 kidd 78} 103:{103 lan 98}]

	for k, v := range m {
		fmt.Println(k, v)
	}
}

結構體切片做爲 map 的 value

結構體切片(本質上是切片)做爲 map 的 value 示例以下:

package main

import "fmt"

type student struct {
	id    int
	name  string
	score int
}

func main() {
	m := make(map[int][]student)
    
	m[101] = append(m[101], student{1, "conan", 88}, student{2, "kidd", 78})
	m[102] = append(m[101], student{1, "lan", 98}, student{2, "blame", 66})

	// 101 [{1 conan 88} {2 kidd 78}]
	// 102 [{1 conan 88} {2 kidd 78} {1 lan 98} {2 blame 66}]
	for k, v := range m {
		fmt.Println(k, v)
	}

	for k, v := range m {
		for i, data := range v {
			fmt.Println(k, i, data)
		}
	}
}

結構體做爲函數參數

你能夠像其它數據類型同樣將結構體類型做爲參數傳遞給函數:

結構體傳遞爲 值傳遞(形參單元和實參單元是不一樣的存儲區域,修改不會影響其它的值)

package main

import "fmt"

type student struct {
	id    int
	name  string
	score int
}

func foo(stu student) {
	stu.name = "lan"
}

func main() {
	stu := student{101, "conan", 88}
	fmt.Println(stu)  // {101 conan 88}
	foo(stu)
	fmt.Println(stu)  // {101 conan 88}
}

經過以上程序,咱們知道:Go 函數給參數傳遞值的時候是以複製的方式進行的。複製傳值時,若是函數的參數是一個 struct 對象,將直接複製整個數據結構的副本傳遞給函數。

這有兩個問題:

函數內部沒法修改傳遞給函數的原始數據結構,它修改的只是原始數據結構拷貝後的副本;

若是傳遞的原始數據結構很大,完整地複製出一個副本開銷並不小。

因此,若是條件容許,應當給須要 struct 實例做爲參數的函數傳 struct 的指針

PS:

  1. 結構體切片做爲函數參數是地址傳遞
  2. 結構體數組做爲函數參數是值傳遞

練習

定義結構體,存儲5名學生,三門成績,求出每名學生的總成績和平均成績。

結構體定義示例:

type student struct {
    id int
    name string
    score []int
}
package main

import "fmt"

type student struct {
	id    int
	name  string
	score []int
}

func main() {
	stus := []student{
		{101, "小明", []int{100, 99, 94}},
		{102, "小紅", []int{60, 123, 98}},
		{103, "小剛", []int{90, 109, 81}},
		{104, "小強", []int{55, 66, 99}},
		{105, "小花", []int{123, 65, 89}},
	}
	for _, stu := range stus {
		// 三門總成績
		sum := 0
		for _, value := range stu.score {
			sum += value
		}
		fmt.Printf("%s 的總成績爲: %d, 平均成績爲: %d\n", stu.name, sum, sum/len(stu.score))
	}
}

李培冠博客

歡迎訪問個人我的網站:

李培冠博客:lpgit.com

相關文章
相關標籤/搜索