• *Go/Golang : pointeurs, fonctions, récepteurs et interfaces

    Un super article qui aborde tout ces sujets de manière progressive et très bien expliqué !

    Article original sur https://ichi.pro

    --------------------------------------------------------------------------------------------------------------------------------------

    Road to Golang pro - Pointeurs et fonctions

     

    Quelques mots avant de commencer. Vous pouvez trouver le code utilisé dans ce didacticiel dans ce référentiel . Vous pouvez trouver le contenu complet de Road to Go Pro ici . Si vous avez manqué le dernier, vous pouvez le retrouver via ce lien .

    Nous avons parlé des contrôles de flux et des boucles dans la dernière partie du didacticiel. Dans celui-ci, nous couvrirons les pointeurs et les fonctions. Après avoir terminé les 4 premières parties de Road to Go Pro, vous êtes bien équipé pour commencer à écrire des scripts ou des applications console à l'aide de Go.

    Pointeurs

    Photo de Nathalie SPEHNER sur Unsplash

    Si vous avez utilisé C ou C ++, vous savez déjà ce qu'est un pointeur. Cependant, dans les langages de programmation les plus populaires tels que Java, C #, Javascript, Python, etc., il n'y a pas de syntaxe explicite pour représenter les pointeurs. Je n'avais aucune idée de ce que c'était quand j'ai entendu parler de ce nom pour la première fois.

    En bref, un pointeur contient l'adresse mémoire sous-jacente d'une valeur. Whoa, attendez, adresse mémoire? Go n'est-il pas un langage de programmation de haut niveau?

    Quand avons-nous même besoin de connaître les adresses mémoire des variables?

    C'est une bonne question mais avant d'explorer la réponse, nous devons faire un petit détour. Voyons comment déclarer des pointeurs et comment les utiliser dans des fonctions. Une fois que nous les aurons couverts, il vous sera plus facile de comprendre le raisonnement et les exemples ci-dessous. Alors accrochez-vous.

    Déclaration de pointeurs

    Chaque fois que nous déclarons une variable dans Go, le compilateur alloue un segment de mémoire pour la stocker. La valeur de cette variable y est stockée jusqu'à ce qu'elle soit recyclée par le garbage collector.

    Les pointeurs sont des types de données composites. Nous formons un type pointeur en ajoutant un *devant le type de données vers lequel il pointe. Par exemple, *stringreprésente le type d'un pointeur pointant vers une variable de type chaîne.

    Pour obtenir la valeur du pointeur d'une variable existante, nous devons ajouter un &devant la variable.

     

     
    pointer := "I'm a string, not a pointer"
    // declare a pointer type
    var pointerType *string
    pointerType = &pointer
    // declare a pointer directly
    p := &pointer
    fmt.Printf("String: %s\nPointerType: %v\nPointer: %v\n", pointer, pointerType, p)
    // output:
    // String: I'm a string, not a pointer
    // PointerType: 0xc000010200
    // Pointer: 0xc000010200

     

    Nous avons mentionné dans le didacticiel précédent que nous n'avons pas besoin de spécifier le type de données lors de la déclaration de variables. Cela s'applique également aux types de pointeurs. Dans l'exemple ci-dessus, le type de variable psera déduit comme *stringautomatiquement.

    Nous pouvons également accéder ou définir la valeur stockée via un pointeur en utilisant *pour déréférencer le pointeur.

     

     
    fmt.Printf("Dereference pointer: %v\n", *p)
    // output:
    // Dereference pointer: I'm a string, not a pointer
     
    *p = "Pretending to be a pointer now."
    fmt.Printf("New value set via pointer: %v\n", *p)
    fmt.Printf("Variable value changed: %s\n", pointer)
    // output:
    // New value set via pointer: Pretending to be a pointer now.
    // Variable value changed: Pretending to be a pointer now.

     

    Notez que lorsqu'il s'agit de pointeurs, nous devons garder nilà l'esprit car la valeur par défaut d'un pointeur est nil. Par conséquent, nous devons faire attention à la déréférence de pointeur nul.

     

     
    // nil pointer
    var nilPointer *int
    fmt.Printf("nil pointer: %v\n", *nilPointer)
    // output:
    // panic: runtime error: invalid memory address or nil pointer dereference

     

    Les fonctions

    Photo de Lenny Kuhne sur Unsplash

    Les fonctions, également appelées méthodes, créent une logique réutilisable qui exécute des tâches spécifiques. Vous pouvez le considérer comme une «usine». Il prend 0 à n nombres d'arguments et renvoie 0 à n nombres de résultats. La structure d'une fonction ressemble à ceci:

     

     
    func (receiver) function_name (input_arguments) (output_arguments) {
    // some dark magic here...

     

    Les récepteurs, les arguments d'entrée et les arguments de sortie sont trois composants facultatifs que nous pouvons spécifier lors de la déclaration d'une fonction.

    Entrée sortie

    Tout d'abord, parlons des arguments d'entrée et de sortie. Les arguments d'entrée sont également appelés paramètres de fonction, ce sont les ingrédients transmis à l'usine. Et les arguments de sortie sont les valeurs de retour produites par l'usine. Voici un exemple de fonction avec des arguments d'entrée et de sortie spécifiés.

     

     
    // function
    func isCountryCovidSafe(country string) bool {
    // casting dark magic spell to determine whether requested country is covid safe or not
    return true // only to make the compiler happy

     

    Une chaîne countryest l'argument d'entrée de la isCovidSafefonction. Lors de la spécification des arguments d'entrée, nous devons d'abord déclarer le nom, puis le type. Alors que pour les arguments de sortie, nous ne déclarons généralement que des types.

    Si nous avons plus d'un argument d'entrée, nous pouvons utiliser un raccourci pratique pour raccourcir la déclaration uniquement lorsque des parties des arguments ont le même type.

     

     
    // function input arguments shortcut
    func isStateCovidSafe(country, state string) bool {
    // casting dark magic spell to determine whether the requested state is covid safe or not
    return true // only to make the compiler happy

     

    Par exemple, la isStateCovidSafefonction prend un argument supplémentaire stateet elle a le même type que country. Au lieu de spécifier les types deux fois (country string, state string), nous pouvons omettre la déclaration de type dupliquée. Cependant, cela ne s'applique qu'aux arguments d'entrée. Pour les arguments de sortie, nous devons les spécifier individuellement même s'ils sont du même type. Sur une autre note pour les sorties, lorsque nous avons plus d'un argument de sortie, nous devons envelopper les types avec des crochets (). Par exemple, si une fonction renvoie deux chaînes, la fonction doit ressembler à func func_name(inputs string) (string, string) {}.

    Comme mentionné ci-dessus, les arguments d'entrée et de sortie sont facultatifs. Cependant, une fonction sans arguments d'entrée et de sortie peut ne pas être très utile.

     

     
    func doNothing() {
    fmt.Println("This factory does nothing. Not very useful.")

     

    Récepteur de valeur / pointeur

    Les exemples de fonctions que nous avons vus jusqu'à présent peuvent être appelés n'importe où dans le même package. Une fois que nous ajoutons un composant récepteur pour une fonction, cette fonction appartient au récepteur. Les récepteurs ne peuvent être que des types locaux. Nous ne pouvons pas définir des fonctions appartenant à des types primitifs ou des types définis en dehors du package actuel. Voyons quelques exemples. Tout d'abord, nous devons définir un type local appelé Person.

     

     
    type Person struct {
    FirstName string
    LastName string
    Age int

     

    Ensuite, nous pouvons ajouter le Persontype en tant que récepteur aux fonctions. Il existe deux formes de récepteurs dans Go, ce sont des récepteurs de valeur et des récepteurs de pointeurs.

     

     
    // value receiver
    func (p Person) FullName() string {
    p.Age = 100
    return fmt.Sprintf("%s %s\n", p.FirstName, p.LastName)
    }
     
    // invoke in main function
    // creating a person first
    person := Person {
    FirstName: "Jon",
    LastName: "Snow",
    Age: 23,
    }
    // invoke full name function
    fullName := person.FullName()
    fmt.Print(fullName)
    // output: Jon Snow
    fmt.Println(person.Age)
    // output: 23
     
    FullName() // invoking directly will get an undefined error.

     

    FullName()La fonction a une valeur receiver ( Person). Lors de la spécification des destinataires, nous devons indiquer le nom et le type du destinataire. Le nom du destinataire est facultatif. Si la valeur du récepteur n'est pas utilisée dans la fonction, nous pouvons omettre le nom du récepteur. Mais le type de récepteur est obligatoire. La convention de dénomination du récepteur utilise le premier caractère du nom du type de récepteur. À l'intérieur de la fonction, nous pouvons accéder à la valeur du récepteur et les utiliser pour effectuer certaines actions. Cependant, le récepteur de valeur ne transmet pas la copie d'origine de la personvariable à la fonction. Au lieu de cela, il a passé une copie de person. Par conséquent, même si nous modifions l'âge à l'intérieur de la fonction, cela n'affectera pas la variable d'origine. Vous voyez que Jon Snow a encore 23 ans. Ouf.

    Si nous voulons changer la valeur d'origine d'un récepteur, nous devons utiliser l'autre forme de récepteurs, les récepteurs pointeurs.

     

     
    // pointer receiver
    func (p *Person) ChangeName(newFirstName, newLastName string) {
    if newFirstName != "" {
    p.FirstName = newFirstName
    }
     
    if newLastName != "" {
    p.LastName = newLastName
    }
    }
     
    // invoke in main function
    // spoiler alert!
    person.ChangeName("Aegon", "Targaryen")
    fmt.Printf("Jon Snow is %v\n", person)
    // output: Jon Snow is {Aegon Targaryen 23}

     

    Un récepteur de pointeur passe un pointeur de la variable d'origine dans la fonction. Ainsi, lorsque nous modifions ses valeurs, cela affectera la variable d'origine. La plupart du temps, j'utilise des récepteurs de pointeur sur des récepteurs de valeur car cela peut éviter de copier la valeur du récepteur. Par conséquent, les récepteurs de pointeurs sont plus efficaces. Cependant, je tiens à souligner que le modèle d'utilisation de récepteurs de pointeur pour modifier la valeur du récepteur n'est pas idéal. J'éviterais d'utiliser ce modèle car il rend la base de code plus difficile à maintenir et à déboguer.

    Implémentation d'une interface

    Nous avons appris ce qu'est une interface dans le tutoriel précédent . Pour récapituler rapidement, une interface est une collection de déclarations de fonctions. Il définit ce qui doit être fait sans spécifier comment faire les travaux.

    Pour implémenter une interface, nous devons définir une structure et cette structure doit avoir toutes les fonctions déclarées de cette interface. Lors de l'implémentation des fonctions requises, nous devons utiliser soit le récepteur de valeur, soit le récepteur de pointeur dans les déclarations de fonction.

     

     
    // The following interface and struct should be declared outside of main function
    // declare an interface
    type Animal interface {
    // function sets
    Eat()
    Drink()
    }
    // implement an interface
    type Dog struct {Name string}
    func (d Dog) Eat() {
    fmt.Printf("%s eats meat\n", d.Name)
    }
    func (d Dog) Drink() {
    fmt.Printf("%s drinks water\n", d.Name)
    }
    // Following code should be inside main function
    // declare a variable with interface type
    var poodle Animal
    poodle = Dog{Name: "Bolt"}
    poodle.Eat() // output: Bolt eats meat
    poodle.Drink() // output: Bolt drinks water

     

    Go reconnaît automatiquement que la Dogstructure implémente l' Animalinterface car elle a implémenté toutes les fonctions déclarées dans l' Animalinterface.

    Vous vous demandez peut-être: pourquoi n'utilisons-nous pas de récepteurs de pointeurs lors de l'implémentation de fonctions?

    Je suis content que vous ayez posé la question. Nous pouvons utiliser des récepteurs pointeurs.

    Créons maintenant une structure appelée Catet nous utiliserons des récepteurs de pointeurs lors de l'implémentation de l' Animalinterface.

     

     
    type Cat struct {Name string}
    func (c *Cat) Eat() {
    fmt.Printf("%s eats fish\n", c.Name)
    }
    func (c *Cat) Drink() {
    fmt.Printf("%s drinks milk\n", c.Name)
    }
    var ragdoll Animal
    ragdoll = &Cat{Name: "Lightning"}
    ragdoll.Eat() // output: Lightning eats fish
    ragdoll.Drink() // output: Lightning drinks milk

     

    Ici, le pointeur a *Catimplémenté l' Animalinterface au lieu de la Catstructure car nous avons utilisé des récepteurs de pointeur dans Eat()et des Drink()fonctions.

    Alors, quelle est la meilleure façon?

    Pour répondre à cela, nous devons revenir à la question de savoir quand devons-nous utiliser des pointeurs.

    Retour aux pointeurs

    Ok, j'avoue, ce n'est pas un détour rapide. Nous savons maintenant ce qu'est un pointeur et comment l'utiliser. Il est temps de regarder quand nous devrions utiliser des pointeurs et ce à quoi nous devons faire attention lorsque nous utilisons des pointeurs.

    Photo de Nick Fewings sur Unsplash

    Une règle générale consiste à n'utiliser des pointeurs que si vous devez absolument le faire. Sinon, donnez-lui un laissez-passer.

    Voici quelques scénarios dans lesquels vous pouvez envisager d'utiliser un pointeur.

    1. Lorsque vous avez besoin d'un type nilable (nullable).

    Créons une exigence commerciale pour ce scénario. Supposons que vous souhaitiez stocker les décisions des gens quant à savoir s'ils veulent aller à votre fête. Il existe trois options disponibles. Tout d'abord, une personne peut décider de se joindre. Deuxièmement, une personne peut décider de ne pas adhérer. Et troisièmement, une personne n'a pas encore pris la décision.

    Le stockage de cette valeur dans un booltype ne répondra pas à l'exigence, car boolil ne peut avoir que deux valeurs, true et false. Dans ce cas, nous pouvons utiliser un pointeur *boolet il peut contenir trois valeurs: true (jointure), false (pas de jointure) et nil (non décidé).

     

     
    type Invitation struct {
    FirstName string
    LastName string
    Going *bool
    }
     
    // The follow code should be executed in the main function
    invite := Invitation{FirstName: "Donald", LastName: "Trump"}
    fmt.Printf("%v\n", invite)
    // output: {Donald Trump <nil>}

     

    Bien que *boolsatisfaisant à l'exigence, il introduit un effet secondaire notoire, nul déréférencement .

    Nous devons vérifier si la valeur du Goingchamp est nulle avant de le déréférencer. Sinon, nous serons frappés par une erreur d'exécution: «adresse mémoire invalide ou déréférence de pointeur nul». Cela ajoute une complexité indésirable lors de la gestion du Goingchamp et est très sujet aux erreurs car nous devons nous rappeler de gérer explicitement le déréférencement nul.

    Il existe une meilleure façon de résoudre ce problème particulier. Il utilise une énumération comme type de Goingchamp.

     

     
    type Decision int
     
    const (
    Going Decision = iota
    NotGoing Decision
    NotDecided Decision
    )
     
    Invitation struct {
    FirstName string
    LastName string
    Going Decision

     

    * Remarque: iotaest un mot-clé pour simplifier les définitions d'entiers incrémentiels. Pour plus d'informations, veuillez consulter le wiki .

    2. Lors du choix du récepteur à utiliser dans une fonction.

    Comme mentionné dans la section précédente, dans la plupart des cas, nous choisissons des récepteurs de pointeur sur des récepteurs de valeur car ils sont plus efficaces.

    Cependant, il apporte également un effet secondaire, qui est des mutations accidentelles. Des mutations de données inattendues rendent le code plus difficile à déboguer et à maintenir. Je ne muterais pas la valeur du pointeur. Si je le dois, je rendrai la mutation petite et évidente. Comme dans l'exemple ci-dessus, la ChangeName()fonction modifie le prénom et le nom d'une personne. Il fait une simple mutation et le nom de la fonction rend l'intention très évidente et claire pour les autres.

    Voici un guide plus détaillé sur la façon de choisir entre les récepteurs de valeur et les récepteurs de pointeur.

    3. En cas de besoin de singletons.

    Un singleton est quelque chose que nous n'initions qu'une seule fois dans le cycle de vie d'une application. Par exemple, lorsque nous devons intégrer une API tierce, nous voulons créer un client pour cette API et cela peut être un singleton. Ou lorsque nous voulons utiliser le cache dans l'application, le client de cache peut également être un singleton.

     

     
    type SlackClient struct {
    URL string
    APIKey string
    }
     
    func NewPointerSlackClient(url, apiKey string) *SlackClient {
    return &SlackClient{
    URL: url,
    APIKey: apiKey,
    }
    }
     
    func NewValueSlackClient(url, apiKey string) SlackClient {
    return SlackClient{
    URL: url,
    APIKey: apiKey,
    }
    }
     
    type PointerService struct {
    SlackClient *SlackClient
    }
     
    func (*PointerService) Description() {
    fmt.Println("Pointer Client is a singleton.")
    }
     
    func NewPointerService(client *SlackClient) *PointerService {
    return &PointerService{
    SlackClient: client,
    }
    }
     
    type ValueService struct {
    SlackClient SlackClient
    }
     
    func NewValueService(client SlackClient) ValueService {
    return ValueService{
    SlackClient: client,
    }
    }
     
    func (ValueService) Description() {
    fmt.Println("Value Client is not a singleton.")
    }
     
    // The following code should be executed inside main()
    pClient := NewPointerSlackClient("slack.com", "apiKey")
    pService := NewPointerService(pClient)
    pService.Description()
    // output: Pointer Client is a singleton.
    vClient := NewValueSlackClient("slack.com", "apiKey")
    vService := NewValueService(vClient)
    vService.Description()
    // output: Value Client is not a singleton.

     

    Nous initialisons normalement l'instance du singleton à l'intérieur de la main()fonction, puis la passons à différentes parties de la base de code. Dans l'exemple ci-dessus, nous avons créé deux singletons potentiels, PointerSlackClientet ValueSlackClient. Si nous passons le singleton en tant que paramètre de fonction comme nous l'avons fait pour ValueService, alors ce ValueSlackClientn'est plus vraiment un singleton car lors du passage en paramètre, Go utilise une copie de ValueSlackClient. Ainsi, nous aurons plus d'une instance de ValueSlackClient. Lorsque vous utilisez un pointeur, Go ne fait pas de copie de l'instance. Il n'y a donc vraiment qu'une seule instance de l' PointerSlackClientintérieur de l'application.

    Ce sont des scénarios dans lesquels je pense que nous pouvons utiliser des pointeurs. Cela dit, la règle numéro un est toujours:

    Évitez d'utiliser des pointeurs dans la mesure du possible.

    Et après?

    Photo d'Olesya Grichina sur Unsplash

    Nous avons appris jusqu'à présent les types de données, les contrôles de flux, les boucles, les pointeurs et les fonctions. Ce sont les éléments de base les plus élémentaires de Go. Dans le prochain tutoriel, nous examinerons Go sous un autre angle. Je parlerai des packages, des modules et de la manière de démarrer un projet Go à partir de zéro. J'espère que vous trouverez ce tutoriel utile. Restez à l'écoute si vous voulez en savoir plus sur Go et je vous verrai dans le prochain.

    N'hésitez pas à laisser un commentaire ci-dessous si vous rencontrez des problèmes ou si vous avez besoin d'un coup de main. Les commentaires sont toujours les bienvenus. Merci pour la lecture!

    Un merci spécial à Mark Hume-Cook pour avoir revu ce tutoriel.

    « *Go : la gestion des packagesbatteryCheck : pour vous aider à préserver votre batterie de portable sous Linux »

  • Commentaires

    1
    Mercredi 3 Janvier à 07:50
    Hi, I think your website may be having browser compatibility issues. When I take a look at your web site in Safari, it looks fine however, if opening in Internet Explorer, it has some overlapping issues. I simply wanted to give you a quick heads up! Apart from that, fantastic website!
    2
    Vendredi 12 Janvier à 16:27
    My brother suggested I might like this web site. He was totally right. This post actually made my day. You cann't imagine simply how much time I had spent for this info! Thanks!
    3
    Dimanche 14 Janvier à 02:03
    "Сверхъестественное" представляет собой увлекательный рассказ о приключениях братьев Винчестеров, путешествующих по миру в поисках паранормальных явлений и борющихся с нечистой силой. Они сталкиваются с самыми разными сущностями - от зловещих демонов и блуждающих духов до небесных ангелов и забытых божеств. Каждая серия этого сериала - это отдельная история, наполненная удивительными событиями и богатая своей уникальной мифологией. Просмотр "Сверхъестественного" в режиме онлайн позволяет зрителям погрузиться в захватывающий мир, в котором реальность переплетается с фантастикой. Этот популярный сериал обязательно поразит вас своей драматичностью, остроумием и запоминающимися героями.
    4
    Lundi 12 Février à 17:02
    Заголовок: Юрист по купле продаже недвижимости Описание: Юрист по купле продаже недвижимости поможет вам оформить и защитить ваши права при покупке или продаже жилья. Как выбрать юриста для сделки по покупке или продаже недвижимости Когда речь заходит о покупке или продаже недвижимости, многие люди испытывают смешанные чувства: с одной стороны, это захватывающий процесс, который открывает новые горизонты и возможности, а с другой стороны, это огромная ответственность и риск. В таких случаях важно иметь надежного партнера, специалиста своего дела – юриста по купле-продаже недвижимости, который поможет вам справиться со всеми юридическими аспектами и защитить ваши интересы. Каждая покупка или продажа недвижимости – это уникальная ситуация, требующая квалифицированного подхода и профессиональных знаний. Юридические нюансы, документы, соглашения – все это может запутать и осложнить процесс сделки. Однако, с профессиональным юристом на вашей стороне, вы можете быть уверены в том, что все будет выполнено в соответствии с законодательством, и вы получите оптимальные условия и защиту ваших прав.
    5
    Mardi 20 Février à 06:08
    Обращение к автоюристу для взыскания компенсации по ОСАГО в Москве может быть крайне полезным, особенно когда столкновение с бюрократией страховых компаний становится утомительным и запутанным. Специалист в области автоправа помогает автовладельцам получить положенные по закону выплаты в случае аварий и других дорожно-транспортных происшествий. Автоюристы осуществляют правовую экспертизу документов, устанавливают размер реального ущерба и корректируют требования к страховой компании. Данная услуга включает подготовку всех необходимых заявлений и документации, проведение переговоров и представление интересов клиента в страховой организации. В случае необходимости, юрист готовит материалы для судебного разбирательства, чтобы обеспечить должное взыскание компенсации. Грамотно составленные претензии и юридическое сопровождение повышают шансы на успешное получение компенсации без лишних задержек. Выбирая автоюриста, стоит обратить внимание на его успешный опыт в подобного рода делах и отзывы других клиентов. Часто страховые компании стремятся снизить сумму выплат, но опытный юрист сможет добиться справедливой компенсации, сделав процесс менее стрессовым для пострадавшего в ДТП.
    6
    Mercredi 10 Avril à 13:23
    Reduslim Bewertungen sind der Schlüssel zum Erfolg für viele Menschen auf der Suche nach einer effektiven Gewichtsabnahme-Lösung. Diese positiven Bewertungen von zufriedenen Kunden bestätigen die Wirksamkeit dieses Produkts. Reduslim ist eine natürliche Ergänzung, die hilft, den Stoffwechsel zu steigern und das Verlangen nach ungesundem Essen zu reduzieren. Das Geheimnis liegt in der speziell formulierten Mischung aus natürlichen Inhaltsstoffen, die zusammenarbeiten, um Fett zu verbrennen und den Körper zu entgiften. Die meisten Benutzer berichten von schnellen Ergebnissen und einem verbesserten Wohlbefinden. Wenn auch Sie von den positiven Reduslim Bewertungen beeindruckt sind, probieren Sie es selbst aus und erleben Sie die Vorteile dieses wirksamen Produkts.
    Suivre le flux RSS des commentaires


    Ajouter un commentaire

    Nom / Pseudo :

    E-mail (facultatif) :

    Site Web (facultatif) :

    Commentaire :