Golang面试

news/2024/11/8 21:05:26/

简单

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 := vm[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 9Q3 := slice1[5:8]fmt.Println(Q3, len(Q3), cap(Q3)) // [6 7 8] 3 7Q3[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 3Q3 := make([]string, 3)copy(Q3, slice1[5:8])fmt.Println(Q3, len(Q3), cap(Q3)) // [6 7 8] 3 3Q3[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线程安全有两种方式

  1. 通过加锁实现slice线程安全,适合对性能要求不高的场景

func main() {var lock sync.Mutex //互斥锁a := make([]int, 0)var wg sync.WaitGroupfor 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
}
  1. 通过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.WaitGroupfor 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] = 123updateMapValue(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 = 8fmt.Println(*v)// 会报错// panic: runtime error: invalid memory address or nil pointer dereference
}

解决:

func main() {var v *intv = new(int)*v = 8fmt.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关键字定义函数。


http://www.ppmy.cn/news/9908.html

相关文章

正则表达式总结,正则表达式匹配不包含某个字符串

1、匹配a标签及其url&#xff1a; Regex regA new Regex("<a[\s][^<>]*href(?:""|)([^<>""])(?:""|)[^<>]*>([^<>])</a>", RegexOptions.IgnoreCase); 说明&#xff1a;在上面的正则表达式中…

【bioinfo】酶切法片段化建库相比超声打断建库引入softclip使用FADE软件识别/去除

FADE软件参考文献 参考文献&#xff1a;片段化酶诱导的双链伪影的表征和缓解 - PMC (nih.gov) 文献提供的酶切产生的错误识别和去除软件FADE&#xff1a;软件git地址 文献补充材料图&#xff1a;由酶切产生的人工错误序列的碱基质量值偏高&#xff0c;相对其他softclip的质量…

【库函数】-了解回调函数,并且手把手带你学习qsort函数!!还不知道的赶快进来看看

&#x1f387;作者&#xff1a;小树苗渴望变成参天大树 &#x1f389;作者宣言&#xff1a;认真写好每一篇博客 &#x1f38a;作者gitee:link 如 果 你 喜 欢 作 者 的 文 章 &#xff0c;就 给 作 者 点 点 关 注 吧&#xff01; qsort&#x1f9e8; 前言✨一、什么是回调函数…

(七)汇编语言——更灵活的定位内存地址的方法

目录 and和or ASCII码 [bxidata] SI和DI寄存器 [bxsi]和[bxdi] [bxsiidata]和[bxdiidata] 总结 例子&#xff08;双重循环的解决方案&#xff09; 我们知道&#xff0c;对于汇编来说&#xff0c;内存是极为重要的&#xff0c;所以&#xff0c;能精准且巧妙地定位内存地…

node.js快速入门指南

Node.js迅速蹿红&#xff0c;衍生了一个强大的开源社区、支持企业&#xff0c;甚至还拥有属于自己的技术大会。我把这种成功归结于它的简介&#xff0c;高校&#xff0c;同时提高了编程生产力。 Node.js 的前置知识很多&#xff0c;例如以下知识 JavaScriptES6Ajax 还不会的…

Compose跨平台第一弹:体验Compose for Desktop

前言 Compose是Android官方提供的声明式UI开发框架&#xff0c;而Compose Multiplatform是由JetBrains 维护的&#xff0c;对于Android开发来说&#xff0c;个人认为学习Jetpack Compose是必须的&#xff0c;因为它会成为Android主流的开发模式&#xff0c;而compose-jb作为一…

寒假本科创新——机器学习(二)

绪论1.3归纳偏好 一般原则&#xff1a;奥卡姆剃刀 什么样的算法比较好&#xff1f;1.4NFL定理 NFL定理的前提&#xff1a; NFL定理的寓意&#xff1a;1.3归纳偏好 归纳偏好&#xff08;lnductive Bias&#xff09;&#xff1a; 机器学习算法在学习过程中对某种类型假设的偏好…

JAVA并发编程工具篇--1.1理解Future获取线程执行结果

背景&#xff1a;在并发编程中&#xff0c;我们可以使用Future来获取子线程执行的结果&#xff0c;然后在主线程一起进行业务处理&#xff1b; 那么Future是如何来工作的&#xff1b; 1 使用&#xff1a; demo1&#xff1a;使用Future每次都阻塞获取任务的执行结果&#xff1a…