Go语言学习笔记(二)十分钟上手
加 Golang学习 QQ群共同学习进步成家立业工作 ^-^ 群号:96933959
变量&常量
变量
变量名由字母、数字、下划线组成,不能以数字开头。
... var ( A int //默认为0 B string //默认为"" C bool //默认为false Name string = "suoning" Age = 18 // 自动识别类型 ) func main() { sex := "man" //等价于 var sex string = "man" var girlfriend string //声明变量 girlfriend = "Dawn" //变量赋值 Name := "Nick" //正确 //girlfriend := "Jenny" //错误的!不能申明两次 ... }
常量
常量使用const 修饰,代表永远是只读的,不能修改。
常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
语法:const identifier [type] = value,其中type可以省略。
iota
iota,特殊常量,可以认为是一个可以被编译器修改的常量。
在每一个const关键字出现时,被重置为0,然后再下一个const出现之前,每出现一次iota,其所代表的数字会自动增加1。
... const ( A = 'a' //97 B = iota //1 C //2 D = "nick" //"nick" iota += 1 E //"nick" iota += 1 F = 618 //618 iota +=1 G //618 iota +=1 H = iota //7 I //8 ) ...
基本数据类型
布尔型
布尔型的值只可以是常量 true 或者 false。
var a bool var a bool = true var a = false
数字类型
int:
- 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))
float:
- float32(IEEE-754 32位浮点型数)
- float64(IEEE-754 64位浮点型数)
- complex64(32 位实数和虚数)
- complex128(64 位实数和虚数)
其它:
- byte(类似 uint8)
- rune(类似 int32)
- uint(32 或 64 位)
- int(与 uint 一样大小)
- uintptr(无符号整型,用于存放一个指针)
类型转换
package main func main() { var a int var b int32 var c int64 a = 15 //b = a + a // compiler error b = int32(a + a) // ok b = b + 5 // ok: 5 is a constant c = c + 5 // ok }
字符类型
存储为ascii码
var a byte = 'a' fmt.Println(a) //97
字符串类型
字符串表示两种方式:
- 双引号
- `` (反引号,不转义)
string底层就是一个byte的数组
string本身是不可变的,因此要改变string中字符,需要如下操作:
str := "hello world" s := []byte(str) s[0] = 'o' str = string(s)
操作符
算术运算符
- + 相加
- - 相减
- * 相乘
- / 相除
- % 求余
- ++ 自增
- -- 自减
关系运算符
- == 检查两个值是否相等,如果相等返回 True 否则返回 False。
- != 检查两个值是否不相等,如果不相等返回 True 否则返回 False。
- > 检查左边值是否大于右边值,如果是返回 True 否则返回 False。
- < 检查左边值是否小于右边值,如果是返回 True 否则返回 False。
- >= 检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。
- <= 检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。
逻辑运算符
- && 逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。
- || 逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False。
- ! 逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。
位运算符
位运算符对整数在内存中的二进制位进行操作。
- & 按位与运算符"&"是双目运算符。 其功能是参与运算的两数各对应的二进位相与。
- | 按位或运算符"|"是双目运算符。 其功能是参与运算的两数各对应的二进位相或。
- ^ 按位异或运算符"^"是双目运算符。 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。
- << 左移运算符"<<"是双目运算符。左移n位就是乘以2的n次方。 其功能把"<<"左边的运算数的各二进位全部左移若干位,由"<<"右边的数指定移动的位数,高位丢弃,低位补0。
- >> 右移运算符">>"是双目运算符。右移n位就是除以2的n次方。 其功能是把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数。
赋值运算符
- = 简单的赋值运算符,将一个表达式的值赋给一个左值
- += 相加后再赋值 (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)
流程控制
If/else & for & range
注意 else if / else 位置
if condition1 { } else if condition2 { } else if condition3 { } else { }
for循环条件没有小括号
for i := 0; i < 10; i++ { }
死循环
for true { } 可以简写为: for { }
range
for i, v := range str { }
package main import ( "fmt" ) func ran(str string) { for i, v := range str { fmt.Printf("index[%d] val[%c] len[%d]\n", i, v, len([]byte(string(v)))) } } func main() { ran("Love, 索宁") } 输出结果: index[0] val[L] len[1] index[1] val[o] len[1] index[2] val[v] len[1] index[3] val[e] len[1] index[4] val[,] len[1] index[5] val[ ] len[1] index[6] val[索] len[3] index[9] val[宁] len[3]
switch case
Go里面switch默认相当于每个case最后带有break,匹配成功后不会自动向下执行其他case,而是跳出整个switch。
switch var { case var1: case var2: case var3: default: }
func sw(num int) { switch num { case 1, 2, 3: fmt.Printf("%s in 1,2,3\n", num) case 4, 5, 6: fmt.Printf("%s in 4,5,6\n", num) fallthrough case 7, 8, 9: fmt.Printf("%s big 789\n", num) default: fmt.Printf("default...\n") } }
func sw2(num int) { switch { case num > 0 && num < 4: fmt.Printf("%s in 1,2,3\n", num) case num > 4 && num < 7: fmt.Printf("%s in 4,5,6\n", num) fallthrough case num > 7 && num < 10: fmt.Printf("%s big 789\n", num) default: fmt.Printf("default...\n") } }
func sw3() { switch num := 5; { case num > 0 && num < 4: fmt.Printf("%s in 1,2,3\n", num) case num > 4 && num < 7: fmt.Printf("%s in 4,5,6\n", num) fallthrough case num > 7 && num < 10: fmt.Printf("%s big 789\n", num) default: fmt.Printf("default...\n") } }
fallthrough
可以使用fallthrough强制执行后面的case代码。
package main import "fmt" func main() { switch { case false: fmt.Println("The integer was <= 4") fallthrough case true: fmt.Println("The integer was <= 5") fallthrough case false: fmt.Println("The integer was <= 6") fallthrough case true: fmt.Println("The integer was <= 7") case false: fmt.Println("The integer was <= 8") fallthrough default: fmt.Println("default case") } } 运行结果: The integer was <= 5 The integer was <= 6 The integer was <= 7
label & goto
label要写在for循环的开始而不是结束的地方。直接break退出到指定的位置。
func lab() { LABLE: for i := 0; i < 10; i++ { for true { i++ if i == 6 { break LABLE } fmt.Println(i) } } }
goto语句可以跳转到本函数内的某个标签
func got() { i := 0 HERE: fmt.Println(i) i++ if i == 5 { return } goto HERE }
select
select与switch类似,不过select有较多限制。
每个case语句里必须是一个channel操作;
select { case ch <- 0: //如果0写入,则进行该case case <- ch: //如果读到数据,则进行该case default: //如果上面的都没有成功,则进入default处理 }
package main import ( "fmt" "time" ) /* 随机向ch中写入一个0或者1的过程,当然这是个死循环。 */ func main() { ch := make(chan int, 1) for { select { case ch <- 0: case ch <- 1: } i := <-ch fmt.Println(i) time.Sleep(time.Second) } }
package main import ( "fmt" "time" ) /* channel超时处理 一直没有从ch中读取到数据,但从timeout中读取到了数据 */ func main() { ch := make(chan bool) timeout := make(chan bool, 1) go func() { time.Sleep(time.Second*2) timeout <- true }() select { case <- ch: fmt.Println("This is ch.") case <- timeout: fmt.Println("This is timeout.") } }
函数
Go 语言最少有个 main() 函数。
函数声明告诉了编译器函数的名称,返回类型,和参数。
不支持重载,一个包不能有两个名字一样的函数。
func function_name( [parameter list] ) [return_types] {
函数体
}
命名返回值的名字(return可以不指定变量):
func add(a, b int) (c int) { c = a + b return }
func calc(a, b int) (sum int, avg int) { sum = a + b avg = (a +b)/2 return }
_标识符,用来忽略返回值:
func calc(a, b int) (sum int, avg int) { sum = a + b avg = (a +b)/2 return } func main() { sum, _ := calc(100, 200) }
函数也是一种类型,一个函数可以赋值给变量
package main import "fmt" //申明一个函数类型 type add_func func(int, int) int func add(a, b int) int { return a + b } func operator(op add_func, a int, b int) int { return op(a, b) } func main() { c := add fmt.Println(c) //0x1087050 sum := operator(c, 1, 2) fmt.Println(sum) //300 }
可变参数
其中arg是一个slice,我们可以通过arg[index]依次访问所有参数;通过len(arg)来判断传递参数的个数。
0个或多个参数 func add(arg…int) int { }
1个或多个参数 func add(a int, arg…int) int { }
2个或多个参数 func add(a int, b int, arg…int) int { }
package main import ( "fmt" ) //返回值指定为sum变量,默认会return这个变量 func add(a int, b int, arg ...int) (sum int) { sum = a + b for i := 0; i < len(arg); i++ { sum += arg[i] } return } func concat(s string, arg ...string) string { str := s for i := 0; i < len(arg); i++ { str += arg[i] } return str } func main() { sum := add(1, 2, 3, 4, 5, 6, 7) fmt.Println(sum) //28 str := concat("nick", " ", "and", " ", "dawn", ".") fmt.Println(str) //nick and dawn. }
main & init & defer
main & init
Go程序会自动调用init()和main(),所以你不需要在任何地方调用这两个函数。
defer
- 当函数返回时,执行defer语句;
- 多个defer语句,按先进后出的方式执行;
- defer语句中的变量,在defer声明时确定变量。
- 触发异常也会走defer语句。
package main import "fmt" //声明defer时,变量i就为0 func test1() { i := 0 defer fmt.Println(i) i++ return } //栈,先进先出 func test2() { for i := 0; i < 5; i++ { defer fmt.Printf("->%d", i) } } func main() { test1() test2() } 输出: 0 ->4->3->2->1->0
package main import ( "fmt" ) func main() { defer_call() } func defer_call() { defer func() { fmt.Println("打印前") }() defer func() { fmt.Println("打印中") }() defer func() { fmt.Println("打印后") }() panic("触发异常") } /* 打印后 打印中 打印前 panic: 触发异常 */
作用域
- 在函数内部声明的变量叫做局部变量,生命周期仅限于函数内部。
- 在函数外部声明的变量叫做全局变量,生命周期作用于整个包,如果是大写的,则作用于整个程序。
package main import "fmt" var name string func main() { name = "Nick" fmt.Println(name) f1() } func f1() { name := "Dawn" fmt.Println(name) f2() } func f2() { fmt.Println(name) } 输出: Nick Dawn Nick
匿名函数 & 闭包
匿名函数
匿名函数是由一个不带函数名的函数声明和函数体组成。
package main func main() { f := func(x, y int) int { return x + y } f(1,1) ch := make(chan int) func (ch chan int) { ch <- 9 }(ch) }
闭包
闭包是一个函数和与其相关的引用环境组合而成的实体。
函数可以存储到变量中作为参数传递给其它函数,能够被函数动态的创建和返回。
func Adder() func(int) int { var x int return func(d int) int { x += d return x } } f := Adder() fmt.Println(f(1)) //1 fmt.Println(f(10)) //11 fmt.Println(f(100)) //111
package main import ( "fmt" "strings" ) func makeSuffix(suffix string) func(string) string { return func(name string) string { if !strings.HasSuffix(name, suffix) { return name + suffix } return name } } func main() { f1 := makeSuffix(".png") fmt.Println(f1("name1")) //name1.png fmt.Println(f1("name2")) //name2.png f2 := makeSuffix(".jpg") fmt.Println(f2("name1")) //name1.jpg fmt.Println(f2("name2")) //name2.jpg }
值传递 & 引用传递
无论是值传递,还是引用传递,传递给函数的都是变量的副本;
值传递是值的拷贝,引用传递是地址的拷贝;
一般来说,地址拷贝更为高效。而值拷贝取决于拷贝的对象大小,对象越大,则性能越低。
map、slice、chan、指针、interface默认以引用的方式传递。
new 内置函数 用来分配内存,主要用来分配值类型,比如int、struct,返回的是指针;
make 内置函数 用来分配内存,主要用来分配引用类型,比如chan、map、slice。
程序初始化与执行过程
指针类型(&*)
普通类型,变量存的就是值,也叫值类型;
指针类型,变量存的是一个地址,这个地址存的才是值。
变量是一种占位符,用于引用计算机内存地址;
Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。
获取指针类型所指向的值,使用:*。
一个指针变量可以指向任何一个值的内存地址它指向那个值的内存地址。
申明如下:
var age *int //指向整型 var height *float32 //指向浮点型
当一个指针被定义后没有分配到任何变量时,它的值为 nil。
nil 指针也称为空指针。
栗子
package main import "fmt" func main() { var ptr *int num := 100 ptr = &num fmt.Println(ptr) //0xc42000e1f8 fmt.Println(*ptr) //100 *ptr = 200 fmt.Println(num) //200 }
package main import "fmt" func change(num *int) { fmt.Println(num) //0xc42000e1f8 fmt.Println(*num) //100 *num = 1000 fmt.Println(num) //0xc42000e1f8 fmt.Println(*num) //1000 } func main() { num := 100 fmt.Println(&num) //0xc42000e1f8 change(&num) fmt.Println(&num) //0xc42000e1f8 fmt.Println(num) //1000 }