编辑
2023-03-07
Golang
0
请注意,本文编写于 676 天前,最后修改于 676 天前,其中某些信息可能已经过时。

目录

一、变量声明
1 变量的声明格式
2 基本数据类型
3 默认值
4 值类型和引用类型
5 常量
二、运算符
1 常见运算符
2 运算符优先级
三、条件判断和循环语句
1 条件语句
1.1 if
1.2 switch
1.3 select
2 循环语句
2.1 for
2.2 range
四、数组和切片
1 数组Array
2 切片Slice
五、MAP
六、结构体
1 自定义类型和类型别名
2 结构体
3 构造函数
4 方法和接收者
4.1 任意类型添加方法
4.2 值传递和指针传递的接收者
5 结构体的匿名字段
6 结构体的继承
7 结构体字段的可见性
七、函数
1 函数声明
2 函数类型
3 不定变参
4 多参数返回值
5 匿名函数
6 闭包
7 延迟调用(defer)
8 函数和方法的区别
八、异常处理
1 panic()和recover()
2 errors.New 和 fmt.Errorf
3 Go实现类似try catch的异常处理
九 接口
1 Go接口定义
2 接口声明
3 接口实现方式

一、变量声明

变量的数据类型声明不是必须的,可以通过值的类型自动推导。

var:=都可以声明变量,不能同时使用。

局部变量声明后必须被使用,否则抛出异常,而全局变量没用这个限制。

常量使用const声明。

1 变量的声明格式

go
// 变量声明格式 var 变量名 变量类型 = 值 // 声明多个变量 var num1, num2 int32 = 1, 2 // 批量声明,一般用于声明全局变量 var ( a int8 = 10 b string = "age" c bool = true ) // 简洁声明,只能用在函数内部 a, b := "age", 10

2 基本数据类型

  • bool:布尔类型
  • string:字符串
  • int(32或64位)、int8int16int32int64:整数类型
  • uint(32或64位)、uint8uint16uint32uint64uintptr:无符号整数类型
  • float32float64:浮点数
  • complex64complex128:实数和虚数
  • byte(uint8的别名)、rune(int32的别名)

3 默认值

  • 数值类型(包括complex64/complex128)为 0
  • 布尔类型为 false
  • 字符串为 "" (空字符串)
  • 以下几种类型为 nil
go
var a *int var a []int var a map[string] int var a chan int var a func(string) int var a error // error 是接口

4 值类型和引用类型

所有像 intfloatboolstringarrayerror这些基本类型都属于值类型,当将一个值类型的变量赋值给另一个变量时,实际上是将值拷贝一份给新变量。

而其他复杂类型,需要使用多个字节,属于引用类型,变量只会存储值的内存地址(内存地址中第一个字所在的位置),当将一个引用类型的变量赋值给另一个变量时,拷贝的只是内存地址,也就是两个变量指向同一个值。

go
// 使用 &str 可获取 str 的内存地址 var str string = "string" fmt.Println(&str) var str2 = str fmt.Println(&str2)

5 常量

go
// 常量声明使用const const 常量名 数据类型 = 值 //多个相同类型的声明可以简写 const c_name1, c_name2 string = "value1", "value2" //批量声明常量,可用作枚举** const ( Unknown = 0 Female = 1 Male = 2 )

iota

特殊常量,可以认为是一个可以被编译器修改的常量。

iota 在 const 关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

