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.Sprintffmt.Printf 格式化字符串并赋值给新串:

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

Sprintf 实例

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

Printf实例

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

Go语言数据类型

Go语言变量

变量声明

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

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

    var v_name = value
    

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

    正确的使用:

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

image-20230712171326290

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

img

image-20230712172010477

算数运算符:

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

赋值运算符:

go的赋值运算符比较多:

image-20230712172338114

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

image-20230712180255334

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

创建异步运行的函数:

image-20230712180329073

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

image-20230712180358373

当通道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 一个意思。

死循环

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

image-20230712180805480

具体布尔值格式:

 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
 }
 

image-20230712181211413

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

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

image-20230712182233307

如果会返回多个值:

image-20230712182258253

闭包

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

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

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

用途:

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

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:

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

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

image-20230712183441192

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

image-20230712183515120

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

评论