08 Go字符串详解

Go字符串详解


总结:

  • 字符串是不可变值类型,内部⽤指针指向 UTF-8 字节数组。
  • 默认值是空字符串 “”。
  • ⽤索引号访问某字节,如 s[i]。取出的是字节,不是字符
  • 不能⽤序号获取字节元素指针, &s[i] ⾮法。
  • 不可变类型,⽆法修改字节数组。使用[]rune进行修改是重新分配内存,并复制字节数组
  • 字符串可以用==和<进行比较;
  • go语言的字符串是utf-8的定长字符序列,会自动转为Unicode,然后可以通过[]rune按索引取出字符。

1、字符串的定义

在Go语言中,字符串字面量使用双引号 “” 或者反引号 `(键盘左上角那个) 来创建。

  • 双引号用来创建可解析的字符串,支持转义,但不能用来引用多行;
  • 反引号用来创建原生的字符串字面量,可能由多行组成,但不支持转义,并且可以包含除了反引号外其他所有字符。
  • 双引号创建可解析的字符串应用最广泛,反引号用来创建原生的字符串则多用于书写多行消息,HTML以及正则表达式。
s:="今天很开心"
s:=`今天开心吗
	不开心啊
		真的?`

2、字符串操作

(1)字符串支持 + 连接操作和+=追加操作

连接跨⾏字符串时, "+" 必须在上⼀⾏末尾,否则导致编译错误。
s := "Hello, " +
	"World!"
s2 := "Hello, "
	+ "World!"   // Error: invalid operation: + untyped string

s+="abc"  

其他函数连接这里暂不说明。

字符串拼接,由于字符串具有不可修改性,新拼接的字符串其实是多个字符串指向的底层内存进行拷贝,重新开辟新的内存地址,然后生成一个新的字符串对象,执行新的内存地址。即使很多的字符串拼接,它会一次性开辟好内存空间,所以拼接字符串的效率主要是消耗在内存的拷贝上面。

(2)支持用两个索引号返回子串。子串依然指向原字节数组,仅修改了指针和长度属性。

s := "Hello, World!"
s1 := s[:5] // Hello
s2 := s[7:] // World!
s3 := s[1:5] // ello

注意:如果字符串中有汉字,汉字占得就是3个字节,所以就不一定能取出

(3)字符串底层分析

struct String{//字符串可以看做是个不可变值类型, 存储了字节和长度
	byte* str;
	intgo len;
};

单从源码看,string 存储的是指针和长度,是一个引用类型。这样的好处是string变得很轻量,不会去拷贝内存。但是String通常是指向一个字面量,字符串的字面量存储位置是只读段,既不在堆上也不栈上,所以内存不允许修改。从内存不可修改上来看,在逻辑上string等同于一个值类型

字符串是 UTF-8 字符的一个序列(当字符为 ASCII 码时则占用 1 个字节,其它字符根据需要占用 2-4 个字节)

go语言中字符串的字节使用UTF-8编码表示Unicode文本。因此Go语言字符串是变宽字符序列,每一个字符都用一个或者多个字节表示,这跟其他的(C++,Java,Python 3)的字符串类型有着本质上的不同,后者为定宽字符序列。

每一个Unicode字符都有一个唯一的叫做“码点”的标识数字。在Go语言中,一个单一的码点在内存中以 rune 的形式表示,rune表示int32类型的别名。可以将Go语言的字符串转化为Unicode码点切片(类型为 [ ]rune),切片是支持直接索引的。

package main
import(
	"fmt"
)
func main() {
	s:="abc好好学习"
	fmt.Println(s)
	for _,b:=range []byte(s){
		fmt.Printf("%x ",b) //打印16进制
	}
	fmt.Println("\n字节长度:",len(s))
	
	for i,ch:=range s{ //ch 是rune  int32别名4个字节
		fmt.Printf("(%d %x)",i,ch)
	}
	fmt.Println()
	for i,ch:=range []rune(s){
		fmt.Printf("(%d %c)",i,ch)
	}
}
//输出结果
abc好好学习
61 62 63 e5 a5 bd e5 a5 bd e5 ad a6 e4 b9 a0 
字节长度: 15
(0 61)(1 62)(2 63)(3 597d)(6 597d)(9 5b66)(12 4e60)
(0 a)(1 b)(2 c)(3)(4)(5)(6)

上面代码显示,我们把字符串转为[]bype数组,打印出utf-8的字节,可以看到**“学”**这个汉字占了e5 ad a6三个16进制字节。看到一个汉字占3个字节,一个英文字母占1个字节,utf-8是可变宽度。

遍历s,取出的是Unicode码,可以看到**“学”**这个汉字的Unicode是 5b66,它是从第9个开始的,(9 5b66)。

将字符串转为[]rune 也就是int32的别名,占4个字节。打印出了abc好好学习。

说明:go语言的字符串是utf-8的定长字符序列,会自动转为Unicode,然后可以通过[]rune按索引取出字符。

(4)String 和[]byte 的转换

[]byte 转为 String

比如以下代码,[]byte 可以强转为string:

func GetStringBySlice(s []byte) string {
	return string(s)
}

转换过程如下:

  1. 跟据切片的长度申请内存空间,假设内存地址为p,切片长度为len(b);
  2. 构建string(string.str = p; string.len = len;)
  3. 拷贝数据(切片中数据拷贝到新申请的内存空间)

也就是,[]byte 转换为String新对象是必须要重新开辟内存空间,进行底层拷贝。

String 转 []byte

func GetSliceByString(str string) []byte {
	return []byte(str)
}

string转换成byte切片,也需要⼀次内存拷贝,其过程如下:

  • 申请切片内存空间
  • 将string拷贝到切片

思考:[]byte 转为string 一定会开辟内存空间吗?

答案是:不一定。因为有时候是要临时使用[]byte转String,并没有去赋值给新对象,只是临时用。比如:

比如,编译器器会识别如下临时场景:

  • 使用m[string(b)]来查找map(map是string为key,临时把切片b转成string);
  • 字符串串拼接,如"<" + “string(b)” + “>”;
  • 字符串串比较: string(b) == "foo

3、String包的使用

查找操作
func Contains(s, substr string) bool
func Index(s, sep string) int
func Count(s, sep string) int
重复操作
func Repeat(s string, count int) string
替换操作
func Replace(s, old, new string, n int) string
删除操作
func Trim(s string, cutset string) string
大小写转换
func Title(s string) string
func ToLower(s string) string
func ToUpper(s string) string
字符串前缀后缀
func HasPrefix(s, prefix string) bool
func HasSuffix(s, suffix string) bool
字符串分割
func Split(s, sep string) []string
func Fields(s string) []string

查找操作

判断给定字符串s中是否包含子串substr, 找到返回true, 找不到返回false

func Contains(s, substr string) bool

实例如下:

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println("包含子串返回:", strings.Contains("helloboy", "boy"))
    fmt.Println("不包含子串返回:", strings.Contains("helloboy", "girl"))
    fmt.Println("子字符串是空字符串返回:", strings.Contains("hello", ""))
    fmt.Println("原字符串、子字符串都是空字符串返回:", strings.Contains("", ""))
    fmt.Println("中文字符串包含子串返回:", strings.Contains("我很帅", "帅"))
}

运行结果:

包含子串返回: true
不包含子串返回: false
子字符串是空字符串返回: true
原字符串、子字符串都是空字符串返回: true
中文字符串包含子串返回: true

在字符串s中查找sep所在的位置, 返回位置值(索引), 找不到返回-1

func Index(s, sep string) int

实例如下:

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println("存在返回第一个匹配字符的位置:", strings.Index("helloboy", "boy"))
    fmt.Println("不存在返回:", strings.Index("hello", "day"))
    fmt.Println("中文字符串存在返回:", strings.Index("我很帅", "很帅"))
}

运行结果:

存在返回第一个匹配字符的位置: 5
不存在返回: -1
中文字符串存在返回: 3    //这里是按照字节的顺序返回 一个汉字是3个字节,所以这个是从索引为3的开始

统计给定子串sep的出现次数, sep为空时, 返回字符串的长度 + 1

func Count(s, sep string) int

实例如下:

package main

import (
    "fmt"
    "strings"
)
func main() {
    fmt.Println("子字符串出现次数:", strings.Count("hello world", "o"))
    fmt.Println("子字符串为空时, 返回:", strings.Count("hello", ""))
}

运行结果:

子字符串出现次数: 2
子字符串为空时, 返回: 6

重复操作

重复s字符串count次, 最后返回新生成的重复的字符串

func Repeat(s string, count int) string

实例如下:

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(strings.Repeat("嘀嗒", 4), "时针它不停在转动")
}

运行结果:

嘀嗒嘀嗒嘀嗒嘀嗒 时针它不停在转动

替换操作

在s字符串中, 把old字符串替换为new字符串,n表示替换的次数,如果n<0会替换所有old子串。

func Replace(s, old, new string, n int) string

实例如下:

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(strings.Replace("hel hel hel", "l", "llo", 2))
    fmt.Println(strings.Replace("hel hel hel", "l", "llo", -1))
}

运行结果:

hello hello hel
hello hello hello

删除操作

删除在s字符串的头部和尾部中由cutset指定的字符, 并返回删除后的字符串

func Trim(s string, cutset string) string

实例如下:

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(strings.Trim("   hello   ", " "))
}

运行结果:

hello

大小写转换

给定字符串转换为英文标题的首字母大写的格式(不能正确处理unicode标点)

func Title(s string) string

返回将所有字母都转为对应的小写版本的拷贝

func ToLower(s string) string

返回将所有字母都转为对应的大写版本的拷贝

func ToUpper(s string) string

实例如下:

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(strings.Title("It is never too late to learn."))
    fmt.Println(strings.ToLower("It Is Never Too Late To Learn."))
    fmt.Println(strings.ToUpper("It is never too late to learn."))
}

运行结果:

It Is Never Too Late To Learn.
it is never too late to learn.
IT IS NEVER TOO LATE TO LEARN.

字符串前缀后缀

判断字符串是否包含前缀prefix,大小写敏感

func HasPrefix(s, prefix string) bool

判断s是否有后缀字符串suffix,大小写敏感

func HasSuffix(s, suffix string) bool

实例如下:

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println("前缀是以hello开头的:", strings.HasPrefix("helloworld", "hello"))
    fmt.Println("后缀是以world开头的:", strings.HasSuffix("helloworld", "world"))
}

运行结果:

前缀是以hello开头的: true
后缀是以world开头的: true

字符串分割

用去掉s中出现的sep的方式进行分割,会分割到结尾,并返回生成的所有片段组成的切片(每一个sep都会进行一次切割,即使两个sep相邻,也会进行两次切割)。如果sep为空字符,Split会将s切分成每一个unicode码值一个字符串。

func Split(s, sep string) []string

返回将字符串按照空白(unicode.IsSpace确定,可以是一到多个连续的空白字符)分割的多个字符串。如果字符串全部是空白或者是空字符串的话,会返回空切片。

func Fields(s string) []string

实例如下:

//如果要分割的字符串位于字符串两端,会多出一个空字符串

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println("Split 函数的用法")
    fmt.Printf("%q\n", strings.Split("Linux,Python,Golang,Java", ","))
    fmt.Printf("%q\n", strings.Split("a mountain a temple", "a "))
    fmt.Printf("%q\n", strings.Split(" abc ", ""))
    fmt.Printf("%q\n", strings.Split("", "oldboy"))
    fmt.Println("Fields 函数的用法")
    fmt.Printf("Fields are: %q\n", strings.Fields(" Linux Python Golang  Java "))
}

运行结果:

Split 函数的用法
["Linux" "Python" "Golang" "Java"]
["" "mountain " "temple"]	
[" " "a" "b" "c" " "]
[""]
Fields 函数的用法
Fields are: ["Linux" "Python" "Golang" "Java"]
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值