fmt.Printf("p1=%v\n", p1) //p1={qcq.cn 北京 18}
fmt.Printf("p1=%#v\n", p1) //p1=main.person{name:"qcq.cn", city:"北京", age:18}
}
1. 直接定义
通过结构体可以定义一个组合字面量,有几规则。
规则一:当最后一个字段和结果不在同一行时, 不可省略。
p1 := person{
name: "qcq.cn",
city: "北京",
age: 18,
}
fmt.Printf("p5=%#v\n", p5) //p5=main.person{name:"qcq.cn", city:"北京", age:18}
反之,不在同一行,就可以省略。
xm := Profile{
name: "小明",
age: 18,
gender: "male"}
规则二:字段名要么全写,要么全不写,不能有的写,有的不写。
例如下面这种写法是会报 mixture of field:value and value initializers
错误的
xm := Profile{
name: "小明",
18,
"male",
}
2 . 使用值的列表初始化
字段名 全都 不写
p8 := &person{
"qcq.cn",
"北京",
18,
}
fmt.Printf("p8=%#v\n", p8) //p8=&main.person{name:"qcq.cn", city:"北京", age:18}
需要注意:
1.必须初始化结构体的
所有字段。
2.初始值的填充
顺序必须与字段在结构体中的声明顺序一致。
3.该方式
不能和键值初始化方式混用。
3. 结构体指针初始化
全部写 对结构体指针进行键值对初始化,例如:
p6 := &person{
name: "pprof.cn",
city: "北京",
age: 18,
}
fmt.Printf("p6=%#v\n", p6) //p6=&main.person{name:"pprof.cn", city:"北京", age:18}
规则三:初始化结构体,并不一定要所有字段都赋值,未被赋值的字段,会自动赋值为其类型的零值。
必须指定字段名才可以赋值部分字段。
xm := Profile{name: "小明"}
fmt.Println(xm.age)
// output: 0
否则会报错
command-line-arguments
./demo.go:19:51: too few values in Profile literal
4. 结构体初始化
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}
}
5. 结构体内存布局
可以看出结构体再内存中是连续的
type test struct {
a int8
b int8
c int8
d int8
}
n := test{
1, 2, 3, 4,
}
fmt.Printf("n.a %p\n", &n.a)
fmt.Printf("n.b %p\n", &n.b)
fmt.Printf("n.c %p\n", &n.c)
fmt.Printf("n.d %p\n", &n.d)
输出:
n.a 0xc0000a0060
n.b 0xc0000a0061
n.c 0xc0000a0062
n.d 0xc0000a0063
面试题
type student struct {
name string
age int
}
func main() {
//这个字典的,值为结构体student
m := make(map[string]*student)
stus := []student{
{name: "qcq", age: 22},
{name: "测试", age: 30},
{name: "博客", age: 38},
}
//map无序,所以结果有多种可能
for _, stu := range stus {
//相当于qcq=qcq,22
//&stu是将 该地址的内容赋值
m[stu.name] = &stu
}
for k, v := range m {
fmt.Println(k, "=>", v.name)
}
}
===========================================================================
不能在结构体内定义方法,
可以用组合函数的方式来定义结构体方法。
type Profile struct {
name string
age int
gender string
mother *Profile // 指针
father *Profile // 指针
}
//方法
func (person Profile) FmtProfile() {
fmt.Printf("名字:%s\n", person.name)
fmt.Printf("年龄:%d\n", person.age)
fmt.Printf("性别:%s\n", person.gender)
}
其中FmtProfile 是方法名,而(person Profile) :表示将 FmtProfile 方法与 Profile 的实例绑定。
我们把 Profile 称为方法的接收者,而 person 表示实例本身
,在方法内可以使用 person.属性名 的方法来访问实例属性。
package main
import "fmt"
// 定义一个名为Profile 的结构体
type Profile struct {
name string
age int
gender string
mother *Profile // 指针
father *Profile // 指针
}
// 定义一个与 Profile 的绑定的方法
func (person Profile) FmtProfile() {
fmt.Printf("名字:%s\n", person.name)
fmt.Printf("年龄:%d\n", person.age)
fmt.Printf("性别:%s\n", person.gender)
}
func main() {
// 实例化
myself := Profile{name: "小明", age: 24, gender: "male"}
// 调用函数
myself.FmtProfile()
}
//输出如下
名字:小明
年龄:24
性别:male
//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()
}
当你想要在方法内改变实例的属性的时候,必须使用指针做为方法的接收者。
package main
import "fmt"
// 声明一个 Profile 的结构体
type Profile struct {
name string
age int
gender string
mother *Profile // 指针
father *Profile // 指针
}
// 重点在于这个星号: *, 说明已经有一个struct对象实例化了
//是 再次基础上做修改
func (person *Profile) increase_age() {
person.age += 1
}
func main() {
myself := Profile{name: "小明", age: 24, gender: "male"}
fmt.Printf("当前年龄:%d\n", myself.age)
myself.increase_age()
fmt.Printf("当前年龄:%d", myself.age)
}
//结果
当前年龄:24
当前年龄:25
可以看到在方法内部对 age 的修改已经生效。你可以尝试去掉 *,使用值做为方法接收者,看看age是否会发生改变(答案是:不会改变)
至此,我们知道了两种定义方法的方式:
-
以
值做为方法接收者 -
以
指针做为方法接收者
那我们如何进行选择呢?
- 直接使用
指针做为方法的接收者。
-
你需要在方法内部改变结构体内容的时候
-
出于性能的问题,当结构体过大的时候(相当于,组合)
- 有些情况下,以值或指针做为接收者都可以,但是考虑到代码一致性,
建议都使用指针做为接收者。
指针类型的接收者
// 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 语言本身并不支持继承。
可以使用组合的方法,实现类似继承的效果。
组合:比如一台电脑,是由机身外壳,主板,CPU,内存等零部件组合在一起,最后才有了我们用的电脑。
在 Go 语言中,把一个结构体嵌入到另一个结构体的方法,称之为组合。
现在这里有一个表示公司(company)的结构体,还有一个表示公司职员(staff)的结构体。
type company struct {
companyName string
companyAddr string
}
type staff struct {
name string
age int
gender string
position string
}
若要将公司信息与公司职员关联起来,一般都会想到将 company 结构体的内容照抄到 staff 里。
借鉴继承的思想,我们可以将公司的属性都“继承”过来。
但是在 Go 中没有类的概念,只有组合,
可以将 company 这个 结构体嵌入到 staff 中,做为 staff 的一个匿名字段,staff 就直接拥有了 company 的所有属性了。
type staff struct {
name string
age int
gender string
position string
company // 匿名字段
// cpmpay compay // 嵌套
}
验证一下。
package main
import "fmt"
type company struct {
companyName string
companyAddr string
}
type staff struct {
name string
age int
gender string
position string
company
}
func main() {
myCom := company{
companyName: "Tencent",
companyAddr: "北京市",
}
staffInfo := staff{
name: "小明",
age: 28,
gender: "男",
position: "云计算工程师",
company: myCom,
}
/*
user1 := User{
Name: "pprof",
Gender: "女",
Address: Address{
Province: "陕西",
City: "西安",
},
}
*/
fmt.Printf("%s 在 %s 工作\n", staffInfo.name, staffInfo.companyName)
fmt.Printf("%s 在 %s 工作\n", staffInfo.name, staffInfo.company.companyName)
}
结果,可见staffInfo.companyName 和 staffInfo.company.companyName 的效果是一样的。
小明 在 Tencent 工作
小明 在 Tencent 工作
嵌套类型
嵌套结构体内部可能存在相同的字段名。这个时候为了避免歧义需要指定具体的内嵌结构体的字段
//Address 地址结构体
type Address struct {
Province string
City string
}
//User 用户结构体
type User struct {
Name string
Gender string
Address //匿名结构体
}
func main() {
var user2 User
user2.Name = "pprof"
user2.Gender = "女"
user2.Address.Province = "陕西" //通过匿名结构体.字段名访问
user2.City = "西安" //直接访问匿名结构体的字段名
fmt.Printf("user2=%#v\n", user2) //user2=main.User{Name:"pprof", Gender:"女", Address:main.Address{Province:"黑龙江", City:"哈尔滨"}}
}
继承
使用结构体也可以实现其他编程语言中面向对象的继承
//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() //乐乐会动!
}
函数名的首字母大小写非常重要,它被来实现控制对方法的访问权限。
-
当方法的首字母为
大写时,这个方法对于所有包都是Public,其他包可以随意调用 -
当方法的首字母为
小写时,这个方法是Private,其他包是无法访问。
第一种:正常实例化
func main() {
xm := Profile{
name: "小明",
age: 18,
gender: "male",
}
}
第二种:使用 new
func main() {
xm := new(Profile)
// 等价于: var xm *Profile = new(Profile)
fmt.Println(xm)
// output: &{ 0 }
xm.name = "iswbm" // 或者 (*xm).name = "iswbm"
xm.age = 18 // 或者 (*xm).age = 18
xm.gender = "male" // 或者 (*xm).gender = "male"
fmt.Println(xm)
//output: &{iswbm 18 male}
}
第三种:使用 &
func main() {
var xm *Profile = &Profile{}
fmt.Println(xm)
// output: &{ 0 }
xm.name = "iswbm" // 或者 (*xm).name = "iswbm"
xm.age = 18 // 或者 (*xm).age = 18
xm.gender = "male" // 或者 (*xm).gender = "male"
fmt.Println(xm)
//output: &{iswbm 18 male}
}
从一个结构体实例对象中获取字段的值,通常都是使用 . 这个操作符,该操作符叫做 选择器。
当你对象是结构体对象的指针时,你想要获取字段属性时,按照常规理解应该这么做
type Profile struct {
Name string
}
func main() {
//这里直接将p1定义为指针类型
p1 := &Profile{"iswbm"}
fmt.Println((*p1).Name) // output: iswbm
}
有一个更简洁的做法,可以直接省去 * 取值的操作,选择器 . 会直接解引用,示例如下
type Profile struct {
Name string
}
func main() {
p1 := &Profile{"iswbm"}
fmt.Println(p1.Name) // output: iswbm
}
JSON是一种轻量级的数据交换格式。
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:"value1" key2:"value2"
结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。
注意事项: 为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。
例如我们为Student结构体的每个字段定义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: "pprof",
}
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":"女"}
}
package main
import "fmt"
type student struct {
id int
name string
age int
}
func main() {
ce := make(map[int]student)
ce[1] = student{1, "xiaolizi", 22}
ce[2] = student{2, "wang", 23}
fmt.Println(ce)
//如何删除
delete(ce, 2)
fmt.Println(ce)
}
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!