※この文章はベータ版です。
変数の種類
- 値型変数:代入した値を保持(保持値=代入値)
- ポインタ型変数:値型変数の一種(代入したアドレス値を保持、保持値=代入値)
- 参照型変数:代入した値の参照値を保持して値を参照(保持値≠代入値)
ここでの参照型変数の定義は、C++の参照やPHPのリファレンスなどの言語定義とは異なりますのでご注意ください。
引数の種類
関数呼び出し側の引数(実引数)に変数を指定したとき、関数側の引数(仮引数)への渡し方に「値渡し」と「参照渡し」があります。
値渡しは更に細かく分類できます。
- 値渡し:保持値のコピーを渡す(値型変数は値コピー、参照型変数は参照コピー値共有)
- ポインタ渡し:ポインタ型変数の値渡しの別名
- 参照の値渡し、共有渡し:参照型変数の値渡しの別名(値共有、オブジェクト共有)
- 参照渡し:呼出し元変数を参照するようにして変数自体を渡す(変数共有)
値型変数も参照型変数も、値渡しと参照渡しができます。
C/C++
C/C++は、型ありき(type first)で設計されています。
型は前置です。
変数宣言時に必ず変数と型を紐付けし、変数の初期化時に型に合わせて、メモリを確保します。
C
- 変数:すべて値型
- 引数:すべて値渡し
Cは、設計段階で、型をつけた変数を定義して、変数間で値をコピーして渡す「値渡し」が先ずあり、「値渡し」だけでは常に値のコピーが発生し、パフォーマンスがでないので、値がある場所(アドレス)を指し、値の直接操作の可能な「ポインタ」が導入された、感じがします。
Cはポインタに独自の型(ポインタ型)を用意せず、型に対して*を後置することでポインタ型を表現しています。
ポインタ変数は、アドレスの入れ物なので、ポインタ変数間で「値渡し」できます。ポインタ変数に「*」を付けることで、ポインタ変数に格納したアドレスにある値を直接操作できます。
ポインタはアドレスの入れ物なので、ポインタ自体は格納されているアドレスにある変数の型の情報を必要とはしていませんが、実装上、ポインタの指すアドレスにある変数の型がわからないと、その変数内にある値にアクセスできないため、変数自体には自分の型の情報を持たせず、ポインタ変数の宣言時にポインタ変数にポインタの指す変数の型を紐付けるか、変数自体に自分の型の情報を持たせておき、ポインタでアクセスするとポインタの指す変数から取得できるようにするか、する必要があります。静的型付言語であるCでは前者を採用しています。
ポインタの説明で、ポインタ型のロジックでの説明はあまり見かけず、(値)型とポインタ型の区別がつきにくく、ポインタの理解がしにくくなってしまってきた気がします。
(ポインタ変数・通常変数モードというロジックでの説明は...。)
ポインタ変数は、宣言が「型* 変数名;」だけでなく「型 *変数名;」とも書けることで、変数名に*をつけて宣言する(*変数名という)変数で、アドレスを格納できる、風の誤解を誘発してしまっている気がします。
C++
- 変数:すべて値型
-
引数:仮引数に
&を付けると参照渡し、つけないと値渡し
C++では、ポインタに加えて、変数を参照して直接操作できる「参照」が新たに導入されました。
参照も、独自の型(参照型)を用意せず、型に対して&を後置することで参照型を表現しています。
C++の「参照」は、宣言時の初期化(参照先のセット)必須です、初期化の後の参照先の変更、NULLを参照先にすること、はできません。
参照の説明でも、参照型のロジックで説明したほうが、理解しやすいのではないかと思います。
C/C++には関数ポインタがあるので、変数経由で関数・メソッドを操作することができます。
関数ポインタ(変数)の宣言は、変数風の「(返値の型 (引数の型 引数))* 変数名;」のようには書けず、関数風に「返値の型 (*変数名)(引数の型 引数);」と書きます。
c++はポインタと参照の2つがあるので、ややこしいです。
Swift
- 変数:構造体と列挙型は値型、クラスは参照型
- 引数:inoutを付けると参照渡し、付けないと値渡し
Objective-Cの影響を強く受け、設計されています。
型は後置です。
変更宣言時の変数への型付けはしてもしなくてもよく、していない場合、変数の初期化時に代入した値の型により、メモリが確保されると共に、変数の型が決まります。(但し、初期化時に代入値がnilの変数は宣言時の型付が必須です。)
ポインタはあります。が、C言語との互換性などでどうしても必要な場合だけに利用すべきモノです。
C/C++のようなポインタありきの言語、ではありません。
ポインタを表すポインタ型が数種あります。
(ポインタはARC対象外なので、メモリ管理する必要があります。)
関数・メソッドの引数はデフォルトでは定数なので、引数として代入した変数を変更するには、型の前にinoutキーワードをつけなくてはなりません。
クラス・構造体の値型のプロパティをインスタンスメソッドから変更するには、メソッド定義の際にfuncキーワードの前にmutatingキーワードをつけなくてはなりません。
関数型があるので、関数・メソッドは変数経由で呼び出すことができます。
Java
- 変数:プリミティブ型(int、float etc)は値型、それ以外は参照型
- 引数:すべて値渡し
C/C++の影響を強く受け、型ありき(type first)で設計されています。
型は前置です。
変数宣言時に必ず変数と型を紐付けし、変数の初期化時に型に合わせて、メモリが確保されます。
ポインタはありません。
関数型がない(メソッド(関数)はオブジェクトではない)ので、メソッド(関数)を変数経由で呼び出すことはできません。
JavaScript
- 変数:プリミティブ型(int、float etc)は値型、それ以外は参照型
- 引数:すべて値渡し
動的型付けを採用しています。
変数は型を付けずに定義し、いかなる型も代入できます。
(型がないわけではなく、代入する値の型で変数の型が決まります。)
関数(クロージャ含む)・メソッドはオブジェクトなので((関数型Functionがあるので)、関数・メソッドも変数経由で呼び出すことができます。
Ruby
- 変数:すべて参照型
- 引数:すべて値渡し
Rubyは動的型付けを採用しています。
変数は型を付けずに定義し、いかなる型も代入できます。
型がないわけではなく、代入する値の型で変数の型が決まります。
処理系(CRuby)では、変数の型は整数型で変数自体は代入値(データ)のオブジェクトID(オブジェクトを識別する一意の値)を格納しています。変数にアクセスすると、オブジェクトIDに対応したデータにアクセスします。実用上、変数の型=代入値の型と考えて、問題はないです。
変更不能(immutable)な型の場合は値渡しのような挙動です。
- 変更不可(immutable)な型:
- 数値型
- シンボル型
関数・メソッドはオブジェクトではない(関数型もない)ので、関数・メソッドを変数経由で呼び出すことはできません。
クロージャ(proc、lambda)はオブジェクトなので(Proc型であるので)、変数経由で呼び出すことができます。(クロージャは関数の一種です。)
Python
- 変数:すべて参照型
- 引数:すべて値渡し
Pythonは動的型付けを採用しています。
変数は型を付けずに定義し、いかなる型も代入できます。
型がないわけではなく、代入する値の型で変数の型が決まります。
厳密には、変数自体の型は整数型?で変数自体は代入値(データ)のオブジェクトID(オブジェクトを識別する一意の値)を格納しています。変数にアクセスすると、オブジェクトIDに対応したデータにアクセスします。実用上、変数の型=代入値の型と考えて、問題はない(はず)です。
処理系(CPython)では、変数の型はポインタ型で、変数は代入値へのポインタを格納します。
変更不能(immutable)な型の場合は値渡しのような挙動です。
-
変更不可(immutable)な型:
- int, float, complexといった数値型
- 文字列型(string)
- タプル型(tuple)
- bytes
- Frozen Set型
-
変更可能(mutable)な型:
- リスト型(list)
- バイト配列(bytearray)
- 集合型(set)
- 辞書型(dictionary)
関数・メソッドはオブジェクトなので(関数型Functionがあるので)、関数・メソッドを変数経由で呼び出すことができます。
最後に
今後、他の言語を追加するかもしれません。