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 = 250p = &a //将a的地址赋给指针p
那么如何根据指针中的地址找到对应的变量呢?使用操作符*:
var b = *p //根据p中的值找到a,将其值赋给bfmt.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 intvar p *int = &a //p被初始化为a的地址*p = 100 //根据p的值找到a,12赋值给a//或者var a intvar p *intp = &a //a的地址赋给p*p = 100 //根据p的值找到a,12赋值给a
完整的例子
package mainimport "fmt"func main() {var a int = 250 // 变量avar p *int = &a // 指针p指向avar 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指向指针pvar 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) // 250var num intnum = *pfmt.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)进行操作包括读取,否则会报空指针异常。解决方法即给该指针分配一个指向,即初始化一个内存,并把该内存地址赋予指针变量,例如```cfunc main1(){var x int = 99var p *int = &xfmt.Println(p) //0xc000114000fmt.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 *intp = new(int)fmt.Println(*p) // 默认类型的 默认值 0}func pNewString() {var p *stringp = new(string)// q% 打印go语言格式的字符串fmt.Printf("%q\n", *p) // 默认类型的 默认值 ""}func pNewBool() {var p *boolp = new(bool)fmt.Println(*p) // 默认类型的 默认值 false}
指针的函数传参(传引用)
- 传地址(引用):将形参的地址值作为函数参数传递。
- 传值(数据据):将实参的 值 拷贝一份给形参。
值传递
// 会在栈中开辟新内存空间 a和b,和main中的a和b没有关系func swap(a, b int) {a, b = b, afmt.Printf("swap: a = %d, b = %d\n", a, b) // swap: a = 20, b = 10}func main() {a, b := 10, 20swap(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, 20swap(&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、类型的实例指针变量**```cfunc (p *Person) ChangeAge(age int) {fmt.Printf("ModifyAge")p.age = age}
Go会根据Person的示例类型,转换成指针类型再拷贝,即 person.ChangeAge会变成 (&person).ChangeAge。
指针类型的接受者,如果实例对象是值,那么go会转换成指针,然后再拷贝,如果本身就是指针对象,那么就直接拷贝指针实例。因为指针都指向一处值,就能修改对象了。

