为什么使用指针?

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

基本上我有三个问题:

  1. 为什么使用指针而不是普通变量?
  2. 我应该在何时何地使用指针?
  3. 如何在数组中使用指针?
343235 次浏览

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

我读过的关于指针和指针算术的最好解释是在K &R C语言。一本适合开始学习c++的好书是c++底漆

在很大程度上,指针是数组(在C/ c++中)——它们是内存中的地址,如果需要(在“正常”情况下),可以像数组一样访问它们。

因为它们是一个项目的地址,所以它们很小:它们只占用一个地址的空间。由于它们很小,将它们发送到函数是很便宜的。然后它们允许该函数在实际项目上工作,而不是一个副本。

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

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

  1. 指针允许您从多个位置引用内存中的相同空间。这意味着您可以在一个位置更新内存,并且可以从程序中的另一个位置看到更改。您还可以通过共享数据结构中的组件来节省空间。
  2. 在需要获取地址并将地址传递到内存中特定位置的任何地方都应该使用指针。你也可以使用指针来导航数组:
  3. 数组是用特定类型分配的连续内存块。数组的名称包含数组起始位置的值。加上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

因为它取每个字符的值并加1。

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

在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的许多特性是有意义的

基本上,标准的CPU体系结构是Von Neumann体系结构,在这样的机器上,能够引用内存中数据项的位置并对其进行运算是非常有用的。如果您了解汇编语言的任何变体,您将很快看到这在低级别上是多么重要。

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

索引1,因为数组从元素0开始。: -)

或者你也可以这样做

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

指针操作符(*)是必需的,因为我们要告诉printf我们想打印一个字符。如果没有*,内存地址本身的字符表示将被打印出来。现在我们用字符本身代替。如果我们使用%s而不是%c,我们将要求printf打印'a' + 1所指向的内存地址的内容(在上面的例子中),并且我们不必把*放在前面:

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数组”分配给另一个变量,只分配了一定的有限空间,可能会引起什么问题。您很可能会在内存中重写其他内容,并导致程序崩溃(如果幸运的话)。

哦,如果你在声明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] = '\0';
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] = '\0';
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] = '\0';
printf("String \"%s\" at address: %d\n", xx, xx);

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

在java和c#中,所有的对象引用都是指针,而在c++中,你可以更好地控制指针指向的地方。记住,能力越大,责任越大。

使用指针而不是变量的一种方法是消除所需的重复内存。例如,如果您有某个大型复杂对象,您可以使用指针为您所做的每次引用指向该变量。对于变量,您需要为每个副本复制内存。

  • 在某些情况下,需要函数指针来使用共享库(. dll或.so)中的函数。这包括跨语言执行工作,其中通常提供DLL接口。
  • 使编译器
  • 制作科学计算器,你有一个数组或向量或字符串映射函数指针?
  • 尝试直接修改显存-制作自己的图形包
  • 做一个API!
  • 数据结构-用于您正在制作的特殊树的节点链接指针

使用指针有很多原因。如果您想要保持跨语言兼容性,那么在dll中使用C名编辑尤其重要。

以下是我的答案,我不会保证自己是专家,但我在我正在编写的一个库中发现了很棒的指针。在这个库中(它是一个带有OpenGL的图形API:-)),你可以创建一个带有顶点对象的三角形。draw方法取这些三角形对象,然后。根据我创建的顶点对象绘制它们。嗯,没关系。

但是,如果我改变顶点坐标呢?移动它或者在顶点类中使用moveX() ?好吧,现在我必须更新三角形,添加更多的方法和性能是浪费的,因为我必须更新三角形每次顶点移动。虽然不是什么大事,但也没那么好。

现在,如果我有一个有大量顶点和三角形的网格,网格在旋转,移动,等等。我必须更新使用这些顶点的每个三角形,可能在场景中的每个三角形,因为我不知道哪个使用哪个顶点。这需要大量的计算机,如果我在一个景观上有几个网格,天哪!我遇到麻烦了,因为我更新了每个三角形,几乎每一帧,因为这些顶点一直在变化!

对于指针,你不需要更新三角形。

如果我有三个*Vertex对象每个三角形类,我不仅节省空间,因为无数的三角形没有三个顶点对象本身很大,而且这些指针将始终指向他们的顶点,无论顶点变化有多频繁。由于指针仍然指向同一个顶点,三角形不会改变,更新过程更容易处理。如果我把你弄糊涂了,我不会怀疑,我并没有假装成专家,只是在讨论中发表我的意见。

C语言中指针的需求描述为在这里

其基本思想是,语言中的许多限制(如使用数组、字符串和修改函数中的多个变量)可以通过操纵数据的内存位置来消除。为了克服这些限制,C语言中引入了指针。

此外,还可以看到,使用指针,您可以更快地运行代码,并在将大数据类型(如具有许多字段的结构)传递给函数时节省内存。在传递之前复制此类数据类型将花费时间并消耗内存。这是程序员更喜欢大数据类型指针的另一个原因。

PS:请参考提供的链接的详细解释与示例代码。

在c++中,如果你想使用子类型多态性,你可以< em > < / em >来使用指针。请看这个帖子:没有指针的c++多态

真的,仔细想想,这是有道理的。当您使用子类型多态性时,最终,您不知道将调用哪个类或子类的方法实现,因为您不知道实际的类是什么。

使用变量保存未知类的对象的想法与c++在堆栈上存储对象的默认(非指针)模式不兼容,在堆栈中分配的空间量直接对应于类。注意:如果一个类有5个而不是3个实例字段,则需要分配更多的空间。

人力资源/ > < p > < 请注意,如果您使用'&'通过引用传递参数,间接(即指针)仍然涉及幕后。'&'只是语法糖,它(1)省去了使用指针语法的麻烦,(2)允许编译器更严格(例如禁止空指针)

关于你的第二个问题,通常你在编程时不需要使用指针,但是有一个例外,那就是当你创建一个公共API时。

c++构造的问题,人们通常用来代替指针非常依赖于您所使用的工具集,这是好当你有你需要的所有控制源代码,但是如果你编译一个静态库与visual studio 2008为例,尝试使用visual studio 2010中你会得到大量的链接器错误,因为新项目与一个新版本的STL不是向后兼容的。如果你编译了一个DLL,并提供了一个人们在不同工具集中使用的导入库,事情就会变得更糟,因为在这种情况下,你的程序迟早会莫名其妙地崩溃。

因此,为了将大型数据集从一个库移动到另一个库,如果你不想强迫其他人使用与你使用的相同工具,你可以考虑给一个指向应该复制数据的函数的数组指针。这样做的好处是它甚至不必是一个c风格的数组,你可以使用std::vector并通过给出第一个元素的地址来给出指针,例如向量[0],并使用std::vector在内部管理数组。

在c++中使用指针的另一个很好的原因与库有关,考虑有一个在程序运行时不能加载的dll,所以如果你使用导入库,那么依赖关系就不能满足,程序就会崩溃。例如,当您在应用程序旁边的dll中提供一个公共api,并且希望从其他应用程序访问它时,就会出现这种情况。在这种情况下,为了使用API,你需要从它的位置加载dll(通常是在注册表项中),然后你需要使用函数指针来调用dll内部的函数。有时候,开发API的人会很友好地给你一个.h文件,其中包含帮助函数来自动化这个过程,并给你所有你需要的函数指针,但如果没有,你可以在windows上使用LoadLibrary和GetProcAddress,在unix上使用dlopen和dlsym来获取它们(考虑到你知道函数的整个签名)。

让我也试着回答这个问题。

指针类似于引用。换句话说,它们不是副本,而是引用原始值的一种方式。

首先,你通常需要使用指针经常出现的一个地方是当你处理使用嵌入式硬件时。也许你需要切换数字IO引脚的状态。也许你正在处理一个中断,需要在一个特定的位置存储一个值。你懂的。但是,如果您不直接处理硬件,只是想知道使用哪种类型,请继续阅读。

为什么使用指针而不是普通变量?当你在处理复杂的类型时,比如类、结构和数组,答案就会变得更清楚。如果你要使用一个普通的变量,你可能最终会生成一个副本(编译器足够聪明,在某些情况下可以防止这种情况,c++ 11也有帮助,但我们现在不讨论这个问题)。

现在如果你想修改原始值会发生什么?你可以使用这样的代码:

MyType a; //let's ignore what MyType actually is right now.
a = modify(a);

这可以很好地工作,如果你不知道为什么要使用指针,你就不应该使用它们。当心“他们可能更快”的理由。运行您自己的测试,如果它们确实更快,那么就使用它们。

但是,假设您正在解决一个需要分配内存的问题。当你分配内存时,你需要释放它。内存分配可能成功,也可能不成功。这就是指针派上用场的地方——它们允许您测试对象是否存在你已经分配了,它们允许你通过取消引用指针来访问内存分配的对象。

MyType *p = NULL; //empty pointer
if(p)
{
//we never reach here, because the pointer points to nothing
}
//now, let's allocate some memory
p = new MyType[50000];
if(p) //if the memory was allocated, this test will pass
{
//we can do something with our allocated array
for(size_t i=0; i!=50000; i++)
{
MyType &v = *(p+i); //get a reference to the ith object
//do something with it
//...
}
delete[] p; //we're done. de-allocate the memory
}

这是为什么你要使用指针的关键- 引用假设所引用的元素已经存在。而指针则不然。

使用指针(或者至少最终不得不处理指针)的另一个原因是因为它们是在引用之前就存在的数据类型。因此,如果你最终使用库来做你知道它们更擅长的事情,你会发现很多库都在到处使用指针,这仅仅是因为它们存在的时间长(其中很多是在c++之前编写的)。

如果你不使用任何库,你可以用这样一种方式来设计你的代码,你可以远离指针,但鉴于指针是语言的基本类型之一,你越快适应使用它们,你的c++技能就越可移植。

从可维护性的角度来看,我还应该提到,当您确实使用指针时,您要么必须测试它们的有效性,并在它们无效时处理情况,要么假设它们有效,并接受这样一个事实,即当这个假设被打破时,您的程序将崩溃或更糟。换句话说,对于指针,您的选择是要么引入代码复杂性,要么在出现故障时增加维护工作量,你试图追踪一个属于指针引入的整个错误类的错误,比如内存损坏。

因此,如果您控制所有代码,请远离指针,而使用引用,尽可能将它们保持为const。这将迫使您考虑对象的生命周期,并最终使您的代码更容易理解。

只要记住这个区别:引用本质上是一个有效的指针。指针并不总是有效的。

所以我是说不可能创建无效引用吗?不。这是完全可能的,因为c++几乎可以让你做任何事情。只是在无意中更难做到,你会惊讶于有多少bug是无意的:)