程序员小白必会!!你真的知道函数和方法有什么区别?
Posted @了凡
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了程序员小白必会!!你真的知道函数和方法有什么区别?相关的知识,希望对你有一定的参考价值。
前言
图片来自Wallpaper Engine 如果侵权联系我立删
博主介绍:
– 本人是了凡,意思为希望本人任何时候以善良为先,以人品为重,喜欢了凡四训中的立命之学、改过之法、积善之方、谦德之效四训,更喜欢每日在简书上投稿每日的读书感悟笔名:三月_刘超。专注于 Go Web 后端,辅学Python、Java、算法、前端等领域。
文章目录
保姆系列
基础篇
ONE:https://blog.csdn.net/weixin_45765795/article/details/117278889
TWO:https://blog.csdn.net/weixin_45765795/article/details/117548389
进阶篇
ONE:https://blog.csdn.net/weixin_45765795/article/details/117257325
什么是函数?
生活中的函数
函数(function)的定义通常分为传统定义和近代定义,函数的两个定义本质是相同的,只是叙述概念的出发点不同,传统定义是从运动变化的观点出发,而近代定义是从集合、映射的观点出发。函数的近代定义是给定一个数集A,假设其中的元素为x,对A中的元素x施加对应法则f,记作f(x),得到另一数集B,假设B中的元素为y,则y与x之间的等量关系可以用y=f(x)表示,函数概念含有三个要素:定义域A、值域C和对应法则f。其中核心是对应法则f,它是函数关系的本质特征。
程序中的函数
1、函数是一个可以多次使用的功能代码块,一个封闭的(空间),它可以在代码里随意调用。利用函数的封装可以减少重复代码的开发,提高代码的利用率。函数可以传参,利用函数内预先定义的内容对传入的不同数据参数进行处理。
2、函数也是对象,也可以为值,它可以存在于变量,数组和对象之中。
3、函数可以当参传递给函数,并且由函数返回,另外函数拥有属性。
4、函数总会有返回值(除了构造函数之外,构造函数默认返回构造器函数调用,当构造函数调用执行时,会显示返回)
逻辑角度
能够完成特定功能的独立代码块
物理角度
能够接收数据
能够对接受的数据进行处理
能够将数据处理的结果返回
为什么需要函数?
1.避免了重复性操作,实现了代码的可重复性
2.有利于程序的模块化
3.易于维护
函数怎么用?
函数定义
这里以最近比较火的一门Golang语言为例:
函数特点
无需声明原型。
支持不定 变参。
支持多返回值。
支持命名返回参数。
支持匿名函数和闭包。
函数也是一种类型,一个函数可以赋值给变量。
不支持 嵌套 (nested) 一个包不能有两个名字一样的函数。
不支持 重载 (overload)
不支持 默认参数 (default parameter)。
函数定义
函数声明包含一个函数名,参数列表, 返回值列表和函数体。如果函数没有返回值,则返回列表可以省略。函数从第一条语句开始执行,直到执行return语句或者执行函数的最后一条语句。
定义函数使用func关键字基本格式:
func 函数名(参数)(返回值){
函数体
}
函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名(包的概念详见后文)。
参数:参数由参数变量和参数变量的类型组成,多个参数之间使用,分隔。
返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用,分隔。
函数体:实现指定功能的代码块。
参数
类型简写
函数的参数中如果相邻变量的类型相同,则可以省略类型
func intSum(x, y int) int {
return x + y
}
类型均为int,因此可以省略x的类型,因为y后面有类型说明,x参数也是该类型
可变参数
可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后加…来标识。
注意:可变参数通常要作为函数的最后一个参数。
返回值
Go语言中通过return关键字向外输出返回值。
多返回值
Go语言中函数支持多返回值,函数如果有多个返回值时必须用()将所有返回值包裹起来
返回值命名
函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回
返回值补充
当我们的一个函数返回值类型为slice时,nil可以看做是一个有效的slice,没必要显示返回一个长度为0的切片
匿名函数
定义
函数可以作为返回值,但在函数内部不能再像之前那样定义函数了,只能定义匿名函数。匿名函数就是没有函数名的函数格式:
func(参数)(返回值){
函数体
}
函数具体深层演练
闭包、递归
闭包
定义
闭包指的是一个函数和与其相关的引用环境组合而成的实体。闭包=函数+引用环境
func adder() func(int) int {
var x int
return func(y int) int {
x += y
return x
}
}
变量f是一个函数并且它引用了其外部作用域中的x变量,此时f就是一个闭包。 在f的生命周期内,变量x也一直有效
func adder2(x int) func(int) int {
return func(y int) int {
x += y
return x
}
}
func makeSuffixFunc(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}
func calc(base int) (func(int) int, func(int) int) {
add := func(i int) int {
base += i
return base
}
sub := func(i int) int {
base -= i
return base
}
return add, sub
}
牢记闭包=函数+引用环境
递归
递归,就是在运行的过程中调用自己。 一个函数调用自己,就叫做递归函数。
递归形成条件:
- 子问题须与原始问题为同样的事,且更为简单。
- 不能无限制地调用本身,须有个出口,化简为非递归状况处理。
题目:
用递归写一个累加和函数
代码案例:
package main
import "fmt"
func main() {
fmt.Println(add(3)) // 用户输入一个数字
}
func add(k int) int{
if k == 0 { // 直到等于零结束累加
return 0
}
return k + add(k - 1)
}
输出结果:
题目:
请查询相关资料或独自完成斐波那契数列
延迟调用(defer)
定义
defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行
使用
defer语句延迟调用的特性,所以defer语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁及记录时间等。
我的理解
defer的底层像一个栈一样,先入后出
执行时机
return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer语句执行的时机就在返回值赋值操作后,RET指令执行前
面试题:
package main
import "fmt"
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main() {
x := 1
y := 2
defer calc("AA", x, calc("A", x, y))
x = 10
defer calc("BB", x, calc("B", x, y))
y = 20
}
请问应该输出什么?
答案:
异常处理
Go语言没有结构化异常,使用 panic 抛出错误,recover 捕获错误。
panic:
- 内置函数
- 假如函数F中书写了panic语句,会终止其后要执行的代码,在panic所在函数F内如果存在要执行的defer函数列表,按照defer的逆序执行
- 返回函数F的调用者G,在G中,调用函数F语句之后的代码不会执行,假如函数G中存在要执行的defer函数列表,按照defer的逆序执行
- 直到goroutine整个退出,并报告错误
recover:
1、内置函数
2、用来控制一个goroutine的panicking行为,捕获panic,从而影响应用的行为
3、一般的调用建议
(1) 在defer函数中,通过recever来终止一个goroutine的panicking过程,从而恢复正常代码的执行
(2) 可以获取通过panic传递的error
什么是方法?
生活中的方法
方法是一个汉语词汇,方法的含义较广泛,一般是指为获得某种东西或达到某种目的而采取的手段与行为方式。方法在哲学,科学及生活中有着不同的解释与定义
从广义上讲:方法是解决问题的条件【解决问题就是使问题发生改变,使其达到所需标准】。
方法的本质是一个据有性质的框架,此框架的性质决定需要解决的问题怎样随着需要解决问题方面的能量【这里指广义上的能量】改变而改变。
不同方法相对于解决的问题的框架不同,所以不同方法解决问题的效率不同。
程序中的方法
- 方法是语句的集合,他们在一起执行一个功能
- 方法是解决一类问题的步骤的有序组合
- 方法包含于对象中
- 方法在程序中被创建,在其他地方被引用
为什么需要方法?
当某个功能被多次(重复)使用的时候,为了简化代码,方便操作,通常会将该功能封装起来,直接使用,用户可以不用考虑功能实现的细节,只需要知道该功能如何使用即可。
方法与方法之间的关系:
并列的,平级的关系,所有的方法都在类中,方法中不能包含另一个方法,但是可以调用另一个方法
方法问题扩展
方法定义好之后,一定要使用吗?(如果不用,为啥要写?)
功能的定义并非是一定要去使用的,定义方法的目的是为了在后续的使用过程中有可能会用的到该功能
定义好的功能,如果要使用一定要通过main函数吗?
如果是要直接运行的的功能,那么必须通过主函数,如果不是直接运行的而是通过其他方法间接运行的,那么考虑的是其所在的方法是否需要直接在主函数中直接运行
方法怎么用?
方法定义
方法特点
Go语言方法总是绑定对象实例,并隐式将实例作为第一实参(recevier )
一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。
只能为当前包内命名类型定义方法。
参数 receiver 可任意命名。如方法中未曾使用 ,可省略参数名。
参数 receiver 类型可以是 T 或 *T。基类型 T 不能是接口或指针。
不支持方法重载,receiver 只是参数签名的组成部分。
可用实例 value 或 pointer 调用全部方法,编译器自动转换。
方法定义
func (recevier type) method(参数列表)(返回值列表){}
参数和返回值可以省略
代码案例:
package main
import "fmt"
// 定义结构体
type Students struct {
name string
}
// 主方法
func main() {
a := new(Students)
a.name = "你好"
a.method()
}
// 定义方法
func (a Students)method() {
fmt.Println(a.name)
}
输入结果:
匿名字段
可以像字段成员那样访问匿名字段方法,编译器负责查找
代码案例:
package main
import "fmt"
type User struct {
id int
name string
}
type Student struct {
User
}
func (a *User) ToString() string { // receiver = &(Manager.User)
return fmt.Sprintf("User: %v" , a)
}
func main() {
m := Student{User{1, "你好"}}
fmt.Println(m.ToString())
}
输入结果:
方法具体深层演练
方法集
方法集:每个类型都有与之关联的方法集,这会影响到接口实现规则
所有给定类型的方法属于该类型的方法集。
类型 T 方法集包含全部 receiver T 方法。
类型 *T 方法集包含全部 receiver T + *T 方法。
如类型 S 包含匿名字段 T,则 S 和 *S 方法集包含 T 方法。
如类型 S 包含匿名字段 *T,则 S 和 *S 方法集包含 T + *T 方法。
不管嵌入 T 或 *T,*S 方法集总是包含 T + *T 方法。
用实例 value 和 pointer 调用方法 (含匿名字段) 不受方法集约束,编译器总是查找全部方法,并自动转换 receiver 实参。
package main
import (
"fmt"
)
type Students struct {
int
}
func (t Students) test1() {
fmt.Println("类型 *T 方法集包含全部 receiver T 方法。")
}
func (t *Students) test2() {
fmt.Println("类型 *T 方法集包含全部 receiver *T 方法。")
}
func main() {
t1 := Students{1}
t2 := &t1
fmt.Printf("t2 is : %v\\n", t2)
t2.test1()
t2.test2()
}
总结:简单来讲方法集类似于java中的类思想,就是Students这个结构体可以new出来一个对象,这个对象可以调取它下面的所有方法,那Students就是方法集
自定义error
这里引用一个中文文档的自定义error的方法:
举例:
package main
import (
"fmt"
"os"
"time"
)
type PathError struct {
path string
op string
createTime string
message string
}
func (p *PathError) Error() string {
return fmt.Sprintf("path=%s \\nop=%s \\ncreateTime=%s \\nmessage=%s", p.path,
p.op, p.createTime, p.message)
}
func Open(filename string) error {
file, err := os.Open(filename)
if err != nil {
return &PathError{
path: filename,
op: "read",
message: err.Error(),
createTime: fmt.Sprintf("%v", time.Now()),
}
}
defer file.Close()
return nil
}
func main() {
err := Open("/Users/5lmh/Desktop/go/src/test.txt")
switch v := err.(type) {
case *PathError:
fmt.Println("get path error,", v)
default:
}
}
这次就先讲到这里,如果想要了解更多的golang语言内容一键三连后序每周持续更新!
以上是关于程序员小白必会!!你真的知道函数和方法有什么区别?的主要内容,如果未能解决你的问题,请参考以下文章
进价程序员:5种必会的Java异步调用转同步的方法你会几种?
转载C#扫盲之:==/Equals /ReferenceEquals 异同的总结,相等性你真的知道吗?
转载C#扫盲之:==/Equals /ReferenceEquals 异同的总结,相等性你真的知道吗?