为什么要增加指针?


25

我最近才开始学习C ++,并且由于大多数人(根据我一直在阅读的内容),我一直在努力使用指针。

不是传统意义上的,我了解它们的含义,为什么使用它们,以及它们如何有用,但是我无法理解递增指针的使用方式,谁能提供一个解释如何递增指针的解释。有用的概念和惯用的C ++?

在我开始阅读Bjarne Stroustrup 的《A C ++之旅》一书之后,就提出了这个问题,我被推荐这本书,因为我对Java非常熟悉,Reddit的家伙告诉我这将是一本很好的“转换”书。 。


11
指针只是一个迭代器
Charles Salvia 2014年

1
它是编写计算机病毒的最喜欢的工具之一,可以读取不应该阅读的内容。这也是应用程序中最常见的漏洞之一(当将指针增加到他们应该到达的区域之后,然后对其进行读写)>请参阅HeartBleed错误。
2014年

1
@vasile这对指针不利
Cruncher

4
C ++的优点/缺点是,它允许您在调用段错误之前做更多的事情。通常,在尝试访问另一个进程的内存,系统内存或受保护的应用程序内存时会遇到段错误。系统允许在常规应用程序页面内进行任何访问,并且由程序员/编译器/语言来实施合理的限制。C ++几乎可以让您做任何您想做的事情。至于具有自己的内存管理器的openssl-事实并非如此。它只是具有默认的C ++内存访问机制。
山姆

1
@INdek:如果要访问的内存受到保护,则只会出现段错误。大多数操作系统在页面级别分配保护,因此您通常可以访问指针开始在页面上的任何内容。如果操作系统使用4K页面大小,则数据量很大。如果您的指针从堆中的某处开始,则可能是有人猜测您可以访问多少数据。
TMN 2014年

Answers:


46

当您拥有一个数组时,可以设置一个指向数组元素的指针:

int a[10];
int *p = &a[0];

这里p指向的第一个元素aa[0]。现在,您可以增加指针以指向下一个元素:

p++;

现在p指向第二个元素a[1]。您可以使用在此处访问元素*p。这不同于Java,在Java中,您必须使用整数索引变量来访问数组的元素。

在C ++中增加一个指针(该指针指向数组元素)的未定义行为


23
是的,使用C ++,您有责任避免编程错误,例如在数组范围之外进行访问。
2014年

9
不,增加指向数组元素以外的任何东西的指针是未定义的行为。但是,如果您正在执行低级且不可移植的操作,则增加指针通常只不过是访问内存中的下一个内容而已,无论发生什么情况。
Greg Hewgill

4
有些东西是数组,或者可以看作数组。字符串实际上是一个字符数组。在某些情况下,长整型被视为字节数组,尽管这很容易使您陷入麻烦。
AMADANON Inc. 2014年

6
可以告诉您类型,但是其行为在5.7加法运算符[expr.add]中进行了描述。具体来说,5.7 / 5表示,除最后一站外,其他任何地方都是UB。
没用

4
最后一段是:如果指针操作数和结果都指向同一数组对象的元素,则评估不应产生溢出;否则,行为是不确定的。因此,如果结果既不在数组中也不在末尾,那么您将得到UB。
没用

37

指针的递增是惯用的C ++,因为指针的语义反映了C ++标准库(基于Alexander Stepanov的STL)背后设计哲学的基本方面。

这里的重要概念是STL是围绕容器,算法和迭代器设计的。指针仅仅是迭代器

当然,增加或减少指针的能力可以追溯到C。许多C字符串操作算法都可以使用指针算法简单地编写。考虑以下代码:

char string1[4] = "abc";
char string2[4];
char* src = string1;
char* dest = string2;
while ((*dest++ = *src++));

此代码使用指针算法复制以空值结尾的C字符串。遇到空值时,循环会自动终止。

使用C ++,指针语义被概括为迭代器的概念。大多数标准C ++容器都提供迭代器,可以通过beginend成员函数进行访问。迭代器的行为类似于指针,因为它们可以递增,解引用,有时可以递减或高级。

要遍历一个std::string,我们会说:

std::string s = "abcdef";
std::string::iterator it = s.begin();
for (; it != s.end(); ++it) std::cout << *it;

我们增加迭代器,就像增加指向普通C字符串的指针一样。这个概念之所以强大,是因为您可以使用模板来编写适用于满足必要概念要求的任何类型的迭代器的函数。这就是STL的强大功能:

std::string s1 = "abcdef";
std::vector<char> buf;
std::copy(s1.begin(), s1.end(), std::back_inserter(buf));

此代码将字符串复制到向量中。该copy函数是一个模板,可与任何支持增量的迭代器(包括普通指针)一起使用。我们可以copy在普通的C字符串上使用相同的函数:

   const char* s1 = "abcdef";
   std::vector<char> buf;
   std::copy(s1, s1 + std::strlen(s1), std::back_inserter(buf));

我们可以copy在支持迭代器的std::mapstd::set任何自定义容器上使用。

请注意,指针是迭代器的一种特定类型:随机访问迭代器,这意味着它们支持使用+and -运算符进行递增,递减和前进。其他迭代器类型仅支持指针语义的子集:双向迭代器至少支持递增和递减;一个前向迭代器支持至少递增。(所有迭代器类型都支持解引用。)该copy函数需要一个至少支持增量的迭代器。

您可以在此处了解不同的迭代器概念。

因此,递增指针是在C数组上进行迭代或访问C数组中的元素/偏移量的一种惯用的C ++方法。


3
尽管我像第一个示例中那样使用指针,但我从未将其视为迭代器,但现在很有意义。
染料

1
“循环在遇到空值时会自动终止。” 这是一个可怕的成语。
查尔斯·伍德

9
@CharlesWood,那么我想您一定觉得C非常恐怖
Siler

7
@CharlesWood:替代方法是使用字符串的长度作为循环控制变量,这意味着遍历字符串两次(一次确定长度,然后一次复制字符)。当您在1MHz PDP-7上运行时,这真的可以累加起来。
TMN 2014年

3
@INdek:首先,C和C ++会不惜一切代价避免引入破坏性的更改-我想说,改变字符串文字的默认行为将是一个很大的修改。但最重要的是,零终止的字符串只是一个约定(很容易理解,字符串文字默认情况下为零终止,并且库函数期望它们),没有人阻止您在C中使用计数的字符串-实际上,几个C库确实使用它们(例如,参见OLE的BSTR)。
Matteo Italia 2014年

16

指针算术在C ++中是因为它在C中。指针算术在C中是因为它是汇编器中的常规用法。

在许多系统中,“增量寄存器”比“加载常数1并加到寄存器”要快。此外,许多系统允许您在一条指令中“将DWORD从寄存器B中指定的地址加载到A,然后将sizeof(DWORD)添加到B”。如今,您可能希望优化的编译器为您解决问题,但这在1973年并不是真正的选择。

这基本上是相同的原因,即不对C数组进行边界检查,并且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.