プリプロセス 解答ページ | Programming Place Plus 新C++編

トップページ新C++編プリプロセス

このページの概要

このページは、練習問題の解答例や解説のページです。



解答・解説

問題1 (確認★)

ある関数の中でだけ有効になるマクロをどのように実現できますか?


#define の効果は、定義を記述した位置から、翻訳単位の終わりまで続きます(本編解説)。したがって、関数内で定義したとしても、関数の終わりを突き抜けていきます。

しかし翻訳単位の終わりを待たずに、効果を止める方法があります。それは #undef です(本編解説)。関数の本体のコードの先頭で #define を記述し、末尾で #undef すれば、関数内でだけ有効なマクロが実現できます。

#include <iostream>

void f();

int main()
{
#define X 100
    std::cout << X << "\n";
    f();
#undef X  // #define X の効果はここで終わり
}

void f()
{
    // 以下のコメントアウトを解除すると、この位置では X は定義されていないため、コンパイルエラーになる
//    std::cout << X << "\n";
}

実行結果:

100

問題2 (基本★★)

ソースコード上のある地点で、ソースファイルの名前と行番号、実行中の関数の名前を出力するデバッグ目的の関数を次のように作成しました。しかし、この関数には実用上の問題があります。問題点を指摘してください。また、どうすれば解決できるでしょうか?

void print_source_location()
{
    std::cout << "File: " << __FILE__ << "  Line: " << __LINE__ << "  Func: " << __func__ << "\n";
}


この関数自体はエラーになりませんし、__FILE__ などの事前定義マクロ(本編解説)、事前定義変数(本編解説)の使い方が間違っているわけでもありません。しかし実用上、この関数は意味がないといえます。

この関数がしていることは、ソースファイルの名前、行番号、関数名を出力することであり、その目的は、プログラムがどこまで実行されたか、どのような経路を通過したかといった情報を確認することです。

たとえば次のプログラムは、入力された整数が 0 かどうかによって分岐しています。どちらの経路を通ったのかを把握したい事情があるとして、print_source_location関数の呼び出しをそれぞれの経路に入れ込んでいます。

#include <iostream>

void print_source_location()
{
    std::cout << "File: " << __FILE__ << "  Line: " << __LINE__ << "  Func: " << __func__ << "\n";
}

int main()
{
    int v {0};
    std::cin >> v;

    if (v == 0) {
        print_source_location();
        std::cout << "zero\n";
    }
    else {
        print_source_location();
        std::cout << v << "\n";
    }
}

このプログラムを実行して、0 が入力されたとします。その場合の実行結果は次のようになります。

0  <-- 入力された整数
File: c:\test_program\main.cpp  Line: 5  Func: print_source_location
zero

一方、1 が入力された場合は次のようになります。

1  <-- 入力された整数
File: c:\test_program\main.cpp  Line: 5  Func: print_source_location
1

print_source_location関数によって出力された情報はまったく同じものになっています。これでは情報に価値がありません。関数名は特に問題が分かりやすく、知りたいのは print_source_location関数ではなくて、print_source_location関数をどの関数から呼び出したかであるはずです。

このような結果になるのは、__FILE__、__LINE__ がプリプロセスの時点で置換されているためです。__func__ はプリプロセスで処理されているわけではないものの、それを記述した位置の関数名になるという仕様なので、print_source_location関数の内側に書いている以上、その結果は “print_source_location” になってしまいます。

この問題を解決する方法として、print_source_location を関数ではなく、マクロとして定義することが考えられます。

#include <iostream>

#define PRINT_SOURCE_LOCATION()  std::cout << "File: " << __FILE__ << "  Line: " << __LINE__ << "  Func: " << __func__ << "\n"

int main()
{
    int v {0};
    std::cin >> v;

    if (v == 0) {
        PRINT_SOURCE_LOCATION();
        std::cout << "zero\n";
    }
    else {
        PRINT_SOURCE_LOCATION();
        std::cout << v << "\n";
    }
}

実行結果:

