第5关 字符串的基本操作

需求=过去困惑+当下渴望+未来希望 ——实干《实干日记》

5-1 为什么len函数获取中文字符串长度有问题?

我们将讲解go语言的字符串相关的操作。
因为字符串本身是非常重要,而且它相对的方法也比较多,所以说我们这里边单独拿一个关卡出来专门讲解字符串。

字符串不管在任何语言当中,它的使用都是非常频繁的。

在Python当中基本上采用的字符串操作,在go当中基本上也是这么回事,它也是这些基本操作。

1.求解字符串的长度。

它有一个内置的函数叫len,名字跟Python是一样的。

比如说这里边我们定义一个字符串,字符串的定义我们可以直接这样来定义,比如说name,然后它是有类型的叫string,然后赋值赋值跟Python是一样的。
注意一下,这里不能用单引号,单引号 引起来/包裹起来 的单个符号是字符。

那就引出了一个问题:
在go语言中,字符和字符串有啥区别?

2.字符和字符串有啥区别
我们通过代码来说明,就不用文字来表述了。
文件位置:c06/demo1.go

package main

import "fmt"

var global int = 1024
func main(){
	str := "keegan"
	fmt.Printf("%T,%v,%s\n",str,str,str)

	var ch1 rune = 'A'
	fmt.Printf("%T,%v,%c\n",ch1,ch1,ch1)

	var ch2 byte = 'B'
	fmt.Printf("%T,%v,%c\n",ch2,ch2,ch2)

	var ch3 int32 = 'C'
	fmt.Printf("%T,%v,%c\n",ch3,ch3,ch3)

	var (
		name string = "程咬铁"
		age int32 = 18
		salary float32 = 12580
	)
	fmt.Printf("name=%s,age=%d,salary=%f\n",name,age,salary)

	fmt.Printf("global=%d\n",global)
	global += 1024
	fmt.Printf("global=%d\n",global)
}
/*在 demo1.go同级目录执行 go run demo1.go
输出结果如下:
string,keegan,keegan
int32,65,A
uint8,66,B
int32,67,C
name=程咬铁,age=18,salary=12580.000000
global=1024
global=2048
*/

解读代码信息:

/*
*1. 定义变量的方法
	(1)标准格式如下
	var 变量名 变量类型
	我们可以用 var name type 的方式来定义变量。
	其中,var 是声明变量的关键字,name是变量名,type是变量的类型
	例如 var ch3 int32 = 'c'
	var 是定义变量的关键字
	ch3是变量的名字
	int32 是变量的类型
	字符 'c' 是变量的值。赋值 = 
	凡是变量,必有3个基本要素:
	变量的类型 + 变量的名称 + 变量的值

	(2)简短格式如下
	名字 := 表达式
	
	必须注意以下3点:
	a. 只用来定义变量,但与此同时也会显示初始化
	b. 不提供数据类型
	c. 只用在函数内部,即它是一个局部变量而非全局变量
	
	(3) 批量格式
	形如:
	var (
	    age int
	    name string
	    salary float32
	)

*2. 变量的作用域
	分为局部变量 和 全局变量
	局部变量:在函数内部定义的变量
	例如 在main函数体内定义的变量都是局部变量
	作用域在函数体内
	
	全局变量:在函数外部定义的变量
	形如:
	var global int = 1024 
*3. 格式化输出【格式控制符,又叫占位符】
	(1)%T %T是go语法打印 值的类型 
	(2)%v %v是默认 打印值
	(3)%c %c是接收单个字符,输出单个字符
	(4)%d %d接收整型的数据,输出整型的数据
	(5)%f %f接收小数,输出小数
	(6)%s %s接收字符串,输出字符串
*4 Printf 和 Println的区别是什么
	两者之间除了换行之外,还有:
	Println 可以打印出字符串变量,整型变量,浮点型变量
	比如打印字符串变量:
	fmt.Println(name) // 程咬铁 //同时换行一定要注意细节
	比如打印浮点型变量:
	fmt.Println(salary) // 12580 //同时换行一定要注意细节
	发现问题:输出结果是 是12580而不是12580.000000
	读起来12580看起来像整型一样,为了提高可读性:
	用格式控制符 + Printf 来格式化输出会大大提高可读性
	就不要用Println来输出除字符串以外的其他变量了,可读性低
	//==============================================
	Printf 只可以打印出格式化的字符串变量。如果要输出其他类型的变量,用格式控制符
	比如输出字符串类型的变量 fmt.Printf(name) // 程咬铁  // //不会换行一定要注意细节
	如果是输出浮点型的变量,那么
	fmt.Printf(salary) 
	// cannot use salary (type float32) as type string in argument to fmt.Printf
    //========总结========
    用Println 来输出字符串类型的变量
    用Printf 输出字符串类型的变量,更多的是格式化输出
   
*/

回答开始的问题:字符和字符串有啥区别?
字符定义:
形如 var ch3 int32 = 'C'
字符变量 ch3 的值是 用单引号 括起来的 大写的 C

字符串定义:
形如 str := "keegan"
字符串变量 str 的值 使用双引号 括起来的 keegan 这 6个字符。
字符串 是 一连串字符的集合。
特别地还有 空白字符的字符串【空字符串】,单个字符的字符串。
可以这样定义:
在这里插入图片描述
到这里,处理完问题2之后,再继续看 1.求解字符串的长度

3.继续看 1.求解字符串的长度
我们先定义以下字符串:
部分代码如下:

var name string = "keegan:写博客" // 一共由10个字符组成
fmt.Println(len(name)) // 16

这里面怎么求它的长度呢?
用len函数。跟Python中的求字符串的长度用的函数都是len函数。
前置信息铺垫:
存储空间的基本单位是 字节。
比如
在go中,1个英文的字符占用1个字节,1个中文的字符占用3个字节。【自己写字符串测试结果】

对于len函数:
用途:用来获取字符串的字节长度,返回的是字节的长度。
处理方法:在go的字符串中,1个英文的字符占用1个字节,1个中文的字符占用3个字节。【自己写字符串测试结果】

我们直接来打印一下,它返回的是16。

为什么是16呢?这里面的逻辑是什么?
用Python来打印:

print(len("keegan:写博客")) # 10

这里又引出了一个问题:
在go语言中,英文字符和中文字符计算长度的方法是怎样的?
在go中,1个英文的字符占用1个字节,1个中文的字符占用3个字节。【自己写字符串测试结果】

我们要知道这个英文字符和中文字符它们的编码规则是不一样的,我们如果把中文内容 写博客 给它去掉,只要是英文加上特殊符号,它的长度就是准确的:

 var name string = "keegan:"
 fmt.Println(len(name)) // 7

我们只要不是中文,然后这里边当然其他国家的语言也不行,一旦涉及到中文或其他国家的语言例如日文,韩文就不准确了。

对于英文来说,英文它实际上是ASCII码来做的,ASCII是一套编码标准,用于显示英文的。128个字符

在这里插入图片描述
通过上图,我们知道了,不同的语言,用的编码标准是不一样的,计算语言符号占用的字节也不一样。
我们这里简化问题一下:
英文在计算机里用的是ASCII编码标准,1个英文字符占用1个字节。
中文在go语言中采用的是Unicode,1个汉字占用3个字节。
Unicode实现方式又包括UTF-8、UTF-16、UTF-32。

以上编码标准有啥意义?
就告诉了计算机 汉字/英文字符 用什么数字代替的?
因为计算机的存储单位是位(bit),每1位的状态用0或1表示。
英文字符 用数字表示:
A:65 B:66 C:67 …依次递增
a:97 b:98 c:99 …依次递增

汉字的话:
在这里插入图片描述
首先,计算机存储的是用二进制来实现的。
把所有国家的字符(英文,中文等)跟数字做好了映射,统一了标准之后,就不存在语言的编码冲突了【一说65对应的啥字母你就能根据标准计算出是A,一说97对应的啥字母你就能根据标准计算出是a】,计算机会根据这套标准帮我们实现了跨语言,跨平台进行的文本转换和处理,这样,在电脑屏幕下,就能显示任何语言的内容了。

