go语言变量
Go中变量名有字母、数字、下划线组成,首个字符不能为数字。
// 声明变量
var identifier type
// 同时命名多个变量
var indetifier1, identifier2 type
指定变量类型,没有初始化,变量默认零值
-
数值类型(包括complex64/128) 为0
-
布尔类型为false
-
字符串为""
-
以下类型为 nil
var a *int var a []int var a map[string]int var a func(string)int var a error
根据值自行判断变量类型
var v_name = value
:= 声明变量
v_name := value
var intVal int
intVal = 1
//相当于
intVal :=1
多变量声明
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3
//因式分解关键字的写法一般用于声明全局变量
var(
vname1 v_type1
vname2 v_type2
)
空白标识符_ 被用于抛弃值,是一个只写变量,不可读。
变量的生命周期
全局变量生命周期是程序存活时间
局部变量是函数存活时间
条件语句
switch中,每个case默认break。 如果希望从某个case顺序往下执行,可以使用fallthrough关键字。
数组与切片
如果初始化切片的长度不为0, 那么切片此时存的是 零值。如果是string类型的切片,那么为空串。
go语言循环
go中循环只有for一种。
for循环的形式
-
类似c语言的for循环
for init; condition; post { } -
类似c语言的while
for condition { } -
类似C语言的 for(; ;)
for { }- Init : 一般为赋值表达式, 给控制变量赋初始值
- condition: 关系表达式或逻辑表达式, 循环控制条件
- post: 为赋值表达式,给控制变量增量或减量。
for i := 0; i < 10; i++{ fmt.Println(i) }
for range
for 循环的range 格式可以对 slice、map、 数组、 字符串等进行迭代循环。
for key, value := range oldMap{
newMap[key] = value
}
key 和 value 可以省略
//只读取key
for key := range oldMap{
}
//只读取value
for _, value := range oldMap{
}
for range的问题
-
for range 获取不到所有元素的地址
arr := [2]int{1, 2} res := []*int{} for _, v := range arr{ res = append(res, &v) } fmt.Println(*res[0], *res[1]) //expect 1 2 //实际这里输入的是 2 2可以看到for-range 其实是语法糖, 内部还是for循环, 初始化会拷贝要遍历的列表(如array、slice、map), 每次遍历的v都是对同一元素的遍历赋值,也就是如果对v取地址, 最后只会是同一个地址,对应的值就是遍历的元素赋值给v的值。
go编译源码
// len_temp := len(range) // range_temp := range // for index_temp = 0; index_temp < len_temp; index_temp++ { // value_temp = range_temp[index_temp] // index = index_temp // value = value_temp // original body // }如果想要得到预期结果
// 使用局部变量v1拷贝v for _, v := range arr{//局部变量v1替换了v,也可用别的局部变量名 v1 := v res = append(res, &v1) } //直接使用索引获取原来的元素 for k := range arr{ res = append(res, &arr[k]) } -
循环是否会停止?
v := []int{1,2,3} for i := range v{ v = append(v, v[i]) }在循环遍历的同时往遍历的切片中追加元素,循环会停止么?
答案:会
for range 在遍历前对 遍历的数组进行了拷贝,所以期间对原来数组的修改不会影响到遍历中。
a := []int{1, 2, 3} temp := a lenTemp := len(temp) for index := 0; index < lenTemp; index++{ valueTemp := temp[index] _ = index value := valueTemp a = append(a, value) fmt.Println("len(a)=", len(a)) }使用for range 在对切片a遍历的时候, 显示将a复制给一个临时变量temp,然后根据temp的长度做遍历,最后在append的时候将取到value追加到原切片a中。
-
对大数组遍历的问题
// 假设值都为1, 这里只赋值3个 var arr = [204800]int{1, 1, 1} for i, n := range arr{ _ = i _ = n }因为for range时,其实是对原数组做了一次深拷贝, 但是像这种大数组拷贝一次,对内存是极大浪费,应该做如何优化?
- 对数组取地址遍历
for i,n := range &arr,因为数组去的是地址,不是整个数组,所以在拷贝的时候是拷贝的地址,比整个数组小的多。 - 对数组做切片引用
for i, n := range arr[:]
- 对数组取地址遍历
-
对大数组重置的效率问题
// 假设值都为1, 这里只赋值3个 var arr = [204800]int{1, 1, 1} for i, n := range arr{ arr[i] = 0 }重置的效率更高
go对这种重置元素值为默认值的遍历是有优化。go源码
-
对map遍历时删除元素能遍历到么?
var m = map[int]int{1:1, 2:2, 3:3} var once sync.Once for i := range m{ once.Do(func(){ for _, key := range []int{1,2,3}{ if key != i{ fmt.Printf("当前 i:%d, del key:%d\n", i, key) delete(m, key) break } } }) fmt.Printf("%d%d\n", i, m[i]) } //结果 当前 i:1, del key:2 11 33map内部实现是一个链式hash表,为保证每次无序,初始化时会随机一个遍历开始的位置,如果删除的元素开始没被遍历到,后边就不会出戏啊 。
-
对map遍历时 新增元素能遍历到么?
var m = map[int]int{1:1, 2:2} for i, _ := range m{ m[4] = 4 fmt.Printf("%d%d\n", i, m[i]) } 可能会,map的hash表初始化时会随机一个遍历开始的位置。
-
for rangez中 这样起goroutine 能得到想要的结果么?
var m = []int{1,2,3} for i := range m{ go func(){ fmt.Print(i) }() } // 阻塞1分钟 等待所有goroutine运行完 time.Sleep(time.Millisecond) //答案 2 2 2可能是因为当for 循环执行完之后, goroutine才开始执行,这时val的值指向切片中最后一个元素,所以三个goroutine打印出来的结果相同。
-
以参数方式传入
for i := range m{ go func(i int){ fmt.Print(i) }(i) } -
使用局部变量拷贝
for i := range m{ j := i go func(){ fmt.Print(j) }() }
-
go语言函数
go语言的函数的作用 为了代码复用, 是一个基本的代码块,执行一个任务。
函数定义
func function_name([parameter list])[return_type]{
//函数体
}
- func:函数关键字,任何一个函数都有func关键字开始声明。
- function_name:函数名称,参数列表和返回值类型构成了函数签名。
- parameter list:参数列表,参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
- return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
- 函数体:函数定义的代码集合。
- 注意:go语言函数区别于c++和java语言的地方是可以有多个返回值,多个返回值用小括号括起来,中间用逗号分隔。
函数变量
函数变量作回调函数的例子:
// 声明一个函数类型
type fc func(int) int
func main() {
CallBack(1, callBack) //执行函数 -- CallBack
}
func CallBack(x int, f fc) { //定义一个函数testCallBack
f(x) //由于传进来的是callBack函数,该函数执行需要传入一个int类型函数,因此传入x
}
func callBack(x int) int {
fmt.Printf("我是回调 x: %d\n", x)
return x
}
函数闭包
闭包就是匿名函数,go语言支持匿名函数。
匿名函数的优越性在于可以直接使用函数内的变量,不必声明,可以使代码更简单,增强代码的可读性。
func getNumber() func() int {
i := 0
return func() int {
i += 1
return i
}
}
func main(){
// nextNumber 作为一个函数,函数i 为0
nextNumber := getNumber()
//调用nextNumber函数, i 变量自增1 并返回
fmt.Println(nextNumber())
fmt.Println(nextNumber())
fmt.Println(nextNumber())
//创建新的函数 nextNumber1
nextNumber1 := getNumber()
fmt.Println(nextNumber1())
fmt.Println(nextNumber1())
fmt.Println(nextNumber())
}
//输出
1
2
3
1
2
i 变量是存储在 getNumber 函数的闭包中的。当调用 getNumber 函数时,它会返回一个内部的匿名函数,这个匿名函数就是闭包函数。
闭包函数中的 i 变量被捕获并绑定到闭包函数中,形成了一个封闭的作用域。每次调用闭包函数时,都可以访问和修改这个 i 变量。由于 i 存在于闭包函数的作用域中,它的值会在每次调用闭包函数时被保留下来,不会随着函数的返回而销毁。
go语言常量
常量的定义: 是一个简单值的标识符,在程序运行时,不可以被修改。(布尔类型、数字类型(浮点型、整数型和复数)和字符串)
const identifier [type] = value
//类型说明符 [type]可以省略,因为编译器可以根据变量的值来推断其类型。
常量用作枚举
常量可以用len(), cap() , unsafe.Sizeof() 函数计算表达式的值。
字符串类型 unsafe.Sizeof() 函数一直是16, 字符串类型对应一个结构体, 结构体有两个域,第一个域 指向该字符串的指针,第二个域是字符串的长度,每个域占8个字节。
const (
a = "abc"
b = len(a)
c = unsafe.Sizeof(a)
)
func main(){
fmt.Println(a, b, c)
}
//答案
abc 3 16
iota
iota是一个特殊常量, 可以被编译器修改的常量, iota 在 const关键字出现时 将被重置为0(const 内部的第一行之前), const中每新增一行常量声明将使iota 计数一次。
func main() {
const (
a = iota
b
c
d = "ha"
e
f = 100
g
h = iota
i
)
fmt.Println(a, b, c, d, e, f, g, h, i)
}
//输出结果
0 1 2 ha ha 100 100 7 8
在 Go 语言中,如果常量没有显式地指定值,则会采用上一个常量的值作为默认值。
const (
j = 1 << iota //<<表示左移操作
k = 3 << iota
l
m
)
func main() {
fmt.Println(j, k, l, m)
}
//输出
1 6 12 24
go语言运算符
-
算术运算符
A=10, B=20
运算符 描述 实例 + 相加 A+B = 30 - 相减 A-B=-10 * 相乘 A * B = 200 / 相除 B / A = 2 % 求余 B % A = 0 ++ 自增 A++ = 11 -- 自减 A-- =9 -
关系运算符
A= 10, B= 20
运算符 描述 实例 == 左边的值是否等于右边值,如果是返回True否则返回False (A==B) 为False != 左边的值是否不等于右边值,如果是返回True否则返回False (A != B)为True 左边的值是否大于右边值,如果是返回True否则返回False (A>B)为False < 左边的值是否小于右边值,如果是返回True否则返回False (A<B)为True >= 左边的值是否大于等于右边值,如果是返回True否则返回False (A>=B)为False <= 左边的值是否小于等于右边值,如果是返回True否则返回False (A<=B)为True -
逻辑运算符
A = true, B = false
运算符 描述 实例 && 逻辑AND 运算符,两边的操作数都是True,则为True,否则False (A && B) false 逻辑OR运算符,两边操作数都是False,则为False,否则为True (A B)为 true ! 逻辑NOT运算符,如果条件为True, 则逻辑NOT条件为False,否则为True !(A && B)为true -
位运算符
位元算符是对内存中二进制数进行按位运算
A=60(00111100) B = 13(00001101)
运算符 描述 实例 & 按位与运算符“&”是双目运算符,参与运算的两数各对应的二进位相与 A&B 结果为12 二进制为00001100 | 按位或元算符" "是双目元算符,参与运算的两数各对应的二进位相或 A|B结果为61二进制为00111101 按位异或运算符,双目运算符,参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。 A^B结果为49,二进制为00110001 >> 右移运算符,右移n位就是除以2的n次方。把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数。 A>>2结果为15,二进制为00001111 << 左移运算符,左移n位就是乘以2的n次方。把"<<"左边的运算数的各二进位全部左移若干位,"<<"右边的数指定移动的位数,右移运算符,右移n位就是除以2的n次方。把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数。 A<<2结果为240,二进制为11110000 右移运算符,右移n位就是除以2的n次方。把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数。
-
赋值运算符
运算符 描述 实例 = 简单的赋值运算符,将一个表达式的值赋给一个左值 C=A+B += 相加后赋值 C +=A等于 C = C + A -= 相减后赋值 C -=A等于 C = C - A *= 相乘后赋值 C *=A等于 C = C * A /= 相除后赋值 C /=A等于 C = C / A %= 求余后赋值 C %=A等于 C = C % A <<= 左移后赋值 C <<=2等于 C = C << 2 >>= 右移后赋值 C >>=2等于 C = C >>2 &= 按位与后赋值 C &=2等于 C = C & 2 ^= 按位异或后赋值 C ^=2等于 C = C ^ 2 = 按位或后赋值 C =2等于 C = C 2 -
其他运算符
有取地址运算符&和指针运算符*
运算符 描述 实例 & 返回变量存储地址 &a 将给出变量a的实际地址 * 指针变量 *a 是一个指针变量
运算符优先级
有些运算符拥有较高的优先级,二元运算符的运算方向均是从左至右。
| 5 | * / % << >> & &^ | ||
|---|---|---|---|
| 4 | + - ^ | ||
| 3 | == != <<= >>= | ||
| 2 | && | ||
| 1 |
通过使用括号来临时提升某个表达式的整体运算优先级。