go
const ( a = iota // 0 b = iota // 1 c = iota // 2 )

二、运算符

1 常见运算符

  • 算术运算符 +(加)、-(减)、*(乘)、/(除)、%(取余)、++(自增)、--(自减)

  • 关系运算符 ==(相等)、!=(不相等)、>(大于)、<(小于)、>=(大于或等于)、<=(小于或等于)

  • 逻辑运算符 &&(与)、||(或)、!(非)

  • 位运算符 &(与)、|(或)、^(异或)、<<(左移)、>>(右移)

  • 赋值运算符 =+=-=*=/=%=<<=>>=&=^=|=

  • 其他运算符 &(返回变量存储地址)、*(声明指针变量)

2 运算符优先级

可以通过使用括号来临时提升某个表达式的整体运算优先级。

三、条件判断和循环语句

不支持三元操作符(三目运算符)

1 条件语句

1.1 if

可省略条件表达式括号。

持初始化语句,可定义代码块局部变量。

代码块左 括号必须在条件表达式尾部。

格式

go
if 布尔表达式 { /* 在布尔表达式为 true 时执行 */ }

if - else if - else

go
var n int = 10 if n > 20 { fmt.Println("n大于20" ); } else if n < 10 { fmt.Println("n小于或等于20,大于10" ); } else { fmt.Println("n小于10" ); }

1.2 switch

可省略条件表达式括号,用于判断的变量也可以不写; case关键字后是要匹配的值或条件,可以声明多个,逗号分隔; case可省略 break,默认只会执行一个,可以使用fallthrough强制执行下一个case,不需要判断下一个case条件是否满足;

go
var grade string = "B" var marks int = 90 // switch关键字后不需要括号 switch marks { // case关键字后是匹配的值,也可以声明多个值,逗号分隔 case 90: grade = "A" case 80: grade = "B" case 50,60,70 : grade = "C" default: grade = "D"   } switch { // case后使用条件判断,也可以写多个条件,逗号分隔 case grade == "A": fmt.Printf("优秀!\n") case grade == "B", grade == "C": fmt.Printf("良好\n") case grade == "D": fmt.Printf("及格\n") case grade == "F": fmt.Printf("不及格\n") default: fmt.Printf("差\n") } fmt.Printf("你的等级是 %s\n", grade) //结果: //优秀! //你的等级是 A
go
// fallthrough:可以强制执行下一个case,不需要判断条件 switch { case false: fmt.Println("1、case 条件语句为 false") fallthrough case true: fmt.Println("2、case 条件语句为 true") fallthrough case false: fmt.Println("3、case 条件语句为 false") fallthrough case true: fmt.Println("4、case 条件语句为 true") case false: fmt.Println("5、case 条件语句为 false") fallthrough default: fmt.Println("6、默认 case") } //结果: //2、case 条件语句为 true //3、case 条件语句为 false //4、case 条件语句为 true

1.3 select

select 是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。

select 随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。

go
select {     case communication clause  :        statement(s);           case communication clause  :        statement(s);     /* 你可以定义任意数量的 case */     default : /* 可选 */        statement(s); }

2 循环语句

2.1 for

go
//标准的for循环,包含初始化、条件和增量 for init; condition; post { } //只有条件,替代while for condition { } //死循环,替代while(true) for { }
go
var len int = 10 for i := 0; i < len; i++ { fmt.Println(i) } var flag bool = true for flag { fmt.Printf("这是无限循环。\n") flag = !flag }

循环控制

  • break:经常用于中断当前 for 循环或跳出 switch 语句
  • continue:跳过当前循环的剩余语句,然后继续进行下一轮循环。
  • goto:调整执行位置,可用于跳出循环。

2.2 range

类似迭代器操作,返回 (索引, 值) 或 (键, 值)。 可用于遍历string、array、slice、map等

go
//字符串 var str string = "helloworld" for _, v := range str { fmt.Print(string(v), " ") } //切片 var nums []int = []int{4, 5, 6, 1, 2} for _, v := range nums { fmt.Print(v, " ") } //map m := map[string]int{"a": 1, "b": 2} for k, v := range m { fmt.Println(k, "->", v) }

四、数组和切片

1 数组Array

数组是同一种数据类型的固定长度的序列,声明是需要指定长度,定义后长度不能变。

长度是数组类型的一部分,因此,var a[5] intvar a[10]int是不同的类型。

数组是值类型,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值。

支持\==!=操作符,因为内存总是被初始化过的。

指针数组[n]\*T,数组指针*[n]T

go
// 未初始化元素值为0 var arr0 [5]int = [5]int{1, 2, 3} var arr1 = [5]int{1, 2, 3, 4, 5} // 通过初始化值确定数组长度 var arr2 = [...]int{1, 2, 3, 4, 5, 6} // 使用索引号初始化元素 var str = [5]string{3: "hello world", 4: "tom"} //多维数组 var arr1 [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}

数组长度

内置函数len()cap()都返回数组长度 (元素数量)

go
var nums = [...]int{1, 2, 3, 4, 5} fmt.Println(len(nums), cap(nums))

数组传参

数组传参时,是值拷贝,为会造成性能问题,通常会建议使用 slice,或数组指针

go
//指针方式 func main() { var nums = [...]int{1, 2, 3, 4, 5} fmt.Println(max(&nums)) } func max(nums *[5]int) int { max := nums[0] for i := 0; i < len(nums); i++ { if nums[i] > max { max = nums[i] } } return max }
go
//切面方式 func main() { var nums = [...]int{1, 2, 3, 4, 5} fmt.Println(max(nums[:])) } func max(nums []int) int { max := nums[0] for i := 0; i < len(nums); i++ { if nums[i] > max { max = nums[i] } } return max }

2 切片Slice

切片并不是数组或数组指针。它通过内部指针和相关属性引用数组片段,以实现变长方案。

切片是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递。

切片的长度可以改变,因此,切片是一个可变的数组。

切片遍历方式和数组一样,可以用len()求长度。表示可用元素数量,读写操作不能超过该限制。

cap可以求出slice最大扩张容量,不能超出数组限制。0 <= len(slice) <= len(array),其中arrayslice引用的数组。

切片的定义:var 变量名 []类型,比如: var str []string var arr []int

如果 slice == nil,那么len()cap()结果都等于 0。

定义和初始化

go
//定义切片,不需要指定长度 var slice1 []int = []int{1, 2, 3, 4, 5} //通过切割数组或切片的方式初始化 var slice2 []int = slice1[:] //全部元素 var slice3 []int = slice1[:3] //前3个元素 var slice4 []int = slice1[2:4] //从下标2到4的元素 //通过make创建,make //make([]type, len) //make([]type, len, cap) var slice5 []int = make([]int, 10) var slice6 []int = make([]int, 10, 15)

追加元素

使用内置函数append()在切片尾部追加一个或多个元素,并返回新的切片。

若追加元素后超过切片的cap,则会创建一个两倍大小的数组,并复制数据,因此创建切面时可以分配足够的空间,避免扩容。

go
var slice []int = []int{1, 2, 3, 4, 5} slice = append(slice, 6, 7, 8)

五、MAP

map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用。

go
//map的定义语法 map[KeyType]ValueType //map类型的变量默认初始值为nil,需要使用make()函数来分配内存 make(map[KeyType]ValueType, [cap])
go
//声明的时候填充元素 var m1 = map[string]string{"name": "张三", "age": "18"} for k, v := range m1 { fmt.Print(k, "=", v, " ") } //使用make分配内存 m2 := make(map[string]string, 5) m2["name"] = "张三" m2["age"] = "18" for k, v := range m2 { fmt.Print(k, "=", v, " ") } //判断某个键是否存在 value, ok := m1["age"] if ok { fmt.Println(value) } //删除键值对,使用内置函数delete()删除 delete(m1, "name")

六、结构体

1 自定义类型和类型别名

之前见过的rune和byte就是类型别名。

类型别名只会在编译阶段存在,编译后将不会有这个类型。

go
//自定义类型 type NewInt int //类型别名 type MyInt = int

2 结构体

类似于面向对象的类,可以通过struct定义自己的类型;

go
// 自定义的Person类型,可设置多个属性 type Person struct { id int64 name string age int8 } func main() { // 使用自定义的结构体,通过键值对进行初始化 var person1 Person = Person{ id: 1, name: "张三", } //对属性进行修改 person1.age = 20 fmt.Println(person1) //使用值的列表初始化,必须按照声明的属性顺序传入 var person2 Person = Person{2, "李四", 19} fmt.Println(person2) //通过内置函数new()初始化结构体,属性赋默认值 var person3 = new(Person) fmt.Println(person3) }

匿名结构体

在定义一些临时数据结构等场景下还可以使用匿名结构体。

go
var person struct {id int64; name string; age int8}

3 构造函数

Go语言的结构体没有构造函数,我们可以自己实现。

构造器的返回值是指针,避免值拷贝性能开销。

go
type Person struct { id int64 name string age int8 } //定义的构造函数 func NewPerson(id int64, name string, age int8) *Person { return &Person{id, name, age} } func main() { //通过构造函数创建Person var person *Person = NewPerson(2, "李四", 19) fmt.Println(person) }

4 方法和接收者

Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。 接收者的概念就类似于其他语言中的this或者self

go
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) { 函数体 }
go
type Person struct { id int64 name string age int8 } //定义的睡觉方法 func (this Person) Sleep() { fmt.Printf("%s正在睡觉!", this.name) } func main() { var person Person = Person{2, "李四", 19} //进行调用 person.Sleep() }

