C ++のポインター変数と参照変数の違いは何ですか?


3263

参照は構文上の砂糖であることを知っているので、コードの読み書きは簡単です。

しかし、違いは何ですか?


100
ポイント2は「ポインタはNULLにすることができますが、参照はできません。NULL参照を作成できるのは不正なコードのみであり、その動作は定義されていません。」
Mark Ransom

19
ポインタはオブジェクトのもう1つのタイプであり、C ++の他のオブジェクトと同様に、変数にすることができます。一方、参照はオブジェクトではなく、変数のみです。
Kerrek SB 2012

19
これは警告なしにコンパイルされます:int &x = *(int*)0;gccで。参照は確かにNULLを指すことができます。
Calmarius

20
参照は可変エイリアスです
Khaled.K '12 / 12/23

20
最初の文が完全に誤りであるのが好きです。参照には独自のセマンティクスがあります。
オービットの軽さのレース2014年

回答:


1708
  1. ポインタを再割り当てすることができます:

    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;
  2. ポインタは、スタック上に独自のメモリアドレスとサイズ(x86では4バイト)を持っていますが、参照は(元の変数と)同じメモリアドレスを共有しますが、スタック上でいくらかの領域を占有します。参照は元の変数自体と同じアドレスを持っているため、参照を同じ変数の別の名前と考えるのが安全です。注:ポインターが指すものは、スタックまたはヒープ上にあります。同上リファレンス。このステートメントでの私の主張は、ポインターがスタックを指す必要があるということではありません。ポインタは、メモリアドレスを保持する単なる変数です。この変数はスタックにあります。参照にはスタック上に独自のスペースがあるため、アドレスは参照先の変数と同じであるためです。スタックとヒープの詳細。これは、コンパイラーが指示しない参照の実際のアドレスがあることを意味します。

    int x = 0;
    int &r = x;
    int *p = &x;
    int *p2 = &r;
    assert(p == p2);
  3. 間接参照の追加レベルを提供するポインターへのポインターへのポインターを持つことができます。一方、参照は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);
  4. ポインタは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
  5. ポインタは配列を反復できます。を使用++して、ポインターが指している次の項目+ 4に移動し、5番目の要素に移動できます。これは、ポインタが指すオブジェクトのサイズに関係ありません。

  6. ポインタは、*それが指すメモリ位置にアクセスするために逆参照する必要がありますが、参照は直接使用できます。->参照がを使用するのに対し、クラス/構造体へのポインタはそのメンバーにアクセスするために使用します.

  7. 参照は配列に詰め込むことはできませんが、ポインターはできます(ユーザー@litbにより言及)

  8. const参照は一時にバインドできます。ポインタは(いくつかの間接なしではありません)できません:

    const int &x = int(12); //legal C++
    int *y = &int(12); //illegal to dereference a temporary.

    これによりconst&、引数リストなどでの使用がより安全になります。


23
...しかしNULLの逆参照は未定義です。たとえば、参照がNULLかどうかをテストすることはできません(たとえば、&ref == NULL)。
Pat Notz

69
番号2は正しくありません。参照は、単に「同じ変数の別の名前」ではありません。参照は、ポインタと非常によく似た方法で、関数に渡したり、クラスに保存したりできます。それらは、指す変数とは独立して存在します。
Derek Park、

31
ブライアン、スタックは関係ありません。参照とポインタは、スタック上のスペースを取る必要はありません。どちらもヒープに割り当てることができます。
Derek Park、

22
ブライアン、変数(この場合はポインターまたは参照)にスペースが必要であるという事実は、スタック上のスペースが必要であることを意味するものではありませ。ポインターと参照はヒープを指すだけなく、実際にはヒープに割り当てられる場合があります。
Derek Park、

38
別の重要な
相違点

384

