Préface
Aller à la définition de la langue
Go(又称 Golang)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 开发的一种静态、强类型、编译型语言。Go 语言语法与 C 相近,但功能上有:内存安全,GC,结构形态及 CSP-style 并发计算
。
Champ d'application
Cet article s'adresse aux débutants qui ont appris d'autres langages orientés objet(Java, Php), mais n'ont pas appris le langage Go. L'article explique principalement les quatre aspects de la syntaxe de base du langage Go, de la programmation orientée objet, de la concurrence et des erreurs issues de la comparaison des fonctions de Go et Java .
1. Grammaire de base
La syntaxe de base du langage Go est fondamentalement similaire à celle des langages de programmation conventionnels. La différence réside dans la manière de déclarer les variables. Les concepts et fonctions des tableaux, des tranches et des dictionnaires ne sont pas les mêmes que ceux de Java. en Java peuvent tous être utilisés par analogie. Utilisés en Go.
1.1 Variables, constantes, valeurs nulles et nulles, méthodes, packages, visibilité, pointeurs
1.1.1 Déclaration des variables
Il y a deux manières en langage Go
1. Utilisez var
la déclaration par mot-clé, et il convient de noter que contrairement à la plupart des langages fortement typés, le type de variable déclaré dans le langage Go est situé après le nom de la variable. Il n'est pas nécessaire de mettre un point-virgule à la fin d'une instruction Go.
var num int
var result string = "this is result"
2. Utilisez :=
l'affectation.
num := 3
Équivalent àvar num int = 3
Le type de la variable correspondra en fonction de la valeur sur le côté droit. Par exemple, "3" correspondra à int, "3.0" correspondra à float64 et "result" correspondra à la chaîne.
1.1.2 Déclaration constante
Utilisez const
pour déclarer une constante. Une constante ne peut pas être modifiée une fois déclarée.
const laugh string = "go"
1.1.3 valeur nulle et nulle
Déclarez uniquement les variables non affectées, dont la valeur est nulle. Similaire à « null » en java .
Les déclarations de variables sans valeurs initiales explicites se voient attribuer leur valeur zéro .
La valeur zéro est :
-
Le type numérique est
0
, -
Le type booléen est
false
, -
La chaîne est
""
(chaîne vide).
1.1.4 Méthodes et packages
Définition des méthodes en Go
Utilisez le mot-clé func pour définir une méthode, suivi du nom de la méthode, puis des paramètres et de la valeur de retour (le cas échéant, s'il n'y a pas de valeur de retour, elle ne sera pas écrite).
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;
}
Plusieurs valeurs de retour
Une grande différence entre les fonctions Go et les autres langages de programmation est qu'elles prennent en charge plusieurs valeurs de retour, ce qui est très utile lors de la gestion des erreurs de programme. Par exemple, si la add
fonction ci-dessus ne prend en charge que l'ajout d'entiers non négatifs, une erreur sera signalée si un nombre négatif est transmis.
//返回值只定义了类型 没有定义返回参数
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)
}
Paramètres de longueur variable
func myfunc(numbers ...int) {
for _, number := range numbers {
fmt.Println(number)
}
}
slice := []int{1, 2, 3, 4, 5}
//使用...将slice打碎传入
myfunc(slice...)
Forfaits et visibilité
Dans le langage Go, qu'il s'agisse de variables, de fonctions ou d'attributs de classe et de méthodes membres, leur visibilité est basée sur la dimension du package, plutôt que comme dans la programmation traditionnelle, où la visibilité des attributs de classe et des méthodes membres est encapsulée dans la classe à laquelle ils appartiennent private
, modifiez sa visibilité avec ces mots-clés, protected
et .public
Le langage Go ne fournit pas ces mots-clés. Qu'il s'agisse de variables, de fonctions ou d'attributs et de méthodes membres de classes personnalisées, leur visibilité est déterminée en fonction de la casse de leurs premières lettres, si le nom de la variable, le nom de l'attribut, le nom de la fonction est en majuscule. la première lettre du nom de la méthode , vous pouvez accéder directement à ces variables, propriétés, fonctions et méthodes en dehors du package, sinon elles ne sont accessibles que dans le package. Par conséquent, la visibilité des attributs de classe du langage Go et des méthodes membres se fait au niveau du package. niveau, et pas classe un.
S'il domain
y a trois fichiers .go dans un dossier nommé, alors les trois fichiers package
doivent être domain
. Parmi eux, le fichier où se trouve la méthode principale d'entrée du programme et le package.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 Pointeurs
Pour ceux qui ont étudié le langage C, les pointeurs sont assez familiers. Le pointeur que je comprends est en fait une véritable valeur d'adresse hexadécimale dans la mémoire. La valeur de la variable de référence est utilisée pour récupérer la valeur réelle correspondante de la mémoire.
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 Conditions, boucles, branches
1.2.1 Conditions
Fondamentalement, c'est la même chose qu'en langage 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 Boucle
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 Succursale
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 Tableaux, tranches, dictionnaires
1.3.1 Tableau
La fonction de tableau est similaire au langage Java, la longueur est immuable et des tableaux multidimensionnels peuvent être utilisés, ou les valeurs peuvent être stockées ou obtenues via des tableaux[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
Les tableaux sont relativement simples à utiliser, mais il existe un problème difficile à résoudre : la longueur fixe .
Par exemple, lorsque nous avons besoin d'une structure de données dans le programme pour stocker tous les utilisateurs obtenus, car le nombre d'utilisateurs change avec le temps, mais la longueur du tableau ne peut pas être modifiée, le tableau n'est donc pas adapté au stockage de données dont la longueur change. . Par conséquent, les problèmes ci-dessus sont résolus en utilisant des tranches en langage Go.
1.3.2 Tranchage
Le découpage est un concept complètement nouveau par rapport à Java. En Java, pour les structures de stockage de données de longueur variable, vous pouvez utiliser l'interface List pour effectuer des opérations, telles que ArrayList et LinkList. Ces interfaces peuvent ajouter et obtenir des données à tout moment, et il n'y a aucune limite de longueur. Cependant, il n'existe pas d'interface de ce type dans Go. Au lieu de cela, le stockage de données de longueur variable est effectué via des tranches .
La plus grande différence entre les tranches et les tableaux est que les tranches n'ont pas besoin de déclarer la longueur. Mais les tranches ne sont pas sans rapport avec les tableaux. Un tableau peut être considéré comme le tableau sous-jacent de la tranche, et la tranche peut être considérée comme une référence à un fragment continu du tableau. Les tranches peuvent être créées en utilisant uniquement une partie d'un tableau ou la totalité du tableau, et il est même possible de créer une tranche plus grande que le tableau sous-jacent :
longueur, capacité
切片的长度就是它所包含的元素个数。
切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
切片 s 的长度和容量可通过表达式 len(s) 和 cap(s) 来获取。
La longueur de la tranche est fonctionnellement analogue à la taille() de List en Java, c'est-à-dire que la longueur de la tranche est perçue via len(slice), et len(slice) peut être bouclée pour contrôler dynamiquement le contenu spécifique dans le tranche. La capacité des tranches n'est pas beaucoup utilisée dans le développement réel, il suffit de comprendre son concept.
Créer des tranches
//声明一个数组
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)
Fonctionnement dynamique des tranches
//使用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:]
Scénarios courants de découpage
Simulez le problème mentionné ci-dessus en utilisant la solution de découpage
//声明切片
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 Dictionnaire
Le dictionnaire, également connu sous le nom de « paire clé-valeur » ou « clé-valeur », est une structure de données couramment utilisée. Il existe différentes interfaces Map en Java, et les plus couramment utilisées sont HashMap, etc. Dans Go, les dictionnaires sont utilisés pour stocker les paires clé-valeur. Les dictionnaires ne sont pas ordonnés, l'ordre des données n'est donc pas garanti en fonction de l'ordre d'addition.
Déclaration et initialisation du dictionnaire
//string为键类型,int为值类型
maps := map[string]int{
"java" : 1,
"go" : 2,
"python" : 3,
}
//还可以通过make来创建字典 100为其初始容量 超出可扩容
maps = make(map[string]int, 100)
Scénarios d'utilisation du dictionnaire
//直接使用
fmt.Println(maps["java"]) //1
//赋值
maps["go"] = 4
//取值 同时判断map中是否存在该键 ok为bool型
value, ok := maps["one"]
if ok { // 找到了
// 处理找到的value
}
//删除
delete(testMap, "four")
2. Programmation orientée objet
2.1 Cours en langage Go
Comme nous le savons tous, dans les langages orientés objet, une classe doit avoir trois structures : les attributs, les constructeurs et les méthodes membres, et le langage Go ne fait pas exception.
2.1.1 Déclaration de classe et initialisation
Il n'y a pas de concept clair de classe dans le langage Go. Seuls struct
les mots-clés peuvent être fonctionnellement analogues aux « classes » dans les langages orientés objet. Par exemple, pour définir une classe d'élève, vous pouvez faire ceci :
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 Méthodes membres
La déclaration de méthode membre dans Go n’est pas la même que dans les autres langues. En prenant la classe Étudiant comme exemple,
//在方法名前,添加对应的类,即可认为改方法为该类的成员方法。
func (s Student) GetName() string {
return s.name
}
//注意这里的Student是带了*的 这是因为在方法传值过程中 存在着值传递与引用传递 即指针的概念 当使用值传递时 编译器会为该参数创建一个副本传入 因此如果对副本进行修改其实是不生效的 因为在执行完此方法后该副本会被销毁 所以此处应该是用*Student 将要修改的对象指针传入 修改值才能起作用
func (s *Student) SetName(name string) {
//这里其实是应该使用(*s).name = name,因为对于一个地址来说 其属性是没意义的 不过这样使用也是可以的 因为编译器会帮我们自动转换
s.name = name
}
2.2 Interfaces
Les interfaces jouent un rôle essentiel dans le langage Go. Si la goroutine et le canal sont les pierres angulaires qui prennent en charge le modèle de concurrence du langage Go, alors les interfaces sont la pierre angulaire de l'ensemble du système de types du langage Go . L'interface du langage Go n'est pas seulement une interface. Explorons étape par étape les fonctionnalités de l'interface du langage Go.
2.2.1 Implémentation d'une interface intrusive traditionnelle
Semblable à l’implémentation de classes, le concept d’interface du langage Go est complètement différent des concepts d’interface fournis dans d’autres langages. En prenant Java et PHP comme exemples, les interfaces existent principalement sous forme de contrats entre différentes classes, et l'implémentation du contrat est obligatoire. Cela se reflète dans les détails spécifiques : si une classe implémente une certaine interface, elle doit implémenter toutes les méthodes déclarées. , c'est ce qu'on appelle « l'exécution du contrat » :
// 声明一个'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;
}
}
À ce stade, s'il existe une autre interface iTemplate2
qui déclare une iTemplate
méthode d'interface qui est exactement la même que , même avec le même nom iTemplate
, mais qui est située dans un espace de noms différent, le compilateur pensera également que la classe ci-dessus Template
implémente uniquement iTemplate
et n'implémente pas iTemplate2
L'interface.
Ceci est pris pour acquis dans notre compréhension précédente. Qu'il s'agisse d'héritage entre classes ou d'implémentation entre classes et interfaces, dans les langages à héritage unique tels que Java et PHP, il existe une relation hiérarchique stricte. classe parent, et une classe ne peut implémenter qu'une interface spécifiée. Si elle n'est pas explicitement déclarée pour hériter d'une classe parent ou implémenter une certaine interface, alors la classe n'a rien à voir avec la classe parent ou la relation d'interface.
Nous appelons ce type d'interface une interface intrusive . Ce qu'on appelle « intrusif » signifie que la classe d'implémentation doit déclarer explicitement qu'elle implémente une interface. Bien que cette méthode de mise en œuvre soit assez claire et simple, il existe encore certains problèmes, en particulier lors de la conception de la bibliothèque standard, car la bibliothèque standard doit impliquer la conception de l'interface. Le demandeur de l'interface est la classe d'implémentation métier, et seule la classe d'implémentation métier peut le faire. être écrit spécifiquement.Ce n'est qu'alors que nous savons quelles méthodes doivent être définies.Avant cela, l'interface de la bibliothèque standard a été conçue, soit nous devons l'implémenter selon l'interface convenue. le concevoir nous-mêmes. Le problème ici est que la conception et la mise en œuvre commerciale sont séparées. Les concepteurs d'interfaces ne peuvent pas toujours prédire quelles fonctions le côté commercial implémentera, ce qui entraîne un décalage entre la conception et la mise en œuvre.
Une conception excessive de l'interface rendra certaines classes d'implémentation de méthodes déclarées complètement inutiles. Si la conception est trop simple, elle ne répondra pas aux besoins de l'entreprise. C'est en effet un problème, et cela n'a aucun sens d'en discuter sans l'utilisateur. Scénarios d'utilisation. Prenons l'exemple de l'interface SessionHandlerInterface . Les méthodes d'interface déclarées par cette interface sont les suivantes :
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
}
Le gestionnaire de session défini par l'utilisateur doit implémenter cette interface, c'est-à-dire qu'il doit implémenter toutes les méthodes déclarées par cette interface. Cependant, lors du développement commercial, certaines méthodes n'ont pas réellement besoin d'être implémentées, par exemple. Redis ou Memcached comme session Quant aux mémoires, elles contiennent elles-mêmes un mécanisme de recyclage d'expiration, gc
la méthode n'a donc pas du tout besoin d'être implémentée. Par exemple, close
la méthode n'a aucun sens pour la plupart des pilotes.
C'est précisément à cause de cette conception déraisonnable que vous devez faire face aux deux problèmes suivants lors de l'écriture de chaque interface dans la bibliothèque de classes PHP (Java est similaire) :
-
Quelles méthodes d’interface doivent être déclarées pour une interface ?
-
Si plusieurs classes implémentent la même méthode d’interface, comment l’interface doit-elle être conçue ? Par exemple, celle ci-dessus
SessionHandlerInterface
, est-il nécessaire de la diviser en plusieurs interfaces plus subdivisées pour s'adapter aux besoins des différentes classes d'implémentation ?
Voyons ensuite comment l'interface du langage Go évite ces problèmes.
2.2.2 Implémentation de l'interface linguistique Go
Dans le langage Go, l'implémentation d'une interface par une classe est la même que l'héritage d'une sous-classe d'une classe parent. Il n'existe pas implement
de mot-clé comme celui-ci pour déclarer explicitement quelle interface la classe implémente. Une classe n'a besoin que d'implémenter toutes les interfaces . méthodes requises par une interface, on dit que cette classe implémente l'interface .
Par exemple, nous définissons une classe et File
implémentons Read()
quatre méthodes :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
Supposons que nous ayons l'interface suivante (le langage Go utilise le mot-clé pour interface
déclarer l'interface afin de montrer la différence avec le type de structure, et les accolades contiennent l'ensemble des méthodes à implémenter) :
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
}
Bien que File
la classe n'implémente pas explicitement ces interfaces, ou même connaisse l'existence de ces interfaces, nous disons que File
la classe implémente ces interfaces car File
elle implémente les méthodes déclarées par toutes les interfaces ci-dessus. Lorsque l'ensemble de méthodes membre d'une classe contient toutes les méthodes déclarées par une interface, en d'autres termes, si l'ensemble de méthodes d'une interface est un sous-ensemble de l'ensemble de méthodes membre d'une certaine classe, nous considérons la classe pour implémenter l'interface.
Par rapport à Java et PHP, nous appelons cette interface du langage Go une interface non intrusive , car la relation d'implémentation entre une classe et une interface n'est pas explicitement déclarée, mais est jugée par le système en fonction de l'ensemble des méthodes des deux. Cela présente deux avantages :
-
Premièrement, la bibliothèque standard du langage Go n'a pas besoin de dessiner l'arbre d'héritage/implémentation de la bibliothèque de classes. Dans le langage Go, l'arbre d'héritage de la classe n'a aucun sens. Il vous suffit de savoir quelles méthodes la classe implémente et. ce que fait chaque méthode suffira.
-
Deuxièmement, lors de la définition d'une interface, vous n'avez qu'à vous soucier des méthodes que vous devez fournir. Vous n'avez plus à vous soucier du niveau de détail que doit avoir l'interface. Il n'est pas nécessaire d'introduire le package dans lequel se trouve l'interface pour le faire. implémenter une interface. L'interface est déterminée par l'utilisateur à la demande, pas besoin de concevoir à l'avance et pas besoin de déterminer si d'autres modules ont déjà défini des interfaces similaires.
De cette façon, les problèmes de conception d’interface dans la programmation orientée objet traditionnelle sont parfaitement évités.
3. Concurrence et multithreading
3.1 Goroutine
Pour tout excellent langage, la capacité à gérer un traitement simultané est la clé pour déterminer ses mérites. Dans le langage Go, le traitement simultané est implémenté via Goroutine.
func say(s string) {
fmt.Println(s)
}
func main() {
//通过 go 关键字新开一个协程
go say("world")
say("hello")
}
Le langage Go ne possède pas autant de verrous que Java pour limiter l'accès simultané aux ressources, et ne fournit que Mutex pour les opérations de synchronisation.
//给类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()
}
Canal 3.2
La communication entre plusieurs coroutines s'effectue via Channel, qui peut être fonctionnellement analogue au mot-clé volatile de Java.
ch := make(chan int)
ch
Déclarez un Channel de type int, et les données int peuvent être communiquées entre deux coroutines .
La transmission des données s'effectue via Channel.
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. Gestion des erreurs
Erreur 4.1
Le mécanisme de gestion des erreurs du langage Go est très simple et clair. Il n'est pas nécessaire d'apprendre des concepts, des fonctions et des types complexes. Le langage Go définit un modèle standard pour la gestion des erreurs, c'est-à-dire error
l'interface. :
type error interface {
Error() string
}
Une seule Error()
méthode est déclarée, qui renvoie un message d'erreur de type chaîne. Pour la plupart des fonctions ou méthodes de classe, si vous souhaitez renvoyer une erreur, vous pouvez la définir comme le modèle suivant : renvoyer le type d'erreur comme deuxième paramètre :
func Foo(param int) (n int, err error) {
// ...
}
Ensuite, lorsque vous appelez la fonction/méthode qui renvoie des informations d'erreur, écrivez simplement le code de traitement selon le modèle « Wei Shu Statement » suivant :
n, err := Foo(0)
if err != nil {
// 错误处理
} else{
// 使用返回值 n
}
Très simple et élégant.
4.2 reporter
defer est utilisé pour garantir qu'après l'exécution d'une méthode, les instructions defer seront exécutées, que le résultat de l'exécution soit réussi ou non. Semblable à l'utilisation de try..catch..finally en Java. Par exemple, lors du traitement de fichiers, le flux de fichiers doit être fermé, que le résultat soit réussi ou non.
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 panique
Il n'y a pas beaucoup de classes d'exception dans le langage Go. Contrairement à Java, il existe des types d'erreur tels que Error et Exception. Bien sûr, il n'y a pas d'instructions try..catch.
La panique signifie qu'une erreur se produit pendant le fonctionnement du programme. Si l'erreur n'est pas détectée, le système plantera et se fermera. Par exemple, une simple panique : a := 1/0
.
Cela va provoquer la panique : un entier divisé par zéro.
La première ligne indique la coroutine avec le problème, la deuxième ligne est le package et la fonction où se trouvent le code du problème, la troisième ligne est l'emplacement spécifique du code du problème et la dernière ligne est l'état de sortie du programme. Ces informations peuvent vous aider à localiser rapidement le problème et à le résoudre.
4.4 récupérer
Lorsqu'une erreur prévisible se produit et que vous ne souhaitez pas que le programme plante et se ferme, vous pouvez utiliser l'instruction recovery() pour capturer la panique non gérée. recovery doit être placé dans l'instruction defer, et l'instruction doit être au début de la méthode pour éviter que le système ne se ferme anormalement si l'instruction defer ne peut pas être exécutée.
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 函数")
}
On peut voir que même si une exception se produit, après avoir utilisé recovery() pour la capturer, le système ne plantera pas et ne se fermera pas, mais mettra simplement fin à la méthode. L' fmt.Printf("%d / %d = %d\n", i, j, k)
instruction n'a pas été exécutée car une exception s'est produite lors de l'exécution du code à l'étape précédente, provoquant la fin anticipée de la méthode.
4 récupérer
Lorsqu'une erreur prévisible se produit et que vous ne souhaitez pas que le programme plante et se ferme, vous pouvez utiliser l'instruction recovery() pour capturer la panique non gérée. recovery doit être placé dans l'instruction defer, et l'instruction doit être au début de la méthode pour éviter que le système ne se ferme anormalement si l'instruction defer ne peut pas être exécutée.
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 函数")
}
On peut voir que même si une exception se produit, après avoir utilisé recovery() pour la capturer, le système ne plantera pas et ne se fermera pas, mais mettra simplement fin à la méthode. L' fmt.Printf("%d / %d = %d\n", i, j, k)
instruction n'a pas été exécutée car une exception s'est produite lors de l'exécution du code à l'étape précédente, provoquant la fin anticipée de la méthode.
5. Résumé
Grâce à l'étude ci-dessus, vous pouvez dans un premier temps comprendre la syntaxe de base de go dans le but de l'utiliser, mais cet article à lui seul ne suffit pas pour apprendre go. Par exemple, l'un des plus grands avantages du go, la « coroutine », n'est pas détaillé en raison du but de l'article. Les étudiants intéressés peuvent continuer à apprendre.