Go 学习之旅

Task1 Go初探

1.1 前言

编程语言,每个特性如果一开始都详细探究,估计都得写一篇文章来说,往往还说的不深入。所以既然是初探,那么就大概了解下特性,如果能和其他语言做比较的话,就做下对比,以求观森林,其次在于树木(虽然不知道这样是否是个好办法)。
以下是Go的语言特性:

  • 自动垃圾回收
  • 更丰富的内置类型
  • 函数多返回值
  • 错误处理
  • 匿名函数和闭包
  • 类型和接口
  • 并发编程
  • 反射
  • 语言交互性

1.2 Go语言特性

1.2.1 自动垃圾回收

Go的垃圾回收官方形容为 非分代 非紧缩 写屏障 三色并发标记清理算法。
非分代:不像Java那样分为年轻代和年老代,自然也没有minor gc和maj o gc的区别。

  • 非紧缩:在垃圾回收之后不会进行内存整理以清除内存碎片。
  • 写屏障:在并发标记的过程中,如果应用程序(mutator)修改了对象图,就可能出现标记遗漏的可能,写屏障就是为了处理标记遗漏的问题。
  • 三色:将GC中的对象按照搜索的情况分成三种:

黑色: 对象在这次GC中已标记,且这个对象包含的子对象也已标记
灰色: 对象在这次GC中已标记, 但这个对象包含的子对象未标记
白色: 对象在这次GC中未标记

  • 并发:可以和应用程序(mutator)在一定程度上并发执行。
  • 标记清理:GC算法分为两个大步骤:标记阶段找出要回收的对象,清理阶段则回收未被标记的对象(要被回收的对象)

这里详细说一下三色标记法:
三色标记法是对标记阶段的改进,原理如下:

初始状态所有对象都是白色。
从root根出发扫描所有根对象(下图a,b),将他们引用的对象标记为灰色(图中A,B),如果灰色对象有引用,则将自身标为黑色,引用为灰色,如果没有引用,则自己标为黑色,完事。
其结果就是,所有被引用的对象,都会变为黑色,没有引用就是白色,将会被回收。
在这里插入图片描述

1.2.2 更丰富的内置类型

Go是静态类型的编程语言,意味着声明变量时需要同时申明变量类型。这样编译器就知道要为这个值分配多少内存,并且知道这段分配的内存表示什么。
基本类型是 Go 语言自带的类型,比如 数值、浮点、字符串、布尔、数组 及 错误 类型,他们本质上是原始类型,也就是不可改变的,所以对他们进行操作,一般都会返回一个新创建的值,所以把这些值传递给函数时,其实传递的是一个值的副本。

引用类型和原始的基本类型恰恰相反,它的修改可以影响到任何引用到它的变量。在 Go 语言中,引用类型切片(slice)、字典(map)、接口(interface)、函数(func) 以及 通道(chan)。

引用类型之所以可以引用,是因为我们创建引用类型的变量,其实是一个标头值,标头值里包含一个指针,指向底层的数据结构,当我们在函数中传递引用类型时,其实传递的是这个标头值的副本,它所指向的底层结构并没有被复制传递,这也是引用类型传递高效的原因。

结构类型是用来描述一组值的,比如一个人有身高、体重、名字和年龄等,本质上是一种聚合型的数据类型。

Go 语言支持我们自定义类型,比如刚刚上面的结构体类型,就是我们自定义的类型,这也是比较常用的自定义类型的方法。

另外一个自定义类型的方法是基于一个已有的类型,就是基于一个现有的类型创造新的类型,这种也是使用 type 关键字。

1.2.3 函数多返回值

Go语言中函数可以返回多个值。对于有其它语言编程经验的人来说,最大的障碍不是学习这个特性,而是很难想到去使用这个特性。但函数返回多个值,我觉得很多语言都有这个功能,go其独特在于吸收了很多其他语言的特性,所以这一个可能更多是完善功能的特性。

func SumProdDiff(i, j int) (int, int, int)
package main

import (
    "fmt"
)

func SumProductDiff(i, j int) (int, int, int) {
    return i+j, i*j, i-j
}

func main() {
    sum, prod, diff := SumProductDiff(3,4)
    fmt.Println("Sum:", sum, "| Product:",prod, "| Diff:", diff)
}

