Go语言学习实践 数组 字符串 切片

Go语言学习实践 数组 字符串 切片,第1张

文章目录 数组(1)定义数组的几种方式(2)遍历数组的几种方式 字符串(1)数据结构(2)字符串的只读特性(3)rune和byte类型 切片(slice)(1)定义和结构分析(2)切片几种定义方式(3)使用 make() 函数构造切片(4)切片的遍历方式(5)添加切片元素和删除切片元素

数组 (1)定义数组的几种方式
/**
  定义数组的几种方式
 */
func test1() {
	var a [3]int // 定义一个长度为3的int类型数组, 元素 全部为0
	var b = [...]int{1, 2, 3} // 定义一个长度为3的int类型数组, 元素 为 1, 2, 3
	var c = [...]int{2: 3, 1: 2} // 定义一个长度为3的int类型数组, 元素 为 0, 2, 3
	var d = [...]int{1, 2, 4: 5, 6} // 定义一个长度为6的int类型数组, 元素 为 1, 2, 0, 0, 5, 6
	fmt.Print(a,b,c,d)
}

第一种方式是定义一个数组变量的最基本的方式,数组的长度明确指定,数组中的 每个元素都以零值初始化。
第二种方式定义数组,可以在定义的时候顺序指定全部元素的初始化值,数组的长 度根据初始化元素的数目自动计算。
第三种方式是以索引的方式来初始化数组的元素,因此元素的初始化值出现顺序比 较随意。这种初始化方式和 map[int]Type 类型的初始化语法类似。数组的长度 以出现的最大的索引为准,没有明确初始化的元素依然用0值初始化。
第四种方式是混合了第二种和第三种的初始化方式,前面两个元素采用顺序初始 化,第三第四个元素零值初始化,第五个元素通过索引初始化,最后一个元素跟在 前面的第五个元素之后采用顺序初始化。

Go语言中数组是值语义。一个数组变量即表示整个数组,它并不是隐式的指向第一个元素的指针(比如C语言的数组),而是一个完整的值。当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。如果数组较大的话,数组的赋值也会有较大的开销。为了避免复制数组带来的开销,可以传递一个指向数组的指针,但是数组指针并不是数组。

(2)遍历数组的几种方式
/**
  遍历数组的几种方式
*/
func iterateArr() {
	var arr = [...]int{1, 3, 4, 6, 8, 4}
	for i := range arr {
		fmt.Printf("数组下标:%d  数组元素:%d  ", i, arr[i])
	}
	fmt.Println()
	for i, v := range arr {
		fmt.Printf("数组下标:%d  数组元素:%d  ", i, v)
	}
	fmt.Println()
	for i := 0; i < len(arr); i++ {
		fmt.Printf("数组下标:%d  数组元素:%d  ", i, arr[i])
	}
}


用 for range 方式迭代的性能可能会更好一些,因为这种迭代可以保证不会出现数组越界的情形,每轮迭代对数组元素的访问时可以省去对下标越界的判断。

字符串 (1)数据结构

Go语言字符串的底层结构在 reflect.StringHeader 中定义:

type StringHeader struct {
 Data uintptr
 Len int 
}

字符串结构由两个信息组成:第一个是字符串指向的底层字节数组,第二个是字符串的字节的长度。字符串其实是一个结构体,因此字符串的赋值 *** 作也就是 reflect.StringHeader 结构体的复制过程,并不会涉及底层字节数组的复制。

字符串“Hello, world”本身对应的内存结构:

分析可以发现,“Hello, world”字符串底层数据和以下数组是完全一致的:

var data = [...]byte{'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o' , 'r', 'l', 'd'}
(2)字符串的只读特性

string类型的数据是不可变的。一旦声明了一个string类型的标识符,无论是变量还是常量,那么该标识符所指代的数据在整个程序的生命周期内便无法被更改。

/**
  测试字符串不可变
*/
func test3() {
	var str="Hello Go"
	fmt.Printf("原始值:%s \n",str)
	var str2=[]byte(str)
	str2[0]='A'       //修改切片后的值
	fmt.Printf("切片的值:%s \n",str2)
	fmt.Printf("原字符串更新后值:%s \n",str)
}


在上述示例中,通过对原字符串进行切片,然后修改切片后的值。通过输出的结果可以看到切片后的字符串发生了改变,但是原来的字符串没有改变,这说明字符串是不可变的,切片 *** 作的数据是原字符串的复制并重新分配的底层存储,其 *** 作不会影响原字符串的 *** 作。

(3)rune和byte类型

字符串中的每一个元素叫做“字符”,在遍历或者单个获取字符串元素时可以获得字符。

Go语言的字符有以下两种:
一种是 uint8 类型,或者叫 byte 型,代表了 ASCII 码的一个字符。
另一种是 rune 类型,代表一个 UTF-8 字符,当需要处理中文、日文或者其他复合字符时,则需要用到 rune 类型。rune 类型等价于 int32 类型。

byte 类型是 uint8 的别名,对于只占用 1 个字节的传统 ASCII 编码的字符来说,完全没有问题,例如 var ch byte = ‘A’,字符使用单引号括起来。
在 ASCII 码表中,A 的值是 65,使用 16 进制表示则为 41,所以下面的写法是等效的:

var ch byte = 65 或 var ch byte = '\x41'      //(\x 总是紧跟着长度为 216 进制数)

