私はCを学び始めています。
さて、私が理解していることから、ポインタは別の変数のアドレスを指し、直接それに近づかないことによってその変数値を変更するために使用されます。
今、私は3つの質問があります:
それも正しいですか?もしそうなら、なぜ私はそれが必要なのですか?
他に用途はありますか?
次のコード行に違いはありますか?
// 1 int x = 10; int *ptr = &x; // 2 int x = 10; int ptr = &x;
ポインタの値は、メモリ内の別のオブジェクトの場所です。したがって、最初の一連の宣言が与えられます。
int x = 10;
int *ptr = &x;
あなたはメモリに次のようなものを持っているでしょう(アドレスは薄い空気から引き出されており、実際のプラットフォームを表すことを意図していません):
アイテムアドレス0x000x010x02 0x03 ---- ------- ---- ---- ---- ---- x 0x08001000 0x00 0x00 0x00 0x0A ptr 0x08001004 0x08 0x00 0x10 0x00
x
値が含まれます10
。 ptr
のアドレスが含まれていますx
。したがって、式 x
と*ptr
は同等です。読み取りまたは書き込み*ptr
は、への読み取りまたは書き込みと同じx
です。
FWIW、それとの違い
int x = 10;
int ptr = &x;
これはptr
整数へのポインタではなく整数として扱われるため、次のようなことを試みるとコンパイラはヤクします*ptr
(単項のオペランドはポインタ型で *
なければなりません)。
では、なぜポインタなのか?
Cのポインターは、次の3つの目的を果たします。
まず、ポインターを使用すると、参照渡しのセマンティクスを模倣できます。Cでは、すべての関数の引数は値で渡されます。つまり、関数の仮引数は、実際の引数とはメモリ内の異なるオブジェクトです。たとえば、次のswap
関数を使用します。
void swap(int a, int b)
{
int t = a;
a = b;
b = t;
}
int main(void)
{
int x = 1, y = 2;
swap(x, y);
...
}
メモリを見ると、次のようなものがあります。
アイテムアドレス0x000x010x02 0x03 ---- ------- ---- ---- ---- ---- x 0x08001000 0x00 0x00 0x00 0x01 y 0x08001004 0x00 0x00 0x00 0x02 a 0x08001010 0x00 0x00 0x00 0x01 b 0x08001014 0x00 0x00 0x00 0x02
これは、またはに加えられた変更が反映されないa
ことを意味します。の呼び出し後、変更されません。 b
x
y
swap
x
y
ただし、次のように、とにポインタを渡すと、次のようになります。x
y
swap
void swap(int *a, int *b)
{
int t = *a;
*a = *b;
*b = t;
}
int main(void)
{
int x = 1, y = 2;
swap(&x, &y);
...
}
すると、私たちの記憶は次のようになります。
アイテムアドレス0x000x010x02 0x03 ---- ------- ---- ---- ---- ---- x 0x08001000 0x00 0x00 0x00 0x01 y 0x08001004 0x00 0x00 0x00 0x02 a 0x08001010 0x08 0x00 0x10 0x00 b 0x08001014 0x08 0x00 0x10 0x04
a
およびにはとのアドレスがb
含まれているため、式からの読み取りと書き込みは、とからの読み取りと書き込みに相当します。したがって、この呼び出しの後、との値が変更されます。x
y
*a
*b
x
y
swap
x
y
ポインタを使用する2つ目の理由は、動的に割り当てられたメモリを追跡するためです。実行時に新しいオブジェクトを作成する場合は、malloc
orcalloc
呼び出しを使用します。これにより、メモリが割り当てられ、メモリへのポインタが返されます。
int *p = malloc(sizeof *p * N); // dynamically allocates enough memory
// to hold N integer values and saves
// the location to p
ポインタは、Cで動的に割り当てられたメモリを追跡 する唯一の方法です。
最後に、ポインターを使用すると、リスト、ツリー、キューなどの動的データ構造を作成できます。構造内の各要素が次の要素を明示的に指すという考え方です。たとえば、整数の単純なリストを定義するタイプは次のとおりです。
struct elem
{
int data;
struct elem *next;
};
タイプの各オブジェクトは、タイプstruct elem
の次のアイテムを明示的に指しますstruct elem
。これにより、必要に応じて(配列とは異なり)リストにアイテムを追加したり、リストからアイテムを削除したりできます。また、リストを作成するときにリストを並べ替えることができます(例を投稿しますが、疲れています。おそらく意味がありません;とにかくすぐにそれらに到達します)。
ポインタは、指定されたタイプのメモリのアドレスを格納します。は、現在値10を格納している&x
変数のアドレスです。Anはこの場所を保持できます。10(別名)を変更するには、x
int* ptr
x
*ptr = 3;
コードの2番目のブロックはptr
、実際の番号であるかのようにアドレスを格納しています。これは、印刷しようとするptr
と、それが半乱数(おそらく非常に高い)であることに気付くであろうことを意味します。
これらの基本的なデータ型がどのようにメモリに格納されるかを調査することをお勧めします。
用途としては、自分でローカルコピーを作成するのではなく、スコープ外のメモリを変更できる関数を作成できます。あなたが学び続けて機能に到達するならば、それはよりよく説明されるべきです。Cを学ぶために何を使っていますか?私はあなたが持っているものを捨ててK&Rを取得することを強くお勧めします。
ポインタは通常、変数のアドレスを格納するために使用されます。ここで最初のケースで&x
は、アドレスx
を指定し、変数*ptr
には、アドレス値がポイントされた場所に値ストアがありますptr
。2番目のケースでは、ptrは変数xのアドレスを格納します。
#include<stdio.h>
#include<inttypes.h>
int main()
{
int x = 10;
int *ptr = &x;
uintptr_t p = (uintptr_t)&x;
printf("x=%d ,*ptr=%d,ptr=%p,p=%p",x,*ptr,ptr,p);
return 0;
}
私のマシンでは、このコードは以下を生成します。
x=10 ,*ptr=10,ptr=0028FF14,p=0028FF14
ポインタはメモリアドレスです。
ただし、必ずしも単一の変数を指しているわけではありません。
次のことを想像してみてください。
int * x = malloc( 2 * sizeof( int ) );
ここに、2つの整数を含むことができるメモリ領域へのポインタがあります。
それで:
x[ 0 ]
最初のintですx[ 1 ]
2番目のintです間接参照するときは、その例で最初のものを割り当てます。
*x = 0; // same as x[ 0 ] = 0
3番目の質問については、確かに違いがあります。
int x = 10;
int *ptr = &x; // As expected, ptr is now a pointer to x
int x = 10;
int ptr = &x; // Certainly not what you expect as ptr is not a pointer
2番目のケースでは、メモリアドレスを整数に割り当てます。これはポインターではありません。ポインターの長さがintと同じでない場合(たとえば、64ビットプラットフォームの場合)に問題が発生します。
算術演算ができるので、ポインタは便利です。
int * x = malloc( 2 * sizeof( int ) );
int * x2 = x; // So we don't touch x, as we'll need to free it.
x2++; // Now x2 points to the second integer.
x2[ 0 ] = 0; // same as x[ 1 ] = 0;
インクリメントは、ポインタのタイプに基づいて行われます。これは通常、魔法が始まるところです。
ショートのサイズが2のプラットフォームを使用している場合、次のことを想像できます。
short * x = malloc( 2 * sizeof( short ) );
char * x2 = ( char * )x;
aのサイズchar
が1であるため、以前の短いポインターの各バイトにアクセスできます(2バイトを割り当てたため4バイト)。
それで:
x2[ 0 ] -> First short / First byte
x2[ 1 ] -> First short / Second byte
x2[ 2 ] -> Second short / First byte
x2[ 3 ] -> Second short / Second byte
そして最後に、たとえば構造体を扱う場合にもポインタは優れており、それらを引数として渡す必要があります。
このような場合、ポインタを使用すると構造体アドレスが渡されます。そうしないと、呼び出し時に構造体をコピーする必要があり、効率がまったく悪くなります。
void foo( struct x p ); // Structure data will be copied
void bar( struct x * p ); // Only the address will be passed, no data copied