searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

go语言初学需要注意的点

2023-11-07 09:05:15
12
0

1. 变量是否必须在代码块中被使用

声明了一个局部变量却没有在相同的代码块中使用它,会得到“a declared but not used”编译错误

声明了一个全局变量却没有在相同的代码块中使用它,不会编译错误

2. _: 空白标识符(抛弃只-不接受值)

eg: _,num1,num2 := reture_fun(),只接受后两个值

3.如何识别局部变量和全局变量,作用域是?生命周期是?

局部变量:在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量。

局部变量:在函数调用时存在,函数调用结束后变量被销毁。

全局变量:在函数体外声明的变量称之为全局变量,全局变量可以在整个包甚至外部包(被导出后)使用。

全局变量:程序在整个执行期间都可以访问这些变量。

全局变量和局部变量名相同时,局部变量优先。

4. s1 := 1不带声明格式的不能在函数体外出现

5. func(){ 函数后后{不能换行

6. iota特殊常量,可以被编译器修改,在const出现时被重置为0,后面每出现一次,新增1

    const(
            x = iota //0
            y = iota //1
            z = "nnn" //2
            zz //3
            e = iota //4
            f = 2<<iota //2<<5
            g //2<<6
            h //2<<7
            i = iota //8 
            j = unsafe.Sizeof(i) //9
            l = iota<<3 //10<<3
            m //11<<3
    )
    fmt.Println(x,y,z,zz,e,f,g,h,i,j,l,m)

result:0 1 nnn nnn 4 64 128 256 8 8 80 88

7. Go语言没有三目运算符 ?=

8. switch条件语句,非要执行满足case后的语句,加上fallthrough;case case1,case2 满足case1,case2任意一项即可走该分支;default不论在switch选项的哪部分都是最后执行;switch还可以判断基本类型和接口类型(v.(type))。

基本类型通过接口类型参数传递判断

package main

import (
	"fmt"
)

func judgeType(v interface{}) {
	switch v.(type) {
	case int:
		fmt.Println(v, "is int")
	case string:
		fmt.Println(v, "is string")
	case bool:
		fmt.Println(v, "is bool")
	}
}

func main() {
	judgeType(4)
	judgeType("==string==")
	judgeType(false)
}

4 is int
==string== is string
false is bool

9. 函数可以作为变量(函数式编程思想)

函数作为变量具有很大的意义,这在编程中被称为"函数式编程"。在很多编程语言中,函数都是一等公民,也就是说函数可以被当做普通变量一样进行传递、赋值和引用。

  1. 作为参数传递:函数可以作为另一个函数的参数进行传递,这样可以方便地实现回调函数、事件处理函数等功能。比如在事件处理中,可以将特定的函数作为参数传递给注册函数,当事件发生时调用传递的函数。(回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用,用于对该事件或条件进行响应(回调函数是一种事件驱动机制)。)

  2. 作为返回值:函数也可以作为另一个函数的返回值,这样可以实现更加灵活的逻辑处理。比如,在工厂模式中,可以根据参数返回不同的函数实例。

  3. 保存状态:通过闭包的方式,函数可以保存外部作用域的状态,实现状态的延续。这种特性在并发编程中尤为重要。

  4. 简化代码:将函数作为变量可以简化代码的逻辑,减少重复的代码片段,提高代码的重用性和可读性。

综上所述,函数作为变量可以使代码更加灵活、模块化,并且能够更好地支持函数式编程范式,使得代码更易于理解和维护。

func isOdd(v int) bool {
	if v%2 == 0 {
		return true
	}
	return false
}

func filter(value []int, f func(int) bool) []bool {
	var result []bool
	for _, v := range value {
		result = append(result, f(v))
	}
	return result
}
testvalue := []int{1, 2, 3, 4, 5, 6, 7}
risOdd := filter(testvalue, isOdd)


or
type FuncOdd func(int) bool


func filter(value []int, f FuncOdd) []bool {
	var result []bool
	for _, v := range value {
		result = append(result, f(v))
	}
	return result
}


10. 在Go语言中存在方法和函数

type Circle struct {
	radius float64
}

func (c Circle) getPerimeter() float64 {
	return 2 * 3.14 * c.radius
}


var c1 Circle
c1.radius = 2
fmt.Println(c1.getPerimeter())

12.56

11. 闭包和匿名函数的关系

匿名函数是指没有函数名的函数,可以直接定义在代码块中,通常用于一次性的场景,例如作为参数传递给其他函数、在 goroutine 中执行等。匿名函数可以捕获并访问其所在作用域中的变量。

闭包是指一个函数和其相关的引用环境组合而成的实体。在 Go 语言中,当你在一个函数内部定义了一个匿名函数,并且该匿名函数引用了外部函数的局部变量时,就创建了一个闭包。这意味着该匿名函数可以访问并修改外部函数的局部变量,即使外部函数已经返回,闭包依然可以持有对外部函数局部变量的引用。

因此,匿名函数可以成为闭包,具有捕获上下文中变量的能力。在 Go 语言中,闭包和匿名函数紧密相关,使用匿名函数可以方便地创建闭包。

func getSequence() func() int {
    i := 0
    return func() int {
        i += 1
        return i
    }
}

func main() {
    // 获取一个函数nextNumber,它持有i这个变量并不断自增
    nextNumber := getSequence()

    fmt.Println(nextNumber()) // 输出:1
    fmt.Println(nextNumber()) // 输出:2
    fmt.Println(nextNumber()) // 输出:3
    
    // 创建一个新的函数nextNumber1,它也持有i这个变量并不断自增,但是与上面的nextNumber无关
    nextNumber1 := getSequence()
    fmt.Println(nextNumber1()) // 输出:1
    fmt.Println(nextNumber1()) // 输出:2
}

在这段代码中,getSequence 函数返回了一个匿名函数。这个匿名函数形成了闭包,因为它引用了外部函数 getSequence 中的局部变量 i

getSequence 被调用时,它会初始化局部变量 i 并返回内部定义的匿名函数。每次调用返回的匿名函数时,i 都会被递增并返回其值。由于闭包的特性,每个返回的匿名函数都拥有对自己版本的 i 的引用,因此它们各自维护着自己的状态,互相不影响。

闭包机制的主要用途在于如下两点:

  • 可以获取函数内部的局部变量,实现公有变量
    • 这相较于全局变量和普通局部变量具有明显优势,虽然全局变量可以重复使用,但是容易造成变量污染;而局部变量仅在局部作用域内有效,虽然不会造成变量污染,但是不可以重复使用。

  • 可以让自由变量的值始终保存在内存中,实现缓存效果
    • 闭包会把父作用域(函数)中的变量保存在内存中,直到闭包的生命周期结束,所以利用这一特点可以将被闭包捕获的变量作为缓存空间来使用。

12. 回调函数和匿名函数的关系

在 Go 语言中,匿名函数常常被用作回调函数的一种形式。回调函数是指在某个事件发生时被调用的函数,通常是作为参数传递给其他函数,以便在适当的时机被调用。

匿名函数作为回调函数需要注意如下两点:

  • 回调函数所必须的值由外部调用函数传入
  • 外部调用函数的参数列表中必须正确声明回调函数
package main

import "fmt"

type Callback func(int)

func processAndCallback(data int, callback Callback) {
	result := data * 2
	callback(result)
}

func main() {
	// 使用匿名函数作为回调函数
	processAndCallback(10, func(result int) {
		fmt.Println("处理并回调结果:", result)
	})
}

13. 在 Go 语言中,结构体指针和结构体变量访问成员的方式是一致的。这是因为在 Go 语言中,使用指针访问结构体成员和直接访问结构体成员的语法是自动进行指针解引用的。

例如,假设我们有一个结构体定义如下:

type Person struct {
    Name string
    Age  int
}
 

现在我们创建一个结构体变量和一个结构体指针:

 
var personVar Person
personPtr := &personVar

那么无论是通过结构体变量还是结构体指针,我们都可以使用相同的语法来访问结构体的成员:

personVar.Name = "Alice"
personPtr.Age = 30

在这个例子中,personVar.NamepersonPtr.Age 使用了相同的语法来访问结构体成员,这是因为在 Go 语言中,使用指针来访问结构体成员时会自动进行解引用操作。

这种设计使得访问结构体成员的语法非常统一,无论是使用结构体变量还是结构体指针,都可以通过 . 运算符来访问成员,这样增强了代码的可读性和一致性。

14. make()是go语言的内置函数,初始化切片等,只声明的话是nil

15. 接口-面向对象编程的思想-实现多态性

在 Go 语言中,接口通过其灵活的特性,实现了多态性。多态性是面向对象编程中的一个重要概念,它允许不同类型的对象对相同的消息做出响应,从而提高了代码的灵活性和可复用性。

在 Go 语言中,多态性是通过接口的方式来实现的。具体来说,一个对象只要实现了接口规定的方法,即可被看作是该接口类型的实例,从而可以赋值给该接口类型的变量。这样一来,不同的具体类型可以以统一的接口类型进行操作,从而实现了多态性。

 

package main

import (
	"fmt"
)

// 定义一个接口
type Shape interface {
	Area() float64
}

// 定义矩形类型
type Rectangle struct {
	Width  float64
	Height float64
}

// 矩形类型实现了 Shape 接口的 Area 方法
func (r Rectangle) Area() float64 {
	return r.Width * r.Height
}

// 定义圆形类型
type Circle struct {
	Radius float64
}

// 圆形类型实现了 Shape 接口的 Area 方法
func (c Circle) Area() float64 {
	return 3.14 * c.Radius * c.Radius
}

func main() {
	// 创建一个 Shape 类型的切片
	shapes := []Shape{
		Rectangle{Width: 3, Height: 4},
		Circle{Radius: 5},
	}

	// 遍历切片,计算每个图形的面积
	for _, shape := range shapes {
		fmt.Println("面积:", shape.Area())
	}
}

在上面的示例中,我们定义了一个 Shape 接口,该接口规定了一个 Area 方法。然后我们创建了 RectangleCircle 两种具体的类型,并让它们分别实现了 Shape 接口的 Area 方法。最后,在 main 函数中,我们创建了一个 Shape 类型的切片,将具体的 RectangleCircle 对象存储其中,并通过统一的接口类型调用 Area 方法来计算不同图形的面积。

这样,即使 RectangleCircle 是不同的具体类型,但它们都可以以统一的 Shape 接口类型进行操作,实现了多态特性。

0条评论
0 / 1000
l****n
28文章数
5粉丝数
l****n
28 文章 | 5 粉丝
原创

go语言初学需要注意的点

2023-11-07 09:05:15
12
0

1. 变量是否必须在代码块中被使用

声明了一个局部变量却没有在相同的代码块中使用它,会得到“a declared but not used”编译错误

声明了一个全局变量却没有在相同的代码块中使用它,不会编译错误

2. _: 空白标识符(抛弃只-不接受值)

eg: _,num1,num2 := reture_fun(),只接受后两个值

3.如何识别局部变量和全局变量,作用域是?生命周期是?

局部变量:在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量。

局部变量:在函数调用时存在,函数调用结束后变量被销毁。

全局变量:在函数体外声明的变量称之为全局变量,全局变量可以在整个包甚至外部包(被导出后)使用。

全局变量:程序在整个执行期间都可以访问这些变量。

全局变量和局部变量名相同时,局部变量优先。

4. s1 := 1不带声明格式的不能在函数体外出现

5. func(){ 函数后后{不能换行

6. iota特殊常量,可以被编译器修改,在const出现时被重置为0,后面每出现一次,新增1

    const(
            x = iota //0
            y = iota //1
            z = "nnn" //2
            zz //3
            e = iota //4
            f = 2<<iota //2<<5
            g //2<<6
            h //2<<7
            i = iota //8 
            j = unsafe.Sizeof(i) //9
            l = iota<<3 //10<<3
            m //11<<3
    )
    fmt.Println(x,y,z,zz,e,f,g,h,i,j,l,m)

result:0 1 nnn nnn 4 64 128 256 8 8 80 88

7. Go语言没有三目运算符 ?=

8. switch条件语句,非要执行满足case后的语句,加上fallthrough;case case1,case2 满足case1,case2任意一项即可走该分支;default不论在switch选项的哪部分都是最后执行;switch还可以判断基本类型和接口类型(v.(type))。

基本类型通过接口类型参数传递判断

package main

import (
	"fmt"
)

func judgeType(v interface{}) {
	switch v.(type) {
	case int:
		fmt.Println(v, "is int")
	case string:
		fmt.Println(v, "is string")
	case bool:
		fmt.Println(v, "is bool")
	}
}

func main() {
	judgeType(4)
	judgeType("==string==")
	judgeType(false)
}

4 is int
==string== is string
false is bool

9. 函数可以作为变量(函数式编程思想)

函数作为变量具有很大的意义,这在编程中被称为"函数式编程"。在很多编程语言中,函数都是一等公民,也就是说函数可以被当做普通变量一样进行传递、赋值和引用。

  1. 作为参数传递:函数可以作为另一个函数的参数进行传递,这样可以方便地实现回调函数、事件处理函数等功能。比如在事件处理中,可以将特定的函数作为参数传递给注册函数,当事件发生时调用传递的函数。(回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用,用于对该事件或条件进行响应(回调函数是一种事件驱动机制)。)

  2. 作为返回值:函数也可以作为另一个函数的返回值,这样可以实现更加灵活的逻辑处理。比如,在工厂模式中,可以根据参数返回不同的函数实例。

  3. 保存状态:通过闭包的方式,函数可以保存外部作用域的状态,实现状态的延续。这种特性在并发编程中尤为重要。

  4. 简化代码:将函数作为变量可以简化代码的逻辑,减少重复的代码片段,提高代码的重用性和可读性。

综上所述,函数作为变量可以使代码更加灵活、模块化,并且能够更好地支持函数式编程范式,使得代码更易于理解和维护。

func isOdd(v int) bool {
	if v%2 == 0 {
		return true
	}
	return false
}

func filter(value []int, f func(int) bool) []bool {
	var result []bool
	for _, v := range value {
		result = append(result, f(v))
	}
	return result
}
testvalue := []int{1, 2, 3, 4, 5, 6, 7}
risOdd := filter(testvalue, isOdd)


or
type FuncOdd func(int) bool


func filter(value []int, f FuncOdd) []bool {
	var result []bool
	for _, v := range value {
		result = append(result, f(v))
	}
	return result
}


10. 在Go语言中存在方法和函数

type Circle struct {
	radius float64
}

func (c Circle) getPerimeter() float64 {
	return 2 * 3.14 * c.radius
}


var c1 Circle
c1.radius = 2
fmt.Println(c1.getPerimeter())

12.56

11. 闭包和匿名函数的关系

匿名函数是指没有函数名的函数,可以直接定义在代码块中,通常用于一次性的场景,例如作为参数传递给其他函数、在 goroutine 中执行等。匿名函数可以捕获并访问其所在作用域中的变量。

闭包是指一个函数和其相关的引用环境组合而成的实体。在 Go 语言中,当你在一个函数内部定义了一个匿名函数,并且该匿名函数引用了外部函数的局部变量时,就创建了一个闭包。这意味着该匿名函数可以访问并修改外部函数的局部变量,即使外部函数已经返回,闭包依然可以持有对外部函数局部变量的引用。

因此,匿名函数可以成为闭包,具有捕获上下文中变量的能力。在 Go 语言中,闭包和匿名函数紧密相关,使用匿名函数可以方便地创建闭包。

func getSequence() func() int {
    i := 0
    return func() int {
        i += 1
        return i
    }
}

func main() {
    // 获取一个函数nextNumber,它持有i这个变量并不断自增
    nextNumber := getSequence()

    fmt.Println(nextNumber()) // 输出:1
    fmt.Println(nextNumber()) // 输出:2
    fmt.Println(nextNumber()) // 输出:3
    
    // 创建一个新的函数nextNumber1,它也持有i这个变量并不断自增,但是与上面的nextNumber无关
    nextNumber1 := getSequence()
    fmt.Println(nextNumber1()) // 输出:1
    fmt.Println(nextNumber1()) // 输出:2
}

在这段代码中,getSequence 函数返回了一个匿名函数。这个匿名函数形成了闭包,因为它引用了外部函数 getSequence 中的局部变量 i

getSequence 被调用时,它会初始化局部变量 i 并返回内部定义的匿名函数。每次调用返回的匿名函数时,i 都会被递增并返回其值。由于闭包的特性,每个返回的匿名函数都拥有对自己版本的 i 的引用,因此它们各自维护着自己的状态,互相不影响。

闭包机制的主要用途在于如下两点:

  • 可以获取函数内部的局部变量,实现公有变量
    • 这相较于全局变量和普通局部变量具有明显优势,虽然全局变量可以重复使用,但是容易造成变量污染;而局部变量仅在局部作用域内有效,虽然不会造成变量污染,但是不可以重复使用。

  • 可以让自由变量的值始终保存在内存中,实现缓存效果
    • 闭包会把父作用域(函数)中的变量保存在内存中,直到闭包的生命周期结束,所以利用这一特点可以将被闭包捕获的变量作为缓存空间来使用。

12. 回调函数和匿名函数的关系

在 Go 语言中,匿名函数常常被用作回调函数的一种形式。回调函数是指在某个事件发生时被调用的函数,通常是作为参数传递给其他函数,以便在适当的时机被调用。

匿名函数作为回调函数需要注意如下两点:

  • 回调函数所必须的值由外部调用函数传入
  • 外部调用函数的参数列表中必须正确声明回调函数
package main

import "fmt"

type Callback func(int)

func processAndCallback(data int, callback Callback) {
	result := data * 2
	callback(result)
}

func main() {
	// 使用匿名函数作为回调函数
	processAndCallback(10, func(result int) {
		fmt.Println("处理并回调结果:", result)
	})
}

13. 在 Go 语言中,结构体指针和结构体变量访问成员的方式是一致的。这是因为在 Go 语言中,使用指针访问结构体成员和直接访问结构体成员的语法是自动进行指针解引用的。

例如,假设我们有一个结构体定义如下:

type Person struct {
    Name string
    Age  int
}
 

现在我们创建一个结构体变量和一个结构体指针:

 
var personVar Person
personPtr := &personVar

那么无论是通过结构体变量还是结构体指针,我们都可以使用相同的语法来访问结构体的成员:

personVar.Name = "Alice"
personPtr.Age = 30

在这个例子中,personVar.NamepersonPtr.Age 使用了相同的语法来访问结构体成员,这是因为在 Go 语言中,使用指针来访问结构体成员时会自动进行解引用操作。

这种设计使得访问结构体成员的语法非常统一,无论是使用结构体变量还是结构体指针,都可以通过 . 运算符来访问成员,这样增强了代码的可读性和一致性。

14. make()是go语言的内置函数,初始化切片等,只声明的话是nil

15. 接口-面向对象编程的思想-实现多态性

在 Go 语言中,接口通过其灵活的特性,实现了多态性。多态性是面向对象编程中的一个重要概念,它允许不同类型的对象对相同的消息做出响应,从而提高了代码的灵活性和可复用性。

在 Go 语言中,多态性是通过接口的方式来实现的。具体来说,一个对象只要实现了接口规定的方法,即可被看作是该接口类型的实例,从而可以赋值给该接口类型的变量。这样一来,不同的具体类型可以以统一的接口类型进行操作,从而实现了多态性。

 

package main

import (
	"fmt"
)

// 定义一个接口
type Shape interface {
	Area() float64
}

// 定义矩形类型
type Rectangle struct {
	Width  float64
	Height float64
}

// 矩形类型实现了 Shape 接口的 Area 方法
func (r Rectangle) Area() float64 {
	return r.Width * r.Height
}

// 定义圆形类型
type Circle struct {
	Radius float64
}

// 圆形类型实现了 Shape 接口的 Area 方法
func (c Circle) Area() float64 {
	return 3.14 * c.Radius * c.Radius
}

func main() {
	// 创建一个 Shape 类型的切片
	shapes := []Shape{
		Rectangle{Width: 3, Height: 4},
		Circle{Radius: 5},
	}

	// 遍历切片,计算每个图形的面积
	for _, shape := range shapes {
		fmt.Println("面积:", shape.Area())
	}
}

在上面的示例中,我们定义了一个 Shape 接口,该接口规定了一个 Area 方法。然后我们创建了 RectangleCircle 两种具体的类型,并让它们分别实现了 Shape 接口的 Area 方法。最后,在 main 函数中,我们创建了一个 Shape 类型的切片,将具体的 RectangleCircle 对象存储其中,并通过统一的接口类型调用 Area 方法来计算不同图形的面积。

这样,即使 RectangleCircle 是不同的具体类型,但它们都可以以统一的 Shape 接口类型进行操作,实现了多态特性。

文章来自个人专栏
AI-llama大模型,go语言开发
28 文章 | 2 订阅
0条评论
0 / 1000
请输入你的评论
0
0