1

我正在阅读一本关于数据结构的书,并且难以掌握指针的概念。让我先说我对C没有很多经验。但是这里......

如果我执行以下操作:

 int num = 5;
 int *ptrNum;

 ptrNum = #

据我了解,该指针为 32 位 int 以及实际指针所需的内存保留了足够的内存,尽管它的值只是变量的内存地址。

如果保留相同数量的内存,这样做的目的是什么?为什么我要使用指针而不是变量 num?我完全不在这儿吗?

4

4 回答 4

12

在值不起作用的情况下使用指针。在您的示例中,您是正确的;没有任何好处。典型的边界线有用的例子是一个交换函数:

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

那么您只需在函数中交换两个局部变量,而不影响调用函数中的值。使用指针,您可以影响调用函数中的变量。

也可以看看:

  • 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

如何将元素添加到动态列表?通过每次创建一个新数组?

您只需添加指向下一个元素的指针,并将前一个单元格的下一个指针链接到它。

如果没有指针,您将受限于数组的顺序和变量的对齐方式。使用指针,您可以选择分配区域中的任何地址以进行任何您喜欢的对齐,您可以让列表元素指向和来自您分配的任何区域。

因此,指针给您更多的自由,同时每个指针只需要 32 或 64 位空间。

于 2013-07-14T18:04:05.997 回答
2

其他几个答案集中在获取变量的地址并将其存储在指针中。这只是指针的一种用途。指针的一个完全不同的用途是指向动态分配的存储,并用于构建该存储。

例如,假设您想读入一个文件并在内存中处理它。但是,您不知道文件有多大。您可以在代码中设置任意上限:

#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 */

指针的另一个主要用途是构造数据,比如在列表、树或其他有趣的结构中。(您的数据结构书将涉及其中的许多内容。)如果您想重新组织数据,移动指针通常比复制数据便宜得多。例如,假设您有以下列表:

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

这是很多数据。如果您只是将其存储在一个数组中,那么对该数据进行排序可能会非常昂贵:

struct mystruct array[1000];

试试qsort那个...它会很慢。

您可以通过存储指向元素的指针并对指针进行排序来加快速度。IE。

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

形参abin是内存中与实际参数和infoo不同的对象,因此对and的任何更改都不会反映在and中。输出将是“x = 0, y = 1”。如果要更改 and 的值,则需要将指针传递给这些变量: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指向变量x和的指针y;写入表达式 *a*bintfoo等效于写入xyin bar。因此,输出为“x = 1,y = 2”。

这就是scanf()其他库函数的工作方式和分数;他们使用指针来引用我们想要操作的实际内存。

跟踪动态分配的内存:库函数malloc,callocrealloc允许我们在运行时分配内存,并且所有三个都返回指向已分配内存的指针(从 C89 开始,所有三个返回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];

三个原因:第一,VLA 没有得到普遍支持,从 2011 标准开始,VLA 支持现在是可选的;其次,我们不能在声明后更改数组的大小,而可以使用它realloc来调整我们分配的内存块的大小;最后,VLA 的使用范围和大小都受到限制——如果您需要在运行时分配大量内存,那么最好通过malloc/calloc/reallocVLA 来完成。

关于指针算术的快速说明:对于任何指针T *p,表达式p+1将计算为类型的下一个元素的地址T,这不一定是地址值 + 1。例如:

T        sizeof T     Original value of p    p + 1
-        --------     -------------------    -----
char            1                  0x8000    0x8001
int             4                  0x8000    0x8004
double          8                  0x8000    0x8008

构建动态数据结构:有时我们希望以一种便于将新元素插入列表、快速搜索值或强制特定访问顺序的方式存储数据。有许多不同的数据结构用于这些目的,并且几乎在所有情况下它们都使用指针。例如,我们可以使用二叉搜索树来组织我们的数据,以便搜索特定值非常快。树中的每个节点都有两个孩子,每个孩子都指向树中的下一个元素:

struct node {
  T key;
  Q data;
  struct node *left;
  struct node *right;
};

leftand成员指向树中的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 回答