Go 零基础教程

Go 基础语法

Go 语言的语法虽然受到了 C 语言等传统语言的影响,但它的核心设计目标是简单、高可读性和易用

本章将带你全面了解 Go 代码的底层构建块,包括包声明、导入、函数、语句、表达式以及注释。我们将探索这些元素是如何协同工作,最终组合成一个完整的、可执行的程序的。

1. 包声明 (Package Declaration)

每一个 Go 程序的开头,都必须包含一个包声明。包 (Package) 是 Go 语言用来组织和复用代码的核心方式。package 关键字用来指明当前文件属于哪一个包。

  • main 包: 这是一个极其特殊的包。它是所有可执行程序的入口点。当你运行一个 Go 程序时,系统会自动寻找并执行 main 包里面的 main 函数。
  • 其他包: 除了 main 之外的其他包,通常被用作代码库(Libraries)或可复用的模块。它们没有 main 函数,主要用来被其他程序或包导入使用。
package main // 声明当前文件属于 main 包(可执行程序的入口)

// 如果是作为代码库的包声明,通常写成下面这样:
// package mylibrary

2. 导入 (Imports)

import 语句用于将其他包中的代码引入到你的程序中。引入后,你就可以使用那些包里定义好的函数、类型和变量了。

  • 标准库 (Standard Library): Go 拥有一个极其丰富的标准库,提供了大量实用的包。例如,用于格式化输入输出的 fmt,用于调用操作系统底层功能的 os,以及用于网络通信的 net/http。
  • 第三方包: 你也可以导入其他开发者编写的包,这些包通常托管在 GitHub 等平台上。
  • 批量导入 (Importing Multiple Packages): 你可以使用一对圆括号 (),在一个 import 语句中同时导入多个包,这能让代码更整洁。
  • 导入别名 (Aliasing Imports): 你可以在代码中为导入的包起一个“小名”(别名)。这在解决不同包名冲突,或者包名太长需要简写时非常有用。
  • 点导入 - 强烈建议避免 (Dot Imports): Go 支持点导入(例如 import . "mypackage")。这会把该包里所有公开的标识符直接倒进当前文件的命名空间中。通常不推荐这种做法,因为它极易导致命名冲突,并且会严重降低代码的可读性。
package main

import (
	"fmt"      // 导入 fmt 包,用于格式化的输入和输出
	"os"       // 导入 os 包,用于操作系统相关的功能
	m "math"   // 导入 math 包,并给它起了一个别名 'm'
)

func main() {
	fmt.Println("Hello, World!")
	fmt.Println(os.Getenv("PATH")) // 使用 os 包获取环境变量的例子
	fmt.Println(m.Pi)              // 使用起了别名 'm' 的 math 包的例子
}

3. 函数 (Functions)

函数是 Go 程序的基石。它们是独立的、自成一体的代码块,专门负责执行某一项特定的任务。

  • main 函数: 可执行程序的绝对入口。它不接收任何参数,也不返回任何值。
  • 函数声明: 使用 func 关键字来声明函数。后面紧跟:函数名、参数列表(如果有的话)、返回值类型(如果有的话)。
  • 参数与返回值: 函数可以接收零个或多个参数,也可以返回零个或多个值。
  • 函数体: 函数的具体逻辑代码必须包裹在大括号 {} 里面。
package main

import "fmt"

// 一个简单的函数,接收两个整数 (int) 参数,并返回它们的和 (也是 int)
func add(x int, y int) int {
	return x + y
}

// 一个既没有参数,也没有返回值的函数
func greet() {
	fmt.Println("Hello!")
}

// 一个可以返回多个值的函数 (Go 语言的一大特色)
func divide(x int, y int) (int, error) {
	if y == 0 {
		return 0, fmt.Errorf("cannot divide by zero (不能除以零)")
	}
	return x / y, nil
}

func main() {
	sum := add(5, 3)
	fmt.Println("Sum:", sum) // 输出: Sum: 8
    
	greet() // 输出: Hello!
    
	result, err := divide(10, 2)
	if err != nil {
		fmt.Println("Error:", err)
	} else {
		fmt.Println("Result:", result) // 输出: Result: 5
	}
    
	result, err = divide(10, 0)
	if err != nil {
		fmt.Println("Error:", err) // 输出: Error: cannot divide by zero (不能除以零)
	} else {
		fmt.Println("Result:", result)
	}
}

4. 语句与表达式 (Statements and Expressions)

  • 语句 (Statements): 是告诉 Go 编译器去执行动作的指令。
  • 表达式 (Expressions): 是值、变量、操作符和函数调用的组合,它们计算后一定会得出一个值。

