欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

关于go容器的一切

程序员文章站 2022-06-04 14:42:18
...

go踩坑报告

测试部分

Q1:channel创建使用的make()函数,make(channel Type)和make(channel Type,0)是否一样?

case1:不设置capacity

In

package main

import "fmt"
func main() {
	//create a channel, capacity equals nil
	var ch1 chan int = make(chan int)

	//send 0 through ch1
	ch1 <- 0

	//print ch1's data
	for k := range ch1{
		fmt.Println(k)
	}

}

Out

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:      

Analysis

out中表明发送失败,我的遍历打印也没有任何结果证明ch1中没有内容,而且出现send error,是因为没有接收。而且[chan send]代表的是发送失败

case2:capacity = 0

In

package main

import "fmt"
func main() {
	//create a channel, capacity equals nil
	var ch1 chan int = make(chan int, 0)

	//send 0 through ch1
	ch1 <- 0

	//print ch1's data
	for k := range ch1{
		fmt.Println(k)
	}

}

Out

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:

Analysis

输出结果和case1一样,out中表明发送失败,我的遍历打印也没有任何结果证明ch1中没有内容,而且出现send error,是因为没有接收。而且[chan send]代表的是发送失败

case2:capacity = 3 (>0)

In

package main

import "fmt"
func main() {
	//create a channel, capacity equals nil
	var ch1 chan int = make(chan int, 3)

	//send  through ch1
	ch1 <- 0
	ch1 <- 1
	ch1 <- 2

	//print ch1's data
	for k := range ch1{
		fmt.Println(k)
	}

}

Out

0
1
2
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:

Analysis

输出的0,1,2是管道缓冲区的缓冲值,后面的报错依然是因为没有接收产生的。而且[chan receive]代表的是接收失败

report: make(channel Type)和make(channel Type,0)是相互等价的

Q2:读关闭的管道以及写已关闭的管道会不会发生阻塞

case1 写关闭的管道

In

package main

import "fmt"
func main() {
	//create a channel, capacity equals nil
	var ch1 chan int = make(chan int,2)

	//close ch1
	close(ch1)

	//get the pointer capacity and length
	fmt.Printf("%p,\n%d,\n%d,\n",ch1,cap(ch1),len(ch1))

	//try send  through ch1
	fmt.Println("send data...")
	ch1 <- 0
}

Out

0xc0000100e0,
2,
0,
send data...
panic: send on closed channel

goroutine 1 [running]:

Analysis

关闭后的channel并没有被nil,或者完全清除,但是在发送数据时,发生了宕机

case2:读关闭的管道

In

package main

import "fmt"

func main() {
	//create a channel, capacity equals nil
	var ch1 chan int = make(chan int,2)
	//send data
	ch1 <- 0
	ch1 <- 1

	//close ch1
	close(ch1)

	//through the data
	for i := 0;i <= cap(ch1)-1; i++{
		//get the data
		v, ok := <- ch1
		//print the data
		fmt.Printf("%v\n%v\n",v,ok)
	}

}

Out

0
true
1
true

Analysis

首先看到读取结果的顺序,证明缓冲区是队列性质。关闭后,依然可以读通道缓存无阻塞

report:关闭管道后,写操作会阻塞宕机,读操作则不会发生阻塞

Q3:make()函数创建的切片,使用append()函数添加的元素的位置在哪?

In

package main

import "fmt"

func main()  {
    // make a slice :num
	num := make([]int, 2, 5)
	//append element
	num = append(num, 2, 3)
	//assuming that element is added to the tail 
	fmt.Println(num[len(num)-1])
}

Out

3

Analysis

假设append在尾部,输出切片的尾部值,假设正确

report: make()函数添加的元素就是在尾部,而且是在长度的尾部,而不是容量的尾部

Q3:在遍历搜索切片中,所谓的键的类型是什么,是指针吗?如果改变了返回的键对应的值,切片本身对应的值会改变吗

package main

import "fmt"

func main()  {
	num := make([]int, 2, 5)
	num = append(num, 2, 3)
	fmt.Println("改变键值前的结果")
	fmt.Println(num)
	fmt.Println("改变键值前切片首地址")
	fmt.Printf("%p\n",&num)
	for k ,v  := range num{
		fmt.Printf("%T-%T\n",k,v)
		num[k] = 0
	}
	fmt.Println("改变键值后的结果")
	fmt.Println(num)
	fmt.Println("改变键值后切片首地址")
	fmt.Printf("%p\n",&num)
}

Out

改变键值前的结果
[0 0 2 3]
改变键值前切片首地址
0xc0000a8060
int-int
int-int
int-int
int-int
改变键值后的结果
[0 0 0 0]
改变键值后切片首地址
0xc0000a8060

Analysis

假设append把元素添加在尾部,输出切片的尾部值,假设正确。返回的k,v的类型是int。改变键值的前后,切片的首地址没有发生变化,证明这个数据的修改,是在原切片上做的,而且这种根据键值来修改的方式确实影响到了原先的内容

report: make()函数添加的元素就是在尾部,通过k,v来修改的方式影响到了内容变化

学习部分

T1:go语言的接口定义,如何用一个类实现接口

定义:

接口是一种类,也是一种抽象结构,接口是双方约定的一种合作协议。形式如下:

type 接口类型方法 接口类型名 interface{    //接口的命名一般在名称后面加上er
	方法名 类型 (参数列表) (返回值列表)      //如果方法名首字母大写,则代表着可以被接口
	                                 //所在的package之外的代码访问,类似public的概念
}
实现条件;

1、接口的方法与实现接口的类型方法格式一致【如果方法中的名称、参数列表、返回参数列表中的任意一项与接口要实现的方法不一致,那么接口的这个方法就不会被实现 】

2、接口中的所有方法都被实现后,接口才能被正确编译并使用

T2:类嵌入类和类包含类的区别

背景:

首先要知道go语言中的每个接口中的方法数量并不要求太多,因为他希望通过一个接口精确描述他自己的功能,同时通过多个接口的嵌入和组合的方式将简单的接口扩展为复杂的接口。

1、接口继承和实现接口
//定义接口A
type A interface{
	Foo()
}
//定义接口B,并继承接口A
type B interface{
	A
	Bar()              //大写意味着这些方法可以被修改
}
//实现这两个接口
type T struct{
	//实现接口A的所有方法
	func (t T) Foo(){      //接收者为非指针,这是一个值方法,而且是只读模式
		....
	}
	//实现接口B 的所有方法
	func (t *T) Bar(){      //接收者为指针,这是一个指针方法,可以写操作
		...
	}
	
}
2、接口组合
//定义接口A
type A interface{
	Foo()
}
//定义接口B
type B interface{
	Bar()              //大写意味着这些方法可以被修改
}
//接口继承
type AB interface{
	A
	B     //在AB接口内部,A和B接口实现了组合,他们相对AB接口是被继承的
}
3、类嵌入类
//定义一个接口
type templates interface{
	Bar()
}

//创建A类型
type A struct{
	age byte
	salary byte
	
	//实现templates接口
	func (t *A) Bar(){
		...
	}
}
//创建B类型并且嵌入A类,由于嵌入了A类,因此同样也实现了接口templates。同时包括接口中的方法集也进行了提升
type B struct{		//B类型被称为外部类
	A			//这时我们就说A类型被嵌入到B类——A类型被称为内部类型
	name string
	salary float64   //外部类型中可以声明同样的字段或者方法来覆盖内部类型——是不是类似于                      //方法的重载和重写?
}

//调用时,可以直接调用内部类型中的属性字段,也可以通过加上内部类型名称前缀的方法来调用

T3:判断一个类有没有实现一个接口(没有implements)

背景:
其他语言

接口主要作为不同类之间的契约存在,对契约的实现是强制的。java和php单继承语言中,存在严格的层级关系,一个类只能直接继承自一个父类,一个类也只能实现指定的接口,如果没有显示的声明继承某个父类或者实现某个接口,那么这个类就与该父类或者接口没有任何关系。这就是侵入式接口,但会造成接口的设计者并不能预判业务方需要实现哪些内容,会造成脱节

interface iTemplates    //声明一个itemplates接口
{
   public function.....
}

//实现接口
class Templates implements iTemplates{   //这就是强制实现这个接口
.....
}
go语言中

类对接口的实现和子类对父类的继承一样,并没有提供类似implements这样的关键字来显示声明该类实现了哪个接口

类和接口的区别:

@一个类可以同时实现多个接口,而接口之间彼此独立,不知道对方的实现

@多个类可以共同实现一个接口的方法

tips:

一个类只要实现了某个接口要求的所有方法,我们就说这个类实现了该接口【当一个类的成员方法集合包含了某个接口声明的所有方法】【接口的方法集合是某个类方法集合的子集】

T4\关键:方法接收者【指针接收器】

*的用法----接收者类型
1、接收者类型为非指针——值方法
//定义一个学生类
type Student struct{
	id unit
	name string
	male bool
	score float64
}

//初始化类
func NewStudent(id unit, name string,male bool, score float64) *Student {
     return &Student{id, name, male, score}
}     //go语言通过NewXXX这样的全局函数作为类的初始化函数  
      //关于‘*’:这是返回了一个指向该类的指针,而不是一个类的实例
      //类中的默认初始化值为0
      
//为类成员添加方法-----这是一个只读方法
func (s Student) getName() string{       //必须在func和方法名之间添加这个方法 所属类                                          //的声明(接收者声明),表示这个方法由谁来接收
	return s.name
}
上面的方法是一个只读方法,如果我们要在外部通过方法的值
2、接收者类型为指针——指针方法
//写student的name值
func (s *Student) SetName(name string){    //在外部进行写操作时,必须在对应的类前面
                                           // 加 *
                                           //方法里当然要声明参数和参数类型
	s.name = name     
}
//上面的操作在函数内部修改了成员变量的值,并且作用到该函数作用域以外,由于这个结构体是一个值类型,不是引用类型。因此是显式传入指针