一、go语言——函数
在Go语言中,函数是一等(first-class)类型。我们可以把函数作为值来传递和使用。函数代表着这样一个过程:它接受若干输入(参数),并经过一些步骤(语句)的执行之后再返回输出(结果)。特别的是,Go语言中的函数可以返回多个结果。
函数类型的字面量由关键字func、由圆括号包裹参数声明列表、空格以及可以由圆括号包裹的结果声明列表组成。其中,参数声明列表中的单个参数声明之间是由英文逗号分隔的。每个参数声明由参数名称、空格和参数类型组成。
参数声明列表中的参数名称是可以被统一省略的。结果声明列表的编写方式与此相同。结果声明列表中的结果名称也是可以被统一省略的。并且,在只有一个无名称的结果声明时还可以省略括号。
示例如下:
func(input1 string ,input2 string) string
这一类型字面量表示了一个接受两个字符串类型的参数且会返回一个字符串类型的结果的函数。
如果我们在它的左边加入type关键字和一个标识符作为名称的话,那就变成了一个函数类型声明,就像这样:
type MyFunc func(input1 string ,input2 string) string
函数值(或简称函数)的写法与此不完全相同。
编写函数的时候需要先写关键字func和函数名称,后跟参数声明列表和结果声明列表,最后是由花括号包裹的语句列表。例如:
func myFunc(part1 string, part2 string) (result string) {
result = part1 + part2
return
}
如果结果声明是带名称的,那么它就相当于一个已被声明但未被显式赋值的变量。我们可以为它赋值且在return语句中省略掉需要返回的结果值。该函数还有一种更常规的写法:
func myFunc(part1 string, part2 string) string {
return part1 + part2
}
注意,函数myFunc是函数类型MyFunc的一个实现。实际上,只要一个函数的参数声明列表和结果声明列表中的数据类型的顺序和名称与某一个函数类型完全一致,前者就是后者的一个实现。
我们可以声明一个函数类型的变量,如:
var splice func(string, string) string // 等价于 var splice MyFunc
然后把函数myFunc赋给它:
splice = myFunc
我们就可以在这个变量之上实施调用动作了:
splice("1", "2")
实际上,这是一个调用表达式。它由代表函数的标识符(这里是splice)以及代表调用动作的、由圆括号包裹的参数值列表组成。
如果你觉得上面对splice变量声明和赋值有些啰嗦,那么可以这样来简化它:
var splice = func(part1 string, part2 string) string {
return part1 + part2
}
我们直接使用了一个匿名函数来初始化splice变量。匿名函数就是不带名称的函数值。匿名函数直接由函数类型字面量和由花括号包裹的语句列表组成。注意,这里的函数类型字面量中的参数名称是不能被忽略的。
其实,我们还可以进一步简化——索性省去splice变量。
既然我们可以在代表函数的变量上实施调用表达式,那么在匿名函数上肯定也是可行的。因为它们的本质是相同的。后者的示例如下:
var result = func(part1 string, part2 string) string {
return part1 + part2
}("1", "2")
可以看到,在这个匿名函数之后的即是代表调用动作的参数值列表。注意,这里的result变量的类型不是函数类型,而与后面的匿名函数的结果类型是相同的。
最后,函数类型的零值是nil。这意味着,一个未被显式赋值的、函数类型的变量的值必为nil。
实例代码:
package main
import (
"fmt"
"strconv"
"sync/atomic"
)
//函数类型声明,三个参数列表,一个结果参数 员工ID生成器
type EmployeeIdGenerator func(company string, department string, sn uint32) string
// 默认公司名称
var company = "Gophers"
// 序列号
var sn uint32
// 生成员工ID,两个参数列表,返回两个参数值,函数类型作为参数类型
func generateId(generator EmployeeIdGenerator, department string) (string, bool) {
// 若员工ID生成器不可用,则无法生成员工ID,应直接返回。
if generator == nil {
return "", false
}
// 使用代码包 sync/atomic 中提供的原子操作函数可以保证并发安全。
newSn := atomic.AddUint32(&sn, 1)
return generator(company, department, newSn), true
}
// 字符串类型和数值类型不可直接拼接,所以提供这样一个函数作为辅助。
func appendSn(firstPart string, sn uint32) string {
return firstPart + strconv.FormatUint(uint64(sn), 10)
}
func main() {
//定义一个是函数类型的变量
var generator EmployeeIdGenerator
//定义一个EmployeeIdGenerator的生成器变量generator,匿名函数
generator = func(company string, department string, sn uint32) string {
return appendSn(company+"-"+department+"-", sn)
} //重新定义generator函数,在generateId中才真正地被调用
//将函数generator和"RD"传入函数generateId
fmt.Println(generateId(generator, "RD"))
}
二、go语言——结构体和方法
Go语言的结构体类型(Struct)比函数类型更加灵活。它可以封装属性和操作。属性即是结构体类型中的字段,而操作则是结构体类型所拥有的方法。
结构体类型的字面量由关键字type、类型名称、关键字struct,以及由花括号包裹的若干字段声明组成。其中,每个字段声明独占一行并由字段名称(可选)和字段类型组成。示例如下:
type Person struct {
Name string
Gender string
Age uint8
}
结构体类型Person中有三个字段,分别是Name、Gender和Age。我们可以用字面量创建出一个该类型的值,像这样:
Person{Name: "Robert", Gender: "Male", Age: 33}
可以看到,结构体值的字面量(或简称结构体字面量)由其类型的名称和由花括号包裹的若干键值对组成。注意,这里的键是其类型中的某个字段的名称(注意,它不是字符串字面量),而对应的值则是欲赋给该字段的那个值。
另外,如果这里的键值对的顺序与其类型中的字段声明完全相同的话,我们还可以统一省略掉所有字段的名称,就像这样:
Person{"Robert", "Male", 33}
我们在编写某个结构体类型的值字面量时可以只对它的部分字段赋值,甚至不对它的任何字段赋值。这时,未被显式赋值的字段的值则为其类型的零值。注意,这两种情况下,字段的名称是不能被省略的。
我们在编写一个结构体值的字面量时不需要先拟好其类型。这样的结构体字面量被称为匿名结构体。与匿名函数类似,我们在编写匿名结构体的时候需要先写明其类型特征(包含若干字段声明),再写出它的值初始化部分。如依据结构体类型Person创建一个匿名结构体:
p := struct {
Name string
Gender string
Age uint8
}{"Robert", "Male", 33}
匿名结构体最大的用处就是在内部临时创建一个结构以封装数据,而不必正式为其声明相关规则。而在涉及到对外的场景中,我强烈建议使用正式的结构体类型。
结构体类型可以拥有若干方法(注意,匿名结构体是不可能拥有方法的)。所谓方法,其实就是一种特殊的函数。它可以依附于某个自定义类型。方法的特殊在于它的声明包含了一个接收者声明。
这里的接收者指代它所依附的那个类型。我们仍以结构体类型Person为例。下面是依附于它的一个名为Grow的方法的声明:
func (person *Person) Grow() {
person.Age++
}
如上所示,在关键字func和名称Grow之间的那个圆括号及其包含的内容就是接收者声明。其中的内容由两部分组成。第一部分是代表它依附的那个类型的值的标识符。第二部分是它依附的那个类型的名称。后者表明了依附关系,而前者则使得在该方法中的代码可以使用到该类型的值(也称为当前值)。
代表当前值的那个标识符可被称为接收者标识符,或简称为接收者。请看下面的示例:
p := Person{"Robert", "Male", 33}
p.Grow()
我们可以直接在Person类型的变量p之上应用调用表达式来调用它的方法Grow。注意,此时方法Grow的接收者标识符person指代的正是变量p的值。这也是“当前值”这个词的由来。在Grow方法中,我们通过使用选择表达式选择了当前值的字段Age,并使其自增。因此,在语句p.Grow()被执行之后,p所代表的那个人就又年长了一岁(p的Age字段的值已变为34)。需要注意的是,在Grow方法的接收者声明中的那个类型是Person,而不是Person。实际上,Person是Person的指针类型。这也使得person指代的是p的指针,而不是它本身。
包含若干字段和方法的结构体类型就相当于一个把属性和操作封装在一起的对象。结构体类型(以及任何类型)之间都不可能存在继承关系。我们可以通过在结构体类型的声明中添加匿名字段(或称嵌入类型)来模仿继承。
结构体类型属于值类型。它的零值并不是nil,而是其中字段的值均为相应类型的零值的值。
举个例子,结构体类型Person的零值若用字面量来表示的话则为Person{}。
实例代码:
package main
import "fmt"
//定义结构体
type Person struct {
Name string
Gender string
Age uint8
Address string
}
//定义一个依附于Person的Move方法
//newaddr 的值是San Francisco
//person.Address的值是Beijing
//这个方法是交换了一下两个地址
func (person *Person) Move(newaddr string) string {
oldaddr := person.Address
person.Address = newaddr
return oldaddr
}
func main() {
//给结构体赋值
p := Person{"Robert", "Male", 33, "Beijing"}
oldAddress := p.Move("San Francisco")
fmt.Printf("%s moved from %s to %s.\n", p.Name, oldAddress, p.Address)
}
以上实例代码的运行效果:
分享先到这里,后续还会不断更新,请各位多多支持!