426

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

基本上我有三个问题:

  1. 为什么在普通变量上使用指针?
  2. 我应该何时何地使用指针?
  3. 如何在数组中使用指针?
4

17 回答 17

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

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

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

简短的回答是:你不能使用其他任何东西。在 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 格式化程序打印整数类型的变量而不是 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() 可能会为您提供不同的地址,因为不能保证第二次内存分配与第一次在同一空间中执行。

于 2008-10-02T17:25:54.580 回答
55

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

在 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;
}
于 2008-10-02T15:45:08.550 回答
45
  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

因为它获取每个字符的值并将其加一。

于 2008-10-02T15:24:38.787 回答
31

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

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

于 2008-10-02T15:22:28.667 回答
25

让我也试着回答一下。

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

首先,您通常需要大量使用指针的地方是处理嵌入式硬件时。也许您需要切换数字 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++ 几乎可以让你做任何事情。无意中更难做到,你会惊讶于有多少错误是无意的:)

于 2013-05-30T21:19:57.777 回答
13

以下是关于 C 的许多功能为何有意义的稍微不同但有见地的看法:http: //steve.yegge.googlepages.com/tour-de-babel#C

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

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

于 2008-10-02T15:52:57.610 回答
12

One use of pointers (I won't mention things already covered in other people's posts) is to access memory that you haven't allocated. This isn't useful much for PC programming, but it's used in embedded programming to access memory mapped hardware devices.

Back in the old days of DOS, you used to be able to access the video card's video memory directly by declaring a pointer to:

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

Many embedded devices still use this technique.

于 2008-10-02T16:23:42.390 回答
10

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

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

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

于 2008-10-02T15:22:39.620 回答
9

指针在许多数据结构中很重要,这些数据结构的设计需要能够有效地将一个“节点”链接或链接到另一个“节点”。您不会“选择”指针而不是像浮点这样的普通数据类型,它们只是有不同的用途。

当您需要高性能和/或紧凑的内存占用时,指针很有用。

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

于 2008-10-02T15:47:01.247 回答
9

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

于 2012-04-30T06:41:01.287 回答
7

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

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

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


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

于 2013-05-30T19:06:02.003 回答
6

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

于 2008-10-02T15:22:52.847 回答
6

这是我的回答者,我不保证自己会成为专家,但我在我正在尝试编写的一个库中发现了一些很棒的指针。在这个库(它是一个带有 OpenGL 的图形 API :-))中,您可以创建一个三角形,并将顶点对象传递给它们。draw 方法采用这些三角形对象,并且......根据我创建的顶点对象绘制它们。嗯,没关系。

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

现在,如果我有一个包含大量顶点和大量三角形的网格,并且网格正在旋转和移动等等。我必须更新使用这些顶点的每个三角形,可能还有场景中的每个三角形,因为我不知道哪些三角形使用了哪些顶点。那是计算机密集型的,如果我在景观顶部有几个网格,天哪!我遇到了麻烦,因为我几乎在每一帧都更新了每个三角形,因为这些顶点一直在变化!

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

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

于 2012-06-18T03:28:24.020 回答
6

此处描述了 C 语言中对指针的需求

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

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

PS:请参阅提供的链接以获取示例代码的详细说明。

于 2013-02-17T01:08:04.073 回答
3

在 java 和 C# 中,所有对象引用都是指针,而 c++ 的问题是您可以更好地控制指针指向的位置。记住,权力越大,责任越大。

于 2008-10-02T18:02:00.577 回答
2
  • 在某些情况下,需要函数指针才能使用共享库(.DLL 或 .so)中的函数。这包括跨语言执行任务,通常会提供 DLL 接口。
  • 制作编译器
  • 制作科学计算器,您在哪里有函数指针的数组或向量或字符串映射?
  • 尝试直接修改显存——制作自己的图形包
  • 制作 API!
  • 数据结构 - 您正在制作的特殊树的节点链接指针

指针的原因有很多。如果您想保持跨语言兼容性,那么在 DLL 中进行 C 名称修饰尤其重要。

于 2012-04-30T07:07:45.787 回答
2

关于您的第二个问题,通常您在编程时不需要使用指针,但是有一个例外,那就是您制作公共 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 文件,其中包含帮助函数以自动化此过程并为您提供所需的所有函数指针,

于 2013-05-30T21:18:55.740 回答