Перестановка двух чисел  —  фундаментальная операция, которая выполняется на C++ по-разному. Сортируете ли вы массивы, реализуете алгоритмы или управляете структурами данных  —  понимание способов ее выполнения очень важно.

Изучим их преимущества и недостатки, применение в реальных сценариях C++.

Метод временной переменной

Это простейший, классический способ перестановки двух чисел:

void swap_temp(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}

Он интуитивно понятен и хорош для всех типов данных. Часто это первый прием, изучаемый на курсах программирования.

Реальное применение. В разработке игр, этим приемом переставляются позиции игроков в списке лидеров:

void update_leaderboard(vector<Player>& leaderboard, int pos1, int pos2) {
swap_temp(leaderboard[pos1], leaderboard[pos2]);
}

Арифметическая перестановка

Значения целочисленных типов перестанавливаются арифметическими операциями без временной переменной:

void swap_arithmetic(int &a, int &b) {
a = a + b;
b = a - b;
a = a - b;
}

Этим приемом экономится память, ведь дополнительная переменная  —  и пространство под нее  —  не требуется. Однако имеются ограничения:

  1. Метод хорош только для целочисленных типов.
  2. Для больших чисел имеется риск целочисленного переполнения.
  3. Метод не очень удобен для восприятия.

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

void calibrate_sensors(int &sensor1, int &sensor2) {
swap_arithmetic(sensor1, sensor2);
// Логика дальнейшей калибровки...
}

Побитовое «исключающее “ИЛИ”»

Здесь применяется операция «исключающего “ИЛИ”» и тоже экономится пространство:

void swap_xor(int &a, int &b) {
a ^= b;
b ^= a;
a ^= b;
}

Этот хитроумный прием популярен на собеседованиях и соревнованиях программистов. Операция побитово эффективна, но без нюансов не обходится:

  1. Метод хорош только для целочисленных типов.
  2. Не очень удобен для восприятия и сложен для сопровождения.
  3. Современными компиляторами часто оптимизируются другие, не столь эффективные методы.

Реальное применение. Криптография, перестановка с «исключающим “ИЛИ”» используется как часть алгоритма шифрования:

void encrypt_block(int &block1, int &block2) {
swap_xor(block1, block2);
// Дополнительные этапы шифрования...
}

std::swap

Это функция стандартной библиотеки C++, применяемая для перестановки значений:

#include <algorithm>

// Использование
std::swap(a, b);

Этот метод типобезопасен, применяется с любым присваиваемым типом и часто оптимизируется компиляторами. Благодаря четкости и гибкости он рекомендуется для большинства ситуаций.

Реальное применение. В алгоритме сортировки, таком как quicksort, std::swap идеален:

void quicksort(vector<int>& arr, int low, int high) {
if (low < high) {
int pivot = partition(arr, low, high);
quicksort(arr, low, pivot - 1);
quicksort(arr, pivot + 1, high);
}
}

int partition(vector<int>& arr, int low, int high) {
int pivot = arr[high];
int i = low - 1;

for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
std::swap(arr[i], arr[j]);
}
}
std::swap(arr[i + 1], arr[high]);
return i + 1;
}

Семантика перемещения

Этот метод применяется для эффективной перестановки в версии C++11 и новее:

template<typename T>
void swap_move(T& a, T& b) {
T temp = std::move(a);
a = std::move(b);
b = std::move(temp);
}

Особенно кстати приходится он для типов с дорогими операциями копирования и дешевыми операциями перемещения, например std::vector или std::string.

Реальное применение. В текстовом редакторе при помощи семантики перемещения большие фрагменты текста перестанавливаются эффективнее:

void swap_paragraphs(std::string& para1, std::string& para2) {
swap_move(para1, para2);
}

Распаковка кортежа: питонический подход на C++

В C++17 появились структурированные привязки для лаконичного синтаксиса перестановки:

#include <tuple>

template<typename T>
void swap_tuple(T& a, T& b) {
std::tie(a, b) = std::tuple(b, a);
}

Этот метод удобен для восприятия и хорош для любого типа, применяемого с std::tuple. Желаете подчеркнуть одновременность перестановки? Воспользуйтесь им.

Реальное применение. В многопоточном приложении с помощью этого метода обмениваются общими ресурсами:

void swap_resources(std::shared_ptr<Resource>& res1, std::shared_ptr<Resource>& res2) {
std::lock_guard<std::mutex> lock(resource_mutex);
swap_tuple(res1, res2);
}

Производительность

Выбор способа перестановки особенно важен в сплошных циклах или коде, где критична производительность. В этой связи:

  1. Метод временной переменной обычно быстрый и оптимизируется компиляторами.
  2. Арифметический метод медленнее из-за многочисленных арифметических операций.
  3. Метод «исключающего “ИЛИ”» потенциально быстрее для простых целочисленных типов, но медленнее в современных архитектурах.
  4. std::swap, чтобы сравняться по скорости с оптимальной пользовательской реализацией, часто оптимизируется.
  5. Семантика перемещения значительно быстрее для больших объектов с дорогими операциями копирования.
  6. Распаковка кортежа из-за его создания чревата небольшим накладными расходами.

В большинстве случаев предпочтителен std::swap  —  хорошая производительность сочетается им с удобством для восприятия и типобезопасностью. Тем не менее для специализированных сценариев другие методы могут оказаться оптимальнее.

Выбор правильного способа перестановки

Оптимальный метод перестановки определяется конкретным сценарием:

  1. Для неспециализированной перестановки используется std::swap.
  2. При работе с большими объектами или пользовательскими типами  —  семантика перемещения.
  3. В средах с ограниченной памятью для целых чисел  —  арифметический метод или перестановка с «исключающим “ИЛИ”».
  4. Для удобства восприятия человеком и подчеркивания одновременности перестановки  —  распаковка кортежа.
  5. В коде, где важна производительность, протестировав методы, выберите оптимальный.

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

Заключение

Перестановка чисел в C++  —  большее, чем просто обмен значениями, это выбор правильного инструмента для работы.

В арсенале разработчика найдется место каждому методу  —  от классического подхода с временной переменной до модерновой семантики перемещения.

Продолжая работать над проектами C++, учитывайте потребности своего приложения в перестановках. Экспериментируйте с различными приемами, тестируйте критические разделы и не бойтесь сочетать методы, где это возможно.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи ryan: C++ Number Swapping: How to Guide

Предыдущая статья18 полезных приемов написания JavaScript-кода
Следующая статьяРазбираемся с новым HTTP-заголовком Deprecated