go语言之结构体、跨平台编译、方法
一、结构体
1.定义(type 结构体名字 struck{})
一系列属性的集合
2、创建结构体
基本使用
可以在包外创建,然后引用,注意字段名首字母大写才能从包外引用。此时在目录下创建一个entity文件夹并在里面创建.go脚本写结构体,然后创建和entity同一个级别的.go脚本使用entity里面的结构体
//在entity下的.go脚本创建结构体 package entity //在包中定义结构体,创建命名结构体 type Person struct { Name string Age int Sex string }
包外使用结构体
package main import ( "fmt" "go/day2/strcture/entity" ) //使用结构体 //已经在entity定义了结构体 func main() { //结构体的使用 //var per entity.Person //表示声明一个变量,类型为结构体里面的Person结构体类型 //fmt.Println(per) //per.Name = "aaa" //per.Age = 18 //fmt.Println(per) //定义并赋值 //var per entity.Person = entity.Person{Name: "aaa"} //不按位置,就会少传 //var per1 entity.Person = entity.Person{"aaa",18,"男"} //按位置,可以全传 //fmt.Println(per1) }
匿名结构体:匿名结构体(定义在内部,只使用一次),用于当定义多个变量时,并且在一次使用
package main import "fmt" func main() { //匿名结构体(定义在内部,只使用一次) //用于1.当定义多个变量时,并且在一次使用 a := struct { HobbyId int64 HobbyName string }{HobbyId: 1,HobbyName: "球"} fmt.Println(a.HobbyName) a.HobbyName = "足球" fmt.Println(a.HobbyName) }
结构体零值:是值类型,copy传递,在函数中修改不会影响原来的,只有引用字段影响
package main import ( "fmt" "go/day2/strcture/entity" ) func main() { //结构体零值,值类型,在函数中修改不会影响原来的 var per entity.Person fmt.Println(per) //属性零值,值类型,参数传递,copy传递,在函数中修改不会影响原来的 { 0 } test3(per) fmt.Println(per) //{ 0 } 此时并未对函数造成影响 //访问结构体字段:结构体.字段 (注意字段大小写) } func test3(per entity.Person) { per.Age=99 fmt.Println(per) // { 99 } }
结构体指针
package main import ( "fmt" "go/day2/strcture/entity" ) func main() { //结构体指针 //var per *entity.Person //fmt.Println(per) //指针零值<nil> //结构指针定义并初始化 var per *entity.Person = &entity.Person{} fmt.Println(per) //&{ 0 } //把per的名字字段改 (*per).Name = "bbb" //支持直接使用(数组也一样) //per.Name = "bbb" fmt.Println(per) //&{bbb 0 } }
匿名字段:用于字段提升。字段匿名(如下面结构体就写了类型string和int,前面没有字段名),类型就是字段名
package main import "fmt" //定义一个结构体,内涵匿名字段,匿名字段类型就是字段名,所以类型不能重复,此时Sex就不能去掉。可做变量提升(面向对象的继承) type Person1 struct { string int Sex string }
func main() { //匿名字段,字段没名字只有类型 per := Person1{"aaa",18,"男"} //字段匿名,类型就是字段名 fmt.Println(per) }
结构体嵌套
package main import "fmt" //结构体嵌套 type Person2 struct { Name string Age int Sex string Hobby //此时Hobby为匿名字段,但其前面也可以加属性名,如下 ////Hobby Hobby Hobby Hobby嵌套下面的结构体Hobby代表属性名,Hobby代表类型,嵌套Hobby结构体 } type Hobby struct { HobbyId int HobbyName string } func main() { //结构体嵌套 //var per4 Person2 = Person2{"aaa",18,"男",Hobby{1,"球球"}} //嵌套的结构体直接用花括号传就是 var per4 Person2 = Person2{Name:"aaa",Age:18,Sex: "男",Hobby:Hobby{HobbyId: 1,HobbyName: "球球"}} //直接指明参数传 fmt.Println(per4.Name) fmt.Println(per4.Hobby.HobbyName) }
字段提升
package main import "fmt" //结构体嵌套 type Person3 struct { Name string Age int Sex string Hobby //Hobby Hobby嵌套下面的结构体Hobby代表属性名,Hobby代表类型,嵌套Hobby结构体 } type Hobby struct { HobbyId int HobbyName string } func main() { //字段提升 var per Person3 = Person3{Name:"aaa",Age:18,Sex: "男",Hobby:Hobby{HobbyId: 1,HobbyName: "球球"}} //打印爱好的名字(Hobby是一个匿名字段,会提升和第一个嵌套体一样的层面) //由于提升Hobby下面两种打印结果一样 fmt.Println(per.HobbyName) fmt.Println(per.Hobby.HobbyName) }
结构体相等性
package main import "fmt" //结构体相等性 type Person5 struct { Name string Age int Sex string //包含不可比较字段 //c []int } func main() { //结构体相等性,如果结构体每字段都是可比较的(值类型),则该结构体可以比较,如果包含不可比较的(引用类型),则不可比较 per1 := Person5{Name: "aaa"} per2 := Person5{Name: "aaa"} fmt.Println(per1==per2) //未包含引用类型true fmt.Println(per1==per2) //包含引用类型直接报错 }
二、跨平台编译
直接在代码终端输入命令
windows编译mac和Linux64可执行文件
SET CGO_ENABLED=0 SET GOOS=darwin SET GOARCH=amd64 go build main.go SET CGO_ENABLED=0 SET GOOS=linux SET GOARCH=amd64 go build main.go
mac编译Windows和liunx
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go
Linux编译mac和windows
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go
三、方法
特殊函数,在函数的基础上加了一些东西,
在 func 这个关键字和方法名中间加入了一个特殊的接收器类型,接收器可以是结构体类型,也可以是非结构体类型
1、定义一个方法:
package main import "fmt" type Person2 struct { Name string Age int Sex string } //定义一个方法 : (p Person2) 绑定给了Person2结构体的对象 func (p Person2) printName() { //在方法内可以使用p fmt.Println(p.Name) } func main() { per := Person2{} per.Name = "aaa" per.printName() //绑定给对象的方法,相当于printName()是Person2的方法.方法的特殊之处在于自动传值 }
有值方法(值类型,值接收器)
package main import "fmt" type Person2 struct { Name string Age int Sex string } //有值的方法(p此时为值类型,改动不会对原数据修改) func (p Person2) ChangeName(name string) { //此时就修改name,调用Person2{}的方法ChangeName,就会传值给name p.Name = name fmt.Println(name) //bbb } func main() { per := Person2{Name: "aaa"} per.ChangeName("bbb") //传入要改的值 fmt.Println(per) //并没有修改 }
有值方法(引用类型,指针接收器)
package main import "fmt" type Person2 struct { Name string Age int Sex string } //有值的方法(p此时为值类型,改动不会对原数据修改) func (p *Person2) ChangeAge(age int) { //此时就修改name,调用Person2{}的方法ChangeName,就会传值给name p.Age = age fmt.Println(age) //19 //fmt.Println((*p).Age) 这个方法也可以,但是指针此时可以直接使用,所以推荐上面这种方法 } func main() { per := Person2{Age: 18} per.ChangeAge(19) //传入要改的值 fmt.Println(per) //此时已经修改,因为指针类型为引用类型,一变全变 { 19 } }
注意:二者使用场景,当拷贝结构体代价过于昂贵,考虑下一个结构体有很多字段,在方法内使用这个结构体作为值接收器需要拷贝这个结构体,这种情况下使用指针。指针用得多
2、匿名字段方法:方法提升
package main import "fmt" //匿名字段的方法 type Person2 struct { Name string Age int Sex string Hobby //匿名字段 } type Hobby struct { Id int Name string } //给结构体绑定方法 func (p Person2)printName() { fmt.Println(p.Name) } //func (h Hobby)printHobbyName() { func (h Hobby)printName() { fmt.Println(h.Name) } func main() { per := Person2{Name: "AAA",Hobby:Hobby{Name: "足球"}} fmt.Println(per.Name) //AAA //per.printHobbyName() //hobby是匿名字段,方法也提升了 足球 //如果方法重名了,优先使用结构体自己的 per.printName() //AAA per.Hobby.printName() //足球 }
3、在方法中使用值接收器 与 在函数中使用值参数
package main import "fmt" type Person2 struct { Name string Age int Sex string } //在方法中使用值接收器 func (p Person2)printName() { fmt.Println(p.Name) //aaa } func (p Person2)changeName(name string) { p.Name=name fmt.Println(p) } //在函数中使用值参数 func printName(p Person2) { fmt.Println(p.Name) } func main() { per1:=Person2{Name: "aaa"} per1.printName() printName(per1) //aaa //per1:=&Person2{Name: "aaa"} //per1是个指针 //per1.printName() //printName(*per1) //小研究 //per1:=&Person2{Name: "aaa"} //per1是个指针 //per1.changeName("bbb") //fmt.Println(per1) //值收器:可以用值来调,也可以用指针来调 //函数的值参数,只能传值 }
4、在方法中使用指针收器 与 在函数中使用指针参数
package main import "fmt" //7 在方法中使用指针接收器 与 在函数中使用指针参数 type Person2 struct { Name string Age int Sex string } //在方法中使用值接收器 func (p *Person2)printName() { fmt.Println(p.Name) } func (p *Person2)changeName(name string) { p.Name=name fmt.Println(p) } //在函数中使用指针参数 func printName(p *Person2) { //fmt.Println((*p).Name) fmt.Println(p.Name) } func main() { per1:=Person2{Name: "aaa"} per1.printName() //值可以来调用 printName(&per1) //per1:=&Person2{Name: "aaa"} //per1.printName() //指针可以来调用 //printName(per1) //小研究 //per1:=Person2{Name: "aaa"} //per1.changeName("bbb") //fmt.Println(per1) //per1:=&Person2{Name: "aaa"} //per1.changeName("bbb") //fmt.Println(per1)} }
对3和4总结:不管是值类型接收器还是指针类型接收器,都可以用值来调用,或者指针来调用。
不管是值还是指针来调用,只要是值类型接收器,改的就是新的,只要是指针类型接收器,改的是原来的
5、非结构体绑定方法(只能是自己定义的非结构体)
package main import "fmt" //8 非结构体上的方法(不允许)自己定义的类型可以绑定方法 //在int类型上绑定一个add方法 //不允许 //func (i int)add(){ // i=i+1 // i++ // //i+=1 //} // 可以在自定义的类型上绑定方法 type Myint int //定义自己的非结构体 //给自己定义的非结构体绑定方法add() func (i *Myint)add(){ (*i)=(*i)+1 //i++ //i+=1 } func main() { //8 非结构体上绑定方法 var a Myint =10 fmt.Println(a) a.add() a.add() a.add() a.add() fmt.Println(a) //var b =11 ////fmt.Println(a+b) //类型不匹配 // //c:=a+Myint(b) //fmt.Println(a+Myint(b)) //类型匹配 //d:=int(a)+b //fmt.Println(int(a)+b) //类型匹配 }
四、接口(type 接口名字 interface{})
go也是鸭子类型:我现在有个鸭子类,内有speak方法 有run方法, 子类只要实现了speak和run,我就认为子类是鸭子
1、定义
面向对象的领域里,接口一般这样定义:接口定义一个对象的行为,规范子类对象的行为。
是一系列方法的集合(规范行为)
2、定义接口
package main import "fmt" type Duck interface { //speak(name string)(int) //speak()方法,第一个括号为参数,第二个括号为返回值类型.若没有就不写,只写一个括号即可 speck() //speak()方法 run() } //定义一个普通鸭子结构体 type PDuck struct { name string sex string age int } //定义一个唐老鸭结构体 type TDuck struct { name string sex string age int wife string } //让唐老鸭和普通鸭子都实现Duck接口 //结构体实现一个接口:只要绑定接口中的所有方法,就叫实现该接口 func (p PDuck)speak() { fmt.Println("普通鸭子嘎嘎叫,普通鸭子名字叫",p.name) } func (p PDuck)run() { fmt.Println("普通鸭子歪歪走,普通鸭子名字叫",p.name) } //唐老鸭也实现Duck接口 func (p TDuck)speak() { fmt.Println("唐老鸭说话,唐老鸭子名字叫",p.name) } func (p TDuck)run() { fmt.Println("唐老鸭人路,唐老鸭子名字叫",p.name) } func main() { //1 得到一个普通鸭子对象 pduck:=PDuck{"狗子","男",1} pduck.run() pduck.speak() ////2 得到一个堂老鸭子对象 tduck:=TDuck{"aaa","男",1,"小刘"} tduck.run() tduck.speak() //侵入式接口(接口没了,子类报错)和非侵入是接口(接口没了,不影响代码,go语言中的接口是非侵入式的) }
3、把接口类型转成struct属性,(这样自有方法也可以使用)
package main import "fmt" type Duck interface { //speak(name string)(int) //speak()方法,第一个括号为参数,第二个括号为返回值类型.若没有就不写,只写一个括号即可 //speck() //speak()方法 run() } //定义一个普通鸭子结构体 type PDuck struct { name string sex string age int } //定义一个唐老鸭结构体 type TDuck struct { name string sex string age int wife string } //让唐老鸭和普通鸭子都实现Duck接口 //结构体实现一个接口:只要绑定接口中的所有方法,就叫实现该接口 func (p PDuck)speak() { fmt.Println("普通鸭子嘎嘎叫,普通鸭子名字叫",p.name) } func (p PDuck)run() { fmt.Println("普通鸭子歪歪走,普通鸭子名字叫",p.name) } //唐老鸭也实现Duck接口 func (p TDuck)speak() { fmt.Println("唐老鸭说话,唐老鸭子名字叫",p.name) } func (p TDuck)run() { fmt.Println("唐老鸭走路,唐老鸭子名字叫",p.name) } func main() { //类型断言(一般不用这个) //var duck Duck =TDuck{"aaa","男",1,"小刘"} ////断言是TDuck类型 ////v, ok := duck.(TDuck) //////断言成功,ok是true,v就是TDuck结构体对象 ////fmt.Println(v) ////fmt.Println(v.name) ////fmt.Println(ok) // ////断言失败 //var v PDuck //var ok bool //v, ok = duck.(PDuck) ////断言失败,ok是false,v是PDuck类型的空置,因为没有复制 //fmt.Println(ok) //fmt.Println(v) //一般采用Switch,基于类型断言来做的 var duck Duck =TDuck{"aaa","男",1,"小刘"} //var duck Duck =PDuck{"aaa","男",1} test4(duck) } //使用switch,选择成功,拿到结构体对象 func test4(duck Duck) { switch v:=duck.(type) { case PDuck: fmt.Println(v.name) //此时判断的v后,即可拿到v的结构体对象,就可以使用其对应的方法。如v.name fmt.Println("我是普通鸭子") case TDuck: fmt.Println(v.wife) fmt.Println("我是唐老鸭") default: fmt.Println(v) fmt.Println("我是鸭子这个类") } }
4、空接口
package main import "fmt" type Empty interface { } func main() { //空接口(没有任何方法,所有数据类型都实现了空接口) var a int=10 var b string="aaa" var c [3]int var e Empty //空接口类型 e=a e=b e=c fmt.Println(e) fmt.Println(1,"xxx") test5(a) test5(b) test5(c) //匿名空接口 test6(10) test6("lll") //var duck TDuck =TDuck{"aaa","男",1,"小刘"} 上面代码的鸭子类型也可以传给匿名空接口 //test6(duck) //8 之前学过的集合,切片等类型,都可以放接口类型 //var a[3]Duck //a[1]=PDuck{} //a[2]=TDuck{} //var a map[string]interface{}= make(map[string]interface{}) 集合 //a["name"]="lqz" //a["age"]=19 //a["duck"]=PDuck{} } //因为是空接口,所以传a,b,c都可以,2.0出了泛型。 func test5(z Empty) { switch v:=z.(type) { case string: fmt.Println("我是字符串") fmt.Println(v) case int: fmt.Println("我是int") fmt.Println(v) case [3]int: fmt.Println("我是数组") fmt.Println(v) } } func test6(b interface{}) { fmt.Println(b) }
接口高级应用:
1、实现多个接口
package main import "fmt" //1 实现多个接口 type Duck interface { speak() //speak()方法 run() } type Animal interface { eat() sleep() } //定义一个普通鸭子结构体 type PDuck struct { name string sex string age int } //定义一个唐老鸭结构体 type TDuck struct { name string sex string age int wife string } //让唐老鸭和普通鸭子都实现Duck接口 //结构体实现一个接口:只要绑定接口中的所有方法,就叫实现该接口 func (p PDuck)speak() { fmt.Println("普通鸭子嘎嘎叫,普通鸭子名字叫",p.name) } func (p PDuck)run() { fmt.Println("普通鸭子歪歪走,普通鸭子名字叫",p.name) } //唐老鸭也实现Duck接口 func (p TDuck)speak() { fmt.Println("唐老鸭说话,唐老鸭子名字叫",p.name) } func (p TDuck)run() { fmt.Println("唐老鸭走路,唐老鸭子名字叫",p.name) } //唐老鸭实现Animal接口 func (p TDuck)sleep() { fmt.Println("唐老鸭说人话,唐老鸭子名字叫",p.name) } func (p TDuck)eat() { fmt.Println("唐老鸭人走路,唐老鸭子名字叫",p.name) } func main() { //1 实现多个接口 var t TDuck=TDuck{} var a Animal var d Duck //一旦转到某个接口上,只能使用接口的方法,自身属性和自身方法需要类型断言后才能使用 a=t //只能调用到a接口的方法 d=t //只能调用到d接口的方法 }
2、接口嵌套
package main import "fmt" //接口嵌套 type Duck interface { Animal speak() //speak()方法 run() } type Animal interface { eat() sleep() } //定义一个普通鸭子结构体 type PDuck struct { name string sex string age int } //让唐老鸭和普通鸭子都实现Duck接口 //结构体实现一个接口:只要绑定接口中的所有方法,就叫实现该接口 func (p PDuck)speak() { fmt.Println("普通鸭子嘎嘎叫,普通鸭子名字叫",p.name) } func (p PDuck)run() { fmt.Println("普通鸭子歪歪走,普通鸭子名字叫",p.name) } //普通鸭实现Animal接口 func (p PDuck)sleep() { fmt.Println("唐老鸭说人话,唐老鸭子名字叫",p.name) } func (p PDuck)eat() { fmt.Println("唐老鸭人走路,唐老鸭子名字叫",p.name) } func main() { // var t PDuck=PDuck{} var a Animal var d Duck //一旦转到某个接口上,只能使用接口的方法,自身属性和自身方法需要类型断言后才能使用 a=t //只能调用到a接口的方法,此时只能调Animal的方法 d=t //能调用Animal和PDuck的四个方法,因为PDuck嵌套了Animal }
3、接口零值:nil
var a Animal //nil 为引用类型 fmt.Println(a)
五、自定义集合类型
package main import "fmt" //定义MySet类型 type MySet map[interface{}]bool //判断元素是否存在 func (m MySet) isExist(a interface{})bool { return m[a] } //返回set长度 func (m MySet) len() int { return len(m) } //设置值 func (m MySet) set(a interface{}) { m[a] = true } //删除值 func (m MySet) delete(a interface{}) { delete(m, a) } //测试代码 func main() { //创建一个set var a MySet = make(MySet) //相当于 var a MySet = make(map[interface{}]bool) //打印set的长度 //fmt.Println(a.len()) //放入一个值 a.set(1) //放入一个相同值 a.set(1) a.set("aaa") a.set("aaa") a.set("aaa") a.set("aaa") a.set("aaa") a.set("aaa") //无论放几个。打印长度,还是1 //fmt.Println(a.len()) //判断1是否存在 //fmt.Println(a.isExist(2)) ////删除1 a.delete(1) ////判断1是否存在 fmt.Println(a.isExist(1)) //false fmt.Println(a.len()) //1 for i,_:=range a{ fmt.Println(i) //aaa } }
六、make和new的区别
new返回type类型的指针,make直接返回type类型的值
package main //make和new的区别 type PDuck1 struct { name string sex string age int } func main() { //make是引用类型初始化的时候用的 //var per *PDuck1 =new(PDuck1) //new 是返回指向这个类型的指针 //fmt.Println(per) //上面的代码可以用new来创造 //var per1 =&PDuck1{} //fmt.Println(per1) //var per2 = make([]int,3,4) //make是具体的造引用类型 //new是造指向这个类型的指针 //var per2 *[]int= new([]int) //fmt.Println(per2) //(*per2)=append((*per2),99) //fmt.Println(per2) }
七、结构体取代类的使用
先去包里面定义一个结构体并绑定方法
package Person import "fmt" type Person struct { Name string Age int Sex string } func New(Name string,Age int,Sex string) Person{ return Person{Name ,Age,Sex} } //绑定方法 func (p Person)PrintName() { fmt.Println(p.Name) }
导入包并使用包里面的绑定方法
package main import ( person "go/day2/strcture/Person" "fmt" ) func main() { per :=person.New("aaa",19,"男") // //var per Person = new Person("aaa",19,"男") fmt.Println(per) per.PrintName() //就可以调用导入包的绑定方法 }