Golang 笔记 2 函数、结构体、接口、指针

1、函数

  Go中函数是一等(first-class)类型。咱们能够把函数看成值来传递和使用。Go中的函数能够返回多个结果。
  函数类型字面量由关键字func、由圆括号包裹声明列表、空格以及能够由圆括号包裹的结果声明列表组成。其中参数声明列表中的单个参数声明之间是由英文逗号分隔的。每一个参数声明由参数名称、空格和参数类型组成。参数声明列表中的参数名称是能够被统一省略的。结果声明列表的编写方式与此相同。结果声明列表中的结果也是能够被省略的,而且在只有一个无名称的结果声明时还能够省略括号。例:函数

func (input1 string, input2 string) string

  这一类型字面量表示了一个接受两个字符串类型的参数且会返回一个字符串类型的结果的函数。若是咱们在它的左边加入type关键字和一个标识符做为名称的话就变成了一个函数类型声明:ui

type MyFunc func(input1 string, input2 string) string

  函数值(或简称函数)的写法与此不彻底相同。编写函数的时候须要先写关键字func和函数名称,后跟参数声明列表和结果声明列表,最后是花括号包裹的语句列表。例如:atom

func myFunc(part1 string, part2 string) (result string) { result = part1 + part2 return }

  若是结果声明是带名称的,那么它就至关于一个已被声明但未被显示赋值的变量。咱们能够为它赋值且在return语句中省略掉须要返回的结果值。该函数还有一种更常规的写法:spa

func myFunc(part1 string, part2 string) string { return part1 + part2 }

  函数myFunc是函数类型MyFunc的一个实现。实际上,只要一个函数的参数声明列表和结果声明列表中的数据类型顺序和名称与某一个函数类型彻底一致,前者就是后者的一个实现。
  咱们能够声明一个函数类型的变量,如:指针

var splice func(string, string) string // 等价于var splice MyFunc

  而后把函数myFunc赋给它:code

splice = myFunc

  如此一来,咱们就能够在这个变量之上实施调用动做了:接口

splice("1", "2")

  实际上,这是一个调用表达式。上面的代码能够简化为:字符串

var splice = func(part1 string, part2 string) string { return part1 + part2 }

  在这个示例中,咱们直接使用了一个匿名函数来初始化splice变量。顾名思义,匿名函数就是不带名称的函数值。匿名函数直接由函数类型字面量和由花括号包裹的语句列表组成。注意,这里的函数类型字面量中的参数名称是不能被省略的。
  还能够进一步简化--省去splice变量。例:input

var result = func(part1 string, part2 string) string { return part1 + part2 }("1", "2");

  函数类型的零值是nil。编译器

2、结构体和方法

  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的指针,而不是它自己。   结构体类型属于值类型。它的零值并非nil。而是其中字段的值为相应类型的零值的值。结构体类型Person的灵值若用字面量来表示的话是Person{}

3、接口

  在Go语言中,一个接口类型老是表明着某一种类型(即全部实现它的类型)的行为。一个接口类型的声明一般会包含关键字type、类型名称、关键字interface以及由花括号包裹的若干方法声明。例:

type Animal interface { Grow() Move(string) string }

  接口类型中的方法声明是普通的方法声明的简化形式。它们只包括方法名称、参数声明列表和结果声明列表。其中的参数的名称和结果的名称均可以被省略。未省略应该是这样的:

