const を使用する理由? シンボリック定数を使用して記述されたコードは保守が容易です。ポインタは書き込み中に移動するのではなく読み取り中に移動することが多く、関数パラメータの多くは読み取り専用で書き込みはできません。const の最も一般的な使用法は、配列の境界およびスイッチ ケースのラベルとして使用することです。
const の使用分類
定数変数:const型指定子変数名
C のマクロ定義ではなく、宣言時に初期化する必要があります (C++ クラスの場合は異なります)。const は定数の使用方法を制限するものであり、定数をどのように割り当てるかについては記述しません。コンパイラが const の用途をすべて知っている場合、const にスペースを割り当てることさえできないかもしれませんが、型の安全性は保証できます (変更された定数にアドレス操作がない場合、その定数はシンボル テーブルに配置されます。そのため、ノーと言いましょう)割り当てられたスペース)。C 標準では、const で定義される定数はグローバルですが、C++ では宣言位置に依存します。
これは C 言語では変数ですが、定数のプロパティを持ち、直接変更することはできませんが、C++ では定数です。
アセンブリの観点から見ると、const は定数を定義しますが、対応するメモリ アドレスを与えるだけであり、const で定義された定数はプログラムの実行中にコピーが 1 つだけ保持されます。
const が変数を変更する場合、変数が格納される場所はそれとはほとんど関係ありません。一般的に言えば、変数はスタックに格納されます。静的変数またはグローバル変数がある場合は、データ セグメントに配置されます。
//C++ の場合
intmain(intargc, char*argv[])
{
// C++ では、基本的な型システムは C 用のスペースを空けず、それをシンボル テーブルに置きます
定数=0;
// int* p = &c; const int* から int* に変換できません
// C++ では、c のアドレスが取得されると、システムは c 用のスペースを空けます。
int*p= (int*)&c; // 正常に変換できます
cout<<"開始..."<<Qt::endl;
// スペースの内容は変更可能
*p=5;
// シンボルテーブルの内容を読み込みます
cout<<c<<Qt::endl;
// スペースの内容を読み取ります
cout<<*p<<Qt::endl;
cout<<"End"<<Qt::endl;
金額=200;
constantb=a; //システムは、b をシンボルテーブルに入れずに、b 用のスペースを直接開きます。
// const カスタム データ型システムは、読み取りと書き込みが可能なスペースを割り当てます。
constPersonper= {100, "トム"};
}
// 結果を出力する
始める...
0
5
終わり
上記のコードは C 言語では c=5 を出力しますが、C++ では C は 0 のままです。C++ でのコンパイル中に定数が使用されていることが判明した場合、その定数はシンボル テーブル内の値に直接置き換えられることに注意してください。
ポインタと定数
ポインターの使用には、ポインター自体とポインターが指すオブジェクトという 2 つのオブジェクトが関係します。
1. const を使用してポインタの宣言を変更するとは、ポインタを定数としてではなく、指す先のオブジェクトを定数にすることです。
charconst*pc1; //const charへのポインタ
constchar*pc2; //const char へのポインタ
これら 2 つの宣言は同等です
2. ポインタ自体を定数として宣言する
char*constcp; //char cp への const ポインタ
記憶方法:右から左に読む
「cp は char への const ポインタです。」 したがって、cp は他の文字列を指すことはできませんが、それが指す文字列の内容を変更することはできます。
「pc1 は const char へのポインタ」であるため、*pc1 の内容は変更できませんが、pc1 は他の文字列を指すことができます。
const 変更された関数の受信パラメータ
関数の受信パラメーターを const として宣言して、そのようなパラメーターの使用が効率上の理由のみであり、呼び出し関数がオブジェクトの値を変更できるようにしないことを示します。同様に、ポインター パラメーターが const として宣言されている場合、関数はこのパラメーターが指すオブジェクトを変更できません。
voidFun(constA*in); //ポインタ型の受信パラメータを変更します
voidFun(constA&in); //参照型の受信パラメータを変更します
voidfunc_02(int*constp)
{
int*pi=newint(100);
//間違い!P はポインタ定数です。値を割り当てることはできません。
p=パイ;
}
intmain()
{
int*p=newint(10);
func_02(p);
削除します。
戻り0;
}
このようなメソッドを使用すると、関数の呼び出し元がパラメーターの値を変更するのを防ぐことができます。ただし、そのような制限には制限があるため、パラメーターの呼び出し元として、パラメーターの値を変更しようとすべきではありません。したがって、次の操作は構文的には正しいですが、パラメーターの値が破損する可能性があります。
#include <iostream>
#include <文字列>
voidfunc(consint*pi)
{
//これは、同じメモリ領域を指すようにポインタを再構築することと同じです。もちろん、このポインタを介してメモリ内の値を変更することもできます。
int*pp= (int*)pi;
*pp=100;
}
intmain()
{
namespacestd を使用します。
int*p=newint(10);
cout<<"*p = "<<*p<<endl;
関数(p);
cout<<"*p = "<<*p<<endl;
削除します。
戻り0;
}
非内部データ型の入力パラメータについては、効率を向上させるために、「値による受け渡し」の方法を「const 参照による受け渡し」に変更する必要があります。たとえば、 void Func(A a) を void Func(const A &a) に変更します。
内部データ型の入力パラメータについては、「値渡し」から「const参照渡し」に変更しないでください。そうしないと、効率化の目的が達成されず、機能のわかりやすさが低下します。たとえば、 void Func(int x) を void Func(const int &x) に変更しないでください。
変更された関数の戻り値
ユーザーが戻り値を変更できないようにすることができます。戻り値もそれに応じて定数または定数ポインターに割り当てる必要があります。
const 定数 (const int max = 100 など); 利点: const 定数にはデータ型がありますが、マクロ定数にはデータ型がありません。コンパイラは前者に対して型安全性チェックを実行できますが、後者に対しては型安全性チェックを行わずに文字置換のみを実行するため、文字置換時に予期しないエラーが発生する可能性があります (限界効果)。
変更されたクラスの const データ メンバー。好き:
クラスA
{
定数サイズ;
…
}
const データ メンバーはオブジェクトの存続期間中のみ定数ですが、クラス全体では変数です。クラスは複数のオブジェクトを作成できるため、オブジェクトごとに const データ メンバーに異なる値を持つことができます。そのため、クラスのオブジェクトが作成されていないとき、コンパイラは const データ メンバーの値がわからないため、クラス宣言で const データ メンバーを初期化することはできません。好き
クラスA
{
consintsize=100; //エラー
intarray[size]; //エラー、サイズが不明
}
const データ メンバーの初期化は、クラスのコンストラクターの初期化テーブルでのみ実行できます。クラス全体で一定の定数を作成したい場合は、クラス内で列挙型定数を使用して実現する必要があります。好き
クラスA
{…
enum {size1=100, size2=200 };
intarray1[サイズ1];
intarray2[サイズ2];
}
列挙型定数はオブジェクトの記憶領域を占有せず、すべてコンパイル時に評価されます。ただし、列挙定数の暗黙のデータ型は整数であり、その最大値は制限されており、浮動小数点数を表すことはできません。
const 修飾ポインタの場合は、次の式を参照してください。
intb=500;
定数*a=&[1]
intconst*a=&[2]
int*consist=& [3]
定数*consist=& [4]
const がアスタリスクの左側にある場合、const はポインタが指す変数、つまりポインタが定数を指すのに使用されます。const がアスタリスクの右側にある場合、const はポインタ自体、つまりポインタ自体が定数です。したがって、[1]と[2]の状況は同じであり、ポインタの指す内容は定数(変数宣言子のconstの位置は関係ありません)であり、この場合、変数宣言子内のconstの位置を変更することはできません。 content, such as *a = 3; [3] は、ポインタ自体は定数であるが、ポインタが指す内容は定数ではないことを意味します。この場合、a++ が間違っているなど、ポインタ自体を変更することはできません。 4] は、ポインタ自体とポイントされたコンテンツの両方が定数であることを意味します。
const A fun2( ); const A* fun3( )
このように戻り値が宣言された後、const は「変更原則」に従って変更され、対応する保護の役割を果たします。
constRationaloperator*(constRational&lhs, constRational&rhs)
{
returnRational(lhs.numerator() *rhs.numerator(),
lhs.denominator() *rhs.denominator());
}
このような操作が許可されないように、戻り値は const で変更されます。 Rational a,b;Radional c;(a*b) = c;
一般にconstを使ってオブジェクト自体(非参照やポインタ)の戻り値を変更するケースは、二項演算子が関数をオーバーロードして新しいオブジェクトを生成する場合によく使われます。
一般に、関数の戻り値がオブジェクトの場合、それが const として宣言されている場合、演算子のオーバーロードによく使用されます。一般に、関数の戻り値の型をオブジェクトまたはオブジェクトへの参照として変更するために const を使用することはお勧めできません。その理由は次のとおりです。戻り値が何らかのオブジェクトに対する const である場合 (const A test = A のインスタンス)、またはあるオブジェクトへの参照が const (const A& test = A のインスタンス) である場合、戻り値には const プロパティが含まれます。返されたインスタンスはアクセスのみ可能です。クラス A のパブリック (保護された) データ メンバーと const メンバー関数は割り当てることができず、一般に使用されることはほとんどありません。
「ポインタ受け渡し」方式を採用した関数の戻り値にconst装飾を付加すると、関数の戻り値(ポインタ)の内容は変更できなくなり、戻り値は同じ型の関数にしか代入できなくなります。 const 装飾が施されたポインター。好き:
const char * GetString(void);
次のステートメントではコンパイル エラーが発生します。
char *str=GetString();
正しい使用法は次のとおりです。
const char *str=GetString();
関数の戻り値に「参照渡し」が採用されるケースは多くありませんが、このメソッドは通常クラスの値関数内でのみ出現し、チェーン表現を実現することが目的です。好き:
クラスA
{…
A&operate= (constA&other); //代入関数
}
Aa,b,c; //a,b,c は A のオブジェクトです
…
a=b=c; //通常
(a=b)=c; //通常ではありませんが、合法です
負の関数の戻り値が const で変更された場合、戻り値の内容は変更できませんが、上記の例の a=b=c は依然として正しいです。(a=b)=c は誤りです。[考え方3]: 代入演算子のオーバーロード関数はこのように定義しても良いでしょうか?
const A& 演算子=(const A& a);
const 変更されたメンバー関数
定数関数は C++ を定数に拡張したもので、C++ でのクラスのカプセル化が非常に適切に行われます。C++ では、クラスのデータ メンバーへの不正アクセスを防ぐために、クラスのメンバー関数は 2 つのカテゴリに分けられ、1 つは定数メンバー関数 (オブザーバーとも呼ばれます)、もう 1 つは非定数メンバーです。関数 (ミュータントになるとも呼ばれます)。キーワード const が関数のシグネチャに追加されると、その関数は定数関数になります。定数関数の場合、最も重要な違いは、コンパイラがクラスのデータ メンバーを変更できないことです。例えば:
Constオブジェクトは const メンバー関数にのみアクセスできますが、非 const オブジェクトは const メンバー関数を含む任意のメンバー関数にアクセスできます。
constオブジェクトのメンバーは変更できませんが、ポインターによって保持されるオブジェクトは実際に変更できます。オブジェクトがconst であるかどうかに関係なく、 constメンバー関数はオブジェクトのデータを変更できません。メンバーデータが変更されているかどうかに基づいて、コンパイル時にチェックされます。
クラステスト
{
公共:
voidfunc() 定数;
プライベート:
inintValue;
};
voidTest::func() const
{
intValue=100;
}
上記のコードでは、定数関数 func がデータ メンバー intValue の値を変更しようとしているため、コンパイル中に例外がスローされます。
もちろん、非定数メンバー関数の場合は、必要に応じてデータ メンバーの値を読み取ったり変更したりできます。ただし、これは関数を呼び出すオブジェクトが定数かどうかによって異なります。通常、クラスを定数として定義する場合、その状態 (データ メンバー) が変更されないことを期待します。では、定数オブジェクトがその非 const 関数を呼び出すとどうなるでしょうか? 以下のコードを見てください。
クラスフレッド
{
公共:
voidinspect() const;
voidmutate();
};
voidUserCode(Fred&変更可能、constFred&unChangeable)
{
changeable.inspect(); // 正しい非定数オブジェクトは定数関数を呼び出すことができます。
changeable.mutate(); // 正しい非定数オブジェクトでは、非定数メンバー関数を呼び出してデータ メンバーを変更する変更も可能です。
unChangeable.inspect(); // 正しい、定数オブジェクトは通常の関数のみを呼び出すことができます。オブジェクトの状態を変更したくないからです。
unChangeable.mutate(); // エラー! 定数オブジェクトの状態は変更できませんが、非定数関数はオブジェクトの状態を変更できます。
}
上記のコードからわかるように、定数オブジェクトの状態は変更できないため、定数オブジェクトを介して非定数関数を呼び出すと構文エラーが発生します。実際、すべてのメンバー関数には、オブジェクト自体を指す暗黙的な this ポインターがあることがわかっています。定数関数には、これへの定数ポインタが含まれています。次のように:
voidinspect(constFred*this) const;
voidmutate(Fred*this);
つまり、定数関数の場合、 this ポインタを介してオブジェクトに対応するメモリ ブロックを変更することはできません。ただし、これはコンパイラの制限にすぎないことはすでにわかっており、オブジェクトの状態を変更するためにコンパイラの制限をバイパスすることもできます。以下のコードを見てください。
クラスフレッド
{
公共:
voidinspect() const;
プライベート:
inintValue;
};
voidFred::inspect() 定数
{
cout<<"最初に。 intValue = "<<intValue<<endl;
// ここで、このポインタに基づいて同じメモリ アドレスへのポインタを再定義します。
// この新しく定義されたポインタを通じて、オブジェクトの状態を変更できます。
フレッド*pフレッド= (フレッド*)これ;
pFred->intValue=50;
cout<<"Fred::inspect() が呼び出されました。 intValue = "<<intValue<<endl;
}
intmain()
{
フレドフレッド;
フレッド.inspect();
戻り0;
}
上記のコードは、必要に応じて定数関数を通じてオブジェクトの状態を変更できることを示しています。同様に、定数オブジェクトの場合、同じメモリへの別のポインタを構築してその状態を変更することもできます。ここではあまり説明しません。
さらに、コンパイラのエラーを回避してクラスのデータ メンバーを変更することはできますが、そのようなケースもあります。ただし、C++ では、データ メンバーの定義の前に mutable を追加して、定数関数内でメンバーを変更できるようにすることもできます。例えば:
クラスフレッド
{
公共:
voidinspect() const;
プライベート:
mutableintintValue;
};
voidFred::inspect() 定数
{
intValue=100;
}
ただし、すべてのコンパイラが mutable キーワードをサポートしているわけではありません。このとき、私たちの上にある曲がった道が役に立つでしょう。
定数関数に関しては、オーバーロードという別の問題があります。
#include <iostream>
#include <文字列>
namespacestd を使用します。
クラスフレッド
{
公共:
voidfunc() 定数;
voidfunc();
};
voidFred::func() const
{
cout<<"const 関数が呼び出されます。"<<endl;
}
voidFred::func()
{
cout<<"非 const 関数が呼び出されます。"<<endl;
}
voidUserCode(Fred&fred, constFred&cFred)
{
cout<<"fred は非 const オブジェクトであり、fred.func() の結果は次のようになります。"<<endl;
フレッド.func();
cout<<"cFred は const オブジェクトであり、cFred.func() の結果は次のようになります。"<<endl;
cFred.func();
}
intmain()
{
フレドフレッド;
ユーザーコード(フレッド, フレッド);
戻り0;
}
出力は次のとおりです。
fred は非 const オブジェクトであり、 fred.func() の結果は次のようになります。
非const関数が呼び出されます。
cFred は const オブジェクトであり、cFred.func() の結果は次のようになります。
const関数が呼び出されます。
上記の出力から、それがわかります。名前、パラメータ、戻り値が同じ定数関数と非定数関数が存在する場合、呼び出し元のオブジェクトが定数オブジェクトであるか非定数オブジェクトであるかによって、どちらの関数を呼び出すかが決まります。定数オブジェクトは const メンバーを呼び出し、非 const オブジェクトは非 const メンバーを呼び出します。
つまり、一定の機能はオブジェクトの安全性を最大限に確保することであることを理解する必要があります。定数関数を使用すると、オブジェクトの状態を変更するために必要な操作のみを許可できるため、誤用によるオブジェクトの状態の破壊を防ぐことができます。ただし、上で見たように、そのような保護は実際には制限されています。重要なのは、開発者が使用規則を厳密に遵守する必要があるということです。もう 1 つ注意すべき点は、定数オブジェクトは非定数関数を呼び出すことができないことです。このような規制は任意ですが、私たち全員が原則に従ってクラスを作成したり使用したりするのであれば、それは完全に理解できます。
定数と参照
定数と参照の関係はもう少し単純です。参照は別の変数のエイリアスにすぎず、それ自体は定数であるためです。つまり、参照は別の変数のエイリアスになることはできなくなり、変数かどうかによって表されるメモリ領域のみを持つことになります。たった今:
インティ=10;
// 正解: 対応するメモリの内容は、この参照を通じて変更できないことを示します。
定数&ri=i;
// 間違い!このように書くことはできません。文法上の誤り
// int& const rci = i;
// 関数の呼び出し元にパラメーターの値を変更させたくない場合にわかります。最も信頼できる方法は、参照を使用することです。次の操作ではコンパイル エラーが発生します。
voidfunc(定数&i)
{
// 間違い!それが表すメモリ領域は、i を通じて変更することはできません。
i=100;
}
intmain()
{
インティ=10;
関数(i);
戻り0;
}
定数とポインタ、および定数と参照の関係はここで理解できました。ただし、以下では詳細な説明が必要です。システムはプログラムをロードする際に、メモリをヒープ領域、スタック領域、グローバル領域(静的)、コード領域の 4 つの領域に分割します。このことから、定数の場合、システムはその中のデータが変更されないように保護するための特別な領域を指定していないことがわかります。つまり、定数を使用してデータを保護する方法は、コンパイラの構文制限によって実現されます。「定数」として定義されたメモリ領域を変更するために、コンパイラの制限をバイパスすることもできます。以下のコードを見てください。
定数=10;
// ここで i は定数として定義されていますが、他の方法でその値を変更することもできます。
// これは、i を定数として定義すると、i で表されるメモリが変更されるのを実際に防ぐことを示しています。
int*pi= (int*) &i;