Go:入门

发布于:2025-04-12 ⋅ 阅读:(42) ⋅ 点赞:(0)

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);
}
  • 导入fmtos包,在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 的行,前面是出现次数 。
  • 实现
    • 导入bufiofmtos包 。在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等 ) 。

image.png

// 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 轴馈电输入的两个正弦波图形 )。

image.png

  • 实现

    • 常量与变量声明
      • var声明palette切片存储颜色 ,包含白色和黑色 。
      • const声明whiteIndexblackIndex常量,分别表示画板中白色和黑色的索引 。const用于给编译期间值固定的量命名,可在包级别或函数内声明,常量类型为数字、字符串或布尔值 。
    • 结构体与复合字面量
      • gif.GIF是结构体类型,anim变量是该结构体实例 。通过复合字面量gif.GIF{LoopCount: nframes}创建anim ,并后续通过点记法显式更新其DelayImage字段 。复合字面量是用一系列元素值初始化 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)发送到通道 。
  • 并发机制原理

    • 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可包含初始语句,如变量声明、递增赋值或函数调用等 。同时breakcontinue可改变控制流,break用于中断forswitchselect最内层,continuefor内层循环开始新迭代 ,还有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 docgodoc工具识别展示 。多行注释可用类似/*...*/形式,注释内///*无特殊含义,且注释不能嵌套 。

Y int } ,可通过var p Point`声明该类型变量 ,类型声明和命名 。

  • 指针:Go 语言提供指针,&操作符获取变量地址,*操作符获取指针引用变量的值,但指针不支持算术运算 。

  • 方法:关联命名类型的函数,Go 中方法可关联几乎所有命名类型 。

  • 接口:用于处理不同具体类型的抽象类型,基于类型包含的方法定义 。

  • :Go 有可扩展实用的标准库,社区也有众多共享库。编程时优先使用现有包,可在https://golang.org/pkg查找标准库索引,https://godoc.org找社区包,通过go doc工具可查看包文档 ,如$ go doc http.ListenAndServe

  • 注释:在函数声明前写注释说明行为是好风格,可被go docgodoc工具识别展示 。多行注释可用类似/*...*/形式,注释内///*无特殊含义,且注释不能嵌套 。

参考资料:《Go程序设计语言》


网站公告

今日签到

点亮在社区的每一天
去签到