1.2.5 错误处理

很多语言都有错误处理,那么Go的错误处理为什么也是特性之一呢?以我的体会,Go的很多函数调用后,会同时返回调用情况err和函数返回值,方便你对这个err进行判断,看是否处理,即不管是调用成否与否,都会有这个err值,成功就是nil,失败就是非零值,这一点就算是老手处理,都有可能出错。所以对于err值的强制处理,是Go的特性之一,例如:

func main() {
    conent,err:=ioutil.ReadFile("filepath")
    if err !=nil{
        //错误处理
    }else {
        fmt.Println(string(conent))
    }
}

error 接口
error其实一个接口,内置的,我们看下它的定义

type error interface {
    Error() string
}

它只有一个方法 Error,只要实现了这个方法,就是实现了error。现在我们自己定义一个错误试试。

type fileError struct {
}

func (fe *fileError) Error() string {
    return "文件错误"
}

这样我们就自定义了一个fileError类型,实现了error接口。

1.2.6 匿名函数和闭包

Go语言支持匿名函数,即函数可以像普通变量一样被传递或使用,Go的匿名函数是一个闭包

以下是《GO语言编程》中对闭包的解释

  • 基本概念

闭包是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块(由于自由变量包含在代码块中,所以这些自由变量以及它们引用的对象没有被释放)为自由变量提供绑定的计算环境(作用域)。

  • 闭包的价值

闭包的价值在于可以作为函数对象或者匿名函数,对于类型系统而言,这意味着不仅要表示数据还要表示代码。支持闭包的多数语言都将函数作为第一级对象,就是说这些函数可以存储到变量中作为参数传递给其他函数,最重要的是能够被函数动态创建和返回。

###1.2.7 类型和接口

这里我无法确定类型指的是什么意思,只能说下对接口的理解,接口就是一个基类,确定了该类具有哪些函数,具体使用时实现了这些函数,就算实现了这个接口。

Go语言中的接口是一种类型,类似于Python中的抽象基类

Go语言中使用接口来体现多态,是duck-type的一种体现

如,只要一个东西会叫,会走,那么我们就可以将它定义为一个动物的接口。
Go提倡面向接口编程,以下是接口的定义,使用关键字interface标识

type 接口类型名 interface{
    方法名1( 参数列表1 ) 返回值列表1
    方法名2( 参数列表2 ) 返回值列表2}

关于接口的定义有以下几点注意事项:

接口名在单词后面一般都需添加er的后缀,代表这是一个接口
当接口名与方法名都是大写时,代表该接口与方法均可被外部包进行访问
参数列表以及返回值列表参数变量名可以省略

1.2.8 并发编程

这个是Go最吸引人的地方,那就是Go天然适合高并发,使用Go进行并发编程也很容易,一行代码就可以开一个协程(goroutine)出来,详细解释如下:

Go 在语言级别支持协程,叫goroutine。Go 语言标准库提供的所有系统调用操作(包括所有同步IO操作),都会出让CPU给其他goroutine。这让轻量级线程的切换管理不依赖于系统的线程和进程,也不需要依赖于CPU的核心数量

有人把Go比作21世纪的C语言。第一是因为Go语言设计简单,第二,21世纪最重要的就是并行程序设计,而Go从语言层面就支持并行。同时,并发程序的内存管理有时候是非常复杂的,而Go语言提供了自动垃圾回收机制。

1.2.9 反射

先看定义,反射在维基百科中的定义是:

在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。

看起来很绕,但其实就是检查变量类型和内存结构,可能很多人会觉得这有什么难的,但其实这个在编程语言中好像还确实挺难。但咱们只是初探,所以就只给出概念,后面慢慢研究。

看Go语言圣经中表述:

Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。

总之,你如果要在debug时查看一个变量类型,就需要使用反射。

1.2.10 语言交互性

大多数语言,都可以调用其他语言的库,Go也不例外,Go的语言交互主要指得是与C得交互。

为了能够重用已有的 C 语言库,我们在使用 Golang 开发项目或系统的时候难免会遇到 Go 和 C语言 混合编程,这时很多人都会选择使用 cgo 。

