golangBasic1-9

GOPATH目录下主要包含三个重要目录:
(1)bin:Go编译可执行文件的存放路径。
(2)pkg:Go编译包时生成的.a中间文件存放路径。
(3)src:Go标准库源码路径。

变量申明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
标准格式
var 变量名 变量类型
var num int

批量格式
var (
a int
b string
c bool
)

初始化变量
var 变量名 变量类型 = 表达式
var num int =1

变量交换
package main
import "fmt"
func main(){
//短变量申明 只能在函数或if/for的初始化项中 自动根据右边推导变量类型
a := 1
b := 2
a ,b =b,a
fmt.Println(a)
fmt.Println(b)
}

匿名变量
package main
import "fmt"
func ReturnData() (int, int) {
return 10,20
}

func main() {
a,_ := ReturnData()//匿名变量不占空间,不分配内存
_,b := ReturnData()
fmt.Println(a, b)
}

数据类型

常见:

整型、浮点型、字符串、字符和转义字符、布尔型、指针、其他数据类型(Go语言还有切片、通道、map和函数等类型)

整型

1
2
3
整型主要分为有符号整型和无符号整型两大类:
有符号整型:int8、int16、int32、int64
无符号整型:uint8、uint16、uint32、uint64

浮点型

1
Go语言支持两种浮点数:float32和float64,float32浮点数的最大范围约为3.4e38,float64浮点数最大范围约为1.8e308

字符串

1
2
3
Go语言中,字符串的值为双引号中的内容而且可以直接输入中文。
字符串中的每个元素就是字符。
Go语言中,字符的值为单引号中的内容而且可以直接输入中文。
转义符 含义
\n 匹配换行符
\r 匹配回车符
\t 匹配制表符
\’ 匹配单引号
\” 匹配双引号
\ 匹配反斜杠

布尔型

布尔型是最简单的数据类型,只有两个值 False(假) 和True(真)。

指针申明

指针是一种地址值,这个地址值代表着计算机内存空间中的某个位置。指针变量就是存放地址值的变量,指针变量的声明格式如下:
var 变量名 *int

取变量地址

1
2
3
4
5
6
7
8
9
10
11
Go语言中,使用操作符”&”取变量地址,取得的地址值可以赋给指针变量。

package main
import "fmt"
func main() {
num :=1
var p *int
p = &num
fmt.Println("num变量的地址为:",p)
fmt.Println("指针变量p的地址为:",&p)
}

常量

常量的显式声明格式如下:
const 常量名 常量类型 = vaule
注意:一个常量被声明后可以不对其使用,但是变量一旦在声明后必须使用。

运算符

算术运算符、比较运算符、赋值运算符、位运算符、逻辑运算符、其它运算符

算术运算符

运算符 描述 例子
+ 加法 - 相加运算符两侧的值 a + b
- 减法 - 左操作数减去右操作数 a – b
***** 乘法 - 相乘操作符两侧的值 a * b
/ 除法 - 左操作数除以右操作数 a / b
取模 - 左操作数除以右操作数的余数 a % b
++ 自增 - 操作数加1 a++
自减 - 操作数减1 a–

比较运算符

运算符 描述 例子
== 等于 - 比较对象是否相等 a == b
!= 不等于 - 比较两个对象是否不相等 a != b
> 大于 - 返回a是否大于b a > b
< 小于 - 返回a是否小于b a < b
>= 大于等于 - 返回x是否大于等于y。 a >= b
<= 小于等于 - 返回x是否小于等于y。 a <= b

赋值运算符

运算符 描述 例子 展开形式
= 右边值赋值给左边 a = 100 a = 100
+= 右边值加到左边 a += 10 a = a + 10
-= 右边值减到左边 a -= 10 a = a - 10
*= 将左边值乘以右边 a *= 10 a = a * 10
/= 将左边值除以右边 a /= 10 a = a / 10
%= 将左边值对右边做取模 a %= 10 a = a % 10

