四时宝库

程序员的知识宝库

【0基础学Go语言】学习笔记1:包、函数、变量

包是一种将相关代码组织在一起的方式,它可以包含函数、变量、常量、类型等的定义。每个 Go 程序都是由包组成的,就像一个项目被划分成不同的模块,每个模块都有特定的功能和职责。

例如,将与数据库操作相关的代码放在一个 “database” 包中,将与网络通信相关的代码放在一个 “network” 包中。

  1. 包声明

每个 Go 源文件的开头都必须有一个包声明语句,例如 “package main” 或 “package utils”。这个声明指定了该文件所属的包。程序从`main`包开始执行。

package main
  1. 导入包

使用 “import” 关键字可以导入其他包。例如,“import "fmt"” 导入了 Go 标准库中的 “fmt” 包,这个包提供了格式化输入输出的功能。

可以导入多个包,每个包占一行。也可以在一行中导入多个包,但不推荐这种方式,因为会降低代码的可读性。

导入包后,可以使用包中的函数、变量、常量等。使用的方式是通过包名和点操作符,例如 “fmt.Println ("Hello")” 调用了 “fmt” 包中的 “Println” 函数。

// 导入单个包
import "fmt"

// 如果导入的包名较长或者有冲突,可以为包指定一个别名
import p1 "package1"

// 导入多个包
import (
    "package1"
    "package2"
    "package3"
)

如果要导入一个名为“test”的包,通常目录结构可以如下设置:

假设你的项目目录为`myproject`:

myproject/
├── main.go
└── test
    ├── test.go
    └── someotherfile.go

在`main.go`中,如果要导入“test”包,可以这样写:

package main

import "myproject/test"

func main() {
    // 可以使用 test 包中的内容
}

这里需要注意几点:

  1. 包名通常与目录名一致,但这不是强制要求。不过为了清晰和便于理解,一般保持一致。
  2. 导入路径是相对于你的项目的根目录或者 GOPATH 中的路径来确定的。在上面的例子中,导入路径就是“myproject/test”。
  3. 如果“test”包不是在项目的根目录下,而是在`$GOPATH/src/otherpath/`下,那么导入路径就会是“otherpath/test”。

函数

函数是一段执行特定任务的代码块。

  1. 代码模块化:将复杂的程序分解为较小的、可管理的部分,每个部分执行特定的任务,使得代码更易于理解、维护和测试。
  2. 代码复用:可以在不同的地方调用同一个函数,避免重复编写相同的代码。

函数创建:

// func:关键字,用于声明一个函数。
// functionName:函数的名称,遵循标识符命名规则。
// parameter1 type1, parameter2 type2,...:函数的参数列表,可以有零个或多个参数。每个参数包括参数名和参数类型。
// returnType:函数的返回类型,可以是单个类型,也可以是多个类型(如果有多个返回值),或者没有返回值时为void(在 Go 语言中用()表示)。
// 函数体:包含了执行特定任务的代码。
// return returnValue:如果函数有返回值,使用return语句返回一个或多个值。如果没有返回值,可以省略return语句或者只使用return而不返回具体的值。
func functionName(parameter1 type1, parameter2 type2,...) returnType {
    // 函数体
    return returnValue
}

参数传递:

  • 值传递:对于基本数据类型(如整数、浮点数、布尔值等),函数参数是按值传递的。这意味着在函数内部对参数的修改不会影响到原始值
/* 在这个例子中,函数modifyValue接受一个整数参数,在函数内部修改这个参数的值。
但是由于是值传递,在main函数中调用modifyValue后,原始的num变量的值并没有改变。
*/

package main

import "fmt"

func modifyValue(i int) {
    i = i + 10
}

func main() {
    num := 5
    fmt.Println("Before modification:", num)
    modifyValue(num)
    fmt.Println("After modification:", num)
}
  • 引用传递:对于复合数据类型(如切片、映射、通道等)和指针,函数参数实际上是传递了一个引用。在函数内部对这些参数的修改会影响到原始值
