变量
变量声明
1.普通声明
var a string //声明一个字符串类型变量---空值类型:“”(空字符串)
var b int//声明一个整型变量---空值类型:0
var c bool//声明一个布尔类型变量---空值类型:false
2.批量声明
这样就不用每次声明变量都写一遍var关键字,十分便捷
var (
a string
b int
c bool
)
注:Go语言中,非全局变量声明了必须被使用,否则会报错
变量初始化
1.单个赋值
var a string = "like"
var b int = 20
在Go中,我们甚至可以不具体写出变量类型,直接赋值,它会为我们自动确定类型
var a = "like"
var b = 20
2.同时多个赋值
我们可以一次性初始化多个变量
var a, b = "like", 20
3.简短变量声明
a := "like"
等同于:
var a = "like"
注:简短变量声明只能在函数
中使用
4.匿名变量
例如一个函数的返回值有两个,而其中一个我们并不关心,可以用匿名变量去参与赋值(实际上就是将返回值舍弃),匿名变量不占用内存,不占用命名空间,可以重复使用。
使用匿名变量可以防止我们产生没有使用的空闲变量,从而避免报错。
func ret() (int, string) {
return 20, "like"
}
func main() {
b, _ := foo()
_, a := foo()
fmt.Println("a=", a)
fmt.Println("y=", b)
}
常量
声明方式类似普通变量,也可以单个赋值
,同时多个赋值
:
const pi = 3.14
const e = 2.71
等同于:
const (
pi = 3.14
e = 2.71
)
特殊用法:
const (
a = 20
b
c
)
利用这种方式,上面一行常量赋了值,下面的赋值进行省略,默认下面常量赋值与上面一行赋了值的相同,此处a
, b
, c
的值全是20
iota:
Go语言中有一种叫做iota
的常量计数器,在const里面首次声明它时,默认值是0
,在const中每次多一行常量声明,iota的值加1
const (
a = iota //0
b //1
c //2
d //3
)
为了方便大家理解iota
的用法,这里给出几个例子:
const (
a = iota //0
b //1
_ //2 (被舍弃了)
d //3
)
const (
a = iota //0
b = 20 //20
c //20
d //20
)
const (
a = iota //0
b = 20 //20
c = iota //2
d //3
)
基本数据类型
整型
类型 | 描述 |
---|---|
uint8 | 无符号 8位整型 (0 到 255) |
uint16 | 无符号 16位整型 (0 到 65535) |
uint32 | 无符号 32位整型 (0 到 4294967295) |
uint64 | 无符号 64位整型 (0 到 18446744073709551615) |
int8 | 有符号 8位整型 (-128 到 127) |
int16 | 有符号 16位整型 (-32768 到 32767) |
int32 | 有符号 32位整型 (-2147483648 到 2147483647) |
int64 | 有符号 64位整型 (-9223372036854775808 到 9223372036854775807) |
特殊整型
类型 | 描述 |
---|---|
uint | 32位操作系统上就是uint32 ,64位操作系统上就是uint64 |
int | 32位操作系统上就是int32 ,64位操作系统上就是int64 |
uintptr | 无符号整型,用于存放一个指针 |
占位符
func main() {
a := 20
fmt.Printf("%T\n",a)//输出数据类型
fmt.Printf("%v\n",a)//输出存储值(任何数据类型都可以)
fmt.Printf("%b\n",a)//输出2进制数
fmt.Printf("%o\n",a)//输出8进制数
fmt.Printf("%x\n",a)//输出16进制数
b := "like"
fmt.Printf("%s\n",b)//输出字符串
}
字符与字符串
组成每个字符串的单独元素为字符,用''
表示,字符串用""
表示
a := "like"
b := 'H'
Go语言字符有两种:
uint8
类型,也可叫做Byte
类型,可代表ASCII
码中的一个字符,占 8bit == 1Byteint32
类型,也可叫做rune
类型,可代表UTF-8
字符,占 32bit == 4 Byte
当我们处理汉字或其他复杂文字时就要用到int32
类型,因为一个汉字可能占据3或4个字节, 推断法赋值字符的时候默认使用in32
多行字符串(利用反引号
进行赋值):
func main() {
s1 := `第一行
第二行
第三行
`
fmt.Println(s1)
}
输出结果为:
第一行
第二行
第三行
反引号会保留其区间内所有字符
修改字符串
Go语言无法直接修改字符串,要想进行修改,首先要将其转换成[]byte
或者[]rune
型,修改后再转换回string
类型,但两种转换都会需要新分配的内存来复制字节数组。
func main() {
a := "like"
// 强制类型转换
S1 := []byte(s1)
S1[0] = 'd'
fmt.Println(string(S1))//输出结果变成 dike
b := "黑猫"
S2 := []rune(b)
S2[0] = '白'
fmt.Println(string(S2))//输出结果变成 白猫
}
类型转换
Go语言只有强制类型转换,且布尔类型bool
不可以进行类型转换
func main(){
var a = 20
fmt.Printf("%T\n",a)//输出结果为 int
b := float64(a)
fmt.Printf("%T\n",b)//输出结果为 float64
}
流程控制
1.if else 分支结构
func main() {
a := 60
if a >= 80 {
fmt.Println("A")
}
else if score > 70 {
fmt.Println("B")
}
else {
fmt.Println("C")
}
}
if 条件判断特殊用法:
可以在条件判断前加入一个执行语句,先执行再判断(注意作用域,在 if 后声明的变量只在 if 中有效)
func main() {
if a := 60; a >= 80 {
fmt.Println("A")
}
else if score > 70 {
fmt.Println("B")
}
else {
fmt.Println("C")
}
}
2.for 循环结构
func main() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}
当然,我们也可以把 i := 0
写在 for 之前,将 i++
写在for循环中。
Go语言中没有while
循环,只有 for 循环。
数组
数组定义
var a [10]int//创建一个长度为10的int类型数组
注意:数组长度
也是数组类型的一部分,不同长度的数组,类型不一样
定义数组时,[]
中只能是常量,不能是变量。
func main(){
var b [20]int
var c [30]int
fmt.Printf("%T\n",b)//输出结果为 [2o]int
fmt.Printf("%T\n",c)//输出结果为 [30]int
}
数组初始化
1.普通赋值,提前确定好数组长度
func main() {
var a [3]int //声明之后,数组会初始化成零值
var b = [3]int{0, 1, 2} //利用确切值进行赋值
var c = [3]string{"like", "apple", "pear"}
fmt.Println(a) //[0 0 0]
fmt.Println(b) //[0 1 2]
fmt.Println(c) //[like apple pear]
}
2.自动推断数组长度
func main() {
var a [3]int
var b = [...]int{1, 2}
var c = [...]string{"like", "apple", "pear"} //...会根据后面赋值情况自动推断数组长度
fmt.Println(a) //[0 0 0]
fmt.Println(b) //[1 2]
fmt.Printf("%T\n", b) //[2]int
fmt.Println(c) //[like apple pear]
fmt.Printf("%T\n", c) //[3]string
}
3.利用索引进行初始化
func main() {
a := [...]int{1: 1, 3: 4} //给a[1]赋值 1,给a[3]赋值 4
fmt.Println(a) //[0 1 0 4]
fmt.Printf("%T\n", a) //[4]int
}
数组的遍历
1.普通for循环
func main() {
var a = [...]string{"apple", "like", "pear"}
for i := 0; i < len(a); i++ {
fmt.Println(a[i])
}
}
2. for range方法
func main() {
var a [10]int
for index, value := range a {//第一个变量存索引值,第二个存数组对应索引存储的值
fmt.Println(index, value)
}
}
多维数组
func main() {
a := [3][2] int{
{1, 2},
{3, 4},
{5, 6}, //如果括号在下一行,这里要加逗号,同一行则不用
}
fmt.Println(a) //[[1 2] [3 4] [5 6]]
fmt.Println(a[2][1]) //6
}
多维数组只有第一个维度
可以利用推断法推断长度:
func main() {
b := [...][2]int{{7,8},{9,10},{10,11}}
fmt.Println(b)
}
注:数组是一个值类型
的变量,赋值和传参会复制整个数组, 支持“==“、”!=” 操作
切片
切片是一个存储相同类型元素长度可变的序列,它不是数组!!!
。
它是一个引用类型,可以理解为是一种指针。
声明
var a []int//声明一个叫 a 的int类型切片
切片初始化零值不管什么类型切片,都是nil
(空),虽然输出结果有点像Java中的空链表,但它就是“空”
func main() {
var a []string
var b []int
var c = []bool{false, true}
fmt.Println(a) //[]
fmt.Println(b) //[]
fmt.Println(c) //[false true]
fmt.Println(a == nil) //true
fmt.Println(b == nil) //true
fmt.Println(c == nil) //false
// fmt.Println(c == d) //切片是引用类型,不支持直接比较,只能和nil比较
}
初始化
1.直接赋值
func main() {
a := []int{1,2,3,4}
b := []string{"like","apple","pear"}
c := [][]int{{1,2},{3,4},{5,6}}
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
}
2.由数组切割得到切片
func main() {
a := [...]int{1,3,5,7,9,11}
b := a[0:4]//通过数组切割得到切片,索引值左包含,右不包含
fmt.Println(b)//[1 3 5 7]
}
注!!!
:当我们创建一个新的切片并且直接赋值时,实际上是在底层创建了一个等长数组,并将等长数组从头切到尾得到一个切片,切片相当于只是一个框,它里面没有实际存储的值,存储的值都在底层数组里,而我们通过这个框(指针)索引出数组里的值。
几种特殊切割方式:
func main() {
a := [...]int{1,3,5,7,9,11}
b := a[:4]// = a[0:4]
d := a[2:]// = a[2:len(a)]
e := a[:]// = a[0:len(a)]
fmt.Println(b)//[1 3 5 7]
fmt.Println(d)//[5 7 9 11]
fmt.Println(e)//[1 3 5 7 9 11]
}
3.从切片里切割得到切片
func main() {
a := [...]int{1,3,5,7,9,11}
b := a[0:5] //[1 3 5 7 9]
c := b[1:3] //[3 5]
fmt.Println(c)
}
注:
如果切片底层数组的值发生变化,那么对应上层的切片返回值也会发生变化
切片长度和切片容量
我们可以使用len()
函数求切片长度,用cap()
函数求切片容量
func main() {
a := [8]int{1,2,3,4,5,6,7,8}
b := a[1:5] // [2 3 4 5]
c := b[1:2] // [3]
fmt.Println(cap(b))// 7
fmt.Println(len(b))// 4
fmt.Println(cap(c))// 6
fmt.Println(len(c))// 1
}
这里我详细说一下切片的长度
以及容量
含义,解释一下为何上面的输出会是这些值。
就像我之前所说的那样,切片相当于一个框,它需要底层有数组做支撑才有意义:
长度
:切片框住元素个数
容量
:切片第一个指针索引的位置到底层数组最后一个位置容量大小
注:像 c 这种切片的切片,他的底层数组和被切切片一样,索引的时候在被切切片处索引,长度定义一样,容量大小依照原底层数组为准,看新切片索引位置在底层数组何处,从其开始到结尾容量大小。
利用Make()函数创建切片
a := make([]Type,len,cap)//创建一个类型为“Type”,长度为“len”,容量为“cap”的切片
b := make([]Type,c)
//假如[]Type后面只写了一个数“c”,表明长度和容量都为c
用make()方法创建出切片,会自动生成一个跟容量同长度的全是零值的底层数组。
make()方法内的“len”和“cap”可以用变量赋值,也可用常量赋值,和数组不同。
func main() {
var a []int = make([]int,5)//生成一个长度和容量均为5的int类型切片
var b [5]int//生成数组,[]中只能写常量,不能写变量
c := make([]int,3,10)//生成一个长度为3,容量为10的int类型切片
fmt.Printf("\nlen(a): %d\ncap(a): %d\n",len(a),cap(a))
//len(a): 5 cap(a): 5
fmt.Println(a)//[0 0 0 0 0]
fmt.Println(b)//[0 0 0 0 0]
fmt.Printf("\nlen(c): %d\ncap(c): %d\n",len(c),cap(c))
//len(c): 3 cap(a): 10
fmt.Println(c)//[0 0 0]
d := 2
e := make([]int,d)
fmt.Println(e)// [0 0]
}
切片之间不可进行比较
不能使用==
来比较两个切片元素是否完全相等,切片只可和nil
比较
注:一个nil
值的切片,长度和容量均为0,但是长度和容量均为0的切片不一定为nil
var a []int //len(a)=0;cap(a)=0;a==nil
b := []int{} //len(b)=0;cap(b)=0;b!=nil
c := make([]int, 0) //len(c)=0;cap(c)=0;c!=nil
var d [0]int //长度为0的数组,没有存任何东西,但是也被分配了内存
nil
的本质在于有无分配内存。
第一行创建了一个空切片,没有底层数组,没有分配内存,所以为空
第二行创建了一个空切片,但是有底层数组,底层数组跟“d”类似,不为空,被分配了内存
第三行创建了一个空切片,但是有底层数组,底层数组跟“d”类似,不为空,被分配了内存
但是对于三者而言,他们的长度和容量均为0。
因此要判断一个切片是否无值,要用len(a) == 0
,不能用a == nil
切片值的修改
原切片的值被赋给新切片后,二者共用同一个底层数组,修改切片中的值,相当于更改底层数组的值,因此两个切片的值都会发生变化。
func main() {
a := []int{1,2,3}
b := a //将切片a的值直接赋值给b,a和b共用一个底层数组
fmt.Println(a,b) //[1 2 3] [1 2 3]
b[0] = 10
fmt.Println(a,b) //[10 2 3] [10 2 3]
}
append() 方法扩容切片
append() 方法可以一次添加1个值或者多个值,也可将其他切片的值添加进去。
注:同时添加其他切片的值和确切数字,变量是不允许的
例如:
a = append(a, b…, 10) 这样会报错
a = append(a, b…, e)这样也会报错
但是我们可以同时添加变量和确切数字
func main(){
var a []int
a = append(a, 1) //[1]
a = append(a, 2, 3, 4) //[1 2 3 4]
b := []int{5, 6, 7}
a = append(a, b...) //[1 2 3 4 5 6 7]
e := 11
a = append(a, e, 12) //[1 2 3 4 5 6 7 11 12]
}
func main() {
a := []int{1,2,3,4,5}
b := a[0:2]
for i:=0;i<len(a);i++{
b = append(b,9)
fmt.Println("value of b:",b,"cap of b:",cap(b))
fmt.Println("valie of a:",a,"cap of a:",cap(a))
fmt.Println("---------------")
}
}
运行结果:
刚开始我们知道切片a
与b
共用一个底层数组。
之后利用append()方法为切片b
扩容。
刚开始扩容时,切片b的索引没有超过底层数组的容量,因此,为其扩容赋值相当于改变切片b对于底层数组的索引范围并且改变底层数组中存储的值。
再继续扩容后,发现底层数组容量不够,新出现的索引会越界,这时append()方法会单独为切片b创建一个新的扩容后的底层数组,并把原来的值拷贝进去,根据图所示,发现cap(b)
变成了10,这就是创建了一个新底层数组给切片b使用的结果,但是原来切片a的底层数组不会继续因为切片b的扩容而改变,因为切片b已经换了一个底层数组去操作。
copy()方法复制切片
copy(des,src) 将切片src的值,复制到 切片des中
实际上就是将src索引范围内底层数组的值,复制到des索引范围内底层数组。
func main() {
a := []int{1, 2, 3, 4, 5}
b := make([]int, 5, 5)
copy(b, a)
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(b) //[1 2 3 4 5]
b[0] = 99
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(b) //[99 2 3 4 5]
}
两个切片范围不一样也可以copy(),多余的部分舍弃
func main() {
a := []int{1, 2, 3, 4, 5}
b := a[1:3] //[2 3]
d := []int{9,10,11}
copy(b, d)
fmt.Println(b) //[9 10]
fmt.Println(a) //[1 9 10 4 5]
}
删除切片中的元素
Go语言没有专门删除切片元素的方法,但是这个可以用 append() 方法解决:
func main() {
a := []int{1, 2, 3, 4, 5, 6, 7, 8}//我们想把4删除
a = append(a[:3], a[4:]...)
fmt.Println(a) //[1 2 3 5 6 7 8]
}
为了更好地理解是如何利用 append() 方法的,再举一个例子:
func main() {
a := [...]int{1, 3, 5}
b := a[:]
fmt.Println(b)//[1 3 5]
b = append(a[:1],a[2:]...)
fmt.Println(b)//[1 5]
fmt.Println(a)//[1 5 5]
}
实质上就是创建一个基于底层数组的切片a[:1]
,索引范围就是“0”,然后在其基础上扩容,将底层数组从a[2]到结尾的值扩充进新创建的切片中,注意之前上面讲的append()方法扩充,当扩充不会导致索引越界时,只会发生覆写,因此这里会看到底层数组的值发生了改变,最后将新生成的切片返回给切片b
,而切片b
实际上就是底层数组改变值后的a[:2]
。