参照は構文上の砂糖であることを知っているので、コードの読み書きは簡単です。
しかし、違いは何ですか?
int &x = *(int*)0;
gccで。参照は確かにNULLを指すことができます。
参照は構文上の砂糖であることを知っているので、コードの読み書きは簡単です。
しかし、違いは何ですか?
int &x = *(int*)0;
gccで。参照は確かにNULLを指すことができます。
回答:
ポインタを再割り当てすることができます:
int x = 5;
int y = 6;
int *p;
p = &x;
p = &y;
*p = 10;
assert(x == 5);
assert(y == 10);
参照は初期化時に割り当てることができず、割り当てる必要があります。
int x = 5;
int y = 6;
int &r = x;
ポインタは、スタック上に独自のメモリアドレスとサイズ(x86では4バイト)を持っていますが、参照は(元の変数と)同じメモリアドレスを共有しますが、スタック上でいくらかの領域を占有します。参照は元の変数自体と同じアドレスを持っているため、参照を同じ変数の別の名前と考えるのが安全です。注:ポインターが指すものは、スタックまたはヒープ上にあります。同上リファレンス。このステートメントでの私の主張は、ポインターがスタックを指す必要があるということではありません。ポインタは、メモリアドレスを保持する単なる変数です。この変数はスタックにあります。参照にはスタック上に独自のスペースがあるため、アドレスは参照先の変数と同じであるためです。スタックとヒープの詳細。これは、コンパイラーが指示しない参照の実際のアドレスがあることを意味します。
int x = 0;
int &r = x;
int *p = &x;
int *p2 = &r;
assert(p == p2);
間接参照の追加レベルを提供するポインターへのポインターへのポインターを持つことができます。一方、参照は1レベルの間接参照しか提供しません。
int x = 0;
int y = 0;
int *p = &x;
int *q = &y;
int **pp = &p;
pp = &q;//*pp = q
**pp = 4;
assert(y == 4);
assert(x == 0);
ポインタはnullptr
直接割り当てることができますが、参照はできません。十分に努力し、その方法を知っている場合は、参照のアドレスを作成できますnullptr
。同様に、十分に努力すれば、ポインターへの参照を作成でき、その参照にを含めることができますnullptr
。
int *p = nullptr;
int &r = nullptr; <--- compiling error
int &r = *p; <--- likely no compiling error, especially if the nullptr is hidden behind a function call, yet it refers to a non-existent int at address 0
ポインタは配列を反復できます。を使用++
して、ポインターが指している次の項目+ 4
に移動し、5番目の要素に移動できます。これは、ポインタが指すオブジェクトのサイズに関係ありません。
ポインタは、*
それが指すメモリ位置にアクセスするために逆参照する必要がありますが、参照は直接使用できます。->
参照がを使用するのに対し、クラス/構造体へのポインタはそのメンバーにアクセスするために使用します.
。
参照は配列に詰め込むことはできませんが、ポインターはできます(ユーザー@litbにより言及)
const参照は一時にバインドできます。ポインタは(いくつかの間接なしではありません)できません:
const int &x = int(12); //legal C++
int *y = &int(12); //illegal to dereference a temporary.
これによりconst&
、引数リストなどでの使用がより安全になります。
リファレンスはと考えることができ、一定のポインタコンパイラが適用される自動間接付き(一定の値へのポインタと混同しないように!)、すなわち*
あなたのための演算子を。
すべての参照はnull以外の値で初期化する必要があります。そうしないと、コンパイルが失敗します。参照のアドレスを取得することはできません-アドレス演算子は代わりに参照された値のアドレスを返します-参照で算術を行うこともできません。
Cプログラマーは、C ++参照を嫌う可能性があります。これは、間接参照が発生したとき、または引数が値またはポインターによって関数シグネチャを調べずに渡されたときに、もはや明らかではないためです。
C ++プログラマーは、安全でないと見なされているため、ポインターの使用を嫌う可能性があります。ただし、参照は、ほとんどの場合を除いて、定数ポインターよりも実際には安全ではありません。
参照は、基になるアセンブリ言語のアドレスを使用して実装されることが多いですが、参照をオブジェクトへの変なポインタとして考えないでください。参照はオブジェクトです。これは、オブジェクトへのポインタでも、オブジェクトのコピーでもありません。それが対象です。
しかし、参照が実際にオブジェクトである場合、参照がぶら下がっているのはどうしてでしょうか?アンマネージ言語では、参照をポインターよりも「安全」にすることは不可能です。通常、スコープの境界を越えて値を確実にエイリアスする方法はありません。
Cの背景から来て、C ++の参照は多少愚かな概念のように見えるかもしれませんが、一つはまだ可能ポインタの代わりにそれらを使用する必要があります。自動間接があり、便利、そして扱う際の参照が特に便利になるRAII -ではなく、理由の任意の知覚安全性の利点ですが、むしろ、慣用的なコードを書きにくくしません。
RAIIはC ++の中心的な概念の1つですが、セマンティクスのコピーと重要な相互作用をします。参照によってオブジェクトを渡すと、コピーが含まれないため、これらの問題を回避できます。言語に参照が存在しない場合は、代わりにポインタを使用する必要があります。これは使用がより面倒なので、ベストプラクティスのソリューションは他の方法よりも簡単であるという言語設計の原則に違反しています。
本当に知識を深めたい場合、参照を使用してポインタで行うことはできません。一時オブジェクトの寿命を延ばすことができます。C ++では、const参照を一時オブジェクトにバインドすると、そのオブジェクトの存続期間が参照の存続期間になります。
std::string s1 = "123";
std::string s2 = "456";
std::string s3_copy = s1 + s2;
const std::string& s3_reference = s1 + s2;
この例では、s3_copyは連結の結果である一時オブジェクトをコピーします。一方、s3_referenceは本質的に一時オブジェクトになります。これは、実際には参照と同じ存続期間を持つようになった一時オブジェクトへの参照です。
これなしでこれを試すとconst
、コンパイルに失敗するはずです。非const参照を一時オブジェクトにバインドしたり、そのアドレスを取得したりすることはできません。
const &
バインディングの事実によって延長され、参照がスコープから外れたときにのみ、実際の参照された型のデストラクタ(参照型と比較して、ベースである可能性があります)が呼び出されます。これは参照であるため、その間にスライスは行われません。
Animal x = fast ? getHare() : getTortoise()
、正しく動作します。x
Animal& x = ...
構文糖とは別に、参照はconst
ポインタです(へのポインタではありませんconst
)。参照変数を宣言するときに参照先を確立する必要があり、後で変更することはできません。
更新:もう少し考えてみると、重要な違いがあります。
constポインタのターゲットは、そのアドレスを取得してconstキャストを使用することで置き換えることができます。
リファレンスのターゲットは、UB以外の方法で置き換えることはできません。
これにより、コンパイラは参照に対してより多くの最適化を行うことができます。
T* const
を異なる構文糖(それがたまたまあなたのコードから*と&の多くを削除することになる)であると常に理解しています。
int i; int const *pci = &i; /* implicit conv to const int* */ int *pi = const_cast<int*>(pci);
。
世論に反して、NULLの参照を持つことは可能です。
int * p = NULL;
int & r = *p;
r = 1; // crash! (if you're lucky)
確かに、参照を使用することははるかに困難ですが、それを管理すると、それを見つけようとすると髪が裂けてしまいます。参照はC ++では本質的に安全ではありません!
技術的には、これは無効な参照であり、null参照ではありません。C ++は、他の言語で見られるような概念としてのnull参照をサポートしていません。他の種類の無効な参照もあります。任意の無効な参照はの亡霊上げる未定義の動作を単に無効なポインタを使用してしまうと、。
実際のエラーは、参照への割り当て前のNULLポインターの逆参照にあります。しかし、その条件でエラーを生成するコンパイラについては知りません。エラーはコードのさらに先のポイントに伝播します。これが、この問題を非常に油断ならないものにしているものです。ほとんどの場合、NULLポインタを逆参照すると、その場所でクラッシュし、それを理解するためにデバッグをあまり必要としません。
上記の私の例は短くて不自然です。これは、より現実的な例です。
class MyClass
{
...
virtual void DoSomething(int,int,int,int,int);
};
void Foo(const MyClass & bar)
{
...
bar.DoSomething(i1,i2,i3,i4,i5); // crash occurs here due to memory access violation - obvious why?
}
MyClass * GetInstance()
{
if (somecondition)
return NULL;
...
}
MyClass * p = GetInstance();
Foo(*p);
null参照を取得する唯一の方法は、不正な形式のコードを使用することであり、一度取得すると未定義の動作が発生することを繰り返し説明します。null参照をチェックしても意味がありません。たとえばif(&bar==NULL)...
、試すことはできますが、コンパイラはステートメントを存在しないように最適化する可能性があります!有効な参照がNULLになることは決してないので、コンパイラーの観点からは比較は常にfalseでありif
、デッドコードとして節を自由に削除できます。これが未定義の動作の本質です。
問題を回避する適切な方法は、参照を作成するためにNULLポインターを逆参照しないようにすることです。これを実現する自動化された方法を次に示します。
template<typename T>
T& deref(T* p)
{
if (p == NULL)
throw std::invalid_argument(std::string("NULL reference"));
return *p;
}
MyClass * p = GetInstance();
Foo(deref(p));
より優れたライティングスキルを持つ誰かによるこの問題の古い見方については、ジムハイスロップとハーブサッターのヌルリファレンスを参照してください。
nullポインターを逆参照することの危険性の別の例については、Raymond Chenがコードを別のプラットフォームに移植しようとしたときに未定義の動作を公開するを参照してください。
あなたは最も重要な部分を忘れました:
ポインタを使用した->
メンバーアクセス、参照を使用したメンバーアクセス.
->
、ポインター自体と同様に、ポインターへの参照を提供するものとまったく同じです。
.
と->
viとemacsの:)とは何かがある
.
より優れているの->
が、ちょうどemacsの対viのように、それは完全に主観的だとあなたは何を証明することはできません
参照はポインタと非常に似ていますが、コンパイラの最適化に役立つように特別に作成されています。
例として:
void maybeModify(int& x); // may modify x in some way
void hurtTheCompilersOptimizer(short size, int array[])
{
// This function is designed to do something particularly troublesome
// for optimizers. It will constantly call maybeModify on array[0] while
// adding array[1] to array[2]..array[size-1]. There's no real reason to
// do this, other than to demonstrate the power of references.
for (int i = 2; i < (int)size; i++) {
maybeModify(array[0]);
array[i] += array[1];
}
}
最適化コンパイラは、a [0]とa [1]にかなりアクセスしていることに気付くかもしれません。アルゴリズムを次のように最適化したいと思います。
void hurtTheCompilersOptimizer(short size, int array[])
{
// Do the same thing as above, but instead of accessing array[1]
// all the time, access it once and store the result in a register,
// which is much faster to do arithmetic with.
register int a0 = a[0];
register int a1 = a[1]; // access a[1] once
for (int i = 2; i < (int)size; i++) {
maybeModify(a0); // Give maybeModify a reference to a register
array[i] += a1; // Use the saved register value over and over
}
a[0] = a0; // Store the modified a[0] back into the array
}
このような最適化を行うには、呼び出し中に何もarray [1]を変更できないことを証明する必要があります。これはかなり簡単です。iが2未満になることはないため、array [i]はarray [1]を参照できません。maybeModify()には参照としてa0が指定されています(array [0]のエイリアス)。「参照」演算がないため、コンパイラーは、maybeModifyがxのアドレスを取得しないことを証明するだけで、配列を変更するものがないことが証明されます[1]。
また、a [0]に一時的なレジスタのコピーがある間、将来の呼び出しがa [0]を読み書きできる方法がないことも証明する必要があります。多くの場合、参照がクラスインスタンスのような永続的な構造体に格納されないことは明らかであるため、これを証明するのは簡単です。
ポインタで同じことをしてください
void maybeModify(int* x); // May modify x in some way
void hurtTheCompilersOptimizer(short size, int array[])
{
// Same operation, only now with pointers, making the
// optimization trickier.
for (int i = 2; i < (int)size; i++) {
maybeModify(&(array[0]));
array[i] += array[1];
}
}
動作は同じです。たった今、すでにポインタを指定しているため、maybeModifyがarray [1]を変更しないことを証明することははるかに困難です。猫は袋から出ています。今では、はるかに難しい証明を行う必要があります。maybeModifyの静的分析で、&x + 1に書き込まれないことを証明する必要があります。また、array [0]を参照できるポインターを保存しないことを証明する必要があります。トリッキー。
最近のコンパイラーは静的解析でますます良くなっていますが、それらを手助けしてリファレンスを使用することは常に素晴らしいことです。
もちろん、そのような賢い最適化を除けば、コンパイラーは実際に必要なときに参照をポインターに変えます。
編集:この回答を投稿してから5年後、参照が同じアドレッシングコンセプトの別の見方とは異なる実際の技術的な違いを発見しました。参照は、ポインターができない方法で一時オブジェクトの寿命を変更できます。
F createF(int argument);
void extending()
{
const F& ref = createF(5);
std::cout << ref.getArgument() << std::endl;
};
通常、への呼び出しによって作成されたオブジェクトなどの一時的なオブジェクトcreateF(5)
は、式の最後で破棄されます。ただし、そのオブジェクトを参照にバインドすることによりref
、C ++はref
スコープ外になるまでその一時オブジェクトの寿命を延長します。
maybeModify
関連するもののアドレスを取得しないと判断することはx
、一連のポインター演算が発生しないことを証明するよりもはるかに簡単です。
void maybeModify(int& x) { 1[&x]++; }
、上記の他のコメントが議論しているの行に沿ってコメントを古いものとして誤ってフラグを立てたと感じています
実際、参照は実際にはポインタのようなものではありません。
コンパイラは、変数への「参照」を保持し、名前をメモリアドレスに関連付けます。これは、コンパイル時に変数名をメモリアドレスに変換する仕事です。
参照を作成するときは、ポインター変数に別の名前を割り当てることだけをコンパイラーに伝えます。変数が存在することはできないため、参照が「nullを指す」ことができないのはこのためです。
ポインタは変数です。他の変数のアドレスが含まれているか、nullの場合があります。重要なことは、ポインタには値があり、参照には参照している変数だけがあるということです。
ここで実際のコードのいくつかの説明:
int a = 0;
int& b = a;
ここでは、を指す別の変数を作成していませんa
。の値を保持するメモリ内容に別の名前を追加するだけですa
。このメモリには、a
との2つの名前がありb
、どちらの名前でもアドレス指定できます。
void increment(int& n)
{
n = n + 1;
}
int a;
increment(a);
関数を呼び出すとき、コンパイラは通常、引数がコピーされるメモリ空間を生成します。関数のシグネチャは、作成する必要があるスペースを定義し、これらのスペースに使用する必要がある名前を与えます。パラメータを参照として宣言すると、メソッドの呼び出し中に新しいメモリ空間を割り当てる代わりに、入力変数のメモリ空間を使用するようにコンパイラに指示するだけです。関数が呼び出しスコープで宣言された変数を直接操作すると言うのは奇妙に思われるかもしれませんが、コンパイルされたコードを実行するとき、それ以上スコープがないことに注意してください。単純なフラットメモリがあり、関数コードは任意の変数を操作できます。
現在、extern変数を使用する場合など、コンパイラーがコンパイル時に参照を認識できない場合があります。そのため、参照は、基になるコードのポインターとして実装される場合と実装されない場合があります。しかし、私があなたに与えた例では、それはおそらくポインターで実装されません。
参照は決してできませんNULL
。
void Foo::bar() { virtual_baz(); }
そうなると、そのようなセグメンテーション違反のようなコードができます。参照がnullである可能性があることに気付いていない場合、nullをその起点まで追跡することはできません。
int &r=*p;
は未定義の動作です。その時点では、「NULLを指す参照」はありません。
参照とポインタの両方を使用して間接的に別の値にアクセスしますが、参照とポインタには2つの重要な違いがあります。1つ目は、参照は常にオブジェクトを参照するということです。参照を初期化せずに定義するとエラーになります。割り当ての動作は2番目の重要な違いです。参照に割り当てると、参照がバインドされているオブジェクトが変更されます。参照を別のオブジェクトに再バインドしません。初期化されると、参照は常に同じ基本オブジェクトを参照します。
次の2つのプログラムフラグメントを考えてみます。最初に、あるポインタを別のポインタに割り当てます。
int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
pi = pi2; // pi now points to ival2
割り当てivalの後、piによってアドレス指定されたオブジェクトは変更されません。割り当てにより、piの値が変更され、別のオブジェクトを指すようになります。次に、2つの参照を割り当てる同様のプログラムを考えます。
int &ri = ival, &ri2 = ival2;
ri = ri2; // assigns ival2 to ival
この割り当ては、参照自体ではなく、riが参照する値であるivalを変更します。割り当て後も、2つの参照は元のオブジェクトを参照しており、これらのオブジェクトの値も同じになっています。
抽象的または学術的な方法でコンピュータ言語を学ぶことに慣れていない場合、難解に見えるかもしれない意味上の違いがあります。
最高レベルでは、参照の概念は、それらが透過的な「エイリアス」であるということです。コンピューターはアドレスを使用してそれらを機能させることができますが、心配する必要はありません。それらを既存のオブジェクトの「単なる別の名前」と考えることになり、構文はそれを反映しています。これらはポインターよりも厳密であるため、ダングリングポインターを作成しようとしているときよりも、ダングリングリファレンスを作成しようとしているときにコンパイラーがより確実に警告を出すことができます。
それを超えて、もちろんポインタと参照の間にいくつかの実際的な違いがあります。それらを使用するための構文は明らかに異なり、参照を「再配置」したり、参照を何も参照したり、参照へのポインタを設定したりすることはできません。
参照は別の変数のエイリアスですが、ポインタは変数のメモリアドレスを保持します。参照は通常、渡されたオブジェクトがコピーではなくオブジェクト自体になるように、関数パラメーターとして使用されます。
void fun(int &a, int &b); // A common usage of references.
int a = 0;
int &b = a; // b is an alias for a. Not so common to use.
実際に使用するスペースの副作用を(コードを実行せずに)確認することはできないため、使用するスペースの量は関係ありません。
一方、参照とポインタの主な違いの1つは、const参照に割り当てられた一時変数が、const参照が範囲外になるまで存続することです。
例えば:
class scope_test
{
public:
~scope_test() { printf("scope_test done!\n"); }
};
...
{
const scope_test &test= scope_test();
printf("in scope\n");
}
印刷されます:
in scope
scope_test done!
これは、ScopeGuardが機能するための言語メカニズムです。
これはチュートリアルに基づいています。書かれていることはそれをより明確にします:
>>> The address that locates a variable within memory is
what we call a reference to that variable. (5th paragraph at page 63)
>>> The variable that stores the reference to another
variable is what we call a pointer. (3rd paragraph at page 64)
単にそれを覚えておくために、
>>> reference stands for memory location
>>> pointer is a reference container (Maybe because we will use it for
several times, it is better to remember that reference.)
さらに、ほとんどすべてのポインターチュートリアルを参照できるので、ポインターは、ポインター算術によってサポートされるオブジェクトであり、ポインターを配列に類似させます。
次のステートメントを見てください。
int Tom(0);
int & alias_Tom = Tom;
alias_Tom
理解することができるalias of a variable
(と異なっtypedef
ています、alias of a type
)Tom
。そのようなステートメントの用語がの参照を作成することであることを忘れても問題ありませんTom
。
nullptr
ますか?このスレッドの他の部分を実際に読んだことがありますか、それとも...?
参照は、メモリに付けられた別の名前ではありません。これは、使用時に自動的に逆参照される不変のポインターです。基本的にそれは要約すると:
int& j = i;
内部的には
int* const j = &i;
const
ポインタを実装するための有効な方法でもあります。その柔軟性は、参照とポインターの間に違いがあることを証明しません。
C ++のリファレンスとは何ですか?オブジェクトタイプではないタイプの特定のインスタンス。
C ++のポインターとは何ですか?オブジェクトタイプであるタイプの特定のインスタンス。
オブジェクトタイプは、(おそらくあるCVは -qualified)関数型ではなく、参照型でないタイプではなく、CVのボイド。
オブジェクトタイプは、C ++のタイプユニバースの最上位のカテゴリです。参照も最上位のカテゴリです。しかし、ポインタはそうではありません。
ポインタと参照は、複合型のコンテキストで一緒に言及されます。これは基本的に、参照のないCから継承(および拡張)された宣言子構文の性質によるものです。(さらに、C ++ 11以降、参照の宣言子には複数の種類がありますが、ポインタはまだ「ユニタイプ化」されています:&
+ &&
対*
)。 。(宣言子の構文は構文の表現力をかなり浪費し、人間のユーザーと実装の両方を苛立たせていると私はまだ主張します。したがって、それらのすべてが組み込みに適格ではありません新しい言語デザインで。ただし、これはPLデザインについてはまったく異なるトピックです。)
それ以外の場合は、ポインタを一緒に参照する特定の種類の型として修飾できることは重要ではありません。構文の類似性以外に共通のプロパティを共有する数が少なすぎるため、ほとんどの場合、それらをまとめる必要はありません。
上記のステートメントでは、タイプとして「ポインタ」と「参照」についてのみ言及していることに注意してください。それらのインスタンス(変数など)についていくつかの興味深い質問があります。誤解も多すぎます。
トップレベルのカテゴリの違いは、ポインタに直接結び付けられていない多くの具体的な違いをすでに明らかにしています。
cv
修飾子を含めることができます。参照はできません。参照に関するいくつかの特別なルール:
&&
テンプレートパラメータの推定中に参照を折りたたむことに基づく(「転送参照」としての)パラメータに関する特別なルールにより、パラメータの「完全な転送」が可能になります。std::initializer_list
は、参照存続期間延長のいくつかの同様のルールに従います。それはワームの別の缶です。参照は構文上の砂糖であることを知っているので、コードの読み書きは簡単です。
技術的には、これは明らかに間違っています。参照は、C ++の他の機能の構文糖衣ではありません。意味上の違いがないと、参照を他の機能に正確に置き換えることができないためです。
(同様に、ラムダ式は、キャプチャされた変数の宣言順序などの「未指定」プロパティでは正確にシミュレートできないため、C ++の他の機能の構文糖ではありません。このような変数の初期化順序は、重要です。)
C ++には、この厳密な意味での数種類の構文糖があります。1つのインスタンスは(Cから継承された)組み込み(オーバーロードされていない)演算子です[]
。これは、組み込み演算子unary *
およびbinary に対する特定の組み合わせの形式の同じセマンティックプロパティを持つように定義されてい+
ます。
したがって、ポインタと参照はどちらも同じ量のメモリを使用します。
上記の文は単に間違っています。このような誤解を避けるために、代わりにISO C ++ルールを確認してください。
...オブジェクトは、その構築期間、存続期間、および破壊期間の保管領域を占めます。...
参照がストレージを必要とするかどうかは不定です。
これらは セマンティックプロパティであることに。
言語設計の意味で参照と一緒に配置するのに十分なほど修飾されていないポインターであっても、たとえば、パラメーター型を選択するときなど、他のコンテキストでそれらの間で選択を行うことを議論するいくつかの議論があります。
しかし、これはすべてではありません。つまり、ポインタと参照よりも考慮しなければならないことがたくさんあります。
このような具体的な選択に固執する必要がない場合、ほとんどの場合、答えは短くなります。ポインタを使用する必要がないため、使用しません。ポインタは、予想外のことを暗に示し、コードの保守性と(さらには)移植性を損なう暗黙的な仮定に依存しすぎるため、通常は十分に悪いものです。不必要にポインターに依存することは間違いなく悪いスタイルであり、最新のC ++の意味では避ける必要があります。目的を再検討すると、ほとんどの場合、ポインタが最後のソートの機能であることが最終的にわかります。
&
1番目のパラメータータイプとしての参照タイプが必要です。(通常はconst
修飾する必要があります。)&&
1番目のパラメータータイプとしての参照タイプが必要です。(通常、修飾子はありません。)operator=
特別なメンバー関数としてオーバーロードするには、コピー/移動コンストラクターの第1パラメーターと同様の参照型が必要です。++
はダミーが必要int
です。unique_ptr
and shared_ptr
(または、不透明にする必要がある場合は自分で自作したもの)のようなスマートポインタを使用します。std::optional
場合は、生のポインタではなく、のようなラッパーを使用します。observer_ptr
、Library Fundamental TSのように、より適切なものがしばしばあります。唯一の例外は、現在の言語では回避できません。
operator new
。(ただし、CVは - void*
あなたは上のいくつかの非準拠の拡張に依存している場合を除き、それは予期しないポインタ算術演算を除外するので、通常のオブジェクトポインタに比べてまだかなり違うと安全ですvoid*
GNUのように。)したがって、実際には、答えは非常に明白です。疑わしい場合は、ポインタを避けてください。ポインターを使用する必要があるのは、他に何も適切でないという明確な理由がある場合のみです。上記のいくつかの例外的なケースを除いて、そのような選択はほとんど常に純粋にC ++固有ではありません(ただし、言語実装固有である可能性があります)。このようなインスタンスは次のとおりです。
Googleの検索結果(C ++に固有ではない)を介して質問が表示される場合は、これが間違った場所である可能性が非常に高いです。
C ++での参照は、基本的にファーストクラスではないため、かなり「奇数」です。参照されるオブジェクトまたは関数として扱われるため、参照されるオブジェクトのタイプに依存しないメンバーアクセス演算子。他の言語は、それらの参照について同様の制限がある場合とない場合があります。
C ++での参照は、異なる言語間で意味を保持しない可能性があります。たとえば、一般に参照はC ++のような値のnull以外のプロパティを意味しないため、そのような仮定は他の一部の言語では機能しない可能性があります(JavaやC#などの反例は非常に簡単に見つかります)。
一般的に、さまざまなプログラミング言語の参照にはいくつかの共通のプロパティがまだ存在する可能性がありますが、SOのその他のいくつかの質問に残しておきましょう。
(補足:この質問は、ALGOL 68とPL / Iのように、「Cのような」言語が関与するよりも早く重要になる場合があります。)
C ++ではポインターへの参照は可能ですが、その逆は不可能であり、参照へのポインターは不可能です。ポインターへの参照は、ポインターを変更するためのより明確な構文を提供します。この例を見てください:
#include<iostream>
using namespace std;
void swap(char * &str1, char * &str2)
{
char *temp = str1;
str1 = str2;
str2 = temp;
}
int main()
{
char *str1 = "Hi";
char *str2 = "Hello";
swap(str1, str2);
cout<<"str1 is "<<str1<<endl;
cout<<"str2 is "<<str2<<endl;
return 0;
}
上記のプログラムのCバージョンを検討してください。Cでは、ポインターツーポインター(複数の間接参照)を使用する必要があり、混乱を招き、プログラムが複雑に見える場合があります。
#include<stdio.h>
/* Swaps strings by swapping pointers */
void swap1(char **str1_ptr, char **str2_ptr)
{
char *temp = *str1_ptr;
*str1_ptr = *str2_ptr;
*str2_ptr = temp;
}
int main()
{
char *str1 = "Hi";
char *str2 = "Hello";
swap1(&str1, &str2);
printf("str1 is %s, str2 is %s", str1, str2);
return 0;
}
ポインターへの参照の詳細については、以下を参照してください。
すでに述べたように、参照へのポインタは不可能です。次のプログラムを試してください。
#include <iostream>
using namespace std;
int main()
{
int x = 10;
int *ptr = &x;
int &*ptr1 = ptr;
}
ポインターと参照には、誰も言及しなかった1つの基本的な違いがあります。参照は、関数の引数で参照渡しのセマンティクスを有効にします。ポインターは、最初は見えませんが、値渡しのセマンティクスを提供するだけです。これはこの記事で非常にうまく説明されています。
よろしく、&rzej
混乱を招く恐れがあるので、いくつかの入力を投入したいのですが、コンパイラが参照を実装する方法に大きく依存していると思いますが、gccの場合、参照はスタック上の変数のみを指すことができるという考えです実際には正しくありません。たとえば、次のようにします。
#include <iostream>
int main(int argc, char** argv) {
// Create a string on the heap
std::string *str_ptr = new std::string("THIS IS A STRING");
// Dereference the string on the heap, and assign it to the reference
std::string &str_ref = *str_ptr;
// Not even a compiler warning! At least with gcc
// Now lets try to print it's value!
std::cout << str_ref << std::endl;
// It works! Now lets print and compare actual memory addresses
std::cout << str_ptr << " : " << &str_ref << std::endl;
// Exactly the same, now remember to free the memory on the heap
delete str_ptr;
}
これはこれを出力します:
THIS IS A STRING
0xbb2070 : 0xbb2070
メモリアドレスもまったく同じであることに気付いた場合は、参照がヒープ上の変数を正常に指していることを意味します。本当に気紛れになりたいなら、これもうまくいきます:
int main(int argc, char** argv) {
// In the actual new declaration let immediately de-reference and assign it to the reference
std::string &str_ref = *(new std::string("THIS IS A STRING"));
// Once again, it works! (at least in gcc)
std::cout << str_ref;
// Once again it prints fine, however we have no pointer to the heap allocation, right? So how do we free the space we just ignorantly created?
delete &str_ref;
/*And, it works, because we are taking the memory address that the reference is
storing, and deleting it, which is all a pointer is doing, just we have to specify
the address with '&' whereas a pointer does that implicitly, this is sort of like
calling delete &(*str_ptr); (which also compiles and runs fine).*/
}
これはこれを出力します:
THIS IS A STRING
したがって、参照は内部ではポインタであり、どちらもメモリアドレスを格納しているだけで、アドレスが指している場所は関係ありません。std:: cout << str_ref;を呼び出した場合、どうなると思いますか。delete&str_refを呼び出した後?まあ、明らかにコンパイルは正常に行われますが、有効な変数を指していないため、実行時にセグメンテーション違反が発生し、(スコープから外れるまで)存在する壊れた参照が本質的に存在しますが、役に立ちません。
言い換えると、参照は、ポインタのメカニズムが抽象化されたポインタにすぎず、安全で使いやすくなっています(偶発的なポインタ計算、「。」と「->」などの混同はありません)。上記の私の例のようなナンセンスを試さないでください;)
今すぐにかかわらず、コンパイラハンドルの参照は、それがする方法の常に参照するので、フードの下ポインタのいくつかの種類を持っている必要があります期待通りの仕事にそれのために特定のメモリアドレスに特定の変数を参照してください、何ので、(この歩き回るはありません「参照」という用語)。
参照について覚えておくべき重要な唯一の主要なルールは、宣言時に定義する必要があるということです(ヘッダー内の参照を除いて、その場合は、コンストラクターで、それが含まれているオブジェクトの後に定義する必要があります)それを定義するには遅すぎます)。
上記の私の例は、参照とは何かを示す例であり、参照をそのような方法で使用することは決してないでしょう。参照を適切に使用するために、頭に釘を打ったすでにここにたくさんの答えがあります
また、インライン化された関数へのパラメーターである参照は、ポインターとは異なる方法で処理されます。
void increment(int *ptrint) { (*ptrint)++; }
void increment(int &refint) { refint++; }
void incptrtest()
{
int testptr=0;
increment(&testptr);
}
void increftest()
{
int testref=0;
increment(testref);
}
多くのコンパイラは、ポインタバージョンをインライン化するときに、実際にメモリへの書き込みを強制します(アドレスを明示的に取得しています)。ただし、参照はより最適なレジスタに残されます。
もちろん、インライン化されていない関数の場合、ポインターと参照は同じコードを生成し、関数によって変更および返されない場合は、参照よりも値で組み込み関数を渡す方が常に優れています。
参照のもう1つの興味深い使用法は、ユーザー定義型のデフォルト引数を提供することです。
class UDT
{
public:
UDT() : val_d(33) {};
UDT(int val) : val_d(val) {};
virtual ~UDT() {};
private:
int val_d;
};
class UDT_Derived : public UDT
{
public:
UDT_Derived() : UDT() {};
virtual ~UDT_Derived() {};
};
class Behavior
{
public:
Behavior(
const UDT &udt = UDT()
) {};
};
int main()
{
Behavior b; // take default
UDT u(88);
Behavior c(u);
UDT_Derived ud;
Behavior d(ud);
return 1;
}
デフォルトのフレーバーは、参照の「一時的な参照へのバインドconst参照」を使用します。
このプログラムは、質問の答えを理解するのに役立ちます。これは、参照「j」と変数「x」を指すポインタ「ptr」の単純なプログラムです。
#include<iostream>
using namespace std;
int main()
{
int *ptr=0, x=9; // pointer and variable declaration
ptr=&x; // pointer to variable "x"
int & j=x; // reference declaration; reference to variable "x"
cout << "x=" << x << endl;
cout << "&x=" << &x << endl;
cout << "j=" << j << endl;
cout << "&j=" << &j << endl;
cout << "*ptr=" << *ptr << endl;
cout << "ptr=" << ptr << endl;
cout << "&ptr=" << &ptr << endl;
getch();
}
プログラムを実行して出力を確認すると、理解できるでしょう。
また、10分空けて、次のビデオをご覧ください。https://www.youtube.com/watch?v = rlJrrGV0iOg
ここでは取り上げられていない点がまだあるようです。
ポインタとは異なり、参照は構文的には参照先のオブジェクトと同等です。つまり、オブジェクトに適用できる操作はすべて参照と同じ構文で機能します(例外はもちろん初期化です)。
これは表面的に見えるかもしれませんが、このプロパティは多くのC ++機能にとって重要であると思います。次に例を示します。
テンプレート。テンプレートパラメータはダックタイプであるため、タイプの構文プロパティが重要であり、同じテンプレートをとの両方で使用できることがよくT
ありT&
ます。
(またはstd::reference_wrapper<T>
そのまだへの暗黙のキャストに依存しているT&
)
テンプレート、カバーは両方T&
とT&&
も、より一般的です。
左辺値。str[0] = 'X';
参照なしのステートメントを考えてみましょう。これはc-strings(char* str
)に対してのみ機能します。参照によって文字を返すことにより、ユーザー定義クラスが同じ表記を持つことができます。
コンストラクタをコピーします。構文的には、オブジェクトへのポインターではなく、コピーコンストラクターにオブジェクトを渡すことが理にかなっています。ただし、コピーコンストラクターがオブジェクトを値で取得する方法はありません。同じコピーコンストラクターが再帰的に呼び出されます。これは、参照をここでの唯一のオプションとして残します。
演算子のオーバーロード。参照を使用すると、演算子の呼び出しに間接参照を導入することができoperator+(const T& a, const T& b)
ます。たとえば、同じ中置記法を保持したままです。これは、通常のオーバーロードされた関数でも機能します。
これらのポイントは、C ++と標準ライブラリのかなりの部分に力を与えるので、これは参照の非常に重要な特性です。
ポインタと参照の間には、非常に重要な非技術的な違いがあります。ポインタによって関数に渡される引数は、非const参照によって関数に渡される引数よりもはるかに見やすくなっています。例えば:
void fn1(std::string s);
void fn2(const std::string& s);
void fn3(std::string& s);
void fn4(std::string* s);
void bar() {
std::string x;
fn1(x); // Cannot modify x
fn2(x); // Cannot modify x (without const_cast)
fn3(x); // CAN modify x!
fn4(&x); // Can modify x (but is obvious about it)
}
Cに戻ると、次のような呼び出しはfn(x)
値でのみ渡すことができるため、変更することはできませんx
。引数を変更するには、ポインタを渡す必要がありますfn(&x)
。したがって、引数の前に引数がない場合は、&
変更されないことがわかっていました。(逆の場合、&
変更されたという意味ですが、大きな読み取り専用構造体をconst
ポインターで渡す必要がある場合があるため、正しくありませんでした。)
これはコードを読み取るときにこのような便利な機能であると主張する人もいconst
ます。たとえ関数がを予期していなくても、ポインターパラメーターは非参照ではなく常に変更可能なパラメーターに使用する必要がありますnullptr
。つまり、これらの人々は、fn3()
上記のような関数シグネチャは許可されるべきではないと主張します。GoogleのC ++スタイルのガイドラインはこの例です。
ポインタは0に初期化できますが、参照は初期化できません。実際、参照はオブジェクトも参照する必要がありますが、ポインターはnullポインターにすることもできます。
int* p = 0;
しかし、私たちは持つことint& p = 0;
もできませんint& p=5 ;
。
実際、適切に行うには、最初にオブジェクトを宣言して定義しておく必要があります。その後、そのオブジェクトへの参照を作成できるため、前のコードの正しい実装は次のようになります。
Int x = 0;
Int y = 5;
Int& p = x;
Int& p1 = y;
もう1つの重要な点は、初期化せずにポインターの宣言を行うことができるということですが、常に変数またはオブジェクトを参照する必要がある参照の場合、そのようなことはできません。ただし、このようなポインタの使用は危険であるため、通常、ポインタが実際に何かを指しているかどうかを確認します。参照の場合、そのようなチェックは必要ありません。宣言時にオブジェクトへの参照が必須であることはすでにわかっているためです。
もう1つの違いは、ポインターは別のオブジェクトを指すことができるということですが、参照は常に同じオブジェクトを参照しているので、次の例を見てみましょう。
Int a = 6, b = 5;
Int& rf = a;
Cout << rf << endl; // The result we will get is 6, because rf is referencing to the value of a.
rf = b;
cout << a << endl; // The result will be 5 because the value of b now will be stored into the address of a so the former value of a will be erased
もう1つのポイント:STLテンプレートのようなテンプレートがある場合、このようなクラステンプレートは常にポインターではなく参照を返し、演算子[]を使用して新しい値を簡単に読み取ったり割り当てたりすることができます。
Std ::vector<int>v(10); // Initialize a vector with 10 elements
V[5] = 5; // Writing the value 5 into the 6 element of our vector, so if the returned type of operator [] was a pointer and not a reference we should write this *v[5]=5, by making a reference we overwrite the element by using the assignment "="
const int& i = 0
。
違いは、非定数ポインター変数(定数へのポインターと混同しないでください)は、プログラムの実行中に変更される可能性があり、ポインターのセマンティクスを使用する必要がある(&、*)演算子であり、参照は初期化時に設定できます。のみ(だから、コンストラクタ初期化子リストでのみ設定でき、他の方法では設定できません)、セマンティクスにアクセスする通常の値を使用します。基本的に参照は、私がいくつかの非常に古い本を読んだときのように、オペレーターのオーバーロードをサポートできるように導入されました。誰かがこのスレッドで述べたように、ポインターは0または任意の値に設定できます。0(NULL、nullptr)は、ポインターが何も指定されずに初期化されることを意味します。nullポインタを逆参照するとエラーになります。ただし、実際には、ポインタには、正しいメモリ位置を指し示さない値が含まれている場合があります。次に、参照では、常に正しいタイプの右辺値を提供するため、参照できないものへの参照をユーザーが初期化できないようにします。参照変数を間違ったメモリ位置に初期化する方法はたくさんありますが、これを詳細に掘り下げない方が良いでしょう。マシンレベルでは、ポインターを介して、ポインターと参照の両方が均一に機能します。本質的な参照では、構文糖としましょう。右辺値参照はこれとは異なります-それらは当然スタック/ヒープオブジェクトです。参照変数を間違ったメモリ位置に初期化する方法はたくさんありますが、これを詳細に掘り下げない方が良いでしょう。マシンレベルでは、ポインターを介して、ポインターと参照の両方が均一に機能します。本質的な参照では、構文糖としましょう。右辺値参照はこれとは異なります-それらは当然スタック/ヒープオブジェクトです。参照変数を間違ったメモリ位置に初期化する方法はたくさんありますが、これを詳細に掘り下げない方が良いでしょう。マシンレベルでは、ポインターを介して、ポインターと参照の両方が均一に機能します。本質的な参照では、構文糖としましょう。右辺値参照はこれとは異なります-それらは当然スタック/ヒープオブジェクトです。