2024年Go最全【golang基础教程(持续更新ing)】_golang教程(4),覆盖所有面试知识点

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

}


**特殊写法:** if 还有一种特殊的写法,可以在 if 表达式之前添加一个执行语句,再根据变量值进行判断,代码如下:



if err := Connect(); err != nil {
fmt.Println(err)
return
}


Connect 是一个带有返回值的函数,err:=Connect() 是一个语句,执行 Connect 后,将错误保存到 err 变量中。err != nil 才是 if 的判断表达式,当 err 不为空时,打印错误并返回。这种写法可以将返回值与判断放在一行进行处理,而且返回值的作用范围被限制在 if、else 语句组合中。  
 **提示**  
 在编程中,变量的作用范围越小,所造成的问题可能性越小,每一个变量代表一个状态,有状态的地方,状态就会被修改,函数的局部变量只会影响一个函数的执行,但全局变量可能会影响所有代码的执行状态,因此限制变量的作用范围对代码的稳定性有很大的帮助。


### 循环结构


与多数语言不同的是,Go语言中的循环语句只支持 for 关键字,而不支持 while 和 do-while 结构,关键字 for 的基本使用方法与C语言和 C++ 中非常接近,主要有以下这几种循环方法



package main

import “fmt”

func main() {
// for循环
s := “1234”
for i := 0; i < len(s); i++ {
fmt.Printf(“i: %v\n”, i)
}

i := 1
for ; i < 5; i++ {
	fmt.Println(i)
}

j := 1
for j < 5 {
	fmt.Println(j)
	j++
}

// for range
var k = [...]int{1, 2, 3, 4, 5}
for i, v := range k {
	fmt.Printf("i:%v v:%v\n", i, v)
}

}


### switch


Go语言的 switch 要比C语言的更加通用,表达式不需要为常量,甚至不需要为整数,case 按照从上到下的顺序进行求值,直到找到匹配的项,如果 switch 没有表达式,则对 true 进行匹配,因此,可以将 if else-if else 改写成一个 switch。



package main

import “fmt”

func main() {
// switch
// 条件匹配 表达式
grade := 1
fmt.Println(grade)
switch grade {
case ‘A’:
fmt.Println(“字母”)
// 跨越 case 的 fallthrough——兼容C语言的 case 设计
fallthrough
case 1, 2, 3:
fmt.Println(“数字”)
default:
fmt.Println(“默认”)
}
}


在Go语言中 case 是一个独立的代码块,执行完毕后不会像C语言那样紧接着执行下一个 case,但是为了兼容一些移植代码,依然加入了 fallthrough 关键字来实现这一功能.


### goto&break&continue



package main

import “fmt”

func f1() {
OuterLoop:
for i := 0; i < 2; i++ {
for j := 0; j < 5; j++ {
switch j {
case 2:
fmt.Println(i, j)
break OuterLoop
case 3:
fmt.Println(i, j)
break OuterLoop
}
}
}
fmt.Println(“end…”)
}

func f2() {
for x := 0; x < 10; x++ {
for y := 0; y < 10; y++ {
if y == 2 {
// 跳转到标签
goto breakHere
}
}
}
// 手动返回, 避免执行进入标签
return
// 标签
breakHere:
fmt.Println(“done”)
}

func f3() {
OuterLoop:
for i := 0; i < 2; i++ {
for j := 0; j < 5; j++ {
switch j {
case 2:
fmt.Println(i, j)
continue OuterLoop
}
}
}
}
func main() {
//流程控制关键字
/*break
结束 for、switch 和 select 的代码块
另外 break 语句还可以在语句后面添加标签,表示退出某个标签对应的代码块,
标签要求必须定义在对应的 for、switch 和 select 的代码块上。
*/
f1()
/*goto
Go语言中 goto 语句通过标签进行代码间的无条件跳转,
同时 goto 语句在快速跳出循环、避免重复退 出上也有一定的帮助,
使用 goto 语句能简化一些代码的实现过程。
*/
f2()
/*continue
Go语言中 continue 语句可以结束当前循环,
开始下一次的循环迭代过程,仅限在 for 循环内使用,
在 continue 语句后添加标签时,表示开始标签对应的循环,
*/
f3()
}


## go语言容器


### 数组


数组是一个由`固定长度`的`特定类型元素`组成的序列,一个数组可以由零个或多个元素组成。因为数组的`长度是固定`的,所以在Go语言中很少直接使用数组。



