理解指针的目的
我正在读一本关于数据结构的书,并且难以掌握指针的概念。 让我先说一下我对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参数a
和b
是内存中与实际参数x
和y
在bar
中不同的对象,因此对a
和b
任何更改都不会反映在x
和y
。 输出将是“x = 0,y = 1”。 如果你想让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 ); }
这一次,forms参数a
和b
是指向变量x
和y
指针 ; 写入表达式 *a
和*b
int foo
相当于在bar
写入x
和y
。 因此,输出是“x = 1,y = 2”。
这就是scanf()
和其他几十个库函数的工作方式; 他们使用指针来引用我们想要操作的实际内存。
跟踪动态分配的内存 :库函数malloc
, calloc
和realloc
允许我们在运行时分配内存,并且所有三个返回指向已分配内存的指针(从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; };
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是元素的总数在树上。