首页 > 其他分享 >【零基础入门Go语言】Go语言的一等公民:函数和方法

【零基础入门Go语言】Go语言的一等公民:函数和方法

时间:2025-01-10 20:59:02浏览次数:3  
标签:函数 int sum 入门 接收者 类型 Go 返回值 语言

函数和方法是我们迈向代码复用,多人协作开发的第一步。通过函数,可以把开发任务分解成一个个小的单元,这些小单元可以被其他单元复用,进而提升开发效率、降低代码重复度。再加上现成的函数已经被充分测试和使用过,所以其他函数在使用这个函数时也更安全,相较自己重新写一个相似功能的函数,Bug率也更低。

本章将会详细讲解 Go 语言的函数和方法,了解它们的声明、使用和区别。虽然在 Go 语言中函数和方法是两种概念,但他木讷的相似度非常高,只是所属的对象不同。我们先从函数开始了解。

函数

函数初探

在前面的内容中,我们已经见到了 Go 语言中一个非常重要的函数:main 函数,它是一个 Go 语言程序的入口函数。

我们来看一个 main 函数的例子:

func main() {
	
}

它由几部分组成:

  1. 任何一个函数的定义都有一个 func 关键字,用于声明一个函数,就像使用 var 关键字声明一个变量一样。
  2. 然后紧跟的 main 是函数的名字,命名符合 Go 语言的规范即可。
  3. main 函数名字后面的一对括号是不能省略的,括号中可以定义函数使用的参数,这里的 main 函数没有参数,所以是空括号。
  4. 括号后面还可以有函数的返回值,因为 main 函数没有返回值,所以这里没有定义。
  5. 最后就是大括号({})函数体了,你可以在函数体内书写代码,写该函数自己的业务逻辑。

函数的声明

经过上一小节的介绍,相信你已经对Go语言函数的构成有了一个比较清晰的了解,现在让我们一起总结函数的声明格式,如下面的代码所示:

func funcName(params) result {
	body
}

这就是一个函数的定义,它包含以下几个部分:

  1. 关键字 func
  2. 函数名字 funcName
  3. 函数的参数 params,用于定义形参的变量名和类型,可以有一个参数,也可以有多个,也可以没有。
  4. result 用于定义函数的返回值,如果没有返回值,省略即可。也可以有多个返回值,需要放到哦同一个括号内。可以给返回值指定名字。
  5. body 是函数体,可以在这里写函数的代码逻辑。

现在,我们根据上面的函数声明格式,自定义一个函数:

func sum(a int, b int) int {
	return a+b
}

这是一个计算两数之和的函数,函数的名字是 sum,它有两个参数 a、b,参数的类型都是 int。sum 函数的返回值也是 int 类型,函数体部分就是把 a 和 b 相加,然后通过 return 关键字返回,如果函数没有返回值,就可以不使用 return 关键字。

函数中形参的定义和我们定义变量是一样的,都是变量名称在前,变量类型在后,只不过在函数中,变量名称叫做参数名称,也就是函数的形参,形参只能在该函数体内使用。函数形参的值由点用着提供,这个值也称为函数实参

在声明函数参数是,相同类型的形参可以省去多余的类型,只留一个即可。

func sum(a, b int) int {
	return a+b
}

像这样使用逗号分隔变量,后面统一使用 int 类型,这与变量的声明是一样的,多个相同类型的变量都可以这样声明。

多值返回

与有的编程语言不一样的是,Go语言的函数可以返回多个值,也就是多值返回。在 Go语言的标准库中,你可以看到很多这样的函数:第一个值返回函数的结果,第二个值返回函数出错的信息,这就是多值返回的经典应用。

对于 sum 函数,假设我们不允许提供的是负数,可以这样写:

func sum(a, b int) (int, error) {
	if a < 0 || b < 0 {
		return 0, errors.New("a 或者 b 不能是负数")
	}
	return a+b, nil
}

这里需要注意的是,如果函数有多个返回值,返回值部分的类型定义需要使用小括号括起来,也就是 (int,error)。这代表函数 sum 有两个返回值,第一个是 int 类型,第二个是 error 类型,我们在函数体中使用 return 返回结果的时候,也要符合这个类型顺序。

