Why use pointers?
我知道这是一个非常基本的问题,但是我刚刚开始用一些高级C++语言编写了一些基本的C++程序。
基本上我有三个问题:
- 为什么在正常变量上使用指针?
简短的回答是:不要。;-)指针用于不能使用其他任何东西的地方。这可能是因为缺乏适当的功能、缺少数据类型或纯粹的性能。下面…
- 我应该在何时何地使用指针?
这里的简短回答是:在那里你不能使用任何其他东西。在C中,您不支持复杂的数据类型,如字符串。也不能通过引用将变量传递给函数。这就是你必须使用指针的地方。此外,您还可以让它们指向几乎所有内容、链接列表、结构成员等等。但我们不要再谈这个了。
- 如何在数组中使用指针?
不费吹灰之力,混乱不堪。;-)如果我们讨论简单的数据类型,比如int和char,那么数组和指针之间的差别就很小。这些声明非常相似(但不相同-例如,
1 2 | char* a ="Hello"; char a[] ="Hello"; |
您可以像这样访问数组中的任何元素
1 | printf("Second char is: %c", a[1]); |
索引1,因为数组以元素0开头。-)
或者你也可以这样做
1 | printf("Second char is: %c", *(a+1)); |
指针运算符(the*)是必需的,因为我们告诉printf我们要打印一个字符。如果没有*,将打印内存地址本身的字符表示形式。现在我们使用的是角色本身。如果我们使用了%s而不是%c,我们会要求printf打印"a"加1(在上面的示例中)指向的内存地址的内容,而不必将*放在前面:
1 | printf("Second char is: %s", (a+1)); /* WRONG */ |
但在找到空字符( )之前,这不会只是打印第二个字符,而是打印下一个内存地址中的所有字符。这就是事情开始变得危险的地方。如果不小心用%s格式化程序打印了类型为integer的变量而不是char指针,该怎么办?
1 2 3 | char* a ="Hello"; int b = 120; printf("Second char is: %s", b); |
这将打印内存地址120上找到的任何内容,并继续打印,直到找到一个空字符。执行这个printf语句是错误的,也是非法的,但它可能无论如何都能工作,因为在许多环境中指针实际上是int类型的。设想一下,如果您使用sprintf()而不是这样,那么您可能会导致的问题是,将过长的"char array"分配给另一个变量,而这个变量只分配了一定的有限空间。最后你很可能会在内存中写下其他东西,导致程序崩溃(如果你幸运的话)。
哦,如果在声明char数组/指针时没有给它赋值,那么在给它赋值之前必须给它分配足够的内存。使用malloc、calloc或类似产品。这是因为您只声明了数组中的一个元素/要指向的单个内存地址。下面是几个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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 ", 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 ", 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 ", xx, xx); |
请注意,在对分配的内存执行了free()之后,仍然可以使用变量x,但您不知道其中包含什么。另外请注意,两个printf()可能会给您提供不同的地址,因为不能保证第二次内存分配与第一次内存分配在同一空间中执行。
使用指针的一个原因是可以在被调用函数中修改变量或对象。
在C++中,使用引用比指针更好。虽然引用基本上是指针,但是C++在某种程度上隐藏了事实,使它看起来像是在通过值。这使得更改调用函数接收值的方式变得容易,而无需修改传递值的语义。
请考虑以下示例:
使用引用:
1 2 3 4 5 6 7 8 9 10 11 12 | 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; } |
使用指针:
1 2 3 4 5 6 7 8 9 10 | public void doSomething() { int i = 10; doSomethingElse(&i); } public void doSomethingElse(int* i) { cout << *i << endl; } |
下面是C中的一个示例:
1 2 3 4 5 6 7 8 9 10 11 12 | char hello[] ="hello"; char *p = hello; while (*p) { *p += 1; // increase the character by one p += 1; // move to the next spot } printf(hello); |
印刷品
1 | ifmmp |
因为它获取每个字符的值并将其递增一个。
指针是获取对另一个变量的间接引用的一种方法。它们告诉您变量的地址,而不是保存变量的值。这在处理数组时特别有用,因为使用指向数组中第一个元素(其地址)的指针,可以通过增加指针(指向下一个地址位置)快速找到下一个元素。
我读过的关于指针和指针算术的最好解释是K&R的C编程语言。初学者学习C++的一本好书是C++入门。
我也试着回答这个问题。
指针与引用类似。换句话说,它们不是复制品,而是引用原始价值的一种方式。
在做其他事情之前,一个通常需要大量使用指针的地方是在处理嵌入式硬件时。也许你需要切换数字IO管脚的状态。可能您正在处理一个中断,需要在特定位置存储一个值。你拿到照片了。但是,如果您不直接处理硬件,只是想知道使用哪种类型,请继续阅读。
为什么使用指针而不是普通变量?当你处理复杂的类型,比如类、结构和数组时,答案会变得更清楚。如果你要使用一个普通变量,你可能会制作一个拷贝(编译器足够聪明,在某些情况下防止这种情况,C++ 11也有帮助,但是我们现在就不要讨论了)。
现在,如果要修改原始值会发生什么?你可以用这样的方法:
1 2 | MyType a; //let's ignore what MyType actually is right now. a = modify(a); |
这样做很好,如果你不知道为什么要使用指针,你就不应该使用它们。注意"他们可能更快"的原因。运行您自己的测试,如果它们实际上更快,那么就使用它们。
但是,假设您正在解决一个需要分配内存的问题。分配内存时,需要解除分配。内存分配可能成功,也可能失败。这就是指针有用的地方——它们允许您测试已分配对象的存在性,并且允许您通过取消引用指针来访问内存分配给的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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++技能就越容易携带。
从可维护性的角度来看,我还应该提到,当您使用指针时,您要么必须测试它们的有效性,并在它们无效时处理这种情况,要么假设它们是有效的,并接受这样一个事实,即当这个假设被破坏时,您的程序将崩溃或更糟。换句话说,使用指针的选择是在发生故障时引入代码复杂性或进行更多的维护工作,并试图跟踪属于指针引入的一类错误的bug,例如内存损坏。
因此,如果您控制了所有的代码,那么就远离指针,而是使用引用,尽可能保持引用不变。这将迫使您考虑对象的生命周期,并最终使代码更容易理解。
记住这一区别:引用本质上是一个有效指针。指针并不总是有效的。
那么,我是说不可能创建一个无效的引用吗?不可能,因为C++让你几乎什么都能做。很难无意中做,你会惊讶于有多少错误是无意的:)
这里有一个稍微不同的,但有见地的理解为什么C的许多特性是有意义的:http://steve.yegge.googlepages.com/tour de babel c
基本上,标准的CPU体系结构是von neumann体系结构,能够在这样的机器上引用数据项在内存中的位置并用它做算术是非常有用的。如果您知道汇编语言的任何变体,您将很快看到这在底层是多么重要。
C++使指针有点混乱,因为它有时会为你管理它们,并用"引用"的形式隐藏它们的效果。如果使用直C,指针的需求就更加明显:没有其他方法可以通过引用来调用,这是存储字符串的最好方法,它是迭代数组的最好方法,等等。
指针的一个用途(我不会提到其他人的帖子中已经介绍的内容)是访问您尚未分配的内存。这对PC编程没有多大帮助,但它被用于嵌入式编程,以访问内存映射的硬件设备。
回到DOS的旧时代,您可以通过声明指向以下内容的指针直接访问视频卡的视频内存:
1 | unsigned char *pVideoMemory = (unsigned char *)0xA0000000; |
许多嵌入式设备仍然使用这种技术。
在很大程度上,指针是数组(C/C++)——它们是内存中的地址,如果需要的话,可以访问数组(在"正常"的情况下)。
因为它们是一个项目的地址,所以很小:它们只占用一个地址的空间。因为它们很小,所以将它们发送到一个函数是很便宜的。然后他们允许这个函数处理实际的项目,而不是一个副本。
如果要执行动态存储分配(例如对于链接列表),则必须使用指针,因为它们是从堆中获取内存的唯一方法。
在变量上使用指针的一种方法是消除所需的重复内存。例如,如果您有一些大型复杂对象,您可以使用指针为您所做的每个引用指向该变量。使用变量,您需要为每个副本复制内存。
指针在许多数据结构中非常重要,这些数据结构的设计要求能够有效地将一个"节点"链接到另一个"节点"。您不会"选择"一个指针而不是像float这样的普通数据类型,它们只是有不同的用途。
当您需要高性能和/或紧凑的内存占用时,指针非常有用。
数组中第一个元素的地址可以分配给指针。然后,您可以直接访问底层分配的字节。不过,数组的整个要点是避免您需要这样做。
在C++中,如果要使用子类型多态性,则必须使用指针。请参阅此帖子:没有指针的C++多态性。
真的,当你想到它的时候,这是有道理的。当您使用子类型多态性时,最终,您不会提前知道将调用方法的哪个类或子类实现,因为您不知道实际的类是什么。
持有一个未知类对象的变量的想法与C++在堆栈上存储对象的默认(非指针)模式不兼容,其中直接分配的空间量对应于该类。注意:如果一个类有5个实例字段,而不是3个实例字段,则需要分配更多的空间。
请注意,如果使用"&;"通过引用传递参数,则在后台仍会涉及间接(即指针)。"&;"只是语法上的糖分,(1)避免了使用指针语法的麻烦,(2)允许编译器更严格(例如禁止空指针)。
因为到处复制大对象会浪费时间和内存。
这是我的答案,我不想自称是专家,但我在一个我想写的图书馆里发现了一些有用的建议。在这个库中(它是一个带有OpenGL:-)的图形API),您可以创建一个三角形,其中包含传递给它们的顶点对象。draw方法采用这些三角形对象,而且……基于我创建的顶点对象绘制它们。好吧,没关系。
但是,如果我改变一个顶点坐标呢?是否在Vertex类中使用movex()移动它或进行其他操作?好吧,好吧,现在我必须更新三角形,添加更多的方法和性能被浪费了,因为每次顶点移动时我都必须更新三角形。还是没什么大不了的,但没那么好。
现在,如果我有一个有无数顶点和无数三角形的网格,网格是旋转和移动的,等等。我将不得不更新每个使用这些顶点的三角形,以及场景中的每个三角形,因为我不知道哪些三角形使用哪些顶点。这是非常计算机密集,如果我有几个网格的景观,哦,上帝!我遇到了麻烦,因为我几乎每帧都更新一个三角形,因为这些顶点随时都在变化!
使用指针,不必更新三角形。
如果每个三角形类有三个*顶点对象,那么我不仅可以节省空间,因为数不清的三角形没有三个本身较大的顶点对象,而且这些指针将始终指向它们要指向的顶点,无论顶点更改的频率如何。因为指针仍然指向同一个顶点,所以三角形不会改变,更新过程更容易处理。如果我把你搞糊涂了,我不会怀疑,我不会假装是专家,只是把我的两分钱投入讨论。
这里描述了对C语言指针的需求
其基本思想是,通过操作数据的内存位置,可以消除语言中的许多限制(如使用数组、字符串和修改函数中的多个变量)。为了克服这些限制,C中引入了指针。
此外,还可以看到,使用指针有时可以更快地运行代码并节省内存(在向函数传递诸如结构之类的大数据类型的情况下)。在传递之前复制这样的数据类型需要花费时间并消耗内存)。这也是程序员喜欢大数据类型指针的另一个原因。
附:请参考所提供的链接,以获得有关示例代码的详细说明。
在爪哇和C++中,所有对象引用都是指针,C++中的东西是对指针指针的位置有更多的控制。记住,伟大的力量是伟大的责任。
- 在某些情况下,函数指针需要使用共享库(.dll或.so)中的函数。这包括跨语言执行任务,通常会提供一个dll接口。
- 生成编译器
- 做科学的计算器,你在哪里有一个函数指针的数组、向量或字符串映射?
- 尝试直接修改视频内存-制作自己的图形包
- 制作一个API!
- 数据结构-您正在生成的特殊树的节点链接指针
有很多原因可以用来指点。如果您想保持跨语言的兼容性,那么在DLL中,C名称管理尤其重要。
关于第二个问题,通常在编程时不需要使用指针,但是有一个例外,那就是当您创建一个公共API时。
C++通常构造用来取代指针的问题非常依赖于您使用的工具集,当您拥有源代码所需的所有控件时,这是很好的,但是如果您使用VisualStudio 2008编译静态库并尝试在VisualStudio 2010中使用它,则将获得大量链接器E。错误是因为新项目链接了一个新版本的STL,该版本不向后兼容。如果您编译一个dll并提供一个导入库,人们在另一个工具集中使用它,那么事情会变得更糟,因为在这种情况下,您的程序迟早会因为没有明显原因而崩溃。
因此,为了将大型数据集从一个库移动到另一个库,您可以考虑将指向数组的指针提供给一个函数,如果您不想强制其他人使用您使用的相同工具,那么该函数应该复制数据。好的方面是,它甚至不必是C样式的数组,例如,您可以使用std::vector并通过给出第一个元素&vector[0]的地址来给出指针,并使用std::vector在内部管理数组。
在C++中使用指针的另一个很好的原因与库有关,考虑在程序运行时有一个DLL不能加载,因此如果使用导入库,则依赖项不满足并且程序崩溃。例如,当您在应用程序旁边的DLL中提供公共API,并且希望从其他应用程序访问它时,就是这种情况。在这种情况下,为了使用API,您需要从DLL的位置加载它(通常它在注册表项中),然后您需要使用函数指针来调用DLL中的函数。有时,制作API的人会给您一个.h文件,该文件包含帮助程序函数,可以自动执行此过程,并为您提供所需的所有函数指针,但如果没有,则可以在Windows上使用LoadLibrary和GetProcAddress,在Unix上使用dlopen和dlsym来获取它们(考虑到您知道fu的完整签名)结论)。
使用指针是因为动态堆区域中数据的分配和检索性能提高了。