4.1 任意类型添加方法

添加方法并不限于struct,也可以给其他自定义类型添加方法。

注意事项: 非本地类型不能定义方法,也就是说我们不能给别的包的类型定义方法。

go
//MyInt 将int定义为自定义MyInt类型 type MyInt int //SayHello 为MyInt添加一个SayHello的方法 func (m MyInt) SayHello() { fmt.Println("Hello, 我是一个int。") } func main() { var m1 MyInt m1.SayHello() //Hello, 我是一个int。 m1 = 100 fmt.Printf("%#v %T\n", m1, m1) //100 main.MyInt }

4.2 值传递和指针传递的接收者

如果接收者是值传递,则方法内部的this只是副本,修改并不会影响原来的值。

如果是指针传递,则修改的是同一个。

go
type Person struct { id int64 name string age int8 } // age的set方法,接收者值传递 func (this Person) SetAge1(age int8) { this.age = age } // age的set方法,接收者引用传递 func (this *Person) SetAge2(age int8) { this.age = age } func main() { var person Person = Person{2, "李四", 19} person.SetAge1(30) fmt.Println(person) //{2 李四 19} person.SetAge2(30) fmt.Println(person) //{2 李四 30} }

什么时候应该使用指针类型接收者

  • 需要修改接收者中的值
  • 接收者是拷贝代价比较大的大对象
  • 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。

