實際編程時,經常需要用相關的不同類型的數據來描述一個數據對象。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之間添加空格。
再次強調:這個系列並不是教程,如果想系統的學習,博主可推薦學習資源。