由于自己是搞python开发的,所以在学习go时,当看到函数和方法时,顿时还是挺蒙的,因为在python中并没有明显的区别,但是在go中却是两个完全不同的东西。在官方的解释中,方法是包含了接收者的函数。

定义

函数的格式是固定的
Func + 函数名 + 参数 + 返回值(可选) + 函数体

Func main( a, b int) (int) {
}

而方法会在方法在func关键字后是接收者而不是函数名,接收者可以是自己定义的一个类型,这个类型可以是struct,interface,甚至我们可以重定义基本数据类型。不过需要注意的是接收者是指针和非指针的区别,我们可以看到当接收者为指针式,我们可以通过方法改变该接收者的属性,但是非指针类型缺做不到。

func (p myint) mysquare() int {  
    p = p * p  
    fmt.Println("mysquare p = ", p)  
    return 0  
}  

 函数

函数的值(闭包)
在Go中,函数被看作第一类值(first-class values):函数像其他值一样,拥有类型,可以被赋值给其他变量,传递给函数,从函数返回。函数类型的零值是nil。调用值为nil的函数值会引起panic错误:
var f func(int) intf(3) // 此处f的值为nil, 会引起panic错误
函数值不仅仅是一串代码,还记录了状态。Go使用闭包(closures)技术实现函数值,Go程序员也把函数值叫做闭包。我们看个闭包的例子

func f1(limit int) (func(v int) bool) {
//编译器发现limit逃逸了,自动在堆上分配
return func (limit int) bool { return v>limit}
}
func main() {
closure := f1(5)
fmt.Printf("%v\n", closure(1)) //false
fmt.Printf("%v\n", closure(5)) //false
fmt.Printf("%v\n", closure(10)) //true
}

在这个例子中,f1函数传入limit参数,返回一个闭包,闭包接受一个参数v,判断v是否大于之前设置进去的limit。

2 可变参数列表

在go中函数提供可变参数,对那些封装不确定参数个数是一个不错的选择。声明如下
func 函数名(变量名...类型) 返回值

package main
import (
"fmt"
) func f1(name string, vals... int) (sum int) {
for _, v := range vals {
sum += v
}
sum += len(name)
return
}
func main() {
fmt.Printf("%d\n", f1("abc", 1,2,3,4 )) //13
}

在函数中提供延迟执行即 defer
包含defer语句的函数执行完毕后(例如return、panic),释放堆栈前会调用被声明defer的语句,常用于释放资源、记录函数执行耗时等,有一下几个特点:
当defer被声明时,其参数就会被实时解析
执行顺序和声明顺序相反
defer可以读取有名返回值
运用最典型的场景及关闭资源,如操作文件,数据库操作等。如下例子

func do() error {
f, err := os.Open("book.txt")
if err != nil {
return err
}
defer func(f io.Closer) {
if err := f.Close(); err != nil {
// log etc
}
}(f) // ..code...
f, err = os.Open("another-book.txt")
if err != nil {
return err
}
defer func(f io.Closer) {
if err := f.Close(); err != nil {
// log etc
}
}(f) return nil
}

 

异常panic

在开始闭包中提到过返回panic,那什么是panic。Go有别于那些将函数运行失败看作是异常的语言。虽然Go有各种异常机制,但这些机制仅仅用于严重的错误,而不是那些在健壮程序中应该被避免的程序错误。runtime在一些情况下会抛出异常,例如除0,我们也能使用panic关键字自己抛出异常。
出现异常,默认程序退出并打印堆栈。如下函数

package main
func f6() {
func () {
func () int {
x := 0
y := 5/x
return y
}()
}()
}
func main() { f6()
}

如果不想程序退出的话,也有办法,就是使用recover捕捉异常,然后返回error。在没发生panic的情况下,调用recover会返回nil,发生了panic,那么就是panic的值。看个例子:

package main
import (
"fmt"
) type shouldRecover struct{}
type emptyStruct struct{}
func f6() (err error) {
defer func () {
switch p := recover(); p {
case nil: //donoting
case shouldRecover{}:
err = fmt.Errorf("occur panic but had recovered")
default:
panic(p)
}
} () func () {
func () int {
panic(shouldRecover{})
//panic(emptyStruct{})
x := 0
y := 5/x
return y
}()
}() return
} func main() {
err := f6()
if err != nil {
fmt.Printf("fail %v\n", err)
} else {
fmt.Printf("success\n")
}
}

 方法

package main
import (
"fmt"
) type Employee struct {
name string
salary int
currency string
}
/*
displaySalary() method has Employee as the receiver type
*/
func (e Employee) displaySalary() {
fmt.Printf("Salary of %s is %s%d", e.name, e.currency, e.salary)
}
func main() {
emp1 := Employee{
name: "Sam Adolf",
salary: 5000,
currency: "$",
}
emp1.displaySalary() //Calling displaySalary() method of Employee type
}

 也许有人会问,方法和函数差不多,为什么还要多此一举使用方法呢?

  • Golang 不是一个纯粹的面向对象的编程语言,它不支持类。因此通过在一个类型上建立方法来实现与 class 相似的行为。
  • 同名方法可以定义在不同的类型上,但是 Golang 不允许同名函数。假设有两个结构体 Square 和 Circle。在 Square 和 Circle 上定义同名的方法是合法的。

