Go语言核心36讲-040606程序实体

程序实体之变量、类型推断

04.05.06 | 程序实体知识点汇总

04 程序实体哪些事(上)

问题1: 声明变量有几种方式

  • var
  • :=

问题解析:

考点:

  • #### 1. 类型推断

简单地说,类型推断是一种编程语言在编译期自动解释表达式类型的能力。

var name  = "zhang"

这里利用了 Go 语言自身的类型推断省去了 string 它只能用于对变量或常量的初始化。

  • #### 2. 短变量声明用法

知识扩展

1. ==Go 语言的类型推断可以带来哪些好处==?

通过使用 Go 语言的类型推断,而节省下来的键盘敲击次数几乎可以忽略不计。但它真正的好处往往体现在我们写代码之后的事情上, 比如代码的 重构。

重构: 通常把“不改变某个程序与外界的任何交互方式和规则,而只改变其内部实现”的代码修改方式,叫做对该程序的重构。

重构的对象可以是一行代码、一个函数、一个功能模块,甚至一个软件系统。

package main

import (
    "flag"
    "fmt"
)

func main() {
    var name = getTheFlag()
    flag.Parse()
    fmt.Printf("Hello, %v!\n", *name)
}

func getTheFlag() *string {
    return flag.String("name", "everyone", "The greeting object.")
}

以上代码我们做了一些重构,你会发现,你可以随意改变函数的内部实现及其返回结果的类型,而不用修改 main 函数中的任何代码。

最终答案:

==Go 语言的类型推断可以明显提升程序的灵活性,使得代码重构变得更加容易,同时又不会给代码的维护带来额外负担(实际上,它恰恰可以避免散弹式的代码修改),更不会损失程序的运行效率。==

2. ==变量的重声明是什么意思?==

这涉及了短变量声明。通过使用它,我们可以对同一个代码块中的变量进行重声明。

每个源码文件也都是一个代码块,每个函数也是一个代码块,每个if语句、for语句、switch语句和select语句都是一个代码块。

甚至,switch 或 select 语句中的 case 子句也都是独立的代码块。

main函数中写一对紧挨着的花括号算不算一个代码块?当然也算,这甚至还有个名词,叫“空代码块”。

变量重声明:其含义是对已经声明过的变量再次声明。

变量重声明的前提条件:

  1. 由于变量的类型在其初始化时就已经确定了,所以对它再次声明时赋予的类型必须与其原本的类型相同,否则会产生编译错误。

  2. 变量的重声明只可能发生在某一个代码块中。如果与当前的变量重名的是外层代码块中的变量,那么就是另外一种含义了,我在下一篇文章中会讲到。

  3. 变量的重声明只有在使用短变量声明时才会发生,否则也无法通过编译。如果要在此处声明全新的变量,那么就应该使用包含关键字

  4. 被“声明并赋值”的变量必须是多个,并且其中至少有一个是新的变量。这时我们才可以说对其中的旧变量进行了重声明。 (其实这里有个术语叫做: 退化赋值。 它有两个条件:

  5. 必须至少要有一个新的变量产生; 2. 必须是同一个作用域中。 )


05 程序实体哪些事(中)

问题2:如果一个变量与其外层代码块中的变量重名会出现什么状况?

问题解析:

优先查找对应最内部的代码块中的变量,再逐步往外一层查找,如果到了最外层也没有找到则会编译报错。

但有个特殊情况,如果我们把代码包导入语句写成 import . XXX 的形式(注意中间的那个“.”),那么就会让这个“XXX”包中公开的程序实体被当前源码文件中的代码,视为当前代码包中的程序实体。

比如,如果有代码包导入语句,那么我们在当前源码文件中引用 fmt.Printf 函数的时候直接用 Printf 就可以了。

在这个特殊情况下,程序在查找当前源码文件后会先去查用这种方式导入的那些代码包。

知识扩展:

==1. 不同代码块中的重名变量与变量重声明中的变量区别到底在哪儿?==

为了方便描述,我就把前者叫做“可重名变量”吧。

注意,在同一个代码块中是不允许出现重名的变量的,这违背了 Go 语言的语法。

