string
, int
… ,浮点数
等type
关键字定义自定义类型type name T
type TypeAlias = Type
TypeAlias
只是Type
的别名,rune
和 byte
就是类型别名:
type rune = int32
type byte = uint8
type NewInt int
: %T
: main.NewInt
type MyInt = int
: %T
: int
结果显示a的类型是main.NewInt
,表示main包下定义的NewInt
类型。
b的类型是int
。MyInt
类型只会在代码中存在,编译完成时并不会有MyInt
类型。
使用type
和 struct
type 类型名 struct{
字段名 字段类型
字段名 字段类型
}
类型名:同一个包内不能重复
字段名:字段名必须唯一,同样字段类型的字段名可写在同一行
字段类型:结构体字段的具体类型
var
声明:var 结构体实例 结构体类型
声明后,使用操作符.
访问结构体字段(成员变量)进行初始化
type person struct {
name string
city string
age int8
}
func main() {
var p1 person
p1.name = "沙河娜扎"
p1.city = "北京"
p1.age = 18
fmt.Printf("p1=%v\n", p1) //p1={沙河娜扎 北京 18}
fmt.Printf("p1=%#v\n", p1) //p1=main.person{name:"沙河娜扎", city:"北京", age:18}
}
定义一些临时数据结构,使用匿名结构体
var user struct{Name string; Age int}
可以使用new
对结构体进行实例化,得到结构体的地址
var p2 = new(person)
fmt.Printf("%T",p2) //*main.person
fmt.Printf("%#v",p2) //&main.person(..)
p2
是一个结构体指针
注意:Go 支持对结构体指针直接使用.
访问结构体成员
使用&
对结构体进行取地址操作,相当于对该结构体类型 进行了一次new
实例化操作
p3 := &person()
p3.name = "qimi"
p3.name = "qimi"
在底层 是(*p3).name = "qimi"
未初始化的结构体,其成员变量都是对应其类型的零值
type person struct {
name string
city string
age int8
}
func main() {
var p4 person
fmt.Printf("p4=%#v\n", p4) //p4=main.person{name:"", city:"", age:0}
}
键对应结构体的字段
值对应该字段的初始值
p5 := person{
name : "小王子",
city : "beijing",
age : 18,
}
对结构体指针进行键值对初始化:
p6 := &person{
name: "小王子",
city: "北京",
age: 18,
}
未指定初始值的字段的值为该字段类型的零值
p7 := &person{
city: "北京",
}
初始化结构体时,直接写值:
p8 := &person{
"沙河娜扎",
"北京",
28,
}
必须要初始化结构体的所有字段
初始值的填充顺序必须与字段,在结构体中的声明顺序一致
不能和键值对初始化混用
fmt.Printf(unsafe.Sizeof(v))
//0
type student struct {
name string
age int
}
func main() {
m := make(map[string]*student)
stus := []student{
{name: "小王子", age: 18},
{name: "娜扎", age: 23},
{name: "大王八", age: 9000},
}
for _, stu := range stus {
m[stu.name] = &stu //m : "小王子":&student{name: "小王子", age: 18}
}
for k, v := range m {
fmt.Println(k, "=>", v.name)
}
}
执行结果:
小王子 => 小王子
娜扎 => 娜扎
大王八 => 大王八
运行结果:
大王八 => 大王八
小王子 => 大王八
娜扎 => 大王八
Go 结构体没有构造函数,需要自己实现
struct
是值类型,值拷贝性能开销比较大,所以构造函数返回的是结构体指针类型
func newPerson(name, city string, age int8) *person {
return &person{
name: name,
city: city,
age: age,
}
}
调用:
p9 := newPerson("张三", "沙河", 90)
Go 中的方法(Method):一种作用于特定类型变量的函数
接收者Receiver
this
, self
方法的定义格式:
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
函数体
}
self
、this
之类的命名示例:
//Person 结构体
type Person struct {
name string
age int8
}
//NewPerson 构造函数
func NewPerson(name string, age int8) *Person {
return &Person{
name: name,
age: age,
}
}
//Dream Person做梦的方法
func (p Person) Dream() {
fmt.Printf("%s的梦想是学好Go语言!\n", p.name)
}
func main() {
p1 := NewPerson("小王子", 25)
p1.Dream()
}
方法与函数的区别:
指针类型的接收者 : 结构体的指针
调用方法时,修改接收者指针的任意成员变量,都是有效的
this
, self
示例:一个方法:修改实例变量的年龄
// SetAge 设置p的年龄
// 使用指针接收者
func (p *Person) SetAge(newAge int8) {
p.age = newAge
}
func main() {
p1 := NewPerson("小王子", 25)
fmt.Println(p1.age) // 25
p1.SetAge(30)
fmt.Println(p1.age) // 30
}
方法作用于 值类型接收者时,Go 会在运行前将接收者的值复制一份
在值类型接收者的方法中,可以获取接收者的成员值,但无法修改接收者变量本身
// SetAge2 设置p的年龄
// 使用值接收者
func (p Person) SetAge2(newAge int8) {
p.age = newAge
}
func main() {
p1 := NewPerson("小王子", 25)
p1.Dream()
fmt.Println(p1.age) // 25
p1.SetAge2(30) // (*p1).SetAge2(30)
fmt.Println(p1.age) // 25
}
在Go中,任何类型都可以拥有方法,接收者可以是任何类型
例如:将内置类型int
使用关键字type,定义为新的自定义类型,可以为其添加方法
//MyInt 将int定义为自定义MyInt类型
type MyInt int
//SayHello 为MyInt添加一个SayHello的方法
func (m MyInt) SayHello() {
fmt.Println("Hello, 我是一个int。")
}
func main() {
var m1 MyInt
m1.SayHello() //Hello, 我是一个int。
m1 = 100
fmt.Printf("%#v %T\n", m1, m1) //100 main.MyInt
}
注意:不能给其他包的 类型定义方法
结构体 允许 其成员字段在声明时,没有字段名,只有类型
==注意:==Go 会默认采用类型名作为字段名,结构体要求字段名称必须唯一
//Person 结构体Person类型
type Person struct {
string
int
}
func main() {
p1 := Person{
"小王子",
18,
}
fmt.Printf("%#v\n", p1) //main.Person{string:"北京", int:18}
fmt.Println(p1.string, p1.int) //北京 18
}
结构体中,可以嵌套包含 另一个结构体或结构体指针
示例
//Address 地址结构体
type Address struct {
Province string
City string
}
//User 用户结构体
type User struct {
Name string
Gender string
Address Address
}
func main() {
user1 := User{
Name: "小王子",
Gender: "男",
Address: Address{
Province: "山东",
City: "威海",
},
}
fmt.Printf("user1=%#v\n", user1)//user1=main.User{Name:"小王子", Gender:"男", Address:main.Address{Province:"山东", City:"威海"}}
}
嵌套的结构体也可采用匿名字段:
//Address 地址结构体
type Address struct {
Province string
City string
}
//User 用户结构体
type User struct {
Name string
Gender string
Address //匿名字段
}
func main() {
var user2 User
user2.Name = "小王子"
user2.Gender = "男"
user2.Address.Province = "山东" // 匿名字段默认使用类型名作为字段名
user2.City = "威海" // 匿名字段可以省略
fmt.Printf("user2=%#v\n", user2) //user2=main.User{Name:"小王子", Gender:"男", Address:main.Address{Province:"山东", City:"威海"}}
}
当访问结构体成员时,现在结构体中查找该字段,再去嵌套的匿名字段找
嵌套结构体内部可能存在相同的字段名。在这种情况下为了避免歧义需要通过指定具体的内嵌结构体字段名。
//Address 地址结构体
type Address struct {
Province string
City string
CreateTime string
}
//Email 邮箱结构体
type Email struct {
Account string
CreateTime string
}
//User 用户结构体
type User struct {
Name string
Gender string
Address
Email
}
func main() {
var user3 User
user3.Name = "沙河娜扎"
user3.Gender = "男"
// user3.CreateTime = "2019" //ambiguous selector user3.CreateTime
user3.Address.CreateTime = "2000" //指定Address结构体中的CreateTime
user3.Email.CreateTime = "2000" //指定Email结构体中的CreateTime
}
Go 中使用结构体,可以实现其他面向对象中的 继承
//Animal 动物
type Animal struct {
name string
}
func (a *Animal) move() {
fmt.Printf("%s会动!\n", a.name)
}
//Dog 狗
type Dog struct {
Feet int8
*Animal //通过嵌套匿名结构体实现继承
}
func (d *Dog) wang() {
fmt.Printf("%s会汪汪汪~\n", d.name)
}
func main() {
d1 := &Dog{
Feet: 4,
Animal: &Animal{ //注意嵌套的是结构体指针
name: "乐乐",
},
}
d1.wang() //乐乐会汪汪汪~
d1.move() //乐乐会动!
}
JSON
:JavaScript Object Notation
JSON 键值对:用于保存 JS 对象的一种方式
键/值对 格式:"键名" : 值,
//Student 学生
type Student struct {
ID int
Gender string
Name string
}
//Class 班级
type Class struct {
Title string
Students []*Student
}
func main() {
c := &Class{
Title: "101",
Students: make([]*Student, 0, 200),
}
for i := 0; i < 10; i++ {
stu := &Student{
Name: fmt.Sprintf("stu%02d", i),
Gender: "男",
ID: i,
}
c.Students = append(c.Students, stu)
}
//JSON序列化:结构体-->JSON格式的字符串
data, err := json.Marshal(c)
if err != nil {
fmt.Println("json marshal failed")
return
}
fmt.Printf("json:%s\n", data)
//JSON反序列化:JSON格式的字符串-->结构体
str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"Gender":"男","Name":"stu01"},{"ID":2,"Gender":"男","Name":"stu02"},{"ID":3,"Gender":"男","Name":"stu03"},{"ID":4,"Gender":"男","Name":"stu04"},{"ID":5,"Gender":"男","Name":"stu05"},{"ID":6,"Gender":"男","Name":"stu06"},{"ID":7,"Gender":"男","Name":"stu07"},{"ID":8,"Gender":"男","Name":"stu08"},{"ID":9,"Gender":"男","Name":"stu09"}]}`
c1 := &Class{}
err = json.Unmarshal([]byte(str), c1)
if err != nil {
fmt.Println("json unmarshal failed!")
return
}
fmt.Printf("%#v\n", c1)
}
Tag
是结构体中的 元信息,运行的时候通过反射机制读取出来
Tag
在结构体字段的后方定义,用反引号包裹
`key1:"balue1" key2:"value2"`
结构体tag
有一个或多个键值对组成
==注意: ==为结构体编写Tag
时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。
示例:每个字段定义json序列化时使用的 Tag
//Student 学生
type Student struct {
ID int `json:"id"` //通过指定tag实现json序列化该字段时的key
Gender string //json序列化是默认使用字段名作为key
name string //私有不能被json包访问
}
func main() {
s1 := Student{
ID: 1,
Gender: "男",
name: "沙河娜扎",
}
data, err := json.Marshal(s1)
if err != nil {
fmt.Println("json marshal failed!")
return
}
fmt.Printf("json str:%s\n", data) //json str:{"id":1,"Gender":"男"}
}
slice 和 map都包含了指向底层数据的指针,在复制时需要特别注意
type Person struct {
name string
age int8
dreams []string
}
func (p *Person) SetDreams(dreams []string) {
p.dreams = dreams
}
func main() {
p1 := Person{name: "小王子", age: 18}
data := []string{"吃饭", "睡觉", "打豆豆"}
p1.SetDreams(data)
// 你真的想要修改 p1.dreams 吗?
data[1] = "不睡觉"
fmt.Println(p1.dreams) // ?
}
正确的做法:在方法中使用传入的 slice 的拷贝,进行结构体赋值
func (p *Person) SetDreams(dreams []string) {
p.dreams = make([]string, len(dreams))
copy(p.dreams, dreams)
}
同样的问题:返回值 slice 和 map情况