package main

import “fmt”

func test() {
// 数字 字符串数组定义
var arr1 [2]int
fmt.Printf(“arr1: %v\n”, arr1)
fmt.Printf(“arr1: %T\n”, arr1)

var arr2 [2]string
fmt.Printf("arr2: %v\n", arr2)
fmt.Printf("arr2: %T\n", arr2)

// 初始化定义 名称 长度 类型
arr1 = [2]int{1, 3}
fmt.Printf("arr1: %v\n", arr1)

// 忽略数组长度
var arr3 = [...]string{"1", "32"}
fmt.Printf("arr3: %v\n", len(arr3))

}

// 数组的遍历
func getArrElement() {
var a1 = […]int{1, 3, 4}
fmt.Printf(“a1: %v\n”, a1)
for _, v := range a1 {
fmt.Printf(“v: %v\n”, v)
}
}

func main() {
test()
getArrElement()
}


### 切片


和数组对应的类型是 Slice(切片),Slice 是可以增长和收缩的动态序列,功能也更灵活。


切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型(因此更类似于 C/C++ 中的数组类型,或者 Python 中的 list 类型),这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内。


Go语言中切片的内部结构包含地址、大小和容量,切片一般用于快速地操作一块数据集合,如果将数据集合比作切糕的话,切片就是你要的“那一块”,切的过程包含从哪里开始(切片的起始位置)及切多大(切片的大小),容量可以理解为装切片的口袋大小,如下图所示。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/e21ea6af952149c6af4dfb9fc999e4d9.png)



package main

import “fmt”

func main() {
var arr1 = […]int{1, 2, 3, 4}
fmt.Printf(“arr1: %v\n”, arr1)

// 切片 声明 定义
var s1 []int
fmt.Printf("slice1: %v\n", s1)
var s2 = make([]int, 2)
fmt.Printf("s2: %v\n", s2)

// 长度 容量
s1 = []int{1, 3, 4, 5}
fmt.Printf("s1: %v\n", s1)
fmt.Printf("len(s1): %v\n", len(s1))
fmt.Printf("cap(s1): %v\n", cap(s1))

//初始化
// 使用数组初始化
s3 := arr1[:]
fmt.Printf("s3: %v\n", s3)
//使用数组的部分元素初始化 (切片表达式)
s4 := arr1[1:4]
fmt.Printf("s4: %v\n", s4)

// 遍历
for i := 0; i < len(s1); i++ {
	fmt.Printf("s1[i]: %v\n", s1[i])
}
for \_, v := range s1 {
	fmt.Printf("v: %v\n", v)
}

// 添加
s1 = append(s1, 100)
fmt.Printf("s1: %v\n", s1)
// 删除
s1 = append(s1[:3], s1[4:]...)
fmt.Printf("s1: %v\n", s1)
// copy
s1Copy := make([]int, len(s1))
copy(s1Copy, s1)
fmt.Printf("s1Copy: %v\n", s1Copy)

}



> 
> **`…` 用法**  
>  ● 第一个用法主要是用于函数有多个不定参数的情况,表示为可变参数,可以接受任意个数但相同类型的参数。  
>  ● 第二个用法是slice可以被打散进行传递。
> 
> 
> 


### map


Go语言中 map 是一种特殊的数据结构,一种元素对(pair)的无序集合,pair 对应一个 key(索引)和一个 value(值),所以这个结构也称为关联数组或字典,这是一种能够快速寻找值的理想结构,给定 key,就可以迅速找到对应的 value。



package main

import “fmt”

func main() {
// 声明 名称 key的类型 value的类型
var m1 map[int]string
fmt.Printf(“m1: %v\n”, m1)
fmt.Printf(“m1: %T\n”, m1)
m2 := make(map[int]string)
fmt.Printf(“m2: %v\n”, m2)

// 初始化
var m3 = map[int]string{1: "TOM", 2: "lib"}
fmt.Printf("m1: %v\n", m3)
m4 := make(map[string]string)
m4["a"] = "1"
fmt.Printf("m3: %v\n", m4)
fmt.Printf("m4[\"a\"]: %v\n", m4["a"])

// 遍历
for k, v := range m3 {
	fmt.Printf("k: %v\n", k)
	fmt.Printf("v: %v\n", v)
}

}


## go语言函数


**特性**