位运算符

应用在两个数的运算上,会对数字的二进制所有位数从低到高进行运算:

运算符 描述 例子
按位与,如果相对应位都是1,则结果为1,否则为0。 a & b
| 按位或,如果相对应位都是0,则结果为0,否则为1。 a | b
^ 按位异或,如果相对应位值相同,则结果为0,否则为1 a ^ b
<< 按位左移运算符。左操作数按位左移右操作数指定的位数。 a << b
>> 按位右移运算符。左操作数按位右移右操作数指定的位数。 a >> b

逻辑运算符

运算符 描述 例子
and 逻辑与,当且仅当两个操作数都为真,条件才为真 a and b
or 逻辑或,如果任何两个操作数任何一个为真,条件为真。 a or b
! 逻辑非,用来反转操作数的逻辑状态。如果条件为True,则逻辑非运算符将得到False。 ! a

其他运算符

运算符 描述 例子
& 返回变量存储地址 &a
***** 指针变量 *a
^ 按位取反(注意与按位异或区分) ^a

运算符优先级

表中第二行“”运算符代表取反运算符,第九行“”运算符代表按位

运算符 关联性
! ^ ++ – 从右到左
* / % 从左到右
+ - 从左到右
<< >> 从左到右
< <= > >= 从左到右
== != 从左到右
& 从左到右
^ 从左到右
| 从左到右
&& 从左到右
|| 从左到右
= += -= *= /= %=>>= <<= &= ^= |= 从右到左

5流程控制

if判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if 表达式1 {
分支1
} else if 表达式2 {
分支2
} else {
分支3
}

package main
import "fmt"
func main() {
a := 101
if a > 100 {
fmt.Println(a," > 100")
} else if a == 100 {
fmt.Println(a," = 100")
} else {
fmt.Println(a," < 100")
}
}

循环控制

Go语言中的循环逻辑通过for关键字实现,不同于其它编程语言,Go语言没有while关键字,不存在while循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
for 初始语句;条件表达式;赋值表达式 {
循环体
}

package main
import "fmt"
func main() {
for i := 1;i <=5;i++ {
fmt.Println(i)
}
}
break跳出循环(层),continue继续循环

package main
import "fmt"
func main() {
OuterLoop:
for i := 0;i < 2;i++{
for j := 0;j < 3;j++{
if j ==1 {
fmt.Println(i,j)
//与break类似,开始标签所对应的循环,此为外循环
continue OuterLoop
}
}
}
}

switch分支

switch语句的执行过程从上至下,直到找到匹配项,匹配项后面也不需要再加 break,每一个switch语句只能包含一个可选的default分支

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
switch var1 {
case value1:
代码块1
case value2:
代码块2
default:
代码块3
}

package main
import "fmt"
func main() {
var a int = 1
switch a {
case 1:
fmt.Println("1+1=1")
fallthrough//关键字,无条件强制执行后面的case2
case 2:
fmt.Println("1+1=2")
case 3:
fmt.Println("1+1=3")
default:
fmt.Println("1+1不等于1或2或3")
}
}

goto跳转

goto语句用于代码间的无条件跳转

1
2
3
4
5
6
7
8
9
10
goto 标签
package mainimport "fmt"
func main() {
fmt.Println("Hello")
goto sign
fmt.Println("无效代码")
sign:
fmt.Println("world")
}
不建议使用,过多会破坏程序结构,使得可读性变差

6内置容器

数组

1
2
3
4
5
6
7
8
9
10
11
var 数组变量名 [数组长度]元素类型声明时进行赋值:
var student = [3]string{“Tom”,”Ben”,”Peter”}
range关键字,主要作用是配合for关键字 对数组/切片/映射等数据结构进行迭代。"..."可代替数组长度,数组元素可以通过数组下标来读取或修改,数组下标从0开始
package mainimport "fmt"
func main() {
var num = [...]int{1, 2, 3, 4}
for k, v := range num {
fmt.Println("变量k:", k," ","变量v:", v)
fmt.Println(num[k])
}
}

