Go-learning(1)

模块1-go入门教程

开始 Go 语言的学习咯!

找了一圈B站,没有看到很好的比较新的教学资源,我就直接看网盘资源图灵课堂了。

笔记就放在这里咯,重要的代码的话后面会备份到 github 上面。

也可以参考菜鸟的教程:Go 语言教程 | 菜鸟教程


01-Introduction

1.1- go 历史

Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。

Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。

1.2-为什么使用 go 语言

  • 简单好记的关键词和语法。轻松上手,简单易学。
  • 更高的效率。比Java,C++等拥有更高的编译速度,同时运行效率媲美C,同时开发效率非常高。
  • 生态强大,网络上库很丰富,很多功能使用Go开发非常简单。
  • 语法检查严格,高安全性
  • 严格的依赖管理,go mod命令。
  • Go拥有强大的编译检查、严格的编码规范和完整的软件生命周期工具,具有很强的稳定性,稳定压倒一切。
  • 跨平台交叉编译,windows就可以编译出mac,linux上可执行的程序。
  • 异步编程复杂度低,易维护,GO 语言中 Channel 设计,异步程序写起来非常自然。
  • 语言层面支持并发,go关键字(协程)使得go的并发效率极高。
  • 严格的语法规范,所有程序员写出来的代码都是一样的,对大团队来说,非常友好。
  • Go 的并发、性能、安全性、易于部署等特性,使它很容易成为"云原生语言"。容器和云的使用上非常广

1.3- Go 下载

下载地址:https://go.dev/dl/

我为了适配教程,选择的1.17.5的版本,下载后解压即可。

解压后需要配置几个环境变量:

  1. GOROOT:go语言所在的目录,用于全局执行go相关的命令。复制下载路径"D:\Download\go\go1.17.5",然后打开环境变量,新建一个。

  2. path中也需要配置

  3. GOPATH:工作目录,工程代码存放的位置,此目录下,一个文件夹就是一个工程

  4. GOPROXY:go需要配置魔法网站(懂得都懂,居然因为这个不让通过审核)

    地址:https://goproxy.io/zh/ 可以去看文档

  5. go env 可以检查环境变量的配置是否正确

配置完环境变量,就可以开始写代码了

1.4-入门案例+开发工具

在GOPATH路径下,新建一个文件夹,hello。

进入hello目录,运行cmd命令,初始化工程,会生成一个go.mod文件,如果学过maven的话,可以将其当成maven的pom.xml,用于管理依赖的。

复制代码
go mod init hello

接下来,写程序

新建man.go,以文本格式打开,写入以下内容。

Go 复制代码
// package 定义包名 main 包名
package main

// import 引用库 fmt 库名
import "fmt"

// func 定义函数 main 函数名
func main() {
	// fmt 包名 . 调用 Print 函数,并且输出定义的字符串
	fmt.Print("Hello Golang")
}

然后运行这个文件。

Go 复制代码
go run man.go

接下来安装开发工具GoLand,这种工具应该无所谓了,我下的最新版:Thank you for downloading GoLand!

注意,不是免费的,但是学生可以申请认证,哦耶哦耶,参考了这篇博客:GoLand的下载与激活【学生认证】_goland学生认证-CSDN博客,我使用的证件申请,需要等待一周左右,可以直接开启30天免费试用。

下载后打开前面的文件夹,尝试运行。

注意在设置中,go相关的配置,也可以直接在这里下载其他不同的 Go 语言版本。


02- Go 语言变量

2.1-变量声明

Go语言是静态类型语言,因此变量(variable)是有明确类型的,编译器也会检查变量类型的正确性。

2.1.1- 标准格式

变量声明以关键字var开头,变量类型后置,行尾无须分号

复制代码
var 变量名 变量类型

举例:

Go 复制代码
//声明了一个名为age的变量,类型为int
var age int

如果你学过C语言,就会体会到这样声明的好处,比如C语言这样声明:int* a, b ,那么只有a是指针,b不是,这样会使人迷惑,如果想要两个变量都为指针,需要这样定义:int *a,*b

