为什么要使用指针?[关闭]


356

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

基本上我有三个问题:

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

5
之前在这个问题上讨论过希望对您有所帮助!
Doug T.

4
有关书籍列表,请参见stackoverflow.com/questions/388242/…。Java之后,我发现Accelerated C ++非常有用。
亚巴

163
我们使用指针,是因为给某人一个住所的地址比给所有人一个住所的副本要容易。
Rishi Dua 2013年

14
@RishiDua这是我遇到过的指针的唯一最好的解释。谢谢你,它增进了我的理解:)
乔什(Josh)

2
当您希望返回多个值/对象时,也可以使用指针。
Gaurav 2015年

Answers:


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

简短的答案是:不。;-)指针将用于不能使用其他任何东西的地方。这是因为缺少适当的功能,缺少数据类型或纯粹是出于性能考虑。以下更多...

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

简短的答案是:无法使用其他任何地方。在C语言中,您不支持复杂的数据类型,例如字符串。也没有办法将变量“通过引用”传递给函数。那是您必须使用指针的地方。您也可以让他们指向几乎所有内容,链接列表,结构成员等。但是,我们不要在这里讨论。

  • 如何将指针与数组一起使用?

毫不费力,却很混乱。;-)如果我们讨论诸如int和char这样的简单数据类型,则数组和指针之间几乎没有什么区别。这些声明非常相似(但不相同-例如,sizeof将返回不同的值):

char* a = "Hello";
char a[] = "Hello";

您可以像这样到达数组中的任何元素

printf("Second char is: %c", a[1]);

由于数组以元素0开头,因此为索引1。

或者您也可以这样做

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数组/指针分配字符串值,则必须在为其赋值之前为其分配足够的内存量。使用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()可能会为您提供不同的地址,因为不能保证第二次分配内存是在与第一个相同的空间中执行的。


8
您的示例中有120个错误。它使用的是%c而不是%s,因此没有错误;它仅显示小写字母x。而且,您随后声称指针是int类型的说法是错误的,并且非常糟糕的建议是给没有经验的C程序员。
R .. GitHub停止帮助ICE,2010年

13
-1您起步不错,但第一个示例是错误的。不,不一样。第一种情况a是指针,第二种情况a是数组。我已经提到了吗?这是不一样的!检查一下自己:比较sizeof(a),尝试为数组分配一个新地址。它不会工作。
sellibitze 2010年

42
char* a = "Hello";而且char a[] = "Hello";不一样,它们是完全不同的。一个声明一个指针,另一个声明一个数组。试试看a sizeof,您会发现差异。
PatrickSchlüter2010年

12
“执行此printf语句没有错或违法”。这是完全错误的。该printf语句具有未定义的行为。在许多实现中,它将导致访问冲突。指针实际上不是int类型,它们实际上是指针(别无其他)。
Mankarse 2011年

19
-1,您在这里有很多错误的事实,我会毫不夸张地说,您不应该获得大量的赞誉或接受的答案状态。
asveikau 2012年

50

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

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

16
提及引用可能更安全,因为您不能传递空引用。
SpoonMeiser

26
是的,这可能是使用引用的最大优势。感谢您指出。
无双

2
您当然可以传递空引用。它不像传递空指针那样简单。
2013年

1
同意@ n0rd。如果您认为您不能传递空引用,那就错了。传递悬挂引用比空引用更容易,但是最终您可以轻松完成。所引用的参考文献不是保护工程师避免用脚射击的灵丹妙药。现场观看
WhozCraig 2013年

2
@ n0rd:这样做显然是未定义的行为。
Daniel Kamil Kozar 2013年

42
  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。


because it takes the value for each character and increments it by one。是用ascii表示还是如何?
Eduardo S.

28

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

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


1
谢谢!最后,对使用堆栈的好处进行实用说明!指向数组中位置的指针是否还能提高访问值@和相对于指针的性能?

