起步
结构体判等
只有在结构体的所有字段类型全部支持直接判等时,才可做判断操作。
map,slice不支持直接判等,需借助reflect.DeepEqual来比较(map整个是一个指针(*hmap), slice是SliceHeader的Data字段是个指针)
package main import "fmt" func main() { type data struct { x int } d1 := data{ x: 100, } d2 := data{ x: 100, } fmt.Println(d1 == d2) }
执行结果为:true
package main import "fmt" func main() { type data struct { x int y map[string]int //字典类型不支持==, } d1 := data{ x: 100, } d2 := data{ x: 100, } fmt.Println(d1 == d2) //struct containing map[string]int cannot be compared }
编译不通过:invalid operation: d1 == d2 (struct containing map[string]int cannot be compared)
空结构体
空结构struct{}是指没有字段的结构类型。
可作为通道元素类型,用于事件通知。
package main import ( "fmt" "time" ) func main() { exit := make(chan struct{}) go func() { for { fmt.Println("hello, world!") exit <- struct{}{} } }() <-exit time.Sleep(2e9) fmt.Println("end.") }
输出为:
hello, world! hello, world! end.
指针操作
可使用指针直接操作(取值/赋值等)结构体字段,但不能是多级指针。
package main import ( "fmt" "reflect" ) func main() { type user struct { name string age int } p := &user{ //获取指针 name: "fliter", age: 26, } fmt.Println(p) //&{fliter 26} p.name = "jack" //通过指针找到对应的程序实体 p.age-- fmt.Println(p) //&{jack 25} fmt.Println(*p) //{jack 25} p2 := &p //&p属于二级指针 fmt.Println(p2) //0xc00000e020 fmt.Println(reflect.TypeOf(p2)) //**main.user //*p2.name = "john" //p2.name undefined (type **user has no field or method name) }
输出为:
&{fliter 26} &{jack 25} {jack 25} 0xc00007c010 **main.user
Struct中的tag
可以为struct中的每个字段,加一个tag。这个tag可以通过反射机制获取到,最常用场景(包括但绝不仅限于此,如还常用于格式校验,数据库关系映射等)就是 json序列化和反序列化.
package main import ( "fmt" "reflect" ) type User struct { name string `姓名` sex string `性别` age int `年龄` } func main() { u := User{"Messi", "男", 32} v := reflect.ValueOf(u) t := v.Type() for i, n := 0, t.NumField(); i < n; i++ { fmt.Printf("%s为: %v\n", t.Field(i).Tag, v.Field(i)) } }
输出为:
姓名为: Messi 性别为: 男 年龄为: 32
用于json序列化
**注意:**
struct转json时,struct里的字段名首字母必须大写,否则无法正常解析;如果想让struct转json后字段名首字母小写,可以通过tag指定
[更多可点击](https://studygolang.com/articles/13455)
package main import ( "encoding/json" "fmt" ) type FiveAlevelArea struct { Name string `json:"name"` Location string `json:"address"` Price float32 `json:"entrance ticket"` englishName string `json:"english_name"` } func main() { heBei := FiveAlevelArea{ "承德市双桥区承德避暑山庄及周围寺庙景区", "承德市山庄南路", 100.00, "Chengde Imperial Summer Resort", } j, err := json.Marshal(heBei) if err != nil { fmt.Println("报错如下:", err) return } fmt.Println("json字符串为:", string(j)) }
输出为:
json字符串为: {"name":"承德市双桥区承德避暑山庄及周围寺庙景区","address":"承德市山庄南路","entrance ticket":100}
可见首字母不为大写的字段名,没有被解析出来;最后json字符串中展示出的键名,是tag标签里指定的名称
匿名字段
所谓匿名字段是指没有名字,仅有类型的字段,也称作嵌入字段
或嵌入类型
。 从编译器角度看,这只是隐式地以类型名作为字段名称。 可直接引用匿名字段的成员,但初始化时必须当作独立字段。
匿名字段不仅仅可以是结构体,除"接口指针"和"多级指针"以外的任何命名类型都可以作为匿名字段。
严格说来,Go并不是传统意义上的面向对象编程语言,或者说仅实现了最小面向对象的机制。 匿名嵌入不是继承,无法实现多态处理。 虽然配合方法集,可用接口来显现一些类似的操作,但其本质完全不同。
package main import "fmt" type attr struct { perm int } type file struct { name string attr //匿名字段,仅有类型名(但IDE在格式化此字段时,不会和其他字段的类型对齐,而会和其他字段的名称对齐) } func main() { //方式1: var c file c.name = "my.txt" var a attr a.perm = 777 c.attr = a fmt.Println(c, c.perm) //直接读取匿名字段成员 //方式2: //如果使用这种方式给包含匿名字段的结构体赋值,须将类型名当作字段名 f := file{ name: "test.dat", attr: attr{ //将类型名当作字段名 perm: 755, }, } f.perm = 500 //直接设置匿名字段成员 fmt.Println(f, f.perm) //直接读取匿名字段成员 }
利用匿名字段实现所谓的"继承"
结构体中字段可以没有名字(只有类型),即匿名字段;
如果一个struct(记为A)嵌套了另一个匿名结构体(记为B),那么A结构体可以直接访问 匿名结构体B的方法,从而实现了继承。
参见前文,golang利用组合实现继承,和php或java面向对象的继承有何不同
方法
Golang中的方法作用在特定类型的变量上,因此自定义类型都可以 有方法,而不仅仅是struct
定义:func (recevier type) methodName(参数列表)(返回值列表){}
方法和函数
golang中的"方法"与"函数"的关系,与其他语言有所差别.
方法 给用户定义的类型添加新的行为。方法和函数相比,声明时在关键字func和方法名之间增加了一个参数
package main import "fmt" type user struct { name string age int } func (u user) say() { fmt.Println("hello", u.name) fmt.Println("十年后你的年龄是:", u.age+10) } func (u *user) run() { fmt.Printf("%s是跑步名将\n", u.name) } func main() { c := user{"Trump", 73} c.say() s := &user{"Bolt", 0} s.run() }
输出:
hello Trump 十年后你的年龄是: 83 Bolt是跑步名将
- 一个基础类型前面加&后,会变为指针类型;
- *作动词时, 后面必须是一个指针类型,叫做解引用~
即 *(&a) = a
- *作形容词时,表示这个类型是指针类型
方法作用在结构体上
package main import ( "fmt" ) type FiveAlevelArea struct { Name string `json:"name"` Location string `json:"address"` Price float32 `json:"entrance ticket"` englishName string `json:"english_name"` } func (cs FiveAlevelArea) set(Name, Location, englishName string, Price float32) { cs.Name = Name cs.Location = Location cs.Price = Price cs.englishName = englishName fmt.Println("当前结构体的值为:", cs) } func (shuang FiveAlevelArea) get() FiveAlevelArea { return shuang } func main() { var heBei FiveAlevelArea heBei.set("保定市安新县白洋淀景区", "保定市安新县", "Bai-yang Lake", 40.00) heBei2 := heBei.get() fmt.Println(heBei) fmt.Println(heBei2) }
输出为:
当前结构体的值为: {保定市安新县白洋淀景区 保定市安新县 40 Bai-yang Lake} { 0 } { 0 }
因为是值传递,故而原来变量的值没有被修改;
改为指针传递:将第14行set()方法作用的变量(类型)修改为cs *FiveAlevelArea
,此时程序的执行结果为:
当前结构体的值为: &{保定市安新县白洋淀景区 保定市安新县 40 Bai-yang Lake} {保定市安新县白洋淀景区 保定市安新县 40 Bai-yang Lake} {保定市安新县白洋淀景区 保定市安新县 40 Bai-yang Lake}
可能会疑惑,第32行调用set()方法的变量类型为FiveAlevelArea
,并不是指针,为何还能编译通过呢?实际上是go自动加了取指针符号&,即第32行:
等同于:
方法作用在其他变量上
package main import "fmt" type cInt int func (cui cInt) f1() { fmt.Println("变量值为:", cui) } func (c *cInt) f2(args cInt) { *c = args + 6 } func main() { var a1 cInt = 10086 fmt.Println(a1) //调用cInt类型的f1()方法 a1.f1() var a2 cInt //&a2是获取变量a2的内存地址值即指针,如果对一个参数需要接收指针(*后面必须要是指针类型)的方法如此处的f2()传值a2,go会自动将a2转换成&a2即变量a2的内存地址,所以以下两种写法都是可以的 a2.f2(12300) fmt.Println(a2) (&a2).f2(12300) fmt.Println(a2) //指针传递可以改变原来变量的值 var a3 cInt = 12360 a3.f2(a3) fmt.Println(a3) (&a3).f2(a3) fmt.Println(a3) }
输出为:
10086 变量值为: 10086 12306 12306 12366 12372
String()方法
如果一个变量实现了String()这个方法,那么fmt.Println在输出时,会默认调用这个变量的String()方法。
package main import "fmt" type Transportation struct { name string speed string } func (o *Transportation) Run() { fmt.Printf("交通工具%s跑起来啦!\n", (*o).name) //此处"o.name"同样可以通过编译(o是一个指针,*o解引用为这个内存地址对应的变量),go会自动转化为(*o).name } //火车继承交通工具 type Train struct { Transportation InventedTime int //发明时间 } func (cs *Train) String() string { str := fmt.Sprintf("%s的速度可以达到%s\n", cs.name, cs.speed) return str } func main() { var train Train train.name = "火车" train.speed = "350km/h" train.InventedTime = 1804 fmt.Println(train) fmt.Println("------------------") train.Run() fmt.Println("******************") fmt.Printf("%s", &train) fmt.Println("##################") fmt.Println("会自动调用String()方法\n", &train) }
输出为:
{{火车 350km/h} 1804} ------------------ 交通工具火车跑起来啦! ****************** 火车的速度可以达到350km/h ################## 会自动调用String()方法 火车的速度可以达到350km/h