理解指针的目的

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

如果我执行以下操作:

int num = 5; int *ptrNum; ptrNum = # 

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

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

在值不起作用的情况下使用指针。 在你的例子中,你是对的; 没有任何好处。 archtetypal边界线有用的例子是交换函数:

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

这种结构严重依赖于正确连接条目的指针。

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

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

 #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就可以了…它会很慢。

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

 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[]而不是整个结构中交换指针。

我不会深入了解本书更好的数据结构。 但是,我想我可能会让你尝试一些指针的其他用途。

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

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

如果没有指针,则会受到数组顺序和变量对齐的约束。 使用指针,您可以选择分配区域中的任何地址以使您具有任何对齐方式,您可以使用指向和分配的任何区域的列表元素。

因此,指针可以为您提供更多自由,同时每个指针只需要32或64位空间。

指针在C中有三个主要用途:

  • 假传递引用语义;
  • 跟踪动态分配的内存;
  • 构建动态数据结构。

假传递引用语义 :在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 ); } 

foo中的forms参数ab是内存中与实际参数xybar中不同的对象,因此对ab任何更改都不会反映在xy 。 输出将是“x = 0,y = 1”。 如果你想让foo改变xy的值,你需要将指针传递给那些变量:

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

这一次,forms参数ab指向变量xy 指针 ; 写入表达式 *a*b int foo相当于在bar写入xy 。 因此,输出是“x = 1,y = 2”。

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

跟踪动态分配的内存 :库函数malloccallocrealloc允许我们在运行时分配内存,并且所有三个返回指向已分配内存的指针(从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+i) == p[i]取消引用 p来访问该内存。

那么为什么不直接声明一个大小为numItems的数组并用它来完成呢? 毕竟,从C99开始,您可以使用可变长度数组,其中在运行时之前不必知道大小:

 // get numItems int p[numItems]; 

原因有三:首先,VLA并未得到普遍支持,截至2011年标准,VLA支持现在是可选的; 第二,我们不能在声明数组之后改变数组的大小,而我们可以使用realloc来调整我们分配的内存块的大小; 最后,VLA在可以使用的位置和可能的大小都受到限制 – 如果您需要在运行时分配大量内存,最好通过malloc/calloc/realloc不是VLA来实现。

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

 T sizeof T Original value of pp + 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; }; 

leftright成员指向树中的其他节点,如果没有子节点则指向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是元素的总数在树上。