这可能是我读过的最好的解释。人们有一种“过度复杂化”的方式:)
Detilium

23

我也尝试回答这个问题。

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

任何事情之前,一个地方,你将通常必须使用指针很多是当你处理与嵌入式硬件。也许您需要切换数字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 ++几乎可以做任何事情。无意识地做起来比较困难,您会惊讶于无意中有多少个错误:)


您可以为内存映射的IO编写漂亮的包装器类,基本上可以避免使用指针。
einpoklum

13

这是为什么C的许多功能有意义的略有不同但有深刻见解的观点: http //steve.yegge.googlepages.com/tour-de-babel#C

基本上,标准的CPU体系结构是冯·诺依曼体系结构,在这样的机器上能够引用数据项在内存中的位置并对其进行算术运算非常有用。如果您知道汇编语言的任何变体,您将快速了解它在底层的重要性。

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


12

指针的一种用法(我不会提到别人的文章中已经提到的内容)是访问您尚未分配的内存。这对PC编程没什么用,但在嵌入式编程中用于访问内存映射的硬件设备。

在DOS的早期,您曾经能够通过声明指向以下内容的指针来直接访问视频卡的视频内存:

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

许多嵌入式设备仍使用此技术。


不是使用指针的理由-跨度类似的结构-也会保留长度-更合适。这些天过去了 gsl::span,很快就会过去了std::span
einpoklum

10

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

由于它们是项目的地址,因此它们很小:它们仅占用地址的空间。由于它们很小,因此将它们发送给函数很便宜。然后,它们允许该功能处理实际项目,而不是副本。

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


8
我认为说指针是数组是一种误导。实际上,数组名称是指向数组第一个元素的const指针。仅仅因为您可以访问任意点,就好像它的数组并不意味着它是……您可能会遇到访问冲突:)
rmeador

2
指针不是数组。阅读comp.lang.c FAQ的第6节。
基思·汤普森

指针实际上不是数组。另外,在原始数组中也没有太多用处-当然在C ++ 11和之后也没有std::array
einpoklum

@einpoklum-指针与数组的距离足够接近,以等效于引用操作并遍历数组:) ....另-C ++ 11距发布于2008
Warren

@warren:指针既不是数组也不是数组,它们只是衰减成数组。在教学上告诉别人他们相似是不合适的。同样,您当然可以引用数组,这些数组与指针的类型不同,并维护大小信息。看到这里
einpoklum

9

指针在许多数据结构中很重要,这些数据结构的设计要求能够有效地将一个“节点”链接或链接到另一个节点。您不会“选择”一个指针来表示诸如float之类的普通数据类型,它们只是具有不同的用途。

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

数组中第一个元素的地址可以分配给一个指针。然后,您可以直接访问基础分配的字节。不过,数组的重点是避免您需要执行此操作。


9

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


这就是使用引用(或最多-引用包装)而不是指针的原因。
einpoklum

6

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

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

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


请注意,如果使用“&”通过引用传递参数,则在幕后仍将涉及间接寻址(即指针)。“&”只是语法糖,(1)避免了使用指针语法的麻烦,(2)允许编译器变得更加严格(例如禁止空指针)。


不,你不具备使用指针-您可以使用引用。当您编写“指针在幕后参与”时,这毫无意义。goto指令也可在后台使用-在目标机器的指令中。我们仍然不声称要使用它们。
einpoklum

@ einpoklum-reinstateMonica如果您要操作一组对象,则依次将每个元素分配给一个临时变量,然后对该变量调用多态方法,那么是的,您需要一个指针,因为您无法重新绑定引用。
Sildoreth

如果您具有对派生类的基类引用,并在该引用上调用虚拟方法,则将调用派生类的重写。我错了吗?
einpoklum

@ einpoklum-reinstateMonica正确。但是您不能更改引用的对象。因此,如果要遍历这些对象的列表/集合/数组,则引用变量将不起作用。
Sildoreth

5

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