在书写 Unicode 字符时,需要在 16 进制数之前加上前缀\u或者\U。因为 Unicode 至少占用 2 个字节,所以我们使用 int16 或者 int 类型来表示。如果需要使用到 4 字节,则使用\u前缀,如果需要使用到 8 个字节,则使用\U前缀。

Unicode 包中内置了一些用于测试字符的函数,这些函数的返回值都是一个布尔值,如下所示(其中 ch 代表字符):
判断是否为字母:unicode.IsLetter(ch)
判断是否为数字:unicode.IsDigit(ch)
判断是否为空白符号:unicode.IsSpace(ch)

/**
   字符串长度
 */
func strLen() {
	var chinese = "Hello  中国" //5个英文字符 2个空格 2个中文汉字,UTF编码中文占3个字符
	fmt.Println("直接使用len函数", len(chinese))
	fmt.Println("转换byte类型后长度", len([]byte(chinese)))
	fmt.Println("转换rune类型后长度", len([]rune(chinese)))
	fmt.Println("使用utf8.RuneCountInString后长度", utf8.RuneCountInString(chinese))
}

切片(slice) (1)定义和结构分析

切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型,这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内。
切片就是一种简化版的动态数组。因为动态数组的长度是不固定,切片的长度自然也就不能是类型的组成部分了。数组虽然有适用它们的地方,但是数组 的类型和 *** 作都不够灵活,因此在Go代码中数组使用的并不多。而切片则使用得相 当广泛。

切片的结构定义, reflect.SliceHeader :

type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}

Data表示存储的数据;
Len表示切片的元素个数
Cap 表示切片指向的内存空间的最大容量(对应元素的个数,而不是字节数)

【网上看到的一个可参考的易懂的描述】
Go语言中切片的内部结构包含地址、大小和容量,切片一般用于快速地 *** 作一块数据集合,如果将数据集合比作切糕的话,切片就是你要的“那一块”,切的过程包含从哪里开始(切片的起始位置)及切多大(切片的大小),容量可以理解为装切片的口袋大小,如下图所示。

(2)切片几种定义方式
/**
  切片定义方式
*/
func sliceDefinition() {
	var (
		a []int               // nil切片, 和 nil 相等, 一般用来表示一个不存在的切片
		b = []int{}           // 空切片, 和 nil 不相等, 一般用来表示一个空的集合
		c = []int{1, 2, 3}    // 有3个元素的切片, len和cap都为3
		d = c[:2]             // 有2个元素的切片, len为2, cap为3,从c中切了俩个元素,但是容量没变
		e = c[0:2:cap(c)]     // 有2个元素的切片, len为2, cap为3
		f = c[:0]             // 有0个元素的切片, len为0, cap为3
		g = make([]int, 3)    // 有3个元素的切片, len和cap都为3
		h = make([]int, 2, 3) // 有2个元素的切片, len为2, cap为3
		i = make([]int, 0, 3) // 有0个元素的切片, len为0, cap为3
	)
	print(a, b, c, d, e, f, g, h, i)
}

和数组一样,内置的 len 函数返回切片中有效元素的长度,内置的 cap 函数返回 切片容量大小,容量必须大于或等于切片的长度

(3)使用 make() 函数构造切片

如果需要动态地创建一个切片,可以使用 make() 内建函数,格式如下:

make( []Type, size, cap )

其中 Type 是指切片的元素类型,size 指的是为这个类型分配多少个元素,cap 为预分配的元素数量,这个值设定后不影响 size,只是能提前分配空间,降低多次分配空间造成的性能问题。

func makeTest()  {
	a := make([]int, 2)
	b := make([]int, 2, 10)
	fmt.Println(a, b)
	fmt.Println(len(a), len(b))
	fmt.Println(cap(a), cap(b))
}

(4)切片的遍历方式
/**
  切片遍历
*/
func sliceIterate() {
	var a = []int{1, 4, 5}

	for i := range a {
		fmt.Printf("b[%d]: %d \n", i, a[i])
	}
	for i, v := range a {
		fmt.Printf("b[ %d]: %d \n", i, v)
	}
	for i := 0; i < len(a); i++ {
		fmt.Printf("b [ %d]: %d \n", i, a[i])
	}
}

(5)添加切片元素和删除切片元素
/**
  切片 *** 作
*/
func sliceOperation() {
	fmt.Println("在切片的尾部追加")
	var a = []int{1, 4, 5}
	a = append(a, 6)                 // 追加1个元素,元素为6
	a = append(a, 1, 2, 3)           // 追加多个元素
	a = append(a, []int{1, 2, 3}...) // 追加一个切片, 切片需要解包
	fmt.Println(a)
	fmt.Println("在切片的头部追加")
	var b = []int{1, 4, 5}
	b = append([]int{7}, b...) // 在开头添加1个元素 a = append([]int{-3,-2,-1}, a...) // 在开头添加1个切片
	fmt.Println(b)
	fmt.Println("删除尾部元素")
	var c = []int{1, 2, 3, 6, 5, 2}
	c = c[:len(c)-1] // 删除尾部1个元素
	c = c[:len(c)-2] // 删除尾部N个元素
	fmt.Println(c)
	fmt.Println("删除开头元素")
	var d = []int{1, 2, 3, 6, 8, 1}
	d = d[1:] // 删除开头1个元素
	d = d[2:] // 删除开头N个元素
	fmt.Println(d)
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/990854.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存