ljzsdut
GitHubToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeBack to homepage

00 推荐阅读

在 Golang 中使用 Cobra 创建 CLI 应用

李文周golang:https://www.liwenzhou.com/posts/Go/golang-menu/

type语法

https://www.cnblogs.com/smartrui/p/11425822.html

https://studygolang.com/articles/17179

https://www.jianshu.com/p/6ca70382bb42

type使用背景

对一个新的类型进行定义,我们称之为“类型声明”。对于某个类型声明可以直接使用,也可以使用type关键字对这个“类型声明”进行命名操作。

比如我们有一个类型声明,定义如下:

struct {
		name string
		age  int
}

下面直接对该类型进行实例化对象:

func b()  {
	s := struct {
		name string
		age  int
	}{"ljz", 15}
	fmt.Printf("%#v",s)
}

而我们常用的方式,是先对类型声明进行命名:

type persion struct {
	name string
	age  int
}
func c()  {
	s := persion{"ljz", 15}
	fmt.Printf("%#v",s)
}

type概述

type关键字的作用:为“类型声明”命名。通过type关键字,可以对各种类型进行命名,使之成为“命名类型(defined type)”

语法:type T 类型声明:此后,T为“类型声明”的名字,可以直接使用T来引用该类型。

golang中的各种数据类型:

int int32int64floating32stringbool  //GO中预先声明类型
数组切片map通道指针结构体接口函数  //  组合类型
				struct{...}  // 结构体类型:通过”关键字struct+大括号“定义
        interface{...}		// 接口类型:通过”关键字interface+大括号“定义
        func(参数) 返回值  //函数类型
 

以上所有类型都可以通过type关键字进行"类型声明"的命名,(为字面量类型做一个命名),从而简化引用。

int  // 是一种类型
type myint int

map[string]int // 是一种类型
type mm map[string]int



struct{}   // 是一种类型
struct{
	name string
  age int
} 					// 是一种类型
type person struct{
	name string
  age int
} 

interface{} // 是一种类型。称之为“空接口”类型
type nilinterface interface{}  // 为类型interface{}做了个命名操作,其名称为nilinterface。

func(a int ,b int) int  // 是一种类型
type myfunc func(a int ,b int) int

类型(T)如果实现了接口(S),则类型T 的实例对象(obj)也是接口类型S的对象。即obj既是T类型,也是S类型。

函数也是一种数据类型,函数的签名就是函数的类型。

//定义函数类型
type handler func (name string) int

//实现函数类型方法
func (h handler) add(name string) int {
   return h(name) + 10
}

//另一个函数定义
func doubler(name string) int {
   return len(name) * 2
}

func main() {
  fmt.Println(handler(doubler).add("taozs")) //doubler函数显式转换成handler函数对象然后调用对象的add方法
}

type常用用法

//1、定义结构体
//结构体定义
type person struct {
   name string //注意后面不能有逗号
   age  int
}

func main() {
   //结构体初始化
   p := person{
      name: "taozs", //注意后面要加逗号
      age:  18, //或者下面的}提到这儿来可以省略逗号
   }

   fmt.Println(p.name)
}

//初始化字段不一定要全部指定,比如下面也是可以的,name默认取长度为0的空字符串
p := person{
   age: 18,
}

//2、定义一个新的类型
type Str string

func main() {
   var myname Str = "taozs" //其实就是字符串类型
   l := []byte(myname) //字符串转字节数组
   fmt.Println(len(l)) //字节长度
}



// 新定义的类型,可以定义方法.上面的Str类型可以像下面这样定义方法:
type Str string

func (n Str) len() int {
   return len(n)
}

func main() {
   var myname Str = "taozs" //其实就是字符串类型
   l := []byte(myname) //字符串转字节数组
   fmt.Println(len(l)) //字节长度
   fmt.Println(myname.len()) //调用对象的方法
}

//3、结构体内嵌匿名成员

//结构体内嵌匿名成员定义
type person struct {
   string //直接写类型,匿名
   age int
}

func main() {
   //结构体匿名成员初始化
   p := person{string: "taozs", age: 18}//可以省略部分字段,如:person{string: "taozs"}。也可以这样省略字段名:person{“taozs”, 18},但必须写全了,不可以省略部分字段
   //结构体匿名成员访问
   fmt.Println(p.string) //注意不能用强制类型转换(类型断言):p.(string)
}

