Skip to content

bfyxzls/lind-go

Repository files navigation

lind-go

对go语言的学习

go.mod

在Go语言中,go.mod文件用于管理项目的依赖关系和版本信息。一般来说,go.mod文件是由go mod init命令自动生成的,它会根据你的项目结构和依赖情况自动创建和更新。

一旦go.mod文件生成后,大部分情况下不需要手工维护。当你使用go getgo buildgo run等命令时,Go工具会自动检查go.mod文件并下载所需的依赖项,同时更新go.modgo.sum文件。

然而,在某些情况下,可能需要手工维护go.mod文件,比如手动添加或删除依赖项、指定特定的依赖版本等。总的来说,大部分情况下go.mod文件可以由Go工具自动维护,但在特殊情况下可能需要手动进行调整。

go中的WEB框架

Go语言有几个成熟的Web开发框架,其中比较流行的包括:

  1. Gin:Gin是一个轻量级的Web框架,性能优异,使用简单,适合快速开发API和Web服务。
  2. Beego:Beego是一个全功能的Web框架,提供了路由、ORM、Session管理等功能,适合构建中大型应用。
  3. Echo:Echo是另一个轻量级的Web框架,与Gin类似,具有高性能和简洁的特点,适合快速构建RESTful API。
  4. Iris:Iris是一个高性能的Web框架,提供了丰富的功能和插件支持,适合构建复杂的Web应用。 这些框架都有相应的文档和社区支持,可以根据项目需求选择合适的框架进行开发。

一 golang基础知识

Go(又称 Golang)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 开发的一种计算机编程语言语言。 设计初衷 Go语言是谷歌推出的一种的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发Go,是因为过去10多年间软件开发的难度令人沮丧。派克表示,和今天的C++或C一样,Go是一种系统语言。他解释道,"使用它可以进行快速开发,同时它还是一个真正的编译语言,我们之所以现在将其开源,原因是我们认为它已经非常有用和强大。"

  • 计算机硬件技术更新频繁,性能提高很快。目前主流的编程语言发展明显落后于硬件,不能合理利用多核多CPU的优势提升软件系统性能。
  • 软件系统复杂度越来越高,维护成本越来越高,目前缺乏一个足够简洁高效的编程语言。
  • 企业运行维护很多c/c++的项目,c/c++程序运行速度虽然很快,但是编译速度确很慢,同时还存在内存泄漏的一系列的困扰需要解决。

应用领域

![image-20230425134447756](D:\github\lind-go\assets\Sample 1. HelloWorld.twinproj)

数据类型

  • int :有符号的整数类型:具体占几个字节要看操作系统的分配:不过至少分配给32位。
  • uint:非负整数类型:具体占几个字节要看操作系统的分配:不过至少分配给32位。
  • int8:有符号的整数类型:占8位bit:1个字节。范围从负的2的8次方到正的2的8次方减1。
  • int16:有符号的整数类型:占16位bit:2个字节。范围从负的2的16次方到正的2的16次方减1。
  • int32:有符号的整数类型:占32位bit:4个字节。范围从负的2的32次方到正的2的32次方减1。
  • int64:有符号的整数类型:占64位bit:8个字节。范围从负的2的64次方到正的2的64次方减1。
  • uint8:无符号的正整数类型:占8位:从0到2的9次方减1.也就是0到255.
  • uint16:无符号的正整数类型:占16位:从0到2的8次方减1.
  • uint32:无符号的正整数类型:占32位:从0到2的32次方减1.
  • uint64:无符号的正整数类型:占64位:从0到2的64次方减1.
  • uintptr:无符号的储存指针位置的类型。也就是所谓的地址类型。
  • rune :等于int32:这里是经常指文字符。
  • byte:等于uint8:这里专门指字节符
  • string:字符串:通常是一个切片类型:数组内部使用rune
  • float32:浮点型:包括正负小数:IEEE-754 32位的集合
  • float64:浮点型:包括正负小数:IEEE-754 64位的集合
  • complex64:复数:实部和虚部是float32
  • complex128:复数:实部和虚部都是float64
  • error:错误类型,真实的类型是一个接口。
  • bool:布尔类型

