go 基础

安装和环境配置

自行百度解决

go项目的目录结构

go命令依赖一个重要的环境变量:$GOPATH
一般的,一个Go项目在GOPATH下,会有如下三个目录:

|--bin
|--pkg
|--src

其中,bin存放编译后的可执行文件;pkg存放编译后的包文件;src存放项目源文件。一般,bin和pkg目录可以不创建,go命令会自动创建(如 go install),只需要创建src目录即可。

go基础数据类型类型

bool 类型

true false

数字类型

整型

无符号:uint8 uint16 uint32 uint64
有符号:int8 int16 int32 int64

浮点型

float32 float64

复数型

complex64 complex128

一些别称类型

byte -- uint8
rune -- int32
uint int 对应CPU平台机器字大小的无符号和有符号类型
uintptr 无符号整形,用于存放一个指针,一般用于底层和C语言对接

string类型

字符串是一个不可改变的字节序列,可以包含任意数据。文本字符串通常被解释为采用UTF-8编码的Unicode码点半序列。

string的操作

  • 内置函数len()返回一个字符串中的字节数目
  • 索引操作s[i]返回第i个字节的字节值,i必须满足0 ≤ i< len(s)条件约束,否则会引发一个panic
  • 子字符串操作s[i:j]基于原始的s字符串的第i个字节开始到第j个字节(并不包含j本身)生成一个新字符串。参见python的切片操作
  • '+' 操作符可以将两个字符串拼接在一起构造一个新的字符串
fmt.Println("goodbye" + ", world") // "goodbye, world"
  • 字符串可以用==和<进行比较;比较通过逐个字节比较完成的,因此比较的结果是字符串自然编码的顺序
  • 字符串的值不可变,尝试修改字符串内部数据的操作会引发一个error
  • 字符串变量的是可变的
  • 字符串处理常用的标准库包bytes、strings、strconv、unicode

常量与变量

常量 一个简单值的标识符,在程序运行时不会被修改的量,数据类型只可能是布尔型、数字型和字符串型。定义格式如下:

const identifier [type] = value
  • iota常量 自行研究

变量

声明方法

var v_name v_type
var v_name = value
v_name := value

第一种,指定变量类型,声明后若不赋值,使用默认值。
第二种,根据值自行判定变量类型。
第三种,省略var, 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误。注意,第三种简短声明形式只能用于函数内,全局变量不允许使用这种声明形式。

值类型和引用类型

基本数字类型和布尔类型都是值类型,这些类型的变量直接指向存在内存中的值。用等号进行赋值操作时,实际上是对值进行了拷贝。
一个引用类型的指针指向的是地址。

‘_’标识符

次标识符用于抛弃值,因为在Go语言中,某一变量被声明但是未被调用会报错,所以引入此变量可以规避这个问题。

基础结构

代码组成

package main

import "fmt"

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

go语言基础组成分为以下几部分:

  • 包声明
    第一行代码 package main 定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。
  • 引入包
    import "fmt" 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数
  • 函数
    下一行 func main() 是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)

执行流程

执行流程

包的概念

包是结构化代码的一种方式:每个程序都由包(通常简称为 pkg)的概念组成,可以使用自身的包或者从其它包中导入内容。
如同其它一些编程语言中的类库或命名空间的概念,每个 Go 文件都属于且仅属于一个包。
在 Go 的安装文件里包含了一些可以直接使用的包,即标准库。
Go 中的包模型采用了显式依赖关系的机制来达到快速编译的目的,如果 A.go 依赖 B.go,而 B.go 又依赖 C.go:
编译 C.go, B.go, 然后是 A.go.
为了编译 A.go, 编译器读取的是 B.o 而不是 C.o.
这种机制对于编译大型的项目时可以显著地提升编译速度。
如果导入一个包但是没有使用,在编译时会引发错误。

可见性

参见C++的公私有成员。
当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的 代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private )。

go系统工具

常用的命令工具

好像都挺常用的,主要介绍以下几个:

  • go build
    go build 命令主要是用于测试编译。在包的编译过程中,若有必要,会同时编译与之相关联的包。
    如果是普通包,当你执行go build命令后,不会产生任何文件。
    如果是main包,当只执行go build命令后,会在当前目录下生成一个可执行文件。
    go build 会忽略目录下以”_”或者”.”开头的go文件。
  • go fmt
    go fmt 命令主要是用来帮你格式化所写好的代码文件。
  • go get
    go get 命令主要是用来动态获取远程代码包的。
    go get 命令本质上可以理解为:首先通过源码工具clone代码到src目录,然后执行go install。

复合数据类型

以不同的方式组合基本类型可以构造出来的复合数据类型。
主要讨论四种类型——数组、slice、map和结构体。
数组和结构体是聚合类型;它们的值由许多元素或成员字段的值组成。数组是由同构的元素组成——每个数组元素都是完全相同的类型——结构体则是由异构的元素组成的。数组和结构体都是有固定内存大小的数据结构。相比之下,slice和map则是动态的数据结构,它们将根据需要动态增长。

