C中的指针
https://www.yuque.com/ueumd/clang/su0rlc 变量的本质分析 https://www.yuque.com/ueumd/clang/tanhre 指针专题
指针的概念
指针的主要作用就是在函数内部改变传递进来变量的值
概念 | 说明 |
---|---|
变量 | 是一种占位符,用于引用计算机的内存地址。可理解为内存地址的标签 |
指针 | 表示内存地址,表示地址的指向。指针是一个指向另一个变量内存地址的值 |
& | 取地址符,例如:{指针}:=&{变量} |
* | 取值符,例如:{变量}:=*{指针} |
声明一个指针:
var p *int
*int
表示p是一个int类型指针p
指针中存的是一个int
类型变量的地址,这意味着p中不能存其他类型变量的地址。
如何获取某个变量的地址呢?使用操作符&:
var a int = 250
p = &a //将a的地址赋给指针p
那么如何根据指针中的地址找到对应的变量呢?使用操作符*:
var b = *p //根据p中的值找到a,将其值赋给b
fmt.Println(b) //250
*p = 100 //根据p中的值找到a,改变a的值
fmt.Println(a) //100
一定要注意指针的初始化,如果不初始化,则指针的的值是其零值—nil。对未初始化的指针赋值,则会出问题:
var p *int //只声明,未初始化
*p = 100 //报错:invalid memory address or nil pointer dereference
原因是指针p中没值,是个nil,自然就无法根据地址找到变量。如果你想使用指针,必须先确保你的指针中有合法的内存地址才行。应当这样写:
var a int
var p *int = &a //p被初始化为a的地址
*p = 100 //根据p的值找到a,12赋值给a
//或者
var a int
var p *int
p = &a //a的地址赋给p
*p = 100 //根据p的值找到a,12赋值给a
完整的例子
package main
import "fmt"
func main() {
var a int = 250 // 变量a
var p *int = &a // 指针p指向a
var b = *p // 获取p指向的变量a的值
fmt.Println("a =",a, ", b =", b, ", p =", p)
fmt.Println("a的地址 =", &a, ", b的地址 =", &b, ", p的地址 =", &p)
*p = 100 // 改变p指向的变量a的值
fmt.Println("a =",a, ", b =", b, ", p =", p)
fmt.Println("a的地址 =", &a, ", b的地址 =", &b, ", p的地址 =", &p)
var pp **int = &p // 指针pp指向指针p
var c = *pp // 获取pp指向的p的值
var d = **pp // 获取pp指向的p指向的a的值
fmt.Println("pp =", pp, ", c =", c, ", d =", d)
fmt.Println("pp的地址 =", &pp, ", c的地址 =", &c, ", d的地址 =", &d)
}
指针地址和变量空间
Go语言保留了指针, 但是与C语言指针有所不同. 主要体现在:
- 默认值: nil,C是NULL
- 操作符 & 取变量地址, * 通过指针访问目标对象
不支持指针运算, 不支持 -> 运算符, 直接用 . 访问目标成员
变量存储
写:等号 左边的变量,代表 变量所指向的内存空间。 p = 100
读:等号 右边的变量,代表 变量内存空间存储的数据值。 num = pfunc main() {
var p *int
// 开辟内存空间
p = new(int)
*p = 250;
fmt.Println(*p) // 250
var num int
num = *p
fmt.Println(num) // 250
}
空指针 & 野指针空指针:未被初始化的指针。 var p int p —> err
- 野指针:被一片无效的地址空间初始化。 ```c func main() { var p int //空指针 fmt.Println(p) }
func main() { var p int = 0 // 无效地址空间初始化 fmt.Println(p) }
<a name="hiv3H"></a>
## new & 默认值
变量声明而没有赋值,默认为零值,不同类型零值不同,例如字符串零值为空字符串;<br />指针声明而没有赋值,默认为nil,即该指针没有任何指向。<br />当指针没有指向的时候,不能对(*point)进行操作包括读取,否则会报空指针异常。
解决方法即给该指针分配一个指向,即初始化一个内存,并把该内存地址赋予指针变量,例如
```c
func main1(){
var x int = 99
var p *int = &x
fmt.Println(p) //0xc000114000
fmt.Println(*p) //99
}
或者通过**new**
开辟一个内存,并返回这个内存的地址。
表达式 new(T)
将创建一个 T
类型的匿名变量,所做的是为T类型的新值分配并清零一块内存空间。
并将这块内存空间的地址作为结果返回,结果就是指向这个新的T
类型值的指针值,返回的指针类型为*T
。
new
创建的内存空间位于heap上,空间的默认值为数据类型默认值。
如:new(int) 则 p为0,new(bool) 则 p为false。
使用new()函数,无需担心其内存的生命周期或怎样将其删除,因为Go语言的内存管理系统会帮我们打理一切。
func pNewInt() {
var p *int
p = new(int)
fmt.Println(*p) // 默认类型的 默认值 0
}
func pNewString() {
var p *string
p = new(string)
// q% 打印go语言格式的字符串
fmt.Printf("%q\n", *p) // 默认类型的 默认值 ""
}
func pNewBool() {
var p *bool
p = new(bool)
fmt.Println(*p) // 默认类型的 默认值 false
}
指针的函数传参(传引用)
- 传地址(引用):将形参的地址值作为函数参数传递。
- 传值(数据据):将实参的 值 拷贝一份给形参。
值传递
// 会在栈中开辟新内存空间 a和b,和main中的a和b没有关系
func swap(a, b int) {
a, b = b, a
fmt.Printf("swap: a = %d, b = %d\n", a, b) // swap: a = 20, b = 10
}
func main() {
a, b := 10, 20
swap(a, b) //变量本身传递,值传递
fmt.Printf("main: a = %d, b = %d\n", a, b) // main: a = 10, b = 20
}
传地址
传引用: 在A栈帧内部,修改B栈帧中的变量值。
// 开辟内存空间 p1和p2 并指向 main方法中的 a和b 空间
func swap(p1, p2 *int) {
*p1, *p2 = *p2, *p1
}
func main() {
a, b := 10, 20
swap(&a, &b) // 地址传递
fmt.Printf("main: a = %d, b = %d\n", a, b)
}
指针结构体
指针结构体实现的目的:
- 通过引用内存空间地址,来为 结构体内 定义的具体的变量实例 进行赋值 — 赋值。
- 通过引用内存空间地址,修改 访问结构体后的变量 的具体数值 — 修改值。
指针的使用
方法中的指针
方法即为有接受者的函数,接受者可以是类型的实例变量或者是类型的实例指针变量。但两种效果不同。
1、类型的实例变量 ```c func main(){ person := Person{“vanyar”, 21} fmt.Printf(“person<%s:%d>\n”, person.name, person.age) person.sayHi() person.ModifyAge(210) person.sayHi() } type Person struct { name string age int } func (p Person) sayHi() { fmt.Printf(“SayHi — This is %s, my age is %d\n”,p.name, p.age) } func (p Person) ModifyAge(age int) { fmt.Printf(“ModifyAge”) p.age = age }
//输出结果
person
尽管 ModifyAge 方法修改了其age字段,可是方法里的p是person变量的一个副本,修改的只是副本的值。下一次调用sayHi方法的时候,还是person的副本,因此修改方法并不会生效。<br />即实例变量的方式并不会改变接受者本身的值。<br />**2、类型的实例指针变量**
```c
func (p *Person) ChangeAge(age int) {
fmt.Printf("ModifyAge")
p.age = age
}
Go会根据Person的示例类型,转换成指针类型再拷贝,即 person.ChangeAge会变成 (&person).ChangeAge。
指针类型的接受者,如果实例对象是值,那么go会转换成指针,然后再拷贝,如果本身就是指针对象,那么就直接拷贝指针实例。因为指针都指向一处值,就能修改对象了。