web-dev-qa-db-ja.com

Cでのポインタ:アンパサンドとアスタリスクをいつ使用するか

私はポインタから始めたところですが、少し混乱しています。 &は変数のアドレスを意味し、*をポインタ変数の前で使用して、ポインタが指すオブジェクトの値を取得できることを私は知っています。しかし、配列や文字列を扱うときや、変数のポインタコピーを使って関数を呼び出すときは、動作が異なります。これらすべての中に論理のパターンを見ることは困難です。

&*はいつ使用すべきですか?

251
Pieter

あなたはポインタと値を持っています:

int* p; // variable p is pointer to integer type
int i; // integer value

*を使ってポインタを値に変換します。

int i2 = *p; // integer i2 is assigned with integer value that pointer p is pointing to

&を使って値をポインタに変換します。

int* p2 = &i; // pointer p2 will point to the address of integer i

編集:配列の場合、それらはポインタのように扱われます。あなたがそれらをポインタとして考えるならば、あなたは上で説明されたようにそれらの中の値を取得するために*を使うでしょう、しかし[]演算子を使うもう一つの、より一般的な方法もあります:

int a[2];  // array of integers
int i = *a; // the value of the first element of a
int i2 = a[0]; // another way to get the first element

2番目の要素を取得するには

int a[2]; // array
int i = *(a + 1); // the value of the second element
int i2 = a[1]; // the value of the second element

したがって、[]インデックス演算子は*演算子の特殊な形式であり、次のように機能します。

a[i] == *(a + i);  // these two statements are the same thing
552
Dan Olson

配列や関数を扱うときにはパターンがあります。最初はちょっと見づらいです。

配列を扱うときは、次のことを覚えておくと便利です。ほとんどの文脈で配列式が現れると、その式の型は暗黙的に "TのN要素配列"から "Tへのポインタ"に変換され、その値が設定されます。配列の最初の要素を指す。この規則の例外は、配列式が&演算子またはsizeof演算子のオペランドとして表示されている場合、または宣言の初期化子として使用されている文字列リテラルの場合です。

したがって、引数として配列式を使用して関数を呼び出すと、その関数は配列ではなくポインタを受け取ります。

int arr[10];
...
foo(arr);
...

void foo(int *arr) { ... }

これが、あなたがしないscanf()の "%s"に対応する引数に&演算子を使う理由です。

char str[STRING_LENGTH];
...
scanf("%s", str);

暗黙的な変換のため、scanf()str配列の先頭を指すchar *値を受け取ります。これは、引数として配列式を使用して呼び出された関数(str*関数、*scanf関数、*printf関数など)についても同様です。

実際には、おそらく以下のように&演算子を使用して配列式で関数を呼び出すことは決してないでしょう。

int arr[N];
...
foo(&arr);

void foo(int (*p)[N]) {...}

そのようなコードはあまり一般的ではありません。関数宣言では配列のサイズを知っておく必要があり、関数は特定のサイズの配列へのポインタでのみ機能します(Tの10要素配列へのポインタは11要素配列へのポインタとは異なる型です)。 T)の。

配列式が&演算子へのオペランドとして現れる場合、結果の式のタイプは "TのN要素配列へのポインター"、またはT (*)[N]で、ポインターの配列(T *[N])およびへのポインターとは異なります。基本型(T *).

関数やポインタを扱うときに覚えておくべき規則は次のとおりです。引数の値を変更してそれを呼び出し側のコードに反映させるには、変更したいものへのポインタを渡す必要があります。繰り返しますが、配列は少しのモンキーレンチを作品に投げ入れますが、最初に通常のケースを扱います。

Cはall関数の引数を値で渡します。仮パラメータは実パラメータ内の値のコピーを受け取り、仮パラメータへの変更は実パラメータには反映されません。一般的な例はスワップ関数です。

void swap(int x, int y) { int tmp = x; x = y; y = tmp; }
...
int a = 1, b = 2;
printf("before swap: a = %d, b = %d\n", a, b);
swap(a, b);
printf("after swap: a = %d, b = %d\n", a, b);

次のような出力が得られます。

スワップ前:a = 1、b = 2 
スワップ後:a = 1、b = 2 

仮パラメータxおよびyは、aおよびbとは異なるオブジェクトであるため、xおよびyへの変更は、aおよびbには反映されません。 abの値を変更したいので、pointersをswap関数に渡さなければなりません。

