コンピュータプログラミングにおいて、定数とは、通常の実行中にプログラムによって変更されない値のことです。識別子に関連付けられた定数は「名前付き定数」と呼ばれますが、「定数」と「名前付き定数」という用語はしばしば同義語として使われます。これに対し、変数は、通常の実行中に値を変更できる識別子です。簡単に言うと、定数の値は一定であり、変数の値は変化するため、それぞれに名前が付けられています。
定数はプログラマーとコンパイラーの両方にとって有用です。プログラマーにとっては、自己文書化コードの形式であり、正しさについての推論を可能にします。一方、コンパイラーにとっては、定数性の仮定が破られていないことを検証するコンパイル時および実行時のチェックを可能にし、 [ a ] 、コンパイラーの最適化を可能にしたり簡素化したりします。
定数という一般的な概念にはさまざまな具体的な実現方法があり、微妙な違いがしばしば見落とされています。最も重要なのは、コンパイル時定数(静的に値をとる定数)、実行時定数(動的に値をとる定数)、不変オブジェクト、および定数型(const)です。
コンパイル時定数の典型的な例としては、数学定数、規格値(ここでは最大伝送単位)、内部設定値(ここでは1行あたりの文字数)などがあり、C言語の例を以下に示します。
const float PI = 3.1415927 ; // 最大単一浮動小数点精度const unsigned int MTU = 1500 ; // Ethernet v2、RFC 894 const unsigned int COLUMNS = 80 ;実行時定数の典型的な例としては、関数への入力に基づいて計算される値が挙げられます。例えば、次のC++の例がそうです。
import std ;std :: stringを使用します。void f ( string & s ) { const size_t len = s . length (); // ... }プログラミング言語によっては、定数と変数のシンボルを明示的に構文的に区別するものがあり、例えば定数への代入は構文エラーとみなされます。一方、他の言語では、これらは構文的に同じもの(どちらも単なる識別子)とみなされ、扱いの違いは意味論的なものです(識別子への代入は構文的に有効ですが、識別子が定数の場合は意味的に無効です)。
定数値は一度定義すれば、プログラム全体で何度でも参照できます。同じ値を複数回指定する代わりに定数を使用することで、コードの保守が容易になり(DRY原則)、例えば3.1415926ではなく、値に意味のある名前を付けることで自己文書化が可能になりますPI。
プログラム実行中に変化しないデータ値を表現する方法はいくつかあり、それらは様々なプログラミング言語で共通しています。最も基本的な方法の一つは、数値、文字、または文字列をそのままプログラムコードに書き込むことです。これはC、C++、および同様の言語では簡単に行えます。
アセンブリ言語では、リテラル数値や文字は、ほとんどのマイクロプロセッサで使用可能な「即値モード」命令を使用して処理されます。「即値」という名前は、メモリ アドレスを検索して間接的にロードするのではなく、命令ストリームから値がすぐに利用可能になることに由来します。 [ 1 ]一方、文字列や配列など、マイクロプロセッサのワード長よりも長い値は間接的に処理され、アセンブラは通常、そのようなデータ テーブルをプログラムに埋め込むための「データ」擬似命令を提供します。
もう一つの方法は、シンボリックマクロを定義することです。多くの高級プログラミング言語やアセンブラにはマクロ機能があり、プログラマはソースファイルの先頭、または別の定義ファイルで、さまざまな値の名前を定義できます。プリプロセッサはコンパイル前にこれらの名前を適切な値に置き換えるため、リテラルを使用した場合と機能的に同じになり、即時モードの速度上の利点が得られます。すべての値がリテラルで記述されたコードの保守は困難な場合があるため、値が繰り返し使用される場合や分かりにくい場合は、マクロで名前を付けることがよくあります。
3つ目の方法は、変数を「定数」として宣言および定義することです。グローバル変数または静的変数は、キーワード修飾子(、、、または(プリミティブ型に適用する場合))を使用して宣言(またはアセンブリで定義されたシンボル)できますconst。これは、constexprその値がコンパイル時に設定され、実行時に変更できないことを意味します。コンパイラは通常、静的定数を、非定数初期化データが格納されるデータセクションではなく、コード自体とともにオブジェクトファイルのテキストセクションに配置します。一部のコンパイラは、定数専用のセクションを生成できます。このような定数が誤ったポインタによって上書きされないように、この領域にメモリ保護を適用できます。constantfinal
これらの定数は、リテラルとはいくつかの点で異なります。コンパイラは通常、定数をマクロのように実行可能ファイル全体に分散させるのではなく、シンボルで識別される単一のメモリ位置に配置しがちです。このため、即時モードの速度上の利点は失われますが、メモリ効率の面で利点があり、デバッガは実行時にこれらの定数を操作できます。また、CやC++ではマクロがヘッダーファイルの競合によって意図せず再定義される可能性がありますが、定数の競合はコンパイル時に検出されます。
言語によっては、定数は型指定なしの場合と型指定ありの場合があります。C言語とC++言語では、マクロが前者を提供し、const/ がconstexpr後者を提供します。
#define PI 3.1415926535constexpr double PI = 3.1415926535 ; // コンパイル時定数const double pi = 3.1415926535 ; // 実行時定数一方、Adaでは、必要に応じて使用できる汎用的な数値型が存在します。
pi :定数:= 3.1415926535 ;pi2 :定数float := 3.1415926535 ;型指定のないバリアントは、使用されるたびに適切な型に暗黙的に変換されます。[ 2 ]
上記で説明した静的定数に加えて、AdaやC++などの多くの手続き型言語では、定数性の概念を、初期化時に作成されるグローバル変数、スタックまたはレジスタに実行時に自動的に作成されるローカル変数、ポインタによってアクセスされる動的に割り当てられたメモリ、および関数ヘッダーのパラメータリストにまで拡張しています。
動的に値が設定される定数は、変数がメモリの特定の領域に存在することを指定するものではなく、コンパイル時に値が設定されるものでもありません。C++ コードでは、
float func ( const float myFloat ) { const float result = someGlobalVariable * someOtherFunction ( myFloat ); // ... }定数が初期化される式自体は定数ではありません。ここでは、プログラムの正当性や意味論的な正しさのために定数性を使用する必要はありませんが、次の3つの利点があります。
動的に値をとる定数は、 ALGOL 68で言語機能として登場しました。[ 3 ] Ada と C++ のコードの調査によると、動的に値をとる定数は、通常オブジェクトの 1% 以下でしか使用されていませんが、ローカルの非クラス オブジェクトの 40〜50% は作成後に不変になるため、もっと使用できるはずです。[ 3 ] [ 4 ]一方、このような「不変変数」は、副作用のないプログラミング スタイル (再帰など) を推奨するか、MLのようにデフォルトでほとんどの宣言を不変にする関数型言語ではデフォルトになる傾向があります。純粋関数型言語では、副作用を完全に禁止しています。
定数性は、関数宣言において、オブジェクトが参照渡しされた場合、呼び出された関数がそれを変更しないことを保証するためによく用いられます。構文によっては、ポインタまたはポインタが指すオブジェクトのいずれかが定数となる場合がありますが、通常は後者が望ましいとされます。特にC++とCでは、プログラム全体を通して適切なデータ構造が定数であることを保証する規律は、const-correctness(定数の正しさ)と呼ばれます。
C/C++では、関数またはメソッドのパラメータを定数として宣言することができます。これは、呼び出し元による初期化後にこのパラメータが意図せず変更されないことを保証するものです。パラメータが定義済み(組み込み)型の場合、値渡しとなるため変更できません。ユーザー定義型の場合、変数はポインタアドレスとなり、これも変更できません。ただし、オブジェクトの内容は無制限に変更できます。パラメータを定数として宣言することは、この値を変更してはならないことを示す一つの方法ですが、コンパイラはオブジェクトの変更チェックを行わないことをプログラマは留意する必要があります。
この機能に加えて、C++では関数やメソッドをとして宣言することも可能ですconst。これにより、そのような関数やメソッドはローカル変数以外のものを変更することができなくなります。
C# では、キーワードはconst存在しますが、C/C++ の場合のように関数パラメータに対して同じ効果はありません。ただし、少し複雑ではありますが、コンパイラにチェックを実行させる「方法」があります。[ 5 ]
オブジェクト指向の用語では、定数データ構造またはオブジェクトは「不変」と呼ばれます。オブジェクトが不変であることは、プログラム設計においていくつかの利点をもたらします。例えば、ポインタまたは参照をコピーするだけで「コピー」できるため、時間のかかるコピー操作を回避し、メモリを節約できます。
C++のようなオブジェクト指向言語では、定数性はさらに拡張されています。構造体やクラスの個々のメンバーは、クラス自体がconstでなくてもconstにすることができます。逆に、`const`mutableキーワードを使用すると、オブジェクトが`const`としてインスタンス化された場合でも、クラスメンバーを変更できます。これは、データのみがconstであるべきで、メタデータやクラス全体の情報が変更される可能性があるconst場合に役立ちます。const
C++では関数もconstにすることができます。ここでいうconstとは、constとしてインスタンス化されたオブジェクトに対してはconst関数のみを呼び出すことができるという意味です。const関数は、変更不可能なデータを一切変更しません。
C# には とconstの両方のreadonly修飾子があります。 の const はコンパイル時定数専用ですが、 はreadonlyコンストラクタやその他の実行時アプリケーションで使用できます。
C++には4種類の異なるタイプがありますconst。
const従来の実行時定数constexprコンパイル時に評価可能な定数または式constevalこれは、関数呼び出しはコンパイル時式である値を生成しなければならないことを宣言するものです。constinitこれは、式が静的(定数)初期化を持つことを宣言するものです。Rustでは、宣言がない限りすべての宣言は定数でありmut、宣言されると可変になります。コンパイル時定数には、constが使用されます。
Java には、参照の変更を防ぎ、参照先が異なるオブジェクトを指すことがないようにする修飾子があります。ただし、これは参照先のオブジェクト自体の変更を防ぐものではありません。Java の は基本的に C++ のポインターに相当します。 の他の機能は提供しません。finalfinalconstconst
Javaでは、修飾子は、影響を受けるデータメンバーまたは変数が代入できないことを示します。以下にその例を示します。final
final int i = 3 ; i = 4 ; // エラー! "final" オブジェクトは変更できませんコンパイラは、マーカーを持つ変数がどこで初期化されるかを判断できなければならず、また、その処理は一度だけ実行されなければならず、そうでなければクラスはコンパイルされません。JavaとC++のキーワードは、プリミティブ変数に適用した場合、同じ意味を持ちます。finalfinalconst
const int i = 3 ; // C++ 宣言i = 4 ; // エラー!ポインタについて考えると、Javaにおける参照は、C++におけるポインタと似た意味を持ちます。C++では、「定数ポインタ型」を宣言することができます。finalconst
Foo * const bar = mem_location ; // const ポインタ型ここで、barは宣言時に初期化する必要があり、その後は変更できませんが、それが指す先は変更可能です。つまり、は有効です。ただし、別の場所を指すことはできません。Java の final 参照も同様に機能しますが、初期化せずに宣言できる点が異なります。*bar=value
final Foo i ; // Java宣言注: Java はポインタをサポートしていません。[ 6 ] これは、 Java ではポインタ(制限付き) がオブジェクトにアクセスするデフォルトの方法であり、Java ではポインタを示すためにアスタリスクを使用しないためです。たとえば、最後の例のiはポインタであり、インスタンスにアクセスするために使用できます。
C++では、「読み取り専用」データへのポインタを宣言することもできます。
const Foo * bar ;ここではbar、いつでも何でも指すように変更できますが、指し示す値自体はポインタを介してbar変更することはできません。
Javaには同等の仕組みはありません。したがって、const対応するメソッドもありません。Javaではconstの正しさを強制することはできませんが、インターフェースを使用し、クラスへの読み取り専用インターフェースを定義してそれを渡すことで、オブジェクトがシステム内で変更されない方法で受け渡せるようにすることは可能です。
Javaコレクションフレームワークは、および同様のメソッドを介して、不変のラッパーを作成する方法を提供します。CollectionCollections.unmodifiableCollection()
Javaのメソッドは「final」と宣言することができ、これはサブクラスでオーバーライドできないことを意味します。
Javaではこのキーワードは予約されているが、現状では使用されていない。const
C#では、修飾子は、Java の およびC++ の とreadonly同様の効果をデータ メンバーに及ぼします。修飾子は、C/C++の と同様の効果を及ぼします。Java の がメソッドやクラスに適用された場合の、継承を抑制するもう 1 つの効果は、C# ではキーワード の助けを借りて実現されます。finalconstconstconstexprfinalsealed
C++とは異なり、C#ではメソッドやパラメーターを読み取り専用としてマークすることはできませんconst。ただし、読み取り専用のサブクラスを渡すことは可能であり、.NET Frameworkは可変コレクションを不変コレクションに変換し、読み取り専用ラッパーとして渡すためのサポートを提供しています。
定数の扱いはプログラミングパラダイムによって大きく異なります。C++ のような命令型言語では、デフォルトでは名前のバインディングによって変数が作成され、名前が示すように変数は変化する可能性があるため、定数の正しさが問題となります。したがって、バインディングを定数としてマークしたい場合は、追加の指示が必要になります。[ b ]他のプログラミング言語パラダイムでも、定数の正しさに類似した問題が発生することがあります。
関数型プログラミングでは、データは通常、デフォルトで可変ではなく定数です。変数(名前と可変値を持つ記憶領域)に値を割り当てる代わりに、Lispletの多くの方言にある構文のように、名前と値のバインディングを作成します。一部の関数型言語、特にCommon Lispのようなマルチパラダイムの言語では、データの変更は一般的ですが、他の言語では、変更は避けられるか、例外的なものとみなされます。これはScheme(Lisp の別の方言)の場合で、構文を使用してデータを変更し、!感嘆符で注意を促します。このような言語は、デフォルトで定数の正確性という目標を達成し、定数性よりも変更に注意を向けます。set!
オブジェクト指向言語の多くは、不変オブジェクトという概念を持ち、特に文字列などの基本型に用いられます。代表的な例としては、Java、JavaScript、Python、C#などが挙げられます。これらの言語では、ユーザー定義型を不変としてマークできるかどうか、また、オブジェクトや型の特定のフィールド(属性)を不変としてマークできるかどうかが異なります。
オブジェクト指向と関数型の両方を許容するマルチパラダイム言語では、これらの機能が組み合わされている場合があります。たとえば、OCamlではオブジェクトフィールドはデフォルトで不変であり、可変にするにはキーワードで明示的にマークする必要がありますmutable。一方、Scala では、バインディングはval「値」に対してで定義すると明示的に不変になり、「変数」に対してで定義すると明示的に可変になりますvar。
定数の命名規則は様々です。他の変数と同じように名前を付ける場合もあります。また、記号マクロの従来の使用法と同様に、定数に大文字とアンダースコアを使用する場合もありますSOME_CONSTANT。[ 7 ]ハンガリアン記法では、「k」接頭辞は定数だけでなく、マクロや列挙型も意味します。
Rubyでは、クラス名を含め、大文字で始まる変数はすべて定数とみなされるという慣習が強制的に適用されています。
[...] パラメータの型によって変数を変更するかどうかが「わかる」メソッドを宣言できます。 [...] これは、C++ の const に似たコンパイル時のチェックを模倣しています。Eric Lippert が正しく指摘したように、これは不変性とは異なります。しかし、C++ プログラマーであれば、そのことはご存知だと思います。