以下是只有单个匿名成员的例子
//结构体内嵌匿名成员定义
type person struct {
   string
}

func main() {
   //结构体匿名成员初始化
   p := person{string: "taozs"} //也可这样:person{"taozs"}
   //结构体匿名成员访问
   fmt.Println(p.string) //注意不能用强制类型转换(类型断言):p.(string)
}

//4、定义接口类型
package main

import (
   "fmt"
)

//接口定义
type Personer interface {
   Run()
   Name() string
}

//实现接口,注意实现接口的不一定只是结构体,也可以是函数对象,参见下面第5条
type person struct {
   name string
   age  int
}

func (person) Run() {
   fmt.Println("running...")
}

//接收参数person不可以是指针类型,否则不认为是实现了接口
func (p person) Name() string {
   return p.name
}

func main() {
//接口类型的变量定义
var p Personer
   fmt.Println(p) //值<nil>
   
   //实例化结构体,并赋值给interface
   p = person{"taozs", 18} //或者:&person{"taozs", 18}
   p.Run()
   fmt.Println(p.Name())
   
   var p2 person = p.(person) //类型断言,接口类型断言到具体类型
   fmt.Println(p2.age)
}

//另外,类型断言返回值也可以有第二个bool值,表示断言是否成功,如下:
if p2, ok := p.(person); ok {//断言成功ok值为true
   fmt.Println(ok)
   fmt.Println(p2.age)
}

//5、定义函数类型
//以下是定义一个函数类型handler
type handler func (name string) int

//针对这个函数类型可以再定义方法,如:
func (h handler) add(name string) int {
   return h(name) + 10
}
// 这种定义方式,和实现接口的方法定义差不多,感觉这种就是让代码更清晰,如果声明太复杂,不用看上去全是很乱的这种定义。

// 6、别名定义:定义和原来一样的类型,就是一个别名alias
type nameMap = map[string]interface{}

func main() {
   m :=make(nameMap)
   m["name"] = "golang"
   fmt.Printf("%v", m)
}

下面让我们详细看一下例子,其中涉及了函数、函数的方法、结构体方法、接口的使用。

package main

import (
   "fmt"
)

//定义接口
type adder interface {
   add(string) int
}

//定义函数类型
type handler func (name string) int

//实现函数类型方法
func (h handler) add(name string) int {
   return h(name) + 10
}

//函数参数类型接受实现了adder接口的对象(函数或结构体)
func process(a adder) {
   fmt.Println("process:", a.add("taozs"))
}

//另一个函数定义
func doubler(name string) int {
   return len(name) * 2
}

//非函数类型
type myint int

//实现了adder接口
func (i myint) add(name string) int {
   return len(name) + int(i)
}

func main() {
   //注意要成为函数对象必须显式定义handler类型
   var my handler = func (name string) int {
       return len(name)
    }
    //以下是函数或函数方法的调用
    fmt.Println(my("taozs"))                   //调用函数
    fmt.Println(my.add("taozs")) //调用函数对象的方法
    fmt.Println(handler(doubler).add("taozs")) //doubler函数显式转换成handler函数对象然后调用对象的add方法
    //以下是针对接口adder的调用
    process(my) //process函数需要adder接口类型参数
    process(handler(doubler)) //因为process接受的参数类型是handler,所以这儿要强制转换
    process(myint(8)) //实现adder接口不仅可以是函数也可以是结构体
}

Type类型系统

可赋值性&类型转换

命名类型&非命名类型

命名类型(defined type或named type)

具有名称的类型:例如:int, int64, float32, string, bool等。这些已经是GO中预先声明好的类型。

通过类型声明语句(type declaration )创建的所有类型都是命名类型type T 类型声明

一个命名类型一定和其它类型不同!

例如:

type myInt int // myInt是一个命名类型,所以myInt类型与其他类型(如int)不同。
type mymap map[string]string // named type

未命名类型(unnamed type)

组合类型:数组,切片,map,通道,指针,结构体,接口,函数, 都是未命名类型。

未命名类型虽然他们没有名字,但却有一个类型字面量(type literal)来描述他们由什么构成(即未命名类型使用类型字面量表示)。

[]string // unnamed type
map[string]string // unnamed type
[10]int // unnamed type

基础类型(underlying type)

任何类型T都有基本类型

