Возможная проблема при обмене значениями двух переменных без использования третьей переменной

Недавно я придумал этот метод обмена значениями двух переменных без использования третьей переменной.

a^=b^=a^=b

Но когда я попробовал приведенный выше код на разных компиляторах, я получил разные результаты, некоторые дали правильные результаты, некоторые нет.

Что-то ужасно не так с кодом?


person Pattrick    schedule 18.09.2010    source источник
comment
Конечно, на самом деле так не обмениваться. Используйте временный, он чище и быстрее работает на современном оборудовании.   -  person GManNickG    schedule 18.09.2010
comment
@GMan: Почему вы просите всех прочитать книгу о C ++ (в about me section вашего профиля) = D?   -  person Prasoon Saurav    schedule 18.09.2010
comment
@Prasoon: Потому что каждый должен прочитать книгу. :)   -  person GManNickG    schedule 18.09.2010
comment
Я бы сказал, что у меня разные результаты, значит, что-то ужасно не так с кодом для всех X, так что X - это код.   -  person msw    schedule 18.09.2010
comment
@GMan: Хм, верно. Но вроде ты злишься или что-то на кого-то, где-то. : D   -  person Prasoon Saurav    schedule 18.09.2010
comment
На самом деле не следует менять вручную временный. в C ++ существует функция STL под названием std :: swap, которая делает то же самое.   -  person BatchyX    schedule 18.09.2010
comment
@Prasoon: мне надоели эти, я знаю Java / я знаю C #, пытаюсь выучить C ++, в Java / C # мы ... вопросы. Ответ - остановись и почитай книгу. Это все. Если вы не знаете C ++ для начинающих, вы узнаете его, прочитав книгу для начинающих по C ++. Здесь нет ярлыков. Пытаться отгадать, используя знание другого языка, все равно что пытаться управлять самолетом, потому что вы умеете ездить на трехколесном велосипеде. Это глупо.   -  person GManNickG    schedule 18.09.2010
comment
@GMan: Да, конечно, stackoverflow.com (вообще) не альтернатива этим действительно хорошим книгам. Эти книги надо прочитать, а потом прийти и спросить здесь свои сомнения. :)   -  person Prasoon Saurav    schedule 18.09.2010
comment
Теги @msw: Swap и Code-Golf были совершенно допустимыми. Я часто видел, как некоторые начинающие программисты использовали эти (своего рода) методы, когда они пытались реализовать некоторые базовые алгоритмы, использующие побитовые операторы.   -  person Prasoon Saurav    schedule 18.09.2010
comment
Так что используйте свою привилегию отката, если хотите, это слишком тривиально, чтобы требовать обсуждения.   -  person msw    schedule 18.09.2010
comment
Много-много дубликатов, например Замена двух значений переменной без использования третьей переменной   -  person Paul R    schedule 18.09.2010
comment
@ Пол: Нет, проблема Патрика здесь другая. Он не спрашивает о Swapping two variable value without using 3rd variable.   -  person Prasoon Saurav    schedule 18.09.2010
comment
замена таким образом будет работать только для целочисленных значений, используйте третьи значения вместо его очистителя, а также в случае значений с плавающей запятой не вызывает усечения   -  person keshav84    schedule 18.09.2010
comment
@ Все, кто проголосовал за закрытие: по моему мнению, это не было дубликатом, потому что вопрос, который задал Паттрик, а не How to swap two variable values without using 3rd variable, я проголосовал за повторное открытие.   -  person Prasoon Saurav    schedule 18.09.2010
comment
Я согласен, что вопрос о том, почему не работает A? не следует закрывать как дубликат Как сделать B? с решением C.   -  person Georg Fritzsche    schedule 18.09.2010
comment
@GMan: позвольте мне процитировать вам комментарий пользователя MetaSO, который, как мне кажется, прекрасно резюмирует, для чего предназначена SO: Это сайт вопросов и ответов. Не сложный вопрос, проницательный ответ, который развивает вас как личность сайта. Люди должны уметь задавать простые вопросы с простыми ответами, которые могут оказаться ложными. Многие люди просто хотят написать работающий код. Не быть наделенным полномочиями. - Оуэн, 19 сен.   -  person RCIX    schedule 19.09.2010
comment
@GMan: Я не говорю, что люди не должны читать книгу, просто на них не следует кричать за то, что они задают совершенно правильный вопрос.   -  person RCIX    schedule 19.09.2010


Ответы (8)


Что-то ужасно не так с кодом?

Да!

a^=b^=a^=b фактически вызывает Undefined Behavior в C и C ++, потому что вы пытаетесь изменить значение a более одного раза между двумя точками последовательности.


Попробуйте написать (хотя и небезопасно)

a ^= b;
b ^= a;
a ^= b;

вместо a^=b^=a^=b.

P.S: никогда не пытайтесь поменять местами две переменные, не используя третью. Всегда используйте третью переменную.

ИЗМЕНИТЬ:

Как заметил @caf, b^=a^=b в порядке, даже если порядок оценки аргументов оператора ^= не указан, поскольку все обращения b в выражении используются для вычисления окончательного значения, которое хранится в b, поведение хорошо определено.

person Prasoon Saurav    schedule 18.09.2010
comment
Модификация b на самом деле допустима, поскольку она предназначена только для чтения, чтобы определить новое значение (b ^= a ^= b; будет в порядке). - person caf; 18.09.2010
comment
@caf: но порядок вычисления аргументов оператора = не указан. - person Prasoon Saurav; 18.09.2010
comment
@ Prasoon_Saurav, почему вы помечаете 3 утверждения как ненадежные? - person ; 18.09.2010
comment
@crypto: потому что этот метод подкачки будет работать только для целых чисел. :-) - person Prasoon Saurav; 18.09.2010
comment
@Prasoon_Saurav, операции просто меняют местами битовое представление, используемое для a & b, какое бы значение имело, если бы это был другой тип данных? - person ; 18.09.2010
comment
@crypto: оператор ^= даже не определен для других типов. - person R.. GitHub STOP HELPING ICE; 18.09.2010
comment
Вы имеете в виду, что ^ = определен только для типов int? - person ; 18.09.2010
comment
@Crypto: Для семьи int. :) - person Prasoon Saurav; 18.09.2010
comment
На этот раз он также не определен для C ++ 0x для исходного выражения. Но b ^= a ^= b на самом деле нормально в C ++ 0x. Присвоение b происходит после вычисления значения a ^= b. На мой взгляд, формулировка C ++ 03 непонятна, но я не против интерпретации @ caf. Но я не собираюсь делать ставку на C ++ 03 :) - person Johannes Schaub - litb; 18.09.2010
comment
@Johannes: Это для информации. Я обнаружил, что C ++ - 03 точки последовательности легче читать и понимать по сравнению с такими терминами, как «упорядоченный / неупорядоченный» в C ++ 0x. И, как сказал R, a ^= ( a^=b , b^=a ); также не определен в C ++ (и я уверен, что он будет хорошо определен в C ++ 0x) . Я буду скучать по точкам последовательности good'ol :-) - person Prasoon Saurav; 18.09.2010
comment
@Prasoon well Правила C ++ 03 слишком неясны, чтобы описать, что на самом деле происходит с b ^= a ^= b IMO. Это выглядит невинно, потому что в Стандарте говорится, что можно читать и записывать одну и ту же переменную в выражении, если чтение должно определять записываемое значение. По-видимому, так обстоит дело с b ^= a ^= b, i = a[i]; и подобными. Но в C ++ 03 для этого нет действительно сильной формулировки, поэтому я не собираюсь делать ставки, в отличие от C ++ 0x :) - person Johannes Schaub - litb; 18.09.2010
comment
@Johannes: Но C ++ 03 также говорит, что порядок вычисления аргументов оператора = не указан. Таким образом, вы никогда не знаете, какая сторона оператора присваивания оценивается первой. :) - person Prasoon Saurav; 18.09.2010
comment
@Prasoon yes a^= (a^=b, b^=a) определенно не определено в C ++ 03 и C ++ 0x = ›a заменяется на a^=b (побочный эффект) и значение вычисляется крайним левым a, и оба они не упорядочены. - person Johannes Schaub - litb; 18.09.2010
comment
@Johannes: Черт! Теперь я запутался. Вы сказали, что в b ^= a ^= b присвоение b упорядочено после вычисления значения a ^= b, верно? так это означает, что в a^= (a^=b, b^=a) присваивание крайнему левому a выполняется после вычисления значения (a^=b, b^=a), верно? Но послушайте, у нас есть оператор запятой в (a^=b, b^=a), который устранит побочный эффект a^=b, тогда как это будет UB в C ++ 0x? - person Prasoon Saurav; 18.09.2010
comment
@Prasoon это UB по вышеуказанной причине. a = b не выполняет последовательность вычисления значения a относительно b. Не имеет значения, содержит ли b собственные последовательные побочные эффекты на a. Они должны быть упорядочены относительно вычисления значения левого операнда a = b, чтобы быть действительными. - person Johannes Schaub - litb; 18.09.2010
comment
Помните, что у нас было аналогичное обсуждение здесь. - person Prasoon Saurav; 18.09.2010
comment
@Johannes: Кстати, спасибо за точное объяснение. :-) - person Prasoon Saurav; 18.09.2010
comment
Здесь также обсуждалась семантика C ++ 0x: stackoverflow.com/questions/3690141/. Теперь неважно, ^= или +=. - person Johannes Schaub - litb; 18.09.2010
comment
@Johannes: Да, еще раз проверил свой ответ. Имеет смысл, спасибо. :) - person Prasoon Saurav; 18.09.2010
comment
b ^= a ^= b; определяется по той же причине, что и i = a[i];. Верно, что b и (a ^= b) оцениваются в неопределенном порядке, но оба должны быть оценены до того, как можно будет вычислить новое значение b. - person caf; 19.09.2010

