Go语言(或 Golang)起源于 2007 年,并在 2009 年正式对外发布。Go 是非常年轻的一门语言,它的主要目标是“兼具 Python 等动态语言的开发速度和 C/C++ 等编译型语言的性能与安全性”。

Go语言是编程语言设计的又一次尝试,是对类C语言的重大改进,它不但能让你访问底层操作系统,还提供了强大的网络编程和并发编程支持。Go语言的用途众多,可以进行网络编程、系统编程、并发编程、分布式编程。

Go语言的推出,旨在不损失应用程序性能的情况下降低代码的复杂性,具有“部署简单、并发性好、语言设计良好、执行性能好”等优势,目前国内诸多 IT 公司均已采用Go语言开发项目。
Go语言有时候被描述为“C 类似语言”,或者是“21 世纪的C语言”。Go 从C语言继承了相似的表达式语法、控制流结构、基础数据类型、调用参数传值、指针等很多思想,还有C语言一直所看中的编译后机器码的运行效率以及和现有操作系统的无缝适配。
因为Go语言没有类和继承的概念,所以它和 Java 或 C++ 看起来并不相同。但是它通过接口(interface)的概念来实现多态性。Go语言有一个清晰易懂的轻量级类型系统,在类型之间也没有层级之说。因此可以说Go语言是一门混合型的语言。
此外,很多重要的开源项目都是使用Go语言开发的,其中包括 Docker、Go-Ethereum、Thrraform 和 Kubernetes。

8天精通go开发(第一天)_赋值

Go语言支持交叉编译,比如说你可以在运行 Linux 系统的计算机上开发可以在 Windows 上运行的应用程序。这是第一门完全支持 UTF-8 的编程语言,这不仅体现在它可以处理使用 UTF-8 编码的字符串,就连它的源码文件格式都是使用的 UTF-8 编码。Go语言做到了真正的国际化!

总之。go出现之前的语言在执行效率和开发效率上都不能做到鱼与熊掌兼得,如下所示


- 执行效率 execution speed:C/C++ > Java > Python

- 开发效率 developing efficiency:Python > Java > C/C++


所以,一门同时拥有高效的执行速度、编译速度和开发速度的编程语言呼之欲出,这就是Go语言诞生的前戏与大环境。

Go 是编译型语言

Go 使用编译器来编译代码。编译器将源代码编译成二进制(或字节码)格式;在编译代码时,编译器检查错误、优化性能并输出可在不同平台上运行的二进制文件。要创建并运行 Go 程序,程序员必须执行如下步骤。


使用文本编辑器创建 Go 程序; 

保存文件; 

编译程序; 

运行编译得到的可执行文件。



这不同于 Python、Ruby 和 JavaScript 等语言,它们不包含编译步骤。Go 自带了编译器,因此无须单独安装编译器。

里程碑式的大事件


8天精通go开发(第一天)_赋值_02


Go语言这两年在语言排行榜(tiobe.com/tiobe-index/)增长曲线如下,总体还是处于上涨趋势的

8天精通go开发(第一天)_赋值_03


go语言的优点

  • 可直接编译成机器码,不依赖其他库,glibc的版本有一定要求,部署就是扔一个文件上去就完成了。
  • 静态类型语言,但是有动态语言的感觉,静态类型的语言就是可以在编译的时候检查出来隐藏的大多数问题,动态语言的感觉就是有很多的包可以使用,写起来的效率很高。
  • 语言层面支持并发,这个就是Go最大的特色,天生的支持并发。Go就是基因里面支持的并发,可以充分的利用多核,很容易的使用并发。
  • 内置runtime,支持垃圾回收,这属于动态语言的特性之一吧,虽然目前来说GC(内存垃圾回收机制)不算完美,但是足以应付我们所能遇到的大多数情况,特别是Go1.1之后的GC。
  • 简单易学,Go语言的作者都有C的基因,那么Go自然而然就有了C的基因,那么Go关键字是25个,但是表达能力很强大,几乎支持大多数你在其他语言见过的特性:继承、重载、对象等。
  • 丰富的标准库,Go目前已经内置了大量的库,特别是网络库非常强大。
  • 内置强大的工具,Go语言里面内置了很多工具链,最好的应该是gofmt工具,自动化格式化代码,能够让团队review变得如此的简单,代码格式一模一样,想不一样都很困难。
  • 跨平台编译,如果你写的Go代码不包含cgo,那么就可以做到window系统编译linux的应用,如何做到的呢?Go引用了plan9的代码,这就是不依赖系统的信息。

Go 语言环境安装

Go 语言支持以下系统:

  • Linux
  • FreeBSD
  • Mac OS X(也称为 Darwin)
  • Windows

安装包下载地址为:https://golang.org/dl/。

如果打不开可以使用这个地址:https://golang.google.cn/dl/。

各个系统对应的包名:


8天精通go开发(第一天)_赋值_04

8天精通go开发(第一天)_ide_05

Windows 系统下安装

Windows 下可以使用 .msi 后缀(在下载列表中可以找到该文件,如go1.4.2.windows-amd64.msi)的安装包来安装。

默认情况下 .msi 文件会安装在 c:\Go 目录下。你可以将 c:\Go\bin 目录添加到 Path 环境变量中。添加后你需要重启命令窗口才能生效。

安装测试

创建工作目录 C:\>Go_WorkSpace

package main


import "fmt"


func main() {
fmt.Println("Hello, World!")
}

使用 go 命令执行以上代码输出结果如下:

C:\Go_WorkSpace>go run test.go


Hello, World!

LiteIDE搭建Go语言开发环境

LiteIDE 是一款专为Go语言开发而设计的开源、跨平台、轻量级集成开发环境(IDE),基于 Qt 开发(一个跨平台的 C++ 框架),支持 Windows、Linux 和 Mac OS X 平台。LiteIDE 的第一个版本发布于 2011 年 1 月初,是最早的面向Go语言的 IDE 之一。至今为止,LiteIDE 已经发布到版本 X36。