常见的语句类型包括:

  • 声明语句 (Declaration Statements): 用于声明变量或常量。
  • 赋值语句 (Assignment Statements): 将一个具体的数值赋给变量。
  • 控制流语句 (Control Flow Statements): 控制代码的执行顺序,例如 ifforswitch(我们将在后续章节详细讲解)。
  • 表达式语句 (Expression Statements): 对表达式进行求值,比如调用一个函数或进行数学运算。
  • 简短语句 (Simple Statements): 经常出现在 iffor 等关键字前面的简短操作。
package main

import "fmt"

func main() {
	// 声明语句
	var x int
    
	// 赋值语句
	x = 10
    
	// 表达式语句 (计算 x + 5 并把结果赋值给 y)
	y := x + 5 
    
	// 打印语句 (这也是一种表达式语句)
	fmt.Println("x:", x, "y:", y) // 输出: x: 10 y: 15
    
	// 包含简短语句的 if 语句 (先执行 z := x * 2,然后再判断 z > 15)
	if z := x * 2; z > 15 {
		fmt.Println("z 大于 15, 当前值为:", z) // 输出: z 大于 15, 当前值为: 20
	}
}

5. 注释 (Comments)

注释是你写给别人(或者是未来的自己)看的解释说明。Go 编译器在编译时会完全忽略所有的注释

  • 单行注释: 以 // 开头,直到该行结束。
  • 多行注释: 以 /* 开头,并以 */ 结尾。它可以跨越多行。
package main

import "fmt"

func main() {
	// 这是一个单行注释
	fmt.Println("Hello, World!") // 这一行的注释用于解释这行代码在干嘛

	/*
	   这是一个多行注释。
	   它可以跨越好多行,
	   非常适合用来解释一大块复杂的逻辑。
	*/
	fmt.Println("这是另一行代码。")
}

6. 代码块与作用域 (Code Blocks and Scope)

代码块 (Code Block) 是由一对大括号 {} 包裹起来的一组语句。代码块不仅把代码组合在一起,更决定了变量的作用域

  • 作用域 (Scope): 指的是一个变量在代码中可以被访问和使用的范围。在一个代码块内部声明的变量,只能在该代码块内部被访问。
  • 嵌套代码块与变量遮蔽 (Shadowing): 代码块是可以嵌套的(一个大括号里面套着另一个大括号)。外层代码块中声明的变量,可以在内层代码块中使用。但是,如果你在内层代码块里声明了一个和外层同名的变量,内层的变量就会“遮蔽”(Shadow)外层的变量。在内层代码块中,你操作的将是内层的新变量。
package main

import "fmt"

func main() {
	x := 10 // x 声明在外层代码块中
	{
		y := 20 // y 声明在内层代码块中
		fmt.Println("内层代码块中的 x:", x) // 在这里可以访问外层的 x
		fmt.Println("内层代码块中的 y:", y) // 在这里可以访问内层的 y
	}
    
	fmt.Println("外层代码块中的 x:", x) // 在这里可以访问 x
	// fmt.Println("外层代码块中的 y:", y) // 取消这行注释会报错!因为 y 在这里不可见(超出了 y 的作用域)

	{
		x := 30 // 注意:这在内层声明了一个全新的变量 x,它“遮蔽”了外层的 x
		fmt.Println("内层的 x:", x) // 输出: 内层的 x: 30
	}
    
	fmt.Println("外层的 x:", x) // 输出: 外层的 x: 10 (最外层的 x 并没有被改变)
}

7. 命名规范 (Naming Conventions)

Go 语言有一套非常严格但实用的命名约定,这保证了所有 Go 代码看起来高度统一。

  • 包名 (Package Names): 应该简短、纯小写、且具有描述性(例如 timehttp)。
  • 变量与函数名: 应该具有描述性,并使用驼峰命名法 (camelCase)(例如 myVariableNamecalculateSum)。
  • 常量名 (Constants): 通常使用全大写字母,单词之间用下划线分隔(例如 MAX_VALUE)。
  • 导出标识符 (首字母大小写规则): 这是 Go 语言最核心的特色之一!
    • 首字母大写: 如果一个变量、函数或类型名以大写字母开头(例如 MyPublicFunction),它就是公开的 (Exported),可以被其他包调用。
    • 首字母小写: 如果以小写字母开头(例如 myPrivateVariable),它就是私有的 (Unexported),只能在它所属的那个包内部使用。
package main

import "fmt"

// 常量
const MAX_SIZE = 100

// 私有变量 (未导出,外部包无法访问)
var myVariable int

// 公开变量 (已导出,外部包可以访问)
var MyPublicVariable string

// 私有函数 (未导出)
func myFunction() {
	fmt.Println("这是一个私有函数")
}

// 公开函数 (已导出)
func MyPublicFunction() {
	fmt.Println("这是一个公开函数")
}

func main() {
	myVariable = 10
	MyPublicVariable = "Hello"
	myFunction()
	MyPublicFunction()
}