回归正文:
到这里,我们知道了:
英文在计算机里用的是ASCII编码标准,1个英文字符占用1个字节。
中文在go语言中采用的是Unicode字符集,1个汉字占用3个字节。

1字节 = 8 位
在这里插入图片描述
一共有 2的8次方 种组合方式,即256种组合方式。
在这里插入图片描述

对于中文来说,一个字节肯定是不能够把所有中文编进来的,因为一个字节的存储上限是256,所以说对于中文来说,它要用3个字节才能够把所有的汉字全部包括进来,或者说是在Unicode中文的数字区域内,它用3个字节才能把所有的中文的汉字给囊括进来。

所以说我们就要知道UTF-8编码,它实际上是用来保存Unicode的这种字符集的一种实现方式。【上文提到过还有UTF-16】

对于go语言来说,go语言对于字符串它采用了UTF-8编码,那UTF-8编码除了能够用三个字节表示一个中文之外,它还能够用一个字节表示英文,这什么意思呢?

实际上很简单,对于英文字符来说它的数字区域,大家可以去看一下Unicode的字符集或查看上文内容,用一个字节就已经能够存储英文字符了,而且是绰绰有余。

对于utf8编码来说,utf8又是一个动态编码,也就是说它不是说所有的字符全部采用等长的这种直接占用,那这样的话会浪费空间,所以说对于英文的编码来说,因为英文的它的编号正好是小于256的,所以说它就用一个字节来表示,就够了。

到这里,就知道了在go语言当中计算英文和中文占用多少字节的算法原因。

再看代码:

var name string = "keegan:写博客" // 一共由10个字符组成
fmt.Println(len(name)) // 16

为什么是7*1 + 3*3 = 16?而不是10 * 3 = 30
我们可以试想一下,如果说每一个字符它都占3个字节的话,那这里边总共有10个字符,那它整个就应该占用10 * 3 = 30个对不对?

但是输出结果是16个,这说明后边3个中文总共占用9个字节,前边7个字符它总共占用7个字节,
总共加起来就是16个对吧?我们也可以只使用中文来验证一下。

 var name string = "写博客"
 fmt.Println(len(name))  //9

然后再一点一点地加点东西:

 var name string = ":写博客"
 fmt.Println(len(name))  //10
 
 var name string = "\n写博客"  //转义字符也是一个字符,占用1个字节
 fmt.Println(len(name))  //10

好了,那对于我们来说这个问题它就产生了一个需求,怎么能得到它真正的长度呢?
这实际上就要涉及到类型转换。

我们可以将name给它转换一下,把它转换成什么类型呢?

把它转换成我们的数组的类型,数组的类型我们在后边也会介绍到。这个数组和我们平时写的数组也有很大的差异,我们把它转成什么数组?
rune

rune它实际上本质上是int32,那是因为它对于这种字符的处理,它主要用于字符的处理,所以说对于int32,我们专门又给它命了个别名rune,专门用于这种字符的处理。
int32是4个字节,int64是8个字节

实际上我们把rune写成int32也是一样的。
看代码:

 var name string = "keegan:写博客"
 fmt.Println(len(name))  // 16
 name_arr := []int32(name)
 fmt.Println(len(name_arr ))  // 10

在这里边什么意思呢?
这里边的意思就是将你的每一个字符,注意一下,每一个字符都转换成一个rune。
对于这种只有占用一个字节的字符的情况,即英文字符,它也会转成一个rune。

对于汉字这种来说,本身你占用3个字节,现在把你转化成int32了,已经占4个字节了,那这样的话大家看看你的中文,我全部都能包含进来对不对?无非多浪费一个空间。【4-3=1】

对于英文字符这种只占用1个字节来说,我给它又加了3个字节,但是它们加起来是一个rune。
对于这种只有3个字节的中文来说,我再给它加1个字节,让它凑够4个字节,也就是1个rune。

这样的话总共有多少个rune呢?
10个。因为这里是10个字符,每个字符都转化成rune了。

我们通过将字符串进行类型转换,转换成rune,再借用len函数计算出rune的长度,这个长度就字符串的长度。
即 rune的长度 = 字符串的长度
其实就是通过类型转换将求字符串长度转化成求rune的长度的问题。
rune的长度就像求数组的长度一样,里面有几个字符rune的长度就是几。

对于len来说,它能够求出数组的元素的个数,就能得到数组的长度。跟Python很相似。

再来看代码:

 var name string = "keegan:写博客"
 fmt.Println(len(name))  // 16
 name_arr := []rune(name) // []int32(name)
 fmt.Println(len(name_arr ))  // 10

现在我们就能够得到它的长度是10个了,对不对?
通过这样的类型转化,我们做到了可以从数字符串有几个字符/元素就能准确计算出字符串的长度就是几了。
即通过这样的类型转化, 字符串的个数 = 字符串的长度

但是我们也知道了这样做它实际上是浪费空间,你本身占的字节没有那么多,因为转成rune的话它占用的空间会多很多。
int32是4个字节,int64是8个字节。

以上是求长度的问题。

多说一点:
字符串可以看成字符数组,用索引取值:

 var name string = "keegan:写博客"
 fmt.Println(len(name))  // 16
 name_arr := []rune(name)
 fmt.Println(len(name_arr ))  // 10
 fmt.Printf("%T,%c\n",name_arr,name_arr[9]) // []int32,客

5-2 什么是转义符?

看例子:
在这里插入图片描述

看定义:
斜线 \ + 特殊符号 = 转义字符
一个转义字符是一个整体,不会单独出现,占用一个字节。

看花容易绣花难。用起来。
示例代码:

/*
*1. 我要 输出 2021\10\24
实例代码:
	package main
	
	import "fmt"
	
	func main(){
		var date string = "2021\\10\\24" // 转义字符 \\ 等价于 \。即我要想输出\,用转移字符\\表示
		// 直接输出字符串可以用Println。格式化输出用Printf
		fmt.Println(date)  // 2021\10\24
	}

*2.我要 输出 keegan:CSDN代码写注释
实例代码:
	package main
	
	import "fmt"
	
	func main(){
		var name string = "keegan:CSDN代码写注释"
		fmt.Println(name) // keegan:CSDN代码写注释
	}

*3我要输出 keegan:"CSDN代码写注释"
实例代码:
	package main
	
	import "fmt"
	
	func main(){
		var name string = "keegan:\"CSDN代码写注释\"" // 用转移字符 \" ---> 表示 "
		fmt.Println(name) // keegan:"CSDN代码写注释"
	}
*/

看问题:
为什么会有转移符?
(0)表示特殊符号
例如:【见上述示例代码】

// 用转移字符 \" ---> 表示 "
// 用转移字符 \\ ---> 表示 \

(1)字符串嵌套问题
认识字符串嵌套问题:

package main

import "fmt"

func main(){
	var name string = "keegan:"CSDN代码写注释""  // syntax error: unexpected CSDN代码写注释 at end of statement

	fmt.Println(name)
}

如果按照执行上述代码,那么会报syntax error: unexpected CSDN代码写注释 at end of statement的异常。
意思是 语法错误:不是被期望的声明。
怎么解决呢?
Python中解决简单:

print("keegan:'CSDN代码写注释'") # keegan:'CSDN代码写注释'
# 或者
print('keegan:"CSDN代码写注释"') # keegan:"CSDN代码写注释"

来看看Go中值怎么解决的。【你已经知道了用 用转移字符 " —> 表示 " 的知识来解决】
解决字符串嵌套问题的办法:

package main

import "fmt"

func main(){
	var name string = "keegan:\"CSDN代码写注释\"" 

	fmt.Println(name) // keegan:"CSDN代码写注释"
}

但是不能写成如下:

package main

import "fmt"

func main(){
	var name string = "keegan:'CSDN代码写注释'"

	fmt.Println(name) // keegan:'CSDN代码写注释'
}

虽然能输出结果,但是'CSDN代码写注释'它既不是字符,也不是字符串,不要这么写。

少即是多。Go语言用 "abc"的形式表示字符串,只有这一种方法。不像Python有3种。
Go表示字符是 'A'。Python中没有字符的概念。
除此之外,Go表示字符串,还可以用 abc的方法。即原样字符的方式展现出来。
看代码:

package main

import "fmt"

func main(){
	var name string = `keegan:CSDN代码写注释`

	fmt.Println(name) // keegan:CSDN代码写注释
}
package main

import "fmt"

func main(){
	var name string = `keegan:"CSDN代码写注释"`

	fmt.Println(name) // keegan:"CSDN代码写注释"
}

像这种 keegan:"CSDN代码写注释"方式也能输出任意的字符串,也可以解决字符串嵌套的问题。
后面讲解 json tag的时候,会见到这种形式。

5-3 子串查询、子串统计、开始和结尾判断

认知常用的字符串操作的函数。

package main

import (
	"fmt"
	"strings"
)

func main(){
	var name string = "keegan:CSDN代码写注释"
	// 1.name中 是否包含某个子串
	fmt.Println(strings.Contains(name,"代码写注释")) // true
	// 2.查看 name中 的子串的开始的位置
	fmt.Println(strings.Index(name,"代码写注释")) // 11。下标从0开始计数。
	// 3.统计出现的次数
	fmt.Println(strings.Count(name,"e")) // 2
	// 4.以什么开头以什么结束。前后缀问题
	fmt.Println(strings.HasPrefix(name,"k")) // true
	fmt.Println(strings.HasSuffix(name,"释")) // true
}

在Python中,是用 in这个关键字来做的。

name = "keegan:CSDN代码写注释"
if "代码写注释" in name:
	print("true") # true
name.index("代")
name.count("e")
name.startswith("k")
name.endswith("释")

5-4 子串的替换、连接和分割

在Python中:

print("keegan".upper())
print("KEEGAN".lower())
package main

import (
	"fmt"
	"strings"
)

func main(){
	//var name string = "keegan:CSDN代码写注释"
	// 1.大小写转化
	fmt.Println(strings.ToUpper("keegan")) // KEEGAN
	fmt.Println(strings.ToLower("KEEGAN")) // keegan

	//2.字符串的比较。其实是ascii值的比较
	// 如果字符串a小于字符串b,则返回 -1
	fmt.Println(strings.Compare("a","b")) // -1
	// 从第一个字符逐个比较ascii值的大小
	fmt.Println(strings.Compare("ab","abc")) // -1
	// 如果字符串a大于字符串b,则返回 1
	fmt.Println(strings.Compare("ac","aa"))  // 1
	// 如果字符串a等于字符串b,则返回 0
	fmt.Println(strings.Compare("你在教我做事","你在教我做事")) // 0

	//3. 去掉空格 和指定的字符串
	fmt.Println(strings.TrimSpace("hello   man")) //hello   man
	// 去掉前后空格
	fmt.Println(strings.TrimSpace("  hello   man  ")) //hello   man
	// 去掉指定的字符。去掉中间空格的话将 h 换成 空格字符即可
	fmt.Println(strings.TrimLeft("hello man","h")) //ello man
	// 左右两边都有h
	fmt.Println(strings.Trim("hah","h")) // a
	// 只有左边有h
	fmt.Println(strings.Trim("hello","h")) // ello
	// 只有右边有h
	fmt.Println(strings.Trim("efgh","h")) // efg

	//4. 字符串切割 split
	// 把&给去掉并替换成空格,返回一个数组
	arrs := strings.Split("I&love&Go,&and&I&also&love&Python","&")
	fmt.Println(arrs) // [I love Go, and I also love Python]
	// 详细参考:https://blog.csdn.net/TCatTime/article/details/100511043

	//5. 字符串合并 join [常用]。将字符串数组连接起来
	// Join连接其第一个参数的元素来创建一个字符串。分隔符sep放在结果字符串的元素之间。
	fmt.Println(strings.Join(arrs,"-")) // I-love-Go,-and-I-also-love-Python
	// 详细参考:https://segmentfault.com/a/1190000012978989

	arr02 := []string{"北京","欢迎","你呀"}
	fmt.Println(strings.Join(arr02,"-")) // 北京-欢迎-你呀

	//6. 字符串替换。从左到右
	fmt.Println(strings.Replace("keegan:18 phone:18825478963","18","24",1)) // keegan:24 phone:18825478963
}

5-5 格式化的输入和输出

可读性差的一种写法:

package main

import (
	"fmt"
	"strconv"
)

func main(){
	name := "keegan"
	age := 18
	fmt.Println("name:"+name+",age:"+strconv.Itoa(age)) // name:keegan,age:18
}

完整代码:

/*
*字符串的基本操作
*/
package main

import (
	"fmt"
	"strconv"
)

func main(){
	demo := 123456
	name := "keegan"
	age := 18
	fmt.Println("name:"+name+",age:"+strconv.Itoa(age)) // name:keegan,age:18
    // 不管值是什么类型的,是什么值就打印什么值
	fmt.Printf("name:%v,age:%v\n",name,age) // name:keegan,age:18

	// go语法打印
	fmt.Printf("name:%#v,age:%#v\n",name,age) //name:"keegan",age:18

	// 类型打印
	fmt.Printf("name:%T,age:%T\n",name,age) // name:string,age:int

	// 必须显示正负符号
	fmt.Printf("name:%s,age:%+d\n",name,age) // name:keegan,age:+18

	// 宽度为4右对齐
	fmt.Println(demo)
	// 宽度是4,18占了2个,4-2=2,余下的2个位置在18的左边补空格
	fmt.Printf("%4d\n",age) //  18

	// 宽度为4左对齐
	fmt.Println(demo)
	// 宽度是4,18占了2个,4-2=2,余下的2个位置在18的右边补空格
	fmt.Printf("%-4d,\n",age) //18  ,

	// 把18显示成二进制
	fmt.Printf("%b\n",age) // 10010
    // 把18显示成八进制
	fmt.Printf("%o\n",age) // 22
	// 把18显示成十六进制
	fmt.Printf("%x\n",age) // 12

	// Sprintf除了格式化输出 还返回一个字符串
	desc := fmt.Sprintf("name:%s,age:%d\n",name,age)
	fmt.Println(desc) // name:keegan,age:18


	// 我想知道数字65对应的ascii值是多少
	ch1 := 65
	fmt.Printf("%c\n",ch1) // A
	// 把字符的单引号 '' 显示出来
	fmt.Printf("%q\n",ch1) // 'A'

    // 科学计数法
    fmt.Printf("%e\n",3.14) // 3.140000e+00

	// 十进制小数
	fmt.Printf("%f\n",3.14) // 3.140000

	// 字符串的宽度,缩进
	str1 := "hello"
	// 宽度为9左对齐,9-5=4,余下的4个在hello的右边补空格
	fmt.Printf(",%-9s,\n",str1)  // ,hello    ,
	// 宽度为9右对齐,余下的4个向左边补空格
	fmt.Printf(",%+9s,\n",str1) // ,    hello,
	// 正右它左补,负左它右补

	// 输入
	var (
		nm string
		salary float32
	)
	fmt.Println("请输入姓名和薪水:")
	fmt.Scanln(&nm,&salary) // 注意按空格输入间隔
	// 注意四舍五入的问题。默认接受7位有效数字,第8位开始四舍五入
	fmt.Println(nm,salary)

	// 类型要匹配。前面是格式化字符串,后面是 取址运算符& + 变量名
	fmt.Println("请输入姓名和薪水:")
	fmt.Scanf("%s %f",&nm,&salary) // keegan 3.14 // 注意敲的细节:敲keegan敲1个空格再敲3.14

	fmt.Println(nm,salary) // keegan 3.14
}

在这里插入图片描述

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

代码写注释

请赞赏我

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

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

打赏作者

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

抵扣说明:

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

余额充值