GO语言编程必备技巧备忘录
  iwbGD3gmtxyT 2023年11月20日 15 0

[提醒]


  • 本文适合于从其他高级语言转型到GO语言开发的读者阅读参考。
  • 本文在一段时间内将一直处于更新状态。。。

常数

GO语言中,使用const关键字作为前缀来声明一个具有特定类型的常量。它不能用”: = “语法来声明。

package main
 
import "fmt"
 
const PI = 3.14
 
func main()
{
    const GFG = "GeeksforGeeks"
    fmt.Println("Hello", GFG)
 
    fmt.Println("Happy", PI, "Day")
 
    const Correct= true
    fmt.Println("Go rules?", Correct)
}

非类型和类型的数字常量:

类型的常量的工作方式类似于不可变的变量,只能与相同的类型相互操作,非类型的常量的工作方式类似于字面意思,可以与类似的类型相互操作。在Go中,常量可以带或不带类型来声明。下面是一个例子,显示了有类型和无类型的数字常量,这些常量都是有名字和无名字的。

const untypedInteger          = 123
const untypedFloating          = 123.12

const typedInteger  int             = 123
const typedFloatingPoint   float64  = 123.12

由于Go是一种静态类型的语言,不允许混合数字类型的操作。你不能把float64加到 int中,甚至不能把 int32加到int中。但是,写1e6*time.Second*math.Exp(1)甚至1 <<('\t'+2.0)都是合法的。

整数常 量。

  • 前缀指定基数或弧度。十六进制为0x或0X,八进制为0,十进制为无。
  • 一个整数常量也可以有一个 后缀 ,是U(大写)和L(小写)的组合,分别代表无符号和长符号。
  • 它可以是一个十进制、八进制或十六进制的常数。
  • 一个int最多能存储一个64位的整数,有时甚至更少。

以下是一些整数常数的例子。

85         /* decimal */
0213       /* octal */
0x4b       /* hexadecimal */
30         /* int */
30u        /* unsigned int */
30l        /* long */
30ul       /* unsigned long */
212         /* Legal */
215u        /* Legal */
0xFeeL      /* Legal */
078         /* Illegal: 8 is not an octal digit */
032UU       /* Illegal: cannot repeat a suffix */


变量

(一)go 语言中,下划线有哪些作用?请举例说明。

在Go语言中,下划线(_)有多种作用,下面列举了一些常见的用法:

忽略变量名

在Go语言中,如果你使用单个下划线(_)作为变量名,它会忽略该变量的值。这意味着在使用该变量时,不会将其值赋给其他变量或进行其他操作。这是一个方便的技巧,特别是在你不需要使用某个变量的值时。

例如:

var _ int = 42  
 
 x := 10  
 
 fmt.Println(x, _) // 输出:10 42

在上面的例子中,_被用于忽略一个整型变量的值,而不会影响其他变量。

  1. 假设已有一个 接口 json.Marshaler, 我们自己编码创建了一个 RawMessage 对象。
  2. 如果 RawMessage 对象能满足 json.Marshaler 接口一切皆大欢喜;但是,如果不满足, 那么在运行中就可能出现严重异常。然而, 在编码阶段编译器并不能自动发现用户对象是否满足接口。
  3. 因此, 使用了 var TheInterface = *CustomStruct{} (不满足不能赋值) 这种方式进行编码阶段的验证。但是golang特性规定: 声明了的变量必须要使用
  4. 为了解决声明但不使用的情况, 引入了空白描述符 _ 下划线解决这个问题。有了空白描述后, 行为就从赋值 变更为 检查而不创建变量 。 var _ TheInterface = *CustomStruct{}
  5. 最后官方提醒, 这种 奇怪 行为不要乱用, 只用在 那些不能 静态检查 的对象上面。

空白标识符:

下划线也可以用作空白标识符(blank identifier),用于表示没有意义的变量或值。这在某些情况下很有用,比如当你需要一个变量名来满足函数签名或结构体定义的语法要求,但实际上并不需要使用该变量时。

例如:

func foo() (int, error) {  
 
     // 返回一个整数和一个错误对象  
 
     return 42, nil  
 
 }  
 
   
 
 // 使用空白标识符来忽略返回的整数  
 
 num, _ := foo()  
 
 fmt.Println(num) // 输出:42

在上面的例子中,使用空白标识符来忽略函数foo()返回的整数,只保留错误对象。

占位符:

下划线还可以用作占位符,用于在代码中临时替代某个变量或表达式的值。这在调试或测试时很有用,可以让你暂时替换某个部分为某个特定的值,以便观察其对程序行为的影响。

例如:

func bar(x int) int {  
 
     // 使用下划线作为占位符,将x的值替换为某个特定值进行调试或测试  
 
     _ = x * 2  
 
     return x + 10  
 
 }

在上面的例子中,使用下划线作为占位符来临时替换x * 2的值,以便进行调试或测试。

这些是下划线在Go语言中的一些常见用法。通过合理利用下划线的特点,你可以更灵活地编写和理解Go代码。

import语句、包和init函数、下划线

在Go语言中,init函数是一个特殊的函数,它在程序启动时自动执行,无需显式调用。init函数常用于初始化操作,例如配置文件读取、数据库连接等,确保程序在运行之前完成必要的准备工作。本文将深入探讨Go语言中的init函数,帮助您理解其用法和作用。

init函数是Go语言中的一种特殊函数,每个源文件都可以包含一个或多个init函数。这些函数在程序加载时按照文件名的字母顺序依次执行。

import 下划线的作用:当导入一个包时,该包下的文件里所有init()函数都会被执行,然而,有些时候我们并不需要把整个包都导入进来,仅仅是是希望它执行init()函数而已。这个时候就可以使用 import _ 引用该包。即使用【import _ 包路径】只是引用该包,仅仅是为了调用init()函数,所以无法通过包名来调用包中的其他函数。


用法示例
单个源文件中的init函数
package main

import "fmt"

func init() {
    fmt.Println("This is the init function.")
}

func main() {
    fmt.Println("This is the main function.")
}

在上面的示例中,init函数在程序启动时被自动调用,输出 "This is the init function."。随后,main函数被调用,输出 "This is the main function."。

多个init函数的执行顺序

如果一个源文件中包含多个init函数,它们会按照文件名的字母顺序依次执行。

// file1.go
package main

import "fmt"

func init() {
    fmt.Println("init in file1.go")
}

// file2.go
package main

import "fmt"

func init() {
    fmt.Println("init in file2.go")
}

在这个示例中,无论这两个文件被导入的顺序如何,init函数的执行顺序都是固定的。

注意事项
  • init函数没有参数和返回值。
  • init函数无法被其他函数调用,只会在程序启动时自动执行。
  • 在一个源文件中,init函数通常用于初始化全局变量、配置信息等。
初始化顺序

在Go程序启动时,初始化过程的顺序如下:

  1. 导入包。
  2. 执行包级别的init函数。
  3. 执行main函数(如果存在)。
总结

通过本文,我们详细了解了Go语言中的init函数,以及它在程序启动时的执行机制。init函数在项目开发中非常有用,可以用于执行初始化操作,确保程序在运行之前达到预期的状态。合理地使用init函数,将有助于提高代码的可维护性和可读性。希望本文对正在学习或使用Go语言的开发者有所帮助,让您能够更好地理解并运用init函数。

(二)多变量声明

第一种,以逗号分隔,声明与赋值分开,若不赋值,存在默认值

var name1, name2, name3 type
name1, name2, name3 = v1, v2, v3

第二种,直接赋值,下面的变量类型可以是不同的类型

var name1, name2, name3 = v1, v2, v3

第三种,集合类型


var (
    name1 type1
    name2 type2
)


for循环语句

GO语言中的循环语句只有一种:for循环(没有其他多数语言中的while等循环)。在Go语言中,for循环有三种循环语句形式:forfor...rangefor...in

  1. for循环:这是最常用的循环语句,它允许您根据指定的条件重复执行代码块。for循环的语法如下(类似于其他多数高级语言中的基本形式):
for 初始化语句; 条件语句; 后续语句 {  
 
     // 循环体  
 
 }

在循环开始前,会执行一次初始化语句。然后,每次循环迭代之前都会检查条件语句是否为真。如果条件为真,则执行循环体内的代码块,然后执行后续语句。如果条件为假,则循环终止。
2. for...range循环:这是用于迭代数组、切片、字符串、映射和通道等可迭代对象的循环语句。for...range循环的语法如下:

for 索引, 值 := range 可迭代对象 {  
 
     // 循环体  
 
 }

在每次迭代中,索引和值会被分配给一个临时变量,然后执行循环体内的代码块。可以通过省略索引来只获取值而不获取索引。
3. for...in循环(也称为无限循环):这是一种特殊类型的循环,它没有条件语句,因此会一直执行下去,直到出现中断或程序终止。for...in循环的语法如下:

for {  
 
     // 循环体  
 
 }

由于没有条件语句,因此必须在循环体内使用break语句或其他方式来手动中断循环。

这些循环语句提供了不同的功能和用法,根据需要选择适合的循环类型来满足特定的需求。

一种特殊的for循环(无限):
for range time.Tick(time.Second * 2) {
        fmt.Println("Hello")
    }


结构与接口

结构体和接口的组合是Golang中一种非常有用的技术,可以使代码更加简洁和灵活。在实际的开发中,我们可以根据需要灵活地使用结构体和接口来设计和实现各种复杂的数据结构和算法。

除了基本的结构体和接口的使用,Golang还提供了一些高级特性,例如匿名字段、嵌套结构体和嵌入接口。

(一)匿名字段

匿名字段是指结构体中的一个未命名字段,它可以是任何类型,包括基本类型、结构体和接口类型。使用匿名字段可以使结构体的定义更加简洁,并且可以直接访问其字段和方法。

例如,我们可以定义一个名为Rectangle的结构体,包含了两个float64类型的字段Width和Height,以及一个匿名字段Shape,它实现了Shape接口:


type Rectangle struct {
    Width  float64
    Height float64
    Shape
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
    return 2*r.Width + 2*r.Height
}

在上面的代码中,我们定义了一个名为Rectangle的结构体,并使用匿名字段将其与Shape接口进行组合。我们还实现了Area和Perimeter方法,用于计算矩形的面积和周长。

(二)嵌套结构体

嵌套结构体是指在一个结构体中嵌套另一个结构体,从而形成一个包含多个结构体的复合结构体。使用嵌套结构体可以使代码更加模块化和可复用。

例如,我们可以定义一个名为Employee的结构体,它包含了一个Person结构体和一个Salary字段:


type Employee struct {
    Person
    Salary float64
}

type Person struct {
    Name string
    Age  int
}

在上面的代码中,我们定义了一个名为Employee的结构体,并使用嵌套结构体将其与Person结构体进行组合。由于Employee包含了Person,因此我们可以直接访问Person结构体中的字段。

(三)嵌入接口

嵌入接口是指在一个接口中嵌套另一个接口,从而形成一个包含多个接口的复合接口。使用嵌入接口可以使代码更加模块化和可复用。

例如,我们可以定义一个名为Geometry的接口,它包含了Shape和Transform接口:


type Geometry interface {
    Shape
    Transform
}

type Transform interface {
    Translate(x, y float64)
}

func TranslateAll(g []Geometry, x, y float64) {
    for _, shape := range g {
        shape.Translate(x, y)
    }
}

在上面的代码中,我们定义了一个名为Geometry的接口,并使用嵌入接口将其与Shape和Transform接口进行组合。我们还定义了一个名为TranslateAll的函数,用于将一个包含多个Geometry对象的切片进行平移。由于Geometry接口包含了Transform接口,因此我们可以在TranslateAll函数中调用每个Geometry对象的Translate方法。

(四)在GO语言中,结构的构造函数是什么?

在Go语言中,没有像传统面向对象语言那样提供构造函数的概念。Go语言通过结构体字面值或工厂函数来创建结构体实例。

对于结构体字面值,可以直接在代码中定义并初始化结构体变量,例如:

type Person struct {  
      Name string  
      Age  int  
  }  

 func main() {  
      p := Person{Name: "Alice", Age: 25} // 使用结构体字面值创建Person结构体实例  
      fmt.Println(p)  
  }

另一种方法是使用工厂函数来创建结构体实例,例如:

type Person struct {  
      Name string  
      Age  int  
  }  
     
 func NewPerson(name string, age int) *Person {  
      return &Person{Name: name, Age: age} // 使用工厂函数创建Person结构体实例  
  }  
   
  func main() {  
      p := NewPerson("Bob", 30) // 使用工厂函数创建Person结构体实例  
      fmt.Println(p)  
  }

这种方式可以提供更灵活的创建结构体的方式,并且可以将必要的初始化逻辑封装在工厂函数中。

(五)在GO语言中,定义结构时能不能指定默认值?

在Go语言中,结构体(struct)的定义并不直接支持指定默认值。然而,你可以通过在结构体字段的零值上设置默认值。在Go中,如果一个字段没有被显式赋值,那么它将自动获得其类型的零值。

举个例子,如果你有一个包含浮点数的结构体字段,它的默认值将是0.0:

type MyStruct struct {  
      MyFloat float64  
  }  
  
 func main() {  
      s := MyStruct{}  
      fmt.Printf("%f\n", s.MyFloat) // 输出: 0.000000  
  }

同样地,如果结构体字段是一个字符串,那么默认值将是空字符串:

type MyStruct struct {  
      MyString string  
  }  
  
 
 func main() {  
      s := MyStruct{}  
      fmt.Printf("%s\n", s.MyString) // 输出: ""  
  }

对于整型字段,其默认值将是0:

type MyStruct struct {  
      MyInt int  
  }  
  
  func main() {  
      s := MyStruct{}  
     fmt.Printf("%d\n", s.MyInt) // 输出: 0  
  }

需要注意的是,Go语言中的结构体字段的默认值只会在字段没有被赋值时使用。如果你为结构体字段赋了其他值,那么该字段将使用你赋的值,而不是默认值。

(六)

package main
import (
    "fmt"
)
// 定义一个接口
type Shape interface {
    Area() float64
}

// 实现 Shape 接口的结构体:矩形
type Rectangle struct {
    Width  float64
    Height float64
}
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}
// 实现 Shape 接口的结构体:圆形
type Circle struct {
    Radius float64
}
func (c Circle) Area() float64 {
    return 3.14 * c.Radius * c.Radius
}
// 计算形状面积的函数,接收 Shape 接口作为参数
func CalculateArea(s Shape) float64 {
    return s.Area()
}
func main() {
    // 创建一个矩形对象,并将其作为参数传递给 CalculateArea 函数
    //  rectangle := Rectangle{Width: 10, Height: 5}
    area := CalculateArea(&Rectangle{Width: 10, Height: 5})
    fmt.Println("矩形面积:", area)
    // 创建一个圆形对象,并将其作为参数传递给 CalculateArea 函数
    //  circle := Circle{Radius: 5}

    area = CalculateArea(&Circle{Radius: 5})
    fmt.Println("圆形面积:", area)
}

(七)go语言中接口作为函数的参数,当调用函数时能不能传递结构的地址?

在Go语言中,当接口作为函数的参数时,可以通过传递结构的地址来实现对结构体的修改。通过传递结构的地址,函数可以直接修改该结构体的值。

下面是一个示例来说明这个概念:

package main  
 
   
 
 import "fmt"  
 
   
 
 type MyStruct struct {  
 
     value int  
 
 }  
 
   
 
 func modifyStruct(s *MyStruct) {  
 
     s.value = 100 // 修改结构体的值  
 
 }  
 
   
 
 func main() {  
 
     myStruct := MyStruct{value: 42}  
 
     modifyStruct(&myStruct) // 传递结构体的地址给函数  
 
     fmt.Println(myStruct.value) // 输出: 100  
 
 }

