Le struct e le classi sono due dei principali elementi costitutivi della programmazione in Swift. Entrambe sono un modo per raggruppare dati e funzioni correlate in unità di funzionalità autonome. E quando definisci una struct o una classe, stai definendo un nuovo tipo da usare in Swift, proprio come hai usato i tipi con cui hai già lavorato, come Int e String.
Ci sono molte somiglianze tra struct e classi in Swift. Tra le altre cose, entrambe sono in grado di memorizzare valori in proprietà e di offrire funzionalità tramite l'uso di metodi.
Ognuna fornisce anche funzionalità aggiuntive, che esulano da questo esercizio.
Sia le struct sia le classi si definiscono più o meno allo stesso modo. Si inizia con la parola chiave appropriata seguita dal nome del tipo che si sta definendo, poi segue il corpo della struct o della classe, racchiuso tra parentesi graffe.
Il corpo può essere formato da proprietà memorizzate, che si definiscono e si comportano proprio come normali costanti o variabili.
struct CharacterStats {
var health = 0.0
var speed = 0
var strength = 0
}
class GameCharacter {
var stats = CharacterStats()
var characterClass: String?
var name: String?
var active = false
let id = makeRandomID()
}
Come detto sopra, definire una struct o una classe significa semplicemente definire un nuovo tipo. È soltanto il progetto di come saranno i valori di quel tipo, ma non crea davvero alcun valore di quel tipo con cui lavorare.
Per creare un'istanza di quel tipo, devi scrivere il nome del tipo seguito da una coppia di parentesi tonde (()).
let someStats = CharacterStats()
let someCharacter = GameCharacter()
Questo creerà valori di questi tipi, con le proprietà popolate con i valori predefiniti forniti nella definizione. Nota che nei casi opzionali come la proprietà name di GameCharacter, se non viene fornito un valore, la proprietà sarà nil per impostazione predefinita, proprio come quando si definiscono normali tipi opzionali in cui non viene fornito subito un valore.
Con le struct, Swift fornisce automaticamente qualcosa chiamato inizializzatore memberwise, in cui i valori per le proprietà della struct possono essere forniti tra parentesi tonde, sovrascrivendo i valori predefiniti della definizione.
let differentStats = CharacterStats(health: 100.0, speed: 6, strength: 18)
Si può accedere alle proprietà di struct e classi usando la notazione a punto, in cui il nome del valore è seguito da un punto (.) e dal nome della proprietà. Se una proprietà di una struct o di una classe ha proprietà proprie, questa notazione a punto può essere usata per accedere anche a queste proprietà annidate.
Questa notazione può essere usata sia per recuperare il valore della proprietà sia, dove consentito, per modificarlo.
someStats.health
// => 0
someCharacter.name
// => nil
someStats.health = 87.3
someStats.health
// => 87.3
someCharacter.name = "Luther"
someCharacter.name
// => "Luther"
someCharacter.id = "new id"
// Error: Cannot assign to property: 'id' is a 'let' constant
Come le proprietà, che memorizzano dati nelle struct e nelle classi, puoi anche definire metodi, che memorizzano funzioni nella struct o nella classe.
I metodi si definiscono allo stesso modo di una normale funzione, solo all'interno del corpo della struct o della classe. Nota che se una funzione cambia il valore di una proprietà in una struct, deve essere preceduta dalla parola chiave mutating. Inoltre, se una proprietà può essere modificata da un metodo, quella proprietà deve essere definita con var invece che con let, proprio come le normali variabili.
struct CharacterStats {
var health = 0.0
var speed = 0
var strength = 0
mutating func takeHit(_ damage: Double) {
health = max(0.0, health - damage)
}
func canLift(_ weight: Int) -> Bool {
weight < strength * 100
}
}
class GameCharacter {
var stats = CharacterStats()
var characterClass: String?
var name: String?
var active = false
let id: String = makeRandomID()
func takesDamage(_ damage: Double) {
stats.takeHit(damage)
if stats.health <= 0 {
active = false
}
}
func sayName() -> String {
return "My name is \(name ?? "no one"), my class is \(characterClass ?? "undetermined")"
}
func lift(_ weight: Int) -> String {
if stats.canLift(weight) {
return "No problem!"
} else {
return "Ooof! No way."
}
}
}
Questi metodi possono essere chiamati con la notazione a punto, proprio come le proprietà.
var myChar = GameCharacter()
myChar.stats = CharacterStats(health: 72.8, speed: 19, strength: 6)
myChar.active = true
myChar.lift(750)
// => "Ooof! No way."
myChar.takesDamage(80)
myChar.active
// => false
Ogni istanza di una struct o di una classe ha una proprietà implicita chiamata self, che si riferisce all'istanza stessa.
L'uso più comune di self è disambiguare tra una proprietà dell'istanza e un parametro che ha lo stesso nome:
struct Position {
var x = 0
mutating func move(x: Int) {
// Here, 'self.x' refers to the struct's property,
// while 'x' refers to the parameter passed to the method.
self.x = x
}
}
Anche i tipi di Swift forniscono una parola chiave Self con l'iniziale maiuscola, che si riferisce al tipo dell'istanza invece che all'istanza stessa.
In questo esercizio simulerai un sistema informatico basato su finestre. Creerai alcune finestre che si possono spostare e ridimensionare e che mostrano il loro contenuto. L'immagine seguente è rappresentativa dei valori con cui lavorerai qui sotto.
<--------------------- screenSize.width --------------------->
^ ╔════════════════════════════════════════════════════════════╗
| ║ ║
| ║ position.x,_ ║
| ║ position.y \ ║
| ║ \<----- size.width -----> ║
| ║ ^ *──────────────────────┐ ║
| ║ | │ title │ ║
| ║ | ├──────────────────────┤ ║
screenSize.height ║ | │ │ ║
| ║ size.height │ │ ║
| ║ | │ contents │ ║
| ║ | │ │ ║
| ║ | │ │ ║
| ║ v └──────────────────────┘ ║
| ║ ║
| ║ ║
v ╚════════════════════════════════════════════════════════════╝
Definisci una struct di nome Size con due proprietà Int, width e height, che memorizzano rispettivamente la larghezza e l'altezza attuali della finestra.
La larghezza e l'altezza iniziali devono essere rispettivamente 80 e 60.
Includi un metodo resize(newWidth:newHeight:) che accetta come parametri la nuova larghezza e la nuova altezza e modifica le proprietà per riflettere le nuove dimensioni.
let size1080x764 = Size(width: 1080, height: 764)
// Returns Size
var size1200x800 = size1080x764
// Returns Size
size1200x800.resize(newWidth: 1200, newHeight: 800)
size1200x800.height
// Returns 800
Definisci una struct di nome Position con due proprietà Int, x e y, che memorizzano rispettivamente la posizione orizzontale e verticale attuali dell'angolo in alto a sinistra della finestra.
I valori iniziali di x e y devono essere entrambi 0. La posizione (0, 0) è l'angolo in alto a sinistra dello schermo: i valori di x aumentano man mano che ci si sposta verso destra e i valori di y aumentano man mano che ci si sposta verso il basso.
Includi un metodo moveTo(newX:newY:) che accetta come parametri la nuova x e la nuova y e modifica le proprietà per riflettere la nuova posizione.
var point = Position(x: 10, y: 20)
// Returns Position
point.moveTo(newX: 100, newY: -100)
point.y
// Returns -100
Definisci una classe Window con le seguenti proprietà:
title : String, il valore iniziale è "New Window"screenSize : Size, valore costante con width = 800 e height = 600size : Size, il valore iniziale è quello predefinito della struct Size
position : Position, il valore iniziale è quello predefinito della struct Position
contents : String?, il valore iniziale è nil
Definisci anche un inizializzatore vuoto per la classe.
let window = Window()
window.title
// Returns "New Window"
resize(to:) : (Size) -> (). Questo metodo accetta come input una struct Size e prova a ridimensionare la finestra alle dimensioni specificate. Tuttavia, le nuove dimensioni non possono superare certi limiti.x = 400, y = 300 e viene richiesto un ridimensionamento a height = 400, width = 300, la finestra verrà ridimensionata a height = 300, width = 300, perché lo schermo non è abbastanza grande nella direzione y per soddisfare completamente la richiesta.move(to:) : (Position) -> (). È simile a resize(to:), ma questo metodo regola la posizione della finestra al valore richiesto, non le dimensioni. Come per resize, la nuova posizione non può superare certi limiti.x che per y.x = 250, y = 100 e viene richiesto uno spostamento a x = 600, y = 200, la finestra verrà spostata a x = 550, y = 200, perché lo schermo non è abbastanza grande nella direzione x per soddisfare completamente la richiesta.update(title:) : (String) -> (). Questo metodo imposta la proprietà title al valore della stringa passata.update(text:) : (String?) -> (). Questo metodo imposta la proprietà contents al valore della stringa opzionale passata.display() : () -> String. Questo metodo restituisce una stringa che descrive lo stato attuale della finestra. Ad esempio, se la finestra ha il title "My First Window" con posizione: x = 10, y = 100; dimensioni: width = 200, height = 150; e contents: "I 😍 my window", dovrebbe restituire la stringa: "My First Window\nPosition: (10, 100), Size: (200 x 150)\nI 😍 my window\n". Se contents è nil, l'ultima riga dovrebbe essere "[This window intentionally left blank]"Il sistema a finestre dovrebbe avere un inizializzatore che permetta di creare la finestra con input personalizzati.
Crea un altro inizializzatore per la classe Window.
L'inizializzatore dovrebbe accettare i seguenti parametri: title, contents e due parametri opzionali, size e position.
I parametri size e position dovrebbero avere come valori predefiniti quelli predefiniti delle struct Size e Position.
let window = Window(title: "My First Window", contents: "I 😍 my window")
window.display()
// Returns "My First Window\nPosition: (0, 0), Size: (80 x 60)\nI 😍 my window\n"
let window2 = Window(title: "My Second Window", contents: "I 😍 my window", size: Size(width: 200, height: 150), position: Position(x: 10, y: 100))
window2.display()
// Returns "My Second Window\nPosition: (10, 100), Size: (200 x 150)\nI 😍 my window\n"
Iscriviti a Exercism per imparare e padroneggiare Swift con 35 concetti116 esercizi e il mentoring di persone reali, tutto gratis.