如果T是预先声明类型boolean, numeric, or string(布尔,数值,字符串)中的一个,或者是一个类型字面量(type literal),他们对应的基础类型就是T自身。否则,T的基础类型就是T所引用的那个类型的类型声明(type declaration)。

即:

基础类型要么是预先声明类型,要么是组合类型的类型字面量,不能是自定义的命名类型的名称。(即如果T是一个类型字面量,它的基础类型就是T自身)

img

type I int32 // numeric
type II float64  // numeric  ,所以I和II具有相同的基础类型numeric

type person struct{
  name string
  age int
} 
// person类型的基础类型为:
struct{
  name string
  age int
}

type HandlerFunc func(ResponseWriter, *Request)  // 基础类型:func(ResponseWriter, *Request)

所以:

第3,8行:他们的类型声明为 string的预先声明的类型,所以他们的基础类型就是T它自身: string

第5,7行:他们有类型字面量,所以他们的基础类型也是T它自身:map[string]int*N 指针。注意:这些类型字面量还是 未命名类型(unnamed type)

第4,6,10行:T的基本类型是T所引用的那个类型的类型声明(type declaration)。

  • 4行:B引用了A,因此B的基础类型是A的类型声明:string
  • 6行:N引用了M, 因此N的基础类型是M的类型声明:map[string]int

需要注意的是第9行:

type T map[S]int. 由于s的基础类型是string,那么是否type T map[S]int的基础类型应该是 map[string]int 而并非map[S]int呢?确定吗? 在此,我们讨论的是基础未命名类型map[S]int因为基础类型只追溯到它的未命名类型的最外层,所以map[S]int内层的S就不再追溯(或者就像说明上说的:如果T是一个类型字面量,它的基础类型就是T自身),所以U的基础类型是map[S]int

可赋值性

什么情况下变量x可以赋值给另一个类型T

img

  • x的类型与T相同
  • T是一个接口类型,而且x的类型实现了接口T
  • x的类型V与T具有相同的基础类型,而且V和T其中至少有一个不是命名类型
  • nil值可以赋值给如下类型:指针类型、函数类型、slice、map、channel、接口类型

具有相同的基础类型的2种类型(T1,T2)的变量是可以相互赋值的,前提是有可能需要做类型转换。

  • 如果T1、T2二者都是命名类型,则需要做类型转换。
  • 如果T1、T2二者都是非命名类型(类型字面量),或二者一个是命名类型一个是类型字面量,而可以直接赋值。

双方应该具有相同的基础类型,而且其中至少有一个不是命名类型(至少有一个是未命名变类型)。

package main

import "fmt"

type aInt int

func main() {
    var i int = 10
    var ai aInt = 100
 		i = ai  // Error: i和ai都是命名类型,无法直接赋值,需要做类型转换:i= int(ai)
    printAiType(i)
}

func printAiType(ai aInt) {
    fmt.print(ai)
}

所以,上面的代码将不会 通过编译并且会报以下编译错误:

8:4: cannot use ai (type aInt) as type int in assignment
9:13: cannot use i (type int) as type aInt in argument to printAiType

因为i是命名类型intai是命名类型aInt, 虽然他们的基础类型是相同的,但是没有未命名类型,所以不能赋值。

package main

import "fmt"

type MyMap map[int]int

func main() {
    m := make(map[int]int)  // m是非命名类型(类型字面量)
    var mMap MyMap          // mMap是命名类型(类型的名称是MyMap)
    mMap = m								// m和mMap具有相同的基础类型,且m是非命名类型,所以可以直接赋值,无需类型转换。
    printAiType(mMap)
    fmt.Print(m)
}

func printAiType(mMap MyMap) {
    fmt.print(mMap)
}

编译通过,因为m是未命名类型,同时mmMap有相同的基础类型。

类型转换

img

  • 具有相同的基础类型,就可以做类型转换。

  • 忽略结构体的tags,只要结构体X和T拥有相同的基础类型,就可以转换。

  • x和T都是非命名的指针类型,并且指针的base类型具有相同的基础类型。

类型一致性

两种类型要么相同,要么不相同。

  • 一个命名类型一定和其它类型都不同。(如果要相互赋值,就需要进行类型转换)
  • 如果他们其中至少有一个为未命名类型,且基础类型的字面量在结构上是等价的,他们就是相同的类型。

因此,预先声明的命名类型 int, int64, 等都是不一致的。

结构体转换规则

忽略结构体的tags,只要结构体X和T拥有相同的基础类型,就可以转换。

