文章目录
一、认识指针
指针是一个变量,其值是另一个变量的内存地址,即存储器位置的直接地址。类似变量或常量一样,必须要先声明一个指针,然后才能使用它来存储任何变量地址。
1.1、C/C++ 中的指针
指针是 C/C++ 语言拥有极高性能的根本所在,在操作大块数据和做偏移时既方便又便捷。因此,操作系统依然使用C语言及指针的特性进行编写。
C/C++ 中指针饱受诟病的根本原因是指针的运算和内存释放,C/C++ 语言中的裸指针可以自由偏移,甚至可以在某些情况下偏移进入操作系统的核心区域,我们的计算机操作系统经常需要更新、修复漏洞的本质,就是为解决指针越界访问所导致的"缓冲区溢出"的问题。
1.2、go语言指针
Go语言对指针的支持介于Java语言和 C/C++ 语言之间, 它既没有像Java那样取消了代码对指针的直接操作的能力, 也避免了 C/C++ 中由于对指针的滥用而造成的安全和可靠性问题.
指针(pointer)在Go语言中可以被拆分为两个核心概念:
- 类型指针,允许对这个指针类型的数据进行修改,传递数据可以直接使用指针,而无须拷贝数据,类型指针不能进行偏移和运算。
- 切片,由指向起始元素的原始指针、元素数量和容量组成。
受益于这样的约束和拆分,Go语言的指针类型变量既拥有指针高效访问的特点,又不会发生指针偏移,从而避免了非法修改关键性数据的问题。同时,垃圾回收也比较容易对不会发生偏移的指针进行检索和回收。
切片比原始指针具备更强大的特性,而且更为安全。切片在发生越界时,运行时会报出宕机,并打出堆栈,而原始指针只会崩溃。
二、go语言指针特性
2.1、指针地址和变量空间
一个指针变量可以指向任何一个值的内存地址,它所指向的值的内存地址在 32 和 64 位机器上分别占用 4 或 8 个字节,占用字节的大小与所指向的值的大小无关。当一个指针被定义后没有分配到任何变量时,它的默认值为 nil。指针变量通常缩写为 ptr。
Go语言保留了指针, 但是与C语言指针有所不同. 主要体现在:
- 默认值:
nil
- 操作符
&
取变量地址,*
通过指针访问目标对象 - 不支持指针运算, 不支持
->
运算符, 直接用.
访问目标成员
package main
import "fmt"
func main(){
var s string = "hello"
var p *string = &s
fmt.Println(p)
}
当我们运行到 var s string = "hello"
时, 在内存中就会生成一个空间, 这个空间我们给它起了个名字叫 s, 同时, 它也有一个地址, 例如: 0xc000010230. 当我们想要使用这个空间时, 我们可以用地址去访问,也可以用我们给它起的名字 s 去访问.
继续运行到 p *string = &s
时, 我们定义了一个指针变量 p , 这个 p 就存储了变量 s 的地址。所以, 指针就是地址, 指针变量就是存储地址的变量。
改变s 和*p:
package main
import "fmt"
func main(){
var s string = "hello"
var p *string = &s
fmt.Println(p)
s = "hello world"
fmt.Println("s: ", s)
fmt.Println("*p: ", *p)
*p = "hello world!"
fmt.Println("s: ", s)
fmt.Println("*p: ", *p)
}
// 结果为
0xc000096210
s: hello world
*p: hello world
s: hello world!
*p: hello world!
可以发现, s 与 *p 的结果一样的。其中, *p 称为 解引用 或者 间接引用。*p = "hello world!"
是通过借助 s 变量的地址, 来操作 s 对应的空间,不管是 s 还是 *p , 操作的都是同一个空间.
2.2、从指针获取指针指向的值
当使用&
操作符对普通变量进行取地址操作并得到变量的指针后,可以对指针使用*
操作符,也就是指针取值:
package main
import "fmt"
func main() {
// 准备一个字符串类型
var school = "heyang middle school"
// 对字符串取地址, ptr类型为*string
ptr := &school
// 打印ptr的类型
fmt.Printf("ptr type: %T\n", ptr)
// 打印ptr的指针地址
fmt.Printf("pointer address: %p\n", ptr)
// 对指针进行取值操作
pointer_value := *ptr
// 取值后的类型
fmt.Printf("pointer_value type: %T\n", pointer_value)
// 指针取值后就是指向变量的值
fmt.Printf("pointer_value: %s\n", pointer_value)
}
// 结果为
ptr type: *string
pointer address: 0xc000096210
pointer_value type: string
pointer_value: heyang middle school
取地址操作符&
和取值操作符*
是一对互补操作符,&
取出地址,*
根据地址取出地址指向的值。
变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:
- 对变量进行取地址操作使用&操作符,可以获得这个变量的指针变量。
- 指针变量的值是指针地址。
- 对指针变量进行取值操作使用*操作符,可以获得指针变量指向的原变量的值。
2.3、使用指针修改值
如下:
package main
import "fmt"
// 交换函数
func swap(a, b *int) {
// 取a指针的值, 赋给临时变量t
t := *a
// 取b指针的值, 赋给a指针指向的变量
*a = *b
// 将a指针的值赋给b指针指向的变量
*b = t
}
func main() {
// 准备两个变量, 赋值1和2
x, y := 1, 2
// 交换变量值
swap(&x, &y)
// 输出变量值
fmt.Println(x, y)
}
// 结果为
2 1
*操作符作为右值时,意义是取指针的值,作为左值时,也就是放在赋值操作符的左边时,表示 a 指针指向的变量。其实归纳起来,*操作符的根本意义就是操作指针指向的变量。当操作在右值时,就是取指向变量的值,当操作在左值时,就是将值设置给指向的变量。
2.4、空指针
当一个指针被定义后没有分配到任何变量时,它的值为 nil
。nil
指针也称为空指针。nil
在概念上和其它语言的null、None、nil、NULL
一样,都指代零值或空值。
package main
import "fmt"
func main() {
var ptr *int
fmt.Printf("ptr 的值为 : %v\n", ptr )
fmt.Printf("ptr 的值为 : %#v\n", ptr )
}
// 结果为
ptr 的值为 : <nil>
ptr 的值为 : (*int)(nil)
空指针判断:
if(ptr != nil) /* ptr 不是空指针 */
if(ptr == nil) /* ptr 是空指针 */
野指针:被一片无效的地址空间初始化.
func main() {
var p *int = 0xc00000a0c8
fmt.Printf("p 的值为 : %v\n", p )
fmt.Printf("p 的值为 : %#v\n", p )
}
// 结果为
cannot use 824633761992 (type int) as type *int in assignment
2.5、new()
创建指针
表达式 new(T)
将创建一个 T 类型的匿名变量, 所做的是为 T 类型的新值分配并清零一块内存空间, 然后将这块内存空间的地址作为结果返回, 而这个结果就是指向这个新的 T 类型值的指针值, 返回的指针类型为 *T.
new()
创建的内存空间位于heap上, 空间的默认值为数据类型的默认值. 如: p := new(int)
则 *p 为 0,这时 p 就不再是空指针或者野指针.
func main(){
p := new(int)
fmt.Println(p)
fmt.Println(*p)
}
// 结果为
0xc000016070
0
2.6、向函数传递指针参数
传地址(引用): 将地址值作为函数参数传递.
传值(数据): 将实参的值拷贝一份给形参.
无论是传地址还是传值, 都是实参将自己的值拷贝一份给形参.只不过这个值有可能是地址, 有可能是数据.所以, 函数传参永远都是值传递.
func modify1(x int) {
x = 100
}
func modify2(x *int) {
*x = 100
}
func main() {
a := 10
modify1(a) //修改失败
fmt.Println(a)
modify2(&a) //修改成功
fmt.Println(a)
}
// 结果为
10
100
2.7、指针数组
在我们了解指针数组前,先看个实例,定义了长度为 3 的整型数组:
package main
import "fmt"
const MAX int = 3
func main() {
a := []int{10,100,200}
fmt.Println(a)
var i int
for i = 0; i < MAX; i++ {
fmt.Printf("a[%d] = %d\n", i, a[i] )
}
}
// 结果为:
[10 100 200]
a[0] = 10
a[1] = 100
a[2] = 200
有一种情况,我们可能需要保存数组,这样我们就需要使用到指针。
以下声明了整型指针数组:
const MAX int = 3
func main() {
a := []int{10,100,200}
var i int
var ptr [MAX]*int
for i = 0; i < MAX; i++ {
ptr[i] = &a[i] /* 整数地址赋值给指针数组 */
}
for i = 0; i < MAX; i++ {
fmt.Printf("a[%d] = %d\n", i,*ptr[i] )
}
}
// 结果为:
a[0] = 10
a[1] = 100
a[2] = 200
三、参考文档
1、https://blog.youkuaiyun.com/qq_39941141/article/details/125741462
2、http://c.biancheng.net/view/21.html
3、https://www.cnblogs.com/cheyunhua/p/16652003.html
4、https://blog.youkuaiyun.com/weixin_44211968/article/details/121343717