Résumé de l'apprentissage rapide du langage GO

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

  1. 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.

  2. 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.

  3. 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 :

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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 :

  1. 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
  2. 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.

  3. 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
  4. 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 :

  1. 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.

  2. 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.

  3. func main() { ... }: C'est la fonction d'entrée du programme, elle sera appelée en premier lors de l'exécution du programme.

  4. 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.Argsest une tranche de chaîne qui contient tous les paramètres de ligne de commande. Le premier paramètre est le nom du programme.

  5. fmt.Println("Hello World", os.Args[1]): Si des paramètres sont transmis au programme, cette ligne de code sera exécutée. Il utilise fmt.Printlnla fonction pour imprimer un message os.Args[1]constitué de la chaîne "Hello World" et os.Args[1]représentant le premier argument passé au programme.

En résumé, ce code couvre les points de connaissances suivants :

  1. Importation de packages et utilisation de la bibliothèque standard : Importez les packages "fmt" et "os" via importle mot-clé, puis utilisez les fonctions et types fournis par ces packages dans votre code.

  2. Obtention des paramètres de ligne de commande : utilisez os.Argspour obtenir les paramètres de ligne de commande.

  3. Instructions conditionnelles : utilisez ifdes instructions conditionnelles pour déterminer si des paramètres de ligne de commande sont transmis au programme.

  4. 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.

  5. Sortie formatée : utilisez fmt.Printlnla 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 :

  1. Déclaration de variable : utilisez vardes mots-clés pour déclarer une variable, par exemple : var x int.
  2. 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.
  3. Affectation de variables : utilisez des opérateurs d'affectation =pour attribuer des valeurs aux variables, par exemple : x = 10.
  4. Déclaration multi-variable : Plusieurs variables peuvent être déclarées en même temps, par exemple : var a, b, c int.
  5. Initialisation des variables : Les variables peuvent être initialisées lorsqu'elles sont déclarées, par exemple : var name string = "John".
  6. 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.
  7. 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 :

  1. package variables: déclare un package nommé "variables", qui est un nom de package utilisé pour les tests.

  2. import "testing": Importation du package "testing" du cadre de test du langage Go pour l'écriture et l'exécution de fonctions de test.

  3. 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.

    • aA l'intérieur de la fonction de test, deux variables entières et sont déclarées bet 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'impression apour 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 bvaleur de dans le journal de test et met à jour les valeurs de aet bpour générer le nombre suivant.
  4. 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 aet sont déclarées bet 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, acette ligne de code pour implémenter l'échange de aet b, 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.

2.Constante

