golang学习2-字符串和部分复合类型1

本文详细介绍了Go语言中数组、切片、字符串的使用,包括它们的定义、操作及与其他语言的区别。强调了切片作为动态数组的概念,以及在处理数组和字符串时的注意事项。同时,概述了map和list(链表)的数据结构,讨论了它们的操作方法和适用场景。最后,探讨了Go中字符串的特殊性质,如不可变性和UTF-8编码处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

字符串与数组,切片,复合数据结构

数组

静态语言的数组

  • 大小确定
  • 类型一致
  • 数组内存分配时是连续空间

定义

初始化
func arraytest() {
	// 一维数组定义 全部定义和java反着来
	var arr1 [5]int
	arr2 := [3]int{1, 3, 5}
	arr3 := [...]int{2, 4, 6, 8, 10}
	arr4 := [2]int{} // 不赋值会初始化为默认值
	arr := [10]int {0:100, 2:200, 8:800} // 可以用index:value的形势指定赋值
	fmt.Println(arr)
 	    [100 0 200 0 0 0 0 0 800 0] // 打印结果
 	// 二维数组定义
	var grid [4][6]bool // 4行6列
	fmt.Println(grid)
	fmt.Println(arr1, arr2, arr3)
	// 数组遍历
	for i := 0; i < len(arr3); i++ {
		fmt.Println(arr3[i])
	}
	// range单个返回值为索引 两个返回值为索引,值 若不用索引只取值 可用匿名变量_取代i
	for i, v := range arr3 {
		fmt.Println(i, v)
	}
}

记录最大值的索引与值
	maxi := -1
	maxValue := -1
	for i, v := range arr3 {
		if v > maxValue {
			maxi, maxValue = i, v
		}
	}
	return maxi, maxValue

range类似python的 for i, element in enumerate(seq):

数组操作

  • 求长度len(arr)
  • 数组的遍历 for index,element := range arr {} or for i:= 0; i < len(arr); i++ {}

数组是值类型 函数传递要拷贝

a := [2]string{}
b := [3]string{}
a和b不是同一种类型, 长度不一样的数组类型不一样 a类型[2]string b类型[3]string 故而函数传参数组 类型必须一致
数组当参数必须指定大小 且值传递 修改某个索引的值不会引起调用方数组的修改
func printArray(arr [5]int) {
	for _, v := range arr { 
		fmt.Println(v)
	}
	arr[0] = 100
}
[10]int 和[5]int是不同类型 printArray只能传[5]int
对应指针操作可以修改
func printArray(arr *[5]int) {
	for _, v := range arr {
		fmt.Println(v)
	}
	// 以下两种操作等价
	arr[0] = 100
	(*arr)[1] = 100
}
调用printArray(&arr)

go一般不直接使用数组 一般用切片
python的值传递是list[:]

切片

slice 相当于动态数组 用法类似python的list

  • a := []int{1,2}
  • a := make([]int, len, cap) **
  • a := arr[:]
  • a := *new([]int) 表达式new(T)将创建一个T类型的匿名变量
    • new()返回指针 *new() 返回指针指向的类型 new([]int) 为*[]int类型 *new([]int) 为[]int切片
创建slice
var s []int // zero value for slice is nil 定义的切片会默认初始化 默认值为nil
	var s []int
	// nil可直接使用append 此时会分配一个容量为1的slice
	for i := 0; i < 100; i++ {
		s = append(s, i * 2)
	}
s := []int{1,2,3} // 直接定义slice
s := make([]int, 16) // 分配一个长度len 16的slice 全是默认值0
s := make([]int, 16, 32) // 分配一个长度len 16 容量cap为32数组的slice



一般是半开半闭区间 前闭后开
	arr := [...]int{0,1,2,3,4,5,6,7}
	a := arr[2:5] // 2到4
	a = arr[:5] // 0到4
	a = arr[2:] // 2到7
	a = arr[:] // 全部的视图

切片是对数组的视图 相当于引用 修改切片会导致原数组发生修改
函数调用
func printArraySlice(arr []int) {// []int 相当于切片传参
	for _, v := range arr {  // python用for in list 内部实现了一个魔法方法__contains__ 语法糖
		fmt.Println(v)
	}
}
// 调用
printArraySlice(arr[:])

slice本身没有数据,是对底层array的一个view
slice组成
    ptr(初始指向) len(控制索引操作) cap(视图的长度)
    可以向后扩展不能向前 向后扩展不能超越底层数组cap(s) s[i]不可以超越len(s)
    cap长度为初始指针到视图的结尾

reslice操作

a = a[:]
a = a[2:]
a = a[:5]
相当于a[2:5]

slice操作

a := [...]int{0,1,2,3,4,5,6,7}
a1 = a[2:6] // 一共4个值
a1[4] // 报错不存在
a2 = a1[3:5] // 此时取3,4可以取出来
即切片操作针对的是原始视图,索引操作针对切片本身
即      0,1,2,3
a[1:3]      1, 2, 灰3
	a := arr[2:3]
	a1 := a[3:5]
	fmt.Printf("arr=%v, len(arr)=%d, cap(arr)=%d", a1, len(a1), cap(a1))
	a1=[5 6], len(a1)=2, cap(a1)=3

切片函数 操作

func append(slice []Type, elems ...Type) []Type   // 可追加多个元素
    添加元素:添加元素时若超越cap,系统会重新分配更大的底层数组
                    扩容机制:申请两倍容量的数组
                由于值传递的关系,必须接收append的返回值
                s = append(s, val)
func copy(dst, src []Type) int 
    copy slice
    copy(slice1, slice2)
        将s2的值赋值到s1 取交集赋值

删除操作
    s2 = append(s2[:3], s2[4:]...) // 删除第3个元素 后面的元素前移
        对于可变参数... 可传递slice 需要在后面加...
头尾弹出
    head := s2[0]
    s2 = s2[1:]
    tail := s2[len(s2) - 1]
    s2 = s2[:len(s2) - 1]

go切片和python的列表区别

  • go的slice是数据结构,python的slice是一种操作,python数据结构是list
  • slice基于数组实现,list也是数组实现
  • go的slice切片获取底层数组的引用其操作会影响底层数组,python的list切片获取新的底层数组 复制
  • go静态语言对处理器友好 python动态语言对开发者友好

slice原理

1. 内置函数copy(a, b) b只复制a长度的元素给a
    因为b不会拓展a的空间
2. a := b[:] 修改a a和b都能显示修改的值
    a,b底层指向同一个数组
    b = [3]int{}
    a := b // 值拷贝 复制的数组 修改拷贝不会修改原数组
    c := b[:] // 切片数组引用 修改切片会修改数组
3. 内置函数a = append(a, 1) 参数a和b指向同一个数组 append后返回的a和b显示的元素不一致
    切片的扩容机制 动态扩容
    cap为0 扩容到1 之后每次扩容为原来的2倍
    当旧切片长度大于等于1024 每次扩容1/4 即新容量是原来的1.25
    扩容之后的新数组和之前的数组是两个数组 切片会指向新的数组
4. cap(a) 切片容量大于等于 len(a) 切片长度
5.make遇到append; a := make([]int, 2); b := append(a, 3); 打印 0 0 3 make的len定义数组已经存放了元素 一共len个
    定义空切片 数组容量3 a := make([]int, 0, 3)
  • 切片的底层是数组 由Ptr *Elem; Len int; Cap int;组成
  • 切片的Cap是Ptr指向位置向后所有容量 即 切片只能向后切不能向前切 a := b[2:5] c := a[3:6] 但是不能 c := a[1:6]

list

go也有list 基于指针的链表一般用不到

  • list 的初始化有两种方法:分别是使用 New() 函数和 var 关键字声明,两种方法的初始化效果都是一致的
    • 通过 container/list 包的 New() 函数初始化 list
      • a := list.New()
    • 通过 var 关键字声明初始化 list
      • var a := list.List
  • 列表与切片和 map 不同的是,列表并没有具体元素类型的限制,因此,列表的元素可以是任意类型
  • 双链表支持从队列前方或后方插入元素,分别对应的方法是 PushFront 和 PushBack
