当前位置: 首页 > news >正文

【Go】一、Go语言基本语法与常用方法容器

GO基础

Go语言是由Google于2006年开源的静态语言

1972:(C语言) — 1983(C++)—1991(python)—1995(java、PHP、js)—2005(amd双核技术 + web端新技术飞速发展)—2006 (Go)

Go语言的优势

  1. 开发效率高 ---- 语法简单
  2. 集各家语言的优势 ---- 出生时间晚,大量参考C和Python
  3. 执行性能高 ---- 直接编译成二进制,部署简单(Python、Java都需要各自的环境才行)
  4. 并发编程效率高 ---- goroutine
  5. 编译速度快 ---- 比C++、Java的编译速度快

Go语言的使用方向

  1. Web开发(Gin、beego)
  2. 容器虚拟化(docker、k8s、istio)
  3. 中间件(etcd、tidb、influxdb、nsq等)
  4. 区块链(以太坊、fabric)
  5. 微服务(go-zero、dapr、rpcx、kratos、dubbo-go、kitex)

关于包(package)

当我们创建好一个Go语言文件时,会自动创建一行代码:package xxx,这是Go语言区分于动态语言的重要标志,我们在其他包中引入这个包的内容时,我们会使用包名来调用相关内容,而动态语言如python是通过文件名来调用的。

HelloWorld

这是一个简单的HelloWorld:

package mainimport "fmt"func main() {fmt.Println("Hello World")
}

注意在这个程序中:package和函数名都必须为main,这标识了这个程序的入口

我们也可以在控制台中进行简单的操作:

go build xxx.go
./xxx.exe
go run xxx.go

上面三条语句,分别是:编译、运行编译后的exe文件、编译并运行

要注意的问题:不要在一个文件夹内使用两个及以上的main函数,这是不推荐的,我们尽量在多个文件夹中对多个入口函数的情况进行使用

关于变量的定义方法

基础变量定义:GO语言的变量定义出来之后是有一个初值的,int为0,string为’',这一点区别于C和Java

变量的基本定义方法:

func variableZeroValue() {var a intvar s stringfmt.Println(a, s)
}

注意这里的string是不会显示的,因为是一个空串

如果我们一定要打印一下的话,可以使用下面的方法:

func variableZeroValuePrintEmpty() {var a intvar s string// 下面这种格式会自动给字符串加一个双引号// 这个是%q的功效fmt.Printf("%d, %q\n", a, s)
}

赋初值的方法:

// 赋初值
func variableInitialValue() {var a, b int = 3, 4var s string = "abc"fmt.Println(a, b, s)
}

更进一步的,我们还可以省略类型

在省略类型之后,我们就可以在一行赋很多不同类型的数值

// 更进一步的,我们还可以省略类型
// 在省略类型之后,我们就可以在一行赋很多不同类型的数值
func variableInitialValueDifferent() {var a, b, c, s = 5, 6, true, "def"fmt.Println(a, b, c, s)
}

再进一步,我们可以省略var,以:=代替:来进行变量定义

// 再进一步,我们可以省略var,以:=代替:来进行变量定义
func variableShorter() {a, b, c, s := 3, 4, true, "var"fmt.Println(a, b, c, s)
}

再另外,我们也可以在方法外直接定义变量,但这种变量不叫做全局变量,其属于包内变量(后续理解)

下面是一种集中的定义方式

var (aa = 33bb = falsecc = "kiu"
)

但注意,我们在函数外定义(全局变量)语言是不能使用 := 的。

若全局变量与局部变量重名,会优先使用局部变量

常量

常量的定义可以使用const进行定义,其方法是:

func main() {const PI float32 = 3.14fmt.Println(PI)// 我们可以通过小括号的方式来批量定义常量// 如果我们定义的常量没有赋初值也没有赋数据类型的话,会默认沿用上一个常量// 第一个常量必须被定义,不能只赋类型不赋值(编译不通过)const (A = 16BC = "abc"D)fmt.Println(A, B, C, D)
}

注意常量的定义一般会将常量名全部大写,涉及到单词问题使用下划线进行分割

iota

iota是一个自增的int数,其用于在常量中进行递增的对常量进行定义,其提供了充足的便捷性

	const (AA = iota + 100BBCC = "iota"DD = iota)fmt.Println(AA, BB, CC, DD)// 100 101 iota 3

我们的iota只要在定义变量,就会自动 + 1

我们可以通过给iota进行加减操作来进行业务处理

我们只需要给第一个常量赋iota,结合常量定义中的操作,我们可以不显示的在后续进行定义,其也能得到递增的效果

在必须接收某些内容,自己却不需要使用这些内容时,我们使用下划线_来进行占位,避免被迫输出该变量的场景,这个下划线就叫做匿名变量

注意:局部变量和全局变量是允许同名的,其访问优先级是:能访问到局部变量的情况先访问局部变量

另外,IF 语句中的局部变量不会因为IF块和ELSE块中都存在而允许被外部访问,其仍然属于局部变量

变量的内建类型

  • bool、string

  • (u)int(int64)、(u)int8(1字节)、(u)int16(2字节)、(u)int32(4字节,正常int)、(u)int64(8字节)、uintptr

    GoLang中没有long、short等类型,其使用int + 数字的形式来定义长整型

    若前面带u,就代表他是一个无符号整数、uintptr代表其长度跟随操作系统变化(32位操作系统为32位、64位操作系统为64位)

  • byte、rune

    byte指的是一个8字节的数据(专门用来存放ASCII码,甚至可以直接赋一个字符给它,但注意其需要使用百分号进行格式化输出)、rune则是Go语言中的char类型,但不同于普通的char、其有32位,即4字节(UTF-8汉语有3字节,也就是说其可以对汉字进行操作),这样更方便其拓展语言

  • float32(3.4e38)、float64(1.8e308)、complex64、complex128

    float也就是浮点数,complex则指的是复数(一半作为虚部、另一半作为实部)

显式类型转换

Go语言没有隐式类型转换,我们在进行参数的传递或计算时,不会有隐式的类型转换,所有的类型转换都要显式的进行,否则编译就不会通过:

func triangle() {var a, b int = 3, 4var c intc = int(math.Sqrt(float64(a * a + b * b)))
}

并且,Go语言中的类型转换中,数据类型不用加括号,但其要转换的内容必须加括号

