抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Go 语言结构

  • 包声明
  • 引入包
  • 函数
  • 变量
  • 语句&表达式
  • 注释

可以通过go run指令,直接运行.go文件。

可以通过go build指令,生成二进制文件。

Go中,{不能单独一行

Go基础语法

标识符

标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母(AZ和az)数字(0~9)、下划线_组成的序列,但是第一个字符必须是字母或下划线而不能是数字。 并且不能使用关键字命名。

以下是有效的标识符:

1
a_123    _temp   j 

以下是无效的标识符:

  • 1ab(以数字开头)
  • case(Go 语言的关键字)
  • a+b(运算符是不允许的)

关键字

字符串连接

用加号直接连接

1
2
3
4
5
package main
import "fmt"
func main(){
fmt.Println("Gryffinbit"+"awesome")
}

格式化字符串

Go 语言中使用 fmt.Sprintffmt.Printf 格式化字符串并赋值给新串:

  • Sprintf 根据格式化参数生成格式化的字符串并返回该字符串
  • Printf 根据格式化参数生成格式化的字符串并写入标准输出

Sprintf 实例

有一种“填入”的感觉,把已经有了的变量,填入到%d%s中去。但想要显示出返回结果的时候不能直接用,他不能自己去“显示结果”,需要借助print函数才可以打印出来

Printf实例

能够像Sprintf一样,返回字符串(填入的感觉),并且可以直接调用来打印出结果,显示出来。即可以写入输出

Go语言数据类型

Go语言变量

变量声明

  1. 指定变量类型,如果没有初始化,则变量默认为零值。

    1
    var v_name v_type
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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

    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 // error 是接口
  2. 根据值,自行判断变量类型

    1
    var v_name = value

  3. 不能用:=重复声明变量

    正确的使用:

    1
    v_name := value

    非正确的使用(重复声明):

    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出现的次数,不会受常量本身值的影响,而是直接计算。

image-20230712171326290

同时,调用iota进行计算之后,对于后续没有赋值的常量,会对iota进行累加,然后重复计算。

img

image-20230712172010477

算数运算符:

常规算数运算符,和关系运算符,以及逻辑运算符,位运算符。

赋值运算符:

go的赋值运算符比较多:

image-20230712172338114

GO条件语句:

If else语句:

和Python差不多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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语句不管写在什么地方,都是默认最后执行的。

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 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 块中的代码。

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 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:

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 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,顾名思义,就是一个通道,效果像是一个水管,可以通过<-将某个值传递到通道里。

image-20230712180255334

使用make函数,创建两个通道。

创建异步运行的函数:

image-20230712180329073

通过<-符号,将string值传递给通道c1。

image-20230712180358373

当通道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++ { // 常见的 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 一个意思。

死循环

通过直接写布尔式,达成死循环:

image-20230712180805480

具体布尔值格式:

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
}

image-20230712181211413

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"
// 忽略 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 里

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) {
//这里的 x,y 都是 int 型。
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] {
函数体
}

/*
func test(a,b,c int) int{
return 123123
}
/*

如果没有返回值就不写返回类型,注意形参也要写类型:

image-20230712182233307

如果会返回多个值:

image-20230712182258253

闭包

闭包是由函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境)。

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 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
}

递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

  1. 关键字 defer 用于注册延迟调用。
  2. 这些调用直到 return 前才被执行。因此,可以用来做资源清理。
  3. 多个defer语句,按先进后出的方式执行。
  4. defer语句中的变量,在defer声明时就决定了。

用途:

  1. 关闭文件句柄
  2. 锁资源释放
  3. 数据库连接释放

defer 定义的东西遵循先进后出

1
2
3
4
5
6
7
8
9
10
package main

import "fmt"

func main() {
var whatever [5]struct{}
for i := range whatever {
defer func() { fmt.Println(i) }()
}
}

输出顺序是4 3 2 1

异常处理

异常

panic:

  1. 内置函数

  2. 假如函数F中,书写了panic语句,会终止其后要执行的代码,在panic所在函数F内如果存在要执行的defer函数列表,按照defer的逆序执行

  3. 返回函数F的调用者G,在G中,调用函数F语句之后的代码不会执行,假如函数G中存在要执行的defer函数列表,按照defer的逆序执行

  4. 直到goroutine整个退出,并报告错误

recover:

  1. 内置函数
  2. 用来控制一个goroutine的panicking行为,捕获panic,从而影响应用的行为
  3. 一般的调用建议
    1. 在defer函数中,通过recover来终止一个goroutine的panicking过程,从而恢复正常代码的执行
    2. 可以获取通过panic传递的error
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

func main() {
test()
}

func test() {
defer func() {
if err := recover(); err != nil {
println(err.(string)) // 将 interface{} 转型为具体类型。
}
}()

panic("panic error!")
}

延迟调用中引发的错误,可被后续延迟调用捕获,但仅最后一个错误可被捕获。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "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 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 里的方法总是绑定对象,也就是对象.方法()这样调用

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 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() {}
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 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语言数组:

声明方法:

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}

不确定数组长度,使用...来进行指代。

image-20230712183441192

可以单独初始化某个元素:

image-20230712183515120

1初始化为2.0,3初始化为7.0

GO指针:

声明指针格式:

1
var var_name *var-type

同样还是使用&作为取地址符。

当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
结构体.成员名"

结构体指针

你可以定义指向结构体的指针类似于其他指针变量,格式如下:

1
var struct_pointer *Books

GO切片:

切片是对数组的一个连续片段的引用,所以切片是一个引用类型,这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集。

通过声明一个未指定大小的数组来定义切片:

1
var identifier []type

或是使用**make()**函数来创建切片:

1
2
3
4
5
var slice1 []type = make([]type, len)

也可以简写为

slice1 := make([]type, len)

也可以指定容量,其中capacity是可选参数。也就是指定切片最多能切多大。

1
make([]T, length, capacity)

这里len是数组的长度,并且也是切片的初始长度。

切片初始化:

1
s :=[] int {1,2,3 } 

[]标识切片类型。

切一个数组:

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计算的集合。

类型转换:

类型转换格式:

1
type_name(expression)

?,转换方式是反的,相当于调用了一个函数去处理。

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 {
/* variables */
}

/* 实现接口方法 */
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 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中,会统一调用所有的函数,根据参数来选择。用于调用的函数写法比较特殊:

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 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

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 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") // 打印完成信息
}

评论