1.1.3 第一个 Go 程序 #
本节将带您编写第一个完整的 Go 程序,从最简单的 “Hello World” 开始,逐步深入了解 Go 程序的结构、编译运行过程,以及基本的语法规则。
Hello World 程序 #
创建第一个程序 #
让我们从经典的 “Hello World” 程序开始:
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
程序结构分析 #
1. package 声明 #
package main
- 每个 Go 文件都必须以
package
声明开始 package main
表示这是一个可执行程序的入口包- 其他包名通常与目录名相同,如
package utils
2. import 语句 #
import "fmt"
import
用于导入其他包fmt
是 Go 标准库中的格式化包,提供输入输出功能- 多个导入可以使用括号分组:
import (
"fmt"
"os"
"time"
)
3. main 函数 #
func main() {
fmt.Println("Hello, World!")
}
main
函数是程序的入口点- 只有
package main
中的main
函数会被执行 fmt.Println
用于输出文本并换行
运行程序 #
方法一:直接运行 #
# 创建文件
echo 'package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}' > hello.go
# 直接运行
go run hello.go
方法二:编译后运行 #
# 编译生成可执行文件
go build hello.go
# 运行可执行文件
./hello # Linux/macOS
hello.exe # Windows
方法三:使用模块 #
# 创建模块
mkdir hello-world
cd hello-world
go mod init hello-world
# 创建 main.go
cat > main.go << 'EOF'
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
EOF
# 运行
go run .
程序的基本元素 #
1. 注释 #
Go 语言支持两种注释方式:
package main
import "fmt"
// 这是单行注释
// 可以连续使用多行
/*
这是多行注释
可以跨越多行
通常用于包或函数的文档说明
*/
func main() {
fmt.Println("Hello, World!") // 行尾注释
}
2. 标识符 #
标识符用于命名变量、函数、类型等:
package main
import "fmt"
// 有效的标识符
var name string
var age int
var isStudent bool
// 函数名也是标识符
func calculateSum(a, b int) int {
return a + b
}
func main() {
// 变量名标识符
userName := "Alice"
userAge := 25
fmt.Printf("Name: %s, Age: %d\n", userName, userAge)
}
标识符规则:
- 必须以字母或下划线开头
- 后续字符可以是字母、数字或下划线
- 区分大小写
- 不能使用 Go 关键字
3. 关键字 #
Go 语言有 25 个关键字:
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
4. 操作符 #
package main
import "fmt"
func main() {
// 算术操作符
a, b := 10, 3
fmt.Printf("a + b = %d\n", a+b) // 加法
fmt.Printf("a - b = %d\n", a-b) // 减法
fmt.Printf("a * b = %d\n", a*b) // 乘法
fmt.Printf("a / b = %d\n", a/b) // 除法
fmt.Printf("a %% b = %d\n", a%b) // 取模
// 比较操作符
fmt.Printf("a == b: %t\n", a == b) // 等于
fmt.Printf("a != b: %t\n", a != b) // 不等于
fmt.Printf("a > b: %t\n", a > b) // 大于
fmt.Printf("a < b: %t\n", a < b) // 小于
// 逻辑操作符
x, y := true, false
fmt.Printf("x && y: %t\n", x && y) // 逻辑与
fmt.Printf("x || y: %t\n", x || y) // 逻辑或
fmt.Printf("!x: %t\n", !x) // 逻辑非
}
更复杂的示例程序 #
1. 计算器程序 #
package main
import (
"fmt"
"os"
"strconv"
)
func main() {
// 检查命令行参数
if len(os.Args) != 4 {
fmt.Println("Usage: calculator <num1> <operator> <num2>")
fmt.Println("Example: calculator 10 + 5")
os.Exit(1)
}
// 解析参数
num1Str := os.Args[1]
operator := os.Args[2]
num2Str := os.Args[3]
// 转换字符串为数字
num1, err1 := strconv.ParseFloat(num1Str, 64)
num2, err2 := strconv.ParseFloat(num2Str, 64)
if err1 != nil || err2 != nil {
fmt.Println("Error: Invalid numbers")
os.Exit(1)
}
// 执行计算
var result float64
switch operator {
case "+":
result = num1 + num2
case "-":
result = num1 - num2
case "*":
result = num1 * num2
case "/":
if num2 == 0 {
fmt.Println("Error: Division by zero")
os.Exit(1)
}
result = num1 / num2
default:
fmt.Printf("Error: Unknown operator '%s'\n", operator)
os.Exit(1)
}
// 输出结果
fmt.Printf("%.2f %s %.2f = %.2f\n", num1, operator, num2, result)
}
使用示例:
go run calculator.go 10 + 5
# 输出: 10.00 + 5.00 = 15.00
go run calculator.go 20 / 4
# 输出: 20.00 / 4.00 = 5.00
2. 文件信息查看器 #
package main
import (
"fmt"
"os"
"time"
)
func main() {
// 检查命令行参数
if len(os.Args) < 2 {
fmt.Println("Usage: fileinfo <filename>")
os.Exit(1)
}
filename := os.Args[1]
// 获取文件信息
fileInfo, err := os.Stat(filename)
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
// 显示文件信息
fmt.Printf("File Information for: %s\n", filename)
fmt.Printf("Size: %d bytes\n", fileInfo.Size())
fmt.Printf("Mode: %s\n", fileInfo.Mode())
fmt.Printf("Modified: %s\n", fileInfo.ModTime().Format(time.RFC3339))
fmt.Printf("Is Directory: %t\n", fileInfo.IsDir())
// 显示权限信息
mode := fileInfo.Mode()
fmt.Printf("Permissions: ")
if mode&0400 != 0 {
fmt.Print("r")
} else {
fmt.Print("-")
}
if mode&0200 != 0 {
fmt.Print("w")
} else {
fmt.Print("-")
}
if mode&0100 != 0 {
fmt.Print("x")
} else {
fmt.Print("-")
}
fmt.Println()
}
3. 简单的 HTTP 服务器 #
package main
import (
"fmt"
"log"
"net/http"
"time"
)
// 处理根路径请求
func homeHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "<h1>Welcome to Go Web Server!</h1>")
fmt.Fprintf(w, "<p>Current time: %s</p>", time.Now().Format(time.RFC3339))
fmt.Fprintf(w, "<p>Request URL: %s</p>", r.URL.Path)
fmt.Fprintf(w, "<p>Request Method: %s</p>", r.Method)
}
// 处理 API 请求
func apiHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
response := `{
"message": "Hello from Go API!",
"timestamp": "` + time.Now().Format(time.RFC3339) + `",
"status": "success"
}`
fmt.Fprint(w, response)
}
// 处理健康检查
func healthHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "OK")
}
func main() {
// 注册路由处理器
http.HandleFunc("/", homeHandler)
http.HandleFunc("/api", apiHandler)
http.HandleFunc("/health", healthHandler)
// 启动服务器
port := ":8080"
fmt.Printf("Starting server on http://localhost%s\n", port)
fmt.Println("Available endpoints:")
fmt.Println(" http://localhost:8080/")
fmt.Println(" http://localhost:8080/api")
fmt.Println(" http://localhost:8080/health")
// 启动 HTTP 服务器
log.Fatal(http.ListenAndServe(port, nil))
}
测试服务器:
# 启动服务器
go run server.go
# 在另一个终端测试
curl http://localhost:8080/
curl http://localhost:8080/api
curl http://localhost:8080/health
Go 程序的编译和构建 #
1. 基本编译 #
# 编译当前目录的程序
go build
# 编译指定文件
go build main.go
# 指定输出文件名
go build -o myapp main.go
# 编译并安装到 GOPATH/bin
go install
2. 跨平台编译 #
# 编译 Linux 64位版本
GOOS=linux GOARCH=amd64 go build -o myapp-linux main.go
# 编译 Windows 64位版本
GOOS=windows GOARCH=amd64 go build -o myapp.exe main.go
# 编译 macOS 64位版本
GOOS=darwin GOARCH=amd64 go build -o myapp-mac main.go
# 编译 ARM 版本(如树莓派)
GOOS=linux GOARCH=arm go build -o myapp-arm main.go
3. 构建标签和条件编译 #
// +build linux darwin
package main
import "fmt"
func main() {
fmt.Println("This runs only on Linux and macOS")
}
// +build windows
package main
import "fmt"
func main() {
fmt.Println("This runs only on Windows")
}
4. 编译优化 #
# 减小可执行文件大小
go build -ldflags="-s -w" main.go
# 禁用调试信息
go build -ldflags="-s" main.go
# 静态链接
CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' main.go
调试和测试 #
1. 使用 fmt 包调试 #
package main
import "fmt"
func main() {
name := "Alice"
age := 30
// 基本输出
fmt.Println("Hello, World!")
// 格式化输出
fmt.Printf("Name: %s, Age: %d\n", name, age)
// 输出变量类型和值
fmt.Printf("name: %T = %v\n", name, name)
fmt.Printf("age: %T = %v\n", age, age)
// 调试输出
fmt.Printf("Debug: name=%q, age=%d\n", name, age)
}
2. 使用 log 包 #
package main
import (
"log"
"os"
)
func main() {
// 设置日志格式
log.SetFlags(log.LstdFlags | log.Lshortfile)
// 基本日志
log.Println("Program started")
// 错误日志
if err := someFunction(); err != nil {
log.Printf("Error occurred: %v", err)
}
// 致命错误(会退出程序)
if criticalError() {
log.Fatal("Critical error, exiting...")
}
}
func someFunction() error {
return nil // 模拟无错误
}
func criticalError() bool {
return false // 模拟无致命错误
}
3. 简单的单元测试 #
创建 math_test.go
文件:
package main
import "testing"
// 被测试的函数
func Add(a, b int) int {
return a + b
}
func Multiply(a, b int) int {
return a * b
}
// 测试函数
func TestAdd(t *testing.T) {
result := Add(2, 3)
expected := 5
if result != expected {
t.Errorf("Add(2, 3) = %d; expected %d", result, expected)
}
}
func TestMultiply(t *testing.T) {
tests := []struct {
a, b int
expected int
}{
{2, 3, 6},
{0, 5, 0},
{-1, 4, -4},
}
for _, test := range tests {
result := Multiply(test.a, test.b)
if result != test.expected {
t.Errorf("Multiply(%d, %d) = %d; expected %d",
test.a, test.b, result, test.expected)
}
}
}
运行测试:
go test
go test -v # 详细输出
常见错误和解决方法 #
1. 语法错误 #
错误示例:
package main
import "fmt"
func main() {
fmt.Println("Hello World") // 缺少分号(实际上Go不需要分号)
if true {
fmt.Println("True")
// 缺少闭合括号
}
正确写法:
package main
import "fmt"
func main() {
fmt.Println("Hello World")
if true {
fmt.Println("True")
}
}
2. 导入包未使用 #
错误示例:
package main
import (
"fmt"
"os" // 导入但未使用
)
func main() {
fmt.Println("Hello World")
}
解决方法:
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}
3. 变量声明但未使用 #
错误示例:
package main
import "fmt"
func main() {
name := "Alice" // 声明但未使用
fmt.Println("Hello World")
}
解决方法:
package main
import "fmt"
func main() {
name := "Alice"
fmt.Printf("Hello, %s!\n", name)
}
项目实践:待办事项管理器 #
让我们创建一个简单的命令行待办事项管理器:
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
// 待办事项结构
type Todo struct {
ID int
Description string
Completed bool
}
// 全局待办事项列表
var todos []Todo
var nextID int = 1
// 添加待办事项
func addTodo(description string) {
todo := Todo{
ID: nextID,
Description: description,
Completed: false,
}
todos = append(todos, todo)
nextID++
fmt.Printf("Added todo: %s (ID: %d)\n", description, todo.ID)
}
// 列出所有待办事项
func listTodos() {
if len(todos) == 0 {
fmt.Println("No todos found.")
return
}
fmt.Println("\nTodo List:")
fmt.Println("----------")
for _, todo := range todos {
status := "[ ]"
if todo.Completed {
status = "[✓]"
}
fmt.Printf("%d. %s %s\n", todo.ID, status, todo.Description)
}
fmt.Println()
}
// 完成待办事项
func completeTodo(id int) {
for i := range todos {
if todos[i].ID == id {
todos[i].Completed = true
fmt.Printf("Completed todo: %s\n", todos[i].Description)
return
}
}
fmt.Printf("Todo with ID %d not found.\n", id)
}
// 删除待办事项
func deleteTodo(id int) {
for i, todo := range todos {
if todo.ID == id {
todos = append(todos[:i], todos[i+1:]...)
fmt.Printf("Deleted todo: %s\n", todo.Description)
return
}
}
fmt.Printf("Todo with ID %d not found.\n", id)
}
// 显示帮助信息
func showHelp() {
fmt.Println("\nAvailable commands:")
fmt.Println(" add <description> - Add a new todo")
fmt.Println(" list - List all todos")
fmt.Println(" complete <id> - Mark todo as completed")
fmt.Println(" delete <id> - Delete a todo")
fmt.Println(" help - Show this help")
fmt.Println(" quit - Exit the program")
fmt.Println()
}
func main() {
fmt.Println("Welcome to Todo Manager!")
fmt.Println("Type 'help' for available commands.")
scanner := bufio.NewScanner(os.Stdin)
for {
fmt.Print("> ")
if !scanner.Scan() {
break
}
input := strings.TrimSpace(scanner.Text())
if input == "" {
continue
}
parts := strings.Fields(input)
command := parts[0]
switch command {
case "add":
if len(parts) < 2 {
fmt.Println("Usage: add <description>")
continue
}
description := strings.Join(parts[1:], " ")
addTodo(description)
case "list":
listTodos()
case "complete":
if len(parts) < 2 {
fmt.Println("Usage: complete <id>")
continue
}
id, err := strconv.Atoi(parts[1])
if err != nil {
fmt.Println("Invalid ID. Please enter a number.")
continue
}
completeTodo(id)
case "delete":
if len(parts) < 2 {
fmt.Println("Usage: delete <id>")
continue
}
id, err := strconv.Atoi(parts[1])
if err != nil {
fmt.Println("Invalid ID. Please enter a number.")
continue
}
deleteTodo(id)
case "help":
showHelp()
case "quit", "exit":
fmt.Println("Goodbye!")
return
default:
fmt.Printf("Unknown command: %s\n", command)
fmt.Println("Type 'help' for available commands.")
}
}
}
使用示例:
go run todo.go
Welcome to Todo Manager!
Type 'help' for available commands.
> add Buy groceries
Added todo: Buy groceries (ID: 1)
> add Write Go tutorial
Added todo: Write Go tutorial (ID: 2)
> list
Todo List:
----------
1. [ ] Buy groceries
2. [ ] Write Go tutorial
> complete 1
Completed todo: Buy groceries
> list
Todo List:
----------
1. [✓] Buy groceries
2. [ ] Write Go tutorial
> quit
Goodbye!
小结 #
通过本节的学习,您已经:
- 掌握了 Go 程序的基本结构:package 声明、import 语句、main 函数
- 了解了 Go 语言的基本元素:注释、标识符、关键字、操作符
- 学会了编译和运行 Go 程序:使用 go run、go build 等命令
- 实践了调试和测试:使用 fmt、log 包和基本的单元测试
- 完成了实际项目:待办事项管理器,综合运用了所学知识
现在您已经具备了 Go 语言编程的基础能力,可以开始学习更深入的语法和特性了。在接下来的章节中,我们将详细学习 Go 语言的数据类型、控制结构、函数等核心概念。
练习题:
- 修改 Hello World 程序,让它接受命令行参数并打印个性化问候语
- 创建一个程序,计算并显示当前年份是否为闰年
- 编写一个简单的猜数字游戏
- 为待办事项管理器添加编辑功能
- 创建一个程序,读取文本文件并统计单词数量