在go语言中,我们使用这样的声明方式:var a,b *int,就可以轻松的将a,b都声明为指针。

变量的命名规则遵循驼峰命名法,即首个单词小写,每个新单词的首字母大写,例如: startDate

2.1.2-基本类型

计算机中数据存储的最小单位为bit(位),0或者1

byte:计算机中数据的基本单元,1字节=8bit,数据在计算机中存储或者计算,至少为1个字节

  • bool
  • string
  • int(随系统,一般是占用4个字节)、int8(占一个字节)、int16(占两个字节)、int32(占4个字节)、int64(占8个字节)
  • uint(无符号整数)、uint8、uint16、uint32、uint64、uintptr
  • byte // uint8 的别名
  • rune // int32 的别名 代表一个 Unicode 码
  • float32、float64
  • complex64、complex128

有符号和无符号的区别:int8 范围 -128-127,uint8 范围:0-255

当一个变量被声明之后,系统自动赋予它该类型的零值:

int 为 0float 为 0.0bool 为 falsestring 为空字符串指针为 nil

所有的内存在 Go 中都是经过初始化的。

Go 复制代码
package main

import "fmt"

var age int
func main() {

	fmt.Println(age);
}

2.1.3- 不指明变量类型

Go 复制代码
//设置游戏中角色的初始等级为1
var level = 1;

像上面这种声明变量的方式,并没有指明类型,Go语言中,在编译时会自动推导类型

我们可以使用

Go 复制代码
fmt.Printf("%T", level)

进行类型输出。

Go 复制代码
package main

import "fmt"

var level = 1

func main() {
	fmt.Printf("%T",level)
}

2.1.4- 批量格式

每行都用 var 声明变量比较烦琐?Go语言提供了批量声明的方式

Go 复制代码
var (
    a int
    b string
    c []float32
)
Go 复制代码
package main

import "fmt"


var (
	a int
	b string
	c []float32
)
func main() {
    //%d 整数占位符,%s 字符串占位符, %f 浮点数占位符(默认精度为6)
	fmt.Printf("%d,%s,%f",a,b,c)
}

2.1.5- 简短格式

我们可以省略var关键字,这样写起来更加便捷

Go 复制代码
//i是变量名 1 是值(或者表达式)
i := 1

上面讲过,如果不指明类型,直接赋值,Go会自动推导类型

使用简短格式有以下限制:

  1. 定义变量,同时显式初始化
  2. 不能提供数据类型
  3. 只能用在函数内部
Go 复制代码
package main

import "fmt"

//不能
//aa :=1
func main() {
	aa :=1
	fmt.Println(aa)
}

简短变量声明被广泛用于大部分的局部变量的声明和初始化,var 形式的声明语句往往用于需要显式指定变量类型的地方

2.2- 初始化变量

Go 复制代码
//创建了一个游戏角色 初始等级为1
var level int = 1
Go 复制代码
//短变量声明
level := 1

以下的代码会出错:

Go 复制代码
package main

func main() {
	
	var level int = 1
    // 再次声明并赋值 会报错 no new variables on left side of := (左边的变量已经被声明了,不能重复声明)
	level := 1
}

但是有特例

比如:net.Dial 提供按指定协议和地址发起网络连接,这个函数有两个返回值,一个是连接对象(conn),一个是错误对象(err)

正常的写法:

Go 复制代码
package main

import (
	"fmt"
	"net"
)
func main() {

	var conn net.Conn
	var err error
	conn, err = net.Dial("tcp", "127.0.0.1:8080")
	fmt.Println(conn)
	fmt.Println(err)
}

短变量的写法:

Go 复制代码
package main

import (
	"fmt"
	"net"
)

func main() {

	conn, err := net.Dial("tcp", "127.0.0.1:8080")
	conn1, err := net.Dial("tcp", "127.0.0.1:8080")
	fmt.Println(conn)
	fmt.Println(conn1)
	fmt.Println(err)
}

在多个短变量声明和赋值中,至少有一个新声明的变量出现在左值中,即便其他变量名可能是重复声明的,编译器也不会报错

2.3- 小demo

变量交换,比如a=100,b=200,交换之后 a=200,b=100

第一种(常规)

Go 复制代码
package main

import "fmt"

func main() {
	a := 100
	b := 200
	var c int
	c = b
	b = a
	a = c
	fmt.Printf("a=%d,b=%d",a,b)
}

第二种(异或)

Go 复制代码
package main

import "fmt"

func main() {
	a := 100
	b := 200

	a = a^b
	b = b^a
	a = a^b
	fmt.Printf("a=%d,b=%d",a,b)
}

第三种(直接交换)

Go 复制代码
package main

import "fmt"

func main() {
	a := 100
	b := 200
	b,a = a,b
	fmt.Printf("a=%d,b=%d",a,b)
}

2.4- 匿名变量

使用多重赋值时,如果不需要在左值中接受变量,可以使用匿名变量

比如上面的例子:

Go 复制代码
package main

import (
	"fmt"
	"net"
)
func main() {

    //conn, err := net.Dial("tcp", "127.0.0.1:8080")
    //如果不想接收err的值,那么可以使用_表示,这就是匿名变量
    conn, _ := net.Dial("tcp", "127.0.0.1:8080")
	fmt.Println(conn)
}

匿名变量以"_"下划线表示

匿名变量不占用命名空间,也不会分配内存。匿名变量可以重复声明使用

"_"本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。

Go 复制代码
package main

import (
	"fmt"
	"net"
)

func main() {

	conn, _ := net.Dial("tcp", "127.0.0.1:8080")
	//匿名变量可以重复声明
	conn1, _ := net.Dial("tcp", "127.0.0.1:8080")
	// 匿名变量不可以直接开头(意思是返回的值不能全部都用匿名变量去接收)
	// _ :=1
	fmt.Println(conn)
	fmt.Println(conn1)
}

2.5 作用域

一个变量(常量、类型或函数)在程序中都有一定的作用范围,称之为作用域

Go语言(静态语言)会在编译时检查每个变量是否使用过,一旦出现未使用的变量,就会报编译错误。

根据变量定义位置的不同,可以分为以下三个类型:

  • 函数内定义的变量称为局部变量
  • 函数外定义的变量称为全局变量
  • 函数定义中的变量称为形式参数

2.5.1 局部变量

在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,函数的参数和返回值变量都属于局部变量。

局部变量不是一直存在的,它只在定义它的函数被调用后存在,函数调用结束后这个局部变量就会被销毁。

Go 复制代码
package main
import (
    "fmt"
)
func main() {
    //声明局部变量 a 和 b 并赋值
    var a int = 3
    var b int = 4
    //声明局部变量 c 并计算 a 和 b 的和
    c := a + b
    fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c)
}

2.5.2 全局变量

在函数体外声明的变量称之为全局变量,全局变量只需要在**一个源文件中定义,就可以在所有源文件中使用**,当然,不包含这个全局变量的源文件需要使用"import"关键字引入全局变量所在的源文件之后才能使用这个全局变量。

全局变量声明必须以 var 关键字开头,如果想要在外部包中使用全局变量的**首字母必须大写**。

Go 复制代码
package main
import "fmt"
//声明全局变量
var c int
func main() {
    //声明局部变量
    var a, b int
    //初始化参数
    a = 3
    b = 4
    c = a + b
    fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c)
}

Go语言程序中全局变量与局部变量名称可以相同,但是函数体内的局部变量会被优先考虑。

Go 复制代码
package main
import "fmt"
//声明全局变量
var bb float32 = 3.14
func main() {
	bb := 3
	fmt.Println(bb)
}
//执行结果 3

2.5.3 形式参数

在定义函数时函数名后面括号中的变量叫做形式参数(简称形参)。形式参数只在函数调用时才会生效,函数调用结束后就会被销毁,在函数未被调用时,函数的形参并不占用实际的存储单元,也没有实际值。

形式参数会作为函数的局部变量来使用

