为了账号安全,请及时绑定邮箱和手机立即绑定

go语言学习笔记(七)

标签:
Go 区块链

1.go语言接口

Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。

在Go语言中,一个接口类型总是代表着某一种类型(即所有实现它的类型)的行为。一个接口类型的声明通常会包含关键字type、类型名称、关键字interface以及由花括号包裹的若干方法声明。示例如下:

type Animal interface {
    Grow()
    Move(string) string
}

注意,接口类型中的方法声明是普通的方法声明的简化形式。它们只包括方法名称、参数声明列表和结果声明列表。其中的参数的名称和结果的名称都可以被省略。如:Move(new string) (old string)

如果一个数据类型所拥有的方法集合中包含了某一个接口类型中的所有方法声明的实现,那么就可以说这个数据类型实现了那个接口类型。所谓实现一个接口中的方法是指,具有与该方法相同的声明并且添加了实现部分(由花括号包裹的若干条语句)。相同的方法声明意味着完全一致的名称、参数类型列表和结果类型列表。其中,参数类型列表即为参数声明列表中除去参数名称的部分。一致的参数类型列表意味着其长度以及顺序的完全相同。对于结果类型列表也是如此。

Go语言的类型转换规则定义了是否能够以及怎样可以把一个类型的值转换另一个类型的值。另一方面,所谓空接口类型即是不包含任何方法声明的接口类型,用interface{}表示,常简称为空接口。正因为空接口的定义,Go语言中的包含预定义的任何数据类型都可以被看做是空接口的实现。我们可以直接使用类型转换表达式把一个*Person类型转换成空接口类型的值,就像这样:

p := Person{"Robert", "Male", 33, "Beijing"}
v := interface{}(&p)

请注意第二行。在类型字面量后跟由圆括号包裹的值(或能够代表它的变量、常量或表达式)就构成了一个类型转换表达式,意为将后者转换为前者类型的值。在这里,我们把表达式&p的求值结果转换成了一个空接口类型的值,并由变量v代表。注意,表达式&p(&是取址操作符)的求值结果是一个*Person类型的值,即p的指针。
实例:

package main

import "fmt"

//定义一个接口
type Animal interface {
    Grow()
    Move(string) string
}

//定义一个结构体
type Cat struct {
    Name     string
    Age      uint8
    Location string
}

func (cat *Cat) Grow() {
    cat.Age--
}

func (cat *Cat) Move(new string) string {
    return new
}

func main() {
    var phone Cat
    phone.Name = "Little c"
    phone.Age = 4
    phone.Location = "In the house"
    //使用类型转换表达式把一个*Cat类型转换成空接口类型的值
    animal, ok := interface{}(&phone).(Animal)
    fmt.Printf("%v,%v\n", ok, animal)
}

打印结果:

true,&{Little c 4 In the house}

2.go语言指针

*Person是Person的指针类型。又例如,表达式&p的求值结果是p的指针。方法的接收者类型的不同会给方法的功能带来什么影响?该方法所属的类型又会因此发生哪些潜移默化的改变?现在,我们就来解答第一个问题。

指针操作涉及到两个操作符——&和。这两个操作符均有多个用途。但是当它们作为地址操作符出现时,前者的作用是取址,而后者的作用是取值。更通俗地讲,当地址操作符&被应用到一个值上时会取出指向该值的指针值,而当地址操作符被应用到一个指针值上时会取出该指针指向的那个值。它们可以被视为相反的操作。

除此之外,当出现在一个类型之前(如Person和[3]string)时就不能被看做是操作符了,而应该被视为一个符号。如此组合而成的标识符所表达的含义是作为第二部分的那个类型的指针类型。我们也可以把其中的第二部分所代表的类型称为基底类型。例如,[3]string是数组类型[3]string的指针类型,而[3]string是*[3]string的基底类型。

好了,我们现在回过头去再看结构体类型Person。它及其两个方法的完整声明如下:

type Person struct {
    Name    string
    Gender  string
    Age     uint8
    Address string
}

func (person *Person) Grow() {
    person.Age++
}

