一、Go语言学习(拉钩-22讲通关go语言)
1.1 Go 语言为开发者的需求而设计
K8s、Docker、etcd 这类耳熟能详的工具,就是用 Go 语言开发的,而且很多大公司(如腾讯、字节跳动等)都在把原来 C/C++、Python、PHP 的技术栈迁往 Go 语言。
在我看来,Go 作为一门高效率的工业化语言备受推崇,这与其语言本身的优势有直接的关系:
- 语法简洁,相比其他语言更容易上手,开发效率更高;
- 自带垃圾回收(GC),不用再手动申请释放内存,能够有效避免 Bug,提高性能;
- 语言层面的并发支持,让你很容易开发出高性能的程序;
- 提供的标准库强大,第三方库也足够丰富,可以拿来即用,提高开发效率;
- 可通过静态编译直接生成一个可执行文件,运行时不依赖其他库,部署方便,可伸缩能力强;
- 提供跨平台支持,很容易编译出跨各个系统平台直接运行的程序。
对比其他语言,Go 的优势也显著。比如 Java 虽然具备垃圾回收功能,但它是解释型语言,需要安装 JVM 虚拟机才能运行;C 语言虽然不用解释,可以直接编译运行,但是它不具备垃圾回收功能,需要开发者自己管理内存的申请和释放,容易出问题。而 Go 语言具备了两者的优势。
如今微服务和云原生已经成为一种趋势,而 Go 作为一款高性能的编译型语言,最适合承载落地微服务的实现 ,又容易生成跨平台的可执行文件,相比其他编程语言更容易部署在 Docker 容器中,实现灵活的自动伸缩服务。
总体来看,Go 语言的整体设计理念就是以软件工程为目的的,也就是说它不是为了编程语言本身多么强大而设计,而是为了开发者更好地研发、管理软件工程,一切都是为了开发者着想。
如果你是有 1~3 年经验的其他语言开发者(如 Python、PHP、C/C++),Go 的学习会比较容易,因为编程语言的很多概念相通。而如果你是有基本计算机知识但无开发经验的小白,Go 也适合尽早学习,吃透它有助于加深你对编程语言的理解,也更有职业竞争力。
而在我与 Go 语言学习者进行交流,以及面试的过程中,也发现了一些典型问题,可概括为如下三点:
第一,学习者所学知识过于零碎,缺乏系统性,并且不是太深入,导致写不出高效的程序,也难以在面试中胜出。比如,我面试时常问字符串拼接的效率问题,这个问题会牵涉到 + 加号运算符、buffer 拼接、build 拼接、并发安全等知识点,但应聘者通常只能答出最浅显的内容,缺乏对语言逻辑的深层思考。
第二,很多入门者已有其他语言基础,很难转换语言思维模式,而且 Go 的设计者还做了很多相比其他语言的改进和创新。作为从 Java 转到 Go 语言的过来人,我非常理解这种情况,比如对于错误的处理,Java 语言使用 Exception,而 Go 语言则通过函数返回 error,这会让人很不习惯。
第三,没有开源的、适合练手的项目。
在过去分享 Go 语言知识的过程中,我融入了应对上述问题的方法并得到好评,比如有用户称“你的文章给我拨云见日的感觉!”“通过你的文章终于懂 context 的用法了!”……这些正向评价更坚定了我分享内容的信心。
于是在经过不断地思考、整理后,我希望设计更有系统性、也更通俗易懂的一门专栏。我的目标是通过这门课程帮助你少走弯路,比其他人更快一步提升职场竞争力。
这门课的亮点和设计思路
- 系统性设计:从基础知识、底层原理到实战,让你不仅可以学会使用,还能从语言自身的逻辑、框架层面分析问题,并做到能上手项目。这样当出现问题时,你可以不再盲目地搜索知识点。
- 案例实操:我设计了很多便于运用知识点的代码示例,还特意站在学习者的视角,演示了一些容易出 Bug 的场景,帮你避雷。我还引入了很多生活化的场景,比如用枪响后才能赛跑的例子演示 sync.Cond 的使用,帮你加深印象,缓解语言学习的枯燥感。
- 贴近实际:我所策划的内容来源于众多学习者的反馈,在不断地交流中,我总结了他们问题的共性和不同,并有针对性地融入专栏。
那我是怎么划分这门课的呢?
作为初学者,不管你是否有编程经验,都需要先学习 Go 语言的基本语法,然后我会在此基础上再向你介绍 Go 语言的核心特性——并发,这也是 Go 最自豪的功能。其基于协程的并发,比我们平时使用的线程并发更轻量,可以随意地在一台普通的电脑上启动成百上千个协程,成本非常低。
掌握了基本知识后,我们来通过底层分析深入理解原理。我会结合源码,并对比其他语言的同类知识,带你理解 Go 的设计思路和底层语言逻辑。
此时你可能还有一些疑惑,比如不知道如何把知识与实际工作结合起来,所以就需要 Go 语言工程质量管理方面的知识了。而最后,我会用两个实战帮你快速上手项目,巩固知识。
所以,我根据这个思路将这门课划分成 5 个模块:
- 模块一:Go 语言快速入门:我挑选了变量、常量等数据类型、函数和方法、结构体和接口等知识点介绍,这部分内容相对简洁,但已经足够你掌握 Go 的基本程序结构。
- 模块二:Go 语言高效并发:主要介绍 goroutine、channel、同步原语等知识,让你对 Go 语言层面的并发支持有更深入的理解,并且可以编写自己的 Go 并发程序设计。最后还会有一节课专门介绍常用的并发模式,可以拿来即用,更好地控制并发。
- 模块三:Go 语言深入理解:Go 语言底层原理的讲解和高级功能的介绍,比如 slice 的底层是怎样的,为什么这么高效等。这个模块也是我特意设计的,我在初学编程时,也有只学习如何使用,而不想研究底层原理的情况,导致工作遇到障碍后又不得不回头恶补,后来发现这是初学者的通病。但理解了底层原理后,你才能灵活编写程序、高效应对问题。
- 模块四:Go 语言工程管理:学习一门语言,不光要掌握它本身的知识,还要会模块管理、性能优化等周边技能,因为这些技能可以帮助你更好地进行多人协作,提高开发效率,写出更高质量的代码。你可以在这个模块学到如何测试 Go 语言以提高代码质量、如何做好性能优化、如何使用第三方库提高自己项目的开发效率等。
- 模块五:Go 语言实战:Go 语言更适合的场景就是网络服务和并发,通过开发 HTTP 服务和 RPC 服务这两个实战,可以把前四个模块的知识运用起来,快速上手。
作者寄语
我一直不厌其烦地跟团队小伙伴说,Go 语言是一门现代编程语言,相比其他编程语言,它对我们开发者有更好的用户体验,因为它的目的就是让我们更专注于自己业务的实现,提高开发效率。与此同时,当下的云原生是一种趋势, Go 语言非常适合部署在这种环境中,越早学习越有竞争力。
此外,我在上文中也反复强调了学习底层原理的重要性。编程语言有很多共通之处(比如概念、关键字、特性语法等),吃透后再学习其他的编程语言会简单得多,原因在于你理解了语言本身。所以在学习 Go 语言的过程中,我希望你多想、多练,深入理解,融会贯通。
现在,跟我一起踏上 Go 语言学习之旅吧,Let's Go!
1.2 基础入门:编写你的第一个Go语言程序
从这节课开始,我会带你走进 Go 语言的世界。我会用通俗易懂的语言,介绍 Go 语言的各个知识点,让你可以从零开始逐步学习,再深入它的世界。不管你以前是否接触过 Go 语言,都可以从这个专栏中受益。
现在,让我以一个经典的例子“Hello World”来带你入门 Go 语言,了解它是如何运行起来的。
Hello, 世界
如果你学过 C 语言,对这个经典的例子应该不会陌生。通过它,我先带你大概了解一下 Go 语言的一些核心理念,让你对 Go 语言代码有个整体的印象。如下所示:
ch01/main.go
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
这五行代码就构成了一个完整的 Go 程序,是不是非常简单?现在我运行这段代码,看看输出的结果,方法是打开终端输入以下命令,然后回车。
$ go run ch01/main.go
Hello, 世界
其中 go run ch01/main.go 是我输入的命令,回车后看到的“Hello, 世界”是 Go 程序输出的结果。
代码中的 go 是一个 Go 语言开发工具包提供的命令,它和你平时常用的 ls 命令一样都是可执行的命令。它可以帮助你运行 Go 语言代码,并进行编译,生成可执行的二进制文件等。
run 在这里是 go 命令的子命令,表示要运行 Go 语言代码的意思。最后的 ch01/main.go 就是我写的 Go 语言代码文件了。也就是说,整个 go run ch01/main.go 表示要运行 ch01/main.go 里的 Go 语言代码。
程序结构分析
要让一个 Go 语言程序成功运行起来,只需要 package main 和 main 函数这两个核心部分, package main 代表的是一个可运行的应用程序,而 main 函数则是这个应用程序的主入口。
在“Hello, 世界”这个简单的示例中,包含了一个 Go 语言程序运行的最基本的核心结构。我们以此为例,来逐一介绍程序的结构,了解 Go 语言的核心概念。
- 第一行的 package main 代表当前的 ch01/main.go 文件属于哪个包,其中 package 是 Go 语言声明包的关键字,main 是要声明的包名。在 Go 语言中 main 包是一个特殊的包,代表你的 Go 语言项目是一个可运行的应用程序,而不是一个被其他项目引用的库。
- 第二行的 import "fmt" 是导入一个 fmt 包,其中 import 是 Go 语言的关键字,表示导入包的意思,这里我导入的是 fmt 包,导入的目的是要使用它,下面会继续讲到。
- 第三行的 func main() 是定义了一个函数,其中 func 是 Go 语言的关键字,表示要定义一个函数或者方法的意思,main 是函数名,() 空括号表示这个 main 函数不接受任何参数。在 Go 语言中 main 函数是一个特殊的函数,它代表整个程序的入口,也就是程序在运行的时候,会先调用 main 函数,然后通过 main 函数再调用其他函数,达到实现项目业务需求的目的。
- 第四行的 fmt.Println("Hello, 世界") 是通过 fmt 包的 Println 函数打印“Hello, 世界”这段文本。其中 fmt 是刚刚导入的包,要想使用一个包,必须先导入。Println 函数是属于包 fmt 的函数,这里我需要它打印输出一段文本,也就是“Hello, 世界”。
- 第五行的大括号 } 表示 main 函数体的结束。现在整个代码片段已经分析完了,运行就可以看到“Hello, 世界”结果的输出。
从以上分析来看,Go 语言的代码是非常简洁、完整的核心程序,只需要 package、import、func main 这些核心概念就可以实现。 在后面的课时中,我还会讲如何使用变量,如何自定义函数等,这里先略过不讲,我们先来看看 Go 语言的开发环境是如何搭建的,这样才能运行上面的 Go 语言代码,让整个程序跑起来。
Go 语言环境搭建
要想搭建 Go 语言开发环境,需要先下载 Go 语言开发包。你可以从官网 golang.org/dl/ 和 golang.google.cn/dl/ 下载(第一个链接是国外的官网,第二个是国内的官网,如果第一个访问不了,可以从第二个下载)。
下载时可以根据自己的操作系统选择相应的开发包,比如 Window、MacOS 或是 Linux 等,如下图所示:
Windows MSI 下安装
MSI 安装的方式比较简单,在 Windows 系统上推荐使用这种方式。现在的操作系统基本上都是 64 位的,所以选择 64 位的 go1.15.windows-amd64.msi 下载即可,如果操作系统是 32 位的,选择 go1.15.windows-386.msi 进行下载。
下载后双击该 MSI 安装文件,按照提示一步步地安装即可。在默认情况下,Go 语言开发工具包会被安装到 c:\Go 目录,你也可以在安装过程中选择自己想要安装的目录。
假设安装到 c:\Go 目录,安装程序会自动把 c:\Go\bin 添加到你的 PATH 环境变量中,如果没有的话,你可以通过系统 -> 控制面板 -> 高级 -> 环境变量选项来手动添加。
Linux 下安装
Linux 系统同样有 32 位和 64 位,你可以根据你的 Linux 操作系统选择相应的压缩包,它们分别是 go1.15.linux-386.tar.gz 和 go1.15.linux-amd64.tar.gz。
下载成功后,需要先进行解压,假设你下载的是 go1.15.linux-amd64.tar.gz,在终端通过如下命令即可解压:
sudo tar -C /usr/local -xzf go1.15.linux-amd64.tar.gz
输入后回车,然后输入你的电脑密码,即可解压到 /usr/local 目录,然后把 /usr/local/go/bin 添加到 PATH 环境变量中,就可以使用 Go 语言开发工具包了。
把下面这段添加到 /etc/profile 或者 $HOME/.profile 文件中,保存后退出即可成功添加环境变量。
export PATH=$PATH:/usr/local/go/bin
macOS 下安装
如果你的操作系统是 macOS,可以采用 PKG 安装包。下载 go1.15.darwin-amd64.pkg 后,双击按照提示安装即可。安装成功后,路径 /usr/local/go/bin 应该已经被添加到了 PATH 环境变量中,如果没有的话,你可以手动添加,和上面 Linux 的方式一样,把如下内容添加到 /etc/profile 或者 $HOME/.profile 文件保存即可。
export PATH=$PATH:/usr/local/go/bin
安装测试
以上都安装成功后,你可以打开终端或者命令提示符,输入 go version 来验证 Go 语言开发工具包是否安装成功。如果成功的话,会打印出 Go 语言的版本和系统信息,如下所示:
$ go version
go version go1.15 darwin/amd64
环境变量设置
Go 语言开发工具包安装好之后,它的开发环境还没有完全搭建完成,因为还有两个重要的环境变量没有设置,它们分别是 GOPATH 和 GOBIN。
- GOPATH:代表 Go 语言项目的工作目录,在 Go Module 模式之前非常重要,现在基本上用来存放使用 go get 命令获取的项目。
- GOBIN:代表 Go 编译生成的程序的安装目录,比如通过 go install 命令,会把生成的 Go 程序安装到 GOBIN 目录下,以供你在终端使用。
假设工作目录为 /Users/flysnow/go,你需要把 GOPATH 环境变量设置为 /Users/flysnow/go,把 GOBIN 环境变量设置为 $GOPATH/bin。
在 Linux 和 macOS 下,把以下内容添加到 /etc/profile 或者 $HOME/.profile 文件保存即可。
export GOPATH=/Users/flysnow/go
export GOBIN=$GOPATH/bin
在 Windows 操作系统中,则通过控制面板 -> 高级 -> 环境变量选项添加这两个环境变量即可。
项目结构
采用 Go Module 的方式,可以在任何位置创建你的 Go 语言项目。在整个专栏中,我都会使用这种方式演示 Go 语言示例,现在你先对 Go Module 项目结构有一个大概了解,后面的课时我会详细地介绍 Go Module。
假设你的项目位置是 /Users/flysnow/git/gotour,打开终端,输入如下命令切换到该目录下:
$ cd /Users/flysnow/git/gotour
然后再执行如下命令创建一个 Go Module 项目:
$ go mod init
执行成功后,会生成一个 go.mod 文件。然后在当前目录下创建一个 main.go 文件,这样整个项目目录结构是:
gotour
├── go.mod
├── lib
└── main.go
其中 main.go 是整个项目的入口文件,里面有 main 函数。lib 目录是项目的子模块,根据项目需求可以新建很多个目录作为子模块,也可以继续嵌套为子模块的子模块。
编译发布
完成了你的项目后,可以编译生成可执行文件,也可以把它发布到 $GOBIN 目录,以供在终端使用。以“Hello 世界”为例,在项目根目录输入以下命令,即可编译一个可执行文件。
$ go build ./ch01/main.go
回车执行后会在当前目录生成 main 可执行文件,现在,我们来测试下它是否可用。
$ ./main
Hello, 世界
如果成功打印出“Hello, 世界”,证明程序成功生成。
以上生成的可执行文件在当前目录,也可以把它安装到 $GOBIN 目录或者任意位置,如下所示:
$ go install ./ch01/main.go
使用 go install 命令即可,现在你在任意时刻打开终端,输入 main 回车,都会打印出“Hello, 世界”,是不是很方便!
跨平台编译
Go 语言开发工具包的另一强大功能就是可以跨平台编译。什么是跨平台编译呢?就是你在 macOS 开发,可以编译 Linux、Window 等平台上的可执行程序,这样你开发的程序,就可以在这些平台上运行。也就是说,你可以选择喜欢的操作系统做开发,并跨平台编译成需要发布平台的可执行程序即可。
Go 语言通过两个环境变量来控制跨平台编译,它们分别是 GOOS 和 GOARCH 。
- GOOS:代表要编译的目标操作系统,常见的有 Linux、Windows、Darwin 等。
- GOARCH:代表要编译的目标处理器架构,常见的有 386、AMD64、ARM64 等。
这样通过组合不同的 GOOS 和 GOARCH,就可以编译出不同的可执行程序。比如我现在的操作系统是 macOS AMD64 的,我想编译出 Linux AMD64 的可执行程序,只需要执行 go build 命令即可,如以下代码所示:
$ GOOS=linux GOARCH=amd64 go build ./ch01/main.go
关于 GOOS 和 GOARCH 更多的组合,参考官方文档的 GOARCH 这一节即可。
Go 编辑器推荐
好的编辑器可以提高开发的效率,这里我推荐两款目前最流行的编辑器。
第一款是 Visual Studio Code + Go 扩展插件,可以让你非常高效地开发,通过官方网站 code.visualstudio.com/ 下载使用。
第二款是老牌 IDE 公司 JetBrains 推出的 Goland,所有插件已经全部集成,更容易上手,并且功能强大,新手老手都适合,你可以通过官方网站 www.jetbrains.com/go/ 下载使用。
总结
这节课中你学到了如何写第一个 Go 语言程序,并且搭建好了 Go 语言开发环境,创建好了 Go 语言项目,同时也下载好了 IDE 严阵以待,那么现在我就给你留个小作业:
改编示例“Hello 世界”的代码,打印出自己的名字。
下节课,我将为你介绍 Go 语言的变量、常量和基本类型,让你的 Go 语言程序更生动!
1.3 数据类型:你必须掌握的数据类型有哪些?
变量声明
变量代表可变的数据类型,也就是说,它在程序执行的过程中可能会被一次甚至多次修改。
在 Go 语言中,通过 var 声明语句来定义一个变量,定义的时候需要指定这个变量的类型,然后再为它起个名字,并且设置好变量的初始值。所以 var 声明一个变量的格式如下:
var 变量名 类型 = 表达式
现在我通过一个示例来演示如何定义一个变量,并且设置它的初始值:
ch02/main.go
package main
import "fmt"
func main() {
var i int = 10
fmt.Println(i)
}
观察上面例子中 main 函数的内容,其中 var i int = 10 就是定义一个类型为 int(整数)、变量名为 i 的变量,它的初始值为 10
这里为了运行程序,我加了一行 fmt.Println(i),你在上节课中就见到过它,表示打印出变量 i 的值。
这样做一方面是因为 Go 语言中定义的变量必须使用,否则无法编译通过,这也是 Go 语言比较好的特性,防止定义了变量不使用,导致浪费内存的情况;另一方面,在运行程序的时候可以查看变量 i 的结果。
通过输入 go run ch02/main.go 命令回车运行,即可看到如下结果:
$ go run ch02/main.go
10
打印的结果是10,和变量的初始值一样。
因为 Go 语言具有类型推导功能,所以也可以不去刻意地指定变量的类型,而是让 Go 语言自己推导,比如变量 i 也可以用如下的方式声明:
var i = 10
这样变量 i 的类型默认是 int 类型。
你也可以一次声明多个变量,把要声明的多个变量放到一个括号中即可,如下面的代码所示:
var (
j int= 0
k int= 1
)
同理因为类型推导,以上多个变量声明也可以用以下代码的方式书写:
var (
j = 0
k = 1
)
这样就更简洁了。
其实不止 int 类型,我后面介绍的 float64、bool、string 等基础类型都可以被自动推导,也就是可以省略定义类型。
演示项目目录结构
为了让你更好地理解我演示的例子,这里我给出演示项目的目录结构,以后的所有课时都会按照这个目录进行演示。
我的演示项目结构如下所示:
gotour
├── ch01
│ └── main.go
├── ch02
│ └── main.go
└── go.mod
其中 gotour 是演示项目的根目录,所有 Go 语言命令都会在这里执行,比如 go run。
ch01、ch02 这些目录是按照课时命名的,每一讲都有对应的目录,便于查找相应的源代码。具体的 Go 语言源代码会存放到对应的课时目录中。
基础类型
任何一门语言都有对应的基础类型,这些基础类型和现实中的事物一一对应,比如整型对应着 1、2、3、100 这些整数,浮点型对应着 1.1、3.4 这些小数等。Go 语言也不例外,它也有自己丰富的基础类型,常用的有:整型、浮点数、布尔型和字符串,下面我就为你详细介绍。
整型
在 Go 语言中,整型分为:
- 有符号整型:如 int、int8、int16、int32 和 int64。
- 无符号整型:如 uint、uint8、uint16、uint32 和 uint64。
它们的差别在于,有符号整型表示的数值可以为负数、零和正数,而无符号整型只能为零和正数。
除了有用“位”(bit)大小表示的整型外,还有 int 和 uint 这两个没有具体 bit 大小的整型,它们的大小可能是 32bit,也可能是 64bit,和硬件设备 CPU 有关。
在整型中,如果能确定 int 的 bit 就选择比较明确的 int 类型,因为这会让你的程序具备很好的移植性。
在 Go 语言中,还有一种字节类型 byte,它其实等价于 uint8 类型,可以理解为 uint8 类型的别名,用于定义一个字节,所以字节 byte 类型也属于整型。
浮点数
浮点数就代表现实中的小数。Go 语言提供了两种精度的浮点数,分别是 float32 和 float64。项目中最常用的是 float64,因为它的精度高,浮点计算的结果相比 float32 误差会更小。
下面的代码示例定义了两个变量 f32 和 f64,它们的类型分别为 float32 和 float64。
ch02/main.go
var f32 float32 = 2.2
var f64 float64 = 10.3456
fmt.Println("f32 is",f32,",f64 is",f64)
运行这段程序,会看到如下结果:
$ go run ch02/main.go
f32 is 2.2 ,f64 is 10.3456
**特别注意:**在演示示例的时候,我会尽可能地贴出演示需要的核心代码,也就是说,会省略 package 和 main 函数。如果没有特别说明,它们都是放在main函数中的,可以直接运行。
布尔型
一个布尔型的值只有两种:true 和 false,它们代表现实中的“是”和“否”。它们的值会经常被用于一些判断中,比如 if 语句(以后的课时会详细介绍)等。Go 语言中的布尔型使用关键字 bool 定义。
下面的代码声明了两个变量,你可以自己运行,看看打印输出的结果。
ch02/main.go
var bf bool =false
var bt bool = true
fmt.Println("bf is",bf,",bt is",bt)
布尔值可以用于一元操作符 !,表示逻辑非的意思,也可以用于二元操作符 &&、||,它们分别表示逻辑和、逻辑或。
字符串
Go 语言中的字符串可以表示为任意的数据,比如以下代码,在 Go 语言中,字符串通过类型 string 声明:
ch02/main.go
var s1 string = "Hello"
var s2 string = "世界"
fmt.Println("s1 is",s1,",s2 is",s2)
运行程序就可以看到打印的字符串结果。
在 Go 语言中,可以通过操作符 + 把字符串连接起来,得到一个新的字符串,比如将上面的 s1 和 s2 连接起来,如下所示:
ch02/main.go
fmt.Println("s1+s2=",s1+s2)
由于 s1 表示字符串“Hello”,s2 表示字符串“世界”,在终端输入 go run ch02/main.go 后,就可以打印出它们连接起来的结果“Hello世界”,如以下代码所示:
s1+s2= Hello世界
字符串也可以通过 += 运算符操作,你自己可以试试 s1+=s2 会得到什么新的字符串。
零值
零值其实就是一个变量的默认值,在 Go 语言中,如果我们声明了一个变量,但是没有对其进行初始化,那么 Go 语言会自动初始化其值为对应类型的零值。比如数字类的零值是 0,布尔型的零值是 false,字符串的零值是 "" 空字符串等。
通过下面的代码示例,就可以验证这些基础类型的零值:
ch02/main.go
var zi int
var zf float64
var zb bool
var zs string
fmt.Println(zi,zf,zb,zs)
变量
变量简短声明
有没有发现,上面我们演示的示例都有一个 var 关键字,但是这样写代码很烦琐。借助类型推导,Go 语言提供了变量的简短声明 :=,结构如下:
变量名:=表达式
借助 Go 语言简短声明功能,变量声明就会非常简洁,比如以上示例中的变量,可以通过如下代码简短声明:
i:=10
bf:=false
s1:="Hello"
在实际的项目实战中,如果你能为声明的变量初始化,那么就选择简短声明方式,这种方式也是使用最多的。
指针
在 Go 语言中,指针对应的是变量在内存中的存储位置,也就说指针的值就是变量的内存地址。通过 & 可以获取一个变量的地址,也就是指针。
在以下的代码中,pi 就是指向变量 i 的指针。要想获得指针 pi 指向的变量值,通过*pi这个表达式即可。尝试运行这段程序,会看到输出结果和变量 i 的值一样。
pi:=&i
fmt.Println(*pi)
赋值
在讲变量的时候,我说过变量是可以修改的,那么怎么修改呢?这就是赋值语句要做的事情。最常用也是最简单的赋值语句就是 =,如下代码所示:
i = 20
fmt.Println("i的新值是",i)
这样变量 i 就被修改了,它的新值是 20。
常量
一门编程语言,有变量就有常量,Go 语言也不例外。在程序中,常量的值是指在编译期就确定好的,一旦确定好之后就不能被修改,这样就可以防止在运行期被恶意篡改。
常量的定义
常量的定义和变量类似,只不过它的关键字是 const。
下面的示例定义了一个常量 name,它的值是“飞雪无情”。因为 Go 语言可以类型推导,所以在常量声明时也可以省略类型。
ch02/main.go
const name = "飞雪无情"
在 Go 语言中,只允许布尔型、字符串、数字类型这些基础类型作为常量。
iota
iota 是一个常量生成器,它可以用来初始化相似规则的常量,避免重复的初始化。假设我们要定义 one、two、three 和 four 四个常量,对应的值分别是 1、2、3 和 4,如果不使用 iota,则需要按照如下代码的方式定义:
const(
one = 1
two = 2
three =3
four =4
)
以上声明都要初始化,会比较烦琐,因为这些常量是有规律的(连续的数字),所以可以使用 iota 进行声明,如下所示:
const(
one = iota+1
two
three
four
)
fmt.Println(one,two,three,four)
你自己可以运行程序,会发现打印的值和上面初始化的一样,也是 1、2、3、4。
iota 的初始值是 0,它的能力就是在每一个有常量声明的行后面 +1,下面我来分解上面的常量:
- one=(0)+1,这时候 iota 的值为 0,经过计算后,one 的值为 1。
- two=(0+1)+1,这时候 iota 的值会 +1,变成了 1,经过计算后,two 的值为 2。
- three=(0+1+1)+1,这时候 iota 的值会再 +1,变成了 2,经过计算后,three 的值为 3。
- four=(0+1+1+1)+1,这时候 iota 的值会继续再 +1,变成了 3,经过计算后,four 的值为 4。
如果你定义更多的常量,就依次类推,其中 () 内的表达式,表示 iota 自身 +1 的过程。
字符串
字符串是 Go 语言中常用的类型,在前面的基础类型小节中已经有过基本的介绍。这一小结会为你更详细地介绍字符串的使用。
字符串和数字互转
Go 语言是强类型的语言,也就是说不同类型的变量是无法相互使用和计算的,这也是为了保证Go 程序的健壮性,所以不同类型的变量在进行赋值或者计算前,需要先进行类型转换。涉及类型转换的知识点非常多,这里我先介绍这些基础类型之间的转换,更复杂的会在后面的课时介绍。
以字符串和数字互转这种最常见的情况为例,如下面的代码所示:
ch02/main.go
i2s:=strconv.Itoa(i)
s2i,err:=strconv.Atoi(i2s)
fmt.Println(i2s,s2i,err)
通过包 strconv 的 Itoa 函数可以把一个 int 类型转为 string,Atoi 函数则用来把 string 转为 int。
同理对于浮点数、布尔型,Go 语言提供了 strconv.ParseFloat、strconv.ParseBool、strconv.FormatFloat 和 strconv.FormatBool 进行互转,你可以自己试试。
对于数字类型之间,可以通过强制转换的方式,如以下代码所示:
i2f:=float64(i)
f2i:=int(f64)
fmt.Println(i2f,f2i)
这种使用方式比简单,采用“类型(要转换的变量)”格式即可。采用强制转换的方式转换数字类型,可能会丢失一些精度,比如浮点型转为整型时,小数点部分会全部丢失,你可以自己运行上述示例,验证结果。
把变量转换为相应的类型后,就可以对相同类型的变量进行各种表达式运算和赋值了。
Strings 包
讲到基础类型,尤其是字符串,不得不提 Go SDK 为我们提供的一个标准包 strings。它是用于处理字符串的工具包,里面有很多常用的函数,帮助我们对字符串进行操作,比如查找字符串、去除字符串的空格、拆分字符串、判断字符串是否有某个前缀或者后缀等。掌握好它,有利于我们的高效编程。
以下代码是我写的关于 strings 包的一些例子,你自己可以根据strings 文档自己写一些示例,多练习熟悉它们。
ch02/main.go
//判断s1的前缀是否是H
fmt.Println(strings.HasPrefix(s1,"H"))
//在s1中查找字符串o
fmt.Println(strings.Index(s1,"o"))
//把s1全部转为大写
fmt.Println(strings.ToUpper(s1))
总结
本节课我讲解了变量、常量的声明、初始化,以及变量的简短声明,同时介绍了常用的基础类型、数字和字符串的转换以及 strings 工具包的使用,有了这些,你就可以写出功能更强大的程序。
在基础类型中,还有一个没有介绍的基础类型——复数,它不常用,就留给你来探索。这里给你一个提示:复数是用 complex 这个内置函数创建的。
本节课的思考题是:如何在一个字符串中查找某个字符串是否存在?提示一下,Go 语言自带的 strings 包里有现成的函数哦。
下一课时起,我将介绍 Go 语言的控制结构,如 if、switch 等,让你可以更加灵活的控制程序的执行流程。