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

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签