☰
Go 语言基础知识精讲:掌握 25 个关键字,打通 Web 应用开发的第一课
2026/10/7 20:36:04 网站建设 项目流程
  • 文档
  • 教程

【免费下载链接】build-web-application-with-golang

A golang ebook intro how to build a web with golang

项目地址:https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang
点击查看免费下载

本篇指南以本仓库(build-web-application-with-golang,一本教你用 Go 构建 Web 应用的电子书)**第 2 章「Go basic knowledge」**为骨架,系统讲解 Go 语言的全部 25 个关键字及其背后的核心语法:变量与常量、包与函数、流程控制、struct 与面向对象、接口、并发等。读完本文,你将掌握这份关键字清单的每个用法,并能在 th/code/src/apps 目录下找到与各小节一一对应的可运行示例代码,为后续章节(Web 基础、表单、数据库、Web 框架开发)打下扎实的语法基础。

25 个关键字:比英文字母还少的完整语法集

Go 是一门编译型系统编程语言,属于 C 语言家族,但它的编译速度远快于同族的其他语言。最令人印象深刻的事实是:Go 的全部关键字只有 25 个——比英文字母表的 26 个字母还要少一个。这正是 Go 语言"简洁"设计哲学的起点。

在开始编码之前,先记住这份完整的清单(摘自 th/02.0.md):

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

这些关键字覆盖了 Go 的全部语法维度。本仓库第 2 章共分 8 个小节(Hello, Go、Go foundation、Control statements and functions、struct、Object-oriented、interface、Concurrency、Summary)逐一展开讲解。下面我们按功能分组,逐个吃透它们。

变量与常量:var、const

定义变量的多种写法

Go 中var是定义变量的基本形式,注意类型放在变量名之后:

// 定义名为 variableName、类型为 type 的变量 var variableName type // 同时定义多个同类型变量 var vname1, vname2, vname3 type // 定义并初始化 var variableName type = value var vname1, vname2, vname3 type = v1, v2, v3

如果带初始值,可以省略类型,让编译器自行推断;更简洁的是用:=短变量声明,直接替代var和类型:

var vname1, vname2, vname3 = v1, v2, v3 // 省略类型 vname1, vname2, vname3 := v1, v2, v3 // 短变量声明

需要特别注意的是::=只能在函数体内使用,在函数体外定义全局变量仍须使用var。

_(下划线)是一个特殊的变量名,赋给它的任何值都会被忽略。它最常见的用途是丢弃函数的多返回值中不需要的部分:

_, b := 34, 35 // 34 被丢弃

Go 编译器对"定义了却未使用"的变量会直接报编译错误,例如下面这段代码就编译不过:

package main func main() { var i int }

常量的定义

常量在编译期确定、运行期不可更改,可用数字、布尔值或字符串作为常量类型:

const constantName = value const Pi float32 = 3.1415926 // 必要时可显式指定类型 // 更多示例 const Pi = 3.1415926 const i = 10000 const MaxThread = 10 const prefix = "astaxie_"

iota是配合const实现枚举的关键字,从 0 开始、逐行递增:

const( x = iota // x == 0 y = iota // y == 1 z = iota // z == 2 w // 省略表达式时沿用上一行的表达式,故 w == 3 ) const v = iota // 遇到新的 const 关键字,iota 重置为 0,所以 v == 0 const ( e, f, g = iota, iota, iota // 同一行内 iota 值相同,e == f == g == 0 )

分组定义与可见性规则

多个变量、常量或 import 可以用分组形式一次性定义:

import( "fmt" "os" ) const( i = 100 pi = 3.1415 prefix = "Go_" ) var( i int pi float32 prefix string )

Go 没有public/private关键字,可见性由命名决定:首字母大写即导出(公开),小写则为私有——变量、函数、常量均遵循此规则。这一规则在 th/02.2.md 中有明确说明。

包与导入:package、import

Go 程序由包(package)组成。每个源码文件第一行用package <pkgName>声明它属于哪个包;main包会被编译成可执行程序,而其他包会编译成.a归档文件。

导入包有三种特殊写法(详见 th/02.3.md):

import ( . "fmt" // 点操作:调用时省略包名,fmt.Printf 变成 Printf f "fmt" // 别名操作:f.Printf("Hello world") _ "github.com/ziutek/mymysql/godrv" // 下划线:只执行该包的 init 函数 )

第三种的_操作常被初学者忽略:它表示"我只需要这个包执行init函数(比如注册数据库驱动),并不直接调用它的函数"。Go 支持两种导入路径:相对路径("./model",不推荐)和绝对路径("shorturl/model",对应$GOPATH/pkg/shorturl/model)。

函数与流程控制:func、return、defer、if、else、for、range、switch、case、break、continue、fallthrough、goto、default

函数定义与多返回值

用func关键字定义函数,参数类型跟在参数名之后,返回值支持多值返回——这是 Go 比 C 更强的地方:

// 两个返回值都不命名 func SumAndProduct(A, B int) (int, int) { return A + B, A * B } // 返回值命名后,只需 return 即可自动返回 func SumAndProduct(A, B int) (add int, multiplied int) { add = A + B multiplied = A * B return }

变参函数使用...表示不定数量参数,在函数体内arg表现为一个slice:

func myfunc(arg ...int) {} for _, n := range arg { fmt.Printf("And the number is: %d\n", n) }

函数默认按值传递(拿到的是实参副本,修改不影响原变量);若要修改原变量,需传入指针*int(传&x):

func add1(a *int) int { *a = *a + 1 // 通过指针修改原变量 return *a } x1 := add1(&x) // 传 x 的内存地址

流程控制四件套

  • if:不需要括号,但支持在条件前附带一条初始化语句,该语句定义的变量仅在 if 块内可见:
if x := computedValue(); x > 10 { fmt.Println("x is greater than 10") } else { fmt.Println("x is less than 10") } // fmt.Println(x) // 编译错误:x 超出作用域
  • for:Go 唯一的循环关键字,等价于其他语言的while;与range配合可遍历array、slice、map和string:
sum := 1 for sum < 1000 { // 省略分号后与 while 完全一致 sum += sum } for _, v := range map { // 用 _ 丢弃不需要的 key fmt.Println("map's val:", v) } for { // 无限循环,等价于 while true }
  • switch:条件不必是常量,从上到下匹配第一个命中项即跳出,无需写 break;多个值可写在同一case;如需继续向下匹配,使用fallthrough:
integer := 6 switch integer { case 4: fmt.Println("integer <= 4") fallthrough case 5: fmt.Println("integer <= 5") fallthrough case 6: fmt.Println("integer <= 6") fallthrough case 7: fmt.Println("integer <= 7") fallthrough case 8: fmt.Println("integer <= 8") fallthrough default: fmt.Println("default case") } // 输出:integer <= 6、integer <= 7、integer <= 8、default case
  • goto 与标签:goto跳转到同代码块内预先定义的标签,标签名区分大小写:
func myFunc() { i := 0 Here: // 标签以冒号结尾 fmt.Println(i) i++ goto Here }

defer:延迟执行的资源管理利器

defer保证函数返回前执行注册的操作(常用来关闭文件、解锁资源),多个defer按逆序执行。对比一下:传统写法要在每个错误分支重复file.Close(),用defer后只需一行:

func ReadWrite() bool { file.Open("file") defer file.Close() if failureX { return false } if failureY { return false } return true }

自定义类型与面向对象:struct、type、interface、map

struct 与内嵌字段

struct用于定义包含多个字段的自定义类型。初始化有三种方式(按顺序、按field:value无序、匿名 struct):

type person struct { name string age int } P := person{"Tom", 25} P := person{age: 24, name: "Bob"} P := struct{name string; age int}{"Amy", 18}

struct 支持内嵌字段(embedded fields):被内嵌 struct 的所有字段会隐式提升为外层 struct 的字段;同名冲突时外层字段优先(可视为字段重载):

type Human struct { name string age int phone string } type Employee struct { Human specialty string phone string // 与 Human.phone 同名,访问 Bob.phone 时取 Employee 的 }

所有类型(包括自定义的 slice 类型、内置类型)都可以作为内嵌字段,完整示例见 th/02.4.md。

方法:带接收者的函数

方法是"隶属于某个类型"的函数,语法为func (r ReceiverType) funcName(parameters) (results)。Rob Pike 的定义是:"方法是一个带有隐式第一参数(接收者)的函数。"同一个方法名可以属于不同的接收者类型:

type Circle struct { radius float64 } type Rectangle struct { width, height float64 } func (c Circle) Area() float64 { return c.radius * c.radius * math.Pi } func (r Rectangle) Area() float64 { return r.width * r.height }

接收者可以是值或指针(*Box)。指针接收者能修改原对象:SetColor若用值接收者,只会在副本上修改。此外,内嵌 struct 的方法会被外层 struct 继承;在外层定义同名方法即可实现方法重写(overriding),见 th/02.5.md。

接口:一组方法的集合

接口定义一组方法签名,任何实现了这些方法的类型即"实现了该接口";一个类型可以同时实现多个接口。空接口interface{}没有任何方法,因此所有类型都实现了它——它类似于 C 的void*,可用于存储任意类型。

判断接口中实际存储的具体类型有两种方式:

  • comma-ok 断言:value, ok := element.(T),ok为 true 表示类型符合预期;
  • switch 类型测试:switch value := element.(type),注意element.(type)只能用在 switch 体内。

接口还可以内嵌,例如container/heap包中的Interface内嵌了sort.Interface,从而自动拥有Len/Less/Swap三个方法;io.ReadWriter内嵌Reader与Writer。更多细节与reflect反射基础(reflect.TypeOf、reflect.ValueOf、通过Elem()修改值)见 th/02.6.md。

map:内置的字典

map类似 Python 的字典,用map[keyType]valueType定义,key 可以是任何能用==/!=比较的类型。它是无序的、长度不固定、引用类型:

numbers := make(map[string]int) numbers["one"] = 1 numbers["ten"] = 10 rating := map[string]float32{"C": 5, "Go": 4.5} csharpRating, ok := rating["C#"] // ok 判断 key 是否存在 if ok { fmt.Println("C# is in the map and its rating is ", csharpRating) } delete(rating, "C") // 删除元素

make 与 new 的区别

  • new(T):为类型分配零值内存,返回指向该零值的指针*T;
  • make(T, args):仅用于slice、map、channel这三种内置引用类型,初始化其底层数据结构并返回非零值(因为这三种类型的底层数据必须初始化后才能使用)。

Go 的零值(zero-value)并不等于"空值",而是变量声明后的默认值:int为 0、float32/64为 0、bool为 false、string为""、byte为 0x0 等,完整零值表见 th/02.2.md。

并发:go、chan、select

Go 被称为"21 世纪的 C",一个重要原因就是语言层面的并发支持。

goroutine:轻量级线程

用go关键字即可启动一个新的 goroutine(底层是一个函数;main()本身也是一个 goroutine)。每个 goroutine 通常只占用 4~5 KB 栈内存,单台机器轻松运行成千上万个:

package main import ( "fmt" "runtime" ) func say(s string) { for i := 0; i < 5; i++ { runtime.Gosched() fmt.Println(s) } } func main() { go say("world") // 创建新 goroutine say("hello") // 当前 goroutine }

runtime.Gosched()让出 CPU,使其他 goroutine 得以执行。在 Go 1.5 之后,GOMAXPROCS默认等于 CPU 核心数;更早版本默认只用单线程调度所有 goroutine(仅并发、非并行),需手动调用runtime.GOMAXPROCS(n)启用多核并行。完整示例在 th/code/src/apps/ch.2.7/goroutine/main.go。

channel:goroutine 之间的通信管道

goroutine 共享同一内存地址空间,需要同步机制。channel是 Go 内置的双向管道,用make创建,用<-收发数据:

ci := make(chan int) cs := make(chan string) cf := make(chan interface{}) ch <- v // 发送 v 到 channel ch v := <-ch // 从 ch 接收数据并赋给 v

无缓冲 channel 的收发默认阻塞,天然实现同步。channel还支持缓冲(make(chan bool, 4))和range遍历(需用close关闭后循环才会结束);生产者关闭 channel 更安全,否则容易触发 panic。

select:多 channel 监听

select默认阻塞,直到某个 channel 就绪才继续;多个 channel 同时就绪时随机选择执行。它也支持default分支(所有 channel 都未就绪时立即执行),可用于实现超时控制:

select { case v := <-c: println(v) case <-time.After(5 * time.Second): println("timeout") o <- true break }

runtime 包中的 goroutine 工具函数

函数作用
runtime.Goexit()退出当前 goroutine,但 defer 函数照常执行
runtime.Gosched()让调度器执行其他 goroutine,之后回来
runtime.NumCPU()返回 CPU 核心数
runtime.NumGoroutine()返回当前 goroutine 数量
runtime.GOMAXPROCS(n)设置使用的 CPU 核心数

基础类型速览:从布尔到复数

  • 布尔:bool,值只能为true/false,默认值为false,且不能与数值类型互相转换;
  • 整数:int/uint长度取决于操作系统(32 位系统为 32 位、64 位系统为 64 位);另有定长类型int8…int64、uint8…uint64,其中rune是int32的别名、byte是uint8的别名。不同整数类型之间不能直接赋值或运算(即使int32与int也会编译错误);
  • 浮点:只有float32和float64,没有float类型,短声明时默认float64;
  • 复数:complex128(默认,64 位实部 + 64 位虚部)与complex64,形如RE+IMi,例如var c complex64 = 5+5i;
  • 字符串:用双引号""或反引号``表示。字符串不能按下标修改,但可以转换为[]byte修改后再转回,或用+拼接、用切片取值;反引号字符串不转义任何字符,可跨行:
s := "hello" c := []byte(s) c[0] = 'c' s2 := string(c) m := `hello world` // 多行字符串
  • 错误:内置error类型,配合errors包处理错误信息:err := errors.New("...")。

数组、切片与内存模型

  • array:长度是类型的一部分,[3]int与[4]int是不同类型;作为函数参数时传递的是副本而非引用;可用[...]int{4,5,6}让编译器推算长度;
  • slice:引用类型,指向底层 array,由"起始指针 + 长度 + 容量"三部分组成;用array[i:j]切片(含 i 不含 j),ar[:]表示整个数组;len取长度、cap取容量、append追加元素、copy复制元素。注意:append会修改 slice 指向的底层数组,进而影响指向同一数组的其他 slice;当(cap-len) == 0时append会分配新数组,旧数组不再受影响;
  • map 是引用类型:两个 map 指向同一底层数据时,任一方的修改都会影响另一方。

这些基础类型的详细示例与图示(含底层内存结构图)请参阅 th/02.2.md,对应可运行代码在 th/code/src/apps/ch.2.2/main.go。

程序的初始化顺序:init与main

每个包都可以有可选的init函数,main包必须有且仅有一个main函数;两者都不能有参数和返回值。程序初始化流程为:先初始化main包导入的所有包(每个包只会被编译一次,按"常量 → 变量 → init 函数"的顺序),全部就绪后再初始化main包自身的常量、变量与init函数,最后执行main()。流程细节见 th/02.3.md。

从 Hello, Go 开始你的第一个程序

按照惯例,我们从打印 "Hello world" 开始。注意 Go 默认支持 UTF-8,你可以在代码中直接使用任何语言的字符:

package main import "fmt" func main() { fmt.Printf("Hello, world or 你好,世界 or Καλημέρα κόσμε or こんにちは世界\n") }

逐行拆解(详见 th/02.1.md):

  • package main:声明本文件属于main包——每个可执行程序有且仅有一个main包;
  • import "fmt":导入提供Printf等格式化输出函数的fmt包,调用语法为<pkgName>.<funcName>;
  • func main():程序入口函数,不能有参数和返回值,函数体用{}包裹(与 C、C++、Java 一致);
  • 第 6 行fmt.Printf(...):包名来自package声明而非文件夹名。

两个容易踩坑的细节:

  1. main 包不需要main文件夹,但每个可执行程序中只能有一个包含main函数的 Go 文件;
  2. Go 代码不需要也不应写分号——编译器在编译时自动补分号。正因如此,左花括号必须与函数名同行:
func main () { // 编译错误!编译器在 main() 后自动补了分号 }

对应示例代码位于 th/code/src/apps/ch.2.1/main.go,编译运行方式:在源码目录执行go build生成二进制,再从应用根目录运行该二进制(若程序依赖静态文件等资源,务必从根目录启动,否则可能遇到"程序找不到资源"的问题)。

回顾:25 个关键字的完整职能表

本仓库 th/02.8.md(第 2 章总结)对全部关键字给出了官方归类:

  • var、const:定义变量与常量;
  • package、import:包的使用;
  • func:定义函数与方法;
  • return:函数/方法返回值;
  • defer:定义延迟执行的函数;
  • go:启动新的 goroutine;
  • select:在多个 channel 间切换通信;
  • interface:定义接口;
  • struct:定义特殊自定义类型;
  • break、case、continue、for、fallthrough、else、if、switch、goto、default:流程控制(第 2.3 节);
  • chan:goroutine 间通信的 channel 类型;
  • type:定义自定义类型;
  • map:定义类似哈希表的映射;
  • range:从slice、map、channel读取数据。

配套代码与继续深入

第 2 章每一节的示例代码都整理在仓库的 th/code/src/apps 目录下,按ch.2.x分目录组织,例如:

  • ch.2.1/main.go(Hello, Go)
  • ch.2.2/main.go(基础类型与数组/切片/map)
  • ch.2.3/main.go 与 basic_functions、variadic_functions(流程控制与函数)
  • ch.2.4/main.go 与 compare_age(struct 与内嵌字段)
  • ch.2.5(box_example、embedded_method、method_overload 三个子示例,方法/指针接收者/继承/重写)
  • ch.2.6(interface、type_check、switch_type_check,接口与类型断言)
  • ch.2.7/goroutine/main.go(并发)

掌握这 25 个关键字,就等于掌握了 Go 语法的全部骨架。下一章 Web foundation 将以此为基础,讲解 Web 工作原理与用 Go 搭建第一个 Web 服务器——届时你会发现,这些语法知识会以net/http包的形式被反复运用。

  • 文档
  • 教程

【免费下载链接】build-web-application-with-golang

A golang ebook intro how to build a web with golang

项目地址:https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询