web-dev-qa-db-ja.com

C ++のポインター/参照を返す

私は、参照解除演算子、演算子のアドレス、および一般的なポインターについてかなりよく理解しています。

ただし、次のようなものを見ると混乱します。

_int* returnA() {
    int *j = &a;
    return j;
}

int* returnB() {
    return &b;
}

int& returnC() {
    return c;
}

int& returnC2() {
    int *d = &c;
    return *d;
}
_
  1. returnA()では、ポインターを返すように求めています。 jがポインターであるため、これが機能することを明確にするだけですか?
  2. returnB()では、ポインターを返すように求めています。ポインターがアドレスを指しているため、returnB()が機能する理由は、_&b_を返しているためです。
  3. returnC()では、intのアドレスが返されるように求めています。 cを返すと、_&_演算子はc?に自動的に「追加」されますか?
  4. returnC2()で、intのアドレスが返されることを再度求めています。ポインターがアドレスを指すため、_*d_は機能しますか?

A、b、cがグローバルとして整数として初期化されると仮定します。

私の4つの質問すべてで正しいかどうかを誰かが検証できますか?

45
anon235370

ReturnA()では、ポインターを返すように求めています。 jがポインタであるため、この動作を明確にするだけですか?

はい、int *j = &ajを指すようにaを初期化します。次に、jの値、つまりaのアドレスを返します。

ReturnB()では、ポインターを返すように求めています。ポインターがアドレスを指しているので、returnB()が機能する理由は、bを返しているからです。

はい。ここでは、上記と同じことが1つのステップで発生します。 &bは、bのアドレスを提供します。

ReturnC()で、返されるintのアドレスを要求しています。 cを返すと、演算子は自動的に追加されますか?

いいえ、返されるのはintへの参照です。参照は、ポインタと同じようにアドレスではありません-変数の単なる代替名です。したがって、変数の参照を取得するために&演算子を適用する必要はありません。

ReturnC2()で、再びintのアドレスを返すように求めています。ポインターがアドレスを指すため、* dは機能しますか?

繰り返しますが、返されるのはintへの参照です。 *dは、cが指す元の変数c(それが何であれ)を指します。そして、これはreturnCのように暗黙的に参照に変換できます。

ポインタは一般にアドレスを指しません(ただし、_int**はintへのポインタへのポインタです)。ポインタare何かのアドレス。 something*のようなポインターを宣言すると、そのsomethingはポインターが指すものになります。したがって、上記の例では、int**int*へのポインターを宣言しますが、これはたまたまポインターそのものです。

28
Péter Török

ピーターはあなたの質問に答えましたが、明らかに紛らわしいのはシンボル*&です。これらを回避するのが難しい部分は、両方とも間接に関係する2つの異なる意味を持つことです(乗算の*とビット単位の&の3番目の意味を除く)。

  • *は、typeの一部として使用すると、型がポインターであることを示します。intは型であるため、int*は、 int型、およびint**はポインターからポインターへのポインター型です。

  • &typeの一部として使用されている場合、タイプが参照であることを示します。 intは型なので、int&はintへの参照です(参照への参照などはありません)。参照とポインターは類似のものに使用されますが、それらはまったく異なり、互換性はありません。参照は、既存の変数の別名または別名として最もよく考えられます。 xintである場合、int& y = xを割り当てるだけで、yの新しい名前xを作成できます。あとがき、xyは同じ整数を参照するために交換可能に使用できます。これの2つの主な意味は、参照をNULLにすることはできません(参照する元の変数がある必要があるため)、および元の値を取得するために特別な演算子を使用する必要がないことです(単なる代替名であるため、ポインタではありません)。参照を再割り当てすることもできません。

  • *単項演算子として使用すると、dereference(参照types!とは関係ありません)と呼ばれる操作を実行します。この操作は、ポインターに対してのみ意味があります。ポインターを逆参照すると、ポインターが指しているものが返されます。したがって、pがintへのポインタである場合、*pはポイントされているintです。

  • &単項演算子として使用すると、address-ofと呼ばれる操作が実行されます。それはかなり自明です。 xが変数の場合、&xxのアドレスです。変数のアドレスは、その変数の型へのポインターに割り当てることができます。したがって、xintである場合、&xint*型のポインターに割り当てることができ、そのポインターはxを指します。 。例えば。 int* p = &xを割り当てると、*pを使用してxの値を取得できます。

型の接尾辞&は参照用であり、単項演算子&とは関係ありません。これは、ポインターで使用するアドレスを取得することに関係しています。 2つの用途は完全に無関係です。また、型接尾辞としての*はポインターを宣言し、単項演算子としての*はポインターに対してアクションを実行します。

74
Tyler McHenry

タイラー、それは非常に有用な説明でしたが、この違いをさらに明確にするために、Visual Studioデバッガーを使用していくつかの実験を行いました。

int sample = 90;
int& alias = sample;
int* pointerToSample  = &sample;

Name                  Address                        Type
&alias                0x0112fc1c {90}                int *
&sample               0x0112fc1c {90}                int *
pointerToSample       0x0112fc1c {90}                int *
*pointerToSample    90                       int
alias   90                                       int &
&pointerToSample      0x0112fc04 {0x0112fc1c {90}}   int * *

メモリレイアウト

PointerToSample       Sample/alias
_______________......____________________
0x0112fc1c |          |   90   |
___________|___.....__|________|_______...

[0x0112fc04]  ...      [0x0112fc1c
5
Berhe Abrha

ReturnC()およびreturnC2()では、アドレスを返すことを求めていません。

これらの関数は両方とも、オブジェクトへの参照を返します。
参照は何かのアドレスではなく、何かの代替名です(これは、コンパイラがアドレスを使用してオブジェクトを表すことができる(または状況に応じない)ことを意味する場合があります(または、レジスタにそれ))。

参照が特定のオブジェクトを指していることを知っているすべて。
参照自体はオブジェクトではなく、単なる代替名です。

3
Martin York

すべての例は、未定義のランタイム動作を生成します。実行が関数を離れると消えるアイテムへのポインターまたは参照を返しています。

明確にしましょう:

int * returnA()
{
  static int a;  // The static keyword keeps the variable from disappearing. 
  int * j = 0;   // Declare a pointer to an int and initialize to location 0.
  j = &a;        // j now points to a.
  return j;      // return the location of the static variable (evil).
}

関数では、変数jaの一時的な場所を指すように割り当てられます。関数を終了すると、変数aは消えますが、以前の場所はjを介して返されます。 aが指す場所にはjが存在しないため、*jにアクセスすると未定義の動作が発生します。

関数内の変数は、他のコードによる参照またはポインターを介して変更しないでください。未定義の動作を生成しますが、発生する可能性があります。

物足りないので、返されるポインターは定数データを指すものとして宣言する必要があります。返される参照はconstである必要があります。

const char * Hello()
{
  static const char text[] = "Hello";
  return text;
}

上記の関数は、定数データへのポインターを返します。他のコードは静的データにアクセス(読み取り)できますが、変更できません。

const unsigned int& Counter()
{
  static unsigned int value = 0;
  value = value + 1;
  return value;
}

上記の関数では、valueは最初のエントリでゼロに初期化されます。この関数を次に実行すると、valueが1ずつ増加します。この関数は、定数値への参照を返します。これは、他の関数が値を(遠くから)変数のように使用できることを意味します(ポインタを間接参照する必要はありません)。

私の考えでは、ポインターはオプションのパラメーターまたはオブジェクトに使用されます。オブジェクトが存在する必要があるときに参照が渡されます。関数内では、参照されたパラメーターは値が存在することを意味しますが、ポインターを参照解除する前にNULLをチェックする必要があります。また、参照を使用すると、ターゲットオブジェクトが有効であることがより確実になります。ポインターは無効なアドレス(null以外)を指し、未定義の動作を引き起こす可能性があります。

2
Thomas Matthews

意味的には、参照はアドレスとして機能します。ただし、構文的には、それらはコンパイラの仕事であり、あなたの仕事ではありません。参照を他の参照にバインドし、元のオブジェクトを参照させるなど、参照を元のオブジェクトであるかのように扱うことができますこの場合、ポインタ演算に別れを告げます。

その欠点は、参照するものを変更できないことです-構築時にバインドされます。

1
Puppy