当前位置: 首页 > article >正文

【Go函数详解】二、参数传递、变长参数与多返回值

文章目录

  • 一、传递参数
    • 1. 按值传参
    • 2. 引用传参
      • 2.1 特殊情况
        • 2.1.1 切片slice
        • 2.1.2 字典map
  • 二、变长参数
    • 1. 基本定义和传值
      • 1.1 基本定义
      • 1.2 传值
        • 1.2.1 普通传值
        • 1.2.2 传递切片
    • 2. 任意类型的变长参数(泛型)
  • 三、多返回值
    • 1. 命名返回值


一、传递参数

1. 按值传参

Go语言默认使用按值传参来传递参数,即传递参数值的一个副本:
函数接收到传递进来的参数后,会将参数值拷贝给声明该参数的变量(形式参数,简称形参),如果在函数体中有对参数值做修改,实际上修改的是形参值,这不会影响到实际传递进来的参数值(简称实参)。

func add(a, b int) int  {
    a *= 2
    b *= 3
    return a + b
}

func main()  {
    x, y := 1, 2
    z := add(x, y)
    fmt.Printf("add(%d, %d) = %d\n", x, y, z)   //add(1, 2) = 8
}

x、y把值传递给a、b,主要原因是x、y的内存地址与a、b的内存地址并不相同,可以看作是两个值相等的不同变量,所以修改a、b的值并不会改变x、y的值。

2. 引用传参

如果想要实现在函数中修改形参值可以同时实参值,需要通过引用传参来完成,此时传递给函数的参数是指一个指针,而指针代表的是实参的内存地址,修改指针引用的值即修改内存地址中存储的值,所以实参的值也会被修改。

! 注意:这种情况下,传递的变量是地址值的拷贝,所以从本质上讲还是按值传递。

func add(a, b *int) int {
    *a *= 2
    *b *= 3
    return *a + *b
}

func main()  {
    x, y := 1, 2
    z := add(&x, &y)
    fmt.Printf("add(%d, %d) = %d\n", x, y, z)
}

在函数调用时,像切片(slice)、字典(map)、接口(interface)、通道(channel)这样的引用类型默认使用引用传参。

2.1 特殊情况

以下具体分析详见:(留个坑)

2.1.1 切片slice

把slice作为函数参数传递,若在函数内slice发生扩容的话,会让函数内外原本指向同一个底层数组的两个slice变量,分别指向两个不同的底层数组

2.1.2 字典map

把map作为函数参数传递,若在函数内map发生扩容的话,函数内外的map变量指向的底层内存仍是一致的。

二、变长参数

所谓变长参数指的是函数参数的数量不确定,即可以传递任意数量的参数到指定函数。合适地使用变长参数,可以让代码更简洁,尤其是输入输出类函数,fmt.Println的参数就是典型的变长参数。

1. 基本定义和传值

1.1 基本定义

在参数类型前加上...前缀,就可以将该参数声明为变长参数

func myfunc(numbers ...int) {
    for _, number := range numbers {
        fmt.Println(number)
    }
}

1.2 传值

1.2.1 普通传值
myfunc(1, 2, 3, 4, 5) 

函数 myfunc() 接受任意数量的参数,这些参数的类型全部是 int

1.2.2 传递切片

传递切片时需要末尾加上...作为标识,表示对应的参数类型是变长参数:

slice := []int{1, 2, 3, 4, 5}
myfunc(slice...)
myfunc(slice[1:3]...)

注:形如...type格式的类型只能作为函数的参数类型存在,并且是函数的最后一个参数

之所以支持传入切片,是因为从底层实现原理上看,类型...type本质上是一个切片,也就是[]type,这也是为什么上面的numbers可以用for循环来获取每个传入的参数值。

2. 任意类型的变长参数(泛型)

即指定变长参数类型为interface{}