Go 复制代码
package main
import (
    "fmt"
)
//全局变量 a
var a int = 13
func main() {
    //局部变量 a 和 b
    var a int = 3
    var b int = 4
    fmt.Printf("main() 函数中 a = %d\n", a)
    fmt.Printf("main() 函数中 b = %d\n", b)
    c := sum(a, b)
    fmt.Printf("main() 函数中 c = %d\n", c)
}
func sum(a, b int) int {
    fmt.Printf("sum() 函数中 a = %d\n", a)
    fmt.Printf("sum() 函数中 b = %d\n", b)
    num := a + b
    return num
}

至此,Go语言变量相关的知识,学习完毕


03- Go 数据类型

3.1-整型

Go语言同时提供了有符号和无符号的整数类型。

  • 有符号整型:int、int8、int64、int32、int64
  • 无符号整型:uint、uint8、uint64、uint32、uint64、uintptr

有符号整型范围:-2^(n-1) 到 2^(n-1)-1

无符号整型范围: 0 到 2^n-1

实际开发中由于编译器和计算机硬件的不同,int 和 uint 所能表示的整数大小会在 32bit 或 64bit 之间变化。

uint在硬件开发中使用

用来表示 Unicode 字符的 **rune 类型int32 类型是等价的,通常用于表示一个 Unicode 码点。这两个名称可以互换使用。同样,byte**和 uint8 也是等价类型,byte 类型一般用于强调数值是一个原始的数据而不是一个小的整数。

无符号的整数类型 uintptr,它没有指定具体的 bit 大小但是足以容纳指针。uintptr 类型只有在底层编程时才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。

在二进制传输、读写文件的结构描述时,为了保持文件的结构不会受到不同编译目标平台字节长度的影响,不要使用 int 和 uint

3.2- 浮点型

Go语言支持两种浮点型数:

  1. float32 : 范围 约1.4e-45 到 约3.4e38
  2. float64 :范围约4.9e-324 到 约1.8e308
Go 复制代码
floatStr1 := 3.2
//保留小数点位数
fmt.Printf("%.2f\n", floatStr1)

算术规范由 IEEE754 浮点数国际标准定义,该浮点数规范被所有现代的 CPU 支持

通常应该优先使用 float64 类型,因为 float32 类型的累计计算误差很容易扩散,并且 float32 能精确表示的正整数并不是很大。

Go 复制代码
var f float32 = 1 << 24;
fmt.Println(f == f+1) // true

浮点数在声明的时候可以只写整数部分或者小数部分

Go 复制代码
var e = .71828 // 0.71828
var f = 1.     // 1
fmt.Printf("%.5f,%.1f",e,f)

很小或很大的数最好用科学计数法书写,通过 e 或 E 来指定指数部分

Go 复制代码
var avogadro = 6.02214129e23  // 阿伏伽德罗常数
var planck   = 6.62606957e-34 // 普朗克常数
fmt.Printf("%f,%.35f",avogadro,planck)

3.3- 布尔型

在Go语言中,以bool类型进行声明:

Go 复制代码
var 变量名 bool

==,>,<<=, >=,&&(AND),||(OR)等都会产生bool值

Go 复制代码
var aVar = 10
aVar == 5  // false
aVar == 10 // true
aVar != 5  // true
aVar != 10 // false

Go语言对于值之间的比较有非常严格的限制,只有两个相同类型的值才可以进行比较,如果值的类型是接口(interface),那么它们也必须都实现了相同的接口。

如果其中一个值是常量,那么另外一个值可以不是常量,但是类型必须和该常量类型相同。

如果以上条件都不满足,则必须将其中一个值的类型转换为和另外一个值的类型相同之后才可以进行比较。

&&(AND),||(OR)是具有短路行为的,如果运算符左边的值已经可以确定整个布尔表达式的值,那么运算符右边的值将不再被求值。(&&优先级高于||)

Go 复制代码
var a = 10
	//因为a>11已经不满足了,所以a < 30不会走,整个表达式为false
	if(a > 11 && a < 30){
		fmt.Println("正确")
	}else{
		fmt.Println("错误")
	}

	//因为a > 5已经满足了,所以a < 30不会走,整个表达式为true
	if(a > 5 || a < 30){
		fmt.Println("正确")
	}else{
		fmt.Println("错误")
	}

