Cのポインター:アンパーサンドとアスタリスクを使用する場合


298

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

ときに私が使用する必要があります&*


5
動作が異なる場合があることを、どのように見ているのか説明してください。そうでなければ、私たちはあなたを混乱させるのは何であるかを推測する必要があります。
Drew Dormann、2010年

1
ニールバターワースに同意する。おそらく、本から直接入手することで、より多くの情報を得ることができ、K&Rの説明は非常に明確です。
トム

145
SOでこのような種類の質問をするのは良い考えではないと言う皆さんのすべてには同意しません。Googleで検索するとき、SOは最大のリソースになりました。あなたはこれらの答えに十分な信用を与えていません。Dan Olsonの応答を読んでください。この答えは、初心者にとって本当に洞察力があり、信じられないほど役に立ちます。RTFM役に立たず、率直に言って非常に失礼です。答える時間がない場合は、これらの質問に時間をかけて答える人を尊重してください。私はこれを "anon"にできればいいのですが、明らかに彼/彼女には意味のある方法で貢献する時間がありません。
SSH

18
SSHこれは絶対に本当であると述べました。「Googleだけで」と叫ぶ人もいますが、最近では「StackOverflowだけを見る」という別の方法になっています。この質問は多くの人に役立ちます。(したがって、賛成投票と反対投票はありません。)
MC Emperor

回答:


610

ポインタと値があります:

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

2
なぜこれが機能しないのですか?int aX[] = {3, 4}; int *bX = &aX;
Pieter

5
配列は特別であり、透過的にポインタに変換できます。これは、ポインターから値に移動する別の方法を強調しています。これを上記の説明に追加します。
Dan Olson、

4
私がこれを正しく理解している場合...はすでにアドレス(つまり)を返すint *bX = &aX;ため、この例は機能しません。そのため、意味のないアドレスのアドレスを取得します。これは正しいです?aXaX[0]&aX[0]&aX
Pieter

6
あなたは正しいですが、実際にアドレスのアドレスが必要な場合があります。その場合は、int ** bX =&aXとして宣言しますが、これはより高度なトピックです。
Dan Olson

3
@Danは、与えられたint aX[] = {3,4};int **bX = &aX;エラーです。 &aX型は「配列[2] intへのポインタ」であり、「ポインタへのポインタ」ではありませんint。特に、配列の名前は、una​​ryの最初の要素へのポインターとして扱われません&。あなたは行うことができます:int (*bX)[2] = &aX;
Alokシングハル

28

配列と関数を扱う際にはパターンがあります。最初は少し見づらいです。

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

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

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

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

これが、「%s」に対応する引数に演算子を使用しない理由です:&scanf()

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

暗黙的な変換のため、配列の先頭を指す値をscanf()受け取りchar *ますstr。これは、配列式を引数として呼び出されるすべての関数に当てはまります(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はすべての関数引数を値で渡すことに注意してください。仮パラメーターは、実パラメーターの値のコピーを受け取ります。仮パラメーターに対する変更は、実パラメーターには反映されません。一般的な例はスワップ関数です:

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

仮パラメータxyから別個のオブジェクトであるab、そうに変化xし、yに反映されていないabaand の値を変更したいので、それらへのポインターをswap関数にb渡す必要があります

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関数では、xおよびの値を変更せずy、what xy pointの値を変更することに注意してください。への書き込みは、への書き込みと*xは異なりxます。値x自体は更新しません。場所を取得し、xその場所の値を更新します。

ポインター値を変更する場合も同様です。私たちが書いたら

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

次にstreamstream 指すパラメータではなく、入力パラメータの値を変更するので、変更し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 = ...;
}

動作しません。


16

簡単に言えば

  • &手段アドレス-のを、あなたは、Cのように、パラメータ変数を変更する機能のためのプレースホルダで、パラメータ変数を参照渡しするアンパサンド手段を用いて、値渡しされていることがわかります。
  • *意味デリファレンスそのポインタ変数の値を取得する意味、ポインタ変数のを。
int foo(int *x){
   *x++;
}

int main(int argc, char **argv){
   int y = 5;
   foo(&y);  // Now y is incremented and in scope here
   printf("value of y = %d\n", y); // output is 6
   /* ... */
}

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

int foo(int x){
   x++;
}

int main(int argc, char **argv){
   int y = 5;
   foo(y);  // Now y is still 5
   printf("value of y = %d\n", y); // output is 5
   /* ... */
}

これが逆参照の使用例です

int main(int argc, char **argv){
   int y = 5;
   int *p = NULL;
   p = &y;
   printf("value of *p = %d\n", *p); // output is 5
}

上記は、address-of を取得し、yそれをポインター変数に割り当てる方法を示していますp。次に、の値を取得するためにその前にを付加することで逆参照 pします。つまり、です。*p*p


10

*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 as well 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 identical

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

&変数または関数の宣言に現れる場合、それは通常、その変数がそのタイプの変数への参照であることを意味します。

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

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


1
@akmozo s / func_takes int_ptr2 / func_takes_int_ptr2 /(無効なスペース)
PixnBits 2017

4

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

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

私はすべての詳細な説明を調べていたので、代わりにニューサウスウェールズ大学からのビデオに助けを求めました。ここに簡単な説明があります:アドレスxと値を持つセルがある場合、値のアドレス7を要求する間接的な方法7&7そして間接的な方法は、アドレスの値を求めることxである*x。だから(cell: x , value: 7) == (cell: &7 , value: *x)、それに見て.Anotherの方法は:Johnで座っ7th seat【選択*7th seatを指しますJohn&Johnなりますaddress/の場所を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

実際、あなたはそれをパットダウンしています、あなたが知る必要がある以上のものはありません:-)

次のビットを追加するだけです。

  • 2つの操作は、スペクトルの両端です。&変数を取得してアドレスを*取得し、アドレスを取得して変数(または内容)を取得します。
  • 配列は、それらを関数に渡すときにポインタに「劣化」します。
  • 実際には、間接参照に複数のレベルを設定できます(char **pつまり、pへのポインターへのポインターcharです。

動作が異なるものについては、実際には:

  • すでに述べたように、配列は関数に渡されると(配列の最初の要素への)ポインターに低下します。サイズ情報は保持されません。
  • Cには文字列はなく、通常、ゼロ(\0)文字で終了する文字列を表す文字配列のみです。
  • 変数のアドレスを関数に渡すとき、ポインターを逆参照して変数自体を変更できます(通常、変数は値で渡されます(配列を除く))。

3

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

このチュートリアルは、初心者に非常に適しています(何が何であるか&を明確に説明してい*ます)。ええ、Kenneth Reekによる本のPointers in 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;
}

1

投稿が編集されたようです...

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

同じことをします。


質問はC ++ではなくCとタグ付けされています。
Prasoon Saurav、2010年

1
そして、問題のある
カウト
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.