如下一个函数就很明了了

package main
import (
"fmt"
"math"
) type Rectangle struct {
length int
width int
} type Circle struct {
radius float64
} func (r Rectangle) Area() int {
return r.length * r.width
} func (c Circle) Area() float64 {
return math.Pi * c.radius * c.radius
}
func main() {
r := Rectangle{
length: 10,
width: 5,
}
fmt.Printf("Area of rectangle %d\n", r.Area())
c := Circle{
radius: 12,
}
fmt.Printf("Area of circle %f", c.Area())
}

值接收者和指针接收者

两者区别在于,以指针作为接收者时,方法内部进行的修改对于调用者是可见的,但是以值作为接收者却不是。

package main
import (
"fmt"
) type Employee struct {
name string
age int
}
/*
Method with value receiver
*/
func (e Employee) changeName(newName string) {
e.name = newName
}
/*
Method with pointer receiver
*/
func (e *Employee) changeAge(newAge int) {
e.age = newAge
} func main() {
e := Employee{
name: "Mark Andrew",
age: 50,
}
fmt.Printf("Employee name before change: %s", e.name)
e.changeName("Michael Andrew")
fmt.Printf("\nEmployee name after change: %s", e.name) fmt.Printf("\n\nEmployee age before change: %d", e.age)
(&e).changeAge(51)
fmt.Printf("\nEmployee age after change: %d", e.age)
}

上面的程序中, changeName 方法有一个值接收者 (e Employee),而 changeAge 方法有一个指针接收者 (e *Employee)。在 changeName 中改变 Employee 的 name 的值对调用者而言是不可见的,因此程序在调用 e.changeName("Michael Andrew") 方法之前和之后,打印的 name 是一样的。而 changeAge 的接受者是一个指针 (e *Employee),因此通过调用方法 (&e).changeAge(51) 来修改 age 对于调用者是可见的。
使用 (&e).changeAge(51) 来调用 changeAge 方法不是必须的,Golang 允许我们省略 & 符号,因此可以写为 e.changeAge(51)。Golang 将 e.changeAge(51) 解析为 (&e).changeAge(51)。

非结构体类型的方法

现在我们定义的都是结构体类型的方法,同样可以定义非结构体类型的方法,不过需要注意一点。为了定义某个类型的方法,接收者类型的定义与方法的定义必须在同一个包中。

package main
import "fmt"
type myInt int
func (a myInt) add(b myInt) myInt {
return a + b
}
func main() {
num1 := myInt(5)
num2 := myInt(10)
sum := num1.add(num2)
fmt.Println("Sum is", sum)
}

在函数和方法中都会接收值参数和指针参数,那么两者又有什么却别?

方法的值接收者和函数的值参数

当一个函数有一个值参数时,它只接受一个值参数。
当一个方法有一个值接收者时,它可以接受值和指针接收者。
如下一个例子

package main
import (
"fmt"
) type rectangle struct {
length int
width int
}
func area(r rectangle) {
fmt.Printf("Area Function result: %d\n", (r.length * r.width))
} func (r rectangle) area() {
fmt.Printf("Area Method result: %d\n", (r.length * r.width))
}
func main() {
r := rectangle{
length: 10,
width: 5,
}
area(r)
r.area() p := &r
/*
compilation error, cannot use p (type *rectangle) as type rectangle
in argument to area
*/
//area(p) //会报错 p.area() //calling value receiver with a pointer
}

我们创建了一个指向 r 的指针 p。如果我们试图将这个指针传递给只接受值的 area 函数那么编译器将报错。
p.area() 使用指针接收者 p 调用一个值接收者方法 area 。这是完全合法的。原因是对于 p.area(),由于 area 方法必须接受一个值接收者,所以 Golang 将其解析为 (*p).area()。

方法的指针接收者和函数的指针参数

具有指针参数的函数将仅接受指针,而具有指针接收者的方法将接受值和指针接收者

