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 语言的命名类型和未命名类型总结如下:

  1. 未命名类型和类型字面量是等价的,我们通常所说的 Go 语言基本类型中的复合类型就是类型字面量,所以未命名类型、类型字面量和 Go 语言基本类型中的复合类型三者等价。
  2. 通常所说的 Go 语言基本类型中的简单类型就是这 20 个预声明类型,它们都属于命名类型。
  3. 预声明类型是命名类型的一种,另一类命名类型是自定义类型。

2. 底层类型

所有“类型”都有一个 underlying type (底层类型)。底层类型的规则如下:

  1. 简单类型和复合类型的底层类型是它们自身。
  2. 自定义类型 type newtype oldtypenewtype 的底层类型是逐层递归向下查找的,直到查到的 oldtype 是简单类型或复合类型为止。

例如:

type T1 string
type T2 Tl
type T3 []string
type T4 T3
type T5 []T1
type T6 T5

T1T2 的底层类型都是 stringT3T4 的底层类型都是 []stringT5T6 的底层类型都是 []T1 。特别注意这里的 T6T5T3T4 的底层类型是不一样的, 一个是 []T1 ,另一个是 []string

底层类型在类型赋值和类型强制转换时会使用,接下来就介绍这两个主题。

3. 类型相同和类型赋值

3.1 类型相同

Go 是强类型的语言,编译器在编译时会进行严格的类型校验。两个命名类型是否相同,参考如下:

  1. 两个命名类型相同的条件是两个类型声明的语句完全相同;
  2. 命名类型和未命名类型永远不相同;
  3. 两个未命名类型相同的条件是它们的类型声明字面量的结构相同,井且内部元素的类型相同;
  4. 通过类型别名语句声明的两个类型相同;

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 必须要满足如下条件中的一个:

  1. T1T2 类型相同;
  2. T1T2 具有相同的底层类型,并且 T1T2 里面至少有一个是未命名类型;
  3. T2 是接口类型, T1 是具体类型, T1 的方法集是 T2 方法集的超集;
  4. T1T2 都是通道类型,它们拥有相同的元素类型,并且 T1T2 中至少有一个是未命名类型;
  5. T1是预声明标识符 nilT2pointerfuncitionslicemapchannelinterface 类型中的一个;
  6. 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 类型的值, Tstring 类型;
(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 的别名
}

注意:

  1. 数值类型和 string 类型之间的相互转换可能造成值部分丢失; 其他的转换仅是类型的转换,不会造成值的改变。

  2. string 和数字之间的转换可使用标准库 strconv

  3. 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

参考书籍:

  1. Go 语言核心编程
  2. Go 语言圣经
  3. Go语言快速入门

