1

データ構造に関する本を読んでいるのですが、ポインターの概念を理解するのに苦労しています。私はCの経験があまりないということで、これを前置きさせてください.しかし、ここに行きます....

私が次のことをした場合:

 int num = 5;
 int *ptrNum;

 ptrNum = #

その値は単に変数のメモリアドレスですが、ポインタは実際のポインタに必要なメモリとともに32ビットintに十分なメモリを予約することを理解しています。

同じ量のメモリが予約されている場合、これを行う目的は何ですか? 変数 num の代わりにポインターを使用するのはなぜですか? 私はここで完全に基地から外れていますか?

4

4 に答える 4

12

値が機能しない状況でポインターを使用します。あなたの例では、あなたは正しいです。メリットはありません。典型的な境界線の有用な例は、swap 関数です。

void swap_int(int *i1, int *i2)
{
    int t1 = *i1;
    *i1 = *i2;
    *i2 = t1;
}

呼び出しシーケンス:

int main(void)
{
    int v1 = 0;
    int v2 = 31;
    printf("v1 = %d; v2 = %d\n", v1, v2);
    swap_int(&v1, &v2);
    printf("v1 = %d; v2 = %d\n", v1, v2);
    return 0;
}

ポインタを使用せずにそれを書くと、次のようになります。

void swap_int(int i1, int i2)
{
    int t1 = i1;
    i1 = i2;
    i2 = t1;
}

int main(void)
{
    int v1 = 0;
    int v2 = 31;
    printf("v1 = %d; v2 = %d\n", v1, v2);
    swap_int(v1, v2);
    printf("v1 = %d; v2 = %d\n", v1, v2);
    return 0;
}

次に、呼び出し元の関数の値に影響を与えることなく、関数内の 2 つのローカル変数を交換するだけです。ポインターを使用すると、呼び出し元の関数の変数に影響を与えることができます。

以下も参照してください。

  • scanf()- 関数ファミリー
  • strcpy()その他

その値は単に変数のメモリアドレスですが、ポインタは実際のポインタに必要なメモリとともに32ビットintに十分なメモリを予約することを理解しています。

あなたが説明しているように見えるのは、次のようです。

int *p1;

以下と同じ働きをします:

int _Anonymous;
int *p1 = &_Anonymous;

そうではありません。これは C です。作成するp1と、ポインターに十分なスペースが割り当てられます。最初に書いたように、初期化しないため、不確定な場所 (または場所なし) を指します。それ (ポインター) は、使用する前に初期化する必要があります。したがって:

int  i1 = 37;
int *p1 = &i1;

ただし、 の割り当てではp1、ポインター用に十分なスペースしか予約されません (通常、32 ビット コンパイルの場合は 32 ビット、64 ビット コンパイルの場合は 64 ビット)。それが指すスペースを個別に割り当てる必要があり、ポインターを初期化する必要があります。ポインターを初期化する別の方法は、動的に割り当てられたメモリを使用することです。

int *p2 = malloc(1000 * sizeof(*p2));

if (p2 != 0)
{
    ...use p2 as an array of 1000 integers...
    free(p2);
}

あなたはもう構造をカバーしましたか?そうでない場合、ツリーや連結リストなどの構造をカバーする例は役に立ちません。ただし、構造もカバーすると、ツリーまたはリンクされたリストを使用できるようになります。

struct list
{
    int data;
    struct list *next;
};

struct tree
{
    int data;
    struct tree *l_child;
    struct tree *r_child;
};

このような構造は、エントリを正しく接続するためにポインターに大きく依存しています。

于 2013-07-14T18:01:49.127 に答える
2

他のいくつかの回答は、変数のアドレスを取得してポインターに格納することに焦点を当てています。これはポインタの 1 つの用途にすぎません。ポインターのまったく別の用途は、動的に割り当てられたストレージを指すことと、そのストレージを構造化することです。

たとえば、ファイルを読み込んでメモリ内で作業するとします。ただし、事前にファイルの大きさはわかりません。コードに任意の上限を設定できます。

#define MAX_FILE_SIZE (640 * 1024)  /* 640K should be large enough for anyone */

char data[ MAX_FILE_SIZE ];

これは、小さなファイルにはメモリを浪費し、大きなファイルには十分な大きさではありません。より良いアプローチは、必要なものを実際に割り当てることです。例えば:

FILE *f = fopen("myfile", "rb");
off_t len;
char *data;

fseek(f, 0, SEEK_END);  /* go to the end of the file */
len = ftell(f);         /* get the actual file size */
fseek(f, 0, SEEK_SET);  /* rewind to the beginning  */

data = malloc( len );   /* Allocate just as much as you need */

ポインターのもう 1 つの主な用途は、データを構造化することです。たとえば、リスト、ツリー、またはその他の楽しい構造です。(あなたのデータ構造の本は、これらの多くに当てはまります。) データを再編成したい場合、ポインターを移動することは、データをコピーするよりもはるかに安価です。たとえば、次のリストがあるとします。

struct mystruct
{
     int x[1000];
     int y[1000];
};

それはたくさんのデータです。それを配列に格納するだけでは、そのデータを並べ替えると非常にコストがかかる可能性があります。

struct mystruct array[1000];

それを試してみてくださいqsort...非常に遅くなります。

代わりに要素へのポインターを格納し、ポインターをソートすることで、これを高速化できます。すなわち。

struct mystruct *array[1000];
int i;
struct mystruct *temp;

/* be sure to allocate the storage, though: */
temp = malloc( 1000 * sizeof( struct mystruct ) );

for (i = 0; i < 1000; i++)
    array[i] = temp + i;

array[]これらの構造体を並べ替える必要がある場合は、構造体全体ではなくポインターを交換します。

あなたの本でよりよくカバーされているより手の込んだデータ構造には立ち入りません。しかし、ポインタの他の用途についても少し触れてみたいと思いました。

于 2013-07-14T18:12:50.850 に答える
2

ポインターは、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およびbinは、実パラメータおよびinとfooは異なるメモリ内のオブジェクトであるため、 および への変更はおよびに反映されません。出力は「x = 0、y = 1」になります。との値を変更したい場合は、代わりにこれらの変数へのポインターを渡す必要があります。xybarabxyfooxy

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

今回は、仮パラメータabは変数andへのポインタです。およびintへの書き込みは、およびへの書き込みと同等です。したがって、出力は「x = 1、y = 2」です。 xy *a*bfooxybar

これはscanf()、他のライブラリ関数がどのように機能するかです。ポインタを使用して、操作したい実際のメモリを参照します。

動的に割り当てられたメモリを追跡する: ライブラリ関数malloccalloc、および を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 は要素の総数です。木の中。

于 2013-07-15T11:41:25.763 に答える