Hello, World
Hello world
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
package main表明这是一个可独立执行的程序包;import "fmt"导入标准库中的fmt包,用于格式化输入输出 ;main函数是程序的入口,fmt.Println函数输出 “Hello, 世界”。
程序运行
go run命令:$ go run helloworld.go可直接编译、链接并运行.go源文件,输出 “Hello, 世界” 。go build命令:$ go build helloworld.go将生成名为helloworld的二进制程序,之后执行$./helloworld也能输出 “Hello, 世界” ,适用于创建可复用程序。
语言特性
Unicode 支持:Go 原生支持 Unicode,能处理各国语言。
包与导入:Go 代码通过包组织,
package声明文件所属包,import导入其他包。main包用于定义独立可执行程序,main函数是程序执行起点。导入包需精确,缺失或多余导入会致编译失败,import声明要在package声明之后 。函数声明:由
func关键字、函数名、参数列表、返回值列表(可空)和函数体组成。语法格式:Go 语言语句或声明后一般不用分号结尾(特殊情况除外);对代码格式化要求严格,可使用
gofmt工具格式化代码,goimports工具可按需管理导入声明。 还提到可通过go get命令获取goimports工具,多数 Go 操作可通过go工具实现。
命令行参数
程序常需处理输入产生输出,输入源多样,命令行参数是常见输入源之一 。Go 通过os包中的os.Args获取命令行参数,os.Args是字符串切片(slice) 。os.Args[0]是命令本身名字,os.Args[1:]是程序执行时传入参数 。
echo
package main
import (
"fmt"
"os"
"strings"
)
// echo1
func main() {
var s, sep string
for i := 1; i < len(os.Args); i++ {
s += sep + os.Args[i];
sep = " ";
}
fmt.Println(s);
}
导入
fmt和os包,在main函数中,通过for循环遍历os.Args[1:],使用+=操作符将参数追加到字符串s中,参数间用空格分隔,最后通过fmt.Println输出所有参数 。Go 语言中变量声明(
var s, sep string)、操作符(+=)以及for循环语法 。for循环有多种形式,for initialization; condition; post,初始化、条件判断和后置操作部分可省略,可实现无限循环等 。
// echo2
func main() {
s, sep := "", ""
for _, arg := range os.Args[1:] {
s += sep + arg;
sep = " ";
}
fmt.Println(s);
}
利用
range关键字遍历os.Args[1:],range会返回索引和对应元素值。当不需要索引时,可使用空白标识符_。短变量声明(如
s, sep := "", "")以及等价的变量声明方式 。
// echo3
func main() {
fmt.Println(strings.Join(os.Args[1:], " "));
}
- 使用
strings.Join函数,传入os.Args[1:]和分隔符" ",可将命令行参数以指定分隔符连接成字符串输出,更为简洁高效 。
找出重复行
// dup1
func main() {
counts := make(map[string]int)
input := bufio.NewScanner(os.Stdin)
for input.Scan() {
counts[input.Text()]++
}
for line, n := range counts {
if n > 1 {
fmt.Printf("%d\t%s\n", n, line)
}
}
}
- 功能:输出标准输入中出现次数大于 1 的行,前面是出现次数 。
- 实现:
- 导入
bufio、fmt、os包 。在main函数中,用make函数创建map[string]int类型的counts,用于统计每行出现次数。 - 通过
bufio.NewScanner(os.Stdin)创建扫描器input读取标准输入,for input.Scan()循环逐行读取,counts[input.Text()]++统计每行出现次数。 - 再通过
for line, n := range counts遍历counts,用if n > 1判断,若出现次数大于 1,使用fmt.Printf("%d\t%s\n", n, line)格式化输出次数和行内容 。
- 导入
- 相关知识:介绍了
if语句语法(条件部分不放在圆括号,程序体用大括号 );map数据结构(存储键值对,make新建,键类型任意可比较,值任意类型 );bufio.Scanner扫描器读取输入 ;fmt.Printf格式化输出(介绍常用占位符如%d、%s等 ) 。