切片

相对于数组,切片(slice)是一种更方便和强大的数据结构,它同样表示多个同类型元素的连续集合,但是切片本身并不存储任何元素而只是对现有数组的引用。

切片结构包括:地址、长度和容量:
地址:切片的地址一般指切片中第一个元素所指向的内存地址,由16进制表示。
长度:切片中实际存在元素的个数
容量:切片的起始元素开始到其底层数组中的最后一个元素的个数
切片的长度和容量都是不固定的,可以通过追加元素使切片的长度和容量增大。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
切片的声明格式:    var 切片变量名 []元素类型     var student = []string
生成切片3种方式:
1从数组中生成 格式 slice[开始位置,结束位置]
例如:
var student = []string{"Tom","Ben","Peter","Danny"}
var student1=student[0,1]//从数组中生成
2从切片中生成 。。。。类似从数组中生成
3直接新生成一个新的切片
var 切片变量名 []元素类型
var student = []string
使用make()函数初始化格式:make([]元素类型,切片长度,切片容量)
make([]string,4,3)
可使用append()函数来对切片进行元素的添加。当切片的不能再容纳其它元素时(即当前切片长度值等于容量值),下一次使用append函数对切片进行元素添加,容量会按2倍数进行扩充。由于Go语言没有对删除切片元素提供方法,需要我们手动将删除点前后的元素连接起来,从而实现对切片中元素的删除。切片的遍历和数组类似,可以通过切片下标来进行遍历,切片下标同样从0开始,第一个元素的数组下标为 0,第二个下标为 1,以此类推。
package mainimport "fmt"
func main() {
var student = []
string{"Tom","Ben","Peter","Danny"}
for k,v :=range student{
fmt.Println("切片下标:",k,",对应元素",v) }
fmt.Println("切片长度:",len(student))
fmt.Println("切片容量:",cap(student))
fmt.Println("切片是否为空:",student==nil) //删除元素
student=append(student[0:1],student[2:]...)
fmt.Println("切片长度:",len(student))
fmt.Println("切片容量:",cap(student))
fmt.Println("切片:",student)
}

映射

map的声明格式如下:
var map [键类型]值类型 //似乎教材少了个映射名
与切片的初始化类似,map也可以使用make()函数来进行初始化,格式如下:
make(map[键类型]值类型,map容量)
注意:使用make函数初始化map时可以不指定map容量,但是对于map的多次扩充会造成性能损耗。cap()函数只能用于切片,不可用于map,可使用len获取map长度
map的遍历主要通过for循环来完成,遍历时可同时获得map的键和值。
Go语言通过delete()函数来对map中的指定键值对进行删除操作,delete()函数格式:delete(map,键)。清空map只能重新定义一个新的map

1
2
3
4
5
6
7
8
9
10
11
12
package mainimport "fmt"
func main() {
var studentScoreMap
map[string]int
studentScoreMap = make(map[string]int)
studentScoreMap["Tom"]=80
studentScoreMap["Ben"]=85
studentScoreMap["Peter"]=90
delete(studentScoreMap,"Tom")
fmt.Println(studentScoreMap)
}
执行结果map[Ben:85,Peter:90]

7函数

声明使用函数

Go语言中,函数的声明以关键字func为标识,具体格式如下:

1
func 函数名(参数列表) (返回参数列表){函数体}

函数名:函数名由字母、数字和下划线构成,但是函数名不能以数字开头,在同一个包内,函数名不可重复。
注意:可简单地将一个包理解为一个文件夹
参数列表:参数列表中的每个参数由参数名称和参数类型两部分组成,参数变量为函数的局部变量。如果函数的参数数量不固定,Go语言函数还支持可变参数。
返回参数列表:返回参数列表中的每个参数由返回的参数名称和参数类型组成,也可简写为返回值类型列表。
函数体:函数的主体代码逻辑,若函数有返回参数列表,则函数体中必须有return语句返回值列表。

