go学习1

安装

https://golang.google.cn/dl/ 官网直接安装go。

打开cmd 输入 go version

然后安装ide,这边使用的是goland。

安装包和破解文件都在里面:直接安装,然后将jetbrains-agent-latest.zip拖到goland窗口,重启即可。

https://pan.baidu.com/s/1eyKoegOhHppqTxtNbHunpQ

w1m2

特性

1
2
3
4
5
6
7
8
package main

import "fmt"

func main() {
fmt.Println("ok")

}

入门代码:输出一个ok

go有如下规则:

  • 每个源文件都有一个专门的package
  • 程序的入口依旧是main函数
  • main函数必须在main 这个package下。
  • 文件名称和package没有直接联系
  • 可以 go run这个go文件,也可以go build这个模块后,生成对应的可执行的机器文件,直接执行。

变量

Go语言是静态强类型语言,所以变量是有明确类型的。

1
2
var age string     //声明 未赋值默认为0
age = "18"

可以声明其类型,然后赋值

1
2
var name = "王钢蛋" 
var age = 10

也可以直接赋值。

Go语言支持根据数据推导数据类型的方法。所以在定义的时候可以不用写数据类型,直接根据你所初始化的值,来推导出定义的数据类型。

  • 变量在使用前必须先声明。
  • 变量名不能重复定义。
  • 如果是简短定义方式,左边至少有一个是新的变量。
  • 如果定义了变量,必须得使用,否则编译无法通过。
  • 全局变量可以不使用也能编译通过,定义的全局变量和局部变量名称如果相同,则会优先使用局部变量。
  • 简短定义方式不能定义全局变量,也就是不能声明在函数外部

常量

1
const name = 111

由var变成了const

  • 常量数值不能修改。
  • 常量定义后可以不使用。
  • 常量定义不能使用简短定义方式。
  • 常量中使用的数据类型只能是 整型、布尔、浮点、复数类型、字符串类型。

iota是常量里面的计数器,初始值默认值是0,可以被编译器自动修改,每定义一组常量时,iota逐行自增1。

1
2
3
4
5
6
7
8
9
10
const(
a=1+iota
b=3+iota
c
d
e="asd"
f
g=iota

)

输出

1
2
3
4
5
6
7
1
4
5
6
asd
asd
6

可以看到iota从0开始,每多一个变量,数字加一。每个常量都有这个属性。可以用其做一些骚操作。

如果下面没有声明常量的值,那么就和上面常量保持一致,如果第一行没有声明,那么报错。

数据类型

数据类型是一门高级语言的基础,Go属于又属于强类型静态编译语言。Go语言拥有两大数据类型,基本数据类型和复合数据类型。

整型

数据类型 说明 取值范围
有符号整数
int8 有符号 8位整数 -128到 127
int16 有符号 16位整数 -32768 到32767
int32 有符号 32位整数 -2147483648到2147483647
int64 有符号 64位整数 -9223372036854775808到9223372036854775807
无符号整数
uint8 无符号8位整数 0到255
uint16 无符号16位整数 0到65535
uint32 无符号32位整数 0到4294967295
uint64 无符号64位整数 0到18446744073709551615

在Go语言中 byte与uint8 是一样的,rune与int32是一样的,代表同一种数据类型。但是int和int64 不是同一种类型。

int不等于上面任意一个类型。

Go语言中int类型的大小与具体的平台有关系,一般来说,int在32位系统中是4字节,在64位系统中是8字节,使用简短定义自动推导类型初始化一个整数,默认为int类型。

字符串:

字符串的概念就是多个byte的集合,一个字符序列用双引号””,或者`` (esc下面的键) 表示。

浮点数

Go语言有两种精度的浮点数 float32 和 float64。浮点类型的数据取值范围可以从很小或者很巨大。

单精度 浮点类型 取值范围
float32 负数时 -3.402823E38 到 -1.401298E-45
float32 正数时 1.401298E-45 到 3.402823E38
双精度 浮点类型 取值范围
float64 -1.79E+308 到 +1.79E+308

1.79E-308 是 1.79 乘以 10的负308次方。 1.79E+308 是 1.79 乘以 10的308次方。

单精度双精度两者区别

在内存中占有的字节数不同

  • 单精度浮点数在机内占4个字节。
  • 双精度浮点数在机内占8个字节。

