https://draveness.me/golang/docs/part1-prerequisite/ch01-prepare/golang-debug/
Go 语言起源 2007 年,并于 2009 年正式对外发布。它从 2009 年 9 月 21 日开始作为谷歌公司 20% 兼职项目,即相关员工利用 20% 的空余时间来参与 Go 语言的研发工作。该项目的三位领导者均是著名的 IT 工程师:Robert Griesemer,参与开发 Java HotSpot 虚拟机;Rob Pike,Go 语言项目总负责人,贝尔实验室 Unix 团队成员,参与的项目包括 Plan 9,Inferno 操作系统和 Limbo 编程语言;Ken Thompson,贝尔实验室 Unix 团队成员,C 语言、Unix 和 Plan 9 的创始人之一,与 Rob Pike 共同开发了 UTF-8 字符集规范。自 2008 年 1 月起,Ken Thompson 就开始研发一款以 C 语言为目标结果的编译器来拓展 Go 语言的设计思想。
在 2008 年年中,Go 语言的设计工作接近尾声,一些员工开始以全职工作状态投入到这个项目的编译器和运行实现上。Ian Lance Taylor 也加入到了开发团队中,并于 2008 年 5 月创建了一个 gcc 前端。
Russ Cox 加入开发团队后着手语言和类库方面的开发,也就是 Go 语言的标准包。在 2009 年 10 月 30 日,Rob Pike 以 Google Techtalk 的形式第一次向人们宣告了 Go 语言的存在。
直到 2009 年 11 月 10 日,开发团队将 Go 语言项目以 BSD-style 授权(完全开源)正式公布了 Linux 和 Mac OS X 平台上的版本。Hector Chu 于同年 11 月 22 日公布了 Windows 版本。
作为一个开源项目,Go 语言借助开源社区的有生力量达到快速地发展,并吸引更多的开发者来使用并改善它。自该开源项目发布以来,超过 200 名非谷歌员工的贡献者对 Go 语言核心部分提交了超过 1000 个修改建议。在过去的 18 个月里,又有 150 开发者贡献了新的核心代码。这俨然形成了世界上最大的开源团队,并使该项目跻身 Ohloh 前 2% 的行列。大约在 2011 年 4 月 10 日,谷歌开始抽调员工进入全职开发 Go 语言项目。开源化的语言显然能够让更多的开发者参与其中并加速它的发展速度。Andrew Gerrand 在 2010 年加入到开发团队中成为共同开发者与支持者。
在 Go 语言在 2010 年 1 月 8 日被 Tiobe(闻名于它的编程语言流行程度排名)宣布为 “2009 年年度语言” 后,引起各界很大的反响。目前 Go 语言在这项排名中的最高记录是在 2017 年 1 月创下的第13名,流行程度 2.325%。
从 2010 年 5 月起,谷歌开始将 Go 语言投入到后端基础设施的实际开发中,例如开发用于管理后端复杂环境的项目。有句话叫 “吃你自己的狗食”,这也体现了谷歌确实想要投资这门语言,并认为它是有生产价值的。
Go 语言的官方网站是 golang.org,这个站点采用 Python 作为前端,并且使用 Go 语言自带的工具 godoc 运行在 Google App Engine 上来作为 Web 服务器提供文本内容。在官网的首页有一个功能叫做 Go Playground,是一个 Go 代码的简单编辑器的沙盒,它可以在没有安装 Go 语言的情况下在你的浏览器中编译并运行 Go,它提供了一些示例,其中包括国际惯例 “Hello, World!”。
正如 “21 世纪的 C 语言” 这句话所说,Go 语言并不是凭空而造的,而是和 C++、Java 和 C# 一样属于 C 系。不仅如此,设计者们还汲取了其它编程语言的精粹部分融入到 Go 语言当中。
在声明和包的设计方面,Go 语言受到 Pascal、Modula 和 Oberon 系语言的影响;在并发原理的设计上,Go 语言从同样受到 Tony Hoare 的 CSP(通信序列进程 Communicating Sequential Processes)理论影响的 Limbo 和 Newsqueak 的实践中借鉴了一些经验,并使用了和 Erlang 类似的机制。
这是一门完全开源的编程语言,因为它使用 BSD 授权许可,所以任何人都可以进行商业软件的开发而不需要支付任何费用。
尽管为了能够让目前主流的开发者们能够对 Go 语言中的类 C 语言的语法感到非常亲切而易于转型,但是它在极大程度上简化了这些语法,使得它们比 C/C++ 的语法更加简洁和干净。同时,Go 语言也拥有一些动态语言的特性,这使得使用 Python 和 Ruby 的开发者们在使用 Go 语言的时候感觉非常容易上手。
为什么会有Go
Go 语言的主要目标是将静态语言的安全性和高效性与动态语言的易开发性进行有机结合,达到完美平衡,从而使编程变得更加有乐趣,而不是在艰难抉择中痛苦前行。
因此,Go 语言是一门类型安全和内存安全的编程语言。虽然 Go 语言中仍有指针的存在,但并不允许进行指针运算。
Go 语言的另一个目标是对于网络通信、并发和并行编程的极佳支持,从而更好地利用大量的分布式和多核的计算机,这一点对于谷歌内部的使用来说就非常重要了。设计者通过 goroutine 这种轻量级线程的概念来实现这个目标,然后通过 channel 来实现各个 goroutine 之间的通信。他们实现了分段栈增长和 goroutine 在线程基础上多路复用技术的自动化
这个特性显然是 Go 语言最强有力的部分,不仅支持了日益重要的多核与多处理器计算机,也弥补了现存编程语言在这方面所存在的不足。
Go 语言中另一个非常重要的特性就是它的构建速度(编译和链接到机器代码的速度),一般情况下构建一个程序的时间只需要数百毫秒到几秒。作为大量使用 C++ 来构建基础设施的谷歌来说,无疑从根本上摆脱了 C++ 在构建速度上非常不理想的噩梦。这不仅极大地提升了开发者的生产力,同时也使得软件开发过程中的代码测试环节更加紧凑,而不必浪费大量的时间在等待程序的构建上。
依赖管理是现今软件开发的一个重要组成部分,但是 C 语言中“头文件”的概念却导致越来越多因为依赖关系而使得构建一个大型的项目需要长达几个小时的时间。人们越来越需要一门具有严格的、简洁的依赖关系分析系统从而能够快速编译的编程语言。这正是 Go 语言采用包模型的根本原因,这个模型通过严格的依赖关系检查机制来加快程序构建的速度,提供了非常好的可量测性。
整个 Go 语言标准库的编译时间一般都在 20 秒以内,其它的常规项目也只需要半秒钟的时间来完成编译工作。这种闪电般的编译速度甚至比编译 C 语言或者 Fortran 更加快,使得编译这一环节不再成为在软件开发中困扰开发人员的问题。在这之前,动态语言将快速编译作为自身的一大亮点,像 C++ 那样的静态语言一般都有非常漫长的编译和链接工作。而同样作为静态语言的 Go 语言,通过自身优良的构建机制,成功地去除了这个弊端,使得程序的构建过程变得微不足道,拥有了像脚本语言和动态语言那样的高效开发的能力。
另外,Go 语言在执行速度方面也可以与 C/C++ 相提并论。
由于内存问题(通常称为内存泄漏)长期以来一直伴随着 C++ 的开发者们,Go 语言的设计者们认为内存管理不应该是开发人员所需要考虑的问题。因此尽管 Go 语言像其它静态语言一样执行本地代码,但它依旧运行在某种意义上的虚拟机,以此来实现高效快速的垃圾回收(使用了一个简单的标记-清除算法)。
尽管垃圾回收并不容易实现,但考虑这将是未来并发应用程序发展的一个重要组成部分,Go 语言的设计者们还是完成了这项艰难的任务。
Go 语言还能够在运行时进行反射相关的操作。
使用 go install
能够很轻松地对第三方包进行部署。
此外,Go 语言还支持调用由 C 语言编写的海量库文件(第 3.9 节),从而能够将过去开发的软件进行快速迁移。
Go 语言从本质上(程序和结构方面)来实现并发编程。
因为 Go 语言没有类和继承的概念,所以它和 Java 或 C++ 看起来并不相同。但是它通过接口(interface)的概念来实现多态性。Go 语言有一个清晰易懂的轻量级类型系统,在类型之间也没有层级之说。因此可以说这是一门混合型的语言。
在传统的面向对象语言中,使用面向对象编程技术显得非常臃肿,它们总是通过复杂的模式来构建庞大的类型层级,这违背了编程语言应该提升生产力的宗旨。
函数是 Go 语言中的基本构件,它们的使用方法非常灵活。在第六章,我们会看到 Go 语言在函数式编程方面的基本概念。
Go 语言使用静态类型,所以它是类型安全的一门语言,加上通过构建到本地代码,程序的执行速度也非常快。
作为强类型语言,隐式的类型转换是不被允许的,记住一条原则:让所有的东西都是显式的。
Go 语言其实也有一些动态语言的特性(通过关键字 var
),所以它对那些逃离 Java 和 .Net 世界而使用 Python、Ruby、PHP 和 JavaScript 的开发者们也具有很大的吸引力。
Go 语言支持交叉编译,比如说你可以在运行 Linux 系统的计算机上开发运行 Windows 下运行的应用程序。这是第一门完全支持 UTF-8 的编程语言,这不仅体现在它可以处理使用 UTF-8 编码的字符串,就连它的源码文件格式都是使用的 UTF-8 编码。Go 语言做到了真正的国际化!
Go 语言被设计成一门应用于搭载 Web 服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。对于高性能分布式系统领域而言,Go 语言无疑比大多数其它语言有着更高的开发效率。它提供了海量并行的支持,这对于游戏服务端的开发而言是再好不过了。
Go 语言一个非常好的目标就是实现所谓的复杂事件处理(CEP),这项技术要求海量并行支持,高度的抽象化和高性能。当我们进入到物联网时代,CEP 必然会成为人们关注的焦点。
但是 Go 语言同时也是一门可以用于实现一般目标的语言,例如对于文本的处理,前端展现,甚至像使用脚本一样使用它。
值得注意的是,因为垃圾回收和自动内存分配的原因,Go 语言不适合用来开发对实时性要求很高的软件。
越来越多的谷歌内部的大型分布式应用程序都开始使用 Go 语言来开发,例如谷歌地球的一部分代码就是由 Go 语言完成的。
如果你想知道一些其它组织使用Go语言开发的实际应用项目,你可以到 使用 Go 的组织 页面进行查看。出于隐私保护的考虑,许多公司的项目都没有展示在这个页面。我们将会在第 21 章讨论到一个使用 Go 语言开发的大型存储区域网络(SAN)案例。
在 Chrome 浏览器中内置了一款 Go 语言的编译器用于本地客户端(NaCl),这很可能会被用于在 Chrome OS 中执行 Go 语言开发的应用程序。
Go 语言可以在 Intel 或 ARM 处理器上运行,因此它也可以在安卓系统下运行,例如 Nexus 系列的产品。
在 Google App Engine 中使用 Go 语言:2011 年 5 月 5 日,官方发布了用于开发运行在 Google App Engine 上的 Web 应用的 Go SDK,在此之前,开发者们只能选择使用 Python 或者 Java。这主要是 David Symonds 和 Nigel Tao 努力的成果。目前最新的稳定版是基于 Go 1.4 的 SDK 1.9.18,于 2015 年 2 月 18 日发布。当前 Go 语言的稳定版本是 Go 1.4.2。
根据 Go 开发团队和基本的算法测试,Go 语言与 C 语言的性能差距大概在 10%~20% 之间( 译者注:由于出版时间限制,该数据应为 2013 年 3 月 28 日之前产生 )。虽然没有官方的性能标准,但是与其它各个语言相比已经拥有非常出色的表现。
如果说 Go 语言的执行效率大约比 C++ 慢 20% 也许更有实际意义。保守估计在相同的环境和执行目标的情况下,Go 程序比 Java 或 Scala 应用程序要快上 2 倍,并比这两门语言占用的内存降低了 70% 。在很多情况下这种比较是没有意义的,而像谷歌这样拥有成千上万台服务器的公司都抛弃 C++ 而开始将 Go 用于生产环境才足够说明它本身所具有的优势。
时下流行的语言大都是运行在虚拟机上,如:Java 和 Scala 使用的 JVM,C# 和 VB.NET 使用的 .NET CLR。尽管虚拟机的性能已经有了很大的提升,但任何使用 JIT 编译器和脚本语言解释器的编程语言(Ruby、Python、Perl 和 JavaScript)在 C 和 C++ 的绝对优势下甚至都无法在性能上望其项背。
如果说 Go 比 C++ 要慢 20%,那么 Go 就要比任何非静态和编译型语言快 2 到 10 倍,并且能够更加高效地使用内存。
其实比较多门语言之间的性能是一种非常猥琐的行为,因为任何一种语言都有其所擅长和薄弱的方面。例如在处理文本方面,那些只处理纯字节的语言显然要比处理 Unicode 这种更为复杂编码的语言要出色的多。有些人可能认为使用两种不同的语言实现同一个目标能够得出正确的结论,但是很多时候测试者可能对一门语言非常了解而对另一门语言只是大概明白,测试者对程序编写的手法在一定程度也会影响结果的公平性,因此测试程序应该分别由各自语言的擅长者来编写,这样才能得到具有可比性的结果。另外,像在统计学方面,人们很难避免人为因素对结果的影响,所以这在严格意义上并不是科学。还要注意的是,测试结果的可比性还要根据测试目标来区别,例如很多发展十多年的语言已经针对各类问题拥有非常成熟的类库,而作为一门新生语言的 Go 语言,并没有足够的时间来推导各类问题的最佳解决方案。
这里有一些评测结果:
比较 Go 和 Python 在简单的 web 服务器方面的性能,单位为传输量每秒:
原生的 Go http 包要比 web.py 快 7 至 8 倍,如果使用 web.go 框架则稍微差点,比 web.py 快 6 至 7 倍。在 Python 中被广泛使用的 tornado 异步服务器和框架在 web 环境下要比 web.py 快很多,Go 大概只比它快 1.2 至 1.5 倍(详见引用 26)。
根据 Robert Hundt(2011 年 6 月,详见引用 28)的文章对 C++、Java、Go 和 Scala,以及 Go 开发团队的反应(详见引用 29),可以得出以下结论:
mac安装go语言环境
brew update
brew install go
Linux安装
查看golang
yum info golang
安装
yum install golang
设置国内代理
go env -w GOPROXY=https://goproxy.cn,direct
在输入go env查看go代理
go的环境变量
$HOME/go
,当然,你也可以安装在别的地方。$GOROOT/bin
,如果你使用的是 Go 1.0.3 及以后的版本,一般情况下你可以将它的值设置为空,Go 将会使用前面提到的默认值。go语言的程序可以进行构建
在大多数 IDE 中,每次构建程序之前都会自动调用源码格式化工具 gofmt
并保存格式化后的源文件。如果构建成功则不会输出任何信息,而当发生编译时错误时,则会指明源码中具体第几行出现了什么错误,如:a declared and not used
。一般情况下,你可以双击 IDE 中的错误信息直接跳转到发生错误的那一行。
如果程序执行一切顺利并成功退出后,将会在控制台输出 Program exited with code 0
从 Go 1 版本开始,使用 Go 自带的更加方便的工具来构建应用程序:
go build
编译自身包和依赖包go install
编译并安装自身包和依赖包常量使用const关键字,变量使用var关键字
存储在常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型
声明时可以显示声明类型,也可以有编译器自行推断
var a string = "run"
var b,c int = 1,2
const b string = "abc" //显式声明
const b = "abc" //编译器自行推断
const c_name,c_name1 = value1,value2 //多个声明简写
常量允许并行赋值
const (
Monday, Tuesday, Wednesday = 1, 2, 3
Thursday, Friday, Saturday = 4, 5, 6
)
常量还可以用作枚举
// 赋值一个常量时,之后没赋值的常量都会应用上一行的赋值表达式
const (
a = iota // a = 0
b // b = 1
c // c = 2
d = 5 // d = 5
e // e = 5
)
// 赋值两个常量,iota 只会增长一次,而不会因为使用了两次就增长两次
const (
Apple, Banana = iota + 1, iota + 2 // Apple=1 Banana=2
Cherimoya, Durian // Cherimoya=2 Durian=3
Elderberry, Fig // Elderberry=3, Fig=4
)
// 使用 iota 结合 位运算 表示资源状态的使用案例
const (
Open = 1 << iota // 0001
Close // 0010
Pending // 0100
)
数值
数字型的常量是没有大小和符号的,并且可以使用任何精度而不会导致溢出
反斜杠 \
可以在常量表达式中作为多行的连接符使用
变量
如果在相同的代码块中,我们不可以再次对于相同名称的变量使用初始化声明,例如:a := 20
就是不被允许的,编译器会提示错误 no new variables on left side of :=
,但是 a = 20
是可以的,因为这是给相同的变量赋予一个新的值。
如果你在定义变量 a 之前使用它,则会得到编译错误 undefined: a
。
如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误,例如下面这个例子当中的变量 a:
func main() {
var a string = "abc"
fmt.Println("hello, world")
}
尝试编译这段代码将得到错误 a declared and not used
另外,全局变量是允许声明但不使用。
同一类型的多个变量允许并行赋值,如:
var a, b, c int
多变量可以在同一行进行赋值或者声明
//声明
a, b, c := 5, 7, "abc"
// 赋值
a, b, c = 5, 7, "abc"
// 空白标识符 _ 也被用于抛弃值,如值 5 在:_, b = 5, 7 中被抛弃。
:_, b = 5, 7
go中的基本类型有布尔型、数字型和字符型
表达式是一种特定的类型的值,它可以由其它的值以及运算符组合而成。每个类型都定义了可以和自己结合的运算符集合,如果你使用了不在这个集合中的运算符,则会在编译时获得编译错误。
一元运算符只可以用于一个值的操作(作为后缀),而二元运算符则可以和两个值或者操作数结合(作为中缀)
Go 语言支持整型和浮点型数字,并且原生支持复数,其中位的运算采用补码
Go 也有基于架构的类型,例如:int、uint 和 uintptr。
这些类型的长度都是根据运行程序所在的操作系统类型所决定的
int
和 uint
在 32 位操作系统上,它们均使用 32 位(4 个字节),在 64 位操作系统上,它们均使用 64 位(8 个字节)。uintptr
的长度被设定为足够存放一个指针即可。Go 语言中没有 float 类型。(Go语言中只有 float32 和 float64)没有double类型。
整数:
无符号整数:
浮点型(IEEE-754 标准):
package main
import "fmt"
func main() {
var n int16 = 34
var m int32
// compiler error: cannot use n (type int16) as type int32 in assignment
//m = n
m = int32(n)
fmt.Printf("32 bit int is: %d\n", m)
fmt.Printf("16 bit int is: %d\n", n)
}
格式化说明符
当进行类似 a32bitInt = int32(a32Float)
的转换时,小数点后的数字将被丢弃。这种情况一般发生当从取值范围较大的类型转换为取值范围较小的类型时,或者你可以写一个专门用于处理类型转换的函数来确保没有发生精度的丢失。
func Uint8FromInt(n int) (uint8, error) {
if 0 <= n && n <= math.MaxUint8 { // conversion is safe
return uint8(n), nil
}
return 0, fmt.Errorf("%d is out of the uint8 range", n)
}
Go 拥有以下复数类型:
complex64 (32 位实数和虚数)
complex128 (64 位实数和虚数)
复数使用 re+imI
来表示,其中 re
代表实数部分,im
代表虚数部分,I
代表根号负 1。
var c1 complex64 = 5 + 10i
fmt.Printf("The value is: %v", c1)
位运算
按位与 &
、按位或 |
、按位异或 ^
、位清除 &^
一元运算符
按位补足 ^
、位左移 <<
、位右移 >>
Go 中拥有以下逻辑运算符:==
、!=
、<
、<=
、>
、>=
随机数
一些像游戏或者统计学类的应用需要用到随机数。rand
包实现了伪随机数的生成
字符串
字符串是 UTF-8 字符的一个序列(当字符为 ASCII 码时则占用 1 个字节,其它字符根据需要占用 2-4 个字节)。UTF-8 是被广泛使用的编码格式,是文本文件的标准编码,其它包括 XML 和 JSON 在内,也都使用该编码。由于该编码对占用字节长度的不定性,Go 中的字符串里面的字符也可能根据需要占用 1 至 4 个字节(示例见第 4.6 节),这与其它语言如 C++、Java 或者 Python 不同(Java 始终使用 2 个字节)。Go 这样做的好处是不仅减少了内存和硬盘空间占用,同时也不用像其它语言那样需要对使用 UTF-8 字符集的文本进行编码和解码。
字符串是一种值类型,且值不可变,即创建某个文本后你无法再次修改这个文本的内容;更深入地讲,字符串是字节的定长数组。
一般的比较运算符(==
、!=
、<
、<=
、>=
、>
)通过在内存中按字节比较来实现字符串的对比。你可以通过函数 len()
来获取字符串所占的字节长度,例如:len(str)
。
字符串的内容(纯字节)可以通过标准索引法来获取,在中括号 []
内写入索引,索引从 0 开始计数:
str[0]
str[i - 1]
str[len(str)-1]
作为一种基本数据结构,每种语言都有一些对于字符串的预定义处理函数。Go 中使用 strings
包来完成对字符串的主要操作
strings.HasPrefix(string,''):判断字符串是不是以指定字符串开头
strings.HasSuffi(string, ''):判断字符串是否是以指定字符串结尾
strings.Contains(s, substr string):判断字符串是否包含substr
strings.Index(s, str string):判断str在s中的位置,没有返回-1
strings.LastIndex(s, str string):判断str在s中的最后出现的位置,没有返回-1
strings.IndexRune(s string, r rune):查询非 ASCII 编码的字符在父字符串中的位置
strings.Replace(str, old, new string, n int):Replace
用于将字符串 str
中的前 n
个字符串 old
替换为字符串 new
,并返回一个新的字符串,如果 n = -1
则替换所有字符串 old
为字符串 new
strings.Count(s, str string): Count
用于计算字符串 str
在字符串 s
中出现的非重叠次数
strings.Repeat(s, count int): Repeat
用于重复 count
次字符串 s
并返回一个新的字符串
strings.ToLower(s): 将字符串中的 Unicode 字符全部转换为相应的小写字符
strings.ToUpper(s): 将字符串中的 Unicode 字符全部转换为相应的大写字符
strings.TrimSpace(s, string):去掉字符串开头和结尾的空格字符
strings.Trim(s, ""):剔除字符串开头和结尾的指定字符
strings.TrimLeft()、strings.TrimRight():剔除开头或者结尾的字符串
strings.Fields(s)
将会利用 1 个或多个空白符号来作为动态长度的分隔符将字符串分割成若干小块,并返回一个 slice
strings.Split(s, sep)
用于自定义分割符号来对指定字符串进行分割,同样返回 slice
strings.Join(sl []string, sep string):用于将元素类型为 string 的 slice 使用分割符号来拼接组成一个字符串
与字符串相关的类型转换都是通过 strconv
包实现的
strconv.Itoa(i int) string
返回数字 i 所表示的字符串类型的十进制数
strconv.FormatFloat(f float64, fmt byte, prec int, bitSize int) string
将 64 位浮点型的数字转换为字符串,其中 fmt
表示格式(其值可以是 'b'
、'e'
、'f'
或 'g'
),prec
表示精度,bitSize
则使用 32 表示 float32,用 64 表示 float64
strconv.Atoi(s string) (i int, err error)
将字符串转换为 int 型
strconv.ParseFloat(s string, bitSize int) (f float64, err error)
将字符串转换为 float64 型。
package main
import (
"fmt"
"strings"
)
func main() {
var str string = "This is an example of a string"
fmt.Printf("T/F? Does the string \"%s\" have prefix %s? ", str, "Th")
fmt.Printf("%t\n", strings.HasPrefix(str, "Th"))
}
时间和日期
time
包为我们提供了一个数据类型 time.Time
(作为值使用)以及显示和测量时间和日期的功能函数
time.Now():获取当前时间
t.Day():获取时间的天数
t.Month():获取当前月份
t.Year():获取当前年份
t.Minute():获取时间的分钟数
t.Format("02 Jan 2006 15:04"): 格式化时间
package main
import "fmt"
type TZ int
func main() {
var a, b TZ = 3, 4
c := a + b
fmt.Printf("c has the value: %d", c) // 输出:c has the value: 7
}
Go 是强类型语言,因此不会进行隐式转换,任何不同类型之间的转换都必须显式说明
数组和切片是 Go 语言中常见的数据结构
数组是由相同类型元素的集合组成的数据结构,计算机会为数组分配一块连续的内存来保存其中的元素,我们可以利用数组中元素的索引快速访问特定元素,常见的数组大多都是一维的线性数组,而多维数组在数值和图形计算领域却有比较常见的应用
数组作为一种基本的数据类型,我们通常会从两个维度描述数组,也就是数组中存储的元素类型和数组最大能存储的元素个数,在 Go 语言中我们往往会使用如下所示的方式来表示数组类型
[10]int
[200]interface{}
Go 语言的数组有两种不同的创建方式,一种是显式的指定数组大小,另一种是使用 [...]T
声明数组,Go 语言会在编译期间通过源代码推导数组的大小
arr1 := [3]int{1, 2, 3}
arr2 := [...]int{1, 2, 3}
上述两种声明方式在运行期间得到的结果是完全相同的,后一种声明方式在编译期间就会被转换成前一种,这也就是编译器对数组大小的推导
数组访问越界是非常严重的错误,Go 语言中可以在编译期间的静态类型检查判断数组越界
数组和字符串的一些简单越界错误都会在编译期间发现,例如:直接使用整数或者常量访问数组;但是如果使用变量去访问数组或者字符串时,编译器就无法提前发现错误,我们需要 Go 语言运行时阻止不合法的访问
Go 语言运行时在发现数组、切片和字符串的越界操作会由运行时的 runtime.panicIndex
和 runtime.goPanicIndex
触发程序的运行时错误并导致崩溃退出
切片
数组在 Go 语言中没那么常用,更常用的数据结构是切片,即动态数组,其长度并不固定,我们可以向切片中追加元素,它会在容量不足时自动扩容
切片是对数组的封装,为数据序列提供了更通用、更强大而方便的接口,除了矩阵变换这种需要明确纬度的情况外,Go中的大部分数组编程是通过切片完成的。
切片保存了对底层数组的引用,若你将某个切片赋予另一个切片,它们会引用同一个数组,若某个函数将一个切片作为参数传入,则它对该切片元素的修改对调用者而言同样可见。
在 Go 语言中,切片类型的声明方式与数组有一些相似,不过由于切片的长度是动态的,所以声明时只需要指定切片中的元素类型
[]int
[]interface{}
Go 语言中包含三种初始化切片的方式:
make
创建切片:arr[0:3] or slice[0:3]
slice := []int{1, 2, 3}
slice := make([]int, 10)
第一种方法使用下标创建切片是最原始也最接近汇编语言的方式,它是所有方法中最为底层的一种,编译器会将 arr[0:3]
或者 slice[0:3]
等语句转换成 OpSliceMake
操作
虽然编译期间可以检查出很多错误,但是在创建切片的过程中如果发生了以下错误会直接触发运行时错误并崩溃:
二维切片
数组和普通切片都是一维的,要创建等价的二维数组或者切片,就必须定义一个数组的数组,或者切片的切片。
由于切片长度是可变的,因此其内部可能拥有多个不同长度的切片,
哈希是除了数组之外,最常见的数据结构。几乎所有的语言都会有数组和哈希表两种集合元素,有的语言将数组实现成列表,而有的语言将哈希称作字典或者映射。无论如何命名或者如何实现,数组和哈希是两种设计集合元素的思路,数组用于表示元素的序列,而哈希表示的是键值对之间映射关系。
映射是方便而强大的内建数据结构,它可以关联不同类型的值,其键可以是任何相等性操作符支持的类型,如整数、浮点数、负数、指针、字符串、接口、结构、以及数组等。切片不能用做映射键,因为它们的相等性还未定义。
与切片一样,映射也是引用类型,若将映射传入函数中,并更改了该映射的内容,则此修改对调用者同样可见
var timeZone = map[string]int{
"UTC": 0*60*60,
"EST": -5*60*60,
"CST": -6*60*60,
"MST": -7*60*60,
"PST": -8*60*60
}
offset := timeZone["EST"]
读取、遍历
_ = hash[key]
for k, v := range hash {
// k, v
}
如果想要删除哈希中的元素,就需要使用 Go 语言中的 delete
关键字,这个关键字的唯一作用就是将某一个键对应的元素从哈希表中删除,无论是该键对应的值是否存在,这个内建的函数都不会返回任何的结果
追加
new和make都在堆上分配内存,但是它们的行为不同,适用于不同的类型。
&T{}
。