Entretien: Swift. Q & A

Le langage de programmation Swift n'a que quatre ans, mais il devient déjà le principal langage de développement pour iOS. En développant la version 5.0, Swift est devenu un langage complexe et puissant qui répond à la fois à un paradigme orienté objet et fonctionnel. Et à chaque nouvelle version, il ajoute encore plus de fonctionnalités.

Mais comment connaissez-vous vraiment Swift? Dans cet article, vous trouverez des exemples de questions pour une interview Swift.

Vous pouvez utiliser ces questions pour interroger les candidats afin de tester leurs connaissances ou vous pouvez tester les vôtres. Si vous ne connaissez pas la réponse, ne vous inquiétez pas: il y a une réponse à chaque question.

Les questions sont divisées en trois groupes:

  • DĂ©butant : pour les dĂ©butants. Vous avez lu quelques livres et appliquĂ© Swift dans vos propres applications.
  • IntermĂ©diaire : adaptĂ© Ă  ceux qui sont vraiment intĂ©ressĂ©s par la langue. Vous en avez dĂ©jĂ  beaucoup lu et expĂ©rimentez souvent.
  • AvancĂ© : adaptĂ© aux dĂ©veloppeurs les plus avancĂ©s - ceux qui aiment entrer dans la jungle de la syntaxe et utiliser des techniques avancĂ©es.

Il existe deux types de questions pour chaque niveau:

  • Ă©crit : adaptĂ© aux tests par e-mail, car ils suggèrent d'Ă©crire du code.
  • oral : peut ĂŞtre utilisĂ© lorsque vous parlez au tĂ©lĂ©phone ou en personne, car il y a suffisamment de rĂ©ponses en mots.

