Go语言学习(八)| 类型、指针

时间:2022-07-22
本文章向大家介绍Go语言学习(八)| 类型、指针,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

指针

Go 有指针,但是没有指针运算。你不能用指针变量遍历字符串的各个字节。

通过类型作为前缀来定义一个指针 * 。 例:

var p *int

现在 p 是一个指向整数值的指针。 变量名和 * 之间要有个空格。

所有新定义的变量都被赋值为其类型的零值,而指针也一样。一个新定义的或者没有任何指向的指针,有值 nil 。 在其他语言中,这经常被叫做 空(NULL)指针 ,在 Go 中就是 nil 。让指针指向某些内容,可以使用取址操作符 &

  • 如果一个 method 的 receiver 是 *T ,你可以在一个 T 类型的实例变量 V 上面调用这个 method,而不需要 &V 去调用这个 method
  • 如果一个 method 的 receiver 是 T ,你可以在一个*T 类型的变量P上面调用这个 method,而不需要 *P 去调用这个 method

例:

package main
import "fmt"
func main(){
    var p *int
    fmt.Printf("%vn", p)
    var i int
    p = &i
    fmt.Printf("%vn", p)
}

输出结果:

<nil>
0x18600118

例2:

package main
import "fmt"
func main(){
    var p *int
    var i int
    p = &i
    *p = 8
    fmt.Printf("%vn", *p)
    fmt.Printf("%vn", i)
}

输出结果:

8
8
因为Go没有指针运算,所以如果这样写:*p++,它表示(*p)++:首先获取指针指向的值,然后对这个值加一。

自定义类型

Go 允许定义新的类型,通过保留字 type 实现,例:

type foo int

创建了一个新的类型 foo 作用跟 int 一样。创建更加复杂的类型需要用到 struct 保留字。

例:

package main
import "fmt"
type NameAge struct {
    name string        //不导出
    age int            //不导出
}
func main(){
    a := new(NameAge)
    a.name = "Pete"
    a.age = 42
    fmt.Printf("%vn", a)
    fmt.Printf("%vn", a.name)
}

输出结果:

&{Pete 42}
Pete

struct 声明

注意 struct{} 中首字母大写的字段可以被导出, 也就是说,在其他包中可以进行读写。 字段名以小写字幕开头是当前包的私有的。

例2:

type person struct {
    name string
    age int
}
var P person  // P现在就是person类型的变量了

P.name = "Astaxie"  // 赋值"Astaxie"给P的name属性.
P.age = 25          // 赋值"25"给变量P的age属性
fmt.Printf("The person's name is %s", P.name)  // 访问P的name属性.

除了上面这种P的声明使用之外,还有几种声明使用方式

  • 按照顺序提供初始化值
P := person{"Tom", 25}
  • 通过 field:value 的方式初始化,这样可以任意顺序(以这种方式初始化的时候每个字段都要写出来)
P := person{age:24, name:"Tom"}
  • 通过 new 函数分配一个指针,此处 P 的类型为 *person
p := new(person)

例3:

package main
import "fmt"

// 声明一个新的类型
type person struct {
    name string
    age int
}

// 比较两个人的年龄,返回年龄大的那个人,并且返回年龄差
// 注意:struct也是传值的
func Older(p1, p2 person) (person, int) {
    if p1.age>p2.age {  // 比较p1和p2这两个人的年龄
        return p1, p1.age-p2.age
    }
    return p2, p2.age-p1.age
}

func main() {
    var tom person
    // 赋值初始化
    tom.name, tom.age = "Tom", 18

    // 两个字段都写清楚的初始化
    bob := person{age:25, name:"Bob"}

    // 按照struct定义顺序初始化值
    paul := person{"Paul", 43}

    tb_Older, tb_diff := Older(tom, bob)
    tp_Older, tp_diff := Older(tom, paul)
    bp_Older, bp_diff := Older(bob, paul)

    fmt.Printf("Of %s and %s, %s is older by %d yearsn",
        tom.name, bob.name, tb_Older.name, tb_diff)

    fmt.Printf("Of %s and %s, %s is older by %d yearsn",
        tom.name, paul.name, tp_Older.name, tp_diff)

    fmt.Printf("Of %s and %s, %s is older by %d yearsn",
        bob.name, paul.name, bp_Older.name, bp_diff)
}

