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

news2024/11/26 0:52:17

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 main

import "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 int
	var s string
	fmt.Println(a, s)
}

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

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

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

赋初值的方法:

// 赋初值
func variableInitialValue() {
	var a, b int = 3, 4
	var 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 = 33
	bb = false
	cc = "kiu"
)

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

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

常量

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

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

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

iota

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

	const (
		AA = iota + 100
		BB
		CC = "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, 4
	var c int
	c = int(math.Sqrt(float64(a * a + b * b)))
}

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

float转int会丢掉小数部分

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

func main() {
	// 基础的类型转还能
	var t1 uint = 12
	var 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进制

	// 字符串转Float
	testStrToFloat := "12.5"
	strToFloat, err := strconv.ParseFloat(testStrToFloat, 64) // 第二个参数是编码参数,一般直接写64
	if 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  = 1
		moon = 2
		star = 3
	)

	// 另外,我们可以利用iota这个表达式
	// 这个表达式可以对我们的常量进行自增
	const (
		sun1 = iota
		sun2
		sun3
	)
	fmt.Println(sun1, sun2, sun3) //0 1 2

	// 我们可以利用这个表达式进行一些操作
	const (
		b = 1 << (10 * iota)
		kb
		mb
		gb
	)
	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转string
		result = 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 + b
	case "-":
		return a - b
	case "*":
		return a * b
	case "/":
		return a / b
	default:
		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 / b
	r = a % b
	return
}

func main() {
	q, r := div(8, 3)
	fmt.Println(q, r)
}

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

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

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, nil
	case "-":
		return a - b, nil
	case "*":
		return a * b, nil
	case "/":
		q, _ := div(a, b)
		return q, nil
	default:
		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 := 0
	for 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, 4
	swap(&a, &b)
	fmt.Println(a, b)  
}

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

通常解法:

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

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

}

数组

数组的定义方式

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

	fmt.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)) // 15

	fmt.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]int
	fmt.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 := 0
	maxLength := 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 := 500
	stringTest := fmt.Sprintf("他的名字是%s, 他%d岁了", name, age)
	fmt.Println(stringTest)

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

	var stringBuilder strings.Builder
	stringBuilder.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("程序被跳转到了这个位置")
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1444360.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

探索Spring Validation:优雅实现后端数据验证的艺术

在现代Web应用开发中&#xff0c;数据验证是一项至关重要的任务&#xff0c;确保应用程序接收到的用户输入符合预期规范&#xff0c;不仅能够提高系统的健壮性&#xff0c;也能有效防止潜在的安全漏洞。Spring Framework通过其内置的Spring Validation模块&#xff0c;为我们提…

案例:CentOS8 在 MySQL8.0 实现半同步复制

异步复制 MySQL 默认的复制即是异步的&#xff0c;主库在执行完客户端提交的事务后会立即将结果返给给客户端&#xff0c;并不关心从库是否已经接收并处理&#xff0c;这样就会有一个问题&#xff0c;主节点如果 crash 掉了&#xff0c;此时主节点上已经提交的事务可能并没有传…

进程间通信-消息队列

消息队列的公共资源是链表结构。 通信双方不会和消息队列进行挂接&#xff0c;而是像管道一样&#xff0c;访问内存中的消息队列。 消息队列由操作系统维护&#xff0c;但是由通信的某一方创建和删除 通信双方都需要获取到消息队列&#xff0c;和共享内存一样。 当发送方有数据…

ARP欺骗攻击利用之抓取https协议的用户名与密码

1.首先安装sslstrip 命令执行&#xff1a;apt-get install sslstrip 2.启动arp欺骗 arpspoof -i ech0 -t 192.168.159.148 192.168.159.2 arpspoof -i ech0(网卡) -t 目标机ip 本地局域网关 3.命令行输入: vim /etc/ettercap/etter.conf进入配置文件 找到下红框的内容&a…

Java核心设计模式:代理设计模式

一、生活中常见的代理案例 房地产中介&#xff1a;客户手里没有房源信息&#xff0c;找一个中介帮忙商品代购&#xff1a;代理者一般有好的资源渠道&#xff0c;降低购物成本&#xff08;如海外代购&#xff0c;自己不用为了买东西出国&#xff09; 二、为什么要使用代理 对…

《动手学深度学习(PyTorch版)》笔记8.5

注&#xff1a;书中对代码的讲解并不详细&#xff0c;本文对很多细节做了详细注释。另外&#xff0c;书上的源代码是在Jupyter Notebook上运行的&#xff0c;较为分散&#xff0c;本文将代码集中起来&#xff0c;并加以完善&#xff0c;全部用vscode在python 3.9.18下测试通过&…

3D立方体图册

