为什么要使用指针?

时间:2020-03-06 15:01:59  来源:igfitidea点击:

我知道这是一个非常基本的问题,但是在使用高级语言编写了一些项目之后,我才开始进行一些基本的C ++编程。

基本上我有三个问题:

  • 为什么在普通变量上使用指针?
  • 我应该在何时何地使用指针?
  • 如何将指针与数组一起使用?

解决方案

指针是间接引用另一个变量的一种方法。它们不告诉变量值,而是告诉我们其地址。这在处理数组时特别有用,因为使用指向数组中第一个元素(其地址)的指针,我们可以通过增加指针(指向下一个地址位置)来快速找到下一个元素。

我读过的关于指针和指针算术的最佳解释是在K&R的C编程语言中。 C ++ Primer是开始学习C ++的一本好书。

指针在很大程度上是数组(在C / C ++中),它们是内存中的地址,并且可以根据需要像数组一样进行访问(在"正常"情况下)。

由于它们是项目的地址,因此它们很小:它们仅占用地址的空间。由于它们很小,因此将它们发送给函数很便宜。然后,它们允许该功能处理实际项目,而不是副本。

如果要进行动态存储分配(例如,用于链表),则必须使用指针,因为它们是从堆中获取内存的唯一方法。

因为到处复制大对象会浪费时间和内存。

  • 指针使我们可以从多个位置引用内存中的相同空间。这意味着我们可以在一个位置更新内存,并且可以从程序的另一位置看到更改。我们还可以通过共享数据结构中的组件来节省空间。
  • 我们应该在需要获取地址并将其传递到内存中特定位置的任何地方使用指针。我们还可以使用指针来导航数组:
  • 数组是已分配特定类型的连续内存块。数组的名称包含数组起始点的值。当我们添加1时,会将我们带到第二位。这样,我们就可以编写循环来增加向下滑动数组的指针,而无需使用显式计数器来访问数组。

这是C语言中的一个示例:

char hello[] = "hello";

char *p = hello;

while (*p)
{
    *p += 1; // increase the character by one

    p += 1; // move to the next spot
}

printf(hello);

印刷

ifmmp

因为它将每个字符的值加一。

使用指针的原因之一是可以在调用的函数中修改变量或者对象。

在C ++中,使用引用比使用指针更好。尽管引用本质上是指针,但是C ++在某种程度上隐藏了事实,使我们似乎好像在按值传递。这使得更改调用函数接收值的方式变得容易,而不必修改传递值的语义。

请考虑以下示例:

使用参考:

public void doSomething()
{
    int i = 10;
    doSomethingElse(i);  // passes i by references since doSomethingElse() receives it
                         // by reference, but the syntax makes it appear as if i is passed
                         // by value
}

public void doSomethingElse(int& i)  // receives i as a reference
{
    cout << i << endl;
}

使用指针:

public void doSomething()
{
    int i = 10;
    doSomethingElse(&i);
}

public void doSomethingElse(int* i)
{
    cout << *i << endl;
}

指针在许多数据结构中很重要,这些数据结构的设计要求能够有效地将一个"节点"链接或者链接到另一个节点。我们不会"选择"一个指针来表示诸如float之类的普通数据类型,它们只是具有不同的用途。

当我们需要高性能和/或者紧凑的内存时,指针非常有用。

数组中第一个元素的地址可以分配给一个指针。然后,这使我们可以直接访问基础分配的字节。不过,数组的重点是避免我们需要执行此操作。

这是为什么C的许多功能有意义的略有不同但很有见地的看法:http://steve.yegge.googlepages.com/tour-de-babel#C

基本上,标准的CPU体系结构是冯·诺依曼体系结构,并且能够在这样的机器上引用数据项在内存中的位置并对其进行算术运算非常有用。如果我们知道汇编语言的任何变体,我们将快速了解它在底层的重要性。

C ++使指针有些混乱,因为它有时会为我们管理指针,并以"引用"的形式隐藏其效果。如果使用直线C,则对指针的需求会更加明显:没有其他方法可以进行按引用调用,这是存储字符串的最佳方法,是遍历数组的最佳方法,等等。

指针的一种用法(我不会提到别人的文章中已经提到的内容)是访问我们尚未分配的内存。这对PC编程没有多大用处,但在嵌入式编程中用于访问内存映射的硬件设备。

在DOS的早期,我们曾经能够通过声明指向以下内容的指针来直接访问视频卡的视频内存:

unsigned char *pVideoMemory = (unsigned char *)0xA0000000;

许多嵌入式设备仍使用此技术。

  • 为什么在普通变量上使用指针?

简短的答案是:不。 ;-)指针将用于不能使用其他任何东西的地方。这是因为缺少适当的功能,缺少数据类型或者纯粹是出于性能考虑。下面的更多内容。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

  • 我应该在何时何地使用指针?

简短的答案是:无法使用其他任何东西。在C语言中,我们不支持复杂的数据类型,例如字符串。也没有办法将变量"通过引用"传递给函数。那是我们必须使用指针的地方。我们也可以让他们指向几乎所有内容,链接列表,结构成员等。但是,我们不要在这里讨论。

  • 如何将指针与数组一起使用?

毫不费力,却很混乱。 ;-)如果我们讨论诸如int和char之类的简单数据类型,则数组和指针之间几乎没有什么区别。
这些声明非常相似(但不尽相同,例如sizeof将返回不同的值):

char* a = "Hello";
char a[] = "Hello";

我们可以像这样到达数组中的任何元素

printf("Second char is: %c", a[1]);

由于数组以元素0开头,因此为索引1.

或者我们也可以这样做

printf("Second char is: %c", *(a+1));

因为我们告诉printf我们要打印一个字符,所以需要使用指针运算符()。如果没有,将打印内存地址本身的字符表示形式。现在我们改用角色本身。如果我们使用%s而不是%c,我们将要求printf打印'a'加上一个指向的内存地址的内容(在上面的示例中),而不必将*在前:

printf("Second char is: %s", (a+1)); /* WRONG */

但这将不会只打印第二个字符,而是会打印下一个内存地址中的所有字符,直到找到空字符(\ 0)。这就是事情开始变得危险的地方。如果我们不小心尝试使用%s格式化程序打印整数类型的变量而不是char指针怎么办?

char* a = "Hello";
int b = 120;
printf("Second char is: %s", b);

这将打印在内存地址120上找到的所有内容,然后继续打印直到找到空字符为止。执行此printf语句是错误和非法的,但是它仍然可以工作,因为在许多环境中指针实际上是int类型的。想象一下,如果我们改为使用sprintf()并将这种方式将太长的"字符数组"分配给另一个仅分配了一定有限空间的变量,可能会引起问题。我们很可能最终会覆盖内存中的其他内容,并导致程序崩溃(如果幸运的话)。

哦,如果我们在声明时未为char数组/指针分配字符串值,则必须在为其赋值之前为其分配足够的内存量。使用malloc,calloc或者类似方法。这是因为我们仅声明了数组中的一个元素/一个指向的单个内存地址。所以这是一些例子:

char* x;
/* Allocate 6 bytes of memory for me and point x to the first of them. */
x = (char*) malloc(6);
x[0] = 'H';
x[1] = 'e';
x[2] = 'l';
x[3] = 'l';
x[4] = 'o';
x[5] = '##代码##';
printf("String \"%s\" at address: %d\n", x, x);
/* Delete the allocation (reservation) of the memory. */
/* The char pointer x is still pointing to this address in memory though! */
free(x);
/* Same as malloc but here the allocated space is filled with null characters!*/
x = (char *) calloc(6, sizeof(x));
x[0] = 'H';
x[1] = 'e';
x[2] = 'l';
x[3] = 'l';
x[4] = 'o';
x[5] = '##代码##';
printf("String \"%s\" at address: %d\n", x, x);
/* And delete the allocation again... */
free(x);
/* We can set the size at declaration time as well */
char xx[6];
xx[0] = 'H';
xx[1] = 'e';
xx[2] = 'l';
xx[3] = 'l';
xx[4] = 'o';
xx[5] = '##代码##';
printf("String \"%s\" at address: %d\n", xx, xx);

请注意,在执行分配的内存的free()之后,我们仍然可以使用变量x,但是我们不知道其中有什么。还要注意,两个printf()可能会为我们提供不同的地址,因为不能保证第二次分配内存的空间与第一个相同。

在java和Call中,对象引用是指针,而c ++的作用是我们可以对指针指向的位置进行更多控制。记住,强大的力量伴随着重大的责任。

使用指针是因为提高了从动态堆区域分配和恢复数据的性能。