likes
comments
collection
share

Go基础语法 (一)

作者站长头像
站长
· 阅读数 5

基础语法

一个目录(dir)一个包(package),所有文件共享作用域

main函数概览

每个文件都需要有一个package声明

函数关键字func + 方法名 + 参数列表 + 返回值 + 方法体组成

go不要求每一句后边要加分号

main函数无参数 无返回值

main函数要在main包里 go run main.go可以运行

package main
  func main(){
    print("hello world")
  }

如果main函数里引用了同一个包(即package的名称一样)的其他方法 那么要加上对应的文件来运行

也可以通过ide来设置,执行时的目录

go run main.go hello.go
go run .   //编译当前目录下的所有文件并运行
go build . 然后再执行build生成可运行文件 ./hello_go
// main.go 

package myapp <----

import "fmt" 
func main() {
  fmt.Println("Main function") 
  helperFunction()// 调用 helper.go 中的函数
} 
// helper.go 

package myapp <----

import "fmt" 
func helperFunction() { 
   fmt.Println("Helper function") 
}

package声明

每一个文件都必须有一个package声明 同一个目录下,go文件package声明必须一致 统一目录下,go文件和test.go文件的package的声明可以不一致 package名字可以和目录名字不一样

基本类型string

基本类型:int整型数 unit无符号整型数 float浮点数

四则运算: go语言只有相同类型才能够进行四则运算,go不支持自动类型转换 可强制类型转换

复杂运算:math 包 包括极值也在math包 IDE会提示

string: 双引号包裹 "hello go" 换行等操作需要转义 || 反引号 hello go-->反引号可以换行

string拼接:使用+ 但是go不支持与其他类型拼接

len:获取string字节长度--->非字符个数---->使用utf8.RuneCountInString

strings包可以看到string相关的的使用 源码中也会有注释

package main

func main() {
    var a uint = 3
    var b int = 3
    var c = uint(b)
    println(a + c)
   
    //此函数返回 float64 类型的值
    var d = math.Abs(-12)
    println(d)
    
    var name = "zzh"
    var newName = name + strconv.Itoa(345)
    println(newName) 
  
    println(len(newName)) //15
    println(utf8.RuneCountInString(newName)) //11

}

基本类型:byte === uint8 uint8 的别名,它用于表示单个字节的值 也会用它来变大ASCII 字符 对应操作 在byte上

      ·rune === uint32·
      
      byte[] 和 string 之间可以相互转换  <--这个在web3 solidity中 就是 string就是byte数组-->
      

基本类型:bool:false true

  bool运算  a && b
            a || b
            !a
  组合取反  !(a && b)    !(a || b)
              

变量与常量的声明

  1. var name type = value 简短的声明方法 a := 12 局部变量使用 依赖类型推断 指定类型还是用var 包(全局)变量
    • 定义:包级变量是在函数之外定义的变量,它们在整个包中都是可见的。
    • 作用域:这些变量可以在整个包的任何地方被访问。
    • 生命周期:包级变量的生命周期与程序的运行期相同。 局部变量
    • 定义:局部变量是在函数内部定义的变量。
    • 作用域:局部变量只能在定义它们的函数内部被访问。
    • 生命周期:局部变量的生命周期仅限于函数的执行期。 块
    • 定义:块级变量是在诸如 if 语句、for 循环等控制结构内部定义的变量。
    • 作用域:这些变量仅在它们所在的特定代码块中可见。
    • 生命周期:块级变量的生命周期从声明开始,到控制结构块的结束。
    使用 var() 块可以让您在一个地方声明多个变量,这有助于保持代码的整洁和组织。变量的作用域(包级别 或函数级别)取决于 var() 块声明的位置。这种方式没有改变 Go 中的作用域规则,只是提供了一种更简洁的语 法来声明多个变量。
  2. 驼峰命名
  3. 首字母大仙控制可访问性,大写的包外可以访问
  4. 支持类型推断:整数默认int 浮点数默认float64
  5. 局部变量声明不用会报错
  6. 也可以只声明变量不赋值 变量的值就是默认值 易错点:同作用域下变量只能被声明一次 常量声明就是把var变成const

iota用法 控制变量初始化 数学运算 位移操作 使用_可以跳过值

主动插入值 会打断

iota 是一个特殊的常量,用于在 const 声明中生成一系列相关的值,通常是递增的整数。每当遇到一个新的 const 关键字时,iota 就会重置为 0,然后在每新增一个常量声明时自动递增。

package main

import "fmt"

const (
    a = iota  // 0
    b         // 1
    c         // 2
)

func main() {
    fmt.Println(a, b, c)  // 输出:0 1 2
}
package main

import "fmt"

const (
    flag1 = 1 << iota  // 1 << 0,即 1
    flag2              // 1 << 1,即 2
    flag3              // 1 << 2,即 4
)

func main() {
    fmt.Println(flag1, flag2, flag3)  // 输出:1 2 4
}
package main

import "fmt"

const (
    _  = iota  // 跳过 0
    a          // 1
    b          // 2
    c          // 3
)

func main() {
    fmt.Println(a, b, c)  // 输出:1 2 3
}

方法的声明与调用

基础

关键字 func 方法名首字符是否大写决定作用域

a := 1
func test(a)int{
  retrun a
}
多个返回值,分割
func test2(a,b,c int,d string)(string,error){
  retrun "Hi",nil
}
//返回值要么都有名字 要么都没有名字
func test3(a,b,c int,d string)(str string,err error){
  str := "胖胖"
  err := nil
  retrun 
}
func test4(a,b,c int,d string)(name string,err error){
  name := "胖胖"
  err := nil
  retrun "newName",err
}
test(a)
name,err := test3(1,2,3,"胖胖")
name,_ = test3(1,2,3,"胖胖")
newName,_ := test3(1,2,3,"胖胖")

递归

小心栈溢出

package main
import "fmt" 
// factorial 函数递归地计算阶乘 
func factorial(n int) int { 
   if n <= 1 { 
   return 1 // 基础情况 
   } 
  return n * factorial(n-1) // 递归调用 
}
func main() { 
fmt.Println(factorial(5)) // 输出: 120
 }

函数式编程

函数是一等公民,什么都能干 可以赋值给变量 可以作为参数 可以在函数内部声明函数 可以作为返回值

package main

import "fmt"

// 定义一个函数类型
type binOp func(int, int) int

// add 是一个可以被赋值给 binOp 类型变量的函数
func add(a, b int) int {
    return a + b
}

// multiply 是另一个可以被赋值给 binOp 类型变量的函数
func multiply(a, b int) int {
    return a * b
}

// operate 接收一个函数作为参数
func operate(a, b int, op binOp) int {
    return op(a, b)
}

// chooseOperation 返回一个函数作为返回值
func chooseOperation(op string) binOp {
    if op == "add" {
        return add
    } else if op == "multiply" {
        return multiply
    } else {
        return nil
    }
}

func main() {
    // 将函数赋值给变量
    var op binOp = add
    fmt.Println("Result of addition:", op(3, 4))  // 输出: 7

    // 将函数作为参数传递
    result := operate(5, 6, multiply)
    fmt.Println("Result of multiplication:", result)  // 输出: 30

    // 从函数中获取一个函数作为返回值,并使用它
    selectedOp := chooseOperation("multiply")
    if selectedOp != nil {
        fmt.Println("Result of selected operation:", selectedOp(3, 3))  // 输出: 9
    }
}

返回一个接收参数并有返回值的匿名函数

package main

import "fmt"

// returnFunc 返回一个接受两个 int 类型参数并返回一个 int 类型结果的匿名函数
func returnFunc() func(a int, b int) int {
    return func(a int, b int) int {
        return a + b
    }
}

func main() {
    sumFunc := returnFunc()
    result := sumFunc(5, 7)
    fmt.Println(result) // 输出: 12
}

闭包