package main

type Meter struct {
    value int64
}

type Centimeter struct {
    value int32
}

func main() {
    cm := Centimeter{
        value: 1000,
    }

    var m Meter
    m = Meter(cm)
    print(m.value)
    cm = Centimeter(m)
    print(cm.value)
}

注意这个词:相同的基础类型,因为字段 Meter.value 的基础类型是 int64 , 字段Centimeter.value 的基础类型是 int32 ,因为一个命名类型一定和其它任意类型都不同。所以他们不一致,不是相同的基础类型,所以不能转换。我们会得到编译错误。

因为Meter的基础类型为:

struct {
    value int64
}

Centimeter的基础类型为:

struct {
    value int32
}

以上两个基础类型不相同,所以不能相互转换。

package main

type Meter struct {
    value int64
}

type Centimeter struct {
    value int64
}

func main() {
    cm := Centimeter{
        value: 1000,
    }

    var m Meter
    m = Meter(cm)
    print(m.value)
    cm = Centimeter(m)
    print(cm.value)
}

字段 Meter.value 的基础类型是 int64 ,字段Centimeter.value 的基础类型是 int64。一致的基础类型,所以可以相互转换,编译通过。

首先,Meter和Centimeter类型都是命名类型,2种命名类型肯定是2种不同的类型,不能直接赋值,需要做类型转换后方可赋值。

其次,因为类型Meter和类型Centimeter的基础类型都是:

struct {
    value int64
}

所以,基础类型是一致的,是可以相互转换的。

error

error与errors:Go语言error类型详解:https://blog.csdn.net/fwhezfwhez/article/details/79175376

包管理

包:https://www.liwenzhou.com/posts/Go/11_package/

依赖:https://www.liwenzhou.com/posts/Go/go_dependency/

导入本地包:https://www.liwenzhou.com/posts/Go/import_local_package_in_go_module/

vender本地包:https://blog.csdn.net/test1280/article/details/120855865

mysql

https://www.liwenzhou.com/posts/Go/go_mysql/

https://www.liwenzhou.com/posts/Go/sqlx/

time模块:

https://blog.csdn.net/wschq/article/details/80114036

sync.Map类型

https://www.cnblogs.com/ricklz/p/13659397.html

golang实现Set集合类型

https://www.cnblogs.com/zhangyafei/p/13276705.html

【推荐使用】https://www.jianshu.com/p/fee89fbb5f27

https://my.oschina.net/claymore/blog/3042078

go-web:net/http模块

源码分析启动流程:https://www.jianshu.com/p/2c2bfe80bd91

源码分析:https://www.cnblogs.com/f-ck-need-u/p/9832538.html

各种io接口实现

go 的读写大总结

【推荐】浅析 Go IO 的知识框架(https://blog.csdn.net/slphahaha/article/details/119792661)

Go语言 io包核心接口详解

缓冲区机制详解带缓冲 IO 与不带缓冲 IO 的区别与联系

image-20220623081534605

ioutil.ReadFile(filename string) ([]byte,error)

io.WriteString(w io.Writer, str string)  # 向Writer中写入字符串
io.WriteFile(filename string,data []byte, perm fs.FileMode)  # 向filename中写入data

https://blog.csdn.net/qq_42305808/article/details/121931574

https://www.jianshu.com/p/d86119d19d77

命令行工具

cobra

https://blog.csdn.net/inthat/article/details/123527784

https://www.jianshu.com/p/753bc2f9d2df

urfave/cli

github.com/urfave/cli

静态连接与动态连接-ldflags

ldflags在golang编译中的2个作用

静态链接,动态链接,静态库,共享库这些概念的详解

golang在编译时用ldflags设置变量的值

也谈Go的可移植性

filepath.Walk

filepath.Walk会变量指定目录下的所有文件和目录,并将每个文件或目录的文件路径path、文件信息info、文件错误err传递给walkFunc来进行回调,回调函数可以自定义实现。

import (
	"fmt"
	"os"
	"path/filepath"
)

func walkFunc(path string, info os.FileInfo, err error) error {
	if info.IsDir() {
		fmt.Printf("----------> dir :%s\n", path)
	}else {
		fmt.Printf("%s\n", path)
	}
	return nil
}

func main() {
	//遍历打印所有的文件名
	filepath.Walk("/Users/lijuzhang/Downloads", walkFunc)
}