zoukankan      html  css  js  c++  java
  • Golang---基本类型(string)

        摘要:由于在实习过程中,做的项目都是基于 Golang 语言,所以在面试时,面试官也一定会理所当然的问 Golang, 所以在最近一段时间,主要学习这门语言的基础知识,以及常出的面试题。

    简单介绍

      字符串虽然在 Go 语言中是基本类型 string, 但是它实际上是由字符组成的数组,类似于 C 语言中的  char [] ,作为数组会占用一片连续的内存空间。Go 语言中的字符串其实只是一个只读的字节数组,不支持直接修改 string 类型变量的内存空间,比如下面代码就是不支持的:

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        s := "hello"
        s[0] = 'A'
    
        fmt.Println(s)
    }
    
    //.main.go:9:7: cannot assign to s[0]
    err-example1

      如果我们想修改字符串,我们可以将这段内存拷贝到堆或者栈上,将遍历的类型转换为 []byte 之后就可以进行,修改后通过类型转换就可以变回 string, 对原变量重新赋值即可。

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        s := "hello"
        sByte := []byte(s)
        sByte[0] = 'A'
    
        //重新赋值
        s = string(sByte)
    
        fmt.Println(s)
    }
    
    //Aello
    right-example1

    数据结构

        字符串在 Go 语言中的接口其实非常简单,每一个字符串在运行时都会使用如下的 StringHeader 结构体表示,其实在”运行时“内部,有一个私有的结构 stringHeader, 它有着完全相同的结构,只是用于存储数据的 Data 字段使用了 unsafe.Pointer 类型:

    // StringHeader is the runtime representation of a string.
    // It cannot be used safely or portably and its representation may
    // change in a later release.
    // Moreover, the Data field is not sufficient to guarantee the data
    // it references will not be garbage collected, so programs must keep
    // a separate, correctly typed pointer to the underlying data.
    type StringHeader struct {
        Data uintptr
        Len  int
    }
    
    // stringHeader is a safe version of StringHeader used within this package.
    type stringHeader struct {
        Data unsafe.Pointer
        Len  int
    }
    string-struct

    声明方式

       使用双引号

    s := "hello world"

       使用反引号

    s := `hello world`

    使用双引号可其它语言没有什么大的区别,如果字符串内部出现双引号,要使用 进行转义;但使用反引号则不需要,方便进行更加复杂的数据类型,比如 Json:

    s := `{"name": "sween", "age": 18}`

    注:上面两种格式的解析函数分别为cmd/compile/internal/syntax.scanner.stdString

    cmd/compile/internal/syntax.scanner.rawString

    类型转换

        在我们使用 Go 语言解析和序列化 Json 等数据格式时,经常需要将数据在 string 和 []byte 之间进行转换,类型转换的开销其实并没有想象中的那么小。

    []byte 到 string 的转换

    runtime.slicebytetostring 这个函数中进行转换的处理,我们看下源码:

    // slicebytetostring converts a byte slice to a string.
    // It is inserted by the compiler into generated code.
    // ptr is a pointer to the first element of the slice;
    // n is the length of the slice.
    // Buf is a fixed-size buffer for the result,
    // it is not nil if the result does not escape.
    func slicebytetostring(buf *tmpBuf, ptr *byte, n int) (str string) {
        if n == 0 {
            // Turns out to be a relatively common case.
            // Consider that you want to parse out data between parens in "foo()bar",
            // you find the indices and convert the subslice to string.
            return ""
        }
        if n == 1 {
            p := unsafe.Pointer(&staticuint64s[*ptr])
            if sys.BigEndian {
                p = add(p, 7)
            }
            stringStructOf(&str).str = p
            stringStructOf(&str).len = 1
            return
        }
    
        var p unsafe.Pointer
        if buf != nil && n <= len(buf) {
            p = unsafe.Pointer(buf)
        } else {
                    //step1: 分配内存空间
            p = mallocgc(uintptr(n), nil, false)
        }
        stringStructOf(&str).str = p
        stringStructOf(&str).len = n
            //step2:执行内存拷贝操作
        memmove(p, unsafe.Pointer(ptr), uintptr(n))
        return
    }
    []byte 转 string 源码

    string 到 []byte 的转换

    runtime.stringtoslicebyte 这个函数中进行转换的处理,我们看下源码:

    func stringtoslicebyte(buf *tmpBuf, s string) []byte {
        var b []byte
        if buf != nil && len(s) <= len(buf) {
                    //step1: 如果缓冲区够用,直接用
            *buf = tmpBuf{}
            b = buf[:len(s)]
        } else {
                    //step2: 如果缓冲区不够用,重新分配一个
            b = rawbyteslice(len(s))
        }
            //step3: 执行内存拷贝操作
        copy(b, s)
        return b
    }
    
    // rawbyteslice allocates a new byte slice. The byte slice is not zeroed.
    func rawbyteslice(size int) (b []byte) {
        cap := roundupsize(uintptr(size))
        p := mallocgc(cap, nil, false)
        if cap != uintptr(size) {
            memclrNoHeapPointers(add(p, uintptr(size)), cap-uintptr(size))
        }
    
        *(*slice)(unsafe.Pointer(&b)) = slice{p, size, int(cap)}
        return
    }
    string 转 []byte 源码

    总结

        字符串和 []byte 中的内容虽然一样,但是字符串的内容是只读的,我们不能通过下标或者其它形式改变其中的数据,而 []byte 中的内容是可读写的,无论哪种类型转换到另一种类型都需要对其中的内容进行拷贝,而内存拷贝的性能损耗会随着字符串和 []byte 长度的增长而增长。所以在做类型转换时候一定要注意性能的损耗。

    参考资料:

    https://draveness.me/golang/docs/part2-foundation/ch03-datastructure/golang-string/

  • 相关阅读:
    C/C++数组名与指针区别深入探索(转)
    mysql 的编译安装
    rpm的问题 ~/.rpmmacros %_rpmlock_path
    GCC中的弱符号与强符号(转)
    关于printf系列函数
    如何修改机器名
    multiple definition of XXXX 的解决
    由无名对象(临时对象)引发的关于“引用”的思考
    关于date中时间字符串的格式
    月薪不同,面试题不同!
  • 原文地址:https://www.cnblogs.com/zpcoding/p/13597903.html
Copyright © 2011-2022 走看看