在上面的示例中,modifyStruct函数接受一个指向MyStruct类型的指针作为参数。在函数内部,通过该指针修改了结构体的值。在main函数中,我们创建了一个MyStruct类型的实例myStruct,并将其地址传递给modifyStruct函数。在函数调用之后,myStructvalue字段的值被修改为100,并且在main函数中输出。

总结起来,当接口作为函数的参数时,可以传递结构的地址,并且通过指针可以在函数内部修改该结构体的值。



函数

(一)go语言中三个点在函数定义中的作用是什么?

在Go语言中,三个点(...)在函数定义中用于表示可变参数(variadic parameter)。

可变参数允许函数接受可变数量的参数,包括零个或多个参数。这些参数被当作切片(slice)传递给函数。

下面是一个使用可变参数的函数定义的示例:

func sum(numbers ...int) int {  
    total := 0  
    for _, num := range numbers {  
        total += num  
    }  
    return total  
}

在上面的示例中,numbers参数是一个可变参数,它接受任意数量的整数作为输入,并将它们作为切片传递给函数。函数内部可以使用numbers参数来遍历并计算它们的总和。

使用可变参数时需要注意以下几点:

  1. 可变参数必须是函数的最后一个参数。
  2. 可变参数必须是切片类型或可转换为切片类型的类型。
  3. 可变参数在传递给函数时会自动转换为切片类型。
  4. 可变参数不支持传递指针或引用类型。
  5. 可变参数的长度是可变的,可以在调用函数时传递任意数量的参数。

(二)Go语言中为什么需要指针?

Go 语言是一门相对年轻的编程语言,在其设计之初,就考虑了并发和高并发这一核心需求。Go 语言中很多的语言设计都属于从“零开始”思路,包括语言结构、标准库和工具链等。在这个背景下我们需要探讨一个问题,那就是,在以指针为代表的复杂指针语义系统中,为什么 Go 还要保留指针?

指针与内存管理

指针是编程语言中很重要的一种数据类型,它能在程序中起到极为重要的作用。我们相信这一点是毋庸置疑的。

而指针的本质是一个地址,从这个角度讲,指针可以看作是一种特殊的整型。然而指针的本质特性也决定了它具有更多的操作能力,比如指针可以用来修改另一个变量的值。指针可以通过对其它变量进行间接操作来实现很多功能和使用场景,比如指向数组、指向字符串、指向结构体等。

但是,直接的指针操作会带来一些问题,一旦程序出现指针使用的错误,那么就将导致程序崩溃。在 C 和 C++ 中,我们常常会看到指针的滥用和错误使用带来的悲剧,比如空指针异常、野指针等等,而这些错误一旦出现,往往会导致程序崩溃,严重影响程序的稳定性。

为了解决这个问题,在内存管理方面,Go 提供了垃圾回收功能,当某个对象不再被使用时,垃圾回收器便会自动将其释放掉。这种内存管理的方式使得程序员可以更加集中精力于业务逻辑处理上,而不必细心地操心内存的使用和释放,从而降低程序耦合度,提高程序的可靠性和稳定性。

在 Go 中,如果某个对象没有被任何变量引用,那么垃圾回收器会将其释放掉。这种自动垃圾回收设计的一个重要目的是让开发者从手工管理内存的复杂工作中解脱出来。

指针的使用情况

尽管指针有很多最优实践的用途,比如在微调性能方面,通过不必要的内存拷贝来避免分配和修改值,这种用法看起来非常类似于直接使用指针。但是在实际开发中,我们要谨慎地使用指针,因为它与所有其他复杂的 C++ 更高级语言的一些内建类型都有相当大的风险和挑战性,比如指针空间安全和指针空间占用等问题。

尽管如此,指针在 Go 中的使用是非常重要的。Go 语言使用指针的情况主要有以下 4 种情况:

1. 引用类型

