指针 指针的概念 指针是存储另一个变量的内存地址的变量。
我们都知道,变量是一种使用方便的占位符,用于引用计算机内存地址。
一个指针变量可以指向任何一个值的内存地址。
获取变量的地址 Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。
1 2 3 4 5 6 7 8 9 package mainimport "fmt" func main () { var a int = 10 fmt.Printf("变量的地址: %x\n" , &a ) }
运行结果:
声明指针 声明指针,*T是指针变量的类型,它指向T类型的值。
var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。
1 2 var ip *int var fp *float32
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" func main () { var a int = 20 var ip *int ip = &a fmt.Printf("a 变量的地址是: %x\n" , &a ) fmt.Printf("ip 变量的存储地址: %x\n" , ip ) fmt.Printf("*ip 变量的值: %d\n" , *ip ) }
运行结果:
1 2 3 a 变量的地址是: 20818 a220 ip 变量的存储地址: 20818 a220 *ip 变量的值: 20
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport "fmt" type name int8 type first struct { a int b bool name } func main () { var a = first{1 , false , 2 } var b *first = &a fmt.Println(a.b, a.a, a.name, &a, b.a, &b, (*b).a) }
运行结果:
1 false 1 2 &{1 false 2} 1 0xc042068018 1
获取指针地址在指针变量前加&的方式
空指针 Go 空指针 当一个指针被定义后没有分配到任何变量时,它的值为 nil。 nil 指针也称为空指针。 nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。 一个指针变量通常缩写为 ptr。
空指针判断:
1 2 if (ptr != nil ) if (ptr == nil )
获取指针的值 获取一个指针意味着访问指针指向的变量的值。语法是:*a
示例代码:
1 2 3 4 5 6 7 8 9 10 11 package main import ( "fmt" ) func main () { b := 255 a := &b fmt.Println("address of b is" , a) fmt.Println("value of b is" , *a) }
操作指针改变变量的数值 示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport ( "fmt" ) func main () { b := 255 a := &b fmt.Println("address of b is" , a) fmt.Println("value of b is" , *a) *a++ fmt.Println("new value of b is" , b) }
运行结果
1 2 3 address of b is 0x1040a124 value of b is 255 new value of b is 256
使用指针传递函数的参数 示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport ( "fmt" ) func change (val *int ) { *val = 55 } func main () { a := 58 fmt.Println("value of a before function call is" ,a) b := &a change(b) fmt.Println("value of a after function call is" , a) }
运行结果
1 2 value of a before function call is 58 value of a after function call is 55
不要将一个指向数组的指针传递给函数。使用切片。
假设我们想对函数内的数组进行一些修改,并且对调用者可以看到函数内的数组所做的更改。一种方法是将一个指向数组的指针传递给函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport ( "fmt" ) func modify (arr *[3]int ) { (*arr)[0 ] = 90 } func main () { a := [3 ]int {89 , 90 , 91 } modify(&a) fmt.Println(a) }
运行结果
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport ( "fmt" ) func modify (arr *[3]int ) { arr[0 ] = 90 } func main () { a := [3 ]int {89 , 90 , 91 } modify(&a) fmt.Println(a) }
运行结果
虽然将指针传递给一个数组作为函数的参数并对其进行修改,但这并不是实现这一目标的惯用方法。我们有切片。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport ( "fmt" ) func modify (sls []int ) { sls[0 ] = 90 } func main () { a := [3 ]int {89 , 90 , 91 } modify(a[:]) fmt.Println(a) }
运行结果:
Go不支持指针算法。
1 2 3 4 5 6 7 package mainfunc main () { b := [...]int {109 , 110 , 111 } p := &b p++ }
nvalid operation: p++ (non-numeric type *[3]int)
指针数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" const MAX int = 3 func main () { a := []int {10 ,100 ,200 } var i int for i = 0 ; i < MAX; i++ { fmt.Printf("a[%d] = %d\n" , i, a[i] ) } }
输出结果
1 2 3 a[0 ] = 10 a[1 ] = 100 a[2 ] = 200
有一种情况,我们可能需要保存数组,这样我们就需要使用到指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "fmt" 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]) } }
输出结果
1 2 3 a[0 ] = 10 a[1 ] = 100 a[2 ] = 200
指针的指针 指针的指针
如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport "fmt" func main () { var a int var ptr *int var pptr **int a = 3000 ptr = &a pptr = &ptr fmt.Printf("变量 a = %d\n" , a ) fmt.Printf("指针变量 *ptr = %d\n" , *ptr ) fmt.Printf("指向指针的指针变量 **pptr = %d\n" , **pptr) }
输出结果
1 2 3 变量 a = 3000 指针变量 *ptr = 3000 指向指针的指针变量 **pptr = 3000
指针作为函数参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package mainimport "fmt" func main () { var a int = 100 var b int = 200 fmt.Printf("交换前 a 的值 : %d\n" , a ) fmt.Printf("交换前 b 的值 : %d\n" , b ) swap(&a, &b); fmt.Printf("交换后 a 的值 : %d\n" , a ) fmt.Printf("交换后 b 的值 : %d\n" , b ) } func swap (x *int , y *int ) { var temp int temp = *x *x = *y *y = temp }
输出结果
1 2 3 4 交换前 a 的值 : 100 交换前 b 的值 : 200 交换后 a 的值 : 200 交换后 b 的值 : 100
结构体 什么是结构体 Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。 结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。
结构体的定义和初始化 1 2 3 4 5 6 type struct_variable_type struct { member definition; member definition; ... member definition; }
一旦定义了结构体类型,它就能用于变量的声明
1 variable_name := structure_variable_type {value1, value2...valuen}
初始化结构体
1 2 3 4 5 6 7 P := person{"Tom" , 25 } P := person{age:24 , name:"Tom" } p := new (person) p.age=24
结构体的访问 访问结构体成员(访问结构的各个字段)
通过点.操作符用于访问结构的各个字段。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package mainimport "fmt" type Books struct { title string author string subject string book_id int } func main () { var Book1 Books var Book2 Books Book1.title = "Go 语言" Book1.author = "www.runoob.com" Book1.subject = "Go 语言教程" Book1.book_id = 6495407 Book2.title = "Python 教程" Book2.author = "www.runoob.com" Book2.subject = "Python 语言教程" Book2.book_id = 6495700 fmt.Printf( "Book 1 title : %s\n" , Book1.title) fmt.Printf( "Book 1 author : %s\n" , Book1.author) fmt.Printf( "Book 1 subject : %s\n" , Book1.subject) fmt.Printf( "Book 1 book_id : %d\n" , Book1.book_id) fmt.Printf( "Book 2 title : %s\n" , Book2.title) fmt.Printf( "Book 2 author : %s\n" , Book2.author) fmt.Printf( "Book 2 subject : %s\n" , Book2.subject) fmt.Printf( "Book 2 book_id : %d\n" , Book2.book_id) }
运行结果:
1 2 3 4 5 6 7 8 Book 1 title : Go 语言 Book 1 author : www.runoob.com Book 1 subject : Go 语言教程 Book 1 book_id : 6495407 Book 2 title : Python 教程 Book 2 author : www.runoob.com Book 2 subject : Python 语言教程 Book 2 book_id : 6495700
结构体指针 指针指向一个结构体,也可以创建指向结构的指针。
结构体指针
1 var struct_pointer *Books
以上定义的指针变量可以存储结构体变量的地址。查看结构体变量地址,可以将 & 符号放置于结构体变量前
1 struct_pointer = &Book1;
使用结构体指针访问结构体成员,使用 “.” 操作符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package mainimport "fmt" type Books struct { title string author string subject string book_id int } func main () { var Book1 Books var Book2 Books Book1.title = "Go 语言" Book1.author = "www.runoob.com" Book1.subject = "Go 语言教程" Book1.book_id = 6495407 Book2.title = "Python 教程" Book2.author = "www.runoob.com" Book2.subject = "Python 语言教程" Book2.book_id = 6495700 printBook(&Book1) printBook(&Book2) } func printBook ( book *Books ) { fmt.Printf( "Book title : %s\n" , book.title); fmt.Printf( "Book author : %s\n" , book.author); fmt.Printf( "Book subject : %s\n" , book.subject); fmt.Printf( "Book book_id : %d\n" , book.book_id); }
运行结果:
1 2 3 4 5 6 7 8 Book title : Go 语言 Book author : www.runoob.com Book subject : Go 语言教程 Book book_id : 6495407 Book title : Python 教程 Book author : www.runoob.com Book subject : Python 语言教程 Book book_id : 6495700
结构体实例化也可以是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport "fmt" type Books struct {} func (s Books) String() string { return "data" } func main () { fmt.Printf("%v\n" , Books{}) }
解释:
在Books
类型上定义了一个String
方法。这个方法具有一个接收者(s Books)
,表示它是Books
类型的一个实例方法。这个方法返回一个字符串"data"
。Books{}
创建了一个Books
类型的实例,然后通过%v
打印输出。由于Books
类型实现了String
方法,该方法将被调用,返回字符串"data"
。
结构体的匿名字段 结构体的匿名字段
可以用字段来创建结构,这些字段只包含一个没有字段名的类型。这些字段被称为匿名字段。
在类型中,使用不写字段名的方式,使用另一个类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 type Human struct { name string age int weight int } type Student struct { Human speciality string } func main () { mark := Student{Human{"Mark" , 25 , 120 }, "Computer Science" } fmt.Println("His name is " , mark.name) fmt.Println("His age is " , mark.age) fmt.Println("His weight is " , mark.weight) fmt.Println("His speciality is " , mark.speciality) mark.speciality = "AI" fmt.Println("Mark changed his speciality" ) fmt.Println("His speciality is " , mark.speciality) fmt.Println("Mark become old" ) mark.age = 46 fmt.Println("His age is" , mark.age) fmt.Println("Mark is not an athlet anymore" ) mark.weight += 60 fmt.Println("His weight is" , mark.weight) }
输出:
1 2 3 4 5 6 7 8 9 10 His name is Mark His age is 25 His weight is 120 His speciality is Computer Science Mark changed his speciality His speciality is AI Mark become old His age is 46 Mark is not an athlet anymore His weight is 180
可以使用”.”的方式进行调用匿名字段中的属性值
实际就是字段的继承
其中可以将匿名字段理解为字段名和字段类型都是同一个
基于上面的理解,所以可以mark.Human = Human{"Marcus", 55, 220}
和mark.Human.age = 46
若存在匿名字段中的字段与非匿名字段名字相同,则最外层的优先访问,就近原则
通过匿名访问和修改字段相当的有用,但是不仅仅是struct字段哦,所有的内置类型和自定义类型都是可以作为匿名字段的。
结构体嵌套 嵌套的结构体 一个结构体可能包含一个字段,而这个字段反过来就是一个结构体。这些结构被称为嵌套结构。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package mainimport ( "fmt" ) type Address struct { city, state string } type Person struct { name string age int address Address } func main () { var p Person p.name = "Naveen" p.age = 50 p.address = Address { city: "Chicago" , state: "Illinois" , } fmt.Println("Name:" , p.name) fmt.Println("Age:" ,p.age) fmt.Println("City:" ,p.address.city) fmt.Println("State:" ,p.address.state) }
提升字段 在结构体中属于匿名结构体的字段称为提升字段,因为它们可以被访问,就好像它们属于拥有匿名结构字段的结构一样。理解这个定义是相当复杂的。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package mainimport ( "fmt" ) type Address struct { city, state string } type Person struct { name string age int address Address } func main () { var p Person p.name = "Naveen" p.age = 50 p.Address = Address{ city: "Chicago" , state: "Illinois" , } fmt.Println("Name:" , p.name) fmt.Println("Age:" , p.age) fmt.Println("City:" , p.city) fmt.Println("State:" , p.state) }
运行结果
1 2 3 4 Name: Naveen Age: 50 City: Chicago State: Illinois
导出结构体和字段 如果结构体类型以大写字母开头,那么它是一个导出类型,可以从其他包访问它。类似地,如果结构体的字段以大写开头,则可以从其他包访问它们。
示例代码:
1.在computer目录下,创建文件spec.go
1 2 3 4 5 6 7 package computertype Spec struct { Maker string model string Price int }
2.创建main.go 文件
1 2 3 4 5 6 7 8 9 10 11 package mainimport "structs/computer" import "fmt" func main () { var spec computer.Spec spec.Maker = "apple" spec.Price = 50000 fmt.Println("Spec:" , spec) }
输出:
结构体比较 结构体是值类型,如果每个字段具有可比性,则是可比较的。如果它们对应的字段相等,则认为两个结构体变量是相等的。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package mainimport ( "fmt" ) type name struct { firstName string lastName string } func main () { name1 := name{"Steve" , "Jobs" } name2 := name{"Steve" , "Jobs" } if name1 == name2 { fmt.Println("name1 and name2 are equal" ) } else { fmt.Println("name1 and name2 are not equal" ) } name3 := name{firstName:"Steve" , lastName:"Jobs" } name4 := name{} name4.firstName = "Steve" if name3 == name4 { fmt.Println("name3 and name4 are equal" ) } else { fmt.Println("name3 and name4 are not equal" ) } }
运行结果
1 2 name1 and name2 are equal name3 and name4 are not equal
如果结构变量包含的字段是不可比较的,那么结构变量是不可比较的
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport ( "fmt" ) type image struct { data map [int ]int } func main () { image1 := image{data: map [int ]int { 0 : 155 , }} image2 := image{data: map [int ]int { 0 : 155 , }} if image1 == image2 { fmt.Println("image1 and image2 are equal" ) } }
invalid operation: image1 == image2 (struct containing map[int]int cannot be compared)
结构体作为函数的参数 结构体作为函数参数使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 ackage main import "fmt" type Books struct { title string author string subject string book_id int } func main () { var Book1 Books var Book2 Books Book1.title = "Go 语言" Book1.author = "www.runoob.com" Book1.subject = "Go 语言教程" Book1.book_id = 6495407 Book2.title = "Python 教程" Book2.author = "www.runoob.com" Book2.subject = "Python 语言教程" Book2.book_id = 6495700 printBook(Book1) printBook(Book2) } func printBook ( book Books ) { fmt.Printf( "Book title : %s\n" , book.title); fmt.Printf( "Book author : %s\n" , book.author); fmt.Printf( "Book subject : %s\n" , book.subject); fmt.Printf( "Book book_id : %d\n" , book.book_id); }
结构体方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport "fmt" type user struct { name string password string } func (u user) checkPassword(password string ) bool { return u.password == password } func (u *user) resetPassword(password string ) { u.password = password } func main () { a := user{name: "wang" , password: "1024" } a.resetPassword("2048" ) fmt.Println(a.checkPassword("2048" )) }
make、new操作
make用于内建类型(map、slice 和channel)的内存分配。new用于各种类型的内存分配。 内建函数new本质上说跟其它语言中的同名函数功能一样:new(T)分配了零值填充的T类型的内存空间,并且返回其地址,即一个*T类型的值。用Go的术语说,它返回了一个指针,指向新分配的类型T的零值。有一点非常重要:new返回指针
内建函数make(T, args)与new(T)有着不同的功能,make只能创建slice、map和channel,并且返回一个有初始值(非零)的T类型,而不是*T。本质来讲,导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被初始化。例如,一个slice,是一个包含指向数据(内部array)的指针、长度和容量的三项描述符;在这些项目被初始化之前,slice为nil。对于slice、map和channel来说,make初始化了内部的数据结构,填充适当的值。make返回初始化后的(非零)值。
new
用于任何类型的内存分配,返回的是类型的指针,指向新分配的零值内存空间。
make
仅用于切片、映射和通道的内存分配,返回的是经过初始化后的非零值。
指针与结构体举例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package mainimport "fmt" type user struct { name string password string } func main () { a := user{name: "wang" , password: "1024" } b := user{"wang" , "1024" } c := user{name: "wang" } c.password = "1024" var d user d.name = "wang" d.password = "1024" fmt.Println(a, b, c, d) fmt.Println(checkPassword(a, "haha" )) fmt.Println(checkPassword2(&a, "haha" )) } func checkPassword (u user, password string ) bool { return u.password == password } func checkPassword2 (u *user, password string ) bool { return u.password == password }
输出:
1 2 3 {wang 1024} {wang 1024} {wang 1024} {wang 1024} false false