十一、Go语法基础(指针、结构体和方法)

Golang中文学习文档地址

1、指针

Go中保留了指针,与C/C++不同的是,Go对指针做了限制,不支持指针运算,也就是说指针无法偏移

1.1 指针的创建

  • 指针相关的两个关键字符
    • &:取地址运算符

      arduino 复制代码
        ```go
        import "fmt"
      
        func main() {
                str := "Hello World"
                p := &str
                fmt.Println(p)//输出:0xc0000280a0
        }
        ```
    • *:定义指针类型和解引用

      go 复制代码
      import "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)
}
  • 自定义函数,用于实例化结构体

    go 复制代码
    import "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 实例化的一种设计模式:选项模式

选项模式适合复杂结构体的实例化。

  • 要实例化的结构体

    go 复制代码
    type Person struct {
      Name    string
      Age     int
      address string
    }
  • 定义一个函数,作为Person结构体的选项方法

    go 复制代码
    type 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
            }
    }
  • 实现结构体的构造函数

    go 复制代码
    func NewPerson(opts ...PersonOptions) *Person {
            p := &Person{}
            for _, opt := range opts {
                    opt(p)
            }
            return p
    }
  • 选项模式的使用

    go 复制代码
    func main() {
            person := NewPerson(
                    WithName("张三"),
                    WithAge(18),
                    WithAddress("xm"))
            fmt.Println(person)
    }

2.3 组合

  • 在go中,结构体的关系使用组合的方式表示,可以显示组合,也可以匿名组合

  • 显示组合

    go 复制代码
    import "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)
    }
  • 匿名组合

    go 复制代码
    import "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结构体指针,不需要解引用就可直接访问结构体的数据。

    go 复制代码
    p := &Person{
       name: "jack",
       age:  18,
    }
    fmt.Println(p.age,p.name)
  • 在编译时,编译器会自动解引用p.age转换为(*p).age

2.5 标签

  • 结构体标签是一种元编程的形式,主要结合反射使用。

    go 复制代码
    type Programmer struct {
        Name     string `json:"name"`
        Age      int `yaml:"age"`
        Job      string `toml:"job"`
        Language []string `properties:"language"`
    }

2.6 内存对齐

  • Go 结构体字段的内存分布遵循内存对齐的规则,这么做可以减少 CPU 访问内存的次数,相应的占用的内存要多一些,属于空间换时间的一种手段。

    go 复制代码
    type 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字节)
  • 内存对齐的合理分布

    go 复制代码
    type Num struct {
        A int8
        B int64
        C int8
      }
    • 以上结构体,内存为24个字节
    • 但如果将结构体字段调整下,就会减少结构体内存,为16字节。

      go 复制代码
      type Num struct {
        A int8
        C int8
        B int64
      }
  • 不过在实际的开发过程中,没必要太过于关心。

2.7 空结构体

  • 空结构体没有定义字段,所以在内存上,没有占用任何内存资源。

    go 复制代码
    type 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)
}
  • 函数的参数传递过程中,是值拷贝的,如果传递的是一个整型,那就拷贝这个整型,如果是一个切片,那就拷贝这个切片,但如果是一个指针,就只需要拷贝这个指针,显然传递一个指针比起传递一个切片所消耗的资源更小,接收者也不例外,值接收者和指针接收者也是同样的道理。
相关推荐
研究司马懿2 小时前
【云原生】Gateway API高级功能
云原生·go·gateway·k8s·gateway api
梦想很大很大16 小时前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
lekami_兰21 小时前
MySQL 长事务:藏在业务里的性能 “隐形杀手”
数据库·mysql·go·长事务
却尘1 天前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
ん贤1 天前
一次批量删除引发的死锁,最终我选择不加锁
数据库·安全·go·死锁
mtngt112 天前
AI DDD重构实践
go
Grassto3 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto5 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室6 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题6 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo