Мне там не всё понятно. В частности, мне не понятно, что такое левоассоциативность и правоассоциативность операторов в C++.
Не могли бы Вы, уважаемые коллеги, объяснить мне получше, что это такое? А заодно и рассказать понятно про приоритет операторов в C++.
Желательно также привести сложные выражения с операторами и объяснить, что они означают.
Я сейчас читаю Страуструпа, но мне хотелось бы прочитать подробно, понятно, популярно про приоритет операторов в C++.
Здравствуйте, RussianFellow, Вы писали:
RF>P.S. Спрашиваю так потому, что я привык писать сложные выражения в C++ со скобками. А если писать без скобок?
лучше все равно всегда писать со скобками за редким-редким исключением.
Здравствуйте, RussianFellow, Вы писали:
RF>Мне там не всё понятно. В частности, мне не понятно, что такое левоассоциативность и правоассоциативность операторов в C++.
Это как оно расставит скобки в цепочке одинаковых (или равноприоритетных) операторов, если скобок нет.
a+b+c интерпретируется (a+b)+c, а не a+(b+c) (для большинства случаев пофиг)
a-b-c — как (a-b)-c, а не a-(b-c) (а вот тут уже не пофиг, результат совсем другой)
это всё были левоассоциативные.
А вот пример правоассоциативного — возведение в степень — почти везде
в Фортране a**b**c понимается как a**(b**c), а не как (a**b)**c, потому что последнее равно a**(b*c), и смысла в такой ассоциативности — никакого.
В Си a=b=c понимается не как (a=b)=c, а как a=(b=c). Первый вариант не имеет смысла. Второй — имеет.
RF>Не могли бы Вы, уважаемые коллеги, объяснить мне получше, что это такое? А заодно и рассказать понятно про приоритет операторов в C++. RF>Желательно также привести сложные выражения с операторами и объяснить, что они означают.
А что они могут означать?
RF>Я сейчас читаю Страуструпа, но мне хотелось бы прочитать подробно, понятно, популярно про приоритет операторов в C++.
Почему не начать с более простой книги? Страуструп явно писал, что "не хочет оскорблять профессиональных программистов подробным разжёвыванием".
Здравствуйте, RussianFellow, Вы писали:
RF>Ну, например, что означают выражения:
(++(*p1)) = (++(*p2));
(*(++p1)) = (*(++p2);
(*(p1++)) = (*(p2++));
RF>И чем выражение RF>
RF>for (i=0; i<10; i++);
RF>
RF> отличается от выражения RF>
RF>for (i=0; i<10; ++i);
RF>
RF>?
С точки зрения наблюдаемого поведения — ничем. Если иначе — значит перегрузку операторов ++, для того чем является i, писал диверсант.
Для сложных типов данных, например итераторы контейнеров, префиксный оператор (++i) написать проще чем суфиксный (i++), и обычно он работает быстрее (ненужна дополнительная переменная).
И этот пример уже не имеет отношения к приоритетам операторов.
Здравствуйте, RussianFellow, Вы писали:
RF>P.S. Спрашиваю так потому, что я привык писать сложные выражения в C++ со скобками. А если писать без скобок?
Всегда лучше писать со скобками, иначе потом или читаемость будет хреновая или же закрадется бага, которую будешь искать пол дня.
Здравствуйте, RussianFellow, Вы писали:
RF>P.S. Спрашиваю так потому, что я привык писать сложные выражения в C++ со скобками. А если писать без скобок?
Открою секрет. Все помнят, что умножение имеет приоритет перед сложением, но более сложных правил не помнит никто. Поэтому все пишут сложные выражения со скобками.
Здравствуйте, RussianFellow, Вы писали:
RF>Ну, например, что означают выражения:
RF>
RF>++*p1 = ++*p2;
RF>
Разыменование двух указателей с последующим инкрементом адресуемых объектов и присваиванием.
Если только все используетые операторы не являются перегруженными, то, согласно пункту 1.9/15 стандарта С++11, такое выражение порождает неопределенное поведение, поскольку скалярный объект, адресуемый указателем p1, модифицируется дважды посредством неупорядоченных побочных эффектов:
If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.
В C++03 это поведение регулировалось таким понятием как "точки следования". Начиная с C++11 точки следования были заменены уточненными понятиями, но принцип сохранился.
--
Не можешь достичь желаемого — пожелай достигнутого.
а если хочется чего-то на пальцах, то набираете в гугле что-нить наподобие "operator precedence associativity" с фильтром по картинкам или видео. и находите, например, это: 05 operator associativity and precedence in c part 1
SkyKnight:
RF>>P.S. Спрашиваю так потому, что я привык писать сложные выражения в C++ со скобками. А если писать без скобок? SK>Всегда лучше писать со скобками, иначе потом или читаемость будет хреновая
Зависит от того, кто читает. Мне, например, удобнее читать
if (*first < 0xDC00 && first + 1 != ending && 0xDC00 <= first[1] && first[1] < 0xF000)
rg45:
R>Если только все используетые операторы не являются перегруженными, то, согласно пункту 1.9/15 стандарта С++11, такое выражение порождает неопределенное поведение, поскольку скалярный объект, адресуемый указателем p1, модифицируется дважды посредством неупорядоченных побочных эффектов:
R>
R>If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.
Если p1 и p2 — это обычные указатели, указывающие на разные неперекрывающиеся объекты скалярного типа, отличного от bool, то по правилам C++11 из-за множественной модификации undefined behavior тут возникнуть не может. Модификация результата ++*p1 оператором = выполняется после value computation для ++*p1, а это value computation выполняется после модификации результата *p1 оператором ++. В C++17 ввели ещё более строгую упорядоченность, и там даже такой вариант
++*p = ++*p;
не страшен возникновением undefined behavior за счёт множественной модификации (оно может возникнуть только по другим причинам).
Здравствуйте, N. I., Вы писали:
NI>Если p1 и p2 — это обычные указатели, указывающие на разные неперекрывающиеся объекты скалярного типа, отличного от bool, то по правилам C++11 из-за множественной модификации undefined behavior тут возникнуть не может. Модификация результата ++*p1 оператором = выполняется после value computation для ++*p1, а это value computation выполняется после модификации результата *p1 оператором ++.
Да, действительно, это, оказывается, является свойством оператора присваивания:
5.17/1
In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.
[Upd]
Да, но как в таком случае понимать пример из 1.9/15?
void f(int, int);
void g(int i, int* v) {
i = v[i++]; // the behavior is undefined
i = 7, i++, i++; // i becomes 9i = i++ + 1; // the behavior is undefined
i = i + 1; // the value of i is incremented
f(i = -1, i = -1); // the behavior is undefined
}
В обоих выделенных случаях, согласно 5.17/1, присваивание выполняется после модификации (инкремента), происходящего при вычислении правой части. Почему же возникает неопределенное поведение?
--
Не можешь достичь желаемого — пожелай достигнутого.
rg45:
R>[Upd] R>Да, но как в таком случае понимать пример из 1.9/15?
R>
R>
R>void f(int, int);
R>void g(int i, int* v) {
R>i = v[i++]; // the behavior is undefined
R>i = 7, i++, i++; // i becomes 9
R>i = i++ + 1; // the behavior is undefined
R>i = i + 1; // the value of i is incremented
R>f(i = -1, i = -1); // the behavior is undefined
R>}
R> R>В обоих выделенных случаях, согласно 5.17/1, присваивание выполняется после модификации (инкремента)
До C++17 эти две модификации не были упорядочены между собой, т.к. здесь инкремент — постфиксный, а у него модификация следует за вычислением значения (у префиксного же вычисление значения следует за модификацией, что в рассмотренном ранее примере позволяет выстроить упорядоченную цепочку модификация -> вычисление значения -> модификация).
Здравствуйте, N. I., Вы писали:
NI>Эти две модификации не упорядочены между собой, т.к. здесь инкремент — постфиксный, а у него модификация следует за вычислением значения (у префиксного же вычисление значения следует за модификацией, что в рассмотренном ранее примере позволяет выстроить упорядоченную цепочку модификация -> вычисление значения -> модификация).
правильно ли я понимаю, что в этом коде нет UB?
void g(int i, int* v) {
i = v[++i];
i = ++i + 1;
}
uzhas:
U>правильно ли я понимаю, что в этом коде нет UB? U>
U>void g(int i, int* v) {
U> i = v[++i];
U> i = ++i + 1;
U>}
U>
Если эту функцию вызывают с подходящими аргументами (так, что в результате не получается разыменование нулевого указателя, выход за границы массива, вычисление значения неинициализированного объекта, целочисленное переполнение и т.д.), то в случае с C++11/14/17 undefined behavior тут не будет.
В соответствии с
If x is not of type bool, the expression ++x is equivalent to x+=1
поведение в этом примере должно быть такое же, как у
void g(int i, int* v) {
i = v[i += 1];
i = (i += 1) + 1;
}
Дальше на основании
In all cases, the assignment is sequenced after the value computation of the right and left operands, and before the value computation of the assignment expression.
и
The value computations of the operands of an operator are sequenced before the value computation of the result of the operator.
можно выстроить цепочки
+= assignment → += value computation → [] value computation → = assignment += assignment → += value computation → + value computation → = assignment
R>>void f(int, int);
R>>void g(int i, int* v) {
R>>i = v[i++]; // the behavior is undefined
R>>i = 7, i++, i++; // i becomes 9
R>>i = i++ + 1; // the behavior is undefined
R>>i = i + 1; // the value of i is incremented
R>>f(i = -1, i = -1); // the behavior is undefined
R>>}
R>>
NI>До C++17 эти две модификации не были упорядочены между собой, т.к. здесь инкремент — постфиксный, а у него модификация следует за вычислением значения...
Что-то я как-то медленно соображать стал. Выходит, что в случае использования постинкремента, в отличие от преинкремента, применение побочного эфффекта не является необходимым для вычисления правой части оператора присваивания, таким образом инкремент и присваивание оказываются неупорядоченными друг с другом. Правильно?
В этом контексте нелишним будет упомянуть правила для постфиксных инкремента/декремента:
The value computation of the ++ expression is sequenced before the modification of the operand object.
Таким образом, при использовании постинкремента в правой части, мы знаем только то, что и присваивание и инкремент произойдут после вычисления значения подвыражения преинкремента, но в каком порядке — неизвестно.
--
Не можешь достичь желаемого — пожелай достигнутого.
rg45:
R>Таким образом, при использовании постинкремента в правой части, мы знаем только, и присваивание и инкремент произойдут после value computation, но в каком порядке — неизвестно.
Примерно так, но тут следует уточнить, что порядок не неизвестен, его вообще не существует. Наличие неизвестного порядка и отсутствие какого-либо порядка — это две разные ситуации. Две unsequenced модификации одного скалярного объекта приводят к undefined behavior, а две indeterminately sequenced модификации одного скалярного объекта — нет. В данном случае у нас именно unsequenced modifications.
В C++17 строгий порядок в таком примере будет обеспечен за счёт нового правила для операторов присваивания:
The right operand is sequenced before the left operand.
Здравствуйте, Andrew.W Worobow, Вы писали:
RF>>P.S. Спрашиваю так потому, что я привык писать сложные выражения в C++ со скобками. А если писать без скобок?
AWW>лучше все равно всегда писать со скобками за редким-редким исключением.
Да и вообще писать так, чтобы скобки не требовались.
Вчера несколько часов отлаживал ошибку после неудачной копипасты выражения с "i++".