l := list.New()
element := l.PushBack("1") // 尾push 返回元素句柄*list.Element 配合其他方法删除或插入元素
l.PushFront(2) // 头push
// 返回一个 *list.Element 结构,若需要删除插入的元素,通过 *list.Element 配合 Remove() 方法进行删除
// 在1之后添加3
l.InsertAfter("3", element)
// 在1之前添加4
l.InsertBefore("4", element)
// 删除1
l.Remove(element)
方法作用
InsertAfter(v interface {}, mark * Element) * Element在 mark 点之后插入元素,mark 点由其他插入函数提供
InsertBefore(v interface {}, mark * Element) *Element在 mark 点之前插入元素,mark 点由其他插入函数提供
PushBackList(other *List)添加 other 列表元素到尾部
PushFrontList(other *List)添加 other 列表元素到头部

遍历list链表:遍历双链表需要配合 Front() 函数获取头元素,遍历时只要元素不为空就可以继续进行,每一次遍历都会调用元素的 Next() 函数

l := list.New()
// 尾部添加
l.PushBack("1")
// 头部添加
l.PushFront(2)
for i := l.Front(); i != nil; i = i.Next() {
    fmt.Println(i.Value)
}

map

map

  • map的key需要支持’==’ 和 ‘!=’ 比较运算符 不能用 function, map,slice
    • 数组和不含有不支持类型的结构体可当key
  • map判断包含 v, exist := m[“k”] 通过exist的布尔值判断;python的字典 通过 k in dict
定义 map[key type]value type 
func cache() {
    // make分配内存的map
	cache := make(map[string]string) // cache == empty map
	cache["name"] = "cache"
	// 普通map
	m := map[string]string {
    	"key": "value",
    }
    // var定义
    var m2 map[string]int // m2 == nil
    // 复合map
	m1 := map[string]map[string]string {
		"key": {
			"key": "value",
		},
	}
	// 函数
	mf := map[string]func(int, int) int{
		"add": func(a, b int) int {
			return a + b
		},
		"subtract": func(a int, b int) int {
			return a - b
		},
	}
	// 调用
	fmt.Println(mf["add"](3, 4))
}

遍历
for k, v := range m { // 可用匿名变量省略v
	fmt.Println(k, v) // 遍历无序
}

map是hashmap 其元素无序

赋值
    一个返回值
    k := m["key"] // key不存在返回默认值 字符串是空串 int是0 type是nil
    两个返回值
    k, exist := m["key"] // 若key不存在 k赋值默认值 exist赋值false

删除操作
    delete(m, "key") // 之后再获取exist为false 不能用m["key"] = nil 返回的exist依然为true

map操作总结

创建:make(map[k]v)
获取元素:m[k]
key不存在,获得value类型的初始值
用value, exist := m[k] 来判断是否存在key
用delete()函数删除一个key

遍历 
    使用range遍历k或k,v
    不保证遍历顺序,若需要顺序则获取key放入slice手动排序
    使用len获取元素个数

map的key
    map使用哈希表,必须能比较相等
    除了slice,map,function的内建类型都可以作为key
    Struct类型不包含上述字段,也可作为key

无重复字符的最长字串 只能用于英文数字 2字节的byte