0  <-- 入力された整数
File: c:\test_program\main.cpp  Line: 11  Func: main
zero
1  <-- 入力された整数
File: c:\test_program\main.cpp  Line: 15  Func: main
1

問題3 (応用★★)

2つの変数の値を交換する関数形式マクロを作成してください。


2つの変数の値を交換する方法は、「代入」のページの練習問題で取り上げたことがあります。交換作業用の変数を用意して、値を退避させながら行うことがポイントでした。

work = a;  // a の値を退避
a = b;     // b を a にコピー
b = work;  // 退避しておいた a の値を b にコピー

これを関数形式マクロにすると、次のように書けそうです。

#define SWAP(a, b)  work = a; \
                    a = b;    \
                    b = work;

この程度なら1行で書いても問題ないですが、改行したければ、この例のように、行末に \ を置いてください(本編解説)。

この SWAPマクロを実際に使おうとすると問題に気付くことになります。

int main()
{
    int v1 {10};
    int v2 {20};
    SWAP(v1, v2);  // コンパイルエラー。work が宣言されていない
}

マクロの置換後の結果に含まれている変数 work が宣言されていないため、エラーになります。もちろん、マクロを使う側で宣言すればエラーは消えますが、マクロ側の実装で完結していないのは、あまり気の利いたマクロとはいえません。

int main()
{
    int v1 {10};
    int v2 {20};
    int work {};
    SWAP(v1, v2);  // 一応 OK
}

マクロは単なるコードの置換なので、work の宣言を置換後の文字の並びに含めることは可能です。

#include <iostream>

#define SWAP(a, b)  int work = a; \
                    a = b;        \
                    b = work;

int main()
{
    int v1 {10};
    int v2 {20};
    SWAP(v1, v2);
    std::cout << v1 << ", " << v2 << "\n";
}

実行結果:

20, 10

これは可能ですが、仮引数a、b には型の指定がないのに、work は int型に固定されることが勿体ないですし、実引数が double型の場合に、暗黙の型変換で int にされる事故が起こり得ます。また、マクロを使う側に、すでに別の目的で work という名前の変数が宣言されていたら、重複が起きてしまいます。

まず、型の問題は、型の指定もマクロに与えるようにすれば解決します。

#include <iostream>

#define SWAP(type, a, b)  type work = a; \
                          a = b;         \
                          b = work;

int main()
{
    int v1 {10};
    int v2 {20};
    SWAP(int, v1, v2);
    std::cout << v1 << ", " << v2 << "\n";
}

実行結果:

20, 10

実引数に型名を記述するというのは、不思議な感じがしますが、マクロは文字の並びの単純な置換なので、このようなことも可能です。

次に、work の宣言が重複する可能性への対処です。これは、置換後のコードをブロックの中に閉じ込めることで解決できます。

#include <iostream>
#include <vector>

#define SWAP(type, a, b)  { type work = a; \
                            a = b;         \
                            b = work; }

int main()
{
    std::vector<int> work {};  // すでに別の用途の work がある
    int v1 {10};
    int v2 {20};

    SWAP(int, v1, v2);
    std::cout << v1 << ", " << v2 << "\n";
}

実行結果:

20, 10

この場合も、work という名前の変数が隠蔽される(隠される)という意味の警告は出るかもしれませんが、エラーは防がれます。


こうして多くの問題を解消した SWAPマクロは実現できますが、マクロよりも関数で実現する方が安全であるといえます。そしてそもそも、C++ の標準ライブラリには std::swap関数が用意されています。std::swap関数の宣言は、<utility> という標準ヘッダにあります。

#include <iostream>
#include <utility>

int main()
{
    int v1 {10};
    int v2 {20};

    std::swap(v1, v2);
    std::cout << v1 << ", " << v2 << "\n";
}

実行結果:

20, 10

問題4 (応用★★★)

ポーカーのプログラムで、カードの番号とマークを出力するとき、現在は club A とか diamond 2 のような表記になっています。マクロによる分岐を使って、クラブのAダイヤの2 といった表記で出力するコードに切り替えられるようにしてください。