下载 LiteIDE

大家可以通过下面三种方式来下载 LiteIDE 安装包:

  • 开源软件平台:https://sourceforge.net/projects/liteide/files/;
  • GitHub:https://github.com/visualfc/liteide/releases;
  • 百度网盘:https://pan.baidu.com/s/1wYHSEfG1TJRC2iOkE_saJg(密码:jzrc)。

我们需要下载 Windows 版本的 LiteIDE 安装包。

8天精通go开发(第一天)_ide_06


因为 LiteIDE 是绿色版的,无需安装,所以下载完成后,得到的是一个 ZIP 格式的压缩文件。

8天精通go开发(第一天)_ide_07

将压缩包解压到任意目录,这里我们将压缩包解压到了 D 盘,解压后会得到一个名为“liteide”的文件夹。

8天精通go开发(第一天)_ide_08

进入 LiteIDE 文件夹中的 bin 文件夹,可以在 bin 文件夹中找到名为“liteide.exe”的文件,它就是 LiteIDE 的启动程序。

8天精通go开发(第一天)_go语言_09

双击运行“liteide.exe”就可以正常打开 LiteIDE 了,为了方便以后的使用,建议大家在桌面创建 LiteIDE 的快捷方式(在“liteide.exe”上右键→“发送到”→“桌面快捷方式”)。

8天精通go开发(第一天)_ide_10

配置 LiteIDE

修改当前的运行环境,需要根据系统的不同设置对应的运行环境,因为我们使用的是 64 位的 Windows 系统,所以这里需要将运行环境设置为 Win64 。

8天精通go开发(第一天)_赋值_11

配置当前运行环境,点击“工具”,在下拉菜单中选择“编辑当前环境”,如下图所示。

8天精通go开发(第一天)_赋值_12

在打开的文件中找到“GOROOT=xxx”,并将其修改为环境变量中 GOROOT 对应的值,如下所示。(注意:修改完成后记得保存)

8天精通go开发(第一天)_赋值_13

如果记不清了也没关系,可以在 CMD 窗口中使用​​go env​​命令查看 GOROOT 对应的值。

8天精通go开发(第一天)_go语言_14

添加自定义 GOPATH,在“工具”菜单中找到“管理 GOPATH/Modules...”,如下图所示。(注意:因为系统中已经包含了一个默认的 GOPATH,所以此步骤不是必须的,了解即可)

8天精通go开发(第一天)_赋值_15

在弹出的窗口中找到“使用自定义 GOPATH”,将其勾选,然后点击“添加目录”,选择合适的目录即可。

8天精通go开发(第一天)_ide_16

新建项目

到这里 LiteIDE 已经基本上配置完成了,下面我们就来创建一个项目来测试一下。

首先,在“文件”菜单中选择“新建”,在弹出的窗口中,选择系统默认的 GOPATH 路径,模板选择“Go1 Command Project”,最后填写项目名称,并选择合适的目录,确认无误后点击“OK”。

8天精通go开发(第一天)_go语言_17

新建好的项目是下面这样的,编辑器自动为我们创建了两个文件,并在“main.go”中生成了简单的代码。

8天精通go开发(第一天)_go语言_18

点击编辑器右上方的“BR”按钮,就可以运行“main.go”中的代码,同时会在当前目录下生成一个 .exe 的可执行文件。

8天精通go开发(第一天)_ide_19


如果遇到问题

解决go: go.mod file not found in current directory or any parent directory;see ‘go help modules‘

解决办法

开启go modules功能,命令行输入

go env -w GO111MODULE=on

在该项目目录下,初始化Go moudle,运行下面命令

go mod init xxx //xxx代表文件夹名

go中文帮助文档

http://books.studygolang.com/#

go构建和运行

go build 把go的源文件编译并且和它所依赖的包打包成可执行文件。

go run 也要进行编译,但是不打包。

go run 运行go源文件要比go build 打包正的可执行文件之后再运行要慢一些,所以在生产环境上我们其实是要求打包成exe。

go build 打包后的包要比go源文件大好多,其实是因为打包依赖了其他的文件。

go数据类型

Go语言作为类C语言,支持常规的基础数据类型的的同时,支持常用的高级数据类型。他们是: 整数,int,uint,int8,uint8,int16,uint16,int32,uint32,int64,uint64 浮点(小数),float32,float64 复数,complex 字符,byte,rune 字符串,string 布尔,bool 指针,pointor 数组,array nil 切片,slice。(引用类型) 映射,map 结构体,struct 接口,interface 函数,func()

整数,int

支持的整型分类很细,主要差异是占用的存储空间不同。基于有无符号列出为:有符号的:int,int8,int16,int32,int64 无符号的:uint,uint8,uint16,uint32,uint64

其中int, unit是基于处理器架构的。意味着会根据处理器来确定是32bit还是64bit。使用时,常常使用int。或者使用int32保证兼容。在类型推导时,推导的整型为int,其他长度整型需要在定义变量时强制声明。整型的默认值为0。

浮点数,float

支持IEEE-754标准浮点数。支持32bit和64bit的浮点数float32和float64。在类型推导时,推导的浮点型为float64。float32需要在定义变量时强制声明。

复数,complex32,complex64

原生支持复数。支持32bit+32bit复数complex64,和64bit+64bit的复数complex128。

字符,byte,rune

使用单引号定义字符型数据,字符指的是单个字符。有两种byte和rune:byte:单字节字符,是uint8的别名。用于存储ASCII字符集字符 rune:多字节字符,是int32的别名。用于存储unicode字符集字符。在类型推导时,推导的字符型为rune。byte需要在定义变量时强制声明。字符的默认值是0。字符的本质就是整数型,根据字符集得到对应的字符。

字符串,string

