go语言基础学习笔记

目录

  • GOROOT:Go的根目录

  • GOPATH:用户工作区,源码必须放这里

  • 系统PATH下增加$GOROOT/bin:可以直接执行的命令

  • src源码

  • pkg go install命令 归档文件 .a

  • bin 可执行文件

编译

  • 直接执行
go run hello_world.go
  • 编译
go build hello_world.go

应用程序入口

  • package 必须是main(目录不强制)
  • func 必须是main
  • 文件名不强制main.go
package main
import "fmt"
func main()  {
	fmt.Println("hello world")
}

退出

func main 不支持返回值
os.Exit 立即中止,返回状态

命令行参数

func main 不支持传入参数
os.Args获取命令行参数

func main()  {
	if len(os.Args) > 1{
		fmt.Println("Hello ",os.Args[1]) //获取命令行参数
	}
	os.Exit(-1) //异常退出
}

测试程序

  • 源码文件_test结尾,xxx_test.go
  • 测试方法Test开头 func TestXX(t *testing.T)
first_test.go

package test
import "testing"
func TestFirst(t *testing.T){
	t.Log("test");
}

变量

//全局
var c  int
func TestFirst(t *testing.T){
	//变量声明
	var a int = 1
	var b int = 1

	//简写
	var (
		a int = 1
		b int = 1
	)

	//类型推断
	a := 1
	b := 1
	fmt.Println(a)
	for i:=0; i<5; i++{
		fmt.Println(b)
		tmp := a
		a = b
		b = tmp + a
	}

变量交换

a,b = b,a

常量

连续常量

//连续常量
const(
	Mon = 1 + iota
	Thus
	Web
)

//一般常量
const(
	GOOGLE =  "go"
	BAIDU = "bd"
)

//位运算常量
const(
	READABLE = 1 << iota  //1的二进制 1
	WRITEABLE //左移一位 10
	EXECABLE //左移一位 100
)

func TestConst(t *testing.T){
	t.Log(Mon,Thus,Web)
	t.Log(GOOGLE,BAIDU)
	t.Log(READABLE,WRITEABLE,EXECABLE)
}


数据类型

bool
string
//标注位数可以忽略平台差别导致的问题
//int uint在32位机器是32位  64位机器是64位
int int8 int16 int32 int64
unit unit8 unit16 uint64 uintptr
byte //uint8的别名
rune //int32别名 unicode编码值
float32 float64
complex64 complex128

类型转换

  • 不支持隐式转换(包括原类型和别名类型) type MyInt int64

类型预定义值

  • math.MaxInt64
  • math.MaxFloat64
  • math.MaxUint64

指针类型

  • 不支持指针运算
  • string是值类型,默认值是空字符串
func TestType(t *testing.T){
	var a int = 1
	var b int64
	b = int64(a) //显式转换
	t.Log(b)
}

func TestPtr(t *testing.T){
	a := 1
	aPtr := &a
	t.Log(aPtr) //指针地址 0xc00000a298
	t.Logf("%T %T",a,aPtr) // %T格式化符号 (int *int)
}

默认值

  • *int ------- nil
  • int ------- 0
  • float32 ------- 0
  • bool ------- false

比较运算

  • 数组比较条件(编译错误):
  1. 相同维
  2. 相同个数
  • 每个元素都相等才是相等

循环

  • 只支持for,不需要括号
//一般循环
func TestLoop(t *testing.T){
	n := 0
	for n <= 5{
		t.Log(n)
		n++
	}
}

//无限循环
func TestRunLoop(t *testing.T){
	n := 0
	for{
		n++
		t.Log(n)
	}
}

判断

  • 不需要括号
  • condition结果布尔
  • 支持变量赋值
func TestCondition(t *testing.T){
	// 变量表达式;条件
	if v,err := someFun(2); err == nil{
		t.Log("正常",v)
	}else{
		t.Log("错误",err)
	}
}

func someFun(b int) (result int,err error){
	err = nil
	if b == 1 {
		result = b
	}else{
		err = errors.New("Test Error")
	}
	return
}

数组

声明

  • var a [3]int //声明并初始化为默认零值
  • a[0] = 1
  • b := [3]int{1, 2, 3} //声明同时初始化
  • c := [2][2]int{{1, 2}, {3, 4}} //多维数组初始化
遍历
func TestTravelArray(t *testing.T) {
    a := [...]int{1, 2, 3, 4, 5} //不指定元素个数
    for idx/*索引*/, elem/*元素*/ := range a {
        fmt.Println(idx, elem)
    }
}

截取

  • a[开始索引(包含), 结束索引(不包含)]
a[1:2] //2

切片

声明

  • var s0 []int
  • s0 = append(s0, 1)
  • s := []int{}
  • s1 := []int{1, 2, 3}
  • s2 := make([]int, 2, 4)
    /*[]type, len, cap
    其中len个元素会被初始化为默认零值,未初始化元素不可以访问
    */

-数组 vs 切片:伸缩 比较

map

声明

  • m := map[string]int{"one": 1, "two": 2, "three": 3}
  • m1 := map[string]int{}
  • m1["one"] = 1
  • m2 := make(map[string]int, 10 /Initial Capacity/)

元素访问

//m["four"]返回两个值,存在的话第二个是bool的true
if v, ok := m["four"]; ok {
    t.Log("four", v)
} else {
    t.Log("Not existing")
}

遍历foreach

m := map[string]int{"one": 1, "two": 2, "three": 3}
for k, v := range m {
    t.Log(k, v)
}

map实现工厂模式

  • Map 的 value 可以是⼀个⽅法
  • 与 Go 的 Dock type 接⼝⽅式⼀起,可以⽅便的实现单⼀⽅法对象的⼯⼚模式
func TestMapWithFunValue(t *testing.T) { 
 	m := map[int]func(op int) int{} 
 	m[1] = func(op int) int { return op } 
 	m[2] = func(op int) int { return op * op } 
 	m[3] = func(op int) int { return op * op * op } 
 	t.Log(m[1](2), m[2](2), m[3](2)) //以2为输入参数
 } 

set的实现

基本定义

  1. 元素的唯⼀性
  2. 基本操作
    1. 添加元素
    1. 判断元素是否存在
    1. 删除元素
    1. 元素个数
func TestMapForSet(t *testing.T) { 
 	mySet := map[int]bool{} 
 	
 	//添加元素
 	mySet[1] = true  
 	n := 3 
 	
 	//判断元素是否存在
 	if mySet[n] { 
 		t.Logf("%d is existing", n) 
 	} else { 
 		t.Logf("%d is not existing", n) 
 	} 
 	
 	//判断元素长度
 	t.Log(len(mySet))
 	
 	//删除元素
 	delete(mySet, 1) 
} 

字符串

  1. string 是数据类型,不是引⽤或指针类型
  2. string 是只读的 byte slice, len 函数可以它所包含的 byte 数(其实就是类似字节数组)
  3. string 的 byte 数组可以存放任何数据(可见字符 不可见字符)
func TestString(t *testing.T) {
	var s string
	t.Log(s) //初始化为默认零值“”
	
	s = "hello"
	t.Log(len(s))  //长度 5 
	
	s[1] = '3' //string是不可变的byte slice,这样写 编译错误

	s = "xE4xBAxFF" //可以存储任何二进制数据
	t.Log(s) //严
	t.Log(len(s)) //长度 3
	
	//byte & unicode区别
	s = "中"
	t.Log(len(s)) //是byte数
	c := []rune(s) //能取出字符串里面的unicode(rune切片)
	t.Log(len(c)) //长度1
	
	
	//	t.Log("rune size:", unsafe.Sizeof(c[0]))
	t.Logf("中 unicode %x", c[0])
	t.Logf("中 UTF8 %x", s)
}
func TestStringToRune(t *testing.T) {
	s := "中华人民共和国"
	for _, c := range s {
		t.Logf("%[1]c %[1]d", c) //汉字 编码
	}
}
  1. Unicode 是⼀种字符集(code point 相当于标准)
  2. UTF8 是 unicode 的存储实现 (转换为字节序列的规则)
字符
Unicode 0x4E2D
UTF-8 0xE4B8AD
string/[]byte [0xE4,0xB8,0xAD]

字符串操作函数

//用逗号分隔成切片
func TestStringFn(t *testing.T) {
	s := "A,B,C"
	parts := strings.Split(s, ",")
	for _, part := range parts {
		t.Log(part)
	}
	
    //字符串连接
	t.Log(strings.Join(parts, "-"))
}

func TestConv(t *testing.T) {
    //整数转字符串
	s := strconv.Itoa(10) 
	t.Log("str" + s)
	
	//字符串转整形
	if i, err := strconv.Atoi("10"); err == nil {
		t.Log(10 + i)
	}
}

函数:一等公民

  1. 可以有多个返回值
  2. 所有参数都是值传递: slice, map, channel 会有传引⽤的错觉(其实也是传值,因为slice对应的数组,整个数据结构有指针指向同一数组)
  3. 函数可以作为变量的值
  4. 函数可以作为参数和返回值
  • 多返回值
func returnMultiValues() (int, int) {
	return rand.Intn(10), rand.Intn(20)
}

//调用时忽略一个返回值
a, _ := returnMultiValues() 
  • 装饰器模式(计时函数)
func timeSpent(inner func(op int) int) func(op int) int {
	return func(n int) int {
		start := time.Now()
		ret := inner(n)
		fmt.Println("time spent:", time.Since(start).Seconds())
		return ret
	}
}

func slowFun(op int) int {
	time.Sleep(time.Second * 1)
	return op
}

//调用
tsSF := timeSpent(slowFun)
t.Log(tsSF(10))

可变参数

//参数会转化成数组
func sum(ops ...int) int {
    s := 0
    for _, op := range ops {
        s += op
    }
    return s
}

defer 延迟最后执行

  • 类似 try .. finally,就上panic的异常中断也会继续执行
func TestDefer(t *testing.T) {
    defer func() {
        t.Log("Clear resources")
    }()
    t.Log("Started")
    panic("Fatal error”) //defer仍会执⾏
}

面对对象

封装数据

type Employee struct {
    Id string
    Name string
    Age int
}
  • 创建实例
e := Employee{"0", "Bob", 20}
e1 := Employee{Name: "Mike", Age: 30}


e2 := new(Employee) //注意这⾥返回的引⽤/指针,相当于 e := &Employee{}
e2.Id = "2" //与其他主要编程语⾔的差异:通过实例的指针访问成员不需要使⽤->
e2.Age = 22
e2.Name = “Rose

t.Logf("%T",e2) //*指针类型

封装行为

//第⼀种定义⽅式在实例对应⽅法被调⽤时,实例的成员会进⾏值复制
func (e Employee) String() string {
    return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age)
}
  • 要修改值就要用这种
//通常情况下为了避免内存拷⻉我们使⽤第⼆种定义⽅式
func (e *Employee) String() string {
    return fmt.Sprintf("ID:%s/Name:%s/Age:%d", e.Id, e.Name, e.Age)
}

接口

接口定义

type Programmer interface {
	WriteHelloWorld() string
}

接口实现 Duck Type

  • 有鸭子的特征就是鸭子
type GoProgrammer struct {
}

func (g *GoProgrammer) WriteHelloWorld() string {
	return "fmt.Println("Hello World")"
}

调用

func TestClient(t *testing.T) {
	var p Programmer
	p = new(GoProgrammer)
	t.Log(p.WriteHelloWorld())
}

特征

  1. 接⼝为⾮⼊侵性,实现不依赖于接⼝定义(其实就是理解为可以先写完实现,发现可以抽象成接口,就直接抽象出来)
  2. 所以接⼝的定义可以包含在接⼝使⽤者包内

接口变量

// 接口 = 实现
var prog Coder = &GoProgrammer{}
  • 接口初始化后

  • 类型

type GoProgrammer struct {}
  • 数据(goprogramer的一个实例)
&GoProgrammer{}

自定义类型

  1. type IntConvertionFn func(n int) int
  2. type MyPoint int
//简化便于阅读
type IntConv func(op int) int  

func timeSpent(inner IntConv) IntConv {
	return func(n int) int {
		start := time.Now()
		ret := inner(n)
		fmt.Println("time spent:", time.Since(start).Seconds())
		return ret
	}
}

func slowFun(op int) int {
	time.Sleep(time.Second * 1)
	return op
}

func TestFn(t *testing.T) {
	tsSF := timeSpent(slowFun)
	t.Log(tsSF(10))
}

复合(集成)

  • Go 不⽀持继承,但可以通过复合的⽅式来复⽤

  • 父类

type Pet struct {
}

func (p *Pet) Speak() {
	fmt.Print("...")
}

func (p *Pet) SpeakTo(host string) {
	p.Speak()
	fmt.Println(" ", host)
}
  • 子类(持有父类的指针)
type Dog struct {
	p *Pet
}

//重载父类的方法
func (d *Dog) Speak() {
	fmt.Print("Wang!")
}
//直接继承
func (d *Dog) Speak() {
	d.p.Speak()
}
  • 测试
func TestDog(t *testing.T) {
	dog := new(Dog)
	dog.SpeakTo("Chao")
}

匿名嵌套类型

  • 没法当成继承来用,不支持LSP,不支持访问子类方法,就算子类重载了父类的方法,也无法被调用
type Dog struct {
	Pet
}

//这里没法被调用
func (d *Dog) Speak() {
	fmt.Print("Wang!")
}

func TestDog(t *testing.T) {
	dog := new(Dog)
	dog.speak // ... 调用了父类的speak
}

多态

type Code string
type Programmer interface {
	WriteHelloWorld() Code
}

type GoProgrammer struct {
}

func (p *GoProgrammer) WriteHelloWorld() Code {
	return "fmt.Println("Hello World!")"
}

type JavaProgrammer struct {
}

func (p *JavaProgrammer) WriteHelloWorld() Code {
	return "System.out.Println("Hello World!")"
}

func writeFirstProgram(p Programmer) {
	fmt.Printf("%T %v
", p, p.WriteHelloWorld())
}

func TestPolymorphism(t *testing.T) {
	goProg := &GoProgrammer{}
	javaProg := new(JavaProgrammer)
	writeFirstProgram(goProg)
	writeFirstProgram(javaProg)
}

空接口

  • 可以表示任何类型,类似object
  • 通过断⾔来将空接⼝转换为制定类型
//转换后的值,是否成功
v, ok := p.(int) //ok=true 时为转换成功
func DoSomething(p interface{}) {
    //如果传入的参数能断言成整形
	 if i, ok := p.(int); ok {
	 	fmt.Println("Integer", i)
	 	return
	 }
	 if s, ok := p.(string); ok {
	 	fmt.Println("stirng", s)
		return
	 }
	fmt.Println("Unknow Type")
	 
	
	//简化版
	switch v := p.(type) {
	case int:
		fmt.Println("Integer", v)
	case string:
		fmt.Println("String", v)
	default:
		fmt.Println("Unknow Type")
	}
}

func TestEmptyInterfaceAssertion(t *testing.T) {
	DoSomething(10)
	DoSomething("10")
}

最佳实践

  • 倾向于使⽤⼩的接⼝定义,很多接⼝只包含⼀个⽅法
type Reader interface {
    Read(p []byte) (n int, err error)
}
type Writer interface {
    Write(p []byte) (n int, err error)
}
  • 较⼤的接⼝定义,可以由多个⼩接⼝定义组合⽽成
type ReadWriter interface {
    Reader
    Writer
}
  • 只依赖于必要功能的最⼩接⼝
func StoreData(reader Reader) error {
    ...
}

错误机制

  1. 没有异常机制
  2. error 类型实现了 error 接⼝
type error interface {
    Error() string
}
  1. 可以通过 errors.New 来快速创建错误实例,不过接受者只能通过字符串匹配来区分错误类型

  2. 最佳实践:及早错误,避免嵌套

errors.New("n must be in the range [0,100]")
var LessThanTwoError = errors.New("n should be not less than 2")
var LargerThenHundredError = errors.New("n should be not larger than 100")

// 返回正常值或者错误类型
func GetFibonacci(n int) ([]int, error) {
	if n < 2 {
		return nil, LessThanTwoError
	}
	if n > 100 {
		return nil, LargerThenHundredError
	}
	fibList := []int{1, 1}

	for i := 2; /*短变量声明 := */ i < n; i++ {
		fibList = append(fibList, fibList[i-2]+fibList[i-1])
	}
	
	//没有错误
	return fibList, nil
}


// 错误检查机制
func TestGetFibonacci(t *testing.T) {
	if v, err := GetFibonacci(1); err != nil {
	
	    //区分不同错误类型
		if err == LessThanTwoError {
			fmt.Println("It is less.")
		}
		t.Error(err)
	} else {
		t.Log(v)
	}

}


//只有所有地方都没有错误,才输出结果,推荐用法
func GetFibonacci2(str string) {
	var (
		i    int
		err  error
		list []int
	)
	if i, err = strconv.Atoi(str); err != nil {
		fmt.Println("Error", err)
		return
	}
	if list, err = GetFibonacci(i); err != nil {
		fmt.Println("Error", err)
		return
	}
	fmt.Println(list)

}

panic

  • panic ⽤于不可以恢复的错误
  • panic 退出前会执⾏ defer 指定的内容
  • panic 的参数是一个空接口

Exit

  • os.Exit 退出时不会调⽤ defer 指定的函数
  • os.Exit 退出时不输出当前调⽤栈信息

recover(相当于try整个函数catch到defer里面的recover判断)

小心用

  • 形成僵⼫服务进程,导致 health check 失效
  • “Let it Crash!” 往往是我们恢复不确定性错误的最好⽅法。(等守护进程拉起)
  • 只有在延迟函数的内部,调用 recover 才有用。在延迟函数内调用 recover,可以取到 panic 的错误信息,并且停止 panic 续发事件(Panicking Sequence),程序运行恢复正常。如果在延迟函数的外部调用 recover,就不能停止 panic 续发事件。
defer func() {
    if err := recover(); err != nil {
        //恢复错误(不建议只打log,可能会导致不可预知行为)
        debug.PrintStack()
        //恢复后也可以打印调用栈
    }
}()
func TestPanicVxExit(t *testing.T) {

	defer func() {
		if err := recover(); err != nil {
			fmt.Println("recovered from ", err)
		}
	}()
	fmt.Println("Start")
	panic(errors.New("Something wrong!"))
}

package

  1. 基本复⽤模块单元,以⾸字⺟⼤写来表明可被包外代码访问(编译不了)
  2. 代码的 package 可以和所在的⽬录不⼀致(与JAVA不同)
  3. 同⼀⽬录⾥的 Go 代码的 package 要保持⼀致(编译不了)

环境变量

export GOPATH="/Users/xxx/yy/go:/Users/xxx/go_learning"
export PATH="$HOME/.cargo/bin:$PATH"
  • /ch15/my_series.go
package series

import "fmt"

func init() {
	fmt.Println("init1")
}

func init() {
	fmt.Println("init2")
}

func Square(n int) int {
	return n * n
}

func GetFibonacciSerie(n int) []int {
	ret := []int{1, 1}
	for i := 2; i < n; i++ {
		ret = append(ret, ret[i-2]+ret[i-1])
	}
	return ret
}

本地包导入

  • /ch15/client/package_test.go
package client

import (
	"ch15/series"
	"testing"
)

func TestPackage(t *testing.T) {
	t.Log(series.GetFibonacciSerie(5))
	t.Log(series.Square(5))
}

init 构造

  • 在 main 被执⾏前,所有依赖的 package 的 init ⽅法都会被执⾏
  • 不同包的 init 函数按照包导⼊的依赖关系决定执⾏顺序(go自动处理)
  • 每个包可以有多个 init 函数(go特点)
  • 包的每个源⽂件也可以有多个 init 函数,这点⽐较特殊

如何用别人的包

  1. 通过 go get 来获取远程依赖
  • go get -u 强制从⽹络更新远程依赖(必须先做这步才能用)
  1. 如果要自己提交代码到github,注意代码在 GitHub 上的组织形式,以适应 go get
  • 直接以代码路径开始,不要有 src
package remote

import (
	"testing"
	 cm "github.com/easierway/concurrent_map" //这里起了别名
)

func TestConcurrentMap(t *testing.T) {
	m := cm.CreateConcurrentMap(99)
	m.Set(cm.StrKey("key"), 10)
	t.Log(m.Get(cm.StrKey("key")))
}

依赖管理

  1. 同⼀环境下,不同项⽬使⽤同⼀包的不同版本(GOPATH GOROOT)
  2. ⽆法管理对包的特定版本的依赖

verdor

随着 Go 1.5 release 版本的发布, vendor ⽬录被添加到除了 GOPATH 和
GOROOT 之外的依赖⽬录查找的解决⽅案。在 Go 1.6 之前,你需要⼿动
的设置环境变量
查找依赖包路径的解决⽅案如下:

  1. 当前包下的 vendor ⽬录
  2. 向上级⽬录查找,直到找到 src 下的 vendor ⽬录
  3. 在 GOPATH 下⾯查找依赖包
  4. 在 GOROOT ⽬录下查找

常用工具

https://img2020.cnblogs.com/blog/456913/202006/456913-20200615224755733-1108195631.jpg

原文地址:https://www.cnblogs.com/jaychan/p/12555437.html