输出结果:

Of Tom and Bob, Bob is older by 7 years
Of Tom and Paul, Paul is older by 25 years
Of Bob and Paul, Paul is older by 18 years

struct 的匿名字段

我们上面介绍了如何定义一个 struct ,定义的时候是字段名与其类型一一对应, 实际上 Go 支持只提供类型, 而不写字段名的方式,也就是匿名字段,也称为嵌入字段。当匿名字段是一个 struct 的时候,那么这个 struct 所拥有的全部字段都被隐式地引入了当前定义的这个 struct。例:

package main
import "fmt"

type Human struct {
    name string
    age int
    weight int
}

type Student struct {
    Human  // 匿名字段,那么默认Student就包含了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)
}

输出结果:

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

方法

可以对新定义的类型创先函数以便操作,可以通过两种途径:

  1. 创建一个函数接受这个类型的参数。
func doSomething(in1 *NameAge, in2 int) { /* ... */ }

你可能已经猜到了,这其实就是函数调用。

  1. 创建一个工作在这个类型上的函数:
func (in1 *NameAge) doSomething(in2 int) { /* ... */ }

这是方法调用,可以类似这样使用:

var n *NameAge
n.doSomething(2)

例1:

package main
import "fmt"
type NameAge struct {
    name string
    age int
}
func main(){
    a := new(NameAge)
    a.name = "Pete"
    a.age = 42
    doSomething(a, 3)
}
func doSomething(in1 *NameAge, in2 int){
    fmt.Printf("%vn", in1.name)
    fmt.Printf("%dn", in2)
}

输出结果:

Pete
3

例2:

package main
import "fmt"
type NameAge struct {
    name string
    age int
}
func main(){
    a := new(NameAge)
    a.name = "Pete"
    a.age = 42
    a.doSomething(333)
}
func (in1 *NameAge) doSomething(in2 int){
    fmt.Printf("%vn", in1.name)
    fmt.Printf("%dn", in2)
}

输出结果:

Pete
333

类型转换

有时需要将一个类型转换为另一个类型。在 Go 中可以做到,不过有一些规则。

注意 Go 的字符串是 UTF-8 编码的,一些字符可能是 1、2、3 或者 4 个字节结尾。

例:

package main
import "fmt"
func main(){
    mystring := "hello world"
    byteslice := []byte(mystring)
    for _, val := range byteslice {
        fmt.Printf("%vn", val)
    }
}

输出结果:

104
101
108
108
111
32
119
111
114
108
100

例2

package main
import "fmt"
func main(){
    mystring := "hello world"
    byteslice := []rune(mystring)
    for _, val := range byteslice {
        fmt.Printf("%cn", val)
    }
}

输出结果:

h
e
l
l
o

w
o
r
l
d

对于数值,定义了下面的转换:

  • 将整数转换到指定的(bit)长度:uint8(int);
  • 从浮点数到整数:int(float32)。这会截断浮点数的小数部分;
  • 其他的类似:float32(int)。例:
package main
import "fmt"
func main(){
    myfloat := 123.72
    myint := int(myfloat)
    fmt.Printf("%.3fn", myfloat)
    fmt.Printf("%dn", myint)
}

输出结果:

123.720
123

用户定义类型的转换

如何在自定义类型之间进行转换?这里创建了两个类型 foo 和 bar ,而 bar 是 foo 的一个别名:例:

package main
import "fmt"
type foo struct { int }        //匿名字段
type bar foo
func main(){
    b := bar{1}                //声明b 为bar 类型,并赋初值
    var f foo = b
    //上面赋值b 到f,这会引起一行错误cannot use b (type bar) as type foo in assignment(不能使用b(类型bar)作为类型foo 赋值)
    var f foo = foo(b)        //这个是可以的
    fmt.Printf("%dn", f)
}

输出结果:

{1}

注意:转换 b 到 int 同样会出错;整数与有整数字段的结构并不一样。