格式化输出 在Go语言的 fmt.Errorf 函数中,您可以使用格式化字符串和参数来构建错误消息。fmt.Errorf 的格式化参数采用类似于 Printf 函数的语法,其中使用占位符 % 来表示格式化参数,并根据需要提供相应的值。

以下是一些常见的格式化占位符:

    %s:字符串参数。
    %d:整数参数。
    %f:浮点数参数。
    %t:布尔值参数。
    %v:任意类型的参数。

您可以在格式化字符串中使用这些占位符,并在函数调用时提供相应的值。例如:

import "fmt"

func main() {
name := "John"
age := 30
err := fmt.Errorf("Error: Name %s, Age %d", name, age)
fmt.Println(err)
}

上面的示例将输出 Error: Name John, Age 30。

您还可以使用其他格式化标识符和选项来控制输出的格式,如精度、填充等。更多的格式化选项可以在标准库的 fmt 包文档中找到。

基础组件分为以下几种

  • 引用类型
    • slice
    • interface
    • chan
    • map
  • 非引用类型
    • array
    • func
    • struct

声明包和引用包

package main

import (
"fmt"
"lind-go/common"
//自定义的本项目的包
_ "lind-go/common"
)

赋值符号

var a
b :=

其中var 这种方式不论是局部还是全局变量都可以使用,但是后者也就是:=只有局部变量可以使用。也就是只有函数内部才能使用。 并且,var后面的变量后面的类型是可以省略的,省略后,go会在编译过程中自动判断。所以如果不省略就是长这样 var a int 。 数组的初始化

// 初始化的方式1
a := [6]string{}
// 初始化的方式2
var a [6]string

a[0] = "0"
a[1] = "1"
a[2] = "2"
a[3] = "3"
a[4] = "4"
a[5] = "5"

二维数组初始化

a:= [][2]string{
		{"WWW-Authenticate", fmt.Sprintf("Basic realm=%s", realm)},
		{"client", fmt.Sprintf("client=%s", clientId)},
	}
// [][2]string表示一个二维数组,其中每个元素都是一个包含两个字符串的数组。换句话说,它是一个由多个长度为2的字符串数组组成的切片
// 这种类型在处理需要二维结构的数据时非常有用,比如表示一组键-值对、坐标点等。

二 golang 推荐的命名规范

很少见人总结一些命名规范,也可能是笔者孤陋寡闻, 作为一个两年的golang 开发者, 我根据很多知名的项目,如 moby, kubernetess 等总结了一些常见的命名规范。 命名规范可以使得代码更容易与阅读, 更少的出现错误。

文件命名规范 由于文件跟包无任何关系, 而又避免windows大小写的问题,所以推荐的明明规范如下:

  • 文件名应一律使用小写
  • 不同单词之间用下划线分割
  • 命名应尽可能地见名知意

常量命名规范 常量明明用 camelcase来命名示例如下

const todayNews = "Hello"

// 如果超过了一个常量应该用括号的方法来组织

const (
systemName = "What"
sysVal = "dasdsada"
)

变量命名规范 与常量命名方式一样,变量也应该使用驼峰的命名方式, 但注意尽量不与包名一致或者以包名开头

var x string
x := new(string)

函数命名规范 由于Golang的特殊性(用大小写来控制函数的可见性),除特殊的性能测试与单元测试函数之外, 都应该遵循如下原则

  • 使用驼峰命名
  • 如果包外不需要访问请用小写开头的函数
  • 如果需要暴露出去给包外访问需要使用大写开头的函数名称 一个典型的函数命名方法如下:
// 注释一律使用双斜线, 对象暴露的方法
func (*fileDao) AddFile(file *model.File) bool {
result := db.NewRecord(*file)
if result {
db.Create(file)
}
return result
}

// 不需要给包外访问的函数如下
func removeCommaAndQuote(content string) string {
re, _ := regexp.Compile("[\\`\\,]+")
return strings.TrimSpace(re.ReplaceAllString(content, ""))
}

接口命名规范 接口命名也是要遵循驼峰方式命名, 可以用 type alias 来定义大写开头的type 给包外访问

type helloWorld interface {
func Hello();
}

type SayHello helloWorld

Struct命名规范

  • 与接口命名规范类似

