Golang 字符串

2023-09-18 11:56:44

1. Golang 字符串

1.1. 基础概念

ASCII 是英文"American Standard Code for Information Interchange"的缩写,中文译为美国信息交换标准代码,它是由美国国家标准学会 (ANSI) 制定的单字节字符编码方案,它使用单个字节 (byte) 的二进制数来编码一个字符。

Unicode 编码规范为世界上现存的所有自然语言中的每一个字符,都设定了一个唯一的二进制编码。它以 ASCII 编码集为出发点,并突破了 ASCII 只能对拉丁字母进行编码的限制。Unicode 编码规范通常使用十六进制表示法来表示 Unicode 代码的整数值,并提供了三种不同的编码格式,即:UTF-8、UTF-16 和 UTF-32。

UTF-8 以 8 个比特(一个字节)作为一个编码单元,它是一种可变宽的编码方案,它会用一个或多个字节的二进制数来表示某个字符,最多使用四个字节。对于一个英文字符,它仅用一个字节的二进制数就可以表示,而对于一个中文字符,它需要使用三个字节才能够表示。rune 是 Go 语言特有的一个基本数据类型,它的一个值就代表一个 Unicode 字符,比如’吕’、‘M’。一个 rune 类型的值会由四个字节宽度的空间来存储,它的存储空间总是能够存下一个 UTF-8 编码值。

1.2. 字符串编码

一个 rune 类型的值在底层其实就是一个 UTF-8 编码值,前者是(便于我们人类理解的)外部展现,后者是(便于计算机系统理解的)内在表达,请看下面代码:

func main() {
	str := "Go 爱好者"
	fmt.Printf("The string: %q\n", str)
	fmt.Printf("runes(char): %q\n", []rune(str))   //['G' 'o' '爱' '好' '者']
	fmt.Printf("runes(hex): %x\n", []rune(str))    //[47 6f 7231 597d 8005]
	fmt.Printf("bytes(hex): [% x]\n", []byte(str)) //[47 6f e7 88 b1 e5 a5 bd e8 80 85]
}

对于第 3 行输出,前面解释的比较清楚,就不赘述。对于第 4 行输出,就是通过 UTF-8 编码,3 个字节的 16 进制展现。第 5 行输出,把每个字符的 UTF-8 编码值都拆成相应的字节序列。

一句话总结一下:一个 string 类型的值在底层就是一个能够表达若干个 UTF-8 编码值的字节序列。

1.3. 遍历字符串

range 遍历:

func main() {
	str := "Go 爱好者"
	fmt.Printf("range 遍历:\n")
	for i, c := range str {
		fmt.Printf("%d: %q [% x]\n", i, c, []byte(string(c)))
	}
	fmt.Printf("for 遍历:\n")
	for i := 0; i < len(str); i++ {
		fmt.Printf("%d: [%c] [%x]\n", i, str[i], str[i])
	}
}

输出如下:

range 遍历:
0: 'G' [47]
1: 'o' [6f]
2: ' ' [20]
3: '爱' [e7 88 b1]
6: '好' [e5 a5 bd]
9: '者' [e8 80 85]
for 遍历:
0: [G] [47]
1: [o] [6f]
2: [ ] [20]
3: [ç] [e7]
4: [ˆ] [88]
5: [±] [b1]
6: [å] [e5]
7: [¥] [a5]
8: [½] [bd]
9: [è] [e8]
10: [] [80]
11: [
] [85]

由此可以看出,通过 range 方式的遍历,是以 rune 为单位,但是相邻字符的索引值并不一定是连续的;通过 for 方式的遍历,是以 byte 为单位。

1.4. 类型转换

字符串是不能直接修改的,如果需要修改,需要转换为可变类型 ([]rune[]bype), 待修改完后再转换回来。但不管如何转换,都需要重新分配内存,并复制数据。

func main() {
	str := "hello, world!"
	bs := []byte(str)  // string 转 byte
	str2 := string(bs) // byte 转 string
	rs := []rune(str)  // string 转 rune
	str3 := string(rs) // rune 转 string
}

前面已经讲解 stringrunebyte 的区别和联系,这里再理解他们的转换,是不是就轻松很多了呢。

1.5. 总结

Go 语言的代码是由 Unicode 字符组成的,它们都必须由 Unicode 编码规范中的 UTF-8 编码格式进行编码并存储,Unicode 编码规范中的编码格式定义的是:字符与字节序列之间的转换方式。其中的 UTF-8 是一种可变宽的编码方案,它会用一个或多个字节的二进制数来表示某个字符,最多使用四个字节。Go 语言中的一个 string 类型值会由若干个 Unicode 字符组成,每个 Unicode 字符都可以由一个 rune 类型的值来承载。这些字符在底层都会被转换为 UTF-8 编码值,而这些 UTF-8 编码值又会以字节序列的形式表达和存储。因此,一个 string 类型的值在底层就是一个能够表达若干个 UTF-8 编码值的字节序列。对于通过 for range 方式遍历字符串,会先把被遍历的字符串值拆成一个字节序列,然后再试图找出这个字节序列中包含的每一个 UTF-8 编码值,或者说每一个 Unicode 字符。相邻的 Unicode 字符的索引值并不一定是连续的,这取决于前一个 Unicode 字符是否为单字节字符,一旦我们清楚了这些内在机制就不会再困惑了。对于 Go 语言来说,Unicode 编码规范和 UTF-8 编码格式算是基础之一,我们应该了解到它们对 Go 语言的重要性,这对于正确理解 Go 语言中的相关数据类型以及日后的相关程序编写都会很有好处。

1.6. String Concatenation (字符串连接)

1.6.1. Using the + operator

package main

import (
    "fmt"
)

func main() {
    s1 := "Go"
    s2 := "Programming"
    s3 := "Language"

    s := s1 + s2 + s3
    fmt.Println(s)
}
$ go run concatenate.go

GoProgrammingLanguage

1.6.2. Using the += operator

p := "Story"
p += "Book"
fmt.Println(p)
go run concatenate.go

StoryBook

1.6.3. Using the Join method

q := []string{"meetgor.com", "tags", "golang", "string"}
r := strings.Join(q, "/")
fmt.Println(r)
go run concatenate.go

meetgor.com/tags/golang/string

1.6.4. Using Sprintf method

// Using Sprintf function to format strings

name := "peter"
domain := "telecom"
service := "ceo"

email := fmt.Sprintf("%s.%s@%s.com", service, name, domain)
fmt.Println(email)
go run concatenate.go

ceo.peter@telecom.com

1.6.5. Using Go string Builder method

package main

import (
    "fmt"
    "strings"
)

func main() {
  // Using Builder function

  c := []string{"j", "a", "v", "a"}
  var builder strings.Builder
  for _, item := range c {
    builder.WriteString(item)
  }
  fmt.Println("builder = ", builder.String())
  b := []byte{'s', 'c', 'r', 'i', 'p', 't'}
  for _, item := range b {
    builder.WriteByte(item)
  }
  fmt.Println("builder = ", builder.String())
  builder.WriteRune('s')
  fmt.Println("builder = ", builder.String())
  fmt.Println("builder = ", builder)
}
go run concatenate.go

builder =  java
builder =  javascript
builder =  javascripts
builder =  {0xc000088dd8 [106 97 118 97 115 99 114 105 112 116 115]}

1.6.6. Using the Bytes buffer method

package main

import (
    "fmt"
    "bytes"
)

func main() {
    // Using bytes buffer method

    var buf bytes.Buffer

    for i := 0; i < 2; i++ {
        buf.WriteString("ja")
    }
    fmt.Println(buf.String())

    buf.WriteByte('r')

    fmt.Println(buf.String())

    k := []rune{'s', 'c', 'r', 'i', 'p', 't'}
    for _, item := range k {
        buf.WriteRune(item)
    }
    fmt.Println(buf.String())
}
go run concatenate.go

jaja
jajar
jajarscript
{[106 97 106 97 114 115 99 114 105 112 116] 0 0}

1.7. String Comparison

1.7.1. Using Comparison operators

package main

import "fmt"

func main() {
    s1 := "gopher"
    s2 := "Gopher"
    s3 := "gopher"

    isEqual := s1 == s2

  //"gopher" is not same as "Gopher" and hence `false`
    fmt.Printf("S1 and S2 are Equal? %t \n", isEqual)
    fmt.Println(s1 == s2)

  // "gopher" is not equal to "Gopher" and hence `true`
    fmt.Println(s1 != s2)

  // "Gopher" comes first lexicographically than "gopher" so return true 
  // G -> 71 in ASCII and g -> 103
    fmt.Println(s2 < s3)
    fmt.Println(s2 <= s3)

  // "Gopher" is not greater than "gopher" as `G` comes first in ASCII table
  // So G value is less than g i.e. 71 > 103 which is false
    fmt.Println(s2 > s3)
    fmt.Println(s2 >= s3)

}
go run comparison.go