/*
对于切片(以及映射、通道等),它们是引用类型。在函数modifySlice中修改切片的元素,
由于是引用传递,在main函数中原始的切片也被修改了。
*/

package main

import "fmt"

func modifySlice(s []int) {
    s[0] = 100
}

func main() {
    slice := []int{1, 2, 3}
    fmt.Println("Before modification:", slice)
    modifySlice(slice)
    fmt.Println("After modification:", slice)
}

多个返回值:Go 语言允许函数返回多个值。这在一些情况下非常有用,例如一个函数可以同时返回结果和错误信息。

//函数divide返回两个值,一个是除法的结果,另一个是表示是否成功进行除法的布尔值。

package main

import "fmt"

func divide(a, b int) (int, bool) {
    if b == 0 {
        return 0, false
    }
    return a / b, true
}

func main() {
    result, ok := divide(10, 2)
    if ok {
        fmt.Println("Result:", result)
    } else {
        fmt.Println("Cannot divide by zero.")
    }
}

匿名函数:可以在代码中定义没有名称的函数,称为匿名函数。匿名函数通常在需要立即执行或者作为参数传递给其他函数时使用。

/* 在这个例子中,展示了两种使用匿名函数的方式。
一种是直接定义并立即调用
另一种是将匿名函数赋值给变量,然后通过变量来调用。
*/

package main

import "fmt"

func main() {
    // 定义一个匿名函数并立即调用
    func() {
        fmt.Println("This is an anonymous function.")
    }()

    // 将匿名函数赋值给变量并调用
    f := func(name string) {
        fmt.Println("Hello,", name)
    }
    f("Alice")
}

函数作为值可以将函数赋值给变量。

/*
这里将函数add赋值给变量operation,然后可以像调用函数一样通过这个变量来调用add函数。
这表明函数可以像普通的值一样被操作和传递。
*/

package main

import "fmt"

func add(a, b int) int {
    return a + b
}

func main() {
    var operation func(int, int) int
    operation = add
    result := operation(3, 4)
    fmt.Println(result)
}

函数作为参数:可以将函数作为参数传递给其他函数。

/* 在applyOperation函数中,接受一个函数作为参数op,然后在函数内部调用这个参数函数。
这体现了函数可以作为一种数据类型被传递和使用。
*/

package main

import "fmt"

/*
applyOperation是一个自定义函数。
它接受三个参数:
    参数a和b都是整数类型,代表两个操作数。
    参数op是一个函数类型,这个函数接受两个整数参数并返回一个整数。这意味着可以传入任何满足这个函数签名的函数作为参数op。
函数体中只有一条语句,即调用传入的函数op并将参数a和b传递给它,然后返回这个函数调用的结果。
这个函数的作用是将一个特定的操作(由传入的函数op表示)应用于两个整数参数a和b
*/
func applyOperation(a, b int, op func(int, int) int) int {
    return op(a, b)
}

func add(a, b int) int {
    return a + b
}

func subtract(a, b int) int {
    return a - b
}

func main() {
    result1 := applyOperation(5, 3, add)
    result2 := applyOperation(5, 3, subtract)
    fmt.Println(result1)
    fmt.Println(result2)
}

函数作为返回值:函数也可以作为另一个函数的返回值。

/*
chooseOperation函数根据传入的参数返回不同的函数。
这进一步说明了函数在 Go 语言中具有类似数据类型的行为,可以被创建、返回和操作。
*/

package main

import "fmt"

func chooseOperation(choice bool) func(int, int) int {
    if choice {
        return func(a, b int) int {
            return a + b
        }
    } else {
        return func(a, b int) int {
            return a - b
        }
    }
}

func main() {
    addFunc := chooseOperation(true)
    subtractFunc := chooseOperation(false)
    result1 := addFunc(5, 3)
    result2 := subtractFunc(5, 3)
    fmt.Println(result1)
    fmt.Println(result2)
}

变量