有效数字位数不同

  • 单精度浮点数 有效数字7位。
  • 双精度浮点数 有效数字16位。

对于float型数据,其长度是4个字节,右边23位用来表示小数点后面的数字,中间8位用来表示e,左边一位用来表示正负。

对于double型数据,其长度是8个字节,右边52位用来表示小数点后面的数字.中间11位表示e,左边一位用来表示正负。

数据类型转换

go 语言是静态语言,要求,定义、赋值、 运算、类型一致才能进行操作。所以要进行操作的时候必须保证数据类型一直。需要注意的是,只有兼容的数据类型才能够转换。 强制类型转换的语法 Type(value)

1
2
3
4
5
6
var a1 int=10
var a2 int32
a2= int32(10 + a1)
a1= int(10 + a2)
fmt.Println(a1)
fmt.Println(a2)

运算符

这个和java差不多,没啥区别。主要说几个没玩过的

  • &^ : a&^b 对于b上面的每个数值,如果为0,则去a上的数值,如果为1,则是0(很有趣不知道有什么用)
  • ^ :异或,不解释。
  • <<,>> 左移和右移,包含符号位的移动

赋值运算符

这块go是蛮有意思的了。

运算符 描述
= 把等号右侧的数值 赋给左边的变量
+= 自身加上后面的值 再赋给左边的变量
-= 自身减去后面的值 在赋给左边
/= 自身除后面的值 再赋值给左边
%= 自身与后面的值求余数后 再赋值给左边
<<= 左移后再赋值
>>= 右移后再赋值
&= 按位与后再赋值
!= 按位或 后再赋值
^= 按位异或后再赋值

这块赋值操作,让代码节省了很多,有趣。

流程控制

if模块

这个和java的语法没有区别。

1
2
3
4
5
if a3==a1{
fmt.Println("as")
}else{
fmt.Println("asd")
}

但是go存在特殊的一些写法。条件判断之间再加上一段执行语句,执行的结果再用作后面的条件判断。例如

1
2
3
4
5
if a4=a3; a3==a1{
fmt.Println("as")
}else{
fmt.Println("asd")
}

试了一下,只能加一个语句哈。

switch模块

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
//switch语法一
switch 变量名{
case 数值1: 分支1
case 数值2: 分支2
case 数值3: 分支3
...
default:
最后一个分支
}


//语法二 省略变量 相当于作用在了bool 类型上

switch{
case true: 分支1
case false: 分支2
}

//语法三 case 后可以跟随多个数值, 满足其中一个就执行
switch num{
case 1,2,3:
fmt.Println("num符合其中某一个 执行代码")
case 4,5,6:
fmt.Println("执行此代码")
}

//语法四 可以添加初始化变量 作用于switch内部

switch name:="huangrong"; name{
case "guojing":
fmt.Println("shideguojing")
case "huangrong":
fmt.Println("shidehuangrong")
}

看看例子

1
2
3
4
5
6
7
var sd="asds"
switch sd {
case"1","asds":
fmt.Println("1sdf")
case" 2":
fmt.Println("12")
}

在switch 语句中,默认每个case后自带一个break,表示到此结束 不向下执行了,跳出整个switch。fallthrough 表示强制执行后面的没有执行的下一个case代码。

for

语法: for init; condition;post{ }

  • init 初始化 只执行一次

  • condition bool类型 执行条件 如果满足继续执行后面的循环体 如果不满足 则终止执行

  • {} 循环体

  • post 表达式 将在循环体执行结束之后执行
1
2
3
for i:=0;i<3;i++{
fmt.Println(i)
}

break默认是结束当前循环,如果当前多重循环,那么可以如下:

1
2
3
4
5
6
7
8
9
10
flag:
for i := 1; i < 10; i++ {
for j := 1; j < i; j++ {
fmt.Println(i, j)
if j == 5 {
break flag
}
}
fmt.Println(i)
}

给循环体加个标签,然后break掉。

goto模块

1
2
3
4
5
6
Test:
var as int =1
Test2:
fmt.Println(as)
goto Test2
goto Test

给语句加个标签,然后goto到那里直接执行。

复合数据类型

数组声明的语法格式为: var 数组名称 [长度]数据类型

  • 数组只能用来存储一组相同类型的数据结构。
  • 数组需要通过下标来访问,并且有长度和容量 。
