2015-08-18 5 views
40

Я знаю, что это был бы очень плохой стиль кодирования, но следующий код отлично работает на моей машине. Но хорошо ли это поведение? Портативный?Является ли отрицательный индекс для оператора [] корректным?

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

Просто на салфетке ответа из памяти, но я считаю, что Foo [х] определяется в стандарте как именно эквивалентно * (Foo + х), где x - целочисленный тип, а foo - указатель, поэтому я думаю, что это было бы законно в любом месте, чем позже. – Vality

+7

Не делайте этого на C++. Если я вижу 'вещь [-1]' я думаю, о, я могу прочитать последний элемент красиво »- как и Python, а не« это то, что до этого указывает на » –

+16

@AlecTeal, что на самом деле не является хорошей причиной для запрета это. Если бы мы отказались от каждого выражения, которое вводило в заблуждение образ формы с другого популярного языка, ничего не оставалось бы. Мы даже не сможем писать IF-инструкцию, так как они очень похожи на арифметику Fortan II IF. –

ответ

42

Это четко определено как синтаксически, так и семантически.

[expr.sub]/1 (N3337):

Выражение E1[E2] идентична (по определению), чтобы *((E1)+(E2)).

Значит, ваше выражение такое же, как *(q-1) = 41;, так что синтаксически допустимо.

[expr.add]/5 (N3337)

Когда выражение, которое имеет целочисленный тип добавляется или вычитается из указателя, результат имеет тип указателя операнда. Если операнд указателя указывает на элемент объекта массива, и массив достаточно велик, результат указывает на смещение элемента от исходного элемента, так что разность индексов результирующих и исходных элементов массива равна интегральному выражению.

С q указывает на элемент объекта массива действительного размера для вашего интегрального выражения, он семантически допустим.

+0

Как насчет '*'? – immibis

+1

@immibis Я решил оставить это, поскольку этот вопрос касается действительности отрицательного индекса; косвенность является отдельной, хотя она, очевидно, связана с результатом, который определяется во второй цитате. Если добавление информации о косвенности поможет OP, я был бы рад добавить его, но я не думаю, что это необходимо. – TartanLlama

2

Совершенно безопасно и портативно. Вы просто используете арифметику указателя для адресации памяти, выделенной оператором new.

Ваш код эквивалентен:

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

Оператор индекс x[idx] равен (*(x +idx)) да idx может быть отрицательным. Однако вы должны убедиться, что указатель с разнесением указывал на действительный адрес памяти.

Обратите внимание, что мы можем переписать его разными способами (например, с помощью алгебры).

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

Да, это четко определено. Встроенный operator[] определяется в терминах арифметики указателя. Это:

p[N] 

где p является указателем и N является целым числом, эквивалентно следующему:

*(p + N) 

Интересным Результатом этого является то, что это:

N[p] 

также эквивалентно, поскольку сложение является коммутативным.

+0

Последний пример предполагает использование встроенного оператора индекса. – edmz

+3

@black: Так же и первый. И я упомянул об этом в верхней части моего сообщения. –

+0

Не уверен, что я бы назвал это «вверх» -выбранным. -p Действительно ли N [p] когда-либо полезен? – Macke

3

Это абсолютно нормально, если вы не пытаетесь разыменовать указатель за пределами массива, на который указывает p.

Кроме того, вы можете установить указатель q на любой элемент массива и, кроме того, на один элемент за массивом. (Не пытайтесь разыграть элемент на конце сиденья, хотя.)

Не забудьте указать delete[] p; в конце вашей функции.

7

В соответствии со стандартом C++ (5.2.1) индексации

1 постфиксного выражение следует выражение в квадратных скобках выражения постфикса. Одно из выражений должно иметь тип «массив Т» или «указатель на Т», а другой должен иметь нумерацию или интегральный тип. Результат имеет тип «T». Тип «T» должен быть полностью определенным типом объекта.65 Выражение E1 [E2] идентично (по определению) на * ((E1) + (E2)). ..

Таким образом, вы можете использовать любой интегральный тип, включая тип int и, соответственно, отрицательные значения при условии, что результат выражения *((E1)+(E2)) хорошо образован.

Примите во внимание, что для пользовательских типов вы можете использовать список начала скобки как индекс. Например

#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) 
Смежные вопросы