Go语言的函数与指针的定义

在学习函数之前,大家都会学到如何打印第一个程序之类的学习进程,会发现Go程序都有一个main()函数,main()函数相当于程序的入口,如果没有main()函数,我们所写的所有代码都无法实现。

本文章主要讲解函数的定义以及怎么使用函数

1.函数定义

在Go语言中,函数构建了代码执行的逻辑结构,函数的基本组成为:关键字func、函数名、参数列表、返回列表、函数体。Go语言是编译型的语言,所以可以不像C语言那样必须先声明才能实现函数那样直接使用即可

编写函数的主要目的是将一个需要多行代码的复杂问题分解成一系列简单的任务逐步解决,同一个函数可被多次调用,有助于代码的重用。当代码执行到代码块最后一行(执行到"}"这一块)或者return 语句时会退出,return 语句可不带或者带多个返回值,这些值作为结果返回给调用者使用,return语句可以结束for循环或者一个进程

Go语言函数的支持特性如下:

1.匿名函数与闭包

支持匿名函数(lambda表达式),可直接赋值给变量或作为参数传递:

f := func(x int) int { return x * x }

闭包特性允许函数访问外部作用域的变量:

复制代码
func adder() func(int) int {
    sum := 0
    return func(x int) int { sum += x; return sum }
}

2.可变参数

func sum(nums ...int) int {

total := 0

for _, num := range nums { total += num }

return total

}

3.方法(Method)

type Circle struct { Radius float64 }

func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius }

  • 值接收者与指针接收者区分是否修改原值。

4.延迟调用(defer)

defer关键字延迟函数执行,常用于资源释放:

file, err := os.Open("file.txt")

if err != nil { return }

defer file.Close() // 确保函数结束时关闭文件

5.错误处理

通过多返回值约定错误处理:

func divide(a, b float64) (float64, error) {

if b == 0 { return 0, errors.New("division by zero") }

return a / b, nil

}

6.高阶函数

函数可作为参数或返回值:

func apply(f func(int) int, x int) int { return f(x) }

7.内置函数

Go提供内置函数如lencapmakenew等,无需导入即可使用。例如:

arr := make([]int, 5) // 使用make创建切片

2.函数创建

2.1函数声明

在Go语言中,函数的声明使用关键字func,语法如下:

func 函数名(参数列表) (返回列表){

函数体

}

func

声明函数的核心关键字,所有函数必须以 func 开头。

函数名

遵循标识符命名规则:首字母大写表示可导出(其他包可访问),小写表示私有(仅当前包内可用)。

参数列表

格式为 (变量名 类型, 变量名 类型, ...)。无参数时保留空括号 () 或省略(当函数为方法时)。

返回值类型

单返回值直接写类型;多返回值需用括号包裹 (类型1, 类型2)。无返回值时可省略。

示例代码

单参数无返回值:

复制代码
func greet(name string) {
    fmt.Println("Hello,", name)
}

多参数多返回值:

复制代码
func swap(a, b int) (int, int) {
    return b, a
}

变长参数声明

使用 ... 表示可变参数(必须是最后一个参数):

复制代码
func sum(nums ...int) int {
    total := 0
    for _, num := range nums {
        total += num
    }
    return total
}

命名返回值

返回值变量可在声明时命名,函数体内直接操作:

复制代码
func divide(a, b float64) (result float64, err error) {
    if b == 0 {
        err = errors.New("division by zero")
        return
    }
    result = a / b
    return
}

2.2函数参数

函数可以有一个参数或者多个参数,函数参数分为形式参数和实际参数。Go语言的函数还支持可变参数(简称变参)。

可变参数

使用...语法支持可变数量参数,类型必须相同,函数内以切片形式访问:

复制代码
func sum(nums ...int) int {
    total := 0
    for _, num := range nums {
        total += num
    }
    return total
}

2.3函数参数的传递方式

函数传递的方式有两种,一个是值传递,另一个是引用传递。值传递是指调用函数时将实参复制一份传递到函数中,这样在函数中对参数进行修改,就不会影响到实参。引用传递是指在调用函数时将实参的地址传递到函数中,如果在函数中对参数进行修改,将影响实参。

Go语言调用函数需遵循以下规则:

1.函数名称需匹配

2.实参和形参必须一 一对应,如顺序、个数、类型等

值传递(默认方式)

基本类型(int/float/bool/string等)和结构体默认值传递,函数内修改不影响原始值:

复制代码
func modifyValue(x int) {
    x = 100
}
func main() {
    v := 1
    modifyValue(v) // v仍为1
}

引用传递

