管道
管道底层是一个环形队列(先进先出);
管道是Go语言在语言级别上提供的goroutine间的通讯方式,我们可以使用channel在多个goroutine之间传递消息。channel是进程内的通讯方式,是不支持跨进程通信的,如果需要进程间通讯的话,可以使用Socket等网络方式。
管道的声明与初始化
var ch chan int //声明一个管道 (先进先出)
//初始化管道
ch = make(chan int, 9)//实例化一个管道
添加元素
package main
import "fmt"
func channelTest() {
var ch chan int //声明一个管道 (先进先出)
//初始化管道
ch = make(chan int, 9)//实例化一个管道
fmt.Println(ch)
//向管道中加入数据 向队尾加入数据
ch <- 1
ch <- 2
ch <- 3
//取出数据 从队尾取出数据
value := <-ch
fmt.Println(value)
value = <-ch
fmt.Println(value)
value = <-ch
fmt.Println(value)
ch<-100
ch<-200
ch<-300
close(ch)
//管道的遍历 遍历之前必须关闭管道否则会报错---fatal error: all goroutines are asleep - deadlock!
for els := range ch {
fmt.Println(els)
}
}
func main() {
channelTest()
}
取出元素
//取出数据 从队尾取出数据
value := <-ch
fmt.Println(value)
value = <-ch
fmt.Println(value)
value = <-ch
fmt.Println(value)
ch<-100
ch<-200
ch<-300
管道的遍历
1,使用range的方式,需要先关闭管道
2,使用普通方式:
func main() {
var g chan int
g = make(chan int, 10)
for i := 0; i < cap(g); i++ {
g <- 10
}
//遍历管道;
for i := 0; i < len(g); i++ {
v := <-g
fmt.Println(v)
}
close(g)
//这里应该是空,因为已经被取走了
for i2 := range g {
fmt.Println(i2)
}
}
管道一旦关闭,就不能再往里面添加数据了
管道的阻塞
如果管道中没有数据,那么从管道中读取数据会导致程序阻塞,直到有数据–
var ch chan int
func putM(ch chan int) {
ch <- 100
ch <- 2
ch <- 200
}
func getM(ch chan int) {
v := <-ch
fmt.Println(v)
v = <-ch
fmt.Println(v)
v = <-ch
fmt.Println(v)
v = <-ch
fmt.Println(v)
}
func main() {
var ch chan int
ch = make(chan int, 10)
ch <- 1
ch <- 2
ch <- 3
putM(ch)
getM(ch)
}
在运行阶段,go会直接抛一个异常
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.putM(...)
D:/GoData/eight/ChannelMain.go:40
main.main()
D:/GoData/eight/ChannelMain.go:60 +0x90
Process finished with the exit code 2
这是因为目前只有一个线程去管道里读取数据,多了的没办法拿走,此时会产生一个死锁出来;
在看一个代码:一个往管道里面放,另一个来取
package main
import (
"fmt"
"math/rand"
"time"
)
const name = "小明"
// 数据生产者
func producer(header string, channel chan<- string) {
fmt.Println(name)
for {
channel <- fmt.Sprintln("%s:%v", header, rand.Int31())
time.Sleep(time.Second)
}
}
//数据消费者
func customer(channel <-chan string) {
for {
message := <-channel
fmt.Println(message)
}
}
/*
*
这是程序执行的入口
*/
func main() {
channel := make(chan string) //实例化一个子字符串通道
go producer("cat", channel)
go producer("dog", channel)
customer(channel)
}
Go中的结构体
go中结构体的定义:
package main
import (
"fmt"
"time"
)
// 定义一个结构体 匿名结构体
var man struct {
name string
age int
}
//定义了一个结构体类型为Human
type Human struct {
name string
age int
birthday time.Time
country string
addr, love string
}
//为结构体编写一个方法
//这样我们也可以向java那样编写一个类似于类这样一个...
func (H Human) Say(word string) {
fmt.Println(man)
fmt.Println(H.name, H.age, H.birthday, H.country, "说", word)
}
func main() {
//准备一个结构体
xm := Human{name: "小明", age: 19, birthday: time.Now(), country: "China"}
xm.Say("我爱中国")
}
go中的结构体是面向对象编程的一个关键点;
Go中结构体指针
在go中我们可以返回一个结构指针,这么做的目的其实是为了避免值的拷贝;
例如:
取得结构体的指针方式:
func test() {
//声明一个结构体为dog
var dog Animal
//获得这个结构体的指针 即d是一个指针变量
d := &dog
fmt.Printf("%v %p\n",d,d)
//获得这个结构体的指针 即d是一个指针变量
d = &Animal{
}
fmt.Printf("%v %p\n",d,d)
//也可以为这个结构体中的属性赋值
d = &Animal{
name: "王啊啊",
age: 2,
}
fmt.Printf("%v %p\n",d,d)
//或者可以通过new()函数
//通过new()函数来获取指针
d=new(Animal)
fmt.Printf("%v %p\n",d,d)
}
观察结果我们可以看到,当我们不赋值的时候
d := &dog
d = &Animal{
}
是等价的;
结构体构造函数
通过结构体,有了类型上的区分,有了类型上的区分就有了类似java中的构造函数:
//这个可以看作是构造函数
func NewPerson(name string ,age int) *Person {
return &Person{
name: "小明",
age: 10,
}
测试一下代码:
//这个可以看作是构造函数
//返回的是Person的值
func NewPerson(name string, age int) *Person {
return &Person{
name: name,
age: age,
}
}
func main(){
fmt.Println("-----------------------------------")
fmt.Println(NewPerson("小红", 22))
}
返回值是指针和不是指针的区别:
首先对于基础的类型,没有什么太大影响,如果是对于引用类型,则就有很大的影响:
举个例子—>
源代码如下,
// 声明的全局变量
var name = "小花"
var age = 17
func main() {
fmt.Println(NewPerson(name , age ))
fmt.Println(newPerson(name , age ))
fmt.Println("\n")
fmt.Printf("%s %d name的地址%p age的地址%p\n", name, age, &name, &age)
fmt.Printf("name的地址 %p age的地址 %p\n", &(NewPerson(
name,
age,
).name), &(NewPerson(
name,
age,
).age))
//fmt.Printf("name的地址 %p age的地址 %p\n", &(newPerson(name, age).name), &(newPerson(name, age).age))
}
// 这个可以看作是构造函数
func NewPerson(name string, age int) *Person {
//fmt.Printf("%s %d name的地址%p age的地址%p\n", name, age, &name, &age)
return &Person{
name: name,
age: age,
}
}
func newPerson(name string, age int) Person {
//fmt.Printf("%s %d name的地址%p age的地址%p\n", name, age, &name, &age)
return Person{
name: name,
age: age,
}
}
如果结构体中含有匿名变量,
像这样:
只能有一个 可以不写名字的同类型变量;
可以看到 对于下划线的不配拥有内存空间
func fun() {
//niming.string="你哈珀"
//niming.int=127
niming.name="匿名"
fmt.Printf(" string %p\n ",&niming.string)
fmt.Printf(" int %p\n ",&niming.int)
fmt.Printf(" name %p\n ",&niming.name)
}
在函数中传入指针和不传入指针的区别
package main
import "fmt"
type User struct {
userName string
loginName string
password string
}
var u = User{
userName: "小明",
loginName: "xiaming",
password: "123456",
}
var su=&u
func hello(u User) User {
u.userName = "奥里给"
u.loginName = "huahua"
u.password = "123456"
fmt.Println(u)
return u
}
// 传入结构体指针
func hi(u *User) {
u.loginName = "aoligei"
u.userName = "xiaohong"
u.password = "123456"
fmt.Println(u)
}
func main() {
fmt.Println(hello(u))
hi(su)
fmt.Println(u)
}
结果如下:
传入指针时,我们在函数中修改了属性,那么指针指向的变量也会发生改变,
如果传的不是指针,那么方法中的操作不会影响原来的传过来的数据,因为传过来的参数是原数据的值拷贝;
结构体的嵌套
package main
import "fmt"
type author struct {
name string
age int
gender string
}
type book struct {
title string
human author
}
func main() {
b:=new(book)
b.title="go"
b.human.name="张三"
b.human.age=18
b.human.gender="男"
fmt.Println(b)
}
这个就像java一样,一个结构体中可以嵌套多个结构体
深拷贝与浅拷贝
浅拷贝很好理解,就是对引用类型的赋值:
例如:
func main() {
var c author
c=author{
name: "老六",
age: 19,
}
d:=c //浅拷贝
fmt.Println(d)
d.name="老七"
fmt.Println(c)
b:=new(author)//这是一个指针,这个就不一样了,修改a也会修改b
a:=b
b.name="里斯"
b.age=24
fmt.Println(a==b)
fmt.Println(a)
a.name="张三"
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
}
- 深拷贝,拷贝的是值,浅拷贝拷贝的是指针;
- 深拷贝开辟了新内存空间,修改操作不影响原先的内存;
- 浅拷贝指向的还是原来的内存空间,修改操作直接作用在运来的内存空间上;
在java中我们做过一个类的属性里面有其他类的对象,
在go中相当于一个结构体中有其他结构体B,在不同的拷贝方式下,我们修改B,会发生不同的事情----代码验证:
type author struct {
name string
age int
gender string
}
type book struct {
title string
human *author //指针
}
func main() {
//先声明一个结构体
//作者
a:=author{
name: "李盛",
age: 66,
gender: "男",
}
//书
b:=book{
title: "山丘",
human: &a,
}
//现在直接用赋值的方式
var c=b
fmt.Printf("%p\n %p\n",&c,&b)//地址不一样
fmt.Printf("%p\n %p\n",b.human,c.human)//里面的结构体指针是一样的
fmt.Println(c)
//修改c中author的属性
c.human.name="李宗盛"
//打印a中的name
fmt.Println(a.name)
}
运行结果:
如果在book中使用的不是结构体指针,那么我们来看一下代码:
type author struct {
name string
age int
gender string
}
type book struct {
title string
human author //指针
}
//深拷贝
func main() {
//先声明一个结构体
//作者
a:=author{
name: "李盛",
age: 66,
gender: "男",
}
//书
b:=book{
title: "山丘",
human: a,
}
//现在直接用赋值的方式
var c=b
fmt.Printf("%p\n %p\n",&c,&b)//地址不一样
fmt.Printf("%p\n %p\n",&b.human,&c.human)//里面的结构体指针是一样的
fmt.Println(c)
//修改c中author的属性
c.human.name="李宗盛"
//打印a中的name
fmt.Println(a.name)
}
前面的文章提到了切片,一个切片中有三部分:
所以当我们在函数中后传入切片时,也会将切片中指向数组的指针传过去,所以我们对于参数中数组的修改也会对原来数组的内容进行修改;
//声明一个切片
var str []string
func main(){
//实例化切片
s:=make([]string,0,10)
s=append(s, "你好")
//然后通过函数操作切片
sliceT(s)
fmt.Println(s)
}
//定义一个方法用于操作切片
func sliceT(s []string){
s[0]="Hello World"
}
type Student struct {
name string
age int
}
var stu []Student
func main() {
////实例化切片
//s := make([]string, 0, 10)
//s = append(s, "你好")
////然后通过函数操作切片
//sliceT(s)
//fmt.Println(s)
t:=Student{
name: "小红",
age: 0,
}
//stu:=[]Student{{name:"小花",age:18}}
arr:=[]Student{t,t,t}
fmt.Println(arr)
changeSlice(arr)
fmt.Println(arr)
fmt.Println(t.name)
}
func changeSlice(stu []Student){
stu[0].name="哈哈"
stu[1].name="hello"
}
可以看到修改arr并不会对t产生什么影响;