但是 cgo 这个东西可算得上是让人又爱又恨,好处在于它可以让你快速重用已有的 C语言 库,无需再用 Gg 重造一遍轮子,而 坏处就在于它会在一定程度上削弱你的系统性能。关于cgo 的种种劣迹,Dave Cheney 大神在他的博客上有一篇专门的文章《cgo is not Go》。
但话说回来,有时候为了快速开发满足项目需求,使用 cgo 也实在是不得已而为之。

以上的10点内容,很多不是Go独有的,但都是Go语言的高级特性,可以作为初学Go语言时的大体了解,方便后续学习。

1.3 Go 语法结构

  • 包声明
  • 引入包
  • 函数
  • 变量
  • 语句 & 表达式
  • 注释
package main

import "fmt"
func main() {
   /* Always Hello, World! */
   fmt.Println("Hello, World!")
}

解释:

  1. package main定义了包名。必须在源文件中非注释的第一行指明这个文件属于哪个包。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。
  2. import "fmt"告诉编译器程序运行需要用fmt包。
  3. func main() 是程序开始执行的函数,main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。
  4. {}中"{"不可以单独放一行。
  5. // 是注释,在程序执行时将被忽略。//单行注释, /* … */ 多行注释也叫块注释,不可以嵌套使用,一般用于包的文档描述或注释成块的代码片段。
  6. fmt.Println(…) 将字符串输出到控制台,并在最后自动增加换行字符 \n。用 fmt.Print(“hello, world\n”) 可以得到相同的结果。
  7. go run main.go,
    • go build main.go:编译成二进制可执行程序
    • ./main:执行该程序
    • goland中 shift+control+R执行编译并运行

1.4 Go环境配置

1.IDE选择

IDE需知:vscode与goland,推荐使用vscode,vscode免费,goland收费(学生可以使用教育邮箱注册白嫖)。

vscode地址:https://code.visualstudio.com/

goland地址:https://blog.jetbrains.com/go/

2.go安装包

下载地址:https://studygolang.com/dl

可根据自己不同系统版本进行选择。

3.配置

  • 不管是哪个系统,需要配置go安装环境到path环境变量里面。
  • goland只需要选择go对应的sdk配置即可,也就是把go安装的路径添加进去,使用安装的go环境进行编译,不需要安装任何额外插件。
  • vscode需要安装额外插件,在插件市场里面直接搜索go,随后进行安装即可,在安装的时候容易出现下载失败问题,此时需要更换为国内源,如下设置:

要求go版本>=1.13。

windows设置:

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct

MacOS or Linux

export GO111MODULE=on
export GOPROXY=https://goproxy.cn
  • 第三方依赖包可以使用go mod

    Go 1.11 版本开始,Go 提供了 Go Modules 的机制。

    • mod基本操作

      1.初始化一个moudle,模块名为你项目名

      go mod init 模块名
      

      2.下载modules到本地cache

      目前所有模块版本数据均缓存在 $GOPATH/pkg/mod$GOPATH/pkg/sum

      go mod download
      

      3.编辑go.mod文件 选项有-json-require-exclude,可以使用帮助go help mod edit

      go mod edit
      

      4.以文本模式打印模块需求图

      go mod graph
      

      5.删除错误或者不使用的modules

      go mod tidy
      

      6.生成vendor目录

      go mod vendor
      

      7.验证依赖是否正确

      go mod verify
      

      8.查找依赖

      go mod why
      

Task2 Go数据类型、关键字、标识符

1.1 数据类型

1.1.1 按类别

  • 布尔型:只可以是常量 true 或者 false。
eg:
var b bool = true
  • 数字类型:整型和浮点型。

  • 位的运算采用补码字符串类型:字符串就是一串固定长度的字符连接起来的字符序列,Go 的字符串是由单个字节连接起来。

  • Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本

  • 复数:complex128(64 位实数和虚数)和 complex64(32 位实数和虚数),其中 complex128 为复数的默认类型。
    注:

    1. 复数的值由三部分组成 RE + IMi,其中 RE 是实数部分,IM 是虚数部分,RE 和 IM 均为 float 类型,而最后的 i 是虚数单位。
var name complex128 = complex(x, y)
或者
z := complex(x, y)
x = real(z)
y = imag(z)
  1. 复数也可以用==和!=进行相等比较,只有两个复数的实部和虚部都相等的时候它们才是相等的

