Go【No-9】指针

本文介绍了Go语言中的指针,包括定义指针、取地址、取值、结构体指针的使用,以及指针作为函数参数和返回值的场景。同时,文章详细阐述了new和make的区别,new用于为任何类型开辟空间并返回其指针,而make则专门用于初始化slice、map和channel,并直接返回这些类型的实例。

9-指针

区别于C/C++中的指针,Go中的指针不能进行偏移和运算,是安全指针,

任何程序数据载入内存后,在内存都有他们的地址,这就是指针。
为了保存一个数据在内存中的地址,我们就需要指针。

Go 中的指针操作非常简单:&取地址符、*根据地址取值。

定义指针、指针取地址

注意:指针是值类型!!!

var identifier *T
var identifier *T = &variable
identifier := &variable

identifier:指针名、T指针基类型、*T某类型指针、variable:变量、&variable:取变量地址

Go 中的值类型(int、float、bool、string、数组、struct结构体)都有对应类型指针,如 *int、*float、*string、*bool、*[5]int

func main() {
	i := 1
	var p *int = &i

	fmt.Printf("i:     %d \n", i)	   // i:    1 
	fmt.Printf("iaddr: %p \n", &i)     // addr: 0xc000012090 
	fmt.Printf("p:     %p \n", p)      // p:    0xc000012090
	fmt.Printf("paddr: %p \n", &p)     // addr: 0xc000006028 
	fmt.Printf("pint Type: %T \n", p)  // pint Type: *int
}

在这里插入图片描述

指针取值

定义指针之后,就可以对指针进行操作了。
指针的操作有两种:&取地址、*根据地址取值

func main() {

	// 指针取值
	a := 10
	b := &a
	fmt.Println(b)  // b 中保存着 a 的地址
	fmt.Println(&a) // 取 a 的地址
	fmt.Println(a)  // a 的值
	fmt.Println(*b) // 取 b
}

// ------------------------------------
// Output:
0xc000012090
0xc000012090
10
10

取地址操作符&和取值操作符*是一对互补操作符,&取出地址,*根据地址取出地址指向的值。

变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:

  • 对变量进行取地址(&)操作,可以获得这个变量的指针变量。
  • 指针变量的值是指针地址。
  • 对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。

结构体指针

结构体指针访问结构体字段 同 结构体变量访问结构体字段一样,使用 . 点操作符,Go 没有 -> 操作符。

eg:

type User struct {    // 定义结构体
    name string
    age  int
}

Boii := User {    // 定义结构体变量
    name: "Boii",
    age:  18
}

p := &Boii    // 定义结构体指针

fmt.Println(p.name)    // 用 . 点操作符访问成员 Boii

指针做形参

func fn1(x int) {
    x = 100
}

func fn2(x *int) {
    *x = 100
}

func main() {
	var a int = 10
	fn1(a)
	fmt.Println(a) // 10

	var b *int = &a
	fn2(b)
	fmt.Println(a) // 100
}

指针做返回值

Go 允许使用“栈逃逸”机制将局部变量的空间分配在堆上。
使用这种机制,需要返回指针

eg:

func add(a, b int) *int {
	sum := a + b
	return &sum
}

func main() {
	psum := add(10, 20)
	fmt.Println(psum)  // 0xc000012090
	fmt.Println(*psum) // 30
}

new 和 make

new

new 是一个内置函数,它的函数签名如下:

func new(Type) *Type

Type 表示类型,new函数只接收一个参数,这个参数只能是类型
*Type 表示类型指针,new 函数返回一个指向该类型内存地址的指针

new函数可以接受所有类型,使用new函数可以得到类型的指针,并且这个指针指向的内存地址中存着该类型的零值。

func main() {
	a := new(int)
	b := new([5]int)
	c := new([]string)
	d := new(map[int]string)
	fmt.Printf("a type: %T \n", a) // a type: *int
	fmt.Printf("b type: %T \n", b) // b type: *[5]int
	fmt.Printf("c type: %T \n", c) // c type: *[]string
	fmt.Printf("d type: %T \n", d) // d type: *map[int]string

	fmt.Println(*a) // 0
	fmt.Println(*b) // [0 0 0 0 0]
	fmt.Println(*c) // []
	fmt.Println(*d) // map[]
}

new 函数会开辟一块空间,然后把这块空间的地址返回出去,这样外面的指针变量(a、b、c、d)就可以操作了。
如果仅仅只是声明一个指针变量,而没有开辟内存空间,这个指针变量是无法使用的。

  • 以下为错误示例:
func main() {
	var i *int
	fmt.Println(i)
	*i = 10    // 这里会引发 panic
}

// ------------------------------------
// Output:
<nil>
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x1 addr=0x0 pc=0x97581b]

goroutine 1 [running]:
main.main()
	e:/---CODE/GO/src/Hello/main.go:23 +0x7b
exit status 2

从上面例子可以看到,声明了一个指针,默认值为 nil
在没有分配内存的情况下去使用指针,会导致 panic

  • 以下为正确示例:
func main() {
	var i *int 		    // 声明一个指针变量
	fmt.Println(i)	    // <nil> | 默认值为 nil
	i = new(int)	    // 给这个指针分配内存
	*i = 10			    // 存个值进去
	fmt.Println(i)	    // 0xc000012098 | 已经有内存空间了
	fmt.Println(*i)     // 10 | 正常~~
}

make

make 也是用于分配内存,但是只能用于slice切片、map字典、chan通道的内存创建,而且它返回的类型 就是这三个类型本身,因为这三种类型就是引用类型,所以没必要返回他们的指针了。

make 的函数签名如下:

func make(t Type, size ...IntegerType) Type

在使用 slice、map、chan的时候,都需要使用 make 进行初始化,然后才可以对它们进行操作

func main() {
	c := new([]string)
	s := make([]string, 10)
	d := new(map[int]string)
	e := make(map[int]string)

	fmt.Printf("c type: %T \n", c) // c type: *[]string    | string切片类型的指针
	fmt.Printf("s type: %T \n", s) // s type: []string     | string切片
	fmt.Printf("d type: %T \n", d) // d type: *map[int]string | map类型的指针
	fmt.Printf("e type: %T \n", e) // e type: map[int]string  | map
}

new 和 make 的区别

  • new 返回的是指针,make返回的是类型本身
  • new 可以给所有类型的指针开辟空间,make 只能用于 slice、map、chan初始化
<think>我们正在讨论Go语言中如何将关系(relationship)定义为指针类型。在Go中,指针类型用于间接引用值,通常用于避免复制大对象或允许函数修改原始数据。在定义关系时,如果关系涉及的结构体较大,或者需要修改原始结构体,那么使用指针类型是合理的。在Go中,我们可以通过定义结构体字段为指针类型来建立关系。例如,假设我们有两个结构体:`Person`和`Company`,一个`Person`可能属于一个`Company`,那么我们可以将`Company`字段定义为`*Company`类型(指针类型)。示例: ```gotype Companystruct {Namestring} typePerson struct{Name stringCompany *Company//指针类型,表示Person所属的公司}```这样,当我们创建一个`Person`实例时,可以将其`Company`字段设置为一个`Company`实例的指针。另外,在数据库模型设计中,我们经常使用指针类型来表示可选的关联关系(即该关系可能为空)。例如,使用GORM(一个Go的ORM库)时,我们可以将关联字段定义为指针类型,以表示该关联是可选的(可以为NULL)。例如: ```gotype Userstruct {gorm.ModelNamestringCompanyID *int//使用指针类型,表示可以为空Company*Company`gorm:"foreignKey:CompanyID"`//指向公司的指针}type Companystruct {gorm.ModelName string}```在这个例子中,`User`的`Company`字段是一个指向`Company`的指针,表示一个用户可能属于一个公司,也可能不属于任何公司(即`Company`字段为`nil`)。因此,根据需求,我们可以通过将结构体字段定义为指针类型来实现关系定义为指针类型。注意:使用指针类型时,需要小心空指针解引用问题。在访问指针字段之前,通常需要检查该指针是否为`nil`。现在,根据指令,我们需要生成相关问题。§§相关问题§§1.在Go语言中,指针类型和值类型在作为结构体字段时有何区别?2.如何避免在Go语言中解引用空指针?3.使用GORM时,如何定义可选的外键关系?</think>在Go语言中,将关系定义为指针类型主要涉及结构体字段的设计和接口实现。以下是关键实现方式: ### 1. 结构体字段使用指针 当关系涉及可选或可能为`nil`的关联对象时,使用指针类型字段: ```go type User struct { ID int Name string } type Order struct { ID int UserID int User *User // 指针类型关系 } ``` **特点**: - 允许`User`字段为`nil`(无关联用户) - 避免值复制,提高大对象传递效率 - 支持动态关联关系 ### 2. 接口实现中的指针接收者 当关系通过接口定义时,方法接收者使用指针实现多态: ```go type Relationship interface { GetTarget() Entity } type UserRelation struct { Target *User // 指针类型存储目标 } // 指针接收者实现接口 func (ur *UserRelation) GetTarget() Entity { return ur.Target // 返回指针实现动态绑定 } ``` **特点**: - 符合接口的动态类型要求(引用[1]) - 运行时才能确定具体类型(引用[2]) ### 3. 空指针检查 访问指针关系前必须检查`nil`: ```go func PrintOrderUser(o Order) { if o.User != nil { // 必须检查 fmt.Println("User:", o.User.Name) } else { fmt.Println("No associated user") } } ``` ### 4. 数据库映射 在ORM(如GORM)中定义指针关系: ```go type Order struct { gorm.Model User *User `gorm:"foreignKey:UserID"` // 指针类型 UserID uint } ``` **典型应用场景**: 1. 可选的一对一关系(如用户-个人资料) 2. 循环依赖结构(树形节点父子关系) 3. 需要动态修改关联对象的场景 > 关键优势:通过指针实现 **延迟绑定** 和 **动态多态**,符合Go的接口设计哲学(引用[1][2])
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TCP404

老板大方~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值