摘要:指针类型的零值指针类型的零值指针类型的零值都是,也就是说,一个没有存储地址的指针等于解除引用解除引用一个指针变量持有另一个变量的地址。
本篇翻译自《Practical Go Lessons》 Chapter 15: Pointer type
指针是“是一个数据项,它存储另外一个数据项的位置”。
在程序中,我们不断地存储和检索数据。例如,字符串、数字、复杂结构…。在物理层面,数据存储在内存中的特定地址,而指针存储的就是这些特定内存地址。
记住指针变量,就像其他变量一样,它也有一个内存地址。
Go 中的指针类型不止一种,每一种普通类型就对应一个指针类型。相应地,指针类型也限定了它自己只能指向对应类型的普通变量(地址)。
指针类型的语法为:
*BaseType
BaseType
指代的是任何普通类型。
我们来看一下例子:
*int
表示指向 int
类型的指针*uint8
表示指向 uint8
类型的指针type User struct { ID string Username string}
*User
表示指向 User
类型的指针下面的语法可以创建:
var p *int
这里我们创建了一个类型为 *int
的变量 p
。*int
是指针类型(基础类型是 int
)。
让我们来创建一个名为 answer
的整型变量。
var answer int = 42
现在我们给变量 p
分配一个值了:
p = &answer
使用 &
符号我们就能得到变 answer
的地址。来打印出这个地址~
fmt.Println(p)// 0xc000012070
0xc000012070
是一个十六进制数字,因为它的以 0x
为前缀。内存地址通常是以十六进制格式表示。你也可以使用二进制(用 0 和 1)表示,但不易读。
指针类型的零值都是 nil
,也就是说,一个没有存储地址的指针等于 nil
var q *intfmt.Println(q == nil)// true
一个指针变量持有另一个变量的地址。如果你想通过指针去访问地址背后的变量值该怎么办?你可以使用解除引用操作符 *
。
来举个例子,我们定义一个结构体类型 Cart
:
type Cart struct { ID string Paid bool}
然后我们创建一个 Cart
类型的变量 cart
,我们可以得到这个变量的地址,也可以通过地址找到这个变量:
*
操作符,你可以通过地址找到变量值&
操作符,你可以得到变量的地址每个 Go 程序员都会遇到这个 panic(报错):
panic: runtime error: invalid memory address or nil pointer dereference[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x1091507]
为了更好地理解它,我们来复现一下:
package mainimport "fmt"func main() { var myPointerVar *int fmt.Println(*myPointerVar)}
在程序里,我们的定义了一个指针变量 myPointerVar
,这个变量的类型是 *int
(指向整型)。
然后我尝试对它进行解引用,myPointerVar
变量持有一个尚未初始化的指针,因此该指针的值为 nil
。因为我们尝试去寻找一个不存在的地址,程序将会报错!我们尝试找到空地址,而空地址在内存中不存在。
Maps 和 channels 变量里保存了对内部结构的指针。因此,即便向一个函数或方法传递的 map 或 channel 不是指针类型,也开始对这个 map 或 channel 进行修改。让我们看一个例子:
func addElement(cities map[string]string) { cities["France"] = "Paris"}
package mainimport "log"func main() { cities := make(map[string]string) addElement(cities) log.Println(cities)}
cities
的 mapaddElement
map[France:Paris]
我们将在专门的部分中更广泛地介绍 channels 和 maps。
切片是相同类型元素的集合。在内部,切片是一个具有三个字段的结构:
EUcountries
的例子:package mainimport "log"func main() { EUcountries := []string{"Austria", "Belgium", "Bulgaria"} log.Println(EUcountries)}
package mainimport "log"func main() { EUcountries := []string{"Austria", "Belgium", "Bulgaria"} addCountries(EUcountries) log.Println(EUcountries)}func addCountries(countries []string) { countries = append(countries, []string{"Croatia", "Republic of Cyprus", "Czech Republic", "Denmark", "Estonia", "Finland", "France", "Germany", "Greece", "Hungary", "Ireland", "Italy", "Latvia", "Lithuania", "Luxembourg", "Malta", "Netherlands", "Poland", "Portugal", "Romania", "Slovakia", "Slovenia", "Spain", "Sweden"}...)}
addCountries
将一个字符串类型切片作为参数append
向切片添加字符串来修改切片[Austria Belgium Bulgaria Croatia Republic of Cyprus Czech Republic Denmark Estonia Finland France Germany Greece Hungary Ireland Italy Latvia Lithuania Luxembourg Malta Netherlands Poland Portugal Romania Slovakia Slovenia Spain Sweden][Austria Belgium Bulgaria]
答案:这个函数实际输出:
[Austria Belgium Bulgaria]
[]string
类型元素作为参数EUcountries
拷贝一份传进去让我们在函数中添加一个日志来可视化它:
func addCountries(countries []string) { countries = append(countries, []string{"Croatia", "Republic of Cyprus", "Czech Republic", "Denmark", "Estonia", "Finland", "France", "Germany", "Greece", "Hungary", "Ireland", "Italy", "Latvia", "Lithuania", "Luxembourg", "Malta", "Netherlands", "Poland", "Portugal", "Romania", "Slovakia", "Slovenia", "Spain", "Sweden"}...) log.Println(countries)}
日志打印出:
[Austria Belgium Bulgaria Croatia Republic of Cyprus Czech Republic Denmark Estonia Finland France Germany Greece Hungary Ireland Italy Latvia Lithuania Luxembourg Malta Netherlands Poland Portugal Romania Slovakia Slovenia Spain Sweden]
package mainimport ( "log" "strings")func main() { EUcountries := []string{"Austria", "Belgium", "Bulgaria"} upper(EUcountries) log.Println(EUcountries)}func upper(countries []string) { for k, _ := range countries { countries[k] = strings.ToUpper(countries[k]) }}
upper
,它将把一个字符串切片的每个元素都转换成大写问题:依你看,程序将传输下面哪个?
[AUSTRIA BELGIUM BULGARIA][Austria Belgium Bulgaria]
答案:这个函数将返回:
[AUSTRIA BELGIUM BULGARIA]
upper
获取切片 EUcountries 的副本(和上面一样)countries[k] = strings.ToUpper(countries[k])
如果使用切片指针,你就可以在函数中修改这个切片了:
package mainimport ( "log")func main() { EUcountries := []string{"Austria", "Belgium", "Bulgaria"} addCountries2(&EUcountries) log.Println(EUcountries)}func addCountries2(countriesPtr *[]string) { *countriesPtr = append(*countriesPtr, []string{"Croatia", "Republic of Cyprus", "Czech Republic", "Denmark", "Estonia", "Finland", "France", "Germany", "Greece", "Hungary", "Ireland", "Italy", "Latvia", "Lithuania", "Luxembourg", "Malta", "Netherlands", "Poland", "Portugal", "Romania", "Slovakia", "Slovenia", "Spain", "Sweden"}...)}
这个程序将输出:
[Austria Belgium Bulgaria Croatia Republic of Cyprus Czech Republic Denmark Estonia Finland France Germany Greece Hungary Ireland Italy Latvia Lithuania Luxembourg Malta Netherlands Poland Portugal Romania Slovakia Slovenia Spain Sweden]
addCountries2
将字符串切片的指针([]string
)作为参数append
调用时的第一个参数是 *countriesPtr
(即我们通过指针 countriesPtr
去找到原值)append
的第二个参数没有改变addCountries2
的结果会影响到外部的变量有一个快捷方式可以让你直接修改 struct 类型的变量而无需使用*
运算符:
type Item struct { SKU string Quantity int}type Cart struct { ID string CreatedDate time.Time Items Item}cart := Cart{ ID: "115552221", CreatedDate: time.Now(),}cartPtr := &cartcartPtr.Items = []Item{ {SKU: "154550", Quantity: 12}, {SKU: "DTY8755", Quantity: 1},}log.Println(cart.Items)// [{154550 12} {DTY8755 1}]
cart
是一个 Cart
类型变量cartPtr := &cart
会获取变量 cart 的地址然后将其存储到 cartPtr
中cartPtr
,我们可以直接修改变量 cart
的 Item
字段(*carPtr).Items = []Item{ {SKU: "154550", Quantity: 12}, {SKU: "DTY8755", Quantity: 1},}
(这也有效,但更冗长)
指针通常用作方法的接收器,让我们以 Cat
类型为例:
type Cat struct { Color string Age uint8 Name string}
你可以定义一个方法,使用指向 Cat
的指针作为方法的接收器(*Cat
):
func (cat *Cat) Meow(){ fmt.Println("Meooooow")}
Meow
方法没有做任何有实际意义的事吗;它只是打印了字符串"Meooooow"
。我们没有修改比变量的值。我们来看另一个方法,它修改了 cat 的 Name
:
func (cat *Cat) Rename(newName string){ cat.Name = newName}
此方法将更改猫的名称。通过指针,我们修改了 Cat 结构体的一个字段。
当然,如果你不想使用指针作为接收器,你也可以:
func (cat Cat) RenameV2(newName string){ cat.Name = newName}
在这个例子中,变量 cat
是一个副本。接收器被命名为“值接收器”。因此,你对 cat 变量所做的任何修改都将在 cat 副本上完成:
package mainimport "fmt"type Cat struct { Color string Age uint8 Name string}func (cat *Cat) Meow() { fmt.Println("Meooooow")}func (cat *Cat) Rename(newName string) { cat.Name = newName}func (cat Cat) RenameV2(newName string) { cat.Name = newName}func main() { cat := Cat{Color: "blue", Age: 8, Name: "Milow"} cat.Rename("Bob") fmt.Println(cat.Name) // Bob cat.RenameV2("Ben") fmt.Println(cat.Name) // Bob}
在主函数的第一行,我们创建了一个 Cat
类型的变量 cat,它的 Name 是 "Millow"
。
当我们调用具有值接收器的 RenameV2
方法时,函数外部变量 cat 的 Name 没有发生改变。
当我们调用 Rename
方法时,cat 的 Name 字段值会发生变化。
Product
指针的变量?Product
指针的变量?*Product
*
*T
表示所有指向 T
类型变量的指针集合&
。它将获取一个变量的地址userId := 12546584p := &userId
`userId` 是 `int` 类型的变量`p` 是 `*int` 类型变量`*int` 表示所有指向 `int` 类型变量的指针
*[]string
)*
userId := 12546584p := &userId*p = 4log.Println(userId)
p
是一个指针
- 我们使用
*p
来对指针p
进行解引用- 我们用指令
*p = 4
修改userId
的值- 在代码片段的末尾,userId 的值为 4(不再是 12546584)
type Cart struct { ID string}var cart CartcartPtr := &cart
- 不需要这样写:
(*cartPtr).ID = "1234"
- 你可直接这样写:
cartPtr.Items = "1234"
- 变量
cart
就会被修改
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/125672.html
摘要:在包下主要包括输入输出两种流,每种输入输出流又可分为字节流和字符流两大类。输入输出是从程序运行所在的内存的角度而言的。的输入流主要由和作为基类,而输出流主要由和作为基类。 本章主要参考和摘自疯狂java讲义上面的(java编程思想的后面看过后有新的内容再补充进去吧)。 输入输出是所有程序都必需的部分————使用输入机制允许程序读取外部数据(包括磁盘、光盘等存储设备上的数据和用户输入的...
摘要:基于版本基于版本。由于中英行文差异,完全的逐字逐句翻译会很冗余啰嗦。译者在翻译中同时参考了谷歌百度有道翻译的译文以及编程思想第四版中文版的部分内容对其翻译死板,生造名词,语言精炼度差问题进行规避和改正。 来源:LingCoder/OnJava8 主译: LingCoder 参译: LortSir 校对:nickChenyx E-mail: 本书原作者为 [美] Bru...
摘要:是目前最热门的一种前端开发框架。对于前端工程师来说,掌握这门炙手可热的技术是完全有必要的。虽然目前已出,但是官方并不会放弃版本,还会持续维护更新,而且掌握的基本知识能更快的帮助我们迈入。 AngularJS是目前最热门的一种前端开发框架。对于前端工程师来说,掌握这门炙手可热的技术是完全有必要的。本书会将作者掌握的AngularJS知识倾囊相授,并从学以致用的角度出发,用实例详细地讲解各...
阅读 3664·2023-01-11 11:02
阅读 4206·2023-01-11 11:02
阅读 3004·2023-01-11 11:02
阅读 5145·2023-01-11 11:02
阅读 4699·2023-01-11 11:02
阅读 5483·2023-01-11 11:02
阅读 5234·2023-01-11 11:02
阅读 3858·2023-01-11 11:02