ポインターは、C で 3 つの主な目的を果たします。
- 偽の参照渡しのセマンティクス。
- 動的に割り当てられたメモリを追跡します。
- 動的データ構造を構築します。
偽の参照渡しセマンティクス: C では、すべての関数引数が値渡しされます。次のスニペットがあるとします。
void foo( int a, int b )
{
a = 1;
b = 2;
}
void bar( void )
{
int x=0, y=1;
foo( x, y );
printf( "x = %d, y = %d\n", x, y );
}
仮パラメータa
およびb
inは、実パラメータおよびinとfoo
は異なるメモリ内のオブジェクトであるため、 および への変更はおよびに反映されません。出力は「x = 0、y = 1」になります。との値を変更したい場合は、代わりにこれらの変数へのポインターを渡す必要があります。x
y
bar
a
b
x
y
foo
x
y
void foo( int *a, int *b )
{
*a = 1;
*b = 2;
}
void bar( void )
{
int x = 0, y = 1;
foo( &x, &y );
printf( "x = %d, y = %d\n", x, y );
}
今回は、仮パラメータa
とb
は変数andへのポインタです。式およびintへの書き込みは、およびへの書き込みと同等です。したがって、出力は「x = 1、y = 2」です。 x
y
*a
*b
foo
x
y
bar
これはscanf()
、他のライブラリ関数がどのように機能するかです。ポインタを使用して、操作したい実際のメモリを参照します。
動的に割り当てられたメモリを追跡する: ライブラリ関数malloc
、calloc
、および をrealloc
使用すると、実行時にメモリを割り当てることができ、3 つすべてが割り当てられたメモリへのポインターを返します (C89 以降、3 つすべてが を返しvoid *
ます)。たとえば、int
実行時に の配列を割り当てたい場合:
int *p = NULL;
size_t numItems;
// get numItems;
p = malloc( sizeof *p * numItems );
if ( p )
{
// do stuff with p[0] through p[numItems - 1];
}
free( p );
ポインター変数には、整数p
を保持するのに十分な大きさのメモリの新しく割り当てられたブロックのアドレスが含まれます。演算子または添字演算子 ( )を使用して逆参照numItems
することで、そのメモリにアクセスできます。 p
*
[]
*(p+i) == p[i]
では、サイズの配列を宣言して、それでnumItems
完了しないのはなぜですか? 結局、C99 の時点で、実行時までサイズを知る必要がない可変長配列を使用できます。
// get numItems
int p[numItems];
3 つの理由があります。1 つ目は、VLA が広くサポートされていないことです。2011 年の標準の時点で、VLA のサポートはオプションになっています。2 つ目は、宣言した後に配列のサイズを変更することはできませんが、realloc
割り当てたメモリ ブロックのサイズを変更するために使用することはできます。最後に、VLA は使用できる場所とサイズの両方に制限があります。実行時に大量のメモリを割り当てる必要がある場合はmalloc/calloc/realloc
、VLA よりもそれを行う方が適切です。
ポインター演算に関する簡単な注意: 任意の pointerT *p
の場合、式p+1
は type の次の要素のアドレスに評価されますがT
、これはアドレス値 + 1 である必要はありません。例:
T sizeof T Original value of p p + 1
- -------- ------------------- -----
char 1 0x8000 0x8001
int 4 0x8000 0x8004
double 8 0x8000 0x8008
動的データ構造の構築: 新しい要素をリストに簡単に挿入したり、値をすばやく検索したり、特定のアクセス順序を強制したりできるような方法でデータを保存したい場合があります。これらの目的で使用されるさまざまなデータ構造が多数あり、ほとんどすべての場合でポインターが使用されます。たとえば、二分探索木を使用して、特定の値の検索が非常に高速になるようにデータを編成できます。ツリー内の各ノードには 2 つの子があり、それぞれがツリー内の次の要素を指しています。
struct node {
T key;
Q data;
struct node *left;
struct node *right;
};
left
およびメンバーはright
、ツリー内の他のノードを指すか、子がない場合は NULL を指します。通常、left
子は値が現在のノードの値より「小さい」ノードをright
指し、子は値が現在のノードより「大きい」ノードを指します。次のようにツリーで値を検索できます。
int find( struct node *root, T key, Q *data )
{
int result = 0;
if ( root == NULL ) // we've reached the bottom of the tree
{ // without finding anything
result = 0;
}
else if ( root->key == key ) // we've found the element we're looking for
{
*data = root->data;
result = 1;
}
else if ( root->key < key )
{
// The input key is less than the current node's key,
// so we search the left subtree
result = find( root->left, key, data );
}
else
{
// The input key is greater than the current node's key,
// so we search the right subtree
result = find( root->right, key, data );
}
return result;
}
ツリーのバランスが取れている (つまり、左側のサブツリーの要素数が右側のサブツリーの要素数と等しい) と仮定すると、チェックされる要素の数は約 log 2 N になります。ここで、N は要素の総数です。木の中。