ジェネリックスワップ
CoddyのCジャーニー「Object Oriented Programming」セクションの一部 — レッスン 51/61。
プログラミングにおける一般的な操作は、2つの値を入れ替えることです。通常、int 用、double 用といったように、型ごとに個別の swap 関数を記述します。しかし、void* と memcpy を使用すれば、任意の2つの変数を入れ替える単一の関数を記述できます。
重要な洞察は、スワップとは単にバイトを移動させることであるということです。データのサイズがわかれば、実際の型を知らなくてもバイトをコピーできます。関数のシグネチャは次のようになります:
void generic_swap(void* a, void* b, size_t size);size パラメータは、移動するバイト数を関数に伝えます。内部では、一時バッファと <string.h> の 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ですvoid* と size_t を使用するこのパターンは、C言語の標準ライブラリが qsort や bsearch のような汎用関数を実装する際の手法です。
チャレンジ
簡単void* ポインタと memcpy の力を利用して、型に関係なく任意の2つの変数の値を交換できる汎用的なスワップユーティリティを作成しましょう。
コードは以下の3つのファイルに分けて構成します:
swap.h: 2つのvoid*ポインタと、スワップするデータのサイズを示すsize_t型のパラメータを受け取るgeneric_swap関数を宣言します。インクルードガードを忘れないようにしてください!swap.c:generic_swap関数を実装します。一時バッファとmemcpyを使用して、2つのメモリ位置の間でバイトを移動させます。3ステップのスワップパターン(最初の値を一時バッファにコピー、2番目の値を最初にコピー、一時バッファを2番目にコピー)を思い出してください。main.c: 異なるデータ型で汎用スワップが動作することを確認します。型インジケータ(整数ならi、ダブルならd)に続いて2つの値を読み取ります。適切な型の変数を2つ作成し、generic_swap関数を使用してそれらをスワップし、スワップ後の両方の値を出力します。
プログラムは3つの入力を受け取ります:
- 型インジケータ:整数なら
i、ダブルならd - 1番目の値
- 2番目の値
スワップ後、両方の値を別々の行に出力してください。ダブルの場合は、小数点以下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 関数がスワップしている型を全く関知せず、単にバイトを移動させているだけだという点です。同じ関数が整数、ダブル、構造体、あるいはその他のあらゆるデータ型に対して機能します。memcpy のために <string.h> を、size_t のために <stddef.h> または <stdlib.h> をインクルードする必要があります。
チートシート
汎用的なスワップ関数は、実際の型を知ることなくバイトを移動させることで、あらゆる型の値を入れ替えるために 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 になります必要なヘッダー: memcpy のための <string.h>、および size_t のための <stddef.h> または <stdlib.h>。
自分で試してみよう
#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: a と b を入れ替えるために generic_swap を呼び出す
// Hint: &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: a と b を入れ替えるために generic_swap を呼び出す
// Hint: &a、&b、および sizeof(double) を使用する
printf("%.2f\n", a);
printf("%.2f\n", b);
}
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。