这里是 go 的基本语法大部分内容来源于一门视频课程
学完后再来看手摸手开发一个全栈项目就基本可以直接上手了,项目本身没有什么难度!
变量
go语言定义变量支持类型推导如 a := 2
a 的类型是int
var
关键字
go
var a int = 1
:=
只能在函数中定义变量
go
a := 1
集中定义变量
go
// 1. 在函数中使用 :=
a,b,c := 1,2,"3"
// 2. 在函数外 包内
var (
a=1
b=2
c="3"
)
变量类型
布尔型:bool
只能声明为 true、false
不能接受其他类型的赋值,包括(0, 1),也不支持自动或强制类型转换
字符串:string
整型:(u)int,(u)int8,(u)int16,(u)int32,(u)int64,uintptr
int 类型初始化默认为0
字符型: byte,rune
byte 对应 utf-8
rune 对应 unicode
浮点型: float32, float64
32 与64可相互转换,比较大小时最好先确定比较精度, 再比较大小
复数型:complex64, complex128
分为实部 与虚部 , 64 类型为 float32 128 为64
欧拉公式cmplx.Pow(math.E, 1i * math.Pi) + 1
强制类型转换
go
var a, b int = 3, 4
// Sqrt 函数接收 float64 返回 float64 所以需要强制类型转换
var c int = int(math.Sqrt(float64(a*a+b*b)))
go
package main
import (
"fmt"
"strconv"
)
func main() {
tt1 := []string{"aaaa", "vvvvv", "dddd", "eeeee", "gfgggg"}
fmt.Println(tt1)
// 布尔转文本
// strconv.FormatBool(false)
// 文字转布尔 true 不能有其他文本 0 false 1 true
res, _ := strconv.ParseBool("1")
fmt.Println(res)
// 格式化 浮点型数字 会四舍五入
value := strconv.FormatFloat(1.55, 'f', 2, 64)
fmt.Println(value)
// 向后追加 int 、 bool
// strconv.AppendUint()
}
常量的定义
常量的定义使用 const
关键字定义const a = 3
类型推导 a 为 int 类型
枚举的定义
普通枚举
go
const(
cpp = 0
java = 1
python = 2
)
自增枚举
go
const(
got = iota
ptython
wget
)
// iota 可参与运算
const (
a = iota * 2
b
c
d
)
fmt.Println(a,b,c,d) // 0 2 4 6
条件语句
if
if
语句 由一个布尔表达式后紧跟一个或多个语句组成。
if else
语句 后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行。
你可以在 if 或 else if 语句中嵌入一个或多个 if 或 else if 语句。
go
// go 语言的if 判断条件没有 ()
// if 条件中可以声明变量 且在后边语句中使用
if fp, err := os.Open("./textTest/a.txt"); err != nil {
fmt.Println(err)
} else {
fmt.Println(fp)
}
switch
switch 语句用于基于不同条件执行不同动作,switch不需要break,也可以直接switch多个条件
go
// 一个判断条件
a:=1
switch a {
case 1:
fmt.Println(a,1)
case 2:
fmt.Println(a,2)
case 3:
fmt.Println(a,3)
}
// 多个判断条件
var str = "abc"
switch {
case a > 10:
str = "不及格"
case a > 20:
str = "及格"
}
return str
循环语句
for
go语言中没有 while 循环,但是 for 可以当作 while 使用
go
func forTest() {
// 完整 for 循环
for a:=0; a< 3 ; a++{
fmt.Println(a)
}
file, fileErr := os.Open("./textTest/a.txt")
if fileErr!=nil {
panic("打开文件错误")
}
scanner := bufio.NewScanner(file)
// 省略起始条件 递增条件
for scanner.Scan() {
// 输出文件的 每行内容
fmt.Println(scanner.Text())
}
// 省略全部条件 是一个死循环 可以通过 break 关键字进行跳出
for {
fmt.Println("这是一个死循环")
break
}
}
跳出循环
break
语句 经常用于中断当前 for 循环或跳出 switch 语句
continue
语句 跳过当前循环的剩余语句,然后继续进行下一轮循环。
goto
语句 将控制转移到被标记的语句。(不常用)
Go 语言函数
在go 语言中 函数是一等公民
go语言最少有一个 main
函数也就是主函数(入口函数)
函数声明告诉了编译器函数的名称,返回类型,和参数。
可返回多个值, 返回值类型写在最后面
函数可以作为参数
没有默认参数,可选参数
函数定义
go 语言的函数 由 func
声明
go
func funcTest() {}
函数调用
go
package main
import "fmt"
func main() {
// 定义局部变量 var a int = 100 等同于 a := 100
var a int = 100
var b int = 200
var ret int
// 返回两者相加
ret = num(a, b)
fmt.Printf( "两数之和为 : %d\n", ret )
}
函数返回两数之和为
func num(num1, num2 int) int {
return num1 + num2
}
函数参数
值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。 (引用传递需要指针)
函数返回多个值
go
// 函数接收两个参数 返回两个参数
func funcTest(a,b int)(c, d int) {
c, d = a, b
// 当为 返回参数定义名字时 return 时 会自动返回对应参数
return
}
函数接收参数固定 无返回值
go
// 函数接收参数固定 无返回值
func funcTest(nums ...int) {
// 不定参数时 nums 是一个数组, range 可以用来遍历数组及map
for i := range nums{
fmt.Println(nums[i])
}
}
函数可以作为参数
go
// 函数式
func apply(fc func(a,b int)int, a, b int)int {
return fc(a,b)
}
// 函数可以作为参数, 接收一个函数返回其相加结果
func funcTest() {
fmt.Println(apply(func(a, b int) int {
return a + b
},3,4))
}
指针
指针就是一个指向另一个内存地址变量的值
Go指针不能运算
Go语言中的函数传参都是值拷贝,当我们想要修改某个变量的时候,我们可以创建一个指向该变量地址的指针变量。
Go语言中的指针操作非常简单,只需要记住两个符号:&
(取地址)和*
(根据地址取值)。
go
var a, b int= 1, 2
swap2(&a, &b)
fmt.Println(a, b)
func swap2(a , b *int) {
*a, *b = *b, *a
}
如何使用指针
定义指针变量。
为指针变量赋值。
访问指针变量中指向地址的值。
获取指针的值 在指针类型前面加上 *
号(前缀)来获取指针所指向的内容。
go
package main
import "fmt"
func main() {
var a int= 20 /* 声明实际变量 */
var ip *int /* 声明指针变量 */
ip = &a /* 指针变量的存储地址 */
fmt.Printf("a 变量的地址是: %x\n", &a )
/* 指针变量的存储地址 */
fmt.Printf("ip 变量储存的指针地址: %x\n", ip )
/* 使用指针访问值 */
fmt.Printf("*ip 变量的值: %d\n", *ip )
}
// 输出
a 变量的地址是: 20818a220
ip 变量储存的指针地址: 20818a220
*ip 变量的值: 20
go 空指针
当一个指针被定义后没有分配到任何变量时,它的值为 nil。
nil 指针也称为空指针。
nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。
一个指针变量通常缩写为 ptr。
go
package main
import "fmt"
func main() {
var ptr *int
fmt.Printf("ptr 的值为 : %x\n", ptr )
// 通过判断指针变量是否等于 nil 判断其是否是 空指针
if(ptr != nil) {
fmt.Println("ptr 不是空指针")
}
if(ptr == nil) {
fmt.Println("ptr 是空指针")
}
}
指针不要操作没有合法指向的内存
go
var p * int
p = nil
fmt.Printf("p = %v", p)
*p = 666 //错误, 指针不要操作没有合法指向的内存
new函数的使用
go
//p是*int类型,指向int类型
p = new(int)
*p = 666
fmt.Printf("*p = %d", *p)
数组
数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。
因为数组的长度是固定的,所以在Go语言中很少直接使用数组。
数组的声明
声明语法 var 数组变量名 [元素数量]Type
数组变量名:数组声明及使用时的变量名。
元素数量:数组的元素数量,可以是一个表达式,但最终通过编译器计算的结果必须是整型数值,元素数量不能含有到运行时才能确认大小的数值。
Type:可以是任意基本类型,包括数组本身,类型为数组本身时,可以实现多维数组。
go
// 初始化一个数组变量 默认情况下,数组的每个元素都会被初始化为元素类型对应的零值,对于数字类型来说就是 0
var arr1 [5]int
// 将第一个元素赋值为1
arr1[0] = 1
// 2
arr2 := [5]int{1,2,4}
fmt.Println(arr2)
// 3 [...] 表示自动计算数组内 元素个数
arr3 := [...]int{1,12,3,4,5}
多维数组
Go语言中允许使用多维数组,因为数组属于值类型,所以多维数组的所有维度都会在创建时自动初始化零值,多维数组尤其适合管理具有父子关系或者与坐标系相关联的数据。
二维数组是最简单的多维数组,二维数组本质上是由多个一维数组组成的。
声明语法 var array_name [size1][size2]...[sizen] array_type
array_name
为数组的名字, array_type
为数组的类型size1、size2
, 等等为数组每一维度的长度。
go
// 声明一个 2×2 的二维整型数组
var array [2][2]int
// 设置每个元素的整型值
array[0][0] = 10
array[0][1] = 20
array[1][0] = 30
array[1][1] = 40
比较两个数组是否相等
[10]int
和 [20]int
是不同类型
如果两个数组类型相同(包括数组的长度,数组中元素的类型)的情况下,我们可以直接通过较运算符(==和!=)来判断两个数组是否相等。
只有当两个数组的所有元素都是相等的时候数组才是相等的,不能比较两个类型不同的数组,否则程序将无法完成编译。
go
a := [2]int{1, 2}
b := [...]int{1, 2}
c := [2]int{1, 3}
fmt.Println(a == b, a == c, b == c) // "true false false"
d := [3]int{1, 2}
fmt.Println(a == d) // 编译错误:无法比较 [2]int == [3]int
数组作为函数参数
数组作函数参数是值传递 如需要引用传递需要使用指针类型数据。
go
func printArray(arr [5]int) {
arr[1] = 100
for i, v:=range arr {
fmt.Println(i, v)
}
}
// 输出 数组作为参数传递到函数后更改元素值 不会更改其原始数组
0 1
1 100
2 4
3 0
4 0
数组遍历 - range
go
// range 返回两个参数 i 下标, v 数组元素
// _ 可用于忽略接收参数
func printArray2(arr *[5]int) {
arr[1] = 100
for i, v:=range arr {
fmt.Println(i, v)
}
}
切片
Go 语言切片是对数组的抽象。
Go 数组的长度不可改变,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
切片会动态扩容 每次为上一次的倍数(2的次方)
定义切片
切片不需要说明长度。var identifier []type
make
函数也可以创建切片 var slice1 []type = make([]type, len)
,简写 slice1 := make([]type, len)
,其中 []type
表示这是一个什么类型切片。
创建切片
go
var arr []int // 初始化一个切片
s1 := []int{1,2,3,4}
fmt.Printf("s1 = ", s1)
//make函数,格式(切片类型,长度,容量)
s2 := make([]int, 5, 10)
fmt.Printf("s1 = ", s2)
//make函数,格式(切片类型,长度) 长度和容量相同
s3 := make([]int, 5)
fmt.Printf("s3 = ", s3)
array := []int{1,2,3,4,5,6,7,8,9}
//操作某个元素,和数组操作方式一样
data := array[1]
fmt.Println("data = ", data)
s3 := array[:6]//从0开始,取6个元素,容量也是6,常用
fmt.Println("s3 = ", s3)
s4 := array[3:]//从下标为3开始,到结束
fmt.Println("s4 = ", s4)
切片操作
len()
获取切片长度
cap()
获取切片容量
appened(arr1, arr2...)
向且切片添加数据 ...将切片展开
copy()
拷贝切片
切片的删除通过 appened()
来间接实现
slice
可以向后扩展,不可以向前扩展
s[i]
不可以超越 len(s)
,向后扩展不可以超越底层数组 cap(s)
go
package main
import "fmt"
func main() {
// 数组定义 var 数组名称 [数据个数] 数组类型
// 切片定义 var 数组名称 [] 数组类型
// 空的切片 长度和容量 均为 0
// 切片扩容 初始化时确定数据长度
// 如果数据不超过 1024 按照 2倍扩容, 超过 1024 按照四分之一扩容
// 切片扩容 不不确定数据长度 -- 数据长度加二(但一定是二的倍数)
var sliceArr []int
fmt.Println(len(sliceArr), "11111111")
// 在append扩张切片时, 切片的地址可能会发生改变.
// 如果容量扩充导致输出存储溢出,切片会自动找寻新的空间进行存储,并与之删除之前的空间
sliceArr = append(sliceArr, 1)
fmt.Println(sliceArr)
fmt.Println("操作===================================")
sliceArr = append(sliceArr, 2, 4, 5, 6, 7, 8, 99, 0)
sliceArr[0] = 10
fmt.Println(cap(sliceArr))
fmt.Println(len(sliceArr))
var copyArr []int = make([]int, 10)
// 接受拷贝数据的数据 一定要有足够的容量, 否则无法拷贝 得到空数组
// 拷贝的两者 互不影响
copy(copyArr, sliceArr)
fmt.Println(copyArr, "拷贝的数据")
/* 创建切片 */
numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}
printSlice(numbers)
/* 打印原始切片 */
fmt.Println("numbers ==", numbers)
/* 打印子切片从索引1(包含) 到索引4(不包含)*/
fmt.Println("numbers[1:4] ==", numbers[1:4])
/* 默认下限为 0*/
fmt.Println("numbers[:3] ==", numbers[:3])
/* 默认上限为 len(s)*/
fmt.Println("numbers[4:] ==", numbers[4:])
numbers1 := make([]int, 0, 5)
printSlice(numbers1)
/* 打印子切片从索引 0(包含) 到索引 2(不包含) */
number2 := numbers[:2]
printSlice(number2)
/* 打印子切片从索引 2(包含) 到索引 5(不包含) */
number3 := numbers[2:5]
printSlice(number3)
}
func printSlice(x []int) {
fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
map 集合
Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。
需要注意的是map
作为函数参数传递是 是地址传递,函数内更改 map
会影响实参的值 除了slice,map,function
的内建类型都可以作为key
, Struct
类型不包含上次字段,也可以作为key
。
定义 Map
使用 map
关键字 或者 make
函数。
map
如果不初始化,那么就会创建一个 nil map
。nil map
不能用来存放键值对。
go
/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type
/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)
遍历map
使用 range
遍历 key
或者遍历 key、value
键值对,不保证遍历顺序,如需顺序,需手动对key
排序。
go
var mMap map[string]string = map[string]string {
"a" : "this is a",
"b" : "this is b",
}
var k, v string
for k, v = range mMap {
fmt.Printf(k, v)
fmt.Println("")
}
判断 map
中是否存在指定 key
用 value,ok:=m[key] 来判断是否存在 key
go
if v, ok :=mMap["a"]; ok {
fmt.Printf(v, ok)
} else {
fmt.Printf("ok is not exist")
}
delete函数
delete函数用于删除集合的元素, 参数为 map 和其对应的 key。
go
package main
import "fmt"
func main() {
/* 创建map */
countryCapitalMap := map[string]string{"France": "Paris", "Italy": "Rome", "Japan": "Tokyo", "India": "New delhi"}
fmt.Println("原始地图")
/* 打印地图 */
for country := range countryCapitalMap {
fmt.Println(country, "首都是", countryCapitalMap[ country ])
}
/*删除元素*/
delete(countryCapitalMap, "France")
fmt.Println("法国条目被删除")
fmt.Println("删除元素后地图")
/*打印地图*/
for country := range countryCapitalMap {
fmt.Println(country, "首都是", countryCapitalMap[ country ])
}
}
// 输出结果为
/*
原始地图
India 首都是 New delhi
France 首都是 Paris
Italy 首都是 Rome
Japan 首都是 Tokyo
法国条目被删除
删除元素后地图
Italy 首都是 Rome
Japan 首都是 Tokyo
India 首都是 New delhi
*/
struct 结构体
Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。
定义结构体及访问其成员
结构体定义需要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体中有一个或多个成员。type 语句设定了结构体的名称。
如果要访问结构体成员,需要使用点号 .
操作符 结构体.成员名"
go
package main
import "fmt"
// 声明一个结构体 Books 结构体名称
type Books struct {
title string
author string
subject string
book_id int
}
func main() {
// 初始化一个新的结构体
fmt.Println(Books{"Go 语言", "www.runoob.com", "Go 语言教程", 6495407})
// 也可以使用 key => value 格式
fmt.Println(Books{title: "Go 语言", author: "www.runoob.com", subject: "Go 语言教程", book_id: 6495407})
// 忽略的字段为 0 或 空
fmt.Println(Books{title: "Go 语言", author: "www.runoob.com"})
// 也可以这样初始化指定字段
// 通过.可以访问其成员
var BooksT Books
BooksT.title = "这是一个书名"
}
结构体作为函数参数
结构体可以和其他类型一样作为函数参数传递
go
package main
import "fmt"
type Books struct {
title string
author string
subject string
book_id int
}
func main() {
var Book1 Books /* 声明 Book1 为 Books 类型 */
var Book2 Books /* 声明 Book2 为 Books 类型 */
/* book 1 描述 */
Book1.title = "Go 语言"
Book1.author = "www.runoob.com"
Book1.subject = "Go 语言教程"
Book1.book_id = 6495407
/* book 2 描述 */
Book2.title = "Python 教程"
Book2.author = "www.runoob.com"
Book2.subject = "Python 语言教程"
Book2.book_id = 6495700
/* 打印 Book1 信息 */
printBook(Book1)
/* 打印 Book2 信息 */
printBook(Book2)
}
func printBook( book Books ) {
fmt.Printf( "Book title : %s\n", book.title)
fmt.Printf( "Book author : %s\n", book.author)
fmt.Printf( "Book subject : %s\n", book.subject)
fmt.Printf( "Book book_id : %d\n", book.book_id)
}
// 执行结果
Book title : Go 语言
Book author : www.runoob.com
Book subject : Go 语言教程
Book book_id : 6495407
Book title : Python 教程
Book author : www.runoob.com
Book subject : Python 语言教程
Book book_id : 6495700
结构体指针
结构体的指针类似于其他指针变量 var struct_pointer *Books
(指针变量可以存储结构体变量的地址)
查看结构体变量地址,可以将 & 符号放置于结构体变量前:struct_pointer = &Book1
指针结构体与普通结构体访问成员方式一致,使用点号 .
操作符 结构体.成员名"
go
type node struct {
value int
left *node
right *node
}
var root node
root = node{value:1}
//root := node{value:1}
root.left = &node{value:2}
root.left.right = &node{value:3}
root.right = new(node)
结构体匿名字段
go
type Person struct {
name string
sex byte
age int
}
type Student struct {
Person //只有类型,没有名字,匿名字段,继承了Person的成员
id int
}
func main() {
var s1 Student = Student{
Person: Person{},
id: 0,
}
fmt.Print(s1)
//自动推导类型
s2 := Student{
Person: Person{},
id: 0,
}
fmt.Println(s2)
}