<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>3D立方体图册</title><style>* {pad…

奶茶点餐|奶茶店自助点餐系统|基于微信小程序的饮品点单系统的设计与实现(源码+数据库+文档)

奶茶店自助点餐系统目录 目录 基于微信小程序的饮品点单系统的设计与实现 一、前言 二、系统功能设计 三、系统实现 1、商品信息管理 2、商品评价管理 3、商品订单管理 4、用户管理 四、数据库设计 1、实体ER图 2、具体的表设计如下所示&#xff1a; 五、核心代码 …

###C语言程序设计-----C语言学习(11)#数据的存储和基本数据类型

前言&#xff1a;感谢您的关注哦&#xff0c;我会持续更新编程相关知识&#xff0c;愿您在这里有所收获。如果有任何问题&#xff0c;欢迎沟通交流&#xff01;期待与您在学习编程的道路上共同进步。 一. 数据的存储 1.整型数据的存储 计算机处理的所有信息都以二进制形式表示…

政安晨:梯度与导数~示例演绎《机器学习·神经网络》的高阶理解

这篇文章确实需要一定的数学基础&#xff0c;第一次接触的小伙伴可以先看一下我示例演绎这个主题的前两篇文章&#xff1a; 示例演绎机器学习中&#xff08;深度学习&#xff09;神经网络的数学基础——快速理解核心概念&#xff08;一&#xff09;&#xff1a; 政安晨&#…

HCIA-HarmonyOS设备开发认证V2.0-3.2.轻量系统内核基础-时间管理

目录 一、时间管理1.1、时间接口 一、时间管理 时间管理以系统时钟为基础&#xff0c;给应用程序提供所有和时间有关的服务。系统时钟是由定时器/计数器产生的输出脉冲触发中断产生的&#xff0c;一般定义为整数或长整数。输出脉冲的周期叫做一个“时钟滴答”。系统时钟也称为…

课时20:全局变量_嵌套shell

2.3.3 嵌套shell 学习目标 这一节&#xff0c;我们从 export原理、嵌套实践、小结 三个方面来学习。 export原理 原理解析 用户登录时:用户登录到Linux系统后&#xff0c;系统将启动一个用户shell。在这个shell中&#xff0c;可以使用shell命令或声明变量&#xff0c;也可…

MySQL篇----第十九篇

系列文章目录 文章目录 系列文章目录前言一、什么是存储过程?用什么来调用?二、如何通俗地理解三个范式?三、什么是基本表?什么是视图?四、试述视图的优点?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这…

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

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

vue项目搭建测试

5&#xff0c;项目测试 导入elementplus以及样式 import ElementPlus from element-plus import element-plus/dist/index.csscreateApp(App).use(store).use(router).use(ElementPlus).mount(#app)<template><el-row class"mb-4"><el-button>De…

Windows10安装PCL1.14.0及点云配准

一、下载visual studio2022 下载网址&#xff1a;Visual Studio: 面向软件开发人员和 Teams 的 IDE 和代码编辑器 (microsoft.com) 安装的时候选择"使用C的桌面开发“&#xff0c;同时可以修改文件路径&#xff0c;可以放在D盘。修改文件路径的时候&#xff0c;共享组件、…

【PWN · heap | Arbitrary Alloc】2015_9447ctf_search-engine

和【PWN heap | House Of Spirit】2014_hack.lu_oreo-CSDN博客略有区别&#xff0c;但都是通过malloc一块fake_chunk到指定区域&#xff0c;获得对该区域的写权限 目录 零、简单介绍 一、题目分析 1.主要功能 2.index_sentence(): 增添一条语句到“库”中 3.search_word(…

ubuntu下修改hosts读写权限

ubuntu下修改hosts文件的操作&#xff1a; 由于需要在hosts文件下添加ip地址信息&#xff0c;但是初始情况下系统该文件为只读权限无法修改&#xff0c;具体操作如下所示&#xff1b; 1.cd到系统etc目录下&#xff0c;执行如下命令,此时会提示输入密码&#xff0c;直接输入回…

Java实现陕西非物质文化遗产网站 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 设计目标2.2 研究内容2.3 研究方法与过程2.3.1 系统设计2.3.2 查阅文献2.3.3 网站分析2.3.4 网站设计2.3.5 网站实现2.3.6 系统测试与效果分析 三、系统展示四、核心代码4.1 查询民间文学4.2 查询传统音乐4.3 增改传统舞…

用C语言列出Linux或Unix上的网络适配器

上代码&#xff1a; 1. #include <sys/socket.h> 2. #include <stdio.h> 3. 4. #include <netdb.h> 5. #include <ifaddrs.h> 6. 7. int main() { 8. struct ifaddrs *addresses; 9. if(getifaddrs(&addresses) -1) { 10. printf("…