• 4.3 Go中的字符串及派生类型


    2.字符串和字符类型

    字符串:string

    var str string
    str = "Hello Go"
    ch := str[0]  //'H'
    
    //格式化输出
    fmt.Printf("The length of \"%s\" is %d \n", str, len(str)) 
    fmt.Printf("The first character of \"%s\" is %c.\n", str, ch)
    // 转义字符:\
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    转义字符:\
    • \n :换行符
    • \r :回车符
    • \t :tab 键
    • \u 或 \U :Unicode 字符
    • \\ :反斜杠自身
    string是不可变值类型

    虽然可以通过数组下标方式访问字符串中的字符,但是和数组不同,在 Go 语言中,字符串是一种不可变值类型,一旦初始化之后,它的内容不能被修改,比如看下面这个例子:

    str := "Hello world"
    str[0] = 'X' // 编译错误
    
    • 1
    • 2

    编译器报错:cannot assign to str[0]

    字符编码

    Go 语言中字符串默认是 UTF-8 编码的 Unicode 字符序列,所以可以包含非 ANSI 字符,比如「Hello, 学院君」可以出现在 Go 代码中。但需要注意的是,如果你的 Go 代码需要包含非 ANSI 字符,保存源文件时请注意编码格式必须选择 UTF-8。特别是在 Windows 下一般编辑器都默认存为本地编码,比如中国地区可能是 GBK 编码而不是 UTF-8,如果没注意这点在编译和运行时就会出现一些意料之外的情况。

    字符串的编码转换是处理文本文档(比如 TXT、XML、HTML 等)时非常常见的需求,不过 Go 语言默认仅支持 UTF-8 和 Unicode 编码,对于其他编码,Go 语言标准库并没有内置的编码转换支持。不过,所幸的是我们可以很容易基于 iconv 库包装一个。这里有一个开源项目可供参考:https://github.com/qiniu/iconv。

    字符串操作
    1. 连接
    str := "hello"
    str = str + ", continue"  // hello, continue
    str += str //hellohello
    
    • 1
    • 2
    • 3

    string.Join()

    s := strings.Join([2]string{"ajwlf","20"}, ",")  
    
    • 1

    buffer.WriteString() 效率比较高

    var buffer bytes.Buffer
    buffer.WriteString("ajwlf");
    buffer.WriteString(",");
    buffer.WriteString("20");
    fmt.Printf("%s", buffer.String())  //ajwlf,20
    
    • 1
    • 2
    • 3
    • 4
    • 5
    2.切片
    str = "hello, world"
    str_1 := str[:5]  // 获取索引5(不含)之前的子串
    str_2 := str[7:]  // 获取索引7(含)之后的子串
    str_3 := str[0:5]  // 获取从索引0(含)到索引5(不含)之间的子串
    fmt.Println(str_1)
    fmt.Println(str_2)
    fmt.Println(str_3)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    上述代码打印结果如下:

    hello
    world
    hello
    
    • 1
    • 2
    • 3
    3. 遍历

    第一种方式:字节数组遍历

    str := "Hello, 世界" 
    n := len(str) 
    for i := 0; i < n; i++ {
        ch := str[i]    // 依据下标取字符串中的字符,类型为byte
        fmt.Println(i, ch) 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这个例子的输出结果为:

    0 72 
    1 101 
    2 108 
    3 108 
    4 111 
    5 44 
    6 32 
    7 228 
    8 184 
    9 150 
    10 231 
    11 149 
    12 140
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    可以看出,这个字符串长度为 13,尽管从直观上来说,这个字符串应该只有 9 个字符。这是因为每个中文字符在 UTF-8 中占 3 个字节,而不是 1 个字节。

    第二种方式:Unicode遍历

    str := "Hello, 世界" 
    for i, ch := range str { 
        fmt.Println(i, ch)    // ch 的类型为 rune 
    }
    
    • 1
    • 2
    • 3
    • 4

    这个时候,打印的就是 9 个字符了,以 Unicode 字符方式遍历时,每个字符的类型是 rune(早期的 Go 语言用 int类型表示 Unicode 字符),而不是 byte

    4.常用操作

    len(str): 求长度

    + 或 fmt.Sprintf(): 拼接字符串

    strings.Split(): 分割字符串

    strings.Contains() : 判断是否包含

    strings.HasPrefix() 或 stings.HasSuffix() 前后缀判断

    strings.Index() 或 strings.LastIndex() 字串出现的位置

    strings.Join(a[] string, seq string) join操作

    3. 派生类型

    数组是一组具有相同数据类型在内存中有序存储的数据集合

    特点

    • 长度固定,不能修改
    • 赋值和函数传递过程是值复制,涉及到内存 cop
    (1)数组

    声明方式 : var name [size]varType

    var arr= [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}  
    var arr1 [6]int
    arr:=[5]int{1,2,3,4,5}
    //如果设置了数组的长度,我们还可以通过指定下标来初始化元素:
    
    var arr1 = [5]float32{1: 2.0, 3: 1323.0}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    数组长度不确定的情况下:

    var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0} 
    balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
    
    • 1
    • 2

    多维数组的声明方式: var var_name[size1][size2]....[sizeN] varType

    a := [3][4] int{  
        {0, 1, 2, 3} ,   /*  第一行索引为 0 */
        {4, 5, 6, 7} ,   /*  第二行索引为 1 */
        {8, 9, 10, 11},   /* 第三行索引为 2 */
     
        //注意:以上代码中倒数第二行的 } 必须要有逗号,因为最后一行的 } 不能单独一行,也可以写成这样:  
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    go语言还支持创建元素数量不一致的多维数组

    package main
     
    import "fmt"
     
    func main() {
        // 创建空的二维数组
        animals := [][]string{}
     
        // 创建三一维数组,各数组长度不同
        row1 := []string{"fish", "shark", "eel"}
        row2 := []string{"bird"}
        row3 := []string{"lizard", "salamander"}
     
        // 使用 append() 函数将一维数组添加到二维数组中
        animals = append(animals, row1)
        animals = append(animals, row2)
        animals = append(animals, row3)
     
        // 循环输出
        for i := range animals {
            fmt.Printf("Row: %v\n", i)
            fmt.Println(animals[i])
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    (2)切片
    切片(slice)是一组具有相同数据类型在内存中有序存储的可扩容的数据集合
    
    • 1

    声明方式:

    var 切片名 []数据类型
    var slice []int    //此时切片只是一个nil,还没有开辟空间。
    make([]数据类型,长度)
    var slice []int = make([]int, 10) //会开辟传入的大小的空间
    
    • 1
    • 2
    • 3
    • 4

    我们可以通过len() cap()函数来求取我们的slice的长度和容量(长度就是我们当前slice中存了多少了元素,而容量是slice最大可以容纳的元素数量。)

    • 切片的截取

    切片在截取的时候返回的新的切片是指向原来的切片的内存地址的。感觉类似于浅拷贝,改变截取后的切片的值也会同步的改变原切片的值。

    func main() {
    	//切片的截取
    	slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
     
    	//切片名[起始下标:结束下标:容量]  左闭右开 包含起始下标 不包含结束下标
    	//s := slice[2:7]
    	//fmt.Println(s)
    	//s:=slice[2:]
    	//fmt.Println(s)
    	//s:=slice[:5]
    	//fmt.Println(s)
    	s := slice[2:5:6] //实际容量=容量-起始下标
    	fmt.Println(s)
    	//fmt.Println(len(s))
    	//fmt.Println(cap(s))
    	s[0] = 333
    	//切片的截取 是将新的切片指向源切片的内存地址  修改一个会影响另外一个
    	fmt.Println(s)
    	fmt.Println(slice)
     
    	fmt.Println(unsafe.Sizeof(slice))
    	fmt.Println(unsafe.Sizeof(s))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 添加元素

    遇到错误:append(s, 6) (value of type []int) is not used

    s := append(s, 6) --> s = append(s, 6)

    package main
    
    import "fmt"
    
    func main() {
    	s := []int{1, 2, 3, 4, 5}
    	s = append(s, 6)
    	s = append(s, 7, 8)
    
    	fmt.Printf("s: %v\n", s)
    
    	s2 := []int{3, 4, 5}
    	s3 := []int{6, 7, 8}
    
    	s3 = append(s2, s3...)  //添加另外一个切片
    	fmt.Printf("s3: %v\n", s3)
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    out:

    s: [1 2 3 4 5 6 7 8]
    s3: [3 4 5 6 7 8]
    
    • 1
    • 2
    • 删除元素
    func main() {
    	index := 1
    	s := []int{1, 2, 3, 4, 5}
    	s = append(s[:index], s[index+1:]...) //删除s[index]	
    	fmt.Printf("s: %v\n", s)
    
    }
    
    //out: s: [1 3 4 5]
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    请添加图片描述

    func main() {
    
    	s := []int{1, 2, 3, 4, 5}
    	s3 := []int{}
    	s4 := make([]int, 5)
    	copy(s3, s)
    	copy(s4, s)
    	fmt.Printf("s: %v\n", s)
    	fmt.Printf("s3: %v\n", s3)
    	fmt.Printf("s4: %v\n", s4)
    
    }
    /*
    	copy( destSlice, srcSlice []T) int
        其中 srcSlice 为数据来源切片,destSlice 为复制的目标(也就是将 srcSlice 复制到 destSlice),目标切片必须分配过空间且足够承载复制的元素个数,并且来源和目标的类型必须一致,copy() 函数的返回值表示实际发生复制的元素个数。
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    (3)map
    • map的声明 赋值

    声明方式:var map_variable map[key_data_type]value_data_type

    m1 := map[string]string{
    		"name": "zhangsan",
    		"age":  "22",
    	}
    	fmt.Println(m1)
     
    	m2 := make(map[string]int) //m2 == empty map
     
    	var m3 map[string]int //m3 == nil
     
    	fmt.Print(m2, m3)
     
    	fmt.Print("遍历...\n")
    	for i, v := range m1 {
    		fmt.Println(i, v)
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 底层基于 Hash 实现,基于 Key-Value,无序的数据集合

    • 函数类型、字典类型和切片类型不能作为 key,不支持的操作类型会导致 panic

    • 检测值是否存在

    name := m1["name"]
    fmt.Println(name)
    //可以返回一个ok值 判断我们的key是否正确
    nema, ok := m1["nema"] //找不到这个键 就会返回false
    fmt.Print(nema, ok)
    //所以可以改进代码
    if name, ok := m1["name"]; ok == true {
        fmt.Println(name)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • var m map[string]int // nil 类型,添加和修改会导致 panic
    • nil: len/map[key]/delete(m, key) // 可以正常工作
    • map 默认并发不安全,多个 goroutine 写同一个 map,引发竞态错误, go run –race 或者 go build - race
    • map 对象即使删除了全部的 key,但不会缩容空间
    (5)指针

    只要将数据存储在内存中都会为其分配内存地址。内存地址使用十六进数据表示。
    内存为每一个字节分配一个 32 位或 64 位的编号(与 32 位或者 64 位处理器相关)。
    可以使用运算符 & (取地址运算符)来获取数据的内存地址。

    func main() {
    	a := 10
    	//取出变量a在内存的地址
    	//fmt.Println(&a)
    	var p *int = &a
    	//fmt.Println(p)
    	//fmt.Println(&a)
    	//通过指针间接修改变量的值
    	*p = 132
    	fmt.Println(a)
     
    	//const MAX int = 100
    	//fmt.Println(&MAX)//err 不可以获取常量的内存地址
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    func main() {
    	//定义指针 默认值为nil 指向内存地址编号为0的空间  内存地址0-255为系统占用 不允许用户读写操作
    	//var p *int = nil
    	//*p = 123 //err
    	//fmt.Println(*p)
     
    	//开辟数据类型大小的空间 返回值为指针类型
    	//new(数据类型)
    	var p *int
    	p = new(int)
    	*p = 123
    	fmt.Println(*p)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  • 相关阅读:
    Xshell远程连接配置 Ubuntu 18.04.6 + Anaconda + CUDA + Cudnn + Pytorch(GPU+CPU)
    C专家编程 第4章 令人震惊的事实:数组和指针并不相同 4.2 我的代码为什么无法运行
    uni-app、小程序项目对pages.json文件拆分、动态生成pages.json文件、动态生成分包配置pages.json文件的解决方案
    【Nov 8th to 13th】Personal work record
    RabbitMQ 入门系列:9、扩展内容:死信队列:真不适合当延时队列。
    C++变量与基本类型
    数据分析:微生物组差异丰度方法汇总
    1.1 HTML4
    docker下移除不使用的镜像、容器、卷、网络
    【Linux】vim_gcc_动静态库
  • 原文地址:https://blog.csdn.net/qq_40893490/article/details/127770133