介绍

  1. Java 和 .NET 等编程语言不同,Go语言提供了控制数据结构指针的能力。但是并不能进行指针运算。
  2. 指针(pointer)在Go语言中可以被拆分为两个核心概念:
    1. 类型指针:允许对这个指针类型的数据进行修改,传递数据可以直接使用指针,而无须拷贝数据,类型指针不能进行偏移和运算。
    2. 切片:由指向起始元素的原始指针、元素数量和容量组成。C语言中指针可以做运算,但代价是内存操作越界,切片在指针的基础上增加了大小,约束了切片对应的内存区域,切片使用中无法对切片内部的地址和大小进行手动调整,因此切片比指针更安全、强大。
  3. 受益于这样的约束和拆分,Go语言的指针类型变量即拥有指针高效访问的特点,又不会发生指针偏移,从而避免了非法修改关键性数据的问题。同时,垃圾回收也比较容易对不会发生偏移的指针进行检索和回收。
  4. 切片比原始指针具备更强大的特性,而且更为安全。切片在发生越界时,运行时会报出宕机,并打出堆栈,而原始指针只会崩溃。


C/C++中的指针

  1. 说到 C/C++ 中的指针,会让许多人“谈虎色变”,尤其是对指针的偏移、运算和转换。
  2. 其实,指针是 C/C++ 语言拥有极高性能的根本所在,在操作大块数据和做偏移时即方便又便捷。因此,操作系统依然使用C语言及指针的特性进行编写。
  3. C/C++ 中指针饱受诟病的根本原因是指针的运算和内存释放,C/C++ 语言中的裸指针可以自由偏移,甚至可以在某些情况下偏移进入操作系统的核心区域,我们的计算机操作系统经常需要更新、修复漏洞的本质,就是为解决指针越界访问所导致的“缓冲区溢出”的问题。

指针

指针类型

  1. 一个指针变量(通常缩写为 ptr)可以指向任何一个值的内存地址。
  2. 指针变量指向的值的内存地址在 32 和 64 位机器上分别占用 4 或 8 个字节,占用字节的大小与所指向的值的大小无关。
  3. 当一个指针变量被定义后没有分配到任何变量时,它的默认值为 nil。 ```go var ptr Type // ptr 的类型为T,称做 T 的指针类型,*代表指针。

var ptr int // ptr 的类型为int,称做 int 的指针类型,*代表指针。

  1. <a name="8rTWc"></a>
  2. ##### new() 创建指针
  3. 1. Go语言还提供了另外一种方法来创建指针变量。
  4. 1. new() 函数可以创建一个对应类型的指针,创建过程会分配内存,被创建的指针指向默认值。
  5. ```go
  6. new(类型)
  7. // demo
  8. str := new(string)
  9. *str = "Go语言教程"
  10. fmt.Println(*str)

指针地址

  1. 每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用在变量名前面添加&操作符(前缀)来获取变量的内存地址(取地址操作)。

    1. T 代表被取地址的变量,使用变量 ptr 进行接收
    2. ptr := &T
    1. func main() {
    2. var cat int = 1
    3. var str string = "banana"
    4. fmt.Printf("%p %p", &cat, &str) // 0xc042052088 0xc0420461b0
    5. }

    指针取值

  2. 可以对指针使用*操作符,取出指针对应的值内容。

    1. func main() {
    2. // 准备一个字符串类型
    3. var house = "Malibu Point 10880, 90265"
    4. // 对字符串取地址, ptr类型为*string
    5. ptr := &house
    6. // 打印ptr的类型
    7. fmt.Printf("ptr type: %T\n", ptr)
    8. // 打印ptr的指针地址
    9. fmt.Printf("address: %p\n", ptr)
    10. // 对指针进行取值操作
    11. value := *ptr
    12. // 取值后的类型
    13. fmt.Printf("value type: %T\n", value)
    14. // 指针取值后就是指向变量的值
    15. fmt.Printf("value: %s\n", value)
    16. }
    17. // log
    18. ptr type: *string
    19. address: 0xc0420401b0
    20. value type: string
    21. value: Malibu Point 10880, 90265

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

关系

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

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

使用

使用指针修改值

  1. // 交换函数
  2. func swap(a, b *int) {
  3. // 取a指针的值, 赋给临时变量t
  4. t := *a
  5. // 取b指针的值, 赋给a指针指向的变量
  6. *a = *b
  7. // 将a指针的值赋给b指针指向的变量
  8. *b = t
  9. }
  10. func main() {
  11. // 准备两个变量, 赋值1和2
  12. x, y := 1, 2
  13. // 交换变量值
  14. swap(&x, &y)
  15. // 输出变量值
  16. fmt.Println(x, y) // 2 1
  17. }
  1. *操作符作为右值时,表示是取指向变量的值。
  2. *操作符作为左值时,表示将值设置给指向的变量。

如果在 swap() 函数中交换操作的是指针值,会发生什么情况?

  1. func swap(a, b *int) {
  2. b, a = a, b
  3. }
  4. func main() {
  5. x, y := 1, 2
  6. swap(&x, &y)
  7. fmt.Println(x, y) // 1 2
  8. }
  1. 结果表明,交换是不成功的。
  2. 上面代码中的 swap() 函数交换的是 a 和 b 的地址,在交换完毕后,a 和 b 的变量值确实被交换。但和 a、b 关联的两个变量并没有实际关联。这就像写有两座房子的卡片放在桌上一字摊开,交换两座房子的卡片后并不会对两座房子有任何影响。