【Golang】快速複習指南QuickReview(六)——結構體struct

實際編程時,經常需要用相關的不同類型的數據來描述一個數據對象。C#中有類(Class),結構(Struct),當然類就不介紹了。Golang中叫結構體(C,C++好像還是結構體),單詞還是Struct,無論是在Golang還是C#,struct都是一個值類型。

struct 結構體

1.C#的結構struct

1.1 構造函數

  • struct有默認無參構造函數,不能再顯式定義這個無參構造函數,編譯器始終會生成一個默認的構造器

結構不能包含顯式的無參數構造函數,默認構造器會把所有字段的自動初始化

public struct Position
        {
    		//public Position()
            //{} // 這是不允許的
    
            public double Lon { get; set; }
            public double Lat { get; set; }
        }
//沒有自定義構造函數,可不適用new
Position positon;
positon.Lon = 39.26;
positon.Lat = 115.25;
  • 自定義的有參構造函數必須初始化所有的字段
public struct Position
        {
            //自定義構造函數需要初始化所有字段、屬性
            public Position(double lon, double lat)
            {
                Lon = lon;
                Lat = lat;
            }

            //結構中不能實例屬性或字段初始值設定項
            //public double Lon { get; set; }=5.5;
            public double Lon { get; set; }
            public double Lat { get; set; }
        }
//有參構造函數,必須使用new爲struct類型的變量賦值
Position positon = new Position(39.26, 39.26);

1.2 方法

結構是可以包含自己的方法。

public struct Position
{
    //自定義構造函數需要初始化所有字段、屬性
    public Position(double lon, double lat)
    {
        Lon = lon;
    }

    //結構中不能實例屬性或字段初始值設定項
    //public double Lon { get; set; }=5.5;
    public double Lon { get; set; }
    public double Lat { get; set; }
    
    //重寫方法
    public override string ToString() => $"經度:{Lon}, 緯度{Lat})";
}

雖然struct在實際開發過程中使用頻率較低,但是使用時需要注意:

  • 結構類型變量作爲參數傳遞給方法從方法返回結構類型值時,將複製結構類型的整個實例。這可能會影響高性能方案中涉及大型結構類型的代碼的性能。 通過按引用傳遞結構類型變量,可以避免值複製操作。 使用 ref、out 或 in 方法參數修飾符,指示必須按引用傳遞參數。使用 ref 返回值按引用返回方法結果。在Golang中也會存在這個問題,下一節會提到。

2.Golang的結構體struct

2.1 定義

像定義函數類型那樣 type開頭,只是把func()換爲關鍵字struct

type person struct {
	name string
	age  int8
}
//結構體匿名字段
type student struct {
	string
	int
}
func main() {
    var p1 person
	p1.name = "RandyField"
	p1.age = 28
    
    //匿名結構體
    var user struct {
		Name string
		Age  int
	}
	user.Name = "Randy"
	user.Age = 18  
}

2.2* 結構體指針--重點

2.2.1 new

type person struct {
	name string
	age  int8
}
//結構體匿名字段
/*
    這裏匿名字段的說法並不代表沒有字段名,
    而是默認會採用類型名作爲字段名,
    結構體要求字段名稱必須唯一,
    因此一個結構體中同種類型的匿名字段只能有一個。
*/
type Student struct {
	string
	int
}
func main() {
    //new分配結構體實例的指針(內存地址)   實例化
	var p2 = new(person)
    fmt.Printf("the type of p2 is %T\n", p2) //*main.person
    
    //沒有初始化的結構體 所有的成員變量都是對應類型的零值
	fmt.Printf("p2=%#v\n", p2)
    
   	stu := &Student{}
	stu.int = 18
	stu.string = "中學生"
}
the type of p2 is *main.person
p2=&main.person{name:"", age:0}

2.2.2 &

type person struct {
	name string
	age  int8
}
func main() {
	p3 := &person{} //使用&對結構體進行取地址操作=> 使用new實例化
	p3.name = "kobe"
	p3.age = 30    //這是語法糖
	(*p3).age = 29 //底層
}
  • 初始化

當某些字段沒有初始值的時候,該字段可以不寫。此時,沒有指定初始值的字段的值就是該字段類型的零值。這點跟C#存在有參構造函數的結構是不一致。

func main() {
    p4 := person{
            name: "RandyField",
            age:  18,
        }
    
    fmt.Printf("p4=%#v\n", p4)

	//結構體指針初始化
	p5 := &person{
		name: "RandyField",
		age:  28,
	}
    fmt.Printf("p5=%#v\n", p5)
}
  • 另類初始化

