Go語言提供的結構體就是把使用各種資料型別定義的不同變數組合起來的高階資料型別。閒話不多說,看例子:
type Rect struct { width float64 length float64}
上面我們定義了一個矩形結構體,首先是關鍵是type表示要定義一個新的資料型別了,然後是新的資料型別名稱Rect,最後是struct關鍵字,表示這個高階資料型別是結構體類型。在上面的例子中,因為width和length的資料型別相同,還可以寫成如下格式:
type Rect struct { width, length float64}
好了,來用結構體乾點啥吧,計算一下矩形面積。
package main import ( "fmt" ) type Rect struct { width, length float64 } func main() { var rect Rect rect.width = 100 rect.length = 200 fmt.Println(rect.width * rect.length) }
從上面的例子看到,其實結構體類型和基礎資料型別使用方式差不多,唯一的差別就是結構體型別可以透過.來存取內部的成員。包括賦予內部成員值和讀取內部成員值。
在上面的例子中,我們是用var關鍵字先定義了一個Rect變量,然後對它的成員賦值。我們也可以使用初始化的方式來給Rect變數的內部成員賦值。
package main import ( "fmt" ) type Rect struct { width, length float64 } func main() { var rect = Rect{width: 100, length: 200} fmt.Println(rect.width * rect.length) }
#當然如果你知道結構體成員定義的順序,也可以不使用key:value的方式賦值,直接按照結構體成員定義的順序給它們賦值。
package main import ( "fmt" ) type Rect struct { width, length float64 } func main() { var rect = Rect{100, 200} fmt.Println("Width:", rect.width, "* Length:", rect.length, "= Area:", rect.width*rect.length) }
輸出結果為
Width: 100 * Length: 200 = Area: 20000
結構體參數傳遞方式
#我們說過,Go函數的參數傳遞方式是值傳遞,這句話對結構體也是適用的。
package main import ( "fmt" ) type Rect struct { width, length float64 } func double_area(rect Rect) float64 { rect.width *= 2 rect.length *= 2 return rect.width * rect.length } func main() { var rect = Rect{100, 200} fmt.Println(double_area(rect)) fmt.Println("Width:", rect.width, "Length:", rect.length) }
上面的例子輸出為:
80000 Width: 100 Length: 200
也就說雖然在double_area函數裡面我們將結構體的寬度和長度都加倍,但仍然沒有影響main函數裡面的rect變數的寬度和長度。
結構體組合函數
上面我們在main函數中計算了矩形的面積,但是我們覺得矩形的面積如果能夠作為矩形結構體的「內部函數”提供會更好。這樣我們就可以直接說這個矩形面積是多少,而不用另外去取寬度和長度去計算。現在我們來看看結構體「內部函數」定義方法:
package main import ( "fmt" ) type Rect struct { width, length float64 } func (rect Rect) area() float64 { return rect.width * rect.length } func main() { var rect = Rect{100, 200} fmt.Println("Width:", rect.width, "Length:", rect.length, "Area:", rect.area()) }
咦?這個是什麼“內部方法”,根本沒有定義在Rect資料類型的內部啊?
確實如此,我們看到,雖然main函數中的rect變數可以直接呼叫函數area()來取得矩形面積,但是area()函數確實沒有定義在Rect結構體內部,這點和C語言的有很大不同。 Go使用組合函數的方式來為結構體定義結構體方法。我們仔細看一下上面的area()函數定義。
首先是關鍵字func表示這是一個函數,第二個參數是結構體類型和實例變量,第三個是函數名稱,第四個是函數傳回值。這裡我們可以看出area()函數和普通函數定義的差別就在於area()函數多了一個結構體類型限定。這樣一來Go就知道這是一個為結構體定義的方法。
這裡要注意一點就是定義在結構體上面的函數(function)一般叫做方法(method)。
結構體和指標
我們在指標一節講到過,指標的主要作用就是在函數內部改變傳遞進來變數的值。對於上面的計算矩形面積的例子,我們可以修改一下程式碼如下:
package main import ( "fmt" ) type Rect struct { width, length float64 } func (rect *Rect) area() float64 { return rect.width * rect.length } func main() { var rect = new(Rect) rect.width = 100 rect.length = 200 fmt.Println("Width:", rect.width, "Length:", rect.length, "Area:", rect.area()) }
上面的例子中,使用了new函數來建立一個結構體指標rect,也就是說rect的類型是*Rect,結構體遇到指標的時候,你不需要使用*去存取結構體的成員,直接使用.引用就可以了。所以上面的範例我們直接使用rect.width=100 和rect.length=200來設定結構體成員值。因為這時候rect是結構體指針,所以我們定義area()函數的時候結構體限定型別為*Rect。
其實在計算面積的這個例子中,我們不需要改變矩形的寬或長度,所以定義area函數的時候結構體限定型別還是Rect也是可以的。如下:
package main import ( "fmt" ) type Rect struct { width, length float64 } func (rect Rect) area() float64 { return rect.width * rect.length } func main() { var rect = new(Rect) rect.width = 100 rect.length = 200 fmt.Println("Width:", rect.width, "Length:", rect.length, "Area:", rect.area()) }
這裡Go夠聰明,所以rect.area()也是可以的。
至於使不使用結構體指標和使不使用指標的出發點是一樣的,那就是你是否試圖在函數內部改變傳遞進來的參數的值。再舉個例子如下:
package main import ( "fmt" ) type Rect struct { width, length float64 } func (rect *Rect) double_area() float64 { rect.width *= 2 rect.length *= 2 return rect.width * rect.length } func main() { var rect = new(Rect) rect.width = 100 rect.length = 200 fmt.Println(*rect) fmt.Println("Double Width:", rect.width, "Double Length:", rect.length, "Double Area:", rect.double_area()) fmt.Println(*rect) }
這個例子的輸出是:
{100 200}
Double Width: 200 Double Length: 400 Double Area: 80000
#{200 400}
結構體內嵌型別
我們可以在一個結構體內部定義另一個結構體型別的成員。例如iPhone也是Phone,我們看下範例:
package main import ( "fmt" ) type Phone struct { price int color string } type IPhone struct { phone Phone model string } func main() { var p IPhone p.phone.price = 5000 p.phone.color = "Black" p.model = "iPhone 5" fmt.Println("I have a iPhone:") fmt.Println("Price:", p.phone.price) fmt.Println("Color:", p.phone.color) fmt.Println("Model:", p.model) }
輸出結果為:
I have a iPhone: Price: 5000 Color: Black Model: iPhone 5
在上面的例子中,我们在结构体IPhone里面定义了一个Phone变量phone,然后我们可以像正常的访问结构体成员一样访问phone的成员数据。但是我们原来的意思是“iPhone也是(is-a)Phone”,而这里的结构体IPhone里面定义了一个phone变量,给人的感觉就是“iPhone有一个(has-a)Phone”,挺奇怪的。当然Go也知道这种方式很奇怪,所以支持如下做法:
package main import ( "fmt" ) type Phone struct { price int color string } type IPhone struct { Phone model string } func main() { var p IPhone p.price = 5000 p.color = "Black" p.model = "iPhone 5" fmt.Println("I have a iPhone:") fmt.Println("Price:", p.price) fmt.Println("Color:", p.color) fmt.Println("Model:", p.model) }
输出结果为
I have a iPhone: Price: 5000 Color: Black Model: iPhone 5
在这个例子中,我们定义IPhone结构体的时候,不再定义Phone变量,直接把结构体Phone类型定义在那里。然后IPhone就可以像访问直接定义在自己结构体里面的成员一样访问Phone的成员。
上面的例子中,我们演示了结构体的内嵌类型以及内嵌类型的成员访问,除此之外,假设结构体A内部定义了一个内嵌结构体B,那么A同时也可以调用所有定义在B上面的函数。
package main import ( "fmt" ) type Phone struct { price int color string } func (phone Phone) ringing() { fmt.Println("Phone is ringing...") } type IPhone struct { Phone model string } func main() { var p IPhone p.price = 5000 p.color = "Black" p.model = "iPhone 5" fmt.Println("I have a iPhone:") fmt.Println("Price:", p.price) fmt.Println("Color:", p.color) fmt.Println("Model:", p.model) p.ringing() }
输出结果为:
I have a iPhone: Price: 5000 Color: Black Model: iPhone 5 Phone is ringing...
接口
我们先看一个例子,关于Nokia手机和iPhone手机都能够打电话的例子。
package main import ( "fmt" ) type NokiaPhone struct { } func (nokiaPhone NokiaPhone) call() { fmt.Println("I am Nokia, I can call you!") } type IPhone struct { } func (iPhone IPhone) call() { fmt.Println("I am iPhone, I can call you!") } func main() { var nokia NokiaPhone nokia.call() var iPhone IPhone iPhone.call() }
我们定义了NokiaPhone和IPhone,它们都有各自的方法call(),表示自己都能够打电话。但是我们想一想,是手机都应该能够打电话,所以这个不算是NokiaPhone或是IPhone的独特特点。否则iPhone不可能卖这么贵了。
再仔细看一下接口的定义,首先是关键字type,然后是接口名称,最后是关键字interface表示这个类型是接口类型。在接口类型里面,我们定义了一组方法。
Go语言提供了一种接口功能,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口,不一定非要显式地声明要去实现哪些接口啦。比如上面的手机的call()方法,就完全可以定义在接口Phone里面,而NokiaPhone和IPhone只要实现了这个接口就是一个Phone。
package main import ( "fmt" ) type Phone interface { call() } type NokiaPhone struct { } func (nokiaPhone NokiaPhone) call() { fmt.Println("I am Nokia, I can call you!") } type IPhone struct { } func (iPhone IPhone) call() { fmt.Println("I am iPhone, I can call you!") } func main() { var phone Phone phone = new(NokiaPhone) phone.call() phone = new(IPhone) phone.call() }
在上面的例子中,我们定义了一个接口Phone,接口里面有一个方法call(),仅此而已。然后我们在main函数里面定义了一个Phone类型变量,并分别为之赋值为NokiaPhone和IPhone。然后调用call()方法,输出结果如下:
I am Nokia, I can call you!
I am iPhone, I can call you!
以前我们说过,Go语言式静态类型语言,变量的类型在运行过程中不能改变。但是在上面的例子中,phone变量好像先定义为Phone类型,然后是NokiaPhone类型,最后成为了IPhone类型,真的是这样吗?
原来,在Go语言里面,一个类型A只要实现了接口X所定义的全部方法,那么A类型的变量也是X类型的变量。在上面的例子中,NokiaPhone和IPhone都实现了Phone接口的call()方法,所以它们都是Phone,这样一来是不是感觉正常了一些。
我们为Phone添加一个方法sales(),再来熟悉一下接口用法。
package main import ( "fmt" ) type Phone interface { call() sales() int } type NokiaPhone struct { price int } func (nokiaPhone NokiaPhone) call() { fmt.Println("I am Nokia, I can call you!") } func (nokiaPhone NokiaPhone) sales() int { return nokiaPhone.price } type IPhone struct { price int } func (iPhone IPhone) call() { fmt.Println("I am iPhone, I can call you!") } func (iPhone IPhone) sales() int { return iPhone.price } func main() { var phones = [5]Phone{ NokiaPhone{price: 350}, IPhone{price: 5000}, IPhone{price: 3400}, NokiaPhone{price: 450}, IPhone{price: 5000}, } var totalSales = 0 for _, phone := range phones { totalSales += phone.sales() } fmt.Println(totalSales) }
输出结果:
14200
上面的例子中,我们定义了一个手机数组,然后计算手机的总售价。可以看到,由于NokiaPhone和IPhone都实现了sales()方法,所以它们都是Phone类型,但是计算售价的时候,Go会知道调用哪个对象实现的方法。
接口类型还可以作为结构体的数据成员。
假设有个败家子,iPhone没有出的时候,买了好几款Nokia,iPhone出来后,又买了好多部iPhone,老爸要来看看这小子一共花了多少钱。
import ( "fmt" ) type Phone interface { sales() int } type NokiaPhone struct { price int } func (nokiaPhone NokiaPhone) sales() int { return nokiaPhone.price } type IPhone struct { price int } func (iPhone IPhone) sales() int { return iPhone.price } type Person struct { phones []Phone name string age int } func (person Person) total_cost() int { var sum = 0 for _, phone := range person.phones { sum += phone.sales() } return sum } func main() { var bought_phones = [5]Phone{ NokiaPhone{price: 350}, IPhone{price: 5000}, IPhone{price: 3400}, NokiaPhone{price: 450}, IPhone{price: 5000}, } var person = Person{name: "Jemy", age: 25, phones: bought_phones[:]} fmt.Println(person.name) fmt.Println(person.age) fmt.Println(person.total_cost()) }
这个例子纯为演示接口作为结构体数据成员,如有雷同,纯属巧合。这里面我们定义了一个Person结构体,结构体内部定义了一个手机类型切片。另外我们定义了Person的total_cost()方法用来计算手机花费总额。输出结果如下:
Jemy
25
14200
更多go语言知识请关注PHP中文网go语言教程栏目。
以上是go語言結構體組合函數介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!