1 与其他语言相比,使用go有什么好处?
- 简洁易学:相比其他编程语言,Go语言具有清晰简洁的语法和规范,减少了代码的复杂性。Go语言拥有较少的关键字和一致的格式,使得代码易于编写、阅读和维护。新手可以很快上手并开始开发应用程序。
- 并发编程:Go语言内置了强大的并发编程功能,使得编写高效、可扩展的并发程序变得容易。它通过“goroutine”来实现并发,而不是使用传统的线程。goroutine是一种轻量级的执行单元,可以在一个Go程序中创建成千上万个goroutine,而且切换的开销非常低。此外,Go语言还提供了丰富的并发相关的工具和库,比如通道(channel)和互斥锁(mutex),以帮助开发人员更好地控制并发操作。
- 高效性能:Go语言的编译器(Go编译器)在编译代码时能够生成高度优化的机器码,从而提供了出色的性能。Go语言还拥有垃圾回收器(garbage collector),能够自动处理内存管理,减轻了开发人员的负担。此外,Go语言的运行时库(runtime library)是非常轻量级的,不仅能够减少内存占用,还提供了许多基本功能的支持,比如网络、文件操作等。
- 跨平台支持:Go语言可以在不同的操作系统和硬件架构上运行,包括Windows、Linux、MacOS等。这使得开发人员可以轻松地将Go程序部署到不同的平台上,而不需要进行大量的修改和适配。同时,Go语言提供了一个工具(称为交叉编译器),可以在一个平台上编译出可以运行在另一个平台上的可执行文件。
- 开发效率:Go语言通过提供丰富的标准库和工具,使开发人员能够更快地构建应用程序。Go语言的标准库提供了各种常用的功能和工具,比如文件处理、网络编程、加密、并发等,使开发人员能够快速构建稳定和高效的应用程序。此外,Go语言还有许多第三方库和框架,提供了更多的功能和工具,进一步提高了开发效率。
综上所述,Go语言具有简洁易学、并发编程、高效性能、跨平台支持和高开发效率等优点。这些优点使得Go语言在不同领域的应用程序开发中变得越来越受欢迎。无论是构建Web应用程序、后端服务、分布式系统还是云原生应用程序,Go语言都是一个强大而可靠的选择。
2 Golang使用什么数据类型?
go语言的数据类型有:1、布尔型;2、数值类型(可分为整型和浮点型);3、字符串类型;4、指针类型;5、数组类型;6、结构化类型;7、Channel类型;8、函数类型;9、切片类型;10、接口类型;11、Map类型。
基本数据类型说明:
类型 | 描述 |
---|---|
uint | 32位或64位 |
uint8 | 无符号 8 位整型 (0 到 255) |
uint16 | 无符号 16 位整型 (0 到 65535) |
uint32 | 无符号 32 位整型 (0 到 4294967295) |
uint64 | 无符号 64 位整型 (0 到 18446744073709551615) |
int | 32位或64位 |
int8 | 有符号 8 位整型 (-128 到 127) |
int16 | 有符号 16 位整型 (-32768 到 32767) |
int32 | 有符号 32 位整型 (-2147483648 到 2147483647) |
int64 | 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807) |
byte | uint8的别名(type byte = uint8) |
rune | int32的别名(type rune = int32),表示一个unicode码 |
uintptr | 无符号整型,用于存放一个指针是一种无符号的整数类型,没有指定具体的bit大小但是足以容纳指针。 uintptr类型只有在底层编程是才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。 |
float32 | IEEE-754 32位浮点型数 |
float64 | IEEE-754 64位浮点型数 |
complex64 | 32 位实数和虚数 |
complex128 | 64 位实数和虚数 |
整型:
- 整型数据分为两类,
有符号
和无符号
两种类型。有符号: int, int8, int16, int32, int64。无符号: uint, uint8, uint16, uint32, uint64, byte - 不同位数的整型区别在于能保存整型数字范围的大小;有符号类型可以存储任何整数,无符号类型只能存储自然数
- int和uint的大小和系统有关,32位系统表示int32和uint32,如果是64位系统则表示int64和uint64
- byte与uint8类似,一般用来存储单个字符
- 在保证程序正确运行下,尽量使用占用空间小的数据类型
- fmt.Printf("%T", var_name)输出变量类型
- unsafe.Sizeof(var_name)查看变量占用字节
浮点型:浮点型也就是小数类型,可以存放小数。比如6.6,-12.34
- 关于浮点数在机器中存放形式的简单说明,浮点数=符号位+指数位+尾数位
- 尾数部分可能丢失,造成精度损失。-123.0000901,float64的精度要比float32的要准确,如果我们要保存一个精度高的数,则应该选择float64。golang的浮点型默认为float64类型,
- 0.123可以简写成.123,也支持科学计数法表示:5.1234e2 等价于512.34
字符:Golang中没有专门的字符类型,如果要存储单个字符(字母),一般使用byte来保存。
字符串就是一串固定长度的字符连接起来的字符序列。Go的字符串是由单个字节连接起来的,也就是说对于传统的字符串是由字符组成的,而Go的字符串不同,它是由字节组成的。
- 字符只能被单引号包裹,不能用双引号,双引号包裹的是字符串
- 当我们直接输出type值时,就是输出了对应字符的ASCII码值
- 当我们希望输出对应字符,需要使用格式化输出
- Go语言的字符使用UTF-8编码,英文字母占一个字符,汉字占三个字符
- 在Go中,字符的本质是一个整数,直接输出时,是该字符对应的UTF-8编码的码值。
- 可以直接给某个变量赋一个数字,然后按格式化输出时%c,会输出该数字对应的unicode字符
- 字符类型是可以运算的,相当于一个整数,因为它们都有对应的unicode码
- 如果我们保存的字符大于255,比如存储汉字,这时byte类型就无法保存,此时可以使用uint或int类型保存
布尔型:
- 布尔类型也叫做bool类型,bool类型数据只允许取值true或false
- bool类型占1个字节
- bool类型适用于逻辑运算,一般用于流程控制
字符串:
- 字符串一旦赋值了,就不能修改了:在Go中字符串是不可变的。
- 字符串的两种标识形式
- 双引号,会识别转义字符
- 反引号,以字符串的原生形式输出,包括换行和特殊字符,可以实现防止攻击、输出源代码等效果
指针:
- 基本数据类型,变量存的就是值,也叫值类型
- 获取变量的地址,用&,比如var num int,获取num的地址:&num
- 指针类型,指针变量存的是一个地址,这个地址指向的空间存的才是值,比如:var ptr *int = &num
- 获取指针类型所指向的值,使用:*,比如,var ptr *int,使用*ptr获取ptr指向的值
- 值类型,都有对应的指针类型,形式为*数据类型,比如int对应的指针就是*int,float64对应的指针类型就是*float64,依此类推。
- 值类型包括:基本数据类型、数组和结构体struct
值类型与引用类型:
- 值类型:变量直接存储值,内存通常在栈中分配
- 引用类型:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何变量应用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收。
- Golang中值类型和引用类型的区分
- 值类型:基本数据类型(int系列、float系列、bool、string)、数组和结构体
- 引用类型:指针、slice切片、map、管道chan、interface等都是引用类型
3 Go程序的包是什么?
Go语言是使用包来组织源代码的,包(package)是多个 Go 源码的集合,是一种高级的代码复用方案。Go语言中为我们提供了很多内置包,如 fmt、os、io 等。
Golang 中的包与文件夹是一一对应的,必须创建在 GOPATH 目录下才可以被使用。Golang 中的一个包需要引用另一个包的内容,那么必须在开始使用 import 关键字进行导入才可以使用。
任何源代码文件必须属于某个包,同时源码文件的第一行有效代码必须是 package pacakgeName
语句,通过该语句声明自己所在的包。
4 Go支持什么类型的数据转换?
Golang支持有符号整数、无符号整数、浮点数、布尔、字符串等类型的转换,类型转换的语法为:T(x),其中T表示要转换成的类型,x表示要转换的值。
整型类型转换:
在Go语言中整数类型包括有符号整数和无符号整数,支持转换的整数类型有int8、int16、int32、int64、uint8、uint16、uint32和uint64。其中,int8和uint8称为字节类型,int16和uint16称为短整数类型,int32和uint32称为长整数类型,int64和uint64称为长长整数类型。
整数类型的转换需要注意以下两点:
- 转换时如果值范围超过要转换到的类型值范围,则会溢出,导致结果不准确。例如,将一个比int8范围大的值转换成int8类型,结果则会在[-128, 127]范围内出现不准确的值。需要避免整数类型的溢出问题。
- 只有同种类型、或者从低精度类型向高精度类型的转换是安全的。例如,从int8转换成int16是安全的,而从int16转换成int8是不安全的,因为在转换成int8时,可能会截取部分数据,导致结果不准确。
浮点型类型转换:
在Go语言中,浮点数类型包括float32和float64,支持转换的浮点数类型只能是float32和float64。浮点数类型的转换也需要注意两点:
- 转换时如果值范围过大或者过小,可能会溢出。
- 只能从低精度类型向高精度类型转换,从高精度类型向低精度类型转换可能会丢失精度。
布尔类型转换:
在Go语言中,布尔类型只有true和false两个值,支持转换的类型只有int和字符串类型。将布尔值转换成int时,true会转换成1,false转换成0。将布尔值转换成字符串时,true转换成"true",false转换成"false"。
字符串类型转换:
在Go语言中,字符串是由字符序列组成的(不可变的)数组,支持转换的类型只有原始类型。字符串转换可以通过strconv包实现。将整数转换成字符串时,可以使用 strconv.Itoa() 函数,将浮点数转换成字符串时,可以使用 strconv.FormatFloat() 函数。
在Go语言中,类型转换是一个非常重要的概念。类型转换可以将不同类型之间的值进行转换,从而满足程序的需要。但需要注意,在进行类型转换时,需要避免数据类型范围溢出和精度丢失的问题,同时需要保证转换后的类型和转换前的类型兼容。
5 什么是Goroutine?如何停止它?
Goroutine是Golang语言中轻量级线程的实现,可以在一个或多个线程上运行。它是由Go运行时系统管理的,是一个独立、轻量级的执行单元。与传统线程不同,goroutine运行时,它是由Go运行时系统自动进行调度,而且它的调度模型也是非常高效的,它不仅可以很好地支持高并发,而且还能够充分利用多核CPU的优势。大多数 Go 语言程序同时使用数千个 Goroutine。要创建 Goroutine,可以在函数声明之前添加 go 关键字。
停止goroutine是一个比较复杂的问题,因为goroutine在运行时没有提供显式的终止和暂停的方法。这是由于Go语言的设计目标之一就是让goroutine在运行时尽量不被阻塞,因此需要一些特殊的技巧来停止它们。
- 使用channel
使用channel是最简单、最安全的停止goroutine的方法之一。可以创建一个bool类型的channel,当我们需要停止goroutine时,给这个channel发送一个信号,goroutine收到信号后就可以正常退出了。
注:每次给channel发送一个信号,只能退出一个协程方法。并不能退出所有的。package main import ( "fmt" "time" ) func worker1(stopChan chan bool) { for { select { case <-stopChan: fmt.Println("worker1 stopped") return default: fmt.Println("worker1 is running") time.Sleep(time.Second) } } } func worker2(stopChan chan bool) { for { select { case <-stopChan: fmt.Println("worker2 stopped") return default: fmt.Println("worker2 is running") time.Sleep(time.Second) } } } func main() { stopchan := make(chan bool) go worker1(stopchan) go worker2(stopchan) time.Sleep(3 * time.Second) stopchan <- true //stopchan <- true time.Sleep(2 * time.Second) }
- 使用Context
在Go1.7中,标准库中加入了context包,提供了一种新的挂起、取消goroutine的方法。我们可以在每个goroutine中传入一个Context参数,然后在需要停止goroutine时,对这个Context变量执行cancel操作。
注:跟channel方式不同,cancle操作执行一次,停止所有协程。package main import ( "context" "fmt" "time" ) func worker3(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("worker3 stopped") return default: fmt.Println("worker3 is running") time.Sleep(time.Second) } } } func worker4(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("worker4 stopped") return default: fmt.Println("worker4 is running") time.Sleep(time.Second) } } } func main() { ctx, cancle := context.WithCancel(context.Background()) go worker3(ctx) go worker4(ctx) time.Sleep(3 * time.Second) cancle() time.Sleep(2 * time.Second) }
- 使用Mutex和WaitGroup
通过共享变量的方式来控制goroutine的停止,这种方法需要使用sync包中的Mutex和WaitGroup。Mutex用于保护共享变量的读写,WaitGroup用于等待所有goroutine完成。package main import ( "fmt" "sync" "time" ) var wg = sync.WaitGroup{} var stop bool var mutex = sync.Mutex{} func worker5() { defer wg.Done() for { mutex.Lock() if stop { mutex.Unlock() fmt.Println("work5 stopped") return } fmt.Println("work is running") mutex.Unlock() time.Sleep(time.Second) } } func main() { for i := 0; i < 3; i++ { wg.Add(1) go worker5() } time.Sleep(3 * time.Second) mutex.Lock() stop = true mutex.Unlock() wg.Wait() time.Sleep(2 * time.Second) }
- 使用runtime包
使用runtime包的方法则要稍微麻烦一些,在goroutine运行的代码中,需要定期地检查全局变量,然后在需要停止goroutine时,使用runtime包中的函数强制将其终止。package main import ( "fmt" "runtime" "time" ) var stop bool func work6() { for { if stop { fmt.Println("work6 stopped") return } fmt.Println("work6 is running") time.Sleep(time.Second) } } func main() { go work6() time.Sleep(3 * time.Second) stop = true time.Sleep(2 * time.Second) runtime.Goexit() }
6 如何在运行时检查变量类型?
整理了三种方法,interface{}.(type)、reflect.TypeOf(varibale)、fmt.Printf("%T\n", varibale)。详情查看下面代码。
package main
import (
"fmt"
"reflect"
)
// 方法1
func typeofObject1(variable interface{}) string {
switch variable.(type) {
case int:
return "int"
case float32:
return "float32"
case float64:
return "float64"
case bool:
return "boolean"
case string:
return "string"
default:
// 如果是结构体类型,返回结构体名称
v := reflect.ValueOf(variable)
if v.Kind() == reflect.Struct {
return v.Type().Name() + " struct{}"
}
return "unknown"
}
}
// 方法2
func typeofObject2(variable any) interface{} {
r := reflect.TypeOf(variable)
return r
}
func main() {
var num float64 = 3.14
type1 := typeofObject1(num)
type2 := typeofObject2(num)
// 方法3:
fmt.Printf("%T\n", num)
fmt.Println(type1)
fmt.Println(type2)
}
7 Go两个接口之间可以存在什么关系?
- 如果两个接口有相同的方法列表,那么他们就是等价的,可以相互赋值。
- 如果接口A的方法列表是接口B的方法列表的子集,那么接口B可以赋值给接口A。
- 接口查询是否成功,要在运行期才能够确定。
8 Go中同步锁有什么特点?作用是什么?
- 当一个goroutine(协程)获得了Mutex后,其他gorouline(协程)就只能乖乖的等待,除非该gorouline释放了该Mutex
- RWMutex在 读锁 占用的情况下,会阻止写,但不阻止读
- RWMutex在 写锁 占用情况下,会阻止任何其他goroutine(无论读和写)进来,整个锁相当于由该goroutine独占
同步锁的作用是保证资源在使用时的独有性,不会因为并发而导致数据错乱,保证系统的稳定性。
9 Go语言当中channel有什么特点?
- 如果给一个 nil 的 channel 发送数据,会造成永远阻塞
- 如果从一个 nil 的 channel 中接收数据,也会造成永久爱阻塞
- 给一个已经关闭的 channel 发送数据, 会引起 pannic
- 从一个已经关闭的 channel 接收数据, 如果缓冲区中为空,则返回一个零值
10 Go语言中的channel缓冲有什么特点?
无缓冲的 channel是同步的,而有缓冲的channel是非同步的。
11 Go语言中的cap函数可以作用于哪些内容?
cap函数用于获取切片、数组或通道的容量,即可以存储的元素数量的最大值。可以作用于的类型有:
- array(数组)
- slice(切片)
- channel(通道)
12 Go convey是什么?一般用来做什么?
- go convey是一个支持golang的单元测试框架
- go convey能够自动监控文件修改并启动测试,并可以将测试结果实时输出到Web界面
- go convey提供了丰富的断言简化测试用例的编写
13 Go语言当中new的作用是什么?
new关键字是用来分配内存的函数,new(Type)作用是为T类型分配并清零一块内存,并将这块内存地址作为结果返回。也就是说new(T)会为类型为T的新项分配已置零的内存空间,并返回它的地址。在go中,返回一个指针,指针指向新分配的内存,类型为T类型的零值。
14 Go语言中make的作用是什么?
make
的作用是为slice, map or chan
的初始化 然后返回引用 make
函数是内建函数,函数定义:
func make(Type, size IntegerType) Type
make(T, args)
函数的目的和new(T)
不同 仅仅用于创建slice, map, channel
而且返回类型是实例
15 Go语言中切片和数组的区别是什么?
数组:
- 数组固定长度
- 数组长度是数组类型的一部分,所以
[3]int
和[4]int
是两种不同的数组类型 - 数组需要指定大小,不指定也会根据处初始化对的自动推算出大小,不可改变
- 数组是通过值传递的
切片:
- 切片可以改变长度
- 切片是轻量级的数据结构,三个属性,指针,长度,容量
- 不需要指定大小
- 切片是地址传递(引用传递)
- 可以通过数组来初始化,也可以通过内置函数
make()
来初始化,初始化的时候len=cap
,然后进行扩容
16 Go语言中的值传递、地址传递(引用传递)
- 值传递只会把参数的值复制一份放进对应的函数,两个变量的地址不同,不可相互修改。
- 地址传递(引用传递)会将变量本身传入对应的函数,在函数中可以对该变量进行值内容的修改。