Go语言基础-程序员宅基地

技术标签: golang  Go  后端  开发语言  

基础语法

变量

变量的定义

声明变量通过使用var关键字

var name type
  • var 声明变量关键字
  • name 变量命名
  • type 变量的类型
    变量命名采用驼峰命名
    go语言中变量没有使用会报错

可以使用var同时定义多个变量

var {
    
	addr string
	phone string
	}

当一个变量被声明之后,如果没有显示的给它赋值,系统会自动赋予该类型的零值

  • 整型和浮点型变量的默认值为0 和0.0
  • 字符串变量的默认值为空字符串
  • 布尔变量默认为false
  • 切片、函数、指针变量的默认为nil

变量的初始化

var 变量名 类型 = 值(表达式)

短变量声明同时初始化

变量名 :=

这是Go语言的推到声明写法,编译器会自动根据右值推断出左值的对应类型

声明变量后,内存会给一块地址给变量使用,可以使用printf("%p", 变量)查看地址

匿名变量

匿名变量的特点是一个下划线" - ",被称为空白标识符,可以用于变量的声明和赋值,但任何值赋予都会被抛弃,不会在后续代码使用
匿名变量不占用内存空间,不会分配内存。匿名变量和匿名变量之间也不会因为多次声明而无法使用

常量

const 常量是一个简单值的标识符,在程序运行时,不会修改的量
常量中的数据类型只可以是布尔类型、整数型、浮点型、复数、字符串

const identifier [type] = value 

显示类型定义:==const b string = “abc” ==
隐式类型定义:==const b = “abc” ==
可以使用 const a1, b1 = value1, value2 声明多类型

iota

特殊常量,可以被编译器修改的常量,go语言的常量计数器
iota在const关键字出现时将被重置为0(const内部的第一行之前),const中每新增一行讲师iota计数一次

package main

import "fmt"

const (
	a = iota
	b
	c
)

func main() {
    
	fmt.Println(a) //0
	fmt.Println(b) //1
	fmt.Println(c) //2
}

第一个iota等于0,每当iota在新的一行被使用时,它的值会自动加1
使用iota,每当有一条常量被声明,iota就加1


基本数据类型

布尔型

布尔型的值只可以时常量true或者false


数字型

整数类型:

int 和 uint:根据运行平台的位数,可以是32位或64位的有符号整数和无符号整数。
int8 和 uint8:8位有符号整数和无符号整数,范围为 -128 到 127 和 0 到 255。
int16 和 uint16:16位有符号整数和无符号整数,范围为 -32768 到 32767 和 0 到 65535。
int32 和 uint32:32位有符号整数和无符号整数,范围为 -2147483648 到 2147483647 和 0 到 4294967295。
int64 和 uint64:64位有符号整数和无符号整数,范围为 -9223372036854775808 到 9223372036854775807 和 0 到 18446744073709551615。
byte 是 uint8 的别名,rune 是 int32 的别名,它们通常用于表示字符的整数值和 Unicode 码点。

浮点数类型:

float32:32位浮点数,可以表示大约 6 个小数位的精度。
float64:64位浮点数,可以表示大约 15 个小数位的精度。

复数类型:

complex64:由两个 float32 类型的浮点数构成的复数。
complex128:由两个 float64 类型的浮点数构成的复数。

package main

import "fmt"

func main() {
    
	//定义一个整型
	var age int = 18
	fmt.Printf("%T, %d\n", age, age) //int, 18

	//定义一个浮点型
	//默认是6位小数打印
	var money float64 = 3.14
	fmt.Printf("%T, %f\n", money, money) //float64, 3.140000

}

字符串

