Golang
笔记:快速学习Golang
基础语法规则
一、基础语法
1.1、环境安装
第一步需要安装go的运行环境,从官网下载安装包:https://golang.google.cn/dl/。
第二步需要安装go的开发工具,可以使用vscode
、goland
。这里推荐使用goland
。
1.2、hello world
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
- 入口程序的包一般是
main
。 - 入口函数名称是
main
。 func
关键字用于定义函数。import
关键字用于导入包。
1.3、package和import
package
关键字,用于声明包名称。声明包的语法:
package 包名称
import
关键字,用于导入包,导入包之后,就可以使用包里面的函数。导入的包语法:
import "包名称"
import 包别名 "包名称"
import
可以批量导入多个包,注意了,一行写一个包名称(没有逗号分隔符),语法:
import (
"包1"
"包2"
"包3"
)
只导入包,但是不使用包,只是为了调用包里面的init()
方法,语法:
import (
"fmt"
_ "math"
)
上面在导入包的时候,包名称前面添加一个【_】下划线,这就表示只导入包,不使用包。
1.4、注释
go
语言中的注释有下面几种:
- 单行注释:
//注释内容
。 - 多行注释:·
/*多行注释*/
1.5、变量
1.5.1、变量声明
golang
中定义变量,需要使用var
关键字。语法规则:
// 声明变量
var 变量名称 数据类型
// 声明并且赋值
var 变量名称 数据类型 = 变量值
变量也可以同时定义多个,语法:
var (
变量名称1 数据类型
变量名称2 数据类型
变量名称3 数据类型
)
// 案例代码
var (
name string
age int
)
另外,go语言也支持变量类型的自动推导,也就是说,go语言可以根据右侧变量值的数据类型,自动推导出当前这个变量的数据类型。
变量名称 := 变量值
上面这个变量声明叫做:短变量声明并初始化。使用这种短变量的声明方式,那么就不能显示的声明变量类型,不需要使用var关键字,否则会编译失败。
1.5.2、变量赋值
go语言中,变量赋值语法如下:
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
// 声明变量并且赋值
var a string = "你好"
fmt.Println(a)
var b int
b = 20
fmt.Println(b)
// 多个赋值
var c string
var d int
c, d = "abc", 100
fmt.Println(c, "---", d)
// 短变量,注意:短变量不能重复赋值
dd := "200"
fmt.Println(dd)
// 短变量多个赋值
ee, ff := 100, true
fmt.Println(ee, ff)
// 变量交换,go中可以直接交换两个变量的值
num1 := 100
num2 := 200
fmt.Println(num1, num2)
num1, num2 = num2, num1
fmt.Println(num1, num2)
// 匿名变量 使用 _ 下划线表示匿名变量,匿名变量不会被接收,会直接被丢弃,go编译器不会分配内存空间
n1, n2, _ := 1, 2, 3
fmt.Println(n1, n2)
}
1.6、常量
go中使用const
定义常量,常量一旦定义赋值之后,就不可以对它的值进行修改,否则编译报错。
go中有一个内置常量,叫做:iota,表示无类型的整数序列。使用这个内置常量,可以生成一些序列号。
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
const (
num1 = iota
num2
num3
_
num5
)
fmt.Println(num1, num2, num3, num5)
const (
n1 = iota << 1
n2
n3
n4
)
fmt.Println(n1, n2, n3, n4)
}
1.7、运算符
go语言中,没有自增自减运算符,只有语句,语法:
变量名称++
变量名称--
需要注意的是,++
和--
只能够放在变量的后面,并且变量不能赋值给其他的变量。
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
a := 1
b := 2
fmt.Println(a<<1, b>>2)
fmt.Println("按位与运算:", a&b)
// a &^ b 等价于 a & (^b) 先对b进行取反操作,在和 a 进行按位与运算
fmt.Println("按位清除运算:", a&^b)
a++
b--
fmt.Println("a++的结果:", a)
fmt.Println("b--的结果", b)
}
1.8、输入输出
1.8.1、Stdout标准输出
输出有三种方式:
package main
import (
"os"
"syscall"
)
func main() {
var (
Stdout = os.NewFile(uintptr(syscall.Stdout), "/dev/stdout")
)
// 控制台输出内容
_, err := Stdout.WriteString("Hello, World!\n")
if err != nil {
return
}
}
1.8.2、println()内置函数
go语言中内置了一个println()函数,可以直接调用这个函数输出内容到控制台。
package main
func main() {
println("内置函数输出内容到控制台")
}
1.8.3、fmt.Println()函数
fmt包中也提供了输出函数,例如:Println()、Printf()函数。
package main
import "fmt"
func main() {
fmt.Println("Hello World")
fmt.Printf("%s==>%d", "111", 100)
}
1.8.4、Scan()输入
Scan()
接收输入数据,根据【空格、换行
】来接收输入数据。Scan()
方法会返回一个int整数,表示本次输入接收了几个数据。
package main
import "fmt"
func main() {
fmt.Println("请输入数据:")
var num int
// 返回成功接收的数据个数
n, err := fmt.Scan(&num)
if err != nil {
fmt.Println("输入错误")
return
}
fmt.Println("接收的输入数据个数:", n)
fmt.Printf("接收的数据num=%d", num)
}
Scan()
方法会直到接收数据个数和指定的&数量一样,才会结束执行,个数没有达到,则空格、回车都将表示一次数据的输入。
1.8.5、Scanln()输入
Scanln()
接收输入数据,通过【&
】符号获取到控制台输入的数据,根据【换行符
】判断是否结束输入数据。
package main
import "fmt"
func main() {
fmt.Println("请输入数据:")
var (
num1 int
num2 int
)
// 返回成功接收的数据个数
// 遇到换行,则停止接收输入数据
n, err := fmt.Scanln(&num1, &num2)
if err != nil {
fmt.Println("输入错误")
return
}
fmt.Println("接收的输入数据个数:", n)
fmt.Printf("接收的数据num1=%d,num2=%d", num1, num2)
}
注意:输入的数据个数,需要和接收的数据个数相同,不相同则接收失败。
Scanln()
方法只要是回车,就表示结束接收数据。
1.8.6、Scanf()输入
Scanf()
根据指定的格式来接收数据,输入数据的格式必须和指定的格式一致,这样才可以正常接收到数据。
package main
import "fmt"
func main() {
fmt.Println("请输入数据:")
var (
num1 int
num2 int
)
// 返回成功接收的数据个数
// \n 表示回车换行
n, err := fmt.Scanf("%d \n %d", &num1, &num2)
if err != nil {
fmt.Println("输入错误")
return
}
fmt.Println("接收的输入数据个数:", n)
fmt.Printf("接收的数据num1=%d,num2=%d", num1, num2)
}
1.8.7、缓冲
go语言中,如果对输入输出数据的性能有要求,则可以使用【bufio】缓冲流。
1.8.7.1、接收数据
接收数据,可以使用Reader和Scanner,其中Scanner提供了一个text()方法,可以逐行读取输入的数据。
Scanner读取的方式,案例代码:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
fmt.Println("请输入数据:")
scan := bufio.NewScanner(os.Stdin)
scan.Scan()
// 逐行读取输入数据
text := scan.Text()
fmt.Println("text=", text)
}
Reader方式读取,案例代码:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
fmt.Println("请输入数据:")
scan := bufio.NewReader(os.Stdin)
// 读取到换行,则结束读取数据
line, err := scan.ReadString('\n')
if err != nil {
fmt.Println("<UNK>")
return
}
fmt.Println("line=", line)
}
1.8.7.2、输出数据
Writer是用于写入数据的,也就是输出数据到指定位置,例如:控制台、文件等。
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
writer := bufio.NewWriter(os.Stdout)
_, err := writer.WriteString("输出数据到控制台")
if err != nil {
fmt.Println(err)
return
}
// 将缓冲区中的数据,刷新输出
err = writer.Flush()
if err != nil {
fmt.Println(err)
return
}
fmt.Println("\n数据输出完成!")
}
1.9、条件控制
1.9.1、if语句
go中也有if条件语句,语法格式:
package main
import "fmt"
func main() {
fmt.Println("请输入数据:")
var (
num1 int
num2 int
)
_, err := fmt.Scanf("%d %d", &num1, &num2)
if err != nil {
fmt.Println("输入错误。。。")
return
}
fmt.Println("if条件表达式")
if num1 > num2 {
fmt.Println("num1 > num2")
} else if num1 < num2 {
fmt.Println("num1 < num2")
} else {
fmt.Println("num1 = num2")
}
}
1.9.2、switch
语句
switch
语句,语法格式和java
中的类似,但是有一点区别。
package main
import "fmt"
func main() {
fmt.Println("请输入数据:")
var str string
_, err := fmt.Scanf("%s", &str)
if err != nil {
fmt.Println("输入错误。。。")
return
}
fmt.Println("switch条件表达式")
switch str {
case "1":
fmt.Println("匹配case1")
case "2":
fmt.Println("匹配case2")
case "3":
fmt.Println("匹配case3")
default:
fmt.Println("case都没有匹配成功")
}
fmt.Println("switch执行结束")
}
fallthrough
关键字
如果想继续执行相邻case
的语句,那么可以使用fallthrough
关键字,这样go
就会执行fallthrough
关键字后面的那个case
语句。
package main
import "fmt"
func main() {
fmt.Println("请输入数据:")
var str string
_, err := fmt.Scanf("%s", &str)
if err != nil {
fmt.Println("输入错误。。。")
return
}
fmt.Println("switch条件表达式")
switch str {
case "1":
fmt.Println("匹配case1")
case "2":
fmt.Println("匹配case2")
// 执行完这个 case2,还会继续执行下一个相邻的 case3 语句块
fallthrough
case "3":
fmt.Println("匹配case3")
default:
fmt.Println("case都没有匹配成功")
}
fmt.Println("switch执行结束")
}
switch
还有另外一种语法,如下所示:
package main
import "fmt"
func main() {
fmt.Println("请输入数据:")
var str string
_, err := fmt.Scanf("%s", &str)
if err != nil {
fmt.Println("输入错误。。。")
return
}
fmt.Println("switch条件表达式")
// 这里switch后面不写条件,相当于是 switch true {}
switch {
case str == "1":
fmt.Println("匹配case1")
case str == "2":
fmt.Println("匹配case2")
// 执行完这个 case2,还会继续执行下一个相邻的 case3 语句块
fallthrough
case str == "3":
fmt.Println("匹配case3")
default:
fmt.Println("case都没有匹配成功")
}
fmt.Println("switch执行结束")
}
1.9.3、goto
关键字(不常用)
go
语言中提供了一个goto
关键字,可以将代码执行跳转到同一个函数中的对应label标签位置。goto
一般和label
标签一起使用才有意义。
label
标签可以给某一块代码做个标记,相当于是做了一个位置标记,使用goto
可以快速跳转到这个位置,执行标签之后的代码。
package main
import "fmt"
func main() {
fmt.Println("请输入数据:")
var str string
_, err := fmt.Scanf("%s", &str)
if err != nil {
fmt.Println("输入错误。。。")
return
}
fmt.Println("switch条件表达式")
// 这里switch后面不写条件,相当于是 switch true {}
switch {
case str == "1":
fmt.Println("匹配case1")
case str == "2":
fmt.Println("匹配case2")
// 跳转到label01位置执行代码
goto label01
case str == "3":
fmt.Println("匹配case3")
default:
fmt.Println("case都没有匹配成功")
}
// 当匹配到case2的时候,这一句代码就不执行了
fmt.Println("switch执行结束")
// label标签位置的代码,始终都会执行
label01:
fmt.Println("跳转到label01位置,执行代码...")
}
1.10、循环控制
1.10.1、for语句
go中没有while语句,但是可以使用for语句来实现while语句的功能。for语法规则:
for 初始条件;循环条件;循环后条件 {
// 代码
}
// 这样就相当于while循环
for 循环条件 {
// 代码
}
for {
// 死循环
}
案例代码:
package main
import "fmt"
func main() {
// 打印九九乘法表
for i := 1; i < 10; i++ {
for j := 1; j <= i; j++ {
fmt.Printf("%dx%d=%d\t", j, i, i*j)
}
fmt.Println()
}
}
1.10.2、for…range语句
for range
可以更加方便的遍历一些可迭代的数据结构,例如:数组,切片,字符串,映射表,通道。语句格式如下:
// for...range 可以拿到 索引index 和 对应索引的value值
for index, value := range iterable {
// 循环体
}
需要注意的是,不同的数据结构,for...range
遍历出来的东西是不一样的,具体情况具体分析。案例代码:
package main
import "fmt"
func main() {
str := "Hello World"
for index, value := range str {
fmt.Printf("index=%d,value=%c\n", index, value)
}
}
1.10.3、break和continue
go中也有break个continue两个关键字,break表示结束当前循环,continue表示结束本次循环,继续执行下一次循环。可以结合label标签一起使用,这样可以跳转到指定的循环之外。
1.11、数组和切片
go中提供了一个切片数据类型,和数组有点类似,但是两者有着很大的区别。切片是不定长的,当容量不足时候,切片可以自动扩容;而数组是定长的,长度一旦确定,就不能改变长度了。
1.11.1、数组
数组是定长的,长度不可改变。需要注意的是,go中数组的长度必须是一个const常量。
1.11.1.1、声明数组
go中数组的定义方式,语法如下:
// 第一种方式
var 数组名称 [长度]数据类型
// 第二种方式
数组名称 := [长度]数据类型{初始化数组的值}
// 第三种方式
数组名称 := new([长度]数据类型)
上面三种方式就是定义go的数组,第三种通过new
关键字定义的方式,返回的是一个数组指针。
package main
import "fmt"
func main() {
var nums [5]int
nums[1] = 1
fmt.Println(nums)
nums2 := [3]int{1, 2, 3}
fmt.Println(nums2)
nums3 := new([2]int)
nums3[0] = 1
fmt.Println(nums3)
}
1.11.1.2、使用数组
数组的使用,可以直接通过下标来访问,格式:
数组名称[下标]
// 也可以使用 len 函数,访问数组中个数
len(arr)
// 可以使用 cap 函数,访问数组容量,数组容量是等于数组长度的
cap(arr)
1.11.2.3、切割
go中数组还可以进行切割的操作,这和python中的切片有点类似。语法格式:
nums := [5]{1,2,3,4,5}
// 切割的语法
nums[起始下标:结束下标]
// 起始下标如果省略,则表示从0开始计算
// 结束下标如果省略,则表示默认结束下标就是数组长度
// 切割的区间范围是:左闭右开,含前不含后
案例代码:
package main
import "fmt"
func main() {
var nums [5]int
for i := 0; i < 5; i++ {
nums[i] = i + 1
}
// 切割
ans := nums[0:2]
fmt.Println(ans)
ans = nums[:3]
fmt.Println(ans)
ans = nums[2:4]
fmt.Println(ans)
ans = nums[2:]
fmt.Println(ans)
}
1.11.2、切片
1.11.2.1、切片的定义
go中切片的定义格式,和数组格式差不多,只不过切片不需要指定长度。格式如下:
var nums []int
nums := []int{1,2,3}
// 推荐使用这种方式定义切片
nums := make([]int, 0, 0)
nums := new([]int)
make方法有三个参数,分别是:切片类型,切片长度,切片容量大小,即可:make(类型,长度,容量)。
1.11.2.2、切片的使用
切片的使用需要通过append()
函数来完成。append()
函数有两个参数,分别是:
- 第一个参数:slice,表示要添加元素的目标切片。
- 第二个参数:elems,表示添加的元素,可以是多个。
(1)插入元素
切片插入元素就有三种方式,分别是:
- 尾部插入,这是最简单的方式,也是切片默认的方式。
- 中间插入,在切片中间的指定
i
位置,插入元素。 - 头部插入,在切片的头部,插入元素。
package main
import "fmt"
func main() {
// 定义一个长度0、容量9的切片
nums := make([]int, 0, 9)
// 尾部插入元素
nums = append(nums, 1, 2, 3, 4, 5, 6, 7, 8, 9)
fmt.Println(nums)
// 头部插入,这里有个注意点,头部插入,那么第一个参数就是插入元素的切片集合
// 可以理解成,将nums切片插入到[]int{-1,0}切片的尾部,然后再将等到的新切片重新赋值给nums
nums = append([]int{-1, 0}, nums...)
fmt.Println(nums)
nums = append([]int{}, 1, 2, 3, 4, 5, 6, 7, 8, 9)
// 中间位置插入元素
// 中间位置插入,那就需要多次嵌套使用append函数
// 比如:要在中间3和4元素之间插入新的元素 88、99
nums = append(nums[0:3], append([]int{88, 99}, nums[3:]...)...)
fmt.Println(nums)
}
注意了,在使用append函数的时候,针对第二个参数,需要使用【...
】符号,这样才可以将切片元素解构出来。
(2)删除元素
切片删除元素,可以结合append函数来实现。常见的删除操作有下面几种:
- 删除头部元素。
- 删除中间元素。
- 删除尾部元素。
- 全部删除。
package main
import "fmt"
func main() {
// 定义一个长度0、容量9的切片
nums := make([]int, 0, 9)
// 尾部插入元素
nums = append(nums, 1, 2, 3, 4, 5, 6, 7, 8, 9)
fmt.Println(nums)
// 删除头部元素 1、2 两个元素
nums = nums[2:]
fmt.Println(nums)
// 删除尾部元素 9
nums = nums[:len(nums)-1]
fmt.Println(nums)
// 删除中间元素 5、6
nums = append(nums[0:2], append([]int{}, nums[4:]...)...)
fmt.Println(nums)
// 全部删除
nums = nums[:0]
fmt.Println(nums)
}
(3)拷贝切片
切片可以拷贝。go中拷贝切片的时候,需要注意的一点是:必须保证目标切片的长度大于源切片的长度,否则拷贝失败。拷贝可以使用copy()
函数实现。copy有两个参数:
- 第一个参数:dest,表示目标切片。
- 第二个参数:src,表示源切片。
- 即:
copy(dest,src)
,返回一个int整数,表示拷贝成功的元素个数。
package main
import "fmt"
func main() {
dest := make([]int, 5)
src := make([]int, 0, 4)
src = append(src, 1, 2, 3, 4)
fmt.Println(dest, src)
// 拷贝切片
n := copy(dest, src)
fmt.Println("n=", n)
fmt.Println(dest, src)
}
(4)遍历切片
可以使用for、for...range
遍历切片。
1.11.2.3、多维切片
多维切片就和多维数组类似,但是切片的长度是不固定的。
// 定义一个长度为 5 的二维切片
// 二维切片中,每一个元素又是一个切片,长度是0
nums := make([][]int, 5)
1.11.2.4、拓展表达式
切片中,如果s2切片是来自于s1切片得到的,那么在对s2切片进行元素操作的时候,会将s1切片中的元素一起修改。这就是切片之间相互影响的问题。
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
s2 := s1[3:4]
fmt.Println(s1)
fmt.Println(s2)
// 修改切片,这样也会同时修改 s1 切片
s2 = append(s2, 11)
fmt.Println(s1)
fmt.Println(s2)
}
// 上面输出结果
[1 2 3 4 5 6 7 8 9]
[4]
// 可以看到 11 也加入到了 s1 切片里面,这说明,对s2进行修改,会影响s1
[1 2 3 4 11 6 7 8 9]
[4 11]
如何解决这个问题呢???
要想解决切片之间相互影响的问题,可以采用拓展表达式
来解决。拓展表达式
是在原先切片基础之上,新增了第三个max参数值,格式如下所示:
// 原先切片表达式
nums[low:high]
// 这个就是拓展表达式,新增了第三个参数max
// (max-low) 表示当前切片的最大容量,当切片中的元素个数大于等于最大容量
// 那么,此时切片会重新分配底层数组存储切片元素,这样就不会影响原先的切片了
nums[low:high:max]
注意了,三个参数的大小关系必须是:low<=high<=max。
案例代码如下:
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
// 这里通过拓展表达式来解决,切片相互影响的问题
// low = 3、high = 4、max = 4
// 所以最大容量:max - low = 4 - 3 = 1
// 但是切片元素是:4-3 = 1
// 切片元素>=最大容量,所以会重新分配底层数组
s2 := s1[3:4:4]
fmt.Println(s1)
fmt.Println(s2)
// 修改切片,这样也会同时修改 s1 切片
s2 = append(s2, 11)
fmt.Println(s1)
fmt.Println(s2)
}
// 上面输出结果
[1 2 3 4 5 6 7 8 9]
[4]
[1 2 3 4 5 6 7 8 9]
[4 11]
1.12、字符串
go中的字符串本质上是一个不可变
的字符数组
。
1.12.1、定义字符串
分为:原生字符串、普通字符串。
普通字符串使用【
双引号
】定义,不支持换行编写。原生字符串使用【
反引号
】定义,支持多行编写,原生字符串中的内容都是原样输出的。
字符串可以使用循环来访问每一个字符,获取到的是对应字符的unicode
编码值。
package main
import "fmt"
func main() {
str := "普通字符串"
str2 := `原生字符串
可以换行
aaa`
fmt.Println(str)
fmt.Println(str2)
}
1.12.2、转换
字符串可以转换成字节切片,字节切片可以转换成字符串。可以使用下面的转换方式:
package main
import "fmt"
func main() {
str := "普通字符串"
bytes := []byte(str)
fmt.Println(bytes)
// 添加字节
bytes = append(bytes, 110)
fmt.Println(string(bytes))
}
string()
函数可以将字节切片转换成字符串。字符串可以通过【[]byte(字符串)
】方式转换成字节切片。
1.12.3、字符串长度
go语言中的字符串长度,不是字面上看到的字符长度,而是指存储字符串的字节数组长度。
package main
import "fmt"
func main() {
str := "普通字符串"
str2 := "123"
fmt.Println(len(str), len(str2))
}
// 上面运行结果,unicode中一个中文字符占用3个字节,所以结果是 3*5=15
15 3
1.12.4、字符串拼接
go中字符串拼接,可以使用下面几种方式:
- 使用【
+
】加号拼接字符串。 - 使用字节切片拼接字符串。
- 使用
strings.Builder
方式拼接字符串,效率最高,推荐使用。Builder
提供了一个WriteString()
函数,通过这个函数,可以实现字符串的拼接写入功能。
package main
import (
"fmt"
"strings"
)
func main() {
str := "普通字符串"
// 第一种方式
str2 := str + ",拼接字符串"
fmt.Printf(str2)
fmt.Println()
// 第二种方式
bytes := []byte(str)
bytes = append(bytes, 110, 111, 112)
fmt.Printf(string(bytes))
fmt.Println()
// 第三种方式
builder := strings.Builder{}
builder.WriteString(str)
builder.WriteString(",使用Builder拼接字符串")
fmt.Println(builder.String())
}
1.13、映射表
go语言中,映射表是基于哈希表实现的,也就是常说的map映射容器,map是无序的。Go中规定,map的key键必须是可比较的,例如:string、int等类型都是可比较的。
1.13.1、定义map
go中定义map有两种方式,如下所示:
// 第一种方式:通过字面量的方式直接定义map
map[key类型]value类型{
key1:val1,
key2:val2,
key3:val3
}
// 第二种方式:通过内置函数make定义map
make(map[key类型]value类型,初始容量)
案例代码:
package main
import "fmt"
func main() {
// 定义map
map1 := map[string]int{
"A": 1,
"B": 2,
}
fmt.Println(map1)
// 定义map
map2 := make(map[int]string, 10)
fmt.Println(map2)
}
1.13.2、map的使用
(1)访问元素
map访问元素,直接通过key获取即可。格式:
r1, r2 := map[key]
// map访问元素之后,有两个返回值
// r1:第一个返回值表示获取到的元素值
// r2:第二个返回值表示是否存在key对应的value值
(2)存储元素
map中存储元素的时候,如果已经存在key值,则会覆盖value值。有一种情况除外,那就是key是math.NaN()
的时候,这种情况下,不会覆盖,而是一直新增。
package main
import (
"fmt"
"math"
)
func main() {
// 定义map
map2 := make(map[float64]string, 10)
fmt.Println(map2)
map2[0] = "A"
map2[1] = "B"
// 重复key,覆盖value
map2[1] = "C"
fmt.Println(map2)
// NaN情况除外
map2[math.NaN()] = "D"
map2[math.NaN()] = "E"
fmt.Println(map2)
}
(3)删除元素
map要删除元素,可以使用delete()
函数。语法格式:
delete(map,key)
delete()
函数有两个参数,并且这个函数没有返回值。两个参数分别是:
- map:待删除元素的map。
- key:要删除map中的哪个key。
package main
import (
"fmt"
)
func main() {
// 定义map
map2 := make(map[float64]string, 10)
fmt.Println(map2)
map2[0] = "A"
map2[1] = "B"
// 重复key,覆盖value
map2[1] = "C"
fmt.Println(map2)
// 删除元素
delete(map2, 1)
fmt.Println(map2)
}
(4)遍历元素
map
可以使用for...range
循环遍历每一个key
和value
。
1.14、指针
Go里面保留了C语言中的指针这个概念。指针中有两个运算符号需要理解,分别是:
&
:取地址符号,表示获取某个变量对应的内存地址。*
:解引用符号,这个符号有2个作用。- 获取内容:当对指针使用【
*
】符号时候,表示获取指针对应地址中存储的内容。 - 声明指针:【
*
】号还可以声明指针变量,也就是将某个变量定义成指针类型。
- 获取内容:当对指针使用【
1.14.1、声明指针
go
中指针和C
语言中的指针用法类似。声明指针有下面几种方式:
package main
import "fmt"
func main() {
num := 520
// 定义指针
p := &num
fmt.Println("<指针地址>", p)
// 通过 * 号定义指针
var pp *int
pp = &num
fmt.Println("<指针地址>", pp)
// 通过 * 和 new 关键字定义指针
var pp2 *int
pp2 = new(int)
fmt.Println("<指针地址>", pp2)
// 上面这种写法,可以简写成短变量写法
pp3 := new(int)
fmt.Println("<指针地址>", pp3)
}
1.14.2、读取指针内容
读取指针中对应的内容,可以使用【*
】解引用符号来实现。
package main
import "fmt"
func main() {
num := 520
// 定义指针
p := &num
fmt.Println("<指针地址>", p)
// 读取指针中内容
fmt.Println("<指针内容>", *p)
}
另外,Go
语言中,是不允许指针进行运算的,也就是禁止指针运算。
1.15、结构体
Go
语言中没有类与继承的概念,也丢弃了构造方法,虽然没有类的概念,但是提供了一个类似于class
类的概念,叫做:结构体。Go
中通过结构体,可以定义复杂的数据类型。
1.15.1、定义结构体
Go
语言中,定义结构体的语法格式:
type 结构体名称 struct {
变量名称1 数据类型1
变量名称2 数据类型2
变量名称3 数据类型3
}
package main
func main() {
// 定义结构体
type Person struct {
name string
age int
hobby []string
}
}
1.15.2、创建结构体数据
结构体定义完成之后,接着就可以创建一个结构体,然后指定结构体中的数据内容了。
package main
import "fmt"
func main() {
// 定义结构体
type Person struct {
name string
age int
hobby []string
}
// 创建结构体
pp := Person{
name: "小朱",
age: 18,
hobby: []string{"C", "C++", "Java"},
}
fmt.Println(pp)
}
注意,创建结构体的时候,字段名称也可以省略不写,但是这样就必须按照结构体中定义的顺序,将每个字段都进行初始化赋值了。
1.15.3、组合
一个结构体中,可以将另一个结构体作为字段属性,这样就实现了结构体之间的组合使用。组合又可以分为显示组合
和匿名组合
。
(1)显示组合
显示组合
,是指在定义结构体的时候,需要主动定义字段名称以及结构体类型,如下所示:
package main
func main() {
// 定义结构体
type Person struct {
name string
age int
hobby []string
}
// 结构体
type Student struct {
// 这里就是显示组合
// 将结构体 Person 定义在结构体 Student里面,并且指定了字段名称是 person
person Person
grade int
}
}
显示组合的情况下,创建结构体和访问结构体的语法,需要按照下面的方式:
package main
import "fmt"
func main() {
// 定义结构体
type Person struct {
name string
age int
hobby []string
}
// 结构体
type Student struct {
// 这里就是显示组合
// 将结构体 Person 定义在结构体 Student里面,并且指定了字段名称是 person
person Person
grade int
}
// 创建结构体
student := Student{
person: Person{
name: "小朱",
age: 18,
},
grade: 100,
}
// 访问结构体中的结构体属性
fmt.Println(student.person.name)
}
(2)匿名组合
匿名组合
,是指在结构体中,不需要定义字段名称,只需要定义结构体的类型即可,如下所示:
package main
import "fmt"
func main() {
// 定义结构体
type Person struct {
name string
age int
hobby []string
}
// 结构体
type Student struct {
// 这里就是匿名组合
// 将结构体 Person 定义在结构体 Student里面,没有指定字段名称
Person
grade int
}
}
匿名组合中,其实也是存在字段名称的,只不过字段名称就是默认类型名称了。
在匿名组合中,访问结构体中的另一个结构体属性时候,不需要指定字段名称,直接通过外部结构体变量名称,访问内部结构体的属性名称即可。
package main
import "fmt"
func main() {
// 定义结构体
type Person struct {
name string
age int
hobby []string
}
// 结构体
type Student struct {
// 这里就是匿名组合
// 将结构体 Person 定义在结构体 Student里面,没有指定字段名称
Person
grade int
}
// 创建结构体
student := Student{
// 注意,这里创建结构体的,字段名称就是结构体的类型名称
Person: Person{
name: "小朱",
age: 18,
hobby: []string{""},
},
grade: 100,
}
// 访问结构体中的结构体属性,不需要指定结构体的字段名称
fmt.Println(student.name)
}
另外,需要注意的一点是,创建结构体的时候,匿名组合的方式下,结构体的字段名称就是结构体的类型名称。
1.15.4、结构体指针
结构体指针,其实就是将一个结构体的内存地址,使用一个指针变量来保持,这个指针就叫做:结构体指针。因为这个指针是指向的一个结构体。
p := &Person{
字段名称1:值1,
字段名称2:值2
}
上面代码中,变量【p
】就是一个结构体指针,指向的就是结构体Person
的内存地址。
在使用结构体指针的时候,不需要【*
】解引用符号就可以获取到结构体中的数据内容,这是因为Go
针对结构体指针,在编译的时候会转换为(*
p).age,相当于默认会加上【*
】解引用符号,这也算是Go
语言提供的一个语法糖。
package main
import "fmt"
func main() {
// 定义结构体
type Person struct {
name string
age int
hobby []string
}
// 结构体
type Student struct {
// 这里就是匿名组合
// 将结构体 Person 定义在结构体 Student里面,没有指定字段名称
Person
grade int
}
// 创建结构体
pp := &Student{
// 注意,这里创建结构体的,字段名称就是结构体的类型名称
Person: Person{
name: "小朱",
age: 18,
hobby: []string{""},
},
grade: 100,
}
// 等价于 (*pp).name
fmt.Println(pp.name)
fmt.Println((*pp).name)
}
到此,Go语言的基础语法就差不多学完了,后续需要继续进阶学习一下Go的其他知识点。