原生支持字符串。使用双引号("")或反引号(``)定义,内部是utf-8编码。双引号:"", 用于单行字符串。反引号:``,用于定义多行字符串,内部会原样解析。字符串支持转义字符,列表如下: \r 回车符(返回行首) \n 换行符(直接跳到下一行的同列位置) \t 制表符 \' 单引号 \" 双引号 \\ 反斜杠 * \uXXXX Unicode字符码值转义。

布尔型,bool

布尔型的值只可以是常量 true 或者 false。

指针,pointer

指针类型用于存储变量地址。使用运算符 & , *完成操作。使用运算符 p:=&v 可以获取v变量的地址。p中的数据格式类似0xc0000b9528,是一个内存地址的16进制表示。使用运算符 *p 可以获取指针p指向变量v的值。


8天精通go开发(第一天)_ide_20

在Go语言中,指针主要用于: 类型指针,对指针指向的数据进行修改。函数调用时传递参数使用指针,避免值传递拷贝额外数据。注意类型指针不能进行偏移和运算。 切片,由指向起始元素的原始指针、元素数量和容量组成。

数组,array

数组是一段固定长度的连续内存区域。是具有相同类型数据元素序列。元素类型支持任意内置类型。数组从声明时就确定长度,可以修改数组元素,但是数组不可修改长度。使用 [长度]类型 进行数组的声明。

会使用类型默认值初始化元素。数组不是引用类型,变量间为值传递。

可以使用range配合循环结构完成遍历。

nil

nil,可以理解为未初始化引用。是以下类型的默认初始值:pointers -> nil slices -> nil maps -> nil channels -> nil functions -> nil interfaces -> nil

切片,slice

切片是一个拥有相同类型元素的可变长度的序列。与数组的固定长度不同,切片也被称之为动态数组。Go提供了4中方案定义切片:

make ( []Type ,length, capacity )
make ( []Type, length)
[]Type{}
[]Type{value1 , value2 , ... , valueN }

或者从数组或切片生成新切片: slice [开始索引:结束索引] slice 表示目标切片对象。 开始索引和结束索引对应目标切片的索引。 不包含结束索引对应的元素 缺省开始索引,表示从头到结束索引。 缺省结束索引,表示从开始索引到末尾。 两者同时缺省时,全部切取,与切片本身等效。 * 两者同时为0时,等效于空切片,一般用于切片复位。

var arr = [4]string{"a", "b", "c", "d"}
var sli = arr[1:3] // ["b", "c"]


以从数组创建切片为例,理解切片,定义语法如上所示,下图为slice:


8天精通go开发(第一天)_ide_21

切片的实现是由一个底层数组以及其上面的动态位置,尺寸来实现。由内部由指向起始元素的指针、元素数量length和容量capacity组成。其中: 指针ptr,用于指向切片在底层数组的起始位置。 尺寸len,用于记录切片内元素数量。* 容量cap,当前切片最大容量,也就是底层数组的容量。可以动态分配。

切片为引用类型。切片的默认初始值为nil。切片支持: len()尺寸, cap()容量, append()追加元素等操作。详见切片的使用。

映射,map

Go语言中的键值对(key->value)集合,称之为映射map。创建语法:var m = map[key_type]value_type{key1: value1, key2: value2} var m = make(map[key_type]value_type) 示例,字符串型下标,字符串型值:

var m = make(map[string]string) //make()会分配内存空间,初始化。
m["east"] = "东"
m["west"] = "西"
fmt.Println(m["east"]) // 东
fmt.Println(m["west"]) // 西


// map演示
var m = map[string]string{"east": "东", "west": "西"}

支持遍历操作,使用range:

for k, v := range mapVar {
fmt.Println(k, v)
}

映射是引用类型。

结构体,struct

Go语言使用结构体来描述现实业务逻辑中实体。是自定义类型。结构体是由一系列数据构成的数据集合,一系列数据类型可以不同。定义结构体,使用struct关键字:type 结构体名 struct { 成员1 类型 成员2 类型 … } 

接口,interface

接口是一种协议,用来规范方法的调用和定义的协议,目的是保证设计的一致性,便于模块化开发以及通讯。Go语言中,也视为一种类型。定义语法:type 接口名 interface { 方法1( 参数列表 ) 返回值类型列表 方法2( 参数列表 ) 返回值类型列表 … } 可以理解成没有方法体的方法。

函数,func()

Go语言中,函数可以作为数据存储变量中,此时变量为函数类型func()。可以通过该变量访问到这个函数。可以用在结构体成员定义上。


关键字

1.25个关键字或保留字

break default func interface select case defer go map struct chan 
else goto package switch const fallthrough if range type continue
for import return var

2.36 个预定义标识符


append bool byte cap close complex complex64 complex128 uint16 copy 
false float32 float64 imag int int8 int16 uint32 int32 int64 iota
len make new nil panic uint64 print println real recover string true
uint uint8 uintptr

注:
1.程序一般由关键字、常量、变量、运算符、类型和函数组成。
2.程序中可能会使用到这些分隔符:括号 (),中括号 [] 和大括号 {}。
3.程序中可能会使用到这些标点符号:.、,、;、: 和 …。

标识符

标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母(A~ Z和a~ z)数字(0~9)、下划线“_”组成的序列,但是第一个字符必须是字母或下划线而不能是数字。

标变量声明和赋值

变量是几乎所有编程语言中最基本的组成元素。从本质上说,变量相当于是对一块数据存储空间的命名,程序可以通过定义一个变量来申请一块数据存储空间,之后可以通过引用变量名来使用这块存储空间。

8天精通go开发(第一天)_ide_22


自动推导类型

// 一. 赋值,赋值前,必须进行变量的声明
var a int
a = 10
fmt.Println("a = ", a)


// 二. :=b 自动推导类型,先声明b的类型,再给b赋值为20
// 自动推导,同一个变量名只能使用一次,用于初始化
b := 20
b = 23
fmt.Println("b = ", b)


// 三 .b := 30 //前面已经有变量b,不能再新建一个变量b