receiver 命名规范 golang 中存在receiver 的概念 receiver 名称应该尽量保持一致, 避免this, super,等其他语言的一些语义关键字如下

  • 定义类型A
  • 定义类型A的方法methodA,没有参数,没有返回值
  • 定义类型A的方法 methodB, 参数int类型的size, 返回值是string类型, 在methodB中调用methodA
type A struct{}

func (a *A) methodA() {
}
func (a *A) methodB(size int) string {
a.methodA()
}

注释规范

  • 注释应一律使用双斜线

三 golang 方法接收者

  • [定义]: golang的方法(Method)是一个带有receiver的函数Function,Receiver是一个特定的struct类型,当你将函数Function附加到该receiver, 这个方法Method就能获取该receiver的属性和其他方法。
  • [面向对象]: golang方法Method允许你在类型上定义函数,是一个面向对象的行为代码, 这也有一些益处:同一个package可以有相同的方法名, 但是函数Function却不行。
func (receiver receiver_type) some_func_name(arguments) return_values

从应用上讲,方法接受者分为值接收者,指针接收者,初级golang学者可能看过这两个接收者实际表现, 但是一直很混淆,很难记忆。

值类型方法接收者

  • 值接受者: receiver是struct等值类型。 下面定义了值类型接受者Person, 尝试使用Person{}, &Person{}去调用接受者函数。
package main
import "fmt"

type Person struct {
 name  string
 age int
}

func (p Person) say() {
 fmt.Printf("I (%p) ma %s, %d years old \n",&p, p.name,p.age)
}

func (p Person) older(){  // 值类型方法接受者: 接受者是原类型值的副本
 p.age = p.age +1
 fmt.Printf("I (%p) am %s, %d years old\n", &p, p.name,p.age)
}

func main() {
  p1 := Person{name: "zhangsan", age: 20}
  p1.older()
  p1.say()
  fmt.Printf("I (%p) am  %s, %d years old\n",&p1, p1.name,p1.age)

  p2 := &Person{ name: "sili", age: 20}
  p2.older()   // 即使定义的是值类型接受者, 指针类型依旧可以使用,但我们传递进去的还是值类型的副本
  p2.say()
  fmt.Printf("I (%p) am %s, %d years old\n",p2, p2.name,p2.age)
}

尝试改变p1=Person{},p2=&Person{}的字段值:

I (0xc000098078) am zhangsan, 21 years old
I (0xc000098090) ma zhangsan, 20 years old
I (0xc000098060) am  zhangsan, 20 years old
I (0xc0000980c0) am sili, 21 years old
I (0xc0000980d8) ma sili, 20 years old
I (0xc0000980a8) am sili, 20 years old

p1=Person{} 未能修改原p1的字段值; p2=&Person{}也未能修改原p2的字段值。

  • 通过Person{}值去调用函数, 传入函数的是原值的副本, 这里通过第一行和第三行的%p印证 (%p:输出地址值, 这两个非同一地址)。
  • 即使定义的是值类型接收者,指针类型依旧可以调用函数, 但是传递进去的还是值类型的副本。

带来的效果是:对值类型接收者内的字段操作,并不影响原调用者。

指针类型接受者

方法接收者也可以定义在指针上,任何尝试对指针接收者的修改,会体现到调用者。

package main

import  "fmt"

type Person struct{
 name string
 age int
}

func  (p Person) say(){
 fmt.Printf("I (%p)  am %s, %d years old\n", &p, p.name,p.age)
}

func (p *Person) older(){   // 指针接受者,传递函数内部的是原类型值(指针), 函数内的操作会体现到原指针指向的空间
 p.age = p.age +1
 fmt.Printf("I (%p)  am %s, %d years old\n", p, p.name,p.age)
}

func main() {
 p1 := Person{"zhangsan",20}
 p1.older()  // 虽然定义的是指针接受者,但是值类型依旧可以使用,但是会隐式传入指针值
 p1.say()
 fmt.Printf("I (%p)  am %s, %d years old\n", &p1, p1.name,p1.age)

 p2:= &Person{"sili",20}
 p2.older()
 p2.say()
 fmt.Printf("I (%p)  am %s, %d years old\n", p2, p2.name,p2.age)
}