float转int会丢掉小数部分

字符串转整数,整数转字符串

func main() {// 基础的类型转还能var t1 uint = 12var t2 = int8(t1)print(t2)// string 和 基本数据之间的类型转换// Itoa 指的是数字转字符串// Atii 指的是字符串转数字var testStr string = "12"resInt, err := strconv.Atoi(testStr)if err != nil {print("类型转换异常1151")}resStr := strconv.Itoa(resInt)print(resStr)
}

字符串转Float、字符串转8、10、16进制

	// 字符串转FloattestStrToFloat := "12.5"strToFloat, err := strconv.ParseFloat(testStrToFloat, 64) // 第二个参数是编码参数,一般直接写64if err != nil {print("类型转换异常")}fmt.Println(strToFloat)// 字符串转int,一般用来进行进制转换tsetBaseConvert := "15"baseConvert10, err := strconv.ParseInt(tsetBaseConvert, 10, 64)if err != nil {print("类型转换异常")}baseConvert8, err := strconv.ParseInt(tsetBaseConvert, 8, 64)if err != nil {print("类型转换异常")}baseConvert16, err := strconv.ParseInt(tsetBaseConvert, 16, 64)if err != nil {print("类型转换异常")}fmt.Println("十进制下15:", baseConvert10)fmt.Println("八进制下15:", baseConvert8)fmt.Println("十六进制下15:", baseConvert16)

另外的,strconv.ParseBool也可以将字符串转换为bool类型,但只能传入true、false、1、0,传入其他就会触发err变量,1会被转换为true、0会被转换为false

Float等转str,FormatInt具有独特的类型转换的功效

	// 第一个参数是转成string的值,第二个参数是格式化的类型,这个要看源码,一般使用f,不额外携带信息,-1也是一般性参数,必须要携带的,64是指使用64位floatStr := strconv.FormatFloat(3.1415, 'E', -1, 64)fmt.Println(floatStr)// 第一个数是转换的参数,第二个数是转为多少进制intStr := strconv.FormatInt(15, 16)fmt.Println(intStr)

运算符

基本运算符都差不多,这里只列举一些值得记录的:

<< 左移运算符 扩大 >> 右移运算符,缩小

字符串

Rune

我们的字符都是由一定的编码组成的,举例:

str := "Yes你好!"

例如上面这样的字符串,是由utf8编码构成的,我们可以使用[]bytes对其进行实验

	for i, ch := range byteList {fmt.Printf("(%d, %X) ", i, ch)}// 这里将字符串转为了byte,在对byte进行遍历时,每一个内容都是一个编码// (0, 59) (1, 65) (2, 73) (3, E4) (4, BD) (5, A0) (6, E5) (7, A5) (8, BD) (9, 21)fmt.Println()

如果我们直接对string进行遍历

	for i, ch := range str {fmt.Printf("(%d, %X) ", i, ch)}// 而当我们直接对字符串进行遍历时,其每次会以人类直视的模式来输出字符,但其下标还是对应byte的坐标,每一个字符都是转为unicode编码形式的字符// (0, 59) (1, 65) (2, 73) (3, 4F60) (6, 597D) (9, 21)

而我们如果想要获取其真正的内容,就需要我们使用%c来接收也可以使其获取真正的内容:

如果我们想要顺序使用下标,获取人类可以识别的下标,就需要我们使用到rune

	runeStr := []rune(str)for i, ch := range runeStr {fmt.Printf("(%d, %c) ", i, ch)}// (0, Y) (1, e) (2, s) (3, 你) (4, 好) (5, !)

其他字符串操作

我们如果希望对字符串进行操作,会用到许多的字符串工具类:

  • Fields、Split、Join
  • Contains、Index
  • ToLower、ToUpper
  • Trim、TrimRight、TrimLeft
	// 常用字符串操作testStr := "-Galaxy-Tree- "// 是否包含var flag bool = strings.Contains(testStr, "-")fmt.Println(flag) // true// 子串出现次数var count int = strings.Count(testStr, "-")fmt.Println(count) // 2// 分隔,注意如果分隔符在第一个位置,最前面的字符也会被截为空串// 在最后一位时,也会把最后一位截成空串var strs []string = strings.Split(testStr, "-")for i := 0; i < len(strs); i++ {fmt.Println(strs[i])}fmt.Println(len(strs))// 是否以xxx为前缀,是否以xxx为后缀var preFlag bool = strings.HasPrefix(testStr, "-Ga")var sufFlag bool = strings.HasSuffix(testStr, "e-")fmt.Println(preFlag, sufFlag)// 字符串出现的位置(按照 UTF8MB3 编码编码个数排序)var indexTest int = strings.Index(testStr, "a")fmt.Println(indexTest)// 子串替换// 最后一个数字的意思是,从左向右替换几次,-1为全部替换var testReplace string = strings.Replace(testStr, "-", ",", -1)fmt.Println(testReplace)// 大小写转换(全部转换)var lowerTest string = strings.ToLower(testStr)var upperTest string = strings.ToUpper(testStr)fmt.Println(lowerTest, upperTest)// 修剪()去除字符串最前和最后的某个字符(指定)// 这种会去掉多个,如果去掉之后的字符仍然需要被去掉,那其仍然会去掉它var trimTest string = strings.Trim(testStr, " G-e")fmt.Println(trimTest)

常量、枚举的定义

使用const进行常量的定义

func testConst() {const fileName = "abc.txt"// 注意,在定义数字变量的时候,它的变量类型是不一定的,有可能是float、也有可能是int、我们在使用它时它会自动进行转换const a, b = 3, 4// 变量的批量定义const (fileName2 = "def.txt"e, f = 8, 9)
}

在Go语言中,枚举类型也是通过const进行定义的:

func enumTest() {const (sun  = 1moon = 2star = 3)// 另外,我们可以利用iota这个表达式// 这个表达式可以对我们的常量进行自增const (sun1 = iotasun2sun3)fmt.Println(sun1, sun2, sun3) //0 1 2// 我们可以利用这个表达式进行一些操作const (b = 1 << (10 * iota)kbmbgb)fmt.Println(b, kb, mb, gb) //1 1024 1048576 1073741824
}

IF、SWITCH

下面是一个尝试读取文件的程序:

func main() {const filename = "abc.txt"// 该行允许试图读取一个文件,若该文件未读取到,则返回err,file为nil// 若文件读取到,则err为nil,返回一个byte[] 类型的file,该数组应该使用%s进行接收file, err := ioutil.ReadFile(filename)if err != nil {fmt.Println(err)} else {fmt.Printf("%s\n", file)}
}

另外,if可以使用类似于for一样进行先执行,再判断

func main() {const filename = "abc.txt"// 但注意,这种形式就类似于定义了一个局部变量,file、err都只能在if语句中使用if file, err := ioutil.ReadFile(filename); err != nil {fmt.Println(err)} else {fmt.Printf("%s\n", file)}
}

下面是一个Switch语句的示例,要注意的是:Switch后可以不跟任何语句,直接在CASE中进行判断:

func grade(score int) string {switch {case score < 0 || score > 100:// panic代表,中断程序并抛出异常信息,异常信息为后面的内容panic(fmt.Sprintf("Wrong Score %d", score))case score < 60:return "F"case score < 80:return "C"case score < 90:return "B"case score <= 100:return "A"default:return "?"}
}

Loop

Go语言中的循环:

同IF一样,GO语言中的循环也不需要括号,其他的基本与JAVA一致

func convertToBin(n int) string {result := ""for ; n > 0; n /= 2 {lsb := n % 2// strconv.Itoa()用来实现将int转stringresult = strconv.Itoa(lsb) + result}return result
}func main() {fmt.Println(convertToBin(13))
}

Go语言中没有while,我们直接在for后面写条件就是传统意义上的while

func printFile(filename string) {file, err := os.Open(filename)if err != nil {panic(err)}scanner := bufio.NewScanner(file)for scanner.Scan() {fmt.Println(scanner.Text()) // 输出文件的一行}
}

如果我们for后面什么也不写,就是一个死循环,相当于while(true)

func forever () {for {fmt.Println("abc")}
}

我们GO语言中的并发编程就是基于GO语言中的死循环的,GO语言对于死循环有非常中意的思路,故其把死循环定义的十分优雅

time.Sleep(time * Second * 2) 使用单位 * 时间数的方式来处理时间问题,更加直观

For+ Range

我们可以使用 for 与 关键字 range 连用的方式来对容器进行遍历(这里的容器也包括字符串):

func main() {name := "GoLang Go"builder := strings.Builder{}// 注意对字符串的打印,其打印的是ASCII码for index, value := range name {builder.WriteString("数据格式:")builder.WriteString(strconv.Itoa(index))builder.WriteString(" --> ")builder.WriteString(string(rune(value)) + "\n")}fmt.Println(builder.String())
}

将ASCII 的 int 数字转为对应字符的方式:两次强转(string(rune(x)))

另外,我们这个地方操作的是这个字符串的拷贝,而不是字符串本身

同时,我们对字符串操作:[]rune(string)

函数

GO语言中的函数示例:

func eval(a, b int, op string) int {switch op {case "+":return a + bcase "-":return a - bcase "*":return a * bcase "/":return a / bdefault:panic("unsupported operation:" + op)}
}

另外,GO语言的另一个显著的特点:其函数可以定义多个返回值:

// 带鱼除法
func div(a, b int) (int, int) {return a / b, a % b
}

另外,我们可以通过给返回值命名的方式来更灵活的处理函数

// 带鱼除法
func div(a, b int) (q, r int) {q = a / br = a % breturn
}func main() {q, r := div(8, 3)fmt.Println(q, r)
}

但是,如果我们只想使用其中一个参数,就需要我们使用下划线在取值时忽略掉另一个参数:

// 带鱼除法
func div(a, b int) (q, r int) {q = a / br = a % breturn
}func main() {q, _ := div(8, 3)fmt.Println(q)
}

同时,我们可以利用Go语言中提供的多返回值机制优化四则运算中错的问题:

func eval(a, b int, op string) (int, error) {switch op {case "+":return a + b, nilcase "-":return a - b, nilcase "*":return a * b, nilcase "/":q, _ := div(a, b)return q, nildefault:return 0, fmt.Errorf("Unsupported Operation: %s ", op)}
}func main() {if result, err := eval(15, 4, "C"); err != nil {fmt.Println("Error:", err)} else {fmt.Println(result)}
}

另外,我们也可以在函数的形参中定义函数,这也被我们叫做函数式编程,在后面详细讲

对于重载等Java中的技术,在Go语言中都不存在,其存在的有一个可变形参列表:

func sumArgs(values ...int) int {sum := 0for i := range values {sum += values[i]}return sum
}// main: fmt.Println(sumArgs(1, 2, 3, 4, 5, 6, 7, 8, 9))

指针

指针指的是通过操作数据的存储地址从而进行对数据操作的工具,不同于C中的指针,Go中的指针没有运算操作,这一点大大简化了学习指针的难度。

值传递和引用传递

在此之前,我们需要理清楚值传递和引用传递的区别:

void pass_by_val(int a) {a++;}
void pass_by_ref(int& a) {a++;}

在上面的C++代码片段中,没有加&的就属于值传递,其会在空间中开辟一片新的区域用来存储一个传进来的形参a的复制,这就叫做值传递,而在下面的代码中,添加了&,这就属于引用传递,其会在传入的形参原值上进行操作

典例:交换两个元素的内容

值传递的经典例子:

func swap(a, b int) {a, b = b, a
}

这种写法不会交换任何元素,这是在元素的复制上进行操作,没有效果

下面是利用指针的写法:

func swap(a, b *int) {*a, *b = *b, *a
}
func main() {a, b := 3, 4swap(&a, &b)fmt.Println(a, b)  
}

但问题就是:我们这种写法对观感来讲非常差,不利于代码维护,故我们一般不使用指针,而是将我们需要的值返回出去并让其他变量接收,进而解决值传递的问题。

通常解法:

func swap(a, b int) (int, int) {return b, a
}func main() {a, b := 3, 4a, b = swap(a, b)fmt.Println(a, b)}

数组

数组的定义方式

func main() {// 定义数组的常规方式,这样定义会给他们赋初值都是0var arr1 [5]int// 以这种方式定义的数组必须赋初值arr2 := [3]int{1, 2, 3}// 这种方式可以定义数量不确定的数组,但同时,使用[...]的话就也必须赋初值arr3 := [...]int{5, 6, 7}// 多维数组的定义方式var grid [3][4]intfmt.Println(arr1, arr2, arr3)fmt.Println(grid)
}

注意一点:[3]string[4]string都不算是同一种类型,因为他们的数组长度不同

另外:[x]string[]string也不是相同的数据类型,[x]string是数组,而[]string叫做切片

注意:数组是可以判断是否相等的,直接使用==来判断,如果两个数组的顺序与内容完全一致,则会输出相等

多维数组的一个小例子:

	var courseInfo [3][4]string// 二维数组的初始化:courseInfo[0] = [4]string{"Java", "static", "4", "back"}

使用range进行遍历:

	var courseInfo [3][4]string// 二维数组的初始化:courseInfo[0] = [4]string{"Java", "static", "4", "back"}courseInfo[1] = [4]string{"Go", "static", "12", "back"}courseInfo[2] = [4]string{"TypeScript", "nonstatic", "24", "front"}for _, rows := range courseInfo {for _, values := range rows {fmt.Print(values)}}

数组的遍历

	// 遍历的方法// 最基础的遍历方法for i := 0; i < len(arr3); i++ {fmt.Println(arr3[i])}// 利用range进行遍历for index, value := range arr3 {fmt.Println(index, value)}// 若我们不想使用下标或值,我们需要使用下划线_代替(因为Go语言不支持定义未使用的变量)for _, v := range arr3 {fmt.Print(v)}

数组是值类型

我们调用函数的时候和Java中是一样的,我们的函数在对形参进行修改的时候,不会修改到传入的参数,而是会修改其传入的形参的复制,也就是说,数组的调用是值传递(Java中会直接传入数组的地址,所以看似是引用传递,但实际上是值传递,实现了引用传递的功能)。

另外:a [10]int和[11]int是不同的类型,再进行形参传递时甚至无法通过编译

如果我们需要对数组的内容进行修改,就需要使用到指针:

func printArray(arr *[3]int) {arr[0] = 100for i, v := range arr {fmt.Println(i, v)}
}printArray(&arr3)

这样使用数组其实不太方便,我们在实际使用中一般使用切片来代替数组的功能

切片(slice)

在我们以后的开发过程中,使用切片就像我们使用Java中的数组一样,我们更多使用Slice进行实际的开发

切片的定义

切片的定义:

	arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}s1 := arr[2:6]fmt.Println(s1) // [2 3 4 5]

切片的灵活定义:

	s2 := arr[:6]s3 := arr[2:]s4 := arr[:]fmt.Println(s2) // [0 1 2 3 4 5]fmt.Println(s3) //[2 3 4 5 6 7 8 9]fmt.Println(s4)//[0 1 2 3 4 5 6 7 8 9]

切片的基本原理

切片是对数组的视图,对这个视图的修改会对原数据造成修改:

func updateSlice(slice []int) {slice[0] = 100
}s5 := arr[2:6]fmt.Println(s5) //[2 3 4 5]updateSlice(s5)fmt.Println(s5) // [100 3 4 5]fmt.Println(arr) // [0 1 100 3 4 5 6 7 8 9]

另外的:我们甚至可以在slice上再slice,但我们最终操作的都是第一个slice基于的数组

Slice的扩展

我们还可以注意一个问题:Slice是可以向后自动拓展的

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

举例:

	s6 := arr[3:5]fmt.Println(s6)s7 := s6[1:4]   //[3 4]fmt.Println(s7) //[4 5 6]s8 := s6[3:4]   // [6]fmt.Println(s8)

按理说s6中只有两个元素,我们不可能取出第3、4个元素,但我们却实实在在的取出来了,这是因为我们的slice会存储一个cap元素用来记录从slice的起始位置到arr的最后位置,这样我们向后延展的时候就可以取出来了,并且我们甚至可以从超出slice长度的后面开始取,但基于这种原理,我们无法向前延展。

同理,由于s[index]不属于slice切片的赋值范畴,所以我们也不能用这种方式拓展

切片的操作

我们可以使用append来向slice中添加元素,这个添加可以超过原数组的长度,并且如果没有超过长度时,会修改对应原数组对应索引位置的数据,如果超过了长度,我们的Slice就不再作为原数组的索引,而是会被系统自动映射一个新的更长的索引,这个索引会比Append以后的切片更长(其实是拥有一个扩容机制,这个扩容机制也是由0、1、2、4、8、16、32、64、128。。。这样进行扩容的,在append时发现cap长度不足时会触发扩容

	s9 := arr[8:9]fmt.Println(s9) // [8]s10 := append(s9, 100)s11 := append(s10, 101)fmt.Println(s11) // [8 100 101]fmt.Println(s11[0:4])	// [8 100 101 0]

另外的,append添加元素后的新切片必须被接收

合并两个切片

	// 切片可以直接定义,类似与动态数组courses1 := []string{"语文", "数学", "英语"}courses2 := []string{"政治", "历史", "地理"}courses := append(courses1, courses2[1:]...)fmt.Println(courses)

Slice如果以基础的方式被声明,其内容为nil,len=0、cap=0

直接声明Slice

以下面这种方式可以直接声明一个切片

使用make方法可以创建一个切片,并声明它的len和cap

一般使用make进行切片的创建,可以避免其在空间不足时进行扩容

所以,我们在直接定义切片时,是不能直接向其中的某个元素赋值的,我们必须使用 append 进行添加

	var s12 []intfmt.Println(s12) // []s13 := make([]int, 15, 16)fmt.Println(s13) // [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]fmt.Println(len(s13)) // 15fmt.Println(cap(s13))	// 16
Slice的复制与删除

Slice的Copy操作:

	s14 := []int{2, 4, 8, 10}copy(s13, s14) // s14 复制给 s13、不会改变s13的len和cap,只从前往后改变,后面的也不便// s13:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]// s14:[2 4 8 10]fmt.Println(s13) // [2 4 8 10 0 0 0 0 0 0 0 0 0 0 0]

另外的,我们若把长的Copy给短的,短的的长度也不会改变,只会从前往后把元素复制过来

使用copy是深拷贝

使用slice = slice[:]是浅拷贝

如果我们要进行删除操作:

我们可以使用slice的切片机制以及append机制进行拼接

	s15 := s14[0:2]s16 := s14[3:4]s17 := append(s15, s16...) // 这个...用来将slice中的元素全部以逗号分隔的形式取出fmt.Println(s17)           // 这样就删除了第3个元素

另外的,我们可以使用s[1:]和s[:len(s)- 1]来掐头去尾

Slice 的底层原理

再进行函数的参数传递时,是值传递,不过其复制出来的是slice的指针,也就是另一个slice,但是其指向的是同一个数组。

所以我们在函数中进行调用时会改变原有的内容

但是,一旦我们使用了append方法令其发生了扩容,其就会将一个新数组进行返回,此时就不会影响原有内容

再另外,slice的扩容是按照 1、2、4、8、16、32、64、128、256、512、814.。。。。。也就是说其一开始以2的次方进行扩容,后来再对扩容的速度进行降低

Map

Map,键值对

Map的定义

Map的定义方式:有初值的定义方式

	// 有初值的定义方式m := map[string]string{"name":    "ccmouse","course":  "golang","site":    "imooc","quality": "notbad",}fmt.Println(m)

使用Make和var进行定义的方式:

	m2 := make(map[string]int)fmt.Println(m2)var m3 map[string]intfmt.Println(m3)

要注意的是:使用make定义出来的map是一个空map、而使用var定义的map是nil

map的操作

Map的遍历

	for k, v := range m {fmt.Println(k, v)}

同样的:我们的k,v也可以使用下划线进行省略

这里要注意的是:map底层是一个HashMap:其内部是无序的,故我们每次遍历其顺序都不同

另外:map是区分声明和初始化的,没有初始化的map无法插入数据

我们需要 var mymap = map[string]string{} 注意后面的花括号,必须加上了花括号才算是初始化完成了

,但相反的:Slice就没有这个需求,其可以直接进行添加

Map的取值:

	courseName := m["name"]fmt.Println(courseName)

要注意的是:

我们就算取了一个不存在的Key值,也可以通过编译(会取到Zero Value)并返回一个空值,我们可以使用第二个返回值来判断是否取到元素

	courseName, ok := m["name"]fmt.Println(courseName, ok)c, ok := m["c"]fmt.Println(c, ok)

这就自然而然的延伸出了一种防止取到空值的方法:

	if courseName, ok := m["name"]; ok {fmt.Println(courseName)}

Map的值的删除:

	delete(m, "name")

另外还要注意:Map是线程不安全的

例题

一个简单的判断子串问题:

func length(s string) int {lastOccurred := make(map[byte]int) // 每个byte为键、int为值start := 0maxLength := 0// 遍历字符串for i, ch := range []byte(s) {if lastI, ok := lastOccurred[ch]; ok && lastI >= start {start = lastI + 1}if i-start+1 > maxLength {maxLength = i - start + 1}lastOccurred[ch] = i}return maxLength
}func main() {fmt.Println(length("abcabcdbb"))
}  

思路是:遍历每一个字符,对于每一个字符:若其在空map中不存在,则认为其在之前的位置都没有出现过,一定可以新加入进来,若其在map中不为空,则证明其在之前出现过,但我们仍然需要判断其最后是否在start的位置之前出现的,若是的话,也可以新加入进来,若不是的话,则证明这个字符串到头了,可以作为一个不重复子串了

list

list 就相当于链表

字符和字符串的处理

一个Demo:

func main() {str := "Luckin瑞幸咖啡"// 将字符串转成字节流就是这个样子(16进制数)// 每一个中文字符都是3个字节for index, value := range []byte(str) {fmt.Printf("(%d %X)", index, value)}fmt.Println()// 若转成char型呢:// 将每一个字符串联在一起,并且其下标是按照字符标注的,遇到中文会跳跃两个for index, value := range str {fmt.Printf("(%d %X)", index, value)}fmt.Println()// 同时,我们可以使用utf8的标准库进行一些操作count := utf8.RuneCountInString(str) // 求字符串字符数fmt.Printf("%d", count)bytes := []byte(str)for len(bytes) > 0 {ch, size := utf8.DecodeRune(bytes) // 可以将字节流转换成字符bytes = bytes[size:]fmt.Printf("%c ", ch) // 输出每一个字符}fmt.Println()
}

要注意的是,我们在直接使用i, v := string的时候,我们的下标是跳跃的,很容易出现乱码的情况。

我们可以使用rune进行操作。(rune会将原先的东西另开一片空间,将他们都存储在新的空间中)

	// 使用rune,这样它的下标是连续的for index, ch := range []rune(str) {fmt.Printf("(%d %c)", index, ch)}fmt.Println()

%d 整数、%c 字符、%X 字节

但是我们在使用fmt.Println的时候是不允许使用百分号的形式来进行格式化输出的

以百分号进行格式化输出会提高格式化的可维护性,但其效率很低

另外的,我们可以使用fmt.Sprintf()的方式来将格式化的字符串存储在变量中

	name := "曹操"age := 500stringTest := fmt.Sprintf("他的名字是%s, 他%d岁了", name, age)fmt.Println(stringTest)

高性能字符串拼接(strings.Builder)

	var stringBuilder strings.BuilderstringBuilder.WriteString("他的名字是")stringBuilder.WriteString(name)stringBuilder.WriteString(", 他")stringBuilder.WriteString(strconv.Itoa(age))stringBuilder.WriteString("岁了")stringb := stringBuilder.String()fmt.Println(stringb)

使用strings.Builder的方式拼接字符串的效率远高于上面两种格式化的方式

字符串的比较

使用 == 来比较字符串的内容是否相同

比较大小是按字符依次比较ASCII码,看谁先更大。

goto语句

goto语句允许我们将程序直接跳转到某个代码块中,其后的代码无论其处在哪个阶段,代码都不再执行

这个写法是很危险的,很容易导致代码的混乱或造成代码的不确定性,极难维护

func main() {for i := 0; i < 10; i++ {print(i)if i == 5 {goto over}}over:print("程序被跳转到了这个位置")
}

相关文章:

【Go】一、Go语言基本语法与常用方法容器

GO基础 Go语言是由Google于2006年开源的静态语言 1972&#xff1a;&#xff08;C语言&#xff09; — 1983&#xff08;C&#xff09;—1991&#xff08;python&#xff09;—1995&#xff08;java、PHP、js&#xff09;—2005&#xff08;amd双核技术 web端新技术飞速发展&…...

杨中科 ASP.NETCORE 高级14 SignalR

1、什么是websocket、SignalR 服务器向客户端发送数据 1、需求&#xff1a;Web聊天;站内沟通。 2、传统HTTP&#xff1a;只能客户端主动发送请求 3、传统方案&#xff1a;长轮询&#xff08;Long Polling&#xff09;。缺点是&#xff1f;&#xff08;1.客户端发送请求后&…...

哪家洗地机比较好用?性能好的洗地机推荐

在众多功能中&#xff0c;我坚信洗地机的核心依旧是卓越的清洁能力以及易于维护的便捷性&#xff0c;其他的附加功能可以看作是锦上添花&#xff0c;那么如何找到性能好的洗地机呢&#xff1f;我们一起看看哪些洗地机既能确保卫生效果还能使用便利。 洗地机工作原理&#xff1…...

学习与非学习

学习与非学习是人类和动物行为表现中的两种基本形式&#xff0c;它们在认知过程和行为适应上有着根本的区别。理解这两者之间的差异对于把握认知发展、心理学以及教育学等领域的核心概念至关重要。 学习 学习是一个获取新知识、技能、态度或价值观的过程&#xff0c;它导致行为…...

牛客网SQL进阶127: 月总刷题数和日均刷题数

官网链接&#xff1a; 月总刷题数和日均刷题数_牛客题霸_牛客网现有一张题目练习记录表practice_record&#xff0c;示例内容如下&#xff1a;。题目来自【牛客题霸】https://www.nowcoder.com/practice/f6b4770f453d4163acc419e3d19e6746?tpId240 0 问题描述 基于练习记录表…...

19:Web开发模式与MVC设计模式-Java Web

目录 19.1 Java Web开发模式19.2 MVC设计模式详解19.3 MVC与其他Java Web开发模式的区别总结19.4 应用场景总结 在Java Web应用程序开发领域&#xff0c;有效的架构模式和设计模式对提高代码可维护性、模块化以及团队协作至关重要。本文将探讨Java Web开发中的常见模式——模型…...

Z字形变换

问题&#xff1a; 将一个给定字符串 s 根据给定的行数 numRows &#xff0c;以从上往下、从左到右进行 Z 字形排列。 比如输入字符串为 "PAYPALISHIRING" 行数为 3 时&#xff0c;排列如下&#xff1a; P A H N A P L S I I G Y I R 之后&#xff0c;你…...

飞书上传图片

飞书上传图片 1. 概述1.1 访问凭证2. 上传图片获取image_key1. 概述 飞书开发文档上传图片: https://open.feishu.cn/document/server-docs/im-v1/image/create 上传图片接口,支持上传 JPEG、PNG、WEBP、GIF、TIFF、BMP、ICO格式图片。 在请求头上需要获取token(访问凭证) …...

Java微服务学习Day1

文章目录 认识微服务服务拆分及远程调用服务拆分服务远程调用提供者与消费者 Eureka注册中心介绍构建EurekaServer注册user-serviceorder-service完成服务拉取 Ribbon负载均衡介绍原理策略饥饿加载 Nacos注册中心介绍配置分级存储负载均衡环境隔离nacos注册中心原理 认识微服务…...

STM32标准库驱动W25Q64模块读写字库数据+OLED0.96显示例程

STM32标准库驱动W25Q64 模块读写字库数据OLED0.96显示例程 &#x1f3ac;原创作者对W25Q64保存汉字字库演示&#xff1a; W25Q64保存汉字字库 &#x1f39e;测试字体显示效果&#xff1a; &#x1f4d1;功能实现说明 利用W25Q64保存汉字字库&#xff0c;OLED显示汉字的时候&…...

【java】简单的Java语言控制台程序

一、用于文本文件处理的Java语言控制台程序示例 以下是一份简单的Java语言控制台程序示例&#xff0c;用于文本文件的处理。本例中我们将会创建一个程序&#xff0c;它会读取一个文本文件&#xff0c;显示其内容&#xff0c;并且对内容进行计数&#xff0c;然后将结果输出到控…...

【服务器数据恢复】HP EVA虚拟化磁盘阵列数据恢复原理方案

EVA存储结构&原理&#xff1a; EVA是虚拟化存储&#xff0c;在工作过程中&#xff0c;EVA存储中的数据会不断地迁移&#xff0c;再加上运行在EVA上的应用都比较繁重&#xff0c;磁盘负载高&#xff0c;很容易出现故障。EVA是通过大量磁盘的冗余空间和故障后rss冗余磁盘动态…...

08-OpenFeign-结合Sentinel,实现熔断降级

当我们在对服务远程调用时&#xff0c;会因为服务的请求超时、抛出异常等情况&#xff0c;导致调用失败。 如果短时间内&#xff0c;产生大量请求异常。引发上游的调用方请求积压&#xff0c;最终会引起整个调用链雪崩。 为此我们需要对核心的调用过程进行监控&#xff0c;当…...

15.实现数组的扁平化

实现方式1(递归) 普通的递归思路很容易理解&#xff0c;就是通过循环递归的方式&#xff0c;一项一项地去遍历&#xff0c;如果每一项还是一个数组&#xff0c;那么就继续往下遍历&#xff0c;利用递归程序的方法&#xff0c;来实现数组的每一项的连接&#xff1a; let arr […...

对话模型Demo解读(使用代码解读原理)

文章目录 前言一、数据加工二、模型搭建三、模型训练1、构建模型2、优化器与损失函数定义3、模型训练 四、模型推理五、所有Demo源码 前言 对话模型是一种人工智能技术&#xff0c;旨在使计算机能够像人类一样进行对话和交流。这种模型通常基于深度学习和自然语言处理技术&…...

Android 自定义BaseFragment

直接上代码&#xff1a; BaseFragment代码&#xff1a; package com.example.custom.fragment;import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx…...

[C#] 如何对列表,字典等进行排序?

对列表进行排序 下面是一个基于C#的列表排序的案例&#xff1a; using System; using System.Collections.Generic;class Program {static void Main(string[] args){// 创建一个列表List<int> numbers new List<int>() { 5, 2, 8, 1, 10 };// 使用Sort方法对列…...

Mac 下载安装Java、maven并配置环境变量

下载Java8 下载地址&#xff1a;https://www.oracle.com/java/technologies/downloads/ 根据操作系统选择版本 没有oracle账号需要注册、激活登录 mac直接选择.dmg文件进行下载&#xff0c;下载后安装。 默认安装路径&#xff1a;/Library/Java/JavaVirtualMachines/jdk-1…...

【多模态】27、Vary | 通过扩充图像词汇来提升多模态模型在细粒度感知任务(OCR等)上的效果

文章目录 一、背景二、方法2.1 生成 new vision vocabulary2.1.1 new vocabulary network2.1.2 Data engine in the generating phrase2.1.3 输入的格式 2.2 扩大 vision vocabulary2.2.1 Vary-base 的结构2.2.2 Data engine2.2.3 对话格式 三、效果3.1 数据集3.2 图像细粒度感…...

|Python新手小白低级教程|第二十章:函数(2)【包括石头剪刀布判断程序(模拟版)】

文章目录 前言一、复习一、函数实战之——if语句特殊系统1.判断等第分数&#xff08;函数名为mark&#xff08;参数num&#xff09;&#xff09;2.石头剪刀布判断程序 二、练习总结 前言 Hello&#xff0c;大家好&#xff0c;我是你们的BoBo仔&#xff0c;感谢你们来阅读我的文…...

vue3 之 商城项目—home

home—整体结构搭建 根据上面五个模块建目录图如下&#xff1a; home/index.vue <script setup> import HomeCategory from ./components/HomeCategory.vue import HomeBanner from ./components/HomeBanner.vue import HomeNew from ./components/HomeNew.vue import…...

git flow与分支管理

git flow与分支管理 一、git flow是什么二、分支管理1、主分支Master2、开发分支Develop3、临时性分支功能分支预发布分支修补bug分支 三、分支管理最佳实践1、分支名义规划2、环境与分支3、分支图 四、git flow缺点 一、git flow是什么 Git 作为一个源码管理系统&#xff0c;…...

【Linux】学习-进程信号

进程信号 信号入门 生活角度的信号 你在网上买了很多件商品,再等待不同商品快递的到来。但即便快递没有到来,你也知道快递来临时,你该怎么处理快递。也就是你能“识别快递”,也就是你意识里是知道如果这时候快递员送来了你的包裹,你知道该如何处理这些包裹当快递员到了你…...

webgis后端安卓系统部署攻略

目录 前言 一、将后端项目编译ARM64 二、安卓手机安装termux 1.更换为国内源 2.安装ssh远程访问 3.安装文件远程访问 三、安装postgis数据库 1.安装数据库 2.数据库配置 3.数据导入 四、后端项目部署 五、自启动设置 总结 前言 因为之前一直做的H5APP开发&#xf…...

【数据分享】1929-2023年全球站点的逐日平均风速数据(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、能见度等指标&#xff0c;说到气象数据&#xff0c;最详细的气象数据是具体到气象监测站点的数据&#xff01; 有关气象指标的监测站点数据&#xff0c;之前我们分享过1929-2023年全球气象站…...

【多模态大模型】视觉大模型SAM:如何使模型能够处理任意图像的分割任务?

SAM&#xff1a;如何使模型能够处理任意图像的分割任务&#xff1f; 核心思想起始问题: 如何使模型能够处理任意图像的分割任务&#xff1f;5why分析5so分析 总结子问题1: 如何编码输入图像以适应分割任务&#xff1f;子问题2: 如何处理各种形式的分割提示&#xff1f;子问题3:…...

Shell之sed

sed是什么 Linux sed 命令是利用脚本来处理文本文件。 可依照脚本的指令来处理、编辑文本文件。主要用来自动编辑一个或多个文件、简化对文件的反复操作、编写转换程序等。 sed命令详解 语法 sed [-hnV][-e <script>][-f<script文件>][文本文件] sed [-nefr] [动作…...

AJAX——认识URL

1 什么是URL&#xff1f; 统一资源定位符&#xff08;英语&#xff1a;Uniform Resource Locator&#xff0c;缩写&#xff1a;URL&#xff0c;或称统一资源定位器、定位地址、URL地址&#xff09;俗称网页地址&#xff0c;简称网址&#xff0c;是因特网上标准的资源的地址&…...

《Docker极简教程》--Docker环境的搭建--在Linux上搭建Docker环境

更新系统&#xff1a;首先确保所有的包管理器都是最新的。对于基于Debian的系统&#xff08;如Ubuntu&#xff09;&#xff0c;可以使用以下命令&#xff1a;sudo apt-get update sudo apt-get upgrade安装必要的依赖项&#xff1a;安装一些必要的工具&#xff0c;比如ca-certi…...

开源微服务平台框架的特点是什么?

借助什么平台的力量&#xff0c;可以让企业实现高效率的流程化办公&#xff1f;低代码技术平台是近些年来较为流行的平台产品&#xff0c;可以帮助很多行业进入流程化办公新时代&#xff0c;做好数据管理工作&#xff0c;从而提升企业市场竞争力。流辰信息专业研发低代码技术平…...

C#系列-C#操作UDP发送接收数据(10)

在C#中&#xff0c;发送UDP数据并接收响应通常涉及创建两个UdpClient实例&#xff1a;一个用于发送数据&#xff0c;另一个用于接收响应。以下是发送UDP数据并接收响应的示例代码&#xff1a; 首先&#xff0c;我们需要定义一个方法来发送UDP数据&#xff0c;并等待接收服务器…...

突破编程_C++_面试(基础知识(10))

面试题29&#xff1a;什么是嵌套类&#xff0c;它有什么作用 嵌套类指的是在一个类的内部定义的另一个类。嵌套类可以作为外部类的一个成员&#xff0c;但它与其声明类型紧密关联&#xff0c;不应被用作通用类型。嵌套类可以访问外部类的所有成员&#xff0c;包括私有成员&…...

初步探索Pyglet库:打造轻量级多媒体与游戏开发利器

目录 pyglet库 功能特点 安装和导入 安装 导入 基本代码框架 导入模块 创建窗口 创建控件 定义事件 运行应用 程序界面 运行结果 完整代码 标签控件 常用事件 窗口事件 鼠标事件 键盘事件 文本事件 其它场景 网页标签 音乐播放 图片显示 祝大家新…...

【npm】安装全局包,使用时提示:不是内部或外部命令,也不是可运行的程序或批处理文件

问题 如图&#xff0c;明明安装Vue是全局包&#xff0c;但是使用时却提示&#xff1a; 解决办法 使用以下命令任意一种命令查看全局包的配置路径 npm root -g 然后将此路径&#xff08;不包括node_modules&#xff09;添加到环境变量中去&#xff0c;这里注意&#xff0c;原…...

Go 语言 for 的用法

For statements 本文简单翻译了 Go 语言中 for 的三种用法&#xff0c;可快速学习 Go 语言 for 的使用方法&#xff0c;希望本文能为你解开一些关于 for 的疑惑。详细内容可见文档 For statements。 For statements with single condition 在最简单的形式中&#xff0c;只要…...

熵权法Python代码实现

文章目录 前言代码数据熵权法代码结果 前言 熵权法做实证的好像很爱用&#xff0c;matlab的已经实现过了&#xff0c;但是matlab太大了早就删了&#xff0c;所以搞一搞python实现的&#xff0c;操作空间还比较大 代码 数据 import pandas as pd data [[100,90,100,84,90,1…...

浏览器提示ERR_SSL_KEY_USAGE_INCOMPATIBLE解决

ERR_SSL_KEY_USAGE_INCOMPATIBLE报错原因 ERR_SSL_KEY_USAGE_INCOMPATIBLE 错误通常发生在使用 SSL/TLS 连接时,指的是客户端和服务器之间进行安全通信尝试失败,原因是证书中的密钥用途(Key Usage)或扩展密钥用途(Extended Key Usage, EKU)与正在尝试的操作不兼容。这意味…...

使用深度学习进行“序列到序列”分类

目录 加载序列数据 定义 LSTM 网络架构 测试 LSTM 网络 此示例说明如何使用长短期记忆 (LSTM) 网络对序列数据的每个时间步进行分类。 要训练深度神经网络以对序列数据的每个时间步进行分类,可以使用“序列到序列”LSTM 网络。通过“序列到序列”LSTM 网络,可以对…...

Python和Java的区别(不断更新)

主要通过几个方面区分Python和Java&#xff0c;让大家有一个对比&#xff1a; 语言类型 Java是一种静态类型、编译型语言。 Python是一种动态类型、解释型语言&#xff0c;注重简洁和灵活的语法。 语法 在Java中&#xff0c;变量需要显式地声明&#xff0c;指定其类型。例如&am…...

Ubuntu22.04 gnome-builder gnome C 应用程序习练笔记(三)

八、ui窗体创建要点 .h文件定义(popwindowf.h)&#xff0c; TEST_TYPE_WINDOW宏是要创建的窗口样式。 #pragma once #include <gtk/gtk.h> G_BEGIN_DECLS #define TEST_TYPE_WINDOW (test_window_get_type()) G_DECLARE_FINAL_TYPE (TestWindow, test_window, TEST, WI…...

vue electron 应用在windows系统上以管理员权限打开应用

打开package.json文件&#xff0c;在build下的win增加配置 "requestedExecutionLevel": "requireAdministrator",...

c实现链表

目录 c实现链表 链表的结构定义&#xff1a; 链表的结构操作&#xff1a; 1、初始化链表 2、销毁链表 3、插入结点 4、输出链表数据 5、查找链表数据 扩展 代码实现 c实现链表 链表的结构定义&#xff1a; /*** 链表结构定义 ***/ typedef struct Node {int data; //…...

力扣231. 2 的幂(数学,二分查找,位运算)

Problem: 231. 2 的幂 文章目录 题目描述思路即解法复杂度Code 题目描述 思路即解法 思路1&#xff1a;位运算 1.易验证2的幂为正数&#xff1b; 2.易得2的幂用二进制表示只能有一个位为数字1 3.即将其转换为二进制统计其二进制1的个数 思路2&#xff1a;数学 当给定数n大于1时…...

Maven私服部署与JAR文件本地安装

Nexus3 是一个仓库管理器&#xff0c;它极大地简化了本地内部仓库的维护和外部仓库的访问。 平常我们在获取 maven 仓库资源的时候&#xff0c;都是从 maven 的官方&#xff08;或者国内的镜像&#xff09;获取。团队的多人员同样的依赖都要从远程获取一遍&#xff0c;从网络方…...

【MySQL】字符串函数的学习

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-J7VN4RbrBi51ozap {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…...

AI助力农作物自动采摘,基于YOLOv5全系列【n/s/m/l/x】参数模型开发构建作番茄采摘场景下番茄成熟度检测识别计数分析系统

去年十一那会无意间刷到一个视频展示的就是德国机械收割机非常高效自动化地24小时不间断地在超广阔的土地上采摘各种作物&#xff0c;专家设计出来了很多用于采摘不同农作物的大型机械&#xff0c;看着非常震撼&#xff0c;但是我们国内农业的发展还是相对比较滞后的&#xff0…...

记录下ibus-libpinyin输入法的重新安装

目前的版本为&#xff1a; 首先把现在的ibus-libpinyin卸了 sudo apt-get --purge remove ibus-libpinyin sudo apt-get autoremove 安装教程请参考 Installation libpinyin/ibus-libpinyin Wiki GitHub yilai sudo apt install pkg-config sudo apt-get install lib…...

第三百一十八回

文章目录 1. 概念介绍2. 使用方法2.1 本地缓冲2.2 服务器缓冲3. 示例代码4. 内容总结我们在上一章回中介绍了"如何让输入键盘不遮挡屏幕"相关的内容,本章回中将介绍如何有效地缓冲网络图片.闲话休提,让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章回中介绍的…...

破除Github API接口的访问次数限制

破除Github API接口的访问次数限制 1、Github介绍2、Github API接口2.1 介绍2.2 使用方法 3、Github API访问限制3.1 访问限制原因3.2 访问限制类别 4、Github API访问限制破除4.1 限制破除原理4.2 限制破除示例 1、Github介绍 Github&#xff0c;是一个面向开源及私有软件项目…...

蓝桥杯嵌入式第8届真题(完成) STM32G431

蓝桥杯嵌入式第8届真题(完成) STM32G431 题目 分析和代码 对比第六届和第七届&#xff0c;这届的题目在逻辑思维上确实要麻烦不少&#xff0c;可以从题目看出&#xff0c;这届题目对时间顺序的要求很严格&#xff0c;所以就可以使用状态机的思想来编程&#xff0c;拿到类似题…...