Pendant que vous lisez cet article, gardez le terrain de jeu ouvert pour pouvoir vérifier le code de la question. Toutes les réponses ont été testées sur Xcode 10.2 et Swift 5 .

  • DĂ©butant


    questions écrites
    Question 1
    Considérez le code suivant:

    struct Tutorial { var difficulty: Int = 1 } var tutorial1 = Tutorial() var tutorial2 = tutorial1 tutorial2.difficulty = 2 

    Quelles sont les valeurs de tutorial1.difficulty et tutorial2.difficulty ? Y aurait-il une différence si le didacticiel était une classe? Pourquoi?

    la réponse
    tutorial1.difficulty vaut 1 et tutorial2.difficulty vaut 2.

    Dans Swift, les structures sont des types de valeur. Ils sont copiés, non référencés. La ligne suivante copie tutorial1 et l'affecte à tutorial2 :

     var tutorial2 = tutorial1 

    Les modifications apportées à tutorial2 n'affectent pas tutorial1 .

    Si Tutorial était une classe, tutorial1.difficulty et tutorial2.difficulty seraient égaux à 2. Les classes dans Swift sont des types de référence. Lorsque vous modifiez la propriété tutorial1, vous verrez la même modification pour tutorial2 - et vice versa.


    Question 2
    Vous avez déclaré view1 avec var et view2 avec let . Quelle est la différence et la dernière ligne est-elle compilée?

     import UIKit var view1 = UIView() view1.alpha = 0.5 let view2 = UIView() view2.alpha = 0.5 //   ? 

    la réponse
    Oui, la dernière ligne est compilée. view1 est une variable et vous pouvez affecter sa valeur à une nouvelle instance de UIView. En utilisant let , vous ne pouvez affecter une valeur qu'une seule fois, de sorte que le code suivant ne se compile pas:

     view2 = view1 // : view2 is immutable 

    Cependant, UIView est une classe avec une sémantique référentielle, vous pouvez donc modifier les propriétés de view2 - ce qui signifie que le code sera compilé .

    Question 3
    Ce code trie le tableau par ordre alphabétique. Simplifiez autant que possible la fermeture.

     var animals = ["fish", "cat", "chicken", "dog"] animals.sort { (one: String, two: String) -> Bool in return one < two } print(animals) 

    la réponse
    Swift détermine automatiquement le type de paramètres de fermeture et le type de retour, vous pouvez donc les supprimer :

     animals.sort { (one, two) in return one < two } 

    Vous pouvez remplacer les noms de paramètres à l'aide de la notation $ i :

     animals.sort { return $0 < $1 } 

    Les fermetures consistant en une seule instruction peuvent ne pas contenir le mot-clé return . La valeur de la dernière instruction exécutée devient le résultat de retour de la fermeture:

     animals.sort { $0 < $1 } 

    Enfin, puisque Swift sait que les éléments du tableau sont conformes au protocole Equatable , vous pouvez simplement écrire:

     animals.sort(by: <) 


    Upd: hummingbirddj simplifié encore plus:
    Dans ce cas, vous pouvez mĂŞme raccourcir:
      animals.sort() 
    - Trie par ordre croissant, fonctionne pour les types qui implémentent Comparable.


    Question 4
    Ce code crée deux classes: Adresse et Personne . Deux instances de la classe Person ( Ray et Brian ) sont également créées.

     class Address { var fullAddress: String var city: String init(fullAddress: String, city: String) { self.fullAddress = fullAddress self.city = city } } class Person { var name: String var address: Address init(name: String, address: Address) { self.name = name self.address = address } } var headquarters = Address(fullAddress: "123 Tutorial Street", city: "Appletown") var ray = Person(name: "Ray", address: headquarters) var brian = Person(name: "Brian", address: headquarters) 

    Supposons que Brian a déménagé vers une nouvelle adresse et que vous souhaitez mettre à jour son enregistrement comme suit:

     brian.address.fullAddress = "148 Tutorial Street" 

    Cela se compile et s'exécute sans erreur. Mais, si vous vérifiez maintenant l'adresse de Ray , vous verrez qu'il a également «déménagé» .

    Que s'est-il passé ici et comment pouvons-nous y remédier?

    la réponse
    L'adresse est une classe et possède une sémantique de référence . Ainsi, le quartier général est la même instance de la classe partagée par ray et brian. Changer le siège changera l'adresse des deux.
    Pour résoudre ce problème, vous pouvez créer une nouvelle instance de la classe Address et l'affecter à Brian, ou déclarer Address en tant que structure au lieu d'une classe .

    questions orales
    Question 1
    Qu'est-ce qui est facultatif et quels problèmes résolvent-ils?

    la réponse
    facultatif permet à une variable de tout type de présenter une situation " sans valeur ". Dans Objective-C, "aucune valeur" n'était disponible que dans les types de référence utilisant la valeur spéciale nil . Les types de valeur, tels que int ou float , n'avaient pas cette capacité.
    Swift a étendu le concept de «aucune valeur» aux types de valeur. La variable facultative peut contenir une valeur ou nil , indiquant l'absence d'une valeur.


    Question 2
    Énumérez brièvement les principales différences entre la structure et la classe .

    la réponse
    Les classes prennent en charge l'héritage, mais pas les structures.
    Les classes sont un type de référence, les structures sont un type de valeur.


    Question 3
    Que sont les génériques et à quoi servent-ils?

    la réponse
    Dans Swift, vous pouvez utiliser des génériques dans les classes, les structures et les énumérations.

    Les génériques résolvent le problème de duplication de code. Si vous avez une méthode qui accepte les paramètres d'un type, vous devez parfois dupliquer du code pour travailler avec des paramètres d'un autre type.

    Par exemple, dans ce code, la deuxième fonction est un «clone» de la première, sauf qu'elle a des paramètres de chaîne et non un entier.

     func areIntEqual(_ x: Int, _ y: Int) -> Bool { return x == y } func areStringsEqual(_ x: String, _ y: String) -> Bool { return x == y } areStringsEqual("ray", "ray") // true areIntEqual(1, 1) // true 

    En utilisant des génériques, vous combinez deux fonctions en une et garantissez en même temps la sécurité du type:

     func areTheyEqual<T: Equatable>(_ x: T, _ y: T) -> Bool { return x == y } areTheyEqual("ray", "ray") areTheyEqual(1, 1) 

    Puisque vous testez l'égalité, vous limitez les types à ceux qui sont conformes au protocole Equatable . Ce code fournit le résultat souhaité et empêche le transfert de paramètres de type incorrect.


    Question 4
    Dans certains cas, il ne sera pas possible d'éviter les options implicitement déballées . Quand et pourquoi?

    la réponse
    Les raisons les plus courantes d'utiliser des options implicitement non enveloppées sont:

    • lorsque vous ne pouvez pas initialiser une propriĂ©tĂ© qui n'est pas nulle au moment de la crĂ©ation. Un exemple typique est le point de vente d'Interface Builder, qui est toujours initialisĂ© après son propriĂ©taire. Dans ce cas particulier, si tout est correctement configurĂ© dans Interface Builder, vous avez la garantie que la prise n'est pas nulle avant de l'utiliser.
    • pour rĂ©soudre le problème de la boucle des rĂ©fĂ©rences fortes , lorsque deux instances de classes se rĂ©fèrent l'une Ă  l'autre et qu'une rĂ©fĂ©rence non nulle Ă  une autre instance est requise. Dans ce cas, vous marquez le lien d'un cĂ´tĂ© comme non propriĂ©taire , et de l'autre cĂ´tĂ© utilisez une extension optionnelle implicite.


    Question 5
    Quelles sont les méthodes de déploiement facultatives? Évaluez-les en termes de sécurité.

     var x : String? = "Test" 

    Astuce: seulement 7 façons.

    la réponse
    Le déballage forcé n'est pas sûr.

     let a: String = x! 

    Le déploiement implicite lors de la déclaration d'une variable n'est pas sûr.

     var a = x! 

    La reliure optionnelle est sûre.

     if let a = x { print("x was successfully unwrapped and is = \(a)") } 

    Le chaînage optionnel est sûr.

     let a = x?.count 

    L'opérateur de coalescence nul est sûr.

     let a = x ?? "" 

    La déclaration de la Garde est sûre.

     guard let a = x else { return } 

    Modèle optionnel - sûr.

     if case let a? = x { print(a) } 




  • IntermĂ©diaire


    questions écrites
    Question 1
    Quelle est la difference entre nil et .none ?

    la réponse
    Il n'y a aucune différence, Optional.none (brièvement .none ) et nil sont équivalents.
    En fait, l'instruction suivante retournera vrai :

     nil == .none 


    L'utilisation de néant est plus généralement acceptée et recommandée.


    Question 2
    Voici un modèle de thermomètre sous forme de classe et de structure. Le compilateur se plaint de la dernière ligne. Qu'est-ce qui ne va pas?

     public class ThermometerClass { private(set) var temperature: Double = 0.0 public func registerTemperature(_ temperature: Double) { self.temperature = temperature } } let thermometerClass = ThermometerClass() thermometerClass.registerTemperature(56.0) public struct ThermometerStruct { private(set) var temperature: Double = 0.0 public mutating func registerTemperature(_ temperature: Double) { self.temperature = temperature } } let thermometerStruct = ThermometerStruct() thermometerStruct.registerTemperature(56.0) 

    la réponse
    ThermometerStruct est correctement déclaré avec une fonction de mutation pour changer la variable interne. Le compilateur se plaint que vous appelez la méthode registerTemperature de l'instance qui a été créée avec let , donc cette instance est immuable. Changer let en var corrigera l'erreur de compilation.

    Dans les structures, vous devez marquer les méthodes qui modifient les variables internes comme mutantes , mais vous ne pouvez pas appeler ces méthodes à l'aide d'une instance immuable.


    Question 3
    Que produira ce code et pourquoi?

     var thing = "cars" let closure = { [thing] in print("I love \(thing)") } thing = "airplanes" closure() 


    la réponse
    Il sera imprimé: j'adore les voitures. La liste de capture crée une copie de la variable lorsque la fermeture est déclarée. Cela signifie que la variable capturée ne changera pas sa valeur, même après l'attribution d'une nouvelle valeur.

    Si vous omettez la liste de capture dans la fermeture, le compilateur utilisera le lien, pas la copie. L'appel de clôture reflétera le changement de la variable:

     var thing = "cars" let closure = { print("I love \(thing)") } thing = "airplanes" closure() // Prints: "I love airplanes" 



    Question 4
    Il s'agit d'une fonction qui compte le nombre de valeurs uniques dans un tableau:

     func countUniques<T: Comparable>(_ array: Array<T>) -> Int { let sorted = array.sorted() let initial: (T?, Int) = (.none, 0) let reduced = sorted.reduce(initial) { ($1, $0.0 == $1 ? $0.1 : $0.1 + 1) } return reduced.1 } 

    Il utilise trié, il n'utilise donc que des types conformes au protocole comparable.

    Vous pouvez l'appeler comme ceci:

     countUniques([1, 2, 3, 3]) //  3 


    Réécrivez cette fonction en tant qu'extension de tableau afin de pouvoir l'utiliser comme ceci:

     [1, 2, 3, 3].countUniques() //   3 


    note du traducteur
    Quelque chose de trop douloureux est une fonction monstrueuse. Pourquoi pas:

     func countUniques<T: Hashable>(_ array: Array<T>) -> Int { return Set(array).count } 


    la réponse
     extension Array where Element: Comparable { func countUniques() -> Int { let sortedValues = sorted() let initial: (Element?, Int) = (.none, 0) let reduced = sortedValues.reduce(initial) { ($1, $0.0 == $1 ? $0.1 : $0.1 + 1) } return reduced.1 } } 



    Question 5
    Voici une fonction qui divise deux doubles optionnels. Trois conditions doivent ĂŞtre remplies:
    • le dividende ne doit pas ĂŞtre nul
    • le diviseur ne doit pas ĂŞtre nul
    • le diviseur ne doit pas ĂŞtre 0


     func divide(_ dividend: Double?, by divisor: Double?) -> Double? { if dividend == nil { return nil } if divisor == nil { return nil } if divisor == 0 { return nil } return dividend! / divisor! } 

    Réécrivez cette fonction à l'aide de l'instruction guard et non à l'aide du déballage forcé.
    la réponse
    L'instruction guard introduite dans Swift 2.0 fournit une sortie si la condition n'est pas remplie. Voici un exemple:

     guard dividend != nil else { return nil } 


    Vous pouvez également utiliser l'instruction guard pour la liaison facultative , après quoi la variable développée sera disponible en dehors de l'instruction guard :

     guard let dividend = dividend else { return .none } 


    Vous pouvez donc réécrire la fonction comme ceci:

     func divide(_ dividend: Double?, by divisor: Double?) -> Double? { guard let dividend = dividend else { return nil } guard let divisor = divisor else { return nil } guard divisor != 0 else { return nil } return dividend / divisor } 

    Faites attention à l'absence de décompression forcée, car nous avons déjà décompressé le dividende et le diviseur et les avons placés dans des variables immuables non facultatives.

    Notez également que le résultat des options non emballées dans la déclaration de garde est également disponible en dehors de la déclaration de garde.

    Vous pouvez simplifier davantage la fonction en regroupant les instructions de garde:

     func divide(_ dividend: Double?, by divisor: Double?) -> Double? { guard let dividend = dividend, let divisor = divisor, divisor != 0 else { return nil } return dividend / divisor } 



    Question 6
    Réécrivez la méthode de la question 5 à l'aide de l' instruction if let .

    la réponse
    L'instruction if let vous permet de décompresser les options et d'utiliser cette valeur dans ce bloc de code. En dehors de cela, ces valeurs ne seront pas disponibles.

     func divide(_ dividend: Double?, by divisor: Double?) -> Double? { if let dividend = dividend, let divisor = divisor, divisor != 0 { return dividend / divisor } else { return nil } } 




    questions orales
    Question 1
    Dans Objective-C, vous déclarez une constante comme celle-ci:

     const int number = 0; 

    Et donc dans Swift:

     let number = 0 

    Quelle est la différence?

    la réponse
    Dans Objective-C, une constante est initialisée au moment de la compilation avec une valeur qui devrait être connue à ce stade.

    Une valeur immuable créée avec let est une constante définie au moment de l'exécution . Vous pouvez l'initialiser avec une expression statique ou dynamique. Par conséquent, nous pouvons le faire:

     let higherNumber = number + 5 


    Veuillez noter qu'une telle mission ne peut être effectuée qu'une seule fois.


    Question 2
    Pour déclarer une propriété ou une fonction statique pour les types de valeur, le modificateur statique est utilisé. Voici un exemple pour la structure:

     struct Sun { static func illuminate() {} } 

    Et pour les classes, il est possible d'utiliser des modificateurs statiques ou de classe . Le résultat est le même, mais il y a une différence. Décrivez-le.

    la réponse
    statique rend une propriété ou une fonction statique et sans chevauchement . L'utilisation de la classe remplacera une propriété ou une fonction.

    Ici, le compilateur jurera lors d'une tentative de remplacement de illuminate () :

     class Star { class func spin() {} static func illuminate() {} } class Sun : Star { override class func spin() { super.spin() } // error: class method overrides a 'final' class method override static func illuminate() { super.illuminate() } } 



    Question 3
    Est-il possible d'ajouter une propriété stockée à un type à l'aide d'une extension ? Comment ou pourquoi pas?

    la réponse
    Non, ce n'est pas possible. Nous pouvons utiliser l'extension pour ajouter un nouveau comportement à un type existant, mais nous ne pouvons pas changer le type lui-même ou son interface. Pour stocker la nouvelle propriété stockée, nous avons besoin de mémoire supplémentaire et l'extension ne peut pas le faire.


    Question 4
    Qu'est-ce qu'un protocole dans Swift?

    la réponse
    Un protocole est un type qui définit un aperçu des méthodes, des propriétés, etc. Une classe, une structure ou une énumération peut prendre un protocole pour implémenter tout cela. Le protocole lui-même n'implémente pas la fonctionnalité, mais la définit.



  • AvancĂ©


    questions écrites
    Question 1
    Supposons que nous ayons une structure qui définit le modèle d'un thermomètre:

     public struct Thermometer { public var temperature: Double public init(temperature: Double) { self.temperature = temperature } } 

    Pour créer une instance, nous écrivons:

     var t: Thermometer = Thermometer(temperature:56.8) 

    Mais quelque chose comme ça serait beaucoup plus pratique:

     var thermometer: Thermometer = 56.8 

    Est-ce possible? Comment?

    la réponse
    Swift définit des protocoles qui vous permettent d'initialiser un type à l'aide de littéraux par affectation. L'application du protocole approprié et la fourniture d'un initialiseur public permettront l'initialisation à l'aide de littéraux. Dans le cas du thermomètre, nous implémentons ExpressibleByFloatLiteral :

     extension Thermometer: ExpressibleByFloatLiteral { public init(floatLiteral value: FloatLiteralType) { self.init(temperature: value) } } 


    Maintenant, nous pouvons créer une instance comme celle-ci:

     var thermometer: Thermometer = 56.8 



    Question 2
    Swift dispose d'un ensemble d'opérateurs prédéfinis pour les opérations arithmétiques et logiques. Il vous permet également de créer vos propres opérateurs, à la fois unaires et binaires.

    Définissez et implémentez votre propre opérateur d'exponentiation (^^) selon les exigences suivantes:
    • prend deux int comme paramètres
    • renvoie le rĂ©sultat de l'augmentation du premier paramètre Ă  la puissance du second
    • traite correctement l'ordre des opĂ©rations algĂ©briques
    • ignore les Ă©ventuelles erreurs de dĂ©bordement


    la réponse
    La création d'un nouvel opérateur se déroule en deux étapes: l'annonce et la mise en œuvre.

    La déclaration utilise le mot-clé operator pour spécifier le type (unaire ou binaire), pour spécifier la séquence de caractères du nouvel opérateur, son associativité et la priorité d'exécution.

    Ici, l'opérateur est ^^ et son type est infixe (binaire). L'associativité a raison.

    Swift n'a pas d'ancienneté prédéfinie pour l'exponentiation. En algèbre, l'exponentiation doit être calculée avant multiplication / division. Ainsi, nous créons un ordre d'exécution personnalisé en plaçant l'exponentiation plus haut que la multiplication.

    Cette annonce:

     precedencegroup ExponentPrecedence { higherThan: MultiplicationPrecedence associativity: right } infix operator ^^: ExponentPrecedence 


    Voici l'implémentation:

     func ^^(base: Int, exponent: Int) -> Int { let l = Double(base) let r = Double(exponent) let p = pow(l, r) return Int(p) } 



    Question 3
    Le code suivant définit la structure de Pizza et le protocole Pizzeria avec une extension pour l'implémentation par défaut de la méthode makeMargherita () :

     struct Pizza { let ingredients: [String] } protocol Pizzeria { func makePizza(_ ingredients: [String]) -> Pizza func makeMargherita() -> Pizza } extension Pizzeria { func makeMargherita() -> Pizza { return makePizza(["tomato", "mozzarella"]) } } 


    Nous définissons maintenant le restaurant Lombardis :

     struct Lombardis: Pizzeria { func makePizza(_ ingredients: [String]) -> Pizza { return Pizza(ingredients: ingredients) } func makeMargherita() -> Pizza { return makePizza(["tomato", "basil", "mozzarella"]) } } 

    Le code suivant crée deux instances de Lombardis . Lequel d'entre eux fait de la margarita au basilic?

     let lombardis1: Pizzeria = Lombardis() let lombardis2: Lombardis = Lombardis() lombardis1.makeMargherita() lombardis2.makeMargherita() 


    la réponse
    Dans les deux. Le protocole Pizzeria déclare la méthode makeMargherita () et fournit une implémentation par défaut. L'implémentation de Lombardis remplace la méthode par défaut. Puisque nous avons déclaré la méthode dans le protocole à deux endroits, l'implémentation correcte sera appelée.

    Mais que se passe-t-il si le protocole ne déclare pas la méthode makeMargherita () et que l'extension fournit toujours l'implémentation par défaut, comme ceci:

     protocol Pizzeria { func makePizza(_ ingredients: [String]) -> Pizza } extension Pizzeria { func makeMargherita() -> Pizza { return makePizza(["tomato", "mozzarella"]) } } 

    Dans ce cas, seul lombardis2 aurait une pizza au basilic, tandis que lombardis1 n'aurait pas de pizza, car il utiliserait la méthode définie en extension.


    Question 4
    Le code suivant ne se compile pas. Pouvez-vous expliquer ce qui ne va pas avec lui? Suggérez des solutions au problème.

     struct Kitten { } func showKitten(kitten: Kitten?) { guard let k = kitten else { print("There is no kitten") } print(k) } 

    Astuce: Il existe trois façons de corriger l'erreur.


    la réponse
    Le bloc else de l'instruction guard nécessite une option de sortie, soit en utilisant return , en lançant une exception ou en appelant @noreturn . La solution la plus simple consiste à ajouter une déclaration de retour .

     func showKitten(kitten: Kitten?) { guard let k = kitten else { print("There is no kitten") return } print(k) } 

    Cette solution lèvera une exception:

     enum KittenError: Error { case NoKitten } struct Kitten { } func showKitten(kitten: Kitten?) throws { guard let k = kitten else { print("There is no kitten") throw KittenError.NoKitten } print(k) } try showKitten(kitten: nil) 


    Enfin, voici l'appel Ă  fatalError () , la fonction @noreturn .

     struct Kitten { } func showKitten(kitten: Kitten?) { guard let k = kitten else { print("There is no kitten") fatalError() } print(k) } 




    questions orales
    Question 1
    Les fermetures sont-elles un type de référence ou un type de valeur?

    la réponse
    Les fermetures sont un type de référence. Si vous affectez une fermeture à une variable, puis la copiez dans une autre variable, vous copiez le lien vers la même fermeture et sa liste de capture.


    Question 2
    Vous utilisez le type UInt pour stocker un entier non signé. Il implémente un initialiseur pour convertir à partir d'un tout avec un signe:

     init(_ value: Int) 

    Toutefois, le code suivant ne compile pas si vous spécifiez une valeur négative:

     let myNegative = UInt(-1) 

    Les entiers signés, par définition, ne peuvent pas être négatifs. Cependant, il est possible d'utiliser la représentation d'un nombre négatif en mémoire pour le traduire en non signé.

    Comment puis-je convertir un entier négatif en UInt tout en conservant sa représentation en mémoire?

    la réponse
    Il existe un initialiseur pour cela:

     UInt(bitPattern: Int) 


    Et utilisez:

     let myNegative = UInt(bitPattern: -1) 



    Question 3
    Décrire les références circulaires dans Swift? Comment peuvent-ils être réparés?

    la réponse
    Les références circulaires se produisent lorsque deux instances contiennent une référence forte l'une à l'autre, ce qui conduit à une fuite de mémoire car aucune de ces instances ne peut être libérée. Une instance ne peut pas être libérée tant qu'il y a encore des références fortes, mais une instance contient l'autre.

    Cela peut être résolu en remplaçant le lien d'un côté en spécifiant le mot clé faible ou sans propriétaire .


    Question 4
    Swift vous permet de créer des énumérations récursives. Voici un exemple d'une telle énumération qui contient une variante de nœud avec deux types associatifs, T et List:

     enum List<T> { case node(T, List<T>) } 

    Il y aura une erreur de compilation. Qu'avons-nous manqué?

    la réponse
    Nous avons oublié le mot-clé indirect , qui permet des options d'énumération récursives similaires:

     enum List<T> { indirect case node(T, List<T>) } 




Source: https://habr.com/ru/post/fr449410/


All Articles