* 3种函数:普通函数 匿名函数 方法(定义在结构体上)
* 不允许重载
* 不能嵌套函数 但可以嵌套匿名函数
* 函数是一个值 可以将函数赋值给一个变量
* 函数可以作为参数传递给另外一个函数
* 函数的返回值可以是一个函数
* 函数调用的时候,如果有参数传递给函数 则先拷贝参数的副本 再将副本传递给函数
* 函数的参数可以没有名称


### 函数



package main

import “fmt”

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

func test1() {
fmt.Println(“没有参数和返回值的函数”)
}

func test2() string {
return “123”
}

func test3() (name string, age int) {
name = “job”
age = 23
return name, age
}

// 形参
func test4(x int) string {
x = 200
return “111”
// return x
}

func test5(s []int) {
s[0] = 1000
}

func test6(args …int) {
for _, v := range args {
fmt.Printf(“v: %v\n”, v)
}
}

func sayHello(name string) {
fmt.Printf(“hello, %s\n”, name)
}
func test7(name string, f func(string)) {
f(name)
}
func main() {
// 返回值
// 没有参数和返回值
test1()
// 有参数一个返回值
r := test2()
fmt.Printf(“r: %v\n”, r)
// 有多个返回值
n, _ := test3()
fmt.Printf(“n: %v\n”, n)

// 参数
ret := sum(1, 2)
fmt.Printf("ret: %v\n", ret)
// 实参
/\*

x的值没有改变 说明参数传递是拷贝一个副本
有些数据类型是就是指针类型 所以拷贝传值也就是拷贝的指针
拷贝后的参数任然指向底层数据结构 可能会改变原来的数据结构的值

*/
x := 100
new_x := test4(x)
fmt.Printf(“new_x: %v\n”, new_x)
fmt.Printf(“x: %v\n”, x)
s := []int{1, 2, 4}
test5(s)
fmt.Printf(“s: %v\n”, s)

// 可变参数
test6(1, 2, 43, 5)

// 高阶函数 函数作为参数
test7("liusan", sayHello)

// 函数作为返回值
f := cal("-")
ff := f(1, 2)
fmt.Printf("ff: %v\n", ff)

}


### 函数类型实现接口



package main
import (
“fmt”
)
// 调用器接口
type Invoker interface {
// 需要实现一个Call方法
Call(interface{})
}
// 结构体类型
type Struct struct {
}
// 实现Invoker的Call
func (s *Struct) Call(p interface{}) {
fmt.Println(“from struct”, p)
}
// 函数定义为类型
type FuncCaller func(interface{})
// 实现Invoker的Call
func (f FuncCaller) Call(p interface{}) {
// 调用f函数本体

}
func main() {
// 声明接口变量
var invoker Invoker
// 实例化结构体
s := new(Struct)
// 将实例化的结构体赋值到接口
invoker = s
// 使用接口调用实例化结构体的方法Struct.Call
invoker.Call(“hello”)
// 将匿名函数转为FuncCaller类型,再赋值给接口
invoker = FuncCaller(func(v interface{}) {
fmt.Println(“from function”, v)
})
// 使用接口调用FuncCaller.Call,内部会调用函数本体
invoker.Call(“hello”)
}


### 匿名函数


匿名函数是指不需要定义函数名的一种函数实现方式,由一个不带函数名的函数声明和函数体组成,下面来具体介绍一下匿名函数的定义及使用。  
 **定义一个匿名函数**



// 匿名函数
nm := func(a int, b int) int {
return a + b
}
fmt.Printf(“nm: %v\n”, nm)


**匿名函数用作回调函数**



package main
import (
“fmt”
)
// 遍历切片的每个元素, 通过给定函数进行元素访问
func visit(list []int, f func(int)) {
for _, v := range list {
f(v)
}
}
func main() {
// 使用匿名函数打印切片内容
visit([]int{1, 2, 3, 4}, func(v int) {
fmt.Println(v)
})
}


**使用匿名函数实现操作封装**



package main
import (
“flag”
“fmt”
)
var skillParam = flag.String(“skill”, “”, “skill to perform”)
func main() {
flag.Parse()
var skill = map[string]func(){
“fire”: func() {
fmt.Println(“chicken fire”)
},
“run”: func() {
fmt.Println(“soldier run”)
},
“fly”: func() {
fmt.Println(“angel fly”)
},
}
if f, ok := skill[*skillParam]; ok {
f()
} else {
fmt.Println(“skill not found”)
}
}


### 闭包