C ++リファレンスとは(Cプログラマー向け

リファレンスはと考えることができ、一定のポインタコンパイラが適用される自動間接付き(一定の値へのポインタと混同しないように!)、すなわち*あなたのための演算子を。

すべての参照はnull以外の値で初期化する必要があります。そうしないと、コンパイルが失敗します。参照のアドレスを取得することはできません-アドレス演算子は代わりに参照された値のアドレスを返します-参照で算術を行うこともできません。

Cプログラマーは、C ++参照を嫌う可能性があります。これは、間接参照が発生したとき、または引数が値またはポインターによって関数シグネチャを調べずに渡されたときに、もはや明らかではないためです。

C ++プログラマーは、安全でないと見なされているため、ポインターの使用を嫌う可能性があります。ただし、参照は、ほとんどの場合を除いて、定数ポインターよりも実際には安全ではありません。

C ++ FAQの次のステートメントを検討してください

参照は、基になるアセンブリ言語のアドレスを使用して実装されることが多いですが、参照をオブジェクトへの変なポインタとして考えないでください。参照オブジェクトです。これは、オブジェクトへのポインタでも、オブジェクトのコピーでもありません。それ対象です。

しかし、参照が実際にオブジェクトである場合、参照がぶら下がっているのはどうしてでしょうか?アンマネージ言語では、参照をポインターよりも「安全」にすることは不可能です。通常、スコープの境界を越えて値を確実にエイリアスする方法はありません。

C ++参照が有用であると考える理由

Cの背景から来て、C ++の参照は多少愚かな概念のように見えるかもしれませんが、一つはまだ可能ポインタの代わりにそれらを使用する必要があります。自動間接があり、便利、そして扱う際の参照が特に便利になるRAII -ではなく、理由の任意の知覚安全性の利点ですが、むしろ、慣用的なコードを書きにくくしません。

RAIIはC ++の中心的な概念の1つですが、セマンティクスのコピーと重要な相互作用をします。参照によってオブジェクトを渡すと、コピーが含まれないため、これらの問題を回避できます。言語に参照が存在しない場合は、代わりにポインタを使用する必要があります。これは使用がより面倒なので、ベストプラクティスのソリューションは他の方法よりも簡単であるという言語設計の原則に違反しています。


17
@kriss:いいえ、参照によって自動変数を返すことにより、ダングリング参照を取得することもできます。
Ben Voigt 2010年

12
@kriss:コンパイラが一般的なケースで検出することは事実上不可能です。クラスメンバー変数への参照を返すメンバー関数について考えてみましょう。これは安全であり、コンパイラーによって禁止されるべきではありません。次に、そのクラスの自動インスタンスを持つ呼び出し元がそのメンバー関数を呼び出し、参照を返します。Presto:ダングリングリファレンス。そして、はい、それは問題を引き起こすだろう、@ kriss:それが私のポイントです。ポインタよりも参照の方が優れているのは参照が常に有効であるということですが、そうではありません。
Ben Voigt

4
@kriss:いいえ、自動保存期間のオブジェクトへの参照は一時オブジェクトとは大きく異なります。とにかく、無効なポインターを逆参照することによってのみ無効な参照を取得できるという、あなたのステートメントに対する反例を提供しただけです。Christophは正解です。参照はポインタよりも安全ではありません。参照のみを使用するプログラムでも、型の安全性が損なわれる可能性があります。
Ben Voigt 2010年

7
参照は一種のポインタではありません。これらは、既存のオブジェクトの新しい名前です。
キャプティブ2011

18
@catphive:言語のセマンティクスを使用する場合はtrue、実際に実装を確認する場合はtrueではありません。C ++はCよりはるかに「魔法の」言語であり、参照から魔法を削除すると、ポインタが作成されます
Christoph

191

本当に知識を深めたい場合、参照を使用してポインタで行うことはできません。一時オブジェクトの寿命を延ばすことができます。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参照を一時オブジェクトにバインドしたり、そのアドレスを取得したりすることはできません。


5
しかし、これのユースケースはどうですか?
Ahmad Mushtaq、

20
まあ、s3_copyは一時ファイルを作成し、それをs3_copyにコピーして作成しますが、s3_referenceは一時ファイルを直接使用します。次に、実際に理解を深めるために、戻り値の最適化を確認する必要があります。これにより、コンパイラーは最初のケースでコピー構成を省略できます。
マット価格

6
@digitalSurgeon:魔法は非常に強力です。オブジェクトの存続期間はconst &バインディングの事実によって延長され、参照がスコープから外れたときにのみ、実際の参照された型のデストラクタ(参照型と比較して、ベースである可能性があります)が呼び出されます。これは参照であるため、その間にスライスは行われません。
デビッドロドリゲス-10

9
C ++ 11の更新:非定数の右辺値参照を一時変数にバインドできるため、最後の文は「非定数の左辺値参照を一時変数にバインドすることはできません」である必要があります。
オクタリスト2013年

4
@AhmadMushtaq:これの主な用途は派生クラスです。継承が含まれていない場合は、値のセマンティクスを使用することもできます。これは、RVO /移動の構築により安価または無料になります。しかし、その場合、古典的なスライスの問題に直面しますがAnimal x = fast ? getHare() : getTortoise()、正しく動作します。xAnimal& x = ...
Arthur Tacca

128

構文糖とは別に、参照はconstポインタです(へのポインタではありませんconst)。参照変数を宣言するときに参照先を確立する必要があり、後で変更することはできません。

更新:もう少し考えてみると、重要な違いがあります。

constポインタのターゲットは、そのアドレスを取得してconstキャストを使用することで置き換えることができます。

リファレンスのターゲットは、UB以外の方法で置き換えることはできません。

これにより、コンパイラは参照に対してより多くの最適化を行うことができます。


8
これは断然最良の答えだと思います。他の人たちは、それらが別の獣であるように参照とポインタについて話し、それらが動作でどのように異なるかを説明します。それは物事を簡単に私見することはありません。私は、参照T* constを異なる構文糖(それがたまたまあなたのコードから*と&の多くを削除することになる)であると常に理解しています。
カルロウッド

2
「constポインターのターゲットは、そのアドレスを取得してconstキャストを使用することで置き換えることができます。」これは未定義の動作です。詳細については、stackoverflow.com / questions / 25209838 /…を参照してください。
dgnuff

1
参照のリファレントまたはconstポインター(または任意のconstスカラー)の値を変更しようとすると、等価性は不正になります。実行できること:暗黙の変換によって追加されたconst修飾を削除します。問題はありませんint i; int const *pci = &i; /* implicit conv to const int* */ int *pi = const_cast<int*>(pci);
curiousguy

1
ここでの違いは、UBと文字通り不可能です。C ++には、参照ポイントを変更できる構文はありません。

不可能ではありませんが、難しいことですが、その参照をモデル化しているポインターのメモリ領域にアクセスして、その内容を変更することができます。それは確かに行うことができます。
Nicolas Bousquet

126

世論に反して、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がコードを別のプラットフォームに移植しようとしたときに未定義の動作公開するを参照してください。


63
問題のコードには未定義の動作が含まれています。技術的には、nullポインターを設定して比較する以外は何もできません。プログラムが未定義の動作を呼び出すと、ビッグボスにデモを行うまでは、正しく機能しているように見えるなど、何でも実行できます。
KeithB 2008

9
マークには有効な引数があります。ポインタがNULLになる可能性があり、そのためにチェックする必要があるという引数も現実ではありません。関数が非NULLを必要とすると、呼び出し側はそれを行わなければなりません。そのため、呼び出し側が呼び出さない場合は、未定義の動作を呼び出しています。マークと同じように悪い参照
Johannes Schaub-litb 2009

13
説明に誤りがあります。このコードは、NULLの参照を作成する場合と作成しない場合があります。その動作は未定義です。それは完全に有効な参照を作成するかもしれません。参照の作成にまったく失敗する可能性があります。
David Schwartz

7
@David Schwartz、私が標準に従って物事が機能しなければならなかった方法について話していたら、あなたは正しいでしょう。しかし、それ私が話していることではありません -私は非常に人気のあるコンパイラで実際に観察された動作について話し、典型的なコンパイラとCPUアーキテクチャに関する私の知識に基づいて、おそらく何が起こるかを推定しています。参照がより安全でポインタよりも優れていると考え、参照が悪いことを考慮していない場合、私と同じように、いつか簡単な問題に悩まされるでしょう。
Mark Ransom

6
nullポインターの逆参照は間違っています。参照を初期化する場合でも、それを行うプログラムはすべて間違っています。ポインタからの参照を初期化する場合は、常にポインタが有効であることを確認する必要があります。これが成功した場合でも、基礎となるオブジェクトはいつでも削除される可能性があり、存在しないオブジェクトを参照する参照を残しますよね?あなたが言っていることは良いことです。ここでの本当の問題は、参照が「nullである」かどうかを確認する必要がなく、少なくともポインターがアサートされている必要があるということです。
t0rakka

115

あなたは最も重要な部分を忘れました:

ポインタを使用した->
メンバーアクセス、参照を使用したメンバーアクセス.

foo.barある明確に優れてfoo->barいることと同じ方法で、VIがあり、明らかに優れてEmacsの :-)


