基础语法
= 和 := 的区别
=
是赋值
:=
声明并赋值
指针的作用
指针指向任意变量的地址
指针的存在 划分了 值类型和引用类型? -> go和java/dart的区别
// `&` 取地址操作符
// `*` 取值操作符
func main(){
a := 1
// a的地址 赋给b
b := &a
// b地址的值 赋给c
c := *b
}
指针的作用:
- 获取变量的值
- 改变变量的值
- 指针替代值传入函数
go的返回值能为多个吗?
go允许返回多个返回值,面向错误编程
go的异常是如何处理的?
使用error类型代替try...catch语句
// 继承以下接口即可
type error interface {
Error() string
}
// errors.New() 定义自己的异常
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
// 多一个函数当作构造函数
func New(text string) error {
return &errorString{text}
}
什么是协程(goroutine)
协程是用户态轻量级线程,线程调度的基本单位。
使用go关键字就可以实现并发。
goroutine以很小的栈启动,空间为2kb或4kb。当栈空间不足时,栈会自动收缩。
go如何拼接字符串
方法预览:
+
对字符串进行遍历,计算并开辟新空间存储字符串fmt.Sprintf
采用接口参数,使用反射获取值,性能损耗strings.Builder
使用WriteString()进行拼接,内部实现是切片+指针,同时返回拼接字符串bytes.Buffer
底层是[]byte切片strings.Join
适合拼接字符串切片
性能比较 => strings.Join
≈ strings.Builder
> bytes.Buffer
> +
> fmt.Sprintf
strings.Builder 比 bytes.Buffer 比较
strings.Builder 比 bytes.Buffer 性能更快
- 预分配内存:sb初始化预分配一定的内存空间,进行字符串拼接时,sb直接修改切片中的内容,不需要进行额外的内存分配和拷贝操作,这是典型的空间换时间
- 零拷贝:sb采用了可变长度切片,bb使用固定长度切片。
- 字符串连接优化:sb提供了
WriteString
方法,直接将字符串加到[]byte切片中,不需要再进行转换和拷贝
总结:
bytes.Buffer 转化为字符串需要重新申请了一块空间存放生成的字符串变量,造成性能消耗
strings.Builder 预分配了一定的内存空间,避免了类型转换和变量拷贝
代码实现
func main(){
a := []string{"c", "y", "帅"}
a := []string{"的", "一"}
//方式1:+
ret := a[0] + a[1] + a[2]
//方式2:fmt.Sprintf
ret := fmt.Sprintf("%s%s%s", a[0],a[1],a[2])
//方式3:strings.Builder
var sb strings.Builder
sb.WriteString(a[0])
sb.WriteString(a[1])
sb.WriteString(a[2])
ret := sb.String()
//方式4:bytes.Buffer
buf := new(bytes.Buffer)
buf.Write(a[0])
buf.Write(a[1])
buf.Write(a[2])
ret := buf.String()
//方式5:strings.Join
ret := strings.Join(a, b)
}
rune类型
打个岔,神马是ASCII码
ASCII只能表示 英文在内的 128个字符
unicode应运而生,ASCII的超集,包含世界上书写系统中的所有字符
回归正题,神马是rune
rune 是 int32 的别名
Go 语言中,字符串的底层表示是 byte (8 bit) 序列,而非 rune (32 bit) 序列。
如何判断map中是否包含某个key
func main(){
var sampleMap map[int]int
// sampleMap[10] 返回 value 和 bool
if _, ok := sampleMap[10]; ok {
...
} else {
...
}
}
go中的_
的作用
_
(下划线)是空白标识符,用于以下几种情况:
忽略返回值:
- 当函数返回多个值而你不需要使用其中的某些值时,可以用
_
来忽略它们。
govalue, _ := someFunction()
- 当函数返回多个值而你不需要使用其中的某些值时,可以用
导入包时避免编译错误:
- 如果你只需要导入包以执行其
init
函数,可以使用_
。
goimport _ "some/package"
- 如果你只需要导入包以执行其
避免变量未使用的错误:
- 用于忽略未使用的变量。
gofor _, value := range someSlice { fmt.Println(value) }
编译时候接口是否实现
_
帮助保持代码简洁和无警告。
go支持默认参数或可选参数吗?
不支持。
但可以利用结构体参数,或者...
传入参数切片数组。
// 传入结构体参数
struct Options {
concurrent bool
}
func test(offset int64, len int64, o *Options) {
//...
}
// 这个函数可以传入任意数量的整型参数
func sumN(nums ...int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}
defer的执行顺序
defer属于是后进先出
在当前函数执行完毕后但在在return或者panic之前执行
func test() int {
i := 0
defer func() {
fmt.Println("defer1")
}()
defer func() {
i += 1
fmt.Println("defer2")
}()
return i
}
func main() {
fmt.Println("return", test())
}
// 输出顺序
// defer2
// defer1
// return 0
test返回值并没有修改,这是由于Go的返回机制决定的,执行Return语句后,Go会创建一个临时变量保存返回值
如果是有名返回 也就是指明返回值 func test() (i int)
func test() (i int) {
i = 0
defer func() {
i += 1
fmt.Println("defer2")
}()
return i
}
func main() {
fmt.Println("return", test())
}
// defer2
// return 1
如何交换2个变量的值
对于变量而言 => a,b = b,a
对于指针而言 => *a,*b = *b,*a
tag的用处
tag可以为结构体成员提供属性。常见的:
序列化/反序列化
- 常用于 json/xml 序列化
gotype Person struct { Name string `json:"name"` Age int `json:"age"` }
db的映射
- 映射数据库的字段
gotype User struct { ID int `db:"id"` Name string `db:"name"` }
验证
- 表单验证
gotype Product struct { Name string `validate:"required"` Price float64 `validate:"min=0"` }
如何获取一个结构体的所有tag
利用反射
type Author struct {
Name int `json:"Name"`
Publications []string `json:"Publication,omitempty"`
}
func main() {
// reflect.TypeOf => 获取对象的类型
t := reflect.TypeOf(Author{})
// NumField() => 获取结构体成员的数量
for i := 0; i < t.NumField(); i++ {
// Field(i) => 获取第i个成员的名字
name := t.Field(i).Name
// FieldByName(name) => 通过name获取成员属性
s, _ := t.FieldByName(name)
// Tag => 获得标签
fmt.Println(name, s.Tag)
}
}
如何判断2个字符串切片(slice)是相等的
reflect.DeepEqual()
: 反射非常影响性能
结构体打印时,%v
和%+v
的区别
%v
:输出结构体的字段值。gofmt.Printf("%v\n", person)
%+v
:输出结构体的字段名和字段值。gofmt.Printf("%+v\n", person)
%#v
:输出结构体名称和结构体的字段名和字段值。gofmt.Printf("%+v\n", person)
go如何表示枚举值
const (
B = 1 << (10 * iota) // 1 << (10 * 0) = 1
KB = 1 << (10 * iota) // 1 << (10 * 1) = 1024
MB = 1 << (10 * iota) // 1 << (10 * 2) = 1048576
GB = 1 << (10 * iota) // 1 << (10 * 3) = 1073741824
)
空struct{}
的用途(没懂)
用map模拟一个set,那么就要把值设置为struct{}, struct{}本身不占任何空间,可以避免任何多余的内存分配
gotype Set map[string]struct{} func main() { set := make(Set) for _, item := range []string{"A", "A", "B", "C"} { set[item] = struct{}{} } fmt.Println(len(set)) // 3 if _, ok := set["A"]; ok { fmt.Println("A exists") // A exists } }
有时候给通道发送一个空结构体,channel<-struct{}{},也是节省了空间。
gofunc main() { ch := make(chan struct{}, 1) go func() { <-ch // do something }() ch <- struct{}{} // ... }
仅有方法的结构体
gotype Lamp struct{}
go里面的int和int32是否同一概念
go语言中int的大小和操作系统位数相关。
32位操作系统,int大小4字节。64位操作系统,int大小8个字节。
uint型变量值分别为 1,2,它们相减的结果是多少?
结果会溢出,如果是32位系统,结果是2^32-1,如果是64位系统,结果2^64-1