一、函数
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
然后把函数myFunc赋给它:
splice = myFunc
如此一来,我们就可以在这个变量之上实施调用动作了:
splice("1", "2")
实际上,这是一个调用表达式。上面的代码可以简化为:
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");
函数类型的零值是nil。
二、结构体和方法
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指代的是p的指针,而不是它