4
@Orion Edwards>ポインタによるメンバーアクセスの使用->>参照によるメンバーアクセスの使用。これは100%真実ではありません。ポインタへの参照を持つことができます。この場合、-> struct Node {Node * next;を使用して、逆参照されたポインターのメンバーにアクセスします。}; ノード*最初; // pはポインタへの参照ですvoid foo(Node *&p){p-> next = first; ノード* bar =新しいノード; foo(バー); -OP:右辺値と左辺値の概念に精通していますか?

3
スマートポインターには両方があります。(スマートポインタークラスのメソッド)および->(基になる型のメソッド)。
JBRウィルキンソン2014

1
@ user6105 Orion Edwardsのステートメントは実際には100%真です。「[参照されない]ポインターのメンバーにアクセスする」ポインターにはメンバーがありません。ポインターが参照するオブジェクトにはメンバーがあり、それらへのアクセスは->、ポインター自体と同様に、ポインターへの参照を提供するものとまったく同じです。
Max Truxa、2015

1
その理由はある.->viとemacsの:)とは何かがある
ARTM

10
@artM-これは冗談で、英語を母国語としない人にはおそらく意味がありません。謝罪いたします。説明すると、viがemacsより優れているかどうかは完全に主観的です。viの方がはるかに優れていると考える人もいれば、正反対の人もいます。同様に、私が使用して考える.より優れているの->が、ちょうどemacsの対viのように、それは完全に主観的だとあなたは何を証明することはできません
オリオンエドワーズ

74

参照はポインタと非常に似ていますが、コンパイラの最適化に役立つように特別に作成されています。

  • 参照は、コンパイラがどの参照をどの変数にエイリアスするかを追跡するのが大幅に容易になるように設計されています。2つの主要な機能が非常に重要です。「参照演算」と参照の再割り当てはありません。これらにより、コンパイラはコンパイル時にどの参照がどの変数にエイリアスを付けるかを把握できます。
  • 参照は、コンパイラーがレジスターに入れることを選択した変数など、メモリー・アドレスを持たない変数を参照することを許可されます。ローカル変数のアドレスを取得する場合、コンパイラーがそれをレジスターに入れるのは非常に困難です。

例として:

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、一連のポインター演算が発生しないことを証明するよりもはるかに簡単です。
Cort Ammon 2013

オプティマイザーは、他の多くの理由で、「ポインターの算術演算は発生しません」というチェックをすでに実行していると思います。
Ben Voigt 2013

「参照はポインタと非常に似ています」-意味的には適切なコンテキストで-生成されたコードに関しては、定義/要件を介さずに、一部の実装でのみ。私はあなたがこれを指摘したことを知っています、そしてあなたの投稿のいずれにも実際的な意見に同意しませんが、「参照は次のように/通常はポインタとして実装される」のような簡単な説明を読みすぎる人々にはすでに多くの問題があります。
underscore_d

