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.3 Difference between type definition and type alias
2.2.3 Create a pointer type structure
2.2.4 Take the address of the structure and instantiate it
2.3.1 Initialize with key-value pairs
2.3.2 List initialization with values
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.8 Anonymous fields of structures
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.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 usedtype
to 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 type
the definition of keywords, MyInt
it is a new type, which has int
the characteristics.
1.2 Type aliases
Type aliases are Go1.9
a 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 rune
are byte
type 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
. MyInt
Types only exist in the code, there are no MyInt
types 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 canstruct
define our own types by.
struct
Object-oriented is implemented in the Go language .
2.1 Definition of structure
Use type
and struct
keywords 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 person
custom type, which has three fields, which represent name
name , city and age respectively.city
age
In this way, we person
can 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
var
keywords 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
new
We 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 p2
it 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:
- All fields of the structure must be initialized.
- The initial values must be filled in the same order as the fields are declared in the structure.
- 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
person
constructor. Becausestruct
it 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)
. this
The 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
self
suchthis
as . For example,Person
receiver variables of type should be namedp
,Connector
receiver variables of type should be namedc
, 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
this
or in other languagesself
. For example, wePerson
add aSetAge
method 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
- Need to modify the value in the receiver
- The receiver is a large object with a relatively high copy cost (saving overhead)
- 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
int
can 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 Address
structure 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)
Tag
It is the meta information of the structure, which can be read through the reflection mechanism at runtime. Tag
It 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 Student
define 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.