- 文档
- 教程
【免费下载链接】build-web-application-with-golang
A golang ebook intro how to build a web 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声明而非文件夹名。
两个容易踩坑的细节:
- main 包不需要
main文件夹,但每个可执行程序中只能有一个包含main函数的 Go 文件; - 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
相关推荐
Go 语言 25 个关键字全解析:掌握 build-web-application-with-golang 第 2 章的基础知识
Go 语言 25 个关键字全解析:掌握 build web application with golang 第 2 章的基础知识 本篇文章围绕开源 Go 语言电
文档教程Go 语言基础篇:25 个关键字与《用 Go 构建 Web 应用》第二章学习路线
Go 语言基础篇:25 个关键字与《用 Go 构建 Web 应用》第二章学习路线 导读 :本文面向《用 Go 构建 Web 应用(build web appli
文档教程Go 语言 25 个关键字全解:《build-web-application-with-golang》第 2 章小结精讲
Go 语言 25 个关键字全解:《build web application with golang》第 2 章小结精讲 本文对应开源 Go 教程 《build
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考