私は誰かがvoid maybeModify(int& x) { 1[&x]++; }、上記の他のコメントが議論しているの行に沿ってコメントを古いものとして誤ってフラグを立てたと感じています
Ben Voigt

69

実際、参照は実際にはポインタのようなものではありません。

コンパイラは、変数への「参照」を保持し、名前をメモリアドレスに関連付けます。これは、コンパイル時に変数名をメモリアドレスに変換する仕事です。

参照を作成するときは、ポインター変数に別の名前を割り当てることだけをコンパイラーに伝えます。変数が存在することはできないため、参照が「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変数を使用する場合など、コンパイラーがコンパイル時に参照を認識できない場合があります。そのため、参照は、基になるコードのポインターとして実装される場合と実装されない場合があります。しかし、私があなたに与えた例では、それはおそらくポインターで実装されません。


2
参照は、必ずしも変数ではなく、l値への参照です。そのため、実際のエイリアス(コンパイル時の構成)よりもポインタにはるかに近くなります。参照できる式の例は* pまたは* p ++です

5
そうです、私は、参照が新しい変数を常に新しい変数をスタックにプッシュするとは限らないという事実を指摘していました。
ビンセントロバート

1
@VincentRobert:ポインターと同じように動作します...関数がインライン化されている場合、参照とポインターの両方が最適化されます。関数呼び出しがある場合、オブジェクトのアドレスを関数に渡す必要があります。
Ben Voigt

1
int * p = NULL; int&r = * p; NULLを指す参照。if(r){}-> boOm;)
sree

2
コンパイルステージでのこのフォーカスは、実行時に参照を渡すことができ、その時点で静的エイリアスがウィンドウの外に出ることに気付くまで、すばらしいように思えます。(そして、参照は通常ポインターとして実装されますが、標準ではこのメソッドは必要ありません。)
underscore_d

45

参照は決してできませんNULL


10
反例については、Mark Ransomの回答を参照してください。これは参照に関して最も頻繁に主張される神話ですが、それは神話です。標準で保証されている唯一の保証は、NULL参照があるとすぐにUBが存在することです。しかし、これは「この車は安全です。道路から降りることは決してできません。(とにかく、道路から外れた場合に発生する可能性があることについては責任を負いません。爆発する可能性があります。)」
cmaster -モニカを2014年

17
@cmaster:有効なプログラムでは、参照をnullにすることはできません。しかし、ポインタはできます。これは神話ではなく、事実です。
user541686 2014

8
@Mehrdadはい、有効なプログラムは継続します。ただし、プログラムが実際に実行することを強制するトラフィックの障壁はありません。道路の大部分には実際にはマーキングがありません。そのため、夜道を降りるのは非常に簡単です。そして、このようなバグが発生する可能性があることを知っていることは、このようなバグをデバッグするために重要です。null ポインタと同様に、null参照がプログラムをクラッシュさせる前に伝播する可能性があります。そして、void Foo::bar() { virtual_baz(); }そうなると、そのようなセグメンテーション違反のようなコードができます。参照がnullである可能性があることに気付いていない場合、nullをその起点まで追跡することはできません。
cmaster-モニカを14

4
int * p = NULL; int&r = * p; NULLを指す参照。if(r){}-> boOm;)–
sree

10
@sree int &r=*p;は未定義の動作です。その時点では、「NULLを指す参照」はありません。
cdhowie 2017年

35

参照とポインタの両方を使用して間接的に別の値にアクセスしますが、参照とポインタには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つの参照は元のオブジェクトを参照しており、これらのオブジェクトの値も同じになっています。


「参照は常にオブジェクトを参照する」は完全に偽です
Ben Voigt '21

32

抽象的または学術的な方法でコンピュータ言語を学ぶことに慣れていない場合、難解に見えるかもしれない意味上の違いがあります。

最高レベルでは、参照の概念は、それらが透過的な「エイリアス」であるということです。コンピューターはアドレスを使用してそれらを機能させることができますが、心配する必要はありません。それらを既存のオブジェクトの「単なる別の名前」と考えることになり、構文はそれを反映しています。これらはポインターよりも厳密であるため、ダングリングポインターを作成しようとしているときよりも、ダングリングリファレンスを作成しようとしているときにコンパイラーがより確実に警告を出すことができます。

それを超えて、もちろんポインタと参照の間にいくつかの実際的な違いがあります。それらを使用するための構文は明らかに異なり、参照を「再配置」したり、参照を何も参照したり、参照へのポインタを設定したりすることはできません。


27

参照は別の変数のエイリアスですが、ポインタは変数のメモリアドレスを保持します。参照は通常、渡されたオブジェクトがコピーではなくオブジェクト自体になるように、関数パラメーターとして使用されます。

    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. 

20

実際に使用するスペースの副作用を(コードを実行せずに)確認することはできないため、使用するスペースの量は関係ありません。

一方、参照とポインタの主な違いの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が機能するための言語メカニズムです。


1
参照のアドレスを取得することはできませんが、それはそれらが物理的に領域を占有しないことを意味しません。最適化を除けば、間違いなく可能です。
オービットでの軽さのレース

2
それにもかかわらず、「スタック上の参照がスペースをまったく使用しない」というのは、明らかに誤りです。
オービットでの軽さのレース2011年

1
@Tomalak、まあ、それはコンパイラにも依存します。しかし、はい、それは少し混乱しています。それを削除するだけで混乱が少ないと思います。
MSN

1
特定の特定のケースでは、そうでない場合があります。したがって、断定的なアサーションとして「しない」は間違っています。私が言ってることはそういうことです。:) [この問題について規格が何を言っているのか思い出せません。基準部材のルールは、「スペースを取ることの言及」の一般的なルールを与えるかもしれないが、私はビーチで、ここで私と一緒に標準の私のコピーを持っていない:D]
軌道上での明度レース

20

これはチュートリアルに基づいています。書かれていることはそれをより明確にします:

>>> 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 typeTom。そのようなステートメントの用語がの参照を作成することであることを忘れても問題ありませんTom


1
また、クラスに参照変数がある場合は、nullptrまたは初期化リスト内の有効なオブジェクトで初期化する必要があります。
Misgevolution

1
この回答の文言は、実際に使用するには混乱しすぎます。また、@ Misgevolution、読者にリファレンスを初期化することを真剣に勧めていnullptrますか?このスレッドの他の部分を実際に読んだことがありますか、それとも...?
underscore_d

1
私が言ったその愚かなことのために私の悪い、申し訳ありません。その時までに睡眠不足だったに違いない。「nullptrで初期化」は完全に間違っています。
Misgevolution、2015年

19

参照は、メモリに付けられた別の名前ではありません。これは、使用時に自動的に逆参照される不変のポインターです。基本的にそれは要約すると:

int& j = i;

内部的には

int* const j = &i;

13
これはC ++標準で述べられていることではなく、コンパイラーが回答で記述された方法で参照を実装する必要はありません。
jogojapan 2013

@jogojapan:C ++コンパイラが参照を実装するために有効な方法は、constポインタを実装するための有効な方法でもあります。その柔軟性は、参照とポインターの間に違いがあることを証明しません。
Ben Voigt 2015

2
@BenVoigt一方の有効な実装がもう一方の有効な実装でもあることは事実ですが、これらの2つの概念の定義から明らかな方法ではありません。良い答えは定義から始まり、2つが最終的に同じであるという主張が真実である理由を実証しました。この答えは、他のいくつかの答えに対するある種のコメントのようです。
jogojapan 2015

参照、オブジェクトに付けられた別の名前です。コンパイラは、違いが分からない限り、あらゆる種類の実装を許可されています。これは「as-if」ルールと呼ばれています。ここで重要なのは、違いがわからないことです。ポインターにストレージがないことが判明した場合、コンパイラーはエラーです。参照にストレージがないことが判明した場合でも、コンパイラは適合しています。
sp2danny 2017

18

直接的な答え

C ++のリファレンスとは何ですか?オブジェクトタイプはないタイプの特定のインスタンス。

C ++のポインターとは何ですか?オブジェクトタイプであるタイプの特定のインスタンス。

オブジェクト型のISO C ++の定義

オブジェクトタイプは、(おそらくあるCVは -qualified)関数型ではなく、参照型でないタイプではなく、CVのボイド。

オブジェクトタイプは、C ++のタイプユニバースの最上位のカテゴリです。参照も最上位のカテゴリです。しかし、ポインタはそうではありません。

ポインタと参照は複合型のコンテキストで一緒言及されます。これは基本的に、参照のないCから継承(および拡張)された宣言子構文の性質によるものです。(さらに、C ++ 11以降、参照の宣言子には複数の種類がありますが、ポインタはまだ「ユニタイプ化」されています:&+ &&*)。 。(宣言子の構文は構文の表現力をかなり浪費し、人間のユーザーと実装の両方を苛立たせていると私はまだ主張します。したがって、それらのすべてが組み込みに適格ではありません新しい言語デザインで。ただし、これはPLデザインについてはまったく異なるトピックです。)

それ以外の場合は、ポインタを一緒に参照する特定の種類の型として修飾できることは重要ではありません。構文の類似性以外に共通のプロパティを共有する数が少なすぎるため、ほとんどの場合、それらをまとめる必要はありません。

上記のステートメントでは、タイプとして「ポインタ」と「参照」についてのみ言及していることに注意してください。それらのインスタンス(変数など)についていくつかの興味深い質問があります。誤解も多すぎます。

トップレベルのカテゴリの違いは、ポインタに直接結び付けられていない多くの具体的な違いをすでに明らかにしています。

  • オブジェクト型にはトップレベルのcv修飾子を含めることができます。参照はできません。
  • オブジェクトタイプの変数は、抽象マシンセマンティクスに従ってストレージを占有します。参照はストレージを占有する必要はありません(詳細については、以下の誤解に関するセクションを参照してください)。
  • ...

参照に関するいくつかの特別なルール:

  • 複合宣言子は参照をより制限します。
  • 参照が折りたたまれる可能性があります。
    • &&テンプレートパラメータの推定中に参照を折りたたむことに基づく(「転送参照」としての)パラメータに関する特別なルールにより、パラメータの「完全な転送」が可能になります。
  • 参照には、初期化に関する特別なルールがあります。参照型として宣言された変数の存続期間は、拡張を介して通常のオブジェクトとは異なる場合があります。
    • ちなみに、初期化のようないくつかの他のコンテキストstd::initializer_listは、参照存続期間延長のいくつかの同様のルールに従います。それはワームの別の缶です。
  • ...

誤解

構文糖

参照は構文上の砂糖であることを知っているので、コードの読み書きは簡単です。

技術的には、これは明らかに間違っています。参照は、C ++の他の機能の構文糖衣ではありません。意味上の違いがないと、参照を他の機能に正確に置き換えることができないためです。

(同様に、ラムダ式は、キャプチャされた変数の宣言順序などの「未指定」プロパティでは正確にシミュレートできないため、C ++の他の機能の構文糖ではありません。このような変数の初期化順序は、重要です。)

C ++には、この厳密な意味での数種類の構文糖があります。1つのインスタンスは(Cから継承された)組み込み(オーバーロードされていない)演算子です[]。これは、組み込み演算子unary *およびbinary に対する特定の組み合わせの形式の同じセマンティックプロパティを持つように定義されてい+ます。

ストレージ

したがって、ポインタと参照はどちらも同じ量のメモリを使用します。

上記の文は単に間違っています。このような誤解を避けるために、代わりにISO C ++ルールを確認してください。

[intro.object] / 1

...オブジェクトは、その構築期間、存続期間、および破壊期間の保管領域を占めます。...

[dcl.ref] / 4

参照がストレージを必要とするかどうかは不定です。

これらは セマンティックプロパティであることに。

語用論

言語設計の意味で参照と一緒に配置するのに十分なほど修飾されていないポインターであっても、たとえば、パラメーター型を選択するときなど、他のコンテキストでそれらの間で選択を行うことを議論するいくつかの議論があります。

しかし、これはすべてではありません。つまり、ポインタと参照よりも考慮しなければならないことがたくさんあります。

このような具体的な選択に固執する必要がない場合、ほとんどの場合、答えは短くなりますポインタを使用する必要がないため、使用しません。ポインタは、予想外のことを暗に示し、コードの保守性と(さらには)移植性を損なう暗黙的な仮定に依存しすぎるため、通常は十分に悪いものです。不必要にポインターに依存することは間違いなく悪いスタイルであり、最新のC ++の意味では避ける必要があります。目的を再検討すると、ほとんどの場合、ポインタが最後のソートの機能であることが最終的にわかります。

  • 言語ルールでは、特定のタイプの使用を明示的に要求する場合があります。これらの機能を使用する場合は、ルールに従ってください。
    • コピーコンストラクターには、特定のタイプのcv - &1番目のパラメータータイプとしての参照タイプが必要です。(通常はconst修飾する必要があります。)
    • 移動コンストラクターには、特定のタイプのcv - &&1番目のパラメータータイプとしての参照タイプが必要です。(通常、修飾子はありません。)
    • 演算子の特定のオーバーロードには、参照型または非参照型が必要です。例えば:
      • operator=特別なメンバー関数としてオーバーロードするには、コピー/移動コンストラクターの第1パラメーターと同様の参照型が必要です。
      • Postfixに++はダミーが必要intです。
      • ...
  • 値渡し(つまり、非参照型を使用する)で十分であるとわかっている場合は、特にC ++ 17必須コピー省略をサポートする実装を使用する場合に、それを直接使用します。(警告:しかし、必要性について徹底的に推論することは非常に複雑になる可能性があります。)
  • いくつかのハンドルを所有権付きで操作したい場合は、生のポインタではなく、unique_ptrand shared_ptr(または、不透明にする必要がある場合は自分で自作したもの)のようなスマートポインタを使用します。
  • 特定の範囲でいくつかの反復を行う場合は、非常に具体的に生のポインターの方が優れている(ヘッダーの依存関係が少ないなど)と確信がない限り、生のポインターではなく、イテレーター(または標準ライブラリではまだ提供されていないいくつかの範囲)を使用します。ケース。
  • 値渡しが十分であることがわかっていて、明示的なNULL可能セマンティクスが必要なstd::optional場合は、生のポインタではなく、のようなラッパーを使用します。
  • 上記の理由で値渡しが理想的でないことがわかっていて、null許容のセマンティクスが必要ない場合は、{lvalue、rvalue、forwarding} -referencesを使用します。
  • 従来のポインターのようなセマンティクスが必要な場合でもobserver_ptr、Library Fundamental TSのように、より適切なものがしばしばあります。

唯一の例外は、現在の言語では回避できません。

  • 上記のスマートポインターを実装する場合、生のポインターを処理する必要があります。
  • 特定の言語相互運用ルーチンには、などのポインタが必要ですoperator new。(ただし、CVは - void*あなたは上のいくつかの非準拠の拡張に依存している場合を除き、それは予期しないポインタ算術演算を除外するので、通常のオブジェクトポインタに比べてまだかなり違うと安全ですvoid*GNUのように。)
  • 関数ポインターはキャプチャなしのラムダ式から変換できますが、関数参照は変換できません。このような場合は、非ジェネリックコードで関数ポインタを使用する必要があります。意図的にnull許容値を使用したくない場合でも同様です。

したがって、実際には、答えは非常に明白です。疑わしい場合は、ポインタを避けてください。ポインターを使用する必要があるのは、他に何も適切でないという明確な理由がある場合のみです。上記のいくつかの例外的なケースを除いて、そのような選択はほとんど常に純粋にC ++固有ではありません(ただし、言語実装固有である可能性があります)。このようなインスタンスは次のとおりです。

  • 古いスタイル(C)のAPIを提供する必要があります。
  • 特定のC ++実装のABI要件を満たす必要があります。
  • 特定の実装の前提に基づいて、実行時にさまざまな言語実装(さまざまなアセンブリ、言語ランタイム、一部の高水準クライアント言語のFFIを含む)と相互運用する必要があります。
  • 極端な場合には、翻訳の効率(コンパイルとリンク)を改善する必要があります。
  • 極端な場合には、シンボルの膨張を回避する必要があります。

言語中立性に関する警告

Googleの検索結果(C ++に固有ではない)を介して質問が表示される場合は、これが間違った場所である可能性が非常に高いです。

C ++での参照は、基本的にファーストクラスではないため、かなり「奇数」です。参照されるオブジェクトまたは関数として扱われるため参照されるオブジェクトのタイプに依存しないメンバーアクセス演算子。他の言語は、それらの参照について同様の制限がある場合とない場合があります。

C ++での参照は、異なる言語間で意味を保持しない可能性があります。たとえば、一般に参照はC ++のような値のnull以外のプロパティを意味しないため、そのような仮定は他の一部の言語では機能しない可能性があります(JavaやC#などの反例は非常に簡単に見つかります)。

一般的に、さまざまなプログラミング言語の参照にはいくつかの共通のプロパティがまだ存在する可能性がありますが、SOのその他のいくつかの質問に残しておきましょう。

(補足:この質問は、ALGOL 68とPL / Iのように、「Cのような」言語が関与するよりも早く重要になる場合があります。)


17

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;
}

16

以下のいずれかが必要でない限り、参照を使用します。

  • nullポインタはセンチネル値として使用できます。多くの場合、関数のオーバーロードやブール値の使用を回避するための安価な方法です。

  • ポインタに対して演算を行うことができます。例えば、p += offset;


5
参照として宣言された&r + offset場所を書くことができrます
MM

15

ポインターと参照には、誰も言及しなかった1つの基本的な違いがあります。参照は、関数の引数で参照渡しのセマンティクスを有効にします。ポインターは、最初は見えませんが、値渡しのセマンティクスを提供するだけです。これはこの記事で非常にうまく説明されています

よろしく、&rzej


1
参照とポインタはどちらもハンドルです。これらはどちらも、オブジェクトが参照によって渡されるセマンティクスを提供しますが、ハンドルはコピーされます。変わりはない。(辞書を検索するためのキーなど、ハンドルを使用する他の方法もあります)
Ben Voigt 2013年

私もこのように考えていました。しかし、そうでない理由を説明しているリンク先の記事を参照してください。
アンジェイ2013年

2
@Andrzj:これは、私のコメントの単一の文の非常に長いバージョンです。ハンドルがコピーされます。
Ben Voigt 2013年

この「ハンドルがコピーされました」について、もう少し説明が必要です。基本的な考え方は理解していますが、物理的には参照とポインタの両方が変数のメモリ位置を指していると思います。エイリアスが値変数を格納し、変数の値が変更されたときに何かを更新するようなものですか?私は初心者です。愚かな質問としてフラグを立てないでください。
Asim

1
@Andrzej誤り。どちらの場合も、値渡しが発生しています。参照は値で渡され、ポインタは値で渡されます。そうでなければ、初心者を混乱させます。
Miles Rout 2014

14

混乱を招く恐れがあるので、いくつかの入力を投入したいのですが、コンパイラが参照を実装する方法に大きく依存していると思いますが、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を呼び出した後?まあ、明らかにコンパイルは正常に行われますが、有効な変数を指していないため、実行時にセグメンテーション違反が発生し、(スコープから外れるまで)存在する壊れた参照が本質的に存在しますが、役に立ちません。

言い換えると、参照は、ポインタのメカニズムが抽象化されたポインタにすぎず、安全で使いやすくなっています(偶発的なポインタ計算、「。」と「->」などの混同はありません)。上記の私の例のようなナンセンスを試さないでください;)

今すぐにかかわらず、コンパイラハンドルの参照は、それがする方法の常に参照するので、フードの下ポインタのいくつかの種類を持っている必要があります期待通りの仕事にそれのために特定のメモリアドレスに特定の変数を参照してください、何ので、(この歩き回るはありません「参照」という用語)。

参照について覚えておくべき重要な唯一の主要なルールは、宣言時に定義する必要があるということです(ヘッダー内の参照を除いて、その場合は、コンストラクターで、それが含まれているオブジェクトの後に定義する必要があります)それを定義するには遅すぎます)。

上記の私の例は、参照とは何かを示す例であり、参照をそのような方法で使用することは決してないでしょう。参照を適切に使用するために、頭に釘を打ったすでにここにたくさんの答えがあります


14

もう1つの違いは、void型へのポインターを持つことができる(そしてそれは何かへのポインターを意味する)が、voidへの参照は禁止されていることです。

int a;
void * p = &a; // ok
void & p = a;  //  forbidden

この特定の違いに本当に満足しているとは言えません。私はそれがアドレスのあるものへの意味参照で許可され、それ以外は参照の同じ動作が許可されることを強く望みます。参照を使用してmemcpyのようなCライブラリ関数に相当するものを定義できます。


13

また、インライン化された関数へのパラメーターである参照は、ポインターとは異なる方法で処理されます。

void increment(int *ptrint) { (*ptrint)++; }
void increment(int &refint) { refint++; }
void incptrtest()
{
    int testptr=0;
    increment(&testptr);
}
void increftest()
{
    int testref=0;
    increment(testref);
}

多くのコンパイラは、ポインタバージョンをインライン化するときに、実際にメモリへの書き込みを強制します(アドレスを明示的に取得しています)。ただし、参照はより最適なレジスタに残されます。

もちろん、インライン化されていない関数の場合、ポインターと参照は同じコードを生成し、関数によって変更および返されない場合は、参照よりも値で組み込み関数を渡す方が常に優れています。


11

参照のもう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参照」を使用します。


11

このプログラムは、質問の答えを理解するのに役立ちます。これは、参照「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


11

ここでは取り上げられていない点がまだあるようです。

ポインタとは異なり、参照は構文的には参照先のオブジェクトと同等です。つまり、オブジェクトに適用できる操作はすべて参照と同じ構文で機能します(例外はもちろん初期化です)。

これは表面的に見えるかもしれませんが、このプロパティは多くの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 ++と標準ライブラリのかなりの部分に力を与えるので、これは参照の非常に重要な特性です。


暗黙のキャスト」キャストは構文構造であり、文法に存在します。キャストは常に露骨です
curiousguy

9

ポインタと参照の間には、非常に重要な非技術的な違いがあります。ポインタによって関数に渡される引数は、非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 ++スタイルのガイドラインはこの例です。


8

多分いくつかの隠喩が役立ちます。デスクトップ画面スペースのコンテキストで-

  • 参照では、実際のウィンドウを指定する必要があります。
  • ポインターには、そのウィンドウタイプのインスタンスが0個以上含まれることを保証する、画面上のスペースの一部の位置が必要です。

6

ポインターと参照の違い

ポインタは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 "="

1
私たちはまだ持つことができますconst int& i = 0
Revolver_Ocelot 2017年

1
この場合、参照は読み取りでのみ使用され、「const_cast」は参照ではなくポインタのみを受け入れるため、「const_cast」を使用してもこのconst参照を変更することはできません。
dhokar.w 2017年

1
const_castはリファレンスとうまく連携します。coliru.stacked
Revolver_Ocelot

1
参照をキャストするのではなく、参照へのキャストを作成しています const int&i =; const_cast <int>(i); 参照の定数を破棄して、書き込みと参照への新しい値の割り当てを可能にしようとしますが、これは不可能です。集中してください!!
dhokar.w 2017年

5

違いは、非定数ポインター変数(定数へのポインターと混同しないでください)は、プログラムの実行中に変更される可能性があり、ポインターのセマンティクスを使用する必要がある(&、*)演算子であり、参照は初期化時に設定できます。のみ(だから、コンストラクタ初期化子リストでのみ設定でき、他の方法では設定できません)、セマンティクスにアクセスする通常の値を使用します。基本的に参照は、私がいくつかの非常に古い本を読んだときのように、オペレーターのオーバーロードをサポートできるように導入されました。誰かがこのスレッドで述べたように、ポインターは0または任意の値に設定できます。0(NULL、nullptr)は、ポインターが何も指定されずに初期化されることを意味します。nullポインタを逆参照するとエラーになります。ただし、実際には、ポインタには、正しいメモリ位置を指し示さない値が含まれている場合があります。次に、参照では、常に正しいタイプの右辺値を提供するため、参照できないものへの参照をユーザーが初期化できないようにします。参照変数を間違ったメモリ位置に初期化する方法はたくさんありますが、これを詳細に掘り下げない方が良いでしょう。マシンレベルでは、ポインターを介して、ポインターと参照の両方が均一に機能します。本質的な参照では、構文糖としましょう。右辺値参照はこれとは異なります-それらは当然スタック/ヒープオブジェクトです。参照変数を間違ったメモリ位置に初期化する方法はたくさんありますが、これを詳細に掘り下げない方が良いでしょう。マシンレベルでは、ポインターを介して、ポインターと参照の両方が均一に機能します。本質的な参照では、構文糖としましょう。右辺値参照はこれとは異なります-それらは当然スタック/ヒープオブジェクトです。参照変数を間違ったメモリ位置に初期化する方法はたくさんありますが、これを詳細に掘り下げない方が良いでしょう。マシンレベルでは、ポインターを介して、ポインターと参照の両方が均一に機能します。本質的な参照では、構文糖としましょう。右辺値参照はこれとは異なります-それらは当然スタック/ヒープオブジェクトです。


4

簡単に言うと、参照は変数の代替名ですが、ポインタは別の変数のアドレスを保持する変数です。例えば

int a = 20;
int &r = a;
r = 40;  /* now the value of a is changed to 40 */

int b =20;
int *ptr;
ptr = &b;  /*assigns address of b to ptr not the value */
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.