【Golang笔记01】Goland基础语法规则
Golang
笔记:快速学习Golang
基础语法规则
一、基础语法
1.1、环境安装
第一步需要安装go的运行环境,从官网下载安装包:https://golang.google.cn/dl/。
第二步需要安装go的开发工具,可以使用vscode
、goland
。这里推荐使用goland
。
1.2、hello world
package mainimport "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 stringage int
)
另外,go语言也支持变量类型的自动推导,也就是说,go语言可以根据右侧变量值的数据类型,自动推导出当前这个变量的数据类型。
变量名称 := 变量值
上面这个变量声明叫做:短变量声明并初始化。使用这种短变量的声明方式,那么就不能显示的声明变量类型,不需要使用var关键字,否则会编译失败。
1.5.2、变量赋值
go语言中,变量赋值语法如下:
package mainimport "fmt"func main() {fmt.Println("Hello World!")// 声明变量并且赋值var a string = "你好"fmt.Println(a)var b intb = 20fmt.Println(b)// 多个赋值var c stringvar d intc, d = "abc", 100fmt.Println(c, "---", d)// 短变量,注意:短变量不能重复赋值dd := "200"fmt.Println(dd)// 短变量多个赋值ee, ff := 100, truefmt.Println(ee, ff)// 变量交换,go中可以直接交换两个变量的值num1 := 100num2 := 200fmt.Println(num1, num2)num1, num2 = num2, num1fmt.Println(num1, num2)// 匿名变量 使用 _ 下划线表示匿名变量,匿名变量不会被接收,会直接被丢弃,go编译器不会分配内存空间n1, n2, _ := 1, 2, 3fmt.Println(n1, n2)
}
1.6、常量
go中使用const
定义常量,常量一旦定义赋值之后,就不可以对它的值进行修改,否则编译报错。
go中有一个内置常量,叫做:iota,表示无类型的整数序列。使用这个内置常量,可以生成一些序列号。
package mainimport "fmt"func main() {fmt.Println("Hello World!")const (num1 = iotanum2num3_num5)fmt.Println(num1, num2, num3, num5)const (n1 = iota << 1n2n3n4)fmt.Println(n1, n2, n3, n4)
}
1.7、运算符
go语言中,没有自增自减运算符,只有语句,语法:
变量名称++
变量名称--
需要注意的是,++
和--
只能够放在变量的后面,并且变量不能赋值给其他的变量。
package mainimport "fmt"func main() {fmt.Println("Hello World!")a := 1b := 2fmt.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 mainimport ("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 mainfunc main() {println("内置函数输出内容到控制台")
}
1.8.3、fmt.Println()函数
fmt包中也提供了输出函数,例如:Println()、Printf()函数。
package mainimport "fmt"func main() {fmt.Println("Hello World")fmt.Printf("%s==>%d", "111", 100)
}
1.8.4、Scan()输入
Scan()
接收输入数据,根据【空格、换行
】来接收输入数据。Scan()
方法会返回一个int整数,表示本次输入接收了几个数据。
package mainimport "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 mainimport "fmt"func main() {fmt.Println("请输入数据:")var (num1 intnum2 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 mainimport "fmt"func main() {fmt.Println("请输入数据:")var (num1 intnum2 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 mainimport ("bufio""fmt""os"
)func main() {fmt.Println("请输入数据:")scan := bufio.NewScanner(os.Stdin)scan.Scan()// 逐行读取输入数据text := scan.Text()fmt.Println("text=", text)
}
Reader方式读取,案例代码:
package mainimport ("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 mainimport ("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 mainimport "fmt"func main() {fmt.Println("请输入数据:")var (num1 intnum2 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 mainimport "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 mainimport "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 语句块fallthroughcase "3":fmt.Println("匹配case3")default:fmt.Println("case都没有匹配成功")}fmt.Println("switch执行结束")
}
switch
还有另外一种语法,如下所示:
package mainimport "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 语句块fallthroughcase str == "3":fmt.Println("匹配case3")default:fmt.Println("case都没有匹配成功")}fmt.Println("switch执行结束")
}
1.9.3、goto
关键字(不常用)
go
语言中提供了一个goto
关键字,可以将代码执行跳转到同一个函数中的对应label标签位置。goto
一般和label
标签一起使用才有意义。
label
标签可以给某一块代码做个标记,相当于是做了一个位置标记,使用goto
可以快速跳转到这个位置,执行标签之后的代码。
package mainimport "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 label01case 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 mainimport "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 mainimport "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 mainimport "fmt"func main() {var nums [5]intnums[1] = 1fmt.Println(nums)nums2 := [3]int{1, 2, 3}fmt.Println(nums2)nums3 := new([2]int)nums3[0] = 1fmt.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 mainimport "fmt"func main() {var nums [5]intfor 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 mainimport "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}切片的尾部,然后再将等到的新切片重新赋值给numsnums = append([]int{-1, 0}, nums...)fmt.Println(nums)nums = append([]int{}, 1, 2, 3, 4, 5, 6, 7, 8, 9)// 中间位置插入元素// 中间位置插入,那就需要多次嵌套使用append函数// 比如:要在中间3和4元素之间插入新的元素 88、99nums = append(nums[0:3], append([]int{88, 99}, nums[3:]...)...)fmt.Println(nums)
}
注意了,在使用append函数的时候,针对第二个参数,需要使用【...
】符号,这样才可以将切片元素解构出来。
(2)删除元素
切片删除元素,可以结合append函数来实现。常见的删除操作有下面几种:
- 删除头部元素。
- 删除中间元素。
- 删除尾部元素。
- 全部删除。
package mainimport "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)// 删除尾部元素 9nums = nums[:len(nums)-1]fmt.Println(nums)// 删除中间元素 5、6nums = 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 mainimport "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 mainimport "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 mainimport "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 mainimport "fmt"func main() {str := "普通字符串"str2 := `原生字符串可以换行aaa`fmt.Println(str)fmt.Println(str2)
}
1.12.2、转换
字符串可以转换成字节切片,字节切片可以转换成字符串。可以使用下面的转换方式:
package mainimport "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 mainimport "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 mainimport ("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 mainimport "fmt"func main() {// 定义mapmap1 := map[string]int{"A": 1,"B": 2,}fmt.Println(map1)// 定义mapmap2 := 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 mainimport ("fmt""math"
)func main() {// 定义mapmap2 := make(map[float64]string, 10)fmt.Println(map2)map2[0] = "A"map2[1] = "B"// 重复key,覆盖valuemap2[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 mainimport ("fmt"
)func main() {// 定义mapmap2 := make(map[float64]string, 10)fmt.Println(map2)map2[0] = "A"map2[1] = "B"// 重复key,覆盖valuemap2[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 mainimport "fmt"func main() {num := 520// 定义指针p := &numfmt.Println("<指针地址>", p)// 通过 * 号定义指针var pp *intpp = &numfmt.Println("<指针地址>", pp)// 通过 * 和 new 关键字定义指针var pp2 *intpp2 = new(int)fmt.Println("<指针地址>", pp2)// 上面这种写法,可以简写成短变量写法pp3 := new(int)fmt.Println("<指针地址>", pp3)
}
1.14.2、读取指针内容
读取指针中对应的内容,可以使用【*
】解引用符号来实现。
package mainimport "fmt"func main() {num := 520// 定义指针p := &numfmt.Println("<指针地址>", p)// 读取指针中内容fmt.Println("<指针内容>", *p)
}
另外,Go
语言中,是不允许指针进行运算的,也就是禁止指针运算。
1.15、结构体
Go
语言中没有类与继承的概念,也丢弃了构造方法,虽然没有类的概念,但是提供了一个类似于class
类的概念,叫做:结构体。Go
中通过结构体,可以定义复杂的数据类型。
1.15.1、定义结构体
Go
语言中,定义结构体的语法格式:
type 结构体名称 struct {变量名称1 数据类型1变量名称2 数据类型2变量名称3 数据类型3
}package mainfunc main() {// 定义结构体type Person struct {name stringage inthobby []string}
}
1.15.2、创建结构体数据
结构体定义完成之后,接着就可以创建一个结构体,然后指定结构体中的数据内容了。
package mainimport "fmt"func main() {// 定义结构体type Person struct {name stringage inthobby []string}// 创建结构体pp := Person{name: "小朱",age: 18,hobby: []string{"C", "C++", "Java"},}fmt.Println(pp)
}
注意,创建结构体的时候,字段名称也可以省略不写,但是这样就必须按照结构体中定义的顺序,将每个字段都进行初始化赋值了。
1.15.3、组合
一个结构体中,可以将另一个结构体作为字段属性,这样就实现了结构体之间的组合使用。组合又可以分为显示组合
和匿名组合
。
(1)显示组合
显示组合
,是指在定义结构体的时候,需要主动定义字段名称以及结构体类型,如下所示:
package mainfunc main() {// 定义结构体type Person struct {name stringage inthobby []string}// 结构体type Student struct {// 这里就是显示组合// 将结构体 Person 定义在结构体 Student里面,并且指定了字段名称是 personperson Persongrade int}
}
显示组合的情况下,创建结构体和访问结构体的语法,需要按照下面的方式:
package mainimport "fmt"func main() {// 定义结构体type Person struct {name stringage inthobby []string}// 结构体type Student struct {// 这里就是显示组合// 将结构体 Person 定义在结构体 Student里面,并且指定了字段名称是 personperson Persongrade int}// 创建结构体student := Student{person: Person{name: "小朱",age: 18,},grade: 100,}// 访问结构体中的结构体属性fmt.Println(student.person.name)
}
(2)匿名组合
匿名组合
,是指在结构体中,不需要定义字段名称,只需要定义结构体的类型即可,如下所示:
package mainimport "fmt"func main() {// 定义结构体type Person struct {name stringage inthobby []string}// 结构体type Student struct {// 这里就是匿名组合// 将结构体 Person 定义在结构体 Student里面,没有指定字段名称Persongrade int}
}
匿名组合中,其实也是存在字段名称的,只不过字段名称就是默认类型名称了。
在匿名组合中,访问结构体中的另一个结构体属性时候,不需要指定字段名称,直接通过外部结构体变量名称,访问内部结构体的属性名称即可。
package mainimport "fmt"func main() {// 定义结构体type Person struct {name stringage inthobby []string}// 结构体type Student struct {// 这里就是匿名组合// 将结构体 Person 定义在结构体 Student里面,没有指定字段名称Persongrade 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 mainimport "fmt"func main() {// 定义结构体type Person struct {name stringage inthobby []string}// 结构体type Student struct {// 这里就是匿名组合// 将结构体 Person 定义在结构体 Student里面,没有指定字段名称Persongrade int}// 创建结构体pp := &Student{// 注意,这里创建结构体的,字段名称就是结构体的类型名称Person: Person{name: "小朱",age: 18,hobby: []string{""},},grade: 100,}// 等价于 (*pp).namefmt.Println(pp.name)fmt.Println((*pp).name)
}
到此,Go语言的基础语法就差不多学完了,后续需要继续进阶学习一下Go的其他知识点。