// dup2
func main() {
counts := make(map[string]int)
files := os.Args[1:];
if len(files) == 0 {
countsLines(os.Stdin, counts)
} else {
for _, arg := range files {
f, err := os.Open(arg)
if err != nil {
fmt.Fprintf(os.Stderr, "dup2: %v\n", err)
continue
}
countsLines(f, counts)
f.Close()
}
}
for line, n := range counts {
if n > 1 {
fmt.Printf("%d\t%s\n", n, line)
}
}
}
func countsLines(f* os.File, counts map[string]int) {
input := bufio.NewScanner(f)
for input.Scan() {
counts[input.Text()]++
}
}
- 功能:可从标准输入或指定文件列表读取,打印输入中多次出现的行的个数和文本 。
- 实现:
- 同样导入相关包,创建
counts统计次数 。先判断命令行参数os.Args[1:],若为空,直接统计标准输入countLines(os.Stdin, counts)。 - 若有参数,遍历参数列表,用
os.Open打开文件,若打开失败用fmt.Fprintf(os.Stderr, "dup2: %v\n", err)输出错误信息并continue继续下一个文件 。打开成功则调用countLines(f, counts)统计行数,最后关闭文件f.Close()。countLines函数逻辑和dup1类似,通过扫描器逐行读取统计 。
- 同样导入相关包,创建
- 相关知识:介绍
os.Open返回文件指针和错误值,文件读取完需Close释放资源;简单错误处理方式 ;函数声明顺序可任意 ;map作为引用类型,函数中对其修改在调用处可见 。
// dup3
func main() {
counts := make(map[string]int)
for _, filename := range os.Args[1:] {
data, err := ioutil.ReadFile(filename)
if err != nil {
fmt.Fprintf(os.Stderr, "dup3: %v\n", err)
continue
}
for _, line := range strings.Split(string(data), "\n") {
counts[line]++
}
}
for line, n := range counts {
if n > 1 {
fmt.Printf("%d\t%s\n", n, line)
}
}
}
- 功能:从指定文件读取,统计重复行 。
- 实现:导包,遍历命令行参数
os.Args[1:],用ioutil.ReadFile读取文件内容到data,若失败输出错误信息并continue。成功读取后,用strings.Split(string(data), "\n")按行分割内容,再遍历分割后的行统计次数并输出重复行 。 - 相关知识:
ioutil.ReadFile读取文件内容为字节切片,可转换为字符串;strings.Split分割字符串 。
GIF动画
var palette = []color.Color{color.White, color.Black}
const (
whiteIndex = 0
blackIndex = 1
)
func main() {
rand.Seed(time.Now().UTC().UnixNano())
if len(os.Args) > 1 && os.Args[1] == "web" {
handler := func (w http.ResponseWriter, r *http.Request) {
lissajous(w)
}
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
return
}
lissajous(os.Stdout)
}
func lissajous(out io.Writer) {
const (
cycles = 5
res = 0.001
size = 100
nframes = 64
delay = 8
)
freq := rand.Float64() * 3.0
anim := gif.GIF{LoopCount: nframes}
phase := 0.0
for i := 0; i < nframes; i++ {
rect:= image.Rect(0, 0, 2*size + 1, 2*size + 1)
img := image.NewPaletted(rect, palette)
for t := 0.0; t < cycles*2*math.Pi; t += res {
x := math.Sin(t)
y := math.Sin(t*freq + phase)
img.SetColorIndex(size + int(x*size + 0.5), size + int(y*size + 0.5), blackIndex)
}
phase += 0.1
anim.Delay = append(anim.Delay, delay)
anim.Image = append(anim.Image, img)
}
gif.EncodeAll(out, &anim)
}
- 功能:利用 Go 标准图像包创建一系列位图图像,并编码为 GIF 动画,展示利萨茹图形(参数化的二维谐振曲线,类似示波器 x 轴和 y 轴馈电输入的两个正弦波图形 )。