在Go语言中,字符串类型使用双引号(")或反引号(`)来表示。以下是关于Go语言字符串的一些重要特点:

  • 字符串的不可变性:在Go语言中,字符串是不可变的,一旦创建就不能更改其中的字符。每次对字符串进行修改时,都会创建一个新的字符串。

  • 字符串字面值:使用双引号来定义字符串字面值,例如:“Hello,World!”。反引号也可以用于定义原始字符串字面值,原始字符串字面值中的特殊字符会保留原始格式,例如:Hello, \nWorld!。

  • Unicode支持:Go语言的字符串是根据Unicode字符集编码的,可以包含任意Unicode字符,包括ASCII字符和非ASCII字符。

  • 字符串操作:Go语言提供了许多内置的字符串操作函数和方法,如拼接字符串(使用+操作符或strings.Join()函数)、获取字符串长度(使用len()函数)、切割字符串(使用strings.Split()函数)等。

  • 字符串索引和切片:使用索引和切片操作可以访问和修改字符串中的单个字符或子串。字符串的索引从0开始,并可以使用[起始索引:结束索引]的形式进行切片操作,其中结束索引不包含在切片结果中。

package main

import (
	"fmt"
	"strings"
)

func main() {
    
	str := "Hello, World!"

	// 字符串长度
	fmt.Println(len(str))

	// 字符串拼接
	str = str + " Welcome"
	fmt.Println(str)

	// 字符串切割
	slice := strings.Split(str, ",")
	fmt.Println(slice)

	// 字符串索引和切片
	fmt.Println(str[0])         // 输出 'H'
	fmt.Println(str[7:12])     // 输出 'World'
}

运算符

  • 算术运算符:
+:相加
-:相减
*:相乘
/:相除
%:取余数
  • 关系运算符:
==:等于
!=:不等于
>:大于
<:小于
>=:大于等于
<=:小于等于
  • 逻辑运算符:
&&:逻辑与
||:逻辑或
!:逻辑非
  • 位运算符:
&:按位与
|:按位或
^:按位异或
<<:左移
>>:右移
&^:按位清零(位清除)
  • 赋值运算符:
=:赋值
+=:加法赋值
-=:减法赋值
*=:乘法赋值
/=:除法赋值
%=:取余赋值
<<=:左移赋值
>>=:右移赋值
&=:按位与赋值
|=:按位或赋值
^=:按位异或赋值
&^=:按位清零赋值
  • 其他运算符:
():括号用于改变运算符的优先级
.:选择器用于访问结构体成员
[]:索引运算符用于访问数组、切片、字符串中的元素
::分割符,用于分割切片、字符串等

流程控制

if

在go语言中,使用if进行判断可以不加括号

当if结构内有break, continue, goto 或者return语句时,Go代码 常见的写法是省略else部分

使用简短方式== := ==声明的变量的作用域只存在if结构中,如果变量的值在if结构之前就已经存在,那么在if结构中,该变量原来的值会被隐藏

switch

Go语言使用快速的查找算法来测试switch条件与case分支的匹配情况,直到算法匹配到某个case或者进入default条件为止

在switch语句中,如果成功匹配到某一分支,在执行完相应代码后就会退出整个swtich代码块,程序不会自动的执行后续分支代码,可以使用fallthrough关键字来到达目的

for

for 初始化语句; 条件语句; 修饰语句{
    }

在Go语言中,左花括号=={==必须和for语句在同一行,计数器的生命周期在遇到右花括号==}==终止

在Go语言中,没有while关键字,for结构中没有头部的条件判断迭代(可以近似于其他语言的while循环)基本形式为:

for 条件语句 {
    }

break关键字会直接跳出循环结构

continue关键字会结束单次循环
continue关键字只能被用于for循环中

for-range循环遍历数组,切片…(返回 下标和对应的值)

for i, v := range str {
    }
package main

import "fmt"

func main() {
    

	str := "hello,jimuwangui"
	fmt.Println(str)

	//获取字符串的长度len
	fmt.Println("字符串的长度为: ", len(str))

	for i, _ := range str {
    
		fmt.Printf("%c\t", str[i])
	}
}

函数

函数声明

函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体

func name(parameter-list) (result-list) {
    
	body
}

在Go语言中,函数的返回值可以是多个

package main

import "fmt"

func main() {
    
	s, s2 := swap("hello", "world") //world hello

	fmt.Println(s, s2)
}

func swap(x, y string) (string, string) {
    
	return y, x
}

在函数体中,函数的形参作为局部变量,被初始化为调用者提供,其作用域是在当前函数体中
实参通过值的方式传递,因此函数的形参是实参的拷贝,对形参进行修改不会影响实参,但是如果实参包括引用类型,如指针,slice(切片),map,function,channel等类型,实参可能会由于函数的间接引用而被修改
实参是在函数调用时传递给函数的具体值

可变参数

一个函数的参数类型确定,但个数不确定,可以使用可变参数

func myfunc(arg ...int){
    }
//arg ...int 告诉这个函数接收不定数量的参数,类型全部是int

函数有多个参数包括可变参数时,可变参数要放在参数列表的最后
一个函数的参数列表最多只能由一个可变参数

package main

import "fmt"

func main() {
    
	getSum(12, 12, 13, 41, 6, 46, 46)
}

func getSum(nums ...int) {
    
	sum := 0
	for i := 0; i < len(nums); i++ {
    
		sum += nums[i]
	}

	fmt.Println(sum) //176
}

递归函数

defer

defer语义:推迟、延迟
在Go语言中,使用defer关键字来延迟一个函数或者方法的执行

package main

import "fmt"

func main() {
    
   f("1")
   fmt.Println("2")
   defer f("3")
   fmt.Println("4")
}

func f(s string) {
    
   fmt.Println(s)
}

defer函数或者方法: 一个函数或方法的执行被延迟了

  • 在函数中添加多个defer语句,当函数执行到最后时,defer语句会按照逆序执行,最后函数返回
  • 调用多个defer,采用后进先出(栈)模式

函数作为数据类型

在Go语言中,函数也是一种数据类型,可以赋值给变量、作为参数传递给其他函数,甚至可以作为函数的返回值。

使用函数类型可以将函数本身作为值进行操作和传递。在Go语言中,函数类型的声明方式是使用func关键字,后面跟着参数和返回值的类型。

//创建一个函数作为数据类型
type MathFunc func(int, int) int


func add(x, y int) int {
    
    return x + y
}

func multiply(x, y int) int {
    
    return x * y
}

func main() {
    
    var mathFunc MathFunc
    mathFunc = add
    result := mathFunc(3, 4) // 调用add函数并传入参数
    fmt.Println(result)      // 输出:7

    mathFunc = multiply
    result = mathFunc(3, 4)   // 调用multiply函数并传入参数
    fmt.Println(result)      // 输出:12
}

匿名函数

在Go语言中,可以创建匿名函数,也称为函数字面量或闭包。匿名函数是一种没有函数名的函数,可以在需要函数体的地方直接定义和使用,而无需提前声明。

func main() {
    
    // 示例1:将匿名函数赋值给变量,并执行
    add := func(x, y int) int {
    	//add的数据类型为函数类型
        return x + y
    }
    result := add(3, 4)	//通过调用add来使用匿名函数
    fmt.Println(result) // 输出:7

    // 示例2:直接在函数调用中定义匿名函数,并执行
    result = func(x, y int) int {
    
        return x * y
    }(3, 4)
    fmt.Println(result) // 输出:12

    // 示例3:将匿名函数作为参数传递给其他函数
    process(5, 6, func(x, y int) {
    
        fmt.Println(x - y)
    })
}

func process(x, y int, callback func(int, int)) {
    
    callback(x, y)
}
package main

import "fmt"

// 定义名为process的函数,接受两个int类型的参数x和y,以及一个回调函数callback
func process(x, y int, callback func(int, int)) {
    
    // 调用回调函数,并将x和y作为参数传入
    callback(x, y)
}

func main() {
    
    // 调用process函数,并传入参数5和6
    // 同时定义一个匿名函数作为回调函数,该匿名函数接受两个int类型的参数x和y,输出x-y的结果
    process(5, 6, func(x, y int) {
    
        fmt.Println(x - y)
    })
}

在调用匿名函数过程中,定义匿名函数时的函数名仅用于在代码中引用和调用具名函数,但对于匿名函数,我们无法直接引用和调用它们。

回调函数

在Go语言中函数可以当作另一个函数的参数
接收函数作为参数的函数叫做高阶函数
作为另一个函数的参数的函数叫做回调函数

package main

import "fmt"

func main() {
    

	r1 := add(1, 2)
	fmt.Println(r1) //3

	r2 := oper(3, 4, add)
	fmt.Println(r2) //7

}

// 创建一个高阶函数,可以接受一个函数作为参数
func oper(a, b int, fun func(int, int) int) int {
    
	r := fun(a, b)
	return r
}

func add(a, b int) int {
    
	return a + b
}

闭包

在Go语言中,闭包(Closure)是指一个函数捕获了其所在环境中的变量,并可以在函数之外被调用。闭包在函数式编程中是一种非常有用的概念,它可以在函数中创建独立的状态,并在函数的多次调用之间保持状态的持久性。

package main

import "fmt"

func main() {
    
	// 创建一个闭包函数并赋值给变量add
	add := closure()

	// 调用闭包函数
	fmt.Println(add()) // 输出:1
	fmt.Println(add()) // 输出:2
	fmt.Println(add()) // 输出:3
}

// 返回一个匿名函数作为闭包
func closure() func() int {
    
	sum := 0
//匿名函数结束后sum在匿名函数作用域外,可以接收return sum的值。sum的值改变
	// 返回一个闭包函数
	return func() int {
    
		sum++
		return sum
	}
}

sum的值存储在匿名函数内部,被闭包所捕获,所以再次调用closure时,匿名函数持有对sum变量的引用,而且闭包的生命周期是返回的匿名函数决定的,闭包的环境会一直存在,直到不在被引用


在闭包中,被捕获的变量会随着闭包一起存在于堆上,而不是栈上。这意味着,当函数调用完毕后,捕获的变量的值会继续被闭包引用,并保存在堆上,而不是随着函数的栈帧被销毁而被清除。

在Go语言中,闭包的生命周期和变量的存储位置可能会受到垃圾收集器的影响。具体而言,在闭包中捕获的变量是否能够被回收取决于以下两个因素:

是否存在对闭包的引用:只要闭包被其他地方引用,闭包中捕获的变量就不会被回收。这是因为闭包仍然可以通过引用来访问和使用它们。

是否有其他引用指向闭包中捕获的变量:即使不再有对闭包的引用,如果闭包中捕获的变量仍然可以通过其他引用被访问,那么它们也不会被回收。这是为了确保在闭包执行期间,闭包中的变量保持有效。

如果闭包不再被引用,且其中的变量也没有其他引用指向它们,那么闭包和其中的变量都可以被垃圾收集器回收。

需要注意的是,Go语言的垃圾收集器是自动的,具体的回收时机是由垃圾收集器自行决定的,而不是由程序员来显式地释放内存。

函数参数传递

init函数

每一个源文件都可以包含一个init函数,该函数会在main函数执行前,被Go运行框架调用,即在main函数前被调用

package main

import "fmt"

var age = test()

// 全局变量先被初始化
func test() int {
    
	fmt.Println("test()")
	return 100
}

// init函数,通常可以在init函数中完成初始化工作
func init() {
    
	fmt.Println("init()...")
}

func main() {
    
	fmt.Println("main()... age = ", age)
	
}

打印结果
在这里插入图片描述

strings内置包

strings包是Go语言标准库中的一个内置包,提供了一系列用于处理字符串的函数。

  1. 统计字符串的长度,按字节 len(str)
    golang的编码统一为utf-8(ascii的字符(字母和数字)占一个字节,汉字占三个字节

	str := "hello易"
	fmt.Println("str len=", len(str)) //str len= 8
  1. 字符串遍历,同时处理有中文的问题 : r := []rune(str)
    []rune() 是一个在 Go 语言中用于将字符串转换为 []rune 类型的内置函数。
    []rune 类型是一个切片(slice),用于存储 Unicode 字符(code point)。每个 Unicode 字符可以由一个或多个字节组成,所以在处理包含非 ASCII 字符的字符串时,[]rune 提供了一种方便的方式来处理和操作 Unicode 字符。
    转换为 []rune 类型的切片会占用更多的内存空间,因为每个 Unicode 字符可能需要多个字节来表示。所以只有在需要处理和操作单个 Unicode 字符时才使用 []rune 切片。
	str := "hello北京"
	r := []rune(str)
	for i := 0; i < len(str); i++ {
    
		fmt.Printf("%c|", r[i])	//h|e|l|l|o|北|京|
	}
  1. 字符串转整数: n, err := strconv.Atoi(“123”)
	n, err := strconv.Atoi("hello")
	if err != nil {
    
		fmt.Println("转换错误", err)
	} else {
    
		fmt.Println("转换的结果是", n)
	}
  1. 整数转字符串: str = strconv.Itoa()
package main

import (
	"fmt"
	"strconv"
)

func main() {
    
	num := 42
	str := strconv.Itoa(num)
	fmt.Println(str) // 输出: "42"
}
  1. 字符串转 []byte: var bytes = []byte()
    可以用于字符串的修改,string本身不可以改变,但是其底层是[]byte,可以转成[]byte再进行修改,然后再转成string
  2. []byte 转字符串: str = string([]byte{97, 98, 99})
  3. 10进制转2、8、16进制: str = strconv.FormatInt(1234, 2)
  4. 查找子串是否在指定的字符串中: strings.Contains(“seafood”, “foo”)
  5. 统计一个字符串有几个指定的子串: strings.Count(“ceheese”,“e”)
  6. 不区分大小写的字符串比较(是区分字母大小写): strings.EqualFold(“abc”, “Abc”)
  7. 返回子串在字符串第一次出现的index值,如果没有返回-1: strings.Index(“NLT_abc”, “abc”)
  8. 返回子串在字符串最后一次出现的index, 如果没有返回-1: strings.LastIndex(“go golang”, “go”)
  9. 将指定的子串替换成另外一个子串: strings.Replace(“go go hello”, “go”,"go语言“,n) n可以指定你希望替换几个,如果n = -1 表示全部替换
package main

import (
	"fmt"
	"strings"
)

func main() {
    
	str := "go go hello"
	newStr := strings.Replace(str, "go", "go语言", -1)
	fmt.Println(newStr) // 输出: "go语言 go语言 hello"
}
  1. 按照指定的某个字符,为分割标识,将字符串拆分成字符串数组: strings.Split(“hello,world,ok”, “,”)
  2. 将字符串的字母进行大小写的转换: strings.ToLower(“Go”)
  3. 将字符串左右两边的空格去掉: strings.TrimSapce("tn a lone gopher ntm)
  4. 将字符串左右两边指定的字符去掉: strings.Trim(“! hello !”, “!”)
  5. 将字符串左边指定字符去掉: strings.TrimLeft(“! hello!”, “!”)
  6. 将字符串右边指定字符去掉: strings.TrimRight("! hello! ", “!”)
  7. 判断字符串是否以指定字符串开头: strings.HasPrefix(“ftp://192.168.10.1”, “ftp”)
  8. 判断字符串是否以指定的字符串结束: strings.HasSuffix(“NLT_abc.jpg”, “abc”)

日期和时间相关的函数

time包是Go语言标准库中的一个内置包,用于处理时间和日期相关的操作。

  1. time.Now():返回当前的系统时间。
  2. time.Parse(layout, value):将指定格式的字符串转换为时间对象。
  3. time.Format(layout,t):将时间对象格式化为指定格式的字符串。
  4. time.Duration:表示一段时间间隔的类型,可以用于计算时间差。
  5. time.Sleep(d):使程序挂起指定的时间段。
  6. time.Tick(d):创建一个定时器,返回一个通道,定期向通道发送时间。

内置函数

Go语言中有一些内置函数,它们是预定义的函数,无需导入任何包就可以直接使用。下面是一些常见的内置函数:

  1. len(v):返回字符串、数组、切片、映射、通道等的长度。
  2. cap(v):返回数组、切片、通道的最大容量。
  3. make(T, size):用于创建指定类型的对象,如切片、映射和通道。
  4. new(T):用于分配值类型的内存并返回指向该内存的指针。
  5. append(slice, elems…):用于向切片末尾追加一个或多个元素。
  6. copy(dst, src):用于将源切片或数组的元素复制到目标切片或数组中。
  7. delete(m, key):用于从映射中删除指定的键值对。
  8. panic(v):引发恐慌,中断当前函数的正常执行流程。
  9. recover():用于从恐慌中恢复,并返回引发恐慌的值。
  10. print(args…):用于打印给定的参数列表到标准输出。
  11. println(args…):用于打印给定的参数列表并换行。
  12. close:关闭通道,表示不再向其中发送数据。
  13. complex(real, imag):用于创建一个复数值。
  14. real:返回复数的实部。
  15. imag:返回复数的虚部。

错误处理

Go语言追求简洁优雅,所以,Go语言不支持传统的try…catch…finally 这种处理
Go引入的处理方式为: defer, panic, recover
GO抛出一个panic异常,任何在defer中通过recover捕获这个异常,然后正常处理

/**
 * ClassName: error
 * Package:
 * Description:
 */
package main

import "fmt"

func test() {
    
	//使用defer + recover 来捕获和处理异常
	defer func() {
    
		err := recover()
		if err != nil {
    
			fmt.Println("err=", err)
		}
	}()

	num1 := 10
	num2 := 0
	res := num1 / num2
	fmt.Println("res=", res)
}

func main() {
    
	test()

	fmt.Println("异常处理后运行")
}


在Go语言中,错误处理是非常重要的,它使用一种特殊的数据类型 error 来表示错误,并提供了一种统一的机制来处理和传播错误。

错误处理的基本原则是:当某个函数发生错误时,它应该返回一个 error 类型的值,以便调用方可以根据需要进行处理。调用方可以使用条件语句进行错误检查,并根据错误的具体情况来决定如何处理这个错误。

以下是一种常见的错误处理模式:

func doSomething() error {
    
    // ...
    if err := someOperation(); err != nil {
    
        return err
    }
    // ...
    return nil
}

func main() {
    
    if err := doSomething(); err != nil {
    
        fmt.Println("发生错误:", err)
        // 可以根据具体情况进行错误处理,如记录日志、返回给用户等
    }
}

在上面的示例中,doSomething 函数执行一些操作并可能返回一个错误。在 main 函数中,调用 doSomething 并检查返回的错误。如果发生了错误,则根据具体情况进行处理,如打印错误信息、记录日志等。

除了使用 if err != nil 来检查错误外,还可以使用 log.Fatal 或 log.Panic 等函数进行错误处理。这些函数会在打印错误信息后直接终止程序的执行。

还有一种特殊的错误类型是 panic,它表示发生了严重的错误,程序无法继续执行。panic 会引发恐慌,并且通常会导致程序崩溃。可以使用 defer 和 recover 来从 panic 中恢复。

Go语言提倡显式地处理错误,这样能够更好地控制错误流,并避免错误被忽略或隐藏。使用错误值作为函数的返回值,可以将错误传播到调用方,并允许调用方做出适当的错误处理。


数组

数组的定义

在Go语言中,数组是一种固定长度的数据结构,用于存储具有相同类型的元素。以下是在Go语言中定义数组的几种方式:

基本的数组定义:

var arr [5]int  // 定义一个长度为5的整型数组

指定初始值的数组定义:

arr := [3]int{
    1, 2, 3}  // 定义一个长度为3的整型数组,并指定初始值

自动推导数组长度:

arr := [...]int{
    1, 2, 3, 4, 5}  // 自动推导数组长度,并指定初始值

多维数组定义:

var matrix [3][3]int  // 定义一个3x3的整型二维数组

使用数组下标定义

arr := [5]int{
    
		0: 10, // 索引为0的元素值为10
		2: 20, // 索引为2的元素值为20
		4: 30, // 索引为4的元素值为30
	}

数组在创建后,如果没有赋值,会初始化默认值 (数值类型数组: 默认值为0) (字符串数组: 默认值为"") (bool数组: 默认值为false)

数组的遍历

for-range结构遍历

arr := [5]int{
    1, 2, 3, 4, 5}
for index, value := range arr {
    
    fmt.Printf("Index: %d, Value: %d\n", index, value)
}

二维数组

语法: var 数组名[大小][大小]类型

// 声明一个二维整数数组
var array2D [3][3]int

// 定义一个具有初始值的二维浮点数数组
array2D := [2][2]float64{
    
  {
    1.1, 2.2},
  {
    3.3, 4.4},
}

// 声明一个切片的切片(动态二维数组)
var slice2D [][]int

// 定义一个具有初始值的切片的切片
slice2D := [][]int{
    
  {
    1, 2, 3},
  {
    4, 5, 6},
}

二维数组的遍历
使用嵌套循环遍历二维数组:

array2D := [][]int{
    
  {
    1, 2, 3},
  {
    4, 5, 6},
}

for i := 0; i < len(array2D); i++ {
    
  for j := 0; j < len(array2D[i]); j++ {
    
    fmt.Printf("%d ", array2D[i][j])
  }
  fmt.Printf("\n")
}

使用 range 关键字遍历二维数组:

array2D := [][]int{
    
  {
    1, 2, 3},
  {
    4, 5, 6},
}

for _, row := range array2D {
    
  for _, element := range row {
    
    fmt.Printf("%d ", element)
  }
  fmt.Printf("\n")
}

切片

切片的入门使用

Go 语言中的切片(slice)是对数组的一个动态窗口或视图,它提供了对数组部分或全部元素的引用。切片使用起来更加灵活,可根据需要动态调整大小。

创建切片

// 使用切片字面量创建切片
s1 := []int{
    1, 2, 3, 4, 5}

// 使用 make() 函数创建指定长度和容量的切片
s2 := make([]int, 3, 5)  // 长度为3,容量为5的切片

切片与数组的关系
切片是对数组的引用,它不存储具体的数据,而是引用底层的数组。对切片的修改将反映在原始数组中。

arr := [5]int{
    1, 2, 3, 4, 5}
s := arr[1:3]  // 创建一个切片,引用 arr 的索引为 1 和 2 的元素
fmt.Println(s)  // 输出 [2 3]

s[0] = 10  // 修改切片的第一个元素
fmt.Println(arr)  // 输出 [1 10 3 4 5],原始数组也被修改

切片的长度和容量:
切片具有长度(length)和容量(capacity)属性。长度表示当前切片包含的元素数量,容量表示从切片的起始位置到底层数组末尾的元素数量。使用 len() 和 cap() 函数获取切片的长度和容量

s := []int{
    1, 2, 3, 4, 5}
fmt.Println(len(s))  // 输出切片的长度:5
fmt.Println(cap(s))  // 输出切片的容量:5

切片的操作:
切片支持一些常用的操作,例如追加元素、截取切片、复制切片等。你可以使用内置的函数和切片的语法来进行操作

s := []int{
    1, 2, 3, 4, 5}
s = append(s, 6)  // 追加元素到切片末尾
fmt.Println(s)  // 输出 [1 2 3 4 5 6]

s = s[1:4]  // 截取索引为1 到 3 的切片
fmt.Println(s)  // 输出 [2 3 4]

s2 := make([]int, len(s), cap(s))  // 创建一个与 s 长度和容量相同的切片
copy(s2, s)  // 复制切片 s 的内容到 s2
fmt.Println(s2)  // 输出 [2 3 4]

切片的定义

切片(slice)是一个引用类型,由三部分组成:指向底层数组的指针、切片的长度和切片的容量
当创建一个切片时,Go语言会自动帮你创建底层数组,并将切片的指针指向这个数组的起始位置

+------------+
|    切片    |
+------------+
|   指针     | --------> +------------+
+------------+           |   数组     |
|   长度     |           +------------+
+------------+           |   容量     |
                         +------------+

切片和底层数组共享一段内存,并且修改切片后会影响底层数组

当切片的长度在执行追加操作超出容量,Go语言会自动为切片扩容,创建一个新的底层数组,将数据复制到新数组中

切片的创建

  1. 定义一个切片,然后让切片去引用一个已经创建好的数组
var arr [5]int = [...]int {
    1,2,3,4,5}
var slice = arr[1:3}
  1. 通过内置函数make来创建切片
    基本语法: var 切片名 []type = make([], len, [cap])
    省略容量后,容量等于长度
var slice []int = make([]int,4, 10)

在底层make创建了一个匿名数组变量,返回一个slice,只有通过slice才能引用底层匿名数组

  1. 定义一个切片,直接指定具体数组
var slice []int = []int {
    1,2,3}

append函数

使用append内置函数,可以对切片进行动态追加

var arr [5] int = [5]int {
    1,2,3,4,5}
var slice = arr[:]

slice = append(slice, 10, 20, 30)

var a = []int{
    100, 200}
slice = append(slice, a...)
//在切片上追加切片

底层原理:对底层数组进行扩容,当数组容量不满足添加后,创建一个新数组,再将原来的元素拷贝到新数组,切片重新引用

copy函数

参数只能是切片

具体步骤:

首先,copy 函数会检查两个切片的长度,取两者中较小的一个作为复制的长度。

然后,copy 函数会将源切片的元素逐个复制到目标切片中。

如果源切片的长度超过目标切片的长度,多余的元素将被丢弃。

如果目标切片的长度超过源切片的长度,则目标切片剩余的元素保持不变。

切片和数组的区别

数组的定义:

var arr [5]int // 创建一个长度为5的整数数组

数组是一个固定大小的数据结构,用于存储相同类型的元素。
数组的长度是在创建时指定的,并且是固定不变的。
数组的索引从0开始,可以通过索引访问和修改数组中的元素。

切片的定义:

var slice []int // 创建一个整数切片

切片是一个动态大小的引用类型,它是基于底层数组的可变长度的片段。
切片没有指定长度,在使用前需要通过内置的make函数进行初始化。
切片的容量是指底层数组中可访问的元素的个数,它通常大于等于切片的长度。
切片可以通过截取现有切片或使用内置的append函数来动态改变其长度。
切片的位置信息包含指向底层数组的指针、长度和容量。

string使用切片

string底层是一个byte数组,因此string可以进行切片处理,但是string是不可改变的,不能通过切片修改string
如果需要修改字符串,可以先将string ->[]byte 或者 []rune -> 修改 ->重写转成string


map

map的定义

在Go语言中,map是一种无序的键值对集合。它使用哈希表来实现,可以高效地存储和检索数据。

要定义一个map,可以使用以下语法:

var myMap map[keyType]valueType

slice, map, function 不可以用来做key, 无法用==来判断
其中,keyType表示键的类型,valueType表示值的类型。可以根据实际需求选择适当的类型。

以下是一个示例:

var studentScores map[string]int

在上面的示例中,studentScores是一个map,键的类型为string,值的类型为int。

当然,还可以使用make函数来创建一个空的map,如下所示:

studentScores := make(map[string]int)

上面的代码创建了一个空的map,可以在后续的代码中动态地添加键值对。

在使用map之前,需要进行初始化操作。可以使用make函数或者直接赋值空的map进行初始化

例如,通过make函数初始化map:

studentScores := make(map[string]int)

或者直接赋值空的map:

var studentScores map[string]int
studentScores = map[string]int{
    }

然后,可以使用[]操作符对map进行读取或修改操作,例如:

studentScores["Tom"] = 85  // 添加键值对
fmt.Println(studentScores["Tom"])  // 输出:85

studentScores["Alice"] = 92   // 添加键值对
fmt.Println(studentScores["Alice"])  // 输出:92

studentScores["Tom"] = 90  // 修改键值对
fmt.Println(studentScores["Tom"])  // 输出:90

需要注意的是,尝试读取一个不存在的键会返回map值类型的零值。可以使用多返回值的方式判断一个键是否存在。

score, ok := studentScores["Bob"]
if ok {
    
    fmt.Println("Bob's score is", score)
} else {
    
    fmt.Println("Bob's score does not exist")
}

map的使用

在Go语言中,map是一种非常常用的数据结构,用于存储和检索键值对数据。下面是一些常见的map的使用场景和操作方法:

创建和初始化map:


studentScores := map[string]int{
    
    "Tom":   85,
    "Alice": 92,
    "Bob":   78,
}

以上代码创建了一个名为studentScores的map,并初始化了三个键值对。

也可以使用make函数创建一个空的map:

studentScores := make(map[string]int)

添加和修改键值对:

studentScores["Tom"] = 85  // 添加键值对
studentScores["Alice"] = 92  // 添加键值对
studentScores["Bob"] = 78  // 添加键值对

studentScores["Tom"] = 90  // 修改键值对

可以使用下标运算符[]来添加或修改map中的键值对。

获取键对应的值:

score := studentScores["Alice"]
fmt.Println("Alice's score:", score)

通过使用下标运算符[]并提供相应的键,可以获取map中键对应的值。

删除键值对:

delete(studentScores, “Bob”)
使用delete函数可以删除map中指定的键值对。

判断键是否存在:

score, ok := studentScores["Bob"]
if ok {
    
    fmt.Println("Bob's score is", score)
} else {
    
    fmt.Println("Bob's score does not exist")
}

通过使用多返回值的方式,可以判断一个键是否存在于map中。ok表示键是否存在,score表示对应的值。

遍历map:

for key, value := range studentScores {
    
    fmt.Println(key, "->", value)
}

可以使用for…range循环遍历map,按无序的方式获取map中的键值对。

需要注意的是,map是一个引用类型,可以通过赋值将一个map变量传递给另一个变量,它们指向同一个底层数据结构。同时,多个goroutine可以同时读取、修改map的不同部分而不会导致数据竞争。但是,在同时读取和修改同一个键值对时是不安全的。

此外,如果对map进行遍历的过程中修改了map的结构(例如添加或删除键值对),可能会导致遍历结果不准确或产生死循环。为了避免这种情况,应该在遍历中使用其他数据结构(如切片)或使用特殊的遍历技巧。

map的特点

在Go语言中,map具有以下特点:

  • 无序性:map中的键值对是无序存储的,遍历顺序不确定。

  • 动态性:map的大小是动态变化的,可以根据需求随时添加或删除键值对。

  • 引用类型:map是引用类型,可以通过赋值将一个map变量传递给另一个变量,它们指向同一个底层数据结构。

  • 快速查找:map使用哈希表实现,可以在常数时间复杂度O(1)内查找指定键的值,具有快速的查找能力。

  • 键的唯一性:map中的键是唯一的,同一个键只能对应一个值。如果对同一个键多次赋值,后面的值会覆盖前面的值。

  • 安全性:map是并发安全的,多个goroutine可以同时读取、修改map的不同部分而不会导致数据竞争。但是,同时读取和修改同一个键值对是不安全的。

  • 可以使用任意类型作为键和值:在Go语言中,map的键和值可以是任意类型,只要它们支持相等操作和哈希计算即可。通常使用基本数据类型、字符串和自定义结构体作为键,使用任意类型作为值。

  • 需要注意的是,由于map是无序存储的,每次遍历的顺序可能会不同。如果需要按照特定顺序遍历map,可以先将键进行排序,然后按照排序后的顺序遍历map。

  • 另外,值类型为切片、映射或接口的map不是线程安全的。如果需要在并发环境下安全地使用map,可以使用sync包中的sync.Map类型。它是一种并发安全的map实现。

map的遍历

在Go语言中,可以使用for…range语句来遍历map。通过for…range可以按照无序的方式遍历map中的所有键值对。

下面是遍历map的示例代码:

studentScores := map[string]int{
    
    "Tom":   85,
    "Alice": 92,
    "Bob":   78,
}

for key, value := range studentScores {
    
    fmt.Println(key, "->", value)
}

在上述示例中,studentScores是一个包含学生姓名和分数的map。使用for…range遍历studentScores,每次循环都会将map的键赋值给key,将对应的值赋值给value,然后输出键值对。

输出结果可能是无序的,因为map是无序的数据结构。例如,可能会输出:

Alice -> 92
Bob -> 78
Tom -> 85
如果只想遍历map的键或只想遍历map的值,可以将不需要的部分用_来忽略。例如,只遍历键的示例代码如下:


for key := range studentScores {
    
    fmt.Println(key)
}
只遍历值的示例代码如下:

for _, value := range studentScores {
    
    fmt.Println(value)
}

需要注意的是,map的遍历顺序是不确定的,每次遍历的结果可能不同。如果需要按照特定顺序遍历map,可以先将键进行排序,然后按照排序后的顺序遍历map。

map的切片

在Go语言中,可以创建和使用切片的元素为map类型的切片。也就是说,可以将map作为切片的元素进行存储和操作。

func main() {
    
    // 创建map切片
    mapSlice := make([]map[string]int, 3)

    // 初始化map
    mapSlice[0] = make(map[string]int)
    mapSlice[0]["Tom"] = 85
    mapSlice[0]["Alice"] = 92

    mapSlice[1] = make(map[string]int)
    mapSlice[1]["Bob"] = 78

    mapSlice[2] = make(map[string]int)
    mapSlice[2]["John"] = 90

    // 遍历map切片
    for _, m := range mapSlice {
    
        for key, value := range m {
    
            fmt.Println(key, "->", value)
        }
        fmt.Println("---")
    }
}

切片本身也是引用类型,可以在不重新分配内存的情况下进行扩容。这意味着可以动态地向map切片中添加或删除元素。使用内置函数append可以向map切片添加元素。

newMap := make(map[string]int)
newMap["Linda"] = 95
mapSlice = append(mapSlice, newMap)

map排序

使用切片对map进行排序:

  • 将map的键复制到一个切片中;
  • 对切片进行排序;
  • 遍历排序后的切片,按照排序后的键顺序访问原始map的值。
import (
    "fmt"
    "sort"
)

func main() {
    
    studentScores := map[string]int{
    
        "Tom":   85,
        "Alice": 92,
        "Bob":   78,
    }

    // 将map的键复制到切片
    keys := make([]string, 0, len(studentScores))
    for key := range studentScores {
    
        keys = append(keys, key)
    }

    // 对切片进行排序
    sort.Strings(keys)

    // 遍历排序后的切片,并按排序后的键顺序访问map的值
    for _, key := range keys {
    
        score := studentScores[key]
        fmt.Println(key, "->", score)
    }
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_62331305/article/details/132111391

智能推荐

攻防世界_难度8_happy_puzzle_攻防世界困难模式攻略图文-程序员宅基地

文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文

达梦数据库的导出(备份)、导入_达梦数据库导入导出-程序员宅基地

文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作  导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释:   cwy_init/init_123..._达梦数据库导入导出

js引入kindeditor富文本编辑器的使用_kindeditor.js-程序员宅基地

文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js

STM32学习过程记录11——基于STM32G431CBU6硬件SPI+DMA的高效WS2812B控制方法-程序员宅基地

文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6

计算机网络-数据链路层_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输

软件测试工程师移民加拿大_无证移民,未受过软件工程师的教育(第1部分)-程序员宅基地

文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...

随便推点

Thinkpad X250 secure boot failed 启动失败问题解决_安装完系统提示secureboot failure-程序员宅基地

文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure

C++如何做字符串分割(5种方法)_c++ 字符串分割-程序员宅基地

文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割

2013第四届蓝桥杯 C/C++本科A组 真题答案解析_2013年第四届c a组蓝桥杯省赛真题解答-程序员宅基地

文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答

基于供需算法优化的核极限学习机(KELM)分类算法-程序员宅基地

文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。

metasploitable2渗透测试_metasploitable2怎么进入-程序员宅基地

文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入

Python学习之路:从入门到精通的指南_python人工智能开发从入门到精通pdf-程序员宅基地

文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf

推荐文章

热门文章

相关标签