Go基础语法
声明变量
func main(){
// 1.
// 定义时直接赋值
var i int = 10
// 2.
// 先定义后赋值
// 注意,在go语言中,即使不赋值变量也是有默认值的
var i int
i = 10
// 3.
// 根据类型推导
var i = 10
// 4.
// 使用:=替换var关键字
// 这种使用方式,只能在初始定义变量时使用
i := 10
}
定义全局变量
var n1 = 10
// 同时定义多个变量
var (
n1 = 10
n2 = 120
)
func main(){
}
数据类型
- 基本数据类型
- 数值型
- 整数类型
- int、int8、int16、int32、int64、uint、uint8、uint16、uint32、uint64、byte
- 浮点数
- float32、float64
- 整数类型
- 字符型(没有专门的字符型,使用byte来保存,中文字符需要int来存)
- 布尔型(bool)
- 字符串(string)
- 数值型
- 派生数据类型
- 指针、数组、结构体、管道、函数、切片、接口、map
基本数据类型
整数类型
整数类型主要有:int、int8、int16、int32、int64、uint、uint8、uint16、uint32、uint64、byte
其中int是定义整数的默认类型
int8表示:8位二进制的整数,同理int16表示16位的二进制整数
uint8表示:8位无符号的二进制整数,同理uint16表示无符号16位二进制整数
浮点数
go语言中有两种浮点数类型:float32和float64
默认定义使用的是float64
package main
import "fmt"
// 演示golang中小数类型的使用
func main() {
// go中默认定义的小数是float64
var price1 = 3.90
var price2 float32 = 2.0
fmt.Printf(`%T`, price1)
fmt.Printf(`%T`, price2)
}
字符类型
Golang中没有专门的字符类型,如果要存储单个字符,一般使用byte来保存
字符串就是一串固定长度的字符连接起来的字符序列。Go的字符串是由单个字节连接起来的。也就是说对于传统的字符串是由字符组成的,而Go的字符串不同,它是由字节组成的。
字符串
注意在Go中字符串属于基本数据类型,在go语言中string的默认值是空字符串。
go中的字符串也是不可变的
go中字符串处理的函数都在strings
包中
-
遍历字符串
var c4 = "测试狙击枪" for _, v := range c4 { fmt.Printf("%c", v) }
其他基本数据类型转字符串:
- 使用fmt.Sprintf()进行格式化
- 使用
strconv.Formatxxx
进行转换
字符串转基本数据类型
- 使用
strconv.Parsexxx
进行转换
使用反引号定义字符串
在使用双引号定义字符串时,如果字符串中存在特殊字符如:""
、\
等字符时,需要单独使用转义字符进行转义,十分麻烦
go中允许使用==`==来定义字符串,这种定义方式不会对其中的特殊字符进行处理
func main() {
var s = `sad\t\b\\asda`
// 这里不会对字符串进行任何转义,直接输出所有内容
println(s)
}
布尔类型
布尔类型在go中只占一个字节
数据类型转换
go语言中只有显示类型转换
T(v)
T:需要转换的类型
派生数据类型
指针
go语言中的指针不像C语言的指针那样复杂,只有对指针的基本赋值、访问等操作
- &a:取变量a的指针
- *a:表示对指针a所指向的内存空间进行赋值
package main
func main() {
a := 10
test(&a)
println(a)
}
func test(a *int) {
*a = 20
}
包
在go中每个go文件都必须有归属的包。
在包中使用其他包的内容需要通过import引入
包名命名
尽量保持package的名字和目录名字相同,不要和标准库冲突
变量名、函数名、常量名
采用驼峰命名
注意如果变量、函数、常量首字母大写就可以被其他包访问
导包
在.go文件中需要导入其他包时,导包路径是从$GOPATH/src/
后开始计算的,使用/
分割
go项目中import其他包时实际上是在引入路径下的目录(并不是按包名进行导入的)
例如对于目录:~/gocode/project/test下存在一个utils.go文件
utils.go文件中定义的包名并不是test,而是test2
此时在其他文件引入该文件时需要使用 gocode/project/test 而不是 gocode/project/test2
但是在程序代码层面,导入后调用对应方法时需要使用test2来进行调用
go命令
编译运行
go有两种运行方式:编译运行、直接运行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yDmobkkp-1651498935545)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20220420162304842.png)]
编译运行:
-
在cmd中可以直接通过
go build xx.go
将go文件编译为可执行文件,例如在Windows系统下执行此命令可以直接编译成.exe
文件来执行编译时编译器会将程序运行依赖的库文件包含在可执行文件中
直接运行:
go run xx.go
文件格式化
go语言提倡开发者编码统一规范化,
gofmt -w xxx.go
如果不加-w,不会写入源文件中
变量
声明变量
声明变量的方式主要有四种
func main() {
// 第一种:var 变量名 变量类型 = 变量值
var num1 int = 1
println(num1)
// 第二种:var 变量名 变量类型
// 这种定义方式变量的值为该类型的默认值
var num2 int
println(num2)
// 第三种:var 变量名 = 变量值
// go编译程序会自动根据变量值对变量进行类型推导
var num3 = 3
println(num3)
// 第四种:变量名 := 变量值
// 编译程序会自动根据变量值赋值并且省略掉 var关键字
sex := "男"
println(sex)
}
go中还可以同时定义多个变量
func main() {
// 声明多个变量
var n1,n2,n3 int
println(n1)
println(n2)
println(n3)
var n4,n5,n6 = "a",'a',10
println(n4)
println(n5)
println(n6)
}
变量作用域
变量分为:全局变量
和局部变量
全局变量定义在方法外部,局部变量定义在方法内
全局变量如果使用首字母大写进行命名,则其他包可以引用,如果首字母小写,则只能在本包中使用
变量、函数、常量同理
自定义变量类型
package main
// 定义新的变量类型,myAge
// 可以看作是对原有类型int的别名
// 但是go在编译时会认为myAge和int类型的变量是两种不同类型,所以不能直接转换
// 必要时需要用显示转换实现
type myAge int
func main() {
var age myAge = 10
println(age)
// 这里age2和age类型不同所以直接赋值会报错
var age2 int = age
}
拓展知识
随机数
使用rand时需要设置一个seed值,如果seed值不变的话,每次运行得到的随即值都相同
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().Unix())
//seed 的参数不变的话 -> 此程序的运行环境是固定的,因此 rand.Intn 总是会返回相同的数字 ->所以要讲seed的参数一直改变(获取时间)
fmt.Printf("随机产生的数字是 : %d \n", rand.Intn(100))
}
转义字符
转移字符 | 解释 |
---|---|
\n | 换行 |
\b | 退格 |
\r | 光标回到开始,后续输入替换前面的输入 |
\t | 制表符 |
\c | 格式化输出(例如将int值按编码进行输出) |
判断-循环
IF判断
func main() {
var score float64
if fmt.Scanln(&score); score > 100 || score < 0 {
println("错误成绩")
} else if score >= 60 {
println("及格")
}
}
SWITCH判断
go中的switch不需要单独加break,自动case结束后出判断。如果需要让case继续向下穿透需要加关键字fallthrough
// 写法一
switch score {
case 100 :
println("")
case 10:
println("")
default:
println()
}
// 写法二
switch {
case score<0 || score>100:
println("非法数值")
case score>60:
println("及格")
}
For循环
// for循环
for i := 0; i < 5; i++ {
println(i)
}
// 使用for循环实现while循环
var i = 0
for i < 10 {
println(i)
i++
}
For Range循环
func main() {
for key, val := range "sdaas" {
fmt.Printf("%v:%c\n", key, val)
}
}
函数
- go中的函数都是值传递
- 支持多个返回值
- 不支持函数重载,即任意函数不能存在多个同名函数(以包为单位,即使函数在两个文件中,只要包名相同则不能同名)
- 支持可变数量的参数
// 函数一:直接调用的函数
func test(nums []int) {
for i, _ := range nums {
nums[i] *= 2
}
for i := range nums {
println(nums[i])
}
//println(nums)
}
// 函数二:只能由具体的结构体调用
func (user User) getName() string {
return user.name
}
函数做参数
Go语言中的函数可以作为变量进行赋值
func main() {
var a func(int) = test1
test2(10, 20, a)
}
func test1(num int) {
println(num)
}
func test2(num1 int, num2 int, fun func(int)) {
fun(num1 + num2)
}
Init函数
每个.go
文件都可以设置一个init()
函数,所有定义的init函数都会在main函数启动前执行
执行顺序
package main
import (
"go_code/project01/learn-01/init/db"
)
var num int = test()
func test() int {
println("main.test")
return 10
}
func init() {
println("main.init")
}
func main() {
db.Test()
}
输出:
db.test
db.init
main.test
main.init
db.Test
从上面可以看到输出顺序为:
- main包中引入的其他包先进行初始化
- 引入包的变量进行初始化
- 引入包的init函数的执行
- main包下的变量初始化
- main包下的init函数执行
- main函数执行
匿名函数
func main() {
// 匿名函数
ret := func(num1 int, num2 int) int {
return num1 + num2
}(10, 20)
println(ret)
}
结构体
和类的概念相似,但是结构体中不能对方法进行定义,只能在外部对
func (user User) getName() string {
return user.name
}
type User struct {
name string
age int
}
闭包
应用场景:闭包可以保留上次引用的某个值,我们传入一次就可以反复使用
package main
func getSum() func(num int) int {
var sum = 0
return func(num int) int {
sum += num
return sum
}
}
func main() {
var fun = getSum()
println(fun(1))
println(fun(2))
println(fun(3))
}
/**
输出
1
3
6
*/
从上面可以看出,sum变量被传入了函数fun中,所以后续对函数进行操作时sum的值进行了累计,此时sum值是属于函数对象fun的
进阶闭包
package main
func getSum() func(num int) int {
var sum = 0
return func(num int) int {
sum += num
return sum
}
}
func main() {
var fun = getSum()
println(fun(1))
println(fun(2))
println(fun(3))
var fun2 = getSum()
println(fun2(1))
println(fun2(10))
}
package main
func getSum() func(num int) int {
var sum = 0
return func(num int) int {
sum += num
return sum
}
}
func main() {
var fun = getSum()
println(fun(1))
println(fun(2))
println(fun(3))
var fun2 = getSum()
println(fun2(1))
println(fun2(10))
}
/**
输出
1
3
6
1
11
*/
系统函数
字符串函数
所有内置函数都存放在 builtin
包下
-
统计字符串的长度按字节进行统计
len("沈洋") // 输出值为6
-
字符串遍历
// 方法一 r := []rune("芜湖起飞") for _, v := range r { fmt.Printf("%c", v) } // 方法二 for _, v := range "芜湖起飞" { fmt.Printf("%c", v) }
-
字符串转整数
n, err := strconv.Atoi("1撒旦撒2") if err != nil { println("数值错误") return } println(n)
-
整数转字符串
str := strconv.Itoa(10) println(str)
-
查找子串是否在指定的字符串中
var flag bool = strings.Contains("javaandgolang", "go") println(flag)
-
统计一个字符串有几个指定的字串
var count = strings.Count("javaandgolang","a") println(count)
-
不区分大小写的字符串进行比较
var same = strings.EqualFold("java", "Java") println(same)
-
返回字串在字符串第一次出现的索引值,如果没有返回-1
var index = strings.Index("javaandgolang", "l") println(index)
-
字符串替换
var replaceStr = strings.Replace("goandjava", "go", "golang", -1) println(replaceStr)
-
按照指定的某个字符进行分割,返回字符串数组
str = "hell nice to meet you" var strArray = strings.Split(str, " ") for i := range strArray { print(strArray[i]) if i != len(strArray)-1 { print("_") } }
-
将字符串进行大小写替换
str = strings.ToUpper(str) str = strings.ToLower(str)
-
去掉字符串左右部分
// 取出字符串左右空格 strings.TrimSpace(str) // 去除字符串左右的~ strings.Trim("str","~") // 去除字符串左边的~ strings.TrimLeft(str,"~") // 去除字符串右边的~ strings.TrimRight(str,"~")
-
判断字符串是否以指定字符串开头
strings.HasPrefix("http://www.baidu.com","http")
-
判断字符串是否以指定字符串结尾
strings.HasSuffix()s("http://www.baidu.com", "com")
日期函数
-
获取当前时间
var now time.Time = time.Now() fmt.Println(now)
-
获取当前年、月、日、时、分、秒等
var now = time.Now() fmt.Println(now.Year()) fmt.Println(now.Month()) fmt.Println(now.Day()) fmt.Println(now.Hour()) fmt.Println(now.Minute()) fmt.Println(now.Second())
-
格式化日期
注意格式化中每个数字是固定的,不能随便修改
var now time.Time = time.Now() fmt.Println(now.Format("2006-01-02 Jan"))
内置函数
内置函数是指go中提供的不需要导入包的函数,内置函数是builtin包下的函数
-
len函数
获取数组、切片、字符串(注意如果带中文需要先转成rune数组,因为其底层是按字节数来统计的)等变量的长度
var arr [10]int fmt.Println(len(arr))
-
new函数
分配内存,用于分配值类型(int系列、float系列、bool、string、数组、结构体)
返回值为指针
package main func main() { // new函数返回的是指针 var sy *Person = new(Person) println(sy.age) println(sy.name) } type Person struct { name string age int }
-
make函数
分配内存,用来分配引用类型(指针、slice、map、管道、interface等)
错误处理
go中使用defer+recover
实现
defer
defer定义的语句不会立即执行,程序会将defer语句压入一个栈中,等函数结束后依次执行栈中的语句
在一个函数中连续定义的两个defer语句会先执行后面的语句
recover
内置函数,允许程序管理Panic过程中的Go程。在defer的函数中,执行recover调用会取回传至panic调用的错误值,恢复正常执行,停止恐慌过程。若recover在defer函数之外被调用,它将不会停止Panic。
处理异常的方式主要有两种:①在方法中处理②抛出异常到方法外部
在当前函数中处理异常
package main
import "fmt"
func main() {
ret := test(10, 1)
fmt.Println(ret)
}
func test(num1 int, num2 int) int {
defer func() {
err := recover()
if err != nil {
fmt.Printf("捕获到异常:%v\n", err)
return
}
println("没有发生异常")
}()
sum := num1 / num2
return sum
}
抛出异常
package main
import (
"errors"
"fmt"
)
func main() {
ret, err := test2(10, 0)
if err != nil {
println("err:" + err.Error())
}
}
func test2(num1 int, num2 int) (int, error) {
if num2 == 0 {
return 0, errors.New("除数不能为0")
}
sum := num1 / num2
return sum, nil
}
数组
在go语言中,数组的类型取决于:基本元素类型+数组长度
与其他语言不同在go中[3]int
和[4]int
不是同一种数据类型并且
[]int
是切片类型,不是数组
数组的定义方式:
// 方法一:直接指定数组长度并静态初始化值
var arr = [3]int{3, 6, 9}
// 方法二:...表示由编译器自动根据后面{}中元素的个数进行推断
// 注意这里不能使用 var arr2 = []int{10,2,3,4}
// 这种方式也能进行定义,但定义出来的arr2不是数组是切片数据类型
var arr2 = [...]int{10, 2, 3, 4}
// 方法三:直接指定大小并且采用默认值
var arr3 [100]int
与其他语言不同的是在go中如果按如下定义方法
这里的arr
其实并不是一个数组,也就是说如果你按上面定义数组的方式进行定义并尝试传入test,将会报错
func test(arr []int) {
for i := range arr {
println(arr[i])
arr[i] += 1
}
}
正确的方式是
func test(arr [3]int){
for i := range arr {
println(arr[i])
arr[i] += 1
}
}
go中所有数组类型都需要指定大小,否者为切片类型
注意
并且在go中数组传递的是数组元素的拷贝,即方法中对数组进行修改后,不会影响外部
而切片传递的是引用,方法内的修改可以影响到外部
切片
切片是go中特有的数据类型,类似于Java中的List
-
切片就是一个结构体,它有三个重要参数:引用数组地址、长度、容量
长度和容量,分别可以使用函数
len()
、cap()
获取到-
长度表示当前切片有元素的个数(遍历和访问都不能超过len的长度)
-
容量表示切片底层使用的数组实际的大小,当长度等于容量时,再继续添加元素时则底层会自动对引用的数组进行扩容和ArrayList相似。注意每次扩容后底层引用的数组和原来的数组不同
-
-
切片会自动扩容
func main() {
// 定义数组
var intarr [6]int = [6]int{1, 2, 3, 4, 5, 6}
// 切片是构建在数组之上的
// 定义一个切片名字为slice,[]动态变化的数组长度不写
// [1:3]切片 - 表示从数组下标1开始 切到下标3之前(不包括3)
slice := intarr[1:3]
// 输出数组
fmt.Println("intarr:", intarr)
// 输出切片
fmt.Println("slice:", slice)
// 切片元素个数 - 2
fmt.Println(len(slice))
// 切片容量 - 5
// slice是从intarr中切出来的,所以此时slice底层使用的数组其实就是intarr
// slice初始是切的是[1:3]所以底层引用时其数组的容量为intarr.len-1
// 如果后续向slice中添加元素超过cap-5之后,切片会进行扩容,扩容后cap增加并且
// 扩容后底层使用的数组将不再是intarr
fmt.Println(cap(slice))
}
注意事项
-
切片的默认值是
nil
,切片定义完后不能直接使用,因为本身是一个空的,需要引用其他数组或者make一个空间供切片来使用 -
cap是内置函数,用于统计切片的容量,即最大可以存放多少各元素(可以扩容)
-
切片可以继续切片
-
切片不能越界访问(0~len(slice)-1)
创建
方式一
// 定义数组
var intarr [6]int = [6]int{1, 2, 3, 4, 5, 6}
// 切片是构建在数组之上的
// 定义一个切片名字为slice,[]动态变化的数组长度不写
// [1:3]切片 - 表示从数组下标1开始 切到下标3之前(不包括3)
slice := intarr[3:]
方式二
// make(切片类型,长度、初始容量)
var slice []int = make([]int,5,10)
方式三、
var slice []int=[]int{1,2,3}
String和Slice关系
- string底层是一个byte数组,因此string也可以进行切片处理
- string和
判断变量地址
package main
import (
"fmt"
)
func main() {
slice1 := []int{10, 20, 30}
slice2 := slice1
i1 := fmt.Sprintf("%p", slice1)
i2 := fmt.Sprintf("%p", slice2)
if i1 == i2 {
println("地址相等地址为:", i1)
} else {
println("地址不同,slice1地址为:", i1, ",slice2地址为:", i2)
println()
}
slice2[2] = 100
slice2 = append(slice1, 10)
slice2[0] = 1000
slice2 = slice2[0:3]
slice2[0] = 10
i1 = fmt.Sprintf("%p", slice1)
i2 = fmt.Sprintf("%p", slice2)
if i1 == i2 {
println("地址相等地址为:", i1)
} else {
println("地址不同,slice1地址为:", i1, ",slice2地址为:", i2)
println()
}
}
Map
基本语法
var map 变量名 map[keytype]valuetype
注意声明是不会分配内存的,初始化需要make,分配内存才能赋值和使用
func main() {
// 方法一
var reMap1 map[int]string = make(map[int]string, 10)
reMap1[10] = "沈洋"
println(reMap1[10])
// 方法二
var reMap2 map[int]string
reMap2 = make(map[int]string, 10)
println(reMap2)
// 方法三
var reMap3 map[int]string = map[int]string{1: "12312"}
println(reMap3)
}
增删改查
增
go中的map直接赋值如果键值对不存在则新增
var reMap1 map[int]string = make(map[int]string, 10)
reMap1[10] = "沈洋"
改
go中的map直接赋值如果键值对存在则修改
var reMap1 map[int]string = make(map[int]string, 10)
reMap1[10] = "沈洋"
查
reMap[10]
会返回两个值,value
和ok
,ok
是bool类型的值
ok
为``true`时表示值存在,否者值不存在,使用时注意先判断是否存在再拿值,否者map会针对不存在的key返回默认值
reMap3 = make(map[int]string, 10)
reMap3[10] = "沈洋"
var name, ok = reMap3[10]
if ok {
println(name)
}
删
delete(map,“key”),delete是一个内置函数,如果key存在,删除该key-value。如果key不存在,不操作并且不会报错
var reMap3 map[int]string = map[int]string{1: "12312"}
println(len(reMap3))
delete(reMap3, 1)
println(len(reMap3))
删除全部map,map中没有clear方法来直接删除所有key-value
- 可以遍历map来依次删除所有key-value
- 直接将
map = make(map[string]int,10)
,来将原来的map成为垃圾,gc回收