Go struct
用来自定义复杂数据结构
struct里面可以包含多个字段(属性),字段可以是任意类型
struct类型可以定义方法,注意和函数的区别
struct是值类型,也就是,赋值的时候,就是copy一份,不会修改原来的值,
struct类型可以嵌套
Go中没有class类型,只有struct类型
make ⽤来分配map、 slice、 channel类型的内存,new用来分配值类型的内存
Struct定义:
声明:
type 标识符 struct{
filed1 int
filed2 string
}
访问:和python一样,使用点的方法来访问
定义一个 Student struct
type Test struct{
A int
B int
}
type Student struct{
//定义类型
Age int
Name string
Sex string
Grader string
Score int
test Int
//Test 是struct类型
sitrstruct Test
//指针类型
c *int
}
func testStruct() {
var s Student
//因为这里s已经定义了。所以不需要:=这种方式了
s.Age =18
s.Name = "zcq"
s.Score = 100
s.Sex = "main"
s.Grader = "36"
//访问
s.sitrstruct.A = 100
//当struct内部有指针类型的时候,默认是空,直接赋值会报错,需要new创建
s.c =new(int)
//指针赋值
*(s.c) = 100
//打印结果
fmt.Printf("name:%s,age:%d,,c:%d\n",s.Name,s.Age,*(s.c))
fmt.Printf("%+v\n",s)
//值类型, 就是改变内容的值,就是copy一份,不会影响原来变量的值
s.Name = "rch"
fmt.Printf("name:%s\n",s.Name)
//s里面的地址赋值给s1,那s1和s是指向同一个内存地址,所以用s1来修改指针,那s也会变
s1:=s
*(s1.c) = 200
fmt.Printf("name:%s,age:%d,,c:%d\n",s.Name,s.Age,*(s.c))
}
Go Struct 三种定义方式:
var stu Student
这种方式操作起来代码量多
var stu *Student = new(Student)
使用new的方式创建,这种方式,stu就是一个指针类型,
stu .Score = 100
//s2,s3的score值都改变了
s3:=stu
//需要注意的是:*(s3).Score = 200 是标准访问形式,但是当你s3.Score 这样访问也是的, 那是因为go会检测s3是值类型,还是指针类型,如果是值类型,那go就帮你转换为*(s3) 这样的形式
var stu *Student = &Student{}
分配内存空间,这种方式可以如果需要初始化一些值,可以直接写在{} 中
其中方法2,3 返回的都是指向结构体的指针,访问形式:
stu.Name、 stu.Age和stu.Score或者 (*stu).Name、 (*stu).Age
自定义类型:结构体是用户单独定义的类型,不能和其他类型进⾏强制转换
type StudentA struct{
Number int
}
type Stu StudentA
func main() {
var a StudentA
a.Number = 20
//需要使用自定义类型强制转换
var b Stu
b=Stu(a)
fmt.Printf("%v",b)
}
Struct内存布局
内存布局:struct中的所有字段在内存是连续的,
代码:
package main
import "fmt"
//内存布局是连续的
type Point struct{
x int
y int
}
type Rect struct{
//p1,p2内存地址连续的
p1 Point
p2 Point
}
type RectA struct{
//p1,p2 指向的是一个内存地址
//那这2个的内存地址不是连续的
p1 *Point
p2 *Point
}
func main() {
//r1不用分配内存,因为定义完了后,内存就已经分配好了,没有指针类型字段
var r1 Rect
//r2里面是2个指针类型,所以需要分配内存,就需要new
var r2 RectA
r2.p1 = new(Point)
r2.p2 = new(Point)
//r1的内存布局
fmt.Printf("ADD:%p\n",&r1.p1.x)
fmt.Printf("ADD:%p\n",&r1.p1.y)
fmt.Printf("ADD:%p\n",&r1.p2.x)
fmt.Printf("ADD:%p\n",&r1.p2.y)
fmt.Println()
//r2的内存布局
fmt.Printf("ADD:%p\n",&r2.p1.x)
fmt.Printf("ADD:%p\n",&r2.p1.y)
fmt.Printf("ADD:%p\n",&r2.p2.x)
fmt.Printf("ADD:%p\n",&r2.p2.y)
}
Go struct 构造函数
go struct 没有构造函数,那就需要自己实现,一般可以使用工厂函数,来解决这个问题
type School struct{
Name string
Addr string
}
func NewSchool(name,addr string) (*School) {
//& 内存地址, new也是一个意思
//实例化struct
return &School{
Name:name,
Addr:addr,
}
//或者可以用new
//第二种写法
//p:=new(School)
//p.Name = name
//p.Age = age
//return p
}
func main() {
s:=School{Name:"aa",Addr:"assaas"}
fmt.Printf(s)
}
Struct 中的方法
访问控制:通过大小写控制,小写不能被其他包外面调用
方法可以作用在特定类型上
函数可以随意调用
函数传参是副本的调用
给struct添加方法
实例代码:
type People struct{
Name string
Score int
all int
string
}
type Student struct{
Name string
Age int
//使用了匿名字段,实现了继承
People
all int
}
//给people这个struct添加了 Format方法
func (p *People) Format() string {
return fmt.Sprintf("name=%s,age=%d",p.Name,p.Score)
}
//给Student这个struct添加了 方法
func (p *Student) Format() string {
return fmt.Sprintf("name=%s,age=%d",p.Name,p.Score)
}
func mian(){
var s Student
//访问父类中的方法
ret :=s.People.Format()
fmt.Printf(ret)
var d People
//访问d中的方法
res := d.Format()
fmt.Printf("11111",res)
}
三种函数接收方式
定义函数:
func Add(a, b int) int {
return a + b
}
func testInt() {
c := Add(100, 200)
fmt.Println(c)
}
值类型接受方式:
type Int int
//这种写法,前面(i Int) 是接受者参数
//在函数的前面加了自定义类型,和变量
//那Bdd的接受者就是i了, 那Bdd也就是Int类型的一个方法了,
func (i Int) Bdd(a, b int) int {
return a + b
}
func main(){
var a Int
d := a.Bdd(100, 200)
fmt.Println(d)
}
指针类型接收方式:
func (i *Int) Cdd(a, b int) {
//这里需要做强制转换,因为a,b是小写int, 但i是大写int, 那赋值就需要强制转换
*i = Int(a + b)
//这样a+b的结果就存在i这个变量里面了
return
}
var e Int
//传递给Cdd的只是e的拷贝,如果想修改e的值,那传递的时候,需要传递e的指针地址
e.Cdd(200, 300) //&(e).Cdd(200,300) 也是一样的, 因为e调用的时候,发现要传一个地址,那go会自动的转换成&(e),你可以不用写&,因为go帮你做了
fmt.Println(e)
在来一列struct 添加方法栗子
栗子1:
type Student struct{
Name string
Age int
}
//struct也是值类型,所以当要修改值,传递给函数的时候,就需要传递指针类型
func (s *Student) SiteSet(name string,age int){
s.Name = name
s.Age = age
}
func teststrcut() {
//这样就给struct定义了方法
var s Student
s.SiteSet("abc",20)
fmt.Println(s)
}
栗子2:
func NewSchool(name,addr string) (*School) {
return &School{
Name:name,
Addr:addr,
}
func (s *School) getaddr() string {
return s.Addr
}
func (s *School) Getaddr() string {
return s.Addr
}
s :=model.NewSchool("北京","海淀")
//调用了实例中的方法
fmt.Printf("school_name:%s",s.Getname())
fmt.Printf("school_name:%s",s.Getaddr())
Struct 中的tag
我们可以为struct中的每个字段,写上⼀个tag。这个tag可以通过反射的机制获取到,最常⽤的场景就是json序列化和反序列化
如下代码中,Zcq做了tag标记,那json就可以通过反射方式匹配值,json会序列化结构体里面的tag,
key:json 后写的值
value: 赋值的参数
在struct中 首字母如果是小写,那就是私有的,只能在main包里面访问
做了tag,那json序列化后的key 可以自定义,解决了:在特殊情况下,必须用小写,但是在go里面小写命名的值,json访问不到的情况,完美
type Student struct{
Name string
Age int
Sex string
ceshi string
//做了tag标记, 还可以写多个值, 往后会学到怎么取这个值
Zcq string `json:"rch";db:"name"`
}
func main(){
s.Zcq = "zcq"
}
>>>
{"Name":"ZCQ","Age":12,"Sex":"mem","rch":"zcq"}
Json序列化/反序列化
序列化
//Marshal 序列化
//data 是一个bytes的切片,
//Marshl是封装
data,err :=json.Marshal(s)
if err != nil{
fmt.Printf("json.Marshal---error",err)
return
}
反序列化
var s1 Student
//Unmarshal :反序列化
//需要传入地址的值,直接去修改值,如果传入的是s1,那传入的是一个副本,
//err这里不需要:= 了,因为上面已经声明了
err =json.Unmarshal(data,&s1)
if err!=nil{
fmt.Printf("json.Unmarshal---error",err)
return
}
fmt.Printf("s1%#v\n",s1)
匿名字段/继承
定义:结构体中字段可以没有名字,即匿名字段,
注意:不能存在相同的名字
type People struct{
Name string
Score int
all int
//没有名字,那就是匿名字段,不能存在相同名字
string
}
继承
注意:在继承中,如果父类和子类都有同一个方法,那么优先访问子类中的方法,
type Student struct{
Name string
Age int
// People 是上面代码中的struct,
//使用了匿名字段,实现了继承
People
//如果都有相同的字段,那默认会先找本身student里面的字段
//相同持有相同字段的话,就需要s.People.Name 才可以访问到
all int
}
func test1() {
var s Student
s.Name = "abc"
s.Age = 100
s.string = "11"
////如果字段没有名字,就用它的类型,来操作这个字段
//s.int = 2000
//s.People.Name = 100
//继承中可以直接访问
s.Score = 100
fmt.Printf("%#v\n",s)
}
func testMethod() {
var s Student
//因为s 继承了People,而且又给People添加了一个方法,所以可以直接访问到这个方法
s.Age = 200
s.People.Name = "anc"
//访问父类中的方法
ret :=s.People.Format()
//访问子类中的方法s.Format()
fmt.Printf(ret)
}
©著作权归作者所有:来自51CTO博客作者zhaichaoqun的原创作品,如需转载,请注明出处,否则将追究法律责任