变量多重赋值

变量多重赋值是指多个变量同时赋值。在Go语言语法中,变量初始化和变量赋值是两个不同的概念,Go语言的变量赋值与其他语言一样,但是Go语言提供了其他程序员期待已久的多重赋值功能,可以实现变量交换。多重赋值让Go语言比其他语言减少了代码量。
以简单的算法交换变量为例,传统写法如下所示。

var a int = 10
var b int = 20
var tmp int
tmp = a
a = b
b = t

新定义的变量是需要内存的,于是有人设计了新的算法来取代中间变量,其中一种写法如下所示。

var a int = 10
var b int = 20
a = a ^ b
b = b ^ a
a = a ^ b

以Go语言的多重赋值功能为例,写法如下所示。

var a int = 10
var b int = 20
b, a = a, b

从以上例子来看,Go语言的写法明显简洁了许多,需要注意的是,多重赋值时,左值和右值按照从左到右的顺序赋值。这种方法在错误处理和函数当中会大量使用。

匿名变量

Go语言的函数可以返回多个值,而事实上并不是所有的返回值都用得上,那么就可以使用匿名变量。匿名变量的特点是一个下画线​​_​​​,​​_​​​本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。

使用匿名变量时,只需要在变量声明的地方使用下画线替换即可,下面通过一个简单的示例来演示一下。

【示例】定义一个函数,功能为返回两个 int 类型变量,第一个返回 10,第二个返回 20,第一次调用舍弃第二个返回值,第二次调用舍弃第一个返回值,具体语法格式如下所示。

package main
import (
"fmt"
)
func GetData() (int, int) {
return 100, 200
}
func main() {
a, _ := GetData() // 舍弃第二个返回值
_, b := GetData() // 舍弃第一个返回值
fmt.Println(a, b)
}

运行结果如下:

100 200

匿名变量不占用内存空间,不会分配内存,而且匿名变量与匿名变量之间也不会因为多次声明而无法使用。

常量

常量是一个简单值的标识符,在程序运行时,不会被修改的量。

常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。

常量的定义格式:

const identifier [type] = value

你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。

  • 显式类型定义: ​​const b string = "abc"​
  • 隐式类型定义: ​​const b = "abc"​

多个相同类型的声明可以简写为:

const c_name1, c_name2 = value1, value2

以下实例演示了常量的应用:

package main


import "fmt"


func main() {
const LENGTH int = 10
const WIDTH int = 5
var area int
const a, b, c = 1, false, "str" //多重赋值


area = LENGTH * WIDTH
fmt.Printf("面积为 : %d", area)
println()
println(a, b, c)
}

以上实例运行结果为:

面积为 : 50
1 false str

常量还可以用作枚举:

const (
Unknown = 0
Female = 1
Male = 2
)


数字 0、1 和 2 分别代表未知性别、女性和男性。

常量可以用len(), cap(), unsafe.Sizeof()函数计算表达式的值。常量表达式中,函数必须是内置函数,否则编译不过:

package main


import "unsafe"
const (
a = "abc"
b = len(a)
c = unsafe.Sizeof(a)
)


func main(){
println(a, b, c)
}

以上实例运行结果为:

abc 3 16


iota

iota,特殊常量,可以认为是一个可以被编译器修改的常量。

iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

iota 可以被用作枚举值:

const (
a = iota
b = iota
c = iota
)

第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:

const (
a = iota
b
c
)

iota 用法

package main


import "fmt"


func main() {
const (
a = iota //0
b //1
c //2
d = "ha" //独立值,iota += 1
e //"ha" iota += 1
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢复计数
i //8
)
fmt.Println(a,b,c,d,e,f,g,h,i)
}

以上实例运行结果为:

0 1 2 ha ha 100 100 7 8

再看个有趣的的 iota 实例:

package main


import "fmt"
const (
i=1<<iota
j=3<<iota
k
l
)


func main() {
fmt.Println("i=",i)
fmt.Println("j=",j)
fmt.Println("k=",k)
fmt.Println("l=",l)
}

以上实例运行结果为:

i= 1
j= 6
k= 12
l= 24


iota 表示从 0 开始自动加 1,所以 i=1<<0, j=3<<1(<< 表示左移的意思),即:i=1, j=6,这没问题,关键在 k 和 l,从输出结果看 k=3<<2,l=3<<3。

简单表述:

  • i=1:左移 0 位,不变仍为 1。
  • j=3:左移 1 位,变为二进制 110,即 6。
  • k=3:左移 2 位,变为二进制 1100,即 12。
  • l=3:左移 3 位,变为二进制 11000,即 24。

注:<<n==*(2^n)。

fmt.Sprintf(格式化输出)

格式化在逻辑中非常常用。使用格式化函数,要注意写法:

fmt.Sprintf(格式化样式, 参数列表…)
  • 格式化样式:字符串形式,格式化动词以​​%​​开头。
  • 参数列表:多个参数以逗号分隔,个数必须与格式化样式中的个数一一对应,否则运行时会报错。

在 Go 语言中,格式化的命名延续C语言风格:

var progress = 2
var target = 8


// 两参数格式化
title := fmt.Sprintf("已采集%d个药草, 还需要%d个完成任务", progress, target)


fmt.Println(title)


pi := 3.14159
// 按数值本身的格式输出
variant := fmt.Sprintf("%v %v %v", "月球基地", pi, true)


fmt.Println(variant)


// 匿名结构体声明, 并赋予初值
profile := &struct {
Name string
HP int
}{
Name: "rat",
HP: 150,
}


fmt.Printf("使用'%%+v' %+v\n", profile)


fmt.Printf("使用'%%#v' %#v\n", profile)


fmt.Printf("使用'%%T' %T\n", profile)

代码输出如下:

已采集2个药草, 还需要8个完成任务
"月球基地" 3.14159 true
使用'%+v' &{Name:rat HP:150}
使用'%#v' &struct { Name string; HP int }{Name:"rat", HP:150}
使用'%T' *struct { Name string; HP int }C语言中, 使用%d代表整型参数