Go 语言中有几种称为引用类型的类型,即切片、映射和通道。这些类型都使用了指针内部(不可见)来实现它们的现代动态结构。

2. 需要修改参数值

Go 语言中的函数参数默认是传值的。如果要在函数内部修改变量的值,并使这些修改在函数外部可见,就需要传递地址,也就是指针。比如:

func add(num *int) {  *num = *num + 1}

这个函数接受一个 int 类型的指针参数,然后在函数体内部对这个指针所指向的变量增加了 1。在这种情况下,我们需要用到指针来传递参数,因为变量的地址在函数内外都是相同的。

3. 内存分配

Go 语言中使用 new() 函数来分配内存,该函数返回一个指向指定类型的指针:

p := new(int) // p 指向一个新分配的 int 类型变量

在 Go 中手动分配内存其实不多见,但是在一些场景中,如果我们确实需要自己手动分配内存,这时候指针就会非常有用。

4. 重用存在的对象

在某些情况下,为了减少内存分配,我们可能希望对某些对象进行重用。这时候,可以使用指针来实现这一点。比如:

type Box struct {  width, height float64}type BoxPool []*Boxfunc (p *BoxPool) Get() *Box {  if len(*p) == 0 {    return &Box{}  }  b := (*p)[len(*p)-1]  *p = (*p)[:len(*p)-1]  return b}func (p *BoxPool) Put(b *Box) {  *b = Box{}  *p = append(*p, b)}

这段代码中实现了一个简单的叫做 BoxPool 的对象池。我们在 Pool 中维护了一组 Box 对象,当需要实例化 Box 时,我们首先检查对象池是否为空,如果不为空,则从对象池中获取一个 Box 对象。如果对象池为空,则实例化一个新的 Box 对象并返回。释放对象时,将该对象放回对象池并做清除工作。

在这个例子中,我们使用指针来表示 Box 对象,因为我们希望在获取 Box 对象之后,能够在其中填充实际数据,而不需要新创建一个 Box 对象。


多线程


工具

(一)go-callvis

地址:https://github.com/ondrajz/go-callvis

gocallvis是一个开源的开发工具,可以帮助使用交互式视图可视化go程序的调用图。

在我的机器MAC Catalina(10.15.7)下安装,可以使用如下命令:

go install github.com/ofabry/go-callvis@latest

如果上述命令安装失败,可以使用如下方式下载并编译后再安装:

# Clone repository,which will create a folder 'go-callvis'
git clone https://github.com/ofabry/go-callvis.git
cd go-callvis

# Compile and install
make install

注意:在运行命令“make install”时可能出现运行权限的问题,类似如下面的提示:

make install
go install -tags "" -ldflags "-X main.commit=v0.7.0-1-g219da14 -w -s" -trimpath
github.com/ofabry/go-callvis: go install github.com/ofabry/go-callvis: copying /var/folders/pm/dqd601mn0nd15jzwtj09vj540000gn/T/go-build2732655783/b001/exe/a.out: open /usr/local/go/bin/go-callvis: permission denied
make: *** [install] Error 1

参考文献在地址:

https://blog.csdn.net/longgeaisisi/article/details/123897404

原因分析:

文件没有权限,解决办法是:切换到/usr/local文件夹下,然后执行如下命令赋予文件读写权限:

sudo chmod -R 777 go/

然后,再运行上面的命令“make install”。

验证一下目标文件所在路径:

which go-callvis

显示所在路径如下:

/usr/local/go/bin/go-callvis

简单使用,运行命令如下:

go-callvis main.go

浏览器中显示如下:

GO语言编程必备技巧备忘录_for循环


引用

https://zhuanlan.zhihu.com/p/645853924,闭包详解。

https://www.jianshu.com/p/f876709b128b,结构与接口。

https://modstart.com/p/ob51v1vmtpy2x2l1

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月20日 0

暂无评论

推荐阅读
  hyrB1Ag4eVs8   17天前   34   0   0 Go
  2xk0JyO908yA   29天前   28   0   0 Go
iwbGD3gmtxyT