Go语言中闭包是引用了自由变量的函数,被引用的自由变量和函数一同存在,即使已经离开了自由变量的环境也不会被释放或者删除,在闭包中可以继续使用这个自由变量,因此,简单的说:  
 `函数 + 引用环境 = 闭包`


同一个函数与不同引用环境组合,可以形成不同的实例,如下图所示。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/16b63b07d3594604b971883d4c9bb5ce.png)  
 一个函数类型就像结构体一样,可以被实例化,函数本身不存储任何信息,只有与引用环境结合后形成的闭包才具有“记忆性”,函数是编译期静态的概念,而闭包是运行期动态的概念


**在闭包内部修改引用的变量**



// 准备一个字符串
str := “hello world”
// 创建一个匿名函数
foo := func() {

// 匿名函数中访问str
str = "hello dude"

}
// 调用匿名函数
foo()


**闭包的记忆效应**  
 被捕获到闭包中的变量让闭包本身拥有了记忆效应,闭包中的逻辑可以修改闭包捕获的变量,变量会跟随闭包生命期一直存在,闭包本身就如同变量一样拥有了记忆效应。



package main

import (
“fmt”
)

// 提供一个值, 每次调用函数会指定对值进行累加
func Accumulate(value int) func() int {

// 返回一个闭包
return func() int {

    // 累加
    value++

    // 返回一个累加值
    return value
}

}

func main() {

// 创建一个累加器, 初始值为1
accumulator := Accumulate(1)

// 累加1并打印
fmt.Println(accumulator())

fmt.Println(accumulator())

// 打印累加器的函数地址
fmt.Printf("%p\n", &accumulator)

// 创建一个累加器, 初始值为1
accumulator2 := Accumulate(10)

// 累加1并打印
fmt.Println(accumulator2())

// 打印累加器的函数地址
fmt.Printf("%p\n", &accumulator2)

}


### 递归函数


几个例子说明  
 **斐波那契数列**



package main
import “fmt”
func main() {
result := 0
for i := 1; i <= 10; i++ {
result = fibonacci(i)
fmt.Printf(“fibonacci(%d) is: %d\n”, i, result)
}
}
func fibonacci(n int) (res int) {
if n <= 2 {
res = 1
} else {
res = fibonacci(n-1) + fibonacci(n-2)
}
return
}


**阶乘**



package main
import “fmt”
func Factorial(n uint64) (result uint64) {
if n > 0 {
result = n * Factorial(n-1)
return result
}
return 1
}
func main() {
var i int = 10
fmt.Printf(“%d 的阶乘是 %d\n”, i, Factorial(uint64(i)))
}


### defer



> 
> 用于注册延迟调用  
>  直到函数return之前执行  
>  多个defer语句 按照先进后出执行  
>  defer语句中的变量 在声明时定义
> 
> 
> 



package main

import “fmt”

func s1() {
defer fmt.Println(“后面执行1”)
fmt.Println(“1111”)
defer fmt.Println(“后面执行”)
fmt.Println(“2222”)
}

func main() {
s1()
}


### init函数



> 
> 先于main函数自动执行 不能被调用  
>  没有输入参数 返回值  
>  每个包有多个init函数  
>  包的每个源文件可以有多个init函数  
>  同一包的init函数执行顺序 没有明确定义 编程时要注意程序不要依赖这个执行顺序  
>  不同的init函数按照包导入的依赖关系决定执行顺序
> 
> 
> 


## go语言指针


与 Java 和 .NET 等编程语言不同,Go语言为程序员提供了控制数据结构指针的能力,但是,并不能进行指针运算。Go语言允许你控制特定集合的数据结构、分配的数量以及内存访问模式,这对于构建运行良好的系统是非常重要的。指针对于性能的影响不言而喻,如果你想要做系统编程、操作系统或者网络应用,指针更是不可或缺的一部分。


指针(pointer)在Go语言中可以被拆分为两个核心概念:


* 类型指针,允许对这个指针类型的数据进行修改,传递数据可以直接使用指针,而无须拷贝数据,类型指针不能进行偏移和运算。
* 切片,由指向起始元素的原始指针、元素数量和容量组成。


受益于这样的约束和拆分,Go语言的指针类型变量即拥有指针高效访问的特点,又不会发生指针偏移,从而避免了非法修改关键性数据的问题。同时,垃圾回收也比较容易对不会发生偏移的指针进行检索和回收。