下表中标出了常用的一些格式化样式中的动词及功能。

8天精通go开发(第一天)_go语言_23


类型别名

类型别名是 Go 1.9 版本添加的新功能,主要用于解决代码升级、迁移中存在的类型兼容性问题。在 C/C++ 语言中,代码重构升级可以使用宏快速定义一段新的代码,Go语言中没有选择加入宏,而是解决了重构中最麻烦的类型名变更问题。

在 Go 1.9 版本之前定义内建类型的代码是这样写的:

type byte uint8
type rune int32

而在 Go 1.9 版本之后变为:

type byte = uint8
type rune = int32

这个修改就是配合类型别名而进行的修改。

定义类型别名的写法为:

type TypeAlias = Type

类型别名规定:TypeAlias 只是 Type 的别名,本质上 TypeAlias 与 Type 是同一个类型,就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。

类型别名与类型定义表面上看只有一个等号的差异,那么它们之间实际的区别有哪些呢?下面通过一段代码来理解。

package main


import (
"fmt"
)


// 将NewInt定义为int类型
type NewInt int


// 将int取一个别名叫IntAlias
type IntAlias = int


func main() {


// 将a声明为NewInt类型
var a NewInt
// 查看a的类型名
fmt.Printf("a type: %T\n", a)


// 将a2声明为IntAlias类型
var a2 IntAlias
// 查看a2的类型名
fmt.Printf("a2 type: %T\n", a2)
}

代码运行结果:

a type: main.NewInt
a2 type: int

代码说明如下:

  • 第 8 行,将 NewInt 定义为 int 类型,这是常见的定义类型的方法,通过 type 关键字的定义,NewInt 会形成一种新的类型,NewInt 本身依然具备 int 类型的特性。
  • 第 11 行,将 IntAlias 设置为 int 的一个别名,使用 IntAlias 与 int 等效。
  • 第 16 行,将 a 声明为 NewInt 类型,此时若打印,则 a 的值为 0。
  • 第 18 行,使用​​%T​​格式化参数,打印变量 a 本身的类型。
  • 第 21 行,将 a2 声明为 IntAlias 类型,此时打印 a2 的值为 0。
  • 第 23 行,打印 a2 变量的类型。

结果显示 a 的类型是 main.NewInt,表示 main 包下定义的 NewInt 类型,a2 类型是 int,IntAlias 类型只会在代码中存在,编译完成时,不会有 IntAlias 类型。

运算符

运算符用于在程序运行时执行数学或逻辑运算。

Go 语言内置的运算符有:

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 位运算符
  • 赋值运算符
  • 其他运算符

接下来让我们来详细看看各个运算符的介绍。

算术运算符

下表列出了所有Go语言的算术运算符。假定 A 值为 10,B 值为 20。

8天精通go开发(第一天)_赋值_24

以下实例演示了各个算术运算符的用法:

package main


import "fmt"


func main() {


var a int = 21
var b int = 10
var c int


c = a + b
fmt.Printf("第一行 - c 的值为 %d\n", c )
c = a - b
fmt.Printf("第二行 - c 的值为 %d\n", c )
c = a * b
fmt.Printf("第三行 - c 的值为 %d\n", c )
c = a / b
fmt.Printf("第四行 - c 的值为 %d\n", c )
c = a % b
fmt.Printf("第五行 - c 的值为 %d\n", c )
a++
fmt.Printf("第六行 - a 的值为 %d\n", a )
a=21 // 为了方便测试,a 这里重新赋值为 21
a--
fmt.Printf("第七行 - a 的值为 %d\n", a )
}

以上实例运行结果:

第一行 - c 的值为 31
第二行 - c 的值为 11
第三行 - c 的值为 210
第四行 - c 的值为 2
第五行 - c 的值为 1
第六行 - a 的值为 22
第七行 - a 的值为 20


关系运算符

下表列出了所有Go语言的关系运算符。假定 A 值为 10,B 值为 20。

8天精通go开发(第一天)_赋值_25

以下实例演示了关系运算符的用法:

package main


import "fmt"


func main() {
var a int = 21
var b int = 10


if( a == b ) {
fmt.Printf("第一行 - a 等于 b\n" )
} else {
fmt.Printf("第一行 - a 不等于 b\n" )
}
if ( a < b ) {
fmt.Printf("第二行 - a 小于 b\n" )
} else {
fmt.Printf("第二行 - a 不小于 b\n" )
}

if ( a > b ) {
fmt.Printf("第三行 - a 大于 b\n" )
} else {
fmt.Printf("第三行 - a 不大于 b\n" )
}
/* Lets change value of a and b */
a = 5
b = 20
if ( a <= b ) {
fmt.Printf("第四行 - a 小于等于 b\n" )
}
if ( b >= a ) {
fmt.Printf("第五行 - b 大于等于 a\n" )
}
}

以上实例运行结果:

第一行 - a 不等于 b
第二行 - a 不小于 b
第三行 - a 大于 b
第四行 - a 小于等于 b
第五行 - b 大于等于 a


逻辑运算符

下表列出了所有Go语言的逻辑运算符。假定 A 值为 True,B 值为 False。

8天精通go开发(第一天)_ide_26

以下实例演示了逻辑运算符的用法:

package main


import "fmt"


func main() {
var a bool = true
var b bool = false
if ( a && b ) {
fmt.Printf("第一行 - 条件为 true\n" )
}
if ( a || b ) {
fmt.Printf("第二行 - 条件为 true\n" )
}
/* 修改 a 和 b 的值 */
a = false
b = true
if ( a && b ) {
fmt.Printf("第三行 - 条件为 true\n" )
} else {
fmt.Printf("第三行 - 条件为 false\n" )
}
if ( !(a && b) ) {
fmt.Printf("第四行 - 条件为 true\n" )
}
}

