十一、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)
}
  • 函数的参数传递过程中,是值拷贝的,如果传递的是一个整型,那就拷贝这个整型,如果是一个切片,那就拷贝这个切片,但如果是一个指针,就只需要拷贝这个指针,显然传递一个指针比起传递一个切片所消耗的资源更小,接收者也不例外,值接收者和指针接收者也是同样的道理。
相关推荐
gopyer21 小时前
Go语言2D游戏开发入门004:零基础打造射击游戏《太空大战》3
golang·go·游戏开发
该用户已不存在1 天前
Golang 上传文件到 MinIO?别瞎折腾了,这 5 个库拿去用
前端·后端·go
华仔啊1 天前
Go 语言未来会取代 Java 吗?别争了,先看完这篇再说
java·后端·go
代码扳手2 天前
Golang 实战:用 Watermill 构建订单事件流系统,一文掌握概念与应用
后端·go
百锦再2 天前
Python、Java与Go:AI大模型时代的语言抉择
java·前端·vue.js·人工智能·python·go·1024程序员节
豆浆Whisky2 天前
掌握Go context:超越基础用法的正确实践模式|Go语言进阶(13)
后端·go
ErizJ3 天前
IM|im-service
golang·kafka·go·im·心跳检测
光头闪亮亮3 天前
curl库应用-c++客户端示例及golang服务端应用示例
c++·go