Prérequis : Créer une constante dans le répertoire chapitre 2. Le résumé d'apprentissage est le suivant :

  1. Déclaration de constante : utilisez constdes mots-clés pour déclarer une constante, par exemple : const pi = 3.14159.
  2. 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.
  3. 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
        // ...
    )
  4. Spécification du type : Le type de la constante peut également être spécifié, par exemple : const speed int = 300000.
  5. Expressions constantes : les constantes peuvent être évaluées à l'aide d'expressions, par exemple : const secondsInHour = 60 * 60.
  6. 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 = 5un 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 :

  1. package constant: déclare un package nommé "constant", qui est un nom de package utilisé pour les tests.

  2. import "testing": Importation du package "testing" du cadre de test du langage Go pour l'écriture et l'exécution de fonctions de test.

  3. const (...): Deux blocs constants sont définis.

    • Dans le premier bloc de constantes, un générateur de constantes est utilisé iotapour définir une série de constantes commençant à 1 et croissante. Dans cet exemple, Mondayse voit attribuer la valeur 1, Tuesdayse voit attribuer la valeur 2 et Wednesdayse voit attribuer la valeur 3. iotaIl 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é iotapour définir une série de constantes décalées vers la gauche au niveau du bit. Dans cet exemple, Readablese voit attribuer la valeur 1, Writablese voit attribuer la valeur 2 (10 en binaire) et Executablese 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.

  4. 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 constantes Mondayet Tuesdaypour le journal de test.
  5. 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 aet 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 asi une variable possède les propriétés Readable, Writableet Executable. Par exemple, a&Readable == Readablel'expression vérifie asi la représentation binaire de contient Readablele bit d'indicateur.
    • Utilisez pour t.Log()imprimer les résultats des trois expressions dans le journal de test.

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 :

  1. 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.int16int32int64uintuint8uint16uint32uint64

  2. Types à virgule flottante : le langage Go propose float32deux float64types à virgule flottante, correspondant respectivement aux nombres à virgule flottante simple précision et double précision.

  3. Types complexes : Le langage Go propose complex64deux complex128types complexes, correspondant à des nombres complexes composés de deux nombres à virgule flottante.

  4. 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.

  5. 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 .`

  6. Type de caractère (Rune Type) : le type de caractère runeest 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'.

  7. 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.

  8. 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.

  9. 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.

  10. 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.

  11. 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.

  12. 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.

  13. 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.

  14. 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 :

  1. type Person struct { ... }: définit un type de structure pour Personreprésenter les informations d'une personne, notamment les champs et .FirstNameLastNameAge

  2. type Shape interface { ... }: Définit un type d'interface Shapequi nécessite l'implémentation d'une méthode Area()qui renvoie un float64type.

  3. type Circle struct { ... }: Définit un type de structure Circlereprésentant le rayon d'un cercle.

    func (c Circle) Area() float64 { ... }: CircleImplémente la méthode Shapede l'interface pour le type Area(), qui est utilisée pour calculer l'aire d'un cercle.
  4. func add(a, b int) int { ... }: Définit une fonction addqui effectue des opérations d'addition d'entiers.

  5. func subtract(a, b int) int { ... }: Définit une fonction subtractqui effectue une soustraction entière.

  6. type Operation func(int, int) int: définit un type de fonction Operationqui accepte deux paramètres entiers et renvoie un résultat entier.

  7. 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, Circlel’attribution de types aux Shapevariables 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 Operationvariables 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 :

  1. 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 intvers float64est sûre, mais une conversion float64de vers intpeut entraîner la troncature de la partie décimale.

  2. 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).

  3. Conversion entre types incompatibles : Le compilateur ne convertit pas automatiquement les types incompatibles. Par exemple, vous ne pouvez pas convertir directement un stringtype en inttype.

  4. 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 :

  1. const (...): Définit trois constantes Readable, somme, Writableet Executableutilise des opérations de déplacement pour générer différentes valeurs.

  2. 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 à.
  3. func TestCompareArray(t *testing.T) { ... }: Définit une fonction de test "TestCompareArray" pour tester la comparaison de tableaux.

    • aDeux tableaux d'entiers et sont déclarés b, ainsi qu'un autre tableau ddont le contenu du tableau aet du tableau dest identique.
    • Utilisez l'opérateur de comparaison ==pour vérifier si les tableaux aet bsont égaux et si les tableaux aet dsont égaux.
  4. 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 aet initialisez-la à 7, c'est-à-dire la représentation binaire 0111.
    • Utilisez l'opération d'effacement de bits &^pour effacer les bits et adans .ReadableExecutable
    • Utilisez l’opération ET au niveau du bit &pour vérifier asi possède les propriétés Readable, Writableet Executable.

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 :

  1. 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.
  2. 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 :

ifdéclaration

ifLes 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 {
    // 代码块
}

switchdéclaration

switchLes 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 switchpeut automatiquement faire correspondre la première branche qui satisfait à la condition sans utiliser breakd'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 :

  1. func TestConditionIf(t *testing.T) { ... }: Testez ifl’utilisation des instructions.

    Selon différentes circonstances d'âge, on juge s'il s'agit d'un mineur, d'un adulte ou d'une personne âgée à travers ifles branches else ifet .else
  2. func TestConditionSwitch(t *testing.T) { ... }: Testez switchl’utilisation des instructions. Selon dayOfWeekla valeur de , utilisez switchl'instruction pour afficher le jour de la semaine correspondant.

  3. func TestSwitchMultiCase(t *testing.T) { ... }: Testez le cas où switchl'instruction a plusieurs casevaleurs. Utilisez switchl'instruction pour déterminer la parité de chaque nombre et afficher les informations correspondantes.

  4. func TestSwitchCaseCondition(t *testing.T) { ... }: Testez switchl'expression conditionnelle dans l'instruction. Utilisez switchl'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 casele traitement de plusieurs valeurs, ainsi que switchl'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 :

forfaire du vélo

forLes 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 posteffectuez des opérations d'incrémentation ou de décrémentation dans l'instruction.

forForme simplifiée de boucle

Les boucles de langage Go peuvent également être simplifiées à la seule partie condition de boucle, similaire aux boucles fordans d'autres langages :while

for condition {
    // 代码块
}

rangefaire du vélo

rangeLes 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 :

  1. func TestLoopFor(t *testing.T) { ... }: Testez forles boucles de base. À l’aide ford’une boucle, parcourez de 1 à 5 pour afficher le nombre d’itérations de boucle.

  2. func TestLoopForBasic(t *testing.T) { ... }: Testez une boucle sans instructions d'initialisation for. À l'aide ford'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.

  3. func TestLoopForRange(t *testing.T) { ... }:Test en utilisant for rangele découpage itératif. Définissez une tranche entière numbers, utilisez for rangeune boucle pour parcourir chaque élément de la tranche et affichez l'index et la valeur de l'élément.

  4. func TestLoopForUnLimit(t *testing.T) { ... }: Testez des boucles et breakdes instructions infinies. Utilisez une boucle et breakune instruction infinies pour déterminer s'il faut terminer la boucle à l'intérieur du corps de la boucle et iquitter la boucle lorsqu'elle est supérieure à 5.

Ces fonctions de test démontrent forl'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 :

  1. func TestJumpBreak(t *testing.T) { ... }: Testez breakl’utilisation des instructions. Utilisez forune boucle pour parcourir de 1 à 5, mais ilorsque la variable d'itération est égale à 3, utilisez breakl'instruction pour terminer la boucle.

  2. func TestJumpContinue(t *testing.T) { ... }: Testez continuel’utilisation des instructions. Utilisez forla boucle pour itérer de 1 à 5, mais lorsque la variable d'itération iest égale à 3, utilisez continuel'instruction pour ignorer cette itération et passer à l'itération suivante.

  3. func TestJumpGoto(t *testing.T) { ... }: Testez gotol’utilisation des instructions. Une boucle infinie est implémentée à l'aide gotode l'instruction, qui utilise des étiquettes startet goto startpermet 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 lorsque iest 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 continueet 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 :

  1. func TestArrayInit(t *testing.T) { ... }: Teste l'initialisation du tableau.

    • Utilisez différentes manières d'initialiser les tableaux arr, arr1et arr3.
    • Modifiez arr1le deuxième élément de 5.
    • Utilisez pour t.Log()afficher les valeurs des éléments et le contenu de différents tableaux.
  2. func TestArrayTravel(t *testing.T) { ... }: Test du parcours du tableau.

    • Utilisez forpour parcourir le tableau arr3et afficher la valeur de chaque élément séparément.
    • Utilisez for rangepour parcourir le tableau arr3et également afficher la valeur de chaque élément.
  3. func TestArraySection(t *testing.T) { ... }: Testez l'utilisation du découpage de tableau.

    • Crée une tranche de tableau arr3_secbasée sur l'ensemble du tableau arr3.
    • Utilisez le contenu t.Log()des tranches du tableau de sortie arr3_sec.

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 makela fonction pour créer 3une tranche avec une capacité initiale de sliceet afficher le contenu initial de la tranche.
  • Utilisez une fonction pour ajouter des éléments appendà une tranche .slice
  • Utilisez copyla fonction pour copier une tranche slicevers une nouvelle tranche copySlice.
  • Utilisez Slice numberspour effectuer des coupes en tranches et créer des sous-tranches subSlice.
  • subSliceLe premier élément de modification est 100, affiche la tranche modifiée et la tranche originale, ainsi que la tranche copiée.
  • Utilisez for rangeune boucle pour parcourir la slice slice, 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 :

  1. func TestBasic(t *testing.T) { ... }: Opérations de base pour tester le mappage.

    • Déclarez et initialisez la carte agespour 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 deletela fonction pour supprimer les paires clé-valeur.
    • Utilisez age, existspour obtenir la valeur et vérifier si la clé existe.
    • Utilisez for rangeune boucle pour parcourir la carte et afficher les informations pour chaque paire clé-valeur.
  2. type Student struct { ... }: Définit une Studentstructure nommée pour stocker les informations sur les étudiants.

  3. func TestComplex(t *testing.T) { ... }: Tester les opérations de mappage contenant des valeurs complexes.

    • Déclarez et initialisez deux mappages, studentScoresutilisés pour stocker les scores et studentInfoles 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-le studentScores["Alice"]pour obtenir les scores des étudiants.
    • Utilisez for rangeune boucle pour parcourir la carte et afficher les informations et le score de chaque élève.

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.Joinla 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 rangeune 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.Comparedes 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 :

  1. func TestString(t *testing.T) { ... }: Opérations de base pour tester les chaînes.

    • Déclarez une variable chaîne squi 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 runetype slice, affichez la longueur de la tranche et l'encodage Unicode et UTF-8 des caractères chinois.
  2. func TestStringToRune(t *testing.T) { ... }: Testez la chaîne jusqu'à runela conversion.

    Déclarez une chaîne contenant des caractères chinois s, rangeconvertissez la chaîne en runetype via traversée et affichez-la.
  3. func TestStringFn(t *testing.T) { ... }: Teste les fonctions liées aux chaînes.

    Déclarez une chaîne contenant des délimiteurs par virgule s, utilisez strings.Splitla fonction pour diviser la chaîne et afficher chaque partie. Utilisez strings.Joinla fonction pour fusionner les parties divisées dans une nouvelle chaîne et la générer.
  4. func TestConv(t *testing.T) { ... }: Teste la conversion des chaînes vers d’autres types.

    • Utilisé strconv.Itoapour convertir un entier en chaîne.
    • Concaténez des chaînes et des entiers et affichez le résultat.
    • Utilisez-le strconv.Atoipour convertir des chaînes en entiers, effectuer des additions et gérer les conditions d'erreur.

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, runela 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 funccommence 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 returnle 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

deferLes 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 newla 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 Circlestructure, puis défini une Areaméthode qui y est nommée. Cette méthode peut c.Area()être appelée via , où cest une Circleinstance 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, Incrementla méthode utilise un récepteur pointeur afin que Countla 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 Rectangledéfinissons une Areaméthode nommée pour la structure, qui peut être appelée par rect.Area(). Les méthodes sont directement associées au type Rectangleet ont accès aux Rectanglechamps de ( Widthet 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 CalculateAreafonction appelée qui accepte un Rectangleparamètre de type pour calculer l'aire d'un rectangle. La fonction est indépendante du Rectangletype, elle ne peut donc pas accéder directement Rectangleaux 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, typedé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 Shapeinterface nommée, qui nécessite la mise en œuvre d'une Areaméthode de calcul de l'aire de la figure. Ensuite, nous avons défini deux structures Circleet Rectangle, et implémenté Areales méthodes respectivement. En utilisant des interfaces, nous pouvons placer différents types d'objets graphiques dans la même tranche, puis appeler leurs Areamé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, shapesdifférents types d'objets sont stockés dans la tranche, mais ils implémentent tous Shapel'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 Carune structure contenant Enginedes structures imbriquées. Grâce à l'imbrication, Carune structure peut accéder directement Engineaux 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 Shapeinterface 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 Circleet Rectanglestructure, afin qu’elles satisfassent Shapel’interface.

En plaçant différents types d'instances de forme dans une tranche, nous pouvons appeler les méthodes et []Shapede 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, Dogla structure imbrique Animalune structure, héritant ainsi Animaldes 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)valueest la valeur de l'interface et Typeest 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 :

  1. func DoSomething(p interface{}) { ... }: définit une fonction DoSomethingqui accepte un paramètre d'interface vide p, 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.

  2. func TestEmptyInterfaceAssertion(t *testing.T) { ... }: Testez l'opération d'assertion de l'interface vide.

    Appelez DoSomething(10), transmettez l'entier 10à la fonction et la fonction génère les informations de type entier basées sur l'assertion de type. Appelez DoSomething("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.
  3. func TestEmptyAssert(t *testing.T) { ... }: Testez l'opération d'assertion de type de l'interface vide.

    Déclarez une variable d'interface vide xet attribuez "hello"-lui la valeur de chaîne. Utilisez l'assertion de type x.(string)pour déterminer xs'il s'agit d'un type de chaîne. Si tel est le cas, affectez-la à une variable stret 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 errortype qui implémente l'interface. errorL'interface n'a qu'une seule méthode, c'est-à-dire Error() stringqu'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 errorl'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 :

  1. FileErrorStructure : 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.
  2. Error()Méthode : FileErrorimplémente la méthode errorde l'interface pour la structure Error(), qui est utilisée pour générer une description textuelle de l'erreur.

  3. ReadFile()Fonction : simule l’opération de lecture de fichiers. Dans cet exemple, la fonction renvoie une FileErrorerreur de type , simulant la situation où le fichier n'existe pas.

  4. 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 filePathet ReadFile(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 *FileErrortype 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.

3. Panique et récupération

Dans le langage Go, panicet recoversont 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 panicet recover, avec un cas d'utilisation spécifique :

panique

Créez panicun répertoire et écrivez panic_test.go. panicest 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 panicpour interrompre le flux normal du programme. Mais toute utilisation abusive doit être évitée paniccar elle peut provoquer le blocage du programme sans fournir de message d'erreur convivial. Généralement panicutilisé 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 indexdépasse arrla plage de la tranche, il sera déclenché panic, provoquant le crash du programme. Dans ce cas, panicil est utilisé pour indiquer une erreur irrécupérable dans le programme.

récupérer

Créez recoverun répertoire et écrivez recover_test.go. recoverÉgalement une fonction intégrée pour récupérer des panicpaniques 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, recovervous 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, panicaprès le déclenchement, cleanuple contenu de la fonction recoverest capturé panicet 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 à panicl'endroit où il a été déclenché, fmt.Printlnil ne sera donc pas exécuté.

En résumé, panicet recoverdoit ê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. panicVous 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 defineun 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 erroraux 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 erroraux exigences de l'interface, c'est-à-dire implémenter Error() stringla 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 :

  1. TimeoutErrorStructure : 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.
  2. Error()Méthode : TimeoutErrorimplémente la méthode errorde l'interface pour la structure Error(), qui est utilisée pour générer une description textuelle de l'erreur.

  3. 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, une TimeoutErrorerreur de type est renvoyée.

  4. 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é.

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, TimeoutErrordes informations supplémentaires sur les opérations d’expiration et les périodes d’expiration sont fournies.

Je suppose que tu aimes

Origine blog.csdn.net/xiaofeng10330111/article/details/132390106
conseillé
Classement