現在のポーカープログラムの最終形は、「ヘッダファイル」のページの練習問題の解答にあります。


カードのマークの文字列表現を返す関数が card.cpp にあります。

// カードのマークの文字列表現を返す
std::string get_mark_string(CardMark card_mark)
{
    switch (card_mark) {
    case CardMark::spade:
        return "spade";
    case CardMark::club:
        return "club";
    case CardMark::diamond:
        return "diamond";
    case CardMark::heart:
        return "heart";
    default:
        return "";
    }
}

このコードを #if を使って変更できるようにします。たとえば、CARD_MARK_STRING_KANA が定義されていたら、カタカナ表記で返すように修正します。

// カードのマークの文字列表現を返す
std::string get_mark_string(CardMark card_mark)
{
#ifdef CARD_MARK_STRING_KANA
    switch (card_mark) {
    case CardMark::spade:
        return "スペード";
    case CardMark::club:
        return "クラブ";
    case CardMark::diamond:
        return "ダイヤ";
    case CardMark::heart:
        return "ハート";
    default:
        return "";
    }
#else
    switch (card_mark) {
    case CardMark::spade:
        return "spade";
    case CardMark::club:
        return "club";
    case CardMark::diamond:
        return "diamond";
    case CardMark::heart:
        return "heart";
    default:
        return "";
    }
#endif
}

では、#define CARD_MARK_STRING_KANA はどこに記述するといいでしょうか。上記の get_mark_string関数があるのは card.cpp なので、別の翻訳単位(たとえば、main.cpp)に記述しても、その定義を発見してくれません。

card.cpp の先頭辺りに書くのがシンプルではありますが、ほかのソースファイルからもマクロの定義の有無が知りたい場合は card.h に置くことも考えられます。今回は、出力時に クラブのA のようにしたいということで、 の部分を出力することは main.cpp の側の担当であるため、カタカナで出力するモードになっているかどうかを、main.cpp の側からも知る必要があります。したがって、#define CARD_MARK_STRING_KANA は card.h に置きます(あるいは、新たなヘッダファイルを導入してもいい)。

//card.h 
#ifndef CARD_H_INCLUDED
#define CARD_H_INCLUDED

#define CARD_MARK_STRING_KANA  // カードのマークの文字列表現をカタカナにする

// 以下省略

main.cpp の側には、カードのマークと番号を出力するコードが何か所かに散らばっています。まずこれを整理しましょう。カードの文字列表現を返す get_card_string関数を作成し、それぞれをこの関数の呼び出しに置き換えます。

// main.cpp
#include <iostream>
#include <sstream>
#include "card.h"
#include "poker_hand.h"

constexpr CardNumber nothing_card_number = -1;  // カードがないことを表すダミー値

// 手札を配る
static void hand_out_cards(cards_type& deck, cards_type& hand_cards);

// カードを補充する
static void reload_cards(cards_type& deck, cards_type& hand_cards);

// カードの情報を出力する
static void print_cards(const cards_type& cards);

// 捨てるカードを選ぶときの出力
static void print_choose_discard(const cards_type& cards, const std::vector<bool>& is_discard);

// カードの文字列表現を返す
static std::string get_card_string(const Card& card);


