Go基础
(一)程序举例
hello.go
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}
运行:
$ go run hello.go
或
$ go build hello.go
$ ./hello
注意事项:
- 源文件开头的第一行必须指明该文件属于哪个包,每个Go程序都含有一个名为main的包
- 程序种使用的所有包必须先引入,引入的包必须使用
- 每个Go程序必须含有一个man函数,该函数一般是启动后第一个执行的函数(除非有init函数)
- 当标识符以大写字母开头,则它能被外部引用,称导出,否则不能被外部引用
- "{"不能单独占一行
(二)基础语法
1. 行分隔符
Go程序的一行代表一个语句,若要在一行内写多个语句,则要用";"分隔
2. 注释
// 单行注释
/*
多行注释
*/
3. 标识符
由数字、字母、下划线组成,数字不能放开头
(三)数据类型
(四)变量
1. 变量声明
Go程序中,变量声明后必须使用
(1)var关键字
package main
import "fmt"
func main() {
var a string = "hello" // 单个变量声明并初始化
fmt.Println(a) // hello
var b, c int = 1, 2 // 多个变量声明并初始化
fmt.Println(b, c) // 1, 2
var d bool // 单个变量声明未初始化,使用零值
fmt.Println(d) // false
}
数字型、布尔型、字符串的零值为0、flase、“”,其他一般为nil
(2)根据值自动推断类型
var a = true
,a自动推断为布尔型,值为true
(3)简略写法
:=
只能在函数体内出现,a := 1
等价于:
var a int
a = 1
:=
也可以用来同时声明并初始化多个变量,如:a, b, c = 1, 2, "hello"
,等价于:
var a, b int
var c string
a, b, c = 1, 2, "hello"
(4)交换值
交换值可以通过a, b = b, a
实现,前提是a、b的类型相同
(5)抛弃值
_, b = 1, 2
中,值1被抛弃,只有b被赋值为2
2. 变量作用域
(1)局部变量
在函数体内声明的变量,作用域在函数体内
(2)全局变量
在函数体外声明的变量,可在整个包或包外部(被导出后)使用
全局变量与局部变量同名,优先考虑局部变量
(3)形式参数
在函数定义中的变量,作为局部变量使用
(五)常量
1. 变量声明
常量只能是布尔型、数字型、字符串型,用const
关键字声明
显式声明:const a string = "hello"
隐式声明:const a = "hello"
可同时声明并初始化多个常量
可用作枚举:
const (
Unknow = 0
Female = 1
Male = 2
)
2. 特殊常量iota
iota是一个可以被编译器修改的常量,第一个iota等于0,const中每增加一行,它的值增加1
const (
a = iota // 0
b // 1
c = "hello" // "hello"
d // "hello"
e = iota // 4
f // 5
)
(六)运算符
(七)条件语句
1. if 语句
if 条件 {
语句
}
2. if…else 语句
if 条件 {
语句
} else {
语句
}
3. if嵌套语句
if 条件1 {
语句1
if 条件2 {
语句2
}
}
4. switch语句
switch 变量 {
case 值1:
语句1
case 值2:
语句2
default:
语句n
}
5. select语句
类似于用于通信的switch语句,每个case必须是一个通信操作
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 10)
ch <- 1
ch <- 2
ch <- 3
close(ch)
for {
select {
case v, ok := <-ch:
if ok {
fmt.Printf("v=%v, ok=%v \n", v, ok)
} else {
fmt.Println("channel is closed")
return
}
default:
fmt.Println("")
}
}
}
(八)循环语句
1. for语句
for 变量赋初值; 循环控制条件; 变量增或减 {
语句
}
for 循环控制条件 {
语句
}
for {
语句
}
2. 嵌套循环
for 变量赋初值; 循环控制条件; 变量增或减 {
语句
for 变量赋初值; 循环控制条件; 变量增或减 {
语句
}
}
3. 循环控制语句
描述 | |
---|---|
break语句 | 跳出当前循环或 switch 语句 |
continue语句 | 跳过当前循环的剩余语句进入下一轮循 |
goto语句 | 将控制转移到标记语句 |
goto 标记
...
标记: 语句
(九)函数
1. 函数定义
格式:
func 函数名(参数列表) 返回类型 {
函数体
}
举例:
/* 返回一个值:函数返回两个数的最大值 */
func max(num1, num2 int) int {
var result int
if (num1 > num2) {
result = num1
} else {
result = num2
}
return result
}
/* 返回多个值:函数交换两个数的值并返回 */
func swap(num1, num2) (int, int) {
return num2, num1
}
2. 函数调用
举例:
package main
import "fmt"
func main() {
var a int = 1
var b int = 2
var ret int
/* 调用函数并返回最大值 */
ret = max(a, b)
fmt.Printf("max = %d\n", ret)
}
/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {
var result int
if num1 > num2 {
result = num1
} else {
result = num2
}
return result
}
举例:
package main
import "fmt"
func main() {
var a int = 1
var b int = 2
fmt.Printf("before: a = %d, b = %d\n", a, b)
/* 调用函数并返回交换的两个值 */
a, b = swap(a, b)
fmt.Printf("after: a = %d, b = %d\n", a, b)
}
/* 函数交换两个数的值并返回 */
func swap(num1, num2 int) (int, int) {
return num2, num1
}
函数调用传递参数的方式:
描述 | |
---|---|
值传递 | 调用函数时将实参复制一份传递到函数中,在函数中对参数进行的修改不会影响到实参 |
引用传递 | 调用函数时将实参的地址传递到函数中,在函数中对参数进行的修改会影响到实参 |
- 默认情况下Go使用值传递
值传递举例:
package main
import "fmt"
func main() {
var a int = 1
var b int = 2
fmt.Printf("before: a = %d, b = %d\n", a, b) // a = 1, b = 2
/* 调用函数交换值 */
swap(a, b)
fmt.Printf("after: a = %d, b = %d\n", a, b) // a = 1, b = 2
}
/* 定义交换值的函数 */
func swap(x, y int) {
var temp int
temp = x // 保存x上的值
x = y // 将y的值赋给x
y = temp // 将temp的值赋给y
}
引用值传递:
package main
import "fmt"
func main() {
var a int = 1
var b int = 2
fmt.Printf("before: a = %d, b = %d\n", a, b) // a = 1, b = 2
/* 调用函数交换值 */
swap(&a, &b)
fmt.Printf("after: a = %d, b = %d\n", a, b) // a = 2, b = 1
}
/* 定义交换值的函数 */
func swap(x, y *int) {
var temp int
temp = *x // 保存x地址上的值
*x = *y // 将y的值赋给x
*y = temp // 将temp的值赋给y
}
3. 函数用法
Go语言中,函数可以作为另一个函数的实参
package main
import (
"fmt"
"math"
)
func main() {
/* 声明函数变量 */
getSquareRoot := func(x float64) float64 {
return math.Sqrt(x)
}
/* 使用函数 */
fmt.Println(getSquareRoot(9)) // 开方
}
Go语言支持匿名函数,可作为闭包,匿名函数可直接使用函数内的变量
package main
import "fmt"
func main(){
/* nextNumber 为一个函数,函数 i 为 0 */
nextNumber := getSequence()
/* 调用 nextNumber 函数,i 变量自增 1 并返回 */
fmt.Println(nextNumber()) // 1
fmt.Println(nextNumber()) // 2
fmt.Println(nextNumber()) // 3
/* 创建新的函数 nextNumber1,并查看结果 */
nextNumber1 := getSequence()
fmt.Println(nextNumber1()) // 1
fmt.Println(nextNumber1()) // 2
}
func getSequence() func() int {
i:=0
return func() int {
i+=1
return i
}
}
Go语言中的方法为包含接收者的函数
格式:
func (参数列表) 方法名() 返回类型 {
函数体
}
举例:
package main
import "fmt"
// 定义结构体
type Circle struct {
radius float64
}
func main() {
var c1 Circle
c1.radius = 10.00
fmt.Println("area = ", c1.getArea()) // 314
}
// Circle类型对象中的方法
func (c Circle) getArea() float64 {
return 3.14 * c.radius * c.radius
}
(十)数组
- 数组:类型相同的已编号且长度固定的序列
1. 数组声明
格式:
var 数组名 [长度] 类型
举例:
var array [5] int
2. 数组初始化
var array = [5]float32{0.0, 2.0, 4.0, 6.0, 8.0}
array := [5]float32{0.0, 2.0, 4.0, 6.0, 8.0}
数组长度不定,元素确定:
var array = [...]float32{0.0, 2.0, 4.0, 6.0, 8.0}
array := [...]float32{0.0, 2.0, 4.0, 6.0, 8.0}
数组长度确定,元素不确定
array := [5]float32{1:2.0, 3:6.0} // 索引为1、3的元素初始化为2.0、6.0
array[4] = 8.0 // 索引为4的元素初始化为8.0
3. 数组访问
直接通过索引读取
package main
import "fmt"
func main() {
var a [10]int
/* 为数组a初始化元素 */
for i := 0; i < 10; i++ {
a[i] = i + 100
}
/* 输出数组a每个元素的值 */
for j := 0; j < 10; j++ {
fmt.Printf("a[%d] = %d\n", j, a[j])
}
}
结果为:
a[0] = 100
a[1] = 101
a[2] = 102
a[3] = 103
a[4] = 104
a[5] = 105
a[6] = 106
a[7] = 107
a[8] = 108
a[9] = 109
4. 多维数组
格式:
var 数组名 [一维长度][二维长度]...[n维长度] 数组类型
二维数组定义:
var array [3][4]
二维数组初始化:
array := [3][4]int{
{0, 1, 2, 3},
{4, 5, 6, 7},
{0, 1, 2, 3},
{4, 5, 6, 7},
}
array := [3][4]int{
{0, 1, 2, 3},
{4, 5, 6, 7},
{0, 1, 2, 3},
{4, 5, 6, 7}}
二维数组访问:
var val int = array[2][3]
val := array[2][3]
举例:
package main
import "fmt"
func main() {
// 创建空的二维数组
a := [][]int{}
// 创建3个一维数组
r1 := []int{1}
r2 := []int{2, 3}
r3 := []int{4, 5, 6}
// 将一维数组添加到二维数组
a = append(a, r1)
a = append(a, r2)
a = append(a, r3)
// 循环输出
for i := range a {
fmt.Println(a[i])
}
}
输出:
[1]
[2 3]
[4 5 6]
5. 数组作函数参数
package main
import "fmt"
func main() {
var score = [5]float32{95.0, 80.0, 90.0, 85.0, 98.0}
var avg float32
// 数组作函数参数传递给函数
avg = getAverage(score, 5)
fmt.Println(avg) // 89.6
}
func getAverage(a [5]float32, size int) float32 {
var sum float32
for i := 0; i < size; i++ {
sum += a[i]
}
return sum / float32(size)
}
(十一)指针
指针变量指向一个值的内存地址
1. 指针使用
package main
import "fmt"
func main() {
a := 20 // 声明实际变量
var p *int // 声明指针变量
p = &a // 指针变量赋值
fmt.Printf("a变量的值是:%d\n", a)
fmt.Printf("a变量的存储地址是:%d\n", &a)
fmt.Printf("p变量存储的地址是:%d\n", p) // 访问指针变量
fmt.Printf("p变量所指地址的值是:%d\n", *p) // 访问指针变量指向的值
}
2. 指针数组
package main
import "fmt"
const MAX int = 3
func main() {
a := []int{1, 2, 3}
var p [MAX]*int // 声明指针数组
for i := 0; i < MAX; i++ {
p[i] = &a[i] // 给指针数组的每个元素赋值
}
for i := 0; i < MAX; i++ {
fmt.Printf("a[%d] = %d\n", i, *p[i]) // 访问指针数组每个指针指向的值
}
}
a[0] = 1
a[1] = 2
a[2] = 3
3. 指向指针的指针
package main
import "fmt"
func main() {
a := 20 // 声明实际变量
var p *int // 声明指针变量
var pp **int // 声明指向指针的变量
p = &a // 指针变量赋值
pp = &p // 指向指针的指针变量赋值
fmt.Printf("变量a = %d\n", a)
fmt.Printf("指针变量*p = %d\n", *p)
fmt.Printf("指向指针的指针变量**pp = %d\n", **pp)
}
4. 指针作函数参数
package main
import "fmt"
func main() {
var a int = 100
var b int = 200
fmt.Printf("Before change: a = %d, b = %d\n", a, b) // Before change: a = 100, b = 200
swap(&a, &b) // 指针作函数参数
fmt.Printf("After change: a = %d, b = %d\n", a, b) // After change: a = 200, b = 100
}
func swap(x *int, y *int) {
var temp int
temp = *x
*x = *y
*y = temp
}
(十二)结构体
1. 定义结构体
格式:
type 结构体名 struct {
成员1 成员类型1
成员2 成员类型2
}
结构体 := 结构体名{成员值1, 成员值2}
结构体 := 结构体名{成员1: 成员值1, 成员2:成员值2}
举例:
package main
import "fmt"
type Student struct {
name string
sex string
age int
}
func main() {
// 创建结构体
student1 := Student{"Tom", "male", 20}
student2 := Student{name: "Amy", sex : "female", age: 18}
fmt.Println(student1)
fmt.Println(student2)
}
{Tom male 20}
{Amy female 18}
2. 访问结构体成员
格式:
结构体.成员名
举例:
package main
import "fmt"
type Student struct {
name string
sex string
age int
}
func main() {
// 声明结构体
var student1 Student
// 结构体描述
student1.name = "Tom"
student1.sex = "male"
student1.age = 20
fmt.Printf("name: %s\n", student1.name)
fmt.Printf("sex: %s\n", student1.sex)
fmt.Printf("age: %d\n", student1.age)
}
name: Tom
sex: male
age: 20
3. 结构体作函数参数
package main
import "fmt"
type Student struct {
name string
sex string
age int
}
func main() {
// 声明结构体
var student1 Student
// 结构体描述
student1.name = "Tom"
student1.sex = "male"
student1.age = 20
printInfo(student1) // 结构体作函数参数
}
func printInfo(student Student) {
fmt.Printf("name: %s\n", student.name)
fmt.Printf("sex: %s\n", student.sex)
fmt.Printf("age: %d\n", student.age)
}
name: Tom
sex: male
age: 20
4. 结构体指针
package main
import "fmt"
type Student struct {
name string
sex string
age int
}
func main() {
var student1 Student // 声明结构体
var p *Student // 声明结构体指针
// 结构体描述
student1.name = "Tom"
student1.sex = "male"
student1.age = 20
p = &student1 // 结构体指针赋值
printInfo(p) // 结构体指针作函数参数
}
func printInfo(p *Student) {
fmt.Printf("name: %s\n", p.name)
fmt.Printf("sex: %s\n", p.sex)
fmt.Printf("age: %d\n", p.age)
}
name: Tom
sex: male
age: 20
(十三)切片
切片是对数组的抽象,与数组相比切片的长度不固定,可追加元素
1. 定义切片
用未指定大小的数组定义切片:
var 切片名 []切片类型
用make()
函数创建切片(容量可省略):
var 切片名 []切片类型 = make([]切片类型, 长度, 容量)
切片名 := make([]切片类型, 长度, 容量)
2. 初始化切片
直接初始化:
s := []int{1, 2, 3, 4, 5}
[]表示切片类型,切片元素为1, 2, 3, 4, 5,其长度和容量均为5
举例:
package main
import "fmt"
func main() {
s := []int{1, 2, 3, 4, 5}
fmt.Println(s1) // [1 2 3 4 5]
}
初始化数组的引用:
s := a[:] // 将数组a的所有元素创建为一个切片
s := a[stratIndex:endIndex] // 将数组a中下标为startIndex到endIndex-1的元素创建为一个切片
s := a[stratIndex:] // 将数组a从下标为startIndex后的元素始创建为一个切片
s := a[:endIndex] // 将数组a从下标为endIndex-1前的元素创建为一个切片
举例:
package main
import "fmt"
func main() {
a := [5]int{0, 2, 4, 6, 8}
s1 := a[:]
fmt.Println(s1) // [0 2 4 6 8]
s2 := a[1:4]
fmt.Println(s2) // [2 4 6]
s3 := a[1:]
fmt.Println(s3) // [2 4 6 8]
s4 := a[:4]
fmt.Println(s4) // [0 2 4 6]
}
3. 截取切片
package main
import "fmt"
func main() {
// 创建切片
s := []int{0, 1, 2, 3, 4}
// 打印原始切片
fmt.Println("s = ", s) // s = [0 1 2 3 4]
// 打印从1(包含)到4(不含)
fmt.Println("s[1:4] = ", s[1:4]) // s[1:4] = [1 2 3]
// 默认下限为0
fmt.Println("s[:4] = ", s[:4]) // s[:4] = [0 1 2 3]
// 默认上限为len(s)
fmt.Println("s[1:] = ", s[1:]) // s[1:] = [1 2 3 4]
}
4. len()和cap()函数
len()
函数可获得切片长度cap()
函数可获得切片的最大长度
package main
import "fmt"
func main() {
var s []int // 切片未初始化,为nil
printSlice(s) // len = 0, cap = 0, slice = []
s = make([]int, 3, 5) // 切片初始化
printSlice(s) // len = 3, cap = 5, slice = [0 0 0]
}
func printSlice(x []int) {
fmt.Printf("len = %d, cap = %d, slice = %v\n", len(x), cap(x), x)
}
5. append()和copy()函数
append()
追加元素copy()
拷贝切片
package main
import "fmt"
func main() {
var s1 []int
printSlice(s1) // len = 0, cap = 0, slice = []
// 向切片添加一个元素
s1 = append(s1, 0)
printSlice(s1) // len = 1, cap = 1, slice = [0]
// 向切片添加多个元素
s1 = append(s1, 1, 2, 3)
printSlice(s1) // len = 4, cap = 4, slice = [0 1 2 3]
// 创建切片s2是之前切片的两倍容量
s2 := make([]int, len(s1), cap(s1) * 2)
// 拷贝切片s1的内容到s2
copy(s2, s1)
printSlice(s2) // len = 4, cap = 8, slice = [0 1 2 3]
}
func printSlice(x []int) {
fmt.Printf("len = %d, cap = %d, slice = %v\n", len(x), cap(x), x)
}
(十四)范围
range关键字用于for循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)中的元素。在数组和切片中它用于返回元素的索引和索引对应的值,在集合中返回键值对。
格式:
for key, value := range oldMap {
...
}
for key := range oldMap {
...
}
for _, value := range oldMap {
...
}
举例:
package main
import "fmt"
func main() {
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
}
2**0 = 1
2**1 = 2
2**2 = 4
2**3 = 8
2**4 = 16
2**5 = 32
2**6 = 64
2**7 = 128
(十五)集合Map
Map是一种集合,可迭代,但由于Map是无序的,返回顺序不确定
定义:
var 集合名[键类型]值类型
初始化(不初始化map为nil):
集合名 := make(map[键类型][值类型])
插入元素值
集合名 [key] = value
删除元素值
delete(集合名, key)
举例:
package main
import "fmt"
func main() {
countryCaptialMap := make(map[string]string) // 创建并初始化Map
// map插入键值对
countryCaptialMap ["China"] = "北京"
countryCaptialMap ["Janpan"] = "东京"
countryCaptialMap ["Italy"] = "罗马"
// 输出键值对
fmt.Println("原始地图")
for country := range countryCaptialMap {
fmt.Println(country, "首都是", countryCaptialMap[country])
}
// 删除元素
delete(countryCaptialMap, "Janpan")
// 输出键值对
fmt.Println("删除后的地图")
for country := range countryCaptialMap {
fmt.Println(country, "首都是", countryCaptialMap[country])
}
}
原始地图
China 首都是 北京
Janpan 首都是 东京
Italy 首都是 罗马
删除后的地图
China 首都是 北京
(十六)递归函数
递归函数即在运行过程中调用自己
举例:递归函数实现斐波那契数列
package main
import "fmt"
func fib(n int) int {
if n < 2 {
return n
}
return fib(n - 2) + fib(n -1)
}
func main() {
for i := 1; i < 10; i++ {
fmt.Printf("%d\t", fib(i))
}
}
1 1 2 3 5 8 13 21 34
(十七)类型转换
格式:
要转化为的类型(表达式)
举例:
package main
import "fmt"
func main() {
var a int = 10
var b int = 2
fmt.Printf("a,b均为整型相除的结果 %d\n", a / b)
fmt.Printf("a,b均为浮点型相除的结果 %f\n", float32(a) / float32(b))
}
a,b均为整型相除的结果 5
a,b均为浮点型相除的结果 5.000000
(十八)接口
接口把所有有共性的方法定义在一起,其他任何类型只要是实现了这些方法就是实现了这个接口
格式:
// 定义接口
type 接口名 interface {
方法名1 [返回类型]
...
}
// 定义结构体
type 结构体名 struct {
变量
}
// 定义接口方法
func (结构体变量名 结构体名) 方法名1() [返回类型] {
方法实现
}
...
举例:
package main
import "fmt"
type Phone interface {
call()
}
type AmyPhone struct {
}
func (amyPhone AmyPhone) call() {
fmt.Println("Hello, I am Amy!")
}
type TomPhone struct {
}
func (tomPhone TomPhone) call() {
fmt.Println("Hello, I am Tom!")
}
func main() {
var phone Phone
phone = new(AmyPhone)
phone.call()
phone = new(TomPhone)
phone.call()
}
Hello, I am Amy!
Hello, I am Tom!
(十九)错误
错误error是一个接口类型
举例:定义除0错误
package main
import (
"fmt"
)
type DivError struct {
}
func (de *DivError) Error() string {
str := "除数为0"
return fmt.Sprintf(str)
}
func Divide(a int, b int) (result int, errMsg string) {
if b == 0 {
dError := DivError{}
errMsg = dError.Error()
return
} else {
return a / b, ""
}
}
func main() {
// 正常
if result, errMag := Divide(100, 10); errMag == "" {
fmt.Println("100/10 = ", result)
}
// 异常
if _, errMag := Divide(100, 0); errMag != "" {
fmt.Println("100/0 = ", errMag)
}
}
(二十)并发
通过go关键字开启goroutine
格式:
开启goroutine
go 函数名(参数列表)
举例:
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("hello")
say("world")
}
输出hello和world的顺序不定
world
hello
world
hello
hello
world
world
hello
hello
world
(二十一)通道
通道为用于传递数据的一个数据结构,可用于在两个goroutline间通过传递一个指定类型的值来同步运行和通讯,<-用于指定通道方向
创建通道:
ch := make(chan int)
通道可以设置缓冲区大小:
ch := make(chan int, 100)
遍历通道:
for i := range(ch) {
fmt.Println(i)
}
关闭通道:
close(ch)
举例:
package main
import (
"fmt"
)
func fib (n int, c chan int) {
x, y := 0, 1
for i := 0; i <n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fib(cap(c), c)
for i := range(c) {
fmt.Println(i)
}
}
0
1
1
2
3
5
8
13
21
34