下载了网站建设asp,焦作 网站建设,建网站最专业,建筑学网站推荐字符串类型是现代编程语言中最常使用的数据类型之一。在Go语言的先祖之一C语言当中#xff0c;字符串类型并没有被显式定义#xff0c;而是以字符串字面值常量或以’\0’结尾的字符类型#xff08;char#xff09;数组来呈现的#xff1a;
#define GOAUTHERS Rober…字符串类型是现代编程语言中最常使用的数据类型之一。在Go语言的先祖之一C语言当中字符串类型并没有被显式定义而是以字符串字面值常量或以’\0’结尾的字符类型char数组来呈现的
#define GOAUTHERS Robert Griesemer, Rob Pike, and Ken Thompson
const char * s hello world
char s[] hello gopher
这给C程序员在使用字符串时带来一些问题诸如
● 类型安全性差
● 字符串操作要时时刻刻考虑结尾的’\0’
● 字符串数据可变主要指以字符数组形式定义的字符串类型
● 获取字符串长度代价大O(n)的时间复杂度
● 未内置对非ASCII字符如中文字符的处理。
Go语言修复了C语言的这一“缺陷”内置了string类型统一了对字符串的抽象。
Go语言的字符串类型 在Go语言中无论是字符串常量、字符串变量还是代码中出现的字符串字面量它们的类型都被统一设置为string const (s string constant
)
func main() {var s1 string string variablefmt.Printf(%T\n, s) // stringfmt.Printf(%T\n, s1) // stringfmt.Printf(%T\n, temporary string literal) // string
}Go的string类型设计充分吸取了C语言字符串设计的经验教训并结合了其他主流语言在字符串类型设计上的最佳实践最终呈现的string类型具有如下功能特点。
1string类型的数据是不可变的
一旦声明了一个string类型的标识符无论是常量还是变量该标识符所指代的数据在整个程序的生命周期内便无法更改。下面尝试修改一下string数据看看能得到怎样的结果。
我们先来看第一种方法
func main() {
// 原始字符串var s string hellofmt.Println(original string:, s)// 切片化后试图改变原字符串sl : []byte(s)sl[0] tfmt.Println(slice:, string(sl))fmt.Println(after reslice, the original string is:, string(s))
}该程序的运行结果如下 original string: hello
slice: tello
after reslice, the original string is: hello在上面的例子中我们试图将string转换为一个切片并通过该切片对其内容进行修改但结果事与愿违。对string进行切片化后Go编译器会为切片变量重新分配底层存储而不是共用string的底层存储因此对切片的修改并未对原string的数据产生任何影响。
我们再来试试通过更为“暴力”一些的手段对string的数据发起“攻击”
func main() {
// 原始string
var s string hello
fmt.Println(original string:, s)
// 试图通过unsafe指针改变原始string
modifyString(s)
fmt.Println(s)}func modifyString(s *string) {// 取出第一个8字节的值p : (*uintptr)(unsafe.Pointer(s))// 获取底层数组的地址var array *[5]byte (*[5]byte)(unsafe.Pointer(*p))var len *int (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(s)) unsafe.Sizeof((*uintptr)(nil))))for i : 0; i (*len); i {fmt.Printf(%p %c\n, ((*array)[i]), (*array)[i])p1 : ((*array)[i])v : (*p1)(*p1) v 1 //try to change the character}
}我们试图通过unsafe指针指向string在运行时内部表示结构具体参考本条后面的讲解中的数据存储块的地址然后通过指针修改那块内存中存储的数据。
运行这段程序得到下面的结果
original string: hello
0x10d1b9d h
unexpected fault address 0x10d1b9d
fatal error: fault
[signal SIGBUS: bus error code0x2 addr0x10d1b9d pc0x109b079]我们看到对string的底层的数据存储区仅能进行只读操作一旦试图修改那块区域的数据便会得到SIGBUS的运行时错误对string数据的“篡改攻击”再次以失败告终。
2零值可用
Go string类型支持“零值可用”的理念。Go字符串无须像C语言中那样考虑结尾’\0’字符因此其零值为长度为0。
var s string
fmt.Println(s) // s
fmt.Println(len(s)) // 0
3获取长度的时间复杂度是O(1)级别
Go string类型数据是不可变的因此一旦有了初值那块数据就不会改变其长度也不会改变。Go将这个长度作为一个字段存储在运行时的string类型的内部表示结构中后文有说明。这样获取string长度的操作即len(s)实际上就是读取存储在运行时中的那个长度值这是一个代价极低的O(1)操作。
4支持通过/操作符进行字符串连接
对开发者而言通过/操作符进行的字符串连接是体验最好的字符串连接操作Go语言支持这种操作
s : Rob Pike,
s s Robert Griesemer,
s Ken Thompson
fmt.Println(s) // Rob Pike, Robert Griesemer, Ken Thompson5支持各种比较关系操作符、! 、、、和
func main() {// s1 : 世界和平s2 : 世界 和平fmt.Println(s1 s2) // true// !s1 Gos2 Cfmt.Println(s1 ! s2) // true// 和 s1 12345s2 23456fmt.Println(s1 s2) // truefmt.Println(s1 s2) // true// 和 s1 12345s2 123fmt.Println(s1 s2) // truefmt.Println(s1 s2) // true
}由于Go string是不可变的因此如果两个字符串的长度不相同那么无须比较具体字符串数据即可断定两个字符串是不同的。如果长度相同则要进一步判断数据指针是否指向同一块底层存储数据。如果相同则两个字符串是等价的如果不同则还需进一步比对实际的数据内容。
6对非ASCII字符提供原生支持
Go语言源文件默认采用的Unicode字符集。Unicode字符集是目前市面上最流行的字符集几乎囊括了所有主流非ASCII字符包括中文字符。Go字符串的每个字符都是一个Unicode字符并且这些Unicode字符是以UTF-8编码格式存储在内存当中的。
我们来看一个例子
func main() {// 中文字符 Unicode码点 UTF8编码// 中 U4E2D E4B8AD// 国 U56FD E59BBD// 欢 U6B22 E6ACA2// 迎 U8FCE E8BF8E// 您 U60A8 E682A8s : 中国欢迎您rs : []rune(s)sl : []byte(s)for i, v : range rs {var utf8Bytes []bytefor j : i * 3; j (i1)*3; j {utf8Bytes append(utf8Bytes, sl[j])}fmt.Printf(%s %X %X\n, string(v), v, utf8Bytes)
}
}我们看到字符串变量s中存储的文本是“中国欢迎您”五个汉字字符非ASCII字符范畴这里输出了每个中文字符对应的Unicode码点Code Point见输出结果的第二列一个rune对应一个码点。UTF-8编码是Unicode码点的一种字符编码形式是最常用的一种编码格式也是Go默认的字符编码格式。我们还可以使用其他字符编码格式来映射Unicode码点比如UTF-16等。
在UTF-8中大多数中文字符都使用三字节表示。[]byte(s)的转型让我们获得了s底层存储的“复制品”从而得到每个汉字字符对应的UTF-8编码字节见输出结果的第三列。
中 4E2D E4B8AD
国 56FD E59BBD
欢 6B22 E6ACA2
迎 8FCE E8BF8E
您 60A8 E682A87原生支持多行字符串
Go语言直接提供了通过反引号构造“所见即所得”的多行字符串的方法
const s 好雨知时节当春乃发生。随风潜入夜润物细无声。野径云俱黑江船火独明。晓看红湿处花重锦官城。
func main() {fmt.Println(s)
}运行结果
好雨知时节当春乃发生。
随风潜入夜润物细无声。
野径云俱黑江船火独明。
晓看红湿处花重锦官城。