有基础转Go语言学习笔记(1. 基本语法篇)

2023-12-13 21:09:16

有基础转Go语言学习笔记(1. 基本语法篇)

1. Go安装和Go模块

Go语言环境配置

  1. 安装Go语言:首先从Go官网下载并安装Go语言。
  2. 配置 GOROOT:这是Go语言安装的目录。大多数情况下,它在安装时自动设置。对于自定义安装路径,需要在系统环境变量中设置 GOROOT
  3. 配置 GOPATHGOPATH是Go语言的工作目录,用于存放Go项目和第三方库。它通常指向用户的一个特定目录,如 $HOME/go(Unix系统)或 C:\Users\用户名\go(Windows)。
  4. 更新环境变量:将Go的二进制文件目录添加到系统的 PATH环境变量中,以便可以从任何地方运行Go命令。
  5. 重启和验证:重启终端或计算机后,通过运行 go version来验证Go环境是否正确配置。

Go模块(自Go 1.11起)

  1. 模块概念:Go模块是Go语言的包管理和依赖管理系统。一个模块是一组版本化的Go包的集合,它记录了其依赖项。
  2. 初始化模块:通过在项目根目录运行 go mod init [module path]来创建一个新的模块。这将生成一个 go.mod文件,用于追踪依赖。
  3. 依赖管理:当你导入新的包并运行 go buildgo test时,Go会自动添加新的依赖到 go.mod文件,并且生成 go.sum文件以确保依赖项的完整性。
  4. 版本控制:Go模块支持语义版本控制。在 go.mod文件中,可以指定依赖包的具体版本。
  5. 模块代理:Go 1.13引入了模块代理的概念,它提供了一个中央服务器来缓存公共模块,加快构建速度和提高模块获取的可靠性。
  6. GOPATH与模块:使用Go模块后,GOPATH的重要性降低。Go代码可以存放在系统的任何位置,不再限制于 GOPATH内。

2. 基本语法

Hello World

package main

func main() {
	println("Hello, world!")
}

直接运行go,不生成二进制文件:

go run helloworld.go

编译运行go:

go build helloworld.go
./helloworld.exe

变量赋值

在Go语言中,变量的声明和赋值可以通过几种方式进行:

  1. 默认值:未初始化的变量有默认值(如 int0)。

    var a int
    
  2. 显式初始化:声明时指定值。

    var b int = 100
    
  3. 类型推导:编译器根据赋值推断类型。

    var c = 100
    
  4. 简短声明:使用 :=,仅在函数内部。

    f := 100
    
  5. 多变量声明:同时声明多个变量。

    var d, e = 100, "hello"
    
  6. 多行声明:组织多个变量声明。

    var (
        g = 100
        h = "hello"
    )
    

常量和Iota

在Go语言中,常量的声明和使用 iota的方法如下:

常量声明

Go中的常量是不可修改的值。常量可以是字符、字符串、布尔或数值类型。

  1. 单个常量声明

    const Pi = 3.14
    
  2. 多常量声明

    const (
        StatusOK = 200
        NotFound = 404
    )
    
使用 Iota

iota是Go语言的一个特殊常量,用于在 const关键字出现时初始化为0,然后在每新声明一个常量时递增。

  1. 基本使用

    const (
        Zero = iota  // 0
        One          // 1
        Two          // 2
    )
    
  2. 跳过某些值

    const (
        _ = iota  // 跳过0
        First     // 1
        Second    // 2
    )
    
  3. 复杂表达式

    const (
    	A = 1 << iota // 1 << 0 = 1
    	B             // 1 << 1 = 2
    	C             // 1 << 2 = 4
    	D = 2 * iota  // 2 * 3 = 6
    	E             // 2 * 4 = 8
    )
    

iota在枚举类型的创建中非常有用,因为它允许你创建一系列相关的常量,而不需要显式地为每个常量指定一个值。在每个新的 const块中,iota都会重置为0。

函数和返回值

在Go语言中,函数是基本的代码组织和执行单元。函数可以有多种返回值的写法,以下是Go函数及其返回值的三种常见写法:

1. 基本函数

基本的函数声明包括函数名、参数列表和返回类型。

func Add(a int, b int) int {
    return a + b
}
  • 这是一个简单的函数,接收两个 int类型的参数,并返回它们的和。
2. 返回值命名

在Go中,你可以给返回值命名,并在函数内部直接操作这些命名变量。

func Swap(x, y string) (a, b string) { // (a string, b string) 也可
    a = y
    b = x
    return
}
  • 这个函数交换两个字符串的值。返回值在声明时已经命名为 ab,因此在 return语句中不需要再次指定。
3. 多返回值

Go支持函数返回多个值,这在错误处理中特别有用。

func Divide(a, b float64) (float64, error) {
    if b == 0.0 {
        return 0.0, errors.New("division by zero")
    }
    return a / b, nil
}
  • 这个函数返回两个值:除法的结果和一个错误信息。如果除数为0,则返回错误。