切片比原始指针具备更强大的特性,而且更为安全。切片在发生越界时,运行时会报出宕机,并打出堆栈,而原始指针只会崩溃。


**指针地址和指针类型**  
 一个指针变量可以指向任何一个值的内存地址,它所指向的值的内存地址在 32 和 64 位机器上分别占用 4 或 8 个字节,占用字节的大小与所指向的值的大小无关。当一个指针被定义后没有分配到任何变量时,它的默认值为 nil。指针变量通常缩写为 ptr。


每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用在变量名前面添加&操作符(前缀)来获取变量的内存地址(取地址操作),格式如下:



ptr := &v // v 的类型为 T


其中 v 代表被取地址的变量,变量 v 的地址使用变量 ptr 进行接收,ptr 的类型为\*T,称做 T 的指针类型,\*代表指针。  
 `指针语法:var var_name *var_type`



package main
import (
“fmt”
)
func main() {
var cat int = 1
var str string = “banana”
fmt.Printf(“%p %p”, &cat, &str)
}


**从指针获取指针指向的值**  
 当使用&操作符对普通变量进行取地址操作并得到变量的指针后,可以对指针使用\*操作符,也就是指针取值,代码如下。



package main

import (
“fmt”
)

func main() {

// 准备一个字符串类型
var house = "Malibu Point 10880, 90265"

// 对字符串取地址, ptr类型为\*string
ptr := &house

// 打印ptr的类型
fmt.Printf("ptr type: %T\n", ptr)

// 打印ptr的指针地址
fmt.Printf("address: %p\n", ptr)

// 对指针进行取值操作
value := \*ptr

// 取值后的类型
fmt.Printf("value type: %T\n", value)

// 指针取值后就是指向变量的值
fmt.Printf("value: %s\n", value)

}


变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:


* 对变量进行取地址操作使用&操作符,可以获得这个变量的指针变量。
* 指针变量的值是指针地址。
* 对指针变量进行取值操作使用\*操作符,可以获得指针变量指向的原变量的值。


**使用指针修改值**



package main

import “fmt”

// 交换函数
func swap(a, b *int) {

// 取a指针的值, 赋给临时变量t
t := \*a

// 取b指针的值, 赋给a指针指向的变量
\*a = \*b

// 将a指针的值赋给b指针指向的变量
\*b = t

}

func main() {

// 准备两个变量, 赋值1和2
x, y := 1, 2

// 交换变量值
swap(&x, &y)

// 输出变量值
fmt.Println(x, y)

}


**创建指针的另一种方法——new() 函数**  
 Go语言还提供了另外一种方法来创建指针变量,格式如下:  
 `new(类型)`


一般这样写:



str := new(string)
*str = “Go语言教程”
fmt.Println(*str)


new() 函数可以创建一个对应类型的指针,创建过程会分配内存,被创建的指针指向默认值。


## go语言结构体


Go 语言通过用自定义的方式形成新的类型,结构体是类型中带有成员的复合类型。Go 语言使用结构体和结构体成员来描述真实世界的实体和实体对应的各种属性。  
 Go 语言中的类型可以被实例化,使用new或&构造的类型实例的类型是类型的指针。  
 结构体成员是由一系列的成员变量构成,这些成员变量也被称为“字段”。字段有以下特性:


* 字段拥有自己的类型和值。
* 字段名必须唯一。
* 字段的类型也可以是结构体,甚至是字段所在结构体的类型。


关于 Go 语言的类(class),Go 语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。  
 Go 语言的结构体与“类”都是复合结构体,但 Go 语言中结构体的内嵌配合接口比面向对象具有更高的扩展性和灵活性。  
 Go 语言不仅认为结构体能拥有方法,且每种自定义类型也可以拥有自己的方法。


`话不多说,全在代码`



package main

import “fmt”

// 嵌套结构体
type Dog struct {
name, color string
age int
}

// 定义结构体
type Person struct {
id int
name string
age int
email string
dog Dog
}

type Customer struct {
name string
}

func (customer Customer) login(name string, pwd string) bool {
fmt.Printf(“customer: %p\n”, customer)
if name == customer.name && pwd == “123” {
return true
}
return false
}

func (customer *Customer) show(name string) {
fmt.Printf(“customer: %p\n”, customer)
fmt.Printf(“%v 欢迎登陆 %v\n”, customer.name, name)
}

func (person Person) eat(food string) {
fmt.Printf(“%v…eat…%v\n”, person.name, food)
}

