私はいつもconst int*
、const int * const
、そしてint const *
の正しい使い方をめちゃくちゃにしています。できることとできないことを定義する一連の規則はありますか?
割り当て、関数への受け渡しなどの観点から、すべてのことやしないことを知りたいです。
逆方向に読んでください( 時計回り/スパイラルルール で動かされるように):
int*
- intへのポインタint const *
- const intへのポインタint * const
- intへのconstポインタint const * const
- const intへのconstポインタこれで最初のconst
は型のどちらの側にも置けるようになりました。
const int *
== int const *
const int * const
== int const * const
あなたが本当に夢中になりたいなら、あなたはこのようなことをすることができます:
int **
- intへのポインタへのポインタint ** const
- intへのポインタへのconstポインタint * const *
- intへのconstポインタへのポインタint const **
- const intへのポインタへのポインタint * const * const
- intへのconstポインタへのconstポインタそして、constの意味を明確にするために
const int* foo;
int *const bar; //note, you actually need to set the pointer
//here because you can't change it later ;)
foo
は、定数整数への可変ポインタです。これにより、自分が指しているものを変更できますが、自分が指している値は変更できません。ほとんどの場合、これはconst char
へのポインタがあるCスタイルの文字列で見られます。どの文字列を指すかは変更できますが、これらの文字列の内容を変更することはできません。これは、文字列自体がプログラムのデータセグメント内にあり、変更してはいけない場合に重要です。
bar
は、変更可能な値への定数ポインタまたは固定ポインタです。これは余分な構文糖がない参照のようなものです。このため、NULL
ポインタを許可する必要がない限り、通常はT* const
ポインタを使用する場所に参照を使用します。
時計回り/螺旋の法則を知らない人のために:変数の名前から始めて、次の ポインタ または type に時計回りに(この場合は後ろに移動)移動してください。式が終了するまで繰り返します。
これがデモです。
私はすべてここですでに答えられていると思います、しかし私はあなたがtypedef
sに注意するべきであると付け加えたいです!それらは単なるテキストの置き換えではありません。
例えば:
typedef char *ASTRING;
const ASTRING astring;
astring
の型はchar * const
であり、const char *
ではありません。これが私がいつもconst
を型の右側に置く傾向がある理由の一つであり、決して始めにはありません。
ほとんどの人が指摘したように:
const X* p
、X* const p
、const X* const p
の違いは何ですか?
ポインタ宣言は右から左に読む必要があります。
const X* p
は、「pがconstであるXを指す」ことを意味します。Xオブジェクトは、pを介して変更することはできません。
X* const p
は「pはconstではないXへのconstポインタ」を意味します。ポインタp自体は変更できませんが、pを介してXオブジェクトを変更することはできます。
const X* const p
は、「pはconstであるXへのconstポインタ」を意味します。ポインタp自体を変更することも、pを介してXオブジェクトを変更することもできません。
定数の参照:
定数(ここではint)への参照。定数です。参照は実際の値よりサイズが小さいため、主に変数を参照として渡しますが、副作用があります。これは、実際の変数の別名のようなものだからです。エイリアスへのフルアクセスを通じて誤って主変数を変更する可能性があるため、この副作用を防ぐために、主変数を一定にします。
int var0 = 0;
const int &ptr1 = var0;
ptr1 = 8; // Error
var0 = 6; // OK
定数ポインタ
定数ポインタが変数を指すと、他の変数を指すことはできません。
int var1 = 1;
int var2 = 0;
int *const ptr2 = &var1;
ptr2 = &var2; // Error
定数へのポインタ
それが指す変数の値を変更することができないポインタは、定数へのポインタとして知られています。
int const * ptr3 = &var2;
*ptr3 = 4; // Error
定数への定数ポインタ
定数への定数ポインタは、それが指すアドレスを変更することも、そのアドレスに保持されている値を変更することもできないポインタです。
int var3 = 0;
int var4 = 0;
const int * const ptr4 = &var3;
*ptr4 = 1; // Error
ptr4 = &var4; // Error
一般的な規則として、const
キーワードはその直前のキーワードに適用されます。例外として、先頭のconst
は以下のものに適用されます。
const int*
はint const*
と同じで、 "定数intへのポインタ" を意味します。const int* const
はint const* const
と同じで、 "定数intへの定数ポインタ" を意味します。編集: DosとDon'tsの場合、 この答え で十分ではない、あなたが望むものについてより正確になることができますか?
この質問は 正確に を示していますなぜ私が私の質問で述べたように物事をやりたいのですか? type idの後のconstは許容できますか
手短に言えば、この規則を覚える最も簡単な方法は、 "const"がが適用されるものの後)になることです。したがって、 "int const *"はintが定数であるのに対して "int"は定数です。 * const "はポインタが定数であることを意味します。
誰かがそれを最前面に置くことを決定した場合(例: "const int *")、その場合の特別な例外として、それはそれ以降のものに適用されます。
彼らはそれがよりきれいに見えると思うので、多くの人がその特別な例外を使うのを好みます。私はそれが嫌いだ、なぜならそれは例外であり、そしてそれ故に物事を混乱させる。
「const」の簡単な使い方
最も簡単な使い方は、名前付き定数を宣言することです。これを行うには、定数を変数のように宣言しますが、その前にconstを追加します。もちろん、後で値を変更することになるため、後で値を設定することはできないため、コンストラクタですぐに初期化する必要があります。例えば、
const int Constant1=96;
想像もつかないほど 'Constant1'と呼ばれる値96の整数定数を作成します。
このような定数は、プログラムで使用されているがプログラムのコンパイル後に変更する必要がないパラメータには便利です。 Cプリプロセッサの '#define'コマンドよりもプログラマにとって利点があります。メインコンパイラに到達する前にプリプロセッサによってプログラムテキストに置き換えられるだけでなく、コンパイラ自体によって理解され使用されるため、エラーメッセージははるかに有用です。 。
これはポインタでも動作しますが、ポインタが指すのか、それが指すものが定数であるのか、あるいはその両方なのかを判断するには、constの場所に注意する必要があります。例えば、
const int * Constant2
constant2が定数整数への可変ポインタであることを宣言し、
int const * Constant2
は同じことをする代替構文ですが、
int * const Constant3
constant3が可変整数への定数ポインタであり、かつ
int const * const Constant4
constant4が定数整数への定数ポインタであることを宣言します。基本的に「const」はそのすぐ左にあるものすべてに適用されます(その場合、そのすぐ右にあるものすべてに適用されるものがない場合を除く)。
ref: http://duramecho.com/ComputerInformation/WhyHowCppConst.html
C++ Guru Scott Meyersによるこの book に出会うまで、私はあなたと同じ疑問を抱いていました。この本の3番目の項目を参照してください。彼はconst
の使い方について詳しく述べています。
このアドバイスに従ってください
const
がアスタリスクの左側に表示されている場合、指しているのは定数です。const
がアスタリスクの右側に表示される場合、ポインタ自体は定数です。const
が両側にある場合、両方とも定数ですそれは簡単ですがトリッキーです。 const
修飾子は任意のデータ型(int
、char
、float
など)と交換できることに注意してください。
以下の例を見てみましょう。
const int *p
==> *p
は読み取り専用です[p
は定数整数へのポインタです]
int const *p
==> *p
は読み取り専用です[p
は定数整数へのポインタです]
int *p const
==> 間違った ステートメント。コンパイラは構文エラーをスローします。
int *const p
==> p
は読み取り専用です[p
は整数への定数ポインタ]。ここでのポインタp
は読み取り専用なので、宣言と定義は同じ場所になければなりません。
const int *p const
==> 間違った ステートメント。コンパイラは構文エラーをスローします。
const int const *p
==> *p
は読み取り専用です
const int *const p1
==> *p
とp
は読み取り専用です[p
は定数整数への定数ポインタ]。ここでのポインタp
は読み取り専用なので、宣言と定義は同じ場所になければなりません。
int const *p const
==> 間違った ステートメント。コンパイラは構文エラーをスローします。
int const int *p
==> 間違った ステートメント。コンパイラは構文エラーをスローします。
int const const *p
==> *p
は読み取り専用で、int const *p
と同じです。
int const *const p
==> *p
とp
は読み取り専用です[p
は定数整数への定数ポインタ]。ここでのポインタp
は読み取り専用なので、宣言と定義は同じ場所になければなりません。
C++におけるconstの正しさを取り巻く微妙な点が他にもたくさんあります。ここでの質問は単にCに関するものであると思いますが、タグはC++なので、いくつかの関連例を挙げます。
文字列のような大きな引数をTYPE const &
として渡すと、オブジェクトの変更やコピーができなくなります。例:
TYPE& TYPE::operator=(const TYPE &rhs) { ... return *this; }
しかし、TYPE & const
は無意味です。なぜなら参照は常にconstだからです。
クラスを変更しないクラスメソッドは常にconst
としてラベル付けする必要があります。そうしないと、TYPE const &
参照からメソッドを呼び出すことができません。例:
bool TYPE::operator==(const TYPE &rhs) const { ... }
戻り値とメソッドの両方がconstであるべき一般的な状況があります。例:
const TYPE TYPE::operator+(const TYPE &rhs) const { ... }
実際、constメソッドは、内部クラスデータを非constへの参照として返してはいけません。
その結果、多くの場合、constオーバーロードを使用して、constメソッドと非constメソッドの両方を作成する必要があります。例えば、もしあなたがT const& operator[] (unsigned i) const;
を定義するならば、あなたはおそらくまた以下によって与えられる非constバージョンが欲しいでしょう:
inline T& operator[] (unsigned i) { return const_cast<char&>( static_cast<const TYPE&>(*this)[](i) ); }
残念ながら、Cにはconst関数はありません、C++では非メンバ関数自体をconstにすることはできません。constメソッドには副作用があり、コンパイラはconst関数を使用して関数呼び出しの重複を避けることはできません。実際、単純なint const &
参照でさえ、それが参照する値が他の場所で変更されているのを目にするかもしれません。
両側にintがあるconstは ポインタを定数int にします。
const int *ptr=&i;
または
int const *ptr=&i;
'*'の後のconstは 定数ポインタをint にします。
int *const ptr=&i;
この場合、これらすべては 定数整数へのポインタ ですが、これらはどれも定数ポインタではありません。
const int *ptr1=&i, *ptr2=&j;
この場合、すべてが 定数整数 へのポインタで、ptr2が 定数整数への定数ポインタ です。しかしptr1は定数ポインタではありません。
int const *ptr1=&i, *const ptr2=&j;
これは、主に2番目の行(ベストプラクティス、割り当て、関数パラメーターなど)に対応しています。
一般的な練習。 const
できるすべてのものを作成してください。または、別の言い方をすれば、すべてをconst
にして、プログラムを機能させるために必要なconst
sの最小セットを完全に削除します。これは、const-correctnessを達成する上で大きな助けとなり、人々が修正するべきではないものに割り当てようとするときに微妙なバグが導入されないようにするのに役立ちます。
ペストのようなconst_cast <>は避けてください。それには1つまたは2つの正当な使用例がありますが、それらはごくわずかであり、その間でもありません。 const
オブジェクトを変更しようとしている場合、最初のペースでconst
を宣言した人を見つけて、何についてのコンセンサスを得るために彼らと話し合うかにより良い結果が得られます。起こるはずです。
これは非常にきちんと割り当てにつながります。非constである場合にのみ、何かに割り当てることができます。 constに割り当てる場合は、上記を参照してください。宣言ではint const *foo;
とint * const bar;
異なるものがconst
であることを覚えておいてください-ここでの他の答えは見事にその問題をカバーしているので、私はそれに入りません。
関数パラメーター:
値渡し:例void func(int param)
呼び出し側でどちらの方法でもかまいません。関数をvoid func(int const param)
として宣言するユースケースがあることを引数にできますが、呼び出し中は関数自体にのみ影響し、呼び出し中に関数によって渡される値は変更できません。 。
参照渡し:例void func(int ¶m)
これで違いが生じます。宣言されたようにfunc
はparam
の変更を許可されており、呼び出し元のサイトは結果に対処する準備ができているはずです。宣言をvoid func(int const ¶m)
に変更すると、契約が変更され、func
がparam
を変更できなくなることが保証されます。つまり、渡されるものが戻ってくることを意味します。他の人が指摘したように、これは変更したくない大きなオブジェクトを安く渡すために非常に便利です。参照を渡すことは、大きなオブジェクトを値で渡すよりもはるかに安価です。
ポインターで渡す:例void func(int *param)
およびvoid func(int const *param)
これら2つは、参照の対応物とほぼ同義です。ただし、他の契約上の保証によりnullptr
が保証されない限り、呼び出された関数はfunc
をチェックする必要がありますnullptr
でparam
を受け取ることはありません。
そのトピックに関する意見書。このようなケースで正しさを証明することは地獄のように困難であり、間違いを犯すのは簡単すぎます。したがって、チャンスをとらないで、nullptr
のポインターパラメーターを常に確認してください。あなたは自分自身の痛みと苦しみを救い、長期的にはバグを見つけるのは難しいでしょう。また、チェックのコストに関して言えば、コストは安く、コンパイラーに組み込まれた静的分析で管理できる場合は、オプティマイザーがそれを排除します。 MSVCのLink Time Code Generation、またはGCCのWOPR(私は思う)をオンにすると、プログラム全体で、つまりソースコードモジュールの境界を越える関数呼び出しでも使用できるようになります。
結局のところ、上記のすべてが常にポインターへの参照を好むという非常に堅実なケースを作ります。ずっと安全です。
他の説明に従ってCを完全にするためだけにあり、C++の場合は不明です。
x
int *p;
int const *p;
int * const p;
int const * const p;
int **pp;
int ** const pp;
int * const *pp;
int const **pp;
int * const * const pp;
int const ** const pp;
int const * const *pp;
int const * const * const pp;
// Example 1
int x;
x = 10;
int *p = NULL;
p = &x;
int **pp = NULL;
pp = &p;
printf("%d\n", **pp);
// Example 2
int x;
x = 10;
int *p = NULL;
p = &x;
int ** const pp = &p; // Definition must happen during declaration
printf("%d\n", **pp);
// Example 3
int x;
x = 10;
int * const p = &x; // Definition must happen during declaration
int * const *pp = NULL;
pp = &p;
printf("%d\n", **pp);
// Example 4
int const x = 10; // Definition must happen during declaration
int const * p = NULL;
p = &x;
int const **pp = NULL;
pp = &p;
printf("%d\n", **pp);
// Example 5
int x;
x = 10;
int * const p = &x; // Definition must happen during declaration
int * const * const pp = &p; // Definition must happen during declaration
printf("%d\n", **pp);
// Example 6
int const x = 10; // Definition must happen during declaration
int const *p = NULL;
p = &x;
int const ** const pp = &p; // Definition must happen during declaration
printf("%d\n", **pp);
// Example 7
int const x = 10; // Definition must happen during declaration
int const * const p = &x; // Definition must happen during declaration
int const * const *pp = NULL;
pp = &p;
printf("%d\n", **pp);
// Example 8
int const x = 10; // Definition must happen during declaration
int const * const p = &x; // Definition must happen during declaration
int const * const * const pp = &p; // Definition must happen during declaration
printf("%d\n", **pp);
ただ進み続けなさい、しかし人類があなたを非難するかもしれない.
int x = 10;
int *p = &x;
int **pp = &p;
int ***ppp = &pp;
int ****pppp = &ppp;
printf("%d \n", ****pppp);
const
が左へ of *
の場合、値を参照します(const int
であるかint const
であるかは関係ありません)const
が右へ of *
の場合、ポインター自体を参照します重要なポイント:const int *p
参照している値が一定であることを意味しない!!。これは変更できないことを意味しますそのポインターを使用。値自体は変更される場合があります。例えば
int x = 5;
const int *p = &x;
x = 6; //legal
printf("%d", *p) // prints 6
*p = 7; //error
これは、関数が渡される引数を変更できないことを保証するために、主に関数シグネチャで使用されることを意図しています。