以上实例运行结果:

第二行 - 条件为 true
第三行 - 条件为 false
第四行 - 条件为 true


位运算符

位运算符对整数在内存中的二进制位进行操作。

下表列出了位运算符 &, |, 和 ^ 的计算:

8天精通go开发(第一天)_赋值_27

假定 A = 60; B = 13; 其二进制数转换为:

A = 0011 1100


B = 0000 1101


-----------------


A&B = 0000 1100


A|B = 0011 1101


A^B = 0011 0001

Go 语言支持的位运算符如下表所示。假定 A 为60,B 为13:

8天精通go开发(第一天)_赋值_28

以下实例演示了位运算符的用法:

package main


import "fmt"


func main() {


var a uint = 60 /* 60 = 0011 1100 */
var b uint = 13 /* 13 = 0000 1101 */
var c uint = 0


c = a & b /* 12 = 0000 1100 */
fmt.Printf("第一行 - c 的值为 %d\n", c )


c = a | b /* 61 = 0011 1101 */
fmt.Printf("第二行 - c 的值为 %d\n", c )


c = a ^ b /* 49 = 0011 0001 */
fmt.Printf("第三行 - c 的值为 %d\n", c )


c = a << 2 /* 240 = 1111 0000 */
fmt.Printf("第四行 - c 的值为 %d\n", c )


c = a >> 2 /* 15 = 0000 1111 */
fmt.Printf("第五行 - c 的值为 %d\n", c )
}

以上实例运行结果:

第一行 - c 的值为 12
第二行 - c 的值为 61
第三行 - c 的值为 49
第四行 - c 的值为 240
第五行 - c 的值为 15


赋值运算符

下表列出了所有Go语言的赋值运算符。

8天精通go开发(第一天)_赋值_29

以下实例演示了赋值运算符的用法:

package main


import "fmt"


func main() {
var a int = 21
var c int


c = a
fmt.Printf("第 1 行 - = 运算符实例,c 值为 = %d\n", c )


c += a
fmt.Printf("第 2 行 - += 运算符实例,c 值为 = %d\n", c )


c -= a
fmt.Printf("第 3 行 - -= 运算符实例,c 值为 = %d\n", c )


c *= a
fmt.Printf("第 4 行 - *= 运算符实例,c 值为 = %d\n", c )


c /= a
fmt.Printf("第 5 行 - /= 运算符实例,c 值为 = %d\n", c )


c = 200;


c <<= 2
fmt.Printf("第 6行 - <<= 运算符实例,c 值为 = %d\n", c )


c >>= 2
fmt.Printf("第 7 行 - >>= 运算符实例,c 值为 = %d\n", c )


c &= 2
fmt.Printf("第 8 行 - &= 运算符实例,c 值为 = %d\n", c )


c ^= 2
fmt.Printf("第 9 行 - ^= 运算符实例,c 值为 = %d\n", c )


c |= 2
fmt.Printf("第 10 行 - |= 运算符实例,c 值为 = %d\n", c )


}

以上实例运行结果:

第 1 行 - =  运算符实例,c 值为 = 21
第 2 行 - += 运算符实例,c 值为 = 42
第 3 行 - -= 运算符实例,c 值为 = 21
第 4 行 - *= 运算符实例,c 值为 = 441
第 5 行 - /= 运算符实例,c 值为 = 21
第 6行 - <<= 运算符实例,c 值为 = 800
第 7 行 - >>= 运算符实例,c 值为 = 200
第 8 行 - &= 运算符实例,c 值为 = 0
第 9 行 - ^= 运算符实例,c 值为 = 2
第 10 行 - |= 运算符实例,c 值为 = 2


其他运算符

下表列出了Go语言的其他运算符。

8天精通go开发(第一天)_赋值_30

以下实例演示了其他运算符的用法:

package main


import "fmt"


func main() {
var a int = 4
var b int32
var c float32
var ptr *int


/* 运算符实例 */
fmt.Printf("第 1 行 - a 变量类型为 = %T\n", a );
fmt.Printf("第 2 行 - b 变量类型为 = %T\n", b );
fmt.Printf("第 3 行 - c 变量类型为 = %T\n", c );


/* & 和 * 运算符实例 */
ptr = &a /* 'ptr' 包含了 'a' 变量的地址 */
fmt.Printf("a 的值为 %d\n", a);
fmt.Printf("*ptr 为 %d\n", *ptr);
}

以上实例运行结果:

第 1 行 - a 变量类型为 = int
第 2 行 - b 变量类型为 = int32
第 3 行 - c 变量类型为 = float32
a 的值为 4
*ptr 为 4


运算符优先级

有些运算符拥有较高的优先级,二元运算符的运算方向均是从左至右。下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低:

8天精通go开发(第一天)_ide_31

当然,你可以通过使用括号来临时提升某个表达式的整体运算优先级。

以上实例运行结果:

package main


import "fmt"


func main() {
var a int = 20
var b int = 10
var c int = 15
var d int = 5
var e int;


e = (a + b) * c / d; // ( 30 * 15 ) / 5
fmt.Printf("(a + b) * c / d 的值为 : %d\n", e );


e = ((a + b) * c) / d; // (30 * 15 ) / 5
fmt.Printf("((a + b) * c) / d 的值为 : %d\n" , e );


e = (a + b) * (c / d); // (30) * (15/5)
fmt.Printf("(a + b) * (c / d) 的值为 : %d\n", e );


e = a + (b * c) / d; // 20 + (150/5)
fmt.Printf("a + (b * c) / d 的值为 : %d\n" , e );
}

以上实例运行结果:

(a + b) * c / d 的值为 : 90
((a + b) * c) / d 的值为 : 90
(a + b) * (c / d) 的值为 : 90
a + (b * c) / d 的值为 : 50


Go语言if else(分支结构)