func showPerson(person *Person) {
person.id = 12
person.name = “func”
fmt.Printf(“person: %v\n”, *person)
}

func main() {
// 定义结构体 var strcut_name struct{}
var james Person

// 初始化
james = Person{
	id:    1,
	name:  "james",
	age:   13,
	email: "www@zijie.com",
}
fmt.Printf("james: %v\n", james)

// 匿名结构体
var dog struct {
	name string
}
fmt.Printf("dog: %v\n", dog)

// 创建结构体指针
var person Person
person.id = 0
person.name = "ROB"
person.age = 11
person.email = "www@baidu.com"
fmt.Printf("p: %v\n", person)
var p_person \*Person
p_person = &person
fmt.Printf("person: %v\n", person)
fmt.Printf("p\_person: %p\n", p_person)
fmt.Printf("p\_person: %v\n", \*p_person)

// new创建
var new_person = new(Person)
fmt.Printf("p\_new: %v\n", new_person)
fmt.Printf("p\_new: %T\n", new_person)

// 结构体为函数参数
func_person := Person{
	id:   100,
	name: "func\_struct",
}
p_func_person := &func_person
fmt.Printf("p\_func\_person: %v\n", \*p_func_person)
showPerson(p_func_person)

//结构体嵌套
d := Dog{
	name:  "修狗",
	color: "red",
	age:   2,
}
zhuren := Person{
	name: "zhuren",
	age:  23,
	dog:  d,
}
fmt.Printf("zhuren: %v\n", zhuren)

// 方法 func (recv mytype) my\_method(param) return\_type
zhuren.eat("狗屎")
var customer Customer
customer = Customer{
	name: "li",
}
customer.login("li", "123")
customer.show("系统")

// 方法接收者类型
// 结构体实例中 也有值类型和引用类型 方法的接收者是结构体
// 值类型 复制 引用类型 不复制

}


## go语言接口


接口本身是调用方和实现方均需要遵守的一种协议,大家按照统一的方法命名参数类型和数量来协调逻辑处理的过程。


Go 语言中使用组合实现对象特性的描述。对象的内部使用结构体内嵌组合对象应该具有的特性,对外通过接口暴露能使用的特性。


Go 语言的接口设计是非侵入式的,接口编写者无须知道接口被哪些类型实现。而接口实现者只需知道实现的是什么样子的接口,但无须指明实现哪一个接口。编译器知道最终编译时使用哪个类型实现哪个接口,或者接口应该由谁来实现。


`·话不多说,全在代码`



package main

import “fmt”

type USB interface {
read()
write()
}

type Computer struct {
name string
}

type Player interface {
playMusic()
}

type Video interface {
playVideo()
}

type Mobile struct {
}

type Pet interface {
eat()
}

type Pig struct{}

type Cat struct{}

type Flyer interface {
fly()
}

type Swimmer interface {
swim()
}
type FlyFish interface {
Flyer
Swimmer
}

type Fish struct{}

func (f Fish) fly() {
fmt.Println(“flying”)
}

func (f Fish) swim() {
fmt.Println(“swimming”)
}

func (p Pig) eat() {
fmt.Printf(“pig eating…\n”)
}

func (c Cat) eat() {
fmt.Printf(“cat eating…\n”)
}

func (m Mobile) playMusic() {
fmt.Println(“play music”)
}

func (m Mobile) playVideo() {
fmt.Println(“play video”)
}

func (c Computer) read() {
fmt.Printf(“c.name: %v\n”, c.name)
fmt.Println(“reading…”)
}

func (c Computer) write() {
fmt.Printf(“c.name: %v\n”, c.name)
fmt.Println(“write…”)
}
func main() {
// 定义接口
/*
type interface_name interface{

}
*/
c := Computer{
name: “huawei”,
}
c.read()
c.write()

// 接口 值类型接收者 指针类型接收者

//接口和类型关系
// 一个类型多个接口
m := Mobile{}
m.playMusic()
m.playVideo()
// 一个接口多个类型
var pet Pet
pet = Pig{}
pet.eat()
pet = Cat{}
pet.eat()

// 接口嵌套
var ff FlyFish
ff = Fish{}
ff.fly()
ff.swim()

}


## go语言包


Go语言的包借助了目录树的组织形式,一般包的名称就是其源文件所在目录的名称,虽然Go语言没有强制要求包名必须和其所在的目录名同名,但还是建议包名和所在目录同名,这样结构更清晰。


