Go 语言结构
- 包声明
- 引入包
- 函数
- 变量
- 语句&表达式
- 注释
可以通过go run
指令,直接运行.go文件。
可以通过go build
指令,生成二进制文件。
Go中,{
不能单独一行
Go基础语法
标识符
标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母(AZ和az)数字(0~9)、下划线_组成的序列,但是第一个字符必须是字母或下划线而不能是数字。 并且不能使用关键字命名。
以下是有效的标识符:
a_123 _temp j
以下是无效的标识符:
- 1ab(以数字开头)
- case(Go 语言的关键字)
- a+b(运算符是不允许的)
关键字
字符串连接
用加号直接连接
package main
import "fmt"
func main(){
fmt.Println("Gryffinbit"+"awesome")
}
格式化字符串
Go 语言中使用 fmt.Sprintf
或 fmt.Printf
格式化字符串并赋值给新串:
Sprintf
根据格式化参数生成格式化的字符串并返回该字符串。Printf
根据格式化参数生成格式化的字符串并写入标准输出。
Sprintf 实例
有一种“填入”的感觉,把已经有了的变量,填入到
%d
、%s
中去。但想要显示出返回结果的时候不能直接用,他不能自己去“显示结果”,需要借助print函数才可以打印出来
Printf实例
能够像Sprintf一样,返回字符串(填入的感觉),并且可以直接调用来打印出结果,显示出来。即可以写入输出
Go语言数据类型
Go语言变量
变量声明
指定变量类型,如果没有初始化,则变量默认为零值。
var v_name v_type
package main import "fmt" func main() { // 声明一个变量并初始化 var a = "RUNOOB" fmt.Println(a) // 没有初始化就为零值 var b int fmt.Println(b) // bool 零值为 false var c bool fmt.Println(c) }
以下类型为nil
var a *int var a []int var a map[string] int var a chan int var a func(string) int var a error // error 是接口
根据值,自行判断变量类型
var v_name = value
不能用
:=
重复声明变量正确的使用:
v_name := value
非正确的使用(重复声明):
var v_name int v_name := 1
多变量声明
1. 使用指定变量的方式
var v_name1,v_name2,v_name3 type
v_name1,v_name2,v_name3 = v1, v2,v3
2. 使用自动推断的方法
var v_name1,v_name2,v_name3 = v1,v2,v3
3. :=
声明变量
v_name1,v_name2,v_name2 := v1,v2,v3
4. 全局变量的声明
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差不多。
package main
import "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语句不管写在什么地方,都是默认最后执行的。
package main
import "fmt"
func main() {
var x interface{}
switch i := x.(type) {
case nil:
fmt.Printf(" x 的类型 :%T",i)
fallthrough //使用fallthrough语句,会强制执行下一条case,不会判断是否可以执行。
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 块中的代码。
package main
import "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): // same as: 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")
}
}
//输出:no communication
需要多了解一下channel类型:
eg:
package main
import (
"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。
s := "abc"
for i, n := 0, len(s); i < n; i++ { // 常见的 for 循环,支持初始化语句。
println(s[i])
}
n := len(s)
for n > 0 { // 替代 while (n > 0) {}
println(s[n]) // 替代 for (; n > 0;) {}
n--
}
for { // 替代 while (true) {}
println(s) // 替代 for (;;) {}
}
for {}|for (;;){}
和 while true 一个意思。
死循环
通过直接写布尔式,达成死循环:
具体布尔值格式:
for init;condition;post {
}
init: 一般为赋值表达式,给控制变量赋初值;
condition: 关系表达式或逻辑表达式,循环控制条件;
post: 一般为赋值表达式,给控制变量增量或减量。
for语句执行过程如下:
① 先对表达式 init 赋初值;
② 判别赋值表达式 init 是否满足给定 condition 条件,若其值为真,满足循环条件,则执行循环体内语句,然后执行 post,进入第二次循环,再判别 condition;否则判断 condition 的值为假,不满足条件,就终止for循环,执行循环体外语句。
通过range格式的关键字,达成foreach()效果;
for key, value := range oldMap {
newMap[key] = value
}
range
range 用于遍历数组或者字典。
func main() {
s := "abc"
// 忽略 2nd value,支持 string/array/slice/map。
for i := range s {
println(s[i])
}
// 忽略 index。
for _, c := range s {
println(c)
}
// 忽略全部返回值,仅迭代。
for range s {
}
m := map[string]int{"a": 1, "b": 2}
// 返回 (key, value)。
for k, v := range m {
println(k, v)
}
}
goto、continue、break
goto 和 C 一样,跳回到指定的 label 里
func main() {
i := 0
loop:
fmt.Println(i)
i++
if i < 10 {
goto loop
}
}
break 和 continue 和其他语言的一样
func main() {
for i := 0; i < 10; i++ {
if i%2 == 0 {
continue
}
fmt.Println(i)
}
}
GO函数:
函数定义
函数声明包含一个函数名,参数列表, 返回值列表和函数体。如果函数没有返回值,则返回列表可以省略。函数从第一条语句开始执行,直到执行 return 语句或者执行函数的最后一条语句。
- 函数可以没有参数或接受多个参数。
- 注意类型在变量名之后 。
- 当两个或多个连续的函数命名参数是同一类型,则除了最后一个类型之外,其他都可以省略。
- 函数可以返回任意数量的返回值。
类型相同的相邻参数,参数类型可合并。 多返回值必须用括号。
func test(x, y int, s string) (int, string) {
//这里的 x,y 都是 int 型。
n := x + y
return n, fmt.Sprintf(s, n)
}
声明方式:
func function_name( [parameter list] ) [return_types] {
函数体
}
/*
func test(a,b,c int) int{
return 123123
}
/*
如果没有返回值就不写返回类型,注意形参也要写类型:
如果会返回多个值:
闭包
闭包是由函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境)。
package main
import (
"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() //不会输出i
}
递归
package main
import "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 定义的东西遵循先进后出
package main
import "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
package main
func main() {
test()
}
func test() {
defer func() {
if err := recover(); err != nil {
println(err.(string)) // 将 interface{} 转型为具体类型。
}
}()
panic("panic error!")
}
延迟调用中引发的错误,可被后续延迟调用捕获,但仅最后一个错误可被捕获。
package main
import "fmt"
func test() {
defer func() {
fmt.Println(recover())
}()
defer func() {
panic("defer panic")
}()
panic("test panic")
}
func main() {
test()
}
自定义error
package main
import (
"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 里的方法总是绑定对象,也就是对象.方法()
这样调用
func (recevier type) methodName(参数列表)(返回值列表){}
//参数和返回值可以省略
package main
type 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() {}
package main
//普通函数与方法的区别(在接收者分别为值类型和指针类型的时候)
import (
"fmt"
)
//1.普通函数
//接收值类型参数的函数
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))
//函数的参数为值类型,则不能直接将指针作为参数传递
//fmt.Println("valueIntTest:", valueIntTest(&a))
//compile error: cannot use &a (type *int) as type int in function argument
b := 5
fmt.Println("pointerIntTest:", pointerIntTest(&b))
//同样,当函数的参数为指针类型时,也不能直接将值类型作为参数传递
//fmt.Println("pointerIntTest:", pointerIntTest(&b))
//compile error:cannot use b (type int) as type *int in function argument
}
//2.方法
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语言数组:
声明方法:
var variable_name [size] type
也就是:
var balance [10] float32
二维数组:
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type
也就是C语言的二位数组声明方法,没什么变化。
数组初始化:
//第一种
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指针:
声明指针格式:
var var_name *var-type
同样还是使用&作为取地址符。
当Go指针被定义之后,没有分配到任何变量的时候,值为nil,称为空指针。
GO结构体:
声明结构体方式:
type struct_variable_type struct {
member definition
member definition
...
member definition
}
声明结构体变量:
variable_name := structure_variable_type {value1, value2...valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}
同时包含内部变量赋值,如果没有进行复制的字段,就是空值,或是0。
访问结构体内部对象,使用
结构体.成员名"
结构体指针
你可以定义指向结构体的指针类似于其他指针变量,格式如下:
var struct_pointer *Books
GO切片:
切片是对数组的一个连续片段的引用,所以切片是一个引用类型,这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集。
通过声明一个未指定大小的数组来定义切片:
var identifier []type
或是使用**make()**函数来创建切片:
var slice1 []type = make([]type, len)
也可以简写为
slice1 := make([]type, len)
也可以指定容量,其中capacity是可选参数。也就是指定切片最多能切多大。
make([]T, length, capacity)
这里len是数组的长度,并且也是切片的初始长度。
切片初始化:
s :=[] int {1,2,3 }
[]
标识切片类型。
切一个数组:
s := arr[startIndex:endIndex]
//引用某个数组
s := arr[:]
len()和cap()函数:
切片是可以索引的,并且可以由len()方法获取长度。
切片提供了计算容量的方法,cap()可以测量切片长度最长可以得到多少
增加切片内容:
使用append()
函数
向切片中追加一个新元素,用于添加一个新元素。
/* 允许追加空切片 */
numbers = append(numbers, 0)
printSlice(numbers)
/* 向切片添加一个元素 */
numbers = append(numbers, 1)
printSlice(numbers)
/* 同时添加多个元素 */
numbers = append(numbers, 2,3,4)
printSlice(numbers)
使用copy()
函数
拷贝切片,但是是从后拷贝到前:
/* 拷贝 numbers 的内容到 numbers1 */
copy(numbers1,numbers)
printSlice(numbers1)
GO范围:
实际上就是Pythonh中的range函数。
GO语言Map:
一种无序的键值对集合,实际上应该和Java中的map是差不多的,一种基于Hash计算的集合。
类型转换:
类型转换格式:
type_name(expression)
?,转换方式是反的,相当于调用了一个函数去处理。
GO接口:
Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
接口可以让我们将不同的类型绑定到一组公共的方法上,从而实现多态和灵活的设计。
Go 语言中的接口是隐式实现的,也就是说,如果一个类型实现了一个接口定义的所有方法,那么它就自动地实现了该接口。因此,我们可以通过将接口作为参数来实现对不同类型的调用,从而实现多态。
声明一个接口:
/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
/* 定义结构体 */
type struct_name struct {
/* variables */
}
/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法实现*/
}
实例:
package main
import (
"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中,会统一调用所有的函数,根据参数来选择。用于调用的函数写法比较特殊:
func (variable_name) func_name return_type{ }
用go写一个发包程序
UDP
package main
import (
"fmt"
"net"
)
func main() {
// 创建一个 UDP 地址
addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:8080")
if err != nil {
fmt.Println(err)
return
}
// 创建一个 UDP 连接
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
package main
import (
"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) // 创建TCP连接
if err != nil {
fmt.Println(err) // 处理错误
return
}
defer conn.Close() // 延迟关闭连接
_, err = conn.Write([]byte("Hello")) // 发送数据包
if err != nil {
fmt.Println(err) // 处理错误
return
}
}()
}
wg.Wait() // 等待所有goroutine完成
fmt.Println("Attack finished") // 打印完成信息
}