golang学习笔记 ---struct补充
1. 定义
结构体:
1、用来自定义复杂数据结构
2、struct里面可以包含多个字段(属性)
3、struct类型可以定义方法,注意和函数的区分
4、strucr类型是值类型
5、struct类型可以嵌套
6、go语言中没有class类型,只有struct类型
struct声明:
type 标识符 struct{ field1 type field2 type }
例子:
type Student struct{ Name string Age int Score int }
struct定义的三种形式 初始化的三种方式
a、var stu Student
b、var stu *Student=new(Student)
c、var stu *Student=&Student{}
其中b和c返回的都是指向结构体的指针,访问形式如下:
a、stu.Name、stu.Age 和stu.Score 或者(*stu).Name、 (*stu).Age等
如果是指针形式可以用上面的普通的方式访问,其实就自动转化为指针访问的形式
完整示例:
package main import ( "fmt" ) type Student struct{ Name string Age int score float32 } func main(){ //声明方式一 var stu Student stu.Name="hua" stu.Age=18 stu.score=80 //声明方式二 var stu1 *Student =&Student{ Age:20, Name:"hua", } //声明方式三 var stu3 =Student{ Age:20, Name:"hua", } fmt.Printf(stu1.Name) fmt.Printf(stu3.Name) }
结构体是用户单独定义的类型,不能和其他类型进行强制转换
package main import ( "fmt" ) type integer int func main(){ //赋值给谁呢么类型,就要强制转换成什么类型 var i integer=1000 fmt.Println(i) var j int=100 //这里i是自定义的类型, j是int类型,所以赋值的时候要强制转换,如下 j=int(i) //i如果赋值给j应该强制转换为int类型 i=integer(j) //j如果想复制给i必须转换为integer类型 fmt.Println(i) fmt.Println(j) }
上面这两个Stu和Student是别名关系,虽说这两个字段一样,但并不是同一个类型,因为是type定义的
工厂模式
golang中的struct没有构造函数,一般可以使用工厂模式来解决这个问题
Package model
type student Struct{
Name string
Age int
}
func NewStudent(name string,age int)*Student{
return &Student{ //创建实例
Name:name
Age:age
}
}
Package main
S:new(student)
S:model.NewStudent(“tony”,20)
再次强调
make用来创建map,slice ,channel
new 用来创建值类型
struct中的tag
我们可以为strct中的每一个字段,协商一个tag,这个tag可以通过反射机制获取到,最常用的场景就是json序列化和反序列化
type student struct{
Name string “this is name field” //每个字段写一个说明,作为这个字段的描述
Age int “this is age field”
}
json打包
json.Marshal()
注意:
json打包的时候,
1、必须要把结构体中的字段大写,才可以
下面是程序声明打包初始化的两种方式
package main import( "encoding/json" "fmt" ) type Student struct{ Name string `json:"Student_name"` age int `json:"student_age"` score int `json:score` } func main(){ //声明 var stu Student=Student{ Name:"stu01", age:10, score:100, } data,err:=json.Marshal(stu) //打包,返回值为byte if err!=nil{ fmt.Println("json encode stu faild,err",err) return } fmt.Println(string(data)) //把byte转化成string } //{"Student_name":"stu01"} 也可以下面的方式书写 package main import( "encoding/json" "fmt" ) type Student struct{ Name string `json:"Student_name"` age int `json:"student_age"` score int `json:score` } func main(){ //初始化 var stu *Student=new(Student) stu.Name="stu01" data,err:=json.Marshal(stu) //打包,返回值为byte if err!=nil{ fmt.Println("json encode stu faild,err",err) return } fmt.Println(string(data)) //把byte转化成string } //{"Student_name":"stu01"}
或者示例
package main import ( "encoding/json" "fmt" ) type Student struct { Name string `json:"Student_name"` Age int `json:"student_age"` score int `json:score` } func main() { //初始化 var stu *Student = new(Student) stu.Name = "stu01" stu.Age = 20 stu.score = 90 data, err := json.Marshal(stu) //打包,返回值为byte if err != nil { fmt.Println("json encode stu faild,err", err) return } fmt.Println(string(data)) //把byte转化成string } //{"Student_name":"stu01","student_age":20}
匿名字段
结构体 中字段可以没有名字,叫做匿名字段
type Car struct{
Name string
Age int
}
type Train struct{
Car //匿名字段
Start time.Time //有名字段
int //匿名字段
}
匿名字段要怎么访问呢?
package main import ( "fmt" "time" ) type Cart struct { name string age int } type Train struct { Cart int strt time.Time } func main() { var t Train //正规写法 t.Cart.name = "001" t.Cart.age = 11 //上面的正规写法可以缩写成下面的写法 t.name = "001" t.age = 11 t.int = 200 fmt.Println(t) }
匿名字段冲突处理
type Cart struct { Name string Age int } type Train struct { Cart strt time.Time Age int }
对于上面的匿名冲突,这里有优先原则:
缩写形式,如果有两个结构体中有相同的字段,会优先找本身的字段
type A struct { a int } type B struct { a int b int } type C struct { A B }
对于上面的匿名冲突,则必须要手动的指定某个字段才可以,不然会报错
2. 成员变量
- 访问控制机制
如果一个结构体的成员变量名称是首字母大写的,那么这个变量是可导出的(即在其他包可以访问到)。
一个结构体可以同时包含可导出和不可导出的成员变量
type A struct { Hour int //可导出 minute int //不可导出 }
- 限制
命名结构体类型S不可以定义一个拥有相同结构体类型s的成员变量,也就是一个聚合类型不可以包含它自己。但是S中可以定义一个s的指针类型,即*S。如下:
type S struct { value int //Next S //错误 Next *S //正确 }
3. 结构体比较
- 如果结构体的所有成员变量都可以比较,那么这个结构体是可以比较的。两个结构体的比较可以使用==或者!=。
package main import "fmt" func main() { type C struct { A int B string } c1 := C{A: 1, B: "abc"} c2 := C{A: 1, B: "abc"} c3 := C{A: 2, B: "abc"} fmt.Println(c1.A == c2.A && c1.B == c2.B) //true fmt.Println(c1 == c2) //true 与上等价 fmt.Println(c1.A == c3.A && c1.B == c3.B) //false fmt.Println(c1 == c3) //false 与上等价 }
- 和其他可比较的类型一样,可比较的结构体类型都可以作为map的键类型。
package main import "fmt" func main() { type C struct { A int B string } mp := make(map[C]int) key := C{A: 1, B: "abc"} mp[key] = 9 fmt.Println(mp[C{A: 1, B: "abc"}]) //9 fmt.Println(mp)//map[{1 abc}:9] }
4. 结构体嵌套和匿名成员
- go允许我们定义不带名称的结构体成员,只需要指定类型即可;这种结构体成员称作匿名成员。这个结构体成员的类型必须是一个命名类型或者指向命名类型的指针。正是因为有了这种结构体嵌套的功能,我们才能直接访问到我们需要的变量而不是指定一大串中间变量。
package main
import "fmt"
type Point struct {
X int
Y int
}
type Circle struct {
Point
}
type Wheel struct {
*Point
}
func main() {
var c Circle
c.X = 10 //等价于 c.Point.X = 10
c.Y = 10 //等价于 c.Point.Y = 10
fmt.Println(c)
var w Wheel
p := Point{1, 3}
w.Point = &p
fmt.Println(w.Point.X)
}
- 结构体字面量初始化没有快捷方式,必须遵循形状类型的定义。
import "fmt" type Point struct { X int Y int } type Circle struct { Point } func main() { var c Circle // c = Circle{1, 1} //错误 c = Circle{Point{1, 1}} //正确 c = Circle{Point: Point{1, 1}} //正确 fmt.Println(c) }
-
因为“匿名成员”拥有隐式的名字,所以你不能在一个结构体里面定义两个相同类型的匿名成员,否则会引起冲突。由于匿名成员的名字是由它们的类型决定的,因此它们的可导出性也是由他们的的类型决定。在下面的例子中,point和circle这两个匿名成员是可导出的,即使这两个结构体是不可导出的(point和circle)。
package main import "fmt" type point struct { X int Y int } type circle struct { point } type Wheel struct { circle } func main() { var w Wheel w.X = 8 // 等价于 w.circle.point.X = 8, w.X是可导出的,w.circle.point.X是不可导出的 fmt.Println(w) }
5. 结构体方法
- 定义
方法的声明和普通函数的声明类似,知识在函数名字前面多加了一个参数。这个参数把这个方法绑定到这个参数对应的类型上。
附加的参数p成为方法的接收者, 它源于早先的面向对象语言,用来描述主调方法向对象发送消息。 -
方法可见性: 将方法定义为以大写字母开头,,Go语言中符号的可访问性是包一级的而不是类型一级的。,小写开头的内部方法,在同一个包中的其他类型也都可以访问到它。
package main import "fmt" type Point struct { X int Y int } func (p Point) print() { fmt.Println(p.X, p.Y) } func main() { var w = Point{1, 2} w.print() }
- 方法定义限制: go和许多其他面向对象的语言不同,它可以将方法绑定到除了指针类型和接口类型的任何类型上。可以很方便的为简单的类型(入整形、字符串、slice、map、甚至函数等)定义附加的行为。
package main import "fmt" //整形 type III int func (i III) print() { fmt.Println(i) } //函数 func Test() { fmt.Println("test") } type FunT func() func (f FunT) Print() { fmt.Println(f) } func main() { var f FunT f = Test f.Print() var i III = 3 i.print() }
- 指针接收者的方法: 由于主调函数会复制每一个实参变量,或者如果一个实参太大而我们希望避免复制整个实参,因此我们必须使用指针来传递变量的地址。这也同样适用于更新接收者我们将它绑定到指针类型。在调用方法的时候,编译器会对变量进行隐式转换。 总结一下结构体方法可以成功调用的条件:
- 实参接收者和形参接收者是同一类型,比如都是T或者都是*T。(1,4,5,7)
- 实参接收者是T类型的变量而形参接收者是*T类型,编译器会隐式的获取变量的地址(3)。
- 实参接收者是T类型而形参接收者是T类型,编译器会隐式的获取实际的取值。(2,6)其中8编译过程报错的原因是:编译器对T类型转化为T类型的隐式转化,只有实参接收者是变量才可以成功,因为无法获取临时变量的地
package main import "fmt" type Point struct { X int Y int } func (p Point) Print() { fmt.Println(p.X, p.Y) } func (p *Point) ScaleBy(factor int) { p.X *= factor p.Y *= factor } func main() { p := Point{1, 1} ptr := &p p.Print() //1. 正确 ptr.Print() //2. 正确 p.ScaleBy(2) //3. 正确 ptr.ScaleBy(2) //4. 正确 Point{1, 1}.Print() //5. 正确 (&Point{1, 1}).Print() //6. 正确 (&Point{1, 1}).ScaleBy(2) //7. 正确 //Point{1, 1}.ScaleBy(2) //8. 错误 }
- nil是一个合法的接收者:就像一些函数允许nil指针作为实参,方法的接收者允许是nil指针
package main import "fmt" type A struct { Data int } func (a *A) FunPtrA() { fmt.Println("FunPtrA") } func main() { var ptrA *A ptrA = nil ptrA.FunPtrA() //FunPtrA }
- 通过结构体内嵌组成类型: 这里主要讨论内嵌结构体和结构体指针后,对于源结构体定义函数的访问权限。