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

基本上我有三个问题:

为什么使用指针而不是普通变量? 我应该在何时何地使用指针? 如何在数组中使用指针?


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

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


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

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

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


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


Pointers allow you to refer to the same space in memory from multiple locations. This means that you can update memory in one location and the change can be seen from another location in your program. You will also save space by being able to share components in your data structures. You should use pointers any place where you need to obtain and pass around the address to a specific spot in memory. You can also use pointers to navigate arrays: An array is a block of contiguous memory that has been allocated with a specific type. The name of the array contains the value of the starting spot of the array. When you add 1, that takes you to the second spot. This allows you to write loops that increment a pointer that slides down the array without having an explicit counter for use in accessing the array.

下面是一个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的许多特性是有意义的:http://steve.yegge.googlepages.com/tour-de-babel#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);

This would print whatever is found on memory address 120 and go on printing until a null character was found. It is wrong and illegal to perform this printf statement, but it would probably work anyway, since a pointer actually is of the type int in many environments. Imagine the problems you might cause if you were to use sprintf() instead and assign this way too long "char array" to another variable, that only got a certain limited space allocated. You would most likely end up writing over something else in the memory and cause your program to crash (if you are lucky).

哦,如果你在声明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() ?好吧,现在我必须更新三角形,添加更多的方法和性能是浪费的,因为我必须更新三角形每次顶点移动。虽然不是什么大事,但也没那么好。

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

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

If I had three *Vertex objects per triangle class, not only am i saving room because a zillion triangles don't have three vertex objects which are large themselves, but also these pointers will always point to the Vertices they are meant to, no matter how often the vertices change. Since the pointers still point to the same vertex, the triangles don't change, and the update process is easier to handle. If I confused you, I wouldn't doubt it, I don't pretend to be an expert, just throwing my two cents into the discussion.


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

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

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

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


在c++中,如果你想使用子类型多态性,你必须使用指针。请参阅这篇文章:没有指针的c++多态性。

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

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

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


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

The problem with C++ constructs that people generally use to replace pointers are very dependent on the toolset that you use which is fine when you have all the control you need over the source code, however if you compile a static library with visual studio 2008 for instance and try to use it in a visual studio 2010 you will get a ton of linker errors because the new project is linked with a newer version of STL which is not backwards compatible. Things get even nastier if you compile a DLL and give an import library that people use in a different toolset because in that case your program will crash sooner or later for no apparent reason.

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

Another good reason to use pointers in C++ again relates to libraries, consider having a dll that cannot be loaded when your program runs, so if you use an import library then the dependency isn't satisfied and the program crashes. This is the case for instance when you give a public api in a dll alongside your application and you want to access it from other applications. In this case in order to use the API you need to load the dll from its' location (usually it's in a registry key) and then you need to use a function pointer to be able to call functions inside the DLL. Sometimes the people that make the API are nice enough to give you a .h file that contain helper functions to automate this process and give you all the function pointers that you need, but if not you can use LoadLibrary and GetProcAddress on windows and dlopen and dlsym on unix to get them (considering that you know the entire signature of the function).


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

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

首先,在处理嵌入式硬件时,通常必须大量使用指针。也许你需要切换数字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++技能就越可移植。

From a maintainability point of view, I should also mention that when you do use pointers, you either have to test for their validity and handle the case when they're not valid, or, just assume they are valid and accept the fact that your program will crash or worse WHEN that assumption is broken. Put another way, your choice with pointers is to either introduce code complexity or more maintenance effort when something breaks and you're trying to track down a bug that belongs to a whole class of errors that pointers introduce, like memory corruption.

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

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

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