4. 异常处理
package main

import (
    "errors"
    "fmt"
)

// Divide 函数计算两个浮点数的除法
func Divide(a, b float64) (float64, error) {
    if b == 0.0 {
        return 0.0, errors.New("division by zero")
    }
    return a / b, nil
}

func main() {
    // 调用 Divide 函数
    result, err := Divide(10.0, 0.0)

    // 检查是否有错误返回
    if err != nil {
        fmt.Println("Error:", err)
        return
    }

    // 打印结果
    fmt.Println("Result:", result)
}

包(package)

包的路径和init()

若文件结构如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

main.go:

package main

import (
    "fmt"
    _ "GoPlayground/InitLib1"
    _ "GoPlayground/InitLib2"
)

func init() {
    fmt.Println("libmain init")
}

func main() {
    fmt.Println("libmian main")
}

被导入的包首先会被调用init方法,再递归调用它所依赖的包的init方法

包的别名

在Go语言中,不同类型的包别名使用可以通过以下代码块进行概括:

1. 普通别名
import fm "fmt"

func main() {
    fm.Println("Hello, world!") // 使用别名fm调用fmt包中的Println函数
}

这里,fmt包被赋予了别名 fm,在代码中通过 fm来引用 fmt包的内容。

2. 点(.)别名
import . "fmt"

func main() {
    Println("Hello, world!") // 直接调用Println,无需包名前缀
}

使用点(.)作为别名后,可以直接调用 fmt包中的函数,无需使用包名作为前缀。

3. 下划线(_)别名
import _ "database/sql"

func main() {
    // 这里不直接使用database/sql包
    // 但该包的init函数会被执行
}

这里,database/sql包被导入,但不直接使用其中的任何标识符。它的 init函数仍然会被执行,这通常用于初始化目的,如注册数据库驱动。

包的创建注意事项

作为被 import的包,在Go语言中对外提供函数时,需要注意以下几个关键点:

1. 公开性(Exporting)
  • 首字母大写:在Go中,只有首字母大写的函数、类型、变量等才能被其他包访问。这被称为“导出”(Exporting)。例如,func MyFunction()可以被外部包访问,而 func myFunction()则不可以。
2. 明确的函数签名
  • 清晰的参数和返回类型:确保函数的参数和返回类型是清晰并且一致的。这有助于使用者了解如何正确使用你的函数。
  • 文档注释:为公共函数编写文档注释是一个好习惯。这些注释应该简洁地描述函数的功能、参数、返回值以及任何副作用或特别的行为。
3. 错误处理
  • 返回错误:在Go中,优雅的错误处理是通过返回一个 error类型的值来实现的。如果函数可能遇到一个应该由调用者处理的错误情况,它应该返回一个 error类型的值。
4. 命名约定
  • 遵守Go的命名约定:使用驼峰式命名(CamelCase)而不是下划线分隔。例如,使用 ComputeAverage而不是 compute_average
5. 最小化外部依赖
  • 减少依赖:尽量减少模块对外部包的依赖。这可以减少使用你的模块时可能出现的依赖冲突。
6. 稳定的API
  • 保持API稳定性:一旦你的包被其他人使用,频繁地更改公共API可能会导致兼容性问题。在更改API时要谨慎。
包的使用注意事项

在Go语言中使用包时,有几个重要的注意事项需要考虑,以确保代码的可读性、维护性和性能:

1. 选择合适的包
  • 标准库优先:优先使用Go标准库中的包,因为它们经过严格测试,性能和可靠性都得到保证。
  • 谨慎选择第三方包:选择社区认可度高、维护活跃的第三方包,注意检查其许可证、文档和社区支持。
2. 导入包的方式
  • 避免不必要的导入:只导入你需要的包,未使用的导入会增加编译时间并潜在地增加最终二进制文件的大小。
  • 使用完全限定的导入路径:避免使用相对路径导入包,这可能导致依赖管理混乱。
3. 包的命名和组织
  • 简洁明了的包名:包名应简短、有描述性,避免使用下划线或混合大小写。
  • 单一职责原则:每个包应只承担单一职责,这有助于维护和重用代码。
  • 公开接口最小化:只公开(首字母大写)必要的类型和函数,减少包的外部API。
4. 管理包依赖
  • 使用Go模块:自Go 1.11以来,使用Go模块来管理依赖是官方推荐的方式。
  • 小心更新依赖:更新依赖时要小心,尤其是涉及到大的版本更改,因为这可能会引入不兼容的更改。
5. 包的初始化
  • 谨慎使用init函数init函数在包导入时自动执行,应当谨慎使用,避免复杂的初始化逻辑,因为这会增加代码的隐式行为和启动时间。
6. 性能考虑
  • 注意导入包的性能影响:一些包可能会显著增加编译时间或运行时开销,特别是那些依赖大量资源或进行复杂初始化的包。
包和模块的区别

在Go语言中,包(Packages)和模块(Modules)是两个不同的概念,它们在组织和管理代码方面扮演不同的角色:

包(Packages)
  1. 基本单位:包是Go代码的基本组织单位。一个包由位于同一目录下的一个或多个 .go文件组成。
  2. 命名空间:每个包提供了一个命名空间来组织其内容(如函数、结构体、接口等)。
  3. 导入机制:包可以被其他包导入,以使用其中定义的公共(首字母大写的)标识符。
  4. 目录结构:包的物理结构与文件系统的目录结构紧密相关。一个目录下的所有文件都被视为同一个包的一部分。
  5. 作用:包用于逻辑上划分代码,使代码易于管理和维护。例如,fmtnet/http都是标准库中的包。
模块(Modules)
  1. 高级依赖管理:模块是Go 1.11中引入的,用于支持版本控制和包依赖管理。
  2. 包的集合:一个模块是一个或多个包的集合。一个模块包含一个 go.mod文件,该文件定义了模块的根目录。
  3. 版本控制:模块使开发者能够指定依赖的确切版本,这有助于确保项目依赖的一致性和可重复性。
  4. 独立于GOPATH:模块的使用意味着可以在 GOPATH之外的任意目录中工作,这是之前Go项目中不支持的。
  5. 包管理:模块提供了对外部包的依赖管理,包括版本控制和可重复的构建。
总结
  • 是代码组织的基本单位,用于划分和封装逻辑。
  • 模块是一种更高级的机制,用于管理包及其依赖,包括版本控制。

在实际应用中,通常会有一个模块包含多个包。模块为这些包提供了一个统一的版本管理和依赖解析框架。

指针

对于有C/C++背景的程序员来说,Go语言中的指针概念会比较容易理解,但也有一些重要的差异需要注意:

相似之处
  1. 基本概念:和C/C++一样,Go中的指针代表内存地址,可以用于间接引用或操作数据。

  2. 语法:使用 &操作符获取变量的地址,使用 *操作符访问指针指向的值。例如:

    var a int = 10
    var p *int = &a
    *p = 20  // 改变a的值
    
差异
  1. 无指针算术:Go中不支持指针算术,即不能像在C/C++中那样对指针进行加减操作。这是一个设计决策,旨在提高程序的安全性和可维护性。
  2. 垃圾回收:Go是一个具有垃圾回收机制的语言,这意味着内存的分配和回收是自动管理的。因此,不需要像在C/C++中那样手动管理内存。
  3. 安全性:Go的指针更加安全,因为不允许直接操作内存,也没有指针算术。这减少了内存泄漏和缓冲区溢出等常见错误。
  4. new和make:Go提供了 newmake两个内建函数来分配内存。new(T)为类型T分配零值内存并返回指向它的指针。make用于创建切片、映射和通道,并返回一个有初始值(非零)的T类型,而不是*T。
示例
var x int = 1
var p *int = &x  // p是指向int类型的指针
fmt.Println(*p)  // 输出1,*p表示指针p指向的内存中的值

defer语句

在Go语言中,defer语句用于安排随后对函数的调用。这意味着你可以在函数执行到某个点后,指定一些操作在函数返回前完成。这在处理资源释放、解锁以及进行一些清理工作时特别有用。对于有C/C++背景的程序员来说,可以将 defer看作是一种确保在函数退出时执行特定代码的机制,类似于C++中的析构函数或Java中的final

基本使用
  • 延迟调用defer后的函数调用会被延迟执行,直到包含它的函数执行完毕。
  • 堆栈行为:如果有多个 defer语句,它们会按照后进先出(LIFO)的顺序执行,即最后一个 defer语句将首先执行。
func example() {
    defer fmt.Println("deferred call in example")
    fmt.Println("doing some work in example")
    // 当example函数返回时,先执行defer语句
}
> doing some work in example
> deferred call in example
应用场景
  1. 资源清理:确保文件、网络连接等资源在不再需要时被正确关闭。
  2. 解锁互斥量:在获取互斥量后,使用 defer解锁可以避免死锁。
  3. 错误处理:配合命名返回值,defer可用于修改函数的返回值。
错误处理示例
func readFile(filename string) (content string, err error) {
    file, err := os.Open(filename)
    if err != nil {
        return "", err
    }
    defer file.Close()  // 确保在退出函数前关闭文件

    // ... 文件读取操作 ...

    return string(fileContent), nil
}

在这个示例中,无论函数因为哪个路径返回,file.Close()都将被执行,确保了文件资源被释放。

注意事项
  • return和defer的先后return先执行,defer后执行
  • 参数立即求值defer语句中的函数参数会立即求值,而非在实际执行时求值。
  • 避免过多使用:虽然 defer很有用,但在循环或频繁调用的函数中过多使用可能会导致性能问题。
  • 错误检查:延迟执行的函数通常也会有返回值,例如用于错误检查,但这些返回值通常会被忽略。可以通过命名返回值或其他方式来处理这些错误。

文章来源:https://blog.csdn.net/gulugulu1103/article/details/134820712
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。