目录
3.1 go: cannot run *_test.go files
一、Mac brew 安装go环境
1.1 安装步骤
1)终端输入,也可以指定下载go版本
brew install go
2)查看golang当前版本
go version
3)查看当前golang环境 执行
go env
1.2 设置GOPATH 及环境变量
1)GOPATH 是工作目录,工作空间目录 go_project 就是一个目录,其中包含三个子目录:
src
目录包含Go的源文件,它们被组织成包(每个目录都对应一个包),pkg
目录包含包对象,bin
目录包含可执行命令。
如下图:
2) 终端输入 vim .zshrc
添加:
GOPATH 后面加你自己的工作空间,这里是我自己建立的go_project
export GOPATH="/Users/mima6ge0/Documents/yanhan_practice/go_project"
export PATH = $PATH:$GOPATH/bin
执行 source ~/.zshrc
生效,使用go env查看是否修改成功
注意:GOROOT,GOPATH,GO111MODULE的用处与区别
1.3 编写第一个go程序
1)新建hellow.go文件
package main
import "fmt"
func main() {
fmt.Println("hello, world")
}
2)进入文件所在目录,执行go run hellow.go,成功,如下图
二、快速入门
2.1 快速入门需求
- 具备1种后端编程语言开发经验(C/C++/Java/Python/PHP)
- 具备基本的网络编程能力和并发思想
- 了解计算机基本体系结构
- 了解Linux基础知识
2.2 go学习(自用)
2.2.1 go基础程序
v%表示打印任何一种类型的格式化
package main //程序的包名
// main函数
//多个文件导入
import (
"fmt"
"time"
)
func main() { //函数的"{"必须和函数名在一行,换到下一行就是语法错误,强制代码风格
fmt.Println("hellow Go!")
//go语言不需要";",可有可无
time.Sleep(10 * time.Second)
}
2.2.2 变量声明
package main
/*
变量的声明方法
*/
import "fmt"
//全局变量,冒等方法不支持全局,其他的都可以
var A int = 100
func main() {
//声明变量默认值是0
var a int
fmt.Println("a = ", a)
fmt.Printf("type of a = %T\n", a)
//声明变量,初始化
var b int = 100
fmt.Println("b = ", b)
fmt.Printf("type of v = %T\n", b)
//初始化可以省去变量类型,通过值去匹配变量的树枝类型
var c = 100
fmt.Println("c = ", c)
fmt.Printf("type of c = %T\n", c)
//字符串
var d string = "dasda"
fmt.Println("d = ", d)
fmt.Printf("type of d = %T\n", d)
//最常用方法:冒等,省略var,直接自动匹配,":="初始化加赋值,函数内部使用
e := 3213.432
fmt.Println("e = ", e)
fmt.Printf("type of e = %T\n", e)
fmt.Println("A = ", A)
fmt.Printf("type of A = %T\n", A)
//声明多个变量
var x, y int = 100, 100
fmt.Println("x = ", x, ", y = ", y)
var m, n = 100, "88090"
n = "kjuhku"
fmt.Println("m = ", m, ", n = ", n)
//多行多变量声明
var (
xx int = 121
zz bool = true
)
fmt.Println("xx = ", xx, ", zz = ", zz)
}
2.2.3 常量和枚举
package main
import "fmt"
//const 可以定义枚举类型
const (
//可以在const里面添加一个关键字 iota,每行的iota都会累加1,第一行的iota的默认值是0
BEIJING = 10 * iota // iota = 0
SHANGHAI //iota = 10
SHENZHEN //iota = 20
)
const (
a, b = iota + 1, iota + 2 //iota = 0, a = 1, b = 2
c, d //iota = 1, c = 2, d = 3
e, f //iota = 2, e = 3, f = 4
g, h = iota * 2, iota * 3 //iota = 3, g = 6, h = 9
i, k //iota = 4, i = 8, k = 12
)
func main() {
//常量,只读,不允许修改
const a int = 100
fmt.Println("a = ", a)
fmt.Println("BEIJING = ", BEIJING)
fmt.Println("SHANGHAI = ", SHANGHAI)
fmt.Println("SHENZHEN = ", SHENZHEN)
fmt.Println("a = ", a, "b = ", b)
fmt.Println("c = ", c, "d = ", d)
fmt.Println("e = ", e, "f = ", f)
fmt.Println("g = ", g, "h = ", h)
fmt.Println("i = ", i, "k = ", k)
//iota只能在const里面使用,var里面不可以
}
2.2.4 函数与多种返回值
package main
import "fmt"
//返回一个值,匿名
func foo1(a string, b int) int {
fmt.Println("a = ", a)
fmt.Println("b = ", b)
c := 100
return c
}
//返回多个值,匿名
func foo2(a string, b int) (int, int) {
fmt.Println("--------foo2----------")
fmt.Println("a = ", a)
fmt.Println("b = ", b)
return 888, 9999
}
//返回多个返回值,有形参名称
func foo3(a string, b int) (r1 int, r2 int) {
fmt.Println("--------foo3----------")
fmt.Println("a = ", a)
fmt.Println("b = ", b)
r1 = 1000
r2 = 100000
return
}
func foo4(a string, b int) (r1, r2 int) {
fmt.Println("--------foo4----------")
fmt.Println("a = ", a)
fmt.Println("b = ", b)
//r1, r2属于foo3的形参,初始化默认的值是0,作用域空间是整个foo4函数{}的整体
fmt.Println("未赋值:r1 = ", r1)
fmt.Println("未赋值:r2 = ", r2)
r1 = 1000
r2 = 100000
return
}
func main() {
c := foo1("func1", 1)
fmt.Println("c = ", c)
ret1, ret2 := foo2("func2", 2)
fmt.Println("ret1 = ", ret1)
fmt.Println("ret2 = ", ret2)
r1, r2 := foo3("func3", 3)
fmt.Println("r1 = ", r1)
fmt.Println("r2 = ", r2)
r1, r2 = foo4("func4", 4)
fmt.Println("r1 = ", r1)
fmt.Println("r2 = ", r2)
}
2.2.5 init函数与import导包
文件目录树状图
lib1.go 代码:
package lib1
import "fmt"
//当前lib1包提供的API
//首字母大写的话代表当前接口对外开放,首字母小写只能在该文件下使用
func Lib1Test () {
fmt.Println("Lib1Test() ...")
}
func init () {
fmt.Println("lib1 init ...")
}
lib2.go 代码:
package lib2
import "fmt"
//当前lib2包提供的API
func Lib2Test () {
fmt.Println("Lib2Test() ...")
}
func init () {
fmt.Println("lib2 init ...")
}
main.go代码:
package main
//需要在GOPATH下
import (
"GolangStudy/5_init/lib1"
"GolangStudy/5_init/lib2"
)
func main () {
lib1.Lib1Test()
lib2.Lib2Test()
}
2.2.6 import匿名、别名导包
基于2.2.5的代码
package main
//需要在GOPATH下,go语言语法比较严格,导入必须使用
import (
// _ 匿名导包,导入但是不使用,不会报错
_ "GolangStudy/5_init/lib1"
//mylib2是lib2的别名
//mylib2 "GolangStudy/5_init/lib2"
//可以不写包名直接使用Lib2Test(),把方法导入当前main包里面,不建议使用,如果有重名函数,会出现问题
. "GolangStudy/5_init/lib2"
)
func main () {
//lib1.Lib1Test()
//mylib2.Lib2Test()
Lib2Test()
}
总结:
2.2.7 defer调用顺序
package main
/*
defer的执行顺序是在函数体全部执行完以后和结束之前,
多个defer语句是压栈的,defer在return后面执行
*/
import "fmt"
func func1() {
fmt.Println("A")
}
func func2() {
fmt.Println("B")
}
func func3() {
fmt.Println("C")
}
func main () {
defer func1()
defer func2()
defer func3()
}
2.2.8 数组的表示和动态数组 slice
1)固定长度数组表示,遍历等(不建议,不常用)
package main
import "fmt"
//不建议使用,可以使用动态数组
func printArray (array [4]int){
//值拷贝
fmt.Println("---------------输出函数--------------")
for i := 0; i < len(array); i++ {
fmt.Println("array2_value = ", array[i])
}
//如果要对数组的值进行修改,下面这种传参是改不了的
// array[0] = 10000
}
func main () {
// 固定长度数组
var array1 [10]int
for i := 0; i < len(array1); i++ {
fmt.Println("array1_value = ", array1[i])
}
array2 := [10]int{1, 2, 4, 5}
array3 := [4]int{1, 2, 4, 5}
for index, value := range array2 {
fmt.Println("array2_index = ", index, "array2_value = ", value)
}
printArray(array3)
// 打印数组类型
fmt.Printf("array1 type : %T\n", array1)
fmt.Printf("array2 type : %T\n", array2)
fmt.Printf("array3 type : %T\n", array3)
}
2)动态数组,切片,slice
package main
import "fmt"
func printArray(array []int) {
// 引用拷贝,传递的是数组指针,所以array[0]会被修改为100
array[0] = 100
// _ 表示匿名的变量,不被使用也不会发生问题
for _, value := range array {
fmt.Println("value = ", value)
}
}
func main() {
// 动态数组,切片,slice
array := []int{1, 2, 3, 4}
//查看array的类型
fmt.Printf("array type : %T\n", array)
printArray(array)
}
3)slice四种定义声明方式
package main
import (
"fmt"
)
func main() {
// 声明slice1是一个切片,并且初始化,长度是3,默认值是1, 2, 3
// slice1 := []int{1, 2, 3}
// 声明slice1是一个切片,但是没有分配空间,len = 0,value = []
// var slice1 []int
// 可以通过make来开辟几个空间,这时候len = 3,value = [0, 0, 0]
// slice1 = make([]int, 3)
// 声明slice1是一个切片,通过make来开辟3空间,这时候len = 3,value = [0, 0, 0]
// var slice1 []int = make([]int, 3)
// 声明slice1是一个切片,通过make来开辟3空间,这时候len = 3,value = [0, 0, 0],通过:=推测出他是一个切片
slice1 := make([] int, 3)
fmt.Printf("len = %d , value = %v\n", len(slice1), slice1)
// 判断一个切片是否为空
if slice1 == nil {
fmt.Println("slice1 is null!")
} else {
fmt.Println("slice2 is not null!!")
}
}
4)slice切片追加与截取
package main
import "fmt"
func main () {
// 切片中len和cap是不同的
var numbers = make([] int, 3, 5) // len = 3, cap容量 = 5
fmt.Printf("len = %d, cap = %d, value = %v\n", len(numbers), cap(numbers), numbers)
//追加一个元素7
numbers = append(numbers, 7)
fmt.Printf("len = %d, cap = %d, value = %v\n", len(numbers), cap(numbers), numbers)
numbers = append(numbers, 8)
// 当追加元素时,容量已满,则自动扩充为原来一开始定义的cap的二倍,扩充为10
numbers = append(numbers, 9)
fmt.Printf("len = %d, cap = %d, value = %v\n", len(numbers), cap(numbers), numbers)
// 没有定义容量的情况,但是在容量已满的情况下追加元素,直接扩充为len的二倍
var numbers2 = make([] int, 3)
fmt.Printf("len = %d, cap = %d, value = %v\n", len(numbers2), cap(numbers2), numbers2)
numbers2 = append(numbers2, 4)
fmt.Printf("len = %d, cap = %d, value = %v\n", len(numbers2), cap(numbers2), numbers2)
// slice切片
s := [] int{1, 2, 3, 4}
s1 := s[0:2] //s1是s中的索引在[0, 2)区间的元素,和python类似
fmt.Println("s1 = ", s1)
s2 := s[1:] //s1是s中的索引在[1, len(s))区间的元素,和python类似
fmt.Println("s2 = ", s2)
//如若修改s1里面的元素,s也会随之改变,因为这种切片相当于指针指向了s,所以改s1值,s和s1会一起发生改变(浅拷贝)
s1[0] = 20000
fmt.Println("s = ", s)
// go提供了copy函数,切片的时候只将值复制过来,(深拷贝)
s3 := make([] int, 3) //[0, 0, 0]
// 将s2的值依次拷贝到s3中
copy(s3, s2)
fmt.Println("s3 = ", s3)
}
2.2.9 map的声明使用
1)map的声明
package main
import "fmt"
func main() {
// 第一种声明方式
var map1 map[string]string
if map1 == nil {
fmt.Println("map1是一个空map!!")
}
// 使用前,需要通过make来给map分配数据空间
map1 = make(map[string]string, 3)
map1["one"] = "java"
map1["two"] = "php"
map1["three"] = "python"
fmt.Println("map1 = ", map1)
// 第二种声明方式,直接:=
map2 := make(map[int]string)
map2[1] = "baidu"
map2[2] = "tengxun"
map2[3] = "ali"
fmt.Println("map2 = ", map2)
// 第三种声明方式,一般带有初始化的map用这种方式
map3 := map[string]string{
"王八": "绿豆",
"傻子": "小丑",
}
fmt.Println("map3 = ", map3)
}
2)map的使用
package main
import (
"fmt"
)
func printMap (city_map map[string]string) {
// 遍历
for key, value := range city_map {
fmt.Println("key = ", key, ", value = ", value)
}
}
func changeMap (city_map map[string]string) {
// city_map是一个引用传递,传递过来是地址,可以修改city_map
city_map["UK"] = "london"
}
func main() {
cityMap := make(map[string]string)
cityMap["china"] = "beijing"
cityMap["USA"] = "DC"
printMap(cityMap)
// 删除
delete(cityMap, "china")
fmt.Println("-----------删除后---------------")
printMap(cityMap)
// 修改
cityMap["USA"] = "london"
fmt.Println("-----------修改后---------------")
printMap(cityMap)
// 增加
cityMap["china"] = "beijing"
fmt.Println("-----------增加后---------------")
printMap(cityMap)
changeMap(cityMap)
fmt.Println("-----------函数增加后---------------")
printMap(cityMap)
}
2.2.10 面向对象
1)结构体的使用
package main
import "fmt"
// 声明一种数据类型,myint是int的别名
type myint int
// 声明结构体
type Book struct{
title string
auth string
}
func changeBook (book Book) {
// 传递了一个book的副本
book.title = "三体"
}
func changeBook2 (book *Book) {
// 指针传递
book.title = "生死疲劳"
}
func main () {
var a myint = 232
fmt.Println("a = ", a)
fmt.Printf("type of a is %T\n", a)
var book1 Book
book1.title = "活着"
book1.auth = "余华"
fmt.Printf("%v\n", book1)
changeBook(book1)
// 使用上面changeBook,修改值并没有成功
fmt.Printf("%v\n", book1)
// 参数是地址,所以要加"&"
changeBook2(&book1)
fmt.Printf("%v\n", book1)
}
2)面向对象类的表示
A、封装
package main
import "fmt"
// 类名、属性名、方法名、首字母大写代表其他包可以访问,首字母小写代表私有,只有本包内可以使用
type Hero struct {
Name string
Ad int
Level int
}
// 如果不写*Hero,这里传进去的一个副本,如果想在函数内对其进行修改,要用*Hero
func (this Hero) Show() {
// 副本
fmt.Println("Name : ", this.Name)
fmt.Println("Ad : ", this.Ad)
fmt.Println("Level : ", this.Level)
}
func (this Hero) GetName() string {
return this.Name
}
func (this *Hero) SetName(name string) {
// 这样才是当前对象
this.Name = name
}
func main() {
hero := Hero{ Name: "freezing", Ad: 33, Level: 1}
hero.Show()
hero.SetName("rebecca")
hero.Show()
}
B、继承
package main
import "fmt"
type Human struct {
name string
age int
}
func (this *Human) Walk() {
fmt.Println("Human walk .....")
}
func (this *Human) Eat() {
fmt.Println("Human eat .....")
}
type Superhero struct {
// 继承Human写法
Human
level int
}
// 重定义(方法重写)父类方法
func (this *Superhero) Eat() {
fmt.Println("Superhero eat .....")
}
// 子类的新方法
func (this *Superhero) Fly() {
fmt.Println("Superhero fly .....")
}
func main() {
human := Human{"freezing", 20}
human.Eat()
human.Walk()
superhero := Superhero{Human{"rebecca", 30}, 1}
superhero.Walk() // 父类方法
superhero.Eat() // 子类方法
superhero.Fly() // 子类方法
// 另外一种初始化对象的方法
var s Superhero
s.age = 24
s.level = 1
s.name = "cookie wan"
}
C、多态
package main
import "fmt"
// interface是一个指针,一个接口实现两个具体的类
type Animal interface {
Sleep()
GetColor() string // 获取动物颜色
GetType() string // 获取动物种类
}
type Cat struct{
// 继承需要把类写在里面,实现多态不需要
color string
}
//实现多态,只需要把这些接口里面的方法实现就行,必须完全实现接口里面的全部东西
func (this *Cat) Sleep() {
fmt.Println("cat is sleep .....")
}
func (this *Cat) GetColor() string{
return this.color
}
func (this *Cat) GetType() string{
return "小猫"
}
type Dog struct{
// 继承需要把类写在里面,实现多态不需要
color string
}
//实现多态,只需要把这些接口里面的方法实现就行,必须完全实现接口里面的全部东西
func (this *Dog) Sleep() {
fmt.Println("Dog is sleep .....")
}
func (this *Dog) GetColor() string{
return this.color
}
func (this *Dog) GetType() string{
return "小猫"
}
func showAnimal(animal Animal){
animal.Sleep()
fmt.Println(animal.GetType(), "的颜色:", animal.GetColor())
}
func main() {
var animal Animal // 接口的数据类型,父类指针
animal = &Cat{"Green"}
// cat := Cat{"Green"}
showAnimal(animal)
animal = &Dog{"Red"}
showAnimal(animal)
// animal.Sleep() // 调用的是dog的sleep,和catsleep并不相同,就是多态的体现
// fmt.Println("狗的颜色:", animal.GetColor())
}
2.2.11 json
1)json.Unmarshal反序列化,将json结构解析,如果解析过程中发生错误,json.Unmarshal
会返回一个非空的错误对象。
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
jsonData := []byte(`{"name":"John", "age":30}`)
var person Person
// 用于将 JSON 格式的数据解析(反序列化)为 Go 对象
err := json.Unmarshal(jsonData, &person)
if err != nil {
fmt.Println("解析 JSON 失败:", err)
return
}
// John 30
fmt.Println(person.Name, person.Age)
}
2.2.11 interface万能接口和反射
接口是fu leifulei,通过变量查出type和value
package main
import "fmt"
// interface是万能数据类型
func myFunc(arg interface{}) {
fmt.Println("myFunc is called...")
fmt.Println(arg)
// 想知道arg是什么类型,想区分interface底层到底是什么数据类型?
// 给interface提供了类型断言机制
value, ok := arg.(string) // 判断是否是string
if !ok{
fmt.Println("arg is not string...")
}else{
fmt.Println("arg is string type, value = ", value)
fmt.Printf("value type is %T\n", value)
}
}
type Book struct {
auth string
}
func main() {
var a string
//a变量里面的pair,<static type:string, value:"dsfs">
a = "dsfs"
// pair,<static type:string, value:"dsfs">
var allType interface{}
allType = a // 连续不变的传递这个pair
value, ok := allType.(string)
if !ok{
fmt.Println("不是sring")
}else{
fmt.Println("类型是string, value是", value)
}
book := Book{"Freezing"}
myFunc(book)
myFunc(100)
myFunc("abc")
myFunc(3.3242343)
}
2.2.12 反射
package main
import (
"fmt"
"io"
"os"
)
func main(){
// /dev/tty代表终端
// tty:pair<type:*os.File, value:"/dev/tty"文件描述符>
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil{
fmt.Println("open file error", err)
return
}
// 不管复制给谁都不会变
var r io.Reader
// r:pair<type:*os.File, value:"/dev/tty"文件描述符>
r = tty
var w io.Writer
// w:pair<type:*os.File, value:"/dev/tty"文件描述符>
w = r.(io.Writer)
w.Write([]byte ("HEllo this is a test!!\n"))
}
三、debug
3.1 go: cannot run *_test.go files
背景:将go文件命名成array_test.go报错go: cannot run *_test.go files
原因:_test.go 是golang专用的测试文件
解决:改名
3.2 深拷贝(copy)和浅拷贝(slice切片)
https://blog.csdn.net/weixin_45440484/article/details/131740125
3.3 引用传递
引用传递是一种变量传递的方式,它不是直接传递变量的值,而是传递变量的内存地址(引用)。在引用传递中,被调用函数可以通过引用修改原始变量的值。
3.4 检测并发程序的正确性 -race选项
在 go 命令加上 -race 选项,例如
结果显示,程序中存在 2 个 goroutine 非同步读写变量 t。
3.5 Go的常用包
3.5.1 time包
- time.Time:表示时间的类型,常见的有两种方式:
- time.Now():获取当前的时间
- time.Date():根据指定的年、月、日、时、分、秒、纳秒等创建时间
- time.Duration:表示两个时间点之间经过的时间,以纳秒为单位,最长时间段大约 290 年
- 格式化时间:time.Format()、time.Parse()
3.6 函数后面有Locker
y原因:函数会修改全局变量,需要加锁后再用