1.初识go语言 第一个Go程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport "fmt" func main () { fmt.Println("hello world!" ) }
命令行运行程序
2.数据类型 命名 计算机用来计算,计算前需要存数,如何存数?
数据类型作用:告诉编译器这个数(变量)应该以多大的内存存储
Go 语言中的函数名、变量名、常量名、类型名、语句标号和包名等所有的命名,都遵循一
个简单的命名规则:一个名字必须以一个字母(Unicode 字母)或下划线开头,后面可以跟
任意数量的字母、数字或下划线。大写字母和小写字母是不同的:heapSort 和 Heapsort 是两
个不同的名字。
Go 语言中类似 if 和 switch 的关键字有 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
此外,还有大约 30 多个预定义的名字,比如 int 和 true 等,主要对应内建的常量、类型和
函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 内建常量: true false iota nil 内建类型: int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr float32 float64 complex128 complex64 bool byte rune string error 内建函数: make len cap new append copy close delete complex real imag panic recover
变量的声明 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func main () { var a int a = 10 fmt.Println("a=" , a) }
变量初始化和自动推导类型
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 package mainimport "fmt" func main () { var a int a = 10 fmt.Println("a=" , a) var b int = 10 b = 35 fmt.Println("b=" , b) c := 30 fmt.Printf("c is type of %T" , c) }
自动推导类型和赋值的区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport "fmt" func main () { var a int a = 10 a = 12 a = 13 fmt.Println("a=" , a) b := 20 fmt.Println("b=" , b) b = 30 fmt.Println("b=" , b) }
Println和Printf的区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package main import "fmt" func main () { a := 10 fmt.Println("a=" , a) fmt.Printf("a=%d\n" , a) b := 20 c := 30 fmt.Println("a=" , a, ",b=" , b, ",c=" , c) fmt.Printf("a=%d,b=%d,c=%d" , a, b, c) }
多重赋值和匿名变量
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 31 32 33 34 35 36 37 38 39 package mainimport "fmt" func test () (a, b, c int ) { return 1 , 2 , 3 } func main () { a, b := 10 , 20 var temp int temp = a a = b b = temp fmt.Println("a=" , a, "b=" , b) i, j := 10 , 10 i, j = j, i fmt.Printf("a=%d,b=%d\n" , i, j) i = 30 j = 20 temp, _ = i, j fmt.Println("temp=" , temp) var c, d, e int c, d, e = test() fmt.Printf("c=%d,d=%d,e=%d\n" , c, d, e) d = 5 _, d, _ = test() fmt.Printf("d=%d" , d) }
常量的使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func main () { const a int = 10 fmt.Println("a=" , a) const b = 20 fmt.Printf("b type is %T\n" , b) fmt.Println("b=" , b) }
多个变量或常量的定义 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 31 package mainimport "fmt" func main () { var ( a int = 10 b float64 = 3.14 ) var ( a = 10 b = 3.14 ) fmt.Println("a=" , a) fmt.Println("b=" , b) const ( i int = 10 j float64 = 3.14 ) fmt.Println("i=" , i) fmt.Println("j=" , j) }
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 28 29 30 31 32 33 34 package mainimport "fmt" func main () { const ( a = iota b = iota c = iota ) fmt.Printf("a=%d,b=%d,c=%d\n" , a, b, c) const d = iota fmt.Printf("d=%d\n" , d) const ( a1 = iota b2 c3 ) fmt.Printf("a1=%d,b2=%d,c3=%d\n" , a1, b2, c3) const ( i = iota j1, j2, j3 = iota , iota , iota k = iota ) fmt.Printf("i=%d,j1=%d,j2=%d,j3=%d,k=%d\n" , i, j1, j2, j3, k) }
bool类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "fmt" func main () { var b bool fmt.Println("b=" , b) b = true fmt.Println("b=" , b) var c = true fmt.Println("c=" , c) d := false fmt.Println("d=" , d) }
浮点数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport "fmt" func main () { var f1 float64 f1 = 3.14 fmt.Println("f1=" , f1) f2 := 31.4 fmt.Printf("f2 type is %T\n" , f2) }
字符类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package main import "fmt" func main() { var ch byte //声明字符类型 ch = 97 //fmt.Println("ch=", ch) //格式化输出,%c以字符方式打印,%d以整型方式打印 fmt.Printf("ch=%c,%d\n", ch, ch) ch = 'a' //字符是单引号 fmt.Printf("ch=%c,%d\n", ch, ch) //大写转小写,小写转大写,大小写相差32,小写大 //转义字符以'\'开头,'\n'换行符 fmt.Printf("大写:%d,小写:%d\n", 'A', 'a') fmt.Printf("大写转小写:%c\n", 'A'+32) fmt.Printf("小写转大写:%c\n", 'a'-32) }
字符串类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport "fmt" func main () { var str1 string str1 = "abc" fmt.Println("str1=" , str1) str2 := "mike" fmt.Println("str2=" , str2) fmt.Println("len(str2)=" , len (str2)) }
字符和字符串的区别 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 package mainimport ( "fmt" ) func main () { var ch byte var str string ch = 'a' fmt.Println("ch=" , ch) str = "a" fmt.Println("str=" , str) str = "hell go" fmt.Printf("str[0]=%c" , str[0 ]) }
复数类型
格式化输出 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func main () { a := 10 b := 'A' c := "hello" d := 3.14 fmt.Printf("%T,%T,%T,%T\n" , a, b, c, d) fmt.Printf("%d,%c,%s,%f\n" , a, b, c, d) fmt.Printf("%v,%v,%v,%v\n" , a, b, c, d) }
变量的输入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "fmt" func main () { var a int var b int fmt.Printf("请输入变量a:" ) fmt.Scanf("%d" , &a) fmt.Printf("请输入变量b:" ) fmt.Scan(&b) fmt.Println("a=" , a) fmt.Println("b=" , b) }
类型转化
类型别名 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "fmt" func main () { type bigint int64 var a bigint fmt.Printf("a type is %T\n" , a) type ( long int64 char byte ) var b long = 11 var ch char = 'a' fmt.Printf("b=%d,ch=%c\n" , b, ch) }
3运算符
只有整除哦
4.流程控制 if的使用,if支持初始化语句 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport "fmt" func main () { s := "王思聪" if s == "王思聪" { fmt.Println("王思聪" ) } if a := 10 ; a == 10 { fmt.Println("a==10" ) } }
if else_if else的用法 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package mainimport "fmt" func main () { if a := 10 ; a == 10 { fmt.Println("a==10" ) } else if a > 10 { fmt.Println("a>10" ) } else if a < 10 { fmt.Println("a<10" ) } b := 15 if b == 10 { fmt.Println("b==10" ) } else if b > 10 { fmt.Println("b>10" ) } else if b < 10 { fmt.Println("b<10" ) } c := 5 if c == 10 { fmt.Println("c==10" ) } else if c > 10 { fmt.Println("c>10" ) } else { fmt.Println("c<10" ) } d := 12 if d == 12 { fmt.Println("d==12" ) } if d > 12 { fmt.Println("d>12" ) } if d < 12 { fmt.Println("d<12" ) } }
switch 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport "fmt" func main () { num := 12 switch num { case 1 : fmt.Println("按下的是1楼" ) case 2 : fmt.Println("按下的是2楼" ) case 3 : fmt.Println("按下的是3楼" ) case 4 : fmt.Println("按下的是4楼" ) default : fmt.Println("按下的是xxx楼" ) } }
for 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" func main () { sum := 0 for i := 0 ; i < 100 ; i++ { sum = sum + i } fmt.Println("sum=" , sum) }
range 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 package mainimport "fmt" func main () { str := "abc" for i := 0 ; i < len (str); i++ { fmt.Printf("str[%d]=%c\n" , i, str[i]) } for i, data := range str { fmt.Printf("str[%d]=%c\n" , i, data) } for i := range str { fmt.Printf("str[%d]=%c\n" , i, str[i]) } for i, _ := range str { fmt.Printf("str[%d]=%c\n" , i, str[i]) } }
break,continue
goto
5.函数 定义格式
无参无返回值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport "fmt" func MyFunc () { a := 666 fmt.Println("a=" , a) } func main () { MyFunc() }
有参无返回值 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 31 32 33 34 35 36 37 38 package mainimport "fmt" func MyFunc01 (a int ) { a = 111 fmt.Println("a=" , a) } func MyFunc02 (a int , b int ) { a = 111 fmt.Println("a=" , a, "b=" , b) } func MyFunc03 (a, b int ) { a = 111 fmt.Println("a=" , a, "b=" , b) } func MyFunc04 (a int , b float64 , c string ) {} func MyFunc05 (a, b float64 , c string ) {} func main () { MyFunc01(121 ) MyFunc03(11 , 22 ) }
不定参数类型 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 31 package mainimport "fmt" func Myfunc01 (a int , b int ) { } func Myfunc02 (args ...int ) { fmt.Println("len(args)=" , len (args)) for i := 0 ; i < len (args); i++ { fmt.Printf("args[%d]=%d\n" , i, args[i]) } fmt.Println("__________" ) for i, data := range args { fmt.Printf("args[%d]=%d\n" , i, data) } fmt.Println("__________" ) } func main () { Myfunc01(1 , 2 ) Myfunc02() Myfunc02(1 ) Myfunc02(1 , 2 ) }
不定参数的传递 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 package mainimport "fmt" func Myfunc01 (args ...int ) { fmt.Println("Myfunc01" ) for _, data := range args { fmt.Println(data) } } func Myfunc02 (args ...int ) { fmt.Println("Myfunc02" ) for _, data := range args { fmt.Println(data) } } func Test (args ...int ) { Myfunc01(args...) Myfunc02(args[1 :]...) } func main () { Test(1 , 2 , 3 ) }
返回一个值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport "fmt" func Test01 () int { return 250 } func Test02 () (value int ) { value = 250 return value } func Test03 () (value int ) { value = 250 return } func main () { v1 := Test01() v2 := Test02() v3 := Test03() fmt.Printf("V1=%d,V2=%d,V3=%d\n" , v1, v2, v3) }
多个返回值 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 package mainimport "fmt" func Test01 () (int , string ) { return 250 , "sb" } func Test02 () (value int , strs string ) { value = 250 strs = "sb" return value, strs } func Test03 () (value int , strs string ) { value = 250 strs = "sb" return } func main () { v1, v2 := Test01() _, v3 := Test02() v4, v5 := Test03() fmt.Printf("V1=%d,V2=%s,V3=%s,v4=%d,v5=%s\n" , v1, v2, v3, v4, v5) }
有参有返回值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "fmt" func max_element (num1 int , num2 int ) (max int , min int ) { if num1 > num2 { min = num2 max = num1 } else { min = num1 max = num2 } return max, min } func main () { v1, v2 := max_element(10 , 20 ) fmt.Printf("v1_max=%d,v2_min=%d\n" , v1, v2) }
递归函数 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 package mainimport "fmt" func Test01 () int { sum := 0 for i := 1 ; i <= 100 ; i++ { sum = sum + i } return sum } func Test02 (num int ) int { if num == 1 { return 1 } return num + Test02(num-1 ) } func main () { result := Test01() fmt.Printf("result=%d\n" , result) result2 := Test02(100 ) fmt.Printf("result2=%d\n" , result2) }
函数类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport "fmt" type FuncType func (int , int ) int func Test01 (a, b int , f FuncType) (result int ) { result = f(a, b) return result } func Add (a, b int ) int { return a + b } func Minus (a, b int ) int { return a - b } func main () { v1 := Test01(1 , 2 , Add) v2 := Test01(1 , 2 , Minus) fmt.Printf("v1=%d,v2=%d\n" , v1, v2) }
函数有一个参数是函数类型,这个函数就是回调函数
匿名函数 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 package mainimport "fmt" func main () { a := 10 str := "mike" f1 := func () { fmt.Println("a=" , a) fmt.Println("str=" , str) } f1() type FuncType func () var f2 FuncType f2 = f1 f2() func () { fmt.Println("a=" , a) fmt.Println("str=" , str) }() f3 := func (i, j int ) { fmt.Printf("i=%d,j=%d\n" , i, j) } f3(1 , 2 ) func (i, j int ) { fmt.Printf("i=%d,j=%d\n" , i, j) }(4 , 5 ) x, y := func (i, j int ) (max, min int ) { if i > j { max = i min = j } else { max = j min = i } return }(10 , 20 ) fmt.Printf("x=%d,y=%d\n" , x, y) }
闭包 所谓闭包就是一个函数“捕获”了和它在同一作用域的其它常量和变量。这就意味着当闭包被调用的时候,不管在程序什么地方调用,闭包能够使用这些常量或者变量。它不关心这些捕获了的变量和常量是否已经超出了作用域,所以只有闭包还在使用它,这些变量就还会存在。
在 Go 语言里,所有的匿名函数(Go 语言规范中称之为函数字面量)都是闭包。匿名函数是指不需要定义函数名的一种函数实现方式,它并不是一个新概念,最早可以回溯到 1958 年的Lisp 语言
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport "fmt" func main () { a := 10 str := "mike" func () { a = 100 str = "go" fmt.Println("a=" , a) fmt.Println("str=" , str) }() fmt.Println("a=" , a) fmt.Println("str=" , str) }
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 package mainimport "fmt" func Test01 () int { var x int x++ return x * x } func Test02 () func () int { var x int return func () int { x++ return x * x } } func main () { fmt.Println(Test01()) fmt.Println(Test01()) fmt.Println(Test01()) f := Test02() fmt.Println(f()) fmt.Println(f()) fmt.Println(f()) fmt.Println(f()) }
defer的作用
多个defer执行顺序
defer和匿名函数结合使用
我的理解:a是值传递(实参已经先给匿名函数了),b不是值传递
获取命令行参数
局部变量
全局变量
不同作用域同名变量
6.工程管理 在实际的开发工作中,直接调用编译器进行编译和链接的场景是少而又少,因为在工程中不会简单到只有一个源代码文件,且源文件之间会有相互的依赖关系。如果这样一个文件一个文件逐步编译,那不亚于一场灾难。 Go 语言的设计者作为行业老将,自然不会忽略这一点。早期 Go 语言使用 makefile 作为临时方案,到了 Go 1 发布时引入了强大无比的 Go 命令行工具。
Go 命令行工具的革命性之处在于彻底消除了工程文件的概念,完全用目录结构和包名来推导工程结构和构建顺序。针对只有一个源文件的情况讨论工程管理看起来会比较多余,因为这可以直接用 go run 和 go build 搞定。下面我们将用一个更接近现实的虚拟项目来展示 Go语言的基本工程管理方法。
工作区 工作区介绍 Go 代码必须放在工作区中 。工作区其实就是一个对应于特定工程的目录,它应包含 3 个子目录:src 目录、pkg 目录和 bin 目录。
src 目录:用于以代码包的形式组织并保存 Go 源码文件。(比如:.go .c .h .s 等)
pkg 目录:用于存放经由 go install 命令构建安装后的代码包(包含 Go 库源码文件)的“.a”归档文件。
bin 目录:与 pkg 目录类似,在通过 go install 命令完成安装后,保存由 Go 命令源码文件生成的可执行文件。
目录 src 用于包含所有的源代码,是 Go 命令行工具一个强制的规则,而 pkg 和 bin 则无需手动创建,如果必要 Go 命令行工具在构建过程中会自动创建这些目录。
需要特别注意的是,只有当环境变量 GOPATH 中只包含一个工作区的目录路径时,go install命令才会把命令源码安装到当前工作区的 bin 目录下。若环境变量 GOPATH 中包含多个工作区的目录路径,像这样执行 go install 命令就会失效,此时必须设置环境变量 GOBIN。
GOPATH 为了能够构建这个工程,需要先把所需工程的根目录加入到环境变量 GOPATH 中。否则,即使处于同一工作目录(工作区),代码之间也无法通过绝对代码包路径完成调用。
在实际开发环境中,工作目录往往有多个。这些工作目录的目录路径都需要添加至 GOPATH。当有多个目录时,请注意分隔符,多个目录的时候 Windows 是分号,Linux 系统是冒号,当有多个 GOPATH 时,默认会将 go get 的内容放在第一个目录下。
包 所有 Go 语言的程序都会组织成若干组文件,每组文件被称为一个包。这样每个包的代码都可以作为很小的复用单元,被其他项目引用。
一个包的源代码保存在一个或多个以.go 为文件后缀名的源文件中,通常一个包所在目录路径的后缀是包的导入路径
自定义包
注意:同一个目录下不能定义不同的 package。
main包 在 Go 语言里,命名为 main 的包具有特殊的含义。 Go 语言的编译程序会试图把这种名字的包编译为二进制可执行文件。所有用 Go 语言编译的可执行程序都必须有一个名叫 main的包。一个可执行程序有且仅有一个 main 包。
当编译器发现某个包的名字为 main 时,它一定也会发现名为 main()的函数,否则不会创建可执行文件。 main()函数是程序的入口,所以,如果没有这个函数,程序就没有办法开始执行。程序编译时,会使用声明 main 包的代码所在的目录的目录名作为二进制可执行文件的文件名。
main函数和init函数 Go 里面有两个保留的函数:init 函数(能够应用于所有的 package)和 main 函数(只能应用于 package main)。这两个函数在定义时不能有任何的参数和返回值。虽然一个 package里面可以写任意多个 init 函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个 package 中每个文件只写一个 init 函数。
Go 程序会自动调用 init()和 main(),所以你不需要在任何地方调用这两个函数。每个 package中的 init 函数都是可选的,但 package main 就必须包含一个 main 函数。
每个包可以包含任意多个 init 函数,这些函数都会在程序执行开始的时候被调用。所有被编译器发现的 init 函数都会安排在 main 函数之前执行。 init 函数用在设置包、初始化变量或者其他要在程序运行前优先完成的引导工作。
程序的初始化和执行都起始于 main 包。如果 main 包还导入了其它的包,那么就会在编译时将它们依次导入。
有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到 fmt包,但它只会被导入一次,因为没有必要导入多次)。
当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行 init 函数(如果有的话),依次类推。等所有被导入的包都加载完毕了,就会开始对 main 包中的包级常量和变量进行初始化,然后执行 main 包中的 init 函数(如果存在的话),最后执行 main 函数。下图详细地解释了整个执行过程:
导入包
测试案例
7.复合类型 分类
类型
名称
长度
默认值
说明
pointer
指针
nil
array
数组
0
slice
切片
nil
引用类型
map
字典
nil
引用类型
struct
结构体
指针 指针是一个代表着某个内存地址的值。这个内存地址往往是在内存中存储的另一个变量的值的起始位置。Go 语言对指针的支持介于 Java 语言和 C/C++语言之间,它既没有想 Java 语言那样取消了代码对指针的直接操作的能力,也避免了 C/C++语言中由于对指针的滥用而造成的安全和可靠性问题。
基本操作 Go 语言虽然保留了指针,但与其它编程语言不同的是:
默认值 nil,没有 NULL 常量
操作符 “&” 取变量地址, “*” 通过指针访问目标对象
不支持指针运算,不支持 “->” 运算符,直接⽤ “.” 访问目标成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func main () { var a int = 10 fmt.Printf("&a=%p\n" , &a) var p *int = nil p = &a fmt.Printf("p=%p\n" , p) fmt.Printf("a=%d,*p=%d\n" , a, *p) *p = 111 fmt.Printf("a=%d,*p=%d\n" , a, *p) }
new函数 表达式 new(T)将创建一个 T 类型的匿名变量,所做的是为 T 类型的新值分配并清零一块内存空间,然后将这块内存空间的地址作为结果返回,而这个结果就是指向这个新的 T 类型值的指针值,返回的指针类型为*T。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "fmt" func main () { var p *int p = new (int ) fmt.Println("p=" , *p) p2 := new (int ) *p2 = 111 fmt.Println("p=" , *p) fmt.Println("p2=" , *p2) }
我们只需使用 new()函数,无需担心其内存的生命周期或怎样将其删除,因为 Go 语言的内存管理系统会帮我们打理一切。
指针做函数参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport "fmt" func swap (a, b int ) { a, b = b, a fmt.Printf("a=%d,b=%d\n" , a, b) } func swap02 (a, b *int ) { *a, *b = *b, *a } func main () { var a int = 10 var b int = 20 swap02(&a, &b) fmt.Printf("a=%d,b=%d\n" , a, b) }
数组 概述
操作数组 数组的每个元素可以通过索引下标来访问,索引下标的范围是从 0 开始到数组长度减 1 的位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "fmt" func main () { var a [10 ]int for i := 0 ; i < 10 ; i++ { a[i] = i + 1 fmt.Printf("a[%d]=%d\n" , i, a[i]) } for i, data := range a { fmt.Printf("a[%d]=%d\n" , i, data) } }
内置函数 len(长度) 和 cap(容量) 都返回数组⻓度 (元素数量) ——数组长度固定所以值相同
1 2 a := [10 ]int {} fmt.Println(len (a), cap (a))
初始化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainfunc main () { a := [3 ]int {1 , 2 } for i, data := range a { println ("i=" , i, "data=" , data) } b := [...]int {1 , 2 , 3 } for i, data := range b { println ("i=" , i, "data=" , data) } c := [5 ]int {2 : 100 , 4 : 100 } for i, data := range c { println ("i=" , i, "data=" , data) } }
在函数传递数组 根据内存和性能来看,在函数间传递数组是一个开销很大的操作。在函数之间传递变量时,总是以值的方式传递的。如果这个变量是一个数组,意味着整个数组,不管有多长,都会完整复制,并传递给函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "fmt" func modify (array [5]int ) { array[0 ] = 10 fmt.Println("In modify(),array values:" , array) } func main () { array := [5 ]int {1 , 2 , 3 , 4 , 5 } modify(array) fmt.Println("In main(),array values:" , array) }
数组指针做函数参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "fmt" func modify (array *[5]int ) { (*array)[0 ] = 10 fmt.Println("In modify(),array values:" , *array) } func main () { array := [5 ]int {1 , 2 , 3 , 4 , 5 } modify(&array) fmt.Println("In main(),array values:" , array) }
冒泡排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package main import "fmt" func BubbleSort (arr []int ) []int { for i := 0 ; i<len (arr) - 1 ; i++{ for j:= 0 ; j<len (arr)-1 -i; j++{ if arr[j] > arr[j+1 ]{ arr[j],arr[j+1 ] = arr[j+1 ],arr[j] } } } return arr } func main () { arr := []int {23 ,45 ,13 ,67 ,35 } fmt.Println(BubbleSort(arr)) }
随机数 1 2 3 4 5 6 7 func main () { rand.Seed(time.Now().Unix()) for i:=0 ; i<10 ; i++ { fmt.Print(rand.Intn(10 ), " " ) } }
切片 概述
切片的创建和初始化 slice 和数组的区别:声明数组时,方括号内写明了数组的长度或使用…自动计算长度,而声明 slice 时,方括号内没有任何字符。
1 2 3 4 5 6 7 8 9 10 11 package main func main() { var s1 []int //声明切片和声明array一样,只是少了长度,此为空切片(nil) s2 := []int{} //make([]T,length,capacity) //capacity省略,则和length的值相同 var s3 []int = make([]int, 0) s4 := make([]int, 0, 0) s5 := []int{1, 2, 3} }
注意 :make 只能创建 slice、map 和 channel,并且返回一个有初始值(非零)。
切片的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func main () { s := []int {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } s1 := s[2 :5 ] s1[2 ] = 100 fmt.Println(s1, s) s2 := s1[2 :6 ] s2[3 ] = 200 fmt.Println(s2, s) }
append 函数向 slice 尾部添加数据,返回新的 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 package mainimport "fmt" func main () { var s1 []int s1 = append (s1, 1 ) s1 = append (s1, 2 , 3 ) s1 = append (s1, 4 , 5 , 6 ) fmt.Println(s1) s2 := make ([]int , 5 ) s2 = append (s2, 6 ) fmt.Println(s2) s3 := []int {1 , 2 , 3 } s3 = append (s3, 4 , 5 ) fmt.Println(s3) }
append 函数会智能地底层数组的容量增长,一旦超过原底层数组容量,通常以 2 倍容量重新分配底层数组,并复制原来的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func main () { s := make ([]int , 0 , 1 ) c := cap (s) for i := 0 ; i < 50 ; i++ { s = append (s, i) if n := cap (s); n > c { fmt.Printf("cap:%d--->%d\n" , c, n) c = n } } }
函数 copy 在两个 slice 间复制数据,复制⻓度以 len 小的为准,两个 slice 可指向同⼀底层数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport "fmt" func main () { data := [...]int {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } s1 := data[8 :] s2 := data[:5 ] copy (s2, s1) fmt.Println(s2) fmt.Println(data) }
切片做函数参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport "fmt" func test (s []int ) { s[0 ] = -1 fmt.Println("test:" ) for i, v := range s { fmt.Printf("s[%d]=%d, " , i, v) } fmt.Println("\n" ) } func main () { s1 := []int {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } test(s1) fmt.Println("main:" ) for i, v := range s1 { fmt.Printf("s[%d]=%d, " , i, v) } fmt.Println("\n" ) }
map 概述
map的创建 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport "fmt" func main () { var m1 map [int ]string fmt.Println(m1 == nil ) m2 := map [int ]string {} m3 := make (map [int ]string ) fmt.Println(m2, m3) m4 := make (map [int ]string , 10 ) fmt.Println(m4) }
初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport "fmt" func main () { var m1 map [int ]string = map [int ]string {1 : "mike" , 2 : "yoo" } fmt.Println(m1) m2 := map [int ]string {1 : "mike" , 2 : "yoo" } fmt.Println(m2) }
赋值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport "fmt" func main () { m1 := map [int ]string {1 : "mike" , 2 : "yoo" } m1[1 ] = "xxx" m1[3 ] = "lily" fmt.Println(m1) m2 := make (map [int ]string , 10 ) m2[0 ] = "aaa" m2[1 ] = "bbb" fmt.Println(m2) fmt.Println(m2[0 ], m2[1 ]) }
遍历 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport "fmt" func main () { m1 := map [int ]string {1 : "mike" , 2 : "yoo" } for k, v := range m1 { fmt.Printf("%d——>%s\n" , k, v) } for k := range m1 { fmt.Printf("%d——>%s\n" , k, m1[k]) } value, ok := m1[1 ] fmt.Println(value, ok) value2, ok2 := m1[3 ] fmt.Println(value2, ok2) }
删除 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" func main () { m1 := map [int ]string {1 : "mike" , 2 : "yoo" } for k, v := range m1 { fmt.Printf("%d——>%s\n" , k, v) } delete (m1, 2 ) fmt.Println("_____" ) for k, v := range m1 { fmt.Printf("%d——>%s\n" , k, v) } }
map做函数参数 在函数间传递映射并不会制造出该映射的一个副本,不是值传递,而是引用传递:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" func DeleteMap (m map [int ]string , key int ) { delete (m, key) for k, v := range m { fmt.Printf("len(m)=%d,%d——>%s\n" , len (m), k, v) } } func main () { m := map [int ]string {1 : "mike" , 2 : "yoo" } DeleteMap(m, 2 ) for k, v := range m { fmt.Printf("len(m)=%d,%d——>%s\n" , len (m), k, v) } }
结构体 结构体类型
结构体初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport "fmt" type Student struct { id int name string sex byte age int address string } func main () { var s1 Student = Student{1 , "mike" , 'm' , 18 , "shanghai" } fmt.Println(s1) s2 := Student{2 , "yoo" , 'f' , 20 , "shanghai" } fmt.Println(s2) s3 := Student{id: 2 , name: "lily" } fmt.Println(s3) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport "fmt" type Student struct { id int name string sex byte age int address string } func main () { var s5 *Student = &Student{3 , "zhou" , 'm' , 18 , "beijing" } fmt.Println(*s5) }
结构体成员的使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport "fmt" type Student struct { id int name string sex byte age int address string } func main () { var s1 Student = Student{1 , "mike" , 'm' , 18 , "shanghai" } fmt.Printf("id=%d,name=%s,sex=%c,age=%d,address=%s\n" , s1.id, s1.name, s1.sex, s1.age, s1.address) var s2 Student s2.id = 2 s2.name = "lily" s2.sex = 'm' s2.age = 18 s2.address = "shanghai" fmt.Println(s2) }
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 package mainimport "fmt" type Student struct { id int name string sex byte age int address string } func main () { s3 := new (Student) s3.id = 3 s3.name = "xxx" fmt.Println(s3, *s3) var s4 Student = Student{4 , "yyy" , 'm' , 18 , "shanghai" } fmt.Println(s4, &s4) var p *Student = &s4 p.id = 5 (*p).name = "zzz" fmt.Println(p, *p, s4) }
结构体比较 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "fmt" type Student struct { id int name string sex byte age int address string } func main () { var s4 Student = Student{4 , "yyy" , 'm' , 18 , "shanghai" } var s5 Student = Student{4 , "yyy" , 'm' , 18 , "shanghai" } fmt.Println("s4==s5" , s4 == s5) fmt.Println("s4!=s5" , s4 != s5) }
结构体作为函数参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport "fmt" type Student struct { id int name string sex byte age int address string } func printStudentValue (tmp Student) { tmp.id = 250 fmt.Println("printStudentValue tmp=" , tmp) } func main () { var s4 Student = Student{4 , "yyy" , 'm' , 18 , "shanghai" } printStudentValue(s4) fmt.Println("main s4=" , s4) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport "fmt" type Student struct { id int name string sex byte age int address string } func printStudentValue (tmp *Student) { tmp.id = 250 fmt.Println("printStudentValue tmp=" , tmp) } func main () { var s4 Student = Student{4 , "yyy" , 'm' , 18 , "shanghai" } printStudentValue(&s4) fmt.Println("main s4=" , s4) }
可见性
8.面向对象编程 概述 对于面向对象编程的支持 Go 语言设计得非常简洁而优雅。因为, Go 语言并没有沿袭传统面向对象编程中的诸多概念,比如继承(不支持继承,尽管匿名字段的内存布局和行为类似继承,但它并不是继承)、虚函数、构造函数和析构函数、隐藏的 this 指针等。
尽管 Go 语言中没有封装、继承、多态这些概念,但同样通过别的方式实现这些特性:
封装:通过方法实现
继承:通过匿名字段实现
多态:通过接口实现
匿名组合 一般情况下,定义结构体的时候是字段名与其类型一一对应,实际上 Go 支持只提供类型,而不写字段名的方式,也就是匿名字段,也称为嵌入字段。
当匿名字段也是一个结构体的时候,那么这个结构体所拥有的全部字段都被隐式地引入了当前定义的这个结构体。
匿名字段,初始化 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 31 32 33 34 package mainimport "fmt" type Person struct { name string sex byte age int } type Student struct { Person id int address string } func main () { s1 := Student{Person{"mike" , 'm' , 18 }, 1 , "shanghai" } fmt.Printf("s1=%v\n" , s1) s3 := Student{Person: Person{"lily" , 'f' , 19 }, id: 2 } fmt.Printf("s3=%v\n" , s3) s4 := Student{Person: Person{name: "Tom" }, id: 3 } fmt.Printf("s4=%v\n" , s4) }
成员的操作 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 31 32 33 34 package mainimport "fmt" type Person struct { name string sex byte age int } type Student struct { Person id int address string } func main () { var s1 Student s1.name = "mike" s1.sex = 'm' s1.age = 18 s1.id = 1 s1.address = "shanghai" fmt.Println(s1) var s2 Student s2.Person = Person{"Lily" , 'f' , 19 } s2.id = 2 s2.address = "beijing" fmt.Println(s2) }
同名字段 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 31 32 33 34 35 36 package mainimport "fmt" type Person struct { name string sex byte age int } type Student struct { Person id int address string name string } func main () { var s1 Student s1.name = "mike" fmt.Printf("%v\n" , s1) s1.Person.name = "lily" fmt.Printf("%v\n" , s1) }
其他匿名字段 所有的内置类型和自定义类型都是可以作为匿名字段的:
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 package mainimport ( "fmt" ) type mystr string type Person struct { name string sex byte age int } type Student struct { Person int mystr } func main () { s1 := Student{Person{"mike" , 'm' , 18 }, 1 , "shanghai" } fmt.Printf("%v\n" , s1) fmt.Printf("%s, %c,%d,%d,%s\n" , s1.name, s1.sex, s1.age, s1.int , s1.mystr) }
结构体指针
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 31 32 33 34 35 36 37 38 package mainimport ( "fmt" ) type Person struct { name string sex byte age int } type Student struct { *Person id int address string } func main () { s1 := Student{&Person{"mike" , 'm' , 18 }, 1 , "beijing" } fmt.Printf("%v\n" , s1) fmt.Printf("%s,%c,%d\n" , s1.name, s1.sex, s1.age) var s2 Student s2.Person = new (Person) s2.name = "Lily" s2.sex = 'f' s2.age = 20 s2.id = 2 s2.address = "shanghai" fmt.Println(s2.name, s2.sex, s2.age, s2.id, s2.address) }
方法 概述 在面向对象编程中,一个对象其实也就是一个简单的值或者一个变量,在这个对象中会包含一些函数,**这种带有接收者的函数,我们称为方法(method)**。 本质上,一个方法则是一个和特殊类型关联的函数。
一个面向对象的程序会用方法来表达其属性和对应的操作,这样使用这个对象的用户就不需要直接去操作对象,而是借助方法来做这些事情。
在 Go 语言中,可以给任意自定义类型(包括内置类型,但不包括指针类型)添加相应的方法。
⽅法总是绑定对象实例,并隐式将实例作为第⼀实参 (receiver),方法的语法如下:
1 func (receiver ReceiverType) funcName(parameters) (results)
参数 receiver 可任意命名。如⽅法中未曾使⽤,可省略参数名。
参数 receiver 类型可以是 T 或 *T。基类型 T 不能是接⼝或指针。
不支持重载方法,也就是说,不能定义名字相同但是不同参数的方法。
为类型添加方法 基础类型作为接收者 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 package mainimport ( "fmt" ) type MyInt int func (a MyInt) Add(b MyInt) MyInt { return a + b } func Add (a, b MyInt) MyInt { return a + b } func main () { var a MyInt = 1 var b MyInt = 1 fmt.Println("a.Add(b)=" , a.Add(b)) fmt.Println("Add(a,b)=" , Add(a, b)) }
通过上面的例子可以看出,面向对象只是换了一种语法形式来表达。方法是函数的语法糖,因为 receiver 其实就是方法所接收的第 1 个参数。
注意:虽然方法的名字一模一样,但是如果接收者不一样,那么方法就不一样。
结构体作为接收者 方法里面可以访问接收者的字段,调用方法通过点( . )访问,就像 struct 里面访问字段一样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" type Person struct { name string sex byte age int } func (p Person) PrintInfo() { fmt.Println(p.name, p.sex, p.age) } func main () { p := Person{"mike" , 'm' , 18 } p.PrintInfo() }
值语义和引用语义 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package mainimport "fmt" type Person struct { name string sex byte age int } func (p *Person) SetInfoPointer() { (*p).name = "Lily" p.sex = 'f' p.age = 22 } func (p Person) SetInfoValue() { p.name = "Lily" p.sex = 'f' p.age = 22 } func main () { p1 := Person{"mike" , 'm' , 18 } fmt.Println("函数调用前=" , p1) (&p1).SetInfoPointer() fmt.Println("函数调用后=" , p1) fmt.Println("---------------" ) p2 := Person{"mike" , 'm' , 18 } fmt.Println("函数调用前=" , p2) p2.SetInfoValue() fmt.Println("函数调用后=" , p2) }
方法集 类型的方法集是指可以被该类型的值调用的所有方法的集合。
用实例实例 value 和 pointer 调用方法(含匿名字段)不受⽅法集约束,编译器编总是查找全部方法,并自动转换 receiver 实参。
类型*T 方法集 一个指向自定义类型的值的指针,它的方法集由该类型定义的所有方法组成,无论这些方法接受的是一个值还是一个指针。
如果在指针上调用一个接受值的方法,Go 语言会聪明地将该指针解引用,并将指针所指的底层值作为方法的接收者。
类型 *T ⽅法集包含全部 receiver T + *T ⽅法:
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 package maintype Person struct { name string sex byte age int } func (p *Person) SetInfoPointer() { (*p).name = "Lily" p.sex = 'f' p.age = 22 } func (p Person) SetInfoValue() { p.name = "xxx" p.sex = 'm' p.age = 33 } func main () { var p *Person = &Person{"mike" , 'm' , 18 } p.SetInfoPointer() p.SetInfoValue() (*p).SetInfoValue() }
类型T 方法集 一个自定义类型值的方法集则由为该类型定义的接收者类型为值类型的方法组成,但是不包含那些接收者类型为指针的方法。
但这种限制通常并不像这里所说的那样,因为如果我们只有一个值,仍然可以调用一个接收者为指针类型的方法,这可以借助于 Go 语言传值的地址能力实现。
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 package maintype Person struct { name string sex byte age int } func (p *Person) SetInfoPointer() { (*p).name = "Lily" p.sex = 'f' p.age = 22 } func (p Person) SetInfoValue() { p.name = "xxx" p.sex = 'm' p.age = 33 } func main () { var p Person = Person{"mike" , 'm' , 18 } (&p).SetInfoPointer() p.SetInfoPointer() p.SetInfoValue() (&p).SetInfoValue() }
还是看是什么语义,引用语义还是会使主函数的值变化
匿名字段 方法的继承 如果匿名字段实现了一个方法,那么包含这个匿名字段的 struct 也能调用该方法
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 package mainimport "fmt" type Person struct { name string sex byte age int } func (p *Person) PrintInfo() { fmt.Println(p.name, p.sex, p.age) } type Student struct { Person id int address string } func main () { p := Person{"mike" , 'm' , 18 } p.PrintInfo() s := Student{Person{"Lily" , 'f' , 20 }, 2 , "shanghai" } s.PrintInfo() }
方法的重写 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 31 32 33 34 35 36 37 38 39 package mainimport "fmt" type Person struct { name string sex byte age int } func (p *Person) PrintInfo() { fmt.Println("Person:" , p.name, p.sex, p.age) } type Student struct { Person id int address string } func (s *Student) PrintInfo() { fmt.Println("Student:" , s.name, s.sex, s.age) } func main () { p := Person{"mike" , 'm' , 18 } p.PrintInfo() s := Student{Person{"Lily" , 'f' , 20 }, 2 , "shanghai" } s.PrintInfo() s.Person.PrintInfo() }
表达式 类似于我们可以对函数进行赋值和传递一样,方法也可以进行赋值和传递。
根据调用者不同,方法分为两种表现形式:方法值和方法表达式。两者都可像普通函数那样赋值和传参,区别在于方法值绑定实例,⽽方法表达式则须显式传参。
方法值
方法表达式
接口 概述 在Go 语言中,接口(interface)是一个自定义类型,接口类型具体描述了一系列方法的集合。
接口类型是一种抽象的类型,它不会暴露出它所代表的对象的内部值的结构和这个对象支持的基础操作的集合,它们只会展示出它们自己的方法。因此接口类型不能将其实例化。
Go 通过接口实现了鸭子类型(duck-typing):“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子”。我们并不关心对象是什么类型,到底是不是鸭子,只关心行为。
接口的使用 接口定义
接口实现 接口是用来定义行为的类型。这些被定义的行为不由接口直接实现,而是通过方法由用户定义的类型实现,一个实现了这些方法的具体类型是这个接口类型的实例。
如果用户定义的类型实现了某个接口类型声明的一组方法,那么这个用户定义的类型的值就可以赋给这个接口类型的值。这个赋值会把用户定义的类型的值存入接口类型的值。
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 package mainimport "fmt" type Humaner interface { SayHi() } type Student struct { name string score float64 } func (s *Student) SayHi() { fmt.Printf("Stduent[%s,%f] say hi!!\n" , s.name, s.score) } type Teacher struct { name string group string } func (t *Teacher) SayHi() { fmt.Printf("Teacher[%s,%s] say hi!!\n" , t.name, t.group) } type Mystr string func (str Mystr) SayHi() { fmt.Printf("Mystr[%s] say hi!!\n" , str) } func WhoSayHi (i Humaner) { i.SayHi() } func main () { s := &Student{"mike" , 88.88 } t := &Teacher{"Lily" , "GO语言" } var temp Mystr = "测试" s.SayHi() t.SayHi() temp.SayHi() WhoSayHi(s) WhoSayHi(t) WhoSayHi(temp) x := make ([]Humaner, 3 ) x[0 ], x[1 ], x[2 ] = s, t, temp for _, value := range x { value.SayHi() } }
通过上面的代码,你会发现接口就是一组抽象方法的集合,它必须由其他非接口类型实现,而不能自我实现。
接口组合 接口嵌入:如果一个 interface1 作为 interface2 的一个嵌入字段,那么 interface2 隐式的包含了 interface1里面的方法。
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 31 32 33 34 35 package mainimport "fmt" type Humaner interface { SayHi() } type Personer interface { Humaner Sing(lyrics string ) } type Student struct { name string score float64 } func (s *Student) SayHi() { fmt.Printf("Stduent[%s,%f] say hi!!\n" , s.name, s.score) } func (s *Student) Sing(lyrics string ) { fmt.Printf("Stduent sing[%s]!!\n" , lyrics) } func main () { s := &Student{"mike" , 88.88 } var i2 Personer i2 = s i2.SayHi() i2.Sing("小帅哥" ) }
接口转化:超级接口对象可转换为子集接口,反之出错:
空接口
类型查询 我们知道 interface 的变量里面可以存储任意类型的数值(该类型实现了 interface)。那么我们怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种方法:
comma-ok 断言:
Go 语言里面有一个语法,可以直接判断是否是该类型的变量: value, ok = element.(T),这里 value 就是变量的值,ok 是一个 bool 类型,element 是 interface 变量,T 是断言的类型。如果 element 里面确实存储了 T 类型的数值,那么 ok 返回 true,否则返回 false。
switch 测试:
9.异常处理 error接口 Go 语言引入了一个关于错误处理的标准模式,即 error 接口,它是 Go 语言内建的接口类型,该接口的定义如下:
1 2 3 type error interface { Error() string }
Go 语言的标准库代码包 errors 为用户提供如下方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 package errorstype errorString struct { text string } func New (text string ) error { return &errorString{text} } func (e *errorString) Error() string { return e.text }
另一个可以生成 error 类型值的方法是调用 fmt 包中的 Errorf 函数:
1 2 3 4 5 package fmtimport "errors" func Errorf (format string , args ...interface {}) error { return errors.New(Sprintf(format, args...)) }
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport ( "errors" "fmt" ) func main () { var err1 error =errors.New("a normal err1" ) fmt.Println(err1) var err2 error =fmt.Errorf("%s" ,"a normal err2" ) fmt.Println(err2) }
函数通常在最后的返回值中返回错误信息:
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 package mainimport ( "errors" "fmt" ) func Divide (a,b float64 ) (result float64 ,err error ){ if b==0 { result=0.0 err=errors.New("runtime error:divide by zero" ) return } result =a/b err=nil return } func main () { r ,err:=Divide(10.0 ,1 ) if err!=nil { fmt.Println(err) } else { fmt.Println(r) } }
panic 在通常情况下,向程序使用方报告错误状态的方式可以是返回一个额外的 error 类型值。
但是,当遇到不可恢复的错误状态的时候,如数组访问越界、空指针引用等,这些运行时错误会引起 painc 异常。这时,上述错误处理方式显然就不适合了。反过来讲,在一般情况下,我们不应通过调用 panic 函数来报告普通的错误,而应该只把它作为报告致命错误的一种方式。当某些不应该发生的场景发生时,我们就应该调用 panic。
一般而言,当 panic 异常发生时,程序会中断运行,并立即执行在该 goroutine(可以先理解成线程,在中被延迟的函数(defer 机制)。随后,程序崩溃并输出日志信息。日志信息包括 panic value 和函数调用的堆栈跟踪信息。
不是所有的 panic 异常都来自运行时,直接调用内置的 panic 函数也会引发 panic 异常;panic函数接受任何值作为参数。
1 func panic (v interface {})
调用 panic 函数引发的 panic 异常:
内置的 panic 函数引发的 panic 异常:
recover 运行时 panic 异常一旦被引发就会导致程序崩溃。这当然不是我们愿意看到的,因为谁也不能保证程序不会发生任何运行时错误。
不过,Go 语言为我们提供了专用于“拦截”运行时 panic 的内建函数——recover。它可以是当前的程序从运行时 panic 的状态中恢复并重新获得流程控制权。
1 func recover () interface {}
注意:recover 只有在 defer 调用的函数中有效。
如果调用了内置函数 recover,并且定义该 defer 语句的函数发生了 panic 异常,recover 会使程序从 panic 中恢复,并返回 panic value。导致 panic 异常的函数不会继续运行,但能正常返回。在未发生 panic 时调用 recover,recover 会返回 nil。
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 31 32 33 34 package mainimport "fmt" func TestA () { fmt.Println("func TestA()" ) } func TestB () (err error ) { defer func () { if x := recover (); x != nil { err = fmt.Errorf("internal error: %v" , x) } }() panic ("func TestB(): panic" ) } func TestC () { fmt.Println("func TestC()" ) } func main () { TestA() err := TestB() fmt.Println(err) TestC() }
延迟调用中引发的错误,可被后续延迟调用捕获,但仅最后⼀个错误可被捕获
10.文本文件处理 字符串处理 字符串在开发中经常用到,包括用户的输入,数据库读取的数据等,我们经常需要对字符串进行分割、连接、转换等操作,我们可以通过 Go 标准库中的 strings 和 strconv 两个包中的函数进行相应的操作。
字符串操作 下面这些函数来自于 strings 包,这里介绍一些我平常经常用到的函数,更详细的请参考官方的文档。
Contains 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport ( "fmt" "strings" ) func main () { fmt.Println(strings.Contains("seafood" ,"foo" )) fmt.Println(strings.Contains("seafood" ,"bar" )) fmt.Println(strings.Contains("seafood" ,"" )) fmt.Println(strings.Contains("" ,"" )) }
Join 1 2 3 4 5 6 s:=[]string {"foo" ,"bar" ,"baz" } fmt.Println(strings.Join(s,"," ))
Index 1 2 3 4 5 6 fmt.Println(strings.Index("chicken" ,"ken" )) fmt.Println(strings.Index("chicken" ,"dmr" ))
Repeat 1 2 3 4 fmt.Println(strings.Repeat("na" ,2 ))
Replace 1 2 3 4 5 6 fmt.Println(strings.Replace("oink oink oink" ,"k" ,"ky" ,2 )) fmt.Println(strings.Replace("oink oink oink" ,"oink" ,"moo" ,-1 ))
splict
Trim
Fields
字符串转化 append
parse
正则表达式 https://github.com/google/re2
https://studygolang.com/articles/28796
JSON处理 JSON (JavaScript Object Notation)是一种比 XML 更轻量级的数据交换格式,在易于人们阅读和编写的同时,也易于程序解析和生成。尽管 JSON 是 JavaScript 的一个子集,但 JSON采用完全独立于编程语言的文本格式,且表现为键/值对集合的文本描述形式(类似一些编程语言中的字典结构),这使它成为较为理想的、跨平台、跨语言的数据交换语言。
开发者可以用 JSON 传输简单的字符串、数字、布尔值,也可以传输一个数组,或者一个更复杂的复合结构。在 Web 开发领域中, JSON 被广泛应用于 Web 服务端程序和客户端之间的数据通信。
Go 语言内建对 JSON 的支持。使用 Go 语言内置的 encoding/json 标准库,开发者可以轻松使用 Go 程序生成和解析 JSON 格式的数据。
JSON 官方网站:http://www.json.org/
在线格式化:http://www.json.cn/
编码json 通过结构体生成json
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package mainimport ( "encoding/json" "fmt" ) type IT struct { Company string Subjects []string IsOk bool Price float64 } func main () { t1:=IT{"itcast" ,[]string {"GO" ,"C++" ,"Python" ,"Test" },true ,666.666 } b,err:=json.MarshalIndent(t1,"" ," " ) if err!=nil { fmt.Println("json err:" ,err) } fmt.Println(string (b)) }
我们看到上面的输出字段名的首字母都是大写的,如果你想用小写的首字母怎么办呢?把结构体的字段名改成首字母小写的?JSON 输出的时候必须注意,只有导出的字段(首字母是大写)才会被输出,如果修改字段名,那么就会发现什么都不会输出,所以必须通过 struct tag定义来实现。
针对 JSON 的输出,我们在定义 struct tag 的时候需要注意的几点是:
字段的 tag 是”-“,那么这个字段不会输出到 JSON
tag 中带有自定义名称,那么这个自定义名称会出现在 JSON 的字段名中
tag 中如果带有”omitempty”选项,那么如果该字段值为空,就不会输出到 JSON 串中
如果字段类型是 bool, string, int, int64 等,而 tag 中带有”,string”选项,那么这个字段在输出到 JSON 的时候会把该字段对应的值转换成 JSON 字符串
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 package mainimport ( "encoding/json" "fmt" ) type IT struct { Company string `json:"-"` Subjects []string `json:"subjects"` 1 IsOk bool `json:",string"` Price float64 `json:"price,omitempty"` } func main () { t1 := IT{Company: "itcast" , Subjects: []string {"Go" , "C++" , "Python" ,"Test" }, IsOk: true } b, err := json.Marshal(t1) if err != nil { fmt.Println("json err:" , err) } fmt.Println(string (b)) }
通过map生成JSON 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 package mainimport ( "encoding/json" "fmt" ) func main () { t1 := make (map [string ]interface {}) t1["company" ] = "itcast" t1["subjects " ] = []string {"Go" , "C++" , "Python" , "Test" } t1["isok" ] = true t1["price" ] = 666.666 b, err := json.Marshal(t1) if err != nil { fmt.Println("json err:" , err) } fmt.Println(string (b)) }
解码Json
解析到结构体 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package mainimport ( "encoding/json" "fmt" ) type IT struct { Company string `json:"company"` Subjects []string `json:"subjects"` IsOk bool `json:"isok"` Price float64 `json:"price"` } func main () { b := []byte (`{ "company": "itcast", "subjects": [ "Go", "C++", "Python", "Test" ], "isok": true, "price": 666.666 }` ) var t IT err := json.Unmarshal(b, &t) if err != nil { fmt.Println("json err:" , err) } fmt.Println(t) type IT2 struct { Subjects []string `json:"subjects"` } var t2 IT2 err = json.Unmarshal(b, &t2) if err != nil { fmt.Println("json err:" , err) } fmt.Println(t2) }
解析到interface 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package mainimport ( "encoding/json" "fmt" ) type IT struct { Company string `json:"company"` Subjects []string `json:"subjects"` Isok bool `json:"isok"` Price float64 `json:"price"` } func main () { b:=[]byte (`{ "company": "itcast", "subjects": [ "GO", "C++", "Python", "Test" ], "isok": true, "price": 666.666 }` ) var t interface {} err:=json.Unmarshal(b,&t) if err!=nil { fmt.Println("json err:" ,err) } fmt.Println(t) m:=t.(map [string ]interface {}) for k,v :=range m{ switch vv:=v.(type ) { case string : fmt.Println(k, "is string" , vv) case int : fmt.Println(k, "is int" , vv) case float64 : fmt.Println(k, "is float64" , vv) case bool : fmt.Println(k, "is bool" , vv) case []interface {}: fmt.Println(k, "is an array:" ) for i, u := range vv { fmt.Println(i, u) } default : fmt.Println(k, "is of a type I don't know how to handle" ) } } }
文件操作
写文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport ( "fmt" "os" ) func main () { fout,err:=os.Create("./xxx.txt" ) if err!=nil { fmt.Println(err) return } defer fout.Close() for i:=0 ;i<5 ;i++{ outstr:=fmt.Sprintf("%s:%d\n" ,"hello go" ,i) fout.WriteString(outstr) fout.Write([]byte ("abcd\n" )) } }
读文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport ( "fmt" "os" ) func main () { fin,err:=os.Open("./xxx.txt" ) if err!=nil { fmt.Println(err) } defer fin.Close() buf:=make ([]byte ,1024 ) for { n,_ :=fin.Read(buf) if n==0 { break } } fmt.Println(string (buf)) }
拷贝文件
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 31 32 33 34 35 36 37 38 39 40 package mainimport ( "fmt" "io" "os" ) func main () { srcPath := "zx.txt" dstPath := "test.txt" srcFile, err1 := os.Open(srcPath) if err1 != nil { fmt.Println(err1) return } dstFile, err2 := os.Create(dstPath) if err2 != nil { fmt.Println(err2) return } buf := make ([]byte , 1024 ) for { n, err := srcFile.Read(buf) if err != nil && err != io.EOF { fmt.Println(err) break } if n == 0 { break } tmp := buf[:n] dstFile.Write(tmp) } srcFile.Close() dstFile.Close() }
11.并发编程 概述 并行和并发 并行 (parallel):指在同一时刻,有多条指令在多个处理器上同时执行。
并发 (concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。
并行是两个队列同时 使用两台咖啡机
并发是两个队列交替 使用一台咖啡机
go语言并发优势 有人把 Go 比作 21 世纪的 C 语言,第一是因为 Go 语言设计简单,第二,21 世纪最重要的就是并行程序设计,而 Go 从语言层面就支持了并行。同时,并发程序的内存管理有时候是非常复杂的,而 Go 语言提供了自动垃圾回收机制。
Go 语言为并发编程而内置的上层 API 基于 CSP(communicating sequential processes, 顺序通信进程)模型。这就意味着显式锁都是可以避免的,因为 Go 语言通过相册安全的通道发送和接受数据以实现同步,这大大地简化了并发程序的编写。
一般情况下,一个普通的桌面计算机跑十几二十个线程就有点负载过大了,但是同样这台机
器却可以轻松地让成百上千甚至过万个 goroutine 进行资源竞争。
goroutine goroutine是什么 goroutine 是 Go 并行设计的核心。goroutine 说到底其实就是协程,但是它比线程更小,十几个 goroutine 可能体现在底层就是五六个线程,Go 语言内部帮你实现了这些 goroutine 之间的内存共享。执行 goroutine 只需极少的栈内存(大概是 4~5KB),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。goroutine 比 thread 更易用、更高效、更轻便。
创建goroutine 只需在函数调⽤语句前添加 go 关键字,就可创建并发执⾏单元。开发⼈员无需了解任何执⾏细节,调度器会自动将其安排到合适的系统线程上执行。
在并发编程里,我们通常想讲一个过程切分成几块,然后让每个 goroutine 各自负责一块工作。当一个程序启动时,其主函数即在一个单独的 goroutine 中运行,我们叫它 main goroutine。新的 goroutine 会用 go 语句来创建。
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 package mainimport ( "fmt" "time" ) func NewTask () { i := 0 for { i++ fmt.Printf("new goroutine:i=%d\n" , i) time.Sleep(1 * time.Second) } } func main () { go NewTask() i := 0 for { i++ fmt.Printf("main goroutine:i=%d\n" , i) time.Sleep(1 * time.Second) } }
主goroutine先退出 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport ( "fmt" "time" ) func NewTask () { i := 0 for { i++ fmt.Printf("new goroutine:i=%d\n" , i) time.Sleep(1 * time.Second) } } func main () { go NewTask() fmt.Println("main goroutine exit" ) }
main goroutine退出,其他goroutine全部退出。
runtime包 Gosched runtime.Gosched() 用于让出 CPU 时间片,让出当前 goroutine 的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢复执行。
这就像跑接力赛,A 跑了一会碰到代码 runtime.Gosched() 就把接力棒交给 B 了,A 歇着了,B 继续跑 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport ( "fmt" "runtime" ) func main () { go func (s string ) { for i := 0 ; i < 5 ; i++ { fmt.Println(s) } }("world" ) for i := 0 ; i < 2 ; i++ { runtime.Gosched() fmt.Println("hello" ) } }
Goexit 调用 runtime.Goexit() 将立即终止当前 goroutine 执⾏,调度器确保所有已注册 defer 延迟调用被执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport ( "fmt" "runtime" ) func main () { go func () { defer fmt.Println("a.defer" ) func () { defer fmt.Println("b.defer" ) runtime.Goexit() fmt.Println("b" ) }() fmt.Println("a" ) }() for { } }
GOMAXPROCS 调用 runtime.GOMAXPROCS() 用来设置可以并行计算的 CPU 核数的最大值,并返回之前的值。
在第一次执行(runtime.GOMAXPROCS(1))时,最多同时只能有一个 goroutine 被执行。所以会打印很多 1。过了一段时间后,GO 调度器会将其置为休眠,并唤醒另一个 goroutine,这时候就开始打印很多 0 了,在打印的时候,goroutine 是被调度到操作系统线程上的。
在第二次执行(runtime.GOMAXPROCS(2))时,我们使用了两个 CPU,所以两个 goroutine 可以一起被执行,以同样的频率交替打印 0 和 1。
channel goroutine 运行在相同的地址空间,因此访问共享内存必须做好同步。goroutine 奉行通过通信来共享内存,而不是共享内存来通信。
引⽤类型 channel 是 CSP 模式的具体实现,用于多个 goroutine 通讯。其内部实现了同步,确保并发安全。
channel类型 和 map 类似,channel 也一个对应 make 创建的底层数据结构的引用。
当我们复制一个 channel 或用于函数参数传递时,我们只是拷贝了一个 channel 引用,因此调用者何被调用者将引用同一个 channel 对象。和其它的引用类型一样,channel 的零值也是nil。
定义一个 channel 时,也需要定义发送到 channel 的值的类型。channel 可以使用内置的 make()函数来创建:
1 2 make (chan Type) make (chan Type, capacity)
当 capacity= 0 时,channel 是无缓冲阻塞读写的,当 capacity> 0 时,channel 有缓冲、是非阻塞的,直到写满 capacity 个元素才阻塞写入。
channel 通过操作符<-来接收和发送数据,发送和接收数据语法:
1 2 3 4 channel <- value <-channel x := <-channel x, ok := <-channel
默认情况下,channel 接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得goroutine 同步变的更加的简单,而不需要显式的 lock。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport "fmt" func main () { c := make (chan int ) go func () { defer fmt.Println("子协程结束" ) fmt.Println("子协程正在运行..." ) c <- 666 }() num := <-c fmt.Println("num=" , num) fmt.Println("main协程结束" ) }
无缓冲的channel 无缓冲的通道(unbuffered channel)是指在接收前没有能力保存任何值的通道。
这种类型的通道要求发送 goroutine 和接收 goroutine 同时准备好,才能完成发送和接收操作。如果两个 goroutine 没有同时准备好,通道会导致先执行发送或接收操作的 goroutine 阻塞等待。
这种对通道进行发送和接收的交互行为本身就是同步的。其中任意一个操作都无法离开另一个操作单独存在。
下图展示两个 goroutine 如何利用无缓冲的通道来共享一个值:
在第 1 步,两个 goroutine 都到达通道,但哪个都没有开始执行发送或者接收。
在第 2 步,左侧的 goroutine 将它的手伸进了通道,这模拟了向通道发送数据的行为。这时,这个 goroutine 会在通道中被锁住,直到交换完成。
在第 3 步,右侧的 goroutine 将它的手放入通道,这模拟了从通道里接收数据。这个goroutine 一样也会在通道中被锁住,直到交换完成。
在第 4 步和第 5 步,进行交换,并最终,在第 6 步,两个 goroutine 都将它们的手从通道里拿出来,这模拟了被锁住的 goroutine 得到释放。两个 goroutine 现在都可以去做别的事情了。
无缓冲的 channel 创建格式:
make(chan Type) //等价于 make(chan Type, 0)
如果没有指定缓冲区容量,那么该通道就是同步的,因此会阻塞到发送者准备好发送和接收者准备好接收。
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 package mainimport ( "fmt" "time" ) func main () { c := make (chan int , 0 ) fmt.Printf("len(c)=%d,cap(c)=%d\n" , len (c), cap (c)) go func () { defer fmt.Println("子协程结束" ) for i := 0 ; i < 3 ; i++ { c <- i fmt.Printf("子协程正在运行[%d]:len(c)=%d,cap(c)=%d\n" , i, len (c), cap (c)) } }() time.Sleep(2 * time.Second) for i := 0 ; i < 3 ; i++ { num := <-c fmt.Println("num=" , num) } fmt.Println("main协程结束" ) }
有缓冲的channel 有缓冲的通道(buffered channel)是一种在被接收前能存储一个或者多个值的通道。
这种类型的通道并不强制要求 goroutine 之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也会不同。只有在通道中没有要接收的值时,接收动作才会阻塞。只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。
这导致有缓冲的通道和无缓冲的通道之间的一个很大的不同:无缓冲的通道保证进行发送和接收的 goroutine 会在同一时间进行数据交换;有缓冲的通道没有这种保证。
在第 1 步,右侧的 goroutine 正在从通道接收一个值。
在第 2 步,右侧的这个 goroutine 独立完成了接收值的动作,而左侧的 goroutine 正在发送一个新值到通道里。
在第 3 步,左侧的 goroutine 还在向通道发送新值,而右侧的 goroutine 正在从通道接收另外一个值。这个步骤里的两个操作既不是同步的,也不会互相阻塞。
最后,在第 4 步,所有的发送和接收都完成,而通道里还有几个值,也有一些空间可以存更多的值。
有缓冲的 channel 创建格式:
make(**chan** Type, capacity)
如果给定了一个缓冲区容量,通道就是异步的。只要缓冲区有未使用空间用于发送数据,或还包含可以接收的数据,那么其通信就会无阻塞地进行。
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 package mainimport ( "fmt" "time" ) func main () { c := make (chan int , 3 ) fmt.Printf("len(c)=%d,cap(c)=%d\n" , len (c), cap (c)) go func () { defer fmt.Println("子协程结束" ) for i := 0 ; i < 3 ; i++ { c <- i fmt.Printf("子协程正在运行[%d]:len(c)=%d,cap(c)=%d\n" , i, len (c), cap (c)) } }() time.Sleep(2 * time.Second) for i := 0 ; i < 3 ; i++ { num := <-c fmt.Println("num=" , num) } fmt.Println("main协程结束" ) }
range和close 如果发送者知道,没有更多的值需要发送到 channel 的话,那么让接收者也能及时知道没有多余的值可接收将是有用的,因为接收者可以停止不必要的接收等待。这可以通过内置的close 函数来关闭 channel 实现。
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 package mainimport "fmt" func main () { c := make (chan int ) go func () { for i := 0 ; i < 5 ; i++ { c <- i } close (c) }() for { if data, ok := <-c; ok { fmt.Println(data) } else { break } } fmt.Println("Finished" ) }
channel 不像文件一样需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束 range 循环之类的,才去关闭 channel;
关闭 channel 后,无法向 channel 再发送数据(引发 panic 错误后导致接收立即返回零值);
关闭 channel 后,可以继续向 channel 接收数据;
对于 nil channel,无论收发都会被阻塞。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport "fmt" func main () { c := make (chan int ) go func () { for i := 0 ; i < 5 ; i++ { c <- i } close (c) }() for data := range c { fmt.Println(data) } fmt.Println("Finished" ) }
单向的channel 默认情况下,通道是双向的,也就是,既可以往里面发送数据也可以同里面接收数据。
但是,我们经常见一个通道作为参数进行传递而值希望对方是单向使用的,要么只让它发送数据,要么只让它接收数据,这时候我们可以指定通道的方向。
单向 channel 变量的声明非常简单,如下:
1 2 3 4 5 var ch1 chan int var ch2 chan <- float64 var ch3 <-chan int
chan<- 表示数据进入管道,要把数据写进管道,对于调用者就是输出。
<-chan 表示数据从管道出来,对于调用者就是得到管道的数据,当然就是输入。
可以将 channel 隐式转换为单向队列,只收或只发,不能将单向 channel 转换为普通channel:
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 package mainimport ( "fmt" ) func counter (out chan <- int ) { defer close (out) for i := 0 ; i < 5 ; i++ { out <- i } } func printer (in <-chan int ) { for num := range in { fmt.Println(num) } } func main () { c := make (chan int ) go counter(c) printer(c) fmt.Println("done" ) }
定时器 Timer Timer 是一个定时器,代表未来的一个单一事件,你可以告诉 timer 你要等待多长时间,它提供一个 channel,在将来的那个时间那个 channel 提供了一个时间值。
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package mainimport ( "fmt" "time" ) func main () { timer := time.NewTimer(time.Second * 2 ) t1 := time.Now() fmt.Printf("t1:%v\n" , t1) t2 := <-timer.C fmt.Printf("t2:%v\n" , t2) timer2 := time.NewTimer(time.Second * 2 ) <-timer2.C fmt.Println("2s后" ) time.Sleep(time.Second * 2 ) fmt.Println("再一次2s后" ) <-time.After(time.Second * 2 ) fmt.Println("再一次2s后" ) timer3 := time.NewTimer(time.Second) go func () { <-timer3.C fmt.Println("Timer 3 expired" ) }() stop := timer3.Stop() if stop { fmt.Println("Timer 3 stopped" ) } fmt.Println("before" ) timer4 := time.NewTimer(time.Second * 5 ) timer4.Reset(time.Second * 1 ) <-timer4.C fmt.Println("after" ) }
Ticker Ticker 是一个定时触发的计时器,它会以一个间隔(interval)往 channel 发送一个事件(当前时间),而 channel 的接收者可以以固定的时间间隔从 channel 中读取事件。
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 package mainimport ( "fmt" "time" ) func main () { ticker := time.NewTicker(time.Second * 1 ) i := 0 go func () { for { <-ticker.C i++ fmt.Println("i = " , i) if i == 5 { ticker.Stop() } } }() for { } }
select Go 里面提供了一个关键字 select,通过 select 可以监听 channel 上的数据流动。select 的用法与 switch 语言非常类似,由 select 开始一个新的选择块,每个选择条件由 case语句来描述。
与 switch 语句可以选择任何可使用相等比较的条件相比, select 有比较多的限制,其中最大的一条限制就是每个 case 语句里必须是一个 IO 操作,大致的结构如下:
1 2 3 4 5 6 7 8 select {case <-chan1:case chan2 <- 1 :default :}
在一个 select 语句中,Go 语言会按顺序从头至尾评估每一个发送和接收的语句。
如果其中的任意一语句可以继续执行(即没有被阻塞),那么就从那些可以执行的语句中任意选择一条来使用。
如果没有任意一条语句可以执行(即所有的通道都被阻塞),那么有两种可能的情况:
如果给出了 default 语句,那么就会执行 default 语句,同时程序的执行会从 select 语句后的语句中恢复。
如果没有 default 语句,那么 select 语句将被阻塞,直到至少有一个通信可以进行下去。
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 package mainimport "fmt" func fibonacci (c, quit chan int ) { x, y := 1 , 1 for { select { case c <- x: x, y = y, x+y case <-quit: fmt.Println("quit" ) return } } } func main () { c := make (chan int ) quit := make (chan int ) go func () { for i := 0 ; i < 6 ; i++ { fmt.Println(<-c) } quit <- 0 }() fibonacci(c, quit) }
有时候会出现 goroutine 阻塞的情况,那么我们如何避免整个程序进入阻塞的情况呢?我们可以利用 select 来设置超时,通过如下的方式实现:
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 package mainimport ( "fmt" "time" ) func main () { c := make (chan int ) o := make (chan bool ) go func () { for { select { case v := <-c: fmt.Println(v) case <-time.After(5 * time.Second): fmt.Println("timeout" ) o <- true break } } }() <-o }
12.网络编程 网络概述
socket编程 什么是socket Socket 起源于 Unix,而 Unix 基本哲学之一就是“一切皆文件”,都可以用“打开 open –> 读写 write/read –> 关闭 close”模式来操作。Socket 就是该模式的一个实现,网络的 Socket数据传输是一种特殊的 I/O,Socket 也是一种文件描述符。Socket 也具有一个类似于打开文件的函数调用:Socket(),该函数返回一个整型的 Socket 描述符,随后的连接建立、数据传输等操作都是通过该 Socket 实现的。常 用 的 Socket 类型有两 种 : 流 式 Socket ( SOCK_STREAM )和数据报式 Socket(SOCK_DGRAM)。流式是一种面向连接的 Socket,针对于面向连接的 TCP 服务应用;数据报式 Socket 是一种无连接的 Socket,对应于无连接的 UDP 服务应用。
TCP的C/S架构
示例程序 服务端
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package mainimport ( "fmt" "log" "net" "strings" ) func dealConn (coon net.Conn) { defer coon.Close() ipAdd := coon.RemoteAddr().String() fmt.Println(ipAdd, "连接成功" ) buf := make ([]byte , 1024 ) for { n, err := coon.Read(buf) if err != nil { fmt.Println(err) return } result := buf[:n] fmt.Printf("接收到数据来自[%s]==>[%d]:%s\n" , ipAdd, n, string (result)) if "exit" == string (result) { fmt.Println(ipAdd, "退出连接" ) return } coon.Write([]byte (strings.ToUpper(string (result)))) } } func main () { listenner, err := net.Listen("tcp" , "127.0.0.1:8000" ) if err != nil { log.Fatal(err) } defer listenner.Close() for { coon, err := listenner.Accept() if err != nil { log.Fatal(err) continue } go dealConn(coon) } }
客户端
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 31 32 33 34 35 36 37 package mainimport ( "fmt" "log" "net" ) func main () { coon, err := net.Dial("tcp" , "127.0.0.1:8000" ) if err != nil { log.Fatal(err) return } defer coon.Close() buf := make ([]byte , 1024 ) for { fmt.Printf("请输入发送的内容:" ) fmt.Scan(&buf) fmt.Printf("发送的内容:%s\n" , string (buf)) coon.Write(buf) n, err := coon.Read(buf) if err != nil { fmt.Println(err) return } result := buf[:n] fmt.Printf("接收到数据[%d]:%s\n" , n, string (result)) } }
HTTP编程 概述
HTTP报文浅析 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 31 32 33 34 35 36 package mainimport ( "fmt" "log" "net" ) func main () { listenner, err := net.Listen("tcp" , "127.0.0.1:8000" ) if err != nil { log.Fatal(err) } defer listenner.Close() conn, err := listenner.Accept() if err != nil { log.Println(err) return } defer conn.Close() ipAddr := conn.RemoteAddr().String() fmt.Println(ipAddr, "连接成功" ) buf := make ([]byte , 4096 ) n, err := conn.Read(buf) if err != nil { fmt.Println(err) return } result := buf[:n] fmt.Printf("接收到数据来自[%s]==>:\n%s\n" , ipAddr, string (result)) }
响应报文格式
http编程 客户端
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 31 32 33 34 35 36 package mainimport ( "fmt" "log" "net/http" ) func main () { resp, err := http.Get("http://127.0.0.1:8000/" ) if err != nil { log.Fatal(err) return } defer resp.Body.Close() fmt.Println("header=" , resp.Header) fmt.Printf("resp status %s\n statuscode %d\n" , resp.Status, resp.StatusCode) fmt.Printf("body type=%T\n" , resp.Body) buf := make ([]byte , 2048 ) var temp string for { n, err := resp.Body.Read(buf) if err != nil { fmt.Println(err) return } if n == 0 { fmt.Println("读取内容结束" ) break } temp += string (buf[:n]) } fmt.Println("buf=" , string (temp)) }
服务端
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 package mainimport ( "fmt" "net/http" ) func myHandler (w http.ResponseWriter, r *http.Request) { fmt.Println(r.RemoteAddr, "连接成功" ) fmt.Println("method=" , r.Method) fmt.Println("url=" , r.URL) fmt.Println("header=" , r.Header) fmt.Println("body=" , r.Body) w.Write([]byte ("hello go" )) } func main () { http.HandleFunc("/go" , myHandler) http.ListenAndServe("127.0.0.1:8000" , nil ) }