前回は switch文 について書きました。
今回は class について書いていこうと思いますが、 class をやる前に 構造体 を理解した上でこの記事を見た方が理解しやすいと思います。
構造体、プロパティ、イニシャライザ、メソッドなどについてこちらの記事で紹介しています。
Contents
classとは
class とは、構造体と同様に複数のプロパティやメソッドをまとめて保持できるものです。
class は設計図と表現されていることが多いかと思いますが、必要なときに インスタンス化( 設計図から実体に ) することで使用することができるようになります。
class の概要
- 構造体と似ているが、構造体は 値型 であるのに対し、class は 参照型 という違いがある。
- 構造体や列挙型とは異なり 継承 することができる。
- 構造体では、イニシャライザを定義しなくてもデフォルトのメンバーワイズイニシャライザで初期化されるが、class ではプロパティの初期値が無い状態で、イニシャライザが無いとエラーになる。
- クラスやクラスの継承はオブジェクト指向型の言語ではとても重要。
※ 継承とは、すでに定義してある class を引き継いで、新しい class を定義する仕組み。
classの書式
class クラス名{
プロパティ定義
イニシャライザ定義
メソッド定義
}
class キーワードを書いた後に、クラス名 を書きます。
{ } の中にプロパティ、イニシャライザ、メソッドを定義していきます。
classの定義例
class Fellow{
//プロパティ
let name:String
var level:Int
var profession:String?
//イニシャライザ
init(name:String, level:Int, profession: String?){
self.name = name
self.level = level
self.profession = profession
}
//メソッド
func printFellow(){
print("\(name)のステータス")
print("LV:\(level)")
print("職業:\(profession ?? "無職")")
}
}
//インスタンス化
var arusu = Fellow(name: "アルス", level: 20, profession: "バトルマスター")
//実行
arusu.printFellow()
上のコードについて解説↓
class名
class Fellow{
}
class の名前は Swift で推奨されているアッパーキャメルケースで書きます。
命名規則についてはこちらの記事で紹介しています。
プロパティ
class Fellow{
//プロパティ
let name:String
var level:Int
var profession:String?
}
Fellow クラスのプロパティには、変数を 3 つ宣言していますが初期値は定義していません。
イニシャライザ
class Fellow{
//プロパティ
var name:String
var level:Int
var profession:String?
//イニシャライザ
init(name:String, level:Int, profession: String?){
self.name = name
self.level = level
self.profession = profession
}
}
イニシャライザの引数を 3 つ( プロパティの数だけ )書いて、後でインスタンス化するときに全てのプロパティに初期値を入力して初期化します。
このとき、Optional型 のプロパティがあった場合は、初期値を入力しなくてもデフォルトで nil が代入されるのでイニシャライザに書くことは必須ではありません。
※ 構造体では、イニシャライザを定義しなくてもデフォルトのメンバーワイズイニシャライザで初期化されるが、class ではプロパティの初期値が無い状態で、イニシャライザが無いとエラーになります。
メソッド
class Fellow{
//プロパティ
var name:String
var level:Int
var profession:String?
//イニシャライザ
init(name:String, level:Int, profession: String?){
self.name = name
self.level = level
self.profession = profession
}
//メソッド
func printFellow(){
print("\(name)のステータス")
print("LV:\(level)")
print("職業:\(profession ?? "無職")")
}
}
インスタンス化したときの、各プロパティの値を出力するメソッドを定義しました。
インスタンス化
class Fellow{
//プロパティ
var name:String
var level:Int
var profession:String?
//イニシャライザ
init(name:String, level:Int, profession: String?){
self.name = name
self.level = level
self.profession = profession
}
//メソッド
func printFellow(){
print("\(name)のステータス")
print("LV:\(level)")
print("職業:\(profession ?? "無職")")
}
}
//インスタンス化
var arusu = Fellow(name: "アルス", level: 20, profession: "バトルマスター")
Fellow クラスの引数に初期値を入力して、変数 arusu に代入します。
これで変数 arusu が Fellow クラスのインスタンスとして使えるようになりました。
インスタンスのメソッドを実行
入力した初期値がプロパティに反映されているか確認したいので、インスタンスのメソッドを実行します。
class Fellow{
//プロパティ
var name:String
var level:Int
var profession:String?
//イニシャライザ
init(name:String, level:Int, profession: String?){
self.name = name
self.level = level
self.profession = profession
}
//メソッド
func printFellow(){
print("\(name)のステータス")
print("LV:\(level)")
print("職業:\(profession ?? "無職")")
}
}
//インスタンス化
var arusu = Fellow(name: "アルス", level: 20, profession: "バトルマスター")
//メソッドを実行
arusu.printFellow()
//実行結果
/*
アルスのステータス
LV:20
職業:バトルマスター
*/
実行した結果、インスタンス化するときに引数に入力した値がきちんと反映されているので、変数 arusu が Fellow クラスのインスタンスとして振る舞っている事がわかります。
classは参照型
class は構造体の値型とは違い、参照型 です。
値型と参照型の違い
値型はデータ自体がコピーされるのに対し、
参照型はデータ自体をコピーするのではなく、参照先の「場所」( データが格納されている場所 )をコピーします。
なので同じインスタンスが 2 つあった場合、片方の値を変更するともう片方の値も同じになります。
~ ~ ~
//インスタンス化
var arusu = Fellow(name: "アルス", level: 20, profession: "バトルマスター")
//メソッドを実行
arusu.printFellow()
//classは参照型
//クラスFellowのインスタンスである変数arusuを、変数rotoにコピー
var roto = arusu
//プロパティの値を変更
roto.name = "ロト"
//rotoのメソッドを実行
roto.printFellow()
//arusuのメソッドを実行
arusu.printFellow()
//実行結果
/*
アルスのステータス
LV:20
職業:バトルマスター
ロトのステータス
LV:20
職業:バトルマスター
ロトのステータス
LV:20
職業:バトルマスター
*/
Fellow クラスのインスタンスである変数 arusu を変数 roto にコピーしてインスタンスを 2 つにします。
インスタンス roto の方だけ、プロパティ name の値を変更して、両方のインスタンスからメソッドを呼び実行してみます。
実行結果を見ると、インスタンス roto の方は name の値を変更したので当然変更されていますが、
インスタンス arusu の方は変更していないのに変更されています。
つまり同じインスタンスを複数作っても、インスタンスのデータ自体は 1 つしかなく、データが格納されている「場所」 をコピーしているということがわかると思います。
classの継承
class の継承とは、新しく class を定義するときにすでに定義されている class のプロパティやメソッドなどを引き継ぐことができる仕組みです。
継承元の定義を利用できるため、新しい class を定義する際に、継承することでコードの量を減らせるなど労力を小さくできるメリットがあります。
継承元になる class をスーパークラス( 親クラス )、継承先の class をサブクラス( 子クラス )と言います。
class を継承することでできること
- サブクラスは、スーパークラスのプロパティやメソッドを使用できる。
- サブクラスでは、プロパティやイニシャライザ、メソッドを新しく定義できる。
- スーパークラスのスーパークラス、サブクラスのサブクラスとなっていっても継承できる。
- サブクラスでは、継承したプロパティやメソッドなどを オーバーライド ( override )する事ができる。
※ オーバーライドとは、スーパークラスのプロパティやメソッドの 上書き をすることです。オーバーロードというものもありますが、全く違うものなので注意。
継承の書式
サブクラス名 : スーパークラス名 {
プロパティ定義
イニシャライザ定義
メソッド定義
}
サブクラスの後に : を書いて継承したいクラス名を書きます。
クラス継承の例
上の例で定義した Fellow クラスを使って新しく定義するクラスに継承します。
class Fellow{
~ ~ ~
}
~ ~ ~
//classの継承
class Monster: Fellow{
//プロパティ
var skill:String
//イニシャライザ
init(name: String, level: Int, skill: String){
self.skill = skill
//スーパークラスのイニシャライザを流用
super.init(name: name, level: level, profession: nil)
}
//スーパークラスのメソッドをオーバーライド
override func printFellow() {
print("\(name)のステータス")
print("LV:\(level)")
print("特技:\(skill)")
}
}
//インスタンス化してメソッド実行
var suraimu = Monster(name: "スライム", level: 18, skill: "メラ")
suraimu.printFellow()
Fellow クラスを継承した、Monster クラスを定義しています。
スーパークラスが Fellow 、サブクラスが Monster となります。
これで、Fellow クラスのプロパティやメソッドなどが Monster クラスでも使えるようになりました。
Monster クラスのプロパティには、新しく変数を 1 つ宣言しています。
var skill:String
次にイニシャライザでプロパティの初期化をしていきます。
引数は name 、level、skill の 3 つ用意します。
init(name: String, level: Int, skill: String){
}
まず、新しく宣言したプロパティを先に初期化します。
init(name: String, level: Int, skill: String){
self.skill = skill
}
プロパティ skill をイニシャライザと紐付けました。
残りの name と level ですが、スーパークラスですでに定義してあるイニシャライザを使いたいと思います。
init(name: String, level: Int, skill: String){
self.skill = skill
super.init(name: name, level: level, profession: nil)
}
スーパークラス Fellow で定義してあるイニシャライザの引数 name と level をサブクラス Monster で定義したイニシャラザの引数 name と level を紐付けます。
super. キーワードを頭に付けることで、スーパークラスのイニシャライザを使うことができます。
Monster クラスでは、引数 profession は使わないので nil としています。
これで引数 name、level、skill の 3 つに値を入力することで、Monster クラスをインスタンス化することができます。
次に Fellow クラスにすでに定義してあるメソッドを使って、引数に入力された値がプロパティに反映されているか確認します。
Fellow クラスのメソッド printFellow は プロパティの name、level、profession に代入されている値を出力するだけのメソッドですが、そのまま使うとMonster クラスでは必要ない profession の値も出力もしてしまうので、Monster クラスで定義したプロパティ skill の値を出力するようにメソッドを変更します。
override func printFellow() {
print("\(name)のステータス")
print("LV:\(level)")
print("特技:\(skill)")
}
override キーワードの後にスーパークラスで定義済みのメソッドを書くと、処理を上書きすることができます。
※ オーバーロードと間違わないように注意。
インスタンス化してメソッドを実行します。
var suraimu = Monster(name: "スライム", level: 18, skill: "メラ")
suraimu.printFellow()
//実行結果
/*
スライムのステータス
LV:18
特技:メラ
*/
name、level、skill の値を出力することができました。
Monster クラスで定義していないプロパティ name や level にアクセス出来ているので、スーパークラスのプロパティが継承されているということがわかると思います。
全コード
class Fellow{
//プロパティ
var name:String
var level:Int
var profession:String?
//イニシャライザ
init(name:String, level:Int, profession: String?){
self.name = name
self.level = level
self.profession = profession
}
//メソッド
func printFellow(){
print("\(name)のステータス")
print("LV:\(level)")
print("職業:\(profession ?? "無職")")
}
}
//インスタンス化
var arusu = Fellow(name: "アルス", level: 20, profession: "バトルマスター")
//メソッドを実行
arusu.printFellow()
//classは参照型
//Fellowのインスタンスである変数arusuを、変数rotoに代入して再度インスタンス化
var roto = arusu
//プロパティの値を変更
roto.name = "ロト"
//rotoのメソッドを実行
roto.printFellow()
//arusuのメソッドを実行
arusu.printFellow()
//実行結果
/*
アルスのステータス
LV:20
職業:バトルマスター
ロトのステータス
LV:20
職業:バトルマスター
ロトのステータス
LV:20
職業:バトルマスター
*/
//classの継承
class Monster: Fellow{
//プロパティ
var skill:String
//イニシャライザ
init(name: String, level: Int, skill: String){
self.skill = skill
//スーパークラスのイニシャライザを流用
super.init(name: name, level: level, profession: nil)
}
//スーパークラスのメソッドをオーバーライド
override func printFellow() {
print("\(name)のステータス")
print("LV:\(level)")
print("特技:\(skill)")
}
}
//インスタンス化してメソッド実行
var suraimu = Monster(name: "スライム", level: 18, skill: "メラ")
suraimu.printFellow()
//実行結果
/*
スライムのステータス
LV:18
特技:メラ
*/
以上 class についてでした。
次回は 列挙型 について書いていこうと思います。
実行環境
version |
---|
Xcode 14.2 (14C18) |
Swift 5.2.4 |
公式ドキュメント
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/