int main()
{
    while (true) {

        // 山札を準備
        cards_type deck(trump_card_num);
        init_trump(deck);
        shuffle_cards(deck);

        // 最初の手札を配る
        cards_type player_cards {};
        hand_out_cards(deck, player_cards);

        // ユーザーにカードを提示
        std::cout << "あなたに配られたカードです。\n";
        print_cards(player_cards);

        // 捨てるカードを選ばせる
        std::vector<bool> is_discard(hand_card_num);
        while (true) {
            print_choose_discard(player_cards, is_discard);

            std::string input_string {};
            std::getline(std::cin, input_string);
            if (!std::cin) {
                std::cout << "入力が正しくありません。\n";
            }
            else if (input_string.empty()) {
                break;
            }
            else {
                int index {input_string[0] - '0'};  // 数字から整数に変換
                if (0 <= index && index < hand_card_num) {
                    is_discard[index] = !is_discard[index];
                }
                else {
                    std::cout << "入力が正しくありません。\n";
                }
            }
        }

        // 選ばれたカードを捨てる
        for (std::vector<bool>::size_type i = 0; i < is_discard.size(); ++i) {
            if (is_discard.at(i)) {
                auto& card = player_cards.at(i);
                std::cout << get_card_string(card) << " を捨てました。\n";

                // 何もないことをあらわすダミー値を入れる
                card.number = nothing_card_number;
            }
        }
        std::cout << "\n";

        // 新しいカードを補充する
        reload_cards(deck, player_cards);
        sort_cards(player_cards);
        std::cout << "\n";
        print_cards(player_cards);

        // 役判定
        auto poker_hand = judge_poker_hand(player_cards);
        if (poker_hand == PokerHand::no_pair) {
            std::cout << "残念でした。\n";
        }
        else {
            std::cout << get_poker_hand_string(poker_hand) << "ができました。\n";
        }

        // ゲームの続行確認
        std::cout << "\n"
                  << "ゲームを続けますか?\n"
                  << "はい: Y  いいえ: N\n";
        std::string input_string {};
        std::getline(std::cin, input_string);
        if (input_string == "Y" || input_string == "y") {
            continue;
        }
        else {
            break;
        }
    }
}


// 手札を配る
static void hand_out_cards(cards_type& deck, cards_type& hand_cards)
{
    while (hand_cards.size() < hand_card_num) {
        hand_cards.push_back(deck.back());
        deck.pop_back();
    }
    sort_cards(hand_cards);
}

// カードを補充する
static void reload_cards(cards_type& deck, cards_type& hand_cards)
{
    // 何もないことをあらわすダミー値が入っているところに、新しいカードを補充する
    for (cards_type::size_type i = 0; i < hand_cards.size(); ++i) {
        if (hand_cards.at(i).number == nothing_card_number) {
            auto& card = deck.back();

            std::cout << get_card_string(card) << " が配られました。\n";
            hand_cards.at(i) = card;
            deck.pop_back();
        }
    }
}

// カードの情報を出力する
static void print_cards(const cards_type& cards)
{
    for (cards_type::size_type i = 0; i < cards.size(); ++i) {
        auto& card {cards.at(i)};

        if (card.number == nothing_card_number) {
            std::cout << i << ": \n";
        }
        else {
            std::cout << i << ": " << get_card_string(card) << "\n";
        }
    }
}

// 捨てるカードを選ぶときの出力
static void print_choose_discard(const cards_type& cards, const std::vector<bool>& is_discard)
{
    std::cout << "\n"
              << "左端の番号を入力して、カードを選んでください。\n"
              << "何も入力せず Enterキーを押すと決定します。\n";

    for (cards_type::size_type i = 0; i < cards.size(); ++i) {
        std::cout << i << ": " << get_card_string(cards.at(i));

        // 捨てるカードに含まれているか
        if (is_discard[i]) {
            std::cout << " <-- 捨てる";
        }

        std::cout << "\n";
    }
}

// カードの文字列表現を返す
static std::string get_card_string(const Card& card)
{
    return get_mark_string(card.mark) + " " + get_card_number_string(card.number);
}

そして、get_card_string関数のコードを、CARD_MARK_STRING_KANAマクロの定義の有無で切り替えます。

// カードの文字列表現を返す
static std::string get_card_string(const Card& card)
{
#ifdef CARD_MARK_STRING_KANA
    return get_mark_string(card.mark) + "の" + get_card_number_string(card.number);
#else
    return get_mark_string(card.mark) + " " + get_card_number_string(card.number);
#endif
}

CARD_MARK_STRING_KANAマクロが定義された状態でビルドし、実行すると、次のような結果が得られます。

実行結果:

あなたに配られたカードです。
0: クラブの2
1: スペードの4
2: クラブの4
3: クラブの7
4: クラブの8

左端の番号を入力して、カードを選んでください。
何も入力せず Enterキーを押すと決定します。
0: クラブの2
1: スペードの4
2: クラブの4
3: クラブの7
4: クラブの8
1

左端の番号を入力して、カードを選んでください。
何も入力せず Enterキーを押すと決定します。
0: クラブの2
1: スペードの4 <-- 捨てる
2: クラブの4
3: クラブの7
4: クラブの8

スペードの4 を捨てました。

ハートの2 が配られました。

0: クラブの2
1: ハートの2
2: クラブの4
3: クラブの7
4: クラブの8
ワンペアができました。

ゲームを続けますか?
はい: Y  いいえ: N
n


現時点で、ポーカープログラムの全体像は次のようになっています。

// main.cpp
#include <iostream>
#include <sstream>
#include "card.h"
#include "poker_hand.h"

constexpr CardNumber nothing_card_number = -1;  // カードがないことを表すダミー値

// 手札を配る
static void hand_out_cards(cards_type& deck, cards_type& hand_cards);

// カードを補充する
static void reload_cards(cards_type& deck, cards_type& hand_cards);

// カードの情報を出力する
static void print_cards(const cards_type& cards);

// 捨てるカードを選ぶときの出力
static void print_choose_discard(const cards_type& cards, const std::vector<bool>& is_discard);

// カードの文字列表現を返す
static std::string get_card_string(const Card& card);


int main()
{
    while (true) {

        // 山札を準備
        cards_type deck(trump_card_num);
        init_trump(deck);
        shuffle_cards(deck);

        // 最初の手札を配る
        cards_type player_cards {};
        hand_out_cards(deck, player_cards);

        // ユーザーにカードを提示
        std::cout << "あなたに配られたカードです。\n";
        print_cards(player_cards);

        // 捨てるカードを選ばせる
        std::vector<bool> is_discard(hand_card_num);
        while (true) {
            print_choose_discard(player_cards, is_discard);

            std::string input_string {};
            std::getline(std::cin, input_string);
            if (!std::cin) {
                std::cout << "入力が正しくありません。\n";
            }
            else if (input_string.empty()) {
                break;
            }
            else {
                int index {input_string[0] - '0'};  // 数字から整数に変換
                if (0 <= index && index < hand_card_num) {
                    is_discard[index] = !is_discard[index];
                }
                else {
                    std::cout << "入力が正しくありません。\n";
                }
            }
        }

        // 選ばれたカードを捨てる
        for (std::vector<bool>::size_type i = 0; i < is_discard.size(); ++i) {
            if (is_discard.at(i)) {
                auto& card = player_cards.at(i);
                std::cout << get_card_string(card) << " を捨てました。\n";

                // 何もないことをあらわすダミー値を入れる
                card.number = nothing_card_number;
            }
        }
        std::cout << "\n";

        // 新しいカードを補充する
        reload_cards(deck, player_cards);
        sort_cards(player_cards);
        std::cout << "\n";
        print_cards(player_cards);

        // 役判定
        auto poker_hand = judge_poker_hand(player_cards);
        if (poker_hand == PokerHand::no_pair) {
            std::cout << "残念でした。\n";
        }
        else {
            std::cout << get_poker_hand_string(poker_hand) << "ができました。\n";
        }

        // ゲームの続行確認
        std::cout << "\n"
                  << "ゲームを続けますか?\n"
                  << "はい: Y  いいえ: N\n";
        std::string input_string {};
        std::getline(std::cin, input_string);
        if (input_string == "Y" || input_string == "y") {
            continue;
        }
        else {
            break;
        }
    }
}


// 手札を配る
static void hand_out_cards(cards_type& deck, cards_type& hand_cards)
{
    while (hand_cards.size() < hand_card_num) {
        hand_cards.push_back(deck.back());
        deck.pop_back();
    }
    sort_cards(hand_cards);
}

// カードを補充する
static void reload_cards(cards_type& deck, cards_type& hand_cards)
{
    // 何もないことをあらわすダミー値が入っているところに、新しいカードを補充する
    for (cards_type::size_type i = 0; i < hand_cards.size(); ++i) {
        if (hand_cards.at(i).number == nothing_card_number) {
            auto& card = deck.back();

            std::cout << get_card_string(card) << " が配られました。\n";
            hand_cards.at(i) = card;
            deck.pop_back();
        }
    }
}

// カードの情報を出力する
static void print_cards(const cards_type& cards)
{
    for (cards_type::size_type i = 0; i < cards.size(); ++i) {
        auto& card {cards.at(i)};

        if (card.number == nothing_card_number) {
            std::cout << i << ": \n";
        }
        else {
            std::cout << i << ": " << get_card_string(card) << "\n";
        }
    }
}

// 捨てるカードを選ぶときの出力
static void print_choose_discard(const cards_type& cards, const std::vector<bool>& is_discard)
{
    std::cout << "\n"
              << "左端の番号を入力して、カードを選んでください。\n"
              << "何も入力せず Enterキーを押すと決定します。\n";

    for (cards_type::size_type i = 0; i < cards.size(); ++i) {
        std::cout << i << ": " << get_card_string(cards.at(i));

        // 捨てるカードに含まれているか
        if (is_discard[i]) {
            std::cout << " <-- 捨てる";
        }

        std::cout << "\n";
    }
}

// カードの文字列表現を返す
static std::string get_card_string(const Card& card)
{
#ifdef CARD_MARK_STRING_KANA
    return get_mark_string(card.mark) + "の" + get_card_number_string(card.number);
#else
    return get_mark_string(card.mark) + " " + get_card_number_string(card.number);
#endif
}
// poker_hand.cpp
#include "poker_hand.h"
#include <algorithm>

// ストレートが成立しているか判定する
static bool is_straight(const cards_type& cards)
{
    // [!] 1 を巻き込むストレートは、1・10・11・12・13 か 1・2・3・4・5 のいずれかしか認めないルールもあるが、
    //     ここでは、とにかく連続していればいいことにしている。

    // 13 から 1 に戻ってくるケースを先に判定
    if (cards[0].number == 1 && cards[4].number == 13) {
        return cards[1].number ==  2 && cards[2].number ==  3 && cards[3].number ==  4
            || cards[1].number ==  2 && cards[2].number ==  3 && cards[3].number == 12
            || cards[1].number ==  2 && cards[2].number == 11 && cards[3].number == 12
            || cards[1].number == 10 && cards[2].number == 11 && cards[3].number == 12
            ;
    }

    return cards[0].number + 1 == cards[1].number
        && cards[1].number + 1 == cards[2].number
        && cards[2].number + 1 == cards[3].number
        && cards[3].number + 1 == cards[4].number
        ;
}

