golang进阶二:结构体,函数与接口

·  阅读 158

1. 结构体

一个结构体(struct)就是一个字段的集合。属于值类型

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    v.X = 4
    fmt.Println(v.X)
}
复制代码

type 是定义类型。不是必须使用的。

var s struct{
    x string
    y int
}
func main() {
    s.x = "title"
    s.y = 10
}
复制代码

以上是个匿名结构体,它一般用来组织全局变量,或作为临时数据模板。

// 定义并初始化并赋值给 data
data := struct {
    name string
    age int
}{
    "Richard",
    18,
}
复制代码

2. 函数

golang 中,函数是一等公民,也是一种类型

func foo(x int, y int) { // 无返回值
    fmt.Println(x + y)
}
func add(x, y int) int { // 有返回值,形参可简写
    return x + y
}
func split(sum int) (x, y int) { // 多返回值
    x = sum * 4 / 9
    y = sum - x
    return  // 命名返回值,可以不再显示写出
}
// foo("Hello", 1, 2, 3, 4, 5)
func foo(title string, y ...int) { // 可变参数,这个 y 是 slice 类型
    fmt.Println(title, y)
}
复制代码

多值返回:函数可以返回任意数量的返回值 命名返回值:Go 的返回值可以被命名,并且像变量那样使用

defer 语句会延迟函数的执行直到上层函数返回。 延迟调用的参数会立刻生成,但是在上层函数返回前函数都不会被调用。

延迟的函数调用被压入一个中。当函数返回时,会按照后进先出的顺序调用被延迟的函数调用

init函数

init() 函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等; 一个包可以出线多个 init() 函数,一个源文件也可以包含多个 init() 函数; 同一个包中多个 init() 函数的执行顺序没有明确定义,但是不同包的init函数是根据包导入的依赖关系决定的; init() 函数在代码中不能被显示调用、不能被引用(赋值给函数变量),否则出现编译错误; 一个包被引用多次,如 A import B,C import B,A import C,B 被引用多次,但 B 包只会初始化一次; 引入包,不可出现死循坏。即 A import B,B import A,这种情况编译失败;

函数可变形参

func hello(num ...int) num 是int类型的切片,接收参数为:hello([]int{5, 6, 7}...)hello(4,5,6) 可以将 slice 传进可变函数,不会创建新的切片,修改 num 将改变传入的 slice 的底层数组。

指针形参

使用指针作为形参,通常用来修改值,如进行基本数据交换

func swap(a, b *int) {
	temp := *a  // 缓存a的值而不是地址
	*a = *b
	*b = temp
}
复制代码

指针形参在传入值的时候,是复制了一份地址,在函数内修改指针的地址,将不会对原指针进行修改,如下例展示:

func ChangeAddr(a *int) {
	fmt.Printf("Addr: %v  Value: %d \n", a, *a)
	b := 20
	a = &b
	fmt.Printf("Addr: %v  Value: %d \n", a, *a)
}
func main() {
	var a = 10
	var pa = &a
	fmt.Printf("Addr: %v  Value: %d \n", pa, *pa)
	ChangeAddr(pa)
	fmt.Printf("Addr: %v  Value: %d \n", pa, *pa)
}
复制代码

输出如下,可以看到函数结束后原变量pa并没被改变。

Addr: 0xc0000140d8  Value: 10 
Addr: 0xc0000140d8  Value: 10 
Addr: 0xc0000140f8  Value: 20 
Addr: 0xc0000140d8  Value: 10 
复制代码

方法

除了常见的结构体可以定义方法外,其他类型也都可以定义:

type N int
func (n N) test(){
    fmt.Println(n)
}
func main()  {
    var n N = 10
    f := n.test
    f()
    
    f1 := N.test
    f1(n) // 等价: N.test(n) 

    f2 := (*N).test
    f2(&n) // 等价:(*N).test(&n)
}
复制代码

题:方法调用

type People struct {
	Name string
}
func (p *People) test() {
	println("haha")
}
func main() {
	var a *People
	a.test()  // OK 
    People{}.test() // Error: People{} 不可寻址
}
复制代码

3. interface

在 golang 中,接口是第一公民,属于值类型。

实现接口采用 duck typing: 长得像鸭子的就认为它是鸭子。

// 只要实现了 speak 方法的,都是 speaker 类型
type speaker interface {
    speak()
}
type cat struct{}
type dog struct{}

func (c cat) speak() {}
func (d dog) speak() {}

func say(x speaker) {
    x.speak()
}

// cat dog 都属于 speaker 类型
var c cat
var d dog
say(c)
say(d)
复制代码

空接口 interface{}

空接口:指没有定义任何方法的接口。因此任何类型都实现了空接口。

map[string]interface{} 这样就可以将 map 的 value 使用任何类型了。 func show(a interface{}) {} 形参就可以传入任意类型的值了。

interface{} 可以接收任何类型的参数,即使是接收指针类型也用 interface{},而不是使用 *interface{}

方法的指针接收者

方法的接收者有两种,一种是值接收者,一种是指针接收者

type People interface {
    Speak(string) string
}

type Student struct{}

func (stu *Student) Speak(think string) (talk string) {
    ...
}

func main() {
    var peo People = Student{} // 编译错误,实现接口的是 *Student
    fmt.Println(peo.Speak("speak"))
}
复制代码

另外值接收者,是接收者的类型是一个值,是一个副本,方法内部无法对其真正的接收者做更改;指针接收者,接收者的类型是一个指针,是接收者的引用,对这个引用可修改之间影响真正的接收者

type Person interface {
    getName() string
    setName(string)
}

type Student struct {
    name string
}

func(p Student) getName() string {
    return p.name
}

func(p Student) setName(name string) { // #1
    p.name = name
}

func main() {
    var student Person = Student{name: "Unknown"} // #2
    fmt.Printf("未初始化默认值:s1:%s\n", student.getName())
    student.setName("Richard")
    fmt.Printf("设置值后:s1:%s\n", student.getName())
}

// 以上无法修改名字
// 如果要能正确使用,将 #1 改为指针接收者:
// func(p *Student) setName(name string) { // #1
// 同时初始化改为:
// var student Person = &Student{name: "Unknown"} // #2
复制代码

类型断言

类型断言 x.(T) 它返回两个值,第一个为变量的值,第二个为布尔值

例如:v, ok := x.(string) ok 表示是否为 string 类型, v 为具体的值。只能用在接口

类型断言 i.(T) 只能用在接口上。其中 i 是接口,Type 是类型或接口。编译时会自动检测 i 的动态类型与 Type 是否一致。但是,如果动态类型不存在,则断言总是失败

func main() {
    x := interface{}(nil)
    y := (*int)(nil)
    a := y == x
    b := y == nil
    _, c := x.(interface{})
    println(a, b, c)
    fmt.Printf("%T %T \n", x, y)
}
// false true false
// <nil> *int 
复制代码
分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改