Go (ten) structure

core:

The Go language does not have the concept of "class", nor does it support object-oriented concepts such as "class" inheritance.

         In the Go language, the built-in interface through the structure is more extensible and flexible than object-oriented.

Table of contents

1. Type aliases and custom types

1.1 Custom types

1.2 Type aliases

1.3 Difference between type definition and type alias

2. Structure

2.1 Definition of structure

2.2 Structure instantiation

2.2.1 Basic instantiation

2.2.2 Anonymous structure

2.2.3 Create a pointer type structure

2.2.4 Take the address of the structure and instantiate it

2.3 Structure initialization

2.3.1 Initialize with key-value pairs

2.3.2 List initialization with values

2.4 Structure memory layout

2.4.1 Empty structure

2.5 Constructor

2.6 Methods and Receivers

2.6.1 Receivers of pointer types

2.6.2 Receivers of value types

2.6.3 When should you use a pointer type receiver

2.7 Any type of adding method

2.8 Anonymous fields of structures

2.9 Nested structures

2.9.1 Nested anonymous fields

2.9.2 Field name conflicts in nested structures

2.10 "Inheritance" of structures

2.11 Visibility of structure fields

2.12 Structure and JSON serialization

2.13 Structure tag (Tag)

2.14 Supplementary Knowledge Points for Structures and Methods


1. Type aliases and custom types

1.1 Custom types

        There are some basic data types in the Go language, such as string, 整型, 浮点型, 布尔and other data types. In the Go language, keywords can be used typeto define custom types .

        A custom type is a definition of a completely new type. We can define based on the built-in basic types, or through struct definitions. For example:

//将MyInt定义为int类型
type MyInt int

        Through typethe definition of keywords, MyIntit is a new type, which has intthe characteristics.

1.2 Type aliases

Type aliases are Go1.9a new feature added by version.

        Type alias rules: TypeAlias ​​is just an alias of Type, and TypeAlias ​​and Type are essentially the same type. It's like a child who had a pet name and a baby name when he was a child, and used his scientific name after school, and his English teacher would give him an English name, but these names all refer to himself.

type TypeAlias = Type

        The sums we have seen before runeare bytetype aliases , and they are defined as follows:

type byte = uint8
type rune = int32

1.3 Difference between type definition and type alias

        On the surface, there is only one difference between a type alias and a type definition. We understand the difference between them through the following code.

//类型定义
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
}

        The result shows that the type of a is main.NewInt, indicating the type defined under the main package NewInt. The type of b is int. MyIntTypes only exist in the code, there are no MyInttypes when compilation is complete.

Summarize: 

        The type alias can only be called in another way, but the type definition completely defines a new type (just use int to demonstrate, so that myint has the characteristics of int)


2. Structure

        The basic data types in the Go language can represent the basic attributes of some things, but when we want to express all or part of the attributes of a thing, it is obviously impossible to use a single basic data type at this time. Go language provides a A custom data type that can encapsulate multiple basic data types. This data type is called a structure with an English name struct. That is, we can structdefine our own types by.

     structObject-oriented is implemented    in the Go language .

2.1 Definition of structure

Use typeand structkeywords to define the structure, the specific code format is as follows:

type 类型名 struct {
    字段名 字段类型
    字段名 字段类型
}

in:

  • Type name: The name that identifies the custom structure, which cannot be repeated in the same package .
  • Field name: Indicates the field name of the structure. Field names in a structure must be unique.
  • Field type: Indicates the specific type of the structure field.

        For example, let's define a Person(person) structure with the following code:

type person struct {
	name string
	city string
	age  int8
}

        Fields of the same type can also be written on one line,

type person1 struct {
	name, city string
	age        int8
}

        In this way, we have a personcustom type, which has three fields, which represent namename , city and age respectively.cityage

        In this way, we personcan easily represent and store personal information in the program by using this structure.

        The basic data types built into the language are used to describe a value, and the structure is used to describe a set of values. For example, a person has a name, age, and city of residence, etc., which is essentially an aggregate data type

2.2 Structure instantiation

        Memory is actually allocated only when the struct is instantiated. That is, the fields of the structure must be instantiated before they can be used.

        The structure itself is also a type, and we can use varkeywords to declare the structure type just like declaring built-in types.

var 结构体实例 结构体类型

2.2.1 Basic instantiation

for example:

type person struct {
	name string
	city string
	age  int8
}
func main() {

	a := person{
		name: "jw",
		city: "cs",
		age:  18,
	}
	fmt.Println(a)      //{jw cs 18}
	fmt.Println(a.name) //jw
}

We .access the fields (member variables) of the structure by