func (person *Person) Move(newAddress string) string {
    old := person.Address
    person.Address = newAddress
    return old
}

注意,Person的两个方法Grow和Move的接收者类型都是*Person,而不是Person。只要一个方法的接收者类型是其所属类型的指针类型而不是该类型本身,那么我就可以称该方法为一个指针方法。上面的Grow方法和Move方法都是Person类型的指针方法。

相对的,如果一个方法的接收者类型就是其所属的类型本身,那么我们就可以把它叫做值方法。我们只要微调一下Grow方法的接收者类型就可以把它从指针方法变为值方法:

func (person Person) Grow() {
    person.Age++
}

那指针方法和值方法到底有什么区别呢?我们在保留上述修改的前提下编写如下代码:

p := Person{"Robert", "Male", 33, "Beijing"}
p.Grow()
fmt.Printf("%v\n", p)   

这段代码被执行后,标准输出会打印出什么内容呢?直觉上,34会被打印出来,但是被打印出来的却是33。这是怎么回事呢?Grow方法的功能失效了?!

解答这个问题需要引出一条定论:方法的接收者标识符所代表的是该方法当前所属的那个值的一个副本,而不是该值本身。例如,在上述代码中,Person类型的Grow方法的接收者标识符person代表的是p的值的一个拷贝,而不是p的值。我们在调用Grow方法的时候,Go语言会将p的值复制一份并将其作为此次调用的当前值。正因为如此,Grow方法中的person.Age++语句的执行会使这个副本的Age字段的值变为34,而p的Age字段的值却依然是33。这就是问题所在。

只要我们把Grow变回指针方法就可以解决这个问题。原因是,这时的person代表的是p的值的指针的副本。指针的副本仍会指向p的值。另外,之所以选择表达式person.Age成立,是因为如果Go语言发现person是指针并且指向的那个值有Age字段,那么就会把该表达式视为(person).Age。其实,这时的person.Age正是(person).Age的速记法。
实例:

package main

import "fmt"

func main() {
    var a int= 20   /* 声明实际变量 */
    var b *int        /* 声明指针变量 */

    b = &a  /* 指针变量的存储地址 */

    fmt.Printf("a 变量的地址是: %x\n", &a  )

    /* 指针变量的存储地址 */
    fmt.Printf("b 变量储存的指针地址: %x\n", b )

    /* 使用指针访问值 */
    fmt.Printf("*b 变量的值: %d\n", *b )
}

运行结果:

a 变量的地址是: c0420080b8

b 变量储存的指针地址: c0420080b8

*b 变量的值: 20

我们在讲接口的时候说过,如果一个数据类型所拥有的方法集合中包含了某一个接口类型中的所有方法声明的实现,那么就可以说这个数据类型实现了那个接口类型。要获知一个数据类型都包含哪些方法并不难。但是要注意指针方法与值方法的区别。

拥有指针方法Grow和Move的指针类型*Person是接口类型Animal的实现类型,但是它的基底类型Person却不是。这样的表象隐藏着另一条规则:一个指针类型拥有以它以及以它的基底类型为接收者类型的所有方法,而它的基底类型却只拥有以它本身为接收者类型的方法。
实例:

package main

import "fmt"

//定义一个接口
type Pet interface {
    Name() string
    Age() uint8
}

//定义结构体
type Dog struct {
    name string
    age  uint8
}

//值方法   实现接口方法
func (dog Dog) Name() string {
    return dog.name
}
func (dog Dog) Age() uint8 {
    return dog.age
}

func main() {
    myDog := Dog{"goggo", 5}
    //使用类型转换表达式把一个Dog类型转换成空接口类型的值
    _, ok1 := interface{}(&myDog).(Pet)
    _, ok2 := interface{}(myDog).(Pet)
    fmt.Printf("%v,%v\n", ok1, ok2)
}

结果:true,true

3.go语言流程控制语句——if语句