S1 and S2 are Equal? false 
false
true
true
true
false
false

1.7.2. Using Compare method

package main

import(
  "fmt"
  "strings"
)

func main() {
    s1 := "gopher"
    s2 := "Gopher"
    s3 := "gopher"

    fmt.Println(strings.Compare(s1, s2))
    fmt.Println(strings.Compare(s1, s3))
    fmt.Println(strings.Compare(s2, s3))
}
go run comparison.go

1
0
-1

1.7.3. Using strings EqualFold

package main

import(
  "fmt"
  "strings"
)

func main() {

    s1 := "gopher"
    s2 := "Gopher"
    s3 := "gophy"

    fmt.Println(strings.EqualFold(s1, s2))
    fmt.Println(strings.EqualFold(s1, s3))
    fmt.Println(strings.EqualFold(s2, s3))
}
go run comparison.go

true
false
false

1.8. String Manipulation and utility methods

1.8.1. ToLower, ToUpper and Title functions

package main

import (
    "fmt"
    "strings"

    "golang.org/x/text/cases"
    "golang.org/x/text/language"
)

func main() {
    s1 := "Ubuntu 22"
    s2 := "meet"
    s3 := "IND"
    fmt.Println(strings.ToLower(s1))
    fmt.Println(strings.ToLower(s2))
    fmt.Println(strings.ToLower(s3))

    fmt.Printf("\n")
    fmt.Println(strings.ToUpper(s1))
    fmt.Println(strings.ToUpper(s2))
    fmt.Println(strings.ToUpper(s3))

    fmt.Printf("\n")
    cases := cases.Title(language.English)
    fmt.Println(cases.String(s1))
    fmt.Println(cases.String(s2))
    fmt.Println(cases.String(s3))
}
# 100-days-of-golang/scripts/strings

go mod init
go get golang.org/x/text/cases
go get golang.org/x/text/language

go run utility.go                                                                                             
ubuntu 22
meet
ind

UBUNTU 22
MEET
IND

Ubuntu 22
Meet
Ind

1.8.2. Contains and ContainsAny functions

package main

import (
    "fmt"
    "strings"
)

func main() {
    str := "javascript"
    substr := "script"
    s := "python"

    fmt.Println(strings.Contains(str, substr))
    fmt.Println(strings.Contains(str, s))

    fmt.Println(strings.ContainsAny(str, "joke"))
    fmt.Println(strings.ContainsAny(str, "xyz"))
    fmt.Println(strings.ContainsAny(str, ""))
}
$ go run main.go

true
false
true
false
false

1.8.3. Split and SplitAfter functions

package main

import (
    "fmt"
    "strings"
)

func main() {
    // Split method for splitting string into slice of string
    link := "meetgor.com/blog/golang/strings"
    fmt.Println(strings.Split(link, "/"))
    fmt.Println(strings.SplitAfter(link, "/"))

    // SplitAfter method for splitting string into slice of string with the pattern
    data := "200kms50kms120kms"
    fmt.Println(strings.Split(data, "kms"))
    fmt.Println(strings.SplitAfter(data, "kms"))
}
go run utility.go

[meetgor.com blog golang strings]
[meetgor.com/ blog/ golang/ strings]
[200 50 120 ]
[200kms 50kms 120kms ]

1.8.4. Repeat and Fields functions

package main

import (
    "fmt"
    "strings"
)

func main() {
    // Repeat method for creating strings with given string and integer
    pattern := "OK"
    fmt.Println(strings.Repeat(pattern, 3))
}
go run utility.go

OKOKOK
package main

import (
    "fmt"
    "strings"
)

func main() {
    // Fields method for extracting string from the given string with white space as delimiters
    text := "Python is a prgramming language.   Go is not"
    text_data := strings.Fields(text)
    fmt.Println(text_data)
    for _, d := range text_data {
        fmt.Println("data = ", d)
    }
}
go run utility.go

[Python is a prgramming language. Go is not]
data =  Python
data =  is
data =  a
data =  prgramming
data =  language.
data =  Go
data =  is
data =  not
更多推荐

RTU遥测终端机,提升水资源管理效率!

2023年水利部发布的《关于推进水利工程配套水文设施建设的指导意见》,强调要聚焦保障水利工程安全高效运行、完善风险监测预警体系、提高防灾减灾能力和水资源水环境水生态综合治理能力、推动新阶段水利高质量发展的要求,加强水利工程配套水文设施建设。遥测终端机在现代水利行业中扮演着重要的角色,可以有效地监测、收集和传输水文数据,