// 役を判定する
PokerHand judge_poker_hand(const cards_type& cards)
{
    if (cards.size() != hand_card_num) {
        return PokerHand::no_pair;
    }
    if (std::is_sorted(std::cbegin(cards), std::cend(cards), cards_sort_compare) == false) {
        return PokerHand::no_pair;
    }

    // フラッシュ系
    if (cards[0].mark == cards[1].mark
     && cards[0].mark == cards[2].mark
     && cards[0].mark == cards[3].mark
     && cards[0].mark == cards[4].mark
    ) {
        // ロイヤルストレートフラッシュ
        if (cards[0].number == 1
         && cards[1].number == 10
         && cards[2].number == 11
         && cards[3].number == 12
         && cards[4].number == 13
        ) {
            return PokerHand::royal_straight_flush;
        }

        // ストレートフラッシュ
        if (is_straight(cards)) {
            return PokerHand::straight_flush;
        }
        
        // フラッシュ
        return PokerHand::flush;
    }

    // ストレート
    if (is_straight(cards)) {
        return PokerHand::straight;
    }

    // 数字ごとのカードの枚数を数える
    std::vector<int> number_count(each_mark_card_num + 1);
    for (auto& card : cards) {
        number_count.at(card.number) += 1;
    }

    // 一番枚数が多い数字
    int number_count_max {*std::max_element(std::cbegin(number_count), std::cend(number_count))};

    // フォーカード
    if (number_count_max == 4) {
        return PokerHand::four_cards;
    }

    // フルハウス or スリーカード
    if (number_count_max == 3) {

        // 2枚ある番号が存在すればフルハウス
        if (std::find(std::cbegin(number_count), std::cend(number_count), 2) != std::cend(number_count)) {
            return PokerHand::full_house;
        }

        // スリーカード
        return PokerHand::three_cards;
    }

    // ツーペア or ワンペア
    if (number_count_max == 2) {

        // 2枚ある番号が2つあればツーペア
        if (std::count(std::cbegin(number_count), std::cend(number_count), 2) == 2) {
            return PokerHand::two_pairs;
        }

        // ワンペア
        return PokerHand::one_pair;
    }

    // ノーペア
    return PokerHand::no_pair;
}

// 役の文字列表現を返す
std::string get_poker_hand_string(PokerHand poker_hand)
{
    switch (poker_hand) {
    case PokerHand::no_pair:                return "ノーペア";
    case PokerHand::one_pair:               return "ワンペア";
    case PokerHand::two_pairs:              return "ツーペア";
    case PokerHand::three_cards:            return "スリーカード";
    case PokerHand::straight:               return "ストレート";
    case PokerHand::flush:                  return "フラッシュ";
    case PokerHand::full_house:             return "フルハウス";
    case PokerHand::four_cards:             return "フォーカード";
    case PokerHand::straight_flush:         return "ストレートフラッシュ";
    case PokerHand::royal_straight_flush:   return "ロイヤルストレートフラッシュ";
    default:                                return "";
    }
}
// poker_hand.h
#ifndef POKER_HAND_H_INCLUDED
#define POKER_HAND_H_INCLUDED

#include <string>
#include "card.h"

constexpr auto hand_card_num = 5;               // 手札の枚数

// 役
enum class PokerHand : signed char {
    no_pair,
    one_pair,
    two_pairs,
    three_cards,
    straight,
    flush,
    full_house,
    four_cards,
    straight_flush,
    royal_straight_flush,
};

// 役を判定する
// cards: 手札 (要素数は 5。card.h の cards_sort_compare() が定義する順序で整列されていなければならない)
// 戻り値: 役
PokerHand judge_poker_hand(const cards_type& cards);

// 役の文字列表現を返す
// poker_hand: 役
// 戻り値: 役の名前
std::string get_poker_hand_string(PokerHand poker_hand);

#endif
// card.cpp
#include "card.h"
#include <algorithm>
#include <random>

// トランプを初期化する
void init_trump(cards_type& cards)
{
    for (auto i = 0; i < each_mark_card_num; ++i) {
        auto number = static_cast<CardNumber>(i + 1);
        cards.at(i + each_mark_card_num * 0).number = number;
        cards.at(i + each_mark_card_num * 0).mark = CardMark::spade;
        cards.at(i + each_mark_card_num * 1).number = number;
        cards.at(i + each_mark_card_num * 1).mark = CardMark::club;
        cards.at(i + each_mark_card_num * 2).number = number;
        cards.at(i + each_mark_card_num * 2).mark = CardMark::diamond;
        cards.at(i + each_mark_card_num * 3).number = number;
        cards.at(i + each_mark_card_num * 3).mark = CardMark::heart;
    }
}

// カードをシャッフルする
void shuffle_cards(cards_type& cards)
{
    std::random_device rand_dev {};
    std::mt19937 rand_engine(rand_dev());
    std::shuffle(std::begin(cards), std::end(cards), rand_engine);
}

// カードを整列する
void sort_cards(cards_type& cards)
{
    std::sort(std::begin(cards), std::end(cards), cards_sort_compare);
}

