Go基础语法 (一)

基础语法

main函数概览

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

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

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

main函数无参数 无返回值

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

go 复制代码
package main
  func main(){
    print("hello world")
  }

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

go 复制代码
// main.go 

package myapp <----

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

package myapp <----

import "fmt" 
func helperFunction() { 
   fmt.Println("Helper function") 
}
go 复制代码
go run main.go hello.go
go run .   //编译当前目录下的所有文件并运行
go build . 然后再执行build生成可运行文件 ./hello_go

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相关的的使用 源码中也会有注释

go 复制代码
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上

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

基本类型:bool:false true

css 复制代码
  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,然后在每新增一个常量声明时自动递增。

go 复制代码
package main

import "fmt"

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

func main() {
    fmt.Println(a, b, c)  // 输出:0 1 2
}
go 复制代码
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
}
go 复制代码
package main

import "fmt"

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

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

方法的声明与调用

基础

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

go 复制代码
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,"胖胖")

递归

小心栈溢出

go 复制代码
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
 }

函数式编程

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

go 复制代码
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
    }
}

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

go 复制代码
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

不定参数

在内部可以作为切片使用

go 复制代码
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 在函数结束前 关闭文件访问

go 复制代码
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的时候就定义了 作为闭包使用,执行的时候才确定值

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

defer 修改命名返回值

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

示例:defer 修改命名返回值

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

go 复制代码
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 将不能以这种方式影响返回值:

go 复制代码
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压入栈中

go 复制代码
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)
       }()
    }
}
相关推荐
研究司马懿1 小时前
【云原生】Gateway API高级功能
云原生·go·gateway·k8s·gateway api
梦想很大很大15 小时前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
lekami_兰20 小时前
MySQL 长事务:藏在业务里的性能 “隐形杀手”
数据库·mysql·go·长事务
却尘1 天前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
ん贤1 天前
一次批量删除引发的死锁,最终我选择不加锁
数据库·安全·go·死锁
mtngt112 天前
AI DDD重构实践
go
Grassto3 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto5 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室6 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题6 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo