Обобщенный обмен (Swap)
Часть раздела Object Oriented Programming путешествия по C на Coddy — урок 51 из 61.
Обмен значениями двух переменных — распространенная операция в программировании. Обычно для каждого типа приходится писать отдельную функцию обмена — одну для int, другую для double и так далее. Но с помощью void* и memcpy мы можем написать одну функцию, которая меняет местами любые две переменные.
Ключевая идея заключается в том, что обмен значениями — это просто перемещение байтов. Если вы знаете размер данных, вы можете копировать байты, не зная фактического типа. Сигнатура функции выглядит следующим образом:
void generic_swap(void* a, void* b, size_t size);Параметр size сообщает функции, сколько байт нужно переместить. Внутри мы используем временный буфер и memcpy из <string.h> для перестановки байтов:
void generic_swap(void* a, void* b, size_t size) {
char temp[size]; // временный буфер
memcpy(temp, a, size); // temp = a
memcpy(a, b, size); // a = b
memcpy(b, temp, size); // b = temp
}Теперь эта единственная функция работает для любого типа:
int x = 5, y = 10;
generic_swap(&x, &y, sizeof(int));
// теперь x равен 10, y равен 5double p = 3.14, q = 2.71;
generic_swap(&p, &q, sizeof(double));
// p теперь 2.71, q теперь 3.14Этот паттерн — использование void* с size_t — это то, как стандартная библиотека C реализует обобщённые функции, такие как qsort и bsearch.
Задание
ЛегкоДавайте создадим универсальную утилиту для обмена значениями (swap), которая может менять местами значения любых двух переменных независимо от их типа, используя возможности указателей void* и функции memcpy.
Вы организуете свой код в трех файлах:
swap.h: Объявите функциюgeneric_swap, которая принимает два указателяvoid*и параметрsize_t, указывающий размер данных для обмена. Не забудьте про include guards!swap.c: Реализуйте функциюgeneric_swap. Используйте временный буфер иmemcpyдля перемещения байтов между двумя участками памяти. Помните о трехэтапном шаблоне обмена: скопировать первое значение во временный буфер, скопировать второе в первое, скопировать временный буфер во второе.main.c: Продемонстрируйте работу вашей универсальной функции обмена с различными типами данных. Считайте индикатор типа (iдля целых чисел,dдля чисел с плавающей точкой double), а затем два значения. Создайте две переменные соответствующего типа, поменяйте их местами с помощью функцииgeneric_swap, а затем выведите оба значения после обмена.
Ваша программа получит три входных значения:
- Индикатор типа:
iдля integer илиdдля double - Первое значение
- Второе значение
После обмена выведите оба значения на отдельных строках. Для типа double используйте 2 знака после запятой.
Пример вывода, когда входные данные — i, 5 и 10:
10
5Пример вывода, когда входные данные — d, 3.14 и 2.71:
2.71
3.14Пример вывода, когда входные данные — i, -7 и 42:
42
-7Пример вывода, когда входные данные — d, 99.99 и 0.01:
0.01
99.99Прелесть этого подхода в том, что функция generic_swap «не знает», типы каких данных она меняет — она просто перемещает байты. Одна и та же функция работает для целых чисел, double, структур или любого другого типа данных. Вам нужно будет включить <string.h> для memcpy и <stddef.h> или <stdlib.h> для size_t.
Шпаргалка
Универсальная функция перестановки (swap) использует указатели void* и memcpy для обмена значениями любого типа путем перемещения байтов без знания фактического типа.
Сигнатура функции:
void generic_swap(void* a, void* b, size_t size);Реализация с использованием временного буфера и memcpy:
void generic_swap(void* a, void* b, size_t size) {
char temp[size]; // временный буфер
memcpy(temp, a, size); // temp = a
memcpy(a, b, size); // a = b
memcpy(b, temp, size); // b = temp
}Использование с различными типами:
int x = 5, y = 10;
generic_swap(&x, &y, sizeof(int));
// теперь x равен 10, y равен 5
double p = 3.14, q = 2.71;
generic_swap(&p, &q, sizeof(double));
// теперь p равно 2.71, q равно 3.14Необходимые заголовки: <string.h> для memcpy и <stddef.h> или <stdlib.h> для size_t.
Попробуйте сами
#include <stdio.h>
#include "swap.h"
int main() {
char type;
scanf("%c", &type);
if (type == 'i') {
int a, b;
scanf("%d", &a);
scanf("%d", &b);
// TODO: Вызовите generic_swap, чтобы поменять местами a и b
// Подсказка: используйте &a, &b и sizeof(int)
printf("%d\n", a);
printf("%d\n", b);
} else if (type == 'd') {
double a, b;
scanf("%lf", &a);
scanf("%lf", &b);
// TODO: Вызовите generic_swap, чтобы поменять местами a и b
// Подсказка: используйте &a, &b и sizeof(double)
printf("%.2f\n", a);
printf("%.2f\n", b);
}
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Основы модульного программирования
Заголовочные файлыСтражи включенияИсходные файлыСтатические функцииПовторение: Модульный калькулятор4Инкапсуляция
Концепция непрозрачных указателейОпределение непрозрачных структурГеттеры и сеттерыВалидация в сеттерахИтоги: Секретный ящик7Указатели на функции
Объявление указателей на функцииВызов указателей на функцииTypedef для указателей на функцииПередача функций в качестве аргументовИтоги: Диспетчеризация калькулятора10Обобщенные контейнеры
Повторение: указатели voidОбобщенная оберткаОбобщенный обмен (Swap)Обобщенное сравнениеПовторение: обобщенный массив2Объекты и методы
Структуры как объектыУказатель 'Self'Константная корректностьУказатель против значенияВспомогательные методыИтоги: Point Manager5Проект: Простой банковский счет
Настройка проектаРеализация счета3Жизненный цикл объекта
Паттерн «Конструктор»Паттерн «Деструктор»Инициализация в стекеГлубокое копированиеПовторение: String Wrapper6Наследование через композицию
Встраивание структурПравило первого элементаДоступ к элементам родителяUpcastingПовторение: Иерархия фигур9Проект: Рисование фигур
Обзор проектаРеализация круга