尝试改变p1= Person{}, p2=&Person{}字段值

I (0xc000098060)  am zhangsan, 21 years old
I (0xc000098078)  am zhangsan, 21 years old
I (0xc000098060)  am zhangsan, 21 years old
I (0xc000098090)  am sili, 21 years old
I (0xc0000980a8)  am sili, 21 years old
I (0xc000098090)  am sili, 21 years old

p1=Person{} 成功修改字段值,p2=&Person{}也成功修改字段值。

  • 通过p1也可以调用指针函数接收者, 但是实际会隐式传递指针值。
  • 指针接收者,入参是原指针值,函数内的操作会体现到原调用者。

带来的效果: 任何对指针接收者的修改会体现到 原调用者。

什么时候使用指针接收者

  • 需要对接受者的变更能体现到原调用者
  • 当struct占用很大内存,最好使用指针接受者,否则每次调用接受者函数 都会形成struct的大副本

golang方法的几种姿势

  • 将接收者函数当扩展函数
Person.say(p1)
(*Person).older(p2)
  • golang 方法链条
func (p Person) printName() Person{
  fmt.Printf("Name:%s", p.Name)
  return p
}
* Non_struct类型golang方法
```go
type myFloat float64
func (m myFloat) ceil() float64 {
   return  math.Ceil(float64(m))
}

四 golang 接口和结构体

Go语言中没有“类”的概念:也不支持像继承这种面向对象的概念。但是Go 语言的结构体与“类”都是复合结构体:而且Go 语言中结构体的组合方式比面向对象具有更高的扩展性和灵活性。 结构体的定义和初始化

结构体定义:
type identifier struct {
field1 type1
field2 type2
}

结构体实例创建
s1 := new(Student) //第一种方式
s2 := Student{“james”, 35} //第二种方式
s3 := &Student { //第三种方式
Name: “LeBron”,
Age: 36,
}

common文件夹定义常量

当前目录的common子目录下建立Info.go文件:内容如下:

package common

const (
	Name  = "lind"
	Title = "go语言"
)

接口定义

// 接口定义
type Usb interface {
	Start()
	Stop()
}

结构体实现接口

// Mp3 将接口两个方法都实现了
type Mp3 struct {
	Name string
}

func (c *Mp3) Start() {
	fmt.Println(c.Name, "start working")
}
func (c *Mp3) Stop() {
	fmt.Println(c.Name, "stop work")
}

**定义一个使用接口的结构:它的方法参数是接口Usb的实例对象

// 定义使用者的结构体
type Computer struct {
	Name string
}

// Working方法接收Usb接口对象:然后调用它的两个方法
func (c *Computer) Working(u Usb) {
	u.Start()
	u.Stop()
}

**在main方法中使用它:程序启动时执行

func main() {
	fmt.Println("auth:%s", common.Name)
	mp3 := Mp3{"mp3"}
	computer := Computer{"computer"}
	computer.Working(&mp3)
}

五 数据和切片的区别

在Go语言中,数组(array)和切片(slice)是两种不同的数据结构,它们有一些重要的区别。

数组(Array)

  • 数组是一个固定长度的数据结构,定义时需要指定数组的长度。
  • 数组的长度是其类型的一部分,因此[5]int[10]int是两种不同的类型。
  • 数组的长度是不可变的,一旦定义后无法改变。
var arr1 [5]int // 定义一个包含5个整数的数组

切片(Slice)

  • 切片是对数组的一个引用,它是一个动态长度的序列。
  • 切片没有固定的长度,在创建时可以指定初始长度,但是可以动态增长。
  • 切片是一个引用类型,它的底层是一个数组。
var slice1 []int // 定义一个整数切片

区别总结

  1. 长度: 数组的长度是固定的,而切片的长度是动态可变的。
  2. 引用: 切片是对数组的引用,对切片的修改会影响到底层数组,而数组是一个完整的数据结构。
  3. 传递方式: 在函数参数传递中,数组是值传递,而切片是引用传递。
  4. 使用场景: 通常情况下,切片更加灵活,更适合在实际开发中使用。数组则更适合于长度固定且预先知道大小的情况。

六 对指针类型的理解

在Go语言中,指针是一种特殊的数据类型,它存储了一个变量的内存地址。通过指针,我们可以直接访问或修改变量的值,而不是对变量进行复制或传递。

下面是对Go语言中指针类型的一些理解:

  1. 声明和使用: 在Go语言中,可以使用*操作符来声明指针类型,例如var ptr *int表示ptr是一个指向int类型的指针。要获取变量的地址,可以使用&操作符,比如&variable会返回variable的内存地址。

  2. 传递参数: 在函数参数列表中,可以使用指针类型作为参数,这样可以直接修改原始变量的值。这在需要修改函数外部变量的情况下非常有用。

  3. 避免拷贝: 通过传递指针而不是值,可以避免在函数调用时进行大量的内存拷贝,从而提高程序的性能。

  4. 空指针: 在Go语言中,指针的零值是nil,表示指针不指向任何有效的内存地址。

  5. 安全性: Go语言中的指针不能进行指针运算,这在一定程度上提高了程序的安全性。

  6. 总的来说,在Go语言中,指针是一种强大的工具,可以用于管理内存、传递参数、避免拷贝等场景。然而,需要小心使用指针,以避免出现潜在的内存错误和问题。

七 方法参数指针传递的好处

相当于java和.net里的引用类型

在Go语言中,方法参数可以传递变量的地址(指针)或者直接传递变量值。使用指针作为方法参数有以下几个好处:

  1. 修改原始变量:通过传递变量的地址,方法可以直接修改原始变量的值。这意味着你可以在方法内部修改调用者的变量,而不是创建一个变量的副本。

  2. 节省内存和时间:当传递大的数据结构(比如大型数组、切片或结构体)时,传递地址可以避免复制整个数据结构,从而节省内存和时间。

  3. 避免拷贝:在Go语言中,将大的数据结构作为参数传递时,会发生一次浅拷贝。如果数据结构很大,这可能会影响性能。通过传递地址,可以避免这种拷贝。

  4. 支持可选参数:通过传递指针,可以实现方法的可选参数。通过检查指针是否为nil,方法可以知道是否需要处理某些参数。

尽管使用指针作为方法参数具有上述好处,但也需要谨慎使用。过度地使用指针可能会导致代码难以理解和维护,因此需要根据具体情况权衡使用指针和传值的利弊。

泛型类型

在Go语言中,这种定义使用了类型参数,是Go语言的泛型特性之一。这个定义表明 ClusterClient 是一个泛型类型,它接受一个类型参数 C,而 C 必须是 Cluster 类型或者实现了 Cluster 接口的类型。

通过这种方式,我们可以定义一个通用的 ClusterClient 类型,在实例化时指定具体的类型参数,从而使得 ClusterClient 可以适用于不同的 Cluster 类型。

举个例子,假设我们有两种不同的 Cluster 类型 ACluster 和 BCluster,它们都实现了 Cluster 接口,那么我们可以分别实例化两个不同类型的 ClusterClient:

type ACluster struct {
    //...
}

type BCluster struct {
    //...
}

func (a ACluster) Method() {
    //...
}

func (b BCluster) Method() {
    //...
}

type Cluster interface {
    Method()
}

type ClusterClient[C Cluster] struct {
    cluster C
}

func main() {
    a := ClusterClient{cluster: ACluster{}}
    b := ClusterClient{cluster: BCluster{}}
}

通过这样的定义,我们可以很方便地创建不同类型的 ClusterClient 实例,而不需要为每种 Cluster 类型都单独定义一个 ClusterClient 类型。这样的泛型特性可以提高代码的复用性和灵活性。

组合代替继承

go中没有类,所以也就没有类的继承,如果希望使用面向对象中的继承特性,你可以通过组合来实现,即将结构的实例做为另一结构里的字段即可,如下代码实例:

type RedisConfig struct {
	name string
}

func (c *RedisConfig) printName() {
	fmt.Println(c.name)
}

type LindRedisConfig struct {
	RedisConfig
	info string
}
func main() {
lindRedisConfig := LindRedisConfig{RedisConfig{"lind"}, "info"}
lindRedisConfig.printName() #可以直接使用RedisConfig中的方法printName或者RedisConfig中的字段name
}