1.Go语言中的变量
变量必须先声明再使用。
go语言中使用var来声明变量
变量声明
//变量声明
var name string
var age int
批量声明
//批量声明
var (
nameFirst string
score int
flag bool
)
类型推导、简短变量声明:
func main() {
name = "xiaoming"
flag = false
fmt.Printf("name:%s",name)
//类型推导(根据变量判断事什么类型)
var name2 = "哈哈哈"
fmt.Printf(name2)
//简短变量声明,效果同var
name3 := "xixixi"
}
匿名变量
多返回值时,不想接收某个值,用_标识
func main() {
res, _:= count.Chen(1, 2)
fmt.Print(res)
}
**补充:**go语言中非全局变量,var以后不使用编译不通过。为了节省空间。
2.Go语言中的常量
const
//变量声明
const pi = 3.1415
const (
name = "中国"
age = "5000+"
//不赋值,默认跟上一行保持一致
age2
)
常量不可以被修改
iota
//类似枚举
const (
//默认0
b1 = iota
b2
b3
_
b4
)
func main() {
fmt.Println(b1,b2,b3,b4)
}
consts中每多一次变量声明。iota就会+1
const (
//加一行变量生成iota才会+1
b1 ,b2 = iota+1,iota+2
b3, b4 = iota+1,iota+2
)
func main() {
fmt.Println(b1,b2,b3,b4)
}
3.Go语言中的基本数据类型
Go语言中有丰富的数据类型,除了基本的整型、浮点型、布尔型、字符串外,还有数组、切片、结构体、函数、map、通道(channel)等。
基本数据类型
整型: 分为以下两个大类: 按长度分为:int8、int16、int32、int64 对应的无符号整型:uint8、uint16、uint32、uint64
其中,uint8就是我们熟知的byte型,int16对应C语言中的short型,int64对应C语言中的long型。
类型 描述
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 无符号整型,用于存放一个指针
浮点型数:float32和float64。这两种浮点型数据格式遵循IEEE 754标准: float32 的浮点数的最大范围约为 3.4e38,可以使用常量定义:math.MaxFloat32。可以使用一个常量定义:math.MaxFloat64。
打印浮点数时,可以使用fmt包配合动词%f,代码如下:
package main
import (
"fmt"
"math"
)
func main() {
fmt.Printf("%f\n", math.Pi)
fmt.Printf("%.2f\n", math.Pi)
}
复数:complex64和complex128。
布尔值:bool类型进行声明布尔型数据,true或false。
字符串:string类型声明,内部实现使用UTF-8编码。
字符串转义符:
字符 | 含义 |
---|---|
\r | 回车符(返回行首) |
\t | 制表符 |
’ | 单引号 |
" | 双引号 |
\ | 反斜杠 |
byte和rune类型:组成每个字符串的元素叫做“字符”,可以通过遍历或者单个获取字符串元素获得字符。 字符用单引号(’)包裹起来,如:
var a = 'a'
var b = 'b'
Go 语言的字符有以下两种:
uint8类型,或者叫 byte 型,代表了ASCII码的一个字符。
rune类型,代表一个 UTF-8字符。
if、for、switch
if
eg:
func getLevel() {
score := 60
if score >= 90 {
fmt.Println("A")
} else if score > 75 {
fmt.Println("B")
} else {
fmt.Println("C")
}
}
for(循环结构)
第一种写法:初始值,范围,i++
func forDemo() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}
第二种写法:初始化先写,后用 ;代替
func forDemo2() {
i := 0
for ; i < 10; i++ {
fmt.Println(i)
}
}
第三种写法:初始值和结束值都可以省略,因为i++放在for里面了
func forDemo3() {
i := 0
for i < 10 {
fmt.Println(i)
i++
}
}
for range(键值循环)
forrange结构是Go语言特有的一种的迭代结构,在许多情况下都非常有用。forrange可以迭代任何一个集合(包括数组和map)。语法上很类似其它语言中foreach语句,但依旧可以获得每次迭代所对应的索引。
数组、切片、字符串返回索引和值。:
for key, value := range []int{1, 2, 3, 4} {
fmt.Printf("key:%d value:%d \n", key, value)
}
s := "hello中国"
for i,v := range s{
fmt.Printf("%d: %c \n",i,v)
}
map返回键和值。:
m := map[string]int{"hello": 100, "world": 200}
for key, value := range m {
fmt.Println(key, value)
}
** 通道(channel)只返回通道内的值。**:
switch
使用switch语句可方便地对大量的值进行条件判断。
func switchDemo() {
init:= 3
switch init{
case 1:
fmt.Println("中午")
case 2:
fmt.Println("晚上")
case 3:
fmt.Println("早上")
default:
fmt.Println("输入错误!")
}
}
其中,case可能有多个参数:
func testSwitch3() {
switch n := 3; n {
case 1, 3, 5, 7, 9:
fmt.Println("奇数")
case 2, 4, 6, 8:
fmt.Println("偶数")
default:
fmt.Println(n)
}
}
分支还可以使用表达式,这时候switch语句后面不需要再跟判断变量。例如:
func switchDemo() {
age := 10
switch {
case age < 10:
fmt.Println("小学生")
case age > 10 && age <= 18:
fmt.Println("年轻人")
case age > 18:
fmt.Println("成年了")
default:
fmt.Println("这岁数不对啊")
}
}
goto(跳转到指定标签)
要退出双层for循环很麻烦,这时候可以用goto
func gotoDemo() {
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if j == 2 {
// 设置退出标签
goto breakFlag
}
fmt.Printf("%d-%d \n", i, j)
}
}
return
// 标签
breakFlag:
fmt.Println("结束for循环")
}
数组
Array(数组):
定义
必须指定数组的长度
// 定义一个长度为5元素类型为int的数组a
var a [5]int
var b [5]bool
补充:数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1,访问越界(下标在合法范围之外),则触发访问越界,会panic。
数组的初始化
1。初始化的时候就赋值
func main() {
var testArray [3]int //数组会初始化为int类型的零值
var numArray = [3]int{1, 2} //使用指定的初始值完成初始化
var cityArray = [3]string{"北京", "上海", "深圳"} //使用指定的初始值完成初始化
fmt.Println(testArray) //[0 0 0]
fmt.Println(numArray) //[1 2 0]
fmt.Println(cityArray) //[北京 上海 深圳]
}
2.可以不指定数组的长度,自行推断
func main() {
var testArray [3]int
var numArray = [...]int{1, 2}
var cityArray = [...]string{"北京", "上海", "深圳"}
fmt.Println(testArray) //[0 0 0]
fmt.Println(numArray) //[1 2]
fmt.Printf("type of numArray:%T\n", numArray) //type of numArray:[2]int
fmt.Println(cityArray) //[北京 上海 深圳]
fmt.Printf("type of cityArray:%T\n", cityArray) //type of cityArray:[3]string
}
数组的遍历
数组遍历两种方法,一种for循环,一种用for range
func main() {
var a = [...]string{"北京", "上海", "深圳"}
// 方法1:for循环遍历
for i := 0; i < len(a); i++ {
fmt.Println(a[i])
}
// 方法2:for range遍历
for index, value := range a {
fmt.Println(index, value)
}
}
多维数组
二位数组的定义:
func main() {
a := [3][2]string{
{"北京", "上海"},
{"广州", "深圳"},
{"成都", "重庆"},
}
fmt.Println(a) //[[北京 上海] [广州 深圳] [成都 重庆]]
fmt.Println(a[2][1]) //支持索引取值:重庆
}
二维数组的遍历:
func main() {
a := [3][2]string{
{"北京", "上海"},
{"广州", "深圳"},
{"成都", "重庆"},
}
for _, v1 := range a {
for _, v2 := range v1 {
fmt.Printf("%s\t", v2)
}
fmt.Println()
}
}
切片(slice)
切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。
声明
var name []int
fmt.Print(name)
切片表达式
切片的底层就是一个数组,所以我们可以基于数组通过切片表达式得到切片。 切片表达式中的low和high表示一个索引范围(左包含,右不包含),也就是下面代码中从数组a中选出1<=索引值<4的元素组成切片s,得到的切片长度=high-low,容量等于得到的切片的底层数组的容量。
a := [5]int{1, 2, 3, 4, 5}
// s := a[low:high],左包含右不包含
s := a[1:3]
//len(数组长度),cap(切片的长度):从指针指向的数组位置到最后一个位置才是容量
fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s))
完整切片表达式
a[low : high : max]
a := [5]int{1, 2, 3, 4, 5}
t := a[1:3:5]
fmt.Printf("t:%v len(t):%v cap(t):%v\n", t, len(t), cap(t))
}
使用make()函数构造切片
我们上面都是基于数组来创建的切片,如果需要动态的创建一个切片,我们就需要使用内置的make()函数,格式如下:
make([]T, size, cap)
a := make([]int, 2, 10)
fmt.Println(a) //[0 0]
fmt.Println(len(a)) //2
fmt.Println(cap(a)) //10
切片的本质
切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。
要检查切片是否为空,请始终使用len(s) == 0来判断,而不应该使用s == nil来判断。
切片的赋值拷贝
拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容.是引用类型,会直接修改内存变量
s1 := make([]int, 4) //[0 0 0]
s2 := s1 //将s1直接赋值给s2,s1和s2共用一个底层数组
s2[0] = 100
fmt.Println(s1) //[100 0 0]
fmt.Println(s2) //[100 0 0]
切片遍历
s := []int{1, 7, 9}
for i := 0; i < len(s); i++ {
fmt.Println(i, s[i])
}
for index, value := range s {
fmt.Println(index, value)
}
append()方法为切片添加元素
函数append()可以为切片动态添加元素。 可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面加…)。
var s []int
s = append(s, 1) // [1]
s = append(s, 2, 3, 4) // [1 2 3 4]
s2 := []int{5, 6, 7}
s = append(s, s2...) // [1 2 3 4 5 6 7]
//直接append
var s3 []int
s3 = append(s, 1, 2, 3)
切片的扩容策略
$GOROOT/src/runtime/slice.go源码:
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
使用copy()函数复制切片
使用直接赋值会导致两个数组的值都变成要修改的值,因为共享同一个内存地址。因此使用copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中,copy()函数的使用格式如下:
copy(targetSlice, sourceSlice []T)
// copy()复制切片
a := []int{1, 2, 3, 4, 5}
c := make([]int, 5, 5)
copy(c, a) //使用copy()函数将切片a中的元素复制到切片c
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(c) //[1 2 3 4 5]
c[0] = 1000
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(c) //[1000 2 3 4 5]
从切片中删除元素
官方没有指定的方法,这里的思路是将要删除的元素的前面的数据拼接上要删除后的数组,算是删除了元素。
// 从切片中删除元素
a := []int{30, 31, 32, 33, 34, 35, 36, 37}
// 要删除索引为2的元素
a = append(a[:0], a[3:]...)
fmt.Println(a) //[ 33 34 35 36 37]
}
指针和make和new
指针
取地址操作符&和取值操作符*是一对互补操作符,&取出地址,*根据地址取出地址指向的值。
变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:
对变量进行取地址(&)操作,可以获得这个变量的指针变量。
指针变量的值是指针地址。
对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。
func main() {
//指针取值
a := 10
b := &a // 取变量a的地址,将指针保存到b中
fmt.Printf("type of b:%T\n", b)
c := *b // 指针取值(根据指针去内存取值)
fmt.Printf("type of c:%T\n", c)
fmt.Printf("value of c:%v\n", c)
}
make和new
new函数不太常用,使用new函数得到的是一个类型的指针,并且该指针对应的值为该类型的零值。
/*var a *int
*a = 100
fmt.Println(*a)*/
//上述代码会报错,因为a是指针但没有初始化,是nil
//应当初始化
var a= new(int)
fmt.Println(a)//0xc000088220
fmt.Println(*a)//0,如果new的是string,这块的值是空字符串
make也是用于内存分配的,区别于new,它只用于slice、map以及chan的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。
new与make的区别
二者都是用来做内存分配的。
make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针
b := make(map[string]string,10)
b["哈哈哈"] = "笑一笑"
b["呜呜呜"] = "哭了"
for key, va := range b {
fmt.Print(key+": ")
fmt.Println(va)
}
//呜呜呜: 哭了
//哈哈哈: 笑一笑
map
Go语言中提供的映射关系容器为map,其内部使用散列表(hash)实现,map是引用类型,必须初始化才能使用。
初始化map并添加值并遍历
var a = make(map[string]int,1)//虽然会自动扩容,但是最高估算好容量
a["hahah"] = 1
a["bbb"] = 2
for key, va := range a {
fmt.Print(key+": ")
fmt.Println(va)
}
b := make(map[string]string,10)
b["哈哈哈"] = "笑一笑"
b["呜呜呜"] = "哭了"
for key, va := range b {
fmt.Print(key+": ")
fmt.Println(va)
}
//查看是否有key
va,er := b["ddd"]
if !er {
fmt.Println("没有这个key:"+va)
}
初始化直接赋值
a:= map[string]string{
"name": "go",
"pa": "123456",
}
delete()函数删除键值对
使用delete()内建函数从map中删除一组键值对
b := make(map[string]string,10)
b["哈哈哈"] = "笑一笑"
b["呜呜呜"] = "哭了"
delete(b,"哈哈哈")
for key, va := range b {
fmt.Print(key+": ")
fmt.Println(va)
}
//呜呜呜: 哭了
按照指定顺序遍历map
没有符合的函数,只能先对key进行排序,之后再输出
sortMap :=make(map[string]int,200)
for i := 1000; i > 0; i-- {
//生成数
sortMap["stu"+strconv.Itoa(i)] = i
}
keys := make([]string,0,200)
for key := range sortMap {
keys = append(keys,key)
}
sort.Strings(keys)
fmt.Println(sortMap)
for _, key := range keys {
fmt.Println(sortMap[key])
}
元素为map类型的切片
在切片中加入map元素
s := make([]map[int]string,0,10)
//要初始化才能使用
mapTemp := make(map[int]string,10)
mapTemp[1] = "哈哈哈"
mapTemp[2] ="xixixi"
//添加元素
s = append(s,mapTemp)
//再次添加一个
mapTemp2 := make(map[int]string,10)
mapTemp2[1] = "哈哈哈2"
mapTemp2[2] ="xixixi2"
s = append(s,mapTemp2)
fmt.Println(s)
结果://[map[1:哈哈哈 2:xixixi] map[1:哈哈哈2 2:xixixi2]]
值为切片类型的map
s :=make(map[string][]int,10)
s["笑"] = []int{1,2,3}
s["哭"] = []int{4,5,6}
fmt.Println(s)
fmt.Println(s["哭"])
结果:
map[哭:[4 5 6] 笑:[1 2 3]]
[4 5 6]