Go指针
Go 有指针,但是没有指针运算。你不能用指针变量遍历字符串的各个字节。
在 Go 中调用函数的时候,得记得变量是 值传递 的
通过类型作为前缀来定义一个指针’ * ’:var p * int
。现在 p 是一个指向整数值的指针。所有新定义的变量都被赋值为其类型的零值,而指针也一样。一个新定义的或者没有任何指向的指针,有值 nil。在其他语言中,这经常被叫做空(NULL)指针,在 Go 中就是 nil 。让指针指向某些内容,可以使用取址操作符 ( & ),像这样:
package main
import "fmt"
func main() {
var p *int
fmt.Printf("%v\n",p) //← 打印 nil
var i int //← 定义一个整形变量 i
p = &i //← 使得 p 指向 i, 获取 i 的地址
fmt.Printf("%v\n",p) //打印内存地址
*p = 8
fmt.Printf("%v\n",*p) //打印8
fmt.Printf("%v\n",i) //打印8
}
前面已经说了,没有指针运算,所以如果这样写: *p++
,它表示 (*p)++
:首先获取指针指向的值,然后对这个值加一。这里注意与C语言的区别
内存分配
Go 同样也垃圾收集,也就是说无须担心内存分配和回收。
Go 有两个内存分配原语, new 和 make 。它们应用于不同的类型,做不同的工作,可能有些迷惑人,但是规则很简单。
用 new 分配内存
内建函数 new 本质上说跟其他语言中的同名函数功能一样: new(T) 分配了零值填充的 T 类型的内存空间,并且返回其地址,一个 *T 类型的值。用 Go 的术语说,它返回了一个指针,指向新分配的类型 T 的零值。记住这点非常重要。
这意味着使用者可以用 new 创建一个数据结构的实例并且可以直接工作。 如
bytes.Buffer 的文档所述 “Buffer 的零值是一个准备好了的空缓冲。” 类似的,sync.Mutex 也没有明确的构造函数或 Init 方法。取而代之, sync.Mutex 的零值被定义为非锁定的互斥量。
type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
}
//SyncedBuffer 的值在分配内存或定义之后立刻就可以使用。在这个片段中, p 和 v 都可以在没有任何更进一步处理的情况下工作。
p := new (SyncedBuffer)// ← Type *SyncedBuffer ,已经可以使用
var v SyncedBuffer //← Type SyncedBuffer ,同上
用 make 分配内存
内建函数 make(T, args) 与 new(T) 有着不同的功能。它 只能 创建
slice,map 和 channel,并且返回一个有初始值(非零)的 T 类型,而不是 *T 。本质来讲,导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被初始化。例如,一个 slice,是一个包含指向数据(内部 array)的指针,长度和容量的三项描述符;在这些项目被初始化之前,slice 为 nil 。对于 slice,map 和 channel, make 初始化了内部的数据结构,填充适当的值。
例如,make ([] int , 10, 100)
分配了 100 个整数的数组,然后用长度 10 和容量 100
创建了 slice 结构指向数组的前 10 个元素。区别是,new ([] int )
返回指向新分配的内存的指针,而零值填充的 slice 结构是指向 nil 的 slice 值
务必记得 make 仅适用于 map,slice 和 channel,并且返回的不是指针。应当用 new 获得特定的指针。
package main
import "fmt"
func main() {
var p *[] int = new ([] int ) //← 分配 slice 结构内存;很少使用
var v [] int = make ([] int , 100) //← v 指向一个新分配的有 100 个整数的数组
//==========================
var p *[] int = new ([] int ) //← 不必要的复杂例子
*p = make ([] int , 100, 100)
//==========================
v := make ([] int , 100) //← 更常见
}
定义自己的类型
自然,Go 允许定义新的类型,通过关键字 type 实现:
type foo int
创建了一个新的类型 foo 作用跟 int 一样。创建更加复杂的类型需要用到 struct 关键字。这有个在一个数据结构中记录某人的姓名( string )和年龄( int ),并且使其成为一个新的类型的例子:
package main
import "fmt"
type NameAge struct{
name string //私有(字段首字母小写为私有,大写为公有属性)
age int //私有
}
func main() {
a := new(NameAge)
a.name = "Peter"
a.age = 55
fmt.Printf("%v\n",a)
fmt.Printf("%v\n",a.age)
fmt.Printf("%v\n",a.name)
}
可以对新定义的类型创建函数以便操作,可以通过两种途径:
1. 创建一个函数接受这个类型的参数。
func doSomething(n1 *NameAge, n2 int ) { /* */ }
2 . 创建一个工作在这个类型上的函数
func (n1 *NameAge) doSomething(n2 int ) { /* */ }
//这是 方法调用 ,可以类似这样使用:
var n *NameAge
n.doSomething(2)
类型转换
有时需要将一个类型转换为另一个类型
并不是所有的转换都是允许的。
字符串转换
package main
import "fmt"
func main() {
//从 string 到字节或者 rune 的 slice。
mystring := "hello this is string"
//转换到 byte slice,每个 byte 保存字符串对应字节的整数值。注意 Go 的字符串是 UTF-8 编码的,一些字符可能是 1、2、3 或者 4 个字节结尾
byteslice := [] byte (mystring)
// 转换到 rune slice,每个 rune 保存 Unicode 编码的指针。字符串中的每个字符对应一个整数
runeslice := [] rune (mystring)
// 从字节或者整形的 slice 到 string。
b := [] byte{ 'h','e','l','l','o' } // 复合声明
s := string (b)
i := [] rune{ 115,116,114,105,110,113 }
r := string (i)
fmt.Printf("mystring = %v\n",mystring)
fmt.Printf("byteslice = %v\n",byteslice)
fmt.Printf("runeslice = %v\n",runeslice)
fmt.Printf("b = %v\n",b)
fmt.Printf("s = %v\n",s)
fmt.Printf("i = %v\n",i)
fmt.Printf("r = %v\n",r)
}
数值转换
对于数值,定义了下面的转换:
• 将整数转换到指定的(bit)长度:uint8 ( int ) ;
• 从浮点数到整数:int ( float32 ) 。这会截断浮点数的小数部分;
• 其他的类似:float32 ( int ) 。
用户定义类型的转换
package main
import "fmt"
func main() {
type foo struct { int }// ← 匿名字段
type bar foo //← bar 是 foo 的别名
var b bar = bar { 1 } //← 声明 b 为 bar 类型
//var f foo = b //← 赋值 b 到 f cannot use b (type bar) as type foo in assignment
// 这可以通过类型转换来修复:
var f foo = foo(b) //类型转换
fmt.Printf("%v\n",f)
}
练习
// Q17. (1) 指针运算
// 1. 在正文的第 54 页有这样的文字:
// … 这里没有指针运算,因此如果这样写: *p++ ,它被解释为 (*p)++ :
// 首先解析引用然后增加值。
// 当像这样增加一个值的时候,什么类型可以工作?
// 2. 为什么它不能工作在所有类型上?
1. 这仅能工作于指向数字( int, uint 等等)的指针值。
2. ++ 仅仅定义在数字类型上,同时由于在 Go 中没有运算符重载,所以会在其他
类型上失败(编译错误)。
// Q18. (2) 使用 interface 的 map 函数
// 1. 使用练习 Q11 的答案,利用 interface 使其更加通用。让它至少能同时工作于
// int 和 string。
// Q11. (1) map 函数 map() 函数是一个接受一个函数和一个列表作为参数的函数。函
// 数应用于列表中的每个元素,而一个新的包含有计算结果的列表被返回。因此:
// map(f(),(a 1 ,a 2 ,...,a n−1 ,a n )) = (f(a 1 ),f(a 2 ),...,f(a n−1 ),f(a n ))
// 1. 编写 Go 中的简单的 map() 函数。它能工作于操作整数的函数就可以了。
// 2. 扩展代码使其工作于字符串列表。
package main
import "fmt"
//* define the empty interface as a type
type e interface {}
func mult2(f e) e {
switch f.( type ) {
case int :
return f.( int ) * 2
case string :
return f.( string ) + f.( string ) + f.( string )+ f.( string )
}
return f
}
func Map(n []e, f func (e) e) []e {
m := make ([]e, len (n))
for k, v := range n {
m[k] = f(v)
}
return m
}
func main() {
m := []e { 1, 2, 3, 4 }
s := []e { "a", "b", "c", "d" }
mf := Map(m, mult2)
sf := Map(s, mult2)
fmt.Printf("%v\n", mf)
fmt.Printf("%v\n", sf)
}
// Q19. (1) 指针
// 1. 假设定义了下面的结构:
type Person struct {
name string
age int
}
// 下面两行之间的区别是什么?
var p1 Person
p2 := new (Person)
// 1.
// 第一行:var p1 Person 分配了 Person 值 给 p1 。 p1 的类型是 Person 。
// 第二行: p2 := new (Person) 分配了内存并且将 指针 赋值给 p2 。 p2 的类型是
// *Person 。
//------------分割线--------------------
// 2. 下面两个内存分配的区别是什么?
func Set(t *T) {
x = t
}
// 和
func Set(t T) {
x= &t
}
// 2.
// 在第二个函数中, x 指向一个新的(堆上分配的)变量 t ,其包含了实际参数值
// 的副本。
// 在第一个函数中, x 指向了 t 指向的内容,也就是实际上的参数指向的内容。
// 因此在第二个函数,我们有了 “额外” 的变量存储了相关值的副本。
// Q20. (1) Linked List
// 1. Make use of the package container/list to create a (doubly) linked list. Push the
// values 1, 2 and 4 to the list and then print it.
// 2. Create your own linked list implementation. And perform the same actions as in
// question
//打印双向链表
package main
import (
"fmt"
"container/list"
)
func main() {
l := list.New()
l.PushBack(1)
l.PushBack(2)
l.PushBack(4)
for e := l.Front() ; e != nil ; e = e.Next() {
fmt.Printf("%v\n", e.Value)
}
}