不建議使用,但是爲了能看懂別人的開源代碼,還是知道機制爲妙。

p6 := &person{
    "RandyField",
    28,
}
fmt.Printf("p6=%#v\n", p6)

2.3* 空結構體

特殊地:空結構體是不佔用空間的。

var v struct{}
fmt.Println(unsafe.Sizeof(v))  // 0

2.4 構造函數

Golang是沒有構造函數的,但是我們可以通過方法去創建一個返回struct

type person struct {
	name string
	age  int8
}

// 複雜的結構體,值拷貝性能開銷會比較大,故返回結構體指針。
func newPerson(name string, age int8) *person {
	return &person{
		name: name,
		age:  age,
	}
}

2.5 方法

Golang結構體的方法並不像C#那樣直接就在定義的{}中定義即可。它必須分開定義,這就出現一個難題,定義的這個方法是屬於這個結構體的,現在分開定義,怎麼辦?接收者應運而生,指明這個方法是屬於結構體,只能通過結構體來調用。

func (接收者變量 接收者類型) 方法名(參數列表) (返回參數) {
    函數體
}
type person struct {
	name string
	age  int8
}

func (p *person) MakeMoney(work string) (res string) {
	return "賺錢了"
}
  • 接收者既可以是指針類型,也可以是值類型
    • 值類型,如果做出了操作,只針對副本有效

使用指針類型場景:

  • 需要修改接收者中的值
  • 接收者是拷貝代價比較大的大對象
  • 如果有某個方法使用了指針類型接收者,其他的方法也應該使用指針類型接收者。

2.5.1 類型定義 與 類型別名

方法的接收者不僅僅可以是結構體,還可以是類型定義

type NewInt int  //類型定義 新類型  可以作爲方法的接收者
type MyInt = int //類型別名 編譯完成時並不會有`MyInt`類型, 這個不能作爲方法接收者的
func (m MyInt) Say() {
	fmt.Println("我其實是int。")
}

2.6 嵌套結構體

type student struct {
	name string
	age  int
}

type middleSchoolStudent struct {
	lesson []string
	*student
}

func (stu *student) play(sport []string) {
	for _, v := range sport {
		fmt.Println(stu.name, "參加如下運行項目:", v)
	}
}
func (m *middleSchoolStudent) learn() {
	for _, v := range m.lesson {
		fmt.Println(m.name, "學習如下課程:", v)
	}
}

func main(){
    s := &middleSchoolStudent{
        lesson: []string{"語文", "數學", "英語", "物理", "化學"},
        student: &student{
            name: "小明",
            age:  13,
        },
    }

    s.learn()
    s.play([]string{"籃球", "足球", "乒乓球"})
}
小明 學習如下課程: 語文
小明 學習如下課程: 數學
小明 學習如下課程: 英語
小明 學習如下課程: 物理
小明 學習如下課程: 化學
小明 參加如下運行項目: 籃球
小明 參加如下運行項目: 足球
小明 參加如下運行項目: 乒乓球

有點像繼承,其實這又是一個語法糖, s.play([]string{"籃球", "足球", "乒乓球"}),內部是s.student.play([]string{"籃球", "足球", "乒乓球"})

如果在定義時,給嵌套結構體一個字段名稱:

type middleSchoolStudent struct {
	lesson []string
	stu *student
}

調用方式必須爲:s.stu.play([]string{"籃球", "足球", "乒乓球"})

2.7 Tag

Tag是結構體的元信息,可以在運行的時候通過反射的機制讀取出來。 Tag在結構體字段的後方定義,由一對反引號包裹起來,具體的格式如下:

`key1:"value1" key2:"value2"`
type MiddleSchoolStudent struct {
	Lesson []string
	Name   string `json:"studentName"`
	Age    int    `json:"studentAge"`
}
func main(){
	ms := &MiddleSchoolStudent{
		Name:   "小明",
		Age:    13,
		Lesson: []string{"語文", "數學", "英語", "物理", "化學"},
	}
	data, err := json.Marshal(ms)
	if err != nil {
		fmt.Println("json marshal failed")
		return
	}
	fmt.Printf("json:%s\n", data)
	// fmt.Println(data)
}
json:{"Lesson":["語文","數學","英語","物理","化學"],"studentName":"小明","studentAge":13}

注意事項: 爲結構體編寫Tag時,必須嚴格遵守鍵值對的規則。結構體標籤的解析代碼的容錯能力很差,一旦格式寫錯,編譯和運行時都不會提示任何錯誤,通過反射也無法正確取值。例如不要在key和value之間添加空格。

再次強調:這個系列並不是教程,如果想系統的學習,博主可推薦學習資源。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章