序文
Go言語の定義
Go(又称 Golang)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 开发的一种静态、强类型、编译型语言。Go 语言语法与 C 相近,但功能上有:内存安全,GC,结构形态及 CSP-style 并发计算
。
適用範囲
この記事は、他のオブジェクト指向言語 (Java、Php) を学習したことはあるが、Go 言語を学習したことがない初心者に適しています。この記事では、GoとJavaの機能比較から、 Go言語の基本構文、オブジェクト指向プログラミング、同時実行性、エラーの4つの側面を中心に解説しています。
1. 基本的な文法
Go 言語の基本的な構文は従来のプログラミング言語と基本的に似ていますが、配列、スライス、辞書の概念と機能が Java のそれと異なります。 Java ではすべて Go で同様に使用できます。
1.1 変数、定数、nil およびゼロの値、メソッド、パッケージ、可視性、ポインタ
1.1.1 変数の宣言
Go言語には2つの方法があります
1.var
キーワード宣言を使用します。ほとんどの厳密に型指定された言語とは異なり、Go 言語で宣言された変数の型は変数名の後に配置されることに注意してください。 Go ステートメントの最後にセミコロンは必要ありません。
var num int
var result string = "this is result"
2.:=
割り当てを使用します。
num := 3
に相当var num int = 3
変数の型は右側の値に従って照合されます。たとえば、「3」は int に一致し、「3.0」は float64 に一致し、「result」は string に一致します。
1.1.2 定数の宣言
定数を宣言するために使用しますconst
。定数は宣言後に変更できません。
const laugh string = "go"
1.1.3 nil とゼロ値
値が nil の未割り当ての変数のみを宣言します。 Java の「 null」に似ています。
明示的な初期値のない変数宣言には、ゼロ値が割り当てられます。
ゼロ値は次のとおりです。
-
数値型は
0
、 -
ブール型は
false
、 -
文字列は (空の文字列) です
""
。
1.1.4 メソッドとパッケージ
Go でのメソッドの定義
func キーワードを使用してメソッドを定義し、その後にメソッド名、パラメータ、戻り値を指定します (戻り値がある場合、戻り値がない場合は書き込まれません)。
func MethodName(p1 Parm, p2 Parm) int{}
//学习一个语言应该从Hello World开始!
package main
import "fmt"
func main() {
fmt.Println("Hello World!")// Hello World!
fmt.Println(add(3, 5)) //8
var sum = add(3, 5)
}
func add(a int, b int) int{
return a+b;
}
複数の戻り値
Go 関数と他のプログラミング言語の大きな違いの 1 つは、複数の戻り値をサポートしていることです。これは、プログラム エラーを処理するときに非常に役立ちます。たとえば、上記のadd
関数が負ではない整数の加算のみをサポートしている場合、負の数値が渡されるとエラーが報告されます。
//返回值只定义了类型 没有定义返回参数
func add(a, b int) (int, error) {
if a < 0 || b < 0 {
err := errors.New("只支持非负整数相加")
return 0, err
}
a *= 2
b *= 3
return a + b, nil
}
//返回值还定义了参数 这样可以直接return 并且定义的参数可以直接使用 return时只会返回这两个参数
func add1(a, b int) (z int, err error) {
if a < 0 || b < 0 {
err := errors.New("只支持非负整数相加")
return //实际返回0 err 因为z只定义没有赋值 则nil值为0
}
a *= 2
b *= 3
z = a + b
return //返回 z err
}
func main() {
x, y := -1, 2
z, err := add(x, y)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Printf("add(%d, %d) = %d\n", x, y, z)
}
可変長パラメータ
func myfunc(numbers ...int) {
for _, number := range numbers {
fmt.Println(number)
}
}
slice := []int{1, 2, 3, 4, 5}
//使用...将slice打碎传入
myfunc(slice...)
パッケージと可視性
Go 言語では、変数、関数、またはクラス属性とメンバー メソッドのいずれであっても、その可視性は、クラス属性とメンバー メソッドの可視性が、クラス属性とメンバー メソッドの可視性が対象となるクラスにカプセル化される従来のプログラミングとは異なり、パッケージのディメンションに基づいています。それらは に属しprivate
、これらのキーワードと を使用してその表示を変更しますprotected
。public
Go 言語では、これらのキーワードが変数、関数、カスタム クラスの属性およびメンバー メソッドのいずれであっても、変数名、属性名、関数名の大文字と小文字に基づいてその可視性が決定されます。メソッド名の最初の文字を指定すると、パッケージの外でこれらの変数、プロパティ、関数、メソッドに直接アクセスできます。それ以外の場合は、パッケージ内でのみアクセスできます。したがって、Go 言語のクラス属性とメンバー メソッドの可視性はパッケージにあります。レベル、およびクラス 1 ではありません。
domain
という名前のフォルダーに 3 つの .go ファイルがある場合、その 3 つのファイルは、プログラムのエントリ main メソッドが配置されているファイルであり、パッケージは次のとおりですpackage
。domain
main
//定义了此文件属于 main 包
package main
//通过import导入标注库中包
import "fmt"
func main() {
fmt.Println("Hello World!")// Hello World!
fmt.Println(add(3, 5)) //8
var sum = add(3, 5)
}
func add(a int, b int) int{
return a+b;
}
1.1.5 ポインタ
C 言語を学習したことのある人にとって、ポインタはよく知られています。ポインタは実際にはメモリ内の 16 進数のアドレス値であり、メモリから対応する実数値を取得するために使用されます。
func main() {
i := 0
//使用&来传入地址
fmt.Println(&i) //0xc00000c054
var a, b int = 3 ,4
//传入 0xc00000a089 0xc00000a090
fmt.Println(add(&a, &b))
}
//使用*来声明一个指针类型的参数与使用指针
func add(a *int, b *int)int{
//接收到 0xc00000a089 0xc00000a090
//前往 0xc00000a089位置查找具体数据 并取赋给x
x := *a
//前往 0xc00000a090位置查找具体数据 并取赋给y
y := *b
return x+y
}
1.2 条件、ループ、分岐
1.2.1 条件
基本的にはJava言語と同じです
// if
if condition {
// do something
}
// if...else...
if condition {
// do something
} else {
// do something
}
// if...else if...else...
if condition1 {
// do something
} else if condition2 {
// do something else
} else {
// catch-all or default
}
1.2.2 ループ
sum := 0
//普通for循环
for i := 1; i <= 100; i++ {
sum += i
}
//无限循环
for{
sum++
if sum = 100{
break;
}
}
//带条件的循环
for res := sum+1; sum < 15{
sum++
res++
}
//使用kv循环一个map或一个数组 k为索引或键值 v为值 k、v不需要时可以用_带替
for k, v := range a {
fmt.Println(k, v)
}
1.2.3 分岐
score := 100
switch score {
case 90, 100:
fmt.Println("Grade: A")
case 80:
fmt.Println("Grade: B")
case 70:
fmt.Println("Grade: C")
case 65:
fmt.Println("Grade: D")
default:
fmt.Println("Grade: F")
}
1.3 配列、スライス、辞書
1.3.1 配列
配列関数は Java 言語に似ており、長さは不変で、多次元配列を使用したり、値を array[i] を通じて保存または取得したりできます。
//声明
var nums [3]int
//声明并初始化
var nums = [3]int{1,2,3} <==> nums:=[3]int{1,2,3}
//使用
for sum := 0, i := 0;i<10{
sum += nums[i]
i++
}
//修改值
num[0] = -1
配列は比較的簡単に使用できますが、固定長という解決が難しい問題があります。
たとえば、プログラム内で取得したすべてのユーザーを格納するデータ構造が必要な場合、ユーザー数は時間とともに変化しますが、配列の長さは変更できないため、配列は長さが変化するデータの格納には適していません。 。そこで、Go言語のスライスを利用することで上記の問題を解決します。
1.3.2 スライス
スライシングは、Java と比較するとまったく新しい概念です。 Java では、可変長のデータ ストレージ構造の場合、ArrayList や LinkList などの操作を完了するために List インターフェイスを使用できます。これらのインターフェイスはいつでもデータを追加および取得でき、長さに制限はありません。ただし、Go にはそのようなインターフェイスはなく、代わりに、可変長データ長のストレージはスライスを通じて実現されます。
スライスと配列の最大の違いは、スライスでは長さを宣言する必要がないことです。ただし、スライスは配列と無関係ではなく、配列はスライスの基礎となる配列と見なすことができ、スライスは配列の連続したフラグメントへの参照とみなすことができます。スライスは、配列の一部のみまたは配列全体を使用して作成できます。また、基になる配列よりも大きなスライスを作成することも可能です。
長さ、容量
切片的长度就是它所包含的元素个数。
切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
切片 s 的长度和容量可通过表达式 len(s) 和 cap(s) 来获取。
スライスの長さは、Java の List の size() に機能的に似ています。つまり、スライスの長さは len(slice) を通じて認識され、len(slice) をループして、リスト内の特定のコンテンツを動的に制御できます。スライス。スライスの容量は実際の開発ではあまり使用されませんが、その概念を理解するだけにしてください。
スライスの作成
//声明一个数组
var nums =[3]int{1, 2, 3}
//0.直接声明
var slice =[]int{0, 1, 2}
//1.从数组中引用切片 其中a:b是指包括a但不包括b
var slice1 = nums[0:2] //{1,2}
//如果不写的则默认为0(左边)或最大值(右边)
var slice2 = slice1[:2] <==> var slice2 = slice1[0:] <==>var slice2 = slice1[:]
//2.使用make创建Slice 其中int为切片类型,4为其长度,5为容量
slice3 := make([]int, 5)
slice4 := make([]int, 4, 5)
スライスの動的操作
//使用append向切片中动态的添加元素
func append(s []T, vs ...T) []T
slice5 := make([]int, 4, 5) //{0, 0, 0, 0}
slice5 = append(slice5, 1) //{0,0,0,0,1}
//删除第一个0
sliece5 = slice5[1:]
スライスの一般的なシナリオ
スライスソリューションを使用して上記の問題をシミュレートします
//声明切片
var userIds = []int{}
//模拟获取所有用户ID
for i := 0; i< 100{
userIds = append(userIdS, i);
i++;
}
//对用户信息进行处理
for k,v := range userIds{
userIds[k] = v++
}
1.3.3 辞書
「キーと値のペア」または「キーと値」とも呼ばれるディクショナリは、一般的に使用されるデータ構造です。Java にはさまざまな Map インターフェイスがあり、一般的に使用されるものは HashMap などです。 Go では、キーと値のペアを格納するためにディクショナリが使用されます。ディクショナリには順序がないため、追加の順序に基づいてデータの順序は保証されません。
辞書の宣言と初期化
//string为键类型,int为值类型
maps := map[string]int{
"java" : 1,
"go" : 2,
"python" : 3,
}
//还可以通过make来创建字典 100为其初始容量 超出可扩容
maps = make(map[string]int, 100)
辞書の使用シナリオ
//直接使用
fmt.Println(maps["java"]) //1
//赋值
maps["go"] = 4
//取值 同时判断map中是否存在该键 ok为bool型
value, ok := maps["one"]
if ok { // 找到了
// 处理找到的value
}
//删除
delete(testMap, "four")
2. オブジェクト指向プログラミング
2.1 Go言語でのクラス
ご存知のとおり、オブジェクト指向言語では、クラスには属性、コンストラクター、メンバー メソッドの 3 つの構造が必要であり、Go 言語も例外ではありません。
2.1.1 クラスの宣言と初期化
Go 言語にはクラスという明確な概念がありません。struct
オブジェクト指向言語の「クラス」に機能的に類推できるのはキーワードだけです。たとえば、学生クラスを定義するには、次のようにします。
type Student struct {
id int
name string
male bool
score float64
}//定义了一个学生类,属性有id name等,每个属性的类型都在其后面
//定义学生类的构造方法
func NewStudent(id uint, name string, male bool, score float64) *Student {
return &Student{id, name, male, score}
}
//实例化一个类对象
student := NewStudent(1, "学院君", 100)
fmt.Println(student)
2.1.2 メンバーメソッド
Go のメンバー メソッドの宣言は他の言語と同じではありません。 Student クラスを例にすると、
//在方法名前,添加对应的类,即可认为改方法为该类的成员方法。
func (s Student) GetName() string {
return s.name
}
//注意这里的Student是带了*的 这是因为在方法传值过程中 存在着值传递与引用传递 即指针的概念 当使用值传递时 编译器会为该参数创建一个副本传入 因此如果对副本进行修改其实是不生效的 因为在执行完此方法后该副本会被销毁 所以此处应该是用*Student 将要修改的对象指针传入 修改值才能起作用
func (s *Student) SetName(name string) {
//这里其实是应该使用(*s).name = name,因为对于一个地址来说 其属性是没意义的 不过这样使用也是可以的 因为编译器会帮我们自动转换
s.name = name
}
2.2 インターフェース
Go 言語でインターフェイスが重要な役割を果たします。ゴルーチンとチャネルが Go 言語の同時実行モデルをサポートする基礎である場合、インターフェイスは Go 言語の型システム全体の基礎となります。 Go 言語のインターフェースは単なるインターフェースではありません。Go 言語のインターフェース機能を段階的に見ていきましょう。
2.2.1 従来の侵入型インターフェイスの実装
クラスの実装と同様に、Go 言語のインターフェイスの概念は、他の言語で提供されるインターフェイスの概念とはまったく異なります。 Java と PHP を例に挙げると、インターフェイスは主に異なるクラス間のコントラクトとして存在し、コントラクトの実装は特定の詳細に反映されています。クラスが特定のインターフェイスを実装する場合、宣言されたすべてのメソッドを実装する必要があります。 、これは「契約履行」と呼ばれます。
// 声明一个'iTemplate'接口
interface iTemplate
{
public function setVariable($name, $var);
public function getHtml($template);
}
// 实现接口
// 下面的写法是正确的
class Template implements iTemplate
{
private $vars = array();
public function setVariable($name, $var)
{
$this->vars[$name] = $var;
}
public function getHtml($template)
{
foreach($this->vars as $name => $value) {
$template = str_replace('{' . $name . '}', $value, $template);
}
return $template;
}
}
このとき、同じ名前であっても、 とまったく同じインターフェイス メソッドiTemplate2
を宣言する別のインターフェイスがあり、異なる名前空間に配置されている場合も、コンパイラは、上記のクラスが実装するだけで、実装しないとみなします。インターフェース。iTemplate
iTemplate
Template
iTemplate
iTemplate2
これは、クラス間の継承であっても、クラスとインターフェイス間の実装であっても、Java や PHP などの単一継承言語では、クラスは厳密な階層関係しか存在しません。親クラスから継承するか、特定のインターフェイスを実装するように明示的に宣言されていない場合、クラスは親クラスまたはインターフェイス関係とは何の関係もありません。
この種のインターフェイスを侵入型インターフェイスと呼びます。いわゆる「侵入型」とは、実装クラスがインターフェイスを実装することを明示的に宣言する必要があることを意味します。この実装方法は非常に明確で簡単ですが、特に標準ライブラリを設計する場合には、まだいくつかの問題があります。これは、標準ライブラリにはインターフェイスの設計が含まれる必要があり、インターフェイスの要求者はビジネス実装クラスだけであるためです。その前に、適切なインターフェイスがない場合は、それに従って標準ライブラリを実装する必要があります。ここでの問題は、デザインとビジネス実装が分離されていることです。インターフェイス設計者は、ビジネス側がどのような機能を実装するかを常に予測できないため、デザインと実装が切り離されてしまいます。
インターフェースを過度に設計すると、宣言されたメソッド実装クラスがまったく必要なくなる可能性があります。設計が単純すぎると、ビジネスのニーズを満たせなくなります。これはユーザーなしで議論するのは無意味です。使用シナリオとして、PHP に付属するSessionHandlerInterface例に挙げます。このインターフェイスによって宣言されるインターフェイス メソッドは次のとおりです。
SessionHandlerInterface {
/* 方法 */
abstract public close ( void ) : bool
abstract public destroy ( string $session_id ) : bool
abstract public gc ( int $maxlifetime ) : int
abstract public open ( string $save_path , string $session_name ) : bool
abstract public read ( string $session_id ) : string
abstract public write ( string $session_id , string $session_data ) : bool
}
ユーザー定義のセッションマネージャーはこのインターフェースを実装する必要があります。つまり、このインターフェースで宣言されたすべてのメソッドを実装する必要があります。セッションとしての Redis または Memcached メモリに関しては、メモリ自体に有効期限のリサイクル メカニズムが含まれているため、gc
このメソッドを実装する必要はまったくありません。たとえば、close
このメソッドはほとんどのドライバにとって無意味です。
この無理な設計だからこそ、PHP クラス ライブラリで各インターフェイスを記述する際には、次の 2 つの問題と格闘する必要があります (Java も同様です)。
-
インターフェイスに対してどのインターフェイス メソッドを宣言する必要がありますか?
-
複数のクラスが同じインターフェイス メソッドを実装する場合、インターフェイスはどのように設計されるべきでしょうか?たとえば、上記のものは
SessionHandlerInterface
、さまざまな実装クラスのニーズに適応するために、さらに細分化された複数のインターフェイスに分割する必要がありますか?
次に、Go 言語インターフェイスがこれらの問題をどのように回避するかを見てみましょう。
2.2.2 Go言語インターフェースの実装
Go 言語では、クラスによるインターフェイスの実装は、親クラスからのサブクラスの継承と同じです。クラスimplement
がどのインターフェイスを実装するかを明示的に宣言するためのこのようなキーワードはありません。インターフェースに必要なメソッドのことを、このクラスがインターフェースを実装していると言います。
たとえば、クラスを定義し、次の 4 つのメソッドFile
を実装します。Read()
Write()
Seek()
Close()
type File struct {
// ...
}
func (f *File) Read(buf []byte) (n int, err error)
func (f *File) Write(buf []byte) (n int, err error)
func (f *File) Seek(off int64, whence int) (pos int64, err error)
func (f *File) Close() error
次のインターフェイスがあるとします (Go 言語では、キーワードを使用してinterface
インターフェイスを宣言し、構造体の型との違いを示します。中括弧には実装されるメソッドのセットが含まれます)。
type IFile interface {
Read(buf []byte) (n int, err error)
Write(buf []byte) (n int, err error)
Seek(off int64, whence int) (pos int64, err error)
Close() error
}
type IReader interface {
Read(buf []byte) (n int, err error)
}
type IWriter interface {
Write(buf []byte) (n int, err error)
}
type ICloser interface {
Close() error
}
クラスはこれらのインターフェイスを明示的に実装していないかFile
、これらのインターフェイスの存在さえ認識していませんが、File
クラスは上記のすべてのインターフェイスによって宣言されたメソッドを実装しているため、クラスはこれらのインターフェイスを実装していると言いますFile
。クラスのメンバー メソッド セットに、インターフェイスによって宣言されたすべてのメソッドが含まれている場合、つまり、インターフェイスのメソッド セットが特定のクラスのメンバー メソッド セットのサブセットである場合、そのクラスがインターフェイスを実装していると見なされます。
Java や PHP と比較して、Go 言語のこのインターフェースを非侵入インターフェースと呼びます。これは、クラスとインターフェースの間の実装関係が明示的に宣言されておらず、システムによって 2 つのメソッドのセットに基づいて判断されるためです。これには次の 2 つの利点があります。
-
まず、Go 言語の標準ライブラリでは、クラス ライブラリの継承/実装ツリー図を描く必要はありません。Go 言語では、クラスがどのメソッドを実装しているかを知るだけで済みます。それぞれの方法で十分です。
-
次に、インターフェイスを定義するときに、どのメソッドを提供する必要があるかだけを考慮する必要があります。インターフェイスを定義するために、インターフェイスが配置されている場所にパッケージを導入する必要はありません。インターフェイスを実装します。インターフェイスはユーザーによって決定され、事前に設計する必要はなく、他のモジュールが以前に同様のインターフェイスを定義したかどうかを考慮する必要もありません。
このようにして、従来のオブジェクト指向プログラミングにおけるインターフェイス設計の問題は完全に回避されます。
3. 同時実行性とマルチスレッド化
3.1 ゴルーチン
優れた言語では、同時処理を処理できることがその利点を決定する鍵となります。 Go 言語では、Goroutine を通じて同時実行処理が実装されます。
func say(s string) {
fmt.Println(s)
}
func main() {
//通过 go 关键字新开一个协程
go say("world")
say("hello")
}
Go 言語には、リソースへの同時アクセスを制限するための Java ほど多くのロックはなく、同期操作用の Mutex のみが提供されます。
//给类SafeCounter添加锁
type SafeCounter struct {
v map[string]int
mux sync.Mutex
}
// Inc 增加给定 key 的计数器的值。
func (c *SafeCounter) Inc(key string) {
//给该对象上锁
c.mux.Lock()
// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
c.v[key]++
//解锁
c.mux.Unlock()
}
3.2チャンネル
複数のコルーチン間の通信はチャネルを介して行われます。チャネルは、機能的には Java の volatile キーワードに類似しています。
ch := make(chan int)
int 型の Channel を宣言すると、 2 つのコルーチン間でch
int データを通信できます。
データ送信はチャネルを通じて実行されます。
ch <- v // 将 v 发送至信道 ch。
v := <-ch // 从 ch 接收值并赋予 v。
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 将和送入 c
}
//对于main方法来说 相当于就是开启了一个协程
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
//通过go关键字开启两个协程 将chaneel当做参数传入
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
//通过箭头方向获取或传入信息
x, y := <-c, <-c // 从 c 中接收
fmt.Println(x, y, x+y)
}
4. エラー処理
4.1 エラー
Go 言語のエラー処理メカニズムは非常にシンプルで明確であり、複雑な概念、関数、型を学ぶ必要はありません。Go 言語では、error
インターフェイスの定義が非常にシンプルです。 :
type error interface {
Error() string
}
宣言されているメソッドは1 つだけError()
であり、文字列型のエラー メッセージを返します。ほとんどの関数またはクラス メソッドでは、エラーを返したい場合は、基本的に次のパターンとして定義できます。エラーの種類を 2 番目のパラメーターとして返します。
func Foo(param int) (n int, err error) {
// ...
}
そして、エラー情報を返す関数/メソッドを呼び出す際には、次の「魏書文」テンプレートに従って処理コードを記述するだけです。
n, err := Foo(0)
if err != nil {
// 错误处理
} else{
// 使用返回值 n
}
とてもシンプルでエレガント。
4.2 延期
defer は、メソッドの実行後、実行結果が成功したかどうかに関係なく、defer 内のステートメントが確実に実行されるようにするために使用されます。 Java での try..catch..finally の使用法に似ています。たとえば、ファイル処理では、結果が成功したかどうかに関係なく、ファイル ストリームを閉じる必要があります。
func ReadFile(filename string) ([]byte, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
//无论结果如何 都要关闭文件流
defer f.Close()
var n int64 = bytes.MinRead
if fi, err := f.Stat(); err == nil {
if size := fi.Size() + bytes.MinRead; size > n {
n = size
}
}
return readAll(f, n)
}
4.3 パニック
Go 言語には Java とは異なり、Error や Exception などの例外クラスがあまりありません。もちろん、try..catch ステートメントはありません。
パニックとは、プログラムの動作中にエラーが発生した場合に、システムがクラッシュして終了することを意味します。たとえば、単純なパニック: a := 1/0
。
それはパニックを引き起こします: 整数のゼロによる除算。
最初の行は問題のあるコルーチンを示し、2 行目は問題のコードが存在するパッケージと関数、3 行目は問題のコードの特定の場所、最後の行はプログラムの終了ステータスを示します。情報は、問題を迅速に特定して解決するのに役立ちます。
4.4 回復する
予見可能なエラーが発生し、プログラムがクラッシュして終了することを望まない場合は、recover() ステートメントを使用して、未処理のパニックをキャプチャできます。 defer ステートメントが実行できない場合にシステムが異常終了するのを避けるために、recover は defer ステートメント内に配置する必要があり、このステートメントはメソッドの先頭に置く必要があります。
package main
import (
"fmt"
)
func divide() {
//通过defer,确保该方法只要执行完毕都要执行该匿名方法
defer func() {
//进行异常捕获
if err := recover(); err != nil {
fmt.Printf("Runtime panic caught: %v\n", err)
}
}()
var i = 1
var j = 0
k := i / j
fmt.Printf("%d / %d = %d\n", i, j, k)
}
func main() {
divide()
fmt.Println("divide 方法调用完毕,回到 main 函数")
}
例外が発生しますが、recover() を使用して例外をキャプチャした後、システムはクラッシュして終了せず、メソッドを終了するだけであることがわかります。fmt.Printf("%d / %d = %d\n", i, j, k)
コードが前のステップまで実行されたときに例外が発生し、メソッドが早期に終了したため、ステートメントは実行されませんでした。
4 回復
予見可能なエラーが発生し、プログラムがクラッシュして終了することを望まない場合は、recover() ステートメントを使用して、未処理のパニックをキャプチャできます。 defer ステートメントが実行できない場合にシステムが異常終了するのを避けるために、recover は defer ステートメント内に配置する必要があり、このステートメントはメソッドの先頭に置く必要があります。
package main
import (
"fmt"
)
func divide() {
//通过defer,确保该方法只要执行完毕都要执行该匿名方法
defer func() {
//进行异常捕获
if err := recover(); err != nil {
fmt.Printf("Runtime panic caught: %v\n", err)
}
}()
var i = 1
var j = 0
k := i / j
fmt.Printf("%d / %d = %d\n", i, j, k)
}
func main() {
divide()
fmt.Println("divide 方法调用完毕,回到 main 函数")
}
例外が発生しますが、recover() を使用して例外をキャプチャした後、システムはクラッシュして終了せず、メソッドを終了するだけであることがわかります。fmt.Printf("%d / %d = %d\n", i, j, k)
コードが前のステップまで実行されたときに例外が発生し、メソッドが早期終了したため、ステートメントは実行されませんでした。
5. まとめ
上記の学習により、最初は go の基本的な構文を理解して使用することができますが、この記事だけでは go を学ぶのに十分ではありません。たとえば、Go の最大の利点の 1 つである「コルーチン」については、記事の目的から詳しく説明しません。興味のある学生は引き続き学習してください。