在Go语言中,关键字 if 是用于测试某个条件(布尔型或逻辑型)的语句,如果该条件成立,则会执行 if 后由大括号​​{}​​括起来的代码块,否则就忽略该代码块继续执行后续的代码。

if condition {
// do something
}

如果存在第二个分支,则可以在上面代码的基础上添加 else 关键字以及另一代码块,这个代码块中的代码只有在条件不满足时才会执行,if 和 else 后的两个代码块是相互独立的分支,只能执行其中一个。

if condition {
// do something
} else {
// do something
}

如果存在第三个分支,则可以使用下面这种三个独立分支的形式:

if condition1 {
// do something
} else if condition2 {
// do something else
}else {
// catch-all or default
}

else if 分支的数量是没有限制的,但是为了代码的可读性,还是不要在 if 后面加入太多的 else if 结构,如果必须使用这种形式,则尽可能把先满足的条件放在前面。

关键字 if 和 else 之后的左大括号​​​{​​​必须和关键字在同一行,如果你使用了 else if 结构,则前段代码块的右大括号​​}​​​必须和 else if 关键字在同一行,这两条规则都是被编译器强制规定的。

非法的 Go 代码:

if x{
}
else { // 无效的
}

要注意的是,在使用 gofmt 格式化代码之后,每个分支内的代码都会缩进 4 个或 8 个空格,或者是 1 个 tab,并且右大括号​​}​​​与对应的 if 关键字垂直对齐。

在有些情况下,条件语句两侧的括号是可以被省略的,当条件比较复杂时,则可以使用括号让代码更易读,在使用 &&、|| 或 ! 时可以使用括号来提升某个表达式的运算优先级,并提高代码的可读性。

举例

通过下面的例子来了解 if 的写法:

var ten int = 11
if ten > 10 {
fmt.Println(">10")
} else {
fmt.Println("<=10")
}

代码输出如下:

>10

代码说明如下:

  • 第 1 行,声明整型变量并赋值 11。
  • 第 2 行,判断当 ten 的值大于 10 时执行第 3 行,否则执行第 4 行。
  • 第 3 和第 5 行,分别打印大于 10 和小于等于 10 时的输出。

特殊写法

if 还有一种特殊的写法,可以在 if 表达式之前添加一个执行语句,再根据变量值进行判断,代码如下:

if err := Connect(); err != nil {
fmt.Println(err)
return
}

Connect 是一个带有返回值的函数,err:=Connect() 是一个语句,执行 Connect 后,将错误保存到 err 变量中。

err != nil 才是 if 的判断表达式,当 err 不为空时,打印错误并返回。

这种写法可以将返回值与判断放在一行进行处理,而且返回值的作用范围被限制在 if、else 语句组合中。


提示 

在编程中,变量的作用范围越小,所造成的问题可能性越小,每一个变量代表一个状态,有状态的地方,状态就会被修改,函数的局部变量只会影响一个函数的执行,但全局变量可能会影响所有代码的执行状态,因此限制变量的作用范围对代码的稳定性有很大的帮助。


switch case语句

Go语言的 switch 要比C语言的更加通用,表达式不需要为常量,甚至不需要为整数,case 按照从上到下的顺序进行求值,直到找到匹配的项,如果 switch 没有表达式,则对 true 进行匹配,因此,可以将 if else-if else 改写成一个 switch。

相对于C语言和 Java 等其它语言来说,Go语言中的 switch 结构使用上更加灵活,语法设计尽量以使用方便为主。

基本写法

Go语言改进了 switch 的语法设计,case 与 case 之间是独立的代码块,不需要通过 break 语句跳出当前 case 代码块以避免执行到下一行,示例代码如下:

var a = "hello"
switch a {
case "hello":
fmt.Println(1)
case "world":
fmt.Println(2)
default:
fmt.Println(0)
}

代码输出如下:

1

上面例子中,每一个 case 均是字符串格式,且使用了 default 分支,Go语言规定每个 switch 只能有一个 default 分支。

1) 一分支多值

当出现多个 case 要放在一起的时候,可以写成下面这样:

var a = "mum"
switch a {
case "mum", "daddy":
fmt.Println("family")
}

不同的 case 表达式使用逗号分隔。

2) 分支表达式

case 后不仅仅只是常量,还可以和 if 一样添加表达式,代码如下:

var r int = 11
switch {
case r > 10 && r < 20:
fmt.Println(r)
}

注意,这种情况的 switch 后面不再需要跟判断变量。

跨越 case 的 fallthrough——兼容C语言的 case 设计

在Go语言中 case 是一个独立的代码块,执行完毕后不会像C语言那样紧接着执行下一个 case,但是为了兼容一些移植代码,依然加入了 fallthrough 关键字来实现这一功能,代码如下:

var s = "hello"
switch {
case s == "hello":
fmt.Println("hello")
fallthrough
case s != "world":
fmt.Println("world")
}

代码输出如下:

hello
world

新编写的代码,不建议使用 fallthrough。

for range(键值循环)

for range 结构是Go语言特有的一种的迭代结构,在许多情况下都非常有用,for range 可以遍历数组、切片、字符串、map 及通道(channel),for range 语法上类似于其它语言中的 foreach 语句,一般形式为:

for key, val := range coll {
...
}

需要要注意的是,val 始终为集合中对应索引的值拷贝,因此它一般只具有只读性质,对它所做的任何修改都不会影响到集合中原有的值。一个字符串是 Unicode 编码的字符(或称之为 rune )集合,因此也可以用它来迭代字符串:

for pos, char := range str {
...
}

每个 rune 字符和索引在 for range 循环中是一一对应的,它能够自动根据 UTF-8 规则识别 Unicode 编码的字符。

通过 for range 遍历的返回值有一定的规律:

  • 数组、切片、字符串返回索引和值。
  • map 返回键和值。
  • 通道(channel)只返回通道内的值。

遍历数组、切片——获得索引和值

在遍历代码中,key 和 value 分别代表切片的下标及下标对应的值,下面的代码展示如何遍历切片,数组也是类似的遍历方法:

for key, value := range []int{1, 2, 3, 4} {
fmt.Printf("key:%d value:%d\n", key, value)
}

代码输出如下:

key:0  value:1
key:1 value:2
key:2 value:3
key:3 value:4


遍历字符串——获得字符

Go语言和其他语言类似,可以通过 for range 的组合,对字符串进行遍历,遍历时,key 和 value 分别代表字符串的索引和字符串中的每一个字符。

下面这段代码展示了如何遍历字符串:

var str = "hello 你好"
for key, value := range str {
fmt.Printf("key:%d value:0x%x\n", key, value)
}

代码输出如下:

key:0 value:0x68
key:1 value:0x65
key:2 value:0x6c
key:3 value:0x6c
key:4 value:0x6f
key:5 value:0x20
key:6 value:0x4f60
key:9 value:0x597d

代码中的变量 value,实际类型是 rune 类型,以十六进制打印出来就是字符的编码。

遍历 map——获得 map 的键和值

对于 map 类型来说,for range 遍历时,key 和 value 分别代表 map 的索引键 key 和索引对应的值,一般被称为 map 的键值对,因为它们是一对一对出现的,下面的代码演示了如何遍历 map。

m := map[string]int{
"hello": 100,
"world": 200,
}


for key, value := range m {
fmt.Println(key, value)
}

代码输出如下:

hello 100
world 200


注意

对 map 遍历时,遍历输出的键值是无序的,如果需要有序的键值对输出,需要对结果进行排序。

遍历通道(channel)——接收通道数据

for range 可以遍历通道(channel),但是通道在遍历时,只输出一个值,即管道内的类型对应的数据。

下面代码为我们展示了通道的遍历:

c := make(chan int)


go func() {


c <- 1
c <- 2
c <- 3
close(c)
}()


for v := range c {
fmt.Println(v)
}

代码说明如下:

  • 第 1 行创建了一个整型类型的通道。
  • 第 3 行启动了一个 goroutine,其逻辑的实现体现在第 5~8 行,实现功能是往通道中推送数据 1、2、3,然后结束并关闭通道。
  • 这段 goroutine 在声明结束后,在第 9 行马上被执行。
  • 从第 11 行开始,使用 for range 对通道 c 进行遍历,其实就是不断地从通道中取数据,直到通道被关闭。

在遍历中选择希望获得的变量

在使用 for range 循环遍历某个对象时,一般不会同时需要 key 或者 value,这个时候可以采用一些技巧,让代码变得更简单,下面将前面的例子修改一下,参考下面的代码示例:

m := map[string]int{
"hello": 100,
"world": 200,
}


for _, value := range m {
fmt.Println(value)
}

代码输出如下:

100
200

在上面的例子中将 key 变成了下划线​​_​​,这里的下划线就是匿名变量。

  • 可以理解为一种占位符。
  • 匿名变量本身不会进行空间分配,也不会占用一个变量的名字。
  • 在 for range 可以对 key 使用匿名变量,也可以对 value 使用匿名变量。

再看一个匿名变量的例子:

for key, _ := range []int{1, 2, 3, 4} {
fmt.Printf("key:%d \n", key)
}

代码输出如下:

key:0
key:1
key:2
key:3

在该例子中,value 被设置为匿名变量,只使用 key,而 key 本身就是切片的索引,所以例子输出索引。

我们总结一下 for 的功能:

  • Go语言的 for 包含初始化语句、条件表达式、结束语句,这 3 个部分均可缺省。
  • for range 支持对数组、切片、字符串、map、通道进行遍历操作。
  • 在需要时,可以使用匿名变量对 for range 的变量进行选取。

break continue

在循环里面有两个关键操作break和continue,break操作是跳出当前循环,continue是跳过本次循环。

备注:break可⽤于for、switch、select

package main //必须有一个main包

import "fmt"
import "time"

func main() {

i := 0

for { //for后面不写任何东西,这个循环条件永远为真,死循环
i++
time.Sleep(time.Second) //演示1s

if i == 5 {
break //跳出循环,如果嵌套多个循环,跳出最近的那个内循环
}
fmt.Println("i = ", i)
}
}

#执行结果:

i =  1
i = 2
i = 3
i = 4

备注: continue仅能用于for循环

package main //必须有一个main包

import "fmt"
import "time"

func main() {

i := 0

for { //for后面不写任何东西,这个循环条件永远为真,死循环
i++
time.Sleep(time.Second) //演示1s

if i == 5 {
continue //跳过本次循环,下一次继续
}
fmt.Println("i = ", i)
}
}

#执行结果:

i =  1
i = 2
i = 3
i = 4 #跳过5
i = 6
i = 7
i = 8


goto 语句

Go 语言的 goto 语句可以无条件地转移到过程中指定的行。

goto 语句通常与条件语句配合使用。可用来实现条件转移, 构成循环,跳出循环体等功能。

但是,在结构化程序设计中一般不主张使用 goto 语句, 以免造成程序流程的混乱,使理解和调试程序都产生困难。

语法

goto 语法格式如下:

goto label;
..
.
label: statement;

goto 语句流程图如下:

8天精通go开发(第一天)_赋值_32

实例

在变量 a 等于 15 的时候跳过本次循环并回到循环的开始语句 LOOP 处:

package main


import "fmt"


func main() {
/* 定义局部变量 */
var a int = 10


/* 循环 */
LOOP: for a < 20 {
if a == 15 {
/* 跳过迭代 */
a = a + 1
goto LOOP
}
fmt.Printf("a的值为 : %d\n", a)
a++
}
}

以上实例执行结果为:

a的值为 : 10
a的值为 : 11
a的值为 : 12
a的值为 : 13
a的值为 : 14
a的值为 : 16
a的值为 : 17
a的值为 : 18
a的值为 : 19


关注公众号 soft张三丰 

8天精通go开发(第一天)_go语言_33