切片(slice)、映射(map)、通道(channel)、接口(interface)等引用类型实际传递底层指针的副本,函数内修改会影响原始数据:

复制代码
func modifySlice(s []int) {
    s[0] = 99
}
func main() {
    nums := []int{1, 2, 3}
    modifySlice(nums) // nums变为[99, 2, 3]
}

3.函数变量

函数在Go语言中是一等公民,可以像其他类型一样被赋值给变量、作为参数传递或作为返回值。函数变量本质是一个指向函数代码的指针,类型由函数签名决定。

函数变量特点:

复制代码
type MathFunc func(int, int) int

var Add MathFunc = func(a, b int) int {
    return a + b
}

result := Add(3, 5) // 返回8
  • 零值为nil,调用nil函数会导致panic
  • 支持类型推断,相同签名的函数可相互赋值
  • 可作为结构体字段或map值
  • 实现接口时需注意方法接收者类型

基本用法示例

声明函数类型并赋值:

复制代码
type MathFunc func(int, int) int

var Add MathFunc = func(a, b int) int {
    return a + b
}

result := Add(3, 5) // 返回8

回调函数示例

函数作为参数传递:

复制代码
func Calculate(a, b int, op func(int, int) int) int {
    return op(a, b)
}

func main() {
    multiply := func(x, y int) int { return x * y }
    fmt.Println(Calculate(4, 5, multiply)) // 输出20
}

闭包示例

函数变量捕获外部作用域:

复制代码
func Counter() func() int {
    i := 0
    return func() int {
        i++
        return i
    }
}

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

方法值示例

从对象获取方法函数:

复制代码
type Point struct{ X, Y float64 }

func (p Point) Distance(q Point) float64 {
    return math.Hypot(q.X-p.X, q.Y-p.Y)
}

func main() {
    p := Point{1, 2}
    distanceFromP := p.Distance // 方法值
    fmt.Println(distanceFromP(Point{4, 6}))
}

接口实现示例

通过函数变量实现接口:

复制代码
type Handler interface {
    Serve(int)
}

type HandlerFunc func(int)

func (f HandlerFunc) Serve(n int) {
    f(n)
}

func main() {
    var h Handler = HandlerFunc(func(n int) {
        fmt.Println("Processing", n)
    })
    h.Serve(42)
}

注意事项

  • 函数变量比较应使用reflect.DeepEqual
  • 避免在热路径频繁创建匿名函数
  • 协程中使用函数变量需注意变量捕获的生存期

4.指针创建

在Go语言中,有个数据类型会常常见到,那就是指针类型。指针是一种地址值,这个地址值代表着计算机内存中的某个位置,指针类型就是存放该地址值的变量。一个指针变量就指向一个值的内存地址,在使用指针类型需通过*符号声明,例如*int表示指向整型变量的指针。指针允许程序间接访问和修改内存中的数据,避免数据拷贝,提升效率。

4.1 指针创建

指针声明语法如下:

var var_name *var_type

var_name:指针变量名

var_type:指针类型

*:指定变量是一个指针

指针的使用注意事项

指针未初始化时值为nil,直接操作nil指针会导致运行时错误(panic)。指针解引用前需确保已指向有效内存地址。

避免悬垂指针(指向已释放内存的指针),Go的垃圾回收机制会自动管理内存,但需注意循环引用问题。

指针传递可能导致数据竞争,多线程环境下需配合互斥锁(如sync.Mutex)保证安全。

指针操作示例代码

复制代码
package main

import "fmt"

func main() {
    // 基础类型指针
    var num int = 42
    var p *int = &num  // 获取num的地址
    fmt.Println("Value via pointer:", *p)  // 解引用输出42

    // 修改指针指向的值
    *p = 100
    fmt.Println("Modified num:", num)  // 输出100

    // 结构体指针
    type Person struct{ Name string }
    person := Person{"Alice"}
    ptr := &person
    ptr.Name = "Bob"  // 等价于(*ptr).Name
    fmt.Println("Updated name:", person.Name)  // 输出Bob

    // 指针作为函数参数
    x := 10
    double(&x)
    fmt.Println("Doubled value:", x)  // 输出20
}

func double(n *int) {
    *n *= 2  // 修改原值
}

指针与引用类型的区别

切片(slice)、映射(map)和通道(channel)本身是引用类型,使用时无需显式取地址。但需注意它们的底层结构可能被复制:

复制代码
func modifySlice(s []int) {
    s[0] = 99  // 会影响外层切片
    s = append(s, 100)  // 可能不影响外层(取决于容量)
}

使用推荐的环境

