type rect struct { width float64 length float64}
上面我们定义了一个矩形结构体,首先是关键是type表示要定义一个新的数据类型了,然后是新的数据类型名称rect,最后是struct关键字,表示这个高级数据类型是结构体类型。在上面的例子中,因为width和length的数据类型相同,还可以写成如下格式:
type rect struct { width, length float64}
好了,来用结构体干点啥吧,计算一下矩形面积。
package mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "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)}
上面的例子输出为:
80000width: 100 length: 200
也就说虽然在double_area函数里面我们将结构体的宽度和长度都加倍,但仍然没有影响main函数里面的rect变量的宽度和长度。
结构体组合函数
上面我们在main函数中计算了矩形的面积,但是我们觉得矩形的面积如果能够作为矩形结构体的“内部函数”提供会更好。这样我们就可以直接说这个矩形面积是多少,而不用另外去取宽度和长度去计算。现在我们看看结构体“内部函数”定义方法:
package mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "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: 5000color: blackmodel: iphone 5
在上面的例子中,我们在结构体iphone里面定义了一个phone变量phone,然后我们可以像正常的访问结构体成员一样访问phone的成员数据。但是我们原来的意思是“iphone也是(is-a)phone”,而这里的结构体iphone里面定义了一个phone变量,给人的感觉就是“iphone有一个(has-a)phone”,挺奇怪的。当然go也知道这种方式很奇怪,所以支持如下做法:
package mainimport ( "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: 5000color: blackmodel: iphone 5
在这个例子中,我们定义iphone结构体的时候,不再定义phone变量,直接把结构体phone类型定义在那里。然后iphone就可以像访问直接定义在自己结构体里面的成员一样访问phone的成员。
上面的例子中,我们演示了结构体的内嵌类型以及内嵌类型的成员访问,除此之外,假设结构体a内部定义了一个内嵌结构体b,那么a同时也可以调用所有定义在b上面的函数。
package mainimport ( "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: 5000color: blackmodel: iphone 5phone is ringing...
接口
我们先看一个例子,关于nokia手机和iphone手机都能够打电话的例子。
package mainimport ( "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 mainimport ( "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 mainimport ( "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语言知识请关注go语言教程栏目。
以上就是go语言结构体组合函数介绍的详细内容。
