1、指针
Go中保留了指针,与C/C++不同的是,Go对指针做了限制,
不支持指针运算,也就是说指针无法偏移。
1.1 指针的创建
- 指针相关的两个关键字符
-
&:取地址运算符arduino```go import "fmt" func main() { str := "Hello World" p := &str fmt.Println(p)//输出:0xc0000280a0 } ``` -
*:定义指针类型和解引用goimport "fmt" func main() { str := "Hello World" var p *string //定义指针类型, p = &str //获取str的值 fmt.Println(*p) //解引用,获取指针对应的值 }
-
1.2 new和make
-
func new(Type) *Type- 返回值是
类型指针 - 接收参数是类型
- 专用于给指针分配内存空间
- 返回值是
-
func make(t Type, size ...IntegerType) Type- 返回值
是值,不是指针 - 接收的第一个参数是类型,不定长参数根据传入类型的不同而不同
- 专用于给
切片,映射表,通道分配内存。
- 返回值
2、结构体
在Go中,结构体可以存储多个不同的类型的数据,是复合类型。Go 抛弃了类与继承,同时也抛弃了构造方法,刻意弱化了面向对象的功能,Go 并非是一个传统 OOP 的语言,但是 Go 依旧有着 OOP 的影子,通过结构体和方法也可以模拟出一个类。
2.1 结构体的声明
go
type Person struct {
Name string
Age int
id string
}
- 结构体本身以及其内部的字段都遵守大小写命名。
2.2 实例化
- Go中没有自带的构造方法(像Java一样的构造方法,但可以自己实现构造方法),所以可以像map一样初始化实例。
go
import "fmt"
func main() {
p := Person{
Name: "John Smith",
Age: 18,
address: "xm",
}
fmt.Println(p)
}
type Person struct {
Name string
Age int
address string
}
- 可以省略变量名
go
func main() {
p := Person{
"John Smith",
18,
"xm",
}
fmt.Println(p)
}
-
自定义函数,用于实例化结构体
goimport "fmt" func main() { person := NewPerson("zhangsan", 12, "fj") fmt.Println(person) } func NewPerson(name string, age int, address string) *Person { return &Person{name, age, address} } type Person struct { Name string Age int address string }
2.2.1 实例化的一种设计模式:选项模式
选项模式适合复杂结构体的实例化。
-
要实例化的结构体
gotype Person struct { Name string Age int address string } -
定义一个函数,作为Person结构体的选项方法
gotype PersonOptions func(p *Person) -
为结构体的每一个变量实现一个选项方法
go// name变量的初始化选项 func WithName(name string) PersonOptions { return func(p *Person) { p.Name = name } } // age变量的初始化选项 func WithAge(age int) PersonOptions { return func(p *Person) { p.Age = age } } // addresss变量的初始化选项 func WithAddress(address string) PersonOptions { return func(p *Person) { p.Address = address } } -
实现结构体的构造函数
gofunc NewPerson(opts ...PersonOptions) *Person { p := &Person{} for _, opt := range opts { opt(p) } return p } -
选项模式的使用
gofunc main() { person := NewPerson( WithName("张三"), WithAge(18), WithAddress("xm")) fmt.Println(person) }
2.3 组合
-
在go中,结构体的关系使用组合的方式表示,可以显示组合,也可以匿名组合
-
显示组合
goimport "fmt" type Person struct { Name string Age int Address string } type Student struct { per Person school string } func main() { stu := &Student{per: Person{Name: "zs", Age: 18, Address: "xm"}, school: "xd"} fmt.Println(stu) } -
匿名组合
goimport "fmt" type Person struct { Name string Age int Address string } type Student struct { Person //组合未指定结构体变量名,默认就是和类型名称一样,Person school string } func main() { stu := &Student{Person: Person{Name: "zs", Age: 18, Address: "xm"}, school: "xd"} fmt.Println(stu) }
2.4 结构体的指针
-
Go结构体指针,不需要解引用就可直接访问结构体的数据。
gop := &Person{ name: "jack", age: 18, } fmt.Println(p.age,p.name) -
在编译时,编译器会自动解引用
p.age转换为(*p).age。
2.5 标签
-
结构体标签是一种元编程的形式,主要结合
反射使用。gotype Programmer struct { Name string `json:"name"` Age int `yaml:"age"` Job string `toml:"job"` Language []string `properties:"language"` }
2.6 内存对齐
-
Go 结构体字段的内存分布遵循内存对齐的规则,这么做可以减少 CPU 访问内存的次数,相应的占用的内存要多一些,属于空间换时间的一种手段。
gotype Num struct { A int64 //8个字节 B int32 //4个字节 C int16 //2个字节 D int8 //1个字节 E int32 //4个字节 }从正常逻辑上看,该结构体的内存大小应该为:
8 + 4 + 2 + 1 + 4 = 19字节,但因Go内存分布遵循内存对齐规则,所以事实上,该结构体的内存大小为:8 + 8 + 8 = 24字节。A字段,8字节,为该结构体字段最大的内存,所以结构体占用的内存,为8字节的整数倍。

以上例子:
- 第一个8字节,刚好能够存A字段
- 第二个8字节,能够存储B(4字节)、C(2字节)、D(1字节)字节,剩余的1个字节,无法存下E字段,所以E字段存在下一个8字节
- 第三个8字节,存最后一个E字段(4字节)
-
内存对齐的合理分布
gotype Num struct { A int8 B int64 C int8 }- 以上结构体,内存为24个字节

-
但如果将结构体字段调整下,就会减少结构体内存,为16字节。
gotype Num struct { A int8 C int8 B int64 }

-
不过在实际的开发过程中,没必要太过于关心。
2.7 空结构体
-
空结构体没有定义字段,所以在内存上,没有占用任何内存资源。
gotype Empty struct{} -
在map上可以使用空结构作为值类型,将map当成set使用。
3、方法
方法和函数的区别: 方法有接收者,函数没有,且只有自定义的类型能够拥有方法,即,只有自定义类型能作为接收者。
go
package main
import "fmt"
type MySlice []int
//m MySlice 指定了接收者
func (m MySlice) get(i int) int {
return m[i]
}
func (m MySlice) put(i ,v int) {
m[i] = v //切片本身是引用类型,所以值接收拷贝的也是一个指针,指向的还是同一个切片。
}
func (m MySlice) len() int {
return len(m)
}
func main() {
m := make(MySlice, 10)
m.put(1)
m.put(2)
m.put(3)
m.put(4)
fmt.Println(m.get(3))
fmt.Println(m.len())
}
接收者有两种类型,值接收者和指针接收者。
3.1 值接收者
go
package main
import "fmt"
type MyInt int
func (m MyInt) set(val int) {
m = MyInt(val) //值接收者,相当于是一个形参传入方法,修改形参不会影响方法外的值。
}
func main() {
m := MyInt(9)
m.set(3) //修改后,但不会受影响
fmt.Println(m) // 9
}
3.2 指针接收者
go
import "fmt"
type MyInt int
//接收者为指针
func (m *MyInt) set(val int) {
*m = MyInt(val) //修改的是指针指向的值,所以会影响方法外的原始值
}
func main() {
m := MyInt(9)
m.set(3)
fmt.Println(m)
}
- 函数的参数传递过程中,是值拷贝的,如果传递的是一个整型,那就拷贝这个整型,如果是一个切片,那就拷贝这个切片,但
如果是一个指针,就只需要拷贝这个指针,显然传递一个指针比起传递一个切片所消耗的资源更小,接收者也不例外,值接收者和指针接收者也是同样的道理。