变量是用于存储数据值的命名容器。

  1. 存储数据:变量可以用来保存各种类型的数据,如整数、浮点数、布尔值、字符串等。这些数据可以在程序的不同部分被读取、修改和使用。
  2. 传递数据:变量可以作为函数的参数传递数据,也可以作为函数的返回值返回数据。通过变量,可以在不同的函数和代码块之间共享数据。
  3. 记录状态:变量可以用来记录程序的状态,例如计数器、标志位等。这些变量可以帮助程序在不同的执行阶段做出不同的决策。

变量声明:

  1. 声明变量:使用var关键字声明变量,并指定变量的类型和名称。
/* 一个是整数类型的age,另一个是字符串类型的name。
此时,变量被初始化为它们类型的零值,对于整数是0,对于字符串是""。
*/

var age int
var name string
  1. 初始化变量:可以在声明变量的同时对其进行初始化。
   var age int = 30
   var name string = "Alice"
  1. 使用短变量声明方式,在函数内部快速声明并初始化变量。
   count := 10

变量的作用域:

  1. 包级变量:在包级别声明的变量可以在整个包中访问。这些变量通常在包的源文件中声明,并且可以被包中的任何函数或代码块访问。
  2. 函数级变量:在函数内部声明的变量只能在该函数内部访问。这些变量的作用域仅限于函数的执行期间,函数执行完毕后,变量将被销毁。

变量的可变性:

  1. 常量与变量:在 Go 语言中,变量的值可以被修改,而常量的值在声明后不能被修改。使用const关键字声明常量。
const pi = 3.14159
const e = 2.71828
  1. 重新赋值:可以使用赋值语句给变量赋予新的值。

指针:指针是一种变量类型,它存储了另一个变量的内存地址。

  • 指针的作用
  1. 间接访问和修改值:通过指针可以间接访问和修改指针所指向的变量的值。这在需要在函数内部修改外部变量、传递大型数据结构以避免值传递的开销等情况下非常有用。
  2. 资源管理:指针可以用于管理动态分配的内存资源,如通过指针来分配和释放内存,确保资源的正确使用和释放,避免内存泄漏。
  3. 实现数据结构:指针在实现复杂的数据结构如链表、树和图等时起着关键作用。通过指针可以将不同的数据元素连接在一起,构建出复杂的数据结构。
  • 指针的声明和使用
  1. 声明指针:使用 * 符号来声明一个指针类型的变量。例如,var ptr *int声明了一个指向整数类型的指针变量ptr。
  2. 获取变量的地址:使用 & 符号可以获取一个变量的地址,并将其赋值给一个指针变量。例如,num := 10,ptr := &num将变量num的地址赋值给指针变量ptr。
  3. 解引用指针:使用*符号可以解引用指针,即获取指针所指向的变量的值。例如,fmt.Println(*ptr)将输出指针ptr所指向的变量的值,即10。
  • 指针与函数参数
  1. 值传递:在 Go 语言中,函数参数默认是值传递。这意味着当一个变量作为参数传递给函数时,函数内部会创建一个该变量的副本,对参数的修改不会影响到原始变量。
  2. 指针传递:如果希望在函数内部修改外部变量的值,可以使用指针传递。将变量的地址作为参数传递给函数,函数内部通过解引用指针来修改原始变量的值。
   package main

   import "fmt"

   // 定义了一个函数modifyValue,它接受一个指向整数的指针ptr作为参数
   func modifyValue(ptr *int) {
       // 通过解引用指针(*ptr)将指针所指向的值修改为20
       *ptr = 20
   }

   func main() {
       num := 10
       fmt.Println("Before modification:", num)
       // 调用modifyValue函数,并将num的地址(&num)作为参数传递给它
       modifyValue(&num)
       // 此时由于在modifyValue函数中修改了num的值,所以输出为20
       fmt.Println("After modification:", num)
   }
  • 指针的安全性
  1. 空指针:在 Go 语言中,指针可以为nil,表示空指针,即不指向任何有效的内存地址。在解引用空指针时会导致运行时错误,因此在使用指针之前需要检查指针是否为nil
  2. 指针的自动转换:Go 语言中的指针在一定程度上是安全的,它不支持指针运算(如 C 语言中的指针算术运算),并且在类型转换方面也有一定的限制,避免了一些不安全的操作。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接