变量重声明 和 可重名变量的区别:

  1. 变量重声明中的变量一定是在某一个代码块内的。注意,这里的“某一个代码块内”并不包含它的任何子代码块,否则就变成了“多个代码块之间”。而可重名变量指的正是在多个代码块之间的由相同的标识符代表的变量。

  2. 变量重声明是对同一个变量的多次声明,这里的变量只有一个。而可重名变量中涉及的变量肯定是有多个的。

  3. 不论对变量重声明多少次,其类型必须始终一致,具体遵从它第一次被声明时给定的类型。而可重名变量之间不存在类似的限制,它们的类型可以是任意的。

  4. 如果可重名变量所在的代码块之间存在直接或间接的嵌套关系,那么它们之间一定会存在“屏蔽”的现象。但是这种现象绝对不会在变量重声明的场景下出现。


06 程序实体哪些事(下)

问题3:怎样判断一个变量的类型?

使用“类型断言”表达式。

value, ok := interface{}(container).([]string)

把变量 container 的值转换为空接口值的 interface{}(container)。

以及一个用于判断前者的类型是否为切片类型 []string 的 .([]string)

ok 如果是true,那么被判断的值将会被自动转换为[]stringl类型的值,并赋值给变量 value,否则 value 将被赋值 nil (即“空”)。

问题解析:

类型断言表达式的语法形式是:x.(T)

其中 x 代表要被判断类型的那个值。这个值当下的类型必须是接口类型的,不过具体是哪个接口类型其实是无所谓的。

所以,当这里的 container 变量类型不是任何的接口类型时,就需要先把它转化成某个接口类型的值。

==请记住,一对不包裹任何东西的花括号,除了可以代表空的代码块之外,还可以用于表示不包含任何内容的数据结构(或者说数据类型)。==

知识扩展:

==1. 类型转换规则中有哪些要注意的地方?==

注意这里是 类型转换 ,而上面提到的是 类型断言,他们的语法形式是不一样的哦。

  • #### 类型断言语法形式: x.(T)
  • #### 类型转换语法形式:T(x) 这里要说的。

首先,对于整数类型值、整数常量之间的类型转换,原则上只要源值在目标类型的可表示范围内就是合法的。

uint8(255) 能把无类型的常量 255 转换为 uint8 类型的值,是因为 255 在【0.255】的范围内。

但是 比如:

var srcInt = int16(-255) 

dstIn := int8(srcInt)

类型是int8。int16类型的可表示范围可比int8类型大了很多,所有最终 dstIn 的值为多少?

整数在 Go 语言以及计算机中都是以补码的形式存储的。这主要是为了简化计算机对整数的运算过程。补码其实就是原码个位求反再加 1。

比如,int16类型的值-255的补码是1111111100000001。

如果我们把该值转换为 int8 类型的值,那么 Go 语言会把在较高位置(或者说最左边位置)上的 8 位二进制数直接截掉,从而得到 00000001。

又由于其最左边一位是 0 ,表示它是个正整数,以及正整数的补码就等于其原码,所以 dstInt 的值就是 1。

==当整数值的类型的有效范围由宽变窄时,只需在补码形式下截掉一定数量的高位二进制数即可。==

类似的快刀斩乱麻规则还有:当把一个浮点数类型的值转换为整数类型值时,前者的小数部分会被全部截掉。

其次,虽然直接把一个整数值转换为一个 string 类型的值是可行的,但值得关注的是,被转换的整数值应该可以代表一个有效的 Unicode 代码点,否则转换的结果将会是”�”(仅由高亮的问号组成的字符串值)。


2. 什么是别名类型?什么是潜在类型?

Go 语言中 可以用关键字type声明自定义的各种类型。

 type MyString = string

这条声明语句表示,MyString是类型的别名类型。顾名思义,别名类型与其源类型的区别恐怕只是在名称上,它们是完全相同的。

源类型与别名类型是一对概念,是两个对立的称呼。别名类型主要是为了代码重构而存在的。更详细的信息可参见 Go 语言官方的文档

Go 语言内建的基本类型中就存在两个别名类型。

  • byte 是 uint8 的别名类型
  • rune 是 int32 的别名类型

如果是一下声明:

type MyString2 string // 注意,这里没有等号

对于这里的类型再定义来说,string 可以被称为 MyString2 的潜在类型。

潜在类型的含义是某个类型在本质上是哪个类型或者是哪个类型的集合。