CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Go 语言入门/课时/基础语法与类型
RawBlame
课时目录12 节

Go 语言入门

  1. 01Go 是什么
  2. 02基础语法与类型
  3. 03数组、切片与 map
  4. 04结构体与接口
  5. 05模块与包管理
  6. 06并发模型
  7. 07错误处理
  8. 08泛型入门
  9. 09I/O 与 CLI
  10. 10Web 与数据库
  11. 11测试与基准
  12. 12综合练习

基础语法与类型

variables、zero-values、const/iota、控制流、基本类型。

35 分钟

基础语法与类型

这一课你将能做到

  1. 用 var、:=、const 三种方式声明变量和常量,并说出每个类型的零值
  2. 熟练使用 if / for / switch 三种控制流,包括 Go 特有的「初始化语句」写法
  3. 编写带多返回值、命名返回值和可变参数的函数

为什么要学

任何程序都是「数据 + 流程」。这一课把 Go 的数据怎么声明、流程怎么控制一次讲透。Go 语法刻意做减法:循环只有 for 一种,分支只有 if 和 switch,没有 do-while,也没有三元运算符。花 35 分钟掌握这一课,就能读懂 90% 的 Go 控制流代码,这也是后面切片、接口、并发一切内容的地基。

从 Java/JS 转过来的初学者会踩三个坑:声明的变量不用会编译报错;类型转换必须显式写;i++ 是语句不是表达式。这三个点下面都会遇到。

先建立图像

package main

import "fmt"

const (
	StatusOK    = iota + 200 // 200,iota 是 const 块的行号,从 0 开始
	StatusFound              // 201,自动延续上一行的表达式
)

func main() {
	count := 10       // := 短声明,类型自动推断为 int
	var ratio float64 // 零值 0

	if count > 5 && ratio == 0 {
		fmt.Println("count 偏大,ratio 未设置")
	}

	for i := 0; i < count; i++ {
		if i%3 == 0 {
			continue
		}
	}

	switch n := count / 3; n {
	case 1, 2:
		fmt.Println("小")
	default:
		fmt.Println("大:", n)
	}

	fmt.Println(StatusOK, StatusFound) // 200 201
}

这一张图就是本课全部:声明、零值、if/for/switch、常量、iota。

跟着做

下面每步都可以放进同一个 go mod init example.com/syntax 的项目里运行。

步骤 1:变量声明的三种方式

package main

import "fmt"

func main() {
	// 方式一:var 完整声明,可批量
	var a int = 10
	var b, c = 20, "三十"

	// 方式二:var 省略类型,靠右值推断
	var d = 3.14

	// 方式三:短声明 :=,只能在函数内部使用
	e := "hello"

	fmt.Println(a, b, c, d, e)
}

规则:包级别只能用 var(:= 不可用),函数内推荐 :=。

步骤 2:零值——Go 没有「未初始化变量」

任何变量声明后不赋值,Go 自动给它确定的「零值」:

package main

import "fmt"

func main() {
	var i int                  // 0
	var f float64              // 0
	var s string               // ""(空串,不是 null!)
	var b bool                 // false
	var arr [3]int             // [0 0 0]
	var p *int                 // nil
	var sl []int               // nil(切片零值可直接 append)
	var m map[string]int       // nil(零值 map 只能读,写入会 panic)

	fmt.Println(i, f, b, arr, p, sl, m)
	fmt.Printf("%q\n", s) // "",用 %q 看清字符串边界
}

零值设计让很多类型「声明即可用」,不需要先初始化再使用。

步骤 3:基本类型一览

package main

import "fmt"

func main() {
	i := 42             // int:位宽随平台(64 位系统上是 64 位)
	i64 := int64(42)    // 明确位宽:int8/16/32/64
	f := 3.14           // float64 是默认浮点;没有叫 float 的类型
	s := "golang"       // 字符串:不可变的 UTF-8 字节序列
	r := '中'            // rune = int32 别名,表示一个 Unicode 码点
	b := true
	by := byte('A')     // byte = uint8 别名

	fmt.Printf("%T %T %T %T %T %T\n", i, i64, f, s, r, by)
	fmt.Println(b)
}

