变量字母数字下划线_, 并且只能字母和_ 开头
go语言同python一样,每个语句的结尾可以不用加分号,因为编译器会在结尾自动加上
常量
标准声明:
var 变量名 类型 , var demo int
批量声明:
var (
a string
b int
c bool
)
变量的初始化
:
go会自动的对每个变量初始化类型默认的初始值,整形和浮点型默认 0 ,字符串默认 空字符串" ",布尔型默认 False, 指针、切片、映射、通道、函数和接口的零值则是 nil
var demo int = 3
var name , age = “asda”,18
短变量
go中可以使用 " := " 的形式来赋值,其次我们也可以省略掉类型,go会自动推导类型并初始化
demo := 3
全局变量和局部变量
同导包过程一样定义在main方法外面的变量,称作全局变量,而在函数,for循环,等结构语句中就是局部变量,都有各自的一个作用域
匿名元变量
比如迭代时,不想要某个值就可以使用 “ _ ” 来接收忽略掉这个值
匿名变量不占用命名空间,不分配内存,所以也就不存在重复声明
注意
1、函数外的每个语句都必须以关键字开始(var、const、func等)
2、:=
不能使用在函数外。
3、_
多用于占位,表示忽略值。
iota常量同变量类似,只是把
var
关键字换成了const
,
常量定义时必须赋初始值
这个东西是go语言的常量计数器,只能在常量表达式中使用
在const关键字出现时将被重置为0。
const中每新增一行常量声明将使 iota 计数一次(iota可理解为const语句块中的行索引)。
const (
n1 = iota //0
n2 //1
_ // _ 表示跳过 2
n3 //3
n4 //4
)
const (
n1 = iota //0
n2 = 100 //100 中间插队
n3 = iota //2
n4 //3
)
const n5 = iota //0
const (
a, b = iota + 1, iota + 2 //1,2
c, d //2,3
e, f //3,4
)
基本数据类型
这一块同其他高级语言是一样的
go也是强类型的语言
整型分为以下两个大类:
按长度分为:int8、int16、int32、int64
对应的无符号整型:uint8、uint16、uint32、uint64
注意
uint8
就是我们熟知的byte型
int16
对应C语言中的short型
int64
对应C语言中的long型
浮点型:
float32
和float64
复数:
complex64
和complex128
复数有实部和虚部,complex64的实部和虚部为32位,complex128的实部和虚部为64位
字符串
go 和 其他语言不一样的地方,双引号 " " 包起来的才是字符串,单引号 ’ ’ 包起来的是字符,一定要注意区别,错了会报错就很烦
然后就是使用多行字符串时用反引号包起来 ,python中是三对引号
if 语句
提一下 rune 类型
前面说了单引号包起来的是字符,go语言中有两种表示
uint8类型
,或者叫 byte 型,代表了ASCII码的一个字符。
rune类型
,代表一个 UTF-8字符。
go使用 rune 类型来处理Unicode字符,字符串更好的扩展性,==rune 类型实际上是一个 int32 ==
for循环
常规的格式
if (表达式1) {
} else if (表达式2){
} else {
}
加执行语句的if
ifscore := 65;
score >= 90 {
fmt.Println(“A”)
} else if score > 75 {
fmt.Println(“B”)
} else {
fmt.Println(“C”)
}
go语言中是没有
while
的,一切的循环语句都可以通过 for 来完成
常规格式
for i := 0; i < 10; i++ {
fmt.Println(i)
}
当然也可以忽略掉初始语句和结束语句,只保留条件语句
其实就类似while(条件语句)
的格式
无限循环
for {
循环体语句
}
可以通过break、goto、return、panic语句强制打断
for range 键值循环
遍历数组、切片、字符串、map 及通道,返回索引和值,有点像python了
a := [...]byte{'a', 'b', 'c', 'd'}
for key, value := range a {
fmt.Printf("%d %c\n", key, value)
}
switch case
数组同C语言一样,直接跳过
var d
emo [10]int
前面讲过不赋值会自动初始化 0
var demo = [5]int{1,2,3,4,5}
...
go自己推导数组长度
var demo = […]int{2,3,4,6,7,6,8,3}
初始化时可以加下标索引
var demo = […]byte{1:‘a’,2:‘b’,4:‘c’}[0,a,b,0,c]
二维数组
func main() {
a := [3][2]string{
{"北京", "上海"},
{"广州", "深圳"},
{"成都", "重庆"},
}
for _, v1 := range a {
for _, v2 := range v1 {
fmt.Printf("%s\t", v2)
}
fmt.Println()
}
}
切片
因为数组的长度是固定的并且数组长度属于类型的一部分,所以数组有很多的局限性
切片是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。非常灵活,支持自动扩容
var demo []int
定义方式和数组不同的就是没有提前给出长度
使用也类似于python的切片
a := [...]byte{'a', 'b', 'c', 'd'}
var src []byte
src = a[1:3]
fmt.Printf("%c", src)
上面都是基于数组来创建的切片
动态切片
,就需要使用内置的make()函数
下面例子a
的内部存储空间分配了10个,但实际上只用了2个。
容量并不会影响当前元素的个数,所以len(a)返回2,cap(a)则返回该切片的容量。
a := make([]int, 2, 10)
fmt.Println(a) //[0 0]
fmt.Println(len(a)) //2
fmt.Println(cap(a)) //10
切片赋值拷贝
s1 := make([]int, 3) //[0 0 0]
s2 := s1 //将s1直接赋值给s2,s1和s2共用一个底层数组
s2[0] = 100
fmt.Println(s1) //[100 0 0]
fmt.Println(s2) //[100 0 0]
//两个变量共享底层数组,指向同一块内存地址
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]
//一个切片中添加另一个切片中的元素,后面要加 ...
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]
切片中删除元素
go语言中没有删除切片元素的函数方法
a := []int{30, 31, 32, 33, 34, 35, 36, 37}
// 要删除索引为2的元素
a = append(a[:2], a[3:]...)
// 跳过下标为2的元素不append加入新切片
fmt.Println(a) // [30 31 33 34 35 36 37]
map
map是一种无序的基于key-value的数据结构,
go语言中的map是引用类型,必须初始化才能使用。
map类型的变量默认初始值为nil,需要使用make()函数来分配内存
map[键数据类型] 值数据类型
scoreMap := make(map[string]int, 8)
scoreMap["张三"] = 90
scoreMap["小明"] = 100
fmt.Println(scoreMap)
fmt.Println(scoreMap["小明"])
user := map[string]int{
"张三":1,
"李四":2,
"王五":3
}
判断某个键是否存在
value , ok := map[key]
// 如果key存在ok为true,v为对应的值
// 不存在ok为false,v为值类型的零值
v, ok := user["张三"]
if ok {
fmt.Println(v)
} else {
fmt.Println("查无此人")
}
delete() 函数删除键值对
delete(map,key)
值为map类型的切片
func main() {
var mapSlice = make([]map[string]string, 3)
for index, value := range mapSlice {
fmt.Printf("index:%d value:%v\n", index, value)
}
fmt.Println("after init")
// 对切片中的map元素进行初始化
mapSlice[0] = make(map[string]string, 10)
mapSlice[0]["name"] = "张三"
mapSlice[0]["password"] = "123456"
mapSlice[0]["address"] = "123@123.com"
for index, value := range mapSlice {
fmt.Printf("index:%d value:%v\n", index, value)
}
}
值为切片类型的map
func main() {
var sliceMap = make(map[string][]string, 3)
fmt.Println(sliceMap)
fmt.Println("after init")
key := "中国"
value, ok := sliceMap[key]
if !ok {
value = make([]string, 0, 2)
}
value = append(value, "北京", "上海")
sliceMap[key] = value
fmt.Println(sliceMap)
}
函数
func 函数名(参数) (返回值类型){
函数体
}
参数和返回值类型可以不是必须的,没有参数和返回值
可变参数
可变参数是指函数的参数数量不固定。go语言中的可变参数通过在参数名后加...
来标识
可变参数要作为函数的最后一个参数
func intSum(x ...int) int {
fmt.Println(x) // 传入的 x 是一个切片
sum := 0
for _, v := range x {
sum = sum + v
}
return sum
}
return
go语言支持函数多返回值,函数如果有多个返回值时必须用()将所有返回值包裹起来
func calc(x, y int) (int, int) {
sum := x + y
sub := x - y
return sum, sub
}
返回值命名
函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。
func calc(x, y int) (sum, sub int) {
sum = x + y
sub = x - y
return
}
函数类型定义 和 函数类型变量
type calculation func(int, int) int
func add(x, y int) int {
return x + y
}
func main() {
var c calculation
c = add
fmt.Println(c(2, 3))
f := add
fmt.Println(f(2,3))
}
函数作为参数
func add(x, y int) int {
return x + y
}
func calc(x, y int, op func(int, int) int) int {
return op(x, y)
}
func main() {
ret2 := calc(10, 20, add)
fmt.Println(ret2)
}
函数作为返回值
func f(s string) (func(int, int) int, error) {
switch s {
case "+":
return add, nil
case "-":
return sub, nil
default:
err := errors.New("无法识别的 *** 作符")
return nil, err
}
}
匿名函数
匿名函数因为没有函数名,所以没办法像普通函数那样调用
所以匿名函数需要保存到某个变量或者作为立即执行函数
func main() {
// 将匿名函数保存到变量
add := func(x, y int) {
fmt.Println(x + y)
}
add(10, 20) // 通过变量调用匿名函数
//自执行函数:匿名函数定义完加()直接执行
func(x, y int) {
fmt.Println(x + y)
}(10, 20)
}
闭包
闭包指的是一个函数和与其相关的引用环境组合而成的实体。
简单来说,闭包 = 函数+引用环境
变量f是一个函数并且它引用了其外部作用域中的x变量,此时f就是一个闭包
在 f 的生命周期内,变量 x 也一直有效
func adder() func(int) int {
var x int
return func(y int) int {
x += y
return x
}
}
func main() {
var f = adder()
fmt.Println(f(10)) //10
fmt.Println(f(20)) //30
fmt.Println(f(30)) //60
f1 := adder()
fmt.Println(f1(40)) //40
fmt.Println(f1(50)) //90
}
闭包进阶1
func adder2(x int) func(int) int {
return func(y int) int {
x += y
return x
}
}
func main() {
var f = adder2(10)
fmt.Println(f(10)) //20
fmt.Println(f(20)) //40
fmt.Println(f(30)) //70
f1 := adder2(20)
fmt.Println(f1(40)) //60
fmt.Println(f1(50)) //110
}
闭包进阶2
func makeSuffixFunc(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}
func main() {
jpgFunc := makeSuffixFunc(".jpg")
txtFunc := makeSuffixFunc(".txt")
fmt.Println(jpgFunc("test")) //test.jpg
fmt.Println(txtFunc("test")) //test.txt
}
闭包进阶3
func calc(base int) (func(int) int, func(int) int) {
add := func(i int) int {
base += i
return base
}
sub := func(i int) int {
base -= i
return base
}
return add, sub
}
func main() {
f1, f2 := calc(10)
fmt.Println(f1(1), f2(2)) //11 9
fmt.Println(f1(3), f2(4)) //12 8
fmt.Println(f1(5), f2(6)) //13 7
}
defer 语句
go语言中的defer语句会将其后面跟随的语句进行延迟处理
在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行
也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行
用法类似于Java中的 finally 语句,一般用于释放某些已分配的资源
func main() {
fmt.Println("start")
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
fmt.Println("end")
}
start
end
3
2
1
由于defer语句延迟调用的特性,所以defer语句能非常方便的处理资源释放问题
比如:资源清理、文件关闭、解锁及记录时间
defer执行时机
go语言中函数 return 在底层并不是原子 *** 作,它分为返回值赋值和RET指令两步
而 defer 语句执行的时机就在返回值赋值 *** 作之后,RET指令执行之前
defer 一道经典实例
func f1() int {
x := 5
defer func() {
x++
}()
return x
// 返回值未命名,所以返回值=5,defer 是对x再 *** 作,不影响返回值,结果 5
}
func f2() (x int) {
defer func() {
x++
}()
return 5
// 返回值命名x, 所以返回值=x=5,defer 对x再 *** 作,结果 6
}
func f3() (y int) {
x := 5
defer func() {
x++
}()
return x
// 返回值命名y=5, defer 对x再 *** 作,不影响返回值,结果 5
}
func f4() (x int) {
defer func(x int) {
x++
}(x)
return 5
// 返回值命名x=5,defer 后函数中传入的是形参x,不影响x的值,结果 5
}
func main() {
fmt.Println(f1())
fmt.Println(f2())
fmt.Println(f3())
fmt.Println(f4())
}
defer 一道面试题
可以发现外围函数会被延时,函数里面的参数不影响
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main() {
x := 1
y := 2
defer calc("AA", x, calc("A", x, y))
x = 10
defer calc("BB", x, calc("B", x, y))
y = 20
}
A 1 2 3
B 10 2 12
BB 10 12 22
AA 1 3 4
panic/recover
go语言没有结构化异常,使用 panic 抛出错误,recover 捕获错误
go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理
panic 可以在任何地方引发,但 recover 只有在 defer 调用的函数中有效
func funcA() {
fmt.Println("func A")
}
func funcB() {
defer func() {
err := recover()
//如果程序出现了panic错误,可以通过recover恢复
if err != nil {
fmt.Println("recover in B")
}
}()
panic("panic in B")
}
func funcC() {
fmt.Println("func C")
}
func main() {
funcA()
funcB()
funcC()
}
func A
recover in B
func C
注意两点
1、recover() 必须搭配defer使用
2、defer 一定要在可能引发panic的语句之前定义
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)