为什么使用指针?

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

基本上我有三个问题:

  1. 为什么使用指针超过正常变量?
  2. 何时何地应该使用指针?
  3. 你如何使用指针与数组?

  • 为什么使用指针超过正常变量?

简短的回答是:不要。 ;-)指针将被用于你不能使用任何东西的地方。 这要么是因为缺乏适当的功能,缺少数据类型或纯粹的性能。 更多下面…

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

这里简短的回答是:在哪里你不能使用其他的东西。 在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'指向的内存地址的内容加上一个(在上面的例子中),我们不必把*前面:

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

但是,这不会仅仅打印第二个字符,而是在下一个存储器地址中的所有字符,直到找到空字符(\ 0)。 这是事情开始变得危险的地方。 如果您不小心尝试使用%s格式化程序打印一个类型为integer的变量而不是字符指针?

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

这将打印内存地址120上找到的内容,并继续打印直到找到空字符。 执行这个printf语句是错误的和非法的,但它可能会起作用,因为在许多环境中指针实际上是int类型的。 想象一下,如果你使用sprintf()而不是使用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] = '\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()可能会给你不同的地址,因为不能保证第二次分配的内存在第一次分配的空间中执行。

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

在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; } 
  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。

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

我读过的指针和指针算法的最佳解释是在K&R的The C Programming Language中 。 开始学习C ++的好书是C ++ Primer 。

让我试着回答这个问题。

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

在别的之前,一个你通常需要使用指针的地方就是当你处理嵌入式硬件的时候 。 也许你需要切换数字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 + +技能将是。

从可维护性的角度来看,我还应该提到,当你使用指针时,你必须测试它们的有效性,当它们无效的时候处理这​​个情况,或者假设它们是有效的,并且接受当这个假设被破坏时,程序将会崩溃或者更糟。 换个角度来说, 你用指针的选择是要么在引发代码复杂性的时候,要么在代码中断的时候进行更多的维护工作,并且试图追踪一个属于指针引入的整个类错误,比如内存损坏。

所以,如果你控制所有的代码,远离指针,而是使用引用,当你可以保持它们。 这将迫使你考虑你的对象的生命周期,并最终会让你的代码更容易理解。

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

那么我是说不可能创建一个无效的参考? 不,它完全可能,因为C ++可以让你做任何事情。 只是无意间做更难,你会惊讶于有多少错误是无意的:)

这里有一个略有不同,但深刻的考虑为什么C的许多功能是有道理的: http : //steve.yegge.googlepages.com/tour-de-babel#C

基本上,标准的CPU体系结构是冯·诺依曼体系结构,能够在内存中引用数据项的位置,并在这样的机器上进行算术运算是非常有用的。 如果您知道汇编语言的任何变体,您将很快看到这是低级别的关键。

C ++使得指针有点混乱,因为它有时会为你管理它们,并以“引用”的形式隐藏它们的效果。 如果直接使用C语言,那么对指针的需求会更加明显:没有其他的方法可以通过引用来调用,这是存储字符串的最好方法,也是迭代数组的最好方法。

指针的使用(我不会提到已经在其他人的帖子中提到过的东西)就是访问你没有分配的内存。 这对PC编程没有多大用处,但是它被用于嵌入式编程来访问内存映射的硬件设备。

在过去的DOS时代,你可以直接通过声明一个指针来访问视频卡的视频内存:

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

许多嵌入式设备仍然使用这种技术。

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

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

如果你想做动态存储分配(比如链接列表),你必须使用指针,因为它们是从堆中获取内存的唯一方法。

指针在许多数据结构中很重要,其设计要求能够有效地链接或链接一个“节点”。 你不会“选择”一个指针,而是说像float这样的普通数据类型,它们只是有不同的目的。

指针在需要高性能和/或紧凑内存占用的地方非常有用。

数组中第一个元素的地址可以分配给一个指针。 这就允许你直接访问底层分配的字节。 数组的全部意义在于避免你需要这样做。

一种使用变量指针的方法是消除所需的重复内存。 例如,如果你有一些大的复杂对象,你可以使用一个指针指向你所做的每个引用的变量。 用一个变量,你需要复制每个副本的内存。

因为在整个地方复制大对象会浪费时间和内存。

在C ++中,如果要使用子类型多态 ,则必须使用用户指针。 看到这个帖子: C + +多态无指针 。

真的,当你想到这件事时,这是有道理的。 当你使用子类型多态时,最终你不会提前知道调用哪个类或子类的实现,因为你不知道实际的类是什么。

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


请注意,如果您使用'&'来通过引用传递参数,则间接(即指针)仍然涉及幕后。 '&'只是语法糖,(1)节省了使用指针语法的麻烦,(2)允许编译器更严格(如禁止空指针)。

这里是我的一个赞助人,我不会成为一名专家,但是我已经在我想写的一个图书馆里发现了一个很棒的指针。 在这个库(这是一个图形API与OpenGL :-)),你可以创建一个三角形顶点对象传递给他们。 绘制方法需要这些三角形对象,并根据我创建的顶点对象绘制它们。 那好吧。

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

现在,如果我有一个网格与吨的顶点和吨的三角形,网格旋转,移动,等等。 我将不得不更新每个使用这些顶点的三角形,也许更新场景中的每个三角形,因为我不知道哪个顶点使用哪个顶点。 这是巨大的电脑密集型,如果我有几个网格在风景的顶部,天啊! 我遇到了麻烦,因为我几乎每一帧都更新每个三角形,因为这些顶点在时间上正在改变!

用指针,你不必更新三角形。

如果每个三角形类有三个顶点对象,那么不仅我节省了空间,因为十亿个三角形没有三个顶点对象,而且这些指针总是指向它们所指向的顶点,无论顶点变化的频率。 由于指针仍然指向同一个顶点,三角形不会改变,更新过程更容易处理。 如果我迷惑了你,我不会怀疑它,我不会冒充专家,只是把我的两分钱投入讨论。

在java和C#中,所有的对象引用都是指针,c ++的事情就是你可以更多地控制指针指向的地方。 记住伟大的力量来自宏伟的责任。

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

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

此外,还可以看到使用指针有时可以更快地运行代码并节省内存(在将大数据类型(如结构)传递给函数的情况下,在传递之前复制这些数据类型需要时间和会消耗内存)。 这是程序员更喜欢大数据类型的指针的另一个原因。

PS:请参考提供的链接以获取详细的解释和示例代码。

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

人们通常用来代替指针的C ++结构的问题非常依赖于你使用的工具集,当你需要通过源代码进行所有的控制时,这是很好的,但是如果你编译一个静态库,例如visual studio 2008并尝试在Visual Studio 2010中使用它,你会得到大量的链接器错误,因为新的项目链接到一个新版本的STL不向下兼容。 如果你编译一个DLL并给出一个人们在不同的工具集中使用的导入库的话,情况就会变得更糟糕,因为在这种情况下,你的程序迟早不会有明显的原因。

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

在C ++中使用指针的另一个好的理由又与库有关,考虑在程序运行时有一个dll不能被加载,所以如果你使用一个导入库,那么这个依赖就不会被满足,程序就会崩溃。 例如,当你在你的应用程序旁边的一个dll中提供一个公共的API,并且你想从其他应用程序访问它时,就是这种情况。 在这种情况下,为了使用API​​,您需要从其位置(通常位于注册表项)中加载DLL,然后您需要使用函数指针来调用DLL中的函数。 有时候,制作API的人可以给你一个.h文件,它包含帮助函数来自动完成这个过程,并给你所有你需要的函数指针,但是如果没有,你可以在窗口上使用LoadLibrary和GetProcAddress,而dlopen和dlsym在unix上得到它们(考虑到你知道函数的全部签名)。

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

指针有很多原因。 如果你想保持跨语言的兼容性,尤其是在C ++中调用C名是非常重要的。

使用指针是因为动态堆区的数据分配和复制的性能增加。