Menu
Coddy logo textTech

ジェネリックスワップ

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言語の標準ライブラリが qsortbsearch のような汎用関数を実装する際の手法です。

challenge icon

チャレンジ

簡単

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つの入力を受け取ります:

  1. 型インジケータ:整数なら i、ダブルなら d
  2. 1番目の値
  3. 2番目の値

スワップ後、両方の値を別々の行に出力してください。ダブルの場合は、小数点以下2桁まで表示してください。

入力が i510 の場合の出力例:

10
5

入力が d3.142.71 の場合の出力例:

2.71
3.14

入力が i-742 の場合の出力例:

42
-7

入力が d99.990.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;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

Object Oriented Programmingのすべてのレッスン