JVM——11.JVM小结

这篇文章我们来小结一下JVMJVM,即java虚拟机,是java代码运行时的环境。我们从底层往上层来说,分别是硬件部分,操作系统,JVM,jre,JDK,java代码。JVM是直接与操作系统打交道的。JVM也是java程序一次编到处运行的主要原因。JVM主要就是讲了一句话,即“Studenta=newStudent()

VScode调试复杂C/C++项目

以前都是用的VScode调试c/cpp的单个文件的编译和执行,但是一遇到大型项目一般就用gdb了,gdb的调试效率和VScode差距还是比较大的,但最近发现VScode其实也能调试复杂的cpp项目,所以记录一下.首先明确一下几点:首先cpp文件需要经过编译,生成可执行文件,然后通过运行/调试可执行文件达到我们想要的效果

OJ练习第175题——打家劫舍 II

打家劫舍II力扣链接:213.打家劫舍II题目描述你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负

OpenHarmony创新赛 | 您有一份创新激励奖待领取 请查收!

2023开源和信息消费大赛开放原子开源大赛OpenHarmony创新赛(以下简称“OpenHarmony创新赛”)正如火如荼的进行当中赛程也即将进入到提交作品的关键阶段为了鼓励更多参赛队伍提交作品OpenHarmony创新赛特别设立“创新激励奖”!前100名按要求提交完整作品的参赛队伍即可获得激励奖——创新赛周边限定礼

【机器学习教程】四、随机森林:从论文到实践

引言随机森林(RandomForest)是机器学习领域中一种强大的集成学习算法。它的优秀性能和广泛应用使得它成为了机器学习领域的一个重要里程碑。本文将从算法的发展历程、重要论文、原理以及实际应用等方面详细介绍随机森林,并提供一个复杂的实战案例。算法发展和重要论文随机森林算法最早由TinKamHo于1995年提出,但直到

Layui快速入门之第十三节 日期与时间选择器

目录一:基本用法API渲染属性弹出提示2.8+获取实例2.8+解除实例绑定2.8+关闭日期面板2.7+获取某月的最后一天二:常规用法三:多类型选择器四:范围选择五:直接静态显示六:更多功能示例一:基本用法Layui是一个基于jQuery的前端UI框架,它提供了众多的组件和工具,其中包括日期选择器组件。在Layui中使用

同为科技(TOWE)专业防雷滤波桌面PDU超级插座

2023年第19届杭州亚运会开幕在即,相较于以往,本届杭州亚运会的一大看点就是电竞项目将首次以正式比赛项目的身份亮相亚运赛场,让更多受众能够领略电竞的魅力。当前社会,电竞作为一种新兴的娱乐、社交方式,让很多年轻人通过高科技设备的便捷和高效,在游戏中展示自己的技能和策略,与志同道合的朋友一起体验团队合作的乐趣,从而展现自

2023华为杯研究生数学建模选题建议和获奖助攻

先说一下自己的看法吧,明天会分享点干货以及赛题的思路给大家!查看文末名片即可!很多同学都是第一次参加数学建模比赛,尤其是含金量最高的华为杯,所以在没有经过系统的学习数学建模知识体系的情况下,大家在拿到题目之后,一定要慎重选题,你要确保你选择的题目是可以在四天内完成的!大家可以看一下华为杯去年的赛题题型A题是华为题,与华

外贸遇到同事撞单怎么处理

有时候感觉这个世界太小了,小到我们经常和自己的同事撞单,甚至很纳闷为什么客户会同时联系我们公司的那么多人,是如何联系上的。比如刚毕业的时候,我曾经开发过一个埃及的客户,每次他回复邮件的时候都会带着我们公司很多其他同事的邮箱。当时公司的规定是谁先给客户发送的开发信或者是客户第一次回复谁的邮件来将客户归为谁管理,其他的同事

类和对象(下)

前言:前面两篇文章我们一共讲述了类和对象的六个默认成员函数,这篇文章我们将接着继续讲。一、再谈构造函数:1.1构造函数体赋值:虽然调用构造函数之后,对象中已经有一个初始值,但是我哦们不能将这称之为对象成员变量的初始化,我们只能将其称之为赋值。因为初始化只能初始化一次,而构造函数体可以进行多次赋值。1.2初始化列表:初始

热文推荐