关键规矩:不同类型不能直接运算,必须显式转换。

var a int = 10
var b int64 = 20
// total := a + b    // 编译错误:mismatched types int and int64
total := a + int(b)  // 正确:显式转换
fmt.Println(total)

步骤 4:常量与 iota

package main

import "fmt"

type Weekday int // 给 int 起名,让枚举有类型含义

const (
	Sunday    Weekday = iota // 0
	Monday                   // 1,iota 每行自动 +1
	Tuesday                  // 2
	Wednesday
	Thursday
	Friday
	Saturday // 6
)

const (
	KB = 1 << (10 * (iota + 1)) // 1 << 10 = 1024
	MB                          // 1 << 20
	GB                          // 1 << 30
)

func main() {
	fmt.Println(Sunday, Wednesday, Saturday)
	fmt.Println(KB, MB, GB)
}

iota 是每个 const 块内的行号计数器,从 0 开始,每换一行 +1。常量必须是编译期可确定的值,不能依赖运行时算出来的变量。

步骤 5:if——可以带初始化语句

package main

import (
	"fmt"
	"strconv"
)

func main() {
	// n 和 err 的作用域仅限 if/else 块内——这是 Go 最常见的惯用法
	if n, err := strconv.Atoi("42"); err != nil {
		fmt.Println("解析失败:", err)
	} else {
		fmt.Println("解析成功:", n*2)
	}
}

条件不需要括号,但左大括号必须同行。

步骤 6:for——唯一的循环

package main

import "fmt"

func main() {
	// 形态一:经典三段式
	for i := 0; i < 3; i++ {
		fmt.Print(i, " ")
	}
	fmt.Println()

	// 形态二:只有条件,替代 while
	n := 0
	for n < 3 {
		n++
	}

	// 形态三:无限循环,配合 break 退出
	count := 0
	for {
		count++
		if count >= 5 {
			break
		}
	}
	fmt.Println(n, count)
}

遍历切片和 map 的 for range 形态在第 3 课讲。

步骤 7:switch——默认自动 break

package main

import (
	"fmt"
	"time"
)

func main() {
	// 带初始化语句、省略条件表达式:相当于 switch true
	switch h := time.Now().Hour(); {
	case h < 12:
		fmt.Println("上午")
	case h < 18:
		fmt.Println("下午")
	default:
		fmt.Println("晚上")
	}

	// case 可以并列多个值;需要穿透时显式写 fallthrough(慎用)
	switch 2 {
	case 1, 2, 3:
		fmt.Println("命中 1/2/3")
		fallthrough // 无条件进入下一个 case,不再判断它的条件
	case 4:
		fmt.Println("被 fallthrough 拖进来")
	}
}

「初始化 + 空条件」的 switch 是比 if-else 长链更清晰的写法,Go 项目里非常常见。

步骤 8:函数——多返回值是 Go 的标志

package main

import (
	"errors"
	"fmt"
)

// 多返回值:结果 + 错误,这是 Go 函数的标准签名
func divide(a, b float64) (float64, error) {
	if b == 0 {
		return 0, errors.New("除数不能为零")
	}
	return a / b, nil
}

// 命名返回值:直接给返回变量赋值,裸 return 自动返回
func swap(a, b int) (x, y int) {
	x, y = b, a
	return
}

// 可变参数:nums 在函数内是 []int
func sum(nums ...int) int {
	total := 0
	for _, n := range nums {
		total += n
	}
	return total
}

func main() {
	q, err := divide(10, 3)
	fmt.Println(q, err)

	_, err2 := divide(1, 0)
	fmt.Println(err2)

	fmt.Println(swap(1, 2))
	fmt.Println(sum(1, 2, 3))

	nums := []int{4, 5, 6}
	fmt.Println(sum(nums...)) // 切片展开为可变参数
}

