Go基础学习04-变量重声明;类型转换;类型断言;Unicode代码点;类型别名;潜在类型
目录
变量重声明
类型断言
类型转换
类型转换注意事项
Unicode代码点
类型别名、潜在类型
类型别名的意义
变量重声明
编写代码:
package main
import "fmt"
var container = []string{"Beijing", "Shanghai"}
func main() {
fmt.Println(container)
container = []string{"Hello", "Hi"}
fmt.Printf("variable redeclare %s\n", container)
container := map[int]string{1: "Beijing", 2: "Shanghai"}
strings, ok := interface{}(container).([]string)
if ok {
fmt.Println("Container type is []string...")
fmt.Println(strings)
} else {
fmt.Println("Container type is map...")
fmt.Printf("strings is %v\n", strings)
}
fmt.Println(container)
}
上述代码首先定义一个字符串数组,其名称为container,在main函数中,首先对container进行短变量重赋值,随后又对container使用短变量重新赋值,但这次赋值的对象类型不是[]string而是一个map类型,其key为int,value为string。此时产生的现象在Go中称为可重名变量的类型不同,原先类型为[]string,随后将其类型更改为map[int][string]。上述代码执行结果如下:
在使用:=对变量进行重声明之后,如果前后对应的变量类型不一致,此时一般需要进行类型断言,来确定具体变量对应的类型如:是[]string还是map[int][string]以确定后面遍历操作。
下面将补充一下类型转换以及类型断言相关知识:
类型断言
在本篇开篇提供的代码中:
上述方框中的.([]string)就是类型断言:
类型断言表达式的语法形式是x.(T)。其中的x代表要被判断类型的那个值。这个值当下的类型必须是接口类型的,不过具体是哪个接口类型其实是无所谓的。所以,当这里的container变量类型不是任何的接口类型时,我们就需要先把它转成某个接口类型的值(具体参考下面类型转换知识)。如果container是某个接口类型的,那么这个类型断言表达式就可以是container.([]string)。
这里使用类型断言将接口类型转换为[]string,同时类型断言等式左边有两个变量,变量ok表示是否能将接口类型变量断言为字符串数组,如果可以断言,则ok值为true同时转换后的字符串数组结果存储到strings变量中;如果不能将接口类型的变量转换为字符串数组,此时ok的值为false,并且strings值为[](nil)参考上面代码运行结果。
类型转换
类型转换的语法形式是T(x)。
其中的x可以是一个变量,也可以是一个代表值的字面量(比如1.23和struct{}),还可以是一个表达式。如果是表达式,那么该表达式的结果只能是一个值,而不能是多个值。在这个上下文中,x可以被叫做源值,它的类型就是源类型,而那个T代表的类型就是目标类型。
如果从源类型到目标类型的转换是不合法的,那么就会引发一个编译错误。
strings, ok := interface{}(container).([]string)
上面代码中的interface{}(containter)就是类型转换,当container不是一个接口类型时,通过类型转换将其转换为接口类型。在Go语言中,interface{}代表空接口,任何类型都是它的实现类型。任何类型的值都可以很方便地被转换成空接口的值。
关于上面interface{}中的{}的解释参考下面图片:
类型转换注意事项
对于类型转换而言常见的需要遵循的转换规则如下:
- 对于整数类型值、整数常量之间的类型转换,原则上只要源值在目标类型的可表示范围内
就是合法的。
- uint8(255)可以把无类型的常量255转换为uint8类型的值,是因为255在 [0,255] 的范围内。但需要特别注意的是,源整数类型的可表示范围较大,而目标类型的可表示范围较小的情况,比如把值的类型从int16转换为int8,此时会涉及到类型值的截断(大范围变为小范围可能涉及到值的缩小)。
- 虽然直接把一个整数值转换为一个string类型的值是可行的,但值得关注的是,被转换
的整数值应该可以代表一个有效的 Unicode 代码点,否则转换的结果将会是"�"(仅由高亮的问号组成的字符串值)。具体关于Unicode代码点的解释可以参考下面阐述。- string类型与各种切片类型之间的互转需要遵守类型编码规则,如UTF-8编码或者其他形式。
代码展示:
package main
import (
"fmt"
"strconv"
)
func main() {
// 类型转换范围限定演练
srcNum := int16(-255)
dstNum := int8(srcNum)
fmt.Printf("srcNum:%d, dstNum:%d\n", srcNum, dstNum)
/**
Go语言中负数以补码的形式存在,补码:源码求反+1
-255 :1111111100000001
从16位转为8位,需要高位截断变为00000001,由于最高位是0所以表示正数,所以是1
*/
/**
Go中有效的Unicode代码点是什么???
在将一个整数值转换为字符串时,这个整数应该是一个有效的 Unicode 代码点的值。Unicode 是一个字符编码标准,它为世界上大多数的文字系统提供了一个唯一的码位。每个 Unicode 代码点都对应一个字符。
在计算机中,字符通常以字节的形式存储,而每个字节可以表示 0 到 255 之间的整数值。当一个整数超出了这个范围,或者它不是一个有效的 Unicode 代码点时,尝试将它转换为字符串可能会导致无法正确显示该字符,从而出现替代字符,通常是 ""(一个黑色菱形,中间有一个问号)。
例如,在 UTF-8 编码中,一个字符可能由一到四个字节表示。如果一个整数对应于一个超出常用 Unicode 字符范围的值(比如大于 0x10FFFF),或者它是一个用于表示字符属性的码点(比如一些控制字符),那么它可能无法被正确地转换为一个可打印的字符。
在 Go 语言中,如果你使用 string() 函数将一个整数值转换为字符串,并且该值超出了有效的 Unicode 代码点范围,你可能会得到一个替代字符。为了避免这个问题,你应该确保转换的整数值在有效的 Unicode 范围内(通常是 0 到 0x10FFFF)。
*/
fmt.Println(string(65))
fmt.Println(string(37))
fmt.Println(string(-1))
/**
正确的将整数转为字符串应该使用Go中提供的转换工具
如:strconv.Itoa()\strconv.FormatInt()
*/
num := -1
fmt.Println(strconv.Itoa(num))
fmt.Println(strconv.FormatInt(int64(num), 10))
}
结果展示:
Unicode代码点
在将一个整数值转换为字符串时,这个整数应该是一个有效的 Unicode 代码点的值。Unicode 是一个字符编码标准,它为世界上大多数的文字系统提供了一个唯一的码位。每个 Unicode 代码点都对应一个字符。 在计算机中,字符通常以字节的形式存储,而每个字节可以表示 0 到 255 之间的整数值。当一个整数超出了这个范围,或者它不是一个有效的 Unicode 代码点时,尝试将它转换为字符串可能会导致无法正确显示该字符,从而出现替代字符,通常是 ""(一个黑色菱形,中间有一个问号)。 例如,在 UTF-8 编码中,一个字符可能由一到四个字节表示。如果一个整数对应于一个超出常用 Unicode 字符范围的值(比如大于 0x10FFFF),或者它是一个用于表示字符属性的码点(比如一些控制字符),那么它可能无法被正确地转换为一个可打印的字符。 在 Go 语言中,如果你使用 string() 函数将一个整数值转换为字符串,并且该值超出了有效的 Unicode 代码点范围,你可能会得到一个替代字符。为了避免这个问题,你应该确保转换的整数值在有效的 Unicode 范围内(通常是 0 到 0x10FFFF)。
Unicode代码点可以借助ASCII码表理解,如A对应的整数类型值为65,这一部分可以借助上面代码理解。
类型别名、潜在类型
在Go语言中,可以使用关键字type声明自定义的各种类型,这些类型必须在Go语言基本类型和高级范畴之内。下面介绍类型别名以及潜在类型。
type Mystring = string
这条声明语句表示,MyString是string类型的别名类型。顾名思义,别名类型与其源类型的
区别恐怕只是在名称上,它们是完全相同的。
源类型与别名类型是一对概念,是两个对立的称呼。别名类型主要是为了代码重构而存在的。在Go语言中已经存在的有别名类型如:byte是unit8的类型别名,rune是int32的类型别名。
type Mystring string
上述Mystring和string是两个不同的类型,Mstring是一个新的类型,不同于其他任何类型。
这种方式也可以被叫做对类型的再定义。我们刚刚把string类型再定义成了另外一个类型Mystring。对于这里的类型再定义来说,string可以被称为Mystring的潜在类型。潜在类型的含义:
某个类型在本质上是哪个类型或者是哪个类型的集合。潜在类型相同的不同类型的值之间是可以进行类型转换的。因此,Mystring类型的值与string类型的值可以使用类型转换表达式进行互转。但对于集合类的类型[]Mystring与[]string来说这样做却是不合法的,因为[]Mystring与[]string的潜在类型不同,分别是Mystring和string。另外,即使两个类型的潜在类型相同,它们的值之间也不能进行判等或比较,它们的变量之间也不能赋值。
代码演示:
package main
import (
"fmt"
)
// 潜在类型
type MyString string
// 类型别名
type MyString2 = string
func main() {
var name MyString
name = "zhang san"
var copyName string
/**
潜在类型相同的不同类型的值之间是可以进行类型转换的。
即使两个类型的潜在类型相同,但这两个类型对应的变量之间也不能进行判等或者比较,也不能进行赋值,只能进行类型转换
copyName = name 不允许的操作
*/
copyName = string(name)
fmt.Println(name)
fmt.Println(copyName)
fmt.Println("=========================")
var name2 MyString2
name2 = "lisi"
var copyName2 string
copyName2 = name2
fmt.Println(name2)
fmt.Println(copyName2)
}
类型别名的意义
对于大型的代码库来说,能够重构其整体结构是非常重要的,包括修改某些 API 所属的包。大型重构应该支持一个过渡期:从旧位置和新位置获得的 API 都应该是可用的,而且可以混合使
用这些 API 的引用。Go 已经为常量、函数或变量的重构提供了可行的机制,但是并不支持类
型。类型别名提供了一种机制,它可以使得 oldpkg.OldType 和 newpkg.NewType 是相同的,并且引用旧名称的代码与引用新名称的代码可以互相操作。
考虑将一个类型从一个包移动到另一个包中的情况,比如从 oldpkg.OldType 到newpkg.NewType。可以在包 oldpkg 中指定一个新类型的别名 type OldType = newpkg.NewType,这样以前的代码都无需修改。