1
2
3
4
5
6
var sd [5] int
sd[1]=11
arr:=[5]int{1,2}
arr1:=[5]int{1:2,3:5}
arr3:=[...]int{1,2}
arr4:=[...]int{1:2,3:5}

数组不足补0处理。

数组的遍历,除了上面说的for,还可以

1
2
3
for index,value:=range arr1{
fmt.Println(index,value)
}

slice

1
2
3
4
var slice [] int
slice =make([]int,38)
slice[0]=1
slice=append(slice,1,2,3)

通过make函数定义slice,然后通过append扩容进去。前面的3是长度,后面的8是容量,扩容时基于容量计算的。容量就是真实的数组长度,而长度则是当前slice元素数目。

除了用make创建slice,还可以通过new创建。但是不一样,看看效果。

1
2
3
4
5
6
7
8
9
slice1 := new([]int)
fmt.Println(slice1) //输出的是一个地址 &[]

//使用make创建切片
slice2 := make([]int, 5)
fmt.Println(slice2)//输出初始值都为0的数组, [0 0 0 0 0]

fmt.Println(slice1[0])//结果出错 slice1是一个空指针 invalid operation: slice1[0] (type *[]int does not support indexing)
fmt.Println(slice2[0])//结果为 0 因为已经初始化了

如果实现了扩容, 地址就会发生改变成新的地址,旧的则自动销毁。

删除数据

1
2
3
4
5
6
7
8
9
slice:=[]int{1,2,3,4,5,6}
slice1:=[]int{1,2,3,4,5,6}
//i := 2 // 要删除的下标为2
//slice = append(slice[:i], slice[i+1:]...) // 删除中间1个元素
//fmt.Println(slice) //结果[1 2 4]
slice=slice[:3]
slice1=slice1[3:]
fmt.Println(slice) //结果[1 2 4]
fmt.Println(slice1) //结果[1 2 4]

:n,表示获取这个slice前面n个元素,n:,表示获取获取第三个元素后面的其余元素。

1
2
3
i := 2      // 要删除的下标为2
slice = append(slice[:i], slice[i+1:]...) // 删除中间1个元素
fmt.Println(slice) //结果[1 2 4]

相当于删除了第i个元素,其逻辑是,获取前面2个元素,然后再把第三个元素后面的元素添加进去。

总结一下

  • 每一个切片都引用了一个底层数组。
  • 切片本身不能存储任何数据,都是这底层数组存储数据,所以修改切片的时候修改的是底层数组中的数据。
  • 当切片添加数据时候,如果没有超过容量,直接进行添加,如果超出容量自动扩容成倍增长。
  • 切片一旦扩容,指向一个新的底层数组内存地址也就随之改变。
  • 声明了长度的就是数组,没有声明就是slice,快速定义模式的,如果是make声明长度的则是slice
  • 数组,slice都是声明的变量,实际数据存储在内存。

map

和java里面的map一样,创建和删除语法如下:

1
2
3
4
5
6
7
8
9
10
11
var map1 map[string]string
if map1==nil{
map1= map[string]string{}
}
var map2=make(map[int]string)
map2[13]="sad"
val :=map2[13]
fmt.Print(val)
delete(map2,12)
map3:=map[int]string{12:"asd"}
fmt.Print(map3[12])

遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
map1:=make(map[int]string)
map1[1]="asd"
map1[2]="asd2"
map1[3]="asd3"
for key,value:=range map1{
fmt.Println(key,value)
}
map1 := make(map[string]string)
map1["name"] = "1name"
map1["age"] = "1age"
map2 := make(map[string]string)
map2["name"] = "2name"
map2["age"] = "2age"
s1:=make([]map[string]string,0,2)
s1=append(s1,map1,map2)
for key,value:=range s1{
fmt.Println(key,value)
}

map在Go语言并发编程中,如果仅用于读取数据时候是安全的,但是在读写操作的时候是不安全的,在Go语言1.9版本后提供了一种并发安全的,sync.Map是Go语言提供的内置map,不同于基本的map,他提供了特有的方法,不需要初始化操作实现增删改查的操作。

1
2
3
var syncmap sync.Map
syncmap.Store("as",12)
fmt.Println(syncmap.Load("as"))