Go 语言结构
可以通过go run
指令,直接运行.go文件。
可以通过go build
指令,生成二进制文件。
Go中,{
不能单独一行
Go基础语法 标识符 标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母(AZ和az)数字(0~9)、下划线_组成 的序列,但是第一个字符必须是字母或下划线 而不能是数字。 并且不能使用关键字命名。
以下是有效的标识符:
以下是无效的标识符:
1ab(以数字开头)
case(Go 语言的关键字)
a+b(运算符是不允许的)
关键字
字符串连接 用加号直接连接
1 2 3 4 5 package mainimport "fmt" func main () { fmt.Println("Gryffinbit" +"awesome" ) }
格式化字符串 Go 语言中使用 fmt.Sprintf
或 fmt.Printf
格式化字符串并赋值给新串:
Sprintf
根据格式化参数生成格式化的字符串并返回该字符串 。
Printf
根据格式化参数生成格式化的字符串并写入标准输出 。
Sprintf 实例
有一种“填入”的感觉,把已经有了的变量,填入到%d
、%s
中去。但想要显示出返回结果的时候不能直接用,他不能自己去“显示结果”,需要借助print函数才可以打印出来
Printf实例
能够像Sprintf一样,返回字符串(填入的感觉),并且可以直接调用来打印出结果,显示出来。即可以写入输出
Go语言数据类型
Go语言变量 变量声明
指定变量类型 ,如果没有初始化,则变量默认为零值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport "fmt" func main () { var a = "RUNOOB" fmt.Println(a) var b int fmt.Println(b) var c bool fmt.Println(c) }
以下类型为nil
1 2 3 4 5 6 var a *int var a []int var a map [string ] int var a chan int var a func (string ) int var a error
根据值,自行判断变量类型
不能用:=
重复声明变量
正确的使用:
非正确的使用(重复声明):
1 2 var v_name int v_name := 1
多变量声明 1. 使用指定变量的方式
1 2 var v_name1,v_name2,v_name3 type v_name1,v_name2,v_name3 = v1, v2,v3
2. 使用自动推断的方法
1 var v_name1,v_name2,v_name3 = v1,v2,v3
3. :=
声明变量
1 v_name1,v_name2,v_name2 := v1,v2,v3
4. 全局变量的声明
1 2 3 4 5 var ( v_name1 type v_name2 type )
空白标识符 空白标识符 _
也被用于抛弃值,如值 5 在_, b = 5, 7
中被抛弃。
_
实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。
Go语言常量 使用const关键字进行创建。
同样使用上述的两种声明类型,显式声明类型和隐式声明类型。
特殊常量: iota
可以认为式一个可以被编译器修改的常量。
iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。
第一个iota等于0,每当iota在新的一行被使用的时候,值会自动加一。
也就是说,iota实际上是在计算const出现的次数,不会受常量本身值的影响,而是直接计算。
同时,调用iota进行计算之后,对于后续没有赋值的常量,会对iota进行累加,然后重复计算。
算数运算符: 常规算数运算符,和关系运算符,以及逻辑运算符,位运算符。
赋值运算符: go的赋值运算符比较多:
GO条件语句: If else语句:
和Python差不多。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "fmt" func main () { var age int = 23 if age == 25 { fmt.Println("true" ) } else if age < 25 { fmt.Println("too small" ) } else { fmt.Println("too big" ) } }
但是可以不写:
号,需要花括号,直接写布尔判断语句。
switch语句:
类似于C语言,同时支持多条件匹配,default语句不管写在什么地方,都是默认最后执行的。
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 () { var x interface {} switch i := x.(type ) { case nil : fmt.Printf(" x 的类型 :%T" ,i) fallthrough case int : fmt.Printf("x 是 int 型" ) case float64 : fmt.Printf("x 是 float64 型" ) case func (int ) float64 : fmt.Printf("x 是 func(int) 型" ) case bool , string : fmt.Printf("x 是 bool 或 string 型" ) default : fmt.Printf("未知型" ) } switch { case 1 ,2 ,3 ,4 : default : } }
select语句:
select 是 Go 中的一个控制结构,类似于 switch 语句。
select 语句只能用于通道操作,每个 case 必须是一个通道操作,要么是发送要么是接收。
select 语句会监听所有指定的通道上的操作,一旦其中一个通道准备好就会执行相应的代码块。
如果多个通道都准备好,那么 select 语句会随机选择一个通道执行。如果所有通道都没有准备好,那么执行 default 块中的代码。
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 c1, c2, c3 chan int var i1, i2 int select { case i1 = <-c1: fmt.Printf("received " , i1, " from c1\n" ) case c2 <- i2: fmt.Printf("sent " , i2, " to c2\n" ) case i3, ok := (<-c3): if ok { fmt.Printf("received " , i3, " from c3\n" ) } else { fmt.Printf("c3 is closed\n" ) } default : fmt.Printf("no communication\n" ) } }
需要多了解一下channel类型:
eg:
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" "time" ) func main () { c1 := make (chan string ) c2 := make (chan string ) go func () { time.Sleep(1 * time.Second) c1 <- "one" }() go func () { time.Sleep(2 * time.Second) c2 <- "two" }() for i := 0 ; i < 2 ; i++ { select { case msg1 := <-c1: fmt.Println("received" , msg1) case msg2 := <-c2: fmt.Println("received" , msg2) } } }
channel,顾名思义,就是一个通道,效果像是一个水管,可以通过<-
将某个值传递到通道里。
使用make函数,创建两个通道。
创建异步运行的函数:
通过<-
符号,将string值传递给通道c1。
当通道c1接收到值的时候,就会被select检测到,然后把c1里面的值流出来,通过短声明赋值给msg1然后,输出。
如果没有任何消息,就直接执行default的子语句。
如果没有default,就直接卡住,直到可以执行为止。
GO循环语句: For
使用for语句和布尔表达式进行循环。没有while。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 s := "abc" for i, n := 0 , len (s); i < n; i++ { println (s[i]) } n := len (s) for n > 0 { println (s[n]) n-- } for { println (s) }
for {}|for (;;){}
和 while true 一个意思。
死循环 通过直接写布尔式,达成死循环:
具体布尔值格式:
1 2 3 for init;condition;post { }
init: 一般为赋值表达式,给控制变量赋初值; condition: 关系表达式或逻辑表达式,循环控制条件; post: 一般为赋值表达式,给控制变量增量或减量。 for语句执行过程如下: ① 先对表达式 init 赋初值; ② 判别赋值表达式 init 是否满足给定 condition 条件,若其值为真,满足循环条件,则执行循环体内语句,然后执行 post,进入第二次循环,再判别 condition;否则判断 condition 的值为假,不满足条件,就终止for循环,执行循环体外语句。
通过range格式的关键字,达成foreach()效果;
1 2 3 4 for key, value := range oldMap { newMap[key] = value }
range range 用于遍历数组或者字典。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func main () { s := "abc" for i := range s { println (s[i]) } for _, c := range s { println (c) } for range s { } m := map [string ]int {"a" : 1 , "b" : 2 } for k, v := range m { println (k, v) } }
goto、continue、break goto 和 C 一样,跳回到指定的 label 里
1 2 3 4 5 6 7 8 9 10 11 func main () { i := 0 loop: fmt.Println(i) i++ if i < 10 { goto loop } }
break 和 continue 和其他语言的一样
1 2 3 4 5 6 7 8 9 func main () { for i := 0 ; i < 10 ; i++ { if i%2 == 0 { continue } fmt.Println(i) } }
GO函数: 函数定义
函数声明包含一个函数名,参数列表, 返回值列表和函数体。如果函数没有返回值,则返回列表可以省略。函数从第一条语句开始执行,直到执行 return 语句或者执行函数的最后一条语句。
函数可以没有参数或接受多个参数。
注意类型在变量名之后 。
当两个或多个连续的函数命名参数是同一类型,则除了最后一个类型之外,其他都可以省略。
函数可以返回任意数量的返回值。
类型相同的相邻参数,参数类型可合并。 多返回值必须用括号。
1 2 3 4 5 func test (x, y int , s string ) (int , string ) { n := x + y return n, fmt.Sprintf(s, n) }
声明方式:
1 2 3 4 5 6 7 8 9 func function_name ( [parameter list] ) [return_types] { 函数体 }
如果没有返回值就不写返回类型,注意形参也要写类型:
如果会返回多个值:
闭包 闭包是由函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境)。
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 a () func () int { i := 0 b := func () int { i++ fmt.Println(i) return i } return b } func main () { c := a() c() c() c() a() }
递归 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func factorial (i int ) int { if i <= 1 { return 1 } return i * factorial(i-1 ) } func main () { var i int = 7 fmt.Printf("Factorial of %d is %d\n" , i, factorial(i)) }
输出结果就是 7 的阶乘
延时调用 defer
关键字 defer 用于注册延迟调用。
这些调用直到 return 前才被执行。因此,可以用来做资源清理。
多个defer语句,按先进后出的方式执行。
defer语句中的变量,在defer声明时就决定了。
用途:
关闭文件句柄
锁资源释放
数据库连接释放
defer 定义的东西遵循先进后出
1 2 3 4 5 6 7 8 9 10 package mainimport "fmt" func main () { var whatever [5 ]struct {} for i := range whatever { defer func () { fmt.Println(i) }() } }
输出顺序是4 3 2 1
异常处理 异常 panic:
内置函数
假如函数F中,书写了panic语句,会终止其后要执行的代码,在panic所在函数F内如果存在要执行的defer函数列表,按照defer的逆序执行
返回函数F的调用者G,在G中,调用函数F语句之后的代码不会执行,假如函数G中存在要执行的defer函数列表,按照defer的逆序执行
直到goroutine整个退出,并报告错误
recover:
内置函数
用来控制一个goroutine的panicking行为,捕获panic,从而影响应用的行为
一般的调用建议
在defer函数中,通过recover来终止一个goroutine的panicking过程,从而恢复正常代码的执行
可以获取通过panic传递的error
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainfunc main () { test() } func test () { defer func () { if err := recover (); err != nil { println (err.(string )) } }() panic ("panic error!" ) }
延迟调用中引发的错误,可被后续延迟调用捕获,但仅最后一个错误可被捕获。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "fmt" func test () { defer func () { fmt.Println(recover ()) }() defer func () { panic ("defer panic" ) }() panic ("test panic" ) } func main () { test() }
自定义error 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 package mainimport ( "fmt" "os" "time" ) type PathError struct { path string op string createTime string message string } func (p *PathError) Error() string { return fmt.Sprintf("path=%s \nop=%s \ncreateTime=%s \nmessage=%s" , p.path, p.op, p.createTime, p.message) } func Open (filename string ) error { file, err := os.Open(filename) if err != nil { return &PathError{ path: filename, op: "read" , message: err.Error(), createTime: fmt.Sprintf("%v" , time.Now()), } } defer file.Close() return nil } func main () { err := Open("test.txt" ) switch v := err.(type ) { case *PathError: fmt.Println("get path error," , v) default : } }
方法 Golang 里的方法总是绑定对象,也就是对象.方法()
这样调用
1 2 3 func (recevier type ) methodName(参数列表)(返回值列表){}
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 package maintype Test struct {}func (t Test) method0() {} func (t Test) method1(i int ) {} func (t Test) method2(x, y int ) {} func (t Test) method3() (i int ) { return } func (t Test) method4(x, y int ) (z int , err error ) { return } func (t *Test) method5() {} func (t *Test) method6(i int ) {} func (t *Test) method7(x, y int ) {} func (t *Test) method8() (i int ) { return } func (t *Test) method9(x, y int ) (z int , err error ) { return } func main () {}
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 package mainimport ( "fmt" ) func valueIntTest (a int ) int { return a + 10 } func pointerIntTest (a *int ) int { return *a + 10 } func structTestValue () { a := 2 fmt.Println("valueIntTest:" , valueIntTest(a)) b := 5 fmt.Println("pointerIntTest:" , pointerIntTest(&b)) } type PersonD struct { id int name string } func (p PersonD) valueShowName() { fmt.Println(p.name) } func (p *PersonD) pointShowName() { fmt.Println(p.name) } func structTestFunc () { personValue := PersonD{101 , "hello world" } personValue.valueShowName() personValue.pointShowName() personPointer := &PersonD{102 , "hello golang" } personPointer.valueShowName() personPointer.pointShowName() } func main () { structTestValue() structTestFunc() }
GO语言数组: 声明方法:
1 var variable_name [size] type
也就是:
1 var balance [10 ] float32
二维数组:
1 var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type
也就是C语言的二位数组声明方法,没什么变化。
数组初始化: 1 2 3 4 5 var balance = [5 ]float32 {1000.0 , 2.0 , 3.4 , 7.0 , 50.0 }balance := [5 ]float32 {1000.0 , 2.0 , 3.4 , 7.0 , 50.0 }
不确定数组长度,使用...
来进行指代。
可以单独初始化某个元素:
1初始化为2.0,3初始化为7.0
GO指针: 声明指针格式:
同样还是使用&作为取地址符。
当Go指针被定义之后,没有分配到任何变量的时候,值为nil,称为空指针。
GO结构体: 声明结构体方式:
1 2 3 4 5 6 type struct_variable_type struct { member definition member definition ... member definition }
声明结构体变量:
1 2 3 variable_name := structure_variable_type {value1, value2...valuen} 或 variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}
同时包含内部变量赋值,如果没有进行复制的字段,就是空值,或是0。
访问结构体内部对象,使用
结构体指针 你可以定义指向结构体的指针类似于其他指针变量,格式如下:
1 var struct_pointer *Books
GO切片: 切片是对数组的一个连续片段的引用,所以切片是一个引用类型,这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集。
通过声明一个未指定大小的数组来定义切片:
或是使用**make()**函数来创建切片:
1 2 3 4 5 var slice1 []type = make ([]type , len )也可以简写为 slice1 := make ([]type , len )
也可以指定容量,其中capacity是可选参数。也就是指定切片最多能切多大。
1 make([]T, length, capacity)
这里len是数组的长度,并且也是切片的初始长度。
切片初始化:
[]
标识切片类型。
切一个数组:
1 2 3 s := arr[startIndex:endIndex] //引用某个数组 s := arr[:]
len()和cap()函数: 切片是可以索引的,并且可以由len()方法获取长度。
切片提供了计算容量的方法,cap()可以测量切片长度最长可以得到多少
增加切片内容: 使用append()
函数
向切片中追加一个新元素,用于添加一个新元素。
1 2 3 4 5 6 7 8 9 10 11 12 /* 允许追加空切片 */ numbers = append(numbers, 0) printSlice(numbers) /* 向切片添加一个元素 */ numbers = append(numbers, 1) printSlice(numbers) /* 同时添加多个元素 */ numbers = append(numbers, 2,3,4) printSlice(numbers)
使用copy()
函数
拷贝切片,但是是从后拷贝到前:
1 2 3 /* 拷贝 numbers 的内容到 numbers1 */ copy(numbers1,numbers) printSlice(numbers1)
GO范围: 实际上就是Pythonh中的range函数。
GO语言Map: 一种无序的键值对集合,实际上应该和Java中的map是差不多的,一种基于Hash计算的集合。
类型转换: 类型转换格式:
?,转换方式是反的,相当于调用了一个函数去处理。
GO接口: Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
接口可以让我们将不同的类型绑定到一组公共的方法上,从而实现多态和灵活的设计。
Go 语言中的接口是隐式实现的,也就是说,如果一个类型实现了一个接口定义的所有方法,那么它就自动地实现了该接口。因此,我们可以通过将接口作为参数来实现对不同类型的调用,从而实现多态。
声明一个接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type interface_name interface { method_name1 [return_type] method_name2 [return_type] method_name3 [return_type] ... method_namen [return_type] } type struct_name struct { } func (struct_name_variable struct_name) method_name1() [return_type] { } ... func (struct_name_variable struct_name) method_namen() [return_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 31 32 33 34 package mainimport ( "fmt" ) type Phone interface { call() } type NokiaPhone struct {} func (nokiaPhone NokiaPhone) call() { fmt.Println("I am Nokia, I can call you!" ) } type IPhone struct {} func (iPhone IPhone) call() { fmt.Println("I am iPhone, I can call you!" ) } func main () { var phone Phone phone = new (NokiaPhone) phone.call() phone = new (IPhone) phone.call() }
逻辑:
创建一个interface,然后声明对应的变量。
然后传入对应的参数,在interface中,会统一调用所有的函数,根据参数来选择。用于调用的函数写法比较特殊:
1 2 3 func (variable_name) func_name return_type{}
用go写一个发包程序 UDP 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 package mainimport ( "fmt" "net" ) func main () { addr, err := net.ResolveUDPAddr("udp" , "127.0.0.1:8080" ) if err != nil { fmt.Println(err) return } conn, err := net.DialUDP("udp" , nil , addr) if err != nil { fmt.Println(err) return } defer conn.Close() data := []byte ("Hello, this is a UDP packet" ) n, err := conn.Write(data) if err != nil { fmt.Println(err) return } fmt.Printf("Sent %d bytes\n" , n) }
运行结果:
使用tcpdump检验是否收到包
TCP 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 package mainimport ( "fmt" "net" "sync" ) func main () { target := "127.0.0.1:8080" n := 100 var wg sync.WaitGroup for i := 0 ; i < n; i++ { wg.Add(1 ) go func () { defer wg.Done() conn, err := net.Dial("tcp" , target) if err != nil { fmt.Println(err) return } defer conn.Close() _, err = conn.Write([]byte ("Hello" )) if err != nil { fmt.Println(err) return } }() } wg.Wait() fmt.Println("Attack finished" ) }