• 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

  • 相关阅读:
    交叉编译环境软件搭建
    (C)struct结构体
    (C)字节对齐#pragma pack()
    常用bluetooth协议
    (C/C++)register关键字
    Android学习
    (C)*p++和*++p区别
    java文件末尾追加内容的两种方式
    java1.7集合源码阅读: Stack
    java1.7集合源码阅读: Vector
  • 原文地址:https://www.cnblogs.com/jaychan/p/12555437.html
Copyright © 2020-2023  润新知