数组

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。

  • 数组的每个元素可以通过索引下标来访问,索引下标的范围是从0开始到数组长度减1的位置。内置的len函数将返回数组中元素的个数。
  • 默认情况下,数组的每个元素都被初始化为元素类型对应的零值。我们也可以使用数组字面值语法用一组值来初始化数组:
var q [3]int = [3]int{1, 2, 3}
var r [3]int = [3]int{1, 2}
fmt.Println(r[2]) // "0"
  • 在数组字面值中,如果在数组的长度位置出现的是“...”省略号,则表示数组的长度是根据初始化值的个数来计算。
q := [...]int{1, 2, 3}
  • 数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型。数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定。
  • 如果数组类型相同,数组元素类型相同,可以用"=="来比较。

slice

Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度而已。slice的底层引用一个数组对象。

  • 一个slice由三个部分构成:指针、长度和容量。
  • 指针指向第一个slice元素对应的底层数组元素的地址。
  • 长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。内置的len和cap函数分别返回slice的长度和容量。
  • slice的切片操作s[i:j],其中0 ≤ i≤ j≤ cap(s),用于创建一个新的slice,引用s的从第i个元素开始到第j-1个元素的子序列。新的slice将只有j-i个元素。
  • slice和数组的字面值语法很类似,它们都是用花括弧包含一系列的初始化元素,但是对于slice并没有指明序列的长度。这会隐式地创建一个合适大小的数组,然后slice的指针指向底层的数组。
s := []int{0, 1, 2, 3, 4, 5}
  • 复制一个slice只是对底层的数组创建了一个新的slice别名。
  • 和数组不同的是,slice之间不能比较,因此我们不能使用==操作符来判断两个slice是否含有全部相等元素。slice唯一合法的比较操作是和nil比较,一个零值的slice等于nil。一个nil值的slice并没有底层数组。一个nil值的slice的长度和容量都是0。
if s== nil { /* ... */ }
  • 内置的append函数用于向slice追加元素。每次调用append函数,必须先检测slice底层数组是否有足够的容量来保存新添加的元素。如果有足够空间的话,直接扩展slice,将新添加的元素复制到新扩展的空间,并返回slice。此时底层数组没有改变。如果没有足够的增长空间,append会进行内存扩展,分配一个足够大的sliace空间存放原来的内容,再把新增的内容添加进去。我们不能确认在原先的slice上的操作是否会影响到新的slice,因此,通常是将append返回的结果直接赋值给输入的slice变量:
runes = append(runes, r)

map

参照python的字典类型,map是一个无序的key/value对的集合,其中所有的key都是不同的,然后通过给定的key可以在常数时间复杂度内检索、更新或删除对应的value。map类型可以写为map[K]V,其中K和V分别对应key和value。

  • map中所有的key都有相同的类型,所有的value也有着相同的类型,但是key和value之间可以是不同的数据类型。其中K对应的key必须是支持==比较运算符的数据类型,所以map可以通过测试key是否相等来判断是否已经存在。
  • 常用操作,创建,存储,访问,查找,删除
    创建可以使用内置函数make来创建,也可以使用map字面值语法来创建。访问方式通过key对应的下标语法访问。删除操作使用内置函数delete,delete会查找该元素,如果查找失败将返回value类型对应的零值,所以这种操作是安全的,即使map中不存在要删除的元素也没关系。
ages := make(map[string]int) // mapping from strings to ints
ages := map[string]int{
    "alice":   31,
    "charlie": 34,
}
ages["alice"] = 32
fmt.Println(ages["alice"]) // "32"
delete(ages, "alice") // remove element ages["alice"]
  • 和slice一样,map之间也不能进行相等比较;唯一的例外是和nil进行比较。如果我们需要对map进行比较操作,需要自己通过循环进行实现。

结构体

参见C语言的结构体,结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员。例:

type Employee struct {
    ID        int
    Name      string
    Address   string
    DoB       time.Time
    Position  string
    Salary    int
    ManagerID int
}

var dilbert Employee

dilbert结构体变量的成员可以通过点操作符访问,比如dilbert.Name和dilbert.DoB。
通常一行对应一个结构体成员,成员的名字在前类型在后,不过如果相邻的成员类型如果相同的话可以被合并到一行,就像下面的Name和Address成员那样:

type Employee struct {
    ID            int
    Name, Address string
    DoB           time.Time
    Position      string
    Salary        int
    ManagerID     int
}
  • 结构体类型的零值是每个成员都对是零值。
  • 如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的,那样的话两个结构体将可以使用==或!=运算符进行比较。

推荐阅读更多精彩内容