Go中的管道,结构体

本文介绍了Go语言中的管道(channel)作为goroutine间通信的机制,包括其初始化、添加与取出元素、遍历以及阻塞情况。同时,文章详细阐述了结构体的定义、指针、构造函数以及结构体嵌套。还讨论了深拷贝与浅拷贝的区别,并通过示例展示了在并发环境下如何处理数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述

管道

管道底层是一个环形队列(先进先出);

管道是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)
}
  1. 深拷贝,拷贝的是值,浅拷贝拷贝的是指针;
  2. 深拷贝开辟了新内存空间,修改操作不影响原先的内存;
  3. 浅拷贝指向的还是原来的内存空间,修改操作直接作用在运来的内存空间上;

在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)

}

在这里插入图片描述

前面的文章提到了切片,一个切片中有三部分:

Go基本数据类型

在这里插入图片描述

所以当我们在函数中后传入切片时,也会将切片中指向数组的指针传过去,所以我们对于参数中数组的修改也会对原来数组的内容进行修改;

//声明一个切片
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产生什么影响;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeMartain

祝:生活蒸蒸日上!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值