Go语言的流程控制主要包括条件分支、循环和并发。
if语句一般会由关键字if、条件表达式和由花括号包裹的代码块组成。所谓代码块,即是包含了若干表达式和语句的序列。在Go语言中,代码块必须由花括号包裹。另外,这里的条件表达式是指其结果类型是bool的表达式。
一条最简单的if语句可以是:

if 100 > number { 
    number += 3
}

这里的标识符number可以代表一个int类型的值。这条if语句的意思是:如果number的值小于100,那么就把其值增加3。我还可以在此之上添加else分支,就像这样:

if 100 > number {
    number += 3
} else {
    number -= 2
}

else分支的含义是,提供在条件不成立(具体到这里是number的值不小于100)的情况下需要执行的操作。除此之外,if语句还支持串联。请看下面的例子:

if 100 > number {
    number += 3
} else if 100 < number {
    number -= 2
} else {
    fmt.Println("OK!")
}   

可以看到,上述代码很像是把多条if语句串接在一起了一样。这样的if语句用于对多个条件的综合判断。上述语句的意思是,若number的值小于100则将其加3,若number的值大于100则将其减2,若number的值等于100则打印OK!。

注意,我们至此还未对number变量进行声明。上面的示例也因此不能通过编译。我们当然可以用单独的语句来声明该变量并为它赋值。但是我们也可以把这样的变量赋值直接加入到if子句中。示例如下:

if number := 4; 100 > number {
    number += 3
} else if 100 < number {
    number -= 2
} else {
    fmt.Println("OK!")
}   

这里的number := 4被叫做if语句的初始化子句。它应被放置在if关键字和条件表达式之间,并与前者由空格分隔、与后者由英文分号;分隔。注意,我们在这里使用了短变量声明语句,即:在声明变量number的同时为它赋值。这意味着这里的number被视为一个新的变量。它的作用域仅在这条i语句所代表的代码块中。也可以说,变量number对于该if语句之外的代码来说是不可见的。我们若要在该if语句以外使用number变量就会造成编译错误。

另外还要注意,即使我们已经在这条if语句所代表的代码块之外声明了number变量,这里的语句number := 4也是合法的。请看这个例子:

var number int
if number := 4; 100 > number {
    number += 3
} else if 100 < number {
    number -= 2
} else {
    fmt.Println("OK!")
}

这种写法有一个专有名词,叫做:标识符的重声明。实际上,只要对同一个标识符的两次声明各自所在的代码块之间存在包含的关系,就会形成对该标识符的重声明。具体到这里,第一次声明的number变量所在的是该if语句的外层代码块,而number := 4所声明的number变量所在的是该if语句的代表代码块。它们之间存在包含关系。因此对number的重声明就形成了。

这种情况造成的结果就是,if语句内部对number的访问和赋值都只会涉及到第二次声明的那个number变量。这种现象也被叫做标识符的遮蔽。上述代码被执行完毕之后,第二次声明的number变量的值会是7,而第一次声明的number变量的值仍会是0。
实例:

//go语言的if...else表达式
package main

import "fmt"

func main() {
    /* 局部变量定义 */
    var a int = 100

    /* 判断布尔表达式 */
    if a < 20 {
        /* 如果条件为 true 则执行以下语句 */
        fmt.Printf("a 小于 20\n")
    } else {
        /* 如果条件为 false 则执行以下语句 */
        fmt.Printf("a 不小于 20\n")
    }
    fmt.Printf("a 的值为 : %d\n", a)
}

结果:
a 不小于 20
a 的值为 : 100

实例:

//go语言if表达式嵌套
package main

import "fmt"

func main() {
    //定义局部变量
    var a int = 100
    var b int = 200

    //判断条件
    if a == 100 {
        //if条件为true执行
        if b == 200 {
            //if条件语句为true执行
            fmt.Printf("a 的值为 100 , b 的值为 200\n")
        }
    }
    fmt.Printf("a 值为 : %d\n", a)
    fmt.Printf("b 值为 : %d\n", b)
}

结果:
a 的值为 100 , b 的值为 200
a 值为 : 100
b 值为 : 200

好了,今晚分享就到这里,后续还会不断更新!请诸君多多支持!

点击查看更多内容
2人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消