Swiftを学ぶ上で、構造体とクラスの違いは必ず憶えておかなければならない重要な点です。この2つの違いは実は意外と掴みどころがないものでもあります。
「構造体は値型、クラスは参照型」と意味不明な定義をされても、イメージが浮かばない方は多いはず。 私もその1人でした。PCファイルのコピーとショートカットをイメージすれば確実に理解できます!
構造体は新しいファイルを完全にコピーして別の場所に作成する感じ。クラスは元のファイルのショートカットを作るだけなので、本体は1つ。これだけで全て納得できました。
この記事では、Swiftの構造体とクラスの違いを明快に解説し、あなたのプログラミングスキルの向上を支援します。
構造体とクラスの基本的な違い
構造体は値型であり、インスタンスのコピーが作成されるたびに新しいメモリ領域が割り当てられます。これは、データが複製され、それぞれのインスタンスが独立していることを意味します。
対照的に、クラスは参照型であり、インスタンス間でメモリ上の同じ場所への参照が共有されます。この性質により、クラスを使用すると、一箇所での変更がすべての参照に影響を及ぼすことになります。
「参照型」とか「参照が共有」という言葉を初めて聞いて分かりますか?
私はまったく分かりませんでした。
私は次のようにイメージして理解しています。
構造体とクラスの違いは、Windowsでファイルを扱う時の「コピー」と「ショートカット」に似ています。
構造体はファイルの「コピー」のようなもので、データを別の場所に全く新しい形で複製します。それぞれが独立しているため、一つを変えても他のコピーには影響しません。一
一方で、クラスは「ショートカット」のようなものです。ショートカットを通じて元のファイルを操作するように、クラスのインスタンス(オブジェクト)を通じてデータを共有し、そのデータに対する変更が、そのショートカット経由でアクセスしているすべての場所に反映されます。だから、クラスを使うと、一箇所での変更が共有されるデータ全体に影響します。
ショートカットが本体そのものであることは、ファイル管理をわかっている人ならすぐに理解できるはずです。
クラスでインスタンスを作るのは、ファイルからそのショートカットを作るようなものです。
構造体とは
Swiftの構造体(Structures)は、関連する値のグループを一つの複合的なデータ型として扱うための方法です。
またそのデータをカプセル化するための機能があります。
カプセル化とは、データをオブジェクトの内部に隠して、容易に値を変更できなくするための入れ物として機能します。
関連するデータをまとめたり、独自のデータ型を作成するために使われます。
構造体の構文
構造体はプロパティを定義すれば作成できます。
必要に応じてメソッドを定義したりプロパティの初期化をおこないます。
実際に構造体を使うには最初にインスタンス化する必要があります。
インスタンス化できていれば構造体を使用することができます。
構造体を作成する手順は次のとおりです。
- 構造体の型定義
- プロパティ定義
- 初期化(任意)
- メソッド定義(任意)
- 構造体のインスタンス化
- インスタンスの使用
構造体の構文は次のとおりになります。
//構造体の型定義
struct 構造体名 {
//プロパティ定義
var プロパティ名1: 型
var プロパティ名2: 型
// 初期化
init(プロパティ名1: 型, プロパティ名2: 型) {
// イニシャライザの処理
self.プロパティ名1: 型 = プロパティ名1
self.プロパティ名2: 型 = プロパティ名2
}
//メソッド定義
func メソッド名(引数名: 型) -> 戻り値の型 {
//処理
return 戻り値
}
}
//構造体のインスタンス化
var 変数名1 = 構造体名(プロパティ名1: 初期値1, プロパティ名2: 初期値2)
//プロパティの値を変更
変数名1.プロパティ名1 = 変更値1
変数名1.プロパティ名2 = 変更値2
//インスタンスの使用
変数名1.メソッド名()
構造体の場合、init()による初期化は必須ではありません。クラスの対比で入っています。入っていてもエラーにはなりません。
クラス、構造体、列挙型を構成する要素の一つで、型もしくは型のインスタンスに紐付いた値(属性を指します。
プロパティはインスタンス化がされる時かその前に初期化されていなければなりません。
メソッドとは特定のクラス、構造体、または列挙型に紐づけられたコードブロックです。
一方関数は特定のクラス、構造体、または列挙型に紐づけられていない独立したコードブロックです。
インスタンスとはクラス、構造体、または列挙型などの特定の型から生成された具体的なオブジェクトのこと
インスタンスを作成すると、当該クラス、構造体、または列挙型のコピーが作成され、利用できるようになります。
構造体のサンプルコード
構文と照らし合わせてコードを見て下さい。
できればSwiftのPlaygroudでコードを入力してエラーが発生しないことと結果が合っていることを確認していただければ、理解が深まります。
メソッドがない構造体
サンプルコード ①
//構造体の型定義
struct Person {
//プロパティ定義と初期値入力
var name: String = ""
var age: Int = 0
}
//構造体のインスタンス化
var person = Person()
// プロパティの値を変更
person.name = "神戸一郎"
person.age = 50
// 結果の出力
print("\(person.name)さんは\(person.age)歳です")
init()
で初期化はせず、プロパティを定義するときに一緒に初期化をしています。
構造体の定義の中で初期化しているのでインスタンス化では初期化していません。
次はインスタンス化のときに初期化した例です。
同じ結果になります。
サンプルコード ②
//構造体の型定義
struct Person {
//プロパティ定義
var name: String
var age: Int
}
//構造体のインスタンス化と初期値入力
var person = Person(name: "",age: 0)
// プロパティの値を変更
person.name = "神戸一郎"
person.age = 50
// 結果の出力
print("\(person.name)さんは\(person.age)歳です")
次は出力したい値をインスタンス化のときに入力した例になります。
同じ結果になります。
サンプルコード ③
//構造体の型義
struct Person {
//プロパティ定義
var name: String
var age: Int
}
// 構造体のインスタンス化と初期値入力
var person = Person(name: "神戸一郎", age: 50)
// 結果の出力
print("\(person.name)さんは\(person.age)歳です")
出力結果
神戸一郎さんは50歳です
メソッドがある構造体
サンプルコード ④
// 構造体の型定義
struct Person {
//プロパティ定義と初期値入力
var name: String = ""
var age: Int = 0
// メソッド定義
func introduceSelf() {
print("\(name)さんは\(age)歳です")
}
}
// 構造体のインスタンス化
var person = Person()
// プロパティの値を変更
person.name = "神戸一郎"
person.age = 50
//インスタンスの使用
person.introduceSelf()
こちらでも結果は同じです。
サンプルコード ⑤
// 構造体の型定義
struct Person {
//プロパティ定義
var name: String
var age: Int
// メソッド定義
func introduceSelf() {
print("\(name)さんは\(age)歳です")
}
}
// インスタンスを作成しながら初期データ入力
var person = Person(name: "神戸一郎", age: 50)
// 自己紹介メソッドの呼び出し
person.introduceSelf()
こちらでも結果は同じです。
サンプルコード ⑥
// 構造体の型定義
struct Person {
//プロパティ定義
var name: String = ""
var age: Int = 0
// メソッド定義
func introduceSelf() -> String{
"\(name)さんは\(age)歳です"
}
}
// インスタンスを作成
var person = Person()
// プロパティの値を変更
person.name = "神戸一郎"
person.age = 50
// 自己紹介メソッドの呼び出し
print(person.introduceSelf())
出力結果
神戸一郎さんは50歳です
init メソッドを使用
サンプルコード ⑦
// 構造体の型定義
struct Person {
//プロパティ定義
var name: String
var age: Int
// メソッド定義
func introduceSelf() {
print("\(name)さんは\(age)歳です")
}
// 初期化
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
// 構造体のインスタンス化と初期値入力
var person = Person(name: "神戸一郎", age: 50)
// 自己紹介メソッドの呼び出し
person.introduceSelf()
出力結果
神戸一郎さんは50歳です
selfキーワードはクラス、構造体、および列挙型のメソッド内で、インスタンス自身を参照するために使用されます。
クラスとは
クラスは参照によってデータを管理します。クラスのインスタンスを生成すると、データを格納するメモリ領域が1回だけ確保されます。その領域への参照(リンク、アドレス)がコピーされる形でインスタンスが渡され処理されます。
この参照方式のため、複数の変数から同じインスタンスにアクセスできます。1つのデータを共有する形になります。また、継承が可能で、子クラスが親クラスの機能やプロパティを受け継げます。
難しく書くと以上のようになりますが、要はクラスはショートカットなので本体とデータ共有するというよりも、本体そのものです。
クラスの構文
クラスを作成する手順は次のとおりです。
- クラスの型定義
- プロパティ定義
- 初期化(任意)
- メソッド定義(任意)
- クラスのインスタンス化
- インスタンスの使用
構造体の「stract」を「class」に変えればいいだけですが、イニシャライズしていないとエラーになります。
//クラスの型定義
class 構造体名 {
//プロパティ定義
var プロパティ名1: 型
var プロパティ名2: 型
// 初期化(コンストラクタ)
init(プロパティ名1: 型, プロパティ名2: 型) {
// イニシャライザの処理
self.プロパティ名1: 型 = プロパティ名1
self.プロパティ名2: 型 = プロパティ名2
}
//メソッド定義
func メソッド名(引数名: 型) -> 戻り値の型 {
//処理
return 戻り値
}
}
//クラスのインスタンス化
var 変数名1 = 構造体名(プロパティ名1: 初期値1, プロパティ名2: 初期値2)
//プロパティの値を変更
変数名1.プロパティ名1 = 変更値1
変数名1.プロパティ名2 = 変更値2
//インスタンスの使用
変数名1.メソッド名()
クラスのサンプルコード
クラスの基本形
サンプルコード ⑧
//クラスの定義
class Person {
var name: String
var age: Int
// 自己紹介を行うメソッド
func introduceSelf() {
print("\(name)です。\(age)歳です。")
}
// イニシャライズ
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
// インスタンスを作成しながら初期データ入力、(varを使用して宣言)
var person = Person(name: "神戸一郎", age: 50)
// 自己紹介メソッドの呼び出し
person.introduceSelf()
出力結果
神戸一郎さんは50歳です
構造体とクラスで出力結果が異なるケース
サンプルコード ⑨(構造体)
// 構造体の型定義
struct Person {
//プロパティ定義
var name: String
var age: Int
// メソッド定義
func introduceSelf() {
print("\(name)さんは\(age)歳です。")
}
// 初期化
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
// 構造体のインスタンス化と初期値入力
var person1 = Person(name: "神戸一郎", age: 50)
var person2 = person1 //person1のコピーperson2を生成
person2.name = "博多和恵"
person2.age = 35
// 自己紹介メソッドの呼び出し
person1.introduceSelf()
person2.introduceSelf()
出力結果
神戸一郎さんは50歳です。
博多和恵さんは35歳です。
サンプルコード ⑩(クラス)
// クラスの型定義
class Person {
//プロパティ定義
var name: String
var age: Int
// メソッド定義
func introduceSelf() {
print("\(name)さんは\(age)歳です。")
}
// 初期化
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
// クラスのインスタンス化と初期値入力
var person1 = Person(name: "神戸一郎", age: 50)
var person2 = person1
person2.name = "博多和恵" //person1のショートカットperson2を生成
person2.age = 35
// 自己紹介メソッドの呼び出し
person1.introduceSelf()
person2.introduceSelf()
出力結果
博多和恵さんは35歳です。
博多和恵さんは35歳です。
サンプルコード⑨とサンプルコード⑩は、コメント以外で変更したのは2行目のstruct
をclass
に変更しただけです。
構造体の例であるサンプルコード⑨の21行目var person2 = person1
はperson1
のコピーperson2
を生成します。
一方、クラスの例であるサンプルコード⑨の21行目var person2 = person1
はperson1
の参照がperson2
に渡されます。
「person1
の参照がperson2
に渡されます。」どんな意味か分かりますか?
私は全く理解不能でした。
私は構造体と対比して次のように理解しています。
「person1
のショートカットperson2
を生成します。」
構造体がコピーでクラスがショートカットを作ると覚えています。
Windowsのファイル管理でおこなうコピーやショートカットのイメージです。
構造体の場合person1
とそのコピーであるperson2
は全く別物ですから、person2
の名前や年齢を変えればperson2
の名前や年齢だけ変わります。
一方、クラスの場合person1
とそのショートカットであるperson2
は一体ですから、person2
の名前や年齢を変えればperson1
の名前や年齢も変わります。
Windowsのファイル管理のショートカットをイメージすれば分かります。
次のコードを見て下さい。
サンプルコード ⑪
// クラスの型定義
class Person {
//プロパティ定義
var name: String
var age: Int
// メソッド定義
func introduceSelf() {
print("\(name)さんは\(age)歳です。")
}
// 初期化
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
// クラスのインスタンス化と初期値入力
var person1 = Person(name: "神戸一郎", age: 50)
var person2 = person1
person2.name = "博多和恵"
person2.age = 35
person1.name = "大阪太郎"
person1.age = 23
// 自己紹介メソッドの呼び出し
person1.introduceSelf()
person2.introduceSelf()
出力結果
大阪太郎さんは23歳です。
大阪太郎さんは23歳です。
24行目と25行目でperson1
を変更しています。Personはクラスですからperson1
とperson2
は一体ですからperson1
と同じ値になります。
まとめ
構造体とクラスはSwiftにおけるデータを表す2つの方式です。構造体は値型で、インスタンスを作ると実体のコピーが作成されます。
一方クラスは参照型で、インスタンスを作った時点でメモリ上に実体が1つだけ作られ、そこを参照することになります。
これをファイルのコピーとショートカットに例えるとイメージしやすいです。
構造体が新しいファイルを別の場所にコピーするように、実体を複製しています。
一方クラスは、元のファイルを参照するショートカットを作成しているようなものです。
構造体を使う場合は値の格納・取り出しで完結する処理に向いています。
クラスは参照渡しするので、情報を共有したい処理に向いています。
Swiftのデータ設計ではこの2つの特徴を理解した上で状況に応じて使い分けることが重要です。