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

基本上我有三个问题:


为什么在普通变量上使用指针?
何时何地应该使用指针?
如何数组使用指针吗?


评论

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

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

我们使用指针的原因是,给某人一个住所的地址比给所有人一个住所的副本要容易。

@RishiDua这是我遇到过的指针的唯一最好的解释。谢谢你,它增加了我的理解:)

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

#1 楼


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

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


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

这里的简短答案是:无法使用其他任何东西的地方。在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指针,该怎么办?

120并继续打印,直到找到空字符。执行此printf语句是错误和非法的,但是它仍然可以工作,因为在许多环境中指针实际上是int类型的。想象一下,如果您改用sprintf()并将这种方式将太长的“字符数组”分配给另一个仅分配了一定有限空间的变量,可能会引起问题。您很可能最终会重写内存中的其他内容,并导致程序崩溃(如果幸运的话)。

哦,如果您不将字符串值分配给char数组, /指针声明它时,必须在为其赋值之前为其分配足够的内存量。使用malloc,calloc或类似方法。这是因为您只声明了数组中的一个元素/一个指向的单个内存地址。因此,这里有一些示例:

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


请注意,在执行分配的内存的free()之后,您仍然可以使用变量x,但是您不能知道里面有什么。还要注意,这两个printf()可能会为您提供不同的地址,因为不能保证第二次分配内存的空间与第一个相同。

评论


您的示例中有120个错误。它使用的是%c而不是%s,因此没有错误;它仅显示小写字母x。此外,您随后声称指针是int类型的说法是错误的,并且对于没有经验的C程序员,这是非常糟糕的建议。

–R .. GitHub停止帮助ICE
2010年7月5日,11:29

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

– sellibitze
2010-09-23 15:20



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

– Mankarse
2011年8月14日,下午3:58

-1,您在这里有很多错误的事实,我会毫不夸张地说,您不应该获得如此多的支持或接受的答案。

–asveikau
2012年6月18日在3:43



comp.lang.c FAQ的第6节很好地解释了C语言中数组与指针之间的关系。这个答案没有。

–基思·汤普森(Keith Thompson)
13年5月30日在18:56

#2 楼

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

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


评论


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

– SpoonMeiser
08年10月2日在16:36

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

–trshiv
08年10月2日在17:19

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

–n0rd
13年5月30日在20:54



同意@ n0rd。如果您认为您不能传递空引用,那就错了。传递悬挂引用比空引用更容易,但是最终您可以轻松完成。参考文献不是保护工程师免于射中脚的银弹。现场观看。

– WhozCraig
2013年9月9日18:14



@ n0rd:这样做显然是未定义的行为。

–丹尼尔·卡米尔·科扎尔(Daniel Kamil Kozar)
2013年12月20日14:04

#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。是用ascii表示还是如何?

–爱德华多·塞巴斯蒂安(Eduardo Sebastian)
6月4日3:24

#4 楼

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

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

评论


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

–user1382306
13年10月2日在4:34



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

–重金属
17年3月21日在11:50

#5 楼

我也尝试回答这个问题。

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

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

评论


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

– einpoklum
19年5月28日20:00

#6 楼

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

基本上,标准的CPU体系结构是冯·诺依曼(Von Neumann)架构,在这样的机器上,能够引用数据项在内存中的位置并对其进行算术运算非常有用。如果您知道汇编语言的任何变体,您将很快了解它在底层的重要性。形式的“参考”。如果使用直线C,则对指针的需求会更加明显:没有其他方法可以进行按引用调用,这是存储字符串的最佳方法,是遍历数组的最佳方法,等等。 />

#7 楼

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

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

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

评论


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

– einpoklum
19年5月28日在20:02

#8 楼

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

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

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

评论


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

–rmeador
08年10月2日在15:51

指针不是数组。阅读comp.lang.c常见问题解答的第6节。

–基思·汤普森(Keith Thompson)
13年5月30日在18:57

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

– einpoklum
19年5月28日在20:02

@einpoklum-指针与数组的距离足够接近,在引用操作和遍历数组时等效:) ....-C ++ 11距发布于2008年的发布还有3年的时间

–沃伦
19年5月29日在16:18

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

– einpoklum
19年5月29日在16:28

#9 楼

指针在许多数据结构中很重要,这些数据结构的设计要求能够有效地将一个“节点”链接或链接到另一个。您不会“选择”诸如float之类的普通数据类型的指针,它们只是有不同的用途。
可以将数组中第一个元素的地址分配给一个指针。然后,您可以直接访问基础分配的字节。数组的主要目的是避免您虽然需要这样做。

#10 楼

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

评论


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

– einpoklum
19年5月28日在20:03

#11 楼

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

真的,当您考虑它时,这是有道理的。最终,当您使用子类型多态时,由于您不知道实际的类是什么,所以您不提前知道将调用哪个类或子类的方法的实现。具有用于保存未知类的对象的变量与C ++在堆栈上存储对象的默认(非指针)模式不兼容,在默认模式下,分配的空间量直接对应于该类。注意:如果一个类具有5个实例字段而不是3个实例字段,则需要分配更多空间。



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

评论


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

– einpoklum
19年5月28日在20:03



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

–西尔多雷斯
19/12/12在13:57

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

– einpoklum
19/12/12在14:13

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

–西尔多雷斯
19/12/12在23:03

#12 楼

此处描述了C语言中对指针的需求。
基本思想是可以通过操纵数据的存储位置来消除语言的许多限制(例如使用数组,字符串和修改函数中的多个变量)。为了克服这些限制,在C语言中引入了指针。
还可以看到,使用指针可以在传递大数据类型(例如具有多个字段的结构)的情况下更快地运行代码并节省内存)转换为功能。在传递之前复制此类数据类型会花费时间并消耗内存。这是程序员偏爱大数据类型的指针的另一个原因。 PS:请参考提供的链接以获取示例代码的详细说明。

评论


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

– einpoklum
19年5月28日在20:06

不,问题被标记为c AND c ++。也许c标签无关紧要,但是从一开始就在这里。

–Jean-FrançoisFabre♦
19年5月28日在21:08



#13 楼

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

评论


指针如何帮助呢?我认为来自Java或.Net的人不知道堆栈和堆之间的区别,因此此答案非常无用。

–马特·弗雷德里克森(Matt Fredriksson)
08年10月2日在15:24

您可以通过引用传递。这样可以防止复制。

–马丁·约克
08-10-2 15:25

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

– John U
2012年6月25日的13:00

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

– einpoklum
19年5月28日在20:05

#14 楼

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

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

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

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

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

#15 楼

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

#16 楼

关于第二个问题,通常不需要在编程时使用指针,但是有一个例外,那就是当您创建公共API时。用于替换指针的方法非常依赖于所使用的工具集,当您拥有对源代码的所有控制权时,这很好,但是例如,如果您使用Visual Studio 2008编译静态库并尝试在Visual Studio中使用它在Studio 2010中,您会遇到大量链接器错误,因为新项目是与不向后兼容的较新版本的STL链接的。如果编译DLL并提供人们在其他工具集中使用的导入库,事情会变得更糟,因为在这种情况下,您的程序迟早会死机,而没有明显的原因。

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

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

#17 楼


在某些情况下,需要使用函数指针才能使用共享库(.DLL或.so)中的函数。这包括跨语言执行操作(通常会提供DLL接口)。
进行编译器
进行科学计算器时,如果您具有函数指针的数组或向量或字符串映射?
尝试进行修改视频内存直接-制作自己的图形包
制作API!如果要保持跨语言兼容性,则在DLL中使用C名称修饰尤为重要。