Go 学习笔记(32)— 类型系统(命名类型、未命名类型、底层类型、类型强制转换、类型别名和新声明类型)相关推荐

  1. C++ 学习笔记(19)new/delete表达式、定位new、typeid、dynamic_cast、type_info、枚举类型、成员函数指针、union、位域、volatile限定符、链接指示

    C++ 学习笔记(19)new/delete表达式.定位new.typeid.dynamic_cast.type_info.枚举类型.成员函数指针.union.位域.volatile限定符.链接指示 ...

  2. 【学习笔记5】管道通信:命名管道

    目录 一.前言 二.基本概念 三.命名管道的创建和使用 3.1 函数原型 3.1.1 CreateNamedPipe 3.1.2 ConnectNamedPipe 3.1.3 WaitNamedPip ...

  3. 影像组学视频学习笔记(32)-使用SimpleITK进行N4偏置场校正、Li‘s have a solution and plan.

    作者:北欧森林 链接:https://www.jianshu.com/p/ae0f502dc146 来源:简书,已获授权转载 RadiomicsWorld.com "影像组学世界" ...

  4. pathon学习笔记一(变量的名命名,基本的数据类型)

    python的命名规则: 命名规则: 项目名前面以数字编号,随着知识点的增加,编号增加 01_python基础,02_python分支 项目下的文件名都以ygb_xx 知识点 方式命名 2.注意: 命 ...

  5. java程序设计_Java程序设计:学习笔记(4-5)(未完工)

    声明: 本文内容基于"吉首大学软件学院-Java程序设计(Java面向对象程序设计)"网课与个人实践经验修改编写而成.本文属于Arcadia项目组成部分.若有错误或不足之处存在请联 ...

  6. SAS学习笔记1——基础知识(库、PDV、变量选择、观测值排序、创建新变量

    SAS学习笔记1--基础知识 1.逻辑库.临时库.永久库 2.数据步 2.1数据步语法 2.2 数据步的编译和执行过程 2.3变量的选择 2.3.1 keep和drop语句 2.4变量的重命名rena ...

  7. STM32学习笔记(八)丨ADC模数转换器(ADC单、双通道转换)

    本篇文章包含的内容 一.ADC 模数转换器 1.1 ADC简介 1.2 逐次逼近型ADC工作原理 1.3 STM32中的ADC基本结构 1.4 STM32中ADC的输入通道 1.5 STM32中的AD ...

  8. Java学习笔记(基本数据类型和变量命名规则)

    java基本数据类型 变量 1.变量就是可变的量. 2.常量就是不可变的量. 3.字面量:Java的变量和常量中存放的具体的数据成为字面量. 变量 命名规则: (1)首字母是英文字母.$或下划线,由字 ...

  9. 安卓学习笔记32:实现补间动画

    文章目录 零.学习目标 一.安卓实现动画的三种方式 1.补间动画(tween animation) 2.帧式动画(frame animation) 3.属性动画(property animation) ...

最新文章

  1. 北大博士干了半年外卖骑手,写出 AI 伦理论文登上顶刊,“系统知道一切”
  2. 使用java调用fastDFS客户端进行静态资源文件上传
  3. Verilog设计分频器(面试必看)
  4. LeetCode 1095. 山脉数组中查找目标值(二分查找)
  5. NAACL 2019 | 怎样生成语言才能更自然,斯坦福提出超越Perplexity的评估新方法
  6. [JavaScript] 判断网页能不能被IFrame 嵌入
  7. PyTorch 1.0 中文官方教程:torch.nn 到底是什么?
  8. 有什么工具或应用可以帮助找到适合搭配一种颜色的另一种颜色?
  9. 百度开源的71款项目
  10. vim 自动格式化代码快捷键
  11. Android Studio 开发APP流程
  12. win10升级助手_现在知道还不晚,玩转Win10系统小技巧大合集
  13. 2020-10-31
  14. 基于lstm+crf实现电子病历实体信息识别 完整的代码+数据集+说明 毕设
  15. java 仿易企秀_鲁班H5(开源可视化搭建系统, 可以理解为开源版本易企秀)核心实现原理解析...
  16. 新手如何在Git Hub上学习开源项目+社交
  17. unity游戏开发知识检测
  18. 解决:RuntimeError: Expected object of scalar type Int but got scalar type Double
  19. 光纤收发器的原理及应用_光纤收发器的作用原理
  20. MyBatis批量插入几千条数据,慎用Foreach

热门文章

  1. 2022-2028年中国专用化学品行业投资分析及前景预测报告
  2. 2022-2028年中国氟橡胶密封件行业市场研究及前瞻分析报告
  3. RabbitMQ 入门系列(2)— 生产者、消费者、信道、代理、队列、交换器、路由键、绑定、交换器
  4. 前端Vue学习之路(五)插件的使用
  5. centos7samba服务的搭建
  6. dropout,batch norm 区别 顺序
  7. 安装win下的Anaconda ----针对python3.6.4版本
  8. pytorch 调用forward 的具体流程
  9. 基于Android和SpringBoot的购物App
  10. LeetCode简单题之有序数组的平方