定义 Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。 结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。
结构体定义需要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体有中一个或多个成员。 type 语句设定了结构体的名称。结构体的格式如下:
type identifier struct { field1 type1 field2 type2 ... } 如果字段在代码中从来也不会被用到,那么可以命名它为 _ variable_name := identifier {value1, value2...valuen}
结构体的字段可以是任何类型,结构体,函数或者接口
访问结构体成员 如果要访问结构体成员,需要使用点号 (.) 操作符,格式为:”结构体.成员名”。 在 Go 语言中这叫 选择器(selector)。无论变量是一个结构体类型还是一个结构体类型指针, 都使用同样的 选择器符(selector-notation) 来引用结构体的字段
type myStruct struct { i int }var v myStruct var p *myStruct v.i p.i 结构体作为函数参数 结构体指针 1. 按照顺序提供初始化值P := person{"Tom" , 25 } 2. 通过field:value的方式初始化,这样可以任意顺序P := person{age:24 , name:"Tom" } 3. 当然也可以通过new 函数分配一个指针,此处P的类型为*personP := new (person)
声明 type person struct { name string age int } var P person P.name = "Astaxie" P.age = 25 fmt.Printf("The person's name is %s" , P.name) P := person{"Tom" , 25 } P := person{age:24 , name:"Tom" } P := new (person)
一个完整的例子:
import "fmt" type person struct { name string age int } func Older (p1, p2 person) (person, int ) { if p1.age>p2.age { return p1, p1.age-p2.age } return p2, p2.age-p1.age } func main () { var tom person tom.name, tom.age = "Tom" , 18 bob := person{age:25 , name:"Bob" } paul := person{"Paul" , 43 } tb_Older, tb_diff := Older(tom, bob) tp_Older, tp_diff := Older(tom, paul) bp_Older, bp_diff := Older(bob, paul) fmt.Printf("Of %s and %s, %s is older by %d years\n" , tom.name, bob.name, tb_Older.name, tb_diff) fmt.Printf("Of %s and %s, %s is older by %d years\n" , tom.name, paul.name, tp_Older.name, tp_diff) fmt.Printf("Of %s and %s, %s is older by %d years\n" , bob.name, paul.name, bp_Older.name, bp_diff) }
结构体的内存布局 Go 语言中,结构体和它所包含的数据在内存中是以连续块的形式存在的,即使结构体中嵌套有其他的结构体,这在性能上带来了很大的优势。不像 Java 中的引用类型,一个对象和它里面包含的对象可能会在不同的内存空间中,这点和 Go 语言中的指针很像。
结构体拷贝 copy lib
func main() { a := &A{1, 1, "a", "b"} aj, _ := json.Marshal(a) b := new(B) _ = json.Unmarshal(aj, b) fmt.Printf("%+v", b) }
递归结构体 结构体类型可以通过引用自身来定义。这在定义链表或二叉树的元素(通常叫节点)时特别有用,此时节点包含指向临近节点的链接(地址) 链表: 这块的 data 字段用于存放有效数据(比如 float64),su 指针指向后继节点。
type Node struct { data float64 su *Node } 双向链表,它有一个前趋节点 pr 和一个后继节点 su type Node struct { pr *Node data float64 su *Node }
匿名字段 我们上面介绍了如何定义一个 struct,定义的时候是字段名与其类型一一对应,实际上 Go 支持只提供类型,而不写字段名的方式,也就是匿名字段,也称为嵌入字段。 当匿名字段是一个 struct 的时候,那么这个 struct 所拥有的全部字段都被隐式地引入了当前定义的这个 struct。
import "fmt" type Human struct { name string age int weight int } type Student struct { Human speciality string } func main () { mark := Student{Human{"Mark" , 25 , 120 }, "Computer Science" } fmt.Println("His name is " , mark.name) fmt.Println("His age is " , mark.age) fmt.Println("His weight is " , mark.weight) fmt.Println("His speciality is " , mark.speciality) mark.speciality = "AI" fmt.Println("Mark changed his speciality" ) fmt.Println("His speciality is " , mark.speciality) fmt.Println("Mark become old" ) mark.age = 46 fmt.Println("His age is" , mark.age) fmt.Println("Mark is not an athlet anymore" ) mark.weight += 60 fmt.Println("His weight is" , mark.weight) }
方法 Go 语言中同时有函数和方法。一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集。语法格式如下:
func (variable_name variable_data_type) function_name () [return_type ] { } type Circle struct { radius float64 } func main () { var c1 Circle c1.radius = 10.00 fmt.Println("Area of Circle(c1) = " , c1.getArea()) } func (c Circle) getArea () float64 { return 3.14 * c.radius * c.radius }
默认结构体是值传递,所以在方法中需要使用指针类型来给成员变量赋值
func (m *Member) setName (name string ) {/将Member改为*Member m.Name = name } m := Member{} m.setName("小明" ) fmt.Println(m.Name)
结构体打印字符串 fmt.Println( fmt.Sprintf("%v" , head)) %+v 打印结构体时,会添加字段名 Printf("%+v" , people) {Name:zhangsan}
内嵌 如果包含另一个结构体的时候不指定变量,可以直接访问。
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) }
如果指定了变量,则需要通过变量访问
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) }
使用 NEW 代替构造函数 项目目录结构 workspacepath -> oop -> employee -> employee.go
package employeeimport ( "fmt" ) type Employee struct { FirstName string LastName string TotalLeaves int LeavesTaken int } func (e Employee) LeavesRemaining () { fmt.Printf("%s %s has %d leaves remaining" , e.FirstName, e.LastName, (e.TotalLeaves - e.LeavesTaken)) }
新建一个文件使用 employee workspacepath -> oop -> employee -> employee.go workspacepath -> oop -> main.go
package mainimport "oop/employee" func main () { e := employee.Employee { FirstName: "Sam" , LastName: "Adolf" , TotalLeaves: 30 , LeavesTaken: 20 , } e.LeavesRemaining() var e employee.Employee e.LeavesRemaining() }
修改上面的 employee 定义
package employeeimport ( "fmt" ) type employee struct { firstName string lastName string totalLeaves int leavesTaken int } func New (firstName string , lastName string , totalLeave int , leavesTaken int ) employee { e := employee {firstName, lastName, totalLeave, leavesTaken} return e } func (e employee) LeavesRemaining () { fmt.Printf("%s %s has %d leaves remaining" , e.firstName, e.lastName, (e.totalLeaves - e.leavesTaken)) }
首先 employee 首字母改为小写,不导出给其他包使用。然后新建一个 New 导出函数用来创建对象
package mainimport "oop/employee" func main () { e := employee.New("Sam" , "Adolf" , 30 , 20 ) e.LeavesRemaining() }