【go基础】string详解

00.前言

如果有任何问题 请指出。

01.string的编码方式

string是utf8编码,所以不能直接通过下标获取中文等字符
例如

a := "aaa心"
fmt.Println(a[3])// 结果错误 å

因为通过下标获取的是一个byte(uint8) 而我们的中文字符占3个byte(int32) 所以我们可以通过强制转换为[]rune类型再通过下标获取,rune类型的值与Unicode码点一一对应,在Go语言中,rune就是int32的别名。

1)通过[]rune获取中文字符

a := "aaa心"
fmt.Printf("%s\n", string([]rune(s2)[3]))// 心 结果正确

2)Unicode与utf8的关系

unicode是字符集 uft8是编码方式
字符集: 为每一个字符分配一个唯一的 ID(学名为码位 / 码点 / Code Point);
编码规则: 将码位转换为字节序列的规则(编码/解码 可以理解为 加密/解密 的过程)

  • unicode是静态的,而且是固定长度的,一一对应;unicode-8是一套规则,字长是可变的,即对应的字符结果是可变的。
  • unicode字符集采用两个字节来存储字符。如果直接用unicode字符集那么像字母等这些只需要占一个字节的字符就会浪费空间,所以我们采用utf8变长编码解决这个问题,但是如果在内存中存储,使用utf-8却由于它的长度不固定,带来了很大的不便,使得在内存处理字符变得复杂。应对这个问题的解决策略是:在内存中存储字符时还是使用unicode编码,因为unicode编码的长度固定,处理起来很方便。而在文件的存储中,则使用utf-8编码,可以压缩内存,节省空间。这里一般有个自动转换的机制,即从文件中读取utf-8编码到内存时,会自动转换为unicode编码,而从内存中将字符保存到文件时,则自动转换为utf-8编码。

3)其它获取中文字符的方法

  • 通过for range遍历
	var s2 = "aaa心"
	for k, v := range s2 { // 用range遍历 获取的是unicode字符(rune)
		fmt.Println(k, v)
	 } // a a a 心
  • 通过切片获取
	var s2 = "aaa心"
	fmt.Println(s2[3:]) // 心
	//本质上就是获取 心这个字符 开始的这个字节到最后一个字节 组成的字符

02.string的不可变性

1)何为不可变

字符串不可变指的是 这个字符串是只读的不可写,不能通过下标改变这个字符串。
例子

     a := "fsdfdsfsd"
	 a[1] = 'x' // 错误

我们不能通过下标改变这个字符串
疑问

    a := "fsdfdsfsd"
	fmt.Printf("%d\n", &a) // 824633786944
	a = "xxxxxxx"
	fmt.Printf("%d\n", &a) // 824633786944

我们发现给a重新赋新的字符串 但是他的地址没有变化 好像不满足我们的不可变性 这个问题我们后文的源码分析会解释。

2)如何实现通过下标改变字符串

通过上文我们知道不能通过 a[1]=‘x’ 这种方式改变字符串,不过我们知道string是utf8编码 本质上[]byte数组,虽然string不能通过下标赋值,但是[]byte数组可以,所以我们可以通过把string类型强转为[]byte类型 改变后再 转回string
例子

    a := "fsdfdsfsd"
    bytes := []byte(a)
	bytes[0] = 'h'
	a = string(bytes)
	fmt.Printf("%s\n", a) // hsdfdsfsd

我们可以看到确实改变了字符串结果。
但是根据我们之前的了解 转换为[]byte只能获取到一个byte 理论上bytes[0]=‘中’ 是错的
验证

    a := "fsdfdsfsd"
    bytes := []byte(a)
	bytes[0] = '中' // 程序爆红 不能编译
	a = string(bytes)
	fmt.Printf("%s\n", a) // hsdfdsfsd

那我们如何把字符串的第一个下标改成中文字符呢,其实与[]byte数组同理 我们改成[]rune数组就可以了
例子

	a := "fsdfdsfsd"
    runes := []rune(s)
	runes[0] = '中'
	a = string(bytes)
	fmt.Printf("%s\n", a) // 中sdfdsfsd

可以看出成功改成了中文字符

03.string源码分析

1)stringStruct结构体

string的数据结构就是stringStruct这个结构体

type stringStruct struct {
    str unsafe.Pointer
    len int
}

我们可以看到它由两个字段构成

  • str unsafe.Pointer: 指向底层数组的指针
  • len:长度

2)gostringnocopy函数

这个函数的作用是在字符串构建过程中把stringStruct转换成string

func gostringnocopy(str *byte) string { // 跟据字符串地址构建string
	ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)} // 先构造stringStruct
	s := *(*string)(unsafe.Pointer(&ss))                             // 再将stringStruct转换成string
	return s
}

3)解释一下不可变那里提出的疑问

我们可以看到 gostringnocopy函数 在构建的时候 先把byte指针赋值给了 str,之后我们又把构建好的ss转换成string指针 然后再解引用 返回string类型。到这里我们明白了什么?

  • 1:返回的string类型 它的地址不是字符串所在的地址,stringStruct中str的地址才是字符串所在地址
  • 2:到这里我们就可以理解了为什么 a:=“sss” a=“xxx”,这种改变a的地址没有变化,因为这里a的地址并不是字符串地址 只是stringStruct转换为string后的地址 而它自始至终没有变化,所以地址不变。

4)不可变性如何实现

首先我们需要了解几个概念

  • 左值:左值指的是可以被赋值的量
  • 右值:只能读的量
  • 字面量:字面量可以理解为不可变的量 以固定形式存放的量 或者说字面量表示的含义就是它本身 在式子中作为右值,例如 "aaa"就是string字面量,0就是int字面量

例子

	a:="aaaa" //a是左值
	"aaa" = "BBB" // "aaa"是右值 也是字面量

前文我们知道了string里记录的是字符串的首地址,我们进行改变也就是对这个地址指向的值进行操作,这个可能实现吗?答案是不可能的。
这里我们把问题简化为了对于指针指向的值,我们可能改变这个值本身吗?
例子

int main() {
    char *str = "aaaaa";
    *str = 'b';  // 使用指针修改字符串字面量的值
    printf("%s\n", str);
    return 0;
}

以上代码运行时会报错
分析
我们首先我们让str指针指向这个字符串的首地址,之后我们再把这个首地址指向的值变成’b’,这里我们分析一下str取到的是什么,它取到的实际是’a’这个字面量,也就是str = ‘b’ 等价与 ‘a’='b’显然不对。不过我猜会有人说 *str 不是等价于 str[0]吗 为什么str[0]可以赋值
例子

int main() {
    char str[] = "aaaaa";
    str[0] = 'b';  
    printf("%s\n", str); // baaaa
    printf("%s\n", str[1]); // a,自动解引用
    return 0;
}

代码正确。
究其原因是数组可以作为左值,str[0] 本质上指针 之所以数组可以通过str[1]下标这种方式获取到值 是因为数组是一个语法糖,他把 指针指向这个字符串 与 解引用这两个步骤 合并了,所以数组在作为右值时 会自动解引用 这也就是 为什么我们说 str[1] 等价于 *(str+1),但是在作为左值的时候,他俩是不等价的 此时数组 是指针并不会解引用,所以str[1]='x’这种方式 其实是指针指向的位置变了 从原来指向’a’所在位置变成了指向’x’所在位置。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

I cream

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值