【问题标题】:Subtracting pointers减法指针
【发布时间】:2014-10-14 16:53:01
【问题描述】:

我被要求描述这些代码行在大学作业中做了什么

int main() {
    int t1[] = {0,0,1,1,1}, t2[] = {0,0,1,1,1};
    int *p1 = t1, *p2 = t2;

    while (!*p1++ || !*p2++);
    cout << (p1-t1) << endl;
    cout << (p2-t2) << endl;
}

我的看法是,创建了 2 个 int 类型的数组并用值填充,创建了 2 个指针并指向每个数组,然后我就开始遇到麻烦了。

while (!*p1++ || !*p2++);

对我来说,这是说当0*p1 的位置移动一个位置或0*p2 的位置移动一个位置时,我真的对这个假设没有信心吗?

cout << (p1-t1) << endl;

然后我们转到cout,现在我的看法是,我从t1 的位置中减去p1 的位置,其中p1 是由while 定位的,@987654332 @ 指向数组中的第一个位置。 再说一次,我可能完全错了,我只是在学习指针,所以如果我的假设有误,请记住这一点。

【问题讨论】:

  • @Johntk: main 应该返回一个int。一些系统期望main 返回0 当且仅当程序成功完成他的工作。当main返回void时,系统会尝试读取退出状态,得到垃圾。
  • 这几行代码在说明程序员不称职,或者在制作笑话代码。如果它进入您的代码库,则表明您的代码库审查过程需要重新设计。哦,它会打印 "5\n3\n",但这不是重要的部分。
  • 我认为这项任务只是为了让我们思考和研究,而不是被告知或教我们这样编码。
  • 我认为,老师应该在课前做出免责声明,“如果你写过这样的代码,我会解雇你,但现在,我们将成为编译器和执行者。" 并要求在学生敢于寻求外部帮助时提醒它。该代码似乎有效并且没有调用 UB(如果我错了,请不要挂我)。 IMO这只是一个学术问题,试图理解黑魔法咒语。错误...语句。
  • @JamesKanze - 但即使我从不编写这样的代码,我仍然必须能够阅读它,因为其他人会编写它。当应用程序崩溃时,“我不知道这段代码做了什么”不是可接受的答案。

标签: c++ pointers


【解决方案1】:

while 循环实际上非常可怕。我在现实生活中从未见过这样的代码,并且会宣称任何在现实生活中这样做的程序员都疯了。我们需要一步一步来:

while (condition);

我们这里有一个带有空语句的 while 语句(单独的“;”是一个空语句)。评估条件,如果为真,则执行语句(它什么也不做,因为它是一个空语句),我们重新开始。换句话说,条件被反复评估,直到它为假。

condition1 || condition2

这是一个“或”语句。评估第一个条件。如果为真,则对第二个条件进行评估,结果为“真”。如果为假,则评估第二个条件,结果相应为“真”或“假”。

while (condition1 || condition2);

这将评估第一个条件。如果是真的,我们从头再来。如果它是假的,我们评估第二个条件。如果这是真的,我们从头开始。如果两者都是假的,我们退出循环。请注意,仅当第一个条件为假时才评估第二个条件。现在我们看看条件:

!*p1++
!*p2++

这与 *(p1++) == 0 和 *(p2++) == 0 相同。每个条件在评估后都会增加 p1 或 p2,无论结果如何。如果 *p1 或 *p2 为零,则每个条件都为真,否则为假。现在我们检查每次迭代会发生什么:

p1 = &t1 [0], p2 = &t2 [0]
*p1++ == 0 is true, *p2++ == 0 is never evaluated, p1 = &t1 [1], p2 = &t2 [0].
*p1++ == 0 is true, *p2++ == 0 is never evaluated, p1 = &t1 [2], p2 = &t2 [0].
*p1++ == 0 is false, *p2++ == 0 is true, p1 = &t1 [3], p2 = &t2 [1].
*p1++ == 0 is false, *p2++ == 0 is true, p1 = &t1 [4], p2 = &t2 [2].
*p1++ == 0 is false, *p2++ == 0 is false, p1 = &t1 [5], p2 = &t2 [3].

t1 与 &t1 [0] 相同。 p1 -​​ t1 == &t1 [5] - &t1 [0] == 5。 t2 与 &t2 [0] 相同。 p2 - t2 == &t2 [3] - &t2 [0] == 3。

【讨论】:

  • 谢谢,这里所有的答案都很好,但是你最后的代码块真的帮助我想象了发生了什么。
  • 我会第二次@GingerPlusPlus 评论。条件下的副作用通常不是一个好主意(尽管有一些神圣的成语使用它们);有条件的副作用是可怕的。并使用! 与零进行比较,而不是明确的。更多的混淆。
  • 除了极少数例外(例如,对于附近有不平等比较的并行结构)恕我直言,应该总是使用!var或裸var写与零的比较,尤其是var 是一个指针时。基本原理与为什么永远不应该写明确的== true== false 相同。
  • @alex.forencich 如果您的意思是应该尽可能避免使用原始指针,当然可以,但是超出数组末尾的指针非常常见且有用;随机访问迭代器通常只是对原始指针的简单包装,在这种情况下,容器的 end() 迭代器实际上与此类指针相同。
  • 这样一个例子的意义与好代码无关。这是一个很棒的调试和逻辑练习。
【解决方案2】:

您对t1t2p1p2 的评估是正确的。

while (!*p1++ || !*p2++);

我不喜欢这种编码风格,因为很容易认为程序员错误地将分号放在那里。为了表明空正文是真正的意图,应该以某种方式区分空正文(例如使用注释、放在单独的行上或使用花括号)。

只要条件为truewhile 就会进入身体。由于这是一个逻辑或表达式,!*p1++!*p2++while 循环终止之前必须是false。当*p1++*p2++ 都变为非零时,就会发生这种情况。因为逻辑或 短路(如果第一个表达式是true,则不计算第二个表达式),@987654335 的进程@ 和 p2 在每次迭代开始时执行以下操作:

iter  p1     *p1    p2     *p2    condition
----  --     ---    --     ---    ---------
 0    &t1[0]  0     &t2[0]  0     !*p1++ is true,  !*p2++ not evaluated
 1    &t1[1]  0     &t2[0]  0     !*p1++ is true,  !*p2++ not evaluated
 2    &t1[2]  1     &t2[0]  0     !*p1++ is false, !*p2++ is true
 3    &t1[3]  1     &t2[1]  0     !*p1++ is false, !*p2++ is true
 4    &t1[4]  1     &t2[2]  1     !*p1++ is false, !*p2++ is false

由于每次迭代都使用后增量p1 以值&amp;t1[5] 结束,p2 以值&amp;t2[3] 结束。 p>

同一数组中的指针减法以数组元素的数量来衡量两个指针之间的距离。大多数表达式中使用的数组名称将衰减为等于指向其第一个元素的指针的值。所以t1 衰减为&amp;t1[0]t2 衰减为&amp;t2[0]

因此:

p1 - t1 => 5
p2 - t2 => 3

【讨论】:

  • +1 表示“同一数组中的指针减法根据数组元素的数量测量两个指针之间的距离”,这在其他答案中并不清楚
【解决方案3】:

这里要注意的关键是表达式(a || b) 是如何计算的。首先,计算表达式a。如果a 返回true,则不会评估b,因为ORTrueTrue。这称为短路。

它有助于以下列方式扩充代码 -

int main(void){
    int t1[] = {0,0,1,1,1}, t2[] = {0,0,1,1,1};
    int *p1 = t1, *p2 = t2;

    cout << *p1 << " " << *p2 << endl;
    cout << p1 << " " << p2 << endl;
    while (!*p1++ || !*p2++) { 
        cout << *p1 << " " << *p2 << endl;
        cout << p1 << " " << p2 << endl;
    }   
    cout << (p1-t1) << endl;
    cout << (p2-t2) << endl;
    return 0;
}

输出:

0 0
0x7fff550709d0 0x7fff550709f0
0 0
0x7fff550709d4 0x7fff550709f0
1 0
0x7fff550709d8 0x7fff550709f0
1 0
0x7fff550709dc 0x7fff550709f4
1 1
0x7fff550709e0 0x7fff550709f8
5 // Final p1 - t1
3 // Final p2 - t2

!*p1++ 等价于(!(*(p1++))。这是后增量运算符。它递增指针但返回旧值(在递增之前)。

循环中的表达式被计算 5 次。

  1. 在第一次迭代中,p1 递增。由于*p1 的当前值(递增之前)为 0,因此为 0 的! 返回1。由于短路,不计算表达式的其余部分。因此只有p1 会增加。

  2. 同样的事情发生在下一个循环中。

现在,我们有 p1 = t1 + 2 indicesp2 = t2

  1. 在第三次迭代中,*p1 的当前值不再是 0。因此p1p2 都会递增。

  2. 同样的事情发生在第四次迭代中。

请注意,在前四次迭代中,p1p2 指向 0 - 因此左侧或右侧的 notTrue,因此 while 循环继续.

  1. 在第五次迭代中,p1 和 p2 都递增,但由于都没有指向 0 值,因此循环退出。

因此p1 增加了 5 次,p2 增加了 3 次。

总结 - p1 - t1 将包含 1 + t1t2 开头连续出现的 0 的数量 (2 + 2 + 1)。 p2 - t2 将计算为 1 + 在 t2 (2 + 1) 开头连续出现的 0 的数量。

【讨论】:

    【解决方案4】:

    第一:

    while (!*p1++ || !*p2++);
    

    这意味着当 p1 的内容是 0 时,会一直循环将 1 添加到 p1 直到它变成 non-zero。此后,虽然p2 的内容是0,但每次都将1 添加到p1p2。如果在任何时候 p1 的内容再次变为 0,逻辑会重复(我知道这很混乱)。

    基本上在while(first || second) 样式测试中,第二部分在第一部分失败时进行测试。无论测试通过还是失败,指针都会递增。

    您对(p1-t1) 的假设是正确的。该计算为您提供了 t1 和 p1 之间的 整数个数(因为它们是 int 指针)。因为t1 是数组的开头,所以计算实际上为您提供了p1 指向的数组的索引(偏移量)。

    注意#1:如果p1t1char 指针,那么减去它们会得到它们之间的字符数。如果它们是 float 指针,那么减去它们会得到 浮点数 等等...指针算术以它们指向的数据类型为单位进行加减运算。

    注意 #2: 严格来说 t1 是一个数组类型。当您在指针上下文中使用它时,它折叠 为指针。例如在指针算术中或将其分配给指针变量时。如果这让您感到困惑,请不要担心,大多数情况下它只是用作指针,因为只要上下文暗示,编译器就会自动进行转换。

    【讨论】:

    • 说 t1 是一个 int 指针是不正确且令人困惑的。它可以隐式转换为int指针,这不是一回事。
    • @Slava 我想保持解释简单,所以我会添加一个注释来给出更全面的解释..
    • 还有你的声明“保持循环,每次都在 p1 和 p2 上加 1。”也不正确,你忘了短路
    • @Slava 是的,我仍在为如何正确表达这一点而奋斗。
    • "...保持循环,每次都在 p1 和 p2 上加 1" - 不是真的。它使用p1 进行扫描,在t1 数组中寻找非零值。对于每个这样的非零值,它会与t2 中的一个零值一起跳过它。最终结果类似于“计算t2 中的前导零;跳过t1 中的非零元素数量以及t1 中的任何零”。
    【解决方案5】:

    至于问题是在控制台上打印什么,答案是 0 0 在您删除之前;在while循环结束时。

    这个循环有什么意义?

    首先你使用 OR 这意味着如果 p1 或 p2 指向的值是 0 块将被执行。因此,直到 p1 指向第三个元素 (p1-t1) 会给您在 t1 中交叉的元素数量,而 (p2-t2) 将为 0,因为 (p1-t1) 将返回 true,因此不会检查第二个条件。当 p1 指向 1 时,它将开始递增 p2 直到它指向 t2 的第三个元素并且结束。

    我相信这就是这项任务的全部内容。

    【讨论】:

      【解决方案6】:

      这种关系可以帮助你更好地理解while循环中的条件:

      arr[ i ] == * ( arr + i )
      

      在做指针减法时(如果指针属于同一类型),结果是两个元素之间的距离(以数组元素为单位)。

      假设p1p2 都是T* 类型的指针。那么,计算的值为:

      ( p2 - p1 ) == ( addr( p2 ) - addr( p1 ) ) / sizeof( T )
      

      【讨论】:

        猜你喜欢
        • 2018-04-10
        • 2019-05-24
        • 1970-01-01
        • 1970-01-01
        • 2021-07-14
        • 1970-01-01
        • 2021-12-16
        • 1970-01-01
        • 2017-01-28
        相关资源
        最近更新 更多