func myPrintf(args ...interface{}) {
    for _, arg := range args {
        switch reflect.TypeOf(arg).Kind() {
        case reflect.Int:
            fmt.Println(arg, "is an int value.")
        case reflect.String:
            fmt.Printf("\"%s\" is a string value.\n", arg)
        case reflect.Array:
            fmt.Println(arg, "is an array type.")
        default:
            fmt.Println(arg, "is an unknown type.")
        }
    }
}

func main() {
    myPrintf(1, "1", [1]int{1}, true)
}

在这里插入图片描述
这里实现的是泛型功能,Go语言并没有在语法层面提供对泛型的支持,目前只能自己通过反射和interface{}类型实现。
interface{}是一个空接口,可以用于表示任意类型。但这个范围过于宽泛,像C语言中的void一样,我们根本不知道真正传递进来的参数到底是什么类型的,这在强类型的静态语言中是不能接受的,为了保证代码类型安全,需要在运行时通过反射对数据类型进行检查,以便让程序在预设的轨道内运行,避免因为类型问题导致程序崩溃。

三、多返回值

Go语言与其他编程语言一大不同之处在于支持多返回值,这才处理程序出错的时候非常有用。

func add(a, b *int) (int, error) {
    if (*a == 0 || *b == 0) {
        err := errors.New("只支持非负整数相加")
        return 0, err
    }
    *a *= 2
    *b *= 3
    return *a + *b, nil
}

func main()  {
    x, y := -1, 2
    z, err := add(x, y)
    if err != nil {
        fmt.Println(err.Error())
        return
    }
    fmt.Printf("add(%d, %d) = %d\n", x, y, z)
}

如上,通过error指定多返回一个表示错误信息的、类型为error的返回值,函数的多个返回值之间可以通过逗号分隔,并且在最外面通过圆括号包起来。

1. 命名返回值

在设置多返回值时,可以对返回值进行变量命名,这样可以在函数中直接对返回值进行赋值,而不必每次都按照指定的返回值格式返回多个变量了。

func add(a, b *int) (c int, err error) {
    if (*a == 0 || *b == 0) {
        err = errors.New("只支持非负整数相加")
        return
    }
    *a *= 2
    *b *= 3
    c = *a + *b
    return
}

这种机制避免了每次进行 return 操作时都要关注函数需要返回哪些返回值,为开发者节省了精力,尤其是在复杂的函数中。


http://www.kler.cn/news/284944.html

相关文章:

  • java定时服务
  • Python学习日志(1)——安装
  • Linux-arm64中断现场保护详解
  • MySQL 集群技术全攻略:从搭建到优化(上)
  • 分类模型评估指标——准确率、精准率、召回率、F1、ROC曲线、AUC曲线
  • 快递盒检测检测系统源码分享 # [一条龙教学YOLOV8标注好的数据集一键训练_70+全套改进创新点发刊_Web前端展示]
  • RAG 向量数据库:掌握 Elasticsearch 作为向量数据库的终极指南
  • 【Python零基础】文件使用和异常处理
  • Vue(四) 组件、单文件组件、非单文件组件,重要的内置关系
  • 【计组 | Cache原理】讲透Cache的所有概念与题型方法
  • 大模型好书案例——《BERT基础教程:Transformer大模型实战》(附PDF)
  • LuaJit分析(一)LuaJit交叉编译
  • TCP的连接与断开
  • java基础开发-xstream解析xml
  • 去中心化(Decentralization)
  • leetcode1514 最大概率路径(Bellman-ford算法详解)
  • 栈算法【基于顺序表】
  • centos 系统yum 安装 mariadb
  • UML类图中的组合关系
  • Vue3 + Axios双Token刷新解决方案
  • MySQL——多表操作(四)子查询(1)带 IN 关键字的子查询
  • Xilinx高速接口之GTP
  • CSS 预处理器
  • 10、ollama启动LLama_Factory微调大模型(llama.cpp)
  • opencv之形态学
  • 喜羊羊做Python真题
  • 基于Android+SQLite数据库开发Java考试App
  • 深度学习100问15:什么是交叉熵误差
  • 【Linux】Linux Bash Shell 教程
  • 工程师们都爱看的Docker容器技术,一看就会!保姆级教程(上)