package main
import (
"fmt"
) type rectangle struct {
length int
width int
}
func perimeter(r *rectangle) {
fmt.Println("perimeter function output:", 2*(r.length+r.width)) } func (r *rectangle) perimeter() {
fmt.Println("perimeter method output:", 2*(r.length+r.width))
}
func main() {
r := rectangle{
length: 10,
width: 5,
}
p := &r //pointer to r
perimeter(p)
p.perimeter() /*
cannot use r (type rectangle) as type *rectangle in argument to perimeter
*/
//perimeter(r) r.perimeter() //calling pointer receiver with a value }

试图以一个值参数 r 调用 perimeter 函数,这是非法的。因为一个接受指针为参数的函数不能接受一个值作为参数。如果去掉注释,则编译报错。

通过一个值接收者 r 调用一个指针接收者 perimeter 方法,这是合法的。r.perimeter() 将被 Golang 解析为 (&r).perimeter()。

 

golang 函数和方法的更多相关文章

  1. Golang 函数以及函数和方法的区别

    在接触到go之前,我认为函数和方法只是同一个东西的两个名字而已(在我熟悉的c/c++,python,java中没有明显的区别),但是在golang中者完全是两个不同的东西.官方的解释是,方法是包含了接 ...

  2. Golang 函数 方法 接口的简单介绍

    函数 函数是基本的代码块,通常我们会将一个功能封装成一个函数,方便我们调用,同时避免代码臃肿复杂. 函数的基本格式 func TestFunc(a int, b string) (int, strin ...

  3. Go - 反射中 函数 和 方法 的调用 - v.Call()

    上一篇文章 说到了 Golang 中的反射的一些基本规则,重点就是文章中最后的三点,但是这篇文章并没有说如何在反射中调用函数和方法,这就是接下来要说的. 反射中调用 函数 众所周知,Golang 中的 ...

  4. Go语言学习笔记(7)——函数和方法

    Go语言中同时有函数和方法! 函数: go程序必须要包含一个main函数.main函数不能有任何参数和返回值! 1. 定义方法示例: func max(num1, num2 int) int { // ...

  5. Golang通脉之方法

    方法和接收者 Go语言中的方法(Method)是一种作用于特定类型变量的函数.这种特定类型变量叫做接收者(Receiver).接收者的概念就类似于其他语言中的this或者 self. Go 语言中同时 ...

  6. Matlab中函数定义方法

    Matlab自定义函数的六种方法 n1.函数文件+调用函数(命令)文件:需单独定义一个自定义函数的M文件: n2.函数文件+子函数:定义一个具有多个自定义函数的M文件: n3.Inline:无需M文件 ...

  7. Oracle数据库中调用Java类开发存储过程、函数的方法

    Oracle数据库中调用Java类开发存储过程.函数的方法 时间:2014年12月24日  浏览:5538次 oracle数据库的开发非常灵活,不仅支持最基本的SQL,而且还提供了独有的PL/SQL, ...

  8. String类中的一些函数使用方法

    最常用的就是Length()函数了,求字符串的长度 String s="";int i=s.length();i结果为0. 如果是String s=null;int i=s.len ...

  9. FastReport里面正确调用函数的方法

    FastReport里面正确调用函数的方法   错误:  [FormatDateTime('yyyy-mm-dd',[frxDBDataset1."日期"])] --------- ...

随机推荐

  1. crm2013js弹出窗口

  2. [LeetCode系列]BST有效性确定问题[前序遍历]

    给定一个BST的根节点, 试判断此BST是否为符合规则的BST? 规则: 对于一个BST的节点, 它左侧的所有节点(包括子节点)必须小于它本身; 它右侧的所有节点(包括子节点)必须大于它本身; 它的左 ...

  3. XAMPP服务器在局域网只能本机访问且无法用IP访问的解决办法 (转)

    XAMPP服务器在局域网只能本机访问且无法用IP访问的解决办法 前几天安装了xampp for pc 1.7.4版本. 装好后在本地电脑通过https://localhost访问正常. 然后换了台电脑 ...

  4. dwr的A request has been denied as a potential CSRF attack.错误

    虽然DWR是个很早就出现的Ajax框架,但一直都没去使用过,今天正好没事就看了一下并参照文档照做了个demo, 在其中碰到一个问题: 后台打印出错误信息:“严重: A request has been ...

  5. NumPy-快速处理数据--矩阵运算

    本文摘自<用Python做科学计算>,版权归原作者所有. 1. NumPy-快速处理数据--ndarray对象--数组的创建和存取 2. NumPy-快速处理数据--ndarray对象-- ...

  6. MQTT的知识点

    问题一:单片机缓存有限,处理能力有限的情况下,消息不可能一次发出,这种情况下要怎样通过MQTT发布消息? 先组装publish协议的头,里面写好payload的长度,通过tcp发出去,然后一点一点发p ...

  7. Windows下安装HBase

    本文转载自:http://blog.csdn.net/kangkanglou/article/details/30748139 本文主要参照Hbase官网:http://hbase.apache.or ...

  8. Nginx.PHP配置Smarty

    下载http://smarty.net: 解压 -> 将 libs 文件夹重命名 smartyLibs -> 放置在自己服务器的 usr/local/lib/ 中 (/usr/local/ ...

  9. 列表:list[1],切片list[1:3],追加insert,修改,删除remove,del,pop,查找index,统计count,清空list.clear() 翻转list.reverse(),排序list.sort(),扩展list.extend,

    列表的定义: 列表的使用以及取值:用逗号的方式,取列表两个值,会打印出2个项目,两个项目之间自动有一个空格. 如果想取中间几个值: 请注意,如果取值1和2,那么要写[1,3],要记住这里是顾头不顾尾. ...

  10. Nginx 之:nginx.conf结构

    nginx.conf文件结构如下: ..... events { ...... } http{ .... server{ .... } server{ listen  192.168.0.12:80; ...