优先使用值传递,除非需要修改原数据或处理大型结构。指针适合用于:

  • 函数内修改调用者作用域的变量
  • 减少大结构体的拷贝开销
  • 实现链表、树等数据结构

空指针检查示例:

复制代码
if p != nil {
    fmt.Println(*p)
} else {
    fmt.Println("Pointer is nil")
}

4.2 取变量地址

一个指针变量可以指向任何一个值的内存地址,它所指向值的内存地址在32位和64位计算机上分别占用4个和8个字节,占用字节大小与所指向值的大小无关。指针变量名通常被定义为ptr(指代point),当一个指针被定义后没有赋值任何地址时,它的默认值为nil。每个变量在运行时都会拥有一个地址,这个地址代表变量在内存中的位置,Go语言提供在变量名前面加上"&"操作符获取变量的内存地址,语法如下:

ptr := &v

提供&操作符取变量v的地址并赋值给ptr

如果变量v的类型为T,则ptr的类型为*T

4.3 取指针地址

使用*运算符可以解引用指针,访问指针指向的值。

复制代码
y := *p // 通过指针p获取x的值并赋值给y

指针的零值

若指针的零值是nil,表示指针未指向任何内存地址。

指针作为函数参数

指针常用于函数参数中,以便直接修改传入的变量值。

复制代码
func modifyValue(ptr *int) {
    *ptr = 100
}

modifyValue(&x) // 传入x的地址
fmt.Println(x)  // 输出: 100

指针与结构体

复制代码
结构体指针可以通过->简写访问字段,但Go语言统一使用.操作符。
复制代码
type Person struct {
    Name string
    Age  int
}

p := &Person{"Alice", 30}
fmt.Println(p.Name) // 直接通过指针访问字段

指针的安全性

Go语言不支持指针算术运算,避免了指针越界等安全问题。

// 以下代码在Go中不合法

// p++

总结

在 Go 语言进阶学习中,函数与指针是构建程序逻辑、优化内存操作的核心基础,也是连接基础语法与高级特性的关键桥梁。通过本次系统学习,我全面掌握了函数的定义、使用、特性以及指针的原理与应用,理清了两者的核心用法、易错点与使用规范,深刻理解了它们在代码复用、性能优化中的重要作用,现将学习内容与心得整理如下,为后续编程实践奠定坚实基础。

函数是 Go 代码执行的逻辑核心,main()函数作为程序唯一入口,是所有代码运行的基础。函数由func关键字、函数名、参数列表、返回值与函数体构成,支持无需提前声明直接定义使用,核心价值是拆分复杂任务、实现代码复用。掌握了多种函数用法:普通函数的声明与调用、可变参数函数、多返回值与命名返回值函数,同时了解了匿名函数、闭包、高阶函数等高级特性等实用功能。函数参数传递分为值传递和引用传递,值传递会复制参数,不影响原值;引用传递可直接操作原始数据,这一知识点让我明白了函数传参的底层逻辑。

指针是 Go 语言操作内存的重要工具,用于存储变量的内存地址,通过&取地址、*解引用实现对数据的间接访问。指针未初始化时默认为nil,直接操作会触发 panic,使用时必须保证指向有效内存。指针的核心作用是修改函数外部变量、减少大结构体的拷贝开销,提升程序运行效率。结构体指针可直接通过.访问字段,简化了代码编写。

通过学习,掌握了函数与指针的组合用法,例如用指针作为函数参数实现原值修改,用函数变量实现回调与闭包。使用规范:优先使用值传递,需要修改数据或优化性能时再使用指针;函数调用严格匹配参数与返回值,避免空指针调用。

相关推荐
liuyao_xianhui1 小时前
map和set_C++
java·开发语言·数据结构·c++·算法·宽度优先
香蕉鼠片1 小时前
八股C++
开发语言·c++
AI视觉网奇1 小时前
python 截取矩形 缩放,旋转
开发语言·python·numpy
Yyyyy123jsjs1 小时前
轻松通过Python调用外汇api获取汇率数据
开发语言·python
墨^O^1 小时前
C++ Memory Order 完全指南:从 relaxed 到 seq_cst,深入理解无锁编程与 happens-before
linux·开发语言·c++·笔记·学习·算法·缓存
阿正的梦工坊2 小时前
JavaScript 闭包:从入门到精通
开发语言·javascript·ecmascript
qq_12084093712 小时前
Three.js 性能实战:大场景从 15FPS 到 60FPS 的工程化优化路径
开发语言·前端·javascript
codeejun2 小时前
每日一Go-51、Go微服务--API网关-Kong
微服务·golang·kong