一. 变量
1.1 变量的声明
学习一门语言,首先要了解其声明变量的方式,go语言java、c、c++等语言声明变量的方式不太一样,go作为一个强类型语言,在声明变量中引入了关键字var,并且变量的类型放在变量名之后,这是go语言中的第一种声明方式,具体声明方式如下:
var v1 int //声明一个int类型,名字为v1的变量
在go语言中,摒弃了传统编程语言中一句话一个分号,不再以分号作为结束符
第二种声明方式:在一个var关键字的后面可以将若干个需要声明的变量放置在一起,大大提高了书写代码的效率,不需要在定义多个变量时写多个var,声明方式如下:
var(
v1 int //声明一个int类型,名字为v1的变量
v2 string //声明一个string类型,名字为v2的变量
)
当我们在声明变量时,有时需要对变量进行初始化。在我们之前所了解到的强类型语言中,对变量进行初始化的时候需要对变量的类型进行声明,但在go语言中,也可以省略掉变量的类型,编译器会像js等弱类型语言一样自动推到出该变量的类型。变量的初始化方式如下:var v1 int = 10 //声明方式1
var v2 = 10 //声明方式2,编译器可以自动推导出变量的类型为int
v3 := "I Love Golang!" //声明方式3,编译器可以自动推导出变量的类型为string
可以观察到,上面的第三种声明方式中不仅没有使用到v3的类型(string),也没有使用到声明变量需要用到的关键字(var),这种声明方式在go语言中也是一种正确的声明方式。其可以大大减少需要输入的字符量,并且编译器也可以自动推导出该变量的类型,是懒人的最佳选择(hhhh)
但是需要注意,使用 := 这种声明方式声明的变量是不能用于声明全局变量的,只能在方法内部声明变量。
go作为一个不折不扣的强类型语言(静态类型语言),可以使用这种声明方式,使其看起来更像是动态类型语言.......
1.2 变量的赋值
下面示范一个最普通的变量赋值:
var v1 int //声明一个名字为v1,类型为int的变量
v1=10 //将10赋给v1,所以v1的值为10
但是在go中,提供了多重赋值的工程,即可以在一句话中为多个变量赋值,示例如下: var(
v1 int
v2 int
v3 string
)
v1,v2,v3=5,20, "Hello"
这种功能可以大大提升写代码效率。例如两个变量i,j交换值的代码,在java中我们这样写:int temp=i; //定义一个中间变量temp,用temp储存值从而实现值的交换
i=j;
j=temp;
但在go语言中,我们可以这样写:i,j=j,i //简直不要太舒服hhhh
二. 常量
在go语言中,常量就是在编译期间就已知,并且不可改变的值。常量包括数值类型(整形、浮点型、负数类型),布尔类型,字符串类型等。
2.1常量定义
定义常量,我们使用const关键字,例如:
const Pi float64 = 3.1415926535897
const zero = 0.0
const (
size int64= 1024
eof= -1
)
const u,v float32=0, 3 //u=0.0 , v=3.0
与定义变量差不多,除了关键字不同外,其他地方都是一样的。(不能使用)go中的常量类型在定义常量时也不是必须的。
定义常量的右值可以是编译器运算的常量表达式,例如:
const mask=1<<3
但是!!由于常量赋值是一个编译器的行为,所以右值不可以出现任何在运行期间才能计算出来值的表达式,例如:const max=getMax() //getMax为获取数组中最大值的一个自定义函数
原理:因为getMax()只有在运行期才能得到返回结果,在编译器不能确定,所以不能作为一个常量的值。iota关键字:
go语言中预定义的常量有true、false、iota。
iota是一个可以被编译器修改的常量,在每一个const关键字出现的时候重置为0,然后在下一个const关键字出现之前,没出现一次iota,其所代表的数字都会+1。
const(
c0= iota //iota被重置为0
c1= iota //iota=1,c1=1
c2= iota //iota=2,c2=2
c3= iota //iota=3,c3=3
)
const(
a=1<< iota //iota=0,a=1
b=1<< iota //iota=1,b=2
c=1<< iota //iota=2,c=4
)
如果两个const赋值的表达式是一样的,那么可以省略后一个赋值表达式,只写第一个就可以,所以上面的两个const语句就可以修改为:
const(
c0= iota //iota被重置为0
c1 //iota=1,c1=1
c2 //iota=2,c2=2
c3 //iota=3,c3=3
)
const(
a=1<< iota //iota=0,a=1
b=1 //iota=1,b=2
c=1 //iota=2,c=4
)
2.2 枚举
在go语言的枚举中,并不支持enum关键字,我们可以在const后跟一对圆括号的方式定义一组常量,这种方法在go语言中用于定义枚举值。
三. 类型
基础类型:
布尔类型:bool
整形:int8,byte,int16,int,uint,uintptr等。
浮点类型:float32,float64。
复数类型:complex64,complex128。
字符串:string。
字符类型:rune。
错误类型:error。
复合类型:
指针(pointer)
数组(array)
切片(slice)
字典(map)
通道(chan)
结构体(struct)
接口(interface)
3.1布尔类型
go语言中布尔类型的关键字为bool,可赋值为预定义的true和false。
3.2整形
在go语言中,int和int32被认为是两种不同的类型,编译器也不会帮你自动转换,如下会发生错误:
var value2 int32
value1:= 25 //value1会被自动推导为int类型
value2=value1 //编译错误,原因是没有进行类型转换
在上面代码的第三行中,使用强制类型转换即可避免报错。在将value2赋值为value1时,将value1强制转换为value2的类型(int32)
value2=int32(value1)
但是,在做强制类型转换时,需要注意数据长度被截短而发生的精度损失和值溢出问题3.3浮点型
在go语言中,浮点型包括float32和float64,其中float32就等价于我们传统变成语言中的float,float64等价于我们传统编程语言中的double。
需要注意的是,如果我们使用 := 定义一个浮点型变量,如果该变量为一个整数,则需要在该整数后面加上 .0 ,否则该变量会被自动推导为整形而不是浮点型。
fvalue1:=12.0
上面的fvalue1会被自动推导为float64,而不管赋给它的数字是否使用32位长度表示。所以在浮点型变量赋值的时候也需要注意和上面整形部分同样的类型转换问题 因为浮点数不是一种精确的表达方式,所以不能够像整形那样用==来判断两个浮点数是否相等,这会导致不稳定的结果,可以使用下面的这种替代方案:
import "math"
//p为用户自定义的比较精度,比如0.00001
func isEqual(f1, f2, p float64) bool {
return math.Abs(f1- f2) < p
}
3.4复数类型
复数的定义示例如下:
var value1 complex64 //两个float32构成的复数类型
value1=3.2+ 12i
value2:=3.2+ 12i //会被自动推导为complex128类型
value3:=complex(3.2,12) //会被自动推导为complex128类型
在go语言中,设 z=x+yi ,可以通过内置函数real(z)来获取复数z的实部x,可以通过内置函数imag(z)来获取复数z的虚部y。
3.5字符串
在go语言中,字符串类型string也是一种内置类型
var str string //声明一个字符串变量
str="Hello World" //字符串赋值
ch:=str[0] //取字符串str的第一个字符,为'H'
可以看到,在上面的第三行代码中,可以通过类似于数组下标的方式获取字符串的内容。但与数组不同的是,在go语言中,字符串的内容不能在初始化后被修改,比如下面的例子就会发生编译错误: str:="Hello World" //字符串也支持声明时进行初始化的做法
str[0]='X' //编译错误 cannot assign to str[0]
可以使用go语言的内置函数len()来获取字符串的额长度。字符串的遍历:
go语言支持两种方式遍历字符串:
一种是以字节数组的方式遍历
str:="Hello World"
n:=len(str)
for i := 0; i < n; i++ {
ch:=str[i]
fmt.Println(i,ch)
}
另一种是以Unicode字符遍历
str:="Hello World"
for i,ch:=range str{
fmt.Println(i,ch) //ch类型为rune
}
3.6字符类型
在go语言中支持两种字符类型,一个是byte(uint8),代表UTF-8字符串的单个字节的值;另一个是rune,代表单个Unicode字符。
3.7数组
在go语言中,数组的长度在定以后就不可更改,在声明时长度可以为一个常量或是一个常量表达式(在编译期间即可计算出结果的表达式)。数组的长度是该数组类型的一个内置常量,可以用go的内置函数len()来获取。
遍历数组:
在go语言中,除了可以用for来遍历数组外,还提供了range关键字,用于便捷地遍历容器中的元素。 arr:=[]int {1,2,3,4,5}
for i,v := range arr{
fmt.Println("Array Element[",i,"]=",v)
}
在上面的代码中可以看到,range具有两个返回值,第一个返回值是元素在容器中的下标,第二个返回值是元素的值。
特别注意:在go语言中,数组是一个值类型(value type)。所有值类型变量在赋值和作为参数传递时都将产生一次赋值动作。如果将数组作为函数的参数类型,则在函数调用时该参数将会发生数据复制。因此,在函数体中无法修改传入的数字的内容,因此函数内操作的只是所传入数组的一个副本。
例如下面的代码:
package main
import "fmt"
func modify(array [5]int) {
array[0] = 10 //试图修改数组的第一个元素
fmt.Println("In modify(),array values:", array)
}
func main() {
array:=[5]int {1,2,3,4,5} //定义并初始化一个数组
modify(array) //将array传到modify()中,并试图修改array的第一个元素
fmt.Println("In main(),array values:",array)
}
运行结果:
In modify(),array values: [10 2 3 4 5]
In main(),array values: [1 2 3 4 5]可以看到,在上面的代码中,array中的元素并没有发生改变,所以在main()中定义的array和传到modify()中等个array是两个不通实例。
3.8数组切片
在go语言中,数组一经定义后长度是无法修改的,并且数组是值类型,每次传递都会产生一个副本,所以这种数据结构是无法满足开发需求的。
go语言提供了数组切片(slice)来弥补数组的不足。
数组切片的数据结构可以抽象为以下三个变量:
1)一个指向原生数组的指针。
2)数组切片中的元素的个数。
3)数组切片已分配的储存空间。
创建数组切片:
创建数组切片的方式有两种,一种是基于数组创建,另一种是直接创建。
基于数组创建:
package main
import "fmt"
func main() {
var myArray []int=[]int{1,2,3,4,5,6,7,8,9,10} //先创建一个数组
var mySlice []int=myArray[:5] //根据数组myArray创建数组切片
fmt.Println("Elements in myArray:")
for _,v:=range myArray {
fmt.Print(v," ")
}
fmt.Println("\nElements in mySlice:")
for _,v :=range mySlice{
fmt.Print(v," ")
}
}
运行结果:
Elements in myArray:
1 2 3 4 5 6 7 8 9 10
Elements in mySlice:
1 2 3 4 5
从上面的代码中可以观察到,go语言支持myArray[first : last]这种基于数组创建数组切片的方式,举几个例子:
mySlice=myArray[:] //基于myArray的所有元素创建数组切片
mySlice=myArray[:5] //基于myArray的前五个元素创建数组切片
mySlice=myArray[5:] //基于从myArray的第五个元素开始到最后所有元素的数组切片
直接创建数组切片:
并非必须要实现准备好一个数组才能创建数组切片,go语言提供内置函数make()可以用于灵活地创建数组切片,举几个例子: mySlice1 := make([]int,5) //创建一个初始元素个数为5的数组切片,元素的初始值为0
mySlice2 := make([]int,5,10) //创建一个初始元素个数为5的数组切片,元素的初始值为0,并且预留10个元素的储存空间
mySlice3 := []int{1,2,3,4,5} //直接创建并初始化包含5个元素的数组切片
操作数组的所有方法都适用于数组切片。数组切片的特性——动态增减元素
可动态增减元素是数组切片比数组更为强大的功能。与数组相比,数字切片多了一个存储能力(capacity)的概念,即元素个数和分配的空间可以是两个不同的值。合理地设置存储能力的值,可以大幅降低数组切片内部重新分配内存和搬送内存块的频率,从而大大提高程序的性能。
假如你明确知道当前创建的数组切片最多可能需要存储的元素个数为50,那么如果你设置的存储能力的值小于50,比如20,那么在元素个数超过20时,底层将会发生至少一次这样的动作——重新分配一块“够大”的内存,并且把原来内存中的内容复制到新的内存中,这会产生比较明显的开销。给“够大”加上引号是因为系统也不知道需要分配多大的内存才足够,所以只是一种简单的猜测。比如,将原有的内从空间扩大两倍,但两倍不一定够,所以之前说到的内存重新分配和内容复制可能会发生多次,从而明显降低系统的整体性能。但如果你知道元素的个数最大为50,并在一开始就把存储能力的值设置为50,那么之后就不会发生这种非常耗费CPU的动作,从而达到空间换时间的效果。
数组切片支持使用go语言的内置函数cap()和len()。cap()函数返回的是数组切片分配的空间大小;len()函数返回的是数组切片中所存储的元素的个数。下面为代码示例:
package main
import "fmt"
func main() {
mySlice := make([]int,5,10) //创建一个初始元素个数为5,初始值为0的数组切片,并且预留10个元素的存储空间
fmt.Println("len(mySlice):",len(mySlice)) //打印出mySlice中的元素个数
fmt.Println("cap(mySlice):",cap(mySlice)) //打印出mySlice所分配的空间大小
}
如果需要往上面代码中的mySlice已包含的五个元素后面添加新的元素,可以使用append()函数。mySlice = append(mySlice,1,2,3) //在mySlice的末尾添加1,2,3三个元素
函数append()的第二个参数其实是一个不定参数,我们可以按照自己的需求添加若干个元素,甚至直接在该数组切片的末尾追加另一个数组切片。例如:package main
import "fmt"
func main() {
mySlice := make([]int,5,10) //创建一个初始元素个数为5,初始值为0的数组切片,并且预留十个元素的存储空间
mySlice2 := []int{1,2,3,4,5} //创建一个初始元素为1,2,3,4,5的数组切片
mySlice = append(mySlice,mySlice2...) //将mySlice2追加到mySlice的末尾
fmt.Print(mySlice) //打印结果为: [0 0 0 0 0 1 2 3 4 5]
}
需要注意的是,在将mySlice2追加到mySlice的末尾的代码中,在mySlice2后面加了“...”。如果不加,会编译报错,因为按append()的语义,从第二个参数起的所有参数都是待附加的元素。因为mySlice中的元素类型为int,所以直接传递mySlice2是行不通的,加上“...”就相当于把mySlice2中的元素打散后传入。
上述调用等同于:mySlice=append(mySlice,1,2,3,4,5)
数组切片会自动处理存储空间不足的问题,如果追加的内容长度超过当前已分配的存储空间(即cap()返回的信息),数组切片会自动分配一块足够大的内存。
基于数组切片创建数组切片
数组切片也可以基于另一个数组切片创建。 oldSlice := []int{1,2,3,4,5} //创建一个初始元素为1,2,3,4,5的数组切片
newSlice := oldSlice[:3] //基于oldSlice的前三个元素创建newSlice
注意:选择oldSlice的范围时,可以超过所包含的元素个数,比如newSlice可以基于oldSlice的前六个元素创建,虽然oldSlice只有5个元素。只要这个范围不超过oldSlice的存储能力(cap()的返回值),那么这个创建程序就是合法的。newSlice中超出oldSlice元素的部分会被填上0。
数组切片的内容复制
数组切片支持go语言的内置函数copy(),用于将内容从一个数组切片复制到另一个数组切片。如果两个数组切片的容量不一样大,就会按其中较小的那个数组切片的元素个数进行复制。 slice1 := []int{1,2,3,4,5}
slice2 := []int{6,7,8}
slice3 := []int{9,10,11,12}
copy(slice1,slice3) //会复制slice3的四个元素到slice的前四个位置
fmt.Println(slice1)
copy(slice2,slice3) //只会复制slice3中的前三个元素到slcie2中
fmt.Println(slice2)
运行结果:
[9 10 11 12 5]
[9 10 11]
3.9 map
map是一堆键值对的未排序集合。(key-value)一个key只能对应一个value,而一个value可以有多个key。
map的声明:
var myMap map[string] PersonInfo
在上面的声明语句中,myMap是声明map的变量名,string是key的类型,PersonInfo是value的类型。
map的创建:
package main
//PersonInfo是一个包含个人详细信息的类型
type PersonInfo struct {
ID string
Name string
Address string
}
func main() {
var myMap1 map[string] PersonInfo //创建方式1:通过var关键字声明
var myMap2=make(map[string] PersonInfo) //创建方式2:通过make()内置函数创建
myMap3 := make(map[string] PersonInfo) //创建方式3:通过:=和make创建
myMap4 := make(map[string] PersonInfo, 100) //创建方式4:指定存储能力为100
myMap5 := map[string] PersonInfo{ //创建方式5:创建并初始化
"Person1":PersonInfo{"1","Jack","Room001"},
}
}
map的赋值:
map的赋值就将key和value像下面的方式对应起来即可:package main
import "fmt"
type PersonInfo struct {
ID string
Name string
Address string
}
func main() {
var myMap = make(map[string] PersonInfo) //通过make内置函数创建myMap
myMap["123"]=PersonInfo{"1","Jack","Room001"} //为myMap赋值,key="123",value=PersonInfo{"1","Jack","Room001"}
fmt.Print(myMap) //运行结果为:map[123:{1 Jack Room001}]
}
注意:map必须要先创建后才可以赋值。map的删除:
go语言提供了内置函数delete(),用于删除容器内的元素。delete(myMap,"123") //删除myMap中key为"123"的键值对
如果"123"这个key不存在,那么调用将什么都不发生,也不会有什么副作用;但如果传入map的变量是nil,该调用将到时程序抛出异常。
map的元素查找:
在go语言中,如果需要从map中查找一个key,可以通过以下代码来实现:value , ok := myMap("123")
if ok { //找到了
//处理找到的value
}
参考资料:《Go语言编程》《Go并发实战》《Google资深工程师讲解Golang》
有疑问加站长微信联系(非本文作者)