Move(new string) (old string)

  若是一个数据类型所拥有的方法集合中包含了某一个接口类型中的全部方法声明的实现,那么就能够说这个数据类型实现了那个接口类型。所谓实现了一个接口中的方法是指,具备与该方法相同的声明而且添加了实现部分(由花括号包裹的若干条语句)。相同的方法声明意味着彻底一致的名称、参数类型列表和结果类型列表。其中,参数类型列表即为参数声明列表中除去参数名称的部分。一致的参数类型列表意味着其长度以及顺序的彻底相同。对于结果类型列表也是如此。   咱们无需在一个数据类型中声明它实现了哪一个接口。只要知足了"方法集合为超集"的条件,就创建了“实现”关系。   如今咱们已经认为*Person类型实现了Animal接口。可是Go语言编译器是否也这么认为呢?这显然须要一种显式的断定方法。在Go语言中,这种断定能够用类型断言来实现。不过,在这里,咱们不能在一个非接口类型的值上应用类型断言来断定它是否属于某一个接口类型。咱们必须先把前者转换成空接口类型的值。这就涉及到了Go的类型转换。
  Go语言的类型转换规则定义了是否可以以及怎样把一个类型的值转换另外一个类型的值。另外一方面,所谓空接口类型便是不包含任何方法声明的接口类型,用interface{}表示,常简称为空接口。Go中的包含预约义的任何数据类型均可以被看做是空接口的实现。咱们可直接使用类型转换表达式把一个*Person类型转换成空接口类型的值,例:

p := Person{"Robert", "Male", 33, "Beijing"} v := interface{}(&p)

  在类型字面量后跟由圆括号包裹的值(或可以表明它的变量、常量或表达式)就构成了一个类型转换表达式,意为将后者转换为前者类型的值。在这里,咱们把表达式,意为将后者转换为前者类型的值。在这里,咱们把表达式&p的求值结果转换成了一个空接口类型的值,并由变量v表明。表达式&p(&是取地址符)的求值结果是一个*Person类型的值,及p的指针。
  在这以后,咱们就能够在v上应用类型断言了,即:

h,ok := v.(Animal)

  类型断言表达式v.(Animal)的求值结果能够有两个。第一个结果是被转换后的那个目标类型(这里是Animal)的值,而第二个结果则是转换操做成功与否的标志。显然,ok表明了一个bool类型的值。它也是这里断定实现关系的重要依据。

4、指针

  指针操做涉及到两个操做符--&和*。这两个操做符均有多个用途。可是当它们做为地址操做符出现时,&的做用是取址,*的做用是取值。   当*出如今一个类型以前(如*Person和*[3]string)时就不能被看做是操做符了,而应该被视为一个符号。如此组合而成的标识符所表达的含义是做为第二部分的那个类型的指针类型。咱们也能够把其中的第二部分所表明的类型称为基底类型。   如今后头看结构体类型Person。它及其两个方法的完整声明以下:

type Person strcut {
    Name    string Gender string Age uint8 Address string } func (person *Person) Grow() { person.Age++ } func (person *Person) Move(newAddres string) string { old := person.Address person.Address = newAddress return old }

  注意,Person的两个方法Grow和Move的接收者类型都是*Person,而不是Person。只要一个方法的接收者类型是其所属类型的指针类型而不是该类型自己,那么就能够称该方法为一个指针方法。上面的Grow方法和Move方法都是Person类型的指针方法。
  相对的,若是一个方法的接收者类型就是其所属的类型自己。那么咱们就能够把它叫作值方法。

func (person Person) Grow() { person.Age++ }

  方法的接收者标识符所表明的是该方法当前所属的那个值的一个副本,而不是该值自己。   之因此表达式person.Age成立,是由于若是Go语言发现person是指针而且指向的那个值有Age字段,那么就会把该表达式视为(*person).Age。

  拥有指针方法Grow和Move的指针类型*Person是接口类型Animal的实现类型,可是它的基底类型Person却不是。这样的表象隐藏了另外一条规则:一个指针类型拥有以它以及以它的基底类型为接收者类型的全部方法,而它的基底类型却至拥有以它自己为接收者类型的方法。

  咱们在基底类型的值上仍然能够调用它的指针方法。例如,若咱们有一个Person类型的变量bp,则调用表达式bp.Grow()是合法的。这是由于,若是Go语言发现咱们调用的Grow方法是bp的指针方法,那么它会把调用表达式是为(&bp).Grow()。

相关文章
相关标签/搜索