【问题标题】:Is negative index for operator[] well defined?operator[] 的负索引是否定义明确?
【发布时间】:2015-11-11 04:55:36
【问题描述】:

我知道这将是非常糟糕的编码风格,但以下代码在我的机器上完美运行。但是行为是否明确定义?便携吗?

int main()
{
    int *p = new int[3];
    int *q = &p[2];
    q[-1] = 41;
    std::cout << p[1];
    delete[] p;
}

【问题讨论】:

  • 只是记忆中的餐巾纸回答,但我相信 foo[x] 在标准中被定义为与 *(foo + x) 完全等价,其中 x 是整数类型,而 foo 是指针,所以我认为在后面的任何地方都是合法的。
  • 不要在 C++ 中这样做。如果我看到thing[-1],我会想“哦,我可以很好地阅读最后一个元素”——比如 Python,而不是“它是指之前的东西”
  • @AlecTeal 这真的不是禁止这样做的好理由。如果我们禁止每一个误导性地类似于另一种流行语言的形式的表达,那么就什么都没有了。我们甚至无法编写 IF 语句,因为它们看起来很像 Fortan II 的算术 IF。
  • @SteveCox 你是对的。未来的程序员将使用它。
  • 你是否重载了一个操作符?

标签: c++ operator-overloading language-lawyer


【解决方案1】:

这在句法和语义上都有很好的定义。

[expr.sub]/1 (N3337):

表达式E1[E2]*((E1)+(E2)) 相同(根据定义)。

所以你的表达式和*(q-1) = 41;一样,所以在语法上是有效的。

[expr.add]/5 (N3337)

当一个整数类型的表达式被添加到指针或从指针中减去时,结果具有指针操作数的类型。如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向与原始元素偏移的元素,使得结果和原始数组元素的下标之差等于积分表达式。

由于q 指向一个数组对象的元素,该元素的大小对您的整数表达式来说是有效的,因此它在语义上是有效的。

【讨论】:

  • @immibis 我决定不提这个问题,因为这个问题是关于负指数的有效性;间接是独立的,尽管它显然与第二个引用中定义的结果有关。如果添加有关间接的信息会对 OP 有所帮助,我很乐意添加它,但我认为没有必要。
【解决方案2】:

是的,它定义明确。内置operator[] 是根据指针算法定义的。这个:

p[N]

其中p 是指针,N 是整数,等价于:

*(p + N)

一个有趣的结果是:

N[p]

也是等价的,因为加法是可交换的。

【讨论】:

  • 后面的例子假设使用了内置的下标操作符。
  • @black:前者也是如此。我在帖子的顶部提到了这一点。
  • 不确定我是否称它为“up”-shot。 ;-p N[p] 有用吗?
【解决方案3】:

根据 C++ 标准(5.2.1 下标)

1 后缀表达式后跟方括号中的表达式是 后缀表达式。其中一个表达式应具有类型 “T 数组”或“指向 T 的指针”,另一个应为无范围的 枚举或整数类型。结果是“T”类型。方式 “T”应是完全定义的对象类型。65 表达式 E1[E2] 与 *((E1)+(E2)) 相同(根据定义)...

因此,只要表达式 *((E1)+(E2)) 的结果格式正确,您就可以使用任何整数类型,包括类型 int 和相应的负值。

考虑到对于用户定义的类型,您可以使用大括号初始化列表作为索引。例如

#include <iostream>

class Point
{
public:    
    Point( int x, int y ) : x( x ), y( y ) {}
    int x, y;
};

class Circle
{
public:    
    Circle( unsigned int r ) : r( r ) {}

    Circle & operator []( Point p )
    {
        std::cout << "Drawing a circle at ( " << p.x << ", " << p.y << " )\n";
        return *this;
    }

    unsigned int r;
};        

int main()
{
    Circle circle( 10 );

    circle[ { 0, 0 } ];
}    

程序输出是

Drawing a circle at ( 0, 0 )

【讨论】:

    【解决方案4】:

    索引运算符x[idx] 等于(*(x +idx)),是的idx 可能是负数。但是,您必须确保取消引用的指针指向一个有效的内存地址。

    请注意,我们可以用多种方式重写它(就像代数一样)。

    x[idx] = (*(x +idx)) = (*(idx + x)) = idx[x]
    

    【讨论】:

      【解决方案5】:

      只要您不尝试取消引用p 指向的数组边界之外的指针,这绝对没问题。

      此外,您可以将指针 q 设置为数组的任何元素,此外,还可以将指针设置为数组后面的一个元素。 (但不要试图取消引用座位末端的元素。)

      别忘了在函数结束时delete[] p;

      【讨论】:

        【解决方案6】:

        它非常安全和便携。您只是使用指针算法来寻址new 运算符分配的内存。

        您的代码相当于:

        int* p = new int[3];
        int* q = p + 2;
        *(q-1) = 41;
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-11-11
          • 1970-01-01
          • 1970-01-01
          • 2014-10-25
          • 2014-11-26
          • 2023-04-03
          相关资源
          最近更新 更多