在函数体中,可以使用 return 返回多个值,返回的多个值通过逗号分隔即可,返回多个值的类型顺序要与函数声明的返回类型顺序一致

函数有多值返回的时候,需要有多个变量接收它的值。

如果有的函数返回值不需要,可以使用**下划线(_)**丢弃它。

返回值命名

在Go语言中,不止函数的参数可以有变量的名称,函数的返回值也可以,也就是说你可以为每个返回值起一个名字,这个名字就可以像参数一样在函数体内使用了。

func sum(a, b int) (sum int, err error) {
	if a < 0 || b < 0 {
		return 0, errors.New("a 或者 b 不能是负数")
	}
	sum = a + b
	err = nil 
	return
}

返回值的命名与参数、变量命名一样,名称在前,类型在后。在以上示例中,对两个返回值进行了命名,一个是 sum,一个是 err,这样就可以在函数体中使用它们了。

你可能注意到了,上面的实例中的 return 后面什么也没有。直接为命名的返回值赋值,也就等于函数有了返回值,所以可以忽略 return 的返回值。

虽然 Go语言支持函数返回值命名,但这并不是太常用,可以根据自己的需求情况,酌情选择是否对函数返回值命名。

可变参数

可变参数就是函数的参数数量是可变的,比如常见的 fmt.Println 函数。

同样一个函数,可以不传参数,可以传一个参数,可以传两个参数,还可以传多个参数,等等,这种函数就是具有可变参数的函数。

fmt.Println()
fmt.Println("hello")
fmt.Println("hello", "world")

如何定义?下面以 Println 函数为例子。

func Println(a ...interface{}) (n int, err error)

现在我们也可以定义自己的带可变参数的函数了。还是以sum函数为例,在下面的代码中,我们通过可变参数的方式,计算调用者传递的所有实参的和.

func (params ...int) int {
	sum := 0
	for _, i := range params {
		sum += i
	}
	return sum
}

该函数的参数是一个可变参数,然后通过 for range 循环来计算这些参数之和。

讲到这里,相信你也看明白了,可变参数的类型其实就是切片,比如示例中 params 参数的类型是 [​]int,所以可以使用 for range 进行循环。函数有了可变参数,就可以灵活使用它了。

这里需要注意的是,如果你定义的函数中既有普通参数,又有可变参数,那么可变参数一定要放在参数列表的最末尾。

包级函数

不管是自定义的函数sum,还是我们前面多次使用过的函数 Println,都会从属于一个包(package)。sum函数属于 main包,Println函数属于 fmt包。

同一个包中的函数哪怕是私有的(函数名称首字母小写)也可以被调用。如果不同包的函数要被调用,那么函数的作用域必须是公有的,也就是函数名称的首字母要大写,比如Println

在后面的一些章节中,我会对包、作用域和模块化做详细讲解,这里可以先记住:

  1. 函数名称首字母小写代表私有函数,只有在同一个包中才可以被调用。
  2. 函数名称首字母大写代表公有函数,在不同的包中也可以被调用。
  3. 任何一个函数都会从属于一个包。

Go语言没有用public、private这样的修饰符来修饰函数是公有还是私有,而是通过函数名称的首字母大小写来代表,这样省略了烦琐的修饰符,使之更简洁。

匿名函数和闭包

顾名思义,匿名函数就是没有名字的函数,这是它与正常函数的主要区别。

func main() {
	sum := func(a, b int) int {
		return a+b
	}
	fmt.Println(sum(1,2))
}

在上面的实例中,变量 sum 所对应的值就是一个匿名函数。需要注意的是,这里的 sum 只是一个函数类型的变量,并不是函数的名字。

有了匿名函数,就可以在函数中再定义函数(函数嵌套),定义的这个匿名函数也可以被称为内部函数。更重要的是,在函数内定义的内部函数,可以使用外部函数的变量等,这种方式也成为闭包

func main() {
    cl:=colsure()
    fmt.Println(cl())
    fmt.Println(cl())
    fmt.Println(cl())
}

func colsure() func() int {
    i:=0
    return func() int {
        i++
        return i
    }
}

运行这个代码,你会看到输出打印的结果是:

1
2
3

这都得益于匿名函数闭包的能力,让我们自定义的colsure函数可以返回一个匿名函数,并且该匿名函数持有外部函数colsure的变量 i。因而在 main函数中,每调用一次cl(),i 的值都会加1。

在Go语言中,函数也是一种类型,它也可以被用来声明函数类型的变量、参数或者作为另一个函数的返回值类型。

方法

不同于函数的方法

在 Go语言中,方法和函数是两个概念,但又非常相似,不同点在于方法必须要有一个接收者,这个接受这是一个类型,这样方法就与这个类型绑定在一起了,成为这个类型的方法。

// 定义一个新的类型 Age,等价于 uint
type Age uint

// 定义 Age 类型的方法,接收者为 Age
func (age Age) String() {
	fmt.Println("the age is ", age)
}

与函数不同,定义方法时会在关键字func和方法名String之间加一个接收者(age Age),接收者使用小括号包围。

接收者的定义和普通变量、函数参数等一样,前面是变量名,后面是接收者类型。

现在方法String()就和类型Age绑定在一起了,String()是类型Age的方法。定义了接收者的方法后,就可以通过点操作符(.)来调用方法。

age := Age(25)
age.String()

运行这段代码,可以看到如下输出:

the age is 25

接收者就是函数和方法的最大不同,此外,上面所讲到的函数具备的能力,方法也都具备。

值类型接收者和指针类型接收者

方法的接收者除了可以是值类型(比如上一小节的示例)​,也可以是指针类型。

如果定义的方法的接收者类型是指针,我们对指针的修改就是有效的,如果不是指针,修改就没有效果。

func (age *Age) Modify() {
	*age = Age(30)
}

调用一次Modify方法后,再调用String方法查看结果,会发现已经变成30了,说明基于指针的修改有效。

在调用方法的时候,传递的接收者本质上都是副本,只不过一个是这个值的副本,一个是指向这个值的指针的副本。指针具有指向原有值的特性,所以修改了指针指向的值,也就修改了原有的值。我们可以简单地理解为值接收者使用的是值的副本来调用方法,而指针接收者使用实际的值来调用方法。

示例中调用指针接收者方法的时候,使用的是一个值类型的变量,并不是一个指针类型,其实这里使用指针变量调用也是可以的.

这是因为Go语言编译器帮我们自动做了如下事情:如果使用一个值类型变量调用指针类型接收者的方法,Go语言编译器会自动帮我们取指针调用,以满足指针接收者的要求。

同样的原理,如果使用一个指针类型变量调用值类型接收者的方法,Go语言编译器会自动帮我们解引用调用,以满足值类型接收者的要求。

总之,方法的调用者既可以是值也可以是指针,不用太关注这些,Go语言会帮我们自动转义,这大大提高了开发效率,同时避免因不小心造成的Bug。

不管是使用值类型接收者,还是指针类型接收者,应先确定你的需求:在对类型进行操作的时候是要改变当前接收者的值,还是要创建一个新值进行返回?在明确了需求之后,就可以决定使用哪种接收者了。

总结

在Go语言中,虽然存在函数和方法两个概念,但是它们基本相同,不同的是所属的对象。函数属于一个包,方法属于一个类型,所以方法也可以简单地理解为与一个类型关联的函数。

不管是函数还是方法,它们都是代码复用的第一步,也是代码职责分离的基础。掌握好函数和方法,可以让你写出职责清晰、任务明确、可复用的代码,提高开发效率、降低Bug率。

有关于匿名函数、闭包以及方法接受者等特性的详细分析,后面会有单独的文章进行分析讲解,这里只需要先记住它的特点。

这部分的内容虽然不是面试的常问考点,但是一旦面试官问了这方面的问题,你就必须要给出正确的答案,有错误的话基本上这场面试也就拜拜了,所以还是非常重要的。

标签:函数,int,sum,入门,接收者,类型,Go,返回值,语言
From: https://blog.csdn.net/2301_76655656/article/details/145041076

相关文章

  • 2025版最新如何在本地部署大语言模型:工具与指南,零基础入门到精通,收藏这一篇就够了
    在快速发展的人工智能领域,大语言模型(LLMs)正成为各类应用的核心。无论是在智能客服、内容生成,还是在教育与医疗等领域,这些模型的应用潜力巨大。然而,云端服务的高昂费用和数据隐私的担忧,让越来越多的用户希望能够在本地环境中部署这些强大的模型。本文将详细介绍如何利用多款......
  • 2025版最新如何用3个月零基础入门网络安全(小白必看)?零基础入门到精通,收藏这一篇就够了
    前言写这篇教程的初衷是很多朋友都想了解如何入门/转行网络安全,实现自己的“黑客梦”。文章的宗旨是:1.指出一些自学的误区2.提供客观可行的学习表3.推荐我认为适合小白学习的资源.大佬绕道哈!一、自学网络安全学习的误区和陷阱1.不要试图先成为一名程序员(以编程为基础的......
  • 【C++】穿越编程岁月,细品C++进化轨迹,深化入门基石(续章)——揭秘函数缺省参数的魅力、函
    文章目录一、函数缺省参数二、函数重载三、引用1.引用的概念和定义2.引用的特性3.引用的使用4.const引用5.指针和引用的关系四、inline内联函数和nullptr1.inline2.nullptr一、函数缺省参数   缺省参数其实就是默认参数,它是声明或定义函数时为函数的参数指定......
  • C语言分支和循环(上)
    分⽀和循环分⽀和循环(上)1.if语句1.1if1.2else2.关系操作符3.条件操作符4.逻辑操作符:&&,||,!5.switch语句分⽀和循环(上)C语⾔是结构化的程序设计语⾔,这⾥的结构指的是顺序结构、选择结构、循环结构,C语⾔是能够实现这三种结构的,其实我们如果仔细分析,我们⽇......
  • R 语言科研绘图 --- 折线图-汇总
     在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式:R语言科研绘图模板---sciRplothttps://mp.weixin.qq.com/s/QA_8LVqjkdg4A16zLonw4w?payreadticket=HJZZ1......
  • 32单片机从入门到精通之测试与验证——单元测试(十五)
    人生苦短,我们都会面临困难和挑战。但是,只要我们保持积极的心态和勇往直前的精神,我们就能战胜一切困难,实现自己的目标。成功并不是一蹴而就的,它需要我们付出努力和坚持不懈。就像爬山一样,我们可能会遇到陡峭的山路和艰难的攀登,但只要我们不放弃,不停止前进,就一定能登上山顶,看到......
  • 数据结构——单链表(C语言版:超详细)
    目录一、引言1.数据结构的重要性2.单链表在其中的地位二、什么是单链表1.单链表的定义2.基本概念解释三、单链表的结构特点1.与数组对比的优势2.存在的劣势四、单链表的基本操作1.节点的创建2.动态申请一个节点3.插入节点3.1尾插3.2头插3.3在pos之前插入3.4在......
  • 模拟ic入门——设计一个压控振荡器(VCO)(一)环形振荡器
    概述:振荡器是微电子不可或缺的一环,应用场景从微处理器的时钟到蜂窝电路的载波合成,要求的结构和性能差别很大。OSC主要分两部分,环形振荡器(RingOSC)和LC振荡器。其中环形振荡器主要由反相器构成,应用于低速的数字时钟中;而LC振荡器一般用于高频场景,如PLL参考资料:拉扎维的《模拟C......
  • 第25章 汇编语言--- 信号量与互斥锁
    信号量(Semaphore)和互斥锁(Mutex,全称MutualExclusionObject)是两种用于管理对共享资源的访问的同步机制。它们在多线程或多进程编程中非常重要,可以确保同一时间只有一个线程或进程能够访问特定的资源,从而避免了竞争条件(RaceCondition)。下面我将详细叙述这两种机制,并给出简单......
  • 十个经典的Java面试题及详解,这些问题涵盖了Java语言特性、多线程、JVM、设计模式、框
    1.Java内存模型(JMM)问题:请解释Java内存模型(JMM)的基本概念。答案:Java内存模型(JMM)定义了多线程程序中变量的访问规则。JMM的主要目标是确保程序在多线程环境下的正确性和性能。JMM主要包括以下几点:主内存与工作内存:所有变量都存储在主内存中,每个线程有自己的工作内存,线程对变......