這是用戶在 2024-6-22 9:07 為 https://ereader.perlego.com/1/book/4378377/4?element_originalid=_idParaDest-14 保存的雙語快照頁面,由 沉浸式翻譯 提供雙語支持。了解如何保存?
Timer

:

Mastering Go 掌握Go
A Quick Introduction to Go
Go 快速簡介

1

A Quick Introduction to Go
Go 快速簡介

Despite its name, this chapter is more than just a quick introduction to Go, as it is also going to be the foundation for the rest of the book. The basics of Go, some design decisions, and the philosophy of Go are explained in this chapter so that you can get the big picture before learning the details of Go. Among other things, we present the advantages and disadvantages of Go so that you know when to use Go and when to consider other alternatives.
儘管它的名字如此,本章不僅僅是對 Go 的快速介紹,因為它也將成為本書其餘部分的基礎。本章解釋了 Go 的基礎知識、一些設計決策以及 Go 的原理,以便您在學習 Go 的優點和缺點,以便您知道何時使用 Go 以及何時考慮其他替代方案。
In the sections that follow, we cover a number of concepts and utilities in order to build a solid foundation of Go, before building a simplified version of the which(1) utility, which is a UNIX utility that locates program files by searching the directories of the PATH environment variable. Additionally, we explain how to write information in log files, as this can help you store error messages and warnings while you are developing software in Go.
在接下來的部分中,我們將介紹一些概念和實用程序,以便在構建 which(1) 實用程式(UNIX 中的一個)的簡化版本之前為Go 打下堅實的基礎。此外,我們還解釋瞭如何在日誌檔案中寫入訊息,因為這可以幫助您在 Go 中開發軟體時儲存錯誤訊息和警告。
At the end of the chapter, we develop a basic command line utility that computes basic statistical properties. It is that command line utility that we are going to improve and expand in the remaining book chapters as we learn more advanced Go features.
在本章的最後,我們開發了一個計算基本統計屬性的基本命令列實用程式。當我們學習更進階的 Go 功能時,我們將在本書的其餘章節中改進和擴展這個命令列實用程式。
The contents of this chapter are:
本章內容為:
  • Introducing Go 介紹Go
  • When to use Go
    何時使用Go
  • Hello World! 你好世界!
  • Running Go code 運行 Go 程式碼
  • What you should know about Go
    關於 Go 您應該了解的內容
  • Developing the which(1) utility in Go
    在 Go 中開發 which(1) 實用程式
  • Logging information 記錄資訊
  • Developing a statistics application
    開發統計應用程式

Introducing Go 介紹Go

Go is an open-source systems programming language, initially developed as an internal Google project that went public back in 2009. The spiritual fathers of Go are Robert Griesemer, Ken Thomson, and Rob Pike.
Go 是一種開源系統程式語言,最初是作為 Google 內部專案開發的,於 2009 年公開。
Although the official name of the language is Go, it is sometimes (wrongly) referred to as Golang. The official reason for this is that https://go.org/ was not available for registration and golang.org was chosen instead—however, nowadays, the official Go website is https://go.dev/. Keep in mind that when you are querying a search engine for Go-related information, the word Go is usually interpreted as a verb; therefore, you should search for golang instead. Additionally, the official Twitter hashtag for Go is #golang.
儘管該語言的官方名稱是 Go,但有時(錯誤地)將其稱為 Golang。官方原因是 https://go.org/ 無法註冊,因此選擇了 golang.org — 然而,現在,官方 Go 網站是 https ://go 相關的資訊時,單字 Go 通常被解釋為動詞;因此,您應該搜尋 golang。此外,Go 的官方 Twitter 主題標籤是#golang。
Let us now discuss the history of Go and what that means for someone who wants to learn Go.
現在讓我們討論 Go 的歷史以及這對想要學習 Go 的人意味著什麼。

The history of Go
Go的歷史

As mentioned earlier, Go started as an internal Google project that went public back in 2009. Griesemer, Thomson, and Pike designed Go as a language for professional programmers who want to build reliable, robust, and efficient software that is easy to manage. They designed Go with simplicity in mind, even if simplicity meant that Go was not going to be a programming language for everyone or everything.
如前所述,Go 最初是一個Google 內部項目,於2009 年公開。他們建立可靠的、強大、高效且易於管理的軟體。他們設計 Go 時考慮到了簡單性,即使簡單性意味著 Go 不會成為適合所有人或所有事物的程式語言。
The figure that follows shows the programming languages that directly or indirectly influenced Go. As an example, Go syntax looks like C, whereas the package concept was inspired by Modula-2.
下圖顯示了直接或間接影響Go的程式語言。例如,Go 語法看起來像 C,而套件概念則受到 Modula-2 的啟發。
A group of white squares with black text  Description automatically generated with low confidence
Figure 1.1: The programming languages that influenced Go
圖 1.1:影響 Go 的程式語言
The deliverable was a programming language with tools and a standard library. What you get with Go, apart from its syntax and tools, is a rich standard library and a type system that tries to save you from easy mistakes, such as implicit type conversions, unused variables, and unused packages. The Go compiler catches most of these easy mistakes and refuses to compile until you do something about them. Additionally, the Go compiler can find difficult-to-catch mistakes such as race conditions.
交付成果是一種帶有工具和標準函式庫的程式語言。除了語法和工具之外,Go 還提供了豐富的標準庫和類型系統,可以幫助您避免簡單的錯誤,例如隱式類型轉換、未使用的變數和未使用的套件。 Go 編譯器會捕捉大多數這些簡單的錯誤,並拒絕編譯,直到您採取一些措施來解決它們。此外,Go編譯器可以發現難以捕捉的錯誤,例如競爭條件。
If you are going to install Go for the first time, you can start by visiting https://go.dev/dl/. However, there is a big chance that your UNIX variant has a ready-to-install package for the Go programming language, so you might want to get Go by using your favorite package manager.
如果您打算首次安裝 Go,可以透過造訪 https://go.dev/dl/ 開始。但是,您的 UNIX 變體很可能具有用於 Go 程式語言的現成安裝包,因此您可能想要使用您最喜歡的套件管理器來取得 Go 。
As Go is a portable programming language, almost all presented code is going to work fine on any modern Microsoft Windows, Linux, or macOS machine without any changes. The only Go code that might need some small or big adjustments is the code that deals with the operating system. Most of that code is covered in Chapter 7, Telling a UNIX System What to Do.
由於 Go 是一種可移植的程式語言,因此幾乎所有提供的程式碼都可以在任何現代 Microsoft Windows、Linux 或 macOS 電腦上正常運行,無需任何更改。唯一可能需要進行一些小或大調整的Go程式碼是處理作業系統的程式碼。大部分程式碼都在第 7 章「告訴 UNIX 系統做什麼」中介紹。

The advantages of Go
Go的優點

Go comes with some important advantages for developers, starting with the fact that it was designed and is currently maintained by real programmers. Go is also easy to learn, especially if you are already familiar with programming languages such as C, Python, or Java. On top of that, due to its simplified and elegant syntax, Go code is pleasant to the eye, which is great, especially when you are programming applications for a living and you have to look at code on a daily basis. Go code is also easy to read, which means that you can make changes to existing Go code easily, and offers support for Unicode out of the box. Lastly, Go has reserved only 25 keywords, which makes it much easier to remember the language. Can you do that with C++?
Go 為開發人員帶來了一些重要的優勢,首先是它是由真正的程式設計師設計和目前所維護的。 Go 也很容易學習,特別是如果您已經熟悉 C、Python 或 Java 等程式語言。最重要的是,由於其簡單而優雅的語法,Go代碼賞心悅目,這很棒,尤其是當你以編程為生,每天都必須看代碼時基礎。 Go 程式碼也容易閱讀,這意味著您可以輕鬆更改現有 Go 程式碼,並提供開箱即用的 Unicode 支援。最後,Go只保留了25個關鍵字,這使得語言更容易記住。你能用 C++ 做到這一點嗎?
Go also comes with concurrency capabilities, using a simple concurrency model that is implemented using goroutines and channels. Go manages OS threads for you and has a powerful runtime that allows you to spawn lightweight units of work (goroutines) that communicate with each other using channels.
Go 還具有並發功能,使用使用 goroutine 和通道實現的簡單並發模型。 Go 為您管理作業系統線程,並具有強大的運行時,可讓您產生使用通道相互通訊的輕量級工作單元(goroutine)。
Although Go comes with a rich standard library, there are really handy Go packages, such as cobra and viper, that allow Go to develop complex command line utilities such as docker and hugo. This is greatly supported by the fact that executable binaries are statically linked, which means that once they are generated, they do not depend on any shared libraries and include all required information. In practice, this means that you can transfer an existing executable file to a different machine with the same architecture and be sure that it is going to run without any issues.
雖然 Go 附帶了豐富的標準庫,但也有非常方便的 Go 包,例如 cobraviper ,它們允許 開發複雜的命令列實用程序,例如 dockerhugo 。可執行二進位檔案是靜態連結的這一事實極大地支持了這一點,這意味著一旦生成它們,它們就不依賴任何共享庫並包含所有必需的資訊。實際上,這意味著您可以將現有的可執行檔傳輸到具有相同架構的不同計算機,並確保它在運行時不會出現任何問題。
Due to its simplicity, Go code is predictable and does not have strange side effects, and although Go supports pointers, it does not support pointer arithmetic like C, unless you use the unsafe package, which can be the root of many bugs and security holes. Although Go is not an object-oriented programming language, Go interfaces are very versatile and allow you to mimic some of the capabilities of object-oriented languages, such as polymorphism, encapsulation, and composition. However, Go offers no support for classes and inheritance. Chapter 5, Reflection and Interfaces, provides more information on the subject.
由於其簡單性,Go程式碼是可預測的,並且不會產生奇怪的副作用,並且雖然Go支援指針,但它不支援像C那樣的指針算術,除非你使用 unsafe 包,這可能是許多錯誤和安全漏洞的根源。雖然 Go 不是物件導向的程式語言,但 Go 介面非常通用,讓您可以模仿物件導向語言的一些功能,例如多態性、封裝性和作品。但是,Go 不提供對類別和繼承的支援。第 5 章「反射和介面」提供了有關該主題的更多資訊。
Additionally, the latest Go versions offer support for generics, which simplifies your code when working with multiple data types. You can learn more about Go generics in Chapter 4, Go Generics. Finally, Go is a garbage-collected language, which means that no manual memory management is needed.
此外,最新的 Go 版本提供對泛型的支持,這可以在處理多種資料類型時簡化您的程式碼。您可以在第 4 章 Go 泛型中了解更多關於 Go 泛型的資訊。最後,Go是一種垃圾收集語言,這意味著不需要手動記憶體管理。

When to use Go
何時使用Go

Although Go is a general-purpose programming language, it is primarily used for writing system tools, command line utilities, web services, and software that works over networks and the internet. You can use Go for teaching programming, and it is a good candidate as your first programming language because of its lack of verbosity and clear ideas and principles.
儘管 Go 是一種通用程式語言,但它主要用於編寫系統工具、命令列實用程式、Web 服務以及透過網路和 Internet 運行的軟體。您可以使用 Go 來教授編程,並且它是作為您的第一編程語言的良好候選者,因為它缺乏冗長且清晰的思想和原則。
Go can help you develop the following kinds of applications:
Go可以幫助您開發以下類型的應用程式:
  • Professional web services
    專業網路服務
  • Networking tools and servers such as Kubernetes and Istio
    網路工具和伺服器,例如 Kubernetes 和 Istio
  • Backend systems 後端系統
  • Robust UNIX and Windows system tools
    強大的 UNIX 和 Windows 系統工具
  • Servers that work with APIs and clients that interact by exchanging data in myriad formats, including JSON, XML, and CSV
    使用 API 的伺服器和透過多種格式(包括 JSON、XML 和 CSV)交換資料進行互動的用戶端
  • WebSocket servers and clients
    WebSocket 伺服器和用戶端
  • gRPC (Remote Procedure Call) servers and clients
    gRPC(遠端過程呼叫)伺服器與客戶端
  • Complex command line utilities with multiple commands, sub-commands, and command line parameters, such as docker and hugo
    具有多個命令、子命令和命令列參數的複雜命令列實用程序,例如 dockerhugo
  • Applications that exchange data in the JSON format
    以 JSON 格式交換資料的應用程式
  • Applications that process data from relational databases, NoSQL databases, or other popular data storage systems
    處理來自關聯式資料庫、NoSQL 資料庫或其他流行資料儲存系統的資料的應用程式
  • Compilers and interpreters for your own programming languages
    適用於您自己的程式語言的編譯器和解釋器
  • Database systems such as CockroachDB and key/value stores such as etcd
    資料庫系統(例如 CockroachDB)和鍵/值儲存(例如 etcd)
Although Go is a very practical and competent programming language, it is not perfect:
雖然 Go 是一種非常實用且稱職的程式語言,但它並不完美:
  • This is a personal preference rather than an actual technical shortcoming: Go has no direct and full support for object-oriented programming, which is a popular programming paradigm.
    這是個人偏好,而不是實際的技術缺點:Go沒有對物件導向程式設計的直接和完整支持,這是一種流行的程式範例。
  • Although goroutines are lightweight, they are not as powerful as OS threads. Depending on the application you are trying to implement, there might exist some rare cases where goroutines will not be appropriate for the job. The Apache web server creates UNIX processes with fork(2) to serve its clients—Go does not support the functionality of fork(2). However, in most cases, designing your application with goroutines and channels in mind will solve your problems.
    雖然 goroutine 很輕量級,但它們不如作業系統線程強大。根據您嘗試實現的應用程序,可能存在一些罕見的情況,其中 goroutine 不適合該工作。 Apache Web 伺服器使用 fork(2) 建立 UNIX 程序來為其客戶端提供服務 - Go 不支援 fork(2) 的功能。然而,在大多數情況下,在設計應用程式時考慮 goroutine 和通道將解決您的問題。
  • Although garbage collection is fast enough most of the time, and for almost all kinds of applications, there are times when you need to handle memory allocation manually, such as when developing an operating system or working with large chunks of memory and want to avoid fragmentation—Go cannot do that. In practice, this means that Go will not allow you to perform any memory management manually.
    雖然垃圾收集在大多數情況下都足夠快,並且對於幾乎所有類型的應用程式來說,但有時您需要手動處理記憶體分配,例如在開發作業系統或使用大塊記憶體並希望避免碎片時—<b1001 ></b1001> 不能這樣做。實際上,這意味著 Go 不允許您手動執行任何記憶體管理。
  • Go does not offer the full functionality of a functional programming language.
    Go 不提供函數式程式語言的全部功能。
  • Go is not good at developing systems with high availability guarantees. In such cases, use Erlang or Elixir instead.
    Go不擅長開發具有高可用性保證的系統。在這種情況下,請改用 Erlang 或 Elixir。
There are many things that Go does better than other programming languages, including the following:
Go 在許多方面都比其他程式語言做得更好,包括:
  • The Go compiler catches a large set of silly errors that might end up being bugs. This includes imported Go packages and variables that are not being used in the code.
    Go 編譯器捕捉大量愚蠢的錯誤,這些錯誤最終可能會成為錯誤。這包括導入的 Go 套件和程式碼中未使用的變數。
  • Go uses fewer parentheses than C, C++, or Java, and no semicolons, which makes Go source code more human-readable and less error-prone.
    Go 使用的括號比 C、C++ 或 Java 少,且不使用分號,這使得 Go 原始碼更容易閱讀且不易出錯。
  • Go comes with a rich and reliable standard library that keeps improving.
    Go 配備了豐富且可靠且不斷改進的標準庫。
  • Go has support for concurrency out of the box through goroutines and channels.
    Go 透過 goroutine 和通道支援開箱即用的並發。
  • Goroutines are lightweight. You can easily run thousands of goroutines on any modern machine without any performance issues.
    Goroutines 是輕量級的。您可以在任何現代機器上輕鬆運行數千個 goroutine,而不會出現任何效能問題。
  • Unlike C, Go considers functions as first-class citizens.
    與 C 不同,Go 將函數視為一等公民。
  • Go code is backward compatible, which means that newer versions of the Go compiler accept programs that were created using a previous version of the language without any modifications. This compatibility guarantee is limited to major versions of Go. For example, there is no guarantee that a Go 1.x program will compile with Go 2.x.
    Go 程式碼向後相容,這意味著較新版本的 Go 編譯器接受使用該語言的早期版本建立的程序,無需任何修改。此相容性保證僅限於 Go 的主要版本。例如,不能保證 Go 1.x 程式可以用 Go 2.x 進行編譯。
The next subsection describes my personal Go journey.
下一小節描述我個人的Go旅程。

My personal Go journey
我個人的Go旅程

In this subsection, I am going to tell you my personal story of how I ended up learning and using Go. I am a UNIX person, which means that I like UNIX and prefer to use it whenever possible. I also love C, and I used to like C++; I wrote a command line FTP client in C++ for my M.Sc. project. Nowadays, C++ is just a huge programming language that is difficult to learn. Although C continues to be a decent programming language, it requires lots of code to perform simple tasks and suffers from difficult-to-find and correct bugs, due to manual memory management and extremely flexible conversion between different data types without any warnings or error messages.
在本小節中,我將向您講述我如何最終學習和使用 Go 的個人故事。我是 UNIX 人,這意味著我喜歡 UNIX 並且喜歡盡可能使用它。我也喜歡C,以前也喜歡C++;我為我的碩士學位用 C++ 編寫了一個命令列 FTP 用戶端。專案.如今,C++只是一門龐大且難以學習的程式語言。儘管C 仍然是一種不錯的程式語言,但它需要大量程式碼來執行簡單的任務,並且由於手動記憶體管理和不同資料類型之間極其靈活的轉換而沒有任何警告或錯誤訊息,因此存在難以發現和糾正的錯誤。
As a result, I used to use Perl to write simple command line utilities. However, Perl is far from perfect for writing serious command line tools and services, as it is a scripting programming language and is not intended for web development.
因此,我過去常常使用 Perl 來編寫簡單的命令列實用程式。然而,Perl 對於編寫嚴肅的命令列工具和服務來說還遠遠不夠完美,因為它是一種腳本程式語言,並不適合 Web 開發。
When I first heard about Go, that it was developed by Google, and that both Rob Pike and Ken Thomson were involved in its development, I instantly became interested in Go.
當我第一次聽說 Go,它是由 Google 開發的,並且 Rob Pike 和 Ken Thomson 都參與了它的開發時,我立即對 Go 產生了興趣。
Since then, I have used Go to create web services, servers, and clients that communicate with RabbitMQ, MySQL, and PostgreSQL, create simple command line utilities, implement algorithms for time series data mining, create utilities that generate synthetic data, etc.
從那時起,我使用Go 創建與RabbitMQ、MySQL 和PostgreSQL 通訊的Web 服務、伺服器和客戶端,創建簡單的命令列實用程序,實現時間序列資料探勘演算法,創建生成生成的實用程式綜合數據等
Soon, we are going to move on to actually learn some Go, using Hello World! as the first example, but before that, we will present the go doc command, which allows you to find information about the Go standard library, its packages, and their functions, as well as the godoc utility.
很快,我們將繼續使用 Hello World! 來實際學習一些Go。作為第一個範例,但在此之前,我們將介紹 go doc 命令,它允許您查找有關 Go 標準庫、其包及其函數的信息,以及 godoc 實用程式。
If you have not already installed Go, this is the right time to do so. To do that, visit https://go.dev/dl/ or use your favorite package manager.
如果您尚未安裝Go,現在正是安裝的最佳時機。為此,請造訪 https://go.dev/dl/ 或使用您最喜歡的套件管理器。

The go doc and godoc utilities
go doc 和 godoc 實用程序

The Go distribution comes with a plethora of tools that can make your life as a programmer easier. Two of these tools are the go doc subcommand and godoc utility, which allow you to see the documentation of existing Go functions and packages without needing an internet connection. However, if you prefer viewing the Go documentation online, you can visit https://pkg.go.dev/.
Go 發行版附帶了大量工具,可以讓您作為程式設計師的生活變得更加輕鬆。其中兩個工具是 go doc 子命令和 godoc 實用程序,它們允許您查看現有 Go 函數和套件的文檔,而無需 Internet 連接。但是,如果您更喜歡在線查看 Go 文檔,可以訪問 https://pkg.go.dev/。
The go doc command can be executed as a normal command line application that displays its output on a terminal, and it is similar to the UNIX man(1) command, but for Go functions and packages only. So, in order to find out information about the Printf() function of the fmt package, you should execute the following command:
go doc 命令可以作為普通命令列應用程式執行,在終端機上顯示其輸出,它類似於 UNIX man(1) 命令,但對於 Go 套件的 Printf() 函數的信息,您應該執行以下命令:
$ go doc fmt.Printf
Similarly, you can find out information about the entire fmt package by running the following command:
同樣,您可以透過執行以下命令找到整個 fmt 套件的資訊:
$ go doc fmt
As godoc is not installed by default, you might need to install it by running go install golang.org/x/tools/cmd/godoc@latest. The godoc binary is going to be installed in ~/go/bin, and you can execute it as ~/go/bin/godoc unless ~/go/bin is in your PATH environment variable.
由於預設未安裝 godoc ,因此您可能需要透過執行 go install golang.org/x/tools/cmd/godoc@latest 來安裝它。 godoc 二進位檔案將安裝在 ~/go/bin 中,並且您可以將其作為 ~/go/bin/godoc 執行,除非 ~/go/bin 位於您的 PATH 環境變數。
The godoc command line application starts a local web server. So you need a web browser to look at the Go documentation.
godoc 命令列應用程式啟動本機 Web 伺服器。因此,您需要一個網頁瀏覽器來查看 Go 文件。
Running godoc requires executing godoc with the -http parameter:
運行 godoc 需要使用 -http 參數執行 godoc
$ ~/go/bin/godoc -http=:8001
The numeric value in the preceding command, which in this case is 8001, is the port number that the HTTP server will listen to. As we have omitted the IP address, godoc is going to listen to all network interfaces.
前面指令中的數值(在本例中為 8001 )是 HTTP 伺服器將偵聽的連接埠號碼。由於我們省略了 IP 位址, godoc 將偵聽所有網路介面。
You can choose any port number that is available if you have the right privileges. However, note that port numbers 01023 are restricted and can only be used by the root user, so it is better to avoid choosing one of those and pick something else, if it is not already in use by a different process. Port number 8001 is usually free and is frequently used for local HTTP servers.
如果您有適當的權限,您可以選擇任何可用的連接埠號碼。但是,請注意,連接埠號碼 01023 受到限制,只能由 root 使用者使用,因此最好避免選擇其中一個並選擇其他連接埠(如果需要)尚未被其他進程使用。連接埠號碼 8001 通常是免費的,並且經常用於本地 HTTP 伺服器。
You can omit the equals sign in the presented command and put a space character in its place. So the following command is completely equivalent to the previous one:
您可以省略所顯示命令中的等號,並在其位置放置一個空格字元。所以下面的指令與上一個指令完全等效:
$ ~/go/bin/godoc -http :8001
After that, you should point your web browser to http://localhost:8001/ in order to get the list of available Go packages and browse their documentation. If no -http parameter is provided, godoc listens to port 6060.
之後,您應該將網頁瀏覽器指向 http://localhost:8001/ 以取得可用 Go 軟體包的清單並瀏覽其文件。如果未提供 -http 參數,則 godoc 偵聽連接埠 6060
If you are using Go for the first time, you will find the Go documentation very handy for learning the parameters and the return values of the functions you want to use — as you progress in your Go journey, you will use the Go documentation to learn the gory details of the functions and variables that you want to use.
如果您是第一次使用 Go,您會發現 Go 文件對於學習您想要使用的函數的參數和返回值非常方便 - 隨著您的進展在您的Go 在旅程中,您將使用Go 文件來了解要使用的函數和變數的詳細資訊。
The next section presents the first Go program of the book and explains the basic concepts of Go.
下一節介紹本書的第一個 Go 程序,並解釋 Go 的基本概念。

Hello World! 你好世界!

The following is the Go version of the Hello World! program. Please type it and save it as hw.go:
以下是Go版本的Hello World!程式.請輸入它並將其另存為 hw.go
package main
import (
    "fmt"
)
func main() {
    fmt.Println("Hello World!")
}
If you are eager to execute hw.go, type go run hw.go in the same directory where you save it. The file can also be found in the ch01 directory of the GitHub repository of the book.
如果您急於執行 hw.go ,請在儲存它的相同目錄中鍵入 go run hw.go 。該檔案也可以在本書的 GitHub 儲存庫的 ch01 目錄中找到。
Each Go source code begins with a package declaration. In this case, the name of the package is main, which has a special meaning in Go—autonomous Go programs should use the main package. The import keyword allows you to include functionality from existing packages. In our case, we only need some of the functionality of the fmt package that belongs to the standard Go library, implementing formatted input and output with functions that are analogous to C’s printf() and scanf(). The next important thing if you are creating an executable application is a main() function. Go considers this the entry point to the application, and it begins the execution of an application with the code found in the main() function of the main package.
每個 Go 原始碼都以套件聲明開頭。在本例中,套件的名稱是 main ,它在 Go 中具有特殊意義 - 自治 Go 程式應使用 main 套件。 import 關鍵字可讓您包含現有套件中的功能。在我們的例子中,我們只需要屬於標準 Go 函式庫的 fmt 套件的一些功能,使用類似 C 的 printf() 。如果您要建立可執行應用程序,下一個重要的事情是 main() 函數。 Go 將此視為應用程式的入口點,並使用 main 套件的 main() 函數中找到的程式碼開始執行應用程式。
hw.go is a Go program that runs on its own. Two characteristics make hw.go a source file that can generate an executable binary: the name of the package, which should be main, and the presence of the main() function—we discuss Go functions in more detail in the next subsection, but we will learn even more about functions and methods, which are functions attached to specific data types, in Chapter 6, Go Packages and Functions.
hw.go 是一個獨立運作的Go 程式。有兩個特徵使 hw.go 成為可以產生可執行二進位檔案的來源檔案:包的名稱(應為 main )以及 main() 函數的存在—我們將在下一小節中更詳細地討論Go 函數,但我們將在第6 章Go 中了解更多有關函數和方法的信息,這些函數和方法是附加到特定資料類型的函數包和功能。

Introducing functions 功能介紹

Each Go function definition begins with the func keyword, followed by its name, signature, and implementation. Apart from the main() function, which has a special purpose, you can name the rest of your functions anything you want—there is a global Go rule that also applies to function and variable names and is valid for all packages except main: everything that begins with a lowercase letter is considered private and is accessible in the current package only. We will learn more about that rule in Chapter 6, Go Packages and Functions. The only exception to this rule is package names, which can begin with either lowercase or uppercase letters. Having said that, I am not aware of a Go package that begins with an uppercase letter!
每個 Go 函數定義都以 func 關鍵字開頭,後面接著其名稱、簽章和實作。除了具有特殊用途的 main() 函數之外,您可以將其餘函數命名為任何您想要的名稱 - 有一條全域 Go 規則也適用於函數和變數名稱並且對除main 之外的所有套件都有效:以小寫字母開頭的所有內容都被視為私有,並且只能在目前套件中存取。我們將在第 6 章 Go 套件和函數中了解有關該規則的更多資訊。此規則的唯一例外是套件名稱,它可以以小寫或大寫字母開頭。話雖如此,我不知道有一個以大寫字母開頭的 Go 包!
You might now ask how functions are organized and delivered. Well, the answer is in packages—the next subsection sheds some light on that.
您現在可能會問功能是如何組織和交付的。嗯,答案就在包中——下一小節將對此進行一些闡述。

Introducing packages 介紹套餐

Go programs are organized in packages—even the smallest Go program should be delivered as a package. The package keyword helps you define the name of a new package, which can be anything you want, with just one exception: if you are creating an executable application and not just a package that will be shared by other applications or packages, you should name your package main. You will learn more about developing Go packages in Chapter 6, Go Packages and Functions.
Go 程式以包的形式組織 — 即使是最小的 Go 程式也應作為包交付。 package 關鍵字可協助您定義新套件的名稱,該名稱可以是您想要的任何名稱,但有一個例外:如果您正在建立一個可執行應用程序,而不僅僅是一個將由其他應用程式共用的套件或套件,您應該將套件命名為 main 。您將在第 6 章 Go 套件和函數中了解有關開發 Go 套件的更多資訊。
Packages can be used by other packages. In fact, reusing existing packages is a good practice that saves you from having to write lots of code or implement existing functionality from scratch.
包可以被其他包使用。事實上,重複使用現有的套件是一種很好的做法,可以讓您不必編寫大量程式碼或從頭開始實現現有功能。
The import keyword is used for importing other Go packages into your Go programs to use some or all of their functionality. A Go package can either be a part of the rich Standard Go library or come from an external source. Packages of the standard Go library are imported by name, for example, import "os" to use the os package, whereas external packages like github.com/spf13/cobra are imported using their full URLs: import "github.com/spf13/cobra".
import 關鍵字用於將其他 Go 套件匯入至 Go 程式中以使用其部分或全部功能。 Go 套件可以是豐富的標準 Go 函式庫的一部分,也可以來自外部來源。標準 Go 庫的套件依名稱匯入,例如 import "os" 使用 os 套件,而外部套件如 github.com/spf13/cobra 使用其完整URL 導入: import "github.com/spf13/cobra"

Running Go code 運行 Go 程式碼

You now need to know how to execute hw.go or any other Go application. As will be explained in the two subsections that follow, there are two ways to execute Go code: as a compiled language, using go build, or by mimicking a scripting language, using go run. So let us find out more about these two ways of running Go code.
現在您需要知道如何執行 hw.go 或任何其他 Go 應用程式。正如接下來的兩小節中將解釋的,有兩種方法可以執行 Go 程式碼:作為編譯語言,使用 go build ,或透過模仿腳​​本語言,使用 go run

Compiling Go code 編譯 Go 程式碼

To compile Go code and create a binary executable file, we need to use the go build command. What go build does is create an executable file for us to distribute and execute manually. This means that when using go build, an extra step is required to run the executable file.
要編譯 Go 程式碼並建立二進位執行文件,我們需要使用 go build 命令。 go build 所做的是建立一個可執行檔供我們手動分發和執行。這表示使用 go build 時,需要額外的步驟來執行可執行檔。
The generated executable is automatically named after the source code filename without the .go file extension. Therefore, because of the hw.go source filename, the executable will be called hw. If this is not what you want, go build supports the -o option, which allows you to change the filename and the path of the generated executable file. As an example, if you want to name the executable file a helloWorld, you should execute go build -o helloWorld hw.go instead. If no source files are provided, go build looks for a main package in the current directory.
產生的執行檔會自動以原始碼檔案名稱命名,不含 .go 檔案副檔名。因此,由於 hw.go 原始檔名,可執行檔將被稱為 hw 。如果這不是您想要的, go build 支援 -o 選項,該選項可讓您變更產生的執行檔的檔案名稱和路徑。例如,如果您想要將可執行檔命名為 helloWorld ,則應該執行 go build -o helloWorld hw.go 。如果未提供原始文件, go build 將在目前目錄中尋找 main 套件。
After that, you need to execute the generated executable binary file on your own. In our case, this means executing either hw or helloWorld. This is shown in the following output:
之後,您需要自行執行生成的可執行二進位。在我們的例子中,這意味著執行 hwhelloWorld 。這顯示在以下輸出:
$ go build hw.go
$ ./hw
Hello World!
Now that we know how to compile Go code, let us continue using Go as if it were a scripting language.
現在我們知道如何編譯 Go 程式碼,讓我們繼續使用 Go ,就好像它是腳本語言一樣。

Using Go like a scripting language
像腳本語言一樣使用 Go

The go run command builds the named Go package, which in this case is the main package implemented in a single file, creates a temporary executable file, executes that file, and deletes it once it is done—to our eyes, this looks like using a scripting language while the Go compiler still creates a binary executable. In our case, we can do the following:
go run 指令建構命名的 Go 包,在本例中是在單一文件中實現的 main 包,建立一個臨時執行文件,執行該文件,並在完成後將其刪除- 在我們看來,這看起來就像使用腳本語言,而Go 編譯器仍然創建二進制可執行檔。在我們的例子中,我們可以執行以下操作:
$ go run hw.go
Hello World!
Using go run is a better choice when testing code. However, if you want to create and distribute an executable binary, then go build is the way to go.
測試程式碼時使用 go run 是更好的選擇。但是,如果您想要建立並分發可執行二進位文件,則 go build 就是 go 的方法。

Important formatting and coding rules
重要的格式和編碼規則

You should know that Go comes with some strict formatting and coding rules that help a developer avoid rookie mistakes and bugs—once you learn these few rules and Go idiosyncrasies as well as the implications they have on your code, you will be free to concentrate on the actual functionality of your code. Additionally, the Go compiler is here to help you follow these rules with its expressive error messages and warnings. Last, Go offers standard tooling (gofmt) that can format your code for you, so you never have to think about it.
您應該知道,Go 附帶了一些嚴格的格式和編碼規則,可以幫助開發人員避免菜鳥錯誤和錯誤 - 一旦您了解了這些規則和 Go 特性以及它們的含義有了您的程式碼,您就可以自由地專注於程式碼的實際功能。此外,Go 編譯器可以透過其表達性錯誤訊息和警告來幫助您遵循這些規則。最後, Go 提供了標準工具 ( gofmt ),可以為您格式化程式碼,因此您無需考慮它。
The following is a list of important Go rules that will help you while reading this chapter:
以下是重要的 Go 規則列表,將在您閱讀本章時提供協助:
  • Go code is delivered in packages, and you are free to use the functionality found in existing packages. There is a Go rule that says that if you import a package, you should use it in some way (call a function or use a datatype), or the compiler is going to complain. There exist exceptions to this rule that mainly have to do with packages that initialize connections with database and TCP/IP servers, but they are not important for now. Packages are covered in Chapter 6, Go Packages and Functions.
    Go 程式碼以軟體包形式提供,您可以自由使用現有軟體包中的功能。有一條 Go 規則,規定如果導入包,則應該以某種方式使用它(調用函數或使用資料類型),否則編譯器會抱怨。此規則存在一些例外情況,主要與初始化與資料庫和 TCP/IP 伺服器的連接的包有關,但目前它們並不重要。包在第 6 章 Go 包和函數中介紹。
  • You either use a variable or you do not declare it at all. This rule helps you avoid errors such as misspelling an existing variable or function name.
    您要么使用變量,要么根本不聲明它。此規則可協助您避免錯誤,例如拼字錯誤現有變數或函數名稱。
  • There is only one way to format curly braces in Go.
    Go 中只有一種格式花括號的方法。
  • Coding blocks in Go are embedded in curly braces, even if they contain just a single statement or no statements at all.
    Go 中的編碼區塊嵌入在花括號中,即使它們只包含單一語句或根本不包含任何語句。
  • Go functions can return multiple values.
    Go 函數可以傳回多個值。
  • You cannot automatically convert between different data types, even if they are of the same kind. As an example, you cannot implicitly convert an integer to a floating point.
    您無法在不同資料類型之間自動轉換,即使它們屬於同一類型。例如,您不能將整數隱式轉換為浮點數。
Go has more rules, but the preceding ones are the most important and will keep you going for most of the book. You are going to see all these rules in action in this chapter as well as in other chapters. For now, let’s consider the only way to format curly braces in Go because this rule applies everywhere.
Go 有更多規則,但前面的規則是最重要的,將幫助您閱讀本書的大部分內容。您將在本章以及其他章節中看到所有這些規則的實際應用。現在,讓我們考慮在 Go 中格式化大括號的唯一方法,因為此規則適用於任何地方。
Look at the following Go program named curly.go:
查看以下名為 curly.go 的 Go 程式:
package main
import (
    "fmt"
)
func main() 
{
    fmt.Println("Go has strict rules for curly braces!")
}
Although it looks just fine, if you try to execute it, you will be disappointed because the code will not compile and, therefore, you will get the following syntax error message:
雖然它看起來很好,但如果您嘗試執行它,您會感到失望,因為程式碼無法編譯,因此您將收到以下語法錯誤訊息:
$ go run curly.go
# command-line-arguments
./curly.go:7:6: missing function body
./curly.go:8:1: syntax error: unexpected semicolon or newline before {
The official explanation for this error message is that Go requires the use of semicolons as statement terminators in many contexts, and the compiler implicitly inserts the required semicolons when it thinks that they are necessary. Therefore, putting the opening curly brace ({) in its own line will make the Go compiler insert a semicolon at the end of the previous line (func main()), which is the main cause of the error message. The correct way to write the previous code is the following:
對此錯誤訊息的官方解釋是,Go在許多上下文中需要使用分號作為語句終止符,並且編譯器在認為有必要時隱式插入所需的分號。因此,將左大括號 ( { ) 放在自己的行中將使 Go 編譯器在上一行的末尾插入分號 ( func main() ) ,這是錯誤訊息的主要原因。前面的程式碼正確的寫法如下:
package main
import (
    "fmt"
)
func main() {
    fmt.Println("Go has strict rules for curly braces!")
}
After learning about this global rule, let us continue by presenting some important characteristics of Go.
了解了這條全域規則後,讓我們繼續介紹 Go 的一些重要特徵。

What you should know about Go
關於 Go 您應該了解的內容

This big section discusses important and essential Go features including variables, controlling program flow, iterations, getting user input, and Go concurrency. We begin by discussing variables, variable declaration, and variable usage.
這一大節討論了重要且基本的 Go 功能,包括變數、控製程式流程、迭代、取得使用者輸入和 Go 並發性。我們首先討論變數、變數宣告和變數使用。

Defining and using variables
定義和使用變數

Imagine that you want to perform basic mathematical calculations. In that case, you need to define variables to keep the input, intermediate computations, and results.
想像一下您想要執行基本的數學計算。在這種情況下,您需要定義變數來保存輸入、中間計算和結果。
Go provides multiple ways to declare new variables to make the variable declaration process more natural and convenient. You can declare a new variable using the var keyword, followed by the variable name, followed by the desired data type (we are going to cover data types in detail in Chapter 2, Basic Go Data Types). If you want, you can follow that declaration with = and an initial value for your variable. If there is an initial value given, you can omit the data type and the compiler will infer it for you.
Go提供了多種宣告新變數的方式,讓變數宣告過程更加自然且方便。您可以使用 var 關鍵字聲明一個新變量,後面跟著變量名稱,然後是所需的數據類型(我們將在第 2 章,基礎 Go 和變數的初始值。如果給定了初始值,您可以省略資料類型,編譯器將為您推斷它。
This brings us to a very important Go rule: if no initial value is given to a variable, the Go compiler will automatically initialize that variable to the zero value of its data type.
這為我們帶來了一個非常重要的Go規則:如果沒有為變數賦予初始值,Go編譯器會自動將該變數初始化為其資料類型的零值。
There is also the := notation, which can be used instead of a var declaration. := defines a new variable by inferring the data of the value that follows it. The official name for := is short assignment statement, and it is very frequently used in Go, especially for getting the return values from functions and for loops with the range keyword.
還有 := 表示法,可以用來代替 var 聲明。 := 透過推斷其後面的值的資料來定義一個新變數。 := 的正式名稱是短賦值語句,它在 Go 中使用非常頻繁,特別是用於從函數和 for 循環中獲取返回值 range 關鍵字。
The short assignment statement can be used in place of a var declaration with an implicit type. You rarely see the use of var in Go; the var keyword is mostly used for declaring global or local variables without an initial value. The reason for the former is that every statement that exists outside of the code of a function must begin with a keyword, such as func or var.
短賦值語句可以用來取代隱式類型的 var 宣告。您很少會在 Go 中看到 var 的使用; var 關鍵字主要用於宣告沒有初始值的全域或局部變數。前者的原因是函數程式碼外部存在的每個語句都必須以關鍵字開頭,例如 funcvar
This means that the short assignment statement cannot be used outside of a function environment because it is not permitted there. Last, you might need to use var when you want to be explicit about the data type. For example, when you want the type of a variable to be int8 or int32 instead of int, which is the default.
這意味著短賦值語句不能在函數環境之外使用,因為那裡不允許使用它。最後,當您想要明確資料類型時,您可能需要使用 var 。例如,當您希望變數的類型為 int8int32 而不是 int (預設值)時。

Constants 常數

There are values, such as the mathematical constant Pi, that cannot change. In that case, we can declare such values as constants using const. Constants are declared just like variables but cannot change once they have been declared.
有些值是無法改變的,例如數學常數 Pi。在這種情況下,我們可以使用 const 將這些值宣告為常數。常量的宣告方式與變數一樣,但一旦宣告就不能更改。
The supported data types for constants are character, string, Boolean, and all numeric data types. There’s more about Go data types in Chapter 2, Basic Go Data Types.
常數支援的資料類型包括字元、字串、布林值和所有數字資料類型。第 2 章「基本 Go 資料類型」中有更多關於 Go 資料類型的資訊。

Global variables 全域變數

Global variables are variables that are defined outside of a function implementation. Global variables can be accessed from anywhere in a package without the need to explicitly pass them to a function, and they can be changed unless they were defined as constants, using the const keyword.
全域變數是在函數實作之外定義的變數。可以從套件中的任何位置存取全域變量,無需將它們明確傳遞給函數,並且可以使用 const 關鍵字更改它們,除非將它們定義為常數。
Although you can declare local variables using either var or :=, only const (when the value of a variable is not going to change) and var work for global variables.
儘管您可以使用 var:= 聲明局部變量,但只能使用 const (當變數的值不會更改時)和 var

Printing variables 列印變數

Programs tend to display information, which means that they need to print data or send it somewhere for other software to store or process it. To print data on the screen, Go uses the functionality of the fmt package. If you want Go to take care of the printing, then you might want to use the fmt.Println() function. However, there are times when you want to have full control over how data is going to be printed. In such cases, you might want to use fmt.Printf().
程式傾向於顯示訊息,這意味著它們需要列印資料或將其發送到其他軟體來儲存或處理它。若要在螢幕上列印數據,Go 使用 fmt 套件的功能。如果您希望 Go 負責列印,那麼您可能需要使用 fmt.Println() 函數。然而,有時您希望完全控制資料的列印方式。在這種情況下,您可能需要使用 fmt.Printf()
fmt.Printf() is similar to the C printf() function and requires the use of control sequences that specify the data type of the variable that is going to be printed. Additionally, the fmt.Printf() function allows you to format the generated output, which is particularly convenient for floating point values because it allows you to specify the digits that will be displayed in the output (%.2f displays two digits after the decimal point of a floating point value). Lastly, the \n character is used for printing a newline character and, therefore, creating a new line, as fmt.Printf() does not automatically insert a newline—this is not the case with fmt.Println(), which automatically inserts a newline, hence the ln at the end of its name.
fmt.Printf() 與 C printf() 函數類似,需要使用控制序列來指定要列印的變數的資料類型。此外, fmt.Printf() 函數可讓您格式化產生的輸出,這對於浮點值特別方便,因為它允許您指定將在輸出中顯示的數字( %.2f 顯示浮點值的小數點後兩位)。最後, \n 字元用於列印換行符,因此建立一個新行,因為 fmt.Printf() 不會自動插入換行符 - fmt.Println() ,它會自動插入換行符,因此在其名稱末尾添加 ln
The following program illustrates how you can declare new variables, how to use them, and how to print them—type the following code into a plain text file named variables.go:
以下程式說明如何聲明新變數、如何使用它們以及如何列印它們 - 將以下程式碼鍵入名為 variables.go 的純文字檔案:
package main
import (
    "fmt"
    "math"
)
var Global int = 1234
var AnotherGlobal = -5678
func main() {
    var j int
    i := Global + AnotherGlobal
    fmt.Println("Initial j value:", j)
    j = Global
    // math.Abs() requires a float64 parameter
    // so we type cast it appropriately
    k := math.Abs(float64(AnotherGlobal))
    fmt.Printf("Global=%d, i=%d, j=%d k=%.2f.\n", Global, i, j, k)
}
Personally, I prefer to make global variables stand out by either beginning them with an uppercase letter or using all capital letters. As you are going to learn in Chapter 6, Go Packages and Functions, the case of the first character of a variable name has a special meaning in Go and changes its visibility. So this works for the main package only.
就我個人而言,我更喜歡透過以大寫字母開頭或全部大寫字母來使全域變數脫穎而出。正如您將在第6 章Go 套件和函數中了解到的那樣,變數名稱的第一個字元的大小寫在Go 中具有特殊含義,並會改變其可見性。所以這只適用於 main 套件。
This above program contains the following:
上述程序包含以下內容:
  • A global int variable named Global.
    名為 Global 的全域 int 變數。
  • A second global variable named AnotherGlobal—Go automatically infers its data type from its value, which in this case is an integer.
    第二個名為 AnotherGlobal —Go 的全域變數會自動從其值推斷其資料類型,在本例中為整數。
  • A local variable named j of type int, which, as you will learn in the next chapter, is a special data type. j does not have an initial value, which means that Go automatically assigns the zero value of its data type, which in this case is 0.
    名為 j 且類型為 int 的局部變量,正如您將在下一章中了解到的那樣,它是一種特殊的資料類型。 j 沒有初始值,這表示 Go 自動分配其資料類型的零值,在本例中為 0
  • Another local variable named i—Go infers its data type from its value. As it is the sum of two int values, it is also an int.
    另一個名為 i - Go 的局部變數根據其值推斷其資料型態。由於它是兩個 int 值的總和,因此它也是一個 int
  • As math.Abs() requires a float64 parameter, you cannot pass AnotherGlobal to it because AnotherGlobal is an int variable. The float64() type cast converts the value of AnotherGlobal to float64. Note that AnotherGlobal continues to have the int data type.
    由於 math.Abs() 需要 float64 參數,因此您無法將 AnotherGlobal 傳遞給它,因為 AnotherGlobalint 變量。 float64() 型別轉換將 AnotherGlobal 的值轉換為 float64 。請注意, AnotherGlobal 仍然具有 int 資料類型。
  • Lastly, fmt.Printf() formats and prints the output.
    最後, fmt.Printf() 格式化並列印輸出。
Running variables.go produces the following output:
運行 variables.go 會產生以下輸出:
Initial j value: 0
Global=1234, i=-4444, j=1234 k=5678.00.
This example demonstrated another important Go rule that was also mentioned previously: Go does not allow implicit data conversions like C. As presented in variables.go, the math.Abs() function that expects (requires) a float64 value cannot work with an int value, even if this particular conversion is straightforward and error-free. The Go compiler refuses to compile such statements. You should convert the int value to a float64 explicitly using float64() for things to work properly.
此範例示範了另一個重要的Go 規則,之前也提到過: Go 不允許像C 那樣的隱式資料轉換。所示, math.Abs() 值的 函數無法使用 int 值,即使此特定轉換是簡單且無錯誤的。 Go 編譯器拒絕編譯此類語句。您應該使用 float64()int 值明確轉換為 float64 ,以便正常運作。
For conversions that are not straightforward (for example, string to int), there exist specialized functions that allow you to catch issues with the conversion, in the form of an error variable that is returned by the function.
對於不簡單的轉換(例如 stringint ),存在專門的函數,可以讓您以 error

Controlling program flow
控製程序流程

So far, we have seen Go variables, but how do we change the flow of a Go program based on the value of a variable or some other condition? Go supports the if/else and switch control structures. Both control structures can be found in most modern programming languages, so if you have already programmed in another programming language, you should already be familiar with both if and switch statements. if statements use no parenthesis to embed the conditions that need to be examined because Go does not use parentheses in general. As expected, if has support for else and else if statements.
到目前為止,我們已經看到了 Go 變量,但是我們如何根據變數的值或其他條件來更改 Go 程式的流程呢? Go 支援 if/elseswitch 控制結構。這兩種控制結構都可以在大多數現代編程語言中找到,因此如果您已經使用另一種編程語言進行過編程,那麼您應該已經熟悉 ifswitch 語句。 if 語句不使用括號來嵌入需要檢查的條件,因為 Go 一般不使用括號。如預期的那樣, if 支援 elseelse if 語句。
To demonstrate the use of if, let us use a very common pattern in Go that is used almost everywhere. This pattern says that if the value of an error variable as returned from a function is nil, then everything is OK with the function execution. Otherwise, there is an error condition somewhere that needs special care. This pattern is usually implemented as follows:
為了示範 if 的用法,讓我們在 Go 中使用一個非常常見的模式,該模式幾乎在任何地方都使用。此模式表示,如果從函數傳回的錯誤變數的值為 nil ,則函數執行一切正常。否則,某個地方會出現錯誤情況,需要特別注意。此模式通常實作如下:
err := anyFunctionCall()
if err != nil {
    // Do something if there is an error
}
err is the variable that holds the error value as returned from a function and != means that the value of the err variable is not equal to nil. You will see similar code multiple times in Go programs.
err 是保存函數傳回的錯誤值的變量, != 表示 err 變數的值不等於 nil 。您將在 Go 程式中多次看到類似的程式碼。
Lines beginning with // are single-line comments. If you put // in the middle of a line, then everything after // until the end of the line is considered a comment. This rule does not apply if // is inside a string value.
// 開頭的行是單行註解。如果將 // 放在一行中間,則 // 之後直到行尾的所有內容都被視為註解。如果 // 位於字串值內,則此規則不適用。
The switch statement has two different forms. In the first form, the switch statement has an expression that is evaluated, whereas in the second form, the switch statement has no expression to evaluate. In that case, expressions are evaluated in each case statement, which increases the flexibility of switch. The main benefit you get from switch is that when used properly, it simplifies complex and hard-to-read if-else blocks.
switch 語句有兩種不同的形式。在第一種形式中, switch 語句有一個要計算的表達式,而在第二種形式中, switch 語句沒有要計算的表達式。在這種情況下,將在每個 case 語句中計算表達式,這增加了 switch 的靈活性。 switch 的主要好處是,如果使用得當,它可以簡化複雜且難以閱讀的 if-else 區塊。
Both if and switch are illustrated in the following code, which is designed to process user input given as command line arguments—please type it and save it as control.go. For learning purposes, we present the code of control.go in pieces in order to explain it better:
下面的程式碼說明了 ifswitch ,該程式碼旨在處理作為命令列參數給出的用戶輸入- 請鍵入它並將其另存為 control.go 。出於學習目的,我們將 control.go 的程式碼分塊呈現,以便更好地解釋:
package main
import (
    "fmt"
    "os"
    "strconv"
)
This first part contains the expected preamble with the imported packages. The implementation of the main() function starts next:
第一部分包含預期的序言以及導入的套件。接下來開始執行 main() 函數:
func main() {
    if len(os.Args) != 2 {
        fmt.Println("Please provide a command line argument")
        return
    }
    argument := os.Args[1]
This part of the program makes sure that you have a single command line argument to process, which is accessed as os.Args[1], before continuing. We will cover this in more detail later, but you can refer to Figure 1.2 for more information about the os.Args slice:
程式的這一部分確保您有一個要處理的命令列參數,在繼續之前可以透過 os.Args[1] 存取該參數。我們稍後會更詳細地介紹這一點,但您可以參考圖 1.2 以了解有關 os.Args 切片的更多資訊:
    // With expression after switch
    switch argument {
    case "0":
        fmt.Println("Zero!")
    case "1":
        fmt.Println("One!")
    case "2", "3", "4":
        fmt.Println("2 or 3 or 4")
        fallthrough
    default:
        fmt.Println("Value:", argument)
    }
Here, you see a switch block with four branches. The first three require exact string matches and the last one matches everything else. The order of the case statements is important because only the first match is executed. The fallthrough keyword tells Go that after this branch is executed, it will continue with the next branch, which in this case is the default branch:
在這裡,您會看到一個具有四個分支的開關塊。前三個需要精確的字串匹配,最後一個匹配其他所有內容。 case 語句的順序很重要,因為只執行第一個符合項目。 fallthrough 關鍵字告訴 Go 執行此分支後,它將繼續執行下一個分支,在本例中是預設分支:
    value, err := strconv.Atoi(argument)
    if err != nil {
        fmt.Println("Cannot convert to int:", argument)
        return
    }
As command line arguments are initialized as string values, we need to convert user input into an integer value using a separate call, which in this case is a call to strconv.Atoi(). If the value of the err variable is nil, then the conversion was successful, and we can continue. Otherwise, an error message is printed onscreen and the program exits.
由於命令列參數被初始化為字串值,因此我們需要使用單獨的呼叫將使用者輸入轉換為整數值,在本例中是對 strconv.Atoi() 的呼叫。如果 err 變數的值為 nil ,則轉換成功,我們可以繼續。否則,螢幕上會列印一條錯誤訊息,並且程式退出。
The following code shows the second form of switch, where the condition is evaluated at each case branch:
以下程式碼顯示了 switch 的第二種形式,其中在每個 case 分支上評估條件:
    // No expression after switch
    switch {
    case value == 0:
        fmt.Println("Zero!")
    case value > 0:
        fmt.Println("Positive integer")
    case value < 0:
        fmt.Println("Negative integer")
    default:
        fmt.Println("This should not happen:", value)
    }
}
This gives you more flexibility but requires more thinking when reading the code. In this case, the default branch should not be executed, mainly because any valid integer value would be caught by the other three branches. Nevertheless, the default branch is there, which is good practice because it can catch unexpected values.
這為您提供了更大的靈活性,但在閱讀程式碼時需要更多的思考。在這種情況下,不應執行預設分支,主要是因為任何有效的整數值都會被其他三個分支捕獲。儘管如此,預設分支仍然存在,這是一個很好的做法,因為它可以捕獲意外的值。
Running control.go generates the following output:
運行 control.go 會產生以下輸出:
$ go run control.go 10
Value: 10
Positive integer
$ go run control.go 0
Zero!
Zero!
Each one of the two switch blocks in control.go creates one line of output.
control.go 中的兩個開關區塊中的每一個都會建立一行輸出。

Iterating with for loops and range
使用 for 迴圈和範圍進行迭代

This section is all about iterating in Go. Go supports for loops as well as the range keyword to iterate over all the elements of arrays, slices, and (as you will see in Chapter 3, Composite Data Types) maps, without knowing the size of the data structure.
本節主要討論 Go 中的迭代。 Go 支援 for 循環以及 range 關鍵字來迭代數組、切片和(如您將在第3 章「複合」中看到的)的所有元素資料型別)映射,而不知道資料結構的大小。
An example of Go simplicity is the fact that Go provides support for the for keyword only, instead of including direct support for while loops. However, depending on how you write a for loop, it can function as a while loop or an infinite loop. Moreover, for loops can implement the functionality of JavaScript’s forEach function when combined with the range keyword.
Go 簡單性的一個例子是 Go 僅提供對 for 關鍵字的支持,而不是包括對 while 循環的直接支持。但是,根據您編寫 for 循環的方式,它可以充當 while 循環或無限循環。此外, for 迴圈與 range 關鍵字結合時可以實現JavaScript的 forEach 函數的功能。
You need to put curly braces around a for loop even if it contains just a single statement or no statements at all.
您需要在 for 迴圈周圍放置花括號,即使它只包含一條語句或根本不包含任何語句。
You can also create for loops with variables and conditions. A for loop can be exited with a break keyword, and you can skip the current iteration with the continue keyword.
您也可以使用變數和條件建立 for 迴圈。可以使用 break 關鍵字退出 for 循環,並且可以使用 continue 關鍵字跳過目前迭代。
The following program illustrates the use of for on its own and with the range keyword—type it and save it as forLoops.go to execute it afterward:
以下程式說明了 for 本身以及與 range 關鍵字一起使用 - 鍵入它並將其儲存為 forLoops.go 以便隨後執行:
package main
import "fmt"
func main() {
    // Traditional for loop
    for i := 0; i < 10; i++ {
        fmt.Print(i*i, " ")
    }
    fmt.Println()
}
The previous code illustrates a traditional for loop that uses a local variable named i. This prints the squares of 0, 1, 2, 3, 4, 5, 6, 7, 8, and 9 onscreen. The square of 10 is not computed and printed because it does not satisfy the 10 < 10 condition.
前面的程式碼示範了使用名為 i 的局部變數的傳統 for 迴圈。這將會列印 01234578910 的平方不會被計算和列印,因為它不滿足 10 < 10 條件。
The following code is idiomatic Go and produces the same output as the previous for loop:
以下程式碼是慣用的 Go 並產生與前面的 for 迴圈相同的輸出:
    i := 0
    for ok := true; ok; ok = (i != 10) {
        fmt.Print(i*i, " ")
        i++
    }
    fmt.Println()
You might use it, but it is sometimes hard to read, especially for people who are new to Go. The following code shows how a for loop can simulate a while loop, which is not supported directly:
您可能會使用它,但有時很難閱讀,特別是對於剛接觸 Go 的人來說。以下程式碼顯示了 for 循環如何模擬 while 循環,但不直接支援:
    // For loop used as while loop
    i = 0
    for {
        if i == 10 {
            break
        }
        fmt.Print(i*i, " ")
        i++
    }
    fmt.Println()
The break keyword in the if condition exits the loop early and acts as the loop exit condition. Without an exit condition that is going to be met at some point and the break keyword, the for loop is never going to finish.
if 條件中的 break 關鍵字提前退出循環並充當循環退出條件。如果沒有在某個時刻滿足的退出條件和 break 關鍵字, for 迴圈將永遠不會完成。
Lastly, given a slice, which you can consider as a resizable array, named aSlice, you iterate over all its elements with the help of range, which returns two ordered values: the index of the current element in the slice and its value. If you want to ignore either of these return values, which is not the case here, you can use _ in the place of the value that you want to ignore. If you just need the index, you can leave out the second value from range entirely without using _:
最後,給定一個切片,您可以將其視為一個可調整大小的數組,名為 aSlice ,您可以在 range 的幫助下迭代其所有元素,它返回兩個有序值:索引切片中目前元素的值及其值。如果您想要忽略這些傳回值中的任何一個(此處不是這種情況),您可以使用 _ 來取代您要忽略的值。如果您只需要索引,則可以完全省略 range 中的第二個值,而不使用 _
    // This is a slice but range also works with arrays
    aSlice := []int{-1, 2, 1, -1, 2, -2}
    for i, v := range aSlice {
        fmt.Println("index:", i, "value: ", v)
    }
If you run forLoops.go, you get the following output:
如果運行 forLoops.go ,您將得到以下輸出:
$ go run forLoops.go
0 1 4 9 16 25 36 49 64 81
0 1 4 9 16 25 36 49 64 81
0 1 4 9 16 25 36 49 64 81
index: 0 value:  -1
index: 1 value:  2
index: 2 value:  1
index: 3 value:  -1
index: 4 value:  2
index: 5 value:  -2
The previous output illustrates that the first three for loops are equivalent and, therefore, produce the same output. The last six lines show the index and the value of each element found in aSlice.
前面的輸出說明前三個 for 循環是等效的,因此產生相同的輸出。最後六行顯示 aSlice 中找到的每個元素的索引和值。
Now that we know about for loops, let us see how to get user input.
現在我們了解了 for 循環,讓我們看看如何取得使用者輸入。

Getting user input 取得用戶輸入

Getting user input is an important part of the majority of programs. This section presents two ways of getting user input, which read from standard input and use the command line arguments of the program.
取得使用者輸入是大多數程式的重要組成部分。本節介紹兩種取得使用者輸入的方法,一種是從標準輸入讀取,另一種是使用程式的命令列參數。

Reading from standard input
從標準輸入讀取

The fmt.Scanln() function can help you read user input while the program is already running and store it to a string variable, which is passed as a pointer to fmt.Scanln(). The fmt package contains additional functions for reading user input from the console (os.Stdin), files, or argument lists.
fmt.Scanln() 函數可以幫助您在程式運行時讀取使用者輸入並將其儲存到字串變數中,該變數會作為指向 fmt.Scanln() 的指標傳遞。 fmt 套件包含用於從控制台 ( os.Stdin )、檔案或參數清單讀取使用者輸入的附加函數。
The fmt.Scanln() function is rarely used to get user input. Usually, user input is read from command line arguments or external files. However, interactive command line applications need fmt.Scanln().
fmt.Scanln() 函數很少用於取得使用者輸入。通常,使用者輸入是從命令列參數或外部檔案讀取的。但是,互動式命令列應用程式需要 fmt.Scanln()
The following code illustrates reading from standard input—type it and save it as input.go:
以下程式碼說明了從標準輸入讀取資料 - 鍵入並將其儲存為 input.go
package main
import (
    "fmt"
)
func main() {
    // Get User Input
    fmt.Printf("Please give me your name: ")
    var name string
    fmt.Scanln(&name)
    fmt.Println("Your name is", name)
}
While waiting for user input, it is good to let the user know what kind of information they have to give, which is the purpose of the fmt.Printf() call. The reason for not using fmt.Println() instead is that fmt.Println() automatically appends a newline character at the end of the output, which is not what we want here.
在等待使用者輸入時,最好讓使用者知道他們必須提供什麼樣的信息,這就是 fmt.Printf() 呼叫的目的。不使用 fmt.Println() 的原因是 fmt.Println() 自動在輸出末尾附加換行符,這不是我們想要的。
Executing input.go generates the following kind of output and user interaction:
執行 input.go 會產生以下類型的輸出和使用者互動:
$ go run input.go
Please give me your name: Mihalis
Your name is Mihalis

Working with command line arguments
使用命令列參數

Although typing user input when needed might look like a nice idea, this is not usually how real software works. Usually, user input is given in the form of command line arguments to the executable file. By default, command line arguments in Go are stored in the os.Args slice.
儘管在需要時鍵入使用者輸入可能看起來是個好主意,但這通常不是真正的軟體的工作方式。通常,使用者輸入以可執行檔的命令列參數的形式給出。預設情況下,Go 中的命令列參數儲存在 os.Args 切片中。
The standard Go library also offers the flag package for parsing command line arguments, but there are better and more powerful alternatives.
標準 Go 函式庫也提供用於解析命令列參數的 flag 包,但還有更好、更強大的替代方案。
The figure that follows shows the way command line arguments work in Go, which is the same as in the C programming language. It is important to know that the os.Args slice is properly initialized by Go and is available to the program when referenced. The os.Args slice contains string values:
下圖顯示了命令列參數在 Go 中的工作方式,這與 C 程式語言中的相同。重要的是要知道 os.Args 切片已由 Go 正確初始化,並且在引用時可供程式使用。 os.Args 切片包含 string 值:
A picture containing text, screenshot, font, black  Description automatically generated
Figure 1.2: How the os.Args slice works
圖 1.2:os.Args 切片的工作原理
The first command line argument stored in the os.Args slice is always the file path of the executable. If you use go run, you will get a temporary name and path; otherwise, it will be the path of the executable as given by the user. The remaining command line arguments are what come after the name of the executable—the various command line arguments are automatically separated by space characters unless they are included in double or single quotes; this depends on the OS.
os.Args 片中儲存的第一個命令列參數始終是可執行檔的檔案路徑。如果使用 go run ,您將獲得一個臨時名稱和路徑;否則,它將是使用者指定的可執行檔的路徑。其餘的命令列參數是可執行檔名稱之後的內容 - 各種命令列參數會自動以空格字元分隔,除非它們包含在雙引號或單引號中;這取決於作業系統。
The use of os.Args is illustrated in the code that follows, which is to find the minimum and the maximum numeric values of its input while ignoring invalid input, such as characters and strings. Type the code and save it as cla.go:
os.Args 的用法如下面的程式碼所示,即求其輸入的最小和最大數值,同時忽略無效的輸入,例如字元和字串。輸入代碼並將其儲存為 cla.go
package main
import (
    "fmt"
    "os"
    "strconv"
)
As expected, cla.go begins with its preamble. The fmt package is used for printing output, whereas the os package is required because os.Args is a part of it. Lastly, the strconv package contains functions for converting strings to numeric values. Next, we make sure that we have at least one command line argument:
正如預期的那樣, cla.go 從序言開始。 fmt 套件用於列印輸出,而 os 套件是必需的,因為 os.Args 是它的一部分。最後, strconv 套件包含將字串轉換為數值的函數。接下來,我們確保至少有一個命令列參數:
func main() {
    arguments := os.Args
    if len(arguments) == 1 {
        fmt.Println("Need one or more arguments!")
        return
    }
Remember that the first element in os.Args is always the path of the executable file, so os.Args is never totally empty. Next, the program checks for errors in the same way we looked for them in previous examples. You will learn more about errors and error handling in Chapter 2, Basic Go Data Types:
請記住, os.Args 中的第一個元素始終是可執行檔的路徑,因此 os.Args 永遠不會完全為空。接下來,程式以與我們在前面的範例中查找錯誤相同的方式檢查錯誤。您將在第 2 章「基本 Go 資料類型」中了解有關錯誤和錯誤處理的更多資訊:
    var min, max float64
    var initialized = 0
    for i := 1; i < len(arguments); i++ {
        n, err := strconv.ParseFloat(arguments[i], 64)
        if err != nil {
            continue
        }
In this case, we use the error variable returned by strconv.ParseFloat() to make sure that the call to strconv.ParseFloat() was successful and there is a valid numeric value to process. Otherwise, we should continue to the next command line argument.
在本例中,我們使用 strconv.ParseFloat() 傳回的 error 變數來確保對 strconv.ParseFloat() 的呼叫成功並且有一個有效的數值要處理。否則,我們應該繼續下一個命令列參數。
The for loop is used to iterate over all available command line arguments except the first one, which uses an index value of 0. This is another popular technique for working with all command line arguments.
for 循環用於迭代除第一個參數之外的所有可用命令列參數,該參數使用索引值 0 。這是處理所有命令列參數的另一種流行技術。
The following code is used to properly initialize the value of the min and max variables after the first valid command line argument is processed:
以下程式碼用於處理第一個有效命令列參數後正確初始化 minmax 變數的值:
        if initialized == 0 {
            min = n
            max = n
            initialized = 1
            continue
        }
We are using initialized == 0 to test whether this is the first valid command line argument. If this is the case, we process the first command line argument and initialize the min and max variables to its value.
我們使用 initialized == 0 來測試這是否是第一個有效的命令列參數。如果是這種情況,我們將處理第一個命令列參數並將 minmax 變數初始化為其值。
The next code checks whether the current value is our new minimum or maximum—this is where the logic of the program is implemented:
下一個程式碼檢查當前值是否是新的最小值或最大值 - 這是程式邏輯的實作位置:
        if n < min {
            min = n
        }
        if n > max {
            max = n
        }
    }
    fmt.Println("Min:", min)
    fmt.Println("Max:", max)
}
The last part of the program is about printing your findings, which are the minimum and maximum numeric values of all valid command line arguments. The output you get from cla.go depends on its input:
程式的最後一部分是列印您的發現,即所有有效命令列參數的最小和最大數值。從 cla.go 獲得的輸出取決於其輸入:
$ go run cla.go a b 2 -1
Min: -1
Max: 2
In this case, a and b are invalid, and the only valid inputs are -1 and 2, which are the minimum value and maximum value, respectively:
在這種情況下, ab 無效,唯一有效的輸入是 -12 ,它們是最小值和最大值分別為:
$ go run cla.go a 0 b -1.2 10.32
Min: -1.2
Max: 10.32
In this case, a and b are invalid input and, therefore, ignored:
在這種情況下, ab 是無效輸入,因此被忽略:
$ go run cla.go
Need one or more arguments!
In the final case, as cla.go has no input to process, it prints a help message. If you execute the program with no valid input values, for example, go run cla.go a b c, then the values of both Min and Max are going to be zero.
在最後一種情況下,由於 cla.go 沒有要處理的輸入,因此它會列印一條幫助訊息。如果您在沒有有效輸入值的情況下執行程序,例如 go run cla.go a b c ,則 MinMax 的值都會為零。
The next subsection shows a technique for differentiating between different data types, using error variables.
下一小節展示了一種使用錯誤變數來區分不同資料類型的技術。

Using error variables to differentiate between input types
使用錯誤變數來區分輸入類型

Now, let me show you a technique that uses error variables to differentiate between various kinds of user input. For this technique to work, you should go from more specific cases to more generic ones. If we are talking about numeric values, you should first examine whether a string is a valid integer before examining whether the same string is a floating-point value, because every valid integer is also a valid floating-point value.
現在,讓我向您展示一種使用錯誤變數來區分各種使用者輸入的技術。要使此技術發揮作用,您應該go從更具體的情況到更通用的情況。如果我們談論數值,則應先檢查字串是否為有效整數,然後再檢查相同字串是否為浮點值,因為每個有效整數也是有效浮點值。
The first part of the program, which is saved as process.go, is the following:
程式的第一部分儲存為 process.go ,如下所示:
package main
import (
    "fmt"
    "os"
    "strconv"
)
func main() {
    arguments := os.Args
    if len(arguments) == 1 {
        fmt.Println("Not enough arguments")
        return
    }
The previous code contains the preamble and the storing of the command line arguments in the arguments variable.
前面的程式碼包含前導碼以及 arguments 變數中命令列參數的儲存。
The next part is where we start examining the validity of the input:
下一部分是我們開始檢查輸入的有效性的地方:
    var total, nInts, nFloats int
    invalid := make([]string, 0)
    for _, k := range arguments[1:] {
        // Is it an integer?
        _, err := strconv.Atoi(k)
        if err == nil {
            total++
            nInts++
            continue
        }
First, we create three variables for keeping a count of the total number of valid values examined, the total number of integer values found, and the total number of floating-point values found, respectively. The invalid variable, which is a slice of strings, is used for keeping all non-numeric values.
首先,我們建立三個變量,分別用於記錄檢查的有效值總數、找到的整數值總數和找到的浮點值總數。 invalid 變數是字串切片,用於保存所有非數字值。
Once again, we need to iterate over all the command line arguments except the first one, which has an index value of 0, because this is the path of the executable file. We ignore the path of the executable, using arguments[1:] instead of just arguments—selecting a continuous part of a slice is discussed in the next chapter.
再次,我們需要迭代除第一個參數之外的所有命令列參數,該參數的索引值為 0 ,因為這是可執行檔的路徑。我們忽略可執行檔的路徑,使用 arguments[1:] 而不是 arguments — 選擇切片的連續部分將在下一章中討論。
The call to strconv.Atoi() determines whether we are processing a valid int value or not. If so, we increase the total and nInts counters:
strconv.Atoi() 的呼叫確定我們是否正在處理有效的 int 值。如果是這樣,我們增加 totalnInts 計數器:
        // Is it a float
        _, err = strconv.ParseFloat(k, 64)
        if err == nil {
            total++
            nFloats++
            continue
        }
Similarly, if the examined string represents a valid floating-point value, the call to strconv.ParseFloat() is going to be successful, and the program will update the relevant counters. Lastly, if a value is not numeric, it is appended to the invalid slice with a call to append():
類似地,如果檢查的字串表示有效的浮點值,則對 strconv.ParseFloat() 的呼叫將成功,並且程式將更新相關計數器。最後,如果值不是數字,則會透過呼叫 append() 將其附加到 invalid 切片:
        // Then it is invalid
        invalid = append(invalid, k)
    }
The last part of the program is the following:
程序的最後一部分如下:
    fmt.Println("#read:", total, "#ints:", nInts, "#floats:", nFloats)
    if len(invalid) > total {
        fmt.Println("Too much invalid input:", len(invalid))
        for _, s := range invalid {
            fmt.Println(s)
        }
    }
}
Presented here is extra code that warns you when your invalid input is more than the valid one (len(invalid) > total). This is a common practice for keeping unexpected input in applications.
這裡提供的是額外的代碼,當您的無效輸入多於有效輸入 ( len(invalid) > total ) 時,它會向您發出警告。這是在應用程式中保留意外輸入的常見做法。
Running process.go produces the following kind of output:
運行 process.go 會產生以下類型的輸出:
$ go run process.go 1 2 3
#read: 3 #ints: 3 #floats: 0
In this case, we process 1, 2, and 3, which are all valid integer values:
在本例中,我們處理 1、2 和 3,它們都是有效的整數值:
$ go run process.go 1 2.1 a    
#read: 2 #ints: 1 #floats: 1
In this case, we have a valid integer, 1, a floating-point value, 2.1, and an invalid value, a:
在這個例子中,我們有一個有效整數 1、一個浮點值 2.1 和一個無效值 a:
$ go run process.go a 1 b
#read: 1 #ints: 1 #floats: 0
Too much invalid input: 2
a
b
If the invalid input is more than the valid one, then process.go prints an extra error message.
如果無效輸入多於有效輸入,則 process.go 會列印額外的錯誤訊息。
The next subsection discusses the concurrency model of Go.
下一小節討論 Go 的同時模型。

Understanding the Go concurrency model
了解Go並發模型

This section is a quick introduction to the Go concurrency model. The Go concurrency model is implemented using goroutines and channels. A goroutine is the smallest executable Go entity. To create a new goroutine, you have to use the go keyword followed by a predefined function or an anonymous function—both these methods are equivalent as far as Go is concerned.
本節是對 Go 並發模型的快速介紹。 Go 並發模型是使用 goroutine 和通道實現的。 Goroutine 是最小的可執行 Go 實體。要建立新的 goroutine,您必須使用 go 關鍵字,後跟預定義函數或匿名函數 - 就 Go 而言,這兩種方法是等效的。
The go keyword works with functions or anonymous functions only.
go 關鍵字僅適用於函數或匿名函數。
A channel in Go is a mechanism that, among other things, allows goroutines to communicate and exchange data. If you are an amateur programmer or are hearing about goroutines and channels for the first time, do not panic. Goroutines and channels, as well as pipelines and sharing data among goroutines, will be explained in much more detail in Chapter 8, Go Concurrency.
Go 中的通道是一種允許 goroutine 進行通訊和交換資料的機制。如果您是業餘程式設計師或第一次聽說 Goroutines 和 Channel,請不要驚慌。 Goroutine 和通道,以及管道和 Goroutine 之間共享數據,將在第 8 章 Go 並發性中進行更詳細的解釋。
Although it is easy to create goroutines, there are other difficulties when dealing with concurrent programming, including goroutine synchronization and sharing data between goroutines—this is a Go mechanism for avoiding side effects by using global state when running goroutines. As main() runs as a goroutine as well, you do not want main() to finish before the other goroutines of the program because once main() exits, the entire program along with any goroutines that have not finished yet will terminate. Although goroutines cannot communicate directly with each other, they can share memory. The good thing is that there are various techniques for the main() function to wait for goroutines to exchange data through channels or, less frequently in Go, use shared memory.
雖然創建goroutine 很容易,但是在處理並發程式設計時還存在其他困難,包括goroutine 同步和goroutine 之間共享資料——這是一種在運行goroutine 時使用全局狀態來避免副作用的Go機制。由於 main() 也作為 Goroutine 運行,因此您不希望 main() 在程式的其他 Goroutine 之前完成,因為一旦 main() 退出,整個程式就會隨著任何尚未完成的goroutine 都會終止。雖然 goroutine 之間不能直接通信,但它們可以共享記憶體。好處是 main() 函數有多種技術可以等待 goroutine 透過通道交換數據,或者在 Go 中不太頻繁地使用共享記憶體。
Type the following Go program, which synchronizes goroutines using time.Sleep() calls (this is not the right way to synchronize goroutines—we will discuss the proper way to synchronize goroutines in Chapter 8, Go Concurrency), into your favorite editor, and save it as goRoutines.go:
輸入以下Go 程序,該程序使用 time.Sleep() 呼叫來同步goroutines(這不是同步goroutines 的正確方法——我們將在第8 章 Concurrency),進入您最喜歡的編輯器,並將其儲存為 goRoutines.go
package main
import (
    "fmt"
    "time"
)
func myPrint(start, finish int) {
    for i := start; i <= finish; i++ {
        fmt.Print(i, " ")
    }
    fmt.Println()
    time.Sleep(100 * time.Microsecond)
}
func main() {
    for i := 0; i < 4; i++ {
        go myPrint(i, 5)
    }
    time.Sleep(time.Second)
}
The preceding naively implemented example creates four goroutines and prints some values on the screen using the myPrint() function—the go keyword is used for creating the goroutines. Running goRoutines.go generates the following output:
前面簡單實現的範例創建了四個 goroutine,並使用 myPrint() 函數在螢幕上列印一些值 - go 關鍵字用於建立 goroutine。運行 goRoutines.go 會產生以下輸出:
$ go run goRoutines.go
2 3 4 5
0 4 1 2 3 1 2 3 4 4 5
5
3 4 5
5
However, if you run it multiple times, you will most likely get a different output each time:
但是,如果多次運行它,您很可能每次都會得到不同的輸出:
1 2 3 4 5 
4 2 5 3 4 5 
3 0 1 2 3 4 5 
4 5
This happens because goroutines are initialized in a random order and start running in a random order. The Go scheduler is responsible for the execution of goroutines, just like the OS scheduler is responsible for the execution of the OS threads. Chapter 8, Go Concurrency, discusses Go concurrency in more detail and presents the solution to that randomness issue with the use of a sync.WaitGroup variable—however, keep in mind that Go concurrency is everywhere, which is the main reason for including this section here. Therefore, as some error messages generated by the compiler discuss goroutines, you should not think that these goroutines were created by you.
發生這種情況是因為 goroutine 以隨機順序初始化並以隨機順序開始運行。 Go 調度器負責 goroutine 的執行,就像作業系統調度器負責作業系統執行緒的執行一樣。第8 章,Go 並發性,更詳細地討論了Go 並發性,並提出了使用 sync.WaitGroup 變數解決隨機性問題的方法,但是,請記住請注意,Go 並發無所不在,這是在此包含此部分的主要原因。因此,當編譯器產生的一些錯誤訊息討論 goroutine 時,你不應該認為這些 goroutine 是你創建的。
The next section shows a practical example that involves developing a Go version of the which(1) utility, which searches for an executable file in the PATH environment value of the current user.
下一節將展示一個實際範例,其中涉及開發 which(1) 實用程式的Go 版本,該實用程式在目前的 PATH 環境值中搜尋可執行文件。

Developing the which(1) utility in Go
在Go中開發which(1)實用程序

Go can work with your operating system through a set of packages. A good way of learning a new programming language is by trying to implement simple versions of traditional UNIX utilities—in general, the only efficient way to learn a programming language is by writing lots of code in that language. In this section, you will see a Go version of the which(1) utility, which will help you understand the way Go interacts with the underlying OS and reads environment variables.
Go 可以透過一組軟體包與您的作業系統搭配使用。學習新程式語言的一個好方法是嘗試實現傳統 UNIX 實用程式的簡單版本 - 一般來說,學習程式語言的唯一有效方法是用該語言編寫大量程式碼。在本部分中,您將看到 which(1) 實用程式的Go 版本,它將幫助您了解Go 與底層作業系統互動並讀取環境變量的方式。
The presented code, which will implement the functionality of which(1), can be divided into three logical parts. The first part is about reading the input argument, which is the name of the executable file that the utility will be searching for. The second part is about reading the value stored in the PATH environment variable, splitting it, and iterating over the directories of the PATH variable. The third part is about looking for the desired binary file in these directories and determining whether it can be found or not, whether it is a regular file, and whether it is an executable file. If the desired executable file is found, the program terminates with the help of the return statement. Otherwise, it will terminate after the for loop ends and the main() function exits.
所提供的程式碼將實現 which(1) 的功能,可以分為三個邏輯部分。第一部分是讀取輸入參數,它是實用程式將搜尋的可執行檔的名稱。第二部分是讀取儲存在 PATH 環境變數中的值,將其拆分,然後迭代 PATH 變數的目錄。第三部分是在這些目錄中尋找想要的二進位檔案並判斷是否能找到、是否是常規檔案、是否是可執行檔。如果找到所需的可執行文件,程式將在 return 語句的幫助下終止。否則,它將在 for 循環結束且 main() 函數退出後終止。
The presented source file is called which.go and is located under the ch01 directory of the GitHub repository of the book. Now, let us see the code, beginning with the logical preamble that usually includes the package name, the import statements, and other definitions with a global scope:
所提供的來源檔案名稱為 which.go ,位於本書 GitHub 儲存庫的 ch01 目錄下。現在,讓我們來看看程式碼,從邏輯序言開始,通常包括套件名稱、 import 語句以及具有全域範圍的其他定義:
package main
import (
    "fmt"
    "os"
    "path/filepath"
)
The fmt package is used for printing onscreen, the os package is for interacting with the underlying operating system, and the path/filepath package is used for working with the contents of the PATH variable that is read as a long string, depending on the number of directories it contains.
fmt 套件用於在螢幕上列印, os 套件用於與底層作業系統交互, path/filepath 套件用於處理內容作為長字串讀取的 PATH 變數的值,取決於它包含的目錄數量。
The second logical part of the utility is the following:
該實用程式的第二個邏輯部分如下:
func main() {
    arguments := os.Args
    if len(arguments) == 1 {
        fmt.Println("Please provide an argument!")
        return
    }
    file := arguments[1]
    path := os.Getenv("PATH")
    pathSplit := filepath.SplitList(path)
    for _, directory := range pathSplit {
First, we read the command line arguments of the program (os.Args) and save the first command line argument into the file variable. Then, we get the contents of the PATH environment variable and split it using filepath.SplitList(), which offers a portable way of separating a list of paths. Lastly, we iterate over all the directories of the PATH variable using a for loop with range, as filepath.SplitList() returns a slice.
首先,我們讀取程式的命令列參數 ( os.Args ) 並將第一個命令列參數儲存到 file 變數中。然後,我們取得 PATH 環境變數的內容並使用 filepath.SplitList() 分割它,這提供了一種分隔路徑清單的可移植方法。最後,我們使用 for 迴圈和 range 迭代 PATH 變數的所有目錄,因為 filepath.SplitList() 傳回一個切片。
The rest of the utility contains the following code:
該實用程式的其餘部分包含以下程式碼:
        fullPath := filepath.Join(directory, file)
        // Does it exist?
        fileInfo, err := os.Stat(fullPath)
        if err != nil {
            continue
        }
        mode := fileInfo.Mode()
        // Is it a regular file?
        if !mode.IsRegular() {
            continue
        }
        // Is it executable?
        if mode&0111 != 0 {
            fmt.Println(fullPath)
            return
        }
    }
}
We construct the full path that we examine using filepath.Join(), which is used for concatenating the different parts of a path using an OS-specific separator—this makes filepath.Join() work on all supported operating systems. In this part, we also get some lower-level information about the file—keep in mind that UNIX considers everything as a file, which means that we want to make sure that we are dealing with a regular file that is also executable.
我們使用 filepath.Join() 建構我們檢查的完整路徑,該路徑用於使用特定於作業系統的分隔符號連接路徑的不同部分- 這使得 filepath.Join() 在所有支援的作業系統上工作。在這一部分中,我們還獲得有關文件的一些較低級別的信息 - 請記住,UNIX 將所有內容視為文件,這意味著我們要確保我們正在處理也是可執行的常規文件。
Executing which.go generates the following kind of output:
執行 which.go 會產生以下類型的輸出:
$ go run which.go which
/usr/bin/which
$ go run which.go doesNotExist
The last command could not find the doesNotExist executable—according to the UNIX philosophy and the way UNIX pipes work, utilities generate no output onscreen if they have nothing to say.
最後一個命令找不到 doesNotExist 可執行檔 - 根據 UNIX 哲學和 UNIX 管道的工作方式,如果實用程式沒有什麼可說的,則它們不會在螢幕上產生輸出。
Although it is useful to print error messages onscreen, there are times that you need to keep all error messages together and be able to search for them later when it is convenient for you. In this case, you need to use one or more log files.
儘管在螢幕上列印錯誤訊息很有用,但有時您需要將所有錯誤訊息保存在一起,以便稍後方便時進行搜尋。在這種情況下,您需要使用一個或多個日誌檔案。
The next section discusses logging in Go.
下一節將討論 Go 中的日誌記錄。

Logging information 記錄資訊

All UNIX systems have their own log files for writing logging information that comes from running servers and programs. Usually, most system log files of a UNIX system can be found under the /var/log directory. However, the log files of many popular services, such as Apache and Nginx, can be found elsewhere, depending on their configuration.
所有 UNIX 系統都有自己的日誌文件,用於寫入來自正在運行的伺服器和程式的日誌資訊。通常UNIX系統的大部分系統日誌檔案都可以在 /var/log 目錄下找到。然而,許多流行服務(例如 Apache 和 Nginx)的日誌檔案可以在其他地方找到,具體取決於它們的配置。
Logging and storing logging information in log files is a practical way of examining data and information from your software asynchronously, either locally, at a central log server, or using server software such as Elasticsearch, Beats, and Grafana Loki.
在日誌檔案中記錄和儲存日誌資訊是一種非同步檢查軟體中的資料和資訊的實用方法,可以在本機、中央日誌伺服器上,也可以使用 Elasticsearch、Beats 和 Grafana Loki 等伺服器軟體。
Generally speaking, using a log file to write some information used to be considered a better practice than writing the same output on screen for two reasons. Firstly, because the output does not get lost, as it is stored on a file, and secondly, because you can search and process log files using UNIX tools, such as grep(1), awk(1), and sed(1), which cannot be done when messages are printed in a terminal window. However, writing to log files is not always the best approach, mainly because many services run as Docker images, which have their own log files that get lost when the Docker image stops.
一般來說,使用日誌檔案寫入一些資訊通常被認為是比在螢幕上寫入相同輸出更好的做法,原因有兩個。首先,因為輸出不會遺失,因為它儲存在文件中,其次,因為您可以使用 UNIX 工具搜尋和處理日誌文件,例如 grep(1)awk(1)sed(1)
As we usually run our services via systemd, programs should log to stdout so that systemd can put logging data in the journal. https://12factor.net/logs offers more information about app logs. Additionally, in cloud-native applications, we are encouraged to simply log to stderr and let the container system redirect the stderr stream to the desired destination.
由於我們通常透過 systemd 來運行我們的服務,程式應該記錄到 stdout ,以便 systemd 可以將日誌記錄資料放入日誌中。 https://12factor.net/logs 提供有關應用程式日誌的更多資訊。此外,在雲端原生應用程式中,我們鼓勵簡單地登入 stderr 並讓容器系統將 stderr 流重定向到所需的目的地。
The UNIX logging service has support for two properties named logging level and logging facility. The logging level is a value that specifies the severity of the log entry. There are various logging levels, including debug, info, notice, warning, err, crit, alert, and emerg, in reverse order of severity. The log package of the standard Go library does not support working with logging levels. The logging facility is like a category used for logging information. The value of the logging facility part can be one of auth, authpriv, cron, daemon, kern, lpr, mail, mark, news, syslog, user, UUCP, local0, local1, local2, local3, local4, local5, local6, or local7 and is defined inside /etc/syslog.conf, /etc/rsyslog.conf, or another appropriate file depending on the server process used for system logging on your UNIX machine. This means that if a logging facility is not defined correctly, it will not be handled; therefore, the log messages you send to it might get ignored and, therefore, lost.
UNIX 日誌記錄服務支援兩個名為日誌記錄等級和日誌記錄工具的屬性。日誌記錄等級是指定日誌條目的嚴重性的值。有多種日誌記錄級別,包括 debuginfonoticewarningerrcritalertemerg ,依照嚴重程度的相反順序。標準 Go 函式庫的 log 套件不支援使用日誌記錄等級。日誌記錄工具就像是用於記錄資訊的類別。日誌記錄工具部分的值可以是 authauthprivcrondaemonkern 之一、 lprmailmarknewssysloguser 、< b20 >、 local0local1local2local3local4local5local7 ,並在 /etc/syslog.conf/etc/rsyslog.conf 或其他適當的檔案內定義,取決於伺服器進程用於UNIX 計算機上的系統日誌記錄。這意味著如果日誌記錄工具未正確定義,則不會對其進行處理;因此,您發送給它的日誌訊息可能會被忽略並因此丟失。
The log package sends log messages to standard error. Part of the log package is the log/syslog package, which allows you to send log messages to the syslog server of your machine. Although by default log writes to standard error, the use of log.SetOutput() modifies that behavior. The list of functions for sending logging data includes log.Printf(), log.Print(), log.Println(), log.Fatalf(), log.Fatalln(), log.Panic(), log.Panicln(), and log.Panicf().
log 包將日誌訊息傳送到標準錯誤。 log 套件的一部分是 log/syslog 套件,它允許您將日誌訊息傳送到電腦的系統日誌伺服器。儘管預設日誌寫入標準錯誤,但使用 log.SetOutput() 會修改該行為。用來傳送日誌資料的函數清單包括 log.Printf()log.Print()log.Println()log.Fatalf()log.Fatalln() 、 < b9> 、 log.Panicln()log.Panicf()
Logging is for application code, not library code. If you are developing libraries, do not put logging in them.
日誌記錄適用於應用程式程式碼,而不是庫程式碼。如果您正在開發庫,請不要在其中新增日誌記錄。
In order to write to system logs, you need to call the syslog.New() function with the appropriate parameters. Writing to the main system log file is as easy as calling syslog.New() with the syslog.LOG_SYSLOG option. After that, you need to tell your Go program that all logging information goes to the new logger—this is implemented with a call to the log.SetOutput() function. The process is illustrated in the following code—type it into your favorite plain text editor and save it as systemLog.go:
為了寫入系統日誌,您需要使用適當的參數呼叫 syslog.New() 函數。寫入主系統日誌檔案就像使用 syslog.LOG_SYSLOG 選項呼叫 syslog.New() 一樣簡單。之後,您需要告訴您的 Go 程序,所有日誌記錄資訊都會傳送到新的記錄器 - 這是透過呼叫 log.SetOutput() 函數來實現的。以下程式碼說明了該過程 - 將其輸入您最喜歡的純文字編輯器並將其另存為 systemLog.go
package main
import (
    "log"
    "log/syslog"
)
func main() {
    sysLog, err := syslog.New(syslog.LOG_SYSLOG, "systemLog.go")
    if err != nil {
        log.Println(err)
        return
    } else {
        log.SetOutput(sysLog)
        log.Print("Everything is fine!")
    }
}
After the call to log.SetOutput(), all logging information goes to the syslog logger variable which sends it to syslog.LOG_SYSLOG. Custom text for the log entries coming from that program is specified as the second parameter to the syslog.New() call.
呼叫 log.SetOutput() 後,所有日誌記錄資訊都會傳送到 syslog 記錄器變量,該變數將其傳送至 syslog.LOG_SYSLOG 。來自程式的日誌條目的自訂文字被指定為 syslog.New() 呼叫的第二個參數。
Usually, we want to store logging data in user-defined files because they group relevant information, which makes them easier to process and inspect.
通常,我們希望將日誌資料儲存在使用者定義的檔案中,因為它們會對相關資訊進行分組,這使得它們更易於處理和檢查。
Running systemLog.go generates no output. However, if you execute journalctl -xe on a Linux machine, you can see entries like the following:
運行 systemLog.go 不會產生任何輸出。但是,如果您在 Linux 電腦上執行 journalctl -xe ,您可以看到如下所示的條目:
Jun 08 20:46:05 thinkpad systemLog.go[4412]: 2023/06/08 20:46:05 Everything is fine!
Jun 08 20:46:51 thinkpad systemLog.go[4822]: 2023/06/08 20:46:51 Everything is fine!
The output on your own operating system might be slightly different, but the general idea is the same.
您自己的作業系統上的輸出可能略有不同,但總體思路是相同的。
Bad things happen all the time, even to good people and good software. So the next subsection covers the Go way of dealing with bad situations.
糟糕的事情總是會發生,即使對於好人和好的軟體也是如此。因此,下一小節將介紹處理不良狀況的Go方法。

log.Fatal() and log.Panic()
log.Fatal() 和 log.Panic()

The log.Fatal() function is used when something erroneous has happened and you just want to exit your program as soon as possible after reporting that bad situation. The call to log.Fatal() terminates a Go program at the point where log.Fatal() was called after printing an error message. In most cases, this custom error message can be Not enough arguments, Cannot access file, or similar. Additionally, it returns a non-zero exit code, which in UNIX indicates an error.
當發生錯誤並且您只想在報告錯誤情況後儘快退出程式時,請使用 log.Fatal() 函數。對 log.Fatal() 的呼叫會在列印錯誤訊息後呼叫 log.Fatal() 的地方終止 Go 程式。在大多數情況下,此自訂錯誤訊息可以是 Not enough argumentsCannot access file 或類似訊息。此外,它還傳回一個非零退出代碼,這在 UNIX 中表示錯誤。
There are situations where a program is about to fail for good and you want to have as much information about the failure as possible—log.Panic() implies that something really unexpected and unknown, such as not being able to find a file that was previously accessed or not having enough disk space, has happened. Analogous to the log.Fatal() function, log.Panic() prints a custom message and immediately terminates the Go program.
在某些情況下,程式即將永遠失敗,而您希望獲得盡可能多的有關失敗的信息 - log.Panic() 意味著確實發生了意外和未知的事情,例如無法找到文件以前訪問過或沒有足夠的磁碟空間的情況已經發生。與 log.Fatal() 函數類似, log.Panic() 列印自訂訊息並立即終止 Go 程式。
Keep in mind that log.Panic() is equivalent to a call to log.Print(), followed by a call to panic(). This is a built-in function that stops the execution of the current function and begins panicking. After that, it returns to the caller function. Conversely, log.Fatal() calls log.Print() and then os.Exit(1), which is an immediate way of terminating the current program. Both log.Fatal() and log.Panic() are illustrated in the logs.go file, which contains the following Go code:
請記住, log.Panic() 相當於呼叫 log.Print() ,然後呼叫 panic() 。這是一個內建函數,可以停止目前函數的執行並開始恐慌。之後,它會返回到呼叫者函數。相反, log.Fatal() 呼叫 log.Print() 然後呼叫 os.Exit(1) ,這是終止目前程式的直接方法。 log.Fatal()log.Panic() 皆在 logs.go 檔案中說明,其中包含以下 Go 程式碼:
package main
import (
    "log"
    "os"
)
func main() {
    if len(os.Args) != 1 {
        log.Fatal("Fatal: Hello World!")
    }
    log.Panic("Panic: Hello World!")
}
If you call logs.go without any command line arguments, it calls log.Panic(). Otherwise, it calls log.Fatal(). This is illustrated in the following output from an Arch Linux system:
如果您在沒有任何命令列參數的情況下呼叫 logs.go ,它將呼叫 log.Panic() 。否則,它會呼叫 log.Fatal() 。 Arch Linux 系統的以下輸出對此進行了說明:
$ go run logs.go
2023/06/08 20:48:42 Panic: Hello World!
panic: Panic: Hello World!
goroutine 1 [running]:
log.Panic({0xc000104f60?, 0x0?, 0x0?})
    /usr/lib/go/src/log/log.go:384 +0x65
main.main()
    /home/mtsouk/code/mGo4th/ch01/logs.go:12 +0x85
exit status 2
$ go run logs.go 1
2023/06/08 20:48:59 Fatal: Hello World!
exit status 1
So the output of log.Panic() includes additional low-level information that, hopefully, will help you resolve difficult situations that arise in your Go code.
因此, log.Panic() 的輸出包含額外的低階訊息,希望能夠幫助您解決 Go 程式碼中出現的困難情況。
Please keep in mind that both of these functions terminate the program abruptly, which may not be what the user wants. As a result, they are not the best way to end a program. However, they can be handy for reporting really bad error conditions or unexpected situations. Two such examples are when a program is unable to save its data or when a configuration file is not found.
請記住,這兩個函數都會突然終止程序,這可能不是使用者想要的。因此,它們並不是結束計劃的最佳方式。然而,它們可以方便地報告非常糟糕的錯誤情況或意外情況。兩個這樣的範例是程式無法保存其資料或找不到設定檔時。
The next subsection is about writing to custom log files.
下一小節是關於寫入自訂日誌檔案的。

Writing to a custom log file
寫入自訂日誌文件

Most of the time, and especially on applications and services that are deployed to production, you need to write your logging data in a log file of your choice. This can be for many reasons, including writing debugging data without messing with the system log files, or keeping your own logging data separate from system logs to transfer it or store it in a database or software, like Elasticsearch. This subsection teaches you how to write to a custom log file that is usually application-specific.
大多數時候,尤其是在部署到生產的應用程式和服務上,您需要將日誌記錄資料寫入您選擇的日誌檔案中。這可能有多種原因,包括在不弄亂系統日誌檔案的情況下編寫偵錯數據,或者將您自己的日誌資料與系統日誌分開以將其傳輸或儲存在資料庫或軟體(如 Elasticsearch)中。本小節將教您如何寫入通常特定於應用程式的自訂日誌檔案。
Writing to files and file input and output are both covered in Chapter 7, Telling a UNIX System What to Do—however, saving information to files is very handy when troubleshooting and debugging Go code, which is why this is covered in the first chapter.
寫入檔案以及檔案輸入和輸出都在第7 章「告訴UNIX 系統做什麼」中介紹- 但是,在故障排除和調試Go 程式碼時,將資訊儲存到檔案非常方便,這就是為什麼這是第一章介紹了。
The path of the log file (mGo.log) that is used is stored on a variable named LOGFILE—this is created using the os.TempDir() function, which returns the default directory used on the current OS for temporary files, in order to prevent your file system from getting full in case something goes wrong.
使用的日誌檔案 ( mGo.log ) 的路徑儲存在名為 LOGFILE 的變數中 - 這是使用 os.TempDir() 函數建立的,該函數傳回預設值目前作業系統上用於臨時檔案的目錄,以防止檔案系統在出現問題時變滿。
Additionally, at this point, this will save you from having to execute customLog.go with root privileges and putting unnecessary files into precious system directories.
此外,此時,這將使您不必使用 root 權限執行 customLog.go 並將不必要的檔案放入寶貴的系統目錄中。
Type the following code and save it as customLog.go:
輸入以下程式碼並將其儲存為 customLog.go
package main
import (
    "fmt"
    "log"
    "os"
    "path"
)
func main() {
    LOGFILE := path.Join(os.TempDir(), "mGo.log")
    fmt.Println(LOGFILE)
    f, err := os.OpenFile(LOGFILE, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
// The call to os.OpenFile() creates the log file for writing, 
// if it does not already exist, or opens it for writing 
// by appending new data at the end of it (os.O_APPEND)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer f.Close()
The defer keyword tells Go to execute the statement just before the current function returns. This means that f.Close() is going to be executed just before main() returns. We will go into more detail on defer in Chapter 6, Go Packages and Functions:
defer 關鍵字告訴 Go 在目前函數傳回之前執行該語句。這意味著 f.Close() 將在 main() 返回之前執行。我們將在第 6 章 Go 套件和函數中go詳細介紹 defer
    iLog := log.New(f, "iLog ", log.LstdFlags)
    iLog.Println("Hello there!")
    iLog.Println("Mastering Go 4th edition!")
}
The last three statements create a new log file based on an opened file (f) and write two messages to it, using Println().
最後三個語句是基於開啟的文件 ( f ) 建立一個新的日誌文件,並使用 Println() 向其中寫入兩個訊息。
If you ever decide to use the code of customLog.go in a real application, you should change the path stored in LOGFILE to something that makes more sense.
如果您決定在實際應用程式中使用 customLog.go 的程式碼,則應該將 LOGFILE 中儲存的路徑變更為更有意義的路徑。
Running customLog.go on an Arch Linux machine prints the file path of the log file:
在 Arch Linux 機器上執行 customLog.go 會列印日誌檔案的檔案路徑:
$ go run customLog.go
/tmp/mGo.log
Depending on your operating system, your output might vary. However, what is important is what has been written in the custom log file:
根據您的作業系統,您的輸出可能會有所不同。然而,重要的是自訂日誌檔案中寫入的內容:
$ cat /tmp/mGo.log
iLog 2023/11/27 22:15:10 Hello there!
iLog 2023/11/27 22:15:10 Mastering Go 4th edition!
The next subsection shows how to print line numbers in log entries.
下一小節介紹如何列印日誌條目中的行號。

Printing line numbers in log entries
列印日誌條目中的行號

In this subsection, you will learn how to print the filename as well as the line number in the source file where the statement that wrote a log entry is located.
在本小節中,您將學習如何列印檔案名稱以及來源檔案中寫入日誌條目的語句所在的行號。
The desired functionality is implemented with the use of log.Lshortfile in the parameters of log.New() or SetFlags(). The log.Lshortfile flag adds the filename as well as the line number of the Go statement that printed the log entry in the log entry itself. If you use log.Llongfile instead of log.Lshortfile, then you get the full path of the Go source file—usually, this is not necessary, especially when you have a really long path.
所需的功能是透過在 log.New()SetFlags() 參數中使用 log.Lshortfile 來實現的。 log.Lshortfile 標誌在日誌條目本身中新增檔案名稱以及列印日誌條目的 Go 語句的行號。如果您使用 log.Llongfile 而不是 log.Lshortfile ,那麼您將獲得 Go 原始檔案的完整路徑 - 通常,這不是必需的,特別是當您有路真的很長。
Type the following code and save it as customLogLineNumber.go:
輸入以下程式碼並將其儲存為 customLogLineNumber.go
package main
import (
    "fmt"
    "log"
    "os"
    "path"
)
func main() {
    LOGFILE := path.Join(os.TempDir(), "mGo.log")
    fmt.Println(LOGFILE)
    f, err := os.OpenFile(LOGFILE, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer f.Close()
    LstdFlags := log.Ldate | log.Lshortfile
    iLog := log.New(f, "LNum ", LstdFlags)
    iLog.Println("Mastering Go, 4th edition!")
    iLog.SetFlags(log.Lshortfile | log.LstdFlags)
    iLog.Println("Another log entry!")
}
In case you are wondering, you are allowed to change the format of the log entries during program execution—this means that when there is a reason, you can print more analytical information in the log entries. This is implemented with multiple calls to iLog.SetFlags().
如果您想知道,您可以在程式執行期間​​更改日誌條目的格式 - 這表示當有原因時,您可以在日誌條目中列印更多分析資訊。這是透過多次呼叫 iLog.SetFlags() 來實現的。
Running customLogLineNumber.go generates the following output:
運行 customLogLineNumber.go 會產生以下輸出:
$ go run customLogLineNumber.go
/var/folders/sk/ltk8cnw50lzdtr2hxcj5sv2m0000gn/T/mGo.log
It also writes the following entries in the file path that is specified by the value of the LOGFILE global variable:
它也會在由 LOGFILE 全域變數的值指定的檔案路徑中寫入以下條目:
$ cat /var/folders/sk/ltk8cnw50lzdtr2hxcj5sv2m0000gn/T/mGo.log
LNum 2023/06/08 customLogLineNumber.go:25: Mastering Go, 4th edition!
LNum 2023/06/08 20:58:09 customLogLineNumber.go:28: Another log entry!
The first error message is from source code line 25, whereas the second one is from source code line 28.
第一條錯誤訊息來自原始碼第 25 行,而第二個錯誤訊息來自原始碼第 28 行。
You will most likely get a different output on your own machine, which is the expected behavior.
您很可能會在自己的計算機上得到不同的輸出,這是預期的行為。

Writing to multiple log files
寫入多個日誌文件

This subsection shows a technique for writing to multiple log files—this is illustrated in multipleLogs.go, which can be found in the GitHub repository of the book under directory ch01 and comes with the following code:
本小節展示了寫入多個日誌檔案的技術- 這在 multipleLogs.go 中進行了說明,可以在本書的GitHub 儲存庫的目錄 ch01 下找到它,並附帶以下代碼:
package main
import (
    "fmt"
    "io"
    "log"
    "os"
)
func main() {
    flag := os.O_APPEND | os.O_CREATE | os.O_WRONLY
    file, err := os.OpenFile("myLog.log", flag, 0644)
    if err != nil {
        fmt.Println(err)
        os.Exit(0)
    }
    defer file.Close()
    w := io.MultiWriter(file, os.Stderr)
    logger := log.New(w, "myApp: ", log.LstdFlags)
    logger.Printf("BOOK %d", os.Getpid())
}
The io.MultiWriter() function is what allows us to write to multiple destinations, which in this case are a file named myLog.log and standard error.
io.MultiWriter() 函數讓我們可以寫入多個目標,在本例中是一個名為 myLog.log 的檔案和標準錯誤。
The results of running multipleLogs.go can be seen in the myLog.log file, which is going to be created in the current working directory, and to standard error, which is usually presented on screen:
運行 multipleLogs.go 的結果可以在 myLog.log 檔案中看到,該檔案將在當前工作目錄中創建,並顯示標準錯誤,通常顯示在螢幕上:
$ go run multipleLogs.go
myApp: 2023/06/24 21:02:55 BOOK 71457
The contents of myLog.log are the same as before:
myLog.log 的內容與之前相同:
$ at myLog.log
myApp: 2023/06/24 21:02:55 BOOK 71457
In the next section, we are going to write the first version of the statistics application.
在下一節中,我們將編寫統計應用程式的第一個版本。

Developing a statistics application
開發統計應用程式

In this section, we are going to develop a basic statistics application stored in stats.go. The statistical application is going to be improved and enriched with new features throughout this book.
在本節中,我們將開發一個儲存在 stats.go 中的基本統計應用程式。本書中的統計應用程式將透過新功能得到改進和豐富。
The first part of stats.go is the following:
stats.go 的第一部分如下:
package main
import (
    "fmt"
    "math"
    "os"
    "strconv"
)
func main() {
    arguments := os.Args
    if len(arguments) == 1 {
        fmt.Println("Need one or more arguments!")
        return
    }
In this first part of the application, the necessary Go packages are imported before the main() function makes sure that we have at least a single command line parameter to work with, using len(arguments) == 1.
在應用程式的第一部分中,在 main() 函數確保我們至少有一個可以使用的命令列參數之前導入必要的 Go 包,使用 len(arguments) == 1
The second part of stats.go is the following:
stats.go 的第二部分如下:
    var min, max float64
    var initialized = 0
    nValues := 0
    var sum float64
    for i := 1; i < len(arguments); i++ {
        n, err := strconv.ParseFloat(arguments[i], 64)
        if err != nil {
            continue
        }
        nValues = nValues + 1
        sum = sum + n
        if initialized == 0 {
            min = n
            max = n
            initialized = 1
            continue
        }
        if n < min {
            min = n
        }
        if n > max {
            max = n
        }
    }
    fmt.Println("Number of values:", nValues)
    fmt.Println("Min:", min)
    fmt.Println("Max:", max)
In the previous code excerpt, we process all valid inputs to count the number of valid values and find the minimum and the maximum values among them.
在前面的程式碼摘錄中,我們處理所有有效輸入以計算有效值的數量並找到其中的最小值和最大值。
The last part of stats.go is the following:
stats.go 的最後一部分如下:
    // Mean value
    if nValues == 0 {
        return
    }
meanValue := sum / float64(nValues)
    fmt.Printf("Mean value: %.5f\n", meanValue)
    // Standard deviation
    var squared float64
for i := 1; i < len(arguments); i++ {
        n, err := strconv.ParseFloat(arguments[i], 64)
        if err != nil {
            continue
        }
        squared = squared + math.Pow((n-meanValue), 2)
    }
    standardDeviation := math.Sqrt(squared / float64(nValues))
    fmt.Printf("Standard deviation: %.5f\n", standardDeviation)
}
In the previous code excerpt, we find the mean value because this cannot be computed without processing all values first. After that, we process each valid value to compute the standard deviation because the mean value is required in order to compute the standard deviation.
在前面的程式碼摘錄中,我們找到了平均值,因為如果不先處理所有值就無法計算平均值。之後,我們處理每個有效值以計算標準差,因為計算標準差需要平均值。
Running stats.go generates the following kind of output:
運行 stats.go 會產生以下類型的輸出:
$ go run stats.go 1 2 3
Number of values: 3
Min: 1
Max: 3
Mean value: 2.00000
Standard deviation: 0.81650

Summary 概括

At the beginning of this chapter, we discussed the advantages, disadvantages, philosophy, and history of Go. Then, the basics of Go were presented, which include variables, iterations, and flow control as well as how to log data.
在本章開頭,我們討論了Go的優點、缺點、哲學和歷史。然後,介紹了Go的基礎知識,包括變數、迭代、流程控制以及如何記錄資料。
After that, we learned about logging, implemented which(1), and created a basic statistics application.
之後,我們了解了日誌記錄,實現了 which(1) ,並創建了一個基本的統計應用程式。
The next chapter is all about the basic Go data types.
下一章將介紹基本的 Go 資料型態。

Exercises 練習

Test out what you have learned by trying to complete the following exercises:
透過嘗試完成以下練習來測試您所學到的知識:
  • Read the documentation of the fmt package using go doc.
    使用 go doc 閱讀 fmt 套件的文件。
  • In UNIX, an exit code of 0 means success, whereas a non-zero exit code usually means failure. Try to modify which.go to do so with the help of os.Exit().
    在 UNIX 中,退出代碼 0 表示成功,而非零退出代碼通常表示失敗。嘗試在 os.Exit() 的幫助下修改 which.go 來執行此操作。
  • The current version of which(1) stops after finding the first occurrence of the desired executable. Make the necessary code changes to which.go in order to find all possible occurrences of the desired executable.
    which(1) 的當前版本在找到第一次出現的所需執行檔後停止。對 which.go 進行必要的程式碼更改,以便找到所需執行檔的所有可能出現位置。

Additional resources 其他資源

Leave a review! 留下評論!

Enjoying this book? Help readers like you by leaving an Amazon review. Scan the QR code below to get a free eBook of your choice.
喜歡這本書嗎?透過留下亞馬遜評論來幫助像您這樣的讀者。掃描下面的二維碼即可取得您選擇的免費電子書。