1.1.2 派生类型

  • 指针类型(Pointer)
  • 数组类型
  • 结构化类型(struct)
  • Channel 类型
  • 函数类型
  • 切片类型
  • 接口类型(interface)
  • Map 类型

1.1.3 基于架构

1整型,同时提供了四种有符号整型,分别对应8、16、32、64bit(二进制)的有符号整数,与此对应四种无符号的整数类型

  • Uint8无符号 8 位整型 (0 到 255)
  • Unit16
  • Unit32
  • Unit64
  • int8
  • int16
  • int32
  • int64

2浮点型:

  • float32
  • float64
  • complex64(实数虚数)
  • complex128

3其他:

  • byte
  • rune
  • uint
  • int
  • uintptr(无符号整型,存放一个指针)

注:

  1. 表示 Unicode 字符的 rune 类型和 int32 类型是等价的,通常用于表示一个 Unicode 码点,是等价的。
  2. byte 和 uint8 也是等价类型,byte 类型一般用于强调数值是一个原始的数据而不是一个小的整数。
  3. 无符号的整数类型 uintptr,它没有指定具体的 bit 大小但是足以容纳指针。只有在底层编程时才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。
  4. 有符号整数采用 2 的补码形式表示,也就是最高 bit 位用来表示符号位,一个 n-bit 的有符号数的取值范围是从 -2(n-1) 到 2(n-1)-1。无符号整数的所有 bit 位都用于表示非负数,取值范围是 0 到 2n-1。
  5. 常量 math.MaxFloat32 表示 float32 能取到的最大数值,大约是 3.4e38。
  6. 常量 math.MaxFloat64 表示 float64 能取到的最大数值,大约是 1.8e308。
  7. float32 和 float64 能表示的最小值分别为 1.4e-45 和 4.9e-324。
  8. 浮点数在声明的时候可以只写整数部分或者小数部分。
const e = .71828 // 0.71828
const f = 1.     // 1
  1. 很小或很大的数最好用科学计数法书写,通过 e 或 E 来指定指数部分
const Avogadro = 6.02214129e23  // 阿伏伽德罗常数
const Planck   = 6.62606957e-34 // 普朗克常数

1.2 关键字

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

1.2.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 知识点

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

1.3 标识符

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

Task3 变量 、常量、枚举

0 前言

程序中储存值的东西叫做变量,按照变量可以赋予的值的多少,分为常量:只有一个可选值,枚举量:有限个可选值,变量:有无限个可选值。量的申明时通过关键字+变量名+量的类型来实现的,具体如下

1.1 变量

变量,计算机语言能存储计算结果或表示值的抽象概念。可以通过变量名访问,变量名由字母、数字、下划线组成,其中首个字符不能为数字。

声明变量的一般形式是使用 var 关键字:

var identifier type
var identifier1, identifier2 type

变量声明方式

  1. 指定变量类型,若没有初始化,

    数值类型(包括complex64/128)默认零值,

    bool默认false,

    字符串默认“”,

    “var a *int、var a []int、var a map[string] int、var a chan int、var a func(string) int、var a error // error 是接口”默认nil

  2. 可根据值自行判断类型

  3. “:=”声明,省略 var, 注意 := 左侧必须声明新的变量,否则产生编译错误,格式:v_name := value

  4. 多变量声明:

//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3

var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不需要显示声明类型,自动推断
vname1, vname2, vname3 := v1, v2, v3 // 出现在 := 左侧的变量不能是已经被声明过的,否则会导致编译错误

// 这种因式分解关键字的写法一般用于声明全局变量
var (
  vname1 v_type1
  vname2 v_type2
)

注意:

  1. “:=” 赋值操作符,高效创建新变量,初始化声明:a := 50 或 b := false,a 和 b 的类型(int 和 bool)将由编译器自动推断。
  2. 这是使用变量的首选形式,但是它只能被用在函数体内,而不可以用于全局变量的声明与赋值。
  3. 在相同的代码块中,我们不可以再次对于相同名称的变量使用初始化声明,但可以赋值;
  4. 声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误
  5. 全局变量可以声明但不用。
  6. _ 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。

1.2 常量

常量是一个简单值的标识符,在程序运行时,不会被修改的量。数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型
常量的定义格式:(省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。)

