这才是Go语言传参的正确打开方式

简介: 这才是Go语言传参的正确打开方式

/ Go 语言函数参数传递效果测试详解 /

在 Go 语言中,理解函数参数的传递机制很重要。参数传递对程序的行为有直接的影响。

本文将详细探讨 Go 语言中参数传递的各种效果,主要内容包括:

  1. 传值效果
  2. 指针传递
  3. 结构体传递
  4. map 传递
  5. channel 传递
  6. 切片传递
  7. 错误传递
  8. 传递效果示例
  9. 传递方式选择

通过学习本文,你将全面理解 Go 语言中参数的传递规则,包括值类型、引用类型、指针等情况下的传递效果。

1

 

一、传值效果

Go 语言中向函数传递参数时采用的值传递(传值调用),实际上传递的是副本。

func foo(i int) {
  i++
}
func main() {
  i := 1
  foo(i)
  fmt.Println(i) // 1
}

在 foo()中修改参数 i 的值,不会影响到主调函数 main()中 i 的值。

这是因为 foo()中接收到的是 i 的一份副本,函数内部对副本的修改不会反映到原始变量上。

1.1

 

1. 值类型传值

基本数据类型(int、float、string 等)都是值类型,按值传递。

func foo(i int) {
  i = 20 // 改变副本值
}
func main() {
  i := 10
  foo(i)
  fmt.Println(i) // 10
}

foo()内部重新赋值,不影响外部 i 的值。

1.2

 

2. 引用类型传值

数组、slice、map、channel 等都是引用类型,但传递给函数时同样是按值传递,传递的是类型的一个副本。

func foo(s [2]int) {
  s[0] = 20 // 修改副本数据
}
func main() {
  s := [2]int{10}
  foo(s)
  fmt.Println(s[0]) // 10
}

foo()内修改副本,不影响外部原 slice。但如果直接重新赋值,外部则会受影响:

func foo(s []int) {
  s = []int{20, 30} // 重新赋值
} 
func main() {
  s := []int{10}
  foo(s)
  fmt.Println(s[0]) // 10 
}

2

 

二、指针传递

要实现修改外部变量的值,可以通过指针传递。

func foo(i *int) {
  *i = 20
}
func main() {
  i := 10
  foo(&i) 
  fmt.Println(i) // 20
}

指针 i 存储了 i 的内存地址,在 foo()内部通过*i 可以修改主调函数中 i 的原始值。

2.1

 

1. 值类型指针

对基本数据类型,可以传指针修改值:

func foo(i *int) {
  *i = 20
}
func main() {
  i := 10
  foo(&i)
  fmt.Println(i) // 20  
}

2.2

 

2. 引用类型指针

对于 slice、map 等引用类型,直接传递指针,就可以在函数内修改外部变量:

func foo(s *[]int) {
  (*s)[0] = 20 
}
func main() {
  s := []int{10}
  foo(&s)
  fmt.Println(s[0]) // 20
} 

     

    三、结构体传递

    结构体传参的情况类似,传递副本,可以通过指针修改外部变量。

    3.1

     

    1. 结构体传值

    type user struct {
      name string
      age  int
    }
    func foo(u user) {
      u.age = 20
    }
    func main() {
      u := user{"John", 30}
      foo(u)
      fmt.Println(u.age) // 30
    }

    foo()内修改 AGE,不影响外部 u 变量。

    3.2

     

    2. 结构体指针

    func foo(u *user) {
      u.age = 20 
    }
    func main() {
      u := user{"John", 30}  
      foo(&u)
      fmt.Println(u.age) // 20
    }

    传指针可以修改外部结构体成员。

    4

     

    四、map 传递

    map 类型的传递也类似。

    4.1

     

    1. map 传值

    传递的是 map 的副本,无法修改外部变量:

    func foo(m map[string]int) {
      m["age"] = 20
    }
    func main() {
      m := map[string]int{"age": 30}
      foo(m)
      fmt.Println(m["age"]) // 30
    }

    4.2

     

    2. map 指针

    传 map 指针可以修改外部变量:

    func foo(m *map[string]int) {
      (*m)["age"] = 20
    }
    func main() {
      m := map[string]int{"age": 30}
      foo(&m)
      fmt.Println(m["age"]) // 20  
    }

    5

     

    五、channel 传递

    channel 的处理也类似,传递副本。

    5.1

     

    1. channel 传值

    func foo(c chan int) {
      c <- 20 
    }
    func main() {
      c := make(chan int)
      foo(c)
      <-c // 死锁
    } 

    传值无法在两端通信。

    5.2

     

    2. channel 指针

    func foo(c *chan int) {
      *c <- 20
    } 
    func main() {
      c := make(chan int)
      foo(&c)
      fmt.Println(<-c) // 20
    }

    通过指针可以修改外部 channel。

    6

     

    六、切片传递

    切片传参需要特殊考虑,既可以传值也可以传指针。

    6.1

     

    1. 切片传值

    可以直接传递切片:

    func foo(s []int) {
      s[0] = 20
    }
    func main() {
      s := make([]int,10)
      foo(s)
      fmt.Println(s[0]) // 20
    }

    这与数组不同,是因为切片包含指向底层数组的指针,函数内部可以跟踪修改数组。

    6.2

     

    2. 切片指针

    传递切片指针也可以:

    func foo(s *[]int) {
      (*s)[0] = 20
    }
    func main() {
      s := []int{10}
      foo(&s)
      fmt.Println(s[0]) // 20
    } 

    但通常情况下传切片更常用。

    7

     

    七、错误传递

    Go 语言中错误处理通过错误接口实现,传递错误时要注意值或指针。

    7.1

     

    1. 错误值传递

    错误作为值传递,函数间无法共享同一个错误变量:

    func readFile(path string) (err error) {
      f, err := os.Open(path)
      // ...
      return
    }
    func main() {
      err := readFile(path) 
      if err != nil {
        // 无法修改函数内部的错误变量
      } 
    }
    

    这样导致无法修改内部错误。

    7.2

     

    2. 指针传递

    应使用指针传递错误:

    func readFile(path string) (err *error) {
      // 通过*err修改
    } 
    func main() {
      var err error
      readFile(path, &err)
      if err != nil {
        // 可以检测到错误
      }
    } 

    修改内部*err 可以将错误状态传递出来。

    8

     

    八、传递效果示例

    综合以上几点,下面我们看一些参数传递的效果示例:

    type user struct {
      name string
      age int
    }
    func foo(u user, s []int, m map[string]int, p *int) {
      u.name = "Jack" // 无效
      s[0] = 20       // 有效
      m["count"] = 10 // 无效
      *p = 20         // 有效
    }
    func main() {
      u := user{"John", 30}
      s := []int{10} 
      m := map[string]int{"count": 1}
      p := 10
      foo(u, s, m, &p)
      fmt.Println(u.name, s[0], m["count"], p)
      // John 20 1 20
    }

    用户结构体、map 传值无效,slice 和指针传递有效。

    9

     

    九、传递方式选择

    选择传值和传指针需要根据具体场景。主要考虑因素:

    • 函数需不需要修改外部变量
    • 数据大小,传指针节省内存
    • 传递对象是否为 nil 指针

    简单规则:

    • 基础类型选择传值
    • 大型结构体或接口传指针
    • 需要修改外部变量时使用指针

    合理使用传值和传指针可以提升程序效率。

    10

     

    十、总结

    Go 语言中参数传递遵循传值的方式,除非使用指针,否则无法修改外部变量。

    主要注意值类型和引用类型的具体传递效果。错误处理需要传指针。

    根据情况选择传值或传指针可以获得最佳性能。

    掌握 Go 的参数传递机制,可以编写出清晰、高效的函数代码。


    目录
    相关文章
    |
    2天前
    |
    安全 Java Go
    探索Go语言在高并发环境中的优势
    在当今的技术环境中,高并发处理能力成为评估编程语言性能的关键因素之一。Go语言(Golang),作为Google开发的一种编程语言,以其独特的并发处理模型和高效的性能赢得了广泛关注。本文将深入探讨Go语言在高并发环境中的优势,尤其是其goroutine和channel机制如何简化并发编程,提升系统的响应速度和稳定性。通过具体的案例分析和性能对比,本文揭示了Go语言在实际应用中的高效性,并为开发者在选择合适技术栈时提供参考。
    |
    6天前
    |
    运维 Kubernetes Go
    "解锁K8s二开新姿势!client-go:你不可不知的Go语言神器,让Kubernetes集群管理如虎添翼,秒变运维大神!"
    【8月更文挑战第14天】随着云原生技术的发展,Kubernetes (K8s) 成为容器编排的首选。client-go作为K8s的官方Go语言客户端库,通过封装RESTful API,使开发者能便捷地管理集群资源,如Pods和服务。本文介绍client-go基本概念、使用方法及自定义操作。涵盖ClientSet、DynamicClient等客户端实现,以及lister、informer等组件,通过示例展示如何列出集群中的所有Pods。client-go的强大功能助力高效开发和运维。
    27 1
    |
    6天前
    |
    SQL 关系型数据库 MySQL
    Go语言中使用 sqlx 来操作 MySQL
    Go语言因其高效的性能和简洁的语法而受到开发者们的欢迎。在开发过程中,数据库操作不可或缺。虽然Go的标准库提供了`database/sql`包支持数据库操作,但使用起来稍显复杂。为此,`sqlx`应运而生,作为`database/sql`的扩展库,它简化了许多常见的数据库任务。本文介绍如何使用`sqlx`包操作MySQL数据库,包括安装所需的包、连接数据库、创建表、插入/查询/更新/删除数据等操作,并展示了如何利用命名参数来进一步简化代码。通过`sqlx`,开发者可以更加高效且简洁地完成数据库交互任务。
    15 1
    |
    6天前
    |
    算法 NoSQL 中间件
    go语言后端开发学习(六) ——基于雪花算法生成用户ID
    本文介绍了分布式ID生成中的Snowflake(雪花)算法。为解决用户ID安全性与唯一性问题,Snowflake算法生成的ID具备全局唯一性、递增性、高可用性和高性能性等特点。64位ID由符号位(固定为0)、41位时间戳、10位标识位(含数据中心与机器ID)及12位序列号组成。面对ID重复风险,可通过预分配、动态或统一分配标识位解决。Go语言实现示例展示了如何使用第三方包`sonyflake`生成ID,确保不同节点产生的ID始终唯一。
    go语言后端开发学习(六) ——基于雪花算法生成用户ID
    |
    7天前
    |
    JSON 缓存 监控
    go语言后端开发学习(五)——如何在项目中使用Viper来配置环境
    Viper 是一个强大的 Go 语言配置管理库,适用于各类应用,包括 Twelve-Factor Apps。相比仅支持 `.ini` 格式的 `go-ini`,Viper 支持更多配置格式如 JSON、TOML、YAML
    go语言后端开发学习(五)——如何在项目中使用Viper来配置环境
    |
    1天前
    |
    监控 NoSQL Go
    Go语言中高效使用Redis的Pipeline
    Redis 是构建高性能应用时常用的内存数据库,通过其 Pipeline 和 Watch 机制可批量执行命令并确保数据安全性。Pipeline 类似于超市购物一次性结账,减少网络交互时间,提升效率。Go 语言示例展示了如何使用 Pipeline 和 Pipelined 方法简化代码,并通过 TxPipeline 保证操作原子性。Watch 机制则通过监控键变化实现乐观锁,防止并发问题导致的数据不一致。这些机制简化了开发流程,提高了应用程序的性能和可靠性。
    6 0
    |
    4天前
    |
    NoSQL Go Redis
    Go语言中如何扫描Redis中大量的key
    在Redis中,遍历大量键时直接使用`KEYS`命令会导致性能瓶颈,因为它会一次性返回所有匹配的键,可能阻塞Redis并影响服务稳定性。为解决此问题,Redis提供了`SCAN`命令来分批迭代键,避免一次性加载过多数据。本文通过两个Go语言示例演示如何使用`SCAN`命令:第一个示例展示了基本的手动迭代方式;第二个示例则利用`Iterator`简化迭代过程。这两种方法均有效地避免了`KEYS`命令的性能问题,并提高了遍历Redis键的效率。
    15 0
    |
    5天前
    |
    监控 Serverless Go
    Golang 开发函数计算问题之Go 语言中切片扩容时需要拷贝原数组中的数据如何解决
    Golang 开发函数计算问题之Go 语言中切片扩容时需要拷贝原数组中的数据如何解决
    |
    5天前
    |
    关系型数据库 MySQL 数据库连接
    Go语言中使用sqlx来操作事务
    在应用中,数据库事务保证操作的ACID特性至关重要。`github.com/jmoiron/sqlx`简化了数据库操作。首先安装SQLX和MySQL驱动:`go get github.com/jmoiron/sqlx`和`go get github.com/go-sql-driver/mysql`。导入所需的包后,创建数据库连接并使用`Beginx()`方法开始事务。通过`tx.Commit()`提交或`tx.Rollback()`回滚事务以确保数据一致性和完整性。
    8 0
    |
    7天前
    |
    SQL 安全 关系型数据库
    Go 语言中的 MySQL 事务操作
    在现代应用中,确保数据完整与一致至关重要。MySQL的事务机制提供了可靠保障。本文首先解释了事务的概念及其ACID特性,随后介绍了如何在Go语言中使用`database/sql`包进行MySQL事务操作。通过一个银行转账的例子,演示了如何通过Go开启事务、执行操作并在必要时回滚或提交,确保数据一致性。最后,还讨论了不同事务隔离级别的含义及如何在Go中设置这些级别。通过本文的学习,开发者能更好地掌握MySQL事务的应用。
    12 0