可变参数

Go语言支持可变参数的特性即函数声明时可以没有固定数量的参数。
可变参数的函数格式如下:

1
func 函数名 (固定参数列表,v ...T ) (返回参数列表) {函数体}

可变参数一般放在函数参数列表的末尾,也可不存在固定参数列表。
v…T代表的其实就是变量v为T类型(参数类型)的切片,v和T之间为3个“.”组成。

1
2
3
4
5
6
7
8
9
10
11
package mainimport "fmt"//返回值都同一类型则可简写 但不推荐 降低可读性//若不想接受,可用匿名变量“_”,但不能所有返回值都是匿名变量
func add(slice ... int) (int) {
sum := 0
for _,value :=range slice{
sum = sum + value
}
return sum
}
func main() {
fmt.Println("1+2+....+9+10=",add(1,2,3,4,5,6,7,8,9,10))
}

匿名函数和闭包

匿名函数即是在需要函数时定义函数,匿名函数可以以变量方式传递,它常常被用于实现闭包。
匿名函数的格式如下:

1
func (参数列表) (返回参数列表) {函数体}

匿名函数的调用有两种方式:
1定义同时调用匿名函数
2将匿名函数赋值给变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1.定义同时调用匿名函数可以在匿名函数后添加“()”直接传入实参:
package mainimport "fmt"
func main(){
func(data string){
fmt.Println("Hello "+data)
}("world!")}
2.匿名函数赋值给变量将匿名函数赋值给一个变量,之后再进行调用:
package mainimport "fmt"
func main(){
f1 := func(data string){
fmt.Println("Hello "+data)
}
f1("world!")
}

闭包就是包含了自由变量的匿名函数,其中的自由变量即使已经脱离了原有的自由变量环境也不会被删除,在闭包的作用域内可继续使用这个自由变量,同一个匿名函数和不同的引用环境组成了不同的闭包。
闭包就如同有“记忆力”一般,可对作用域内的变量的引用进行修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package mainimport "fmt"
func main(){
num := 1
fmt.Printf("%p\n",&num) func() {
num++
fmt.Println(num)
fmt.Printf("%p\n",&num)
}()
func() {
num++
fmt.Println(num)
fmt.Printf("%p\n",&num)
}()
}

延迟执行语句

Go语言中存在一种延迟执行的语句,由defer关键字标识,格式如下:
defer 任意语句
defer后的语句不会马上被执行,在defer所属的函数即将返回时,函数体中的所有defer语句将会按出现的顺序被逆序执行,即函数体中最后一个defer语句最先被执行。

1
2
3
4
5
6
7
8
package mainimport "fmt"
func main(){
fmt.Println("start now")
defer fmt.Println("这是第一句defer语句")
defer fmt.Println("这是第二句defer语句")
defer fmt.Println("这是第三句defer语句")
fmt.Println("end")
}

8包管理

包的申明/导入/初始化

包的申明

包是结构化代码的一种方式:每个程序都由包(通常简称为 pkg)的概念组成,可以使用自身的包或者从其它包中导入内容。我们先来了解一下Go 语言源码的组织方式:
Go语言的源代码以代码包为基本的组织单位。
在文件系统中,代码包是与文件目录一一对应的,文件目录的子目录也就是代码包的子包。
在工作区中,一个代码包的导入路径实际上就是从 src 子目录,到该包的实际存储位置的相对路径
每一个Go源文件的第一行都需要声明包的名称,声明一个包使用关键字package,如声明一个main包:package main

导入

1
导入包需要使用关键字import,它会告诉编译器你想引用该位置的包中的代码,包的路径可以是相对路径,也可以是绝对路径。// 声明方式一import "fmt"import "time"// 声明方式二import (	"fmt"	"time")

初始化

在Go语言里面有两个保留的函数: init 函数(能够应用于所有的package)和main函数(只能应用于package main)。这两个函数在定义时不能有任何的参数和返回值。虽然一个package里面可以写任意多个init函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个package中每个文件只写一个init函数。

包的命名

每个包都有一个包名,包名一般是短小的名字,包名在包的声明处指定通常来说,默认的包名就是包导入路径名的最后一段,因此即使两个包的导入路径不同,它们依然可能有一个相同的包名。包的命名有如下规则:
第一条规则,同目录下的源码文件的代码包声明语句要一致。也就是说,它们要同属于一个代码包。这对于所有源码文件都是适用的。
第二条规则,源码文件声明的代码包的名称可以与其所在的目录的名称不同。(import后面的最后一个元素是路径,就是目录,并非包名)如当前目录名为project1,但包名可以为main,或者为mypkg。

依赖包管理

为了解决这个问题,go在1.5版本引入了vendor属性(默认关闭,需要设置go环境变量GO15VENDOREXPERIMENT=1),并在1.6版本中默认开启了vendor属性。
简单来说,vendor属性就是让go编译时,优先从项目源码树根目录下的vendor目录查找代码(可以理解为GOPATH),如果vendor中有,则不再去GOPATH中去查找。
然而有时候依赖包多了,一个一个拷贝是非常费时费力的,在Go 1.11 之后官方发布支持了版本管理工具 mod。

Go语言命名规范

Go语言中,我们推荐使用驼峰式命名法来对各类标识符(变量、常量和结构体等)和文件进行命名。驼峰式命名法有两种格式:小驼峰命名法和大驼峰命名法。
1.小驼峰命名法
第一个单词以小写字母开始,第二个单词的首字母则需要大写:
var sumNum int
var firstName string
var isValid bool
var printValue func()
2.大驼峰命名法
每一个单词的首字母都采用大写字母:
var SumNum int
var FirstName string
var IsValid bool
var PrintValue func()

9结构体

理解结构体

通过使用结构体,我们可以自定义一系列具有相同类型或不同类型的数据构成的数据集合,用来实现较复杂的数据结构。
Go语言结构体是一种对现实生活中实体的抽象,结构体由一系列成员变量构成,这些成员变量对应着实体不同的属性。

例如,对于某个图书馆中的所有书籍,我们可以抽象出书这个结构体,经过总结归纳,该图书馆中的书都拥有以下属性:
书名
作者
数量
ID号
那么我们可以定义以下结构体:
结构体名:book
成员1:书名
成员2:作者
成员3:数量
成员4:ID号

定义结构体

GO语言中通过关键字type定义自定义类型,结构体定义需要使用 typestruct 关键字,具体定义格式如下:

1
Type 结构体名 struct {成员变量1 类型1成员变量2 类型2成员变量3 类型3............}

注意:
结构体名:同一个包被结构体名不能重复。
成员名:同一个结构体内,成员名不能重复。
同类型的成员名可以写在一行。
当结构体、方法名或变量名的首字母为大写时(可被导出),就可以在当前包外进行访问。
类型1、类型2…:表示结构体成员变量的类型。

1
2
3
4
5
6
7
8
9
10
type Book struct {	
title string
author string
num int
id int
}或者
type Book struct {
title,author string
num,id int
}

结构体实例化,会真正分配内存

实例化3种方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1标准实例化var 结构体实例 结构体类型(结构体名称)
type Book struct {
title,author string
num,id int
}
func main(){
var book1 Book
}
执行结果:{ 0 0}//有空字符串2new函数实例化,实例化完成,返回结构体的指针类型
func main(){
var book1:=new(Book)
}
执行结果:&{ 0 0}3取地址实例化,与new类似,返回结构体的指针类型结构体实例 := &结构体类型{}
func main(){
var book1:=&Book{}
}
执行结果:&{ 0 0}

