本記事は、プログラムとは何か(5)~データ編~ からの続編です。
シンボル
番地指定の参照はわかりにくい
プログラムとは何か(5)~データ編~ のアドレスのところでも述べたように、プログラムで扱うデータとしては、使い回しがしやすいように、即値よりも参照の方がよく使われます。
しかし、参照先のアドレスは、メモリの先頭からの連番、つまり数値です。たとえ仮想アドレス空間で、固定のアドレスにできたとしても、やはり数値のままだと、その中身を想像して扱うのは難しくなります。
たとえば、
[10番地の中身] + [100番地の中身] を、[1000番地の中身] に入れる
と書かれたプログラムが正しいかどうかは、そこだけ見てもわかりません。
仮想アドレスに呼び名を付ける
しかし、もしこれが、
[男性人数] + [女性人数] を、[全体人数] に入れる
だったら、正しいとすぐわかりますし、
[男性平均体重] + [女性平均身長] を、[年間平均気温] に入れる
だったら、全くナンセンスだとすぐわかります。
そこで仮想メモリアドレスに対して、文字列による「呼び名」を付けられる機能が、アセンブラや高級言語の処理系に入れられました。
各参照先アドレスに、入る中身に合わせた自然言語的な呼び名が付けられれば、その中身を想像してプログラミングができるようになります。
この文字列による呼び名をシンボルと言います。
また、言語処理系がこの呼び名処理に使う、仮想メモリアドレスとシンボルとの対応表をシンボルテーブルと言います。
宣言
特定の仮想メモリアドレスにシンボルを対応させるためには、プログラムのソースコード中で、シンボルとしての「宣言」を行います。
宣言では、シンボルと、そのアドレスに入るべき中身のデータ型、場合によっては、さらにその中身自体(初期値)を明記し、言語処理系へ伝えます。
ソースコードのプログラミング言語とデータ型によっては、データ型ごとに決められた記号( []、()、{}、* など )をシンボルに含めて宣言する必要があります。
シンボルから、これらの記号を除いた部分を特にシンボル名と言います。シンボル名は、この後で説明する識別子にする必要があります。
シンボルテーブルへの記録
言語処理系は、ソースコードをマシンコード(オブジェクトファイルまたは実行ファイル)へ変換する際に、ソースコード中に宣言されたシンボルをシンボルテーブルへ登録します。
そして、各シンボルが対応する仮想メモリアドレスも、リンカやローダによってプログラム実行時までに、シンボルテーブルに追記されます。
プログラム実行中におけるシンボルの中身
シンボルの中身(シンボルが対応するメモリアドレスに記録されたバイナリデータ)は、プログラム実行によって書き換えられる場合もあります。
そしてプログラム中に、データ型を指定してシンボルの中身を出力する指示が書かれていれば、シンボルの中身のデータは、指定されたデータ型として、出力先に渡されます。
C言語で「symbolの中身を文字列型として画面表示する」例
printf(%s, symbol);
%sによって、文字列(string)型を指定しています。
なお、言語処理系によっては、プログラムでデータ型を指定していなくても、シンボルテーブルに記録されたデータ型として、シンボルの中身のデータを出力先に渡すものもあります。
出力先でのデータの扱い
そして、出力先がそのデータ型に対応していれば、データは適切に出力されます。対応していない場合は、エラーになったり、意図しない出力結果になったりします。
例えば、テキストデータ表示用の画面に、文字型や数値型のデータは出力できますが、バイナリ型のデータは、多くの場合、正しく出力できません。
エラー
言語処理系によるデータ型チェック
データを処理するプログラムにミスがあると、出力結果のバイナリデータを元のデータ型に復元できなかったり、復元できたとしてもおかしな結果になりかねません。
プログラムミスの例
例えば、[文字型]と[文字型]を掛け算するようなミスをしたプログラムをそのままマシンコード化すると、プロセッサーはその数値化された文字コード同士を掛け算処理することになります。そして得られた結果の数値を文字コードとみなして、文字コード表を検索し、おそらく意図しない結果を出力するでしょう。
【プログラムミスの例】
正) 2 × 3 → 6 (数値で掛け算)
誤) 2 × 3 → ৶ (文字で掛け算)
2[50] × 3[51] → ৶[2550]
[]内はバイナリデータ(文字コード)。わかりやすくするため、ここでは10進数で表した。
この例が示すのは、2 × 3 の結果として、本来は数値で掛け算して、6[54] という結果を期待していましたが、文字コードで掛け算して、そうはならなかったということです。
正しくは、
- 掛け算の対象( 2[50] と 3[51] )が、数字どうか( [48~57] であるかどうか)をチェックする。 → 2[50] と 3[51] は数字
- 数字であれば、文字ではなく数値として扱うために、数字を数値化する。(各文字コードから [48] を引く)
- 掛け算をする。( [2] × [3] → [6] )
- 掛け算した結果 [6] を文字化する。( [48] を足し戻す)
- 6[54] を結果として表示する。
とするべきでした。
数字は「字」、つまり文字であり、数値は「値」、つまり直接計算できるもの、として使い分けられます。
構文エラー(syntax error)
このようなミスを防ぐために、元のデータ型に合わない処理、あるいは元のデータ型が壊れて復元できなくなるような処理は、通常、ソースコードからマシンコード(オブジェクトファイルまたは実行ファイル)への変換時(主にコンパイル時)に、構文が正しくないということで、言語処理系が構文エラーを出力します。
エラー出力とデバッグ
一般にエラー出力には、エラーコードやエラーメッセージなどが含まれ、そこにエラーの詳細情報(いつ、どこで、何が、どう…)が出力されます。(内容は、言語処理系やその設定によって異なります)
構文エラーに限らず、プログラミングのミスのことをバグ(bug)と言います。プログラムからバグを取り除く作業をデバッグ(debug)と言い、上記の言語処理系によるエラー判定機能のように、デバッグを目的とするプログラムや作業者のことをデバッガ(debugger)と言います。
なお、シンボルがバイナリ型で宣言されている場合、そのバイナリデータ化前のデータ型(音声か?、画像か?、はたまた何か?)は言語処理系にはわかりません。そのため、言語処理系としてはバイナリ型をそれ以上チェック(デバッグ)することはできなくなります。逆に言えば、異なるデータ型への変換処理や、大量データの高速処理が必要な場合には、ノーチェックで処理を進められるバイナリ型が好まれます。
識別子(ID)
個々のシンボル名やデータ型のように、何を指すかが定義されている名前のことを識別子(identifier)と言います。”identifier” は略して “ID” と言った方が、すでにおなじみかもしれません。
具体的には、特定のメモリ領域(先頭アドレスおよびバイト数)を指す名前になります。これには、ユーザープログラムで使われるシンボルの対応アドレスだけでなく、言語処理系プログラム内のデータ型等の定義も含まれます。
社会一般で使われるIDは番号になっていることもありますが、プログラミングではIDが文字列になっている方が、IDが指すものをイメージしやすいため、シンボル名のように簡潔かつ他と重複しない文字列が識別子としてよく使われます。
異なる複数の識別子が、同一のメモリ領域を指すことも可能です。
逆に、同一の識別子が、異なる複数のメモリ領域を指すことはできません。
リテラル(literal)
literal は「文字どおり」という意味です。ソースコード中に、シンボルではなくそのまま書かれてデータとして扱われるテキストデータをリテラルと言います。
アセンブリ言語などの低級言語で使われた即値(immediate)の、高級言語版とも言えます。
リテラルはソースコード中にそのまま書かれているので、プログラムがメモリにロードされるのに合わせ、リテラルも仮想メモリアドレス空間のテキストセグメントに入ります。
ソースコードでは、リテラルの冗長を避けるため、シンボルが多用されます。そしてシンボルがまた別のシンボルを指し、さらにそのシンボルもまた別のシンボルを指し…と、シンボルの連鎖が続いていく場合もあります。その場合でも、シンボルをたどって行き着く終点には、リテラルが必要です。
文字列リテラル
文字列リテラルでは、識別子と区別するために、リテラルの先頭と最後に、
シングルクォーテーション(’)・・・引用符とも言う
または、
ダブルクォーテーション(”)・・・二重引用符とも言う
を使ってくくるやり方が、多くのプログラミング言語で使われています。
クォーテーション(quotation)とは、「引用」という意味です。日本語で使うカギかっこ「」のようなイメージです。
文字列リテラルの具体例
- “Hello!”
- ‘string’
- ‘日本語も使えます。’
その他のリテラル
文字列リテラル以外にも、数値リテラル、配列リテラルなど、各データ型に対応したリテラルがあり、各言語処理系で書式が決められています。
変数(variable)
シンボルが示すメモリアドレスには、データを書き込むことができます。さらに2バイト以上のデータは、シンボル以降のメモリアドレスにも続けて書き込め、そのシンボルのデータ型として確保されたメモリ領域分まで、ひとかたまりのデータとして扱えます。
※ メモリアドレスは通常、1バイト単位で割り振られています。
このデータ型分確保されたメモリ領域を参照するシンボルを変数と言います。変「数」と数はつきますが、数値以外のアドレスや文字列や論理値などのデータ型も、変数の参照対象になります。
変数が参照するメモリ領域に、その中身としてのデータを書き込むことを代入と言います。
定数(constant)
変数と同様に定数も、データ型分確保されたメモリ領域を参照するシンボルです。ただし、そのメモリ領域には、初期値未設定の状態からの初期代入1回しかデータ書き込みが許されません。
- 変数・・・再代入可なメモリ領域を参照するシンボル
- 定数・・・再代入不可なメモリ領域を参照するシンボル
なお、言語によって、定数へ変数を初期代入できる/できないの違いがあります。定数へ変数を初期代入すると、定数へ別のデータを再代入することはできませんが、変数へは再代入ができてしまいます。この場合、結果的に定数の中身が変わることが起こり得ることになります。
配列(array)
変数などのシンボルには、中身に合わせたシンボル名(識別子)を付けられました。
複数の変数それぞれに同じ種類の中身(同じデータ型)を入れて使う場合、それらの変数で一つのグループを作り、まとめて処理をさせたり、各変数に通し番号を付けて個々の処理をさせたり、と使い分けられれば便利です。
多くの高級言語では、「配列」という呼び名で、このようなグループが実現されています。
配列の宣言では、仮想メモリアドレス空間上に、まとまった領域を確保します。配列のデータ型に合わせた枠を、配列の個数分、連続させた領域です。
こうすることで、配列名を使ってひとまとめの処理ができ(配列名は先頭アドレスを指します)、0から始まる配列内の通し番号で個別変数としての処理もできます。
- 配列名・・・先頭アドレス
- 配列名[]・・・配列全体
- 配列名[番号]・・・配列内の指定番号要素(変数)
列挙(enumeration)
配列は、複数の「変数」を「連番」で識別可能にし、グループ化したものでしたが、
列挙は、複数の「整数値」を「識別子」で識別可能にし、グループ化したものです。
列挙された識別子は、整数値を表す定数と同じ扱いとなり、シンボルテーブルへ、その対応付けが記録されます。
整数値は自動だと連番になりますが、飛び飛びの整数値を自由に指定したり、同じ整数値に複数の識別子を対応させたりもできます。
関数(function)
そもそもコンピューターは、2進数データを計算回路に入力して、別の2進数データに変換し、出力するものです。
論理回路を組み替えれば、異なる計算回路ができ、組み替え前後で同じ2進数データを入力をしていても、計算結果として異なった2進数データが出力されます。
内部にたくさんの論理回路が配線されたプロセッサへ、オペコードを入力すると、オペコードに従った計算回路となるように、プロセッサ内部の論理回路が組み替えられます。
つまりプロセッサへ、マシンコード化されたプログラムを入力することで、
- プログラムの各行のオペコードに従った計算回路へと組み替える
- 計算回路へ、メモリから2進数データを入力する
- 計算回路から出力された2進数データを、またメモリへ書き込む
を、1→2→3→1→2→3→1→2→3→・・・と繰り返させているのです。
関数は、このようにメモリを仲介して入出力を連結した複数の計算回路を、ひと続きの長い計算回路であるかのように、シンボル化したものになります。
関数の処理の流れ
したがって、プログラム中での関数の使い方は、一般に以下のようになります。
- 関数の宣言
- 関数の呼び出し
- 関数の処理
- 関数の処理結果の利用
関数の宣言
ソースコード中で、関数名(シンボル名)に関数リテラルを対応付けます。これを関数の宣言と言います。関数の定義とも言います。
ソースコード中に宣言することで、変数と同じように言語処理系によるデバッグが可能になります。
関数の実行時までには、関数名(シンボル名)が、マシンコード化された関数リテラルの先頭アドレス(仮想アドレス)に対応付けられ、シンボルテーブルに登録されます。
関数の呼び出し(call)
ソースコード中に、関数名(シンボル名)と、その関数への入力データを合わせて書くことで、関数を呼び出します。
高級言語で書かれた関数呼び出し(call)も、言語処理後は、アセンブリ言語のCALL命令と同じリンク動作のマシンコードに変換されます。
また、関数への入力データを、引数と言います。
仮引数
関数リテラル(関数そのもののソースコード)の中に書かれた引数は、変数の形で宣言されていることが多く、宣言だけで中身が未定であることが多いので、特に仮引数と言います。
実引数
仮引数に対して、プログラムの実行時(runtime)に関数へ入力される、中身が実在する引数データは、実引数と言います。関数の呼び出し(call)まで実行(execute)されると、実引数がコールスタックへ積まれます。
関数の処理
プログラムの実行時、関数が呼び出されると、その関数リテラル(マシンコード)の先頭アドレスへプログラムカウンタが入れ替えられ、プロセッサの処理が関数リテラルに移ります。
そして関数の処理結果は、レジスタやコールスタックへ記録されます。
関数の処理が終わると、return(アセンブリ言語のRET命令)によって、プログラムカウンタは、元のプログラムのリターンアドレスへ戻され、プロセッサは関数の次の処理へ進みます。
関数の処理結果(return)の利用
関数の処理が終わり、元のプログラムにプロセッサの処理が戻ると、元のプログラムは、レジスタやコールスタックに記録された関数の処理結果へアクセスできるようになります。こうして元のプログラムは、その関数の処理結果をまた別の処理の対象として使うことができるようになるのです。
実引数の渡され方
元のプログラムから関数への実引数の渡され方には、大きく2つの方式があります。
値渡し
実引数が記録されているメモリ領域から、そのデータ(値)をコールスタック上の関数が使うスタックフレームにコピーして関数処理に使う方式です。
参照渡し
実引数が記録されているメモリ領域への参照データ(仮想メモリアドレス)を、コールスタック上の関数が使うスタックフレームにPUSHして、その参照をたどって関数処理を行う方式です。
●●関数
高階(higher-order)関数
関数を引数にとる関数を高階関数と言います。
「関数を引数にとる」と言っても、関数は、一連の処理が書かれた関数リテラルを、シンボル化しただけのものです。つまりその実体は、「関数リテラルの先頭メモリアドレス(仮想アドレス)を引数にとる」ということになります。
高階関数の引数に、どの関数を指定するかで、当然その結果は変わってきます。
コールバック(callback)関数
高階関数の引数となる方の関数を、コールバック関数と言います。
コールバックとは、直訳するなら「call返し」という意味です。
本体プログラムの処理の中で、高階関数を call する(呼び出す)時、同時にその引数としてコールバック関数を指定します。
その後、call された高階関数にプロセッサの処理が移りますが、高階関数の処理の中で、今度はコールバック関数が call されます。
このコールバック関数は、高階関数の call 元であった本体プログラムで指定していた関数なので、あたかも call されている関数から call した関数へ、 call し返しているような形になります。
このため、高階関数からコールバック関数への call は、コールバック(callback)と言われます。
第一級(first-class)関数
「第一級」という言葉は、first-class(ファーストクラス)の日本語訳です。飛行機のファーストクラスと同様に、個々のプログラミング言語内で最上位レベルであることを意味します。例えば整数型は、様々な計算処理や関数の引数など、最も多くの場面で使えるデータ型であり、「第一級データ型」と言われます。その整数型と同じだけ様々な場面で使える関数は「第一級関数」と言われます。第一級関数は、コールバック関数としても使える必要があります。
無名(anonymous)関数
関数名(シンボル)を付けられていない、関数リテラルを無名関数と言います。
- 関数の戻り値をまたすぐ別の処理に使う
- その関数が使われるのはその処理の一度だけ
- 関数リテラルの内容が単純
このような場合は、関数名を付けて関数を定義(シンボル化)するより、無名関数のまま使った方が、シンボルテーブルの参照を使わないので、より処理効率を上げられます。
オブジェクト(object)
オブジェクトとは
オブジェクトとは、様々なカタチをとれるデータ型のことです。カタチが定められていないということは、具体的なカタチにとらわれないということであり、具体的の反対、つまり抽象的なデータ型と言われます。

抽象性が高く、どんなものにでも当てはまる。
オブジェクト指向プログラミング
抽象的なデータ型であるオブジェクトを使えば、様々なものを表せます。突き詰めれば、プログラムに登場する要素(変数や関数など、メモリ上に記録される意味のあるまとまり)の全てを、オブジェクトとしてとらえることも可能です。
例: 変数オブジェクト、関数オブジェクト、…
このように、プログラムを「オブジェクトの集まり」として考え直し、極論して「様々なオブジェクトへの処理を順序付けたり、制御したりできるようにしたものがプログラムなんだ!」とした逆転の発想をオブジェクト指向と言います。オブジェクト指向でするプログラミングを、オブジェクト指向プログラミング(Object-Oriented Programming、略してOOP)と言います。
また、オブジェクト指向プログラミングの三大要素として、カプセル化、継承、多態性のサポートが、その言語処理系に必要とされています。
クラスベースとプロトタイプベース
オブジェクト指向プログラミングには、クラスベースとプロトタイプベースの二つの流派があります。クラスベースは、生物の分類のような考え方で、プロトタイプベースは、生物の進化のような考え方です。
クラスベースの代表例は、C++やJavaです。PythonやVBAもクラスベースです。一方、JavaScriptは、プロトタイプベースの代表例です。
それではまず、より一般的なクラスベースのオブジェクト指向の方から以下に説明していきます。
クラス(class)
オブジェクトはその抽象性を活かして、様々なデータに使われることが、その存在意義になります。しかし同じ抽象データ型でも完全に具体性を持たないバイナリ型とは違い、「分類」というある程度の具体性を段階的に持つのがオブジェクトの特徴です。オブジェクトが持つ、この段階的な分類をクラスと言います。
![生物[動物[脊椎動物[魚類|両生類|爬虫類|鳥類|哺乳類]|無脊椎動物]|植物]](https://p-cs.work/wp-content/uploads/2019/12/bioclass-1024x534.png)
段階的な分類をすると、上図の生物の分類例でもわかるとおり、大分類の下に中分類があって、中分類の下にさらに小分類があって、…というように、分類が何段にも分けられることで、抽象的なイメージから、具体的なイメージへと、より限定されていくことになります。
カプセル化(encapsulation)
オブジェクトは抽象的なデータ型ですが、具体性を持たせるために付け加えられていく要素が、フィールド(field)と呼ばれるデータと、メソッド(method)と呼ばれる関数です。
一つのクラスには、複数種類のフィールドやメソッドを持たせることができます。このように、フィールドとメソッドをひとまとめにすることをカプセル化と言います。


フィールド
そのオブジェクトについてのデータを、変数として扱えるようにしたものです。フィールドには、オブジェクト変数も使えます。
メソッド
そのオブジェクトがとる反応を関数にしたものです。通常の関数と同様、メソッドの定義では、引数と戻り値を指定できます。
- 引数・・・メソッドの呼び出し元から、メソッドへのインプット
- 戻り値・・・メソッドから、メソッドの呼び出し元へのアウトプット
引数や戻り値が省略される場合は、暗にメソッドの親インスタンス自身(this)が、メソッドのインプットやアウトプットとなります。
メンバー
フィールドとメソッドのことを併せてメンバーと言います。
狭義のカプセル化
カプセル化という言葉には、「ひとまとめにする」という意味だけでなく、「閉じ込める」という意味合いも含まれます。
例えば、
- ●●クラスの、▲▲フィールド ・・・通常、●●.▲▲ と表す
- ●●クラスの、■■()メソッド ・・・通常、●●.■■() と表す
のように、「●●クラスの、」(●●.)を頭につけないと、▲▲や■■にアクセスできなくする・・・というところまで含めてはじめてカプセル化と言えるのだ、という考え方です。
このような、フィールドやメソッドへのアクセス制限を情報隠蔽(information hiding)と言います。「隠蔽」というと悪いことをしているようですが、「事件を防ぐセキュリティ」と考えるとよいでしょう。
例えるなら、何屋だかわかるように看板やメニューは店頭に出しておくけど、店の内と外とはキチンと分ける(万引きや破壊、不法侵入を、簡単にはできなくする)・・・ようなものです。
つまり、フィールドやメソッドの中身は直接触れないけれど、使用上の注意に従って識別子指定すれば、必要なことはできる・・・という考え方です。逆に隠蔽されていなくて、直接フィールドやメソッドを見たり変えたり好き勝手できてしまうと、正しい処理ができない、正しい結果が出ない、といった事件が起こります。
プロパティとアクセサ
アクセサ(accessor)と呼ばれるメソッドからのみアクセスできるようにしたフィールドを特にプロパティと言います。アクセサには、以下の2種類があります。
- フィールドへの代入をするsetter()
- フィールドの値を利用するgetter()
アクセサにチェック機能を持たせることで、より安全にフィールドデータを扱えるようになります。したがってプロパティは、単なるデータでしかなかったフィールドよりも高機能になります。 例えば、読取専用(変更不可)や読取不可のプロパティが実現できます。
継承(inheritance)
上の階層から下の階層へ下がるに連れて、オブジェクトのイメージがより具体的になっていくのは、下の階層には、上の階層からのフィールドやメソッドが引き継がれて、そこへさらに、その階層の分類を決めるフィールドやメソッドが追加されるからです。下の階層に、上の階層からのフィールドやメソッドが引き継がれることを継承と言います。つまり下の階層へ行くほど、フィールドやメソッドの種類は増えていきます。

多態性(polymorphism)
多態性とは、
1つのメソッドが、クラスに応じて複数(poly)の形態(morph)を持つ特性(ism)
のことです。1つのメソッドと言っても、その形態(入出力形式や処理内容)が異なれば、当然、異なるプログラムが必要になります。つまり多態性と言っても、実際には同一のメソッド名を使っているだけで、同一のプログラムではなく別々のプログラムになります。そして別々のプログラムでも、上位クラスからの継承は、共通で再利用されます。
異なるプログラムを同一のメソッド名で使い分けられるのは、情報隠蔽によって「●●クラスの、■■()メソッド」という指定がされるからです。それで同一のメソッド名であっても、クラスに応じて別々の形態をとれるのです。
【例】両性類の卵生()では卵塊を作り、鳥類の卵生()では固い殻のある卵を産む。
クラス定義
クラスは分類ではありますが、プログラム中にすべてのクラス定義を記述する必要はありません。通常、プログラム中には、そのプログラムで必要なクラスと、そのフィールドやメソッドのみを記述します。(プログラムで必要なのが、哺乳類クラスだけであれば、魚類~鳥類クラスや、植物クラス、無脊椎動物クラスの定義は不要。)
クラス定義で、そのクラスの上位クラスを指定することにより、クラス階層構造が作られます。
subClass : superClass
: (コロン)で結ぶことにより、superClassのメンバーがsubClassに継承されます。
また、よく利用されるクラスの多くは、プログラミング言語ごとに提供されるクラスライブラリに、そのクラス定義が含まれています。
クラスとインスタンス
データになれないクラス
クラスはオブジェクトの「分類」です。具体的なオブジェクトのデータにはなれません。よってクラス定義は、関数定義などと同様、メモリのデータセグメントにではなく、テキストセグメント(プログラム実行中書き換え不可)に記録されます。
データになれるインスタンス
それに対して、具体化されたオブジェクトのデータとして宣言され、メモリのデータセグメント(より正確にはヒープセグメント)に記録されるのが、インスタンスです。
メモリのデータセグメント(ヒープセグメント)への領域確保は、プログラム中のnew演算子で行われ、続けてプログラム中のコンストラクタ(インスタンス生成メソッド)によって、具体的データが入ったフィールドとメソッドへの参照が、確保した領域へ記録されます。
instance1 = new Constructor(data1);
instance2 = new Constructor(data2);
instance3 = new Constructor(data3);
つまりインスタンスは、具体的なフィールドデータとメソッド参照の集まりとして生成されます。
そして、「同一のインスタンスに関係していること」が集められたメンバー(具体的なフィールドデータとメソッド参照)の共通点になります。
コンストラクタ
各クラスに用意された、インスタンスを生成するためのメソッドをコンストラクタと言います。
コンストラクタには、通常、戻り値がありません。戻り値を指定しても、エラーにされたり、無視されたりします。
また、コンストラクタ名は、頭文字大文字で、クラス名と同じ名前を付けるのが通例となっています。
オブジェクト変数
ヒープセグメントにインスタンスを生成したら、まずそのインスタンスを識別するために、オブジェクト変数という識別子(ID)へ、そのインスタンスへの参照を代入します。
例) 新たな三毛猫に「たま」と名前を付ける場合
tama = new Cat(mike);
初期値 mike で Cat クラスのインスタンスを作成し、オブジェクト変数 tama に、そのインスタンスへの参照を代入。
ここでソースコードとしては、インスタンスをオブジェクト変数へ代入する形式をとりますが、ここでの代入は必ず参照渡しになり、値渡しされることはありません。
以降、このオブジェクト変数を使ってこのインスタンスを扱うことができるようになるので、このオブジェクト変数名をインスタンス名とも言います。
this キーワード
this は、コンストラクタのリテラル内で使われるキーワード(特殊な意味を持つ言葉)で、そのコンストラクタからインスタンスが作られると、その作られたインスタンスに、this という仮のシンボルが割り当てられます。
それを利用して、各インスタンスへのメンバー設定には、コンストラクタ内で、thisキーワードを使います。
this.member = data;
プロトタイプ
一方、プロトタイプからカスタマイズして新しいオブジェクトを創造していくプログラミングスタイルを、プロトタイプベースのプログラミングと言います。
プロトタイプは、オブジェクトの簡易版模型オブジェクトです。
プロトタイプベースでは、クラスという概念が存在しないので、それに対するインスタンスという概念も存在しません。そこにはオブジェクトという概念だけが存在し、クラスに対応する模型オブジェクトが、プロトタイプになります。
プロトタイプベースの考え方は、クラスベースより後に出てきたため、クラスベースの方が先に広く普及してしまいました。このため、プロトタイプベースの説明であっても、クラスやインスタンスなどのクラスベース用語が使われているケースがよく見受けられます。(本来はあまり正しくありません)
プロトタイプの実体
オブジェクトの簡易版模型オブジェクトであるプロトタイプは、モデルとなるオブジェクト(下の例ではsample)のプロパティの1つとして作られます。
sample = {a:a0, b:b0, c:c0, …, x:x0, y:y0, z:z0};
{a, b, c, …, x, y, z}は、a,b,c,…,x,y,zをメンバーに持つオブジェクトを表します。
sampleのprototypeプロパティ→ sample.prototype = {a:a0, c:c0, y:y0}; ←sampleの簡易版オブジェクト
prototypeプロパティには、{a:a0, c:c0, y:y0}オブジェクトへの参照が入ります。
プロトタイプの変更
新しいオブジェクトは、プロトタイプをカスタマイズして創り出せます。
カスタマイズは通常、コンストラクタのプロトタイプで行います。
クラスベースのコンストラクタがクラス内のメソッドであったのに対し、プロトタイプベースのコンストラクタは、プロトタイプ(簡易版オブジェクト)内のメソッドになります。戻り値が無く、コンストラクタ名が頭文字大文字であることは共通です。
Sampleはsampleのコンストラクタ→ Sample.prototype.c = c1; ← c を c0 から c1 に変更
Sample.prototypeには、{a:a0, c:c1, y:y0}オブジェクトへの参照が入ります。
プロトタイプの利用
コンストラクタからオブジェクトを生成すると、そのオブジェクトにも、コンストラクタのprototypeプロパティで指定されたプロトタイプが引き継がれます。
sample2 = new Sample();
c が変更された新しいSampleコンストラクタにnew演算子を使い、sample2インスタンスを生成
sample2には、{a:a0, c:c1, y:y0}オブジェクトへの参照が入ります。
プロトタイプの継承
その創り出したオブジェクトから、また新しいプロトタイプを作り出すこともできます。
その場合、新しいプロトタイプには、オブジェクトのプロトタイプを継承させます。継承も通常、コンストラクタのプロトタイプで行います。
BrandNewObject.prototype = sample2;
BrandNewObjectは、新しいコンストラクタ
クラスとプロトタイプ
クラスとプロトタイプの違いは、継承と多態性の違いと言っていいでしょう。
カプセル化は、クラスベースでもプロトタイプベースでも共通です。
継承の違い
継承の違いは、クラスチェーンとプロトタイプチェーンの違いになります。
クラスチェーン
プロトタイプチェーン
プロトタイプでの継承は、prototypeプロパティによる参照だけで、直接、コンストラクタ間の親子関係を持たないため、クラスのような階層にはならず、単純な連鎖になります。
これをプロトタイプチェーンと言います。
ミュータブル・イミュータブル
オブジェクト変数は、インスタンスへの参照になります。
オブジェクト変数Aを、別のオブジェクト変数Bに代入すると、オブジェクト変数Bには、オブジェクト変数Aと同じインスタンスへの参照がセットされます。
複数のオブジェクト変数が、同一のインスタンスを参照しているとき、それらオブジェクト変数のどれかからでも、共通参照先であるインスタンスの中身を変更できてしまいます。これをミュータブル(変異可能)と言います。
逆に、どのオブジェクト変数からもインスタンスを変更できなくしたり、変更を加えるオブジェクト変数にはコピーインスタンスを参照させたりして、インスタンスが変異しないようにしたオブジェクトをイミュータブルなオブジェクトと言います。
仮想アドレス空間での領域分け
メモリ利用の仕方による区分
仮想アドレス空間は、テキストセグメント、データセグメント、スタックセグメントに分かれています。
- テキストセグメント
- マシンコード化されたプログラムが記録される。いったん領域が確保されたら、終了するまで不変。
- データセグメント
- プログラムで使用するデータが記録される。プロセス実行時(runtime)に、領域中の各ビットのオン/オフが変化する。
- スタックセグメント
- スタックフレーム単位で、主にモジュール切り替えのための参照が記録される。LIFO(Last-In|First-Out)を実現するために、全メモリ領域の最後尾アドレスから、データ領域の確保とは逆進行で領域確保されていく。
データ変化による区分
データセグメントは、さらに静的データ領域、BSS領域、ヒープ領域に分かれています。
- 静的データ領域
- 静的変数(中身は変わるが、データサイズは一定、個数も一定)が記録される領域。この領域のみを指して「データ領域」と呼ぶ場合もある。
- BSS領域
- BSSは、Block Started by Symbol の略。初期値なしの静的変数(中身は変わるが、データサイズは一定、個数も一定)用。ロード前のオブジェクトファイルには、BSSセクションというものは無いが、カーネルがオブジェクトファイルをロードする時に、シンボルテーブルをもとに、BSSセグメントが追加確保される。「静的データ領域」の一部とされる場合もある。
- ヒープ領域
- オブジェクトなどの動的データ(中身も、データサイズも、個数も変わる)が入る。