func max(x, y int) int {
	if x < y {
		return y
	}
	return x
}
// 分左右指针 i为左指针
func lengthOfLongestSubstring(str string) int {
	m := map[byte]bool{}
	n := len(str)
	end, ans := -1, 0
	for i := 0; i < n; i++ {
		if i > 0 {
			delete(m, str[i - 1])
		}
		for end+1 < n && !m[str[end+1]] {
			m[str[end + 1]] = true
			end++
		}
		ans = max(ans, end - i + 1)
	}
	return ans
}
// 遍历一遍 i为右指针
// 对每一个字母
/**
    lastOccurred[x]不存在,或者< start 即索引落在当前指针左边 无视
    lastOccurred[x] >= start 索引落在当前指针右边 则更新start为索引左1
**/
func lengthOfLongestSubstr(s string) int {
	lastOccurred := make(map[byte]int)
	start, maxLength := 0, 0
	for i, ch := range []byte(s) { // string索引取出来的是int32 rune
		if lastIndex, ok := lastOccurred[ch]; ok && lastIndex >= start {
			start = lastIndex + 1
		}
		maxLength = max(maxLength, i - start + 1)
		lastOccurred[ch] = i
	}
	return maxLength
}

字符串

  • 字符串是常量,可通过数组索引访问字节单元,但不能修改某字节的值
  • 字符串转为[]byte(s)切片会复制 数据量大时注意 发生类型强转都会发生内存拷贝
    a := ""
    b := []byte(a)
  • 避免类型强转导致内存拷贝
type StringHeader struct {  // 字符串的底层结构
 Data uintptr
 Len  int
}
type SliceHeader struct {  // 切片的底层结构
 Data uintptr
 Len  int
 Cap  int
}
1.unsafe.Pointer(&a)方法可以得到变量a地址的unsafe.Pointer指针
2.(*reflect.StringHeader)(unsafe.Pointer(&a)) 可以把字符串a地址的unsafe.Pointer指针转成底层结构的指针
3.(*[]byte)(unsafe.Pointer(&b)) 可以把b底层结构体转成byte的切片的指针
4.再通过 *转为指针指向的内容  之后获取时 通过长度遍历 每个元素的长度由指针类型确定 如[]byte切片 指针每个元素遍历1个字节

a :="aaaa"
b := *(*reflect.StringHeader)(unsafe.Pointer(&a))
c := *(*[]byte)(unsafe.Pointer(&b))  
fmt.Printf("%v",c)
  • 字符串类型底层实现是一个二元数据结构,一个指针指向字节数组的起点,另一个是长度
例:runtime/string.go
type stringStruct struct {
	str unsafe.Pointer
	len int
}
  • 基于字符串创建的切片和原字符串指向相同的底层字符数组,不能修改,对字符串的切片操作返回的字串仍然是string而非slice
  • 字符串运算
a := "a"
b := "b"
// 字符串拼接
c := a + b
// 遍历字节数组
for i := 0; i < len(c); i++ {}
// 遍历rune数组
for i, v := range c {}s
  • 字符串和切片的转换:字符串可转换为字节数组,也可转为Unicode的字数组
b := []byte(a)
c := []rune(a)

字符串的长度

python中len() 获取字符串的长度 编码细节隐藏
go中len() 获取字符串字节的长度 utf8动态编码 英文一字节 中文3字节 转移字符是一个字节\n

类型转换

r := []rune(str) // 每个字符转一个rune 也可以[]int32(str)
len(r) // 字符的数量 即字符串的长度 但是rune浪费空间 英文1字节占用一个rune 4字节

字符串转义符

python的转义 \n or '""' or r'xxx'
go的转义 \n or `""` ``内可以输入各种需要转义的字符

字符串操作

  • 是否包含 strings.Contains(parentStr, subStr) parentStr是否包含subStr字串
  • python用法 是否包含 if subStr in parentStr: true表示包含字串
  • python用法 某个字符包含在切片r中 if c in r:
  • 某个字串的开始位置 strings.Index(parentStr, subStr)
  • 统计出现的次数 string.Count(parentStr, subStr)
  • 前后缀 strings.HasPrefix(parentStr, prefix) strings.HasSuffix(parentStr, suffix)
  • 大小写转换 strings.ToUpper(str) strings.ToLower(str)
  • 字符串的比较 strings.Compare(stra, strb) // 比较两者的ascii码 返回-1(a的ascii码小于b的码) 1 0 ;若是多个字符的字符串会逐个字符对比ascii码
  • trim strings.TrimSpace(str) 只去掉空格 Trim(str, curset) 去掉左右指定curset TrimLeft TrimRight
  • 字符串分割 strings.Split(str, sep) 用指定的sep分割字符串为数组 返回切片
  • 合并 join strings.Join(arrs, sep) 用sep将切片指向的数组连接起来
  • 字符串替换 strings.Replace(srcStr, old, new, n) 用new替换src中的old 替换n次

