声明变量通过使用var关键字
var name type
变量命名采用驼峰命名
go语言中变量没有使用会报错
可以使用var同时定义多个变量
var {
addr string
phone string
}
当一个变量被声明之后,如果没有显示的给它赋值,系统会自动赋予该类型的零值
var 变量名 类型 = 值(表达式)
短变量声明同时初始化
变量名 := 值
这是Go语言的推到声明写法,编译器会自动根据右值推断出左值的对应类型
声明变量后,内存会给一块地址给变量使用,可以使用printf("%p", 变量)查看地址
匿名变量的特点是一个下划线" - ",被称为空白标识符,可以用于变量的声明和赋值,但任何值赋予都会被抛弃,不会在后续代码使用
匿名变量不占用内存空间,不会分配内存。匿名变量和匿名变量之间也不会因为多次声明而无法使用
const 常量是一个简单值的标识符,在程序运行时,不会修改的量
常量中的数据类型只可以是布尔类型、整数型、浮点型、复数、字符串
const identifier [type] = value
显示类型定义:==const b string = “abc” ==
隐式类型定义:==const b = “abc” ==
可以使用 const a1, b1 = value1, value2 声明多类型
特殊常量,可以被编译器修改的常量,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'
}
+:相加
-:相减
*:相乘
/:相除
%:取余数
==:等于
!=:不等于
>:大于
<:小于
>=:大于等于
<=:小于等于
&&:逻辑与
||:逻辑或
!:逻辑非
&:按位与
|:按位或
^:按位异或
<<:左移
>>:右移
&^:按位清零(位清除)
=:赋值
+=:加法赋值
-=:减法赋值
*=:乘法赋值
/=:除法赋值
%=:取余赋值
<<=:左移赋值
>>=:右移赋值
&=:按位与赋值
|=:按位或赋值
^=:按位异或赋值
&^=:按位清零赋值
():括号用于改变运算符的优先级
.:选择器用于访问结构体成员
[]:索引运算符用于访问数组、切片、字符串中的元素
::分割符,用于分割切片、字符串等
在go语言中,使用if进行判断可以不加括号
当if结构内有break, continue, goto 或者return语句时,Go代码 常见的写法是省略else部分
使用简短方式== := ==声明的变量的作用域只存在if结构中,如果变量的值在if结构之前就已经存在,那么在if结构中,该变量原来的值会被隐藏
Go语言使用快速的查找算法来测试switch条件与case分支的匹配情况,直到算法匹配到某个case或者进入default条件为止
在switch语句中,如果成功匹配到某一分支,在执行完相应代码后就会退出整个swtich代码块,程序不会自动的执行后续分支代码,可以使用fallthrough关键字来到达目的
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语义:推迟、延迟
在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函数或者方法: 一个函数或方法的执行被延迟了
在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函数,该函数会在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包是Go语言标准库中的一个内置包,提供了一系列用于处理字符串的函数。
golang的编码统一为utf-8(ascii的字符(字母和数字)占一个字节,汉字占三个字节
str := "hello易"
fmt.Println("str len=", len(str)) //str len= 8
转换为 []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|北|京|
}
n, err := strconv.Atoi("hello")
if err != nil {
fmt.Println("转换错误", err)
} else {
fmt.Println("转换的结果是", n)
}
package main
import (
"fmt"
"strconv"
)
func main() {
num := 42
str := strconv.Itoa(num)
fmt.Println(str) // 输出: "42"
}
可以用于字符串的修改,string本身不可以改变,但是其底层是[]byte,可以转成[]byte再进行修改,然后再转成string
package main
import (
"fmt"
"strings"
)
func main() {
str := "go go hello"
newStr := strings.Replace(str, "go", "go语言", -1)
fmt.Println(newStr) // 输出: "go语言 go语言 hello"
}
time包是Go语言标准库中的一个内置包,用于处理时间和日期相关的操作。
Go语言中有一些内置函数,它们是预定义的函数,无需导入任何包就可以直接使用。下面是一些常见的内置函数:
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语言会自动为切片扩容,创建一个新的底层数组,将数据复制到新数组中
var arr [5]int = [...]int {
1,2,3,4,5}
var slice = arr[1:3}
省略容量后,容量等于长度
var slice []int = make([]int,4, 10)
在底层make创建了一个匿名数组变量,返回一个slice,只有通过slice才能引用底层匿名数组
var slice []int = []int {
1,2,3}
使用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 函数会将源切片的元素逐个复制到目标切片中。
如果源切片的长度超过目标切片的长度,多余的元素将被丢弃。
如果目标切片的长度超过源切片的长度,则目标切片剩余的元素保持不变。
数组的定义:
var arr [5]int // 创建一个长度为5的整数数组
数组是一个固定大小的数据结构,用于存储相同类型的元素。
数组的长度是在创建时指定的,并且是固定不变的。
数组的索引从0开始,可以通过索引访问和修改数组中的元素。
切片的定义:
var slice []int // 创建一个整数切片
切片是一个动态大小的引用类型,它是基于底层数组的可变长度的片段。
切片没有指定长度,在使用前需要通过内置的make函数进行初始化。
切片的容量是指底层数组中可访问的元素的个数,它通常大于等于切片的长度。
切片可以通过截取现有切片或使用内置的append函数来动态改变其长度。
切片的位置信息包含指向底层数组的指针、长度和容量。
string底层是一个byte数组,因此string可以进行切片处理,但是string是不可改变的,不能通过切片修改string
如果需要修改字符串,可以先将string ->[]byte 或者 []rune -> 修改 ->重写转成string
在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")
}
在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的结构(例如添加或删除键值对),可能会导致遍历结果不准确或产生死循环。为了避免这种情况,应该在遍历中使用其他数据结构(如切片)或使用特殊的遍历技巧。
在Go语言中,map具有以下特点:
无序性:map中的键值对是无序存储的,遍历顺序不确定。
动态性:map的大小是动态变化的,可以根据需求随时添加或删除键值对。
引用类型:map是引用类型,可以通过赋值将一个map变量传递给另一个变量,它们指向同一个底层数据结构。
快速查找:map使用哈希表实现,可以在常数时间复杂度O(1)内查找指定键的值,具有快速的查找能力。
键的唯一性:map中的键是唯一的,同一个键只能对应一个值。如果对同一个键多次赋值,后面的值会覆盖前面的值。
安全性:map是并发安全的,多个goroutine可以同时读取、修改map的不同部分而不会导致数据竞争。但是,同时读取和修改同一个键值对是不安全的。
可以使用任意类型作为键和值:在Go语言中,map的键和值可以是任意类型,只要它们支持相等操作和哈希计算即可。通常使用基本数据类型、字符串和自定义结构体作为键,使用任意类型作为值。
需要注意的是,由于map是无序存储的,每次遍历的顺序可能会不同。如果需要按照特定顺序遍历map,可以先将键进行排序,然后按照排序后的顺序遍历map。
另外,值类型为切片、映射或接口的map不是线程安全的。如果需要在并发环境下安全地使用map,可以使用sync包中的sync.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。
在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进行排序:
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)
}
}
文章浏览阅读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..._达梦数据库导入导出
文章浏览阅读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
文章浏览阅读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
文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输
文章浏览阅读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...
文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure
文章浏览阅读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++ 字符串分割
文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答
文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。
文章浏览阅读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怎么进入
文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf