Go语言基础语法

Go语言基础语法

1. 文档信息

  • 阶段:第一阶段:基础入门
  • 预计学习时间:6小时
  • 前置知识
    • 已阅读《Go语言简介》
    • 已完成《开发环境搭建》
    • 具备基本的编程概念
  • 学习目标
    • 掌握Go语言的基本语法规则
    • 理解变量和常量的声明和使用
    • 掌握基本的输入输出操作
    • 理解Go的命名规范和代码风格
    • 能够编写符合Go规范的代码

2. 引言

2.1 为什么需要学习这个主题?

💡 问题场景:你已经搭建好了Go开发环境,迫不及待想要开始编写代码。但是,你面临以下问题:

  • Go的语法和我熟悉的语言有什么不同?
  • 如何声明变量?有几种方式?
  • 什么是短变量声明?什么时候使用它?
  • Go的命名规范是什么?
  • 如何进行输入输出操作?
  • 为什么Go没有分号?

传统语言的复杂性

许多编程语言的语法规则复杂,有多种方式完成同一件事:

  • C/C++:多种变量声明方式,复杂的指针语法
  • Java:冗长的类型声明,必须写在类中
  • Python:动态类型,缺少编译时检查
  • JavaScript:灵活但容易出错的类型系统

Go的解决方案

Go语言的设计哲学是"简单",体现在语法上:

  • 简洁的语法:只有25个关键字
  • 统一的风格:gofmt强制统一代码格式
  • 明确的规则:一种正确的方式做事
  • 编译时检查:未使用的变量和导入会报错
  • 自动推导:=短变量声明自动推导类型

2.2 本章学习内容

本章将学习以下内容:

  1. Go程序结构

    • package声明
    • import导入
    • 函数定义
    • 注释规范
  2. 变量声明

    • var关键字声明
    • 短变量声明(:=)
    • 多变量声明
    • 零值概念
  3. 常量声明

    • const关键字
    • iota枚举器
    • 常量表达式
  4. 命名规范

    • 标识符规则
    • 导出和未导出
    • 命名风格
  5. 基本输入输出

    • fmt包的使用
    • Print系列函数
    • Scan系列函数
    • 格式化占位符
  6. 代码组织

    • 代码块和作用域
    • 包的组织
    • 文件结构

2.3 知识导图

下图展示了本章涉及的主要知识点及其关系:
Go基础语法
程序结构
package声明
import导入
函数定义
注释
变量
var声明
短变量声明
多变量声明
零值
常量
const声明
iota枚举
常量表达式
命名规范
标识符规则
导出规则
命名风格
输入输出
fmt包
Print系列
Scan系列
格式化
代码组织
作用域
包组织
文件结构

从图中可以看出,Go的基础语法主要包括六个方面:程序结构、变量、常量、命名规范、输入输出和代码组织。这些内容是编写Go程序的基础。


3. 核心概念

3.1 Go程序结构

定义和解释

一个标准的Go程序由以下部分组成:

  1. package声明:每个Go文件必须属于一个包
  2. import语句:导入需要使用的包
  3. 函数、变量、常量、类型声明:程序的主体内容
  4. 注释:代码说明

最小的Go程序

go 复制代码
package main

func main() {
}
为什么需要它?

问题背景

在编写程序时,我们需要:

  • 组织代码:将相关功能放在一起
  • 重用代码:导入其他包的功能
  • 程序入口:明确程序从哪里开始执行
  • 代码说明:让其他人理解代码的作用

Go的解决方案

Go通过清晰的程序结构解决这些问题:

  • package:组织代码
  • import:重用代码
  • main函数:程序入口
  • 注释:代码文档
基础示例
go 复制代码
// 文件名:hello.go
// 这是一个完整的Go程序示例

// package声明:声明当前文件属于哪个包
// main包是特殊的,表示这是一个可执行程序
package main

// import语句:导入需要使用的包
// fmt包提供格式化输入输出功能
import "fmt"

// main函数:程序的入口点
// 当程序运行时,会自动执行main函数
func main() {
	// 调用fmt包的Println函数输出文本
	// Println会自动添加换行符
	fmt.Println("Hello, Go!")
}

// 运行方式:
// go run hello.go

// 输出:
// Hello, Go!
工作原理

Go程序的执行流程:


开始
读取package声明
处理import语句
查找main包
是否存在main函数?
执行main函数
编译错误
执行函数体代码
程序结束
报错退出

使用场景
  1. 可执行程序:package main + main函数
  2. 库包:package 其他名称,供其他程序导入使用
  3. 测试文件:package xxx_test,用于测试

3.2 变量和零值

定义和解释

变量:用于存储数据的容器,有名称、类型和值。

零值:Go中每种类型都有默认的零值,声明变量时如果不初始化,会自动赋予零值。

常见类型的零值:

  • 数值类型:0
  • 布尔类型:false
  • 字符串类型:""(空字符串)
  • 指针、切片、映射、通道、函数、接口:nil
为什么需要它?

问题背景

在其他语言中,未初始化的变量可能包含随机值,导致:

  • 程序行为不可预测
  • 难以调试的bug
  • 安全漏洞

Go的解决方案

Go保证所有变量都有明确的初始值(零值),这样:

  • 程序行为可预测
  • 减少bug
  • 代码更安全
基础示例
go 复制代码
package main

import "fmt"

func main() {
	// 示例1:声明变量但不初始化,会自动赋予零值
	var age int        // 零值:0
	var name string    // 零值:""
	var isStudent bool // 零值:false
	
	fmt.Printf("age = %d\n", age)           // 输出:age = 0
	fmt.Printf("name = %q\n", name)         // 输出:name = ""
	fmt.Printf("isStudent = %t\n", isStudent) // 输出:isStudent = false
	
	// 示例2:声明并初始化变量
	var score int = 95
	var city string = "北京"
	var graduated bool = true
	
	fmt.Printf("score = %d\n", score)       // 输出:score = 95
	fmt.Printf("city = %s\n", city)         // 输出:city = 北京
	fmt.Printf("graduated = %t\n", graduated) // 输出:graduated = true
	
	// 示例3:类型推导(省略类型)
	var height = 175      // 自动推导为int
	var weight = 65.5     // 自动推导为float64
	var country = "中国"   // 自动推导为string
	
	fmt.Printf("height类型:%T, 值:%d\n", height, height)
	fmt.Printf("weight类型:%T, 值:%.1f\n", weight, weight)
	fmt.Printf("country类型:%T, 值:%s\n", country, country)
	
	// 示例4:短变量声明(最常用)
	// 使用 := 可以省略var关键字,自动推导类型
	email := "user@example.com"
	phone := "13800138000"
	age2 := 25
	
	fmt.Printf("email = %s\n", email)
	fmt.Printf("phone = %s\n", phone)
	fmt.Printf("age2 = %d\n", age2)
}

// 输出:
// age = 0
// name = ""
// isStudent = false
// score = 95
// city = 北京
// graduated = true
// height类型:int, 值:175
// weight类型:float64, 值:65.5
// country类型:string, 值:中国
// email = user@example.com
// phone = 13800138000
// age2 = 25
使用场景
  1. var声明:包级别变量、需要明确类型时
  2. 短变量声明(:=):函数内部、类型明显时(最常用)
  3. 零值:初始化数据结构、重置变量

4. 代码示例详解

4.1 基础示例

示例1:Hello World详解

问题描述:理解最简单的Go程序的每个部分。

完整代码

go 复制代码
// 文件名:hello_world.go
// 功能:输出Hello World

// 1. package声明
// 每个Go文件都必须以package声明开始
// main包表示这是一个可执行程序(而不是库)
package main

// 2. import语句
// 导入标准库中的fmt包,用于格式化输入输出
// fmt是"format"的缩写
import "fmt"

// 3. main函数
// main函数是程序的入口点
// 程序运行时会自动调用main函数
// main函数没有参数,也没有返回值
func main() {
	// 4. 函数调用
	// fmt.Println是fmt包中的函数
	// Println = Print Line,打印一行并换行
	fmt.Println("Hello, World!")
	
	// 可以打印多个值,用逗号分隔
	fmt.Println("Hello", "Go", "Language")
	
	// Print不换行,Println换行
	fmt.Print("Hello ")
	fmt.Print("World\n") // 需要手动添加换行符\n
}

// 运行方式:
// go run hello_world.go

// 输出:
// Hello, World!
// Hello Go Language
// Hello World

代码解释

  • package main:声明这是main包,可执行程序必须有main包
  • import "fmt":导入fmt包,提供输入输出功能
  • func main():定义main函数,程序入口
  • fmt.Println():调用fmt包的Println函数输出内容

常见陷阱

  • ⚠️ 可执行程序必须有package mainfunc main()
  • ⚠️ 导入的包如果不使用会报错
  • ⚠️ main函数不能有参数和返回值
示例2:变量声明的四种方式

问题描述:Go中有多种变量声明方式,如何选择?

完整代码

go 复制代码
package main

import "fmt"

// 方式1:标准声明(包级别变量)
// 在函数外部声明的变量称为包级别变量
var globalName string = "全局变量"
var globalAge int = 30

// 方式2:类型推导(包级别)
var globalCity = "北京" // 自动推导为string

// 方式3:批量声明(包级别)
var (
	globalCountry = "中国"
	globalPopulation = 1400000000
	globalArea = 9600000.0
)

func main() {
	fmt.Println("=== 包级别变量 ===")
	fmt.Printf("globalName: %s\n", globalName)
	fmt.Printf("globalAge: %d\n", globalAge)
	fmt.Printf("globalCity: %s\n", globalCity)
	
	fmt.Println("\n=== 函数内变量声明 ===")
	
	// 方式1:标准声明
	var name string = "张三"
	var age int = 25
	fmt.Printf("name: %s, age: %d\n", name, age)
	
	// 方式2:类型推导
	var height = 175      // int
	var weight = 65.5     // float64
	var isStudent = true  // bool
	fmt.Printf("height: %d, weight: %.1f, isStudent: %t\n", height, weight, isStudent)
	
	// 方式3:短变量声明(最常用)
	// 只能在函数内使用
	email := "zhangsan@example.com"
	phone := "13800138000"
	score := 95.5
	fmt.Printf("email: %s, phone: %s, score: %.1f\n", email, phone, score)
	
	// 方式4:批量声明
	var (
		address = "北京市朝阳区"
		zipCode = "100000"
		company = "科技公司"
	)
	fmt.Printf("address: %s, zipCode: %s, company: %s\n", address, zipCode, company)
	
	// 方式5:多变量同时声明
	var x, y, z int = 1, 2, 3
	fmt.Printf("x: %d, y: %d, z: %d\n", x, y, z)
	
	// 方式6:多变量短声明(不同类型)
	name2, age2, height2 := "李四", 28, 180
	fmt.Printf("name2: %s, age2: %d, height2: %d\n", name2, age2, height2)
	
	fmt.Println("\n=== 零值示例 ===")
	
	// 声明但不初始化,会自动赋予零值
	var num int
	var str string
	var flag bool
	fmt.Printf("num零值: %d\n", num)       // 0
	fmt.Printf("str零值: %q\n", str)       // ""
	fmt.Printf("flag零值: %t\n", flag)     // false
}

// 输出:
// === 包级别变量 ===
// globalName: 全局变量
// globalAge: 30
// globalCity: 北京
//
// === 函数内变量声明 ===
// name: 张三, age: 25
// height: 175, weight: 65.5, isStudent: true
// email: zhangsan@example.com, phone: 13800138000, score: 95.5
// address: 北京市朝阳区, zipCode: 100000, company: 科技公司
// x: 1, y: 2, z: 3
// name2: 李四, age2: 28, height2: 180
//
// === 零值示例 ===
// num零值: 0
// str零值: ""
// flag零值: false

代码解释

  • var声明:适用于包级别变量或需要明确类型的场景
  • 短变量声明(:=):只能在函数内使用,最常用
  • 批量声明 :使用var ()声明多个变量
  • 零值:未初始化的变量自动赋予零值

常见陷阱

  • ⚠️ :=只能在函数内使用,包级别必须用var
  • ⚠️ 短变量声明至少要声明一个新变量
  • ⚠️ 已声明的变量不能用:=重复声明
示例3:常量声明

问题描述:如何声明和使用常量?

完整代码

go 复制代码
package main

import "fmt"

// 包级别常量
const Pi = 3.14159
const AppName = "我的应用"

// 批量声明常量
const (
	StatusOK = 200
	StatusNotFound = 404
	StatusInternalError = 500
)

// 使用iota枚举器
const (
	Sunday = iota    // 0
	Monday           // 1
	Tuesday          // 2
	Wednesday        // 3
	Thursday         // 4
	Friday           // 5
	Saturday         // 6
)

// iota高级用法:位运算
const (
	ReadPermission = 1 << iota  // 1 << 0 = 1
	WritePermission             // 1 << 1 = 2
	ExecutePermission           // 1 << 2 = 4
)

func main() {
	fmt.Println("=== 基本常量 ===")
	fmt.Printf("Pi = %.5f\n", Pi)
	fmt.Printf("AppName = %s\n", AppName)
	
	fmt.Println("\n=== HTTP状态码 ===")
	fmt.Printf("StatusOK = %d\n", StatusOK)
	fmt.Printf("StatusNotFound = %d\n", StatusNotFound)
	fmt.Printf("StatusInternalError = %d\n", StatusInternalError)
	
	fmt.Println("\n=== 星期枚举 ===")
	fmt.Printf("Sunday = %d\n", Sunday)
	fmt.Printf("Monday = %d\n", Monday)
	fmt.Printf("Friday = %d\n", Friday)
	fmt.Printf("Saturday = %d\n", Saturday)
	
	fmt.Println("\n=== 权限位 ===")
	fmt.Printf("ReadPermission = %d (二进制: %b)\n", ReadPermission, ReadPermission)
	fmt.Printf("WritePermission = %d (二进制: %b)\n", WritePermission, WritePermission)
	fmt.Printf("ExecutePermission = %d (二进制: %b)\n", ExecutePermission, ExecutePermission)
	
	// 组合权限
	fullPermission := ReadPermission | WritePermission | ExecutePermission
	fmt.Printf("FullPermission = %d (二进制: %b)\n", fullPermission, fullPermission)
	
	fmt.Println("\n=== 函数内常量 ===")
	const localConst = "局部常量"
	const (
		maxRetry = 3
		timeout = 30
	)
	fmt.Printf("localConst = %s\n", localConst)
	fmt.Printf("maxRetry = %d, timeout = %d\n", maxRetry, timeout)
	
	// 常量表达式
	const (
		a = 10
		b = 20
		c = a + b  // 常量表达式在编译时计算
	)
	fmt.Printf("a + b = %d\n", c)
}

// 输出:
// === 基本常量 ===
// Pi = 3.14159
// AppName = 我的应用
//
// === HTTP状态码 ===
// StatusOK = 200
// StatusNotFound = 404
// StatusInternalError = 500
//
// === 星期枚举 ===
// Sunday = 0
// Monday = 1
// Friday = 5
// Saturday = 6
//
// === 权限位 ===
// ReadPermission = 1 (二进制: 1)
// WritePermission = 2 (二进制: 10)
// ExecutePermission = 4 (二进制: 100)
// FullPermission = 7 (二进制: 111)
//
// === 函数内常量 ===
// localConst = 局部常量
// maxRetry = 3, timeout = 30
// a + b = 30

代码解释

  • const:声明常量,值在编译时确定,运行时不可修改
  • iota:常量生成器,从0开始自动递增
  • 位运算:使用iota和位移运算创建标志位
  • 常量表达式:常量可以参与编译时计算

常见陷阱

  • ⚠️ 常量不能用:=声明
  • ⚠️ 常量的值必须在编译时确定
  • ⚠️ iota在每个const块中重新从0开始
示例4:格式化输出

问题描述:如何使用fmt包进行格式化输出?

完整代码

go 复制代码
package main

import "fmt"

func main() {
	// 基本数据
	name := "张三"
	age := 25
	height := 175.5
	isStudent := true
	score := 95.8
	
	fmt.Println("=== Print系列函数 ===")
	
	// Print:输出内容,不换行
	fmt.Print("Hello ")
	fmt.Print("World\n")
	
	// Println:输出内容并换行
	fmt.Println("Hello", "Go")
	
	// Printf:格式化输出
	fmt.Printf("姓名:%s,年龄:%d岁\n", name, age)
	
	fmt.Println("\n=== 常用格式化占位符 ===")
	
	// %v:默认格式
	fmt.Printf("%%v: %v\n", name)
	
	// %+v:结构体会显示字段名
	// %#v:Go语法表示
	fmt.Printf("%%#v: %#v\n", name)
	
	// %T:类型
	fmt.Printf("%%T: %T\n", name)
	
	// %d:十进制整数
	fmt.Printf("%%d: %d\n", age)
	
	// %b:二进制
	fmt.Printf("%%b: %b\n", age)
	
	// %o:八进制
	fmt.Printf("%%o: %o\n", age)
	
	// %x:十六进制(小写)
	fmt.Printf("%%x: %x\n", age)
	
	// %X:十六进制(大写)
	fmt.Printf("%%X: %X\n", age)
	
	// %f:浮点数
	fmt.Printf("%%f: %f\n", height)
	
	// %.2f:保留2位小数
	fmt.Printf("%%.2f: %.2f\n", height)
	
	// %e:科学计数法
	fmt.Printf("%%e: %e\n", height)
	
	// %s:字符串
	fmt.Printf("%%s: %s\n", name)
	
	// %q:带引号的字符串
	fmt.Printf("%%q: %q\n", name)
	
	// %t:布尔值
	fmt.Printf("%%t: %t\n", isStudent)
	
	// %p:指针地址
	fmt.Printf("%%p: %p\n", &name)
	
	fmt.Println("\n=== 宽度和精度 ===")
	
	// %5d:宽度为5,右对齐
	fmt.Printf("|%5d|\n", age)
	
	// %-5d:宽度为5,左对齐
	fmt.Printf("|%-5d|\n", age)
	
	// %05d:宽度为5,用0填充
	fmt.Printf("|%05d|\n", age)
	
	// %8.2f:总宽度8,小数点后2位
	fmt.Printf("|%8.2f|\n", score)
	
	// %10s:宽度为10
	fmt.Printf("|%10s|\n", name)
	
	// %-10s:宽度为10,左对齐
	fmt.Printf("|%-10s|\n", name)
	
	fmt.Println("\n=== Sprintf:格式化为字符串 ===")
	
	// Sprintf返回格式化后的字符串,不输出
	message := fmt.Sprintf("姓名:%s,年龄:%d岁,身高:%.1fcm", name, age, height)
	fmt.Println(message)
	
	// Sprintln:格式化为字符串并添加换行
	message2 := fmt.Sprintln("Hello", "Go")
	fmt.Print(message2)
	
	fmt.Println("\n=== 多个值的输出 ===")
	
	// 输出多个值
	fmt.Printf("姓名:%s,年龄:%d,身高:%.1f,学生:%t,分数:%.1f\n",
		name, age, height, isStudent, score)
	
	// 使用索引(重复使用参数)
	fmt.Printf("%[1]s今年%[2]d岁,%[1]s是学生\n", name, age)
}

// 输出:
// === Print系列函数 ===
// Hello World
// Hello Go
// 姓名:张三,年龄:25岁
//
// === 常用格式化占位符 ===
// %v: 张三
// %#v: "张三"
// %T: string
// %d: 25
// %b: 11001
// %o: 31
// %x: 19
// %X: 19
// %f: 175.500000
// %.2f: 175.50
// %e: 1.755000e+02
// %s: 张三
// %q: "张三"
// %t: true
// %p: 0xc000010230
//
// === 宽度和精度 ===
// |   25|
// |25   |
// |00025|
// |   95.80|
// |      张三|
// |张三      |
//
// === Sprintf:格式化为字符串 ===
// 姓名:张三,年龄:25岁,身高:175.5cm
// Hello Go
//
// === 多个值的输出 ===
// 姓名:张三,年龄:25,身高:175.5,学生:true,分数:95.8
// 张三今年25岁,张三是学生

代码解释

  • Print:直接输出,不换行
  • Println:输出并换行
  • Printf:格式化输出
  • Sprintf:格式化为字符串
  • 占位符:%v、%d、%f、%s、%t等

常见陷阱

  • ⚠️ 占位符数量要与参数数量匹配
  • ⚠️ 占位符类型要与参数类型匹配
  • ⚠️ %v是通用占位符,但不如具体类型清晰
示例5:输入操作

问题描述:如何从控制台读取用户输入?

完整代码

go 复制代码
package main

import "fmt"

func main() {
	fmt.Println("=== Scan系列函数 ===")
	
	// 示例1:Scan - 读取空格分隔的输入
	fmt.Println("\n请输入姓名和年龄(用空格分隔):")
	var name string
	var age int
	
	// Scan会读取输入,遇到空格或换行停止
	// 返回成功读取的参数个数和错误
	n, err := fmt.Scan(&name, &age)
	if err != nil {
		fmt.Println("输入错误:", err)
	} else {
		fmt.Printf("成功读取%d个参数\n", n)
		fmt.Printf("姓名:%s,年龄:%d\n", name, age)
	}
	
	// 示例2:Scanln - 读取一行
	fmt.Println("\n请输入城市名称:")
	var city string
	fmt.Scanln(&city)
	fmt.Printf("城市:%s\n", city)
	
	// 示例3:Scanf - 格式化输入
	fmt.Println("\n请按格式输入(姓名:年龄):")
	var name2 string
	var age2 int
	fmt.Scanf("%s:%d", &name2, &age2)
	fmt.Printf("姓名:%s,年龄:%d\n", name2, age2)
	
	// 示例4:读取多个值
	fmt.Println("\n请输入三个整数(用空格分隔):")
	var a, b, c int
	fmt.Scan(&a, &b, &c)
	fmt.Printf("a=%d, b=%d, c=%d, 和=%d\n", a, b, c, a+b+c)
	
	// 示例5:读取浮点数
	fmt.Println("\n请输入身高(米)和体重(公斤):")
	var height, weight float64
	fmt.Scan(&height, &weight)
	bmi := weight / (height * height)
	fmt.Printf("身高:%.2fm,体重:%.1fkg,BMI:%.2f\n", height, weight, bmi)
}

// 运行示例:
// go run input.go

// 交互示例:
// === Scan系列函数 ===
//
// 请输入姓名和年龄(用空格分隔):
// 张三 25
// 成功读取2个参数
// 姓名:张三,年龄:25
//
// 请输入城市名称:
// 北京
// 城市:北京
//
// 请按格式输入(姓名:年龄):
// 李四:30
// 姓名:李四,年龄:30
//
// 请输入三个整数(用空格分隔):
// 10 20 30
// a=10, b=20, c=30, 和=60
//
// 请输入身高(米)和体重(公斤):
// 1.75 70
// 身高:1.75m,体重:70.0kg,BMI:22.86

代码解释

  • Scan:读取空格分隔的输入
  • Scanln:读取一行输入
  • Scanf:按格式读取输入
  • &变量:传递变量的地址,以便修改变量的值

常见陷阱

  • ⚠️ Scan函数需要传递变量的地址(&变量)
  • ⚠️ 输入类型要与变量类型匹配
  • ⚠️ Scan遇到空格会停止读取当前变量

4.2 进阶示例

示例6:作用域和变量遮蔽

问题描述:理解Go中的作用域规则和变量遮蔽现象。

完整代码

go 复制代码
package main

import "fmt"

// 包级别变量
var globalVar = "全局变量"

func main() {
	fmt.Println("=== 作用域示例 ===")
	
	// 函数级别变量
	var localVar = "函数变量"
	fmt.Printf("globalVar: %s\n", globalVar)
	fmt.Printf("localVar: %s\n", localVar)
	
	// 代码块作用域
	{
		// 块级别变量
		var blockVar = "块变量"
		fmt.Printf("blockVar: %s\n", blockVar)
		
		// 可以访问外层变量
		fmt.Printf("访问外层localVar: %s\n", localVar)
	}
	
	// blockVar在这里不可访问
	// fmt.Println(blockVar) // 编译错误:undefined: blockVar
	
	fmt.Println("\n=== 变量遮蔽示例 ===")
	
	x := 10
	fmt.Printf("外层x: %d\n", x)
	
	{
		// 内层声明同名变量,遮蔽外层变量
		x := 20
		fmt.Printf("内层x: %d\n", x)
		
		// 修改内层x不影响外层x
		x = 30
		fmt.Printf("修改后内层x: %d\n", x)
	}
	
	// 外层x未被修改
	fmt.Printf("外层x仍然是: %d\n", x)
	
	fmt.Println("\n=== if语句作用域 ===")
	
	// if语句可以包含初始化语句
	if y := 100; y > 50 {
		fmt.Printf("if块内y: %d\n", y)
		// y只在if块内有效
	}
	
	// y在这里不可访问
	// fmt.Println(y) // 编译错误:undefined: y
	
	fmt.Println("\n=== for循环作用域 ===")
	
	// for循环变量的作用域
	for i := 0; i < 3; i++ {
		fmt.Printf("循环内i: %d\n", i)
	}
	
	// i在这里不可访问
	// fmt.Println(i) // 编译错误:undefined: i
	
	fmt.Println("\n=== 包级别变量遮蔽 ===")
	
	// 函数内声明同名变量,遮蔽包级别变量
	globalVar := "局部globalVar"
	fmt.Printf("局部globalVar: %s\n", globalVar)
	
	// 如何访问被遮蔽的包级别变量?
	// 在Go中,没有直接方法访问被遮蔽的包级别变量
	// 最好避免这种情况
}

// 输出:
// === 作用域示例 ===
// globalVar: 全局变量
// localVar: 函数变量
// blockVar: 块变量
// 访问外层localVar: 函数变量
//
// === 变量遮蔽示例 ===
// 外层x: 10
// 内层x: 20
// 修改后内层x: 30
// 外层x仍然是: 10
//
// === if语句作用域 ===
// if块内y: 100
//
// === for循环作用域 ===
// 循环内i: 0
// 循环内i: 1
// 循环内i: 2
//
// === 包级别变量遮蔽 ===
// 局部globalVar: 局部globalVar

代码解释

  • 作用域:变量的可见范围
  • 变量遮蔽:内层变量遮蔽外层同名变量
  • 块作用域{}创建新的作用域
  • 语句作用域:if、for等语句可以有自己的作用域

常见陷阱

  • ⚠️ 变量遮蔽可能导致意外的bug
  • ⚠️ 被遮蔽的包级别变量无法直接访问
  • ⚠️ 循环变量在循环外不可访问
示例7:类型转换

问题描述:Go是强类型语言,不同类型之间如何转换?

完整代码

go 复制代码
package main

import (
	"fmt"
	"strconv"
)

func main() {
	fmt.Println("=== 数值类型转换 ===")
	
	// int转float64
	var i int = 42
	var f float64 = float64(i)
	fmt.Printf("int %d 转 float64: %.2f\n", i, f)
	
	// float64转int(会截断小数部分)
	var f2 float64 = 3.14159
	var i2 int = int(f2)
	fmt.Printf("float64 %.5f 转 int: %d\n", f2, i2)
	
	// int32转int64
	var i32 int32 = 100
	var i64 int64 = int64(i32)
	fmt.Printf("int32 %d 转 int64: %d\n", i32, i64)
	
	// 不同大小的int类型转换
	var a int8 = 127
	var b int16 = int16(a)
	var c int32 = int32(b)
	var d int64 = int64(c)
	fmt.Printf("int8(%d) -> int16(%d) -> int32(%d) -> int64(%d)\n", a, b, c, d)
	
	fmt.Println("\n=== 字符串转数值 ===")
	
	// 字符串转int
	str1 := "123"
	num1, err := strconv.Atoi(str1)
	if err != nil {
		fmt.Println("转换错误:", err)
	} else {
		fmt.Printf("字符串 %q 转 int: %d\n", str1, num1)
	}
	
	// 字符串转int64
	str2 := "9876543210"
	num2, err := strconv.ParseInt(str2, 10, 64)
	if err != nil {
		fmt.Println("转换错误:", err)
	} else {
		fmt.Printf("字符串 %q 转 int64: %d\n", str2, num2)
	}
	
	// 字符串转float64
	str3 := "3.14159"
	num3, err := strconv.ParseFloat(str3, 64)
	if err != nil {
		fmt.Println("转换错误:", err)
	} else {
		fmt.Printf("字符串 %q 转 float64: %.5f\n", str3, num3)
	}
	
	// 字符串转bool
	str4 := "true"
	bool1, err := strconv.ParseBool(str4)
	if err != nil {
		fmt.Println("转换错误:", err)
	} else {
		fmt.Printf("字符串 %q 转 bool: %t\n", str4, bool1)
	}
	
	fmt.Println("\n=== 数值转字符串 ===")
	
	// int转字符串
	num4 := 456
	str5 := strconv.Itoa(num4)
	fmt.Printf("int %d 转字符串: %q\n", num4, str5)
	
	// int64转字符串
	num5 := int64(123456789)
	str6 := strconv.FormatInt(num5, 10)
	fmt.Printf("int64 %d 转字符串: %q\n", num5, str6)
	
	// float64转字符串
	num6 := 3.14159
	str7 := strconv.FormatFloat(num6, 'f', 2, 64)
	fmt.Printf("float64 %.5f 转字符串: %q\n", num6, str7)
	
	// bool转字符串
	bool2 := true
	str8 := strconv.FormatBool(bool2)
	fmt.Printf("bool %t 转字符串: %q\n", bool2, str8)
	
	fmt.Println("\n=== 使用fmt.Sprintf转换 ===")
	
	// 任意类型转字符串
	num7 := 789
	str9 := fmt.Sprintf("%d", num7)
	fmt.Printf("使用Sprintf: %q\n", str9)
	
	num8 := 2.71828
	str10 := fmt.Sprintf("%.3f", num8)
	fmt.Printf("使用Sprintf: %q\n", str10)
	
	fmt.Println("\n=== 转换错误处理 ===")
	
	// 无效的字符串转int
	invalidStr := "abc"
	_, err = strconv.Atoi(invalidStr)
	if err != nil {
		fmt.Printf("转换 %q 失败: %v\n", invalidStr, err)
	}
	
	// 溢出
	overflowStr := "999999999999999999999"
	_, err = strconv.Atoi(overflowStr)
	if err != nil {
		fmt.Printf("转换 %q 失败: %v\n", overflowStr, err)
	}
}

// 输出:
// === 数值类型转换 ===
// int 42 转 float64: 42.00
// float64 3.14159 转 int: 3
// int32 100 转 int64: 100
// int8(127) -> int16(127) -> int32(127) -> int64(127)
//
// === 字符串转数值 ===
// 字符串 "123" 转 int: 123
// 字符串 "9876543210" 转 int64: 9876543210
// 字符串 "3.14159" 转 float64: 3.14159
// 字符串 "true" 转 bool: true
//
// === 数值转字符串 ===
// int 456 转字符串: "456"
// int64 123456789 转字符串: "123456789"
// float64 3.14159 转字符串: "3.14"
// bool true 转字符串: "true"
//
// === 使用fmt.Sprintf转换 ===
// 使用Sprintf: "789"
// 使用Sprintf: "2.718"
//
// === 转换错误处理 ===
// 转换 "abc" 失败: strconv.Atoi: parsing "abc": invalid syntax
// 转换 "999999999999999999999" 失败: strconv.Atoi: parsing "999999999999999999999": value out of range

代码解释

  • 类型转换 :使用类型(值)语法
  • strconv包:字符串和基本类型之间的转换
  • 错误处理:转换可能失败,需要检查错误
  • fmt.Sprintf:通用的转字符串方法

常见陷阱

  • ⚠️ float转int会截断小数部分,不是四舍五入
  • ⚠️ 字符串转数值可能失败,必须检查错误
  • ⚠️ 不同大小的整数类型转换可能溢出
示例8:命名规范实践

问题描述:Go的命名规范是什么?如何写出符合规范的代码?

完整代码

go 复制代码
package main

import "fmt"

// ✅ 好的命名:大写字母开头,表示导出(public)
// 其他包可以访问
const MaxConnections = 100
const DefaultTimeout = 30

// ✅ 好的命名:小写字母开头,表示未导出(private)
// 只能在当前包内访问
const minRetry = 3
const maxRetry = 10

// ✅ 好的命名:使用驼峰命名法
var userCount int
var totalAmount float64
var isConnected bool

// ❌ 不好的命名:使用下划线(不符合Go风格)
// var user_count int
// var total_amount float64

// ✅ 好的命名:缩写词全部大写或全部小写
var userID int      // ID全部大写
var httpServer string // HTTP全部大写
var urlPath string    // URL全部大写

// ❌ 不好的命名:缩写词大小写混合
// var userId int
// var httpServer string

// ✅ 好的函数命名:动词开头,描述功能
func GetUserName() string {
	return "张三"
}

func SetUserAge(age int) {
	fmt.Printf("设置年龄:%d\n", age)
}

func IsValidEmail(email string) bool {
	return len(email) > 0
}

func CalculateTotal(price, quantity int) int {
	return price * quantity
}

// ✅ 好的命名:接收者使用类型的首字母(小写)
type User struct {
	Name string
	Age  int
}

// 接收者命名为u(User的首字母)
func (u User) GetInfo() string {
	return fmt.Sprintf("%s, %d岁", u.Name, u.Age)
}

// ✅ 好的命名:常量使用有意义的名称
const (
	StatusPending  = "pending"
	StatusApproved = "approved"
	StatusRejected = "rejected"
)

// ✅ 好的命名:错误变量以Err开头
var (
	ErrNotFound     = fmt.Errorf("未找到")
	ErrInvalidInput = fmt.Errorf("无效输入")
	ErrTimeout      = fmt.Errorf("超时")
)

func main() {
	fmt.Println("=== 命名规范示例 ===")
	
	// ✅ 好的变量命名:简短但有意义
	name := "李四"
	age := 30
	email := "lisi@example.com"
	
	fmt.Printf("姓名:%s,年龄:%d,邮箱:%s\n", name, age, email)
	
	// ✅ 好的命名:循环变量使用i, j, k
	for i := 0; i < 3; i++ {
		fmt.Printf("循环 %d\n", i)
	}
	
	// ✅ 好的命名:range循环使用有意义的名称
	numbers := []int{1, 2, 3, 4, 5}
	for index, value := range numbers {
		fmt.Printf("索引:%d,值:%d\n", index, value)
	}
	
	// ✅ 好的命名:不使用的变量用_忽略
	for _, value := range numbers {
		fmt.Printf("值:%d\n", value)
	}
	
	// ✅ 好的命名:布尔变量以is, has, can等开头
	isValid := true
	hasPermission := false
	canEdit := true
	
	fmt.Printf("isValid: %t, hasPermission: %t, canEdit: %t\n",
		isValid, hasPermission, canEdit)
	
	// ✅ 好的命名:使用完整单词,避免缩写
	// 除非是广为人知的缩写(如ID, URL, HTTP)
	userName := "王五"  // ✅ 好
	// usrName := "王五"  // ❌ 不好
	
	userID := 12345    // ✅ 好(ID是常见缩写)
	// userId := 12345    // ❌ 不好(大小写不对)
	
	fmt.Printf("userName: %s, userID: %d\n", userName, userID)
	
	fmt.Println("\n=== 包级别标识符 ===")
	
	// 导出的标识符(大写开头)
	fmt.Printf("MaxConnections: %d\n", MaxConnections)
	fmt.Printf("DefaultTimeout: %d\n", DefaultTimeout)
	
	// 未导出的标识符(小写开头)
	fmt.Printf("minRetry: %d\n", minRetry)
	fmt.Printf("maxRetry: %d\n", maxRetry)
	
	fmt.Println("\n=== 函数命名 ===")
	
	// 调用导出的函数
	userName2 := GetUserName()
	fmt.Printf("用户名:%s\n", userName2)
	
	SetUserAge(25)
	
	isValid2 := IsValidEmail("test@example.com")
	fmt.Printf("邮箱有效:%t\n", isValid2)
	
	total := CalculateTotal(100, 5)
	fmt.Printf("总计:%d\n", total)
	
	fmt.Println("\n=== 结构体和方法 ===")
	
	user := User{Name: "赵六", Age: 28}
	info := user.GetInfo()
	fmt.Println(info)
}

// 输出:
// === 命名规范示例 ===
// 姓名:李四,年龄:30,邮箱:lisi@example.com
// 循环 0
// 循环 1
// 循环 2
// 索引:0,值:1
// 索引:1,值:2
// 索引:2,值:3
// 索引:3,值:4
// 索引:4,值:5
// 值:1
// 值:2
// 值:3
// 值:4
// 值:5
// isValid: true, hasPermission: false, canEdit: true
// userName: 王五, userID: 12345
//
// === 包级别标识符 ===
// MaxConnections: 100
// DefaultTimeout: 30
// minRetry: 3
// maxRetry: 10
//
// === 函数命名 ===
// 用户名:张三
// 设置年龄:25
// 邮箱有效:true
// 总计:500
//
// === 结构体和方法 ===
// 赵六, 28岁

代码解释

  • 导出规则:大写开头导出,小写开头未导出
  • 驼峰命名:使用驼峰而不是下划线
  • 缩写词:全部大写或全部小写
  • 有意义的名称:避免无意义的缩写

常见陷阱

  • ⚠️ 不要使用下划线命名(除了特殊情况如_忽略变量)
  • ⚠️ 缩写词要么全大写要么全小写,不要混合
  • ⚠️ 导出的标识符要有文档注释
示例9:注释规范

问题描述:如何写出规范的Go注释?

完整代码

go 复制代码
// Package main 是程序的入口包
// 这个程序演示了Go语言的注释规范
package main

import "fmt"

// 常量定义
// MaxRetry 定义最大重试次数
const MaxRetry = 3

// DefaultPort 定义默认端口号
const DefaultPort = 8080

/*
批量常量定义
这些常量用于表示用户状态
*/
const (
	// StatusActive 表示用户处于活跃状态
	StatusActive = "active"
	// StatusInactive 表示用户处于非活跃状态
	StatusInactive = "inactive"
	// StatusBanned 表示用户被禁用
	StatusBanned = "banned"
)

// User 表示系统用户
// 包含用户的基本信息
type User struct {
	// Name 是用户的姓名
	Name string
	// Age 是用户的年龄
	Age int
	// Email 是用户的电子邮箱
	Email string
}

// NewUser 创建一个新的用户实例
// 参数:
//   - name: 用户姓名
//   - age: 用户年龄
//   - email: 用户邮箱
//
// 返回值:
//   - *User: 新创建的用户指针
//
// 示例:
//
//	user := NewUser("张三", 25, "zhangsan@example.com")
func NewUser(name string, age int, email string) *User {
	return &User{
		Name:  name,
		Age:   age,
		Email: email,
	}
}

// GetInfo 返回用户的信息字符串
// 格式:姓名 (年龄岁) - 邮箱
func (u *User) GetInfo() string {
	return fmt.Sprintf("%s (%d岁) - %s", u.Name, u.Age, u.Email)
}

// Validate 验证用户信息是否有效
// 返回true表示有效,false表示无效
func (u *User) Validate() bool {
	// 检查姓名是否为空
	if u.Name == "" {
		return false
	}
	
	// 检查年龄是否在合理范围内
	if u.Age < 0 || u.Age > 150 {
		return false
	}
	
	// 检查邮箱是否为空
	if u.Email == "" {
		return false
	}
	
	return true
}

// calculateBMI 计算BMI指数(未导出函数)
// 这是一个内部辅助函数
func calculateBMI(weight, height float64) float64 {
	// BMI = 体重(kg) / 身高(m)²
	return weight / (height * height)
}

func main() {
	fmt.Println("=== 注释规范示例 ===")
	
	// 创建用户
	user := NewUser("李四", 30, "lisi@example.com")
	
	// 输出用户信息
	fmt.Println(user.GetInfo())
	
	// 验证用户信息
	if user.Validate() {
		fmt.Println("用户信息有效")
	} else {
		fmt.Println("用户信息无效")
	}
	
	/*
		多行注释示例
		可以用于较长的说明
		但通常推荐使用多个单行注释
	*/
	
	// TODO: 添加更多用户验证逻辑
	// FIXME: 邮箱格式验证需要改进
	// NOTE: 这里可以添加日志记录
	
	// 计算BMI
	weight := 70.0  // 体重(公斤)
	height := 1.75  // 身高(米)
	bmi := calculateBMI(weight, height)
	fmt.Printf("BMI: %.2f\n", bmi)
}

// 输出:
// === 注释规范示例 ===
// 李四 (30岁) - lisi@example.com
// 用户信息有效
// BMI: 22.86

代码解释

  • 包注释:在package语句前,描述包的功能
  • 导出标识符注释:必须以标识符名称开头
  • 函数注释:描述功能、参数、返回值
  • 行内注释:解释复杂逻辑

注释最佳实践

  1. 所有导出的标识符都要有注释
  2. 注释要以标识符名称开头
  3. 使用完整的句子,首字母大写,句号结尾
  4. 避免无意义的注释
  5. 使用TODO、FIXME、NOTE等标记

常见陷阱

  • ⚠️ 导出的标识符缺少注释会被golint警告
  • ⚠️ 注释不要以标识符名称开头会被警告
  • ⚠️ 过时的注释比没有注释更糟糕
示例10:多文件程序

问题描述:如何组织多个Go文件?

文件结构

复制代码
myapp/
├── main.go
├── user.go
└── utils.go

main.go

go 复制代码
// Package main 是程序的入口包
package main

import "fmt"

func main() {
	fmt.Println("=== 多文件程序示例 ===")
	
	// 使用user.go中定义的函数
	user := CreateUser("张三", 25)
	fmt.Println(GetUserInfo(user))
	
	// 使用utils.go中定义的函数
	result := Add(10, 20)
	fmt.Printf("10 + 20 = %d\n", result)
	
	message := FormatMessage("Hello", "Go")
	fmt.Println(message)
}

user.go

go 复制代码
// Package main 的用户相关功能
package main

import "fmt"

// UserInfo 存储用户信息
type UserInfo struct {
	Name string
	Age  int
}

// CreateUser 创建新用户
func CreateUser(name string, age int) UserInfo {
	return UserInfo{
		Name: name,
		Age:  age,
	}
}

// GetUserInfo 获取用户信息字符串
func GetUserInfo(user UserInfo) string {
	return fmt.Sprintf("用户:%s,年龄:%d岁", user.Name, user.Age)
}

utils.go

go 复制代码
// Package main 的工具函数
package main

import "fmt"

// Add 计算两个整数的和
func Add(a, b int) int {
	return a + b
}

// Subtract 计算两个整数的差
func Subtract(a, b int) int {
	return a - b
}

// FormatMessage 格式化消息
func FormatMessage(prefix, content string) string {
	return fmt.Sprintf("[%s] %s", prefix, content)
}

运行方式

bash 复制代码
# 方式1:运行所有文件
go run main.go user.go utils.go

# 方式2:运行当前目录所有.go文件
go run .

# 方式3:构建可执行文件
go build -o myapp
./myapp

输出

复制代码
=== 多文件程序示例 ===
用户:张三,年龄:25岁
10 + 20 = 30
[Hello] Go

代码解释

  • 同一个包的多个文件可以互相访问
  • 所有文件必须声明相同的package
  • 可以将相关功能分散到不同文件
  • 运行时需要指定所有文件或使用.

常见陷阱

  • ⚠️ 所有文件必须在同一个包中
  • ⚠️ 不能有重复的函数或变量定义
  • ⚠️ go run需要指定所有相关文件

4.3 高级应用

示例11:包的导入和别名
go 复制代码
package main

import (
	"fmt"
	f "fmt"  // 别名
	. "fmt"  // 点导入(不推荐)
	_ "image/png"  // 仅执行init函数
)

func main() {
	fmt.Println("标准导入")
	f.Println("使用别名")
	Println("点导入(不推荐)")
}
示例12:init函数
go 复制代码
package main
import "fmt"

var globalVar = initGlobal()

func initGlobal() int {
	fmt.Println("1. 初始化全局变量")
	return 100
}

func init() {
	fmt.Println("2. 执行init函数")
}

func main() {
	fmt.Println("3. 执行main函数")
}
示例13:空白标识符
go 复制代码
package main
import "fmt"

func getValues() (int, string, bool) {
	return 42, "hello", true
}

func main() {
	// 忽略不需要的返回值
	num, _, flag := getValues()
	fmt.Printf("num=%d, flag=%t\n", num, flag)
}
示例14:类型别名
go 复制代码
package main
import "fmt"

type UserID int
type Username string

func main() {
	var id UserID = 12345
	var name Username = "张三"
	fmt.Printf("ID: %d, Name: %s\n", id, name)
}
示例15:常量表达式计算
go 复制代码
package main
import "fmt"

const (
	KB = 1024
	MB = 1024 * KB
	GB = 1024 * MB
	TB = 1024 * GB
)

func main() {
	fmt.Printf("1 TB = %d bytes\n", TB)
}

4.4 对比示例

示例16:var vs :=
go 复制代码
package main
import "fmt"

var globalVar = "包级别"  // ✅ 必须用var

func main() {
	var localVar1 = "函数内var"  // ✅ 可以
	localVar2 := "函数内:="      // ✅ 推荐
	
	// := 不能用于包级别
	// globalVar2 := "错误"  // ❌ 编译错误
	
	fmt.Println(globalVar, localVar1, localVar2)
}
示例17:Print vs Println vs Printf
go 复制代码
package main
import "fmt"

func main() {
	name := "Go"
	
	fmt.Print("Hello ")
	fmt.Print(name)
	fmt.Print("\n")
	
	fmt.Println("Hello", name)
	
	fmt.Printf("Hello %s\n", name)
}
示例18:显式类型 vs 类型推导
go 复制代码
package main
import "fmt"

func main() {
	var x int = 10        // 显式类型
	var y = 10            // 类型推导为int
	z := 10               // 类型推导为int
	
	fmt.Printf("x=%d(%T), y=%d(%T), z=%d(%T)\n", x, x, y, y, z, z)
}
示例19:单行注释 vs 多行注释
go 复制代码
package main

// 单行注释:推荐用于大多数情况
// 可以连续使用多个单行注释

/*
多行注释:
用于较长的说明
或临时注释掉代码块
*/

func main() {
	// 这是单行注释
	x := 10
	
	/*
	这是多行注释
	可以跨越多行
	*/
	y := 20
}
示例20:导出 vs 未导出
go 复制代码
package main
import "fmt"

// ExportedFunc 可以被其他包访问(大写开头)
func ExportedFunc() {
	fmt.Println("导出的函数")
}

// unexportedFunc 只能在当前包内访问(小写开头)
func unexportedFunc() {
	fmt.Println("未导出的函数")
}

func main() {
	ExportedFunc()
	unexportedFunc()
}

5. 问题与解决方案

5.1 问题1:未使用的变量导致编译错误

问题描述 :声明了变量但未使用,导致编译失败。
解决方案

go 复制代码
package main
import "fmt"

func main() {
	// 使用空白标识符忽略不需要的值
	_, err := fmt.Println("Hello")
	if err != nil {
		return
	}
}

5.2 问题2:变量重复声明

问题描述 :使用:=重复声明已存在的变量。
解决方案

go 复制代码
package main
import "fmt"

func main() {
	x := 10
	// x := 20  // 错误:重复声明
	x = 20     // 正确:赋值
	fmt.Println(x)
}

5.3 问题3:类型不匹配

问题描述 :不同类型的值不能直接运算。
解决方案

go 复制代码
package main
import "fmt"

func main() {
	var a int = 10
	var b float64 = 20.5
	// c := a + b  // 错误:类型不匹配
	c := float64(a) + b  // 正确:类型转换
	fmt.Println(c)
}

5.4 问题4:导入包未使用

问题描述 :导入的包没有使用会报错。
解决方案

go 复制代码
package main
import (
	"fmt"
	_ "image/png"  // 使用_表示只执行init
)

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

5.5 问题5:常量不能修改

问题描述 :尝试修改常量的值。
解决方案

go 复制代码
package main

func main() {
	const x = 10
	// x = 20  // 错误:常量不能修改
	
	var y = 10  // 使用变量
	y = 20      // 正确
}

6. 最佳实践

6.1 实践1:优先使用短变量声明

好的示例

go 复制代码
name := "张三"
age := 25

不好的示例

go 复制代码
var name string = "张三"
var age int = 25

6.2 实践2:使用有意义的变量名

好的示例

go 复制代码
userCount := 100
totalAmount := 1500.50

不好的示例

go 复制代码
x := 100
y := 1500.50

6.3 实践3:使用gofmt格式化代码

bash 复制代码
gofmt -w *.go

6.4 实践4:为导出的标识符添加注释

go 复制代码
// MaxRetry 定义最大重试次数
const MaxRetry = 3

6.5 实践5:避免使用点导入

go 复制代码
// ❌ 不推荐
import . "fmt"

// ✅ 推荐
import "fmt"

7. 常见错误

7.1 错误1:忘记package声明

错误代码

go 复制代码
import "fmt"
func main() {
	fmt.Println("Hello")
}

正确代码

go 复制代码
package main
import "fmt"
func main() {
	fmt.Println("Hello")
}

7.2 错误2:main函数有参数或返回值

错误代码

go 复制代码
package main
func main(args []string) int {
	return 0
}

正确代码

go 复制代码
package main
func main() {
	// 无参数,无返回值
}

7.3 错误3:变量名使用保留字

错误代码

go 复制代码
var for = 10  // for是关键字

正确代码

go 复制代码
var count = 10

7.4 错误4:字符串拼接使用+号时类型不匹配

错误代码

go 复制代码
age := 25
msg := "年龄:" + age  // 错误

正确代码

go 复制代码
age := 25
msg := fmt.Sprintf("年龄:%d", age)

7.5 错误5:常量使用:=声明

错误代码

go 复制代码
const x := 10  // 错误

正确代码

go 复制代码
const x = 10

9. 练习题

9.1 基础练习

练习1:变量声明和输出

编写程序,声明姓名、年龄、身高变量并输出。

练习2:常量定义

定义一周七天的常量,使用iota。

练习3:格式化输出

使用Printf输出个人信息,包含姓名、年龄、身高。

9.2 进阶练习

练习4:类型转换

编写程序,输入身高(厘米),转换为米并计算BMI。

练习5:多文件程序

创建包含main.go和utils.go的程序,实现基本计算功能。

9.3 挑战练习

练习6:命令行参数处理

编写程序,接收命令行参数并进行处理。


10. 思考问题

  1. 为什么Go要求未使用的变量和导入必须删除?
  2. 短变量声明:=和var声明各适用于什么场景?
  3. Go的零值设计有什么优势?
  4. 为什么Go使用大小写来控制导出而不是public/private关键字?
  5. iota在实际开发中有哪些应用场景?

11. 总结

11.1 知识回顾

本章学习了Go语言的基础语法,包括程序结构、变量声明、常量定义、命名规范、输入输出等核心内容。

11.2 知识图谱

Go基础语法
程序结构
变量
常量
命名规范
输入输出
代码组织

11.3 下一步学习

下一章:数据类型详解


12. 参考资料


附录:练习题参考答案

练习1答案

go 复制代码
package main
import "fmt"

func main() {
	name := "张三"
	age := 25
	height := 175.5
	fmt.Printf("姓名:%s,年龄:%d,身高:%.1f\n", name, age, height)
}

练习2答案

go 复制代码
package main
import "fmt"

const (
	Sunday = iota
	Monday
	Tuesday
	Wednesday
	Thursday
	Friday
	Saturday
)

func main() {
	fmt.Println(Sunday, Monday, Friday)
}

练习3答案

go 复制代码
package main
import "fmt"

func main() {
	name := "李四"
	age := 30
	height := 180.0
	fmt.Printf("个人信息:\n姓名:%s\n年龄:%d岁\n身高:%.1fcm\n", name, age, height)
}
相关推荐
Nyarlathotep01134 小时前
Go结构体字段定义
go
2301_816997887 小时前
Go语言开发环境搭建
go
2301_816997888 小时前
Go语言简介
golang·go
KeithChu1 天前
Go 语言中的 slice 类型
go
追随者永远是胜利者2 天前
(LeetCode-Hot100)253. 会议室 II
java·算法·leetcode·go
追随者永远是胜利者2 天前
(LeetCode-Hot100)207. 课程表
java·算法·leetcode·go
追随者永远是胜利者2 天前
(LeetCode-Hot100)169. 多数元素
java·算法·leetcode·go
追随者永远是胜利者2 天前
(LeetCode-Hot100)226. 翻转二叉树
java·算法·leetcode·职场和发展·go