記事とコードは [Github ウェアハウス: https://github.com/timerring/backend-tutorial ]にアーカイブされています。または、 go に返信することで公開アカウント [AIShareLab]を取得することもできます。
記事ディレクトリ
序章
Go言語とは何ですか
- 高いパフォーマンスと高い同時実行性
- シンプルな構文と穏やかな学習曲線
- 豊富な標準ライブラリ(多くの場合、サードパーティライブラリの助けを借りずに基本機能の開発を完了できます)
- 完璧なツールチェーン (コンパイル、コード検査、補足プロンプトなど、対応するツールがあり、完全な単体テスト フレームワーク、パフォーマンス テストなどが組み込まれています)
- 静的リンク (すべてのコンパイル結果は静的にリンクされ、コンパイルされた実行可能ファイルをコピーするだけで済み、何も追加せずに直接実行でき、デプロイメントは便利で高速です。Java と比較して、実行するには巨大な jre が必要です)
- 非常に高速なコンパイル
- クロスプラットフォーム (さまざまなデバイスで実行可能、クロスコンパイル機能があり、クロスコンパイル環境を構成する必要はありません)
- ガベージ コレクション (ビジネス ロジックに焦点を当て、メモリの割り当てと解放を考慮する必要はありません)
Go言語を使用している企業はどこですか
まず第一に、ByteDance は go 言語を完全に採用しています。社内には golang で書かれた何万ものマイクロサービスがあります。少し前には、GORPC フレームワーク KiteX もオープンソース化されました。Tencent、Baidu、Meituan、Didi、Sangfor、Ping An、OPPO、Zhihu、Qunar、360、Jinshan、Weibo、bilibili、Qiniu、PingCAP などの企業も Go 言語を広く使用しています。
Google や Facebook などの外国企業も Go 言語を広く使用しています。
ビジネスの観点から見ると、言語はクラウド コンピューティング、マイクロサービス、ビッグ データ、ブロックチェーン、モノのインターネットなどの分野で栄えています。そして、クラウド コンピューティングとマイクロサービスの分野では、
Docker、Kubernetes、Istio、etcd、prometheus がすでに非常に高い市場シェアを獲得しており、ほぼすべてのクラウド ネイティブ コンポーネントが Go で実装されています。
Go 言語を変革する理由
- 当初、同社のバックエンド ビジネスは Web バックエンドが中心でしたが、初期のチームには Java のバックグラウンドがなく、C++ はオンライン Web ビジネスには適していませんでした。
- 当初のサービスは Python でしたが、2014 年以降、ビジネス量の増加に伴い、Python ではパフォーマンスの問題が発生しました。
- 一部のチームは最初に Go を使用しようとしたところ、簡単に始めることができ、開発効率が高く、パフォーマンスが比較的良好であることがわかりました。
- Go 言語の開発とデプロイは非常に簡単で、ちなみに、以前は Python によって引き起こされていた厄介な依存関係ライブラリのバージョン問題も解決されます。
- —一部の企業が甘さを味わった後、企業レベルで精力的に推進し始め、社内の golang ベースの rpc フレームワークと http フレームワークが誕生しました。
Go の入門
インストールに行く
go.dev/ にアクセスするか、studygolang.com/dl にアクセスしてください。
Goの開発環境
- クラウドベースの開発環境: gitpods.IO オンライン プログラミング環境、GitHub アカウントが必要
- 統合開発環境: Vs Code (無料) または Goland (有料)
基本的な文法
// package main 代表这个文件属于main包的一部分, main包也就是程序的入口包。
package main
// 导入了标准库里面的 FMT 包。这个包主要是用来往屏幕输入输出字符串、格式化字符串。
import (
"fmt"
)
// import 下面是 main 函数,main 函数的话里面调用了 fmt.Println 输出 helloworld要运行这个程序的话,我们就直接 go run heloworld.go。
func main() {
fmt.Println("hello world")
}
バイナリにコンパイルする場合は、 でgo build
コンパイルできます。コンパイルが完了したら、./helloword
FMT で直接実行できます。パッケージには、さまざまな入力および出力の書式設定タスクを実行するための関数が多数あります。エディターでコードの上にマウスを置くと、各関数のドキュメントが表示されます。
また、「」と入力しpkg.Go.Dev
、FMT などのパッケージ名を追加すると、このパッケージのオンライン ドキュメントが表示され、そこから使用する必要がある機能を選択できます。
変数
package main
import (
"fmt"
"math"
)
func main() {
var a = "initial"
var b, c int = 1, 2
var d = true
var e float64
f := float32(e)
g := a + "foo"
fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
fmt.Println(g) // initialapple
const s string = "constant"
const h = 500000000
const i = 3e20 / h
fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
}
変数の型に関する 2 番目の例を見てみましょう。
Go 言語は厳密に型指定された言語であり、各変数には独自の変数型があります。一般的な変数の型には、文字列、整数、浮動小数点型、ブール型などが含まれます。Go 言語の文字列は組み込み型であり、プラス記号で直接連結したり、2 つの文字列を等号で比較するために直接使用したりできます。 。
Go 言語での変数の宣言、Go 言語で変数を宣言するには 2 つの方法があります
- 1 つは、
var name string=" "
このように変数を宣言する方法で、変数を宣言すると、通常、変数の型が自動的に推定されます。必要に応じて、変数の型を明示的に書き出すこともできます。 - 変数を宣言するもう 1 つの方法は、 を使用することです
变量 := 等于值
。
定数の場合は、var を const に変更します。上記の値は Go 言語の定数です。明確な型はなく、型は使用状況に応じて自動的に決定されます。
if-else
package main
import "fmt"
func main() {
if 7%2 == 0 {
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}
if 8%4 == 0 {
fmt.Println("8 is divisible by 4")
}
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
}
Go言語におけるif elseの書き方はCやC++と似ています。違いは、if の後に括弧がないことです。Golang では if の後に中括弧が続く必要があり、C や C++ のように同じ行に if ステートメントを直接置くことはできません。
サイクル
package main
import "fmt"
func main() {
i := 1
for {
fmt.Println("loop")
break
}
for j := 7; j < 9; j++ {
fmt.Println(j)
}
for n := 0; n < 5; n++ {
if n%2 == 0 {
continue
}
fmt.Println(n)
}
for i <= 3 {
fmt.Println(i)
i = i + 1
}
}
go には while ループと do while ループはありませんが、for ループは 1 種類だけです。最も単純な for ループは、for の後に何も書かないことです。これは無限ループを意味します。Break または continue を使用してブレークアウトするか、ループを継続することができます。
スイッチ
package main
import (
"fmt"
"time"
)
func main() {
a := 2
switch a {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
case 4, 5:
fmt.Println("four or five")
default:
fmt.Println("other")
}
t := time.Now()
switch {
// case 里面写条件分支
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
}
スイッチの後ろの変数名には括弧は必要ありません。ここには大きな違いがあり、C++ では switch ケースでブレークが表示されない場合、すべてのケースで実行が継続されますが、go 言語ではブレークを追加する必要がありません。
C や C++ と比較して、go 言語の switch 関数はより強力です。任意の変数タイプを使用でき、任意の if else ステートメントを置き換えるために使用することもできます。スイッチの背後に変数を追加して、ケース内に条件分岐を記述することはできません。こうすることで、複数の if else コードを使用する場合よりもコード ロジックが明確になります。
配列
package main
import "fmt"
func main() {
// 这里的话是一个可以存放 5 个 int 元素的数组 A
var a [5]int
a[4] = 100
fmt.Println("get:", a[2])
fmt.Println("len:", len(a))
b := [5]int{
1, 2, 3, 4, 5}
fmt.Println(b)
var twoD [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("2d: ", twoD)
}
配列は、番号が付けられた固定長の要素のシーケンスです。配列の場合、特定のインデックスの値を取得したり、特定のインデックスに値を格納したりして、配列を直接出力すると非常に便利です。ただし、実際のビジネス コードでは、配列の長さが固定されており、より多くのスライスを使用するため、配列を直接使用することはほとんどありません。
スライス
package main
import "fmt"
func main() {
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get:", s[2]) // c
fmt.Println("len:", len(s)) // 3
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s) // [a b c d e f]
c := make([]string, len(s))
copy(c, s)
fmt.Println(c) // [a b c d e f]
fmt.Println(s[2:5]) // [c d e]
fmt.Println(s[:5]) // [a b c d e]
fmt.Println(s[2:]) // [c d e f]
good := []string{
"g", "o", "o", "d"}
fmt.Println(good) // [g o o d]
}
配列とは異なり、スライスは長さを任意に変更でき、より多くの操作が可能です。makeを使ってスライスを作成したり、配列のように値を取得したり、appendを使って要素を追加したりできます。
append の使用法に注意してください。append の結果を元の配列に代入する必要があります。
スライスの原理は、実際には長さと容量に加えて配列へのポインタを持っているため、追加操作を実行するときに容量が十分でない場合は、拡張して新しいスライスを返します。
スライスは初期化時に長さを指定することもできます。
Slice は Python と同じスライス操作で、例えば 5 番目の要素を除いた 2 番目から 5 番目までの要素を取り出すことを意味します。ただし、Python とは異なり、ここでは負のインデックスはサポートされていません。
地図
package main
import "fmt"
func main() {
// 用 make 来创建一个空 map,这里会需要两个类型。
// 第一个是那个 key 的类型,这里是 string 另一个是 value 的类型,这里是 int。
m := make(map[string]int)
// 可以从里面去存储或者取出键值对。
m["one"] = 1
m["two"] = 2
fmt.Println(m) // map[one:1 two:2]
fmt.Println(len(m)) // 2
fmt.Println(m["one"]) // 1
fmt.Println(m["unknow"]) // 0
r, ok := m["unknow"]
fmt.Println(r, ok) // 0 false
// 可以用 delete 从里面删除键值对。
delete(m, "one")
m2 := map[string]int{
"one": 1, "two": 2}
var m3 = map[string]int{
"one": 1, "two": 2}
fmt.Println(m2, m3)
}
Map は実際に使用されるデータ構造として最も頻繁に使用されます。
Golang のマップは完全に順序付けされておらず、トラバースするときにアルファベット順でも、挿入順でも出力されず、ランダムな順序で出力されます。
範囲
package main
import "fmt"
func main() {
nums := []int{
2, 3, 4}
sum := 0
for i, num := range nums {
sum += num
if num == 2 {
fmt.Println("index:", i, "num:", num) // index: 0 num: 2
}
}
fmt.Println(sum) // 9
m := map[string]string{
"a": "A", "b": "B"}
for k, v := range m {
fmt.Println(k, v) // b 8; a A
}
for k := range m {
fmt.Println("key", k) // key a; key b
}
}
スライスまたはマップの場合、range を使用して素早く移動できるため、コードをより簡潔にすることができます。Range がトラバースすると、配列に対して 2 つの値が返されます。1 つ目はインデックス、2 つ目は対応する位置の値です。インデックスが必要ない場合は、アンダースコアを使用して省略できます。
関数
package main
import "fmt"
func add(a int, b int) int {
return a + b
}
func add2(a, b int) int {
return a + b
}
func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k]
return v, ok
}
func main() {
res := add(1, 2)
fmt.Println(res) // 3
// 第一个是真正的返回结果,第二个值是一个错误信息
v, ok := exists(map[string]string{
"a": "A"}, "a")
fmt.Println(v, ok) // A True
}
これは、2 つの変数を追加する Golang の単純な関数です。Golang と他の多くの言語の違いは、変数の型が後置されることです。
Golang の関数は、複数の値を返すことをネイティブにサポートしています。実際のビジネス ロジック コードでは、ほとんどすべての関数が 2 つの値を返します。1 つ目は実際の戻り結果で、2 つ目はエラー メッセージです。
ポインタ
package main
import "fmt"
func add2(n int) {
n += 2
}
func add2ptr(n *int) {
*n += 2
}
func main() {
n := 5
add2(n)
fmt.Println(n) // 5
add2ptr(&n)
fmt.Println(n) // 7
}
Go ではポインターもサポートされています。もちろん、C や C++ のポインターと比較すると、サポートされる操作は非常に限られています。ポインターの主な用途の 1 つは、受信パラメーターを変更することです。
たとえば、上記の関数は変数に 2 を加算しようとします。しかし、上記のように単に書くだけでは実際には無効です。関数に渡されるパラメータは実際にはシェルであるため、この +2 はコピーの +2 であるとも言われ、機能しません。作業する必要がある場合は、その型をポインター型として記述する必要があります。その後、型のマッチングのために、呼び出し時に & 記号が追加されます。
構造
package main
import "fmt"
type user struct {
name string
password string
}
func main() {
a := user{
name: "wang", password: "1024"}
b := user{
"wang", "1024"}
c := user{
name: "wang"}
c.password = "1024"
var d user
d.name = "wang"
d.password = "1024"
fmt.Println(a, b, c, d) // {wang 1024} {wang 1024} {wang 1024} {wang 1024}
fmt.Println(checkPassword(a, "haha")) // false
fmt.Println(checkPassword2(&a, "haha")) // false
}
func checkPassword(u user, password string) bool {
return u.password == password
}
// 同样的结构体我们也能支持指针,这样能够实现对于结构体的修改,也可以在某些情况下避免一些大结构体的拷贝开销。
func checkPassword2(u *user, password string) bool {
return u.password == password
}
構造体は型付きフィールドのコレクションです。
たとえば、ユーザー構造には、名前とパスワードという 2 つのフィールドが含まれています。構造体の名前を使用して構造体変数を初期化できますが、構築時に各フィールドの初期値を渡す必要があります。このキーと値のペアを使用して初期値を指定し、フィールドの一部のみを初期化することもできます。
構造体メソッド
package main
import "fmt"
type user struct {
name string
password string
}
func (u user) checkPassword(password string) bool {
return u.password == password
}
func (u *user) resetPassword(password string) {
u.password = password
}
func main() {
a := user{
name: "wang", password: "1024"}
a.resetPassword("2048")
fmt.Println(a.checkPassword("2048")) // true
}
Golang では、構造に対していくつかのメソッドを定義できます。これは他の言語のクラス メンバー関数に少し似ています。たとえば、ここではcheckPassword
上記の例の の実装を通常の関数から構造体メソッドに変更しました。このようにして、ユーザーは a.checkPassword ("x ) のように呼び出すことができます。 D 具体的なコードの変更は、最初のパラメーターを追加し、括弧を追加して、関数名の前に記述することです。
構造体のメソッドを実装する際にも、ポインタを使用する場合とポインタを使用しない場合の2つの書き方があります。ポインターがある場合は、この構造を変更できます。ポインタがない場合、操作は実際にはコピーとなり、構造を変更することはできません。
エラー処理
package main
import (
"errors"
"fmt"
)
type user struct {
name string
password string
}
// 在函数里面,我们可以在那个函数的返回值类型里面,后面加一个 error,就代表这个函数可能会返回错误。
func findUser(users []user, name string) (v *user, err error) {
for _, u := range users {
if u.name == name {
return &u, nil
}
}
return nil, errors.New("not found")
}
func main() {
u, err := findUser([]user{
{
"wang", "1024"}}, "wang")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(u.name) // wang
if u, err := findUser([]user{
{
"wang", "1024"}}, "li"); err != nil {
fmt.Println(err) // not found
return
} else {
fmt.Println(u.name)
}
}
Go 言語でのエラー処理は、エラー情報を伝えるために別の戻り値を使用するという慣用的なアプローチに準拠しています。
これは、Java 自体で使用される例外とは異なります。Go 言語の処理メソッドは、どの関数がエラーを返すかを明確に認識でき、単純な if else を使用してエラーを処理できます。
関数では、その関数の戻り値の型にエラーを追加できます。これは、関数がエラーを返す可能性があることを意味します。
その後、関数が実装されると、 return は同時に 2 つの値を返す必要があります。または、エラーが発生した場合はreturn nil
1 つの値を組み合わせることができますerror
。そうでない場合は、元の結果と nil を返します。
文字列操作
package main
import (
"fmt"
"strings"
)
func main() {
a := "hello"
fmt.Println(strings.Contains(a, "ll")) // true
fmt.Println(strings.Count(a, "l")) // 2
fmt.Println(strings.HasPrefix(a, "he")) // true
fmt.Println(strings.HasSuffix(a, "llo")) // true
fmt.Println(strings.Index(a, "ll")) // 2
fmt.Println(strings.Join([]string{
"he", "llo"}, "-")) // he-llo
fmt.Println(strings.Repeat(a, 2)) // hellohello
fmt.Println(strings.Replace(a, "e", "E", -1)) // hEllo
fmt.Println(strings.Split("a-b-c", "-")) // [a b c]
fmt.Println(strings.ToLower(a)) // hello
fmt.Println(strings.ToUpper(a)) // HELLO
fmt.Println(len(a)) // 5
b := "你好"
fmt.Println(len(b)) // 6
}
標準ライブラリの strings パッケージには、文字列に別の文字列が含まれているかどうかを判断する contains、文字列を数える count、特定の文字列の位置を見つける Index など、一般的に使用される文字列ツール関数が多数あります。Join は複数の文字列を結合します。repeat は複数の文字列を繰り返します。replace は文字列を置き換えます。
文字列の書式設定
package main
import "fmt"
type point struct {
x, y int
}
func main() {
s := "hello"
n := 123
p := point{
1, 2}
fmt.Println(s, n) // hello 123
fmt.Println(p) // {1 2}
fmt.Printf("s=%v\n", s) // s=hello
fmt.Printf("n=%v\n", n) // n=123
fmt.Printf("p=%v\n", p) // p={1 2}
fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}
f := 3.141592653
fmt.Println(f) // 3.141592653
fmt.Printf("%.2f\n", f) // 3.14
}
Go 言語では、%v
数値文字列を区別せずに、任意のタイプの変数を出力するために簡単に使用できます。を使用すると、より詳細な%+v
結果を出力することもできます。%#v
JSON処理
package main
import (
"encoding/json"
"fmt"
)
type userInfo struct {
Name string
Age int `json:"age"`
Hobby []string
}
func main() {
a := userInfo{
Name: "wang", Age: 18, Hobby: []string{
"Golang", "TypeScript"}}
buf, err := json.Marshal(a)
if err != nil {
panic(err)
}
fmt.Println(buf) // [123 34 78 97...]
fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
buf, err = json.MarshalIndent(a, "", "\t")
if err != nil {
panic(err)
}
fmt.Println(string(buf))
var b userInfo
err = json.Unmarshal(buf, &b)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
}
Go 言語での JSON 操作は非常に単純で、既存の構造の場合、各フィールドの最初の文字が大文字、つまりパブリック フィールドである限り、何もする必要はありません。次に、この構造を JSON.Marshaler で逆シリアル化し、JSON 文字列に変換できます。
シリアル化された文字列は、JSON.Unmarshaler を使用して空の変数に逆シリアル化することもできます。
この方法で文字列がデフォルトでシリアル化される場合、そのスタイルはアンダースコアではなく大文字で始まります。json タグなどの構文を使用して、出力された JSON 結果のフィールド名を後で変更できます。
時間処理
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println(now) // 2022-03-27 18:04:59.433297 +0800 CST m=+0.000087933
t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
fmt.Println(t) // 2022-03-27 01:25:36 +0000 UTC
fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) // 2022 March 27 1 25
fmt.Println(t.Format("2006-01-02 15:04:05")) // 2022-03-27 01:25:36
diff := t2.Sub(t)
fmt.Println(diff) // 1h5m0s
fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900
t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
if err != nil {
panic(err)
}
fmt.Println(t3 == t) // true
fmt.Println(now.Unix()) // 1648738080
}
以下は時間処理で、Go 言語で最も一般的に使用されるのはtime.Now ()
現在時刻を取得することですが、time.Date
タイムゾーンを使用して時刻を構築したり、構築された時間を使用することもできます。現時点での年、月、日、時、分、秒を取得する方法は上記に多数ありますが、sub を使用して 2 つの時刻を減算して期間を取得することもできます。期間は何時間、何分、何秒かを取得できます。
特定のシステムと対話するとき、タイムスタンプを使用することがよくあります。次に、 を使用して.UNIX()
タイムスタンプを取得できます。time.format
、time.parse
時間を解析するために使用されます。詳細についてはドキュメントを参照してください。
デジタル分析
package main
import (
"fmt"
"strconv"
)
func main() {
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234
n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n) // 111
n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n) // 4096
n2, _ := strconv.Atoi("123")
fmt.Println(n2) // 123
n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
}
文字列と数値の間で変換する方法を学びましょう。Go 言語では、文字列と数値の間のすべての変換はstrconv
このパッケージの下にあります。これは、string Convert という 2 つの単語の略称です。
parselnt または parseFloat を使用して文字列を解析できます。Parseint パラメータ
Atoi を使用して 10 進数の文字列を数値に変換できます。itoA を使用して数値を文字列に変換できます。入力が無効な場合、これらの関数はエラーを返します。
プロセス情報
package main
import (
"fmt"
"os"
"os/exec"
)
func main() {
// 比如我们编译的一个二进制文件,command。后面接 abcd 来启动,输出就是os.argv会是一个长度为5的 slice ,第一个成员代表:进制自身的名字。
// go run example/20-env/main.go a b c d
fmt.Println(os.Args) // [/var/folders/8p/n34xxfnx38dg8bv_x8l62t_m0000gn/T/go-build3406981276/b001/exe/main a b c d]
fmt.Println(os.Getenv("PATH")) // /usr/local/go/bin...
fmt.Println(os.Setenv("AA", "BB"))
buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
if err != nil {
panic(err)
}
fmt.Println(string(buf)) // 127.0.0.1 localhost
}
go では、これをos.argv
使用して。
これをos.getenv
使用して。