布尔型数据只有true和false,且不能参与任何计算以及类型转换

3.4- 字符类型

Go语言的字符有以下两种:

  • 一种是 uint8 类型,或者叫 byte 型,代表了 ASCII 码的一个字符。
  • 另一种是 rune 类型,代表一个 UTF-8 字符,当需要处理中文、日文或者其他复合字符时,则需要用到 rune 类型。rune 类型等价于 int32 类型。

byte 类型是 uint8 的别名,rune 类型是int32的别名

ASCII 码的一个字符占一个字节

ASCII 定义 128 个字符,由码位 0 -- 127 标识。它涵盖英文字母,拉丁数字和其他一些字符。

字符的定义:

Go 复制代码
//使用单引号 表示一个字符
var ch byte = 'A'
//在 ASCII 码表中,A 的值是 65,也可以这么定义
var ch byte = 65
//65使用十六进制表示是41,所以也可以这么定义 \x 总是紧跟着长度为 2 的 16 进制数
var ch byte = '\x41'
//65的八进制表示是101,所以使用八进制定义 \后面紧跟着长度为 3 的八进制数
var ch byte = '\101'

fmt.Printf("%c",ch)

Unicode 是 ASCII 的超集,它定义了 1,114,112 个代码点的代码空间。 Unicode 版本 10.0 涵盖 139 个现代和历史文本集(包括符文字母,但不包括 Klingon )以及多个符号集。

Go语言同样支持 Unicode(UTF-8), rune来表示, 在内存中使用 int 来表示。

在书写 Unicode 字符时,需要在 16 进制数之前加上前缀\u或者\U。如果需要使用到 4 字节,则使用\u前缀,如果需要使用到 8 个字节,则使用\U前缀。

Go 复制代码
var ch rune = '\u0041'
	var ch1 int64 = '\U00000041'
	//格式化说明符%c用于表示字符,%v或%d会输出用于表示该字符的整数,%U输出格式为 U+hhhh 的字符串。
	fmt.Printf("%c,%c,%U",ch,ch1,ch)

Unicode 包中内置了一些用于测试字符的函数,这些函数的返回值都是一个布尔值,如下所示(其中 ch 代表字符):

  • 判断是否为字母:unicode.IsLetter(ch)
  • 判断是否为数字:unicode.IsDigit(ch)
  • 判断是否为空白符号:unicode.IsSpace(ch)

3.4.1- UTF-8 和 Unicode 有何区别?

Unicode 与 ASCII 类似,都是一种字符集。

字符集为每个字符分配一个唯一的 ID,我们使用到的所有字符在 Unicode 字符集中都有一个唯一的 ID,例如 a 在 Unicode 与 ASCII 中的编码都是 97。汉字"你"在 Unicode 中的编码为 20320,在不同国家的字符集中,字符所对应的 ID 也会不同。而无论任何情况下,Unicode 中的字符的 ID 都是不会变化的。

UTF-8 是编码规则,将 Unicode 中字符的 ID 以某种方式进行编码,UTF-8 的是一种变长编码规则,从 1 到 4 个字节不等。编码规则如下:

  • 0xxxxxx 表示文字符号 0~127,兼容 ASCII 字符集。
  • 从 128 到 0x10ffff 表示其他字符。

根据这个规则,拉丁文语系的字符编码一般情况下每个字符占用一个字节,而中文每个字符占用 3 个字节

广义的 Unicode 指的是一个标准,它定义了字符集及编码规则,即 Unicode 字符集和 UTF-8、UTF-16 编码等。

3.5- 字符串型

一个字符串是一个不可改变的字节序列,字符串可以包含任意的数据,但是通常是用来包含可读的文本,字符串是 UTF-8 字符的一个序列。

字符串的定义:

Go 复制代码
var mystr string = "hello"

go语言从底层就支持UTF-8编码。

UTF-8 是一种被广泛使用的编码格式,是文本文件的标准编码。

由于该编码对占用字节长度的不定性,在Go语言中字符串也可能根据需要占用 1 至 4 个字节,这与其它编程语言不同。

Go语言这样做不仅减少了内存和硬盘空间占用,同时也不用像其它语言那样需要对使用 UTF-8 字符集的文本进行编码和解码。

字符串是一种值类型,且值不可变,即创建某个文本后将无法再次修改这个文本的内容。

当字符为 ASCII 码表上的字符时则占用 1 个字节

字符串中可以使用转义字符来实现换行、缩进等效果,常用的转义字符包括:

  1. \n:换行符
  2. \r:回车符
  3. \t:tab 键
  4. \u 或 \U:Unicode 字符
  5. \:反斜杠自身
Go 复制代码
var str = "ms的go教程\nGo大法好"
fmt.Print(str)

如果使用``反引号,会被原样进行赋值和输出

Go 复制代码
 fmt.Println(`\t ms的go教程Go大法好`)  // \t ms的go教程Go大法好
 fmt.Println(`\t ms的go教程
 Go大法好`) //使用反引号 可以进行字符串换行
//反引号一般用在 需要将内容进行原样输出的时候 使用

字符串是字节的定长数组,byte 和 rune 都是字符类型,若多个字符放在一起,就组成了字符串

比如 hello ,对照 ascii 编码表,每个字母对应的编号是:104,101,108,108,111

Go 复制代码
import (
    "fmt"
)

func main() {
    var mystr01 string = "hello"
    var mystr02 [5]byte = [5]byte{104, 101, 108, 108, 111}
    fmt.Printf("myStr01: %s\n", mystr01)
    fmt.Printf("myStr02: %s", mystr02)
}

思考:hello,ms的go教程 占用几个字节

Go 复制代码
package main

import (
	"fmt"
)

func main() {
   //中文三字节,字母一个字节
var myStr01 string = "hello,ms的go教程"
fmt.Printf("mystr01: %d\n", len(myStr01))
}

3.5.1- 字符串的应用

一般的比较运算符(==、!=、<、<=、>=、>)是通过在内存中按字节比较来实现字符串比较的,因此比较的结果是字符串自然编码的顺序。

字符串所占的字节长度可以通过函数 len() 来获取,例如 len(str)。

字符串的内容(纯字节)可以通过标准索引法来获取,在方括号[ ]内写入索引,索引从 0 开始计数:

  • 字符串 str 的第 1 个字节:str[0]
  • 第 i 个字节:str[i - 1]
  • 最后 1 个字节:str[len(str)-1]

需要注意的是,这种转换方案只对纯 ASCII 码的字符串有效

注意:获取字符串中某个字节的地址属于非法行为,例如 &str[i]。

ASCII字符使用**len()**函数

Unicode字符串长度使用**utf8.RuneCountInString()**函数("你好"实际占用6个字节,但希望输出为2个字,所以使用这个函数)

Go 复制代码
  //如何计算字符串的长度
    str3 := "hello"
    str4 := "你好"
    fmt.Println(len(str3))  // 1个字母占1个字节
    fmt.Println(len(str4))  // 1个中文占3个字节,go从底层支持utf8
    fmt.Println(utf8.RuneCountInString(str4)) // 2

字符串拼接符"+"

两个字符串 s1 和 s2 可以通过 s := s1 + s2 拼接在一起。将 s2 追加到 s1 尾部并生成一个新的字符串 s。(效率不高)

Go 复制代码
//因为编译器会在行尾自动补全分号,所以拼接字符串用的加号"+"必须放在第一行末尾。
	str := "第一部分 " +
		"第二部分"

也可以使用"+="来对字符串进行拼接:

Go 复制代码
s := "hel" + "lo,"
s += "world!"
fmt.Println(s) //输出 "hello, world!"

除了使用+进行拼接,我们也可以使用WriteString()(效率更高,也节省内存,建议使用)

Go 复制代码
	str1 := "你好,"
	str2 := "ms的go教程"
	var stringBuilder bytes.Buffer
	//节省内存分配,提高处理效率
	stringBuilder.WriteString(str1)
	stringBuilder.WriteString(str2)
	fmt.Println(stringBuilder.String())

如果从字符串 hello 码神之路 中获取 码 该如何获取呢?

直接索引对rune类型无效,可以使用string方法转换

Go 复制代码
string([]rune(str6)[0])
Go 复制代码
var myStr01 string = "hello 码神之路"
fmt.Println(string([]rune(myStr01)[6]))

遍历

unicode字符集使用for range 进行遍历,ascii字符集可以使用for range或者for循环遍历

Go 复制代码
	var str1 string = "hello"
	var str2 string = "hello,ms的go教程"
	// 遍历
	for i :=0; i< len(str1); i++{
		fmt.Printf("ascii: %c %d\n", str1[i], str1[i])
	}
	for _, s := range  str1{
		fmt.Printf("unicode: %c %d\n ", s, s)
	}
	// 中文只能用 for range
	for _, s := range  str2{
		fmt.Printf("unicode: %c %d\n ", s, s)
	}

字符串的格式化

  • Printf : 结果写到标准输出
  • Sprint:结果会以字符串形式返回
Go 复制代码
	str1 := "你好,"
	str2 := "ms的go教程"
	var stringBuilder bytes.Buffer
	stringBuilder.WriteString(str1)
	stringBuilder.WriteString(str2)
// Sprint 以字符串形式返回
result := fmt.Sprintf(stringBuilder.String())
fmt.Println(result)
Go 复制代码
%c  单一字符
%T  动态类型
%v  本来值的输出
%+v 字段名+值打印
%d  十进制打印数字
%p  指针,十六进制
%f  浮点数
%b 二进制
%s string

几种输出的差别可以参考这篇博客:go 语言 print、printf、println的区别 - 华腾智算 - 博客园

  1. print()函数,可输出到控制台(不接受任何格式化),语法"fmt.print(str)";
  2. println()函数,可输出到控制台并换行,语法"fmt.println(tmp)";
  3. printf()函数,只可以打印出格式化的字符串;
  4. sprintf()函数,可格式化并返回一个字符串;
  5. fprintf()函数,可格式化并输出到"io.writers"。

字符串查找

如何获取字符串中的某一段字符?

  • strings.Index(): 正向搜索子字符串
  • strings.LastIndex():反向搜索子字符串
Go 复制代码
package main

import (
	"fmt"
	"strings"
)

func main() {
	// 查找
	tracer := "张三来了,张三bye bye"

	// 正向搜索字符串
	comma := strings.Index(tracer, ",")
	fmt.Println(",所在的位置:",comma)
	fmt.Println(tracer[comma+1:])  // 张三bye bye

	add := strings.Index(tracer, "+")
	fmt.Println("+所在的位置:",add)  // +所在的位置: -1

	pos := strings.Index(tracer[comma:], "张三")
	fmt.Println("张三,所在的位置", pos) // 张三,所在的位置 1

	fmt.Println(comma, pos, tracer[5+pos:])  // 12 1 张三bye bye
}

3.6- 类型转换

在必要以及可行的情况下,一个类型的值可以被转换成另一种类型的值。由于Go语言不存在隐式类型转换,因此所有的类型转换都必须显式的声明:

Go 复制代码
//类型 B 的值 = 类型 B(类型 A 的值)
valueOfTypeB = type B(valueOfTypeA)

示例:

Go 复制代码
a := 5.0
b := int(a)

类型转换只能在定义正确的情况下转换成功,例如从一个取值范围较小的类型转换到一个取值范围较大的类型(将 int16 转换为 int32)。

当从一个取值范围较大的类型转换到取值范围较小的类型时(将 int32 转换为 int16 或将 float32 转换为 int),会发生精度丢失的情况。

只有相同底层类型的变量之间可以进行相互转换(如将 int16 类型转换成 int32 类型),不同底层类型的变量相互转换时会引发编译错误(如将 bool 类型转换为 int 类型):

