前几天刚把 Go 入门指南看了一下,所以趁热打铁,再来加深一下印象。
go 中 Printf 函数常用转义字符表:
verb 描述
%d 十进制整数
%x 十六进制
%o 八进制
%b 二进制
%f 浮点数:如 3.141593
%g 浮点数:如 3.141592653589793
%e 浮点数:如 3.141593e+00
%t 布尔型:true 或 false
%c 字符
%s 字符串
%q 带引号的字符串(如 "abc")或者字符(如 'c')
%v 内置格式的任何值
%T 任何值的类型
%% 百分号本身
先来个Go语言实战的示例程序(这个示例程序包含了 go 中常见的大部分知识,也展示了 go 代码的组织风格),目录结构如下:
.
├── data
│ └── data.json
├── go.mod
├── main.go
├── matchers
│ └── rss.go
└── search
├── default.go
├── feed.go
├── match.go
└── search.go
package main
import (
"log"
"os"
_ "github.com/goinaction/code/chapter2/sample/matchers"
"github.com/goinaction/code/chapter2/sample/search"
)
// init is called prior to main.
func init() {
// Change the device for logging to stdout.
log.SetOutput(os.Stdout)
}
// main is the entry point for the program.
func main() {
// Perform the search for the specified term.
search.Run("president")
}
module github.com/goinaction/code/chapter2/sample
replace github.com/goinaction/code/chapter2/sample => ./
go 1.17
package matchers
import (
"encoding/xml"
"errors"
"fmt"
"log"
"net/http"
"regexp"
"github.com/goinaction/code/chapter2/sample/search"
)
type (
// item defines the fields associated with the item tag
// in the rss document.
item struct {
XMLName xml.Name `xml:"item"`
PubDate string `xml:"pubDate"`
Title string `xml:"title"`
Description string `xml:"description"`
Link string `xml:"link"`
GUID string `xml:"guid"`
GeoRssPoint string `xml:"georss:point"`
}
// image defines the fields associated with the image tag
// in the rss document.
image struct {
XMLName xml.Name `xml:"image"`
URL string `xml:"url"`
Title string `xml:"title"`
Link string `xml:"link"`
}
// channel defines the fields associated with the channel tag
// in the rss document.
channel struct {
XMLName xml.Name `xml:"channel"`
Title string `xml:"title"`
Description string `xml:"description"`
Link string `xml:"link"`
PubDate string `xml:"pubDate"`
LastBuildDate string `xml:"lastBuildDate"`
TTL string `xml:"ttl"`
Language string `xml:"language"`
ManagingEditor string `xml:"managingEditor"`
WebMaster string `xml:"webMaster"`
Image image `xml:"image"`
Item []item `xml:"item"`
}
// rssDocument defines the fields associated with the rss document.
rssDocument struct {
XMLName xml.Name `xml:"rss"`
Channel channel `xml:"channel"`
}
)
// rssMatcher implements the Matcher interface.
type rssMatcher struct{}
// init registers the matcher with the program.
func init() {
var matcher rssMatcher
search.Register("rss", matcher)
}
// Search looks at the document for the specified search term.
func (m rssMatcher) Search(feed *search.Feed, searchTerm string) ([]*search.Result, error) {
var results []*search.Result
log.Printf("Search Feed Type[%s] Site[%s] For URI[%s]\n", feed.Type, feed.Name, feed.URI)
// Retrieve the data to search.
document, err := m.retrieve(feed)
if err != nil {
return nil, err
}
for _, channelItem := range document.Channel.Item {
// Check the title for the search term.
matched, err := regexp.MatchString(searchTerm, channelItem.Title)
if err != nil {
return nil, err
}
// If we found a match save the result.
if matched {
results = append(results, &search.Result{
Field: "Title",
Content: channelItem.Title,
})
}
// Check the description for the search term.
matched, err = regexp.MatchString(searchTerm, channelItem.Description)
if err != nil {
return nil, err
}
// If we found a match save the result.
if matched {
results = append(results, &search.Result{
Field: "Description",
Content: channelItem.Description,
})
}
}
return results, nil
}
// retrieve performs a HTTP Get request for the rss feed and decodes the results.
func (m rssMatcher) retrieve(feed *search.Feed) (*rssDocument, error) {
if feed.URI == "" {
return nil, errors.New("No rss feed uri provided")
}
// Retrieve the rss feed document from the web.
resp, err := http.Get(feed.URI)
if err != nil {
return nil, err
}
// Close the response once we return from the function.
defer resp.Body.Close()
// Check the status code for a 200 so we know we have received a
// proper response.
if resp.StatusCode != 200 {
return nil, fmt.Errorf("HTTP Response Error %d\n", resp.StatusCode)
}
// Decode the rss feed document into our struct type.
// We don't need to check for errors, the caller can do this.
var document rssDocument
err = xml.NewDecoder(resp.Body).Decode(&document)
return &document, err
}
package search
// defaultMatcher implements the default matcher.
type defaultMatcher struct{}
// init registers the default matcher with the program.
func init() {
var matcher defaultMatcher
Register("default", matcher)
}
// Search implements the behavior for the default matcher.
func (m defaultMatcher) Search(feed *Feed, searchTerm string) ([]*Result, error) {
return nil, nil
}
package search
import (
"encoding/json"
"os"
)
const dataFile = "data/data.json"
// Feed contains information we need to process a feed.
type Feed struct {
Name string `json:"site"`
URI string `json:"link"`
Type string `json:"type"`
}
// RetrieveFeeds reads and unmarshals the feed data file.
func RetrieveFeeds() ([]*Feed, error) {
// Open the file.
file, err := os.Open(dataFile)
if err != nil {
return nil, err
}
// Schedule the file to be closed once
// the function returns.
defer file.Close()
// Decode the file into a slice of pointers
// to Feed values.
var feeds []*Feed
err = json.NewDecoder(file).Decode(&feeds)
// We don't need to check for errors, the caller can do this.
return feeds, err
}
package search
import (
"log"
)
// Result contains the result of a search.
type Result struct {
Field string
Content string
}
// Matcher defines the behavior required by types that want
// to implement a new search type.
type Matcher interface {
Search(feed *Feed, searchTerm string) ([]*Result, error)
}
// Match is launched as a goroutine for each individual feed to run
// searches concurrently.
func Match(matcher Matcher, feed *Feed, searchTerm string, results chan<- *Result) {
// Perform the search against the specified matcher.
searchResults, err := matcher.Search(feed, searchTerm)
if err != nil {
log.Println(err)
return
}
// Write the results to the channel.
for _, result := range searchResults {
results <- result
}
}
// Display writes results to the console window as they
// are received by the individual goroutines.
func Display(results chan *Result) {
// The channel blocks until a result is written to the channel.
// Once the channel is closed the for loop terminates.
for result := range results {
log.Printf("%s:\n%s\n\n", result.Field, result.Content)
}
}
package search
import (
"log"
"sync"
)
// A map of registered matchers for searching.
var matchers = make(map[string]Matcher)
// Run performs the search logic.
func Run(searchTerm string) {
// Retrieve the list of feeds to search through.
feeds, err := RetrieveFeeds()
if err != nil {
log.Fatal(err)
}
// Create an unbuffered channel to receive match results to display.
results := make(chan *Result)
// Setup a wait group so we can process all the feeds.
var waitGroup sync.WaitGroup
// Set the number of goroutines we need to wait for while
// they process the individual feeds.
waitGroup.Add(len(feeds))
// Launch a goroutine for each feed to find the results.
for _, feed := range feeds {
// Retrieve a matcher for the search.
matcher, exists := matchers[feed.Type]
if !exists {
matcher = matchers["default"]
}
// Launch the goroutine to perform the search.
go func(matcher Matcher, feed *Feed) {
Match(matcher, feed, searchTerm, results)
waitGroup.Done()
}(matcher, feed)
}
// Launch a goroutine to monitor when all the work is done.
go func() {
// Wait for everything to be processed.
waitGroup.Wait()
// Close the channel to signal to the Display
// function that we can exit the program.
close(results)
}()
// Start displaying results as they are available and
// return after the final result is displayed.
Display(results)
}
// Register is called to register a matcher for use by the program.
func Register(feedType string, matcher Matcher) {
if _, exists := matchers[feedType]; exists {
log.Fatalln(feedType, "Matcher already registered")
}
log.Println("Register", feedType, "matcher")
matchers[feedType] = matcher
}
[
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=1001",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=1008",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=1006",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=1007",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=1057",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=1021",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=1012",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=1003",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=2",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=3",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=5",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=13",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=46",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=7",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=10",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=39",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=43",
"type" : "rss"
},
{
"site" : "bbci",
"link" : "http://feeds.bbci.co.uk/news/rss.xml",
"type" : "rss"
},
{
"site" : "bbci",
"link" : "http://feeds.bbci.co.uk/news/business/rss.xml",
"type" : "rss"
},
{
"site" : "bbci",
"link" : "http://feeds.bbci.co.uk/news/world/us_and_canada/rss.xml",
"type" : "rss"
},
{
"site" : "cnn",
"link" : "http://rss.cnn.com/rss/cnn_topstories.rss",
"type" : "rss"
},
{
"site" : "cnn",
"link" : "http://rss.cnn.com/rss/cnn_world.rss",
"type" : "rss"
},
{
"site" : "cnn",
"link" : "http://rss.cnn.com/rss/cnn_us.rss",
"type" : "rss"
},
{
"site" : "cnn",
"link" : "http://rss.cnn.com/rss/cnn_allpolitics.rss",
"type" : "rss"
},
{
"site" : "cnn",
"link" : "http://rss.cnn.com/rss/cnn_crime.rss",
"type" : "rss"
},
{
"site" : "cnn",
"link" : "http://rss.cnn.com/rss/cnn_tech.rss",
"type" : "rss"
},
{
"site" : "cnn",
"link" : "http://rss.cnn.com/rss/cnn_health.rss",
"type" : "rss"
},
{
"site" : "cnn",
"link" : "http://rss.cnn.com/rss/cnn_topstories.rss",
"type" : "rss"
},
{
"site" : "foxnews",
"link" : "http://feeds.foxnews.com/foxnews/opinion?format=xml",
"type" : "rss"
},
{
"site" : "foxnews",
"link" : "http://feeds.foxnews.com/foxnews/politics?format=xml",
"type" : "rss"
},
{
"site" : "foxnews",
"link" : "http://feeds.foxnews.com/foxnews/national?format=xml",
"type" : "rss"
},
{
"site" : "foxnews",
"link" : "http://feeds.foxnews.com/foxnews/world?format=xml",
"type" : "rss"
},
{
"site" : "nbcnews",
"link" : "http://feeds.nbcnews.com/feeds/topstories",
"type" : "rss"
},
{
"site" : "nbcnews",
"link" : "http://feeds.nbcnews.com/feeds/usnews",
"type" : "rss"
},
{
"site" : "nbcnews",
"link" : "http://rss.msnbc.msn.com/id/21491043/device/rss/rss.xml",
"type" : "rss"
},
{
"site" : "nbcnews",
"link" : "http://rss.msnbc.msn.com/id/21491571/device/rss/rss.xml",
"type" : "rss"
},
{
"site" : "nbcnews",
"link" : "http://rss.msnbc.msn.com/id/28180066/device/rss/rss.xml",
"type" : "rss"
}
]
test.txt
hello
hello
world
world
world
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
counts := make(map[string]int)
f, err := os.Open("test.txt")
if err == nil {
input := bufio.NewScanner(f)
for input.Scan() {
counts[input.Text()]++
}
}
f.Close()
for line, n := range counts {
if n > 1 {
fmt.Printf("line:%s\tcount:%d\n", line, n)
}
}
}
//line:hello count:2
//line:world count:3
package main
import (
"fmt"
"io/ioutil"
"strings"
)
func main() {
counts := make(map[string]int)
data, err := ioutil.ReadFile("test.txt")
if err != nil {
return
}
for _, line := range strings.Split(string(data), "\r\n"){
counts[line]++
}
for line, n := range counts {
if n > 1 {
fmt.Printf("line:%s\tcount:%d\n", line, n)
}
}
}
//line:hello count:2
//line:world count:3
package main
import (
"image"
"image/color"
"image/gif"
"io"
"log"
"math"
"math/rand"
"net/http"
"os"
"time"
)
var palette = []color.Color{color.White, color.Black}
const (
whiteIndex = 0
blackIndex = 1
)
func main() {
// go run main.go >out.gif
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))
}
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)
}
package main
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"time"
)
func main() {
start := time.Now()
ch := make(chan string)
for _, url := range os.Args[1:] {
go fetch(url, ch)
}
for range os.Args[1:] {
fmt.Println(<-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)
}
//E:\Project\test_go>go build main.go
//E:\Project\test_go>main.exe http://gopl.io https://godoc.org
//1.50s 30539 https://godoc.org
//1.84s 4154 http://gopl.io
//1.84s elapsed
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/hello", 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)
}
添加访问计数功能
package main
import (
"fmt"
"log"
"net/http"
"sync"
)
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()
}
打印消息头
package main
import (
"fmt"
"log"
"net/http"
"sync"
)
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, "%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)
}
}
func counter(w http.ResponseWriter, r *http.Request) {
mu.Lock()
fmt.Fprintf(w, "Count %d\n", count)
mu.Unlock()
}
整合gif到web
package main
import (
"image"
"image/color"
"image/gif"
"io"
"log"
"math"
"math/rand"
"net/http"
)
var palette = []color.Color{color.White, color.Black}
const (
whiteIndex = 0
blackIndex = 1
)
func main() {
handler := func(w http.ResponseWriter, r *http.Request) {
lissajous(w)
}
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
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)
}
类型和表达式部分可以省略一个,但是不能都省略
var name type = expression
var i, j, k int
var b, f, s = true, 2.3, "four"
var f, err = os.Open(name)
短变量声明可以用来声明和初始化局部变量。
name := expression
i, j := 0, 1
i, j := j, i
f, err := os.Open(name)
短变量声明最少声明一个新变量,否则,代码无法编译通过(这个还是比较好理解,因为短变量声明本意就是为新的局部变量准备的,如果没有新变量,直接使用普通赋值岂不是更好)。
.\main.go:7:4: no new variables on left side of :=
指针的值是一个变量的地址,用以指示值所保存的位置。
package main
import "fmt"
func main() {
x := 1
p := &x
fmt.Println(*p) // 1
*p = 2
fmt.Println(x) // 2
var y, z int
fmt.Println(&y == &y, &y == &z, &y == nil) // true false false
}
函数返回局部变量的地址
package main
import "fmt"
func main() {
fmt.Println(f() == f()) // false
v := 1
incr(&v) // 2
fmt.Println(incr(&v)) // 3
}
func f() *int {
v := 1
return &v
}
func incr(p *int) int {
*p ++
return *p
}
指针对于 flag 包很关键,使用程序的命令行参数来设置整个程序内变量的值。
package main
import (
"flag"
"fmt"
)
func main() {
var name string
var age int
var married bool
flag.StringVar(&name, "name", "everyone", "The greeting object.")
flag.IntVar(&age, "age", 18, "The greeting object's age.")
flag.BoolVar(&married, "married", false, "Is the greeting object married?")
flag.Parse() //更新标识变量的默认值
fmt.Println("name:", name)
fmt.Println("age:", age)
fmt.Println("married:", married)
}
D:\MyProject\Go\test>main -name="Looking" -age=30 -married
name: Looking
age: 30
married: true
D:\MyProject\Go\test>main --help
flag provided but not defined: -marrie
Usage of main:
-age int
The greeting object's age. (default 18)
-married
Is the greeting object married?
-name string
The greeting object. (default "everyone")
可以使用内置预声明的 new 函数创建变量,只是不需要引入一个虚拟的名字。
package main
import "fmt"
func main() {
p := new(int)
fmt.Println(*p) // 0
*p = 2
fmt.Println(*p) // 2
}
// new 是一个预声明的函数,可以重定义为其他类型
func delta(old, new int) int { // 在 delta 函数内,内置的 new 函数暂时不可用
return new - old
}
package main
import "fmt"
type Celsius float64
type Fahrenheit float64
const (
AbsoluteZeroC Celsius = -273.15
FreezingC Celsius = 0
BollingC Celsius = 0
)
func main() {
var c Celsius
var f Fahrenheit
fmt.Println(c == 0) // true
fmt.Println(f == 0) // true
//fmt.Println(c == f) // 不同命名类型的值不能直接比较
fmt.Println(c == Celsius(f)) // true 强制类型转换后可以进行比较
c = 100
fmt.Println(c.String())
}
func (c Celsius) String() string {
return fmt.Sprintf("%g C", c)
}
目录结构
.
├── main.go
└── tempconv
├── conv.go
└── tempconv.go
tempconv/conv.go
package tempconv
func CToF(c Celsius) Fahrenheit {
return Fahrenheit(c*9/5 + 32)
}
func FToC(f Fahrenheit) Celsius {
return Celsius(f-32) * 5 / 9
}
tempconv/tempconv.go
package tempconv
import "fmt"
type Celsius float64
type Fahrenheit float64
const (
AbsoluteZeroC Celsius = -273.15
FreezingC Celsius = 0
BollingC Celsius = 100
)
func (c Celsius) String() string {
return fmt.Sprintf("%g C", c)
}
func (f Fahrenheit) String() string {
return fmt.Sprintf("%g F", f)
}
./main.go
package main
import "fmt"
import "./tempconv"
func main() {
f := tempconv.CToF(tempconv.BollingC)
fmt.Println(f) // 212 F
}
结果调用的时候报错了
build command-line-arguments: cannot find module for path XXXXXX
然后设置一下环境变量就好了(目前还是新手,暂且不去追究具体原理)
go env -w GO111MODULE=auto
还有就是我同步依赖的时候,一直同步失败,后来按照网上的方案配置了一下公共代理才搞定
包的初始化按照声明顺序初始化,在依赖已解析的情况下,根据依赖顺序进行初始化。init 函数可以用来初始化,这个函数不能被调用和引用。
func init(){ /*...*/ }
在包级别(就是在任何函数外)的声明,可以被同一个包的任何文件引用,比如conv.go不用显式导入,就可以引用同包里边tempconv.go 里声明的变量。
同其他语言一样,如果内外语法块都有声明,内部声明(局部声明)的优先级更高,会覆盖外部声明。
局部变量让外部变量失效
package main
import (
"fmt"
"log"
"os"
)
var cwd string
func init(){
cwd, err := os.Getwd() // := 将变量变成了局部声明
if err != nil {
log.Fatalf("os.Getwd failed: %v", err)
}
fmt.Println("init", cwd)
}
func main() {
fmt.Println("main", cwd)
}
不使用 := ,声明变量 err,
package main
import (
"fmt"
"log"
"os"
)
var cwd string
func init(){
var err error
cwd, err = os.Getwd()
if err != nil {
log.Fatalf("os.Getwd failed: %v", err)
}
fmt.Println("init", cwd)
}
func main() {
fmt.Println("main", cwd)
}
Go 具备有符号整数和无符号整数,有符号分别为 int8,int16,int32,int64,对应的无符号整数为 uint8, uint16,uint32,uint64,还有两种类型 int 和 uint。
rune 同义于 int32,byte 同义于 uint8,使用 rune 和 byte 时,强调值是原始数据,而非量值。
int, uint, uintptr 和其他大小明确的相似类型的区别,比如 int 天然大小是 32 位,但 int 若要当做 int32使用,必须显示转换,反之亦然。有符号位的最高位作为符号位。int8可以从 -128到127取值,uint8 从0到255取值。
二元运算符优先级:
* / % << >> & &^
+ - | ^
== != < <= > >=
&&
||
Go中取模运算符的正负号始终与被除数保持一致:
package main
import "fmt"
func main() {
fmt.Println(5 % -3) // 2
fmt.Println(-5 % -3) // -2
}
无论是有符号还是无符号数,超出类型范围高位会毫无提示丢失:
package main
import "fmt"
func main() {
var u uint8 = 255
fmt.Println(u, u+1, u*u) // 255 0 1; 255*255=65025 "1111111000000001"
var i int8 = 127
fmt.Println(i, i+1, i*i) // 127, -128 1; 127*127=16129 "11111100000001"
}
全部基本类型(布尔值,数值,字符串)的值都可以比较,位清空 x&^y 也可简单理解成 x&(^y),也即 y 取反以后和 x 按位与。
Go也支持基本位运算,
& 按位与
| 按位或
^ 按位异或
&^ 位清空(z = x&^y,若 y 的某位是1,则 z 的对应位为0,否则为 x 的对应位)
<< 左移
>> 右移
package main
import "fmt"
func main() {
var x uint8 = 1<<1 | 1<<5
var y uint8 = 1<<1 | 1<<2
fmt.Printf("%08b\n", x) // "00100010"
fmt.Printf("%08b\n", y) // "00100010"
fmt.Printf("%08b\n", x&y) // "00100010"
fmt.Printf("%08b\n", x|y) // "00100110"
fmt.Printf("%08b\n", x^y) // "00100110"
fmt.Printf("%08b\n", x&^y) // "00100000"
fmt.Printf("%08b\n", x&(^y)) // "00100000"
for i := uint(0); i < 8; i++ {
if x&(1<>1) // "00010001"
}
Go的无符号整形往往只用于位运算和特定算术运算符,例如实现位集,解析二进制文件,散列或加密,无符号整数很少用于表示非负值。对于算术和逻辑的二元运算符,操作数的类型必须相同。
很多整型到整型的相互转换并不会引起值的变化,仅仅告知编译器应该如何解读该值。不过缩减大小的整型转换可能改变值或者损失精度。
fmt 格式化输出技巧:
package main
import "fmt"
func main() {
i := 438
// [1]告知 Printf 重复使用第一个操作数,%b %o %x 告知输出的进制,#告知输出相应的表示进制的前缀
fmt.Printf("%d %[1]b %#[1]o %#[1]x %[1]X", i) // 438 110110110 0666 0x1b6 1B6
}
Go支持两种大小的浮点数 float32和float64,math 包给出了浮点值的极限。
package main
import (
"fmt"
"math"
)
func main() {
fmt.Println(math.MaxFloat32) // 3.4028234663852886e+38
fmt.Println(math.MaxFloat64) // 1.7976931348623157e+308
fmt.Println(math.E) // 2.718281828459045
fmt.Println(math.Pi) // 3.141592653589793
var f float32 = 16777216
fmt.Println(f == f+1) // true, float32能精确表示的正整数范围有限
for i := 0; i < 8; i++ {
fmt.Printf("i = %d e^x = %8.3f\n", i, math.Exp(float64(i))) // 这种表示倒是和 python 的 format 比较像
}
// >>> "pi = {:8.2f}".format(3.1415926)
//'pi = 3.14'
//
//i = 0 e^x = 1.000
//i = 1 e^x = 2.718
//i = 2 e^x = 7.389
//i = 3 e^x = 20.086
//i = 4 e^x = 54.598
//i = 5 e^x = 148.413
//i = 6 e^x = 403.429
//i = 7 e^x = 1096.633
}
bool值只有 真(true)和假(false)两种可能,且无法隐式转换成数值,反之也不行。如果转换中常常用到,就需要专门写个函数。
func btoi(b bool) int {
if b {
return 1
}
return 0
}
func itob(i int) bool {
return i != 0
}
内置的len函数返回字符串的字节数(不是字符数):
package main
import "fmt"
func main() {
s := "hello, world"
fmt.Println(s[0:5]) // hello
fmt.Println(s[7:]) // world
fmt.Println(s[:]) // hello, world
fmt.Println("goodbye" + s[5:]) // goodbye, world
s = "left foot"
t := s
s += ", right foot"
fmt.Println(s)
fmt.Println(t) // 字符串值本身无法改变,无法被修改
}
字符串的值可以直接写成字符串字面量。其中转义序列使用 \ 开始,原生的字符串字面量使用反引号而不是双引号括起来。
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "hello, 世界"
fmt.Println(len(s)) // 13 字节数
fmt.Println(utf8.RuneCountInString(s)) // 9 字符数
for i := 0; i < len(s); {
r, size := utf8.DecodeRuneInString(s[i:])
fmt.Printf("%d\t%c\n", i, r)
i += size
}
//0 h
//1 e
//2 l
//3 l
//4 o
//5 ,
// 6
//7 世
//10 界
}
package main
import "fmt"
func main() {
s := "hello, 世界"
n := 0
for _, c := range s { // 对字符串每个字符进行遍历
fmt.Printf("%c\n", c)
n++
}
fmt.Printf("% x\n", s)
}
fmt.Printf 的谓词 % x(中间有空格)以16进制形式输出,并在两个数位间插入空格。
package main
import (
"fmt"
)
func main() {
fmt.Println(string("65")) // 65
fmt.Println(string(65)) // A
fmt.Println(string(0x4eac)) // 京
fmt.Println(string(1234567)) // �
}
4个标准库对字符串的操作特别重要:bytes, strings, strconv 和 unicode。
package main
import (
"fmt"
)
func main() {
fmt.Println(comma("123456789"))
}
// 整数字符串递归添加逗号
func comma(s string) string {
n := len(s)
if n <= 3 {
return s
}
return comma(s[:n-3]) + "," + s[n-3:]
}
为了避免转换和不必要的内存分配,bytes 和 strings 都预备了很多对应的实用函数,唯一不同的是操作对象由字符串变成了字节数组:
package main
import (
"bytes"
"fmt"
"strings"
)
func main() {
s := "aa bc"
b := []byte(s)
fmt.Println(strings.Contains(s, "a")) // true
fmt.Println(bytes.Contains(b, []byte("a"))) // true
fmt.Println(strings.Count(s, "a")) // 2
fmt.Println(bytes.Count(b, []byte("a"))) // 2
fmt.Println(strings.Fields(s)) // [aa bc]
fmt.Println(bytes.Fields(b)) // [[97 97] [98 99]]
fmt.Println(strings.HasPrefix(s, "a")) // true
fmt.Println(bytes.HasPrefix(b, []byte("a"))) // true
fmt.Println(strings.Index(s, "b")) // 3
fmt.Println(bytes.Index(b, []byte("b"))) // 3
fmt.Println(strings.Join([]string{"a", "b", "cd"}, "")) // abcd
fmt.Println(bytes.Join([][]byte{[]byte("a"), []byte("b"), []byte("cd")}, []byte(""))) // [97 98 99 100]
}
bytes.Buffer
package main
import (
"bytes"
"fmt"
)
func main() {
fmt.Println(intsToString([]int{1, 2, 3})) // [1,2,3]
}
func intsToString(values []int) string {
var buf bytes.Buffer
buf.WriteByte('[')
for i, v := range values {
if i > 0 {
buf.WriteString(",")
}
fmt.Fprintf(&buf, "%d", v)
}
buf.WriteByte(']')
return buf.String()
}
如果要追加非 ASCII 字符,需要使用 but.WriteRune,否则可能出现溢出报错比如: constant 22909 overflows byte
package main
import (
"fmt"
"strconv"
)
func main() {
x := 123
y := fmt.Sprintf("%d", x)
fmt.Println(y, strconv.Itoa(x)) // 123 123
fmt.Println(strconv.FormatInt(int64(x), 2)) // 111011
s := fmt.Sprintf("x=%b", x) // x=1111011
fmt.Println(s)
fmt.Printf("x=%b", x) // x=1111011
fmt.Println()
a, _ := strconv.Atoi("1234")
b, _ := strconv.ParseInt("1111", 2, 8) // 二进制,最长8位,若超过最大位数,显示能表示的最大值
fmt.Println(a) // 1234
fmt.Println(b) // 15
}
常量是一种表达式,其在编译阶段就计算出表达式的值,本质上都属于基本类型:布尔型,字符串或数字。常量操作数的数学运算、逻辑运算和比较运算依然是常量。
package main
import (
"fmt"
"time"
)
func main() {
const noDelay time.Duration = 0
const timeout = 5 * time.Minute
fmt.Printf("%T %[1]v\n", noDelay) // time.Duration 0s
fmt.Printf("%T %[1]v\n", timeout) // time.Duration 5m0s
fmt.Printf("%T %[1]v\n", time.Minute) // time.Duration 1m0s
const (
a = 1
b
c = 2
d
)
fmt.Println(a, b, c, d) // 1 1 2 2
}
package main
import "fmt"
type WeekDay int
const (
Sunday WeekDay = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
func main() {
fmt.Println(Wednesday) // 3
fmt.Println(Friday) // 5
}
只有常量才是无类型的,若将无类型常量声明为变量或者赋值给变量,则会隐式转换成该变量的类型。
package main
import "fmt"
func main() {
var f float64 = 212
fmt.Println((f - 32) * 5 / 9) // 100
fmt.Println(5 / 9 * (f - 32)) // 0
fmt.Println(5.0 / 9.0 * (f - 32)) // 100
}
Go中只有大小不明确的 int 类型,却没有大小不明确的 float 和 complex 类型。
数组是拥有相同数据类型元素的固定长度序列,因其长度固定,Go中很少直接使用。
package main
import "fmt"
func main() {
var a [3]int // [0 0 0]
var q [3]int = [3]int{1, 2, 3} // [1 2 3]
var r [3]int = [3]int{1, 2} // [1 2 0]
fmt.Println(a, q, r)
}
数组字面量中,... 如果出现在数组长度的位置,那么数组长度由其初始长度决定,数组的长度必须是常量表达式,因此 [3]int 和 [4]int 是不同的数组类型。
package main
import "fmt"
func main() {
q := [...]int{1, 2, 3}
fmt.Printf("%T\n", q) // [3]int
q = [4]int{1, 2, 3} // cannot use [4]int{...} (type [4]int) as type [3]int in assignment
}
如果一个数组的元素可比较,那么这个数组也可比较。
package main
import "fmt"
func main() {
a := [2]int{1, 2}
b := [...]int{1, 2}
c := [2]int{1, 3}
fmt.Println(a == b, a == c, b == c) // true false false
d := [3]int{1, 2}
fmt.Println(a == d) // invalid operation: a == d (mismatched types [2]int and [3]int)
}
package main
import (
"crypto/sha256"
"fmt"
)
func main() {
c1 := sha256.Sum256([]byte("x"))
c2 := sha256.Sum256([]byte("X"))
fmt.Printf("%x\n%x\n%t\n%T\n", c1, c2, c1 == c2, c1)
//
//2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881
//4b68ab3847feda7d6c62c1fbcbeebfa35eab7351ed5e78f4ddadea5df64b8015
//false
//[32]uint8
}
切片可以理解为是可变长度的数组
package main
import "fmt"
func main() {
months := [...]string{1: "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"}
Q2 := months[4:7]
summer := months[6:9]
fmt.Println(Q2) // [April May June]
fmt.Println(summer) // [June July August]
endlessSummer := summer[:5] // [June July August September October]
fmt.Println(endlessSummer)
}
package main
import "fmt"
func main() {
data := []string{"one", "", "three"}
fmt.Printf("%q\n", nonempty(data)) // ["one" "three"]
fmt.Printf("%q\n", data) // ["one" "three" "three"]
}
func nonempty(strings []string) []string {
i := 0
for _, s := range strings {
if s != "" {
strings[i] = s
i++
}
}
return strings[:i]
}
实现堆栈
package main
import "fmt"
func main() {
stack := []string{"one", "two", "three"} // ["one" "three" "three"]
fmt.Println(stack)
top := stack[len(stack)-1]
stack = stack[:len(stack)-1]
fmt.Println(stack) // [one two]
fmt.Println(top) // three
}
移除元素
package main
import "fmt"
func main() {
stack := []int{1, 2, 3, 4}
fmt.Println(remove(stack, 1)) // [1 3 4]
}
func remove(s []int, i int) []int {
copy(s[i:], s[i+1:])
return s[:len(s)-1]
}
map初始化以前是零值
package main
import (
"fmt"
"sort"
)
func main() {
//ages := map[string]int{"alice": 34, "charlie":31}
var ages map[string]int // map 的零值是 nil
fmt.Println(ages == nil) // true
ages["charlie"] = 31 // panic: assignment to entry in nil map
ages = map[string]int{}
fmt.Println(ages == nil) // false
ages["charlie"] = 31
ages["alice"] = 34
fmt.Println(ages) // map[alice:34 charlie:31]
delete(ages, "alice") // 即使 alice 不存在,delete 操作也不会报错
fmt.Println(ages) // map[charlie:31]
ages["alice"] = 31
for name, age := range ages {
fmt.Printf("%s\t%d\n", name, age)
}
// 排序输出
var names []string = make([]string, 0, len(ages)) // 提前知道names的长度,
for name := range ages {
names = append(names, name)
}
fmt.Println(names) // [charlie alice]
sort.Strings(names)
fmt.Println(names) // [alice charlie]
for _, name := range names {
fmt.Printf("%s\t%d\n", name, ages[name])
}
age, ok := ages["bob"]
if !ok {
fmt.Printf("bob not in ages, get default age %d", age) // bob not in ages, get default age 0
}
}
Go读取输入串并处理
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
input := bufio.NewScanner(os.Stdin)
for input.Scan() {
line := input.Text()
fmt.Println("line:", line)
}
}
延迟初始化
package main
import "fmt"
var graph = make(map[string]map[string]bool)
func main() {
addEdge("hello", "world")
fmt.Println(graph)
fmt.Println(hasEdge("hello", "world")) // true
fmt.Println(hasEdge("hello", "World")) // false
}
func addEdge(from, to string) {
edges := graph[from]
if edges == nil {
edges = make(map[string]bool)
graph[from] = edges
}
edges[to] = true
}
func hasEdge(from, to string) bool {
return graph[from][to]
}
package main
import (
"fmt"
"time"
)
type Employee struct {
ID int
Name string
Address string
DoB time.Time
Position string
Salary int
ManagerID int
}
type Point struct {
X, Y int
}
type address struct {
hostname string
port int
}
func main() {
fmt.Println(Scale(Point{1, 2}, 5)) // {5 10}
p := Point{1, 2}
q := Point{2, 1}
fmt.Println(p.X == q.Y && p.Y == q.X) // true
fmt.Println(p == q) // false
hits := make(map[address]int)
hits[address{"golang.org", 443}]++
fmt.Println(hits) // map[{golang.org 443}:1]
}
func Scale(p Point, factor int) Point {
return Point{p.X * factor, p.Y * factor}
}
package main
import (
"fmt"
"time"
)
type Employee struct {
ID int
Name string
Address string
DoB time.Time
Position string
Salary int
ManagerID int
}
type Point struct {
X, Y int
}
type Circle struct {
Point // 匿名成员
Radius int
}
type Wheel struct {
Circle // 匿名成员
Spokes int
}
func main() {
var w Wheel
w.X = 8 // 等价于w.Circle.Point.X = 8
w.Y = 8 // 匿名成员的名字就是对应类型的名字
w.Radius = 5
w.Spokes = 20
fmt.Println(w) // {{{8 8} 5} 20}
}
# 使得 Printf 的格式化符号 %v 以类似 Go 语法的方式输出对象
package main
import (
"fmt"
"time"
)
type Employee struct {
ID int
Name string
Address string
DoB time.Time
Position string
Salary int
ManagerID int
}
type Point struct {
X, Y int
}
type Circle struct {
Point // 匿名成员
Radius int
}
type Wheel struct {
Circle // 匿名成员
Spokes int
}
func main() {
var w Wheel
//w = Wheel{Circle{Point{8, 8}, 5}, 20}
w = Wheel{
Circle: Circle{
Point: Point{
X: 8,
Y: 8,
},
Radius: 5,
},
Spokes: 20,
}
// main.Wheel{Circle:main.Circle{Point:main.Point{X:8, Y:8}, Radius:5}, Spokes:20}
fmt.Printf("%#v\n", w) // # 使得 Printf 的格式化符号 %v 以类似 Go 语法的方式输出对象
}
marshal
package main
import (
"encoding/json"
"fmt"
"log"
)
type Movie struct {
Title string
Year int `json:"released"`
Color bool `json:"color,omitempty"` // omitempty 表示如果这个成员的值为空或零值,则不输出成员到 json
Actors []string
}
func main() {
var movies = []Movie{
{
Title: "Casablanca",
Year: 1942,
Color: false,
Actors: []string{"Humphrey Bogart", "Ingrid Bergman"},
},
{
Title: "Cool Hand Luke",
Year: 1967,
Color: true,
Actors: []string{"Paul Newman"},
},
{
Title: "Bullitt",
Year: 1968,
Color: true,
Actors: []string{"Steve McQueen", "Jacqueline Bisset"},
},
}
fmt.Println(movies) // [{Casablanca 1942 false [Humphrey Bogart Ingrid Bergman]} {Cool Hand Luke 1967 true [Paul Newman]} {Bullitt 1968 true [Steve McQueen Jacqueline Bisset]}]
//data, err := json.Marshal(movies) // [{"Title":"Casablanca","released":1942,"Actors":["Humphrey Bogart","Ingrid Bergman"]},{"Title":"Cool Hand Luke","released":1967,"color":true,"Actors":["Paul Newman"]},{"Title":"Bullitt","released":1968,"color":true,"Actors":["Steve McQueen","Jacqueline Bisset"]}]
data, err := json.MarshalIndent(movies, "", " ") // 比 json.Marshal 多了两个参数,一个前缀字符串和缩进字符串
if err != nil {
log.Fatalf("JSON marshaling failed: %s", err)
}
fmt.Printf("%s\n", data)
//[
// {
// "Title": "Casablanca",
// "released": 1942,
// "Actors": [
// "Humphrey Bogart",
// "Ingrid Bergman"
// ]
// },
// {
// "Title": "Cool Hand Luke",
// "released": 1967,
// "color": true,
// "Actors": [
// "Paul Newman"
// ]
// },
// {
// "Title": "Bullitt",
// "released": 1968,
// "color": true,
// "Actors": [
// "Steve McQueen",
// "Jacqueline Bisset"
// ]
// }
//]
}
unmarshal
通过合理定义数据结构,我们可以选择将哪部分 JSON 数据解码到结构体对象中,如下,就将 JSON 中 Title 和 Year 的其他字段丢弃了。
package main
import (
"encoding/json"
"fmt"
"log"
)
type Movie struct {
Title string
Year int `json:"released"` // Year 对应到 json 的 released
Color bool `json:"color,omitempty"` // omitempty 表示如果这个成员的值为空或零值,则不输出成员到 json
Actors []string
}
func main() {
var movies = []Movie{
{
Title: "Casablanca",
Year: 1942,
Color: false,
Actors: []string{"Humphrey Bogart", "Ingrid Bergman"},
},
{
Title: "Cool Hand Luke",
Year: 1967,
Color: true,
Actors: []string{"Paul Newman"},
},
{
Title: "Bullitt",
Year: 1968,
Color: true,
Actors: []string{"Steve McQueen", "Jacqueline Bisset"},
},
}
data, err := json.MarshalIndent(movies, "", " ") // 比 json.Marshal 多了两个参数,一个前缀字符串和缩进字符串
if err != nil {
log.Fatalf("JSON marshaling failed: %s", err)
}
var titles []struct {
Title string
Year int `json:"released"`
}
if err := json.Unmarshal(data, &titles); err != nil {
log.Fatalf("JSON unmarshaling failed: %s", err)
}
fmt.Println(titles) // [{Casablanca 1942} {Cool Hand Luke 1967} {Bullitt 1968}]
}
即使JSON的首字母不是大写,结构体成员的名称也必须首字母大写。
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"os"
"strings"
"time"
)
const IssuesURL = "https://api.github.com/search/issues"
type IssuesSearchResult struct {
TotalCount int `json:"total_count"`
Items []*Issues
}
type Issues struct {
Number int
HTMLURL string `json:"html_url"`
Title string
State string
User *User
CreatedAt time.Time `json:"created_at"`
Body string
}
type User struct {
Login string
HTMLURL string `json:"html_url"`
}
func SearchIssues(terms []string) (*IssuesSearchResult, error) {
q := url.QueryEscape(strings.Join(terms, " "))
resp, err := http.Get(IssuesURL + "?q=" + q)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, fmt.Errorf("search query failed: %s", resp.Status)
}
var result IssuesSearchResult
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
resp.Body.Close()
return nil, err
}
resp.Body.Close()
return &result, nil
}
func main() {
result, err := SearchIssues(os.Args[:1])
if err != nil {
log.Fatal(err)
}
fmt.Printf("%d issues:\n", result.TotalCount)
for _, item := range result.Items {
fmt.Printf("#%-5d %9.9s %.55s\n", item.Number, item.User.Login, item.Title)
}
}
package main
import (
"html/template"
"log"
"os"
)
func main() {
const templ = `A: {{.A}}
B: {{.B}}
`
t := template.Must(template.New("escape").Parse(templ))
var data struct {
A string
B template.HTML
}
data.A = "Hello!"
data.B = "Hello!"
if err := t.Execute(os.Stdout, data); err != nil { // A: <b>Hello!</b>
B: Hello!
log.Fatal(err)
}
}
package main
import (
"fmt"
"math"
)
func main() {
fmt.Println(hypot(3, 4))
fmt.Printf("%T\n", hypot) // func(float64, float64) float64
}
func hypot(x, y float64) (z float64) { // 返回列表有变量时,需要括号括起来
z = math.Sqrt(x*x + y*y)
return
}
函数如果有命名的返回值,可以省略return 语句的操作数,也称为裸返回。也即将每个命名返回结果按照顺序返回的快捷方法。
package main
import (
"fmt"
"log"
"net/http"
"time"
)
func main() {
//log.SetPrefix("wait: ") // 可以自定义错误前缀
//log.SetFlags(0)
url := "xxx"
if err := WaitForServer(url); err != nil {
log.Fatalf("Site is down: %v\n", err) // 会将时间和日期作为前缀添加到错误消息前
//fmt.Fprintf(os.Stderr, "Site is down: %v\n", err)
//os.Exit(1)
}
//2023/06/13 18:45:31 server not responding (Head "xxx": unsupported protocol scheme ""); retrying...
//2023/06/13 18:45:32 server not responding (Head "xxx": unsupported protocol scheme ""); retrying...
//2023/06/13 18:45:34 server not responding (Head "xxx": unsupported protocol scheme ""); retrying...
//2023/06/13 18:45:38 server not responding (Head "xxx": unsupported protocol scheme ""); retrying...
//2023/06/13 18:45:46 server not responding (Head "xxx": unsupported protocol scheme ""); retrying...
//2023/06/13 18:46:02 server not responding (Head "xxx": unsupported protocol scheme ""); retrying...
//2023/06/13 18:49:17 Site is down: server xxx failed to respond after 1m0s
}
func WaitForServer(url string) error {
const timeout = 30 * time.Second
deadline := time.Now().Add(timeout)
for tries := 0; time.Now().Before(deadline); tries++ { // 截止日期之前,指数时间间隔重试
_, err := http.Head(url)
if err == nil {
return nil
}
log.Printf("server not responding (%s); retrying...", err)
time.Sleep(time.Second << uint(tries)) // 指数退避政策
}
return fmt.Errorf("server %s failed to respond after %s", url, timeout)
}
Go的错误处理规律,先进行错误检查,成功的逻辑(实际的函数体)在外层作用域。
package main
import (
"bufio"
"fmt"
"io"
"log"
"os"
)
func main() {
in := bufio.NewReader(os.Stdin)
for {
r, _, err := in.ReadRune()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("read failed: %v", err)
}
fmt.Println(r)
}
}
package main
import (
"fmt"
"strings"
)
func main() {
f := square
fmt.Printf("%T\n", f)
f = negative
fmt.Printf("%T\n", f)
//f = product // cannot use product (type func(int, int) int) as type func(int) int in assignment
var t func(int) int
fmt.Println(t == nil) // true。函数类型的零值(空函数)是 nil
// strings.Map 对 字符串的每个字符都调用 func
fmt.Println(strings.Map(add1, "HAL9000")) // IBM:111
fmt.Println(strings.Map(add1, "VMS")) // WNT
fmt.Println(strings.Map(add1, "Admin")) // Benjo
fmt.Printf("%*s%s>\n", 4, "he")
}
func add1(r rune) rune {
return r + 1
}
func square(n int) int {
return n * n
}
func negative(n int) int {
return -n
}
func product(m, n int) int {
return m * n
}
package main
import (
"fmt"
"strings"
)
func main() {
// strings.Map 对 字符串的每个字符都调用 func
fmt.Println(strings.Map(func(r rune) rune {return r + 1 }, "HAL9000")) // IBM:111 匿名函数
f := squares()
fmt.Println(f()) // 1
fmt.Println(f()) // 4
fmt.Println(f()) // 9
fmt.Println(squares()()) // 1
fmt.Println(squares()()) // 1
fmt.Println(squares()()) // 1
}
func squares() func() int {
var i int
return func() int { // 里层匿名函数可以获取并更新外层函数的局部变量,因此函数是引用类型,且无法进行比较
i++ // 变量的生命周期不是由它的作用域决定的,x 隐藏在函数变量 f 中
return i * i
}
}
匿名函数进行递归调用时,必须先声明变量,再将匿名函数赋值给这个变量。
package main
import (
"fmt"
"sort"
)
var prereqs = map[string][]string{
"algorithms": {"data structures"},
"calculus": {"linear algebra"},
"compilers": {
"data structures",
"formal languages",
"computer organization",
},
"data structures": {"discrete math"},
"databases": {"data structures"},
"discrete math": {"intro to programming"},
"networks": {"operating systems"},
"operating systems": {"data structures", "computer organization"},
"programming languages": {"data structures", "computer organization"},
}
func main() {
for _, item := range topoSort(prereqs){
fmt.Println(item)
}
}
func topoSort(m map[string][]string) []string {
var order []string
seen := make(map[string]bool)
var visitAll func(items []string) // 内部声明匿名函数
visitAll = func(items []string) {
for _, item := range items {
if !seen[item] {
seen[item] = true
visitAll(m[item]) // 递归调用, 优先学习先行课
order = append(order, item) // 将 item 的先行课学完以后,再学当前这门课程
}
}
}
var keys []string
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys)
visitAll(keys)
fmt.Println(seen)
return order
}
package main
import "fmt"
func main() {
fmt.Println(sum()) // 0
fmt.Println(sum(3)) // 3
fmt.Println(sum(1, 2, 3, 4)) // 10
values := []int{1, 2, 3, 4}
fmt.Println(sum(values...)) // 10
fmt.Printf("%T\n", f) // func([]int)
fmt.Printf("%T\n", g) // func(...int) int
}
func f([]int) {}
func g(...int) {}
func sum(vals ...int) int {
total := 0
for _, val := range vals {
total += val
}
return total
}
package main
import (
"fmt"
"log"
"time"
)
func main() {
bigSlowOperation() // defer 会把最外一层函数放到最后调用
//2023/06/15 21:59:16 enter bigSlowOperation
//2023/06/15 21:59:26 exit bigSlowOperation (10.0080289s)
double(4) // double(4) = 8
}
func bigSlowOperation() {
defer trace("bigSlowOperation")()
time.Sleep(10 * time.Second)
}
func trace(msg string) func() {
start := time.Now()
log.Printf("enter %s", msg)
return func() {
log.Printf("exit %s (%s)", msg, time.Since(start))
}
}
func double(x int) (result int) {
defer func() {fmt.Printf("double(%d) = %d\n", x, result)}() //
return x + x
}
发生宕机时,所有的延迟函数以倒序执行。
package main
import (
"fmt"
)
func main() {
f(3)
//f(3)
//f(2)
//f(1)
//defer 1
//defer 2
//defer 3
}
func f(x int) {
fmt.Printf("f(%d)\n", x+0/x)
defer fmt.Printf("defer %d\n", x)
f(x -1)
}
Go的宕机机制让延迟执行的函数在栈清理之前调用。
package main
import (
"fmt"
"os"
"runtime"
)
func main() {
defer printStack()
f(3)
}
func printStack() {
var buf[4096]byte
n := runtime.Stack(buf[:], false)
os.Stdout.Write(buf[:n]) // 将堆栈信息写入到标准输出
}
func f(x int) {
fmt.Printf("f(%d)\n", x+0/x)
defer fmt.Printf("defer %d\n", x)
f(x -1)
}
package main
import (
"fmt"
"math"
)
type Point struct{ X, Y float64 }
// 普通函数
func Distance(p, q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
// 方法, p 称为方法的接收者,由于其会频繁使用,最好简短且始终保持一致
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
func main() {
p := Point{1, 2}
q := Point{4, 6}
fmt.Println(Distance(p, q)) // 5
fmt.Println(p.Distance(q)) // 5
}
每一个类型有他自己命名空间,编译器会根据方法名和接受者类型来决定调用哪一个方法。相同类型拥有的方法名必须唯一,不同类型可以使用相同的方法名。
package main
import (
"fmt"
"math"
)
type Point struct{ X, Y float64 }
type Path []Point
// 普通函数
func Distance(p, q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
// 方法, p 称为方法的接收者,由于其会频繁使用,最好简短且始终保持一致
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
// 方法,针对 Point 数组的 Distance 方法
func (path Path) Distance() float64 {
sum := 0.0
for i := range path{
if i > 0 {
sum += path[i-1].Distance(path[i])
}
}
return sum
}
func main() {
p := Point{1, 2}
q := Point{4, 6}
fmt.Println(Distance(p, q)) // 5
fmt.Println(p.Distance(q)) // 5
perim := Path{
{1, 1},
{5, 1},
{5, 4},
{1, 1},
}
fmt.Println(perim.Distance()) // 12
}
习惯上,如果Point的任何一个方法使用指针接收者,那么所有的 Point 方法都应该使用指针接受者。
package main
import (
"fmt"
"math"
)
type Point struct{ X, Y float64 }
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}
// 方法, p 称为方法的接收者,由于其会频繁使用,最好简短且始终保持一致
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
func main() {
p := &Point{1, 2}
p.ScaleBy(2)
fmt.Println(*p) // {2 4}
q := Point{1, 2}
qptr := &q
qptr.ScaleBy(3)
fmt.Println(q) // {3 6}
fmt.Println(qptr.Distance(q)) // 0 ,编译器隐式解引用接收者,获取实际地址。
r := Point{1, 2}
(&r).ScaleBy(4)
fmt.Println(r) // {4 8}
//Point{1, 2}.ScaleBy(5) // 编译错误,不能获取字面量的地址
fmt.Println(Point{1, 2}.Distance(q))
}
nil是合法的接受者
package main
import "fmt"
// *IntList的类型nil代表空列表
type IntList struct {
Value int
Tail *IntList
}
func (list *IntList) Sum() int {
if list == nil {
return 0
}
return list.Value + list.Tail.Sum()
}
func main() {
a := IntList{
Value: 1,
Tail: &IntList{
Value: 2,
Tail: &IntList{
Value: 3,
Tail: &IntList{
Value: 4,
Tail: nil,
},
},
},
}
fmt.Println((&a).Sum()) // 10
}
package main
import (
"fmt"
"net/url"
)
func main() {
m := url.Values{"lang": {"en"}}
m.Add("item", "1")
m.Add("item", "2")
fmt.Println(m.Get("item"))
m = nil
fmt.Println(m.Get("item"))
m.Add("item", "3") // 宕机
}
package main
import (
"fmt"
"image/color"
"math"
)
type Point struct {
X, Y float64
}
type ColoredPoint struct {
Point
Color color.RGBA
}
func (p Point) Distance(q Point) float64 {
return math.Hypot(p.X-q.X, p.Y-q.Y)
}
func main() {
var cp ColoredPoint
cp.X = 1
fmt.Println(cp.Point.X) // 1
cp.Point.Y = 2
fmt.Println(cp.Y) // 2
red := color.RGBA{255, 0, 0, 255}
blue := color.RGBA{0, 0, 255, 255}
p := ColoredPoint{Point{1, 2}, red}
q := ColoredPoint{Point{4, 6}, blue}
// Point 实现了 Distance 方法,Point 作为内嵌结构体,所以 ColoredPoint 也可以调用这个方法
fmt.Println(p.Distance(q)) // 报错
fmt.Println(p.Distance(q.Point)) // 5
fmt.Println(p.Point.Distance(q.Point)) // 5
q.Point = p.Point
fmt.Println(p, q) // {{1 2} {255 0 0 255}} {{1 2} {0 0 255 255}}
}
结构体可以声明多个匿名字段,这个类型的值可以拥有 Point 所有方法和 RGBA 所有方法,以及任何其他在 ColoredPoint 类型中声明的方法。
type ColoredPoint struct {
Point
color.RGBA
}
package main
import "sync"
// 将两个相关变量放到了一个包级别的变量中
var cache = struct {
sync.Mutex
mapping map[string]string
}{mapping: make(map[string]string),}
func Lookup(key string) string {
cache.Lock()
v := cache.mapping[key]
cache.Unlock()
return v
}
和调用普通函数不同,调用方法必须提供接受者。方法表达式写成T.f,其中 T 是类型,把原来的方法接收者替换成函数的第一个形参,且可以像平常的函数一样调用。
package main
import (
"fmt"
"math"
)
type Point struct {
X, Y float64
}
func (p Point) Distance(q Point) float64 {
return math.Hypot(p.X-q.X, p.Y-q.Y)
}
func (p *Point) scaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}
func main() {
p := Point{1, 2}
q := Point{4, 6}
distance := Point.Distance
fmt.Println(distance(p, q)) // 5
fmt.Printf("%T\n", distance) // func(main.Point, main.Point) float64
scale := (*Point).scaleBy
scale(&p, 2)
fmt.Println(p) // {2 4}
fmt.Printf("%T\n", scale) // func(*main.Point, float64)
}
通过方法向量对点集合进行位移操作
package main
import "fmt"
type Point struct {
X, Y float64
}
func (p Point) Add(q Point) Point {
return Point{p.X + q.X, p.Y + q.Y}
}
func (p Point) Sub(q Point) Point {
return Point{p.X - q.X, p.Y - q.Y}
}
type Path []Point
func (path Path) TranslateBy(offset Point, add bool) Path {
var op func(p, q Point) Point
if add {
op = Point.Add
} else {
op = Point.Sub
}
for i := range path {
path[i] = op(path[i], offset)
}
return path
}
func main() {
p := Point{1, 2}
q := Point{4, 6}
r := Point{1, 1}
translate := Path.TranslateBy
path := translate([]Point{p, q}, r, true) // [{2 3} {5 7}]
fmt.Println(path)
path = translate([]Point{p, q}, r, false) // [{0 1} {3 5}]
fmt.Println(path)
}
package main
import "fmt"
type Counter struct {
n int
}
func (c *Counter) N() int {
return c.n
}
func (c *Counter) Incr() {
c.n++
}
func (c *Counter) Reset() {
c.n = 0
}
func main() {
a := &Counter{5}
fmt.Println(a.N()) // 5
a.Incr()
fmt.Println(a.N()) // 6
a.Reset()
fmt.Println(a.N()) // 0
a.n = 7
fmt.Println(a.N()) // 7
}
一个接口类型定义了一套方法,如果一个具体类型要实现该接口,则必须实现接口类型定义的所有方法。
空接口对实现类型没有任何要求,可以把任何值赋值给接口类型。
package main
import "fmt"
func main() {
var any interface{}
any = true
fmt.Println(any) // true
any = 12.34
fmt.Println(any) // 12.34
any = "hello"
fmt.Println(any) // hello
}
如果一个类型实现了接口的所有方法,那么这个类型实现了这个接口。
package main
import (
"bytes"
"fmt"
"io"
"os"
)
func main() {
var w io.Writer
fmt.Printf("%T\n", w) //
w = os.Stdout
fmt.Printf("%T\n", w) // *os.File
w = new(bytes.Buffer)
fmt.Printf("%T\n", w) // *bytes.Buffer
}
当main调用f时,他把一个类型为 *bytes.Buffer 的空指针给了 out 参数,所以 out 的动态值确实是 空,但是他的动态类型是 *bytes.Buffer(也即值为nil,但是类型不为 nil),所以 out 是包含空指针的非空接口,防御性检查 out != nil 仍然为 true。
package main
import (
"bytes"
"fmt"
"io"
)
func main() {
var buf *bytes.Buffer
var debug bool
debug = false
if debug {
buf = new(bytes.Buffer)
}
f(buf)
if debug {
fmt.Println(buf)
}
}
func f(out io.Writer) {
if out != nil {
out.Write([]byte("done!\n")) // panic: runtime error: invalid memory address or nil pointer dereference
}
}
按照艺术家进行排序和逆排序输出
package main
import (
"fmt"
"os"
"sort"
"text/tabwriter"
"time"
)
type Track struct {
Title string
Artist string
Album string
Year int
Length time.Duration
}
func length(s string) time.Duration {
d, err := time.ParseDuration(s)
if err != nil {
panic(s)
}
return d
}
var tracks = []*Track{
{"Go", "Delilah", "From the Roots Up", 2012, length("3m38s")},
{"Go", "Moby", "Moby", 1992, length("3m37s")},
{"Go Ahead", "Alicia Keys", "As I Am", 2007, length("4m36s")},
{"Ready to Go", "Martin Solveig", "Smash", 2011, length("4m24s")},
}
func printTracks(tracks []*Track) {
const format = "%v\t%v\t%v\t%v\t%v\t\n"
tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, ' ', 0)
fmt.Fprintf(tw, format, "Title", "Artist", "Album", "Year", "Length")
fmt.Fprintf(tw, format, "-----", "------", "-----", "----", "------")
for _, t := range tracks {
fmt.Fprintf(tw, format, t.Title, t.Artist, t.Album, t.Year, t.Length)
}
tw.Flush()
}
type byArtist []*Track
func (x byArtist) Len() int {
return len(x)
}
func (x byArtist) Less(i, j int) bool {
return x[i].Artist < x[j].Artist
}
func (x byArtist) Swap(i, j int) {
x[i], x[j] = x[j], x[i]
}
func main() {
printTracks(tracks)
fmt.Println()
sort.Sort(byArtist(tracks)) // 排序
printTracks(tracks)
fmt.Println()
sort.Sort(sort.Reverse(byArtist(tracks))) // 逆排序
printTracks(tracks)
fmt.Println()
}
Title Artist Album Year Length
----- ------ ----- ---- ------
Go Delilah From the Roots Up 2012 3m38s
Go Moby Moby 1992 3m37s
Go Ahead Alicia Keys As I Am 2007 4m36s
Ready to Go Martin Solveig Smash 2011 4m24s
Title Artist Album Year Length
----- ------ ----- ---- ------
Go Ahead Alicia Keys As I Am 2007 4m36s
Go Delilah From the Roots Up 2012 3m38s
Ready to Go Martin Solveig Smash 2011 4m24s
Go Moby Moby 1992 3m37s
Title Artist Album Year Length
----- ------ ----- ---- ------
Go Moby Moby 1992 3m37s
Ready to Go Martin Solveig Smash 2011 4m24s
Go Delilah From the Roots Up 2012 3m38s
Go Ahead Alicia Keys As I Am 2007 4m36s
自定义多层比较函数
package main
import (
"fmt"
"os"
"sort"
"text/tabwriter"
"time"
)
type Track struct {
Title string
Artist string
Album string
Year int
Length time.Duration
}
func length(s string) time.Duration {
d, err := time.ParseDuration(s)
if err != nil {
panic(s)
}
return d
}
var tracks = []*Track{
{"Go", "Delilah", "From the Roots Up", 2012, length("3m38s")},
{"Go", "Moby", "Moby", 1992, length("3m37s")},
{"Go Ahead", "Alicia Keys", "As I Am", 2007, length("4m36s")},
{"Ready to Go", "Martin Solveig", "Smash", 2011, length("4m24s")},
}
func printTracks(tracks []*Track) {
const format = "%v\t%v\t%v\t%v\t%v\t\n"
tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, ' ', 0)
fmt.Fprintf(tw, format, "Title", "Artist", "Album", "Year", "Length")
fmt.Fprintf(tw, format, "-----", "------", "-----", "----", "------")
for _, t := range tracks {
fmt.Fprintf(tw, format, t.Title, t.Artist, t.Album, t.Year, t.Length)
}
tw.Flush()
}
type customSort struct {
t []*Track
less func(x, y *Track) bool
}
func (x customSort) Len() int {
return len(x.t)
}
func (x customSort) Less(i, j int) bool {
return x.less(x.t[i], x.t[j])
}
func (x customSort) Swap(i, j int) {
x.t[i], x.t[j] = x.t[j], x.t[i]
}
func main() {
sort.Sort(customSort{tracks, func(x, y *Track) bool {
if x.Title != y.Title {
return x.Title < y.Title
}
if x.Year != y.Year {
return x.Year < y.Year
}
if x.Length != y.Length {
return x.Length < y.Length
}
return false
}})
printTracks(tracks)
}
Title Artist Album Year Length
----- ------ ----- ---- ------
Go Moby Moby 1992 3m37s
Go Delilah From the Roots Up 2012 3m38s
Go Ahead Alicia Keys As I Am 2007 4m36s
Ready to Go Martin Solveig Smash 2011 4m24s
package main
import (
"fmt"
"sort"
)
func main() {
values := []int{3, 1, 4, 1}
fmt.Println(sort.IntsAreSorted(values)) // false
sort.Ints(values)
fmt.Println(values) // [1 1 3 4]
fmt.Println(sort.IntsAreSorted(values)) // true
sort.Sort(sort.Reverse(sort.IntSlice(values)))
fmt.Println(values) // [4 3 1 1]
fmt.Println(sort.IntsAreSorted(values)) // false
}
尽管 sqlQuote 支持任意类型的实参,但仅当实参类型能够符合类型分支中的一个时才能正常运行到结束,其他类型会抛错,表面上x的类型是 interface{},实际上我们把他当作 int, uint, bool, string 和 nil 的一个可识别联合体。
package main
import "fmt"
func sqlQuote(x interface{}) string {
switch x := x.(type) {
case nil:
return "NULL"
case int, uint:
return fmt.Sprintf("%d", x)
case bool:
if x {
return "TRUe"
}
case string:
return sqlQuote(x)
default:
panic(fmt.Sprintf("unexpected type %T: %v", x, x))
}
return ""
}
func main() {
fmt.Println(sqlQuote(nil))
}
package main
import (
"fmt"
"io"
"os"
"strings"
)
import "encoding/xml"
func containsAll(x, y []string) bool {
for len(y) <= len(x) {
if len(y) == 0 {
return true
}
if x[0] == y[0] {
y = y[1:]
}
x = x[1:]
}
return false
}
func main() {
dec := xml.NewDecoder(os.Stdin)
var stack []string
for {
tok, err := dec.Token()
if err == io.EOF {
break
}else if err != nil {
fmt.Fprintf(os.Stderr, "xmlselect:%v\n", err)
os.Exit(1)
}
switch tok := tok.(type) {
case xml.StartElement:
stack = append(stack, tok.Name.Local)
case xml.EndElement:
stack = stack[:len(stack)-1]
case xml.CharData:
if containsAll(stack, os.Args[1:]) {
fmt.Printf("%s: %s\n", strings.Join(stack, " "), tok)
}
}
}
}
main函数返回时,所有的goroutine都暴力直接终结,然后程序退出。
package main
import (
"fmt"
"time"
)
func main() {
go spinner(100 * time.Millisecond)
const n = 45
fibN := fib(n)
fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN) // Fibonacci(45) = 1134903170
}
func spinner(delay time.Duration) {
for {
for _, r := range `-\|/` {
fmt.Printf("\r%c", r)
time.Sleep(delay)
}
}
}
func fib(x int) int {
if x < 2 {
return x
}
return fib(x-1) + fib(x-2)
}
server
package main
import (
"io"
"log"
"net"
"time"
)
func main() {
listener, err := net.Listen("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Print(err)
continue
}
handleConn(conn)
}
}
func handleConn(c net.Conn) {
defer c.Close()
for {
_, err := io.WriteString(c, time.Now().Format("15:04:05\n"))
if err != nil {
return
}
time.Sleep(1*time.Second)
}
}
client
client从server 读取,然后写入到标准输出
package main
import (
"io"
"log"
"net"
"os"
)
func main() {
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
mustCopy(os.Stdout, conn)
}
func mustCopy(dst io.Writer, src io.Reader) {
if _, err := io.Copy(dst, src); err != nil {
log.Fatal(err)
}
}
若有多个客户端进行请求,则第二个客户端必须等第一个客户端结束才能正常工作,可以使用 goroutine 并发处理请求。
package main
import (
"io"
"log"
"net"
"time"
)
func main() {
listener, err := net.Listen("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Print(err)
continue
}
go handleConn(conn) // 并发处理连接
}
}
func handleConn(c net.Conn) {
defer c.Close()
for {
_, err := io.WriteString(c, time.Now().Format("15:04:05\n"))
if err != nil {
return
}
time.Sleep(1*time.Second)
}
}
server
package main
import (
"bufio"
"fmt"
"log"
"net"
"strings"
"time"
)
func main() {
listener, err := net.Listen("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Print(err)
continue
}
go handleConn(conn) // 并发处理连接
}
}
func handleConn(c net.Conn) {
input := bufio.NewScanner(c)
for input.Scan() {
go echo(c, input.Text(), 1*time.Second) // 并发处理同一个连接的多个呼叫
}
c.Close()
}
func echo(c net.Conn, shout string, delay time.Duration) {
fmt.Fprintln(c, "\t", strings.ToUpper(shout)) // 将呼叫转换后回写给客户端
time.Sleep(delay)
fmt.Fprintln(c, "\t", shout)
time.Sleep(delay)
fmt.Fprintln(c, "\t", strings.ToLower(shout))
}
client
package main
import (
"io"
"log"
"net"
"os"
)
func main() {
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
go mustCopy(os.Stdout, conn) // 并发将服务器的回声写到标准输出
mustCopy(conn, os.Stdin) // 通过标准输入进行呼叫
}
func mustCopy(dst io.Writer, src io.Reader) {
if _, err := io.Copy(dst, src); err != nil {
log.Fatal(err)
}
}
Hello
HELLO
Hello
hello
ch <- x // 发送语句
x = <-ch // 接收赋值
<-ch // 丢弃赋值
可以使用内置的 close 来关闭通道
无缓冲通道也称为同步通道,发送操作会阻塞,直到另一个 goroutine 在对应通道执行接收操作。
结束时,只有在通知接收方 goroutine所有数据都已发送完毕时才需要关闭管道。关闭 naturals 通道导致计算平方的循环快速运转,并将 0 传递给 printer
package main
import (
"fmt"
"time"
)
func main() {
naturals := make(chan int)
squares := make(chan int)
// counter
go func() {
for x := 0; x < 10; x++ {
naturals <- x
time.Sleep(time.Second)
}
close(naturals)
}()
// squarer
go func() {
for {
x, ok := <-naturals
if !ok {
break // 通道关闭且读完
}
squares <- x * x
}
close(squares)
}()
// printer
for {
fmt.Println(<-squares)
}
}
双向通道可以隐式转换成单向通道,比如 可以把 chan int 转换成输入通道 chan<- int 或者输出通道 <-chan int
package main
import "fmt"
// chan<- 发送(输入)管道,只允许往管道发送,不允许从管道接收
// <-chan 接收(输出)管道,只允许从管道接收,不允许从管道发送
func counter(out chan<- int) {
for x := 0; x <= 10; x++ {
out <- x
}
close(out)
}
func squarer(out chan<- int, in <-chan int) {
for v := range in {
out <- v * v
}
close(out)
}
func printer(in <-chan int) {
for v := range in {
fmt.Println(v)
}
}
func main() {
naturals := make(chan int)
squares := make(chan int)
go counter(naturals)
go squarer(squares, naturals)
printer(squares)
}
package main
import "fmt"
func main() {
ch := make(chan string, 3)
ch <- "A"
ch <- "B"
ch <- "C"
//ch <- "D" // 超过缓冲长度,阻塞
fmt.Println(<-ch) // A
fmt.Println(cap(ch)) // 缓冲区长度 3
fmt.Println(len(ch)) // 通道元素的长度 2
fmt.Println(<-ch) // B
ch <- "D"
ch <- "E"
}
package main
import "fmt"
func main() {
fmt.Println(mirroredQuery())
}
func mirroredQuery() string {
responses := make(chan string, 3)
go func() { responses <- request("asia.gopl.io") }()
go func() { responses <- request("europe.gopl.io") }()
go func() { responses <- request("americas.gopl.io") }()
return <-responses
}
func request(hostname string) (response string) {
return hostname
}
package main
import "fmt"
func main() {
ch := make(chan int, 1)
// 偶数只能往通道写,奇数只能从通道读取
for i := 0; i < 10; i++ {
fmt.Println("index", i)
select {
case x := <-ch:
fmt.Println(x) // 0 2 4 6 8
case ch <- i:
}
}
//index 0
//index 1
//0
//index 2
//index 3
//2
//index 4
//index 5
//4
//index 6
//index 7
//6
//index 8
//index 9
//8
}
select 的默认情况,用来指定没有其它通信时可以立即执行的操作(如果没有 default,有没有其它通信,接收操作将被阻塞)。
package main
import (
"fmt"
"os"
"time"
)
func main() {
fmt.Println("Commencing countdown.")
tick := time.Tick(time.Second)
abort := make(chan struct{})
go func() {
os.Stdin.Read(make([]byte, 1))
abort <- struct{}{}
}()
for countdown := 10; countdown > 0; countdown-- {
fmt.Println(countdown)
select {
case <-tick:
case <-abort:
fmt.Println("Launch aborted!")
return
default:
// nothing to do
}
}
print("Launch starting...")
}
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
)
func main() {
roots := []string{"."}
fileSizes := make(chan int64)
go func() {
for _, root := range roots {
walkDir(root, fileSizes)
}
close(fileSizes)
}()
var nfiles, nbytes int64
for size := range fileSizes {
nfiles++
nbytes += size
}
fmt.Printf("%d files %.1f KB\n", nfiles, float64(nbytes)/1e6)
}
func walkDir(dir string, fileSizes chan<- int64) {
for _, entry := range dirents(dir) {
if entry.IsDir() {
subdir := filepath.Join(dir, entry.Name())
walkDir(subdir, fileSizes)
} else {
fileSizes <- entry.Size()
}
}
}
func dirents(dir string) []os.FileInfo {
entries, err := ioutil.ReadDir(dir)
if err != nil {
fmt.Fprintf(os.Stderr, "du1: %v\n", err)
return nil
}
return entries
}
server
package main
import (
"bufio"
"fmt"
"log"
"net"
)
type client chan<- string
var (
entering = make(chan client)
leaving = make(chan client)
messages = make(chan string)
)
func main() {
listener, err := net.Listen("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
go broadcaster()
for {
conn, err := listener.Accept()
if err != nil {
log.Print(err)
continue
}
go handleConn(conn)
}
}
func broadcaster() {
clients := make(map[client]bool)
for {
select {
case msg := <-messages:
for cli := range clients {
cli <- msg
}
case cli := <-entering:
clients[cli] = true
case cli := <-leaving:
delete(clients, cli)
close(cli)
}
}
}
func handleConn(conn net.Conn) {
ch := make(chan string)
go clientWriter(conn, ch)
who := conn.RemoteAddr().String()
ch <- "You are " + who
messages <- who + " has arrived"
entering <- ch
input := bufio.NewScanner(conn)
for input.Scan() {
messages <- who + ": " + input.Text()
}
leaving <- ch
messages <- who + " has left"
conn.Close()
}
func clientWriter(conn net.Conn, ch <-chan string) {
for msg := range ch {
fmt.Fprintln(conn, msg)
}
}
cli1、cli2、cli3...
package main
import (
"io"
"log"
"net"
"os"
)
func main() {
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
mustCopy(os.Stdout, conn) // 将服务端的消息打印到标准输出
}
func mustCopy(dst io.Writer, src io.Reader) {
if _, err := io.Copy(dst, src); err != nil {
log.Fatal(err)
}
}
You are 127.0.0.1:51853
127.0.0.1:51855 has arrived
127.0.0.1:51855 has left
使用容量为1的通道来实现计数信号量
package main
import (
"fmt"
"sync"
)
var (
sema = make(chan struct{}, 1)
balance int
)
func Deposit(wg *sync.WaitGroup, amount int) {
defer wg.Done()
sema <- struct{}{} // 因为容量为1,若有其他 goroutine 进行写操作,将被阻塞
balance += amount
<-sema
}
func Balance() int {
sema <- struct{}{}
b := balance
<-sema
return b
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go Deposit(&wg, 5)
}
wg.Wait()
fmt.Println(Balance()) // 50
}
package main
import (
"fmt"
"sync"
)
var (
mu sync.Mutex
balance int
)
func Deposit(amount int) {
mu.Lock()
defer mu.Unlock()
deposit(amount)
}
func deposit(amount int) {
balance += amount
}
func Balance() int {
mu.Lock()
defer mu.Unlock()
return balance
}
func Withdraw(amount int) bool {
mu.Lock()
defer mu.Unlock()
Deposit(-amount)
if balance < 0 {
Deposit(amount)
return false
}
return true
}
func main() {
for i := 0; i < 10; i++ {
go Deposit(5)
}
fmt.Println(Balance())
}
package main
import (
"fmt"
"sync"
)
var (
mu sync.Mutex
rmu sync.RWMutex
balance int
)
func Deposit(amount int) {
mu.Lock()
defer mu.Unlock()
deposit(amount)
}
func deposit(amount int) {
balance += amount
}
func Balance() int {
// 共享锁,读取的时候可以同时运行
rmu.RLock()
defer rmu.RUnlock()
return balance
}
func Withdraw(amount int) bool {
mu.Lock()
defer mu.Unlock()
Deposit(-amount)
if balance < 0 {
Deposit(amount)
return false
}
return true
}
func main() {
for i := 0; i < 10; i++ {
go Deposit(5)
}
fmt.Println(Balance())
}
像通道通信和互斥锁操作的同步原语都会导致处理器把累积的写操作刷回内存并提交。因为赋值和 Print 对应不同的变量,所以编译器会认为这两个语句的执行顺序不会影响结果。这些并发问题可以采用成熟的模式来避免,即尽量把变量限制到单个的 goroutine 中,其他使用互斥锁。
package main
import (
"fmt"
"time"
)
func main() {
var x, y int
go func() {
x = 1
fmt.Print("y:", y, " ")
}()
go func() {
y = 1
fmt.Print("x:", x, " ")
}()
// x:1 y:0
// x:0 y:1
// y:0 x:1
// ...
time.Sleep(time.Second)
}
延迟一个昂贵的初始化步骤到实际需要时是一个很好的实践(比如Ruby的单例,调用 .instance 时才实例化一个对象)。
package main
import "image"
var icons map[string]image.Image
func loadIcons() {
icons = map[string]image.Image{
"spades.png": loadIcon("spades.png"),
"hearts.png": loadIcon("heats.png"),
"diamonds.png": loadIcon("diamonds.png"),
"clubs.png": loadIcon("clubs.png"),
}
}
func loadIcon(icon string) image.Image { return nil }
func Icon(name string) image.Image {
if icons == nil {
loadIcons()
}
return icons[name]
}
func main() {
}
因此,一个 goroutine 发现 icons 不是 nil 并不意味着变量的初始化已经完成(有可能才完成内存分配)。
package main
import (
"image"
"sync"
)
var mu sync.RWMutex
var icons map[string]image.Image
func loadIcons() {
icons = map[string]image.Image{
"spades.png": loadIcon("spades.png"),
"hearts.png": loadIcon("heats.png"),
"diamonds.png": loadIcon("diamonds.png"),
"clubs.png": loadIcon("clubs.png"),
}
}
func loadIcon(icon string) image.Image { return nil }
// 并发安全
func Icon(name string) image.Image {
// 为了避免不能并发访问变量,可以使用共享锁改进这个问题
mu.RLock()
if icons != nil {
icon := icons[name]
mu.RUnlock()
return icon
}
mu.RUnlock()
// 获取互斥锁
mu.Lock()
if icons != nil {
loadIcons()
}
icon := icons[name]
mu.Unlock()
return icon
}
func main() {
}
package main
import (
"fmt"
"io"
"os"
"reflect"
)
func main() {
t := reflect.TypeOf(3)
fmt.Println(t) // int
fmt.Println(t.String()) // int
fmt.Printf("%T\n", 3) // int
var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(w)) // *os.File
fmt.Println("--------")
v := reflect.ValueOf(3)
fmt.Println(v) // 3
x := v.Interface()
fmt.Println(x) // 3
i := x.(int)
fmt.Println(i) // 3
fmt.Printf("%v\n", v) // 3
fmt.Println(v.String()) // ,value 的 String() 方法仅仅暴露了类型
fmt.Println(v.Type()) // int
}
package main
import (
"fmt"
"os"
"reflect"
)
func main() {
x := 2
a := reflect.ValueOf(2) // 2
b := reflect.ValueOf(x) // 2
c := reflect.ValueOf(&x) // &x
d := c.Elem() // 2
fmt.Println(a.CanAddr()) // false
fmt.Println(b.CanAddr()) // false
fmt.Println(c.CanAddr()) // false
fmt.Println(d.CanAddr()) // true
px := d.Addr().Interface().(*int)
*px = 3
fmt.Println(x) // 3
d.Set(reflect.ValueOf(4))
fmt.Println(x) // 4
// d.SetString("hello") // panic
var y interface{}
ry := reflect.ValueOf(&y).Elem()
ry.Set(reflect.ValueOf(5))
// ry.SetInt(6) // panic
fmt.Println(y) // 5
stdout := reflect.ValueOf(os.Stdout).Elem()
fmt.Println(stdout.Type()) // os.File
fd := stdout.FieldByName("fd")
fmt.Println(fd.CanAddr(), fd.CanSet()) // false false
}
package main
import (
"fmt"
"unsafe"
)
var x struct {
a bool
b int16
c []int
}
func main() {
fmt.Println(unsafe.Sizeof(x), unsafe.Alignof(x)) // 32 8
fmt.Println(unsafe.Sizeof(x.a), unsafe.Alignof(x.a), unsafe.Offsetof(x.a)) // 1 1 0
fmt.Println(unsafe.Sizeof(x.b), unsafe.Alignof(x.b), unsafe.Offsetof(x.b)) // 2 2 2
fmt.Println(unsafe.Sizeof(x.c), unsafe.Alignof(x.c), unsafe.Offsetof(x.c)) // 24 8 8
}