访问成员,通过“.”,例如:book1.title

初始化结构体

Go语言中可以通过键值对格式和列表格式对结构体进行初始化。
键值对初始化的格式如下:

1
结构体实例 := 结构体类型{成员变量1:值1,成员变量2:值2,成员变量3:值3,}

这种类型的初始化类似对map数据类型的初始化操作,键和值之间以冒号分隔,键值对之间以逗号分隔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package mainimport "fmt"
type Book struct {
title string
author string
num int
id int
}//键值对格式初始化
func main (){
book1 := &Book{//取地址实例化 同时赋值 可去除&,为非指针类型实例化
title:"Go语言",
author:"Tom",
num:20,
id:152368,
}
fmt.Println("title:",book1.title)
fmt.Println("author:",book1.author)
fmt.Println("num:",book1.num)
fmt.Println("id:",book1.id)}或者列表格式初始化
func main (){
book1 := &Book{//取地址实例化 同时赋值
"Go语言", "Tom", 20, 152368,
}
fmt.Println("title:",book1.title)
fmt.Println("author:",book1.author)
fmt.Println("num:",book1.num)
fmt.Println("id:",book1.id)
}
注意:列表格式初始化,必须初始化所有成员,并且声明顺序保持一致,且不能与键值对初始化混用

结构体内嵌

1
type 结构体名1 struct{  成员变量1 类型1  成员变量2 类型2}type 结构体名2 struct{  结构体名1  成员变量3 类型3}

内嵌匿名结构体,使代吗的编写更加便利,无需type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
type Book1 struct{
title string
yy string
}
type Book2 struct{
Book1 ee string
}
type BookBorrow struct{
Book1//Book1内嵌结构体
Book struct{//结构体中 内嵌匿名结构体Book
tt string
ii string
}
uu string
}
func main (){//bookBorrow实例初始化 变量赋值
bookBorrow :=&BookBorrow{
Book1:Book1{//Book1内嵌结构体 初始化
title:"Tom",
yy:"Ma"
},
Book:struct{//Book匿名结构体 初始化
tt string
ii string
}{
tt: "hah",
ii: "ha1"
},
uu:"jiaj"
}
fmt.PrintIn(bookBorrow)
}
执行结果:
&{{Tom Ma} {hah ha1} jiaj}

匿名结构体

匿名结构体顾名思义即没有名字的结构体,与匿名函数类似,匿名结构体无需type关键字就可以直接使用,匿名结构体在创建同时也要创建对象。
匿名结构体初始化和使用更加简单,无需通过type关键字定义且不用写出类型名称。
匿名结构体初始化时需进行匿名结构体定义和成员变量初始化(可选),格式如下:

结构体实例 := struct{
//匿名结构体定义
成员变量1 类型1
成员变量2 类型2
成员变量3 类型3

}{
//成员变量初始化(可选)
成员变量1:值1,
成员变量2:值2,
成员变量3:值3,

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main
import "fmt"
type Book struct {
title string
author string
num int
id int
}
func main (){
book1 := struct{//匿名结构体
title string
author string
num int
id int
}{
title:"Go语言",
author:"Tom",
num:20,
id:152368,
}
fmt.Println("title:",book1.title)
fmt.Println("author:",book1.author)
fmt.Println("num:",book1.num)
fmt.Println("id:",book1.id)
}

若需要初始化多个不同实例,会存在大量重复代码,实际开发中不建议这么使用结构体,一般用于组织全局变量、构建数据模板和解析JSON等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import (	
"encoding/json"
"fmt
)
func main (){
data :=&struct{
Code int
Msg string
}{}
jsonData :=`{"code":200,"msg":""}`
if err :=json.Unmarshal([]byte(jsonData),data);
err !=nil{
fmt.Println(err)
}
fmt.Println("code",data.Code)
fmt.Println("msg",data.Msg)
}

执行结果:code 200msg