基本上到这里的时候,就是上了一个台阶了。Go的精华特点即将展开。

结构体定义

上面我们说过Go的指针和C的不同,结构体也是一样的。Go是一门删繁就简的语言,一切令人困惑的特性都必须去掉。

结构体使用各种数据类型定义不同变量组合起来高级数据类型
type Rect struct {width float64length float64
}
type定义一个新的数据类型了Rectstructwidth和length的数据类型相同
type Rect struct {width, length float64
}

好了,来用结构体干点啥吧,计算一下矩形面积。

package main
import ("fmt"
)
type Rect struct {width, length float64
}
func main() {var rect Rectrect.width = 100rect.length = 200fmt.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)
}
结构体参数传递方式
Go函数的参数传递方式是值传递对结构体也是适用的
package main
import ("fmt"
)
type Rect struct {width, length float64
}
func double_area(rect Rect) float64 {rect.width *= 2rect.length *= 2return 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数据类型的内部啊?

Go使用组合函数的方式来为结构体定义结构体方法
func结构体类型和实例变量函数名称函数返回值区别就在于多了一个结构体类型限定方法
定义在结构体上面的函数(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 = 100rect.length = 200fmt.Println("Width:", rect.width, "Length:", rect.length,"Area:", rect.area())
}
不需要使用*去访问结构体的成员.rect.width=100rect.length=200*Rect
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 = 100rect.length = 200fmt.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 *= 2rect.length *= 2return rect.width * rect.length
}
func main() {var rect = new(Rect)rect.width = 100rect.length = 200fmt.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 intcolor string
}
type IPhone struct {phone Phonemodel string
}
func main() {var p IPhonep.phone.price = 5000p.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也是(is-a)Phone”“iPhone有一个(has-a)Phone”
package main
import ("fmt"
)
type Phone struct {price intcolor string
}
type IPhone struct {Phonemodel string
}
func main() {var p IPhonep.price = 5000p.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
不再定义Phone变量直接把结构体Phone类型定义在那里像访问直接定义在自己结构体里面的成员一样访问Phone的成员,有点类似与继承

上面的例子中,我们演示了结构体的内嵌类型以及内嵌类型的成员访问,除此之外,假设结构体A内部定义了一个内嵌结构体B,那么A同时也可以调用所有定义在B上面的函数。

package main
import ("fmt"
)
type Phone struct {price intcolor string
}
func (phone Phone) ringing() {fmt.Println("Phone is ringing...")
}
type IPhone struct {Phonemodel string
}
func main() {var p IPhonep.price = 5000p.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 NokiaPhonenokia.call()var iPhone IPhoneiPhone.call()
}

我们定义了NokiaPhone和IPhone,它们都有各自的方法call(),表示自己都能够打电话。但是我们想一想,是手机都应该能够打电话,所以这个不算是NokiaPhone或是IPhone的独特特点。否则iPhone不可能卖这么贵了。

接口的定义type接口名称interface在接口类型里面,我们定义了一组方法
任何其他类型只要实现了这些方法就是实现了这个接口不一定非要显式地声明
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 Phonephone = 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语言式静态类型语言,变量的类型在运行过程中不能改变
一个类型A只要实现了接口X所定义的全部方法A类型的变量X类型的变量

我们为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 = 0for _, phone := range phones {totalSales += phone.sales()}fmt.Println(totalSales)
}

输出结果:

14200

上面的例子中,我们定义了一个手机数组,然后计算手机的总售价。可以看到,由于NokiaPhone和IPhone都实现了sales()方法,所以它们都是Phone类型,但是计算售价的时候,Go会知道调用哪个对象实现的方法。

接口类型还可以作为结构体的数据成员。

假设有个败家子,iPhone没有出的时候,买了好几款Nokia,iPhone出来后,又买了好多部iPhone,老爸要来看看这小子一共花了多少钱。

package main
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 []Phonename   stringage    int
}
func (person Person) total_cost() int {var sum = 0for _, 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的结构体和接口的实现方法可谓删繁就简,去除了很多别的语言令人困惑的地方,而且学习难度也不大,很容易上手。不过由于思想比较独到,也有可能会有人觉得功能太简单而无用,这个就各有看法了,不过在逐渐的使用过程中,我们会慢慢领悟到这种设计所带来的好处,以及所避免的问题。