Если вы используете C ++, почему бы не использовать алгоритм подкачки в STL? Он идеально подходит для этой цели и очень понятно, что он делает:

#include <algorithm>
using namespace std;

// ...

int x=5, y=10;    // x:5 y:10
swap(x,y);        // x:10 y:5
person Component 10    schedule 18.09.2010
comment
Честно говоря, вопрос тоже помечен как C. - person GManNickG; 18.09.2010
comment
@GMan: справедливо! Я соответствующим образом отредактировал свой ответ. Спасибо. - person Component 10; 18.09.2010

На основе материалов R. & sellibitze:

Используйте оператор запятой:

 (a^=b,b^=a,a^=b);

Из текста и из Википедии:

"Оператор запятой может использоваться для связывания связанных выражений вместе. Список выражений, связанных запятыми, оценивается слева направо, а значение самого правого выражения является значением комбинированного выражения. Оно действует как точка последовательности. "

"Точка последовательности гарантирует, что все побочные эффекты предыдущих оценок будут выполнены, и что побочные эффекты от последующих оценок еще не были выполнены. Она устраняет неопределенное поведение, возникающее из-за нечеткого порядка выполнения исходного выражения. "

person Community    schedule 18.09.2010
comment
Это не помогает. Исходный оператор эквивалентен вашему, и оба имеют неопределенное поведение. - person R.. GitHub STOP HELPING ICE; 18.09.2010
comment
Я думаю, что отредактированная версия должна работать, потому что запятая - это точка последовательности. - person sellibitze; 18.09.2010
comment
Я думаю, что это все еще неопределенное поведение. Это эквивалентно a = a ^ (a ^= b, b ^= a);, а левый размер оператора ^ может быть вычислен до или после выражения в скобках. - person R.. GitHub STOP HELPING ICE; 18.09.2010
comment
Текущая версия определена. Тем не менее, это все еще неразумно, так как на современных процессорах он медленный по сравнению с использованием локального временного (который, конечно, был бы оптимизирован для регистра). - person Donal Fellows; 19.09.2010
comment
@R ..: Версия определена, поскольку оператор запятой имеет свою собственную точку следования. a ^= b оценивается полностью, затем b ^= a, затем a ^= b. Поскольку все они четко определены, объединение их вместе с операторами запятых хорошо определено. Это, конечно, не относится к похожим запятым, разделяющим аргументы функций. - person David Thornley; 21.09.2010
comment
@David: Ответ был снова отредактирован после моего комментария, что должно быть очевидно из содержания моего комментария. - person R.. GitHub STOP HELPING ICE; 21.09.2010

Я предлагаю вам использовать std :: swap () для C ++.

Для c используйте этот макрос. Обратите внимание, что вам нужно сначала сравнить a и b, иначе, когда они указывают на одну и ту же ячейку памяти, вы сотрете значение, и оно станет 0.

#define swap(a, b)  ((a) == (b) || (a) ^= (b), (b) ^= (a), (a) ^= (b))
person grokus    schedule 18.09.2010
comment
Как вы рассчитываете, что в итоге 0 когда a == b? Изначально a = b = X. После a ^= b, a == 0. После b ^= a, b == X. Наконец, с a ^= b, a == X && b == X. Это именно то, что мы ожидаем. - person Phil Miller; 19.09.2010
comment
@Novelocrat, я имел в виду особый случай, когда a и b - это одно и то же место в памяти, тогда вы бы стерли значение. Например. int a = 5; int & b = a; Я отредактировал свой ответ, чтобы уточнить. - person grokus; 19.09.2010
comment
решением было бы проверить (& (a) == & (b)) - person flownt; 19.09.2010
comment
@flownt, конечно, вы можете это сделать, но даже a и b не указывают на один и тот же адрес, но имеют одно и то же значение, зачем вообще менять местами? - person grokus; 20.09.2010

Делай это так:

a ^= b;
b ^= a;
a ^= b;
person Matt Joiner    schedule 18.09.2010
comment
В этом ответе есть ошибка, см. Мой ответ. - person grokus; 18.09.2010

Что насчет этого?

a = a + b;
b = a - b;
a = a - b;
person Benone Dorneanu    schedule 08.01.2011

Я удивился, почему никто не предложил заключить это выражение в скобки. Кажется, это уже не UB.

a^=(b^=(a^=b));
person Hossein    schedule 10.05.2012

Вы также можете попробовать следующий, но если числа достаточно большие, значение будет переполняться

a=a*b;
b=a/b;
a=a/b;
person Kumareshan    schedule 19.10.2013