包可以定义在很深的目录中,包名的定义是不包括目录路径的,但是包在引用时一般使用全路径引用。比如在GOPATH/src/a/b/ 下定义一个包 c。在包 c 的源码中只需声明为package c,而不是声明为package a/b/c,但是在导入 c 包时,需要带上路径,例如import “a/b/c”。


包的习惯用法:


* 包名一般是小写的,使用一个简短且有意义的名称。
* 包名一般要和所在的目录同名,也可以不同,包名中不能包含- 等特殊符号。
* 包一般使用域名作为目录名称,这样能保证包名的唯一性,比如 GitHub 项目的包一般会放到GOPATH/src/github.com/userName/projectName 目录下。
* 包名为 main 的包为应用程序的入口包,编译不包含 main 包的源码文件时不会得到可执行文件。
* 一个文件夹下的所有源码文件只能属于同一个包,同样属于同一个包的源码文件不能放在多个文件夹下。


### 包的导入


要在代码中引用其他包的内容,需要使用 import 关键字导入使用的包。具体语法如下:  
 `import "包的路径"`  
 **注意事项:**


* import 导入语句通常放在源码文件开头包声明语句的下面;
* 导入的包名需要使用双引号包裹起来;
* 包名是从GOPATH/src/ 后开始计算的,使用/ 进行路径分隔。


包的导入有两种写法,分别是单行导入和多行导入。  
 **单行导入**  
 单行导入的格式如下:



import “包 1 的路径”
import “包 2 的路径”


**多行导入**  
 多行导入的格式如下:



import (
“包 1 的路径”
“包 2 的路径”
)


**包的导入路径**  
 包的引用路径有两种写法,分别是全路径导入和相对路径导入。  
 `全路径导入`  
 包的绝对路径就是GOROOT/src/或GOPATH/src/后面包的存放路径,如下所示:



import “lab/test”
import “database/sql/driver”
import “database/sql”


上面代码的含义如下:  
 test 包是自定义的包,其源码位于GOPATH/src/lab/test 目录下;  
 driver 包的源码位于GOROOT/src/database/sql/driver 目录下;  
 sql 包的源码位于GOROOT/src/database/sql 目录下。


`相对路径导入`  
 相对路径只能用于导入GOPATH 下的包,标准包的导入只能使用全路径导入。




![img](https://img-blog.csdnimg.cn/img_convert/d7b57c8e0af8d31ed22c857084641c82.png)
![img](https://img-blog.csdnimg.cn/img_convert/d490286a90861361adcd728aa60ae795.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618658159)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

c/github.com/userName/projectName 目录下。
* 包名为 main 的包为应用程序的入口包,编译不包含 main 包的源码文件时不会得到可执行文件。
* 一个文件夹下的所有源码文件只能属于同一个包,同样属于同一个包的源码文件不能放在多个文件夹下。


### 包的导入


要在代码中引用其他包的内容,需要使用 import 关键字导入使用的包。具体语法如下:  
 `import "包的路径"`  
 **注意事项:**


* import 导入语句通常放在源码文件开头包声明语句的下面;
* 导入的包名需要使用双引号包裹起来;
* 包名是从GOPATH/src/ 后开始计算的,使用/ 进行路径分隔。


包的导入有两种写法,分别是单行导入和多行导入。  
 **单行导入**  
 单行导入的格式如下:



import “包 1 的路径”
import “包 2 的路径”


**多行导入**  
 多行导入的格式如下:



import (
“包 1 的路径”
“包 2 的路径”
)


**包的导入路径**  
 包的引用路径有两种写法,分别是全路径导入和相对路径导入。  
 `全路径导入`  
 包的绝对路径就是GOROOT/src/或GOPATH/src/后面包的存放路径,如下所示:



import “lab/test”
import “database/sql/driver”
import “database/sql”


上面代码的含义如下:  
 test 包是自定义的包,其源码位于GOPATH/src/lab/test 目录下;  
 driver 包的源码位于GOROOT/src/database/sql/driver 目录下;  
 sql 包的源码位于GOROOT/src/database/sql 目录下。


`相对路径导入`  
 相对路径只能用于导入GOPATH 下的包,标准包的导入只能使用全路径导入。




[外链图片转存中...(img-QKHV3AHa-1715709147875)]
[外链图片转存中...(img-p7UlPMJE-1715709147875)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618658159)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 29
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值