// カードの整列に使う比較関数
bool cards_sort_compare(const Card& a, const Card& b)
{
    if (a.number == b.number) {
        return a.mark < b.mark;
    }
    return a.number < b.number;
}

// カードの数字の文字列表現を返す
std::string get_card_number_string(CardNumber number)
{
    switch (number) {
    case 1:     return "A";
    case 2:     return "2";
    case 3:     return "3";
    case 4:     return "4";
    case 5:     return "5";
    case 6:     return "6";
    case 7:     return "7";
    case 8:     return "8";
    case 9:     return "9";
    case 10:    return "10";
    case 11:    return "J";
    case 12:    return "Q";
    case 13:    return "K";
    default:    return "";
    }
}

// カードのマークの文字列表現を返す
std::string get_mark_string(CardMark card_mark)
{
#ifdef CARD_MARK_STRING_KANA
    switch (card_mark) {
    case CardMark::spade:
        return "スペード";
    case CardMark::club:
        return "クラブ";
    case CardMark::diamond:
        return "ダイヤ";
    case CardMark::heart:
        return "ハート";
    default:
        return "";
    }
#else
    switch (card_mark) {
    case CardMark::spade:
        return "spade";
    case CardMark::club:
        return "club";
    case CardMark::diamond:
        return "diamond";
    case CardMark::heart:
        return "heart";
    default:
        return "";
    }
#endif
}
// card.h
#ifndef CARD_H_INCLUDED
#define CARD_H_INCLUDED

#include <string>
#include <vector>

// カードの数字の型
using CardNumber = signed char;

// カードのマークの型
enum class CardMark : signed char {
    spade,
    club,
    diamond,
    heart,
};

// カード型
struct Card {
    CardNumber number;  // 数字
    CardMark mark;      // マーク
};

// カードの集まりを表す型
using cards_type = std::vector<Card>;

constexpr auto card_mark_num = 4;                                    // マークの総数
constexpr auto each_mark_card_num = 13;                              // 各マークのカードの枚数
constexpr auto trump_card_num = each_mark_card_num * card_mark_num;  // トランプに含まれるカードの枚数


// トランプを初期化する
// cards: カードの集まり
void init_trump(cards_type& cards);

// カードをシャッフルする
// cards: カードの集まり
void shuffle_cards(cards_type& cards);

// カードを整列する
// cards: カードの集まり
void sort_cards(cards_type& cards);

// カードの整列に使う比較関数
// a: 対象のカード
// b: 対象のカード
// 戻り値: a と b が適切な順番で並んでいたら true、並んでいなければ false
bool cards_sort_compare(const Card& a, const Card& b);

// カードの数字の文字列表現を返す
// たとえば、3 は "3"、1 は "A"、13 は "K" と表現される。
//
// number: カードの数字
// 戻り値: カードの数字を文字列で表現したもの
std::string get_card_number_string(CardNumber number);

// カードのマークの文字列表現を返す
// card_mark: カードのマーク
// 戻り値: カードのマークを文字列で表現したもの
std::string get_mark_string(CardMark card_mark);

#endif

問題5 (調査★★★)

使っているコンパイラが実装しているプラグマについて調べてみてください。


使っているコンパイラが Visual Studio なら、Microsoft のドキュメントを確認します。Microsoft のドキュメントは、Microsoft Docs という Webサイトにあります。ここには、多数ある Microsoft 製品のドキュメントが集められているので、そこから絞り込んでいくか、検索機能を活用します。あるいは、Google検索などで「VisualStudio pragma」などを検索してみるのでもいいでしょう。

Visual Studio のプラグマについては、「プラグマ ディレクティブと __ pragma キーワードと _Pragmaキーワード」というページにまとめられています。Microsoft Docs の日本語訳はかなり酷いものなので、英語で読むか(画面右上に「英語で読む」というボタンがあります)、英語ページを Google翻訳などで日本語訳しなおして読む方がいいかもしれません。


参考リンク



更新履歴




はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る