#1 入门必备
##1.1 Go语言简介
###1.1.1 Go语言的特点
- 静态类型(需要指明类型或至少能推导出来)、编译型的开源语言
- 脚本化的语法,支持多种编程范式:函数式编程、面向对象
- 原生、给力的并发编程支持:原生支持并发和通过函数库支持并发是有明显区别的
##1.2 优势与劣势
###1.2.1 Go语言的优势
- 脚本化的语法
- 静态类型+编译型:程序运行速度有保证
- 原生支持并发编程:有利于服务端软件开发优势
- 降低开发、维护成本
- 程序可以更好的运行
###1.2.2 Go语言的劣势
- 语法糖没有python和ruby多
- 目前的程序运行速度不如c
- 第三方函数库暂时不像绝对主流的编程语言那么多
##1.3 安装
###1.3.1 下载
https://storage.googleapis.com/golang/go1.4.2
http://golang.org/dl
###1.3.2 安装
1 | $ tar -zxvf go1.4.2.linux-amd64.tar.gz -C /usr/local |
##1.4 linux下的设置
###1.4.1 需要设置什么
- 4个环境变量:GOROOT、GOPATH、GOBIN、PATH
1 | #或/etc/profile |
#2 基本规则
##2.1 工作区和GOPATH
###2.1.1 工作区
含义:放置Go源码文件的目录
注意:一般情况下,Go源码文件都需要存放到工作区中,但是对于命令文件来说,这不是必须的。
平台相关目录:两个隐藏的Go语言环境变量(GOOS
和GOARCH
)
bin目录注意事项:
- 当环境变量呢GOBIN已有效设置时,该目录会变的无意义
- 当
GOPATH
的值中包含多个工作区的路径时,必须设置GOBIN
,否则无法成功安装Go程序的可执行文件
1 | ├── bin//用于存放当前工作区中的Go程序的可执行文件 |
##2.2 源码文件的分类和含义
##2.2.1 Go源码文件
特点:
- 名称以
.go
为后缀,内容以Go
语言代码组织的文件 - 多个Go源码文件是需要用代码包组织起来的
分类:
- 命令源码文件
- 库源码文件
- 测试源码文件(辅助源码文件)
####1. 命令源码文件
特点: 声明自己属于main代码包、包含无参数声明和结果声明的main
函数
安装行为: 被安装后,相应的可执行文件会被存放到GOBIN
指向的目录或<当前工作目录/bin>
注意事项:
- 命令源码文件是Go程序的入口,但不建议把程序都写在一个文件中
- 同一个代码包中强烈不建议直接包含多个命令源码文件
####2.库源码文件
特点:不具备命令源码文件的那两个特征(普通源码文件)
安装行为:被安装后,相应的归档文件回被存放到<当前工作区目录>/pkg/<平台相关目录>
下
####3.测试源码文件
特点:
- 不具备命令源码文件的那两个特征的源码文件,名称以
_test.go
为后缀 - 其中至少有一个函数的名称以
Test
或Benchmark
为前缀,接受一个类型为*testing.T
或*testing.B
的参数
1 | //功能测试函数 |
##2.3 代码包相关知识
作用:
- 编译和归档Go程序的最基本单位
- 代码划分、集结和依赖的有效组织形式,也是权限控制的辅助手段
说明:一个代码包实际上就是一个由导入路径代表的目录
2.3.1 导入路径:以下目录下的某段子路径
<工作区目录>/src
<工作区目录>/pkg/<平台相关目录>
例如:代码包eli.cn
,/home/eli/golib/
是一个工作区目录
1 | /home/eli/golib/src/eli.cn |
###2.3.2 代码包的声明
#####语法
- 每个源码文件必须声明其所属的代码包
- 同一个代码包中的所有源文件声明的代码包应该是相同的
注意:代码包声明是代码包导入路径的最右子路径
1 | //对应导入路径:eli.cn/phgtool |
###2.3.3 代码包的导入
语法:代码包导入语句中使用的包名称应该与其导入路径一致
1 | import( |
####2.3.3.1 导入方式
- 带别名的导入:使用别名来调用导入的包中的成员
1 | import str "strings" |
- 本地化导入:调用导入的包的成员时可以省略包名
1 | import . "strings" |
- 仅仅初始化:仅执行代码包中的初始化函数
1 | import _ "strings" |
代码包初始化函数:无参数声明和结果声明的init
函数
特点:init
函数可以被声明在任何文件中,且可以有多个
####2.3.3.2 init
函数的执行
- 同一代码包中的执行情况
注意:不保证同一包中多个init
函数执行顺序
1 | st=>start: 导入代码包 |
- 不同代码包之间
init
的执行
递归导入的情况:先导入后执行(A导入B导入C)C->B->A
一个文件导入多个包:导入多个包时,不保证多个包之间的执行顺序
#3 命令基础
##3.1 go run命令简介
###3.1.1 go run
用途:线编译文件再运行
使用:只能接受一个命令源码文件以及若干个库源码文件作为参数
###3.1.2 go build、go install
###3.1.3 go get
##3.2 示例来源说明
《Go并发编程实战》示例项目:https://github.com/hyper-carrot/goc2p.git
- ds命令:显示指定目录的目录结构(goc2p/src/helper/ds/showds.go)
- pds命令:显示指定代码包的依赖关系(goc2p/src/helper/pds/showpds.go)
##3.3 go run 命令案例演示
1 | $ go run showds.go -p ~/Music |
##3.4-3.5 go run常用标记的使用
参数 | 含义 |
---|---|
-a | 强制编译相关代码,不论它们的编译结果是不是最新的,即已经编译过的部分也会被重新编译 |
-n | 打印编译过程中所需运行的命令,但不真正执行它们 |
-p n | 并行编译,其中n为并行的数量 |
-v | 列出被编译的代码包的名称(1.3包含标准库,1.4不包含) |
-work | 显示编译时创建的临时工作目录的路径,并且不删除它 |
-x | 打印编译过程中所需进行的命令,并执行它们 |
##3.6-3.7 go build命令简介
用途:用于编译源码文件或代码包
执行特点:
- 编译非命令文件(库文件),不会产生任何结果文件
- 编译命令文件,会在该命令的执行目录中生成一个可执行文件
参数:
参数 | 用途 |
---|---|
不加任何参数 | 它会试图把当前目录作为代码包并编译 |
以代码包的导入路径作为参数时 | 该代码包及其依赖会被编译 |
-a | 所有涉及到的代码包都会被重新编译(没有该参数只会编译归档文件而不是最新的代码包) |
若干源码文件 | 只有这些文件会被编译 |
##3.8-3.9 go install 命令简介
用途:用于编译代码包或源码文件
安装:
安装对象 | 生成 |
---|---|
代码包 | 会在当前工作区的pkg/<平台相关目录> 下生成归档文件 |
命令源码文件 | 会在当前工作区的bin 目录或$GOBIN 目录下生成可执行文件 |
参数:
参数 | 用途 |
---|---|
不追加参数 | 会试图把当前目录作为代码包并安装 |
以代码包的导入路径作为参数 | 该代码包及其依赖会被安装 |
以命令源码文件及其相关库源码文件作为参数 | 只有这些文件会被编译并安装 |
##3.10-3.11 go get简介
用途:从远程代码仓库(如github)上下载并安装代码包
受支持的代码版本控制系统:Git,Mercurial(hg),SVN,Bazaar
放置路径:$GOPATH
中包含的第一个工作区的src
目录中
1 | //下载 |
##3.12-3.13 go get常用标记
参数 | 意义 |
---|---|
-d | 只执行下载动作,而后进行编译和安装 |
-fix | 在下载代码包后先执行修正动作(修正其中不受支持的旧版本go的代码),而后再进行编译和安装 |
-u | 利用网络来更新已有的代码包及其依赖 |
##3.14 本章总结
#4 基本数据类型
##4.1 程序实体和关键字
###4.1.1 程序实体
包含:变量、常量、函数、结构体和接口
###4.1.2 标识符
书写规范:可以是任何Unicode编码可以表示的字母字符、数字以及下划线“_”。不过,首字母不能是数字或下划线。
注意:名字首字母为大写的程序实体可以被任何代码包中的代码访问到。而名字首字母为小写的程序实体则只能被同一个代码包中的代码所访问。
###4.1.3 关键字
用途 | 关键字 |
---|---|
程序声明 | import , package |
程序实体声明和定义 | chan ,const ,func ,interface ,map ,struct ,type ,var |
程序流程控制 | go ,select ,break ,case ,continue ,default , defer , else , fallthroughfor ,goto ,if ,range ,return ,switch |
1 | package main // 代码包声明语句。 |
##4.2 变量和常量
比较 | 变量 | 常量 |
---|---|---|
关键字 | var |
const |
赋值 | 绝大多数的数据类型的值,包括函数 | 字面量 |
声明 | 可以不赋值 | 必须赋值 |
1 | // 注释:普通赋值,由关键字var、变量名称、变量类型、特殊标记=,以及相应的值组成。 |
1 | package main |
##4.3 整数类型的命名和宽度
###4.3.1 种类:10种
数据类型 | 有符号 | 类型宽度(bit) |
---|---|---|
int | 是 | 平台相关 |
int8 | 是 | 8 |
int16 | 是 | 16 |
int32 | 是 | 32 |
int64 | 是 | 64 |
uint | 否 | 平台相关 |
uint8 | 否 | 8 |
uint16 | 否 | 16 |
uint32 | 否 | 32 |
uint64 | 否 | 64 |
###4.3.2 有无符号整数的区别
有符号整数:使用最高位的比特(bit)表示整数的正负
无符号整数:使用所有的比特位来表示数值
1 | package main // 代码包声明语句 |
##4.4 整数类型值的表示法
###4.4.1 大小
类型宽度(比特) | 数值范围(有符号整数) | 数值范围(无符号整数) |
---|---|---|
8 | -128~127 | 0~255 |
16 | -32768~32767 | 0~65535 |
32 | 约-21.47~21.47亿 | 约0~42.94亿 |
64 | 约-922亿亿~922亿亿 | 约0~1844亿 |
###4.4.2 进制
1 | var num1 int = 12//十进制 |
1 | package main |
##4.5 浮点数类型
注意:在Go语言里,浮点数的相关部分只能由10进制表示法表示,而不能由8进制表示法或16进制表示法表示。
###4.5.1 分类
浮点类型 | 宽度 |
---|---|
float32 | 4 |
float64 | 8 |
###4.5.2 表示方式
- 普通方式:整数部分、小数点“.”和小数部分组成
- 指数方式:在其中加入指数部分。指数部分由“E”或“e”以及一个带正负号的10进制数组成
###4.5.3 简化形式
说明:比如,37.0可以被简化为37。又比如,0.037可以被简化为.037。
1 | package main |
##4.6 复数类型
表示方式:复数类型的值一般由浮点数表示的实数部分、加号“+”、浮点数表示的虚数部分,以及小写字母“i”组成。
###4.6.1 分类
复数类型 | 宽度(bit) | 说明 |
---|---|---|
comples64 | 8 | 由两个float32类型的值分别表示实数部分和虚数部分 |
complex128 | 16 | 由两个float64类型的值分别表示实数部分和虚数部分 |
###4.6.2 实例
1 | package main |
##4.7 byte和rune
###4.7.1 别名类型
别名类型 | 对应类型 | 用途 |
---|---|---|
byte | unit8 | 用来8bit的整数或字符 |
rune | int32 | 用来表示Unicode字符 |
###4.7.2 rune支持的表示方式
注意:rune
类型值可以用多种方式表示,其中第一种最直观通用
表示形式 | 案例 | 支持的字符范围 |
---|---|---|
直接使用Unicode字符 | '郝' |
Unicode支持的字符 |
“\x”加两个十六进制数 | '\x41' (表示”A”) |
ASCLL编码的字符 |
“\”加三位八进制数 | '\101'(表示"A") |
编码值在[0,255)内的字符 |
“\u”加四位十六进制数 | \u90DD (表示”郝”) |
编码值在[0,65535)内的字符 |
“\U”加四位十六进制数 | \U000090DD (表示”郝”) |
所有Unicode字符 |
###4.7.3 rune支持的转义字符
转义符 | Unicode代码点 | 说明 |
---|---|---|
\a | U+0007 | 告警铃声或蜂鸣声 |
\b | U+008 | 退格符 |
\f | U+000C | 换页符 |
\n | U+000A | 换行符 |
\r | U+000D | 回车符 |
\t | U+0009 | 水平制表符 |
\v | U+000b | 垂直制表符 |
\ | U+005c | 反斜杠 |
\’ | U+0027 | 单引号。仅在rune值中有效 |
“ | U+0022 | 双引号。仅在string值中有效 |
1 | package main |
##4.8 字符串类型
特点:不可变。一旦创建了一个字符串类型的值就不可能再对它本身做任何修改。
###4.8.1 底层实现
- 一个字符串值却是由若干个字节来表现和存储的
- 一个字符串(也可以说字符序列)会被Go语言用Unicode编码规范中的UTF-8编码格式编码为字节数组
###4.8.2 两种表示方法
表示方法 | 语法 | 行为 |
---|---|---|
原生表示法 | 反引号`把字符序列包裹起来 | 前者表示的值是所见即所得的(除了回车符) |
解释型表示法 | 则需用双引号“””包裹字符序列 | 后者所表示的值中的转义符会起作用并在程序编译期间被转义 |
1 | package main |
#5 高级数据类型
##5.1 数组类型
数组: 一个数组(Array)就是一个可以容纳若干类型相同的元素的容器。这个容器的大小(即数组的长度)是固定的,且是体现在数组的类型字面量之中的。
###5.1.1 类型声明
关键字:type
语法:type [类型别名] [类型字面量]
用途:为某种类型声明一个别名类型,使我们可以把自定义的别名类型当作该类型来使用。
1 | type MyNumbers [3]int |
###5.1.2 字面量
类型字面量:就是用于表示某个类型的字面表示(或称标记方法),比如[3]int
值字面量:用于表示某个类型的值的字面表示可被称为值字面量,或简称为字面量。比如之前提到过的3.7E-2
就可被称为浮点数字面量。
###5.1.3 数组
1 | package main |
####5.1.3.1 声明
1 | //这是一条变量声明语句。它在声明变量的同时为该变量赋值。 |
或
1 | //在其中的类型字面量中省略代表其长度的数字 |
####5.1.3.2 索引
方式:索引表达式由字符串、数组、切片或字典类型的值(或者代表此类值的变量或常量)和由方括号包裹的索引值组成。
注意:
- 索引值既不能小于0也不能大于或等于数组值的长度
- 索引值的最小有效值总是0,而不是1
1 | numbers[0] // 会得到第一个元素 |
####5.1.3.2 修改
1 | numbers[1] = 4 |
####5.1.3.3 len
用途:len是Go语言的内建函数的名称。该函数用于获取字符串、数组、切片、字典或通道类型的值的长度。我们可以在Go语言源码文件中直接使用它。
1 | var length = len(numbers) |
####5.1.3.4 默认值
规则:如果我们只声明一个数组类型的变量而不为它赋值,那么该变量的值将会是指定长度的、其中各元素均为元素类型的零值(或称默认值)的数组值。
1 | var numbers2 [5]int//[5]int{0, 0, 0, 0, 0} |
####5.1.3.5 切片表达式
用途:可以在一个数组或切片上进行“切片”操作,获得一个新的切片。
语法:切片表达式一般由字符串、数组或切片的值以及由方括号包裹且由英文冒号“:”分隔的两个正整数组成。这两个正整数分别表示元素下界索引和元素上界索引。
1 | var numbers3 = [5]int{1, 2, 3, 4, 5} |
##5.2 切片类型
特点:
- 被“切下”的部分不包含元素上界索引指向的元素。
- 切片表达式的求值结果会是切片类型的,且其元素类型与被“切片”的值的元素类型一致。
- 切片类型属于引用类型,默认值
nil
###5.2.1 数组与切片
相同点:切片(Slice)与数组一样,也是可以容纳若干类型相同的元素的容器
不同点:
- 无法通过切片类型来确定其值的长度。
- 不同长度的切片值是有可能属于同一个类型的;不同长度的数组值必定属于不同类型。
联系:每个切片值都会将数组作为其底层数据结构。我们也把这样的数组称为切片的底层数组。
###5.2.2 切片字面量
语法:[]{类型}
特点:
- 不包含代表其长度的信息,不同长度的切片值是有可能属于同一个类型的
1 | []int |
或
1 | []string |
###5.2.3 切片声明和创建
注意:切片是引用类型,底层是数组,对切片的成员的修改实际上是对底层数组的修改。
####5.2.3.1 声明切片类型
1 | type MySlice []int |
####5.2.3.2 创建切片类型
注意:作为切片表达式求值结果的切片值的长度总是为元素上界索引与元素下界索引的差值。
方式一:创建
1 | var slice3 []int//nil |
方式二:切片操作
1 | var numbers3 = [5]int{1, 2, 3, 4, 5} |
####5.2.3.3 容量
注意:除了长度,切片值以及数组值还有另外一个属性——容量。数组值的容量总是等于其长度。而切片值的容量则往往与其长度不同。
- cap:获得切片的容量(即为它的第一个元素值在其底层数组中的索引值与该数组长度的差值的绝对值)
1 | package main |
##5.3 切片的更多操作方法
###5.3.1 限制切片容量
用途:限制我们对其底层数组中的更多元素的访问。
语法:在切片操作中加入第3个正整数作为容量
1 | var numbers3 = [5]int{1, 2, 3, 4, 5} |
###5.3.2 扩展切片
方法:append
函数
用途:会对切片值进行扩展并返回一个新的切片值
注意:一旦扩展操作超出了被操作的切片值的容量,那么该切片的底层数组就会被自动更换(不影响原本底层数组)
1 | //slice1 = []int{2,3,4} |
###5.3.3 复制切片
方法:copy
函数
用途:该函数接受两个类型相同的切片值作为参数,并会把第二个参数值中的元素复制到第一个参数值中的相应位置(索引值相同)上。
注意:
- 这种复制遵循最小复制原则,即:被复制的元素的个数总是等于长度较短的那个参数值的长度。
- 与append函数不同,copy函数会直接对其第一个参数值进行修改。
1 | var slice4 = []int{0, 0, 0, 0, 0, 0, 0} |
1 | package main |
##5.4 字典类型
字典类型:map[K]T
(“K”意为键的类型,而“T”则代表元素(或称值)的类型)
说明:Go语言的字典(Map)类型其实是哈希表(Hash Table)的一个实现。
用途:字典用于存储键-元素对(更通俗的说法是键-值对)的无序集合。
特点:
- 同一个字典中的每个键都是唯一的
- 字典的键类型必须是可比较的,否则会引起错误。也就是说,它不能是切片、字典或函数类型。
- 对于字典值来说,如果其中不存在索引表达式欲取出的键值对,那么就以它的值类型的空值(或称默认值)作为该索引表达式的求值结果。
- 字典类型为引用类型,零值为
nil
###5.4.1 使用
1 | //1:创建。首先,最左边是类型字面量,右边紧挨着由花括号包裹且有英文逗号分隔的键值对。每个键值对的键和值之间由英文冒号分隔。 |
###5.4.2 使用多返回值
说明:针对字典的索引表达式可以有两个求值结果
用途:第二个求值结果是bool类型的,它用于表明字典值中是否存在指定的键值对
1 | e, ok := mm[5]//变量ok必为false。因为mm中不存在以5为键的键值对。 |
##5.5 通道类型
说明:通道(Channel)是Go语言中一种非常独特的数据结构。它可用于在不同Goroutine之间传递类型化的数据,并且是并发安全的。
特点:
- 并发安全
- 无法用字面量来为通道类型的变量赋值(只能通过调用内建函数make来达到目的)
- 与切片和字典类型相同,通道类型属于引用类型。它的零值即为nil。
注意:
- 对通道值的重复关闭会引发运行时恐慌。这会使程序崩溃。
- 在通道值有效的前提下,针对它的发送操作会在通道值已满(其中缓存的数据的个数已等于它的长度)时被阻塞。
- 向一个已被关闭的通道值发送数据会引发运行时恐慌
- 针对有效通道值的接收操作会在它已空(其中没有缓存任何数据)时被阻塞
###5.5.1 make
说明:第一个参数是代表了将被初始化的值的类型的字面量(比如chan int),而第二个参数则是值的长度。
用途:除了通道类型,make函数也可以被用来初始化切片类型或字典类型的值。
特点:
- 通道值的长度应该被称为其缓存的尺寸。换句话说,它代表着通道值中可以暂存的数据的个数。
- 暂存在通道值中的数据是先进先出的,即:越早被放入(或称发送)到通道值的数据会越先被取出(或称接收)。
1 | //初始化一个长度为5且元素类型为int的通道值 |
###5.5.2 基本操作
1 | //1.创建通道 |
###5.5.3 多返回值
用途:消除与零值有关的歧义
解释:如果在接收操作进行之前或过程中通道值被关闭了,则接收操作会立即结束并返回一个该通道值的元素类型的零值。
1 | //这里的变量ok的值同样是bool类型的。它代表了通道值的状态,true代表通道值有效,而false则代表通道值已无效(或称已关闭) |
###5.5.4 案例
1 | package main |
##5.6 通道的更多种类
##5.6.1 按照缓存数据数量划分
非缓冲意味着:发送方在向通道值发送数据的时候会立即被阻塞,直到有某一个接收方已从该通道值中接收了这条数据。
分类 | 缓存数据数量 | 案例 | 特点 |
---|---|---|---|
缓冲通道 | >=1 | make(chan string,1) |
可以缓存N个数据 |
非缓冲通道 | 0 | make(chan int, 0) |
不会缓存任何数据 |
###5.6.2 按照数据在通道中的传输方向划分
单向通道的用途:主要作用是约束程序对通道值的使用方式。比如:
- 调用一个函数时给予它一个发送通道作为参数,以此来约束它只能向该通道发送数据
- 一个函数将一个接收通道作为结果返回,以此来约束调用该函数的代码只能从这个通道中接收数据
注意:
- 可以将双向通道类型复制给当想通道,反之则不行
- 在初始化一个通道值的时候不能指定它为单向。但是,在编写类型声明的时候,我们却是可以这样做的。
通道类型 | 声明相应类型 | 特点 |
---|---|---|
双向通道 | make(chan int, 3) |
默认情况下,通道都是双向的,即双向通道。 |
接收通道 | type Receiver <-chan int |
类型Receiver代表了一个只可从中接收数据的单向通道类型 |
发送通道 | type Sender chan<- int |
类型Sender代表了一个只可以向其中发送数据的单向通道类型 |
1 | //声明一个接受通道类型 |
###5.6.3 案例
1 |
|
#6 高级数据类型2
##6.1 函数
特点:
- 可以把函数作为值来传递和使用
- 它接受若干输入(参数),并经过一些步骤(语句)的执行之后再返回输出(结果)
- 可以返回多个结果
- 零值为
nil
###6.1.1 函数类型字面量
组成:由关键字func、由圆括号包裹参数声明列表、空格以及可以由圆括号包裹的结果声明列表组成
语法:func(input1 string ,input2 string) string
- 参数声明列表和结果声明列表中的单个参数声明之间是由英文逗号分隔的
- 每个参数声明和结果声明由名称、空格和类型组成
- 参数声明列表和结果声明列表中的参数名称是可以被统一省略的
- 结果声明列表在只有一个无名称的结果声明时还可以省略括号
1 | //函数类型声明 |
###6.1.2 函数(函数值)
语法:先写关键字func和函数名称,后跟参数声明列表和结果声明列表,最后是由花括号包裹的语句列表
技巧:如果结果声明是带名称的,那么它就相当于一个已被声明但未被显式赋值的变量。我们可以为它赋值且在return语句中省略掉需要返回的结果值。
函数实例:函数myFunc是函数类型MyFunc的一个实现。实际上,只要一个函数的参数声明列表和结果声明列表中的数据类型的顺序和名称与某一个函数类型完全一致,前者就是后者的一个实现。
1 | //1创建函数实例 |
###6.1.3 匿名函数
说明:匿名函数就是不带名称的函数值。
特点:
- 匿名函数直接由函数类型字面量和由花括号包裹的语句列表组成。
- 这里的函数类型字面量中的参数名称是不能被忽略的。
1 | var splice = func(part1 string, part2 string) string { |
###6.1.4 立即执行的匿名函数
说明:既然我们可以在代表函数的变量上实施调用表达式,那么在匿名函数上肯定也是可行的。因为它们的本质是相同的。
1 | var result = func(part1 string, part2 string) string { |
###6.1.5 实例
1 | package main |
##6.2 结构体和方法
用途:可以封装属性和操作。前者即是结构体类型中的字段,而后者则是结构体类型所拥有的方法。
结构体类型字面量:
- 结构体类型的字面量由关键字type、类型名称、关键字struct,以及由花括号包裹的若干字段声明组成
- 每个字段声明独占一行并由字段名称(可选)和字段类型组成
- 结构体类型属于值类型。它的零值并不是nil,而是其中字段的值均为相应类型的零值的值。
1 | type Person struct { |
结构体字面量:
- 由其类型的名称和由花括号包裹的若干键值对组成
- 这里的键是其类型中的某个字段的名称(注意,它不是字符串字面量),而对应的值则是欲赋给该字段的那个值
- 如果这里的键值对的顺序与其类型中的字段声明完全相同的话,可以统一省略掉所有字段的名称(只对它的部分字段赋值,甚至不对它的任何字段赋值的情况除外)
- 未被显式赋值的字段的值则为其类型的零值
1 | Person{Name: "Robert", Gender: "Male", Age: 33} |
###6.2.1 匿名结构体
定义:与代表函数值的字面量类似,我们在编写一个结构体值的字面量时不需要先拟好其类型。这样的结构体字面量被称为匿名结构体。
用途:在内部临时创建一个结构以封装数据,而不必正式为其声明相关规则
语法:在编写匿名结构体的时候需要先写明其类型特征(包含若干字段声明),再写出它的值初始化部分
注意:匿名结构体是不可能拥有方法的
1 | p := struct { |
###6.2.2 方法
定义:就是一种特殊的函数。它可以依附于某个自定义类型。
语法:方法的特殊在于它的声明包含了一个接收者声明。这里的接收者指代它所依附的那个类型。
接收者声明:
- 组成:其中的内容由两部分组成。第一部分是代表它依附的那个类型的值的标识符。第二部分是它依附的那个类型的名称
- 语法:后者表明了依附关系,而前者则使得在该方法中的代码可以使用到该类型的值(也称为当前值,是个指针类型)
- 接受者:代表当前值的那个标识符可被称为接收者标识符,或简称为接收者。
1 | //1. 创建结构体类型 |
###6.2.3 结构体和面向对象
继承:结构体类型(以及任何类型)之间都不可能存在继承关系
模仿继承:通过在结构体类型的声明中添加匿名字段(或称嵌入类型)
###6.2.4 实例
1 | package main |
##6.3 接口
定义:一个接口类型总是代表着某一种类型(即所有实现它的类型)的行为
语法:一个接口类型的声明通常会包含关键字type
、类型名称、关键字interface
以及由花括号包裹的若干方法声明
###6.3.1 接口类型中的方法
声明:只包括方法名称、参数声明列表和结果声明列表(参数的名称和结果的名称都可以被省略)
实现一个接口中的方法:具有与该方法相同的声明并且添加了实现部分(由花括号包裹的若干条语句
注意:相同的方法声明意味着完全一致的名称、参数类型列表和结果类型列表。其中,参数类型列表即为参数声明列表中除去参数名称的部分。一致的参数类型列表意味着其长度以及顺序的完全相同。对于结果类型列表也是如此。
###6.3.2 实现接口
无侵入式:无需在一个数据类型中声明它实现了哪个接口(只要满足了“方法集合为其超集”的条件)
###6.3.3 类型转换
说明:Go语言的类型转换规则定义了是否能够以及怎样可以把一个类型的值转换另一个类型的值
空接口:所谓空接口类型即是不包含任何方法声明的接口类型,用interface{}
表示,常简称为空接口
类型转换表达式:在类型字面量后跟由圆括号包裹的值(或能够代表它的变量、常量或表达式),意为将后者转换为前者类型的值
注意:Go语言中的包含预定义的任何数据类型都可以被看做是空接口的实现
1 | p := Person{"Robert", "Male", 33, "Beijing"} |
###6.3.4 类型断言
用途:判断一个类型是否是某个接口的实现
返回值:类型断言表达式的求值结果可以有两个。第一个结果是被转换后的那个目标类型的值,而第二个结果则是转换操作成功与否的标志
1 | //ok代表了一个bool类型的值 |
###6.3.5 实例
1 | package main |
##6.4 指针
###6.4.1 *
和&
作为地址操作符
地址操作符 | 作用对象 | 返回值 |
---|---|---|
* |
指针 | 取值(取出指针指向的那个值) |
& |
值 | 取址(取出指向改值的指针指) |
###6.4.2 *[基底类型]
场景:当出现在一个类型之前,当出现在一个类型之前(如Person和[3]string)时就不能被看做是操作符了,而应该被视为一个符号
用途:如此组合而成的标识符所表达的含义是作为第二部分的那个类型的指针类型。
基底类型:可以把其中的第二部分所代表的类型称为基底类型
###6.4.3 指针方法和值方法
指针方法:只要一个方法的接收者类型是其所属类型的指针类型而不是该类型本身,那么我就可以称该方法为一个指针方法
1 | type Person struct { |
值方法:如果一个方法的接收者类型就是其所属的类型本身,那么我们就可以把它叫做值方法
1 | //1.为结构体添加值方法 |
###6.4.4 实例
1 | package main |
##6.5 指针(续)
###隐藏规则
###6.5.1 指针类型和其基底类型所拥有的方法
原理: 一个指针类型拥有以它以及以它的基底类型为接收者类型的所有方法,而它的基底类型却只拥有以它本身为接收者类型的方法
导致:拥有指针方法Grow和Move的指针类型*Person是接口类型Animal的实现类型,但是它的基底类型Person却不是
###6.5.2 基础类型可以调用指针方法吗?
可以:如果Go语言发现我们调用的Grow
方法是bp
的指针方法,那么它会把该调用表达式视为(&bp).Grow()
###6.5.3 案例
1 | package main |
#7 基本流程控制
##7.1 if语句
###7.1.1 简单if语句
1 | var number = 0 |
###7.1.2 包含变量初始化过程的if语句
短变量声明语句:在声明变量number的同时为它赋值。if
语句的初始化语句:
语法:应被放置在if关键字和条件表达式之间,并与前者由空格分隔、与后者由英文分号;分隔
作用域:仅在这条if
语句所代表的代码块中
标识符的重声明:只要对同一个标识符的两次声明各自所在的代码块之间存在包含的关系,就会形成对该标识符的重声明。导致的结果就是标识符的遮蔽。
1 | if number := 4; 100 > number { |
###7.1.3 案例
1 | package main |
##7.2 switch语句
switch表达式:switch语句中要被判定的那个表达式
注意:case
表达式结果类型需要与switch
表达式的结果类型一致
分类:每一个case
可以携带一个表达式或一个类型说明符,据此分两类
分类 | case携带的代码 |
---|---|
表达式switch 语句 |
表达式 |
类型switch 语句 |
类型说明符 |
##7.3 for语句
##7.4 select语句