公告

微信

欢迎大家私信交流

Skip to content

基础语法

= 和 := 的区别

= 是赋值

:= 声明并赋值

指针的作用

指针指向任意变量的地址

指针的存在 划分了 值类型和引用类型? -> go和java/dart的区别

go
// `&` 取地址操作符
// `*` 取值操作符

func main(){
    a := 1

    // a的地址 赋给b
    b := &a

    // b地址的值 赋给c
    c := *b
}

指针的作用:

  • 获取变量的值
  • 改变变量的值
  • 指针替代值传入函数

go的返回值能为多个吗?

go允许返回多个返回值,面向错误编程

go的异常是如何处理的?

使用error类型代替try...catch语句

go
// 继承以下接口即可
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.Joinstrings.Builder > bytes.Buffer > + > fmt.Sprintf

strings.Builder 比 bytes.Buffer 比较

strings.Builder 比 bytes.Buffer 性能更快

  1. 预分配内存:sb初始化预分配一定的内存空间,进行字符串拼接时,sb直接修改切片中的内容,不需要进行额外的内存分配和拷贝操作,这是典型的空间换时间
  2. 零拷贝:sb采用了可变长度切片,bb使用固定长度切片。
  3. 字符串连接优化:sb提供了WriteString方法,直接将字符串加到[]byte切片中,不需要再进行转换和拷贝

总结:

bytes.Buffer 转化为字符串需要重新申请了一块空间存放生成的字符串变量,造成性能消耗

strings.Builder 预分配了一定的内存空间,避免了类型转换和变量拷贝

代码实现

go
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

go
func main(){
    var sampleMap map[int]int
    // sampleMap[10] 返回 value 和 bool
    if _, ok := sampleMap[10]; ok {
            ...
    } else {
            ...
    }
}

go中的_的作用

_(下划线)是空白标识符,用于以下几种情况:

  1. 忽略返回值

    • 当函数返回多个值而你不需要使用其中的某些值时,可以用_来忽略它们。
    go
    value, _ := someFunction()
  2. 导入包时避免编译错误

    • 如果你只需要导入包以执行其init函数,可以使用_
    go
    import _ "some/package"
  3. 避免变量未使用的错误

    • 用于忽略未使用的变量。
    go
    for _, value := range someSlice {
        fmt.Println(value)
    }
  4. 编译时候接口是否实现

_帮助保持代码简洁和无警告。

go支持默认参数或可选参数吗?

不支持。

但可以利用结构体参数,或者...传入参数切片数组。

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之前执行

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

go
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可以为结构体成员提供属性。常见的:

  1. 序列化/反序列化

    • 常用于 json/xml 序列化
    go
    type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
    }
  2. db的映射

    • 映射数据库的字段
    go
    type User struct {
    ID   int    `db:"id"`
    Name string `db:"name"`
    }
  3. 验证

    • 表单验证
    go
    type Product struct {
    Name  string `validate:"required"`
    Price float64 `validate:"min=0"`
    }

如何获取一个结构体的所有tag

利用反射

go
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:输出结构体的字段值。

    go
    fmt.Printf("%v\n", person)
  • %+v:输出结构体的字段名和字段值。

    go
    fmt.Printf("%+v\n", person)
  • %#v:输出结构体名称和结构体的字段名和字段值。

    go
    fmt.Printf("%+v\n", person)

go如何表示枚举值

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{}本身不占任何空间,可以避免任何多余的内存分配

    go
    type 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{}{},也是节省了空间。

    go
    func main() {
        ch := make(chan struct{}, 1)
        go func() {
                <-ch
                // do something
        }()
        ch <- struct{}{}
        // ...
    }
  • 仅有方法的结构体

    go
    type Lamp struct{}

go里面的int和int32是否同一概念

go语言中int的大小和操作系统位数相关。

32位操作系统,int大小4字节。64位操作系统,int大小8个字节。

uint型变量值分别为 1,2,它们相减的结果是多少?

结果会溢出,如果是32位系统,结果是2^32-1,如果是64位系统,结果2^64-1

上次更新于: