这是我参与「第五届青训营 」伴学笔记创作活动的第 11 天
结构体
-
特点:①不纯粹的面向对象语言②没有class,struct代替了class③仍然存在继承、封装、多态的概念
-
定义:
**type 结构体名称 struct{ field1 type field2 type } 1.结构体名称如果首字母如果是大写,那么可以在其他包引用结构体, 不是大写也可以通过工厂模式来调用,就类似于面对对象语言的构造函数 2. 结构体是自定义的数据类型,代表的是一类事务 -- Cat 3. 结构体变量是具体的、实际的,代表的是一个具体的变量 -- cat1.Name 4. 在创建一个结构体变量,如果没有赋值,则默认是默认值** -
字段/属性
- 从概念上看,结构体字段 = 属性 = field
- 结构体中的变量如果没有被使用,不会报错(和普通变量不一样)
- 指针,slice和map的零值都是nil,还没有分配可见,如果要使用,要make后才能使用(编译时报错)
- 同一结构体的不同变量的字段是独立的,互不影响,若要影响可以使用&
**type Cat struct{ Name string Age int Scores [5]float64 //数组 /*---上面是值类型 ,下面是引用类型 ---*/ ptr *int //指针 slice []int //切片 map1 map[string]string //map - key:string ,value:string } func main(){ /* 使用slice */ /* p.slice[0] = 100 会报错 */ p.slice = make([]int,10) p.slice[0] = 100 fmt.Println(p.slice) /*使用map */ p.map1 = make(map[string]string) //不需要分配空间也不报错,和slice不同,why p.map1["b"] = "100" p.map1["a"] = "200" fmt.Println(p.map1) }** -
声明/初始化:
-
直接声明:
var person Person -
{}-最方便
**person := Person{} person.Name = "lisa" person.Age = 18**或:
**person := Person{"lisa,18"}** -
new—返回指针
**var person *Person = new(Person) /*--------或-------*/ person := new(Person) /*person是一个指针,赋值: */ (*person).Name = "lisa" (*person).Age = 18 /*简化赋值 -- Go特性,在底层会对其处理*/ person.Name = "lisa" person.Age = 18 fmt.Println(*person) /*或 fmt.Println(person) */**注意:使用new省略
*只是Golang特性,其他语言需要加上,不可以省略 -
&Person:返回之后指针
**var person *Person = &Person{"lisa,18"} // person.Name = "lisa" // person.Age = 18 /* --------------------------*/ // (*person).Name = "lisa" // (*person).Age = 18 fmt.Println(*person)**- ***和方法三一样,在Golang中可以省略,``底层会对其处理,会自动加上 * ****
- 同时和方法二一样,在{}中可以直接赋值
-
-
访问:变量名.成员名
-
内存分配:默认为0,所以是值类型(帮助我理解c语言中难以理解的结构体和指针)
-
结构体内定义结构体:地址连续
**package main import "fmt" type Point struct{ x,y int } type Rect struct{ a,b Point } func main(){ r := Rect{Point{1,2},Point{3,4}} //r有4个int整数,在内存中连续分布 fmt.Printf("r.a.x的地址%p \n r.a.y的地址%p \n",&r.a.x,&r.a.y) fmt.Printf("r.b.x的地址%p \n r.b.y的地址%p \n",&r.b.x,&r.b.y) fmt.Println("r.a.x+1的地址为:",&(r.a.x+1)) }** -
结构体内定义结构体指针:地址跳跃
**package main import "fmt" type Point struct{ x,y int } type Rect struct{ a,b *Point } func main(){ r := Rect{&Point{1,2},&Point{3,4}} //r有4个int整数,在内存中连续分布 fmt.Printf("r.a本身的地址%p \n r.a本身的地址%p \n",&r.a,&r.b) fmt.Printf("r.b指向的地址%p \n r.b指向的地址%p \n",r.a,r.b) fmt.Printf("r.a指向的值%d \n r.b指向的值%d \n", *r.a, *r.b)//读取地址存储的内容 }**
-
-
强制类型转换:需要有完全相同的字段(名字,个数和类型)
a = A(b) -
tag标签:反射(在结构体内部字段首字母没有大写的情况下,依旧可以JSON格式化字符串)
**package main import ( "fmt" "encoding/json" ) type Cat struct{ Name string `json:"姓名"` Age int `json:"年龄"` Color string `json:"颜色"` Hobby string `json:"hobby"` } func main(){ /*创建变量*/ cat := Cat{"张三",20,"红色","hanhan"} //将cat变量序列化为json格式字串 jsoncat,err := json.Marshal(cat) if err != nil{ fmt.Println("json处理错误",err) } fmt.Println("jsoncat = ",string(jsoncat)) } 没有tag之前: jsoncat = {"Name":"张三","Age":20,"Color":"红色","Hobby":"hanhan"} 有tag之后: jsoncat = {"姓名":"张三","年龄":20,"颜色":"红色","hobby":"hanhan"}**
方法
-
方法声明:方法声明和定义是一样的,可以先声明,也可不声明,不会产生影响
**func (variable_name variable_data_type) function_name(参数列表) (int,int){ /* 函数体*/ } type A struct{ Age int } func (a A)test(){ fmt.Println(a.Age) }**- (int,int) 返回值类型,和函数中返回值类型使用相同,可以进行绑定
- return和返回值列表是相对应的
- 参数列表:表示方法的输入
- variable_data_type不一定要和结构体绑定,可以是自定义类型,都可以有方法
- 方法函数里面的是值拷贝,所以方法里面的变量并改变不会影响变量的值
-
方法调用
- 方法的调用和传参机制和函数基本一样,不一样的地方是方法调用时,会将调用方法的变量,当作实参也传递给方法
-
传参机制
-
在调用方法中,是值类型,所以被被拷贝,和函数不同的是:变量调用方法时,该变量也会作为一个参数传递到方法(如果是引用类型,则进行地址拷贝),指针的 和传参时的&都可以省略*
**type Circle struct { radius float64 //定义半径 } //2)声明一个方法area和Circle绑定,可以返回面积。 //法一:结构体Circle做绑定 func (c Circle) area() float64 { return 3.14 * c.radius * c.radius } //法二:结构体指针Circle做绑定 //为了提高效率,通常我们方法和结构体的指针类型绑定 func (c *Circle) area2() float64 { //因为 c是指针,因此我们标准的访问其字段的方式是 (*c).radius //return 3.14 * (*c).radius * (*c).radius // (*c).radius 等价 c.radius fmt.Printf("c 是 *Circle 指向的地址=%p", c) c.radius = 10 return 3.14 * c.radius * c.radius } func main() { //创建一个Circle 变量 var c Circle fmt.Printf("main c 结构体变量地址 =%p\n", &c) c.radius = 7.0 //res2 := (&c).area2() //编译器底层做了优化 (&c).area2() 等价 c.area() //因为编译器会自动的给加上 &c res2 := c.area2() fmt.Println("面积=", res2) fmt.Println("c.radius = ", c.radius) //10 }**
-
-
函数和方法的区别
-
调用方式不一样
- 函数:函数名(实参列表)
- 方法:变量.方法名(实参列表)
-
对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
- 传递的时候
&不可以省略,接受的时候&可以省略
- 传递的时候
-
对于方法(如strucr的方法),接收者为值类型时候,可以直接用指针的变量调用方法,反过来同样可以(接受者为指针类型,也可以传递值类型)
- 传参时加不加&不重要,重要的是接受者是否有*
- 方法体内有没有不重要,重要的是方法关联的结构体加没加
-