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)
// 转义字符:\

转义字符:\
  • \n :换行符
  • \r :回车符
  • \t :tab 键
  • \u 或 \U :Unicode 字符
  • \\ :反斜杠自身
string是不可变值类型

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

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

编译器报错: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

string.Join()

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

buffer.WriteString() 效率比较高

var buffer bytes.Buffer
buffer.WriteString("ajwlf");
buffer.WriteString(",");
buffer.WriteString("20");
fmt.Printf("%s", buffer.String())  //ajwlf,20
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)

上述代码打印结果如下:

hello
world
hello
3. 遍历

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

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

这个例子的输出结果为:

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

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

第二种方式:Unicode遍历

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

这个时候,打印的就是 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}

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

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}

多维数组的声明方式: 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 */
 
    //注意:以上代码中倒数第二行的 } 必须要有逗号,因为最后一行的 } 不能单独一行,也可以写成这样:  

}

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])
    }
}
(2)切片
切片(slice)是一组具有相同数据类型在内存中有序存储的可扩容的数据集合

声明方式:

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

我们可以通过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))
}
  • 添加元素

遇到错误: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)

}

out:

s: [1 2 3 4 5 6 7 8]
s3: [3 4 5 6 7 8]
  • 删除元素
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]


请添加图片描述

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() 函数的返回值表示实际发生复制的元素个数。
*/
(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)
	}
  • 底层基于 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)
}
  • 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 不可以获取常量的内存地址
}
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)
}

猜你喜欢

转载自blog.csdn.net/qq_40893490/article/details/127770133
4.3