5 结构体的匿名字段

结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段。

go
type Person struct { string int } func main() { var person Person = Person{"张三", 18} fmt.Printf("%#v", person) //main.Person{string:"张三", int:18} }

嵌套匿名结构体

一个结构体中可以嵌套包含另一个结构体或结构体指针。

嵌套结构体内部可能存在相同的字段名。这个时候为了避免歧义需要指定具体的内嵌结构体的字段。

go
//Address 地址结构体 type Address struct { Province string City string } //User 用户结构体 type User struct { Name string Gender string Address //匿名结构体 } func main() { var user2 User user2.Name = "pprof" user2.Gender = "女" user2.Address.Province = "黑龙江" //通过匿名结构体.字段名访问 user2.City = "哈尔滨" //直接访问匿名结构体的字段名 fmt.Printf("user2=%#v\n", user2) //user2=main.User{Name:"pprof", Gender:"女", Address:main.Address{Province:"黑龙江", City:"哈尔滨"}} }

6 结构体的继承

Go语言中使用结构体也可以实现其他编程语言中面向对象的继承。

go
//Animal 动物 type Animal struct { name string } func (a *Animal) move() { fmt.Printf("%s会动!\n", a.name) } //Dog 狗 type Dog struct { Feet int8 *Animal //通过嵌套匿名结构体实现继承 } func (d *Dog) wang() { fmt.Printf("%s会汪汪汪~\n", d.name) } func main() { d1 := &Dog{ Feet: 4, Animal: &Animal{ //注意嵌套的是结构体指针 name: "乐乐", }, } d1.wang() //乐乐会汪汪汪~ d1.move() //乐乐会动! }

7 结构体字段的可见性

结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。

七、函数

支持不定 变参。 支持多返回值。 支持命名返回参数。 支持匿名函数和闭包。 函数也是一种类型,一个函数可以赋值给变量。

不支持 嵌套 (nested) 一个包不能有两个名字一样的函数。 不支持 重载 (overload) 不支持 默认参数 (default parameter)。

1 函数声明

  • 使用关键字 func 定义函数,左大括号依旧不能另起一行;

  • 函数的返回值可以有多个,如果没有返回值,括号可以省略;

go
func test(x, y int, s string) (int, string) { // 类型相同的相邻参数,参数类型可合并。 多返回值必须用括号。 n := x + y return n, fmt.Sprintf(s, n) }

2 函数类型

函数不仅可以复制给变量,进行传参,还可以声明为类型。

go
// 定义函数类型。 type FormatFunc func(s string, x, y int) string func format(fn FormatFunc, s string, x, y int) string { return fn(s, x, y) } func main() { //匿名函数传参 s2 := format(func(s string, x, y int) string { return fmt.Sprintf(s, x, y) }, "%d, %d", 10, 20) println(s2) }

3 不定变参

函数支持不定变参,可以传0个或多个参数。

也可以直接传递一个切片,特别注意的是在参数后加上“…”即可。

go
func sum(nums ...int) int { var sum int = 0 for _, v := range nums { sum += v } return sum } func main() { //传入多个参数 fmt.Println(sum(1, 2, 3)) var nums [3]int = [...]int{1, 2, 3} //传入切片 fmt.Println(sum(nums[:]...)) }

4 多参数返回值

返回值可以有多个,并且支持返回值命名,return时隐式返回

go
//返回值可以有多个,并且支持返回值命名,return时隐式返回 func max(nums ...int) (max int, min int) { max, min = nums[0], nums[0] for _, v := range nums { if v > max { max = v } if v < min { min = v } } return } func main() { //接收返回值,不需要的可以用 _ 忽略 ma, _ := max(1, 2, 3) fmt.Println(ma) //返回的多个值不能用array等容器对象接收 // marr := make([]int, 2) // marr = max(1, 2, 3) // Error: multiple-value test() in single-value context //返回的多个值可以作为另一个函数的参数 ma2, mi2 := max(max(1, 2, 3)) fmt.Println(ma2, mi2) }

5 匿名函数

go
//匿名函数自调用,也可以赋值给变量 func() { println("Hello, World!") }()

6 闭包

函数内嵌套函数,内部函数引用了外部函数的变量,导致被引用的变量外部函数结束后并没有被GC回收

go
// 内部函数引用了外部函数的局部变量 func add(base int) func(int) int { return func(i int) int { base += i return base } } func main() { //tmp1和tmp2的外部环境不同,引用的base并不是同一个,并且没有被GC回收 tmp1 := add(10) fmt.Println(tmp1(1), tmp1(2)) tmp2 := add(100) fmt.Println(tmp2(1), tmp2(2)) }

7 延迟调用(defer)

关键字 defer 用于注册延迟调用。

这些调用直到 return 前才被执。因此,可以用来做资源清理。

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

defer语句中的变量,在defer声明时就决定了。

defer是先进后出,函数内最后的defer最先调用。

go
func main() { defer fmt.Print(1, " ") defer fmt.Print(2, " ") defer func() { fmt.Print(3, " ") }() defer fmt.Print(4, " ") //4 3 2 1 } func main() { var whatever [5]struct{} for i := range whatever { //defer是函数return前执行,因此deffer遇到闭包时,用到外部变量的值一定是return前的值 //而这里i变量return前的值是4,因此defer打印的全都是4, defer func() { fmt.Print(i, " ") }() } //4 4 4 4 4 }

defer f.Close

go
type Test struct { name string } func (t *Test) Close() { fmt.Print(t.name, " closed ") } func main() { ts := []Test{{"a"}, {"b"}, {"c"}} for _, t := range ts { // for循环内,每次循环t变量只是值变,而t是同一个,因此每次关闭的都是return前t的值 Test{"c"} // defer t.Close() //c closed c closed c closed // 将t赋值给for循环内新的变量,每次遍历都不是同一个变量,问题解决 t2 := t defer t2.Close() //c closed b closed a closed } }

defer执行出现异常

若defer执行中出现异常,仍然会执行其他为执行的defer

go
func test(x int) { defer fmt.Println("a") defer func() { fmt.Println(100 / x) // div0 异常未被捕获,逐步往外传递,最终终止进程。 }() defer fmt.Println("b") } func main() { test(0) } //打印: //b //a //panic: runtime error: integer divide by zero

8 函数和方法的区别

函数的形参如果是值类型或者是指针类型,都只能传入对应类型的参数,而方法没有这个限制;

八、异常处理

Golang 没有结构化异常,使用 panic() 抛出错误,recover() 捕获错误。

异常的使用场景简单描述:Go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理。

1 panic()和recover()

  • panic(v any)

    painc()用于抛出异常,异常抛出后,后面的代码将不会被执行,直到线程结束或异常被捕获;

    若异常抛出前有需要执行的defer列表,则会倒序执行,可以在defer中使用recover()来捕获异常;

    使用painc()抛出异常后在defer再次抛出异常时,异常会被覆盖;

  • recover() any

    recover()用于捕获异常,必须在抛出异常前的defer中使用,否则无法捕捉异常,返回值始终是nil;

    recover()处理异常后,所在函数不会被恢复,将会在上一个调用自己的函数中恢复执行;

异常抛出和捕获

go
func test(x int) { defer func() { fmt.Println(recover()) }() fmt.Println(3) panic("错误!") fmt.Println(4) //执行 } func main() { defer func() { fmt.Println(recover()) }() fmt.Println(1) test(0) fmt.Println(2) } //打印: //1 //3 //错误! //2 //<nil>

在defer中抛出异常

在defer抛出的异常可以在下一个defer中捕获;

go
func test() { defer func() { fmt.Println(1, recover()) }() defer func() { fmt.Println(2, recover()) }() defer func() { panic("defer panic") //异常被覆盖 }() panic("test panic") } func main() { test() } //打印: //2 defer panic //1 <nil>

可以在可能抛出异常的位置使用匿名函数,异常捕获后保证后面的代码可以执行

异常捕获后结束的是匿名函数,当前函数仍然正常运行;

go
func test(x, y int) { var z int func() { defer func() { if recover() != nil { z = 0 } }() panic("test panic") z = x / y return }() fmt.Printf("x / y = %d\n", z) } func main() { test(2, 1) } //打印: //x / y = 0

2 errors.New 和 fmt.Errorf

除用 panic 引发中断性错误外,还可返回 error 类型错误对象来表示函数调用状态

声明异常类型

go
type error interface { Error() string }

errors.New 和 fmt.Errorf函数用于快速创建实现 error 接口的错误对象,通过判断错误对象实例来确定具体错误类型

go
import ( "errors" "fmt" ) //声明异常类型 var ErrDivByZero = errors.New("division by zero") //第二个参数固定返回异常 func div(x, y int) (int, error) { if y == 0 { return 0, ErrDivByZero } return x / y, nil } func main() { defer func() { fmt.Println(recover()) }() //通过switch判断异常类型进行处理 switch z, err := div(10, 0); err { case nil: println(z) case ErrDivByZero: panic(err) } } //输出: //division by zero

如何区别使用 panic 和 error 两种方式?

惯例是:导致关键流程出现不可修复性错误的使用 panic,其他使用 error。

3 Go实现类似try catch的异常处理

go
func Try(fun func(), handler func(interface{})) { defer func() { if err := recover(); err != nil { handler(err) } }() fun() } func main() { Try(func() { panic("test panic") }, func(err interface{}) { fmt.Println(err) }) }

九 接口

在Go语言中接口(interface)是一种类型,一种抽象的类型。

interface是一组method的集合,是duck-type programming的一种体现。接口做的事情就像是定义一个协议(规则),只要一台机器有洗衣服和甩干的功能,我就称它为洗衣机。不关心属性(数据),只关心行为(方法)。

1 Go接口定义

  • 接口是一个或多个方法签名的集合。
  • 任何类型的方法集中只要拥有该接口'对应的全部方法'签名。就表示它 "实现" 了该接口,无须在该类型上显式声明实现了哪个接口。这称为Structural Typing。所谓对应方法,是指有相同名称、参数列表 (不包括参数名) 以及返回值。
  • 接口只有方法声明,没有实现,没有数据字段。
  • 对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个复制品的指针,既无法修改复制品的状态,也无法获取指针。
  • 只有当接口存储的类型和对象都为nil时,接口才等于nil。
  • 接口调用不会做receiver的自动转换。
  • 接口同样支持匿名字段方法。
  • 接口也可实现类似OOP中的多态。
  • 空接口可以作为任何类型数据的容器。
  • 一个类型可实现多个接口。
  • 接口命名习惯以 er 结尾。

2 接口声明

go
type 接口类型名 interface{ 方法名1( 参数列表1 ) 返回值列表1 方法名2( 参数列表2 ) 返回值列表2 … }

3 接口实现方式

一个对象只要全部实现了接口中的方法,那么就实现了这个接口。换句话说,接口就是一个需要实现的方法列表。

go
// Sayer 接口 type Sayer interface { say() } type dog struct{} // dog实现了Sayer接口 func (d dog) say() { fmt.Println("汪汪汪") } type cat struct{} // cat实现了Sayer接口 func (c cat) say() { fmt.Println("喵喵喵") } func main() { //多态 var d Sayer = dog{} d.say() //汪汪汪 var c Sayer = cat{} c.say() //喵喵喵 }

本文作者:牟相波

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!