字符串格式化

fmt.Println(str + int) 不能直接将字符串和整型相加 需要str + strconv.ItoA(int) 故而需要字符串格式化 fmt.Printf()
python中可以前缀f格式化字符串
    println(f"name:{name}, 字符串:{变量名}")
go用fmt.Printf
    %d十进制数字%3d前3个空格,%x十六进制,%b二进制,%o二进制
    %T类型打印,%v原来是什么值就打印什么值 数组打印[1,2] 缺省格式
    %c 字符,%U unicode值
fmt.Sprintf 可以将格式化字符串用于赋值
输入格式化用 fmt.Scan
	var str string
    fmt.Scanln(&str)
	fmt.Scanf("%s", &str)

示例

utf-8 可变 英文1字节 中文3字节
	s := "hello世界"
	fmt.Println(len(s))
	for _, b := range []byte(s) {
	    // utf-8编码 可变 
		fmt.Printf("%X ", b)
	}
	//  11
    //  68 65 6C 6C 6F E4 B8 96 E7 95 8C
	for i, ch := range s {
	    // unicode编码 2字节前面空0
		fmt.Printf("(%d, %X)", i, ch)
	}

utf8操作
    // 获取rune对应长度
	fmt.Println("Rune count:", utf8.RuneCountInString(s))
	bytes := []byte(s)
	for len(bytes) > 0 {
	    // 解码 每次解码一字符 size是该字符长度 单位byte
		ch, size := utf8.DecodeRune(bytes)
		bytes = bytes[size:]
		fmt.Printf("%c", ch)
	}

直接用rune遍历 一个字符一个字符获取 一次获取4字节 重新开辟rune数组
带有 range 子句的 for 语句会先把被遍历的字符串值拆成一个字节序列,[]rune是转换成golang中的rune字符类型,它本质是int32,存储的值是该字符对应的unicode码点
	for i, ch := range []rune(s) {
		fmt.Printf("(%d, %c)", i, ch)
	}

rune相当于go的char
用utf8.RuneCountInString获得字符数量
使用len获得字节长度
[]byte获得所有字节
[]rune会先转换获得所有字符

改造 支持中文 国际化

func lengthOfLongestSubstr(s string) int {
	lastOccurred := make(map[rune]int) // byte换rune
	start, maxLength := 0, 0
	for i, ch := range []rune(s) { // string索引取出来的是int32 rune
		if lastIndex, ok := lastOccurred[ch]; ok && lastIndex >= start {
			start = lastIndex + 1
		}
		maxLength = max(maxLength, i - start + 1)
		lastOccurred[ch] = i
	}
	return maxLength
}

其他字符串操作

strings包下
    Fields,Split,Join
    Contails,Index 查找字串
    ToLower,ToUpper
    Trim,TrimRight,TrimLeft

`` :跨行字符串 不转译字符串的意思,可以json yaml 等一起使用
    可作为struct tag

ascii

二进制在计算机中通过补码表示 补码是反码+1 反码正数是原码 负数是原码的反过来
计算机存储单位是字节 1byte=8bit 1字节表示2^8个数
    python中查看二进制用内置函数 bin() 看ascii用ord() 本质是unicode 对应go的fmt.Printf("%c, %d", c, d) 打印字符,数字 %T打印类型rune int32
ascii码一开始是一个字节表示
统一编码unicode unicode兼容ascii码
URL编码是一种用于将URL中的非ASCII字符的特殊字符转换为可以为Web浏览器和服务器普遍接受的、有明确的表示形式的格式,因为URL只能通过使用ASCII字符集(十六进制)将特殊字符在Web浏览器和服务器上显示
UTF-8和UTF-16是数据传输的动态编码规则
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值