10 结构与方法
组成结构体类型的那些数据称为字段,每个字段都有一个类型和一个名字;在一个结构体中,字段名字必须是唯一的。
10.1 结构体定义
type identifier struct {
field1 type1
field2 type2
}
结构体的字段可以是任何类型,可以是结构体本身,函数,接口等。
初始化方法
type person struct {
name string
age int
sex string
height float32
weight int
}
type myStruct struct {
i int
}
func main() {
var tom person
tom.sex = "male"
tom.name = "tom"
tom.age = 10
fmt.Println(tom)
fmt.Printf("%p\n", &tom)
//lilly的类型 是 指向person实例的指针
lilly := new(person)
lilly.sex = "female"
fmt.Println(lilly) //&{ 0 female 0 0}
fmt.Printf("%p\n", &lilly) //0xc000006030
fmt.Printf("%v\n", *lilly) //{ 0 female 0 0}
var v myStruct
//var p *myStruct
v.i = 10
fmt.Println(v.i)
//fmt.Println(p.i)
ms := &myStruct{99}
fmt.Println(getStructI(ms))
fmt.Println(*ms)
}
func getStructI(strcut *myStruct) (num int) {
strcut.i = 1000
num = strcut.i
return
}
{tom 10 male 0 0}
0xc000062040
&{ 0 female 0 0}
0xc000006030
{ 0 female 0 0}
10
1000
{1000}
定义指向结构体指针的方法
//混合字面量语法
&struct1{a, b, c}
等价于
new()
type Interval struct {
start int
end int
}
intr := Interval{0,3}//start = 0 end = 3
intr := Interval{end:3,start:10}
intr := Interval{end:11}//start为默认值0
结构体和指向它的指针的内存布局
type Point struct{x,y int}
使用new初始化:
使用结构体字面量初始化
pp := &Point{10,20}
例子
type People struct {
firstName string
lastName string
}
func main() {
var tom People
tom.firstName = "Tom"
tom.lastName = "James"
toUpper(&tom)
fmt.Printf("toUpper firstName: %s\n", tom.firstName)
fmt.Printf("toUpper lastName: %s\n", tom.lastName)
l := new(People)
//可以直接通过指针l 对结构体字段赋值 也可以用*指针变量 拿到指针指向的变量 对结构体字段进行赋值
l.lastName = "Ben"
l.firstName = "Lilly"
(*l).lastName = "Black"
toUpper(l)
fmt.Printf("toUpper firstName: %s\n", l.firstName)
fmt.Printf("toUpper lastName: %s\n", l.lastName)
x := &People{"James", "Harden"}
toUpper(x)
fmt.Printf("toUpper firstName: %s\n", x.firstName)
fmt.Printf("toUpper lastName: %s\n", x.lastName)
}
func toUpper(p *People) {
p.firstName = strings.ToUpper(p.firstName)
p.lastName = strings.ToUpper(p.lastName)
}
toUpper firstName: TOM
toUpper lastName: JAMES
toUpper firstName: LILLY
toUpper lastName: BLACK
toUpper firstName: JAMES
toUpper lastName: HARDEN
结构体的内存布局
Go中,结构体和它所包含的数据在内存中是以连续块的形式存在的。
type Rect1 struct {Min, Max Point }
type Rect2 struct {Min, Max *Point }
递归结构体
结构体类型可以通过引用自身来定义。
type Node struct{
//data存放数据 su指针指向后继节点
data float64
su *Node
}
链表第一个元素为head指向下一个元素,最后一个元素交tail,它没有后继节点,是链表的末尾。
结构体转换
当给结构体定义一个alias(别名)类型时,此结构体类型和它的alias类型都有相同的底层类型,它们之间可以相互转换。
type Boy struct {
name string
age int
}
type Man Boy
func main(){
boy := Boy{"tom", 10}
man := Man{"Ben", 34}
//var c Boy = man//Cannot use 'man' (type Man) as the type Boy
fmt.Println(boy)
fmt.Println(man)
//fmt.Println(c)
var c = Boy(man)
var d = Man(boy)
fmt.Println(c)
fmt.Println(d)
}
10.2 使用工厂方法创建结构体实例
工厂方法的名字以new或New开头。
type File struct{
fd int
name string
}
func NewFile(fd int, name string) *File{
if fd < 0 {
return nil
}
return &File{fd,name}
}
例子
type File struct {
fd int
name string
}
func main() {
file1 := NewFile(10, "test")
file2 := NewFile(342, "test1")
fmt.Println(file1)
fmt.Println(file2)
}
//结构体对应的工厂方法
func NewFile(fd int, name string) *File {
if fd < 0 {
return nil
}
return &File{fd, name}
}
&{10 test}
&{342 test1}
如何强制使用工厂方法
定义结构体为私有,定义工厂方法为公有,则可以在外部调用工厂方法创建结构体
type matrix struct{
}
func NewMatrix(params) *matrix {
m :=new(matrix)//初始化m
return m
}
在其他包中使用工厂方法
package main
import "matrix"
x := matrix.NewMatrix(...)
map,struct,new(),make()
type Bar struct {
name string
address string
}
type Foo map[string]string
func main() {
y := new(Bar)
y.name = "王朝酒吧"
y.address = "互动司法是的会"
//x := make(Bar)//Cannot make Bar
z := make(Foo)
z["aa"] = "AA"
z["bb"] = "BB"
z["cc"] = "CC"
q := new(Foo)
(*q)["aa"] = "djfsd"//assignment to entry in nil map
(*q)["bb"] = "32432432"
(*q)["cc"] = "dgafdsaf"
}
10.3 使用自定义包中的结构体
自定义包中的结构体
package pack1
type expStruct struct {
Name string
Age int
Sex string
}
func MakeExpStruct(age int, name, sex string) *expStruct {
m := &expStruct{name, age, sex}
return m
}
其他的包调用pack1包中的工厂方法
import (
"fmt"
"hello/hello1"
)
func main() {
qqq := pack1.MakeExpStruct(10, "jack", "male")
fmt.Println(qqq)
}
&{jack 10 male}
10.4 带标签的结构体
结构体的字段除了名字和类型外,还可以有一个可选的标签(tag):它是附属于字段的字符串,可以是文档或其他重要的标记。标签的内容不可以在一般的编程中使用,只有包reflect(反射)能获取到。
type Boy struct {
name string "年龄"
age int
}
func main() {
boy := Boy{"tom", 10}
fmt.Println(boy)
boyType := reflect.TypeOf(boy)
ixField := boyType.Field(0)
fmt.Printf("%v\n", ixField.Tag)//年龄
}
10.5 匿名字段和内嵌结构体
结构体可以包含一个或多个匿名(或内嵌)字段,即这些字段没有显式的名字,自由字段的类型是必须的,此时类型就是字段的名字。匿名字段本身可以是一个结构体类型,即结构体可以包含内嵌结构体。
type inners struct {
in1 int
in2 int
}
type outerS struct {
b int
c float32
int //匿名
inners //匿名
}
func main() {
//使用结构体字面量
i := inners{1, 1}
o := outerS{10, 34.3432, 90, i}
fmt.Println(o)
outer := new(outerS)
outer.in1 = 1
outer.in2 = 2
outer.b = 10
outer.c = 11
outer.int = 99
fmt.Printf("%v\n", *outer)
}
{10 34.3432 90 {1 1}}
{10 11 99 {1 2}}
内嵌结构体
结构体也可以作为 一个结构体的字段,也可以作为匿名字段来使用。如上文,外层结构体通过outer.in1直接进入内层结构体的字段,内嵌结构体也可以来自其他包。
命名冲突
当两个字段用于相同的名字
1.外层名字会覆盖内层名字(但两者的内存空间都保留),这提供了一种重载字段或方法的方式
2.如果相同的名字在同一级别出现了两次,如果这个名字被程序使用了,会引发一个错误(不使用不会报错)
type A struct{a int}
type B struct{a,b int}
type C struct {
A
B
}
func main(){
//var c C
//不知道是 c.A.a 还是 c.B.a
//c.a//Ambiguous reference 'a'
}
10.6 方法
10.6.1 方法是什么
Go方法是作用在接收者(receiver)上的一个函数,接收者是某种类型(不能是接口,指针)
一个结构体加上它的方法 等价于面向对象中的一个类。区别是:Go中,类型的代码和绑定在它上面的方法的代码可以不放置在一起,可以存放在不同的源文件中,但它们必须在同一个包中。
定义方法的一般格式:
func (recv receiver_type) methodName(parameter_list)(return_value_list) {...}
在方法名之前,func关键字之后的括号中指定receiver。
如果recv是receiver的实例,Methods1是它的方法名,那么方法调用为recv.Method1()。
如果方法不需要使用recv的值,可以用_代替它。
func (_ receiver_type) methodName(parameter_list) (return_value_list) { ... }
recv就像是面向对象中的this。
例子:
package main
import "fmt"
type TwoInts struct {
a int
b int
}
type IntVector []int
func main() {
tow1 := new(TwoInts)
tow1.a = 12
tow1.b = 10
fmt.Println(tow1.AddThem())
fmt.Println(tow1.AddToParam(20))
fmt.Println(IntVector{1, 2, 3}.Sum())
}
func (this *TwoInts) AddThem() int {
return this.a + this.b
}
func (this *TwoInts) AddToParam(param int) int {
return this.a + this.b + param
}
func (v IntVector) Sum() (s int) {
for _, x := range v {
s += x
}
return
}
22
42
6
类型和作用在它上面定义的方法必须在同一个包里定义,所以不能在int、float或类似的这些类型上定义方法。
有两个间接的方法:
1.可以先定义该类型(比如int、float)的别名类型,然后再为别名类型定义方法。
2.将此类型作为匿名类型嵌入在一个新的结构体中,此方法只在这个别名类型上有效。
package main
import (
"fmt"
"time"
)
type myTime struct {
time.Time //匿名属性
}
type aliasInt int
func main() {
m := myTime{time.Now()}
fmt.Println(m.first4Chars())
var a aliasInt = 10
fmt.Println(a.add())
}
//截取time类型前4位
func (t myTime) first4Chars() string {
return t.Time.String()[0:4]
}
func (i aliasInt) add() aliasInt {
i++
return i
}
10.6.2 函数和方法的区别
函数将变量作为参数:
Function1(recv)
方法在变量上被调用
recv.Method1()
在接收者是指针时,方法可以改变接收者的值(或状态),函数也可以(当参数作为指针传递)
接收者必须有一个显式的名字,这个名字必须在方法中被使用。
receiver_type叫接收者基本类型,这个类型必须在和方法同样的包中被声明
10.6.3 指针或值作为接收者
想要方法改变接收者的数据,就在接收者的指针类型上定义该方法。否则,就在普通的值类型上定义方法。
在调用方法时,值类型和指针类型既可以调用值接收者的方法,也可以调用指针接收者的方法。值类型调用指针接收者方法,会自动加引用。指针类型调用值类型接收者方法时,会自动解引用。
type BB struct {
thing int
}
func main(){
var b1 BB //b1是值
b1.change()
b2 := new(BB) //b2是指针
b2.change()
fmt.Println(b2.write())
}
func (b *BB) change() {
b.thing = 1
fmt.Println("调用了change 方法")
}
func (b BB) write() string {
return fmt.Sprint(b)
}
调用了change 方法
{1}
调用了change 方法
{1}
10.6.4 方法和未导出字段
自定义结构体 属性为私有的 提供了外部可以的get和set
package pack1
type Animal struct {
name string
varieties string
}
func (a *Animal) GetName() string {
return a.name
}
func (a *Animal) GetVarieties() string {
return a.varieties
}
func (a *Animal) SetName(name string) {
a.name = name
}
func (a *Animal) SetVarieties(varieties string) {
a.varieties = varieties
}
外部类中
dog := new(pack1.Animal)
dog.SetName("xiaohei")
dog.SetVarieties("dog")
fmt.Printf("name: %s, varieties: %s\n", dog.GetName(), dog.GetVarieties())
//name: xiaohei, varieties: dog
10.6.5 内嵌类型的方法和继承
当一个匿名类型被内嵌在结构体中,匿名类型的可见方法也同样被内嵌,在效果上等同于外层类型继承了这些方法。
package main
import (
"fmt"
"math"
)
type Point struct {
x, y float64
}
//内嵌类型的方法 由于 与外层类型定义的方法重名 被覆写
func (p *Point) Abs() float64 {
return math.Sqrt(p.x*p.x + p.y*p.y)
}
type NamedPoint struct {
Point
name string
}
func (n *NamedPoint) Abs() float64 {
return n.x*100 + n.y*100
}
func main() {
n := &NamedPoint{Point{1.1, 2.1}, "xxxx"}
fmt.Println(n.Abs())
}
内嵌将一个已存在类型的字段和方法注入到另一个类型里,匿名字段上的可见方法成为了外层类型的方法。当外层定义与内嵌类型同名的方法时会覆写内嵌类型对应的方法
结构体内嵌和自己在同一个包中的结构体时,可以彼此访问对方所有的字段和方法。
10.6.6 如何在类型中嵌入功能
两种方法来实现在类型中嵌入功能:
1.聚合:包含一个所需功能类型的具名字段
2.内嵌:内嵌所需功能类型
例子:在Customer类型中,通过Log类型来包含日志功能。
方法一:
package main
import "fmt"
type Log struct {
msg string
}
type Customer struct {
Name string
log *Log
}
func main() {
c := &Customer{"Tom", &Log{"Wuhu,qifei"}}
//c.log.Add("dasima")
c.Log().Add("dasima")
//fmt.Println(c.log)
fmt.Println(c.Log())
}
func (l *Log) Add(s string) {
l.msg += "\n" + s
}
func (l *Log) String() string {
return l.msg
}
func (c *Customer) Log() *Log {
return c.log
}
Wuhu,qifei
dasima
方法二
package main
import "fmt"
type log struct {
msg string
}
type Customer1 struct {
name string
log
}
func main() {
c := &Customer1{"jack", log{"wuhu"}}
c.add("qqq")
fmt.Println(c.string())
}
func (l *log) add(str string) {
l.msg += "\n" + str
}
func (c *Customer1) string() string {
return c.name + "\n" + c.msg
}
jack
wuhu
qqq
10.6.7 多重继承
例子:有一个类型CameraPhone,通过它可以Call(),也可以TakeAPicture(),但第一个方法属于类型Phone,第二个方法属于类型Camera
package main
import "fmt"
type Camera struct {
}
func (c *Camera) TakeAPicture() string {
return "take a photo"
}
type Phone struct {
}
func (p *Phone) Call() string {
return "take a call"
}
type CaremaPhone struct {
Camera
Phone
}
func main() {
c := &CaremaPhone{}
fmt.Println(c.Call())
fmt.Println(c.TakeAPicture())
}
take a call
take a photo
例题;
定义一个结构体类型 Base,它包含一个字段 id,方法 Id() 返回 id,方法 SetId() 修改 id。结构体类型 Person 包含 Base,及 FirstName 和 LastName 字段。结构体类型 Employee 包含一个 Person 和 salary 字段。
创建一个 employee 实例,然后显示它的 id。
package main
import "fmt"
type Base struct {
id int
}
func (b *Base) GetId() int {
return b.id
}
func (b *Base) SetId(id int) {
b.id = id
}
type Person1 struct {
Base
FirstName string
LastName string
}
type Employee struct {
Person1
salary float64
}
func main() {
e := &Employee{Person1{Base{1001}, "James", "Harden"}, 1110.34}
fmt.Println(e.GetId())
}
例题2 : 说出执行结果
package main
import (
"fmt"
)
type Base1 struct{}
func (Base1) Magic() {
fmt.Println("base magic")
}
func (self Base1) MoreMagic() {
self.Magic()
self.Magic()
}
type Voodoo struct {
Base1
}
func (Voodoo) Magic() {
fmt.Println("voodoo magic")
}
func main() {
v := new(Voodoo)
v.Magic()
v.MoreMagic()
}
voodoo magic
base magic
base magic
10.7 类型的String()方法和格式化描述符
如果类型定义了 String() 方法,它会被用在 fmt.Printf() 中生成默认的输出:等同于使用格式化描述符 %v 产生的输出。还有 fmt.Print() 和 fmt.Println() 也会自动使用 String() 方法。
package main
import (
"fmt"
"strconv"
)
type TwoInts struct {
a int
b int
}
func main() {
two1 := new(TwoInts)
two1.a = 12
two1.b = 10
fmt.Printf("two1 is: %v\n", two1)
fmt.Println("two1 is:", two1)
fmt.Printf("two1 is: %T\n", two1)
fmt.Printf("two1 is: %#v\n", two1)
}
func (tn *TwoInts) String() string {
return "(" + strconv.Itoa(tn.a) + "/" + strconv.Itoa(tn.b) + ")"
}
two1 is: (12/10)
two1 is: (12/10)
two1 is: *main.TwoInts
two1 is: &main.TwoInts{a:12, b:10}