我正在阅读一本关于数据结构的书,并且难以掌握指针的概念。让我先说我对C没有很多经验。但是这里......
如果我执行以下操作:
int num = 5;
int *ptrNum;
ptrNum = #
据我了解,该指针为 32 位 int 以及实际指针所需的内存保留了足够的内存,尽管它的值只是变量的内存地址。
如果保留相同数量的内存,这样做的目的是什么?为什么我要使用指针而不是变量 num?我完全不在这儿吗?
我正在阅读一本关于数据结构的书,并且难以掌握指针的概念。让我先说我对C没有很多经验。但是这里......
如果我执行以下操作:
int num = 5;
int *ptrNum;
ptrNum = #
据我了解,该指针为 32 位 int 以及实际指针所需的内存保留了足够的内存,尽管它的值只是变量的内存地址。
如果保留相同数量的内存,这样做的目的是什么?为什么我要使用指针而不是变量 num?我完全不在这儿吗?
在值不起作用的情况下使用指针。在您的示例中,您是正确的;没有任何好处。典型的边界线有用的例子是一个交换函数:
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;
};
这种结构严重依赖指针来正确连接条目。
如何将元素添加到动态列表?通过每次创建一个新数组?
您只需添加指向下一个元素的指针,并将前一个单元格的下一个指针链接到它。
如果没有指针,您将受限于数组的顺序和变量的对齐方式。使用指针,您可以选择分配区域中的任何地址以进行任何您喜欢的对齐,您可以让列表元素指向和来自您分配的任何区域。
因此,指针给您更多的自由,同时每个指针只需要 32 或 64 位空间。
其他几个答案集中在获取变量的地址并将其存储在指针中。这只是指针的一种用途。指针的一个完全不同的用途是指向动态分配的存储,并用于构建该存储。
例如,假设您想读入一个文件并在内存中处理它。但是,您不知道文件有多大。您可以在代码中设置任意上限:
#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[]
而不是整个结构。
我不会深入探讨您的书更好地介绍的更高级的数据结构。但是,我想我可以让您体验一下指针的其他用途。
指针在 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是内存中与实际参数和infoo
不同的对象,因此对and的任何更改都不会反映在and中。输出将是“x = 0, y = 1”。如果要更改 and 的值,则需要将指针传递给这些变量: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
是指向变量x
和的指针y
;写入表达式 *a
和*b
intfoo
等效于写入x
和y
in bar
。因此,输出为“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
*
[]
*(p+i) == p[i]
那么为什么不直接声明一个大小数组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 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;
};
left
and成员指向树中的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 是元素的总数在树上。