讲清楚要点

  1. 未使用的局部变量是编译错误,未使用的 import 同样报错。这是 Go 强制「代码即真实」的手段;显式丢弃用空白标识符 _。
  2. := 是声明 + 赋值,只能用在函数内;= 是纯赋值。同一作用域再写 := 时,左侧至少要有一个新变量。
  3. 没有隐式类型转换:int 与 int64、int 与 float64 混算都必须手动转,编译器绝不替你猜,杜绝了隐式截断。
  4. 循环只有 for;i++ 是语句不是表达式,不能写 x = i++。
  5. switch 自动 break,case 值可以逗号并列;fallthrough 很少用,出现时应能给出充分理由。
  6. (T, error) 是 Go 的标准函数签名:出错时 T 返回零值、error 非 nil;成功时 error 为 nil。

你可能会懵的地方

  • int 到底是多少位?和 int64 一样吗? int 的位宽跟随平台(64 位机上是 64 位),但它是独立类型,与 int64 混算仍需转换。日常优先用 int;只有序列化、数据库字段等需要明确位宽时用 int64。

  • rune 和 byte 有什么区别? byte 是 uint8(1 字节),rune 是 int32(1 个 Unicode 码点)。len("中") 是 3(3 个字节),len([]rune("中")) 才是 1(1 个字符)。字符串细节在第 3 课展开。

  • 为什么 Go 没有三元运算符 ?:? 官方理由:三元表达式容易嵌套成天书。Go 宁愿多写三行 if-else,也要保证扫一眼就懂。

  • 常量为什么必须是编译期确定的? 常量在编译阶段就替换进代码,不是存在内存里的运行时实体。运行时才算得出的值请用变量。

  • iota 能跳号或参与运算吗? 能。中间插一行 _ = iota 即可跳号;表达式如 iota * 10、1 << iota 会自动被后续行复用,只改 iota 值。

错法对照

错法问题正解
声明 x := 5 后不使用declared and not used 编译错误用掉、删除,或用 _ 显式丢弃
包级变量写 x := 10:= 不能在函数外用包级用 var x = 10
{ 换到下一行自动分号导致语法错误左大括号必须与语句同行
var b float64 = a(a 是 int)无隐式转换,编译错误float64(a) 显式转换
期望 switch case 顺序执行默认自动 break,逻辑不执行需要穿透显式写 fallthrough
int 与 int64 直接相加mismatched types转换为同一类型再运算

卡点急救

  1. declared and not used:删除变量、用 _ 接收,或真的使用它;也检查是否把 = 误写成 := 造成重复声明。
  2. cannot use ... as ...(类型不匹配):做显式类型转换,把操作数统一成同一类型。
  3. undefined: xxx:拼写错误,或变量定义在 if/switch 的初始化语句里、出了块就失效。

面试常问

  • Go 的零值机制有什么好处? 任何类型都有确定的零值,没有「未初始化」状态,杜绝了读野值和空指针这类问题;var mu sync.Mutex、var buf bytes.Buffer 声明即可用,代码更简洁。

  • := 和 var 的区别与选择? := 短声明仅限函数内,要求左侧至少一个新变量;包级必须 var 或 const。惯例:函数内用 :=,需要显式类型或包级用 var。

  • iota 的原理和典型用途? const 块内的行号计数器,从 0 起每行自增,可被表达式复用。典型用途是类型化枚举和位掩码(1 << iota),如文件权限、日志级别定义。

  • 为什么 Go 强制显式类型转换? 把类型边界写进代码,避免隐式截断/溢出;也让评审时数据流向一目了然。

自测三问

  1. string、切片、map 的零值分别是什么?向零值 map 写入会发生什么?
  2. 写出 for 的三种形态,Go 用哪种形态替代 while?
  3. divide 返回 (float64, error),为什么出错时要返回零值而不是 -1 这类魔数?

小结一句话

Go 用「零值保证 + 显式转换 + 单一 for + 多返回值 error」四件套,把最容易出事故的角落提前焊死:语法小,但约束严。

延伸

  • Effective Go:控制结构
  • Go 语言规范:常量
  • 下一课:数组、切片与 map——Go 最常用的容器,切片底层结构是面试超高频题。
加载本节练习…
← 上一课Go 是什么下一课 →数组、切片与 map