实现:
- 常量与变量声明:
- 用
var声明palette切片存储颜色 ,包含白色和黑色 。 - 用
const声明whiteIndex和blackIndex常量,分别表示画板中白色和黑色的索引 。const用于给编译期间值固定的量命名,可在包级别或函数内声明,常量类型为数字、字符串或布尔值 。
- 用
- 结构体与复合字面量:
gif.GIF是结构体类型,anim变量是该结构体实例 。通过复合字面量gif.GIF{LoopCount: nframes}创建anim,并后续通过点记法显式更新其Delay和Image字段 。复合字面量是用一系列元素值初始化 Go 复合类型的紧凑表达方式 。
- 核心函数
lissajous:- 函数接受
io.Writer类型的out参数,用于输出 GIF 动画 。函数内通过一系列常量定义动画参数,如cycles(x 振荡器变化个数 )、res(角度分辨率 )、size(图像画布尺寸 )、nframes(动画帧数 )、delay(帧间延迟 )等 。 - 生成随机的 y 振荡器相对频率
freq,创建gif.GIF结构体anim并设置循环帧数 。通过两层嵌套循环,外层循环控制帧数,内层循环在每个帧内,基于正弦函数计算 x 和 y 坐标,设置图像对应坐标点颜色(白色或黑色 ),并将延迟和图像帧追加到anim中 ,最后用gif.EncodeAll将动画编码写入out。
- 函数接受
main函数:设置随机数种子,根据命令行参数判断是否作为 Web 服务运行(若参数为"web"),若为 Web 服务则注册处理函数并启动服务;否则直接调用lissajous函数将动画输出到标准输出 。
- 常量与变量声明:
使用:
go run .\main\lissajous.go web运行代码,浏览器中访问http://localhost:8000,就能看到生成的 Lissajous 曲线的 GIF 动画。
获取一个URL
func main() {
for _, url := range os.Args[1:] {
resp, err := http.Get(url)
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
os.Exit(1)
}
b, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err)
os.Exit(1)
}
fmt.Printf("%s", b)
}
}
功能:
fetch程序用于获取指定 URL 返回的 HTTP 内容,并将其输出,是从互联网获取信息的示例 。背景:Go 语言在
net包下提供系列工具处理网络信息,利用这些工具可通过互联网收发信息、创建服务器等,其并发特性在网络应用场景很有用 。实现:
通过
for _, url := range os.Args[1:]遍历命令行参数中传入的 URL 。对每个 URL,使用
http.Get(url)发起 HTTP 请求,若请求出错,通过fmt.Fprintf(os.Stderr, "fetch: %v\n", err)在标准错误输出打印错误信息,然后用os.Exit(1)以状态码 1 退出程序 。若请求成功,用
ioutil.ReadAll(resp.Body)读取响应体内容到b,读取完成后关闭响应体resp.Body.Close(),若读取过程出错,打印错误信息并退出 。最后用fmt.Printf("%s", b)将获取到的内容输出到标准输出 。
使用:
go run .\main\fetch.go https://example.com
<!doctype html>
<html>
<head>
<title>Example Domain</title>
// 省略...
</div>
</body>
</html>
并发获取多个URL
func main() {
start := time.Now()
ch := make(chan string)
for _, url := range os.Args[1:] {
go fetch(url, ch) // 启动 goroutine
}
for range os.Args[1:] {
fmt.Println(<-ch) // 从通道 ch 接收
}
fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds)
}
func fetch(url string, ch chan<- string) {
start := time.Now()
resp, err := http.Get(url)
if err != nil {
ch <- fmt.Sprint(err)
return
}
nbytes, err := io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
if err != nil {
ch <- fmt.Sprintf("while reading %s: %v", url, err)
return
}
secs := time.Since(start).Seconds()
ch <- fmt.Sprintf("%.2fs %7d %s", secs, nbytes, url)
}
功能:
fetchall程序并发获取多个 URL 的内容,报告每个响应的大小和花费时间,不处理响应具体内容 。背景:Go 语言支持并发编程,
fetchall程序简单展示了 Go 的并发机制—goroutine 和通道(channel) 。main函数逻辑:- 记录开始时间
start := time.Now(),创建字符串通道ch := make(chan string)。 - 通过
for _, url := range os.Args[1:]遍历命令行参数中的 URL ,对每个 URL 使用go fetch(url, ch)启动一个 goroutine 来处理 。 - 再通过
for range os.Args[1:]循环从通道ch接收信息并打印 ,最后打印总耗时fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())。
- 记录开始时间
fetch函数逻辑:- 记录每个 URL 请求开始时间
start := time.Now(),使用http.Get(url)发起 HTTP 请求 ,若出错将错误信息通过ch <- fmt.Sprint(err)发送到通道 。 - 若请求成功,用
io.Copy(ioutil.Discard, resp.Body)丢弃响应内容(获取字节数 ),关闭响应体resp.Body.Close(),若读取过程出错也将错误信息发送到通道 。 - 计算请求耗时
secs := time.Since(start).Seconds(),将耗时、字节数、URL 信息通过ch <- fmt.Sprintf("%.2fs %7d %s", secs, nbytes, url)发送到通道 。
- 记录每个 URL 请求开始时间
并发机制原理:
goroutine:是一种并发执行的函数,
main函数在一个 goroutine 中执行,go语句创建额外的 goroutine ,可异步执行任务 。通道(channel):用于在 goroutine 之间传递指定类型的值 ,起到同步和通信作用 。
fetch函数向通道发送信息,main函数从通道接收并处理信息,避免多个 goroutine 输出交织 。
使用:
go run .\main\fetchall.go https://baidu.com https://bilibili.com https://example.com
0.32s 1374 https://bilibili.com
0.47s 2381 https://baidu.com
0.74s 1256 https://example.com
%!f(func() float64=0x7e9900)s elapsed
一个 Web 服务器
// server1
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
}
- 功能:创建一个迷你 Web 服务器,返回客户端请求 URL 的路径部分 。
// server2
var mu sync.Mutex
var count int
func main() {
http.HandleFunc("/", handler)
http.HandleFunc("/count", counter)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
func handler(w http.ResponseWriter, r *http.Request) {
mu.Lock()
count++
mu.Unlock()
fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
}
func counter(w http.ResponseWriter, r *http.Request) {
mu.Lock()
fmt.Fprintf(w, "Count %d\n", count)
mu.Unlock()
}
- 功能:在
server1基础上扩展,除返回 URL 路径外,对特定的/count请求,返回当前为止请求的数量 。 handler函数:在处理请求时,先加锁mu.Lock(),计数count++,解锁mu.Unlock(),再返回 URL 路径 ;counter函数加锁后,通过fmt.Fprintf(w, "Count %d\n", count)返回请求计数 ,然后解锁 。引入互斥锁是为防止并发请求时计数变量count的竞争问题 。
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s %s %s\n", r.Method, r.URL, r.Proto)
for k, v := range r.Header {
fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
}
fmt.Fprintf(w, "Host = %q\n", r.Host)
fmt.Fprintf(w, "RemoteAddr = %q\n", r.RemoteAddr)
if err := r.ParseForm(); err != nil {
log.Print(err)
}
for k, v := range r.Form {
fmt.Fprintf(w, "Form[%q] = %q\n", k, v)
}
}
功能:处理程序回显 HTTP 请求,包括请求方法、URL、协议,以及请求头和表单数据等信息 。
使用:
go run .\main\server.go运行代码,浏览器中访问http://localhost:8000,就能对应的HTTP请求。
其他
switch coinflip() {
case "heads":
heads++
case "tails":
tails++;
default:
fmt.Println("landed on edge!")
}
switch {
case x > 0:
return +1
default:
return 0
case x < 0
return -1
}
switch语句:是多路分支控制语句。switch coinflip()根据coinflip函数返回值与case条件值比较,执行第一个匹配的case语句块,若都不匹配则执行default语句块 。switch语句无需操作数,可像布尔表达式列表,还有无标签(tagless)选择形式switch { }。switch可包含初始语句,如变量声明、递增赋值或函数调用等 。同时break和continue可改变控制流,break用于中断for、switch或select最内层,continue让for内层循环开始新迭代 ,还有goto语句,但一般不建议使用 。
命名类型:用
type声明为已有类型命名,如定义2D图形系统的Point类型type Point struct { X, Y int },可通过var p Point声明该类型变量 ,类型声明和命名 。指针:Go 语言提供指针,
&操作符获取变量地址,*操作符获取指针引用变量的值,但指针不支持算术运算 。方法:关联命名类型的函数,Go 中方法可关联几乎所有命名类型 。
接口:用于处理不同具体类型的抽象类型,基于类型包含的方法定义 。
包:Go 有可扩展实用的标准库,社区也有众多共享库。编程时优先使用现有包,可在
https://golang.org/pkg查找标准库索引,https://godoc.org找社区包,通过go doc工具可查看包文档 ,如$ go doc http.ListenAndServe。注释:在函数声明前写注释说明行为是好风格,可被
go doc和godoc工具识别展示 。多行注释可用类似/*...*/形式,注释内//和/*无特殊含义,且注释不能嵌套 。
Y int } ,可通过var p Point`声明该类型变量 ,类型声明和命名 。
指针:Go 语言提供指针,
&操作符获取变量地址,*操作符获取指针引用变量的值,但指针不支持算术运算 。方法:关联命名类型的函数,Go 中方法可关联几乎所有命名类型 。
接口:用于处理不同具体类型的抽象类型,基于类型包含的方法定义 。
包:Go 有可扩展实用的标准库,社区也有众多共享库。编程时优先使用现有包,可在
https://golang.org/pkg查找标准库索引,https://godoc.org找社区包,通过go doc工具可查看包文档 ,如$ go doc http.ListenAndServe。注释:在函数声明前写注释说明行为是好风格,可被
go doc和godoc工具识别展示 。多行注释可用类似/*...*/形式,注释内//和/*无特殊含义,且注释不能嵌套 。
参考资料:《Go程序设计语言》