头条真实面试题

下面代码会输出什么?

package main


import "fmt"


type People struct{}


func (p *People) ShowA() {
fmt.Println("showA")
p.ShowB()
}


func (p *People) ShowB() {
fmt.Println("showB")
}


type Teacher struct {
People
}


func (t *Teacher) ShowB() {
fmt.Println("teachershowB")
}


func main() {
t := Teacher{}
t.ShowA()
}
函数

Go 语言最少有个 main() 函数。

函数声明告诉了编译器函数的名称,返回类型,和参数。

不支持重载,一个包不能有两个名字一样的函数。

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


命名返回值的名字(return可以不指定变量):

func add(a, b int) (c int) {
c = a + b
return
}
func calc(a, b int) (sum int, avg int) {
sum = a + b
avg = (a +b)/2
return
}


_标识符,用来忽略返回值:

func calc(a, b int) (sum int, avg int) {
sum = a + b
avg = (a +b)/2
return}
func main() {
sum, _ := calc(100, 200)
}


函数也是一种类型,一个函数可以赋值给变量

package main


import "fmt"


//申明一个函数类型
type add_func func(int, int) int


func add(a, b int) int {
return a + b
}


func operator(op add_func, a int, b int) int {
return op(a, b)
}


func main() {
c := add
fmt.Println(c) //0x1087050
sum := operator(c, 1, 2)
fmt.Println(sum) //300
}



可变参数

其中arg是一个slice,我们可以通过arg[index]依次访问所有参数;通过len(arg)来判断传递参数的个数。

0个或多个参数
func add(arg…int) int {
}
1个或多个参数
func add(a int, arg…int) int {
}
2个或多个参数
func add(a int, b int, arg…int) int {
}



main & init & defer

main & init

init() 方法是在任何package中都可以出现;

main() 方法只能用在package main 中。
Go程序会自动调用init()和main(),所以你不需要在任何地方调用这两个函数。

每个package中的init函数都是可选的,但package main就必须包含一个main函数。

main()函数不能带参数,也不能定义返回值。命令行传入的参数在os.Args变量中保存。如果需要支持命令行开关,可使用flag包。 

 

defer

  1. 当函数返回时,执行defer语句;

  2. 多个defer语句,按先进后出的方式执行;

  3. defer语句中的变量,在defer声明时确定变量。

  4. 触发异常也会走defer语句。


package main


import (
"fmt"
)


func main() {
defer_call()
}


func defer_call() {
defer func() { fmt.Println("打印前") }()
defer func() { fmt.Println("打印中") }()
defer func() { fmt.Println("打印后") }()


panic("触发异常")
}



作用域

  1. 在函数内部声明的变量叫做局部变量,生命周期仅限于函数内部。

  2. 在函数外部声明的变量叫做全局变量,生命周期作用于整个包,如果是大写的,则作用于整个程序。

package main


import "fmt"


var name string


func main() {
name = "Nick"
fmt.Println(name)
f1()
}
func f1() {
name := "Dawn"
fmt.Println(name)
f2()
}
func f2() {
fmt.Println(name)
}


输出:
Nick
Dawn
Nick



匿名函数 & 闭包

匿名函数

匿名函数是由一个不带函数名的函数声明和函数体组成。

package main


func main() {


f := func(x, y int) int {
return x + y
}
f(1,1)


ch := make(chan int)
func (ch chan int) {
ch <- 9
}(ch)
}



闭包

闭包是一个函数和与其相关的引用环境组合而成的实体。

函数可以存储到变量中作为参数传递给其它函数,能够被函数动态的创建和返回。

func Adder() func(int) int {
var x int
return func(d int) int {
x += d
return x
}
}


f := Adder()
fmt.Println(f(1)) //1
fmt.Println(f(10)) //11
fmt.Println(f(100)) //111



值传递 & 引用传递

无论是值传递,还是引用传递,传递给函数的都是变量的副本;

值传递是值的拷贝,引用传递是地址的拷贝;

一般来说,地址拷贝更为高效。而值拷贝取决于拷贝的对象大小,对象越大,则性能越低。

map、slice、chan、指针、interface默认以引用的方式传递。

new 内置函数 用来分配内存,主要用来分配值类型,比如int、struct,返回的是指针;

make 内置函数 用来分配内存,主要用来分配引用类型,比如chan、map、slice。


指针类型(&*)  

普通类型,变量存的就是值,也叫值类型;

指针类型,变量存的是一个地址,这个地址存的才是值。

变量是一种占位符,用于引用计算机内存地址;

Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。

获取指针类型所指向的值,使用:*。

 

一个指针变量可以指向任何一个值的内存地址它指向那个值的内存地址。

申明如下:

var age *int           /指向整型
var height *float32 /指向浮点型

当一个指针被定义后没有分配到任何变量时,它的值为 nil。

nil 指针也称为空指针。


栗子

package main


import "fmt"


func main() {
var ptr *int
num := 100
ptr = &num
fmt.Println(ptr) //0xc42000e1f8
fmt.Println(*ptr) //100
*ptr = 200
fmt.Println(num) //200
}
package main


import "fmt"


func change(num *int) {
fmt.Println(num) //0xc42000e1f8
fmt.Println(*num) //100
*num = 1000
fmt.Println(num) //0xc42000e1f8
fmt.Println(*num) //1000
}


func main() {
num := 100
fmt.Println(&num) //0xc42000e1f8
change(&num)
fmt.Println(&num) //0xc42000e1f8
fmt.Println(num) //1000
}