Go 复制代码
package main
import (
        "fmt"
        "math"
)
func main() {
        // 输出各数值范围
        fmt.Println("int8 range:", math.MinInt8, math.MaxInt8)
        fmt.Println("int16 range:", math.MinInt16, math.MaxInt16)
        fmt.Println("int32 range:", math.MinInt32, math.MaxInt32)
        fmt.Println("int64 range:", math.MinInt64, math.MaxInt64)
        // 初始化一个32位整型值
        var a int32 = 1047483647
        // 输出变量的十六进制形式和十进制值
        fmt.Printf("int32: 0x%x %d\n", a, a)
        // 将a变量数值转换为十六进制, 发生数值截断
        b := int16(a)
        // 输出变量的十六进制形式和十进制值
        fmt.Printf("int16: 0x%x %d\n", b, b)
        // 将常量保存为float32类型
        var c float32 = math.Pi
        // 转换为int类型, 浮点发生精度丢失
        fmt.Println(int(c))
}
Go 复制代码
//结果
int8 range: -128 127
int16 range: -32768 32767
int32 range: -2147483648 2147483647
int64 range: -9223372036854775808 9223372036854775807
int32: 0x3e6f54ff 1047483647
int16: 0x54ff 21759
3

根据输出结果,16 位有符号整型的范围是 -32768~32767,而变量 a 的值 1047483647 不在这个范围内。1047483647 对应的十六进制为 0x3e6f54ff,转为 int16 类型后,长度缩短一半,也就是在十六进制上砍掉一半,变成 0x54ff,对应的十进制值为 21759。

浮点数在转换为整型时,会将小数部分去掉,只保留整数部分。

3.6.1- 修改字符串

Golang语言的字符串是不可变

修改字符串时,可以将字符串转换为[]byte进行修改

[ ]byte和string可以通过强制类型转换

案例:将8080改为8081

Go 复制代码
package main

import "fmt"

func main() {

	s1 := "localhost:8080"
	fmt.Println(s1)
	// 强制类型转换 string to byte
	strByte := []byte(s1)

	// 下标修改
	strByte[len(s1)-1] = '1'
	fmt.Println(strByte)

	// 强制类型转换 []byte to string
	s2 := string(strByte)
	fmt.Println(s2)
}

3.7- 小练习

字符串替换, 比如将 "Hello, ms的go教程Java教程" 替换为 "Hello, ms的go教程Go教程"

思路:

  1. 找到Java所在的位置
  2. 根据Java的长度将其分为两部分
  3. 加上Go总共三部分,进行拼接
Go 复制代码
package main

import (
	"bytes"
	"fmt"
	"strings"
)

func main() {
	str1 := "Hello, ms的go教程Java教程"
	source := "Java"
	target := "Go"
	index := strings.Index(str1, source)
	sourceLength := len(source)
	start := str1[:index]
	end := str1[index+sourceLength:]

	var stringBuilder bytes.Buffer
	stringBuilder.WriteString(start)
	stringBuilder.WriteString(target)
	stringBuilder.WriteString(end)
	fmt.Println(stringBuilder.String())

}
相关推荐
小秋学嵌入式-不读研版2 小时前
智能台灯功能重设计方案
开发语言
雁于飞2 小时前
【无标题】
笔记·面试·职场和发展·跳槽·产品经理·创业创新·学习方法
好好沉淀2 小时前
Elasticsearch (ES) 核心笔记
大数据·笔记·elasticsearch
光电大美美-见合八方中国芯2 小时前
【SOA仿真6】多层膜仿真计算
后端·restful
宵时待雨2 小时前
STM32笔记归纳5:SPI
笔记·stm32·嵌入式硬件
子木鑫2 小时前
[SUCTF2019 & GXYCTF2019] 文件上传绕过实战:图片马 + .user.ini / .htaccess 构造 PHP 后门
android·开发语言·安全·php
挖矿大亨2 小时前
C++中const修饰成员函数
开发语言·c++
洋九八2 小时前
Hi3861 OpenHarmony 多线程操作、Timer 定时器、点灯、 IO 相关设备控制
开发语言·华为·harmonyos
小马爱打代码2 小时前
Spring Boot:Sentinel 企业级熔断、降级与限流实战
spring boot·后端·sentinel