语法
1. 格式
1.1、包
package:main函数所在包,一定是main包
1.2、导包
import:导包
// 单个包
import "fmt"
// 多个包
import (
"fmt"
"time"
)
1.3、分号
go 每行结束,可写可不写 ;
推荐是不写
1.4、函数
定义函数, { 必须与函数名在同一行
2. 变量声明
2.1、单变量声明
默认初始值
| 类型 | 初始值 |
|---|---|
| int | 0 |
| float | 0 |
| bool | false |
| string | "" |
声明方式
// 方法一: 带类型,无初始值
var a int
// 方法二: 带类型,有初始值
var b int = 100
// 方法三: 不戴类型,有初始值
var c = 100
// 方法四: 省去var,指定初始值
d := 100
打印类型:%T
var a int
fmt.Printf("type of a = %T\n", a) // type of a = int
2.2、多变量声明
// 单行写法
var xx, yy int = 100, 200
var kk, ll = 100, "Aceld"
// 多行写法
var (
vv int = 100
jj bool = true
)
2.3、全局、局部变量声明
其他三种方式都支持
:= 方式不支持,不能声明全局变量
3. 常量与iota
3.1、常量
常量使用 const 声明,只读,初始化后不能更改
可用于定义 枚举
const {
BEIJING = 0
SHANGHAI = 1
SHENZHEN = 3
}
3.2、iota
iota 只能在 const 中使用
iota 可以理解为 所在第几行,iota 初始值为0
iota 用于指定当前行,即以下行的常量的赋值规则
const (
// 每行的iota都会累加1, 第一行的iota的默认值是0
BEIJING = iota // iota = 0
SHANGHAI // iota = 1
SHENZHEN // iota = 2
)
const (
a, b = iota+1, iota+2 // iota = 0, a = iota + 1, b = iota + 2, a = 1, b = 2
c, d // iota = 1, c = iota + 1, d = iota + 2, c = 2, d = 3
e, f // iota = 2, e = iota + 1, f = iota + 2, e = 3, f = 4
g, h = iota * 2, iota *3 // iota = 3, g = iota * 2, h = iota * 3, g = 6, h = 9
i, k // iota = 4, i = iota * 2, k = iota * 3 , i = 8, k = 12
)
4. 函数
4.1、单返回值
返回值类型,指定在后面
func foo1(a string, b int) int {
return 100
}
4.2、多返回值
有名的返回值 会被定义为 局部变量并赋上初始值
// 返回多个返回值,匿名的
func foo2(a string, b int) (int, int) {
return 666, 777
}
// 返回多个返回值,有形参名称的
func foo3(a string, b int) (r1 int, r2 int) {
// r1 r2 属于foo3的形参,初始化默认的值是0
// r1 r2 作用域空间 是foo3 整个函数体的{}空间
fmt.Println("r1 = ", r1) // 0
fmt.Println("r2 = ", r2) // 0
// 给有名称的返回值变量赋值
r1 = 1000
r2 = 2000
return r1,r2
}
func foo4(a string, b int) (r1, r2 int) {
// 给有名称的返回值变量赋值
r1 = 1000
r2 = 2000
return r1,r2
}
4.3、匿名函数
func main() {
res := func(n1 int, n2 int) int {
return n1 * n2
}(10, 20)
fmt.Printf("res: %v\n", res)
}
5. 导包
5.1、包作用链
在导入包时
- 会先解析其导入的子包
- 解析全局常量
- 调用 当前包的 init函数
注意:在导包时 要写清楚 包在 GOPATH (配置的go工程路径) 下的完整路径
package lib1
import "fmt"
func init() {
fmt.Println("init...")
}
func main() {
fmt.Println("hello world!")
}
// 导包
package main
import "GoStudy/lib/lib1" // %GOPATH/GoStudy/lib/lib1
func main() {
}
5.2、导包方式
Go 编译检查非常严格,导入的包必须使用,否则会出现语法报错
如果只想使用 包的 init 方法,而不想使用其中的函数,则需要使用匿名导包的方式
- import _ "fmt":匿名导包
给 fmt 包一个匿名, ⽆法使用该包的⽅法,但是会执行该包内部的 init() 方法
- import aa "fmt":别名导包
给 fmt 包起一个别名 aa,可以用别名直接调用:aa.Println()
- import . "fmt":导入到当前
将 fmt 包中的全部方法,导入到当前包的作用域中,全部方法可以直接调用,无需 fmt.API 的形式
6. 指针
Go 语言中也有指针、二级指针的概念
7. defer
defer 定义的行,会在整个函数声明周期结束后执行,在 return 之后执行
defer 的声明是 压栈 的方式,先声明的 defer 后执行
// main::hello go
// main end
func main() {
defer fmt.Println("main end")
fmt.Println("main::hello go ")
}
8. if-else
- 后面不需要括号,如果写了,编译器会自动去掉
- if 后面需要直接跟 { , 不可让 if 与 判定后的业务语句同行
9. for
Go 中 没有 while、dowhile循环,只有唯一一种 for循环
9.1、for循环
- 后面没有 ( )
- 三个条件,任意一个都可以省略
9.2、遍历数组
类似于 Python
// 普通遍历
for i:=0; i<len(arr); i++{
fmt.Println(arr[i])
}
// range 遍历
for index,value := range arr{
fmt.Println("index ==>",index," value ==>",value)
}
// range 遍历任意一个都可以匿名,即不使用
for _,value := range arr{
fmt.Println("index ==>",index," value ==>",value)
}
10. switch
- 不加 ( )
- 不需要加 break,匹配成功后会自动 break,不会出现 switch 穿透情况
- switch 后面可以不知道当前匹配的变量,直接在 case 中指定匹配条件
11. 数组
数组大差不差,一般使用切片(动态数组)
11.1、关于类型
数组的 长度和类型 是一个整体,[4]int & [10]int 不是一个类型,在函数传参时 不能认为 [10]int > [4]int 就随意传递
11.2、关于传递
数组之间的传递是 值传递,传递的是值的副本
func main() {
var arr1 [4]int
var arr2 = arr1
arr2[0] = 2
fmt.Println(arr1) // 0 0 0 0
fmt.Println(arr2) // 2 0 0 0
}
12. 切片
切片与数组类似,不过在定义时不指定数组的大小,所以切片的大小是动态的
切片底层 有一个数组,切片的变量名 指向底层数组的首地址
12.1、关于传递
切片与数组不同,切片是 引用传递
func main() {
arr1 := []int{0,0,0,0}
var arr2 = arr1
arr2[0] = 2
fmt.Println(arr1)
fmt.Println(arr2)
}
12.2、关于声明
// 1 声明一个切片,并且初始化,默认值是1,2,3,长度是3
slice1 := []int{1, 2, 3} // [1 2 3]
// 2 声明一个切片,但是没有给它分配空间
var slice2 []int // slice2 == nil
// 开辟3个空间,默认值是0
slice2 = make([]int, 3) // [0 0 0]
// 3 声明一个切片,同时给slice分配3个空间,默认值是0
var slice3 []int = make([]int, 3) // [0 0 0]
// 4 声明一个切片,同时给slice分配3个空间,默认值是0,通过:=推导出slice是一个切片
slice4 := make([]int, 3) // [0 0 0]
12.3、关于空间
切片中有两个 空间的概念
- 长度 len:合法空间
- 容量 cap:总空间(可使用空间)
当声明一个切片,且不指定 cap时 ,cap默认等于 len
当追加后超出原 cap,切片会扩容为原来的 2倍,类似 Java 的 HashMap(不过 Java 是根据随机因子)
注意:追加完 将结果赋值回原切片
// len = 3 , cap = 5
var numbers = make([]int, 3, 5)
// 当 追加后 len <= cap 时,直接追加
numbers = append(numbers, 3)
// 当追加后 len > cap, 切片会扩容为原来的 2倍
numbers = append(numbers, 3)
fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)
12.4、关于截取
截取 切片,与 Python 的序列截取类似,左闭右开
但是截取是浅拷贝,子切片改变,也会导致 原切片改变
func main() {
numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}
fmt.Println("numbers[1:4] ==", numbers[1:4])
fmt.Println("numbers[:3] ==", numbers[:3])
fmt.Println("numbers[4:] ==", numbers[4:])
numbers3 := numbers[2:5]
fmt.Println(numbers3)
}
12.5、关于拷贝
上述 直接截取是浅拷贝
Go 提供了一种 切片的深拷贝方式 -> copy
注意:此处的 des、src,与 Java 是反着的
slice1 := []int{1, 2, 3}
slice2 := make([]int, 3)
copy(slice2, slice1)
slice2[0] = 10
fmt.Println(slice1) // [1 2 3]
13. map
13.1、声明方式
①:使用时,再分配空间
// 声明一种map类型 key - string,value - string
var myMap1 map[string]string
fmt.Println(myMap1 == nil) // true
// 使用map前,需要先用make给map分配数据空间
myMap1 = make(map[string]string, 10)
myMap1["one"] = "java"
myMap1["two"] = "c++"
myMap1["three"] = "python"
②:声明时就 分配空间
myMap2 := make(map[int]string)
myMap2[1] = "java"
myMap2[2] = "c++"
myMap2[3] = "python"
fmt.Println(myMap2)
③:声明时,初始化空间
myMap3 := map[string]string {
"one": "php",
"two": "c++",
"three": "python",
}
fmt.Println(myMap3)
13.2、CRUD
- 删除:delete( map , key )
func main() {
cityMap := make(map[string]string)
// 添加
cityMap["China"] = "Beijing"
cityMap["Japan"] = "Tokyo"
cityMap["USA"] = "NewYork"
// 删除
delete(cityMap, "China")
// 修改
cityMap["USA"] = "DC"
// 遍历
for key, value := range cityMap {
fmt.Println("key = ", key+", value = ", value)
}
}
13.3、传递
map 之间的传递是引用传递
13.4、判断存在
两个返回值
- 是否存在
- 值
func main() {
m := make(map[string]interface{})
m["a"] = "AAA"
if v, ok := m["a"]; ok {
fmt.Println("存在", v)
} else {
fmt.Println("不存在")
}
}
14. type
14.1、type
type 可以单独用于定义一种新的数据类型,即给类型取别名
type myint int
func main() {
var a myint = 10
fmt.Println("a = ", a)
fmt.Printf("type of a = %T\n", a) // type of a = main.myint
}
15、struct
struct 用于定义结构体,与 type 配合,即可以定义一个 新的 (由 "基本类型" 构成的)复杂结构类型,类似于 Java 的类
type Book struct {
title string
price string
}
func main() {
var book Book
book.title = "Golang"
book.price = "111"
fmt.Printf("%v\n", book) // {Golang 111}
}
15.1、struct传递
struct 之间的传递 是值传递,即传递一个副本,想要修改 外部的值,则需要定义为指针
一道面试题
type student struct {
name string
age int
}
func main() {
m := make(map[string]*student)
stus := []student{
{name: "aaa", age: 18},
{name: "bbb", age: 23},
{name: "ccc", age: 28},
}
for _, stu := range stus {
m[stu.name] = &stu // 注意此处,每一个v,其实记录的都是 stu的地址值,所有key的v都是同一个stu
}
// aaa => ccc bbb => ccc ccc => ccc
for k, v := range m {
fmt.Println(k, "=>", v.name) // 此时取,v.name 都是同一个 地址的name,即最后一次更新的 stu
}
}
// 解决
for _, stu := range stus {
// 方法1
temp := stu // 每次创建一个新变量,值传递更新一个地址
m[stu.name] = &temp
}
for i, stu := range stus {
// 方法2
m[stu.name] = &stus[i] // 指定 第几个的地址
}
15.2、封装
Golang 中,类名、属性名、方法名 首字母大写 表示对外(其他包)可以访问,否则只能够在本包内访问
// 如果类名首字母大写,表示其他包也能够访问
type Hero struct {
// 如果类的属性首字母大写, 表示该属性是对外能够访问的,否则的话只能够类的内部访问
Name string
Ad int
level int // 只能本包访问
}
15.3、结构体方法
func( 绑定结构体 )
因为 结构体之间的传递是 值传递,如果不加 * ,则是传递当前对象的一个副本,无法实现对当前对象的 写
所以,要加上 * 来实现对 对象的读写操作
func (h *Hero) Show() {
fmt.Println("Name = ", h.Name)
fmt.Println("Ad = ", h.Ad)
fmt.Println("Level = ", h.level)
fmt.Println("---------")
}
func (h *Hero) GetName() string {
return h.Name
}
// 不用指针则传递的是副本,无法赋值
func (h *Hero) SetName(newName string) {
h.Name = newName
}
func main() {
hero := Hero{Name: "zhang3", Ad: 100}
hero.Show()
hero.SetName("li4")
hero.Show()
}
15.4、继承
-
继承,直接在子类写入父类的结构体名即可
-
对象
- 在创建对象时,父类的属性单独写
- 或者,先不初始化,后面再通过 .Attr 来赋值即可(不区分 父子属性)
// 父类
type Human struct {
name string
sex string
}
func (h *Human) Eat() {
fmt.Println("Human.Eat()...")
}
func (h *Human) Walk() {
fmt.Println("Human.Walk()...")
}
// 子类
type SuperMan struct {
Human // SuperMan类继承了Human类的方法和属性
level int
}
// 重定义父类的方法Eat()
func (s *SuperMan) Eat() {
fmt.Println("SuperMan.Eat()...")
}
// 子类的新方法
func (s *SuperMan) Fly() {
fmt.Println("SuperMan.Fly()...")
}
func main() {
// 定义一个子类对象
// s := SuperMan{Human{"li4", "female"}, 88}
var s SuperMan
s.name = "li4"
s.sex = "male"
s.level = 88
s.Walk() // 父类的方法
s.Eat() // 子类的方法
s.Fly() // 子类的方法
}
16. interface
16.1、本质
接口:本质是一个指针,所以 传入的是 子类的地址
面试题
// 编译能通过吗?
type People interface {
Speak(string) string
}
type Student struct{}
func (stu *Student) Speak(think string) (talk string) {
if think == "hello" {
talk = "你好"
} else {
talk = "hi"
}
return
}
func main() {
var peo People = Student{}
think := "wow"
fmt.Println(peo.Speak(think))
}
// 不能
// 不能。修改 var peo People = Student{} 为 var peo People = &Student{} 即可
16.2、实现
要实现接口,满足两个条件
接口定义方法
// 本质是一个指针
type AnimalIF interface {
Sleep()
GetColor() string // 获取动物的颜色
GetType() string // 获取动物的种类
}
子类实现父类的全部方法
// 具体的类
type Cat struct {
color string // 猫的颜色
}
func (c *Cat) Sleep() {
fmt.Println("Cat is Sleep")
}
func (c *Cat) GetColor() string {
return c.color
}
func (c *Cat) GetType() string {
return "Cat"
}
多态
// 接口的数据类型,父类指针
var animal AnimalIF
animal = &Cat{"Green"}
animal.Sleep() // 调用的就是Cat的Sleep()方法, 多态
16.3、空接口+断言
interface{ } 表示空接口,可以用它引用任意类型的数据类型
与 Java 中的 Object类似
// 示例
// interface{}是万能数据类型
func myFunc(arg interface{}) {
fmt.Println(arg)
}
type Book struct {
auth string
}
func main() {
book := Book{"Golang"}
myFunc(book)
myFunc(100)
myFunc("abc")
myFunc(3.14)
}
断言
// if判断
func myFunc(arg interface{}) {
// 类型断言: 值、bool
value, ok := arg.(string)
if !ok {
fmt.Println("arg is not string type")
} else {
fmt.Println("arg is string type, value = ", value)
fmt.Printf("value type is %T\n", value)
}
}
// switch 选择
func justifyType(x interface{}) {
switch v := x.(type) {
case string:
fmt.Printf("x is a string,value is %v\n", v)
case int:
fmt.Printf("x is a int is %v\n", v)
case bool:
fmt.Printf("x is a bool is %v\n", v)
default:
fmt.Println("unsupport type!")
}
}
17. Error
17.1、捕获异常
func main() {
// 定义并调用
defer func() {
if err := recover(); err != nil {
fmt.Println("捕获:", err) // 捕获: runtime error: index out of range [4] with length 3
}
}()
nums := []int{1, 2, 3}
fmt.Println(nums[4]) // 系统抛出异常
}
17.2、抛出异常
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println("捕获:", err)
}
}()
panic("出现异常!") // 手动抛出异常 // 捕获: 出现异常!
}
17.3、返回异常
func getCircleArea(radius float32) (area float32, err error) {
if radius < 0 {
// 构建异常对象
err = errors.New("半径不能为负")
return
}
area = 3.14 * radius * radius
return
}
func main() {
area, err := getCircleArea(-5)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(area)
}
}
17.4、自定义异常
type PathError struct {
path string
op string
createTime string
message string
}
func (p *PathError) Error() string {
return fmt.Sprintf("path=%s \nop=%s \ncreateTime=%s \nmessage=%s",
p.path, p.op, p.createTime, p.message)
}
func Open(filename string) error {
file, err := os.Open(filename)
if err != nil {
return &PathError{
path: filename,
op: "read",
message: err.Error(),
createTime: fmt.Sprintf("%v", time.Now()),
}
}
defer file.Close()
return nil
}
func main() {
err := Open("test.txt")
switch v := err.(type) {
case *PathError:
fmt.Println("get path error,", v)
default:
}
}
18. string
因为 Go 使用 UTF-8 编码,UTF-8 的 Unicode 中文占 3个字节
18.1、操作
package main
import (
"fmt"
"strings"
)
func main() {
a := "hello"
fmt.Println(strings.Contains(a, "ll")) // true
fmt.Println(strings.Count(a, "l")) // 2
fmt.Println(strings.HasPrefix(a, "he")) // true
fmt.Println(strings.HasSuffix(a, "llo")) // true
fmt.Println(strings.Index(a, "li")) // -1
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
fmt.Println(strings.Repeat(a, 2)) // hellohello
fmt.Println(strings.Replace(a, "e", "E", -1)) // hEllo
fmt.Println(strings.Split("a-b-c", "-")) // [a b d]
fmt.Println(strings.ToLower(a)) // hello
fmt.Println(strings.ToUpper(a)) // HELLO
fmt.Println(len(a)) // 5
b := "你好"
fmt.Println(len(b)) // 6
}
18.2、打印
与 C 的printf类似
- %v : 可以打印任意类型数据
- %+v : 打印详细信息(带上属性名)
- %#v: 打印更详细的信息(属性名+类名)
package main
import (
"fmt"
)
type point struct {
x, y int
}
func main() {
s := "hello"
n := 123
p := point{1, 2}
fmt.Println(s, n) // hello 123
fmt.Println(p) // {1 2}
fmt.Printf(" s=%v \n", s) // s=hello
fmt.Printf(" n=%v\n", n) // n=123
fmt.Printf(" p=%v\n", p) // p={1 2}
fmt.Printf(" p=%+v\n", p) // p={x:1 y:2}
fmt.Printf(" p=%#v \n", p) // p=main.point{x:1, y:2}
f := 3.141592653
fmt.Println(f) // 3.141592653
fmt.Printf("%.2f\n", f) // 3.14
}
18.3、json
Go 数据 <===> Json:
如果想改变 转为 Json后的字段名,使用
Age int `json:"age"` // 指定字段名 注意 ``
-
Marshal:Go ==> json
- 返回值:byte数组 + error
-
MarshalIndent:go ==> json
-
参数:数据 + 前缀 + 缩进
-
返回值: byte数组 + error
-
-
Unmarshal:json ==> Go
-
参数: 传化后的变量地址 + 数据
-
返回值:error
-
package main
import (
"encoding/json"
"fmt"
)
type userInfo struct {
Name string
Age int `json:"age"`
Hobby []string
}
func main() {
a := userInfo{Name: "wang", Age: 18, Hobby: []string{" Golang", "TypeScript"}}
buf, err := json.Marshal(a)
if err != nil {
panic(err)
}
fmt.Println(buf)
fmt.Println(string(buf))
buf, err = json.MarshalIndent(a, "", "\t")
if err != nil {
panic(err)
}
fmt.Println(string(buf))
var b userInfo
err = json.Unmarshal(buf, &b)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", b)
}
19. 时间
package main
import (
"fmt"
"time"
)
func main() {
// 获取当前时间
now := time.Now()
fmt.Println(now) // 2023-05-12 17:22:05.9443335 +0800 CST m=+0.002569701
fmt.Println(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), now.Nanosecond()) // 2023 May 12 17 22 5 944333500
// 返回值是string
t := now.Format("2006-01-02 15:04:05")
fmt.Println(t) // 2023-05-12 17:22:05
// 注意时区
t3, _ := time.Parse("2006-01-02 15:04:05", "2023-05-12 17:12:00")
fmt.Println(t3) // 2023-05-12 17:12:00 +0000 UTC
// Sub 时间相减
newNow := time.Now()
fmt.Println(newNow.Sub(now)) // 13.3879ms
// 获取时间戳
fmt.Println(newNow.Unix()) // 1683883325
}
20. 数字 - 字符串
数字和字符串之间的相互转换
package main
import (
"fmt"
"strconv"
)
func main() {
// 数字 字符串 + 进制 + 精度 (如果不指定: _ + 自动推断 + 64位)
num, _ := strconv.ParseInt("100", 10, 64)
fmt.Println(num)
flo, _ := strconv.ParseFloat("1.35", 64)
fmt.Println(flo)
// 快速将字符串转 10 进制
intNum, _ := strconv.Atoi("123") // 数字 + error
fmt.Println(intNum)
}
21. 进程信息
22. 输入
22.1、Scanf
与 C语言的 Scanf 差不多
func main() {
var i int
var s string
var f float64
var c byte
fmt.Scanf("%d %s %f %c", &i, &s, &f, &c)
fmt.Println(i, s, f, c)
}
22.2、Scanln
func main() {
var i int
var s string
var f float64
var c byte
fmt.Scanln(&i)
fmt.Scanln(&s)
fmt.Scanln(&f)
fmt.Scanln(&c)
fmt.Println(i, s, f, c)
}
22.3、Scan
func main() {
var i int
var s string
var f float64
var c byte
fmt.Scanln(&i, &s, &f, &c)
fmt.Println(i, s, f, c)
}
22.4、bufio
bufio包是对IO的封装,可以操作文件等内容
同样可以用来接收键盘的输入,此时对象不是文件,而是os.Stdin,也就是标准输入设备
bufio包含了Reader、Wrter、Scanner等对象,封装了很多对IO内容处理方法,但要从键盘输入,使用Reader对象(或Scanner对象)。
22.4.1、Readbyte
只会读取输入的第一个字节,如果 string强转:中文会因读取不全而乱码
func main() {
reader := bufio.NewReader(os.Stdin)
res, error := reader.ReadByte();
if error == nil {
fmt.Println(res)
} else {
fmt.Println(error)
}
}
22.4.2、ReadBytes
读取到指定字符才结束(包括),并且,可以接收空格
func main() {
reader := bufio.NewReader(os.Stdin)
res, error := reader.ReadBytes('\n')
if error == nil {
fmt.Println(string(res))
fmt.Println(res)
} else {
fmt.Println(error)
}
}
22.4.3、ReadRune
也是读取一个字符,返回值多了一个 size
func main() {
reader := bufio.NewReader(os.Stdin)
res, size, error := reader.ReadRune()
if error == nil {
fmt.Println(string(res))
fmt.Println(size)
fmt.Println(res)
} else {
fmt.Println(error)
}
}
22.4.5、Readstring
读取字符串,与 ReadBytes 类似,不过返回值是一个字符串
func main() {
reader := bufio.NewReader(os.Stdin)
res, error := reader.ReadString('\n')
if error == nil {
fmt.Println(string(res))
fmt.Println(res)
} else {
fmt.Println(error)
}
}
22.5、综合:判断随机数
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano()) // 随机数种子,与C类似
maxNum := 66 // 随机数最大值
random := rand.Intn(maxNum)
var guess int
for {
// 可以替换为 fmt.Scan(&guess)
fmt.Scanf("%d\n", &guess) // 注意 Scanf不会刷新缓存区,需要读取这个\n
if guess < random {
fmt.Println("bigger bro")
} else if guess > random {
fmt.Println("no no no smaller")
} else {
fmt.Println("Bingo!")
break
}
}
}