本文是李文周的博客go语言学习之路的目录提要版,方便复习查看使用,原文参见文章连接:
https://www.liwenzhou.com/posts/Go/go_menu/
Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。
结构体总结
类型定义和类型别名的区别
//类型定义
type NewInt int
//类型别名
type MyInt = int
func main() {
var a NewInt
var b MyInt
fmt.Printf("type of a:%T\n", a) //type of a:main.NewInt
fmt.Printf("type of b:%T\n", b) //type of b:int
}
结构体
// 结构体定义
type 类型名 struct {
字段名 字段类型
字段名 字段类型
…
}
type person struct {
name string
city string
age int8
}
type person1 struct {
name, city string
age int8
}
结构体实例化
type person struct {
name string
city string
age int8
}
// 实例化总结
var p1 person
var p2 = new(person)
var p3 = person{
name: "yzy"
age: 18
}
var p4 = &person{
name: "yyy"
city: "南京"
}
// 1. 基本实例化
var p person // 声明一个p 类型是person
p.name = "yzy"
// 2.创建指针类型结构体
var p2 = new(person)
fmt.Printf("%T\n", p2) //*main.person
fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"", city:"", age:0}
p2.name = "小王子"
p2.age = 28
p2.city = "上海"
fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"小王子", city:"上海", age:28}
// 3.结构体的地址实例化
p3 := &person{}
fmt.Printf("%T\n", p3) //*main.person
fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"", city:"", age:0}
p3.name = "七米"
p3.age = 30
p3.city = "成都"
fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"七米", city:"成都", age:30}
// 使用键值对初始化
p5 := person{
name: "小王子",
city: "北京",
age: 18,
}
fmt.Printf("p5=%#v\n", p5) //p5=main.person{name:"小王子", city:"北京", age:18}
p6 := &person{
name: "小王子",
city: "北京",
age: 18,
}
fmt.Printf("p6=%#v\n", p6) //p6=&main.person{name:"小王子", city:"北京", age:18}
// 使用值的列表初始化
p8 := &person{
"沙河娜扎",
"北京",
28,
}
fmt.Printf("p8=%#v\n", p8) //p8=&main.person{name:"沙河娜扎", city:"北京", age:28}
构造函数
Go语言的结构体没有构造函数,我们可以自己实现。 例如,下方的代码就实现了一个person
的构造函数。 因为struct
是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大,所以该构造函数返回的是结构体指针类型。
func newPerson(name, city string, age int8) *person{
return &person{
name: name,
city: city,
age: age,
}
}
调用构造函数
p1 := bewPerson("yzy","南京",18)
fmt.Printf("%#v\n", p1) //&main.person{name:"张三", city:"沙河", age:90}
方法的接收者
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
函数体
}
// 值类型的接收者
//Dream Person做梦的方法
func (p Person) Dream() {
fmt.Printf("%s的梦想是学好Go语言!\n", p.name)
}
// 指针类型的接收者
指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其他语言中面向对象中的this或者self。 例如我们为Person添加一个SetAge方法,来修改实例变量的年龄。
func (p *Person) SetAge(newAge int8){
p.age = newAge
}
注:什么时候应该使用指针类型接收者
1. 需要修改接收者中的值
2. 接收者是拷贝代价比较大的大对象
3. 保证一致性,如果有某个使用方法使用了指针接收者,那么其他的方法也应该使用指针接收者
为任意类型添加方法
//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
}
注意事项: 非本地类型不能定义方法,也就是说我们不能给别的包的类型定义方法。
嵌套结构体
一个结构体中可以嵌套包含另一个结构体或结构体指针。
type Address struct{
Province string
City string
}
type User struct{
Name string
Gender string
Address Address
}
func main(){
user1 := User{
Name: "yzy"
Gender: "男"
Address: {
Province: "山东"
City: "威海"
}
}
}
嵌套匿名结构体
type Address struct{
Province string
City string
}
type User struct{
Name string
Gender string
Address // 匿名结构体
}
func main(){
var user User
user.Name = "小王子"
user.Gender = "男"
user.Address.Province ="江苏" // 通过匿名结构体.字段名访问
user.City = "南京" // 直接访问匿名结构体的字段名
fmt.Printf("user=%#v\n", user) // user2=main.User{Name:"小王子", Gender:"男", Address:main.Address{Province:"山东", City:"威海"}}
}
}
注:
- 当访问结构体成员时会先在结构体中查找该字段,找不到再去匿名结构体中查找。
- 嵌套结构体的字段名冲突:嵌套结构体内部可能存在相同的字段名。这个时候为了避免歧义需要指定具体的内嵌结构体的字段。
结构体的继承:
Go语言中使用匿名结构体也可以实现其他编程语言中面向对象的继承
// dog是animal一种 继承名字
type Animal struct{
Name string
Age int8
}
func (a *Animal) move(){
fmt.Printf("%s会动!\n",a.name)
}
type Dog struct{
Feet int8
Animal // 使用匿名结构体的特性实现“继承”
}
func ( d *Dog) say(){
fmt.Printf("%s can wangwangwang",d.name)
}
func main(){
d := &Dog{
Feet: 4,
Animal: &Animal{ //注意嵌套的是结构体指针
name: "five",
}
}
d.move()
d.wang()
}
注:
结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。
结构体与JSON序列化
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)
//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":"男"}
}
结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。 注意事项: 为结构体编写Tag
时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。