Go 标识符 在编程语言中标识符就是程序员定义的具有特殊意义的词,比如变量名、常量名、函数名等等。 Go语言中标识符由字母数字和_
(下划线)组成,并且只能以字母和_
开头。 举几个例子:abc
, _
, _123
, a123
。
关键字 关键字是指编程语言中预先定义好的具有特殊含义的标识符。 关键字和保留字都不建议用作变量名。
Go语言中有25个关键字:
1 2 3 4 5 break default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var
此外,Go语言中还有37个保留字。
1 2 3 4 5 6 7 8 9 10 Constants: true false iota nil Types: int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr float32 float64 complex128 complex64 bool byte rune string error Functions: make len cap new append copy close delete complex real imag panic recover
变量var 变量声明
1 2 3 4 var name string var age int var isOk bool
Go中*非全局*
声明变量必须使用 批量声明变量 1 2 3 4 5 6 var ( name string age int isOk bool )
声明变量同时赋值 1 2 var name string = "boyiz"
类型推导
短变量声明
匿名变量 在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量(anonymous variable)
。
匿名变量用一个下划线_
表示,匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明。
1 2 3 4 5 6 7 8 9 func foo () (int , string ) { return 10 , "QQWechat" } func main () { x, _ := foo() _, y := foo() fmt.Println("x=" , x) fmt.Println("y=" , y) }
注意事项:
函数外的每个语句都必须以关键字开始(var、const、func等)
:=
不能使用在函数外。
_
多用于占位,表示忽略值。
常量const 定义之后不能被修改, 数值可作为各种类型使用
常量声明
批量声明常量(枚举) 1 2 3 4 5 6 7 8 9 10 11 12 const ( statueOK = 200 notFound = 404 ) const ( n1 = 100 n2 n3 )
iota iota
是go语言的常量计数器,只能在常量的表达式中使用。
iota
在const关键字出现时将被重置为0。
const中每新增*一行*
常量声明将使iota
计数一次(iota可理解为const语句块中的行索引)。
使用iota能简化定义,在定义枚举时很有用。
1 2 3 4 5 6 const ( n1 = iota n2 n3 n4 )
几个常见的iota
示例: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 const ( n1 = iota n2 _ n4 ) const ( n1 = iota n2 = 100 n3 n4 ) --------------------- const ( n1 = iota n2 = 100 n3 = iota n4 ) const n5 = iota --------------------- const ( d1,d2 = iota + 1 ,iota + 2 d3,d4 = iota + 1 ,iota + 2 )
定义数量级 这里的<<
表示左移操作,1<<10
表示将1的二进制表示向左移10位,也就是由1
变成了10000000000
,也就是十进制的1024。同理2<<2
表示将2的二进制表示向左移2位,也就是由10
变成了1000
,也就是十进制的8。
1 2 3 4 5 6 7 8 const ( _ = iota KB = 1 << (10 * iota ) MB = 1 << (10 * iota ) GB = 1 << (10 * iota ) TB = 1 << (10 * iota ) PB = 1 << (10 * iota ) )
fmt占位符 1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { var n = 100 ; fmt.Printf("%T\n" ,n) fmt.Printf("%v\n" ,n) fmt.Printf("%b\n" ,n) fmt.Printf("%d\n" ,n) fmt.Printf("%o\n" ,n) fmt.Printf("%x\n" ,n) var s = "Hello" fmt.Printf("%s\n" ,s) fmt.Printf("%v\n" ,s) fmt.Printf("%#v\n" ,s) }
整型 整型分为以下两个大类: 按长度分为: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
无符号整型,用于存放一个指针
注意: 在使用int
和 uint
类型时,不能假定它是32位或64位的整型,而是考虑int
和uint
可能在不同平台上的差异。
注意事项 : 获取对象的长度的内建len()
函数返回的长度可以根据不同平台的字节长度进行变化。实际使用中,切片或 map 的元素数量等都可以用int
来表示。在涉及到二进制传输、读写文件的结构描述时,为了保持文件的结构不会受到不同编译目标平台字节长度的影响,不要使用int
和 uint
。
数字字面量语法(Number literals syntax) Go1.13版本之后引入了数字字面量语法,这样便于开发者以二进制、八进制或十六进制浮点数的格式定义数字,例如:
v := 0b00101101
, 代表二进制的 101101,相当于十进制的 45。 v := 0o377
,代表八进制的 377,相当于十进制的 255。 v := 0x1p-2
,代表十六进制的 1 除以 2²,也就是 0.25。
而且还允许我们用 _
来分隔数字,比如说: v := 123_456
表示 v 的值等于 123456。
1 2 3 4 5 6 7 8 9 10 11 12 13 var a int = 10 fmt.Printf("%d \n" , a) fmt.Printf("%b \n" , a) var b int = 077 fmt.Printf("%o \n" , b) var c int = 0xff fmt.Printf("%x \n" , c) fmt.Printf("%X \n" , c)
浮点型 Go语言支持两种浮点型数:float32
和float64
。这两种浮点型数据格式遵循IEEE 754
标准: float32
的浮点数的最大范围约为 3.4e38
,可以使用常量定义:math.MaxFloat32
。 float64
的浮点数的最大范围约为 1.8e308
,可以使用一个常量定义:math.MaxFloat64
。
1 2 3 4 5 6 7 func main () { f1 := 1.23456 fmt.Printf("%T\n" ,f1) f2 := float32 (1.23456 ) fmt.Printf("%T\n" ,f2) f1 = f2 }
复数 complex64和complex128
1 2 3 4 5 6 var c1 complex64 c1 = 1 + 2i var c2 complex128 c2 = 2 + 3i fmt.Println(c1) fmt.Println(c2)
复数有实部和虚部,complex64的实部和虚部为32位,complex128的实部和虚部为64位。
布尔值 Go语言中以bool
类型进行声明布尔型数据,布尔型数据只有true(真)
和false(假)
两个值。
注意:
布尔类型变量的默认值为false
。
Go 语言中不允许将整型强制转换为布尔型.
布尔型无法参与数值运算,也无法与其他类型进行转换。
1 2 3 4 b1 := true ; var b2 bool fmt.Printf("%T\n" ,b1) fmt.Printf("%T value:%v \n" ,b2,b2)
字符串 Go语言中的字符串以原生数据类型出现,使用字符串就像使用其他原生数据类型(int、bool、float32、float64 等)一样。 Go 语言里的字符串的内部实现使用UTF-8
编码。
字符串的值为双引号(")
中的内容, 单引号(')
表示为字符
字符串转义符
转义符
含义
\r
回车符(返回行首)
\n
换行符(直接跳到下一行的同列位置)
\t
制表符
\'
单引号
\"
双引号
\\
反斜杠
多行字符串 Go语言中要定义一个多行字符串时,就必须使用反引号
字符,反引号间换行将被作为字符串中的换行,但是所有的转义字符均无效,文本将会原样输出。
1 2 3 4 5 s1 := `第一行 第二行 第三行 ` fmt.Println(s1)
字符串的常用操作
方法
介绍
len(str)
求长度
+或fmt.Sprintf
拼接字符串
strings.Split
分割
strings.contains
判断是否包含
strings.HasPrefix,strings.HasSuffix
前缀/后缀判断
strings.Index(),strings.LastIndex()
子串出现的位置
strings.Join(a[]string, sep string)
join操作
1 2 3 4 5 6 7 8 9 fmt.Println(len (s3)) name := "hello" world := "world" ss := name + world fmt.Println(ss) ss1 := fmt.Sprintf("%s%s" ,name,world) fmt.Printf("%s%s" ,name,world) fmt.Println(ss1)
if if的条件不需要括号
if的条件可以赋值,变量作用域只在if语句范围内
1 2 3 4 5 6 7 8 9 func main () { const filename = "abc.txt" contents , err := ioutil.ReadFile(filename) if err!= nil { fmt.Println(err) }else { fmt.Printf("%s\n" , contents) } }
1 2 3 4 5 if contents , err := ioutil.ReadFile(filename);err!= nil { fmt.Println(err) }else { fmt.Printf("%s\n" , contents) }
特殊写法:在 if 表达式之前添加一个执行语句,再根据变量值进行判断
1 2 3 4 5 6 7 8 9 func ifDemo () { if score := 65 ; score >= 90 { fmt.Println("A" ) } else if score > 75 { fmt.Println("B" ) } else { fmt.Println("C" ) } }
switch switch后面可以没有表达式
switch可以自动break,除非使用fallthrough
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func grade (score int ) string { g := "" switch { case score<0 || score>100 : panic (fmt.Sprintf("Error score : %d" , score)) case score< 60 : g = "F" case score<80 : g = "C" case score<90 : g = "B" case score<=100 : g = "A" default : panic (fmt.Sprintf("Error score : %d" , score)) } return g }
一个分支可以有多个值,多个case值中间使用英文逗号分隔。
1 2 3 4 5 6 7 8 9 10 func testSwitch () { switch n := 7 ; n { case 1 , 3 , 5 , 7 , 9 : fmt.Println("奇数" ) case 2 , 4 , 6 , 8 : fmt.Println("偶数" ) default : fmt.Println(n) } }
for for条件不需要括号
for可以省略 初始条件、结束条件,递增表达式
省略初始条件,相当于while
1 2 3 4 5 6 7 8 func convertToBin (n int ) string { result := "" for ;n>0 ;n /= 2 { lsb := n%2 result = strconv.Itoa(lsb)+result } return result }
1 2 3 4 5 6 7 8 9 10 func printFile (fileName string ) { file ,err := os.Open(fileName) if err != nil { fmt.Println(err) } scanner := bufio.NewScanner(file) for scanner.Scan() { fmt.Printf("%s\n" , scanner.Text()) } }
1 2 3 4 5 func forever () { for { fmt.Println("abc" ) } }
for range(键值循环) Go语言中可以使用for range
遍历数组、切片、字符串、map 及通道(channel)。 通过for range
遍历的返回值有以下规律:
数组、切片、字符串返回索引和值。
map返回键和值。
通道(channel)只返回通道内的值。
goto goto
语句通过标签进行代码间的无条件跳转。goto
语句可以在快速跳出循环、避免重复退出上有一定的帮助。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func gotoDemo1 () { var breakFlag bool for i := 0 ; i < 10 ; i++ { for j := 0 ; j < 10 ; j++ { if j == 2 { breakFlag = true break } fmt.Printf("%v-%v\n" , i, j) } if breakFlag { break } } }
使用goto
语句能简化代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func gotoDemo2 () { for i := 0 ; i < 10 ; i++ { for j := 0 ; j < 10 ; j++ { if j == 2 { goto breakTag } fmt.Printf("%v-%v\n" , i, j) } } return breakTag: fmt.Println("结束for循环" ) }
break break
语句可以结束for
、switch
和select
的代码块。
break
语句还可以在语句后面添加标签,表示退出某个标签对应的代码块,标签要求必须定义在对应的for
、switch
和 select
的代码块上。
1 2 3 4 5 6 7 8 9 10 11 12 func breakDemo () {BREAKDEMO: for i := 0 ; i < 10 ; i++ { for j := 0 ; j < 10 ; j++ { if j == 2 { break BREAKDEMO } fmt.Printf("%v-%v\n" , i, j) } } fmt.Println("..." ) }
continue continue
语句可以结束当前循环,开始下一次的循环迭代过程,仅限在for
循环内使用。
在 continue
语句后添加标签时,表示开始标签对应的循环。
1 2 3 4 5 6 7 8 9 10 11 12 func continueDemo () {forloop1: for i := 0 ; i < 5 ; i++ { for j := 0 ; j < 5 ; j++ { if i == 2 && j == 2 { continue forloop1 } fmt.Printf("%v-%v\n" , i, j) } } }
函数 没有默认参数,可选参数
函数可以返回多个值
1 2 3 4 5 6 func div (a,b int ) (int ,int ) { return a/b,a%b } func main () { fmt.Println(div(13 ,4 )) }
返回多个值时,可以取名字(仅用于非常简单的函数 ,对于调用者没差别 )
1 2 3 4 5 6 7 8 func div (a,b int ) (q,r int ) { q = a/b r = a%b return } func main () { a,b :=div(13 ,4 ) }
函数的参数也可以是函数
调用时也可直接写匿名函数
1 2 3 4 5 6 7 func apply (op func (int ,int ) int , a,b int ) int { fmt.Println("Calling %s with %d %d \n" , runtime.FuncForPC(reflect.ValueOf(op).Pointer()).Name(), a, b) return op(a,b) }
可变参数列表(不确定个数的参数)
1 2 3 4 5 6 7 8 9 10 11 func sum (numbers ...int ) int { s := 0 for i := range numbers{ s += numbers[i] } return s } func main () { fmt.Println(sum(1 ,2 ,3 ,4 ,5 )) }
指针 指针不能运算
1 2 3 4 5 6 func main () { var a int = 2 var po *int = &a *po = 3 fmt.Println(a) }
Go语言只有值传递
一种方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func swap1 (a ,b *int ) { *a ,*b = *b ,*a } func swap2 (a ,b int ) (int ,int ){ return b, a } func main () { a,b := 3 ,4 swap1(&a,&b) fmt.Println(a,b) a ,b := swap2(3 ,4 ) fmt.Println(a,b) }
数组 Go中一般不使用数组,而使用切片
1 2 3 4 5 6 7 8 9 10 func main () { var arr1 [5 ]int arr2 := [3 ]int {1 ,3 ,5 } arr3 := [...]int {2 ,4 ,6 ,8 } var grid [3 ][3 ] int fmt.Println(arr1,arr2,arr3) fmt.Println(grid) }
遍历数组
1 2 3 4 5 6 7 8 9 10 11 for i:=0 ;i<len (arr3);i++{ fmt.Println(arr3[i]) } for i:=range arr3{ fmt.Println(arr3[i]) } for i,v:=range arr3{ fmt.Println(i,v) }
[5]int
和[3]int
是不同类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func printArray (arr [5]int ) { for i,v:=range arr{ fmt.Println(i,v) } } func main () { var arr1 [5 ]int arr2 := [3 ]int {1 ,3 ,5 } arr3 := [...]int {2 ,4 ,6 ,8 ,10 } printArray(arr1) printArray(arr2) printArray(arr3) }
数组是值类型——在调用时对数组进行一个拷贝
,不会修改原数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func printArray (arr [5]int ) { arr[1 ] = 100 for i,v:=range arr{ fmt.Print(i,v) } } func main () { var arr1 [5 ]int fmt.Println("调用printArray(arr1)" ) printArray(arr1) fmt.Println("输出arr1" ) fmt.Println(arr1) }
可使用指针类型调用时修改数组
1 2 3 4 5 6 func printArray (arr *[5]int ) { arr[1 ] = 100 for i,v:=range arr{ fmt.Println(i,v) } }
二维数组 二维数组定义:
1 2 3 4 5 6 7 8 9 func main () { a := [3 ][2 ]string { {"北京" , "上海" }, {"广州" , "深圳" }, {"成都" , "重庆" }, } fmt.Println(a) fmt.Println(a[2 ][1 ]) }
遍历:
1 2 3 4 5 6 for _, v1 := range a { for _, v2 := range v1 { fmt.Printf("%s\t" , v2) } fmt.Println() }
切片Slice Slice底层没有数据,是对底层array的一个view
Slice可以向后扩展,不能向前扩展
使用内置的len()
函数求长度,使用内置的cap()
函数求切片的容量
1 2 3 4 5 6 7 func main () { arr := [...]int {0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 } fmt.Println(arr[2 :6 ]) fmt.Println(arr[2 :]) fmt.Println(arr[:6 ]) fmt.Println(arr[:]) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 func updateSlice (s []int ) { s[0 ] = 100 } func main () { arr := [...]int {0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 } s1 := arr[2 :] fmt.Println("s1=" ,s1) s2 := arr[:] fmt.Println("s2=" ,s2) fmt.Println("after updateSlice(s1)" ) updateSlice(s1) fmt.Println(s1) fmt.Println(arr) fmt.Println("after updateSlice(s2)" ) updateSlice(s2) fmt.Println(s2) fmt.Println(arr) fmt.Println("Reslice" ) fmt.Println(s2) s2 =s2[:5 ] fmt.Println(s2) s2 =s2[2 :] fmt.Println(s2) }
Slice的扩展
(勘误)上图应为 s2:=s1[3:5]
s[i]不能超越len(s),向后扩展不能超越底层数组cap(s)
1 2 3 4 5 6 7 8 9 10 11 12 func main () { arr := [...]int {0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 } s1 := arr[2 :6 ] fmt.Println("s1=" ,s1) s2 := s1[3 :5 ] fmt.Println("s2=" ,s2) fmt.Printf("s1=%v , len(s1)=%d , cap(s1)=%d \n" , s1 , len (s1) , cap (s1)) fmt.Printf("s2=%v , len(s2)=%d , cap(s2)=%d \n" , s2 , len (s2) , cap (s2)) }
Slice中添加元素append
若添加元素超越原始slice长度,系统会重新分配一个新的更大的底层数组,并将原数据copy过去。
由于值传递的缘故,必须接收append的返回值——> s = append(s,val)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func main () { arr := [...]int {0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 } s1 := arr[2 :6 ] fmt.Println("s1=" ,s1) s2 := s1[3 :5 ] s3:=append (s2,10 ) fmt.Println("s3=" ,s3) fmt.Println("arr=" ,arr) s4:=append (s3,11 ) fmt.Println("s4=" ,s4) fmt.Println("arr=" ,arr) s5:=append (s4,12 ) fmt.Println("s5=" ,s5) fmt.Println("arr=" ,arr) }
Slice的创建 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 func printSlice (s []int ) { fmt.Printf("%v , len=%d , cap=%d \n" ,s ,len (s),cap (s)) } func main () { var s []int for i := 0 ;i<100 ;i++ { printSlice(s) s = append (s,2 *i+1 ) } fmt.Println(s) s1 :=[]int {2 ,4 ,6 ,8 } printSlice(s1) s2 := make ([]int ,16 ) printSlice(s2) s3 := make ([]int ,16 ,32 ) printSlice(s3) }
Slice的copy 1 copy (destSlice, srcSlice []T)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main () { s1 :=[]int {2 ,4 ,6 ,8 } printSlice(s1) s2 := make ([]int ,16 ) printSlice(s2) copy (s2,s1) printSlice(s2) }
Slice的delete 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 func main () { s1 :=[]int {2 ,4 ,6 ,8 } printSlice(s1) s2 := make ([]int ,16 ) printSlice(s2) copy (s2,s1) printSlice(s2) s2 = append (s2[:3 ],s2[4 :]...) printSlice(s2) front := s2[0 ] s2 = s2[1 :] printSlice(s2) tail := s2[len (s2)-1 ] s2 = s2[:len (s2)-1 ] printSlice(s2) fmt.Println(front,tail) }
slice判空 使用len(s) == 0
来判断,而不应该使用s == nil
来判断
slice不能直接比较 不能使用==
操作符来判断两个切片是否含有全部相等元素。 切片唯一合法的比较操作是和nil
比较。 一个nil
值的切片并没有底层数组,一个nil
值的切片的长度和容量都是0。但是我们不能说一个长度和容量都是0的切片一定是nil
1 2 3 var s1 []int s2 := []int {} s3 := make ([]int , 0 )
slice赋值拷贝 拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容
1 2 3 4 5 6 7 func main () { s1 := make ([]int , 3 ) s2 := s1 s2[0 ] = 100 fmt.Println(s1) fmt.Println(s2) }
slice遍历 1 2 3 4 5 6 7 8 9 10 11 func main () { s := []int {1 , 3 , 5 } for i := 0 ; i < len (s); i++ { fmt.Println(i, s[i]) } for index, value := range s { fmt.Println(index, value) } }
Map 1 2 3 4 map [KeyType]ValueType map [K1]map [K2]V
map使用哈希表,可以比较相等
除了slice、map、function的内建类型都可以作为key
struct类型不包含上述字段,也可作为key
map的创建 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func main () { m1:= map [string ] string { "name" :"zzz" , "couse" :"golang" , "site" :"study" , "quality" :"good" , } fmt.Println(m1) m2 := make (map [string ]int ) fmt.Println(m2) var m3 map [string ]int fmt.Println(m3) }
map的遍历 用range遍历,但不保证顺序,需手动对key排序
1 2 3 4 5 6 7 8 9 10 for k,v := range m1 { fmt.Println(k,v) } for k := range m1 { fmt.Println(k) } for _,v := range m1 { fmt.Println(v) }
map值的获取 获取元素:m[key]
,key不存在时,返回value类型的初始值
用value, ok := m[key]
来判断是否存在key
1 2 3 4 5 couseName := m1["couse" ] fmt.Println(couseName) causeName := m1["cause" ] fmt.Println(causeName)
1 2 3 4 5 6 7 8 9 10 11 couseName,ok := m1["couse" ] fmt.Println(couseName,ok) causeName,ok := m1["cause" ] fmt.Println(causeName,ok) if couseName,ok := m1["couse" ];ok { fmt.Println(couseName) }else { fmt.Println("key does not exit" ) }
map的删除
元素为map类型的切片 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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" ) mapSlice[0 ] = make (map [string ]string , 10 ) mapSlice[0 ]["name" ] = "小王子" mapSlice[0 ]["password" ] = "123456" mapSlice[0 ]["address" ] = "沙河" for index, value := range mapSlice { fmt.Printf("index:%d value:%v\n" , index, value) } }
值为切片类型的map 1 2 3 4 5 6 7 8 9 10 11 12 13 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) }
1 2 3 4 5 6 7 8 9 10 11 func main () { type Map map [string ][]int m := make (Map) s := []int {1 , 2 } s = append (s, 3 ) fmt.Printf("%+v\n" , s) m["q1mi" ] = s s = append (s[:1 ], s[2 :]...) fmt.Printf("%+v\n" , s) fmt.Printf("%+v\n" , m["q1mi" ]) }
寻找最长不含有重复字符的子串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func lenOfNonRepeatingSubStr (s string ) int { lastOccurred:= make (map [byte ]int ) start:=0 maxLength := 0 for i,ch := range []byte (s){ lastI,ok := lastOccurred[ch] if ok&&lastI>=start { start = lastI +1 } if i-start+1 >maxLength { maxLength= i-start+1 } lastOccurred[ch]=i } return maxLength }
字符串 rune相当于go中的char
使用range遍历pos,rune对
使用utf8.RuneCountInString(s)获得字符数量
使用len()获得字节长度
使用[] byte获得字节
其他字符操作:strings包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func main () { s:="Hi好好学习!" for _,b := range []byte (s) { fmt.Printf("%X " ,b) } fmt.Println() for i,ch := range s { fmt.Printf("(%d %X) " ,i,ch) } fmt.Println() fmt.Println(utf8.RuneCountInString(s)) bytes := []byte (s) for len (bytes)> 0 { ch,size := utf8.DecodeRune(bytes) bytes = bytes[size:] fmt.Printf("%c " ,ch) } fmt.Println() for i,ch := range []rune (s){ fmt.Printf("(%d %c) " ,i,ch) } }
类型别名和自定义类型 在Go语言中有一些基本的数据类型,如string
、整型
、浮点型
、布尔
等数据类型, Go语言中可以使用type
关键字来定义自定义类型。
自定义类型是定义了一个全新的类型。我们可以基于内置的基本类型定义,也可以通过struct定义。
类型别名是Go1.9
版本添加的新功能。
类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型。就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。
rune
和byte
就是类型别名,定义如下:
1 2 type byte = uint8 type rune = int32
类型定义和类型别名的区别 类型别名与类型定义表面上看只有一个等号的差异,我们通过下面的这段代码来理解它们之间的区别。
1 2 3 4 5 6 7 8 9 10 11 12 13 type NewInt int type MyInt = int func main () { var a NewInt var b MyInt fmt.Printf("type of a:%T\n" , a) fmt.Printf("type of b:%T\n" , b) }
结果显示a的类型是main.NewInt
,表示main包下定义的NewInt
类型。
b的类型是int
。MyInt
类型只会在代码中存在,编译完成时并不会有MyInt
类型。
结构体 Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct
。
Go语言中通过struct
来实现面向对象。
1 2 3 4 5 type 类型名 struct { 字段名 字段类型 字段名 字段类型 … }
其中:
类型名:标识自定义结构体的名称,在同一个包内不能重复。
字段名:表示结构体字段名。结构体中的字段名必须唯一。
字段类型:表示结构体字段的具体类型。
结构体实例化 只有当结构体实例化时,才会真正地分配内存。
结构体本身也是一种类型,可以像声明内置类型一样使用var
关键字声明结构体类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 type person struct { name string city string age int8 } func main () { var p1 person p1.name = "正方形" p1.city = "南京" p1.age = 18 fmt.Printf("p1=%v\n" , p1) fmt.Printf("p1=%#v\n" , p1) }
匿名结构体 1 2 3 4 5 6 func main () { var user struct {Name string ; Age int } user.Name = "小王子" user.Age = 18 fmt.Printf("%#v\n" , user) }
指针类型结构体 使用new
关键字对结构体进行实例化,得到的是结构体的地址。 格式如下:
1 2 3 var p2 = new (person)fmt.Printf("%T\n" , p2) fmt.Printf("p2=%#v\n" , p2)
从打印的结果可以看出p2
是一个结构体指针。
取结构体的地址实例化 使用&
对结构体进行取地址操作相当于对该结构体类型进行了一次new
实例化操作。
1 2 3 4 5 6 7 p3 := &person{} fmt.Printf("%T\n" , p3) fmt.Printf("p3=%#v\n" , p3) p3.name = "正方形" p3.age = 18 p3.city = "南京" fmt.Printf("p3=%#v\n" , p3)
p3.name = "正方形"
其实在底层是(*p3).name = "正方形"
,这是Go语言帮我们实现的语法糖。
使用键值对初始化 使用键值对对结构体进行初始化时,键对应结构体的字段,值对应该字段的初始值。
1 2 3 4 5 6 p5 := person{ name: "正方形" , city: "南京" , age: 18 , } fmt.Printf("p5=%#v\n" , p5)
也可以对结构体指针进行键值对初始化,例如:
1 2 3 4 5 6 p6 := &person{ name: "正方形" , city: "南京" , age: 18 , } fmt.Printf("p6=%#v\n" , p6)
当某些字段没有初始值的时候,该字段可以不写。此时,没有指定初始值的字段的值就是该字段类型的零值。
1 2 3 4 p7 := &person{ city: "南京" , } fmt.Printf("p7=%#v\n" , p7)
使用值的列表初始化 初始化结构体的时候可以简写,也就是初始化的时候不写键,直接写值:
1 2 3 4 5 6 p8 := &person{ "正方形" , "南京" , 18 , } fmt.Printf("p8=%#v\n" , p8)
使用这种格式初始化时,需要注意:
必须初始化结构体的所有字段。
初始值的填充顺序必须与字段在结构体中的声明顺序一致。
该方式不能和键值初始化方式混用。
结构体内存布局 结构体占用一块连续的内存。
空结构体不占用空间的。
1 2 3 4 5 6 7 8 9 10 11 12 13 type test struct { a int8 b int8 c int8 d int8 } n := test{ 1 , 2 , 3 , 4 , } fmt.Printf("n.a %p\n" , &n.a) fmt.Printf("n.b %p\n" , &n.b) fmt.Printf("n.c %p\n" , &n.c) fmt.Printf("n.d %p\n" , &n.d)
面向对象 go中仅支持封装,不支持继承和多态 go没有class,只有struct 结构定义,无论是地址还是结构本身,一律使用.来访问成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type treeNode struct { value int left,right *treeNode } func main () { var root treeNode fmt.Println(root) root = treeNode{value: 3 } root.left = &treeNode{} root.right = &treeNode{5 , nil , nil } root.right.left = new (treeNode) nodes := []treeNode{ {value: 3 }, {}, {7 ,nil ,&root}, } fmt.Println(nodes) }
1 2 3 4 5 6 7 func createNode (value int ) *treeNode { return &treeNode{value:value} } root.left.right = createNode(1 )
给结构定义方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func (node treeNode) print () { fmt.Print(node.value) } func (node treeNode) setValue(value int ) { node.value = value } func (node *treeNode) setValue(value int ) { node.value = value } root.right.left.setValue(99 ) root.right.left.print ()
nil也可以调用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func (node *treeNode) setValue(value int ) { if node == nil { fmt.Println("node is nil" ) return } node.value = value } var pRoot *treeNode pRoot.setValue(100 ) pRoot = &root pRoot.setValue(200 ) pRoot.print ()
遍历treeNode
1 2 3 4 5 6 7 8 func (node *treeNode) tranverse() { if node == nil { return } node.left.tranverse() node.print () node.right.tranverse() }
值接收者 VS 指针接收者
要改变内容必须要用指针接收者
结构过大也要考虑使用指针接收者
Tip:一致性:如果有指针接收者,最好都是用指针接收者
封装
名字一般使用CamelCase
首字母大写:public(针对于包)
首字母小写:private(针对于包)
包
每个目录只能有一个包
main包 包含一个可执行入口
为结构定义的方法必须放在同一个包内,可以是不同文件
扩充系统类型或者别人的类型 定义别名:最简单 使用组合:最常用 Embedding 内嵌:省代码,一种语法糖,不易懂 依赖管理 三个阶段:GOPATH,GOVENDOR,go mod
GOPATH,GOVENDOR 已过时 go mod 主流
要启用go module
支持首先要设置环境变量GO111MODULE
,通过它可以开启或关闭模块支持,它有三个可选值:off
、on
、auto
,默认值是auto
。
GO111MODULE=off
禁用模块支持,编译时会从GOPATH
和vendor
文件夹中查找包。
GO111MODULE=on
启用模块支持,编译时会忽略GOPATH
和vendor
文件夹,只根据 go.mod
下载依赖。
GO111MODULE=auto
,当项目在$GOPATH/src
外且项目根目录有go.mod
文件时,开启模块支持。
使用 go module 管理依赖后会在项目根目录下生成两个文件go.mod
和go.sum
。
设置GOPROXY:go env -w GOPROXY=https://goproxy.cn,direct
常用的go mod
命令
1 2 3 4 5 6 7 8 go mod download 下载依赖的module到本地cache(默认为$GOPATH/pkg/mod目录) go mod edit 编辑go.mod文件 go mod graph 打印模块依赖图 go mod init 初始化当前文件夹, 创建go.mod文件 go mod tidy 增加缺少的module,删除无用的module go mod vendor 将依赖复制到vendor下 go mod verify 校验依赖 go mod why 解释为什么需要依赖
迁移到go mod管理
1、创建go.mod文件 go mod init xxx文件名
2、go build ./…
接口 接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。
interface
是一组method
的集合,接口做的事情就像是定义一个协议(规则),不关心对方是什么类型,只关心对方能做什么。这是duck-type programming
的一种体现,只要一个物体能像鸭子一样叫那我们就可以称它为鸭子;只要一个软件能存储和查询数据我们就可以称它为数据库;只要一台机器有洗衣服和甩干的功能我们就可以称它为洗衣机。
Go语言中的接口(interface)是一种类型,是一种抽象的类型。 接口的定义 1 2 3 4 5 type 接口类型名 interface { 方法名1 ( 参数列表1 ) 返回值列表1 方法名2 ( 参数列表2 ) 返回值列表2 … }
其中:
接口名:使用type
将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er
,如有写操作的接口叫Writer
,有字符串功能的接口叫Stringer
等。接口名最好要能突出该接口的类型含义。
方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。
1 2 3 4 5 6 7 type Retriever interface { Get(url string ) string } func download (r Retriever) string { return r.Get("http://www.baidu.com" ) }
接口的实现是隐式的,只需要实现接口里的方法。
接口变量
接口变量自带指针
接口变量同样采用值传递,几乎不需要使用接口的指针
指针接收者实现只能以指针方式使用;值接收者都可以
接口的组合(嵌套) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type Retriever interface { Get(url string ) string } func download (r Retriever) string { return r.Get("http://www.baidu.com" ) } type Poster interface { Post(url string ,form map [string ]string ) string } func post (poster Poster) { poster.Post("http://www.baidu.com" , map [string ]string { "name" :"baidu" , "value" :"百度" , }) } type RetrieverPoster interface { Retriever Poster }
空接口,任意类型 interface{} 空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。
空接口类型的变量可以存储任意类型的变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { var x interface {} s := "Hello" x = s fmt.Printf("type:%T value:%v\n" , x, x) i := 100 x = i fmt.Printf("type:%T value:%v\n" , x, x) b := true x = b fmt.Printf("type:%T value:%v\n" , x, x) }
空接口的应用
空接口类型作为函数的参数
1 2 3 4 func show (a interface {}) { fmt.Printf("type:%T value:%v\n" , a, a) }
空接口类型作为map的value
1 2 3 4 var m = make (map [string ]interface {}) m["name" ] = "正方形" m["age" ] = 18 m["hobby" ] = []string {"music" ,"dance" ,"game" }
接口类型断言 一个接口的值(简称接口值)是由一个具体类型
和具体类型的值
两部分组成的。这两部分分别称为接口的动态类型
和动态值
。
语法格式:
1 2 3 4 5 6 7 8 9 10 var x interface {} x = "hello" x = 18 x = true res,ok :=x.(string ) if !ok { fmt.Println("不是字符串" ) } else { fmt.Println("是字符串" ,res) }
使用switch进行类型断言
1 2 3 4 5 6 7 8 9 10 switch v := x.(type ) { case string : fmt.Printf("字符串,value=%v\n" , v) case int : fmt.Printf("int类型,value=%v\n" , v) case bool : fmt.Printf("布尔类型,value=%v\n" , v) default : fmt.Println("unsupport type!" ) }
函数与闭包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 func adder () func (int ) int { sum:=0 return func (i int ) int { sum+=i return sum } } type iAdder func (int ) (int ,iAdder)func adder2 (base int ) iAdder { return func (i int ) (int , iAdder) { return base+i,adder2(base+i) } } func main () { a := adder() for i := 0 ; i <10 ; i++ { fmt.Println(a(i)) } a := adder2(0 ) for i := 0 ; i < 10 ; i++ { var s int s , a = a(i) fmt.Println(s) } }
defer调用
确保调用在函数结束时发生
参数在defer语句时计算
defer列表为后进先出
错误处理 1 err = errors.New("this is a custom error" )
1 2 3 4 5 6 7 8 file,err := os. Open ("abc.txt" ) if err!=nil { if pathError, ok := err. (*os. PathError); ok { fmt. Println(pathError.Err) } else { fmt. Println("unknown error" , err) } }
goroutine 协程Corutine
是一个轻量级“线程”,
是一个非抢占式
多任务处理,由协程主动交出控制权
编译器/解释器/虚拟机层面的多任务
多个协程可能在一个或多个线程上运行
1 2 3 4 5 6 7 8 9 10 func main () { for i := 0 ; i < 1000 ; i++ { go func (i int ) { for { fmt.Printf("hello, goroutine %d \n" ,i) } }(i) } time.Sleep(time.Millisecond) }
goroutine定义
任何函数只需要加上go就能送给调度器运行
不需要在定义时区分是否是异步函数
调度器在合适的点进行切换
使用-race来检测数据访问冲突