Remarque : Ce blog partagera le résumé de mon apprentissage initial de GO. J'espère que tout le monde pourra rapidement maîtriser les capacités de programmation de base de GO grâce à ce blog en peu de temps. S'il y a des erreurs, veuillez laisser un message pour me corriger. Merci!
1. Compréhension préliminaire du langage Go
(1) Principaux enjeux et objectifs de la naissance du langage Go
-
Architecture matérielle multicœur : avec le développement du matériel informatique, les processeurs multicœurs sont devenus courants, rendant le calcul parallèle courant. Cependant, les langages de programmation traditionnels peuvent rencontrer des difficultés pour gérer le parallélisme multicœur car ils ne disposent pas d’un support natif approprié. Le langage Go facilite la programmation simultanée en introduisant des mécanismes légers de goroutine et de canal. Les développeurs peuvent facilement créer des milliers de coroutines s'exécutant simultanément sans se soucier de la complexité de la gestion des threads.
-
Clusters informatiques distribués à très grande échelle : avec l'essor du cloud computing et des systèmes distribués, il est devenu de plus en plus courant de créer et de maintenir des clusters informatiques distribués à très grande échelle. Ces clusters doivent être capables de gérer efficacement de gros volumes de demandes, de partage de données et de coordination. Les fonctionnalités de concurrence et le mécanisme de canal du langage Go facilitent l'écriture de systèmes distribués. Les développeurs peuvent utiliser des coroutines et des canaux pour gérer les tâches simultanées, la transmission de messages et le travail de coordination.
-
L'augmentation de l'échelle de développement et de la vitesse de mise à jour provoquée par le modèle Web : l'essor des applications Web a entraîné une échelle de développement sans précédent et des exigences de mise à jour continue. Les langages de programmation traditionnels peuvent être confrontés à des problèmes tels que la maintenabilité, les performances et l'efficacité du développement lors du développement d'applications Web à grande échelle. Grâce à sa syntaxe concise, sa vitesse de compilation efficace et sa prise en charge de la concurrence, le langage Go permet aux développeurs d'itérer et de déployer des applications Web plus rapidement, et peut également mieux gérer les requêtes réseau hautement concurrentes.
Dans l'ensemble, lorsque le langage Go est né, il s'est concentré sur la résolution de défis techniques tels que l'architecture matérielle multicœur, les clusters informatiques distribués à très grande échelle, ainsi que l'échelle et la vitesse de développement en mode Web. L'un de ses objectifs de conception est de fournir un langage de programmation qui s'adapte aux besoins du développement de logiciels modernes afin que les développeurs puissent mieux relever ces défis.
(2) Représentants typiques des applications du langage Go
Le langage Go a été largement utilisé dans le développement d'applications actuel. De nombreuses entreprises et projets bien connus utilisent le langage Go pour créer différents types d'applications. Voici quelques produits et projets représentatifs qui utilisent le langage Go comme langage de développement principal :
Ce ne sont là que quelques exemples d’applications en langage Go. Il existe en fait de nombreux autres projets et produits utilisant le langage Go pour créer des applications performantes, fiables et faciles à entretenir. Cela montre que le langage Go joue un rôle important dans le développement d'applications modernes, notamment dans les domaines des systèmes distribués, du cloud computing et des applications hautes performances.
(3) Malentendus entre les programmeurs Java, C++ et C lors de l'apprentissage de l'écriture Go
Lorsque les programmeurs de Java, C++, C et d'autres langages de programmation commencent à apprendre à écrire le langage Go, ils peuvent rencontrer des malentendus car Go est différent de ces langages traditionnels à certains égards. Voici quelques idées fausses courantes :
-
Surutilisation des modèles de concurrence traditionnels : les langages de programmation traditionnels tels que Java, C++ et C utilisent généralement des threads et des verrous pour gérer la concurrence, mais dans Go, l'utilisation de goroutines et de canaux est un meilleur moyen . Les programmeurs qui découvrent Go peuvent continuer à utiliser les modèles de concurrence traditionnels sans tirer pleinement parti des coroutines et des canaux légers de Go, perdant ainsi les avantages de Go en matière de concurrence.
-
Utilisation excessive de pointeurs : les langages tels que C et C++ mettent l'accent sur l'utilisation de pointeurs, mais le langage Go évite les opérations excessives de pointeurs lors de la conception. Les programmeurs qui découvrent Go peuvent abuser des pointeurs, ce qui rend leur code complexe. Dans Go, essayez d'éviter d'utiliser des pointeurs, sauf si vous avez vraiment besoin de modifier la valeur.
-
Ignorer la gestion des erreurs : Go encourage explicitement la gestion des erreurs plutôt que de simplement les ignorer. Ceci est différent de la convention de certains autres langages, où les erreurs sont souvent ignorées ou simplement renvoyées. Les programmeurs qui découvrent Go peuvent négliger la gestion des erreurs, laissant ainsi des problèmes potentiels non détectés.
-
Surutilisation des variables globales : les variables globales peuvent être une pratique courante dans des langages comme C et C++. Cependant, en Go, l’utilisation de variables globales est considérée comme une mauvaise pratique. Go encourage l'utilisation de variables locales et la transmission de paramètres pour transmettre des données afin d'éviter d'introduire des couplages et des effets secondaires inutiles.
-
Pas familier avec les tranches et les cartes : les tranches et les cartes dans Go sont des structures de données puissantes, mais peuvent ne pas être familières aux programmeurs d'autres langages. Il est important d'apprendre à utiliser correctement les tranches et les cartes, car elles sont largement utilisées dans Go pour les collections et le traitement des données.
-
Style Wrong Go : chaque langue a son propre style et ses propres conventions de codage. Les programmeurs qui découvrent Go peuvent appliquer des styles de codage d'autres langages à leur code Go, ce qui peut rendre le code difficile à lire et à comprendre. Il est important de comprendre et de suivre les conventions de codage de Go.
Afin d'éviter ces malentendus, les programmeurs qui apprennent Go devraient investir du temps pour comprendre les concepts fondamentaux du langage Go, y compris les modèles de concurrence, la gestion des erreurs, les structures de données, etc., et en même temps participer activement à la communauté Go et lire Documentation officielle de Go et exemples de codes afin de mieux s'adapter à la philosophie de conception et aux meilleures pratiques de Go.
2. Préparation de l'environnement (expliquée sous Mac)
(1) Paramètres d'environnement
Mettre en place un environnement de développement du langage Go sur macOS est très simple, vous pouvez suivre les étapes suivantes :
-
Installer à l'aide de Homebrew : C'est la méthode la plus pratique si vous utilisez le gestionnaire de packages Homebrew. Ouvrez un terminal et exécutez la commande suivante pour installer le langage Go :
brew install go
-
Installation manuelle : Si vous souhaitez installer le langage Go manuellement, vous pouvez suivre ces étapes :
a. Visitez le site Web officiel pour télécharger le package d'installation `goX.XXdarwin-amd64.pkg
b. Double-cliquez sur le package d'installation téléchargé et suivez les instructions pour exécuter le programme d'installation. Suivez simplement les paramètres par défaut, le chemin d'installation est généralement
/usr/local/go
. -
Définir les variables d'environnement : Une fois l'installation terminée, vous devez ajouter le chemin binaire du langage Go à la variable d'environnement PATH dans le fichier de configuration de votre terminal. Cela vous permet d'exécuter des commandes Go directement dans le terminal.
a. Ouvrez un terminal et modifiez le fichier de configuration de votre terminal à l'aide d'un éditeur de texte tel que nano, vim ou tout autre éditeur de votre choix. Par exemple:
nano ~/.bash_profile
b. Ajoutez les lignes suivantes au fichier (ajustez en fonction de votre chemin d'installation), puis enregistrez et quittez l'éditeur :
export PATH=$PATH:/usr/local/go/bin
c. Pour que la configuration prenne effet, vous pouvez exécuter la commande suivante ou redémarrer le terminal :
source ~/.bash_profile
-
Vérifier l'installation : ouvrez un terminal et entrez la commande suivante pour vérifier que Go a été correctement installé :
go version
Si vous voyez le numéro de version Go, l'installation est réussie.
(2) Instructions de sélection de l'IDE
Personnellement, j’utilise GoLand. Après l’avoir téléchargé directement sur le site officiel, vous pouvez acheter la version crackée en ligne. Je n’entrerai pas dans les détails ici !
3. Aller à l'apprentissage d'un programme de langue
Créez votre propre répertoire de projet/Users/zyf/zyfcodes/go/go-learning et créez un nouveau répertoire src.
(1) Le premier écrit en langage Go
Créez le répertoire chapitre1/hello sous le répertoire src, créez un nouveau fichier hello.go et écrivez le code comme suit :
package main
import (
"fmt"
"os"
)
/**
* @author zhangyanfeng
* @description 第一个godaima
* @date 2023/8/20 23:45
* @param
* @return
**/
func main() {
if len(os.Args) > 1 {
fmt.Println("Hello World", os.Args[1])
}
}
Ce code est un simple programme en langage Go qui accepte les paramètres de ligne de commande et imprime un message "Hello World" avec des paramètres. Voici une analyse ligne par ligne du code :
-
package main
: Déclarez que ce fichier appartient au package nommé "main", qui est le nom du package d'entrée d'un programme Go. -
import ("fmt" "os")
: Deux packages de bibliothèques standard sont introduits, "fmt" pour le formatage de la sortie et "os" pour interagir avec le système d'exploitation. -
func main() { ... }
: C'est la fonction d'entrée du programme, elle sera appelée en premier lors de l'exécution du programme. -
if len(os.Args) > 1 { ... }
: Cette instruction conditionnelle vérifie si le nombre de paramètres de ligne de commande est supérieur à 1, c'est-à-dire si des paramètres sont transmis au programme.os.Args
est une tranche de chaîne qui contient tous les paramètres de ligne de commande. Le premier paramètre est le nom du programme. -
fmt.Println("Hello World", os.Args[1])
: Si des paramètres sont transmis au programme, cette ligne de code sera exécutée. Il utilisefmt.Println
la fonction pour imprimer un messageos.Args[1]
constitué de la chaîne "Hello World" etos.Args[1]
représentant le premier argument passé au programme.
En résumé, ce code couvre les points de connaissances suivants :
-
Importation de packages et utilisation de la bibliothèque standard : Importez les packages "fmt" et "os" via
import
le mot-clé, puis utilisez les fonctions et types fournis par ces packages dans votre code. -
Obtention des paramètres de ligne de commande : utilisez
os.Args
pour obtenir les paramètres de ligne de commande. -
Instructions conditionnelles : utilisez
if
des instructions conditionnelles pour déterminer si des paramètres de ligne de commande sont transmis au programme. -
Opérations sur les chaînes : utilisez les opérations de concaténation de chaînes pour concaténer "Hello World" avec les paramètres de ligne de commande.
-
Sortie formatée : utilisez
fmt.Println
la fonction pour afficher les messages sur la sortie standard.
Remarque : Si aucun paramètre n'est transmis au programme, ce code n'imprimera aucun message. Si plusieurs paramètres sont passés, le code utilise uniquement le premier paramètre et ignore les autres.
Exécutez "go run hello.go ZYF" dans ce répertoire et le résultat en cours d'exécution est "Hello World ZYF".
(2) Apprendre à rédiger des structures de programme de base
Créer le chapitre 2 dans le répertoire src
1.Variables
Prérequis : Créer des variables dans le répertoire chapitre 2. Le résumé d'apprentissage est le suivant :
- Déclaration de variable : utilisez
var
des mots-clés pour déclarer une variable, par exemple :var x int
. - Inférence de type : vous pouvez utiliser
:=
des opérateurs pour la déclaration et l'affectation de variables, et Go déduira automatiquement le type de variable en fonction de la valeur à droite, par exemple :y := 5
. - Affectation de variables : utilisez des opérateurs d'affectation
=
pour attribuer des valeurs aux variables, par exemple :x = 10
. - Déclaration multi-variable : Plusieurs variables peuvent être déclarées en même temps, par exemple :
var a, b, c int
. - Initialisation des variables : Les variables peuvent être initialisées lorsqu'elles sont déclarées, par exemple :
var name string = "John"
. - Valeur zéro : les variables non initialisées se verront attribuer une valeur zéro, le type numérique est 0, le type booléen est le type booléen
false
, le type de chaîne est une chaîne vide, etc. - Déclaration de variable courte : À l'intérieur d'une fonction, vous pouvez utiliser une déclaration de variable courte, par exemple :
count := 10
.
Créer un nouveau fib_test.go, background : séquence de Fibonacci simple et pratique pour s'entraîner
package variables
import "testing"
func TestFibList(t *testing.T) {
a := 1
b := 1
t.Log(a)
for i := 0; i < 5; i++ {
t.Log(" ", b)
tmp := a
a = b
b = tmp + a
}
}
func TestExchange(t *testing.T) {
a := 1
b := 2
// tmp := a
// a = b
// b = tmp
a, b = b, a
t.Log(a, b)
}
Les points de connaissances impliqués dans le code sont expliqués un par un ci-dessous :
-
package variables
: déclare un package nommé "variables", qui est un nom de package utilisé pour les tests. -
import "testing"
: Importation du package "testing" du cadre de test du langage Go pour l'écriture et l'exécution de fonctions de test. -
func TestFibList(t *testing.T) { ... }
: Définit une fonction de test "TestFibList", qui est utilisée pour tester la logique de génération de séquence de Fibonacci. Il s'agit du nom standard d'une fonction de test, commençant par "Test", suivi du nom de la fonction testée.a
A l'intérieur de la fonction de test, deux variables entières et sont déclaréesb
et initialisées à 1, qui sont les deux premiers nombres de la séquence de Fibonacci.- Utilisez la valeur
t.Log(a)
de la variable d'impressiona
pour tester le journal. - Utilisez une boucle pour générer les 5 premiers nombres de la séquence de Fibonacci, chaque itération imprime la
b
valeur de dans le journal de test et met à jour les valeurs dea
etb
pour générer le nombre suivant.
-
func TestExchange(t *testing.T) { ... }
: Une autre fonction de test "TestExchange" est définie, qui permet de tester la logique d'échange de variables.- A l'intérieur de la fonction de test, deux variables entières
a
et sont déclaréesb
et initialisées respectivement à 1 et 2. - L'utilisation de commentaires montre une manière d'écrire l'échange de variables (par l'intermédiaire de variables intermédiaires), mais il est en réalité commenté. Utilisez ensuite
a, b = b, a
cette ligne de code pour implémenter l'échange dea
etb
, qui est une méthode d'échange unique dans le langage Go et ne nécessite pas de variables intermédiaires supplémentaires. - Utilisez pour
t.Log(a, b)
imprimer les valeurs des variables échangées dans le journal de test.
- A l'intérieur de la fonction de test, deux variables entières
2.Constante
Prérequis : Créer une constante dans le répertoire chapitre 2. Le résumé d'apprentissage est le suivant :
- Déclaration de constante : utilisez
const
des mots-clés pour déclarer une constante, par exemple :const pi = 3.14159
. - Affectation de constante : La valeur d'une constante doit être affectée lors de sa déclaration. Une fois attribuée, elle ne peut plus être modifiée.
- Constantes d'énumération : Une énumération peut être simulée à l'aide d'un ensemble de constantes, par exemple :
const ( Monday = 1 Tuesday = 2 // ... )
- Spécification du type : Le type de la constante peut également être spécifié, par exemple :
const speed int = 300000
. - Expressions constantes : les constantes peuvent être évaluées à l'aide d'expressions, par exemple :
const secondsInHour = 60 * 60
. - Constantes non typées : les constantes peuvent être non typées, le type étant automatiquement déduit en fonction du contexte. Par exemple,
const x = 5
un type entier sera déduit.
Créez un nouveau constant_test.go et écrivez le code comme suit :
package constant
import "testing"
const (
Monday = 1 + iota
Tuesday
Wednesday
)
const (
Readable = 1 << iota
Writable
Executable
)
func TestConstant1(t *testing.T) {
t.Log(Monday, Tuesday)
}
func TestConstant2(t *testing.T) {
a := 1 //0001
t.Log(a&Readable == Readable, a&Writable == Writable, a&Executable == Executable)
}
Les points de connaissances impliqués dans le code sont expliqués un par un ci-dessous :
-
package constant
: déclare un package nommé "constant", qui est un nom de package utilisé pour les tests. -
import "testing"
: Importation du package "testing" du cadre de test du langage Go pour l'écriture et l'exécution de fonctions de test. -
const (...)
: Deux blocs constants sont définis.-
Dans le premier bloc de constantes, un générateur de constantes est utilisé
iota
pour définir une série de constantes commençant à 1 et croissante. Dans cet exemple,Monday
se voit attribuer la valeur 1,Tuesday
se voit attribuer la valeur 2 etWednesday
se voit attribuer la valeur 3.iota
Il est incrémenté à chaque fois qu'il est utilisé dans un bloc de constantes, de sorte que les constantes suivantes seront incrémentées dans l'ordre. -
Dans le deuxième bloc de constantes, est utilisé
iota
pour définir une série de constantes décalées vers la gauche au niveau du bit. Dans cet exemple,Readable
se voit attribuer la valeur 1,Writable
se voit attribuer la valeur 2 (10 en binaire) etExecutable
se voit attribuer la valeur 4 (100 en binaire). Dans les opérations au niveau du bit, l'opération de décalage vers la gauche peut déplacer un nombre binaire vers la gauche d'un nombre spécifié de chiffres.
-
-
func TestConstant1(t *testing.T) { ... }
: Définit une fonction de test "TestConstant1" pour tester les constantes définies dans le premier bloc de constantes.- Utilisez pour
t.Log(Monday, Tuesday)
imprimer les valeurs des constantesMonday
etTuesday
pour le journal de test.
- Utilisez pour
-
func TestConstant2(t *testing.T) { ... }
: Définit une autre fonction de test "TestConstant2" pour tester les opérations sur les bits et l'utilisation de constantes.- Dans la fonction de test, une variable entière est déclarée
a
et initialisée à 1, soit 0001 en binaire. - Utilisez des opérations au niveau du bit et des opérations ET au niveau du bit pour vérifier
a
si une variable possède les propriétésReadable
,Writable
etExecutable
. Par exemple,a&Readable == Readable
l'expression vérifiea
si la représentation binaire de contientReadable
le bit d'indicateur. - Utilisez pour
t.Log()
imprimer les résultats des trois expressions dans le journal de test.
- Dans la fonction de test, une variable entière est déclarée
3.Type de données
Prérequis : Créer un type dans le répertoire chapitre 2. Le résumé d'apprentissage est le suivant :
Description des principaux types de données
Le langage Go possède un riche ensemble de types de données intégrés qui sont utilisés pour représenter différents types de valeurs et de données. Ce qui suit est une analyse récapitulative de certains principaux types de données en langage Go :
-
Types entiers : le langage Go fournit des types entiers de différentes tailles, tels que
int
,int8
, et . Les types d’entiers non signés incluent , , et . La taille du type entier dépend de l'architecture de l'ordinateur, par exemple 32 bits ou 64 bits.int16
int32
int64
uint
uint8
uint16
uint32
uint64
-
Types à virgule flottante : le langage Go propose
float32
deuxfloat64
types à virgule flottante, correspondant respectivement aux nombres à virgule flottante simple précision et double précision. -
Types complexes : Le langage Go propose
complex64
deuxcomplex128
types complexes, correspondant à des nombres complexes composés de deux nombres à virgule flottante. -
Type booléen : le type booléen est utilisé pour représenter les valeurs vraies (
true
) et fausses (false
) et est utilisé pour le jugement conditionnel et les opérations logiques. -
Type de chaîne : le type de chaîne représente une séquence de caractères. Les chaînes sont immuables et peuvent être définies à l'aide de guillemets doubles
"
ou de backticks .`
-
Type de caractère (Rune Type) : le type de caractère
rune
est utilisé pour représenter les caractères Unicode, qui est un alias de int32. Les guillemets simples sont généralement utilisés'
pour représenter des caractères, tels que'A'
. -
Types de tableaux : un tableau est une collection d’éléments du même type avec une taille fixe. Lors de la déclaration d'un tableau, vous devez spécifier le type et la taille de l'élément.
-
Types de tranches : une tranche est une couche d'encapsulation d'un tableau et une séquence variable de longueur dynamique. Les tranches ne stockent pas d'éléments, elles font simplement référence à une partie du tableau sous-jacent.
-
Types de cartes : les cartes sont des collections non ordonnées de paires clé-valeur utilisées pour stocker et récupérer des données. Les clés et les valeurs peuvent être de n'importe quel type, mais les clés doivent être comparables.
-
Types de structure : une structure est un type de données composite défini par l'utilisateur qui peut contenir des champs de différents types. Chaque champ a un nom et un type.
-
Types d'interface : une interface est un type abstrait qui définit un ensemble de méthodes. L'ensemble des méthodes d'un type qui implémente une interface implémente l'interface.
-
Types de fonctions : les types de fonctions représentent la signature d'une fonction, y compris les types de paramètres et de valeurs de retour. Les fonctions peuvent être passées en paramètres et renvoyées.
-
Types de canaux : les canaux sont un mécanisme de communication et de synchronisation entre les coroutines. Les canaux ont des opérations d'envoi et de réception.
-
Types de pointeurs : les types de pointeurs représentent l’adresse mémoire des variables. La valeur d'une variable est directement accessible et modifiée via un pointeur.
Les types de données du langage Go ont une syntaxe et une sémantique claires et prennent en charge de riches fonctions intégrées. Une sélection et une utilisation appropriées de différents types de données peuvent améliorer l’efficacité et la lisibilité du programme.
Analyse d'expansion de code spécifique
package main
import "fmt"
type Person struct {
FirstName string
LastName string
Age int
}
type Shape interface {
Area() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}
func add(a, b int) int {
return a + b
}
func subtract(a, b int) int {
return a - b
}
type Operation func(int, int) int
func main() {
fmt.Println("整数类型(Integer Types)")
var x int = 10
var y int64 = 100
fmt.Println(x)
fmt.Println(y)
fmt.Println("浮点数类型(Floating-Point Types)")
var a float32 = 3.14
var b float64 = 3.14159265359
fmt.Println(a)
fmt.Println(b)
fmt.Println("布尔类型(Boolean Type)")
var isTrue bool = true
var isFalse bool = false
fmt.Println(isTrue)
fmt.Println(isFalse)
fmt.Println("字符串类型(String Type)")
str1 := "Hello, "
str2 := "Go!"
concatenated := str1 + str2
fmt.Println(concatenated)
fmt.Println("切片类型(Slice Types)")
numbers := []int{1, 2, 3, 4, 5}
fmt.Println(numbers)
// 修改切片元素
numbers[0] = 10
fmt.Println(numbers)
// 切片操作
subSlice := numbers[1:4]
fmt.Println(subSlice)
fmt.Println("映射类型(Map Types)")
ages := map[string]int{
"Alice": 25,
"Bob": 30,
"Eve": 28,
}
fmt.Println(ages)
fmt.Println("Alice's age:", ages["Alice"])
// 添加新的键值对
ages["Charlie"] = 22
fmt.Println(ages)
fmt.Println("结构体类型(Struct Types)")
person := Person{
FirstName: "John",
LastName: "Doe",
Age: 30,
}
fmt.Println(person)
fmt.Println("Name:", person.FirstName, person.LastName)
fmt.Println("接口类型(Interface Types)")
var shape Shape
circle := Circle{Radius: 5}
shape = circle
fmt.Println("Circle Area:", shape.Area())
fmt.Println("函数类型(Function Types)")
var op Operation
op = add
result := op(10, 5)
fmt.Println("Addition:", result)
op = subtract
result = op(10, 5)
fmt.Println("Subtraction:", result)
fmt.Println("通道类型(Channel Types)")
messages := make(chan string)
go func() {
messages <- "Hello, Go!"
}()
msg := <-messages
fmt.Println(msg)
fmt.Println("指针类型(Pointer Types)")
x = 10
var ptr *int
ptr = &x
fmt.Println("Value of x:", x)
fmt.Println("Value stored in pointer:", *ptr)
*ptr = 20
fmt.Println("Updated value of x:", x)
}
Les points de connaissances impliqués dans le code sont expliqués un par un ci-dessous :
-
type Person struct { ... }
: définit un type de structure pourPerson
représenter les informations d'une personne, notamment les champs et .FirstName
LastName
Age
-
type Shape interface { ... }
: Définit un type d'interfaceShape
qui nécessite l'implémentation d'une méthodeArea()
qui renvoie unfloat64
type. -
type Circle struct { ... }
: Définit un type de structureCircle
représentant le rayon d'un cercle.func (c Circle) Area() float64 { ... }
:Circle
Implémente la méthodeShape
de l'interface pour le typeArea()
, qui est utilisée pour calculer l'aire d'un cercle. -
func add(a, b int) int { ... }
: Définit une fonctionadd
qui effectue des opérations d'addition d'entiers. -
func subtract(a, b int) int { ... }
: Définit une fonctionsubtract
qui effectue une soustraction entière. -
type Operation func(int, int) int
: définit un type de fonctionOperation
qui accepte deux paramètres entiers et renvoie un résultat entier. -
main() { ... }
: Fonction d'entrée du programme.
- Plusieurs types différents de variables sont définis, notamment des entiers, des flottants, des booléens, des chaînes, des tranches, des cartes, des structures, des interfaces, des fonctions, des canaux et des types de pointeurs.
- Démontre l'initialisation, l'affectation, l'accès et les opérations de base de différents types de variables.
- Extrayez des tranches partielles à l’aide de l’opération de tranche.
- Montre l’utilisation des mappages, notamment l’ajout de nouvelles paires clé-valeur et l’accès aux paires clé-valeur.
- Montre la définition et l’initialisation des structures et accède aux champs de structure.
- Montre l’utilisation des interfaces,
Circle
l’attribution de types auxShape
variables de type et l’appel des méthodes d’interface. - Montre la définition et l'utilisation des types de fonctions, attribue différentes fonctions aux
Operation
variables de type et les appelle. - Utilisez des canaux pour mettre en œuvre une communication simultanée, l'envoi et la réception de messages dans des goroutines via des fonctions anonymes.
- Montre l'utilisation de pointeurs, y compris des opérations telles que la création de variables de pointeur et la modification de la valeur des variables via des pointeurs.
Tapez les instructions de conversion en langage Go
Le langage Go prend en charge la conversion de type, mais il existe certaines règles et restrictions à prendre en compte. La conversion de type est utilisée pour convertir une valeur d'un type de données en un autre type de données pour une utilisation dans un contexte différent. Voici quelques informations importantes sur la conversion de type en langage Go :
-
Conversion entre types de base : La conversion entre types de données de base est possible, mais il faut prêter attention à la compatibilité des types et à une éventuelle perte de données. Par exemple, une conversion de
int
versfloat64
est sûre, mais une conversionfloat64
de versint
peut entraîner la troncature de la partie décimale. -
Conversions explicites : dans Go, les conversions sont utilisées pour spécifier explicitement la conversion d'une valeur en un autre type. La syntaxe est :
destinationType(expression)
. Par exemple :float64(10)
. -
Conversion entre types incompatibles : Le compilateur ne convertit pas automatiquement les types incompatibles. Par exemple, vous ne pouvez pas convertir directement un
string
type enint
type. -
Conversion d'alias de type : s'il existe un alias de type (Type Alias), vous devez faire attention à la compatibilité de l'alias lors de la conversion.
Voici quelques exemples pour illustrer la conversion de type :
package main
import "fmt"
func main() {
// 显式类型转换
var x int = 10
var y float64 = float64(x)
fmt.Println(y)
// 类型别名的转换
type Celsius float64
type Fahrenheit float64
c := Celsius(25)
f := Fahrenheit(c*9/5 + 32)
fmt.Println(f)
}
4.Opérateur
Prérequis : Créer un opérateur dans le répertoire chapitre 2. Le résumé d'apprentissage est le suivant :
En fait, cette partie est similaire aux autres langages, j'ai personnellement l'impression qu'il n'y a rien à revoir et à consolider. Le langage Go prend en charge une variété d'opérateurs pour effectuer diverses opérations arithmétiques, logiques et de comparaison.
opérateurs réguliers
Voici quelques opérateurs courants ainsi que leur utilisation et leurs points de connaissance dans Go :
Opérateurs arithmétiques :
+
:ajout-
:Soustraction*
:multiplication/
:division%
: Modulo (reste)
Opérateurs d'affectation :
=
: Affectation+=
: Affectation d'ajout-=
: affectation de soustraction*=
:Affectation de multiplication/=
: Affectation de division%=
: Affectation modulo
Opérateurs logiques:
&&
: ET logique (ET)||
: OU logique (OU)!
: NON logique (NON)
Opérateurs de comparaison :
==
:égal!=
:pas égal à<
: moins que>
:plus que le<=
: inférieur ou égal à>=
: supérieur ou égal à
Opérateurs au niveau du bit :
&
: ET au niveau du bit (ET)|
: OU au niveau du bit (OU)^
: OU exclusif au niveau du bit (XOR)<<
: Décaler vers la gauche>>
:Déplacer vers la droite
Autres opérateurs :
&
:Opérateur d'adresse*
: opérateur pointeur++
:opérateur d'incrémentation--
: Opérateur de décrémentation
Lorsque vous utilisez des opérateurs, il y a quelques éléments à prendre en compte :
- Les opérandes d'un opérateur doivent correspondre au type attendu de l'opérateur.
- Certains opérateurs ont une priorité plus élevée et nécessitent l'utilisation de parenthèses pour clarifier la priorité.
- Les opérandes des opérateurs peuvent être des variables, des constantes, des expressions, etc.
Créez un nouveau Operator_test.go. Voici quelques exemples pour montrer l'utilisation des opérateurs :
package operator
import (
"fmt"
"testing"
)
const (
Readable = 1 << iota
Writable
Executable
)
func TestOperatorBasic(t *testing.T) {
// 算术运算符
a := 10
b := 5
fmt.Println("Sum:", a+b)
fmt.Println("Difference:", a-b)
fmt.Println("Product:", a*b)
fmt.Println("Quotient:", a/b)
fmt.Println("Remainder:", a%b)
// 逻辑运算符
x := true
y := false
fmt.Println("AND:", x && y)
fmt.Println("OR:", x || y)
fmt.Println("NOT:", !x)
// 比较运算符
fmt.Println("Equal:", a == b)
fmt.Println("Not Equal:", a != b)
fmt.Println("Greater Than:", a > b)
fmt.Println("Less Than:", a < b)
fmt.Println("Greater Than or Equal:", a >= b)
fmt.Println("Less Than or Equal:", a <= b)
}
func TestCompareArray(t *testing.T) {
a := [...]int{1, 2, 3, 4}
b := [...]int{1, 3, 2, 4}
// c := [...]int{1, 2, 3, 4, 5}
d := [...]int{1, 2, 3, 4}
t.Log(a == b)
//t.Log(a == c)
t.Log(a == d)
}
func TestBitClear(t *testing.T) {
a := 7 //0111
a = a &^ Readable
a = a &^ Executable
t.Log(a&Readable == Readable, a&Writable == Writable, a&Executable == Executable)
}
Les points de connaissances impliqués dans le code sont expliqués un par un ci-dessous :
-
const (...)
: Définit trois constantesReadable
, somme,Writable
etExecutable
utilise des opérations de déplacement pour générer différentes valeurs. -
func TestOperatorBasic(t *testing.T) { ... }
: Définit une fonction de test "TestOperatorBasic" pour tester l'utilisation des opérateurs de base.- Opérateurs arithmétiques : affiche les opérations d'addition, de soustraction, de multiplication, de division et de reste.
- Opérateurs logiques : illustre les opérations ET logique, OU logique et NON logique.
- Opérateurs de comparaison : affiche les opérations égales, non égales, supérieures à, inférieures à, supérieures ou égales et inférieures ou égales à.
-
func TestCompareArray(t *testing.T) { ... }
: Définit une fonction de test "TestCompareArray" pour tester la comparaison de tableaux.a
Deux tableaux d'entiers et sont déclarésb
, ainsi qu'un autre tableaud
dont le contenu du tableaua
et du tableaud
est identique.- Utilisez l'opérateur de comparaison
==
pour vérifier si les tableauxa
etb
sont égaux et si les tableauxa
etd
sont égaux.
-
func TestBitClear(t *testing.T) { ... }
: Définit une fonction de test "TestBitClear" pour tester les opérations d'effacement de bits.- Déclarez une variable entière
a
et initialisez-la à7
, c'est-à-dire la représentation binaire0111
. - Utilisez l'opération d'effacement de bits
&^
pour effacer les bits eta
dans .Readable
Executable
- Utilisez l’opération ET au niveau du bit
&
pour vérifiera
si possède les propriétésReadable
,Writable
etExecutable
.
- Déclarez une variable entière
Opérateur d'effacement au niveau du bit &^
Dans le langage Go, &^
il s'agit de l'opérateur Bit Clear. Il est utilisé pour effacer les bits à certaines positions, c'est-à-dire définir les bits à la position spécifiée sur 0. &^
Les opérateurs sont très utiles lorsqu’il s’agit d’opérations sur bits binaires.
&^
Les opérateurs effectuent les opérations suivantes :
- Pour chaque bit, si le bit correspondant de l'opérande de droite est 0, le bit résultat est le même que celui de l'opérande de gauche.
- Pour chaque bit, si le bit correspondant dans l'opérande de droite est 1, le bit résultat est forcé à 0.
Cela signifie que &^
l'opérateur est utilisé pour "effacer" un bit spécifique de l'opérande de gauche afin que le bit correspondant de l'opérande de droite ne soit pas affecté. Écrivez un code pour vérifier :
func TestOther(t *testing.T) {
var a uint8 = 0b11001100 // 二进制表示,十进制为 204
var b uint8 = 0b00110011 // 二进制表示,十进制为 51
result := a &^ b
fmt.Printf("a: %08b\n", a) // 输出:11001100
fmt.Printf("b: %08b\n", b) // 输出:00110011
fmt.Printf("Result: %08b\n", result) // 输出:11000000
fmt.Println("Result (Decimal):", result) // 输出:192
}
5. Déclarations conditionnelles
Prérequis : Créer une condition dans le répertoire chapitre 2. Le résumé d'apprentissage est le suivant :
if
déclaration
if
Les instructions sont utilisées pour décider d'exécuter ou non un certain morceau de code en fonction d'une condition. Sa syntaxe de base est la suivante :
if condition {
// 代码块
} else if anotherCondition {
// 代码块
} else {
// 代码块
}
switch
déclaration
switch
Les instructions sont utilisées pour exécuter différentes branches de code en fonction de différentes valeurs d'une expression. Contrairement à d'autres langages, Go switch
peut automatiquement faire correspondre la première branche qui satisfait à la condition sans utiliser break
d'instruction. Sa syntaxe est la suivante :
switch expression {
case value1:
// 代码块
case value2:
// 代码块
default:
// 代码块
}
Créez condition_test.go pour l'analyse de vérification. Le code spécifique est le suivant :
package condition
import (
"fmt"
"testing"
)
func TestConditionIf(t *testing.T) {
age := 18
if age < 18 {
fmt.Println("You are a minor.")
} else if age >= 18 && age < 60 {
fmt.Println("You are an adult.")
} else {
fmt.Println("You are a senior citizen.")
}
}
func TestConditionSwitch(t *testing.T) {
dayOfWeek := 3
switch dayOfWeek {
case 1:
fmt.Println("Monday")
case 2:
fmt.Println("Tuesday")
case 3:
fmt.Println("Wednesday")
case 4:
fmt.Println("Thursday")
case 5:
fmt.Println("Friday")
default:
fmt.Println("Weekend")
}
}
func TestSwitchMultiCase(t *testing.T) {
for i := 0; i < 5; i++ {
switch i {
case 0, 2:
t.Logf("%d is Even", i)
case 1, 3:
t.Logf("%d is Odd", i)
default:
t.Logf("%d is not 0-3", i)
}
}
}
func TestSwitchCaseCondition(t *testing.T) {
for i := 0; i < 5; i++ {
switch {
case i%2 == 0:
t.Logf("%d is Even", i)
case i%2 == 1:
t.Logf("%d is Odd", i)
default:
t.Logf("%d is unknow", i)
}
}
}
Le contenu de chaque fonction de test est expliqué une par une ci-dessous :
-
Selon différentes circonstances d'âge, on juge s'il s'agit d'un mineur, d'un adulte ou d'une personne âgée à traversfunc TestConditionIf(t *testing.T) { ... }
: Testezif
l’utilisation des instructions.if
les brancheselse if
et .else
-
func TestConditionSwitch(t *testing.T) { ... }
: Testezswitch
l’utilisation des instructions. SelondayOfWeek
la valeur de , utilisezswitch
l'instruction pour afficher le jour de la semaine correspondant. -
func TestSwitchMultiCase(t *testing.T) { ... }
: Testez le cas oùswitch
l'instruction a plusieurscase
valeurs. Utilisezswitch
l'instruction pour déterminer la parité de chaque nombre et afficher les informations correspondantes. -
func TestSwitchCaseCondition(t *testing.T) { ... }
: Testezswitch
l'expression conditionnelle dans l'instruction. Utilisezswitch
l'instruction pour déterminer la parité d'un nombre en prenant le reste du nombre et affichez les informations correspondantes.
Ces fonctions de test démontrent différentes utilisations des instructions conditionnelles dans le langage Go, y compris le jugement de branchement basé sur des conditions et case
le traitement de plusieurs valeurs, ainsi que switch
l'utilisation d'expressions conditionnelles dans les instructions.
6. Instructions de boucle
Prérequis : Créer une boucle dans le répertoire chapitre 2. Le résumé d'apprentissage est le suivant :
for
faire du vélo
for
Les boucles sont utilisées pour exécuter de manière répétée des blocs de code et prendre en charge les instructions d'initialisation, les conditions de boucle et les instructions après la boucle. Sa forme de base est la suivante :
for initialization; condition; post {
// 代码块
}
Dans l'instruction d'initialisation, vous initialisez les variables de boucle, puis utilisez les conditions dans le corps de la boucle pour contrôler la boucle et enfin post
effectuez des opérations d'incrémentation ou de décrémentation dans l'instruction.
for
Forme simplifiée de boucle
Les boucles de langage Go peuvent également être simplifiées à la seule partie condition de boucle, similaire aux boucles for
dans d'autres langages :while
for condition {
// 代码块
}
range
faire du vélo
range
Les boucles sont utilisées pour parcourir des structures de données itérables telles que des tableaux, des tranches, des cartes et des chaînes. Il renvoie l'index et la valeur de chaque itération. Exemple:
for index, value := range iterable {
// 使用 index 和 value
}
Créez loop_test.go pour l'analyse de vérification. Le code spécifique est le suivant :
package loop
import (
"fmt"
"testing"
)
func TestLoopFor(t *testing.T) {
for i := 1; i <= 5; i++ {
fmt.Println("Iteration:", i)
}
}
func TestLoopForBasic(t *testing.T) {
i := 1
for i <= 5 {
fmt.Println("Iteration:", i)
i++
}
}
func TestLoopForRange(t *testing.T) {
numbers := []int{1, 2, 3, 4, 5}
for index, value := range numbers {
fmt.Printf("Index: %d, Value: %d\n", index, value)
}
}
func TestLoopForUnLimit(t *testing.T) {
i := 1
for {
fmt.Println("Iteration:", i)
i++
if i > 5 {
break
}
}
}
Le contenu de chaque fonction de test est expliqué une par une ci-dessous :
-
func TestLoopFor(t *testing.T) { ... }
: Testezfor
les boucles de base. À l’aidefor
d’une boucle, parcourez de 1 à 5 pour afficher le nombre d’itérations de boucle. -
func TestLoopForBasic(t *testing.T) { ... }
: Testez une boucle sans instructions d'initialisationfor
. À l'aidefor
d'une boucle, parcourez de 1 à 5 pour afficher le nombre d'itérations de boucle, mais aucune instruction d'initialisation n'est déclarée en tête de la boucle. -
func TestLoopForRange(t *testing.T) { ... }
:Test en utilisantfor range
le découpage itératif. Définissez une tranche entièrenumbers
, utilisezfor range
une boucle pour parcourir chaque élément de la tranche et affichez l'index et la valeur de l'élément. -
func TestLoopForUnLimit(t *testing.T) { ... }
: Testez des boucles etbreak
des instructions infinies. Utilisez une boucle etbreak
une instruction infinies pour déterminer s'il faut terminer la boucle à l'intérieur du corps de la boucle eti
quitter la boucle lorsqu'elle est supérieure à 5.
Ces fonctions de test démontrent for
l'utilisation de différents types de boucles dans le langage Go, notamment les boucles de comptage standard, les boucles sans instructions d'initialisation, les tranches traversantes, ainsi que les boucles infinies et les conditions de fin de boucle.
7. Déclarations de saut
Prérequis : Créer un saut dans le répertoire chapitre 2. Le résumé d'apprentissage est le suivant :
Le langage Go prend également en charge plusieurs instructions de saut pour contrôler le flux dans les boucles et les conditions :
break
: Sortez de la boucle.continue
: Ignorez cette itération de boucle et continuez avec l'itération suivante.goto
: Accédez directement à la balise spécifiée dans le code (non recommandé) .
Créez jump_test.go pour l'analyse de vérification. Le code spécifique est le suivant :
package jump
import (
"fmt"
"testing"
)
func TestJumpBreak(t *testing.T) {
for i := 1; i <= 5; i++ {
if i == 3 {
break
}
fmt.Println("Iteration:", i)
}
}
func TestJumpContinue(t *testing.T) {
for i := 1; i <= 5; i++ {
if i == 3 {
continue
}
fmt.Println("Iteration:", i)
}
}
func TestJumpGoto(t *testing.T) {
i := 1
start:
fmt.Println("Iteration:", i)
i++
if i <= 5 {
goto start
}
}
Le contenu de chaque fonction de test est expliqué une par une ci-dessous :
-
func TestJumpBreak(t *testing.T) { ... }
: Testezbreak
l’utilisation des instructions. Utilisezfor
une boucle pour parcourir de 1 à 5, maisi
lorsque la variable d'itération est égale à 3, utilisezbreak
l'instruction pour terminer la boucle. -
func TestJumpContinue(t *testing.T) { ... }
: Testezcontinue
l’utilisation des instructions. Utilisezfor
la boucle pour itérer de 1 à 5, mais lorsque la variable d'itérationi
est égale à 3, utilisezcontinue
l'instruction pour ignorer cette itération et passer à l'itération suivante. -
func TestJumpGoto(t *testing.T) { ... }
: Testezgoto
l’utilisation des instructions. Une boucle infinie est implémentée à l'aidegoto
de l'instruction, qui utilise des étiquettesstart
etgoto start
permet de passer à la position de départ de la boucle à l'intérieur du corps de la boucle. La condition de terminaison de la boucle est lorsquei
est supérieur à 5.
Ces fonctions de test démontrent les instructions de saut de contrôle de boucle dans le langage Go, y compris les instructions permettant de terminer les boucles break
, de sauter l'itération en cours continue
et les boucles infinies goto
.
(3) Collections et chaînes couramment utilisées
Créez le chapitre 3 dans le répertoire src. Dans le langage Go, un ensemble est une structure de données qui stocke un ensemble de valeurs. Les types de collections couramment utilisés incluent les tableaux, les tranches, les cartes et les canaux.
1.Tableau
Prérequis : Créer un tableau dans le répertoire chapitre 3. Le résumé d'apprentissage est le suivant :
Un tableau en langage Go est une collection d’éléments de même type de longueur fixe. Ce qui suit est un résumé et une application des connaissances sur les baies Go :
Caractéristiques des tableaux
- La longueur d'un tableau est spécifiée au moment de la déclaration et ne peut pas être modifiée après la création.
- Les tableaux sont des types valeur, et lorsqu'un tableau est affecté à une nouvelle variable ou passé en paramètre, une nouvelle copie est créée.
- Les tableaux sont stockés en permanence en mémoire et prennent en charge l'accès aléatoire.
Déclaration et initialisation du tableau
var arrayName [size]dataType
arrayName
: Le nom du tableau.size
: La longueur du tableau doit être une expression constante.dataType
: Le type d'éléments stockés dans le tableau.
Comment initialiser un tableau
// 使用指定的值初始化数组
var arr = [5]int{1, 2, 3, 4, 5}
// 根据索引初始化数组
var arr [5]int
arr[0] = 10
arr[1] = 20
// 部分初始化
var arr = [5]int{1, 2}
// 自动推断数组长度
arr := [...]int{1, 2, 3, 4, 5}
Accès et traversée du tableau
// 访问单个元素
value := arr[index]
// 遍历数组
for index, value := range arr {
fmt.Printf("Index: %d, Value: %d\n", index, value)
}
Tableau comme paramètre de fonction
Une copie du tableau est créée lorsque le paramètre de fonction est passé, donc les modifications apportées au tableau dans la fonction n'affecteront pas le tableau d'origine. Si vous devez modifier le tableau d'origine dans la fonction, vous pouvez passer un pointeur vers le tableau.
func modifyArray(arr [5]int) {
arr[0] = 100
}
func modifyArrayByPointer(arr *[5]int) {
arr[0] = 100
}
Tableaux multidimensionnels
Le langage Go prend en charge les tableaux multidimensionnels, tels que les tableaux à deux dimensions et les tableaux à trois dimensions. L'initialisation et l'accès aux tableaux multidimensionnels sont similaires à ceux des tableaux unidimensionnels, sauf que plusieurs index doivent être spécifiés.
var matrix [3][3]int = [3][3]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
Les tableaux sont très utiles pour stocker un nombre fixe d'éléments du même type, mais en raison de leurs limitations de longueur fixe, les tranches sont généralement plus couramment utilisées dans le développement réel, qui présentent les caractéristiques d'une longueur dynamique. Les tranches peuvent être ajoutées, supprimées et réaffectées selon les besoins, ce qui les rend plus flexibles.
Créez array_test.go pour l'analyse de vérification. Le code spécifique est le suivant :
package array
import "testing"
func TestArrayInit(t *testing.T) {
var arr [3]int
arr1 := [4]int{1, 2, 3, 4}
arr3 := [...]int{1, 3, 4, 5}
arr1[1] = 5
t.Log(arr[1], arr[2])
t.Log(arr1, arr3)
}
func TestArrayTravel(t *testing.T) {
arr3 := [...]int{1, 3, 4, 5}
for i := 0; i < len(arr3); i++ {
t.Log(arr3[i])
}
for _, e := range arr3 {
t.Log(e)
}
}
func TestArraySection(t *testing.T) {
arr3 := [...]int{1, 2, 3, 4, 5}
arr3_sec := arr3[:]
t.Log(arr3_sec)
}
Le contenu de chaque fonction de test est expliqué une par une ci-dessous :
-
func TestArrayInit(t *testing.T) { ... }
: Teste l'initialisation du tableau.- Utilisez différentes manières d'initialiser les tableaux
arr
,arr1
etarr3
. - Modifiez
arr1
le deuxième élément de5
. - Utilisez pour
t.Log()
afficher les valeurs des éléments et le contenu de différents tableaux.
- Utilisez différentes manières d'initialiser les tableaux
-
func TestArrayTravel(t *testing.T) { ... }
: Test du parcours du tableau.- Utilisez
for
pour parcourir le tableauarr3
et afficher la valeur de chaque élément séparément. - Utilisez
for range
pour parcourir le tableauarr3
et également afficher la valeur de chaque élément.
- Utilisez
-
func TestArraySection(t *testing.T) { ... }
: Testez l'utilisation du découpage de tableau.- Crée une tranche de tableau
arr3_sec
basée sur l'ensemble du tableauarr3
. - Utilisez le contenu
t.Log()
des tranches du tableau de sortiearr3_sec
.
- Crée une tranche de tableau
2. Trancher
Prérequis : Créer une slice dans le répertoire chapitre 3. Le résumé d'apprentissage est le suivant :
Le langage Slice in Go est une couche d'encapsulation de tableaux, offrant une séquence de longueur dynamique plus flexible. Ce qui suit est un résumé des connaissances et des applications sur le découpage :
Caractéristiques des tranches
- Une tranche est un type référence, elle ne stocke pas de données, elle fait simplement référence à une partie du tableau sous-jacent.
- Les tranches sont de longueur dynamique et peuvent être agrandies ou réduites selon les besoins.
- Les tranches sont indexables et peuvent être coupées par index de tranche.
Déclaration et initialisation des tranches
var sliceName []elementType
Comment initialiser les tranches
// 声明切片并初始化
var slice = []int{1, 2, 3, 4, 5}
// 使用 make 函数创建切片
var slice = make([]int, 5) // 创建长度为 5 的 int 类型切片
// 使用切片切割已有数组或切片
newSlice := oldSlice[startIndex:endIndex] // 包括 startIndex,但不包括 endIndex
Fonctions et opérations intégrées pour le découpage
len(slice)
: Renvoie la longueur de la tranche.cap(slice)
: Renvoie la capacité de la tranche, qui est la longueur du tableau sous-jacent.append(slice, element)
:Ajouter des éléments à la fin de la tranche et renvoyer la nouvelle tranche.copy(destination, source)
: copie les éléments de la tranche source vers la tranche cible.
Traversée de tranche
for index, value := range slice {
// 使用 index 和 value
}
Tranches comme paramètres de fonction
Lorsqu'une tranche est passée en paramètre à une fonction, les modifications apportées à la tranche au sein de la fonction affecteront la tranche d'origine.
func modifySlice(s []int) {
s[0] = 100
}
func main() {
numbers := []int{1, 2, 3, 4, 5}
modifySlice(numbers)
fmt.Println(numbers) // 输出:[100 2 3 4 5]
}
Le découpage est largement utilisé dans le langage Go pour gérer des ensembles de données dynamiques, tels que des collections, des listes, des files d'attente, etc. Il fournit des moyens pratiques de gérer les éléments tout en évitant les limitations des tableaux fixes. Dans les applications pratiques, les tranches sont souvent utilisées pour stocker et traiter des données de longueur variable.
Créez slice_test.go pour l'analyse de vérification. Le code spécifique est le suivant :
package slice
import (
"fmt"
"testing"
)
func TestSlice(t *testing.T) {
// 声明和初始化切片
numbers := []int{1, 2, 3, 4, 5}
fmt.Println("Original Slice:", numbers)
// 使用 make 函数创建切片
slice := make([]int, 3)
fmt.Println("Initial Make Slice:", slice)
// 添加元素到切片
slice = append(slice, 10)
slice = append(slice, 20, 30)
fmt.Println("After Append:", slice)
// 复制切片
copySlice := make([]int, len(slice))
copy(copySlice, slice)
fmt.Println("Copied Slice:", copySlice)
// 切片切割
subSlice := numbers[1:3]
fmt.Println("Subslice:", subSlice)
// 修改切片的值会影响底层数组和其他切片
subSlice[0] = 100
fmt.Println("Modified Subslice:", subSlice)
fmt.Println("Original Slice:", numbers)
fmt.Println("Copied Slice:", copySlice)
// 遍历切片
for index, value := range slice {
fmt.Printf("Index: %d, Value: %d\n", index, value)
}
}
Le contenu de chaque fonction de test est expliqué une par une ci-dessous :
func TestSlice(t *testing.T) { ... }
: Testez les opérations de base du découpage.
- Déclarez et initialisez le slice
numbers
, et affichez le contenu initial de la tranche. - Utilisez
make
la fonction pour créer3
une tranche avec une capacité initiale deslice
et afficher le contenu initial de la tranche. - Utilisez une fonction pour ajouter des éléments
append
à une tranche .slice
- Utilisez
copy
la fonction pour copier une trancheslice
vers une nouvelle tranchecopySlice
. - Utilisez Slice
numbers
pour effectuer des coupes en tranches et créer des sous-tranchessubSlice
. subSlice
Le premier élément de modification est100
, affiche la tranche modifiée et la tranche originale, ainsi que la tranche copiée.- Utilisez
for range
une boucle pour parcourir la sliceslice
, en affichant l'index et la valeur de chaque élément.
Cette fonction de test montre diverses opérations de tranches en langage Go, notamment la création de tranches, l'ajout d'éléments, la copie de tranches, la découpe de tranches, la modification d'éléments de tranche, etc.
3.Carte
Prérequis : Créer une carte dans le répertoire chapitre 3. Le résumé d'apprentissage est le suivant :
Une carte en langage Go est une collection non ordonnée de paires clé-valeur, également appelée tableau associatif ou dictionnaire. Ce qui suit est un résumé des connaissances et des applications en matière de cartographie :
Caractéristiques de cartographie
- Une carte est utilisée pour stocker un ensemble de paires clé-valeur, où chaque clé est unique.
- La carte n'est pas ordonnée et l'ordre des paires clé-valeur n'est pas garanti.
- Les clés peuvent être de n'importe quel type comparable et les valeurs peuvent être de n'importe quel type.
- Les cartes sont des types de référence qui peuvent être attribués et transmis aux fonctions.
Déclaration et initialisation du mappage
var mapName map[keyType]valueType
Comment initialiser le mappage
// 声明和初始化映射
var ages = map[string]int{
"Alice": 25,
"Bob": 30,
"Eve": 28,
}
// 使用 make 函数创建映射
var ages = make(map[string]int)
Opérations de cartographie
- Ajoutez des paires clé-valeur :
ages["Charlie"] = 35
- Supprimez les paires clé-valeur :
delete(ages, "Eve")
- Obtenez de la valeur :
value := ages["Alice"]
Parcours de cartographie
for key, value := range ages {
fmt.Printf("Name: %s, Age: %d\n", key, value)
}
Mappage en tant que paramètre de fonction
Lorsqu'un mappage est passé en paramètre à une fonction, les modifications apportées au mappage au sein de la fonction affecteront le mappage d'origine.
func modifyMap(m map[string]int) {
m["Alice"] = 30
}
func main() {
ages := map[string]int{
"Alice": 25,
"Bob": 30,
}
modifyMap(ages)
fmt.Println(ages) // 输出:map[Alice:30 Bob:30]
}
Map est une structure de données très couramment utilisée dans le langage Go pour stocker et récupérer des données. Il est très utile lors du stockage d'un ensemble de paires clé-valeur associées, telles que la correspondance entre le nom et l'âge, la correspondance entre les mots et les définitions, etc. Dans les applications pratiques, la cartographie est un outil important pour le traitement et le stockage des données clé-valeur.
Créez map_test.go pour l'analyse de vérification. Le code spécifique est le suivant :
package my_map
import (
"fmt"
"testing"
)
func TestBasic(t *testing.T) {
// 声明和初始化映射
ages := map[string]int{
"Alice": 25,
"Bob": 30,
"Eve": 28,
}
fmt.Println("Original Map:", ages)
// 添加新的键值对
ages["Charlie"] = 35
fmt.Println("After Adding:", ages)
// 修改已有键的值
ages["Bob"] = 31
fmt.Println("After Modification:", ages)
// 删除键值对
delete(ages, "Eve")
fmt.Println("After Deletion:", ages)
// 获取值和检查键是否存在
age, exists := ages["Alice"]
if exists {
fmt.Println("Alice's Age:", age)
} else {
fmt.Println("Alice not found")
}
// 遍历映射
for name, age := range ages {
fmt.Printf("Name: %s, Age: %d\n", name, age)
}
}
type Student struct {
Name string
Age int
Grade string
}
func TestComplex(t *testing.T) {
// 声明和初始化映射,用于存储学生信息和成绩
studentScores := make(map[string]int)
studentInfo := make(map[string]Student)
// 添加学生信息和成绩
studentInfo["Alice"] = Student{Name: "Alice", Age: 18, Grade: "A"}
studentScores["Alice"] = 95
studentInfo["Bob"] = Student{Name: "Bob", Age: 19, Grade: "B"}
studentScores["Bob"] = 85
// 查找学生信息和成绩
aliceInfo := studentInfo["Alice"]
aliceScore := studentScores["Alice"]
fmt.Printf("Name: %s, Age: %d, Grade: %s, Score: %d\n", aliceInfo.Name, aliceInfo.Age, aliceInfo.Grade, aliceScore)
// 遍历学生信息和成绩
for name, info := range studentInfo {
score, exists := studentScores[name]
if exists {
fmt.Printf("Name: %s, Age: %d, Grade: %s, Score: %d\n", info.Name, info.Age, info.Grade, score)
} else {
fmt.Printf("No score available for %s\n", name)
}
}
}
Le contenu de chaque fonction de test est expliqué une par une ci-dessous :
-
func TestBasic(t *testing.T) { ... }
: Opérations de base pour tester le mappage.- Déclarez et initialisez la carte
ages
pour stocker les paires clé-valeur du nom et de l'âge de la personne. - Sortez le contenu du mappage initial.
- Utilisez
ages["Charlie"]
pour ajouter de nouvelles paires clé-valeur. - Utilisé pour
ages["Bob"]
modifier la valeur d'une clé existante. - Utilisez
delete
la fonction pour supprimer les paires clé-valeur. - Utilisez
age, exists
pour obtenir la valeur et vérifier si la clé existe. - Utilisez
for range
une boucle pour parcourir la carte et afficher les informations pour chaque paire clé-valeur.
- Déclarez et initialisez la carte
-
type Student struct { ... }
: Définit uneStudent
structure nommée pour stocker les informations sur les étudiants. -
func TestComplex(t *testing.T) { ... }
: Tester les opérations de mappage contenant des valeurs complexes.- Déclarez et initialisez deux mappages,
studentScores
utilisés pour stocker les scores etstudentInfo
les informations des étudiants. - Ajoutez des informations et des scores sur les élèves au mappage.
- Utilisez-le
studentInfo["Alice"]
pour obtenir des informations sur les étudiants et utilisez-lestudentScores["Alice"]
pour obtenir les scores des étudiants. - Utilisez
for range
une boucle pour parcourir la carte et afficher les informations et le score de chaque élève.
- Déclarez et initialisez deux mappages,
Ces fonctions de test démontrent diverses opérations de mappage dans le langage Go, notamment la création, l'ajout, la modification, la suppression de paires clé-valeur, la vérification de l'existence d'une clé et le parcours des paires clé-valeur mappées.
4. Ensemble d'outils
Prérequis : Créer un ensemble dans le répertoire chapitre 3. Le résumé d'apprentissage est le suivant :
Dans le langage Go, bien que la bibliothèque standard ne fournisse pas de type Set intégré, vous pouvez utiliser différentes manières pour implémenter la fonction Set. Voici plusieurs façons courantes d’implémenter Set :
Utiliser des tranches
Créer un exercice set_slice_test.go
Utilisez des tranches pour stocker des éléments et vérifiez si les éléments existent en parcourant la tranche. Il s'agit d'une implémentation simple qui fonctionne bien pour les petites collections.
package set
import (
"fmt"
"testing"
)
type IntSet struct {
elements []int
}
func (s *IntSet) Add(element int) {
if !s.Contains(element) {
s.elements = append(s.elements, element)
}
}
func (s *IntSet) Contains(element int) bool {
for _, e := range s.elements {
if e == element {
return true
}
}
return false
}
func TestSet(t *testing.T) {
set := IntSet{}
set.Add(1)
set.Add(2)
set.Add(3)
set.Add(2) // Adding duplicate, should be ignored
fmt.Println("Set:", set.elements) // Output: [1 2 3]
}
Utiliser le mappage
Créer un exercice set_map_test.go
Utilisez une carte pour stocker des éléments. Les clés de la carte représentent les éléments de la collection et les valeurs peuvent être de n'importe quel type. Cette implémentation est plus rapide et adaptée aux grandes collections car la complexité de la recherche de mappage est O(1) .
package set
import (
"fmt"
"testing"
)
type Set map[int]bool
func (s Set) Add(element int) {
s[element] = true
}
func (s Set) Contains(element int) bool {
return s[element]
}
func TestSetMap(t *testing.T) {
set := make(Set)
set.Add(1)
set.Add(2)
set.Add(3)
set.Add(2) // Adding duplicate, should be ignored
fmt.Println("Set:", set) // Output: map[1:true 2:true 3:true]
}
Utiliser des bibliothèques tierces
Créer un exercice set_third_test.go
Afin d'éviter de l'implémenter vous-même, vous pouvez utiliser certaines bibliothèques tierces, par exemple github.com/deckarep/golang-set
, qui fournissent des fonctions Set plus riches.
Ajoutez un proxy : go env -w GOPROXY=https://goproxy.io,direct
Installez ensuite le package : allez chercher github.com/deckarep/golang-set
package set
import (
"fmt"
"github.com/deckarep/golang-set"
"testing"
)
func TestSetThird(t *testing.T) {
intSet := mapset.NewSet()
intSet.Add(1)
intSet.Add(2)
intSet.Add(3)
intSet.Add(2) // Adding duplicate, will be ignored
fmt.Println("Set:", intSet) // Output: Set: Set{1, 2, 3}
}
Vous trouverez ci-dessus plusieurs façons d'implémenter Set. Vous pouvez choisir la méthode d'implémentation appropriée en fonction de vos besoins et de vos considérations en matière de performances. Les bibliothèques tierces peuvent fournir davantage de fonctionnalités et d'optimisations de performances pour les collections de données à grande échelle.
5. Chaîne
Prérequis : Créer une chaîne dans le répertoire chapitre 3. Le résumé d'apprentissage est le suivant :
Déclaration et initialisation des chaînes
Dans le langage Go, une chaîne est composée d'une série de caractères. Vous pouvez utiliser des guillemets doubles "
ou des guillemets ``` pour déclarer et initialiser la chaîne.
package main
import "fmt"
func main() {
str1 := "Hello, World!" // 使用双引号声明
str2 := `Go Programming` // 使用反引号声明
fmt.Println(str1) // Output: Hello, World!
fmt.Println(str2) // Output: Go Programming
}
longueur de chaîne
Utilisez la fonction intégrée len()
pour obtenir la longueur d'une chaîne, c'est-à-dire le nombre de caractères dans la chaîne.
package main
import "fmt"
func main() {
str := "Hello, 世界!"
length := len(str)
fmt.Println("String Length:", length) // Output: String Length: 9
}
Indexation et découpage de chaînes
Les caractères d'une chaîne sont accessibles par index, qui commence à 0. Vous pouvez utiliser l'opération de découpage pour obtenir les sous-chaînes d'une chaîne.
package main
import "fmt"
func main() {
str := "Hello, World!"
// 获取第一个字符
firstChar := str[0]
fmt.Println("First Character:", string(firstChar)) // Output: First Character: H
// 获取子串
substring := str[7:12]
fmt.Println("Substring:", substring) // Output: Substring: World
}
Concaténation de chaînes
Utilisez +
l'opérateur pour concaténer deux chaînes en une nouvelle chaîne. De plus, strings.Join
la fonction est utilisée pour concaténer des tranches de chaîne en une nouvelle chaîne, qui peut être utilisée pour assembler plusieurs chaînes.
Enfin, l'utilisation de la mise en mémoire tampon d'octets permet une concaténation de chaînes efficace sans effectuer de copies de chaînes redondantes.
package main
import (
"fmt"
"strings"
"bytes"
)
func main() {
str1 := "Hello, "
str2 := "World!"
result := str1 + str2
fmt.Println("Concatenated String:", result) // Output: Concatenated String: Hello, World!
strSlice := []string{"Hello", " ", "World!"}
result := strings.Join(strSlice, "")
fmt.Println(result) // Output: Hello World!
var buffer bytes.Buffer
buffer.WriteString(str1)
buffer.WriteString(str2)
result := buffer.String()
fmt.Println(result) // Output: Hello, World!
}
chaîne multiligne
Utilisez les backticks ``` pour créer des chaînes multilignes.
package main
import "fmt"
func main() {
multiLineStr := `
This is a
multi-line
string.
`
fmt.Println(multiLineStr)
}
Itération de chaîne
Utilisez for range
une boucle pour parcourir chaque caractère de la chaîne.
package main
import "fmt"
func main() {
str := "Go语言"
for _, char := range str {
fmt.Printf("%c ", char) // Output: G o 语 言
}
}
Conversion entre chaîne et tableau d'octets
Dans le langage Go, les chaînes et les tableaux d'octets peuvent être convertis les uns vers les autres.
package main
import "fmt"
func main() {
str := "Hello"
bytes := []byte(str) // 转换为字节数组
strAgain := string(bytes) // 字节数组转换为字符串
fmt.Println("Bytes:", bytes) // Output: Bytes: [72 101 108 108 111]
fmt.Println("String Again:", strAgain) // Output: String Again: Hello
}
Comparaison de chaînes
Les chaînes peuvent être comparées à l’aide des opérateurs ==
et !=
. Bien entendu, il existe d'autres types de fonctions directement applicables : strings.Compare
des fonctions qui comparent deux chaînes et renvoient un entier basé sur le résultat de la comparaison.
Vous pouvez également utiliser des fonctions de comparaison personnalisées pour comparer des chaînes et définir une logique de comparaison en fonction de vos propres besoins.
package main
import (
"fmt"
"strings"
)
func customCompare(str1, str2 string) bool {
// 自定义比较逻辑
return str1 == str2
}
func main() {
str1 := "Hello"
str2 := "World"
if str1 == str2 {
fmt.Println("Strings are equal")
} else {
fmt.Println("Strings are not equal") // Output: Strings are not equal
}
result := strings.Compare(str1, str2)
if result == 0 {
fmt.Println("Strings are equal")
} else if result < 0 {
fmt.Println("str1 is less than str2")
} else {
fmt.Println("str1 is greater than str2") // Output: str1 is less than str2
}
if customCompare(str1, str2) {
fmt.Println("Strings are equal")
} else {
fmt.Println("Strings are not equal") // Output: Strings are not equal
}
}
Ces concepts et opérations de base peuvent vous aider à mieux comprendre et utiliser les chaînes dans le langage Go. Soyez conscient de l'immuabilité des chaînes, ainsi que des conversions et des comparaisons avec d'autres types de données.
Créer un exercice string_test.go
package string
import (
"strconv"
"strings"
"testing"
)
func TestString(t *testing.T) {
var s string
t.Log(s) //初始化为默认零值“”
s = "hello"
t.Log(len(s))
//s[1] = '3' //string是不可变的byte slice
//s = "\xE4\xB8\xA5" //可以存储任何二进制数据
s = "\xE4\xBA\xBB\xFF"
t.Log(s)
t.Log(len(s))
s = "中"
t.Log(len(s)) //是byte数
c := []rune(s)
t.Log(len(c))
// t.Log("rune size:", unsafe.Sizeof(c[0]))
t.Logf("中 unicode %x", c[0])
t.Logf("中 UTF8 %x", s)
}
func TestStringToRune(t *testing.T) {
s := "中华人民共和国"
for _, c := range s {
t.Logf("%[1]c %[1]x", c)
}
}
func TestStringFn(t *testing.T) {
s := "A,B,C"
parts := strings.Split(s, ",")
for _, part := range parts {
t.Log(part)
}
t.Log(strings.Join(parts, "-"))
}
func TestConv(t *testing.T) {
s := strconv.Itoa(10)
t.Log("str" + s)
if i, err := strconv.Atoi("10"); err == nil {
t.Log(10 + i)
}
}
Le contenu de chaque fonction de test est expliqué une par une ci-dessous :
-
func TestString(t *testing.T) { ... }
: Opérations de base pour tester les chaînes.- Déclarez une variable chaîne
s
qui renvoie sa valeur par défaut de zéro. - Attribuez la valeur de chaîne à "hello" et affichez la longueur de la chaîne.
- Essayer de modifier un caractère dans la chaîne entraînera une erreur car les chaînes sont immuables.
- Utilisez des chaînes pour stocker des données binaires et le codage Unicode.
- Utilisez une chaîne pour stocker un caractère chinois et afficher sa longueur.
- Convertissez la chaîne en
rune
type slice, affichez la longueur de la tranche et l'encodage Unicode et UTF-8 des caractères chinois.
- Déclarez une variable chaîne
-
Déclarez une chaîne contenant des caractères chinoisfunc TestStringToRune(t *testing.T) { ... }
: Testez la chaîne jusqu'àrune
la conversion.s
,range
convertissez la chaîne enrune
type via traversée et affichez-la. -
Déclarez une chaîne contenant des délimiteurs par virgulefunc TestStringFn(t *testing.T) { ... }
: Teste les fonctions liées aux chaînes.s
, utilisezstrings.Split
la fonction pour diviser la chaîne et afficher chaque partie. Utilisezstrings.Join
la fonction pour fusionner les parties divisées dans une nouvelle chaîne et la générer. -
func TestConv(t *testing.T) { ... }
: Teste la conversion des chaînes vers d’autres types.- Utilisé
strconv.Itoa
pour convertir un entier en chaîne. - Concaténez des chaînes et des entiers et affichez le résultat.
- Utilisez-le
strconv.Atoi
pour convertir des chaînes en entiers, effectuer des additions et gérer les conditions d'erreur.
- Utilisé
Ces fonctions de test démontrent diverses opérations sur les chaînes dans le langage Go, notamment la longueur des chaînes, le codage UTF-8, rune
la conversion de type, le fractionnement et la fusion de chaînes et la conversion de chaînes en d'autres types.
(4) Fonction
Créez le chapitre 4 dans le répertoire src. Dans le langage Go, une fonction est un bloc de code utilisé pour effectuer une tâche spécifique et peut être appelée plusieurs fois. Voici quelques points de connaissances importants sur les fonctions du langage Go :
1. Déclaration de fonction
Dans Go, une déclaration de fonction func
commence par le mot-clé suivi du nom de la fonction, de la liste des paramètres, de la valeur de retour et du corps de la fonction.
func functionName(parameters) returnType {
// 函数体
// 可以包含多个语句
return returnValue
}
2. Paramètres de fonction
Les fonctions peuvent avoir zéro ou plusieurs paramètres, constitués de noms de paramètres et de types de paramètres. Utilisez des virgules pour séparer les paramètres.
func greet(name string) {
fmt.Printf("Hello, %s!\n", name)
}
3. Plusieurs valeurs de retour
Les fonctions du langage Go peuvent renvoyer plusieurs valeurs. Les valeurs de retour sont mises entre parenthèses et séparées par des virgules.
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
4. Nommez la valeur de retour
Les fonctions peuvent déclarer des valeurs de retour nommées, et ces noms peuvent être utilisés directement pour l'affectation dans le corps de la fonction. Enfin, il n'est pas nécessaire d'utiliser explicitement return
le mot-clé.
func divide(a, b float64) (result float64, err error) {
if b == 0 {
err = errors.New("division by zero")
return
}
result = a / b
return
}
5. Nombre variable de paramètres
Le langage Go prend en charge l'utilisation de ...
la syntaxe pour représenter un nombre variable de paramètres. Ces paramètres sont utilisés comme tranches dans le corps de la fonction.
func sum(numbers ...int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}
6. Fonction comme paramètre
En langage Go, les fonctions peuvent être transmises en paramètres à d’autres fonctions.
func applyFunction(fn func(int, int) int, a, b int) int {
return fn(a, b)
}
func add(a, b int) int {
return a + b
}
func main() {
result := applyFunction(add, 3, 4)
fmt.Println(result) // Output: 7
}
7. Fonctions et fermetures anonymes
Le langage Go prend en charge les fonctions anonymes, également appelées fermetures. Ces fonctions peuvent être définies à l'intérieur d'autres fonctions et accéder aux variables à partir de la fonction externe.
func main() {
x := 5
fn := func() {
fmt.Println(x) // 闭包访问外部变量
}
fn() // Output: 5
}
8. déclaration différée
defer
Les instructions sont utilisées pour retarder l'exécution des fonctions, généralement pour effectuer certaines opérations de nettoyage avant le retour de la fonction.
func main() {
defer fmt.Println("World")
fmt.Println("Hello")
}
Voici quelques points de connaissances de base sur les fonctions du langage Go. Les fonctions jouent un rôle très important dans Go, utilisées pour organiser le code, implémenter la modularisation fonctionnelle et améliorer la maintenabilité du code.
Vérification 1 : vérification du cas d'utilisation de base
Créez une nouvelle base sous le chapitre 4 et créez la pratique func_basic_test.go
package basic
import (
"errors"
"fmt"
"testing"
)
// 普通函数
func greet(name string) {
fmt.Printf("Hello, %s!\n", name)
}
// 多返回值函数
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// 命名返回值函数
func divideNamed(a, b int) (result int, err error) {
if b == 0 {
err = errors.New("division by zero")
return
}
result = a / b
return
}
// 可变数量的参数函数
func sum(numbers ...int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}
// 函数作为参数
func applyFunction(fn func(int, int) int, a, b int) int {
return fn(a, b)
}
// 匿名函数和闭包
func closureExample() {
x := 5
fn := func() {
fmt.Println(x)
}
fn() // Output: 5
}
// defer语句
func deferExample() {
defer fmt.Println("World")
fmt.Println("Hello") // Output: Hello World
}
func TestBasic(t *testing.T) {
greet("Alice") // Output: Hello, Alice!
q, err := divide(10, 2)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Quotient:", q) // Output: Quotient: 5
}
qNamed, errNamed := divideNamed(10, 0)
if errNamed != nil {
fmt.Println("Error:", errNamed) // Output: Error: division by zero
} else {
fmt.Println("Quotient:", qNamed)
}
total := sum(1, 2, 3, 4, 5)
fmt.Println("Sum:", total) // Output: Sum: 15
addResult := applyFunction(func(a, b int) int {
return a + b
}, 3, 4)
fmt.Println("Addition:", addResult) // Output: Addition: 7
closureExample()
deferExample()
}
Vérification 2 : exemple de petite entreprise
Créez une nouvelle entreprise au chapitre 4 et créez l'exercice func_biz_test.go. Supposons que vous développiez un système de traitement de commande simple et que vous deviez calculer le prix total des marchandises dans la commande et appliquer des remises. Vous pouvez utiliser des fonctions pour gérer cette logique métier. Voici un exemple simple :
package biz
import (
"fmt"
"testing"
)
type Product struct {
Name string
Price float64
}
func calculateTotal(products []Product) float64 {
total := 0.0
for _, p := range products {
total += p.Price
}
return total
}
func applyDiscount(amount, discount float64) float64 {
return amount * (1 - discount)
}
func TestBiz(t *testing.T) {
products := []Product{
{Name: "Product A", Price: 10.0},
{Name: "Product B", Price: 20.0},
{Name: "Product C", Price: 30.0},
}
total := calculateTotal(products)
fmt.Printf("Total before discount: $%.2f\n", total)
discountedTotal := applyDiscount(total, 0.1)
fmt.Printf("Total after 10%% discount: $%.2f\n", discountedTotal)
}
(5) Programmation orientée objet
Créez le chapitre 5 dans le répertoire src. Le langage Go prend en charge la programmation orientée objet (POO), bien que par rapport à certains langages de programmation orientés objet traditionnels (tels que Java et C++), l'implémentation de Go puisse être légèrement différente. Dans le langage Go, il n'y a pas de concept de classes, mais des fonctionnalités orientées objet peuvent être obtenues grâce à des structures et des méthodes.
1. Définition de la structure
Dans le langage Go, une structure est un type de données personnalisé utilisé pour combiner des champs (variables membres) de différents types pour créer un nouveau type de données. Créez le répertoire struct et écrivez struct_test.go. Voici des exemples de définition, d'utilisation et de vérification de la structure :
package _struct
import (
"fmt"
"testing"
)
// 定义一个结构体
type Person struct {
FirstName string
LastName string
Age int
}
func TestStruct(t *testing.T) {
// 创建结构体实例并初始化字段
person1 := Person{
FirstName: "Alice",
LastName: "Smith",
Age: 25,
}
// 访问结构体字段
fmt.Println("First Name:", person1.FirstName) // Output: First Name: Alice
fmt.Println("Last Name:", person1.LastName) // Output: Last Name: Smith
fmt.Println("Age:", person1.Age) // Output: Age: 25
// 修改结构体字段的值
person1.Age = 26
fmt.Println("Updated Age:", person1.Age) // Output: Updated Age: 26
}
La définition d'une structure peut contenir plusieurs champs, et chaque champ peut être d'un type de données différent. Vous pouvez également imbriquer d'autres structures au sein de structures pour former des structures de données plus complexes. Exemple d'écriture de struct_cmpx_test.go :
package _struct
import (
"fmt"
"testing"
)
type Address struct {
Street string
City string
ZipCode string
}
type PersonNew struct {
FirstName string
LastName string
Age int
Address Address
}
func TestCmpxStruct(t *testing.T) {
person2 := PersonNew{
FirstName: "Bob",
LastName: "Johnson",
Age: 30,
Address: Address{
Street: "123 Main St",
City: "Cityville",
ZipCode: "12345",
},
}
fmt.Println("Full Name:", person2.FirstName, person2.LastName)
fmt.Println("Address:", person2.Address.Street, person2.Address.City, person2.Address.ZipCode)
}
2.Création et initialisation de l'instance
En langage Go, il existe de nombreuses façons de créer et d’initialiser des instances de structure. Créez le répertoire creatinit. Voici plusieurs méthodes courantes de création et d'initialisation d'instances. Le code spécifique est creatinit_test.go
- Initialisation littérale : vous pouvez utiliser des accolades
{}
pour initialiser les champs d'une instance de structure. - Initialisation partielle des champs : Si vous souhaitez initialiser uniquement certains champs de la structure, vous pouvez omettre d'autres champs.
- Initialisation à l'aide de noms de champs : les valeurs de champ peuvent être spécifiées en fonction des noms de champ sans initialisation séquentielle.
- Initialisation de la valeur par défaut : Les champs d'une structure peuvent être initialisés selon la valeur par défaut de son type.
- Utilisation de la nouvelle fonction : Vous pouvez utiliser
new
la fonction pour créer un pointeur vers une structure et renvoyer son pointeur. - Initialisation de la séquence de champs : les noms de champs peuvent éventuellement être omis, mais à ce stade, les valeurs doivent être attribuées dans l'ordre des champs de structure.
package creatinit
import (
"fmt"
"testing"
)
type Person struct {
FirstName string
LastName string
Age int
}
/**
* @author zhangyanfeng
* @description 字面量初始化
* @date 2023/8/26 15:09
**/
func TestCreateObj1(t *testing.T) {
person1 := Person{
FirstName: "Alice",
LastName: "Smith",
Age: 25,
}
fmt.Println(person1.FirstName, person1.LastName, person1.Age) // Output: Alice Smith 25
}
/**
* @author zhangyanfeng
* @description 部分字段初始化
* @date 2023/8/26 15:10
**/
func TestCreateObj2(t *testing.T) {
person2 := Person{
FirstName: "Bob",
Age: 30,
}
fmt.Println(person2.FirstName, person2.LastName, person2.Age) // Output: Bob 30
}
/**
* @author zhangyanfeng
* @description 使用字段名初始化
* @date 2023/8/26 15:12
**/
func TestCreateObj3(t *testing.T) {
person3 := Person{
LastName: "Johnson",
FirstName: "Chris",
Age: 28,
}
fmt.Println(person3.FirstName, person3.LastName, person3.Age) // Output: Chris Johnson 28
}
/**
* @author zhangyanfeng
* @description 默认值初始化
* @date 2023/8/26 15:13
**/
func TestCreateObj4(t *testing.T) {
var person4 Person
fmt.Println(person4.FirstName, person4.LastName, person4.Age) // Output: 0
}
/**
* @author zhangyanfeng
* @description 使用 new 函数
* @date 2023/8/26 15:14
**/
func TestCreateObj5(t *testing.T) {
person5 := new(Person)
person5.FirstName = "David"
person5.Age = 22
fmt.Println(person5.FirstName, person5.LastName, person5.Age) // Output: David 22
}
/**
* @author zhangyanfeng
* @description 字段顺序初始化
* @date 2023/8/26 15:24
**/
func TestCreateObj6(t *testing.T) {
// 使用字段顺序初始化
person := Person{"Alice", "Smith", 25}
fmt.Println(person.FirstName, person.LastName, person.Age) // Output: Alice Smith 25
}
3. Définition du comportement (méthode)
Dans Go, une méthode est une fonction associée à un type spécifique qui peut être appelée sur des instances de ce type. Les méthodes permettent de placer les opérations de type avec la définition de type, améliorant ainsi la lisibilité et la maintenabilité du code.
Créez un répertoire de méthodes pour la pratique du codage.Voici la définition, l'utilisation et l'analyse des méthodes du langage Go :
définition de la méthode
En langage Go, les méthodes sont définies en ajoutant des récepteurs aux fonctions. Le récepteur est un paramètre ordinaire, mais il est placé avant le nom de la méthode pour spécifier à quel type la méthode est associée. Créer method_define_test.go
package method
import (
"fmt"
"testing"
)
type Circle struct {
Radius float64
}
// 定义 Circle 类型的方法
func (c Circle) Area() float64 {
return 3.14159 * c.Radius * c.Radius
}
func TestMethodDef(t *testing.T) {
c := Circle{Radius: 5}
area := c.Area()
fmt.Printf("Circle area: %.2f\n", area) // Output: Circle area: 78.54
}
Dans l'exemple ci-dessus, nous avons défini une Circle
structure, puis défini une Area
méthode qui y est nommée. Cette méthode peut c.Area()
être appelée via , où c
est une Circle
instance de type.
appel de méthode
La syntaxe de l'appel de méthode est 实例.方法名()
, c'est-à-dire appeler la méthode via l'instance. Créer method_rpc_test.go
package method
import (
"fmt"
"testing"
)
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func TestMethonRpc(t *testing.T) {
rect := Rectangle{Width: 3, Height: 4}
area := rect.Area()
fmt.Printf("Rectangle area: %.2f\n", area) // Output: Rectangle area: 12.00
}
récepteur de pointeur
Le langage Go prend en charge l'utilisation de pointeurs comme récepteurs de méthodes, afin que les valeurs de champ de l'instance du récepteur puissent être modifiées. Créer method_rec_test.go
package method
import (
"fmt"
"testing"
)
type Counter struct {
Count int
}
func (c *Counter) Increment() {
c.Count++
}
func TestMethonRec(t *testing.T) {
counter := Counter{Count: 0}
counter.Increment()
fmt.Println("Count:", counter.Count) // Output: Count: 1
}
Dans l'exemple ci-dessus, Increment
la méthode utilise un récepteur pointeur afin que Count
la valeur du champ soit modifiée après l'appel de la méthode.
La différence entre les méthodes et les fonctions
La principale différence entre les méthodes et les fonctions est qu'une méthode est une fonction d'un type spécifique, qui est plus étroitement liée au type et peut accéder aux champs et autres méthodes du type. Les fonctions sont des blocs de code indépendants d’un type spécifique. Les méthodes sont souvent utilisées pour implémenter des types de comportement spécifiques, tandis que les fonctions peuvent être utilisées pour des opérations générales.
En définissant des méthodes, vous pouvez rendre le fonctionnement des types plus naturel et cohérent, améliorant ainsi la lisibilité et la modularité de votre code.
On peut expliquer ici que dans method_rpc_test.go, nous Rectangle
définissons une Area
méthode nommée pour la structure, qui peut être appelée par rect.Area()
. Les méthodes sont directement associées au type Rectangle
et ont accès aux Rectangle
champs de ( Width
et Height
).
Afin de comparer avec la méthode, nous créons une méthode dans le corps de méthode correspondant comme suit
// 定义一个函数来计算矩形的面积
func CalculateArea(r Rectangle) float64 {
return r.Width * r.Height
}
Dans cet exemple, nous définissons une CalculateArea
fonction appelée qui accepte un Rectangle
paramètre de type pour calculer l'aire d'un rectangle. La fonction est indépendante du Rectangle
type, elle ne peut donc pas accéder directement Rectangle
aux champs du .
Résumé : La différence entre les méthodes et les fonctions réside dans le fait que les méthodes sont des fonctions d'un type spécifique, qui sont plus étroitement liées au type et peuvent accéder aux champs et autres méthodes du type. Une fonction est un bloc de code indépendant d’un type spécifique et généralement utilisé pour des opérations courantes. Dans l'exemple ci-dessus, la méthode est associée au rectangle et peut accéder directement aux champs du rectangle ; la fonction est un processus de calcul indépendant et n'est directement associée à aucun type spécifique.
En utilisant des méthodes, nous pouvons rendre notre code plus naturel et cohérent, améliorant ainsi sa lisibilité et sa modularité, notamment lors de la mise en œuvre de types de comportement spécifiques.
4. Définition et utilisation de l'interface
Dans le langage Go, une interface est un moyen de définir un ensemble de méthodes. Elle spécifie la signature d'un ensemble de méthodes sans impliquer les détails d'implémentation. Grâce aux interfaces, le polymorphisme et le découplage du code peuvent être obtenus, permettant à des objets de différents types de fonctionner de manière cohérente.
Créez un répertoire d'interface pour les exercices ultérieurs. Voici une explication de l'interface du langage Go :
Définir l'interface
Une interface est un ensemble de méthodes, type
définies par des mots-clés. Une interface définit un ensemble de signatures de méthode mais ne contient pas d'implémentations de méthode. Créez interface_test.go pour la pratique du code
package interface_test
import (
"fmt"
"testing"
)
// 定义一个简单的接口
type Shape interface {
Area() float64
}
// 定义两个实现 Shape 接口的结构体
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14159 * c.Radius * c.Radius
}
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func TestInterface(t *testing.T) {
shapes := []Shape{
Circle{Radius: 2},
Rectangle{Width: 3, Height: 4},
}
for _, shape := range shapes {
fmt.Printf("Area of %T: %.2f\n", shape, shape.Area())
}
}
Dans l'exemple ci-dessus, nous définissons une Shape
interface nommée, qui nécessite la mise en œuvre d'une Area
méthode de calcul de l'aire de la figure. Ensuite, nous avons défini deux structures Circle
et Rectangle
, et implémenté Area
les méthodes respectivement. En utilisant des interfaces, nous pouvons placer différents types d'objets graphiques dans la même tranche, puis appeler leurs Area
méthodes via une boucle.
Implémentation de l'interface
Tout type est considéré comme implémentant l'interface tant qu'il implémente toutes les méthodes définies dans l'interface. L'implémentation de l'interface est implicite et ne nécessite pas de déclaration explicite. Tant que la signature de la méthode est la même que la signature de la méthode dans l'interface, le type est considéré comme implémentant l'interface.
Polymorphisme d'interface
En raison du polymorphisme des interfaces, nous pouvons considérer les objets qui implémentent l’interface comme l’interface elle-même. Dans l'exemple ci-dessus, shapes
différents types d'objets sont stockés dans la tranche, mais ils implémentent tous Shape
l'interface afin que les méthodes puissent être appelées de manière unifiée Area
.
En utilisant des interfaces, le code peut être abstrait et découplé, ce qui le rend plus flexible et extensible. Les interfaces sont largement utilisées dans le langage Go pour définir des comportements et des contraintes communes.
5. Expansion et réutilisation
Dans le langage Go, la manière d'étendre et de réutiliser le code est différente des langages orientés objet traditionnels (comme Java). Go encourage l'utilisation de fonctionnalités telles que la composition, les interfaces et les champs anonymes pour réaliser l'extension et la réutilisation du code, plutôt que via l'héritage de classe.
Créez le répertoire d'extension pour les exercices suivants. Voici une explication détaillée de l'extension et de la réutilisation dans le langage Go :
Combinaison et imbrication
La composition dans le langage Go vous permet d'imbriquer un type de structure dans un autre type de structure pour réaliser la réutilisation du code. Les structures imbriquées peuvent accéder à leurs membres directement via les noms de champs. Créer composition_test.go
package extend
import (
"fmt"
"testing"
)
type Engine struct {
Model string
}
type Car struct {
Engine
Brand string
}
func TestComposition(t *testing.T) {
car := Car{
Engine: Engine{Model: "V6"},
Brand: "Toyota",
}
fmt.Println("Car brand:", car.Brand)
fmt.Println("Car engine model:", car.Model) // 直接访问嵌套结构体的字段
}
Dans cet exemple, nous utilisons la composition pour créer Car
une structure contenant Engine
des structures imbriquées. Grâce à l'imbrication, Car
une structure peut accéder directement Engine
aux champs de la structure.
Implémentation de l'interface
Avec une interface, vous pouvez définir un ensemble de méthodes que différents types peuvent ensuite implémenter. Cela permet le polymorphisme et le découplage du code, de sorte que des objets de différents types puissent être exploités via la même interface. Créer interface_ext_test.go
package extend
import (
"fmt"
"math"
"testing"
)
// 定义 Shape 接口
type Shape interface {
Area() float64
Perimeter() float64
}
// 定义 Circle 结构体
type Circle struct {
Radius float64
}
// 实现 Circle 结构体的方法,以满足 Shape 接口
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
// 定义 Rectangle 结构体
type Rectangle struct {
Width float64
Height float64
}
// 实现 Rectangle 结构体的方法,以满足 Shape 接口
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
func TestInterfaceExt(t *testing.T) {
circle := Circle{Radius: 3}
rectangle := Rectangle{Width: 4, Height: 5}
shapes := []Shape{circle, rectangle}
for _, shape := range shapes {
fmt.Printf("Shape Type: %T\n", shape)
fmt.Printf("Area: %.2f\n", shape.Area())
fmt.Printf("Perimeter: %.2f\n", shape.Perimeter())
fmt.Println("------------")
}
}
Dans l'exemple ci-dessus, nous avons défini une Shape
interface appelée qui comporte deux méthodes Area()
et Perimeter()
, utilisées pour calculer respectivement l'aire et le périmètre d'une forme. Ensuite, nous avons implémenté respectivement les deux méthodes de Circle
et Rectangle
structure, afin qu’elles satisfassent Shape
l’interface.
En plaçant différents types d'instances de forme dans une tranche, nous pouvons appeler les méthodes et []Shape
de manière unifiée , obtenant ainsi le polymorphisme et le découplage du code. De cette façon, même si nous ajoutons de nouvelles formes ultérieurement, tant qu'elles implémentent les méthodes d'interface, elles peuvent être intégrées de manière transparente dans la calculatrice.Area()
Perimeter()
Shape
Réutilisation anonyme des champs et des méthodes
En utilisant des champs anonymes, une structure peut hériter des champs et méthodes d’une autre structure. Créer other_ext_test.go
package extend
import (
"fmt"
"testing"
)
type Animal struct {
Name string
}
func (a Animal) Speak() {
fmt.Println("Animal speaks")
}
type Dog struct {
Animal
Breed string
}
func TestOtherExt(t *testing.T) {
dog := Dog{
Animal: Animal{Name: "Buddy"},
Breed: "Golden Retriever",
}
fmt.Println("Dog name:", dog.Name)
dog.Speak() // 继承了 Animal 的 Speak 方法
}
Dans l'exemple ci-dessus, Dog
la structure imbrique Animal
une structure, héritant ainsi Animal
des champs et des méthodes de .
De cette manière, vous pouvez étendre et réutiliser le code dans le langage Go. Bien que Go ne mette pas autant l'accent sur l'héritage de classe que les langages orientés objet traditionnels, grâce à des fonctionnalités telles que la composition, les interfaces et les champs anonymes, vous pouvez toujours obtenir des effets similaires, rendant le code plus flexible, plus lisible et maintenant un faible couplage.
6. Interfaces et assertions vides
Les interfaces et assertions vides sont des concepts importants dans le langage Go pour gérer les types non définis et les conversions de types.
Créez le répertoire emptyassert pour les exercices suivants. Voici un résumé de l'apprentissage des interfaces et des assertions vides :
Interface vide
L'interface vide est l'interface la plus basique du langage Go. Elle ne contient aucune déclaration de méthode. Par conséquent, l’interface vide peut être utilisée pour représenter n’importe quel type de valeur. La méthode de déclaration d'une interface vide est interface{}
.
L'interface vide est principalement utilisée dans les scénarios dans lesquels vous devez gérer des types non définis. En utilisant l'interface vide, tout type de valeur peut être accepté et stocké, comme le typage dynamique dans d'autres langages de programmation. Cependant, il convient de noter que l'utilisation d'une interface vide peut entraîner une sécurité de type réduite car le type concret ne peut pas être vérifié au moment de la compilation.
Affirmation
L'assertion est un mécanisme pour récupérer un type concret dans une interface vide, qui nous permet de vérifier le type réel d'une valeur dans une interface vide au moment de l'exécution et de la convertir dans le type correspondant. La syntaxe des assertions est value.(Type)
où value
est la valeur de l'interface et Type
est le type concret à affirmer.
Créez emptyassert_test.go pour vérification :
package emptyassert
import (
"fmt"
"testing"
)
func DoSomething(p interface{}) {
switch v := p.(type) {
case int:
fmt.Println("Integer", v)
case string:
fmt.Println("String", v)
default:
fmt.Println("Unknow Type")
}
}
func TestEmptyInterfaceAssertion(t *testing.T) {
DoSomething(10)
DoSomething("10")
}
func TestEmptyAssert(t *testing.T) {
var x interface{} = "hello"
str, ok := x.(string)
if ok {
fmt.Println("String:", str)
} else {
fmt.Println("Not a string")
}
}
Le contenu de chaque fonction de test est expliqué une par une ci-dessous :
-
func DoSomething(p interface{}) { ... }
: définit une fonctionDoSomething
qui accepte un paramètre d'interface videp
, puis exécute des assertions de type basées sur le type réel de la valeur d'interface et génère différentes informations basées sur différents types. -
Appelezfunc TestEmptyInterfaceAssertion(t *testing.T) { ... }
: Testez l'opération d'assertion de l'interface vide.DoSomething(10)
, transmettez l'entier10
à la fonction et la fonction génère les informations de type entier basées sur l'assertion de type. AppelezDoSomething("10")
, transmettez la chaîne"10"
à la fonction et la fonction génère les informations de type de chaîne en fonction de l'assertion de type. -
Déclarez une variable d'interface videfunc TestEmptyAssert(t *testing.T) { ... }
: Testez l'opération d'assertion de type de l'interface vide.x
et attribuez"hello"
-lui la valeur de chaîne. Utilisez l'assertion de typex.(string)
pour déterminerx
s'il s'agit d'un type de chaîne. Si tel est le cas, affectez-la à une variablestr
et affichez la valeur de la chaîne ; sinon, affichez "Pas une chaîne".
Ces fonctions de test montrent l'opération d'assertion de l'interface vide dans le langage Go. Grâce aux assertions de type, le type spécifique dans l'interface vide peut être déterminé et les opérations correspondantes peuvent être effectuées.
Résumé : Les interfaces et assertions vides sont des outils puissants dans le langage Go pour gérer les types non définis et les conversions de types. Les interfaces vides permettent de stocker n'importe quel type de valeur, tandis que les assertions nous permettent de vérifier et de convertir le type réel de valeurs d'interface au moment de l'exécution. L'utilisation de ces mécanismes permet d'obtenir un code plus flexible et plus polyvalent lorsque vous devez gérer différents types de valeurs. Mais lorsque vous utilisez des interfaces et des assertions vides, veillez à maintenir la sécurité des types et à effectuer une gestion appropriée des erreurs.
Bonnes pratiques de l’interface 7.GO
En langage Go, les bonnes pratiques d'utilisation des interfaces peuvent améliorer la lisibilité, la maintenabilité et la flexibilité du code. Voici quelques bonnes pratiques pour les interfaces Go :
- Petites interfaces et grandes interfaces : essayez de concevoir une petite interface. Une interface ne doit contenir qu'un petit nombre de méthodes au lieu de concevoir une interface grande et complète. Cela évite une charge inutile lors de la mise en œuvre de l’interface et rend l’interface plus générale.
- Concevoir des interfaces basées sur des scénarios d'utilisation : lors de la conception d'interfaces, vous devez envisager des scénarios d'utilisation plutôt que de partir d'implémentations spécifiques. Réfléchissez à la manière dont les interfaces sont utilisées dans votre application et aux méthodes que l'interface doit fournir pour satisfaire ces cas d'utilisation.
- Utilisez un nom approprié : utilisez un nom clair pour les interfaces et les méthodes afin qu'elles traduisent leur objectif et leurs fonctionnalités. La dénomination doit être lisible et expressive afin que les autres développeurs puissent facilement comprendre le but de l'interface.
- Évitez les interfaces inutiles : ne créez pas d'interface pour chaque type, utilisez les interfaces uniquement lorsqu'il existe effectivement un comportement et des fonctionnalités partagés entre plusieurs types. N'abusez pas des interfaces pour éviter une complexité inutile.
- Utiliser des interfaces comme paramètres de fonction et valeurs de retour : L'utilisation d'interfaces comme paramètres de fonction et valeurs de retour peut rendre la fonction plus polyvalente, permettant de transmettre différents types de paramètres et de renvoyer différents types de résultats. Cela améliore la réutilisabilité et l’évolutivité du code.
- Commentaires et documentation : fournissez une documentation et des commentaires clairs pour l'interface, expliquant le but de l'interface, la fonctionnalité des méthodes et le comportement attendu. Cela peut aider d'autres développeurs à mieux comprendre comment l'interface est utilisée.
- Utiliser une conception axée sur les cas : lors de la conception d'une interface, vous pouvez commencer du point de vue de l'utilisation, considérer d'abord la manière dont l'interface est appelée dans des scénarios réels, puis concevoir les méthodes et les signatures de l'interface.
- Séparer l'implémentation et la définition de l'interface : séparer l'implémentation de l'interface de la définition de l'interface peut rendre l'implémentation plus flexible et de nouveaux types peuvent être implémentés sans modifier la définition de l'interface.
- Implémentation par défaut : dans la définition de l'interface, vous pouvez fournir des implémentations par défaut pour certaines méthodes, réduisant ainsi la charge de travail lors de l'implémentation de l'interface. Ceci est utile pour les méthodes facultatives ou le comportement par défaut de certaines méthodes.
- Utilisez l'interface vide avec prudence : utilisez l'interface vide (
interface{}
) avec prudence car elle réduit la sécurité du type. N'utilisez des interfaces vides que lorsque vous avez vraiment besoin de gérer des valeurs de différents types et faites attention aux assertions de type et à la gestion des erreurs.
En bref, lors de la conception et de l'utilisation des interfaces, vous devez choisir la solution appropriée en fonction des besoins réels et des caractéristiques du projet. Suivre les bonnes pratiques ci-dessus peut aider à écrire du code Go plus maintenable, évolutif et lisible.
(6) Écrivez un bon mécanisme d'erreur
Créez le chapitre 6 dans le répertoire src. Le mécanisme de gestion des erreurs dans le langage Go est implémenté en renvoyant les valeurs d'erreur au lieu d'utiliser des exceptions. Ce mécanisme de gestion des erreurs est très clair et contrôlable, permettant aux développeurs de gérer avec précision diverses situations d'erreur.
1. Introduction à l'utilisation de base
Créez le répertoire de base et écrivez basic_error_test.go
Type d'erreur
Dans Go, les erreurs sont représentées comme un error
type qui implémente l'interface. error
L'interface n'a qu'une seule méthode, c'est-à-dire Error() string
qu'elle renvoie une chaîne décrivant l'erreur.
type error interface {
Error() string
}
Renvoyer la valeur d'erreur
Lorsqu'une fonction rencontre une condition d'erreur, elle renvoie généralement une valeur d'erreur. Cette valeur d'erreur peut être un type personnalisé qui implémente error
l'interface, ou il peut s'agir d'un type d'erreur prédéfini dans la bibliothèque standard Go, comme errors.New()
une erreur créée par .
vérification des erreurs
L'appelant doit généralement vérifier explicitement l'erreur renvoyée par la fonction pour déterminer si une erreur s'est produite. Ceci peut être réalisé en utilisant l'instruction après avoir appelé la fonction if
.
Les deux ci-dessus écrivent directement le code comme suit :
package basic
import (
"errors"
"fmt"
"testing"
)
var LessThanTwoError = errors.New("n should be not less than 2")
var LargerThenHundredError = errors.New("n should be not larger than 100")
func GetFibonacci(n int) ([]int, error) {
if n < 2 {
return nil, LessThanTwoError
}
if n > 100 {
return nil, LargerThenHundredError
}
fibList := []int{1, 1}
for i := 2; /*短变量声明 := */ i < n; i++ {
fibList = append(fibList, fibList[i-2]+fibList[i-1])
}
return fibList, nil
}
func TestGetFibonacci(t *testing.T) {
if v, err := GetFibonacci(1); err != nil {
if err == LessThanTwoError {
fmt.Println("It is less.")
}
t.Error(err)
} else {
t.Log(v)
}
}
2.Chaîne d'erreurs
Créez le répertoire de la chaîne et écrivez error_chain_test.go
Dans certains cas, les erreurs peuvent contenir des informations supplémentaires permettant de mieux comprendre la cause de l'erreur. fmt.Errorf()
Des erreurs contenant des informations supplémentaires peuvent être créées à l'aide de la fonction.
Supposons que nous construisions une bibliothèque pour les opérations sur les fichiers, qui contient des fonctions de lecture et d'écriture de fichiers. Parfois, diverses erreurs peuvent survenir lors de la lecture ou de l'écriture d'un fichier, telles que l'inexistence d'un fichier, des problèmes d'autorisation, etc. Nous aimerions pouvoir fournir plus d'informations contextuelles sur l'erreur.
package chain
import (
"errors"
"fmt"
"testing"
)
// 自定义文件操作错误类型
type FileError struct {
Op string // 操作类型("read" 或 "write")
Path string // 文件路径
Err error // 原始错误
}
// 实现 error 接口的 Error() 方法
func (e *FileError) Error() string {
return fmt.Sprintf("%s %s: %v", e.Op, e.Path, e.Err)
}
// 模拟文件读取操作
func ReadFile(path string) ([]byte, error) {
// 模拟文件不存在的情况
return nil, &FileError{Op: "read", Path: path, Err: errors.New("file not found")}
}
func TestChain(t *testing.T) {
filePath := "/path/to/nonexistent/file.txt"
_, err := ReadFile(filePath)
if err != nil {
fmt.Println("Error:", err)
// 在这里,我们可以检查错误类型,提取上下文信息
if fileErr, ok := err.(*FileError); ok {
fmt.Printf("Operation: %s\n", fileErr.Op)
fmt.Printf("File Path: %s\n", fileErr.Path)
fmt.Printf("Original Error: %v\n", fileErr.Err)
}
}
}
Voici une explication du code :
-
FileError
Structure : définit un type d'erreur personnaliséFileError
, contenant les champs suivants :Op
: Type d'opération, indiquant s'il s'agit d'une opération de lecture ("read") ou d'écriture ("write").Path
: Chemin du fichier, indiquant quel fichier est impliqué.Err
: Erreur d'origine, contenant des informations sur l'erreur sous-jacente.
-
Error()
Méthode :FileError
implémente la méthodeerror
de l'interface pour la structureError()
, qui est utilisée pour générer une description textuelle de l'erreur. -
ReadFile()
Fonction : simule l’opération de lecture de fichiers. Dans cet exemple, la fonction renvoie uneFileError
erreur de type , simulant la situation où le fichier n'existe pas. -
TestChain()
Fonction de test : montre comment utiliser les types d'erreur personnalisés dans la gestion des erreurs.- Un chemin de fichier est défini
filePath
etReadFile(filePath)
la fonction est appelée pour simuler une opération de lecture de fichier. - Recherchez les erreurs et affichez un message d'erreur si une erreur se produit.
- Dans la gestion des erreurs, les assertions de type sont utilisées pour vérifier si l'erreur est de
*FileError
type et, si tel est le cas, des informations plus contextuelles peuvent être extraites, telles que le type d'opération, le chemin du fichier et les informations d'erreur d'origine.
- Un chemin de fichier est défini
3. Panique et récupération
Dans le langage Go, panic
et recover
sont des mécanismes de gestion des situations d'exception, mais ils doivent être utilisés avec prudence et uniquement dans des situations spécifiques, et non en remplacement des mécanismes normaux de gestion des erreurs. Voici une explication détaillée de panic
et recover
, avec un cas d'utilisation spécifique :
panique
Créez panic
un répertoire et écrivez panic
_test.go. panic
est une fonction intégrée utilisée pour provoquer une panique à l'exécution. Lorsque le programme rencontre une erreur fatale qui ne peut pas poursuivre l'exécution, vous pouvez utiliser panic
pour interrompre le flux normal du programme. Mais toute utilisation abusive doit être évitée panic
car elle peut provoquer le blocage du programme sans fournir de message d'erreur convivial. Généralement panic
utilisé pour indiquer une erreur irrécupérable dans un programme, telle qu'un index de tranche hors limites.
package panic
import (
"fmt"
"testing"
)
func TestPanic(t *testing.T) {
arr := []int{1, 2, 3}
index := 4
if index >= len(arr) {
panic("Index out of range")
}
element := arr[index]
fmt.Println("Element:", element)
}
Dans l'exemple ci-dessus, si l'index index
dépasse arr
la plage de la tranche, il sera déclenché panic
, provoquant le crash du programme. Dans ce cas, panic
il est utilisé pour indiquer une erreur irrécupérable dans le programme.
récupérer
Créez recover
un répertoire et écrivez recover
_test.go. recover
Également une fonction intégrée pour récupérer des panic
paniques d'exécution causées par . Il ne peut defer
être utilisé qu'à l'intérieur d'une fonction de retard ( ) et est utilisé pour restaurer le flux de contrôle du programme, pas pour gérer les erreurs. En règle générale, après une occurrence panic
, recover
vous pouvez l'intercepter dans une fonction de retard panic
, effectuer un nettoyage, puis le programme continue son exécution.
package recover
import (
"fmt"
"testing"
)
func cleanup() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}
func TestRecover(t *testing.T) {
defer cleanup()
panic("Something went wrong")
fmt.Println("This line will not be executed")
}
Dans l'exemple ci-dessus, panic
après le déclenchement, cleanup
le contenu de la fonction recover
est capturé panic
et un message d'erreur est imprimé. Le programme continuera alors à s'exécuter, mais il convient de noter que le flux de contrôle ne reviendra pas à panic
l'endroit où il a été déclenché, fmt.Println
il ne sera donc pas exécuté.
En résumé, panic
et recover
doit être utilisé avec prudence et uniquement dans des cas particuliers, tels que des erreurs irrécupérables ou des opérations de nettoyage dans les fonctions différées. Dans la plupart des cas, les valeurs de retour d'erreur doivent être utilisées de préférence à la gestion des exceptions, car cette approche est plus sûre, plus contrôlable et fournit de meilleures informations sur les erreurs et une meilleure gestion des erreurs. panic
Vous ne devez envisager d'utiliser and que dans des circonstances spécifiques, par exemple lorsque vous rencontrez une erreur irrécupérable recover
.
4. Types d'erreurs personnalisés
Créez define
un répertoire et écrivez error_define
_test.go.
Dans Go, vous pouvez définir vos propres types d'erreurs selon vos besoins, il vous suffit de répondre error
aux exigences de l'interface. Cela vous permet de créer des types d'erreurs plus descriptifs et contextuels.
Dans Go, les types d’erreurs personnalisés constituent un moyen puissant de créer des erreurs plus descriptives et contextuelles afin de fournir de meilleures informations sur les erreurs. Les types d'erreur personnalisés doivent répondre error
aux exigences de l'interface, c'est-à-dire implémenter Error() string
la méthode. Voici un exemple montrant comment personnaliser un type d'erreur et valider son cas d'utilisation :
package define
import (
"fmt"
"testing"
"time"
)
// 自定义错误类型
type TimeoutError struct {
Operation string // 操作名称
Timeout time.Time // 超时时间
}
// 实现 error 接口的 Error() 方法
func (e TimeoutError) Error() string {
return fmt.Sprintf("Timeout error during %s operation. Timeout at %s", e.Operation, e.Timeout.Format("2006-01-02 15:04:05"))
}
// 模拟执行某个操作,可能会超时
func PerformOperation() error {
// 模拟操作超时
timeout := time.Now().Add(5 * time.Second)
if time.Now().After(timeout) {
return TimeoutError{Operation: "PerformOperation", Timeout: timeout}
}
// 模拟操作成功
return nil
}
func TestDefineError(t *testing.T) {
err := PerformOperation()
if err != nil {
// 检查错误类型并打印错误信息
if timeoutErr, ok := err.(TimeoutError); ok {
fmt.Println("Error Type:", timeoutErr.Operation)
fmt.Println("Timeout At:", timeoutErr.Timeout)
}
fmt.Println("Error:", err)
} else {
fmt.Println("Operation completed successfully.")
}
}
Voici une explication du code :
-
TimeoutError
Structure : définit un type d'erreur personnaliséTimeoutError
, contenant les champs suivants :Operation
: Nom de l'opération, indiquant quelle opération a expiré.Timeout
: Délai d'expiration, indiquant le moment où l'opération expire.
-
Error()
Méthode :TimeoutError
implémente la méthodeerror
de l'interface pour la structureError()
, qui est utilisée pour générer une description textuelle de l'erreur. -
PerformOperation()
Fonction : simule l'exécution d'une opération et peut expirer. Dans cet exemple, si l'heure actuelle dépasse le délai d'attente, uneTimeoutError
erreur de type est renvoyée. -
TestDefineError()
Fonction de test : montre comment utiliser les types d'erreur personnalisés dans la gestion des erreurs.- Appelez
PerformOperation()
la fonction pour simuler l'opération et vérifiez si une erreur s'est produite. - Si une erreur se produit, vérifiez d'abord si le type d'erreur est
TimeoutError
, si tel est le cas, extrayez l'opération de délai d'attente et le délai d'expiration, et affichez les informations pertinentes. - Enfin, qu'une erreur se soit produite ou non, un message d'erreur ou un message de réussite est généré.
- Appelez
Cet exemple montre comment personnaliser les types d'erreurs et comment exploiter ces types d'erreurs personnalisés dans la gestion des erreurs pour fournir des informations plus contextuelles, rendant la gestion des erreurs plus informative et flexible. Ici, TimeoutError
des informations supplémentaires sur les opérations d’expiration et les périodes d’expiration sont fournies.