4
指针如何帮助呢?我想从Java或.NET来不知道栈和堆之间的差别的人,所以这个答案是很没用..
垫弗雷迪克森

您可以通过引用传递。这样可以防止复制。
马丁·约克

1
@MatsFredriksson-无需传递(复制)大型数据结构,然后再次复制回结果,只需指向它在RAM中的位置,然后直接对其进行修改即可。
约翰·U

因此,请勿复制大对象。没有人说您需要为此的指针。
einpoklum

5

这是我的助手,我不会答应成为专家,但是我发现指针在我要编写的其中一个库中很棒。在这个库中(这是带有OpenGL的图形API :-),您可以创建一个三角形,并向其中传递顶点对象。draw方法采用这些三角形对象,并根据我创建的顶点对象来绘制它们。好吧,没关系。

但是,如果我更改顶点坐标怎么办?用顶点类中的moveX()移动它还是其他东西?好吧,好的,现在我必须更新三角形,添加更多的方法,并且浪费了性能,因为每次顶点移动时我都必须更新三角形。仍然没什么大不了的,但这不是那么好。

现在,如果我有一个包含大量顶点和大量三角形的网格,并且该网格正在旋转,移动等等。我将必须更新使用这些顶点的每个三角形,可能还要更新场景中的每个三角形,因为我不知道哪个三角形使用哪个顶点。那是大量的计算机密集型工作,如果我在风景之上有多个网格物体,天哪!我有麻烦了,因为即时更新几乎每个帧的每个三角形,因为这些顶点随时都在变化!

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

如果每个三角形类有三个* Vertex对象,我不仅节省了空间,因为无数三角形没有三个本身很大的顶点对象,而且无论这些指针如何,它们始终将指向它们要指向的顶点顶点多久更改一次。由于指针仍指向相同的顶点,因此三角形不会改变,并且更新过程更易于处理。如果我对您感到困惑,我不会怀疑,我不会假装自己是专家,只是将我的两分钱投入到讨论中。


4

描述了对使用C语言的指针的需求 这里了

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

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

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


问题是关于C ++的,这个答案是关于C的
。– einpoklum

不,问题被标记为c AND c ++。也许c标签无关紧要,但是从一开始它就在这里。
让·弗朗索瓦·法布尔

3

在Java和C#中,所有对象引用都是指针,而c ++的作用是您可以对指针指向的位置进行更多控制。记住,强大的力量伴随着重大的责任。


1

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

人们通常用来替换指针的C ++构造问题在很大程度上取决于您使用的工具集,当您拥有对源代码的所有控制权时,这很好,但是例如,如果使用Visual Studio 2008编译静态库,并尝试在Visual Studio 2010中使用它,则将出现大量链接器错误,因为新项目是与较新版本的STL链接的,该STL不能向后兼容。如果您编译DLL并提供人们在其他工具集中使用的导入库,事情甚至会变得更糟,因为在这种情况下,您的程序迟早会由于没有明显的原因而崩溃。

因此,出于将大型数据集从一个库移动到另一个库的目的,如果您不想强迫其他人使用与您使用的相同工具,则可以考虑提供一个指向要复制数据的函数的数组的指针。这样做的好处在于,它甚至不必是C样式的数组,您可以使用std :: vector并通过给出第一个元素&vector [0]的地址来提供指针,然后使用std :: vector内部管理数组。

在C ++中再次使用指针的另一个很好的原因与库有关,请考虑让程序运行时无法加载dll,因此,如果您使用导入库,则无法满足依赖关系并且程序崩溃。例如,当您在应用程序旁边的dll中提供了一个公共api并希望从其他应用程序访问它时,就是这种情况。在这种情况下,为了使用API​​,您需要从其位置加载dll(通常是在注册表项中),然后需要使用函数指针才能在DLL中调用函数。有时,制作API的人会很友好,可以为您提供一个.h文件,其中包含帮助程序功能以自动执行此过程,并为您提供所需的所有功能指针,


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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.