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{}中的{}的解释参考下面图片: 

 类型转换注意事项

对于类型转换而言常见的需要遵循的转换规则如下:

  1. 对于整数类型值、整数常量之间的类型转换,原则上只要源值在目标类型的可表示范围内
    就是合法的。
    1. uint8(255)可以把无类型的常量255转换为uint8类型的值,是因为255在 [0,255] 的范围内。但需要特别注意的是,源整数类型的可表示范围较大,而目标类型的可表示范围较小的情况,比如把值的类型从int16转换为int8,此时会涉及到类型值的截断(大范围变为小范围可能涉及到值的缩小)。
  2. 虽然直接把一个整数值转换为一个string类型的值是可行的,但值得关注的是,被转换
    的整数值应该可以代表一个有效的 Unicode 代码点,否则转换的结果将会是"�"(仅由高亮的问号组成的字符串值)。具体关于Unicode代码点的解释可以参考下面阐述。
  3. 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,这样以前的代码都无需修改。

上一篇:Windows系统使用Docker快速部署Focalboard结合内网穿透远程管理项目


下一篇:仓库场景物品检测分类系统源码分享-[一条龙教学YOLOV8标注好的数据集一键训练_70+全套改进创新点发刊_Web前端展示]