2.2.2 Anonymous structure

        Anonymous structures can also be used in scenarios such as defining some temporary data structures.

package main
     
import (
    "fmt"
)
     
func main() {
    var user struct{Name string; Age int} //此时这个user不是结构体名 而是变量名
    user.Name = "小王子"
    user.Age = 18
    fmt.Printf("%#v\n", user)
}

2.2.3 Create a pointer type structure

newWe can also instantiate the structure by using keywords to get the address of the structure. The format is as follows:

var p2 = new(person)
fmt.Printf("%T\n", p2)     //*main.person
fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"", city:"", age:0}

        From the printed results, we can see that p2it is a structure pointer.

It should be noted that the Go language supports direct use of structure pointers .to access structure members.

var p2 = new(person)
p2.name = "小王子"
p2.age = 28
p2.city = "上海"
fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"小王子", city:"上海", age:28}

2.2.4 Take the address of the structure and instantiate it

        Using  the address-taking operation on the structure is equivalent to instantiating the structure type once new.

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}

p3.name = "七米"In fact, at the bottom layer (*p3).name = "七米", this is the syntactic sugar that the Go language helps us achieve.

2.3 Structure initialization

A structure that has not been initialized has its member variables all zero values ​​corresponding to its type.

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}
}

2.3.1 Initialize with key-value pairs

When using a key-value pair to initialize a structure, the key corresponds to the field of the structure, and the value corresponds to the initial value of the field.

p5 := person{
	name: "小王子",
	city: "北京",
	age:  18,
}
fmt.Printf("p5=%#v\n", p5) //p5=main.person{name:"小王子", city:"北京", age:18}

It is also possible to initialize the key-value pair of the structure pointer , for example:

p6 := &person{
	name: "小王子",
	city: "北京",
	age:  18,
}
fmt.Printf("p6=%#v\n", p6) //p6=&main.person{name:"小王子", city:"北京", age:18}

When some fields have no initial value, this field can be omitted. At this time, the value of a field that does not specify an initial value is the zero value of the field type .

p7 := &person{
	city: "北京",
}
fmt.Printf("p7=%#v\n", p7) //p7=&main.person{name:"", city:"北京", age:0}

2.3.2 List initialization with values

When initializing the structure, it can be abbreviated, that is, when initializing, the key is not written, but the value is written directly:

p8 := &person{
	"沙河娜扎",
	"北京",
	28,
}
fmt.Printf("p8=%#v\n", p8) //p8=&main.person{name:"沙河娜扎", city:"北京", age:28}

When initializing with this format, you need to pay attention to:

  1. All fields of the structure must be initialized.
  2. The initial values ​​must be filled in the same order as the fields are declared in the structure.
  3. This method cannot be mixed with the key-value initialization method.

2.4 Structure memory layout

A structure occupies a contiguous block of memory.

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)

output:

n.a 0xc0000a0060
n.b 0xc0000a0061
n.c 0xc0000a0062
n.d 0xc0000a0063

2.4.1 Empty structure

                Empty structures take up no space .

var v struct{}
fmt.Println(unsafe.Sizeof(v))  // 0

2.5 Constructor

        The structure of the Go language does not have a constructor, we can implement it ourselves.

         For example, the code below implements a personconstructor. Because structit is a value type, if the structure is more complex, the value copy performance overhead will be relatively large, so the constructor returns a structure pointer type.

func newPerson(name, city string, age int8) *person {
	return &person{
		name: name,
		city: city,
		age:  age,
	}
}

        call the constructor

p9 := newPerson("张三", "沙河", 90)
fmt.Printf("%#v\n", p9) //&main.person{name:"张三", city:"沙河", age:90}

2.6 Methods and Receivers

        A function in Go  方法(Method)is a function that acts on a variable of a specific type. Variables of this particular type are called 接收者(Receiver). thisThe concept of receiver is similar to or  in other languages self.

The method definition format is as follows:

func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
    函数体
}

in,

  • Receiver variable: When naming the parameter variable name in the receiver, the official suggestion is to use the lowercase of the first letter of the receiver type name instead of naming selfsuch thisas . For example, Personreceiver variables of type should be named  p, Connectorreceiver variables of type should be named c, etc.
  • Receiver type: The receiver type is similar to the parameter and can be a pointer type or a non-pointer type.
  • Method name, parameter list, return parameter: the specific format is the same as the function definition.

for example:

//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()
}

The difference between a method and a function is that a function does not belong to any type, and a method belongs to a specific type.