const identifier [type] = value
const b = "abc"

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

const c_name1, c_name2 = value1, value2

常用于枚举:

const (
  Unknown = 0
  Female = 1
  Male = 2
)
0,1,2 代表未知、女、男

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

iota,特殊常量,可认为是可以被编译器修改的常量。在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;

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)。

1.3枚举

在一些实际应用问题中,有些变量的取值被限定在一个有限的范围内。例如一周只有七天,一年只有12个月等,可以把此类变量定义为枚举类型。枚举类型的定义中列举出所有可能的取值,说明为该枚举类型的变量取值不能超过定义的范围。

个人理解,枚举类型是对变量值为有限值时的特殊变量,同时提高了安全性,但Go里使用const配合iota实现的枚举,总感觉有点奇怪,好像只能实现int类型的量。

注意:枚举类型是一种基本的数据类型,而不是构造类型,因为枚举类型变量只能取一个值,它也不能再分解为任何类型。

Go语言中没有枚举这种数据类型的,但是可以使用const配合iota模式来实现

1.3.1 普通枚举

const (
	a = 0
	b = 1
	c = 2
	d = 3
)

1.3.2 自增枚举

  1. iota只能用于常量表达式
  2. 它默认开始值是0,const中每增加一行加1,同行值相同
const (
	a = iota //0
	c        //1
	d        //2
)
const (
	e, f = iota, iota //e=0, f=0
	g    = iota       //g=1
)
  1. 若中间中断iota,必须显式恢复。
const (
  a = iota    //0
  b           //1
  c = 100     //100
  d           //100
  e = iota    //4
)

Task4.运算符、控制语句

1.1 运算符

假定 A 值为 10,B 值为 20。

1.1.1 算数运算符

运算符描述实例
+相加A + B 输出结果 30
-相减A - B 输出结果 -10
*相乘A * B 输出结果 200
/相除B / A 输出结果 2
%求余B % A 输出结果 0
++自增A++ 输出结果 11
自减A-- 输出结果 9

1.1.2 关系运算符

运算符描述
==检查两个值是否相等,如果相等返回 True 否则返回 False。
!=检查两个值是否不相等,如果不相等返回 True 否则返回 False。
>检查左边值是否大于右边值,如果是返回 True 否则返回 False。
<检查左边值是否小于右边值,如果是返回 True 否则返回 False。
>=检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。
<=检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。

1.1.3 逻辑运算符

运算符描述
&&逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。
| |逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False。
!逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。

1.1.4 位运算符,假定 A 为60,B 为13

运算符描述
&按位与运算符"&"是双目运算符。 其功能是参与运算的两数各对应的二进位相与。
|按位或运算符"|"是双目运算符。 其功能是参与运算的两数各对应的二进位相或
^按位异或运算符"^"是双目运算符。 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。
<<左移运算符"<<“是双目运算符。左移n位就是乘以2的n次方。 其功能把”<<“左边的运算数的各二进位全部左移若干位,由”<<"右边的数指定移动的位数,高位丢弃,低位补0。
>>右移运算符">>“是双目运算符。右移n位就是除以2的n次方。 其功能是把”>>“左边的运算数的各二进位全部右移若干位,”>>"右边的数指定移动的位数。

1.1.5 赋值运算符

运算符描述实例
=简单的赋值运算符,将一个表达式的值赋给一个左值C = A + B 将 A + B 表达式结果赋值给 C
+=相加后再赋值C += A 等于 C = C + A
-=相减后再赋值C -= A 等于 C = C - A
*=相乘后再赋值C *= A 等于 C = C * A
/=相除后再赋值C /= A 等于 C = C / A
%=求余后再赋值C %= A 等于 C = C % A
<<=左移后赋值C <<= 2 等于 C = C << 2
>>=右移后赋值C >>= 2 等于 C = C >> 2
&=按位与后赋值C &= 2 等于 C = C & 2
^=按位异或后赋值C ^= 2 等于 C = C ^ 2
=按位或后赋值 C

1.1.6 其他运算符

运算符描述实例
&返回变量存储地址&a; 将给出变量的实际地址。
*指针变量*a; 是一个指针变量

1.1.7 优先级

优先级运算符
5* / % << >> & &^
4+ - | ^
3== != < <= > >=
2&&
1||

1.2 控制语句

1.2.1 条件语句

指定一个或多个条件,并通过测试条件是否为 true 来决定是否执行指定语句,并在条件为 false 的情况在执行另外的语句。

1.2.1.1 if语句
  • if 语句 由一个布尔表达式后紧跟一个或多个语句组成。
  • if 语句 后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行。
  • if 或 else if 语句中可嵌入一个或多个 if 或 else if 语句。
  • 同各类主流语言,不多赘述。但注意,Go 没有三目运算符,所以不支持 ?: 形式的条件判断
1.2.1.2 switch语句
  • 用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配为止。
  • 匹配项后面不需要再加 break。
  • switch 默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case,如果我们需要执行后面的 case,可以使用 fallthrough 。
  • fallthrough:强制执行后面的 case 语句,fallthrough 不会判断下一条 case 的表达式结果是否为 true
switch x.(type){
  case type:
    statement(s);
  case type:
    statement(s);
  default: // 可选
    statement(s);
}

解释:从第一个判断表达式为 true 的 case 开始执行,如果 case 带有 fallthrough,程序会继续执行下一条 case,且它不会去判断下一个 case 的表达式是否为 true。

  1. 支持多条件匹配
  2. 不同的 case 之间不使用 break 分隔,默认只会执行一个 case
  3. 如果想要执行多个 case,需要使用 fallthrough 关键字,也可用 break 终止
1.2.1.3 select语句
select {
  case communication clause  :
    statement(s);
  case communication clause  :
    statement(s);
  default : // 可选
    statement(s);
}
  • 每个 case 都必须是一个通信
  • 所有 channel 表达式都会被求值
  • 所有被发送的表达式都会被求值
  • 如果任意某个通信可以进行,它就执行,其他被忽略。
  • 如果有多个 case 都可以运行,Select 会随机公平地选出一个执行。其他不会执行。
    否则:
    1. 如果有 default 子句,则执行该语句。
    2. 如果没有 default 子句,select 将阻塞,直到某个通信可以运行;Go 不会重新对 channel 或值进行求值。

1.2.3 循环语句

1.2.3.1 for循环
for init; condition; post { } //for
for condition { } //while
for {}
init: 一般为赋值表达式,给控制变量赋初值;
condition: 关系表达式或逻辑表达式,循环控制条件;
post: 一般为赋值表达式,给控制变量增量或减量。

for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环:

for key, value := range oldMap {
  newMap[key] = value
1.2.3.2 循环嵌套

循环套循环,格式:

for [condition |  ( init; condition; increment ) | Range] {
 for [condition |  ( init; condition; increment ) | Range] {
   statement(s);
 }
 statement(s);
}
  • 循环控制语句
  1. break语句:
    • 用于循环语句中跳出循环,并开始执行循环之后的语句。
    • break 在 switch(开关语句)中在执行一条 case 后跳出语句的作用。
    • 在多重循环中,可以用标号 label 标出想 break 的循环。
  2. continue语句:跳过当前循环的剩余语句,然后继续进行下一轮循环。
  3. goto:无条件转移到过程中指定行,与条件语句配合,实现条件转移、构成循环、跳出循环体等(不建议用,造成混乱)

Task5.字典、字符串

1.1 字典

map是一种较为特殊的数据结构,在任何一种编程语言中都可以看见他的身影,它是一种键值对结构,通过给定的key可以快速获得对应的value。

1.1.1 如何定义字典

var m1 map[string]int
m2 := make(map[int]interface{}, 100)
m3 := map[string]string{
	"name": "james",
	"age":  "35",
}

在定义字典时不需要为其指定容量,因为map是可以动态增长的,但是在可以预知map容量的情况下为了提高程序的效率也最好提前标明程序的容量。需要注意的是,不能使用不能比较的元素作为字典的key,例如数组,切片等。而value可以是任意类型的,如果使用interface{}作为value类型,那么就可以接受各种类型的值,只不过在具体使用的时候需要使用类型断言来判断类型。

1.1.2 字典操作

向字典中放入元素也非常简单

m3["key1"] = "v1"
m3["key2"] = "v2"
m3["key3"] = "v3"

你可以动手试一下,如果插入的两个元素key相同会发生什么?

与数组和切片一样,我们可以使用len来获取字典的长度。

len(m3)

在有些情况下,我们不能确定键值对是否存在,或者当前value存储的是否就是空值,go语言中我们可以通过下面这种方式很简便的进行判断。

if value, ok := m3["name"]; ok {
		fmt.Println(value)
	}

上面这段代码的作用就是如果当前字典中存在key为name的字符串则取出对应的value,并返回true,否则返回false。

对于一个已经存在的字典,我们如何对其进行遍历呢?可以使用下面这种方式:

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

如果多运行几次上面的这段程序会发现每次的输出顺序并不相同,对于一个字典来说其默认是无序的,那么我们是否可以通过一些方式使其有序呢?你可以动手尝试一下。(提示:可以通过切片来做哦)

如果已经存在与字典中的值已经没有作用了,我们想将其删除怎么办呢?可以使用go的内置函数delete来实现。

delete(m3, "key1")

除了上面的一些简单操作,我们还可以声明值类型为切片的字典以及字典类型的切片等等,你可以动手试试看。

不仅如此我们还可以将函数作为值类型存入到字典中。

func main() {
	m := make(map[string]func(a, b int) int)
	m["add"] = func(a, b int) int {
		return a + b
	}
	m["multi"] = func(a, b int) int {
		return a * b
	}
	fmt.Println(m["add"](3, 2))
	fmt.Println(m["multi"](3, 2))
}

1.2 字符串

字符串应该可以说是所有编程语言中最为常用的一种数据类型,接下来我们就一起探索下go语言中对于字符串的常用操作方式。

1.2.1 字符串定义

字符串是一种值类型,在创建字符串之后其值是不可变的,也就是说下面这样操作是不允许的。

s := "hello"
s[0] = 'T'

编译器会提示cannot assign to s[0]。在C语言中字符串是通过\0来标识字符串的结束,而go语言中是通过长度来标识字符串是否结束的。

如果我们想要修改一个字符串的内容,我们可以将其转换为字节切片,再将其转换为字符串,但是也同样需要重新分配内存。

func main() {
	s := "hello"
	b := []byte(s)
	b[0] = 'g'
	s = string(b)
	fmt.Println(s) //gello
}

与其他数据类型一样也可以通过len函数来获取字符串长度。

len(s)

但是如果字符串中包含中文就不能直接使用byte切片对其进行操作,go语言中我们可以通过这种方式

func main() {
	s := "hello你好中国"
	fmt.Println(len(s)) //17
	fmt.Println(utf8.RuneCountInString(s)) //9

	b := []byte(s)
	for i := 0; i < len(b); i++ {
		fmt.Printf("%c", b[i])
	} //helloä½ å¥½ä¸­å›½
	fmt.Println()

	r := []rune(s)
	for i := 0; i < len(r); i++ {
		fmt.Printf("%c", r[i])
	} //hello你好中国
}

在go语言中字符串都是以utf-8的编码格式进行存储的,所以每个中文占三个字节加上hello的5个字节所以长度为17,如果我们通过utf8.RuneCountInString函数获得的包含中文的字符串长度则与我们的直觉相符合。而且由于中文对于每个单独的字节来说是不可打印的,所以可以看到很多奇怪的输出,但是将字符串转为rune切片则没有问题。

1.2.2 strings包

strings包提供了许多操作字符串的函数。在这里你可以看到都包含哪些函数https://golang.org/pkg/strings/

下面演示几个例子:

func main() {
	var str string = "This is an example of a string"
	//判断字符串是否以Th开头
	fmt.Printf("%t\n", strings.HasPrefix(str, "Th"))
	//判断字符串是否以aa结尾
	fmt.Printf("%t\n", strings.HasSuffix(str, "aa"))
	//判断字符串是否包含an子串
	fmt.Printf("%t\n", strings.Contains(str, "an"))
}

1.2.3 strconv包

strconv包实现了基本数据类型与字符串之间的转换。在这里你可以看到都包含哪些函数https://golang.org/pkg/strconv/

下面演示几个例子:

i, err := strconv.Atoi("-42") //将字符串转为int类型
s := strconv.Itoa(-42) //将int类型转为字符串

若转换失败则返回对应的error值。

1.2.4 字符串拼接

除了以上的操作外,字符串拼接也是很常用的一种操作,在go语言中有多种方式可以实现字符串的拼接,但是每个方式的效率并不相同,下面就对这几种方法进行对比。(关于测试的内容会放在后面的章节进行讲解,这里大家只要知道这些拼接方式即可)

1.SPrintf

const numbers = 100

func BenchmarkSprintf(b *testing.B) {
	b.ResetTimer()
	for idx := 0; idx < b.N; idx++ {
		var s string
		for i := 0; i < numbers; i++ {
			s = fmt.Sprintf("%v%v", s, i)
		}
	}
	b.StopTimer()
}

2.+拼接

func BenchmarkStringAdd(b *testing.B) {
	b.ResetTimer()
	for idx := 0; idx < b.N; idx++ {
		var s string
		for i := 0; i < numbers; i++ {
			s += strconv.Itoa(i)
		}
	}
	b.StopTimer()
}

3.bytes.Buffer

func BenchmarkBytesBuf(b *testing.B) {
	b.ResetTimer()
	for idx := 0; idx < b.N; idx++ {
		var buf bytes.Buffer
		for i := 0; i < numbers; i++ {
			buf.WriteString(strconv.Itoa(i))
		}
		_ = buf.String()
	}
	b.StopTimer()
}

4.strings.Builder拼接

func BenchmarkStringBuilder(b *testing.B) {
	b.ResetTimer()
	for idx := 0; idx < b.N; idx++ {
		var builder strings.Builder
		for i := 0; i < numbers; i++ {
			builder.WriteString(strconv.Itoa(i))
		}
		_ = builder.String()
	}
	b.StopTimer()
}

5.对比

BenchmarkSprintf-8         	   68277	     18431 ns/op
BenchmarkStringBuilder-8   	 1302448	       922 ns/op
BenchmarkBytesBuf-8        	  884354	      1264 ns/op
BenchmarkStringAdd-8       	  208486	      5703 ns/op

可以看到通过strings.Builder拼接字符串是最高效的。

参考

1.搞懂Go垃圾回收
2.《Go语言实战》Go 类型:基本类型、引用类型、结构类型、自定义类型
3.Golang初级系列教程-函数多个返回值
4.Go语言(golang)的错误(error)处理的推荐方案
5.Golang之匿名函数和闭包
6.Golang之匿名函数和闭包
7.Go匿名函数和闭包
8.Go 接口类型
9.Go并发编程
10.深度解密Go之反射
11.Go语言得高级特性
12.Golang语言交互性
13.Go语言命名规范
14.枚举类型
15.Go语言学习枚举和类型
16.golang枚举类型 - iota用法拾遗

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
"Go语言学习CSDN"指的是在中国最大的IT技术专业社区CSDN上学习Go语言。作为一门新兴的编程语言,Go语言由Google开发,具有简洁、高效、强大的特点,广泛应用于网络编程和分布式系统开发。 在CSDN学习Go语言有以下几个好处。首先,CSDN是中国最大的IT技术专业社区,拥有海量的技术文章、教程和学习资料。在CSDN可以找到大量关于Go语言学习资源,包括入门教程、深入剖析、项目实战等。这些资源可以帮助初学者快速入门,提高学习效率。 其次,CSDN上有活跃的技术社区和问答板块。学习者可以在这里提问、交流和分享经验。在学习Go语言过程中遇到困惑或问题时,可以通过发帖提问获取帮助。也可以浏览他人提问的解答,学习其他人的经验和技巧。 另外,CSDN还有专业的技术博客和技术讲座。在这些博客和讲座中,一些有经验的Go语言开发者会分享自己的实践心得、案例分析和最佳实践,对于学习者来说是很宝贵的学习资源。 最后,CSDN还有一些Go语言学习社群和论坛。学习者可以加入这些社群,与其他Go语言爱好者一起讨论和学习。这些社群通常有线上和线下的活动,参与其中可以结识更多志同道合的人,扩展自己的人脉和交流圈。 总而言之,在CSDN学习Go语言可以获得丰富的学习资源、交流机会和实践经验,对于快速掌握这门语言非常有帮助。不仅如此,CSDN也是一个互联网技术圈的门户,通过学习Go语言还可以了解其他热门技术和行业趋势。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值