博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Go基础学习四之函数function、结构struct、方法method
阅读量:6068 次
发布时间:2019-06-20

本文共 9951 字,大约阅读时间需要 33 分钟。

Go编程语言:支持并发、垃圾回收的编译型系统级编程语言!本文主要是按照无闻的《Go 编程基础》开源视频学习并记录笔记。

一、函数function

1、基本概念

函数是基本的代码块,用于执行一个任务。

Go 语言最少有个 main() 函数
函数声明告诉了编译器函数的名称,返回类型,和参数
Go 语言标准库提供了多种可动用的内置的函数。例如,len() 函数可以接受不同类型参数并返回该类型的长度。如果我们传入的是字符串则返回字符串的长度,如果传入的是数组,则返回数组中包含的函数个数。

2、函数定义

函数定义格式如下:

func function_name( [parameter list] ) [return_types] {   函数体}

函数定义解析:

  • func:函数由 func 开始声明
  • function_name:函数名称,函数名和参数列表一起构成了函数签名。
  • parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  • return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
  • 函数体:函数定义的代码集合。

示例:

/* 函数返回两个数的最大值 */func max(num1, num2 int) int {   /* 声明局部变量 */   var result int   if (num1 > num2) {      result = num1   } else {      result = num2   }   return result }

3、函数特性

  • Go 函数不支持嵌套、重载和默认参数
  • Go 函数支持无需声明原型、不定长度变参、多返回值、命名返回值参数、匿名函数、闭包
  • 定义函数使用关键字func,且左大括号不能另起一行
  • 函数也可以作为一种类型使用
ackage mainimport "fmt"  func main() {  A(1, 2, 3, 4 ,5)}// ... 不定长变参func A(a ...int) {    fmt.Println(a)    }

输出:

➜  myfirstgo go run func.go[1 2 3 4 5]

不定长变参特性:

1、不可以在不定长变参后边添加其他参数 func b(a ...int, b string), 这种写法是错误
2、不定长参数可以放在其他参数后边 func b(b string, a ...int)

4、匿名函数

func main() {  // 将一个函数赋值一个变量,该变量是函数类型  a := func(){    fmt.Println("匿名函数")}  // 调用  a()}

5、闭包

/*** 闭包函数** 该闭包函数接收一个int型参数,其返回值是函数类型**/func closure(x int) func(int) int {    fmt.Println("%p\n", &x)    return func (y int) int {        fmt.Println("%p\n", &x)        fmt.Println(x)        fmt.Println(y)            return x + y    }}func main() {  f := closure(10);  fmt.Println(f(1))  fmt.Println(f(2))}

打印结果:

➜  myfirstgo go run func.go%p 0xc42000e228%p 0xc42000e22810111%p 0xc42000e22810212➜  myfirstgo

6、defer

  • defer 的执行方式类似其他语言中的析构函数,在函数体执行结束后按照调用顺序的相反顺序逐个执行
  • 即使函数发生严重错误也会执行
  • 支持匿名函数的调用
  • 常用于资源清理、文件关闭、解锁以及记录时间等操作
  • 通过与匿名函数配合可在return之后修改函数计算结果
  • 如果函数体内某个变量作为defer时匿名函数的参数,则在定义defer时即已经获得了拷贝,否则则是引用某个变量的地址
  • Go没有异常机制,但有 panic/recover 模式来处理错误
  • Panic 可以在任何地方引发,但 recover 只有defer 调用的函数中有效

简单的测试:

func main() {  fmt.Println("a")    defer fmt.Println("b")  defer fmt.Println("c")}

上边的执行会打印什么结果呢? 会打印:a b c 吗?

让我们实际执行一下:

myfirstgo go run func.goacb

实际打印的结果为:a c b

defer 的执行方式类似其他语言中的析构函数,在函数体执行结束后按照调用顺序的相反顺序逐个执行

使用闭包

func main() {  for i := 0; i < 3; i++ {       // defer 普通调用       // defer fmt.Println(i)  // 打印 2 1 0     // 使用闭包,引用局部变量       defer func () {           fmt.Println(i)       }()  }  }

打印结果:

➜  myfirstgo go run func.go333

panic 使用示例

func main() {  A()  B()  C()}func A() {    fmt.Println("FUNC A")}func B() {   // 匿名函数,如果没有参数,则末尾需要使用括号    defer func() {        if err := recover(); err != nil {            fmt.Println("Recover is B")        }            }()    panic("B panic")}func C() {    fmt.Println("FUNC C")}

打印结果:

➜  myfirstgo go run func.goFUNC ARecover is BFUNC C➜  myfirstgo

二、结构Struct

1.基本概念

Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型

结构体由一系列具有相同类型或不同类型的数据构成的数据集合
结构体表示一项记录,比如保存图书馆的书籍记录,每本书有以下属性:
Title :标题
Author : 作者
Subject:学科
ID:书籍ID

2.结构体定义

结构体定义需要使用 typestruct 语句。struct 语句定义一个新的数据类型,结构体中有一个或多个成员。type 语句设定了结构体的名称。结构体的格式如下:

type struct_variable_type struct {   member definition;   member definition;   ...   member definition;}

一旦定义了结构体类型,它就能用于变量的声明,语法格式如下:

variable_name := structure_variable_type {value1, value2...valuen}

type 是定义名称,struct 是结构体类型,如同 int 类型一样。

结构体可以包含多种数据类型,数组只能是单一类型的数据集合。

访问结构体成员

如果要访问结构体成员,需要使用点号 (.) 操作符,格式为:"结构体.成员名"
结构体类型变量使用struct关键字定义,实例如下:

package mainimport "fmt"type Books struct {   title string   author string   subject string   book_id int}func main() {   var Book1 Books        /* 声明 Book1 为 Books 类型 */   var Book2 Books        /* 声明 Book2 为 Books 类型 */   /* book 1 描述 */   Book1.title = "Go 语言"   Book1.author = "www.runoob.com"   Book1.subject = "Go 语言教程"   Book1.book_id = 6495407   /* book 2 描述 */   Book2.title = "Python 教程"   Book2.author = "www.runoob.com"   Book2.subject = "Python 语言教程"   Book2.book_id = 6495700   /* 打印 Book1 信息 */   fmt.Printf( "Book 1 title : %s\n", Book1.title)   fmt.Printf( "Book 1 author : %s\n", Book1.author)   fmt.Printf( "Book 1 subject : %s\n", Book1.subject)   fmt.Printf( "Book 1 book_id : %d\n", Book1.book_id)   /* 打印 Book2 信息 */   fmt.Printf( "Book 2 title : %s\n", Book2.title)   fmt.Printf( "Book 2 author : %s\n", Book2.author)   fmt.Printf( "Book 2 subject : %s\n", Book2.subject)   fmt.Printf( "Book 2 book_id : %d\n", Book2.book_id)}

以上实例执行运行结果为:

Book 1 title : Go 语言Book 1 author : www.runoob.comBook 1 subject : Go 语言教程Book 1 book_id : 6495407Book 2 title : Python 教程Book 2 author : www.runoob.comBook 2 subject : Python 语言教程Book 2 book_id : 6495700

3.struct特性

  • Go中的struct与C中的struct非常相似,并且Go没有class
  • 使用 type <Name> struct{} 定义结构,名称遵循可见性规则
  • 支持指向自身的指针类型成员
  • 支持匿名结构,可用作成员或定义成员变量
  • 匿名结构也可以用于map的值
  • 可以使用字面值对结构进行初始化
  • 允许直接通过指针来读写结构成员
  • 相同类似的成员可进行直接拷贝赋值
  • 支持 == 与 != 比较运算符,但不支持 > 或 <
  • 支持匿名字段,本质上是定义了以某个类型名为名称的字段
  • 嵌入结构作为匿名字段看起来像继承,但不是继承
  • 可以使用匿名字段指针

示例:

package mainimport "fmt"  type person struct{    Name string    Age int}func main() {  a := person{      Name:"Jam",      Age:19,  }  // a.Age = 20  fmt.Println(a)}

打印:

➜  myfirstgo go run struct.go{Jam 19}

传递指针变量:

package mainimport "fmt"  type person struct{    Name string    Age int}func main() {  a := person{      Name:"Jam",      Age:19,  }  // a.Age = 20  fmt.Println(a)  A(a)  // 值拷贝,如果需要原来的改变,则需要添加指针  B(&a)  fmt.Println(a)}// per 为变量名,person表示为结构体类型func A(per person) {    per.Age = 25    fmt.Println("A", per)}// per 为变量名,person表示为结构体类型func B(per *person) {    per.Age = 18    fmt.Println("B", per)}

打印:

➜  myfirstgo go run struct.go{Jam 19}A {Jam 25}B &{Jam 18}{Jam 18}

或者在初始化结构体时,获取到变量地址并赋值变量,这样做的好处是在传递参数时,不需要传递地址符号了,只需在函数定义时,给参数加星号即可。

package mainimport "fmt"  type person struct{    Name string    Age int}func main() { // 在结构初识化时,我们习惯取地址符号,这样a就为指向某个结构的指针  a := &person{      Name:"Jam",      Age:19,  }  a.Name = "Corwien"  // a.Age = 20  fmt.Println(a)  A(a)  // 值拷贝,如果需要原来的改变,则需要添加指针  B(a)  fmt.Println(a)}// per 为变量名,person表示为结构体类型func A(per *person) {    per.Age = 25    fmt.Println("A", per)}// per 为变量名,person表示为结构体类型func B(per *person) {    per.Age = 18    fmt.Println("B", per)}

打印:

➜  myfirstgo go run struct.go&{Corwien 19}A &{Corwien 25}B &{Corwien 18}&{Corwien 18}

匿名结构:

匿名结构,没有名称的结构体

func main() {// 匿名结构,没有名称的结构体  a := struct {      Name string      Age int  }{      Name:"Corwien",      Age: 20,  }  fmt.Println(a)}

打印:

➜  myfirstgo go run struct.go{Corwien 20}

匿名结构嵌套:

type person struct{    Name string    Age int    Contact struct {        Phone, City string        Code int           // 门牌号    }}func main() {    a := person{Name:"Corwien", Age:15}    a.Contact.Phone = "10086"    a.Contact.City = "Guangzhou"    a.Contact.Code = 2007      fmt.Println(a)}

打印:

➜  myfirstgo go run struct.go{Corwien 15 {10086 Guangzhou 2007}}

匿名字段:

匿名字段:结构体没有命名结构体属性的字段,只有类型,匿名字段必须严格遵守字段类型声明的顺序。

type person struct{    string    int}func main() {    // 匿名字段必须严格遵守字段类型声明的顺序    a := person{"Corwien", 12}        fmt.Println(a)}

打印:

➜  myfirstgo go run struct.go{Corwien 12}

结构类型比较

type person struct{    Name string    Age int}func main() {    // 匿名字段必须严格遵守字段类型声明的顺序    a := person{Name:"Corwien", Age:12}    b := person{Name:"Corwien", Age:12}        fmt.Println(a == b)}

打印:

➜  myfirstgo go run struct.gotrue

Go如何继承呢

我们知道其他语言有继承,比如相同的属性,我们不必重复去写,只需继承父类的公共属性即可。遗憾的是Go没有继承,但Go有组合.

package mainimport "fmt"  // 嵌入结构作为匿名字段type human struct {    Sex int}type teacher struct {    human            // Go会将嵌入字段默认作为属性名,所以在赋值时需要这样写:human: human{Sex: 1}    Name string    Age int}type student struct {    human    Name string    Age int}func main() {    a := teacher{Name:"Corwien", Age:25, human: human{Sex: 1}}    b := student{Name:"mark", Age:12, human: human{Sex: 1}}        a.Name = "Jack"    a.Age = 10    // a.human.Sex = 0    a.Sex = 0    fmt.Println(a, b)}

打印:

➜  myfirstgo go run struct.go{
{0} Jack 10} {
{1} mark 12}

三、方法method

1.基本概念

Go不像其它面相对象语言一样可以写个class,然后在class里面写一堆方法,但是它也很巧妙的实现了这种效果,我们只需要在普通函数前面加个接受者(receiver,写在函数名前面的括号里面),这样编译器就知道这个函数(方法)属于哪个struct了

method是附属在一个给定的类型上,语法和函数的声明语法几乎一样,只是再func后面增加了一个recevier(也就是method所依从的主体)

2.method定义

func (r ReceiverType) funcName(parameters) (results)

形象一点说,就是 ReceiverType 类型的所有字段,方法 funcName 都是可以使用的,可以认为 funcName 属于 ReceiverType

示例:

package mainimport (    "fmt"    "math")type Rectangle struct {    width, height float64}type Circle struct {    radius float64}func (r Rectangle) area() float64 {    return r.width * r.height}func (c Circle) area() float64 {    return c.radius * c.radius * math.Pi}func main() {    r1 := Rectangle{12, 2}    r2 := Rectangle{9, 4}    c1 := Circle{10}    c2 := Circle{25}    fmt.Println("Area of r1 is: ", r1.area())    fmt.Println("Area of r2 is: ", r2.area())    fmt.Println("Area of c1 is: ", c1.area())    fmt.Println("Area of c2 is: ", c2.area())}

输出:

Area of r1 is:  24Area of r2 is:  36Area of c1 is:  314.1592653589793Area of c2 is:  1963.4954084936207

method 是通过 . 来访问,就像访问struct里面字段一样。

method 里面可以访问接受者的字段,比如 r1.area() 就可以访问 r1 里面的 width 和 height。

虽然 method 的名字是一样的,但是不同的 receiver 不一样,那么 method 就不一样。这一点很重要哦

还有一点,method不仅能作用再struct上,也可以定义再任何自定义的类型、内置类型等各种类型上面。

method 中的 receiver 可以是值传递,也可以是指针。指针的话,就可以直接修改 receiver 中的内容。

3.method特性

  • Go中虽没有class,但依旧有method
  • 通过显示说明receiver来实现与某个类型的组合
  • 只能为同一个包中的类型定义方法
  • Receiver可以是类型的值或者指针
  • 不存在方法重载
  • 可以使用值或指针来调用方法,编译器会自动完成转换
  • 从某种意义上来说,方法是函数的语法糖,因为receiver其实就是方法所接收的第1个参数)
  • 如果外部结构和嵌入结构存在同名方法,则优先调用外部结构的方法
  • 类型别名不会拥有底层类型所附带的方法
  • 方法可以调用结构中的非公开字段

举例:

package mainimport "fmt"  type A struct {    Name string}type B struct {    Name string}func main() {    a := A{}    a.Print()    fmt.Println(a.Name)    b := B{}    b.Print()    fmt.Println(b.Name)}// 指针传递func (a *A) Print() {    a.Name = "AA"    fmt.Println("A")}func (b B) Print() {    b.Name = "BB"    fmt.Println("B")}

打印:

➜  myfirstgo go run method.goAAAB➜  myfirstgo

其他类型的方法绑定

type TZ intfunc main() {    var a TZ    a.Print()    fmt.Println(a)}func (a *TZ) Print() {    fmt.Println("TZ")}

打印:

➜  myfirstgo go run method.goTZ0

最后说下访问权限,因为Go是以大小写来区分是公有还是私有,但都是针对包级别的,所以在包内所有的都能访问,而方法绑定本身只能绑定包内的类型,所以方法可以访问接收者所有成员。如果是包外调用某类型的方法,则需要看方法名是大写还是小写,大写能被包外访问,小写只能被包内访问。

转载地址:http://xxfgx.baihongyu.com/

你可能感兴趣的文章
临时设置jdk环境变量
查看>>
ext
查看>>
Android——NDK基础概念——Application.mk文件介绍
查看>>
centos 下安装nginx
查看>>
一个mysql多列索引的问题
查看>>
Jsp乱码问题
查看>>
C#根据IP地址和子网掩码计算广播地址
查看>>
EasyExcel使用记录
查看>>
Cloudify源码解读
查看>>
无锡格瑞驰车业 ipad做汽车导航
查看>>
C Primer Plus 第5章 运算符、表达式和语句 5.3 其他运算符
查看>>
Swift 和 Objective-C 混编后对ipa包大小的影响
查看>>
通用户权限管理设计_Index
查看>>
Win2008 R2 IIS7.5+PHP5(FastCGI)+MySQL5环境搭建教程
查看>>
如何给VSFTP增加用户,只能访问指定目录
查看>>
http 错误代码表
查看>>
phalcon使用namespace
查看>>
centos5.8 安装tomcat7、solr4.9
查看>>
Openbiz Cubi 快速应用开发向导
查看>>
mysql 从一个表中查数据,插入另一个表
查看>>