如果是按照js来说的话,闭包 就是函数返回函数 而 内部函数又引用外层的变量,那被返回的函数就会背上一个严格私密的背包,背包里就是他所引用的外部的变量,不允许别人访问,所以也不会被垃圾收集。 函数走到哪就背到哪 函数死了 包还在(: 简单来说 不管函数在出生的时候,就和其所引用的所有变量保持长久的联系,永远不会丢失。因为函数是一等公民,所有高阶函数。被返回的函数引用着外部变量,因为这个长久的联系导致外部函数执行完毕后,被返回函数所引用的变量不会被清除 并保持长久的联系 严格私密 所以叫 close backpack || closure

不定参数

在内部可以作为切片使用

package main

import "fmt"

// sum 接受任意数量的 int 类型参数,并返回它们的总和
func sum(nums ...int) int {
    total := 0
    for _, num := range nums {
        total += num
    }
    return total
}

func main() {
    fmt.Println(sum(1, 2))         // 输出: 3
    fmt.Println(sum(1, 2, 3, 4))   // 输出:

defer

返回的前调用的函数

先定义的后执行

使用defer 在函数结束前 关闭文件访问

package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.Open("example.txt")
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }
    defer file.Close()  // 确保在函数结束时关闭文件

    // 执行一些文件操作
    // ...

    fmt.Println("文件成功打开,并在函数结束时关闭")
}

derfer与闭包

确定值的原则:作为参数传入的:定义defer的时候就定义了 作为闭包使用,执行的时候才确定值

fun fn1(){
i := 0
defer (){
   println(i)//闭包  println函数引用外部变量
}()
defer fn2(num int){
   print(num)
}(i)  //参数传入
}

defer 修改命名返回值

在 Go 语言中,defer 语句可以在函数返回之前执行一些代码。然而,defer 对函数返回值的影响有一些微妙之处。具体来说,如果一个函数返回一个命名的返回值,并且在该函数中使用 defer 语句修改了这个返回值,那么修改后的值将被返回。这是因为命名的返回值在函数开始时就被声明了,而 defer 语句在函数结束时才执行,因此它可以操作这些返回值。

示例:defer 修改命名返回值

下面是一个使用 defer 修改命名返回值的示例:

goCopy code
package main

import "fmt"

func test() (result int) {
    defer func() {
        result *= 2 // 修改命名返回值
    }()

    return 5 // 返回值先被设置为 5
}

func main() {
    fmt.Println(test()) // 输出: 10
}

在这个例子中:

  • 函数 test 定义了一个命名返回值 result
  • 在返回 5 之前,defer 语句中的匿名函数被调用,它将 result 的值乘以 2。
  • 因此,最终返回的结果是 10 而不是 5。

示例:defer 和未命名返回值

如果函数返回的是未命名的返回值,defer 将不能以这种方式影响返回值:

goCopy code
package main

import "fmt"

func test() int {
    var result int
    defer func() {
        result *= 2 // 这不会影响返回值
    }()

    return 5 // 直接返回 5
}

func main() {
    fmt.Println(test()) // 输出: 5
}

在这个例子中:

  • result 只是函数内部的一个局部变量,并非返回值。
  • 即使 defer 语句修改了 result 的值,也不会影响函数实际返回的值,函数仍然返回 5。

总结

defer 语句在处理命名返回值时能够影响函数的最终返回值,这是因为它在函数结束时执行,可以操作在函数返回之前设置的返回值。在使用 defer 修改返回值时需要特别小心,以确保代码的行为符合预期。这种特性在某些特定的场景下很有用,但也可能导致不直观的结果,因此需要谨慎使用。

测试

关键点 defer的执行顺序 与 闭包 for循环会形成10个defer压入栈中

package main

func main() {
    test3()
}
func test() {
    for i := 0; i < 10; i++ {
       defer func() {
          println(i)
       }()
    }
}
func test2() {
    for i := 0; i < 10; i++ {
       defer func(num int) {
          println(num)
       }(i)
    }
}
func test3() {
    for i := 0; i < 10; i++ {
       j := i
       defer func() {
          println(j)
       }()
    }
}