Go 学习笔记(32)— 类型系统(命名类型、未命名类型、底层类型、类型强制转换、类型别名和新声明类型)
1. 命名类型和未命名类型
1.1 命名类型
类型可以通过标识符来表示,这种类型称为命名类型( Named Type
)。 Go
语言的基本类型中有 20 个预声明简单类型都是命名类型, Go
语言还有一种命名类型一一用户自定义类型。
1.2 未命名类型
一个类型由预声明类型、关键字和操作符组合而成,这个类型称为未命名类型( Unamed Type
)。未命名类型又称为类型字面量( Type Literal
)。
Go
语言的基本类型中的复合类型:数组( array
)、切片( slice
)、字典( map
)、通道( channel
)、指针( pointer
) 、函数字面量( function
)、结构( struct
)和接口( interface
)都属于类型字面量,也都是未命名类型。
所以 *int
, []int
, [2]int
, map[k]v
都是未命名类型。
注意:前面所说的结构和接口是未命名类型,这里的结构和接口没有使用 type
格式定义,具体见下方示例说明。
package mainimport "fmt"// Person 使用 type 声明的是命名类型
type Person struct {name stringage int
}func main() {// 使用 struct 字面量声明的是未命名类型a := struct {name stringage int}{"Jim", 20}fmt.Printf("%T\n", a) // struct { name string; age int }fmt.Printf("%v\n", a) // {Jim 20}b := Person{"Tom", 22}fmt.Printf("%T\n", b) // main.Personfmt.Printf("%v\n", b) // {Tom 22}}
Go
语言的命名类型和未命名类型总结如下:
- 未命名类型和类型字面量是等价的,我们通常所说的
Go
语言基本类型中的复合类型就是类型字面量,所以未命名类型、类型字面量和Go
语言基本类型中的复合类型三者等价。 - 通常所说的
Go
语言基本类型中的简单类型就是这 20 个预声明类型,它们都属于命名类型。 - 预声明类型是命名类型的一种,另一类命名类型是自定义类型。
2. 底层类型
所有“类型”都有一个 underlying type
(底层类型)。底层类型的规则如下:
- 简单类型和复合类型的底层类型是它们自身。
- 自定义类型
type newtype oldtype
中newtype
的底层类型是逐层递归向下查找的,直到查到的oldtype
是简单类型或复合类型为止。
例如:
type T1 string
type T2 Tl
type T3 []string
type T4 T3
type T5 []T1
type T6 T5
T1
和 T2
的底层类型都是 string
, T3
和 T4
的底层类型都是 []string
, T5
和 T6
的底层类型都是 []T1
。特别注意这里的 T6
、 T5
与 T3
、 T4
的底层类型是不一样的, 一个是 []T1
,另一个是 []string
。
底层类型在类型赋值和类型强制转换时会使用,接下来就介绍这两个主题。
3. 类型相同和类型赋值
3.1 类型相同
Go
是强类型的语言,编译器在编译时会进行严格的类型校验。两个命名类型是否相同,参考如下:
- 两个命名类型相同的条件是两个类型声明的语句完全相同;
- 命名类型和未命名类型永远不相同;
- 两个未命名类型相同的条件是它们的类型声明字面量的结构相同,井且内部元素的类型相同;
- 通过类型别名语句声明的两个类型相同;
Go 1.9 引入了类型别名语法 type T1 = T2
, T1
的类型完全和 T2
一样。
3.2 类型赋值
不同类型的变量之间一般是不能直接相互赋值的,除非满足一定的条件。类型为 T1
的变量 a
可以赋值给类型为 T2
的变量 b
, 称为类型 T1
可以赋值给类型 T2
,伪代码表述如下:
// a 是类型为T1 的变量,或者a 本身就是一个字面常量或 nil
// 如果如下语句可以执行,则称之为类型 Tl 可以赋值给类型T2
var b T2 = a
a
可以赋值给变量 b
必须要满足如下条件中的一个:
T1
和T2
类型相同;T1
和T2
具有相同的底层类型,并且T1
和T2
里面至少有一个是未命名类型;T2
是接口类型,T1
是具体类型,T1
的方法集是T2
方法集的超集;T1
和T2
都是通道类型,它们拥有相同的元素类型,并且T1
和T2
中至少有一个是未命名类型;T1
是预声明标识符nil
,T2
是pointer
、funcition
、slice
、map
、channel
、interface
类型中的一个;a
是一个字面常量值,可以用来表示类型T
的值;
示例如下:
package mainimport "fmt"type Map map[string]stringfunc (m Map) Print() {for _, v := range m {fmt.Println("v is ", v)}
}type iMap Map// 只要底层类型是slice 、map 等支持range 的类型字面量,新类型仍然可以使用range 迭代
func (m iMap) Print() {for _, v := range m {fmt.Println("v is ", v)}
}type slice []intfunc (s slice) Print() {for _, v := range s {fmt.Println("v is ", v)}
}func main() {mp := make(map[string]string, 10)mp["hi"] = "hello"// mp 与ma 有相同的底层类型map[string]stirng ,并且mp 是未命名类型// 所以mp 可以直接赋值给mavar ma Map = mp/*im 与 ma 虽然有相同的底层类型map[string]stirng,但它们中没有一个是未命名类型不能赋值, 如下语句不能通过编译*/// var im iMap = mama.Print()// im.Print()var i interface {Print()} = mai.Print()s1 := []int{1, 2, 3}var s2 slices2 = s1s2.Print()
}
4. 类型强制转换
由于 Go
是强类型的语言, 如果不满足自动转换的条件,则必须进行强制类型转换。任意两个不相干的类型如果进行强制转换,则必须符合一定的规则。
强制类型的语法格式:
var a T = (T) (b)
使用括号将类型和要转换的变量或表达式的值括起来。
非常量类型的变量 x
可以强制转化并传递给类型 T
, 需要满足如下任一条件:
(1) x
可以直接赋值给 T
类型变量;
(2) x
的类型和 T
具有相同的底层类型;
继续上一节使用的示例:
/*im 与ma 虽然有相同的底层类型,但是二者中没有一个是字面量类型,不能直接赋值,可以强制进行类型转换*/var im iMap = (iMap)(ma)
(3) x
的类型和 T
都是未命名的指针类型,并且指针指向的类型具有相同的底层类型;
(4) x
的类型和 T
都是整型,或者都是浮点型;
(5) x
的类型和 T
都是复数类型;
(6) x
是整数值或 []byte
类型的值, T
是 string
类型;
(7) x
是一个字符串, T
是 []byte
或 []rune
;
字符串和字节切片之间的转换最常见,示例如下:
func main() {s := "hello,你好"var a []bytea = []byte(s)var b stringb = string(a)var c []runec = []rune(s)fmt.Printf("%T\n", a) // []uint8 byte 是 int8 的别名fmt.Printf("%T\n", b) // stringfmt.Printf("%T\n", c) // []int32 rune 是 int32 的别名
}
注意:
数值类型和
string
类型之间的相互转换可能造成值部分丢失; 其他的转换仅是类型的转换,不会造成值的改变。string
和数字之间的转换可使用标准库strconv
。Go
语言没有语言机制支持指针和interger
之间的直接转换,可以使用标准库中的unsafe
包进行处理。
5. 类型别名和新声明类型
类型别名与类型定义(新声明类型)不同之处在于,使用类型别名需要在别名和原类型之间加上赋值符号(=);使用类型别名定义的类型与原类型等价,而使用类型定义出来的类型是一种新的类型。
package mainimport ("fmt"
)type a = string
type b stringfunc SayA(str a) {fmt.Println(str)
}func SayB(str b) {fmt.Println(str)
}func main() {var str = "test"SayA(str)//错误参数传递,str是字符串类型,不能赋值给b类型变量SayB(str)
}
这段代码在编译时会出现如下错误:
cannot use str (type string) as type b in argument to SayB
从错误信息可知,str
为字符串类型,不能当做 b
类型参数传入 SayB
函数中。而 str
却可以当做 a
类型参数传入到 SayA
函数中。由此可见,使用类型别名定义的类型与原类型一致,而类型定义定义出来的类型,是一种新的类型。
5.1 类型别名
示例代码:
package mainimport "fmt"func main() {// 示例1。{type MyString = stringstr := "BCD"myStr1 := MyString(str)myStr2 := MyString("A" + str)fmt.Printf("%T(%q) == %T(%q): %v\n", str, str, myStr1, myStr1, str == myStr1)fmt.Printf("%T(%q) > %T(%q): %v\n", str, str, myStr2, myStr2, str > myStr2)fmt.Printf("Type %T is the same as type %T.\n", myStr1, str)fmt.Println()strs := []string{"E", "F", "G"}myStrs := []MyString(strs)fmt.Printf("A value of type []MyString: %T(%q)\n", myStrs, myStrs)fmt.Printf("Type %T is the same as type %T.\n", myStrs, strs)fmt.Println()}
}
输出结果:
string("BCD") == string("BCD"): true
string("BCD") > string("ABCD"): true
Type string is the same as type string.A value of type []MyString: []string(["E" "F" "G"])
Type []string is the same as type []string.
5.2 新声明类型
示例代码:
package mainimport "fmt"func main() {{type MyString stringstr := "BCD"myStr1 := MyString(str)myStr2 := MyString("A" + str)_ = myStr2// 这里的判等不合法,会引发编译错误。// invalid operation: str == myStr1 (mismatched types string and MyString)// fmt.Printf("%T(%q) == %T(%q): %v\n", str, str, myStr1, myStr1, str == myStr1)// 这里的比较不合法,会引发编译错误。// fmt.Printf("%T(%q) > %T(%q): %v\n", str, str, myStr2, myStr2, str > myStr2)fmt.Printf("Type %T is different from type %T.\n", myStr1, str)strs := []string{"E", "F", "G"}var myStrs []MyString// 这里的类型转换不合法,会引发编译错误。// cannot convert strs (type []string) to type []MyString// myStrs = []MyString(strs)//fmt.Printf("A value of type []MyString: %T(%q)\n", myStrs, myStrs)fmt.Printf("Type %T is different from type %T.\n", myStrs, strs)fmt.Println()}}
5.3 类型别名和新声明类型相互赋值
package mainfunc main() {{type MyString1 = stringtype MyString2 stringstr := "BCD"myStr1 := MyString1(str)myStr2 := MyString2(str)myStr1 = MyString1(myStr2)myStr2 = MyString2(myStr1)myStr1 = str// 这里的赋值不合法,会引发编译错误。// cannot use str (type string) as type MyString2 in assignment// myStr2 = str//myStr1 = myStr2 // 这里的赋值不合法,会引发编译错误。//myStr2 = myStr1 // 这里的赋值不合法,会引发编译错误。}
}
5.4 给类型别名新增方法,会添加到原类型方法集中
给类型别名新增方法后,原类型也能使用这个方法。下边请看一段示例代码:
package mainimport ("fmt"
)// 根据string类型,定义类型S
type S string
func (r *S) Hi() {fmt.Println("S hi")
}// 定义S的类型别名为T
type T = S
func (r *T) Hello() {fmt.Println("T hello")
}// 函数参数接收S类型的指针变量
func exec(obj *S) {obj.Hello()obj.Hi()
}func main() {t := new(T)s := new(S)exec(s)// 将T类型指针变量传递给S类型指针变量exec(t)
}
输出信息是:
T hello
S hi
T hello
S hi
上边的示例中,S 是原类型,T 是 S 类型别名。在给 T 增加了 Hello 方法后,S 类型的变量也可以使用 Hello 方法。说明给类型别名新增方法后,原类型也能使用这个方法。从示例中可知,变量 t 可以赋值给 S 类型变量 s,所以类型别名是给原类型取了一个小名,本质上没有发生任何变化。
类型别名,只能对同一个包中的自定义类型产生作用。举个例子,Golang SDK 中有很多个包,是不是我们可以使用类型别名,给 SDK 包中的结构体类型新增方法呢?答案是:不行。请牢记一点:类型别名,只能对包内的类型产生作用,对包外的类型采用类型别名,在编译时将会提示如下信息:
cannot define new methods on non-local type string
参考书籍:
- Go 语言核心编程
- Go 语言圣经
- Go语言快速入门
Go 学习笔记(32)— 类型系统(命名类型、未命名类型、底层类型、类型强制转换、类型别名和新声明类型)相关推荐
- C++ 学习笔记(19)new/delete表达式、定位new、typeid、dynamic_cast、type_info、枚举类型、成员函数指针、union、位域、volatile限定符、链接指示
C++ 学习笔记(19)new/delete表达式.定位new.typeid.dynamic_cast.type_info.枚举类型.成员函数指针.union.位域.volatile限定符.链接指示 ...
- 【学习笔记5】管道通信:命名管道
目录 一.前言 二.基本概念 三.命名管道的创建和使用 3.1 函数原型 3.1.1 CreateNamedPipe 3.1.2 ConnectNamedPipe 3.1.3 WaitNamedPip ...
- 影像组学视频学习笔记(32)-使用SimpleITK进行N4偏置场校正、Li‘s have a solution and plan.
作者:北欧森林 链接:https://www.jianshu.com/p/ae0f502dc146 来源:简书,已获授权转载 RadiomicsWorld.com "影像组学世界" ...
- pathon学习笔记一(变量的名命名,基本的数据类型)
python的命名规则: 命名规则: 项目名前面以数字编号,随着知识点的增加,编号增加 01_python基础,02_python分支 项目下的文件名都以ygb_xx 知识点 方式命名 2.注意: 命 ...
- java程序设计_Java程序设计:学习笔记(4-5)(未完工)
声明: 本文内容基于"吉首大学软件学院-Java程序设计(Java面向对象程序设计)"网课与个人实践经验修改编写而成.本文属于Arcadia项目组成部分.若有错误或不足之处存在请联 ...
- SAS学习笔记1——基础知识(库、PDV、变量选择、观测值排序、创建新变量
SAS学习笔记1--基础知识 1.逻辑库.临时库.永久库 2.数据步 2.1数据步语法 2.2 数据步的编译和执行过程 2.3变量的选择 2.3.1 keep和drop语句 2.4变量的重命名rena ...
- STM32学习笔记(八)丨ADC模数转换器(ADC单、双通道转换)
本篇文章包含的内容 一.ADC 模数转换器 1.1 ADC简介 1.2 逐次逼近型ADC工作原理 1.3 STM32中的ADC基本结构 1.4 STM32中ADC的输入通道 1.5 STM32中的AD ...
- Java学习笔记(基本数据类型和变量命名规则)
java基本数据类型 变量 1.变量就是可变的量. 2.常量就是不可变的量. 3.字面量:Java的变量和常量中存放的具体的数据成为字面量. 变量 命名规则: (1)首字母是英文字母.$或下划线,由字 ...
- 安卓学习笔记32:实现补间动画
文章目录 零.学习目标 一.安卓实现动画的三种方式 1.补间动画(tween animation) 2.帧式动画(frame animation) 3.属性动画(property animation) ...
最新文章
- 北大博士干了半年外卖骑手,写出 AI 伦理论文登上顶刊,“系统知道一切”
- 使用java调用fastDFS客户端进行静态资源文件上传
- Verilog设计分频器(面试必看)
- LeetCode 1095. 山脉数组中查找目标值(二分查找)
- NAACL 2019 | 怎样生成语言才能更自然,斯坦福提出超越Perplexity的评估新方法
- [JavaScript] 判断网页能不能被IFrame 嵌入
- PyTorch 1.0 中文官方教程:torch.nn 到底是什么?
- 有什么工具或应用可以帮助找到适合搭配一种颜色的另一种颜色?
- 百度开源的71款项目
- vim 自动格式化代码快捷键
- Android Studio 开发APP流程
- win10升级助手_现在知道还不晚,玩转Win10系统小技巧大合集
- 2020-10-31
- 基于lstm+crf实现电子病历实体信息识别 完整的代码+数据集+说明 毕设
- java 仿易企秀_鲁班H5(开源可视化搭建系统, 可以理解为开源版本易企秀)核心实现原理解析...
- 新手如何在Git Hub上学习开源项目+社交
- unity游戏开发知识检测
- 解决:RuntimeError: Expected object of scalar type Int but got scalar type Double
- 光纤收发器的原理及应用_光纤收发器的作用原理
- MyBatis批量插入几千条数据,慎用Foreach
热门文章
- 2022-2028年中国专用化学品行业投资分析及前景预测报告
- 2022-2028年中国氟橡胶密封件行业市场研究及前瞻分析报告
- RabbitMQ 入门系列(2)— 生产者、消费者、信道、代理、队列、交换器、路由键、绑定、交换器
- 前端Vue学习之路(五)插件的使用
- centos7samba服务的搭建
- dropout,batch norm 区别 顺序
- 安装win下的Anaconda ----针对python3.6.4版本
- pytorch 调用forward 的具体流程
- 基于Android和SpringBoot的购物App
- LeetCode简单题之有序数组的平方