简单
golang里的数组和切片有了解过吗
数组和切片都是拥有相同类型一组元素集合
数组为固定长度,切片为可变长度
数组不可扩容,切片可以,切片扩容,如果不足1024每次扩容为两倍扩容,如果高于1024,为1.25倍扩容
切片实际底层指向的也是一个数组的指针,切片的底层的结构体可以看到,有 指针 容量长度
数组是值类型,将一个数组赋值给另一个数组时,传递的是一份深拷贝,赋值和函数传参操作都会复制整个数组数据,会占用额外的内存;切片是引用类型,将一个切片赋值给另一个切片时,传递的是一份浅拷贝,赋值和函数传参操作只会复制len和cap,但底层共用同一个数组,不会占用额外的内存。
for range (slice和map)时遇到的“坑”
-
func main() {
-
s := []
int{
1,
2,
3}
-
m :=
make(
map[
int]*
int)
-
for i, v :=
range s {
-
// m[i] = &v 这样是不行的,
-
// 因为for range 迭代过程是根据slice中的变量遍历出来的新变量,遍历出来的值都是同一地址。所以应该用一个变量进行接受,这样每个不同值的地址就不同了
-
n := v
-
m[i] = &n
//这样才是正确的姿势
-
}
-
fmt.Println(s)
// [1 2 3]
-
for _, v :=
range m {
-
fmt.Println(*v)
// 1 2 3
-
}
-
}
多个切片如果共享同一个底层数组,这种情况下,对其中一个切片或者底层数组的更改,会影响到其他切片
-
func main() {
-
slice1 := []
string{
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12"}
-
Q2 := slice1[
3:
6]
-
fmt.Println(Q2,
len(Q2),
cap(Q2))
// [4 5 6] 3 9
-
Q3 := slice1[
5:
8]
-
fmt.Println(Q3,
len(Q3),
cap(Q3))
// [6 7 8] 3 7
-
Q3[
0] =
"Unknown"
-
fmt.Println(Q2, Q3)
// [4 5 Unknown] [Unknown 7 8]
-
-
a := []
int{
1,
2,
3,
4,
5}
-
shadow := a[
1:
3]
-
fmt.Println(shadow, a)
// [2 3] [1 2 3 4 5]
-
shadow =
append(shadow,
100)
-
// 会修改指向数组的所有切片
-
fmt.Println(shadow, a)
// [2 3 100] [1 2 3 100 5]
-
}
使用 func copy(dst, src []Type) int 解决
-
func main() {
-
slice1 := []
string{
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12"}
-
Q2 :=
make([]
string,
3)
-
copy(Q2, slice1[
3:
6])
-
fmt.Println(Q2,
len(Q2),
cap(Q2))
// [4 5 6] 3 3
-
Q3 :=
make([]
string,
3)
-
copy(Q3, slice1[
5:
8])
-
fmt.Println(Q3,
len(Q3),
cap(Q3))
// [6 7 8] 3 3
-
Q3[
0] =
"Unknown"
-
fmt.Println(Q2, Q3)
// [4 5 6] [Unknown 7 8]
-
-
a := []
int{
1,
2,
3,
4,
5}
-
shadow :=
make([]
int,
2)
-
copy(shadow, a[
1:
3])
-
fmt.Println(shadow, a)
// [2 3] [1 2 3 4 5]
-
shadow =
append(shadow,
100)
-
fmt.Println(shadow, a)
// [2 3 100] [1 2 3 4 5]
-
}
实现slice线程安全有两种方式
通过加锁实现slice线程安全,适合对性能要求不高的场景
-
func main() {
-
var lock sync.Mutex
//互斥锁
-
a :=
make([]
int,
0)
-
var wg sync.WaitGroup
-
for i :=
0; i <
10000; i++ {
-
wg.Add(
1)
-
go
func(i int) {
-
defer wg.Done()
-
lock.Lock()
-
defer lock.Unlock()
-
a =
append(a, i)
-
}(i)
-
}
-
wg.Wait()
-
fmt.Println(
len(a))
// 10000
-
}
通过channel实现slice线程安全,适合对性能要求高的场景
-
func main() {
-
buffer :=
make(
chan
int)
-
a :=
make([]
int,
0)
-
// 消费者
-
go
func() {
-
for v :=
range buffer {
-
a =
append(a, v)
-
}
-
}()
-
// 生产者
-
var wg sync.WaitGroup
-
for i :=
0; i <
10000; i++ {
-
wg.Add(
1)
-
go
func(i int) {
-
defer wg.Done()
-
buffer <- i
-
}(i)
-
}
-
wg.Wait()
-
-
fmt.Println(
len(a))
// 10000
-
}
数组怎么转集合
-
func main() {
-
m :=
make(
map[
int]
int)
-
arr := []
int{
1,
2,
3,
4,
5}
-
for i, v :=
range arr {
-
m[i] = v
-
}
-
fmt.Println(m)
-
}
介绍一下通道
通道用于协程之间数据的传递,通道有三种类型,读和写的单向通道和双向通道
通道可以控制协程的并发数
map取一个key,然后修改这个值,原map数据的值会不会变化
-
func main() {
-
ma :=
make(
map[
int]
int)
-
ma[
1] =
123
-
updateMapValue(ma)
-
-
fmt.Println(ma[
1])
//321
-
}
-
-
func updateMapValue(ma map[int]int) {
-
ma[
1] =
321
-
}
channel有缓冲和无缓冲在使用上有什么区别?
无缓冲的与有缓冲channel有着重大差别:一个是同步的 一个是非同步的
比如
ch1:=make(chan int) 无缓冲
ch2:=make(chan int,1) 有缓冲
ch1<-1 无缓冲的
不仅仅是 向 c1 通道放 1 而是 一直要有别的协程 <-ch1 接手了 这个参数,那么ch1<-1之后的代码才会继续执行下去,要不然就一直阻塞着
而 ch2<-1 则不会阻塞,因为缓冲大小是1 (其实是缓冲大小为0)只有当 放第二个值的时候 第一个还没被人拿走,这时候才会阻塞
打个比喻
无缓冲的 就是一个送信人去你家门口送信 ,你不在家 他不走,你一定要接下信,他才会走。
无缓冲保证信能到你手上
有缓冲的 就是一个送信人去你家仍到你家的信箱 转身就走 ,除非你的信箱满了 他必须等信箱空下来。
有缓冲的 保证 信能进你家的邮箱
如何判断channel是否关闭
用 select 和 <-ch 来结合可以解决这个问题
ok的结果和含义:
true:读到数据,并且通道没有关闭。
false:通道关闭,无数据读到。
-
func main() {
-
ch :=
make(
chan
int,
10)
-
for i :=
1; i <=
9; i++ {
-
ch <- i
-
}
-
close(ch)
-
for i :=
0; i <
10; i++ {
-
select {
-
case v, ok := <-ch:
-
if ok {
-
fmt.Println(v)
-
}
else {
-
fmt.Println(
"关掉了")
-
}
-
default:
-
fmt.Println(
"没啥事")
-
}
-
}
-
}
需要注意:
case 的代码必须是 _, ok:= <- ch 的形式,如果仅仅是 <- ch 来判断,是错的逻辑,因为主要通过 ok的值来判断;
select 必须要有 default 分支,否则会阻塞函数,我们要保证一定能正常返回;
写入channel的时候判断其是否已经关闭,此时如果 channel 关闭,写入时触发panic: send on closed channel
make 与 new 的区别
简单的说,new只分配内存,make用于slice,map,和channel的初始化。
-
func main() {
-
var v *
int
-
-
*v =
8
-
-
fmt.Println(*v)
-
// 会报错
-
// panic: runtime error: invalid memory address or nil pointer dereference
-
}
解决:
-
func main() {
-
var v *
int
-
v =
new(
int)
-
-
*v =
8
-
-
fmt.Printf(
"%d\n", *v)
-
}
go语言的引用类型有什么?
map:golang中map是一种无序的、键值对的集合,其是通过key检索数据,且key类似于索引,指向数据的值,golang中常使用hash表来实现map。
pointers:golang中golang是指计算机内存中变量所在的内存地址,使用pointers可以节省内存,但golang中pointers不能进行偏移和运算,只能读取指针的位置。
slice:golang中slice是对数组的抽象,相对于数组,slice的长度是不固定的,可以追加元素,且在追加元素时可以增大slice的容量。
channel:golang中channel是指管道,是一种用于实现并行计算方程间通信的类型,允许线程间通过发送和接收来传输指定类型的数据,初始值为nil。
interface:golang中interface是指接口,是一组方法签名的集合,可以使用接口来识别一个对象够进行的操作。
function:golang中function是指函数,function不支持嵌套、重载和默认参数,但无需声明原型,常使用func关键字定义函数。
转载:https://blog.csdn.net/qq_42490050/article/details/128571402