2.6.1 Receivers of pointer types

        The receiver of the pointer type is composed of a pointer to a structure. Due to the characteristics of the pointer, any member variable of the receiver pointer is modified when the method is called, and the modification is valid after the method ends. This approach is very close to object-oriented thisor in other languages self. For example, we Personadd a SetAgemethod to modify the age of the instance variable.

If you don’t use *, just use the modified method to pass the value

// SetAge 设置p的年龄
// 使用指针接收者
func (p *Person) SetAge(newAge int8) {
	p.age = newAge
}

        Call the method:

func main() {
	p1 := NewPerson("小王子", 25)
	fmt.Println(p1.age) // 25
	p1.SetAge(30)
	fmt.Println(p1.age) // 30
}

2.6.2 Receivers of value types

        When a method acts on a value type receiver, the Go language will make a copy of the receiver's value when the code runs. In the method of the value type receiver, the member value of the receiver can be obtained, but the modification operation is only for the copy, and the receiver variable itself cannot be modified.

// 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
}

2.6.3 When should you use a pointer type receiver

  1. Need to modify the value in the receiver
  2. The receiver is a large object with a relatively high copy cost (saving overhead)
  3. To ensure consistency, if a method uses pointer receivers, other methods should also use pointer receivers.

2.7 Any type of adding method

        In the Go language, the type of the receiver can be any type, not just a structure, any type can have methods.

        For example, we intcan define a new custom type based on the built-in type using the type keyword, and then add methods to our custom 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
}

Note :  Non-local types cannot define methods, which means we cannot define methods for types in other packages.

2.8 Anonymous fields of structures

A structure allows its member fields to have no field names but only types when they are declared. Such fields without names are called anonymous fields.

//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
}

        Note : The anonymous field here does not mean that there is no field name, but the type name is used as the field name by default. The structure requires that the field name must be unique, so there can only be one anonymous field of the same type in a structure.

2.9 Nested structures

A struct can be nested within another struct or a pointer to a struct, as in the sample code below.

//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:"威海"}}
}

2.9.1 Nested anonymous fields

        The nested Addressstructure in the user structure above can also use anonymous fields, for example:

//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:"威海"}}
}

        When accessing a member of a structure, it will first search for the field in the structure, and if it cannot find it, it will search in the nested anonymous field.

2.9.2 Field name conflicts in nested structures

        The same field name may exist inside a nested structure. In this case, in order to avoid ambiguity, it is necessary to specify the specific field name of the embedded structure .

//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
}

2.10 "Inheritance" of structures

The use of structures in Go language can also implement object-oriented inheritance in other programming languages.

//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() //乐乐会动!
}

2.11 Visibility of structure fields

        Fields in the structure start with uppercase for public access, and lowercase for private (accessible only in the package that defines the current structure).

If the first letter of an identifier defined in a Go language package is capitalized, then it is public/private of the courseware

2.12 Structure and JSON serialization

        JSON (JavaScript Object Notation) is a lightweight data exchange format. Easy for humans to read and write. It is also easy for machines to parse and generate. JSON key-value pairs are a way to save JS objects. The key names in the key/value pair combination are written in front and wrapped in double quotes "", separated by colons :, and then followed by values; multiple key-values ​​are separated by ,English .

//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)
}

2.13 Structure tag (Tag)

   TagIt is the meta information of the structure, which can be read through the reflection mechanism at runtime. TagIt is defined behind the structure field and wrapped by a pair of backticks . The specific format is as follows:

`key1:"value1" key2:"value2"`

        The structure tag consists of one or more key-value pairs. Keys and values ​​are separated by colons, and values ​​are enclosed in double quotes. Multiple key-value pairs can be set for the same structure field, and different key-value pairs are separated by spaces.

Note: When  writing for a structure Tag, the rules for key-value pairs must be strictly followed. The error tolerance of the parsing code of the structure tag is very poor. Once the format is wrongly written, no error will be prompted during compilation and runtime, and the value cannot be correctly obtained through reflection. For example don't add spaces between key and value.

        For example, we Studentdefine the Tag used for json serialization for each field of the structure:

//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":"男"}
}

2.14 Supplementary Knowledge Points for Structures and Methods

        Because both data types, slice and map, contain pointers to the underlying data, we must pay special attention when we need to copy them. Let's look at the following example:

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)  // ?
}

        The correct way is to use the copy of the slice passed in for structure assignment in the method.

func (p *Person) SetDreams(dreams []string) {
	p.dreams = make([]string, len(dreams))
	copy(p.dreams, dreams)
}

        The same problem also exists in the case of return value slice and map, which must be paid attention to in the actual coding process.

Guess you like

Origin blog.csdn.net/qq_54729417/article/details/127872196