void swap(int *x, int *y) {int tmp = *x; *x = *y; *y = tmp; }
...
int a = 1, b = 2;
printf("before swap: a = %d, b = %d\n", a, b);
swap(&a, &b);
printf("after swap: a = %d, b = %d\n", a, b);

これであなたの出力は

スワップ前:a = 1、b = 2 
スワップ後:a = 2、b = 1 

Swap関数では、xyの値は変更しませんが、what xyの値はを指すことに注意してください。 *xへの書き込みはxへの書き込みとは異なります。 x自体の値を更新するのではなく、xから場所を取得し、その場所の値を更新します。

ポインタ値を変更したい場合も同様です。書くなら

int myFopen(FILE *stream) {stream = fopen("myfile.dat", "r"); }
...
FILE *in;
myFopen(in);

それで、入力パラメータstreamの値を変更します。streamを指すものではないので、streamを変更してもinの値には影響しません。これが機能するためには、ポインタへのポインタを渡す必要があります。

int myFopen(FILE **stream) {*stream = fopen("myFile.dat", "r"); }
...
FILE *in;
myFopen(&in);

繰り返しになりますが、配列は少しのモンキーレンチを作品に投げ入れます。関数に配列式を渡すと、その関数が受け取るものはポインタです。配列添字の定義方法のため、配列で使用できるのと同じ方法でポインタに添字演算子を使用できます。

int arr[N];
init(arr, N);
...
void init(int *arr, int N) {size_t i; for (i = 0; i < N; i++) arr[i] = i*i;}

配列オブジェクトは代入できないことに注意してください。つまり、こんなことはできません

int a[10], b[10];
...
a = b;

そのため、配列へのポインタを扱うときは注意が必要です。何かのようなもの

void (int (*foo)[N])
{
  ...
  *foo = ...;
}

動作しません。

24
John Bode

簡単に言えば

  • &address-ofを意味し、Cのようにパラメータ変数を変更する関数のプレースホルダでは、パラメータ変数は参照渡しのアンパサンドを使って値渡しされます。
  • *は、ポインタ変数の参照解除を意味します。つまり、そのポインタ変数の値を取得します。
 int foo(int * x){
 * x ++; 
} 
 
 int main(int argc、char ** argv){[ int y = 5; 
 foo(&y); //ここでyはインクリメントされ、その範囲内にある
 printf( "yの値=%d\n"、y); //出力は6 
/* ... */
} 

上記の例は、参照渡しを使用して関数fooを呼び出す方法を示しています。これと比較してください。

 int foo(int x){
 x ++; 
} 
 
 int main(int argc、char ** argv){[.____ int y = 5; 
 foo(y); //これでyはまだ5 
 printf( "yの値=%d\n"、y); //出力は5 
/* ... */
} 

これが間接参照の使い方の実例です。

 int main(int argc、char ** argv){
 int y = 5; 
 int * p = NULL; 
 p =&y; 
 printf( "* pの値=%d\n"、* p); //出力は5 
} 

上記は、address-ofyを取得し、それをポインタ変数pに割り当てる方法を示しています。それから間接参照pの前に*を付けてpの値、つまり*pを取得します。

11
t0mm13b

*は、C/C++ではさまざまな目的に使用されるため、かなり複雑になる可能性があります。

*がすでに宣言されている変数/関数の前にある場合は、次のいずれかを意味します。

  • a)*はその変数の値へのアクセスを与えます(その変数の型がポインタ型であるか、*演算子をオーバーロードしている場合)。
  • b)*は乗算演算子の意味を持ちます、その場合、*の左側に別の変数がなければなりません

*が変数または関数の宣言にある場合、それはその変数がポインタであることを意味します。

int int_value = 1;
int * int_ptr; //can point to another int variable
int   int_array1[10]; //can contain up to 10 int values, basically int_array1 is an pointer aswell which points to the first int of the array
//int   int_array2[]; //illegal, without initializer list..
int int_array3[] = {1,2,3,4,5};  // these two
int int_array4[5] = {1,2,3,4,5}; // are indentical

void func_takes_int_ptr1(int *int_ptr){} // these two are indentical
void func_takes int_ptr2(int int_ptr[]){}// and legal

&が変数または関数の宣言の中にある場合、それは一般的にその変数がその型の変数への参照であることを意味します。

&がすでに宣言されている変数の前にある場合は、その変数のアドレスを返します。

さらに、配列を関数に渡すときは、配列が0で終わるcstring(char配列)のような場合を除いて、必ずその配列の配列サイズも渡す必要があることを知っておく必要があります。

9
smerlin

ポインタ変数または関数パラメータを宣言するときは、*を使用します。

int *x = NULL;
int *y = malloc(sizeof(int)), *z = NULL;
int* f(int *x) {
    ...
}

注意:宣言された各変数はそれ自身の*を必要とします。

値のアドレスを取得したい場合は、&を使用してください。ポインタの値を読み書きしたいときは*を使います。

int a;
int *b;
b = f(&a);
a = *b;

a = *f(&a);

配列は通常ポインタのように扱われるだけです。関数の中で配列パラメータを宣言するときは、ポインタと同じように簡単に宣言できます(同じことを意味します)。配列を関数に渡すときは、実際には最初の要素へのポインタを渡しています。

関数ポインタは、規則にまったく従わない唯一のものです。 &を使用せずに関数のアドレスを取得したり、*を使用せずに関数ポインタを呼び出すことができます。

4
Jay Conrod

私はすべての冗長な説明を調べていたので、代わりにニューサウスウェールズ大学の救助のためのビデオに目を向けました。ここに簡単な説明があります。アドレスxと値7を持つセルがある場合、値7のアドレスを求める間接的な方法は&7であり、アドレスxの値を求める間接的な方法は*x。So (cell: x , value: 7) == (cell: &7 , value: *x)です。 John7th seatにあります。*7th seatJohnを指し、&Johnaddress /の場所を示します7th seat。この簡単な説明は私を助け、他の人にも役立つことを願っています。優れたビデオのリンクは次のとおりです。 ここをクリック

別の例を次に示します。

#include <stdio.h>

int main()
{ 
    int x;            /* A normal integer*/
    int *p;           /* A pointer to an integer ("*p" is an integer, so p
                       must be a pointer to an integer) */

    p = &x;           /* Read it, "assign the address of x to p" */
    scanf( "%d", &x );          /* Put a value in x, we could also use p here */
    printf( "%d\n", *p ); /* Note the use of the * to get the value */
    getchar();
}

アドオン:使用する前に常にポインターを初期化します。そうでない場合、ポインターは何かを指し、オペレーティングシステム自分が所有していないことがわかっているメモリにアクセスできなくなります。しかし、単にp = &x;を置くだけで、ポインタに特定の場所を割り当てます。

3
user6288471

実際には、あなたはそれを軽視しています、あなたが知る必要があるより多くの何もありません:-)

以下のビットを追加します。

  • 2つの操作はスペクトルの反対側にあります。 &は変数を取ってアドレスを与え、*はアドレスを取って変数(または内容)を与えます。
  • 配列を関数に渡すと、ポインタは「劣化」します。
  • 間接参照には実際に複数のレベルを設定できます(char **pは、pcharへのポインタへのポインタであることを意味します)。

異なる動作をするものに関しては、実際ではありません。

  • すでに述べたように、配列は関数に渡されると(配列の最初の要素への)ポインタに劣化します。サイズ情報は保存されません。
  • cには文字列はありません。慣例により、ゼロ(\0)文字で終了する文字列を表す文字配列だけです。
  • 変数のアドレスを関数に渡すと、ポインタを参照解除して変数自体を変更できます(通常、変数は値で渡されます(配列を除く))。
3
paxdiablo

私はあなたが少し混乱していると思います。あなたはポインタに関するよいチュートリアル/本を読むべきです。

This チュートリアルは初心者にはとても便利です(&*が何であるかを明確に説明しています)。そして、ええ、Kenneth ReekによるCのポインタの本を読むのを忘れないでください。

&*の違いは非常に明白です。

例:

#include <stdio.h>

int main(){
  int x, *p;

  p = &x;         /* initialise pointer(take the address of x) */
  *p = 0;         /* set x to zero */
  printf("x is %d\n", x);
  printf("*p is %d\n", *p);

  *p += 1;        /* increment what p points to i.e x */
  printf("x is %d\n", x);

  (*p)++;         /* increment what p points to i.e x */
  printf("x is %d\n", x);

  return 0;
}
3
Prasoon Saurav

あなたの投稿が編集されたようです。

double foo[4];
double *bar_1 = &foo[0];

&を使って配列構造の先頭のアドレスを取得する方法を見てください。以下

Foo_1(double *bar, int size){ return bar[size-1]; }
Foo_2(double bar[], int size){ return bar[size-1]; }

同じことをします。

1
wheaties