先頭へ戻る

関数から値を返す 解答ページ | Programming Place Plus 新C++編

Programming Place Plus トップページ新C++編関数から値を返す

先頭へ戻る

このページの概要

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



解答・解説

問題1 (確認★)

次の関数 f1、f2、f3、f4 の戻り値の型は何になりますか?

auto f1()
{
    std::pair<int, std::string> result {};
    // ...
    return result;
}

auto f2(int value)
{
    if (value < 0) {
        return -value;
    }
    return value;
}

auto f3(std::vector<int>& vec)
{
    for (int& e : vec) {
        e = 0;
    }
}

auto f4(std::vector<int>& vec)
{
    for (int& e : vec) {
        e = 0;
    }
    return vec;
}


戻り値の型が auto(あるいは auto&)で、戻り値をうしろに書く構文でなければ、return文の内容に応じて型推論されます(本編解説)。

f1関数は return result; となっているので、変数result の型になります。したがって std::pair<int, std::string>型に型推論されます。

f2関数は return文が2箇所にあります。return文が複数ある場合、それぞれが同じ型になるのなら、その型に推論されます。異なる型になってしまう場合は、コンパイルエラーです。return value; は変数value が int型なので int型に推論されます。return -value; の方は、int型の値に対して負符号演算子を適用していますが、この結果もやはり int型です。いずれも int型なので、f2関数の戻り値の型は int型です。

f3関数には return文がありません。この場合、戻り値の型は void になります。

f4関数は、return vec; です。vec の型は std::vector<int>& です。auto による型推論では、参照型に推論されないというルールがあるため、この場合、戻り値の型は std::vector<int>型になります。

問題2 (確認★)

次の関数には問題があります。問題がないように書き換えてください。

// 0 からはじまる連番値を生成する
// length: 生成される数値の列の長さ
// 戻り値: 生成された数値の列
std::vector<int>& generate_numbers(int length)
{
    std::vector<int> numbers {};
    for (int i = 0; i < length; ++i) {
        numbers.push_back(i);
    }
    return numbers;
}


この関数がしていることは、ste::vector<int> に 0 から始まる連番値を格納することです。おそらく想定では、次のような使い方を望んだのでしょう。

auto numbers = generate_numbers(100);  // 0~99 が入った std::vector<int> を作る

この関数の問題点は、関数内で宣言した変数 numbers を参照型で返してしまっていることです。関数内で宣言した変数は、その関数から抜け出したあとに使うことはできないため、別名を返したところで、その別名になった変数numbers をアクセスする行為は未定義の動作です(本編解説)。Visual Studio 2015 などのコンパイラは、このプログラムをコンパイルエラーにしてしまいます。

書き換えの方法としてもっとも簡単なのは、戻り値の型を std::vector<int> に変更することです。参照型でなく、コピーとして返せば安全になります。

#include <iostream>
#include <vector>

// 0 からはじまる連番値を生成する
// length: 生成される数値の列の長さ
// 戻り値: 生成された数値の列
std::vector<int> generate_numbers(int length)
{
    std::vector<int> numbers {};
    for (int i = 0; i < length; ++i) {
        numbers.push_back(i);
    }
    return numbers;
}

int main()
{
    auto numbers = generate_numbers(100);  // 0~99 が入った std::vector<int> を作る

    for (int e : numbers) {
        std::cout << e << " ";
    }
}

実行結果:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

ただし、コピーにかかる処理時間のコストが新たな問題になるかもしれません(本当にそれが問題なのかどうかはよく検証するべきです)。もう1つの方法は、参照型の仮引数を使うことです。

#include <iostream>
#include <vector>

// 0 からはじまる連番値を生成する
// length: 生成される数値の列の長さ
// 戻り値: 生成された数値の列
void generate_numbers(int length, std::vector<int>& numbers)
{
    for (int i = 0; i < length; ++i) {
        numbers.push_back(i);
    }
}

int main()
{
    std::vector<int> numbers {};
    generate_numbers(100, numbers);  // 0~99 が入った std::vector<int> を作る

    for (int e : numbers) {
        std::cout << e << " ";
    }
}

実行結果:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

この方法は効率が良いですが、呼び出し元で適切な std::vector を宣言しなければならず、使う側としてはやや不便になります。

この書き換えを行うのなら、関数内で clear関数を呼んで、確実に連番値だけが格納されるようにしたほうがいいかもしれません。

問題3 (基本★)

std::vector<int> に、指定した値の要素が含まれているかどうかを調べて、結果を bool型の戻り値で返す contains関数を作成してください。


目的の値を持った要素は std::find関数で探せます(「要素を探索する」のページを参照)が、少々記述が長くなる不便さがあります。また、if文の結果が true のときと false のときのどちらが、要素が存在した場合なのかが一瞬わかりづらいことも欠点かもしれません。

if (std::find(std::cbegin(v), std::cend(v), 3) != std::cend(v)) {
    std::cout << "found.\n";
}
else {
    std::cout << "not found.\n";
}

自作する関数では、次のように書けます。

if (contains(v, 3)) {
    std::cout << "found.\n";
}
else {
    std::cout << "not found.\n";
}

記述量が大幅に減り、true のときに要素が存在している、false のときに要素が存在していないという自然なかたちになります。

contains関数の定義と、それを使うプログラムは次のように書けます。

#include <iostream>
#include <vector>

// 指定の値を持った要素が含まれているか調べる。
// vec: 対象の配列
// value: 探したい値
// 戻り値: vec に value が含まれていたら true、含まれていなかったら false
bool contains(const std::vector<int>& vec, int value)
{
    return std::find(std::cbegin(vec), std::cend(vec), value) != std::cend(vec);
}

int main()
{
    std::vector<int> v {2, 3, 4, 6, 7};

    if (contains(v, 3)) {
        std::cout << "found.\n";
    }
    else {
        std::cout << "not found.\n";
    }
    if (contains(v, 5)) {
        std::cout << "found.\n";
    }
    else {
        std::cout << "not found.\n";
    }
}

実行結果:

found.
not found.

問題4 (基本★★)

std::vector<int> の要素のうち、偶数番目の要素(0,2,4,・・・) と、奇数番目の要素(1,3,5,・・・)の合計値をそれぞれ計算し、std::pair<int, int> で返す関数を作成してください。要素が不足している場合は 0 とします。


たとえば次のように書けます。

#include <iostream>
#include <utility>
#include <vector>

// 偶数番目の要素と、奇数番目の要素の合計値を返す。
// vec: 対象の配列。
// 戻り値: 2つの合計値のペア。first に偶数番目の要素の合計、second に奇数番目の要素の合計が入る。
std::pair<int, int> total_every_other_elements(const std::vector<int>& vec)
{
    std::pair<int, int> totals {0, 0};

    for (std::vector<int>::size_type i = 0; i < vec.size(); i += 2) {
        totals.first += vec.at(i);
    }
    for (std::vector<int>::size_type i = 1; i < vec.size(); i += 2) {
        totals.second += vec.at(i);
    }

    return totals;
}

int main()
{
    std::vector<int> v {2, 3, 3, 5, 6, 7, 10, 13};

    auto totals = total_every_other_elements(v);
    std::cout << totals.first << ", " << totals.second << "\n";
}

実行結果:

21, 28

std::tie関数を使うと、std::pair の要素を分解して、戻り値を別個の変数に受け取ることもできます(本編解説)。

#include <iostream>
#include <tuple>
#include <utility>
#include <vector>

// 偶数番目の要素と、奇数番目の要素の合計値を返す。
// vec: 対象の配列。
// 戻り値: 2つの合計値のペア。first に偶数番目の要素の合計、second に奇数番目の要素の合計が入る。
std::pair<int, int> total_every_other_elements(const std::vector<int>& vec)
{
    std::pair<int, int> totals {0, 0};

    for (std::vector<int>::size_type i = 0; i < vec.size(); i += 2) {
        totals.first += vec.at(i);
    }
    for (std::vector<int>::size_type i = 1; i < vec.size(); i += 2) {
        totals.second += vec.at(i);
    }

    return totals;
}

int main()
{
    std::vector<int> v {2, 3, 3, 5, 6, 7, 10, 13};

    int even_numbered_total {};
    int odd_numbered_total {};
    std::tie(even_numbered_total, odd_numbered_total) = total_every_other_elements(v);
    std::cout << even_numbered_total << ", " << odd_numbered_total << "\n";
}

実行結果:

21, 28

問題5 (応用★★)

蔵書リストのプログラムで、各コマンドの処理をそれぞれ別個の関数に切り分けてください。

現時点での蔵書リストのプログラムのソースコードは、前のページの練習問題の解答ページにあります。


たとえば次のようになります。

#include <algorithm>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

constexpr auto booklist_path = "booklist.txt"; // 蔵書リストファイルのパス

struct Book {
    std::string    title;      // 書名
    std::string    author;     // 著者名
    int            price;      // 価格
    int            evaluate;   // 評価
};

// 本のデータを標準出力へ書き出す。
// book: 本のデータ
void print_book_data(const Book& book);

// 「a: 本を登録する」
// books: 本のデータの配列
// 戻り値: 登録できたら true、失敗したら false
bool command_add(std::vector<Book>& books);

// 「d: 本を削除する」
// books: 本のデータの配列
// 戻り値: 削除できたら true、失敗したら false
bool command_delete(std::vector<Book>& books);

// 「c: すべての本を削除する」
// books: 本のデータの配列
void command_clear(std::vector<Book>& books);

// 「p: リストを確認する」
// books: 本のデータの配列
void command_print(const std::vector<Book>& books);

// 「st: 書名で検索する」
// books: 本のデータの配列
void command_search_by_title(const std::vector<Book>& books);

//「sa: 著者名で検索する」
// books: 本のデータの配列
void command_search_by_author(const std::vector<Book>& books);

// 「sp: 価格で検索する」
// books: 本のデータの配列
void command_search_by_price(const std::vector<Book>& books);

// 「se: 評価で検索する」
// books: 本のデータの配列
void command_search_by_evaluate(const std::vector<Book>& books);

// 「r: ファイルから読み込む」
// books: 本のデータの配列
// 戻り値: 成功したら true、失敗したら false
bool command_read_file(std::vector<Book>& books);

// 「w: ファイルに書き込む」
// books: 本のデータの配列
// 戻り値: 成功したら true、失敗したら false
bool command_write_file(const std::vector<Book>& books);

// 「e: 終了する」
// isChanged: 変更があるかどうか
// 戻り値: 終了できる場合は true、終了しない場合は false
bool command_exit(bool isChanged);


int main()
{
    std::vector<Book> books {};
    bool isChanged {false};

    while (true) {
        std::cout << "----------------------------\n"
                  << "コマンドを入力してください。\n"
                  << "a: 本を登録する。\n"
                  << "d: 本を削除する。\n"
                  << "c: すべての本を削除する。\n"
                  << "p: リストを確認する。\n"
                  << "st: 書名で検索する。\n"
                  << "sa: 著者名で検索する。\n"
                  << "sp: 価格で検索する。\n"
                  << "se: 評価で検索する。\n"
                  << "r: ファイルから読み込む。\n"
                  << "w: ファイルに書き込む。\n"
                  << "e: 終了する。\n"
                  << "----------------------------\n";

        std::string command {};
        std::getline(std::cin, command);
        if (command == "a") {
            if (command_add(books)) {
                isChanged = true;
            }
        }
        else if (command == "d") {
            if (command_delete(books)) {
                isChanged = true;
            }
        }
        else if (command == "c") {
            command_clear(books);
            isChanged = true;
        }
        else if (command == "p") {
            command_print(books);
        }
        else if (command == "st") {
            command_search_by_title(books);
        }
        else if (command == "sa") {
            command_search_by_author(books);
        }
        else if (command == "sp") {
            command_search_by_price(books);
        }
        else if (command == "se") {
            command_search_by_evaluate(books);
        }
        else if (command == "r") {
            if (command_read_file(books)) {
                isChanged = false;
            }
        }
        else if (command == "w") {
            if (command_write_file(books)) {
                isChanged = false;
            }
        }
        else if (command == "e") {
            if (command_exit(isChanged)) {
                return 0;
            }
        }
        else {
            // 無効なコマンド
            continue;
        }
    }
}

void print_book_data(const Book& book)
{
    std::cout << "書名: " << book.title << "\n"
              << "著者名: " << book.author << "\n"
              << "価格: " << book.price << "\n"
              << "評価: " << book.evaluate << "\n"
              << "\n";
}

bool command_add(std::vector<Book>& books)
{
    Book book {};
    std::istringstream iss {};
    std::string tmp {};

    std::cout << "書名を入力してください。\n";
    std::getline(std::cin, book.title);

    std::cout << "著者名を入力してください。\n";
    std::getline(std::cin, book.author);

    std::cout << "価格を入力してください。\n";
    while (true) {
        std::getline(std::cin, tmp);
        iss.str(tmp);
        iss >> book.price;
        if (!iss || book.price < 0) {
            std::cout << "価格は 0以上の整数で入力してください。\n";
            iss.clear();
        }
        else {
            break;
        }
    }

    std::cout << "評価を入力してください。\n";
    std::getline(std::cin, tmp);
    iss.clear();
    iss.str(tmp);
    iss >> book.evaluate;
    if (!iss) {
        std::cout << "入力内容に不備があります。\n";
        return false;
    }
    else {
        books.push_back(book);
        std::cout << "登録しました。\n";
        return true;
    }
}

bool command_delete(std::vector<Book>& books)
{
    std::cout << "書名を入力してください。\n";
    std::string title {};
    std::getline(std::cin, title);

    auto it = std::find_if(std::cbegin(books), std::cend(books), [title](Book e){ return e.title == title; });
    if (it == std::cend(books)) {
        std::cout << "指定された本は登録されていません。\n";
        return false;
    }
    else {
        books.erase(it);
        std::cout << "削除しました。\n";
        return true;
    }
}

void command_clear(std::vector<Book>& books)
{
    books.clear();
    std::cout << "削除しました。\n";
}

void command_print(const std::vector<Book>& books)
{
    for (auto& book : books) {
        print_book_data(book);
    }
}

void command_search_by_title(const std::vector<Book>& books)
{
    std::cout << "書名を入力してください。\n";
    std::string title {};
    std::getline(std::cin, title);

    for (auto& book : books) {
        if (book.title == title) {
            print_book_data(book);
        }
    }
}

void command_search_by_author(const std::vector<Book>& books)
{
    std::cout << "著者名を入力してください。\n";
    std::string author {};
    std::getline(std::cin, author);

    for (auto& book : books) {
        if (book.author == author) {
            print_book_data(book);
        }
    }
}

void command_search_by_price(const std::vector<Book>& books)
{
    std::cout << "価格を入力してください。\n";
    std::string tmp {};
    std::getline(std::cin, tmp);
    std::istringstream iss {tmp};
    int price {};
    iss >> price;
    if (!iss || price < 0) {
        std::cout << "入力内容に不備があります。\n";
    }
    else {
        for (auto& book : books) {
            if (book.price >= price) {
                print_book_data(book);
            }
        }
    }
}

void command_search_by_evaluate(const std::vector<Book>& books)
{
    std::cout << "評価を入力してください。\n";
    std::string tmp {};
    std::getline(std::cin, tmp);
    std::istringstream iss {tmp};
    int evaluate {};
    iss >> evaluate;
    if (!iss) {
        std::cout << "入力内容に不備があります。\n";
    }
    else {
        for (auto& book : books) {
            if (book.evaluate >= evaluate) {
                print_book_data(book);
            }
        }
    }
}

bool command_read_file(std::vector<Book>& books)
{
    std::ifstream ifs(booklist_path);
    if (!ifs) {
        std::cout << "ファイルが開けません。\n";
        return false;
    }

    // メモリ上の本のデータを消す
    books.clear();

    while (!ifs.eof()) {
        Book book {};
        std::istringstream iss {};
        std::string tmp {};

        std::getline(ifs, book.title);
        if (book.title.empty()) {
            break;
        }

        std::getline(ifs, book.author);

        std::getline(ifs, tmp);
        iss.str(tmp);
        iss >> book.price;
        iss.clear();

        std::getline(ifs, tmp);
        iss.str(tmp);
        iss >> book.evaluate;

        books.push_back(book);
    }

    std::cout << "読み込みました。\n";
    return true;
}

bool command_write_file(const std::vector<Book>& books)
{
    std::ofstream ofs(booklist_path);
    if (!ofs) {
        std::cout << "ファイルが開けません。\n";
        return false;
    }

    for (auto& book : books) {
        ofs << book.title << "\n"
            << book.author << "\n"
            << book.price << "\n"
            << book.evaluate << "\n";
    }

    ofs.close();
    if (!ofs) {
        std::cout << "書き込みに失敗しました。\n";
        return false;
    }

    std::cout << "書き込みました。\n";
    return true;
}

bool command_exit(bool isChanged)
{
    if (isChanged) {
        std::cout << "変更した内容をファイルに書き込んでいませんが、終了してよろしいですか? (Y/N)\n";
        std::string s {};
        std::cin >> s;
        if (s != "Y" && s != "y") {
            return false;
        }
    }

    std::cout << "終了します。\n";
    return true;
}

変数books と isChanged を使わなければならない場合、それぞれを引数で渡すようにしています。books の方は大きなデータになるので参照型で渡しており、関数内での書き換えが必要でなければ const参照にしてあります。

変数isChanged の書き換えは main関数の側でおこなう方針にしています(これも参照型で渡せば関数内で書き換えられますが、あまり参照型の仮引数に頼りすぎると分かりづらくなります)。変数isChanged は、コマンドの実行が無事に行えたかどうかによって変化するものなので、その情報を戻り値で返し、呼び出し元の判断によって書き換えを行います。

現時点で説明している機能でできることを考えて、このような方法を採っています。books や isChanged が、それぞれの関数から共有して使えるようなところにあれば、引数や戻り値で情報を受け渡す必要がなくなり、コードはよりシンプルになります。

問題6 (応用★★)

蔵書リストのプログラムには、標準入力から int型の整数を受け取る箇所がいくつかあります。この処理を関数に切り分けてください。


該当のコードは、たとえば以下のような部分です。

std::string tmp {};
std::getline(std::cin, tmp);
std::istringstream iss {tmp};
int evaluate {};
iss >> evaluate;
if (!iss) {
    // 失敗している(int型の値が得られていない)
}
else {
    // 成功
}

int型の値の入力を得るには、いったん std::getline関数を使って文字列を受け取り、そこから整数に変換しなければなりません。また、入力された内容が int型では表現できない可能性もあり、エラーチェックも必要で、やりたいことは単純なのにコード量は多くなっています。そのうえ頻繁に必要になる処理なので、関数に切り出しておく価値があります。

関数はたとえば次のように定義できます。

// 標準入力から int型の値を受け取る。
// 戻り値: 入力された int型の値と、処理の成否を格納した std::pair。
std::pair<int, bool> get_int_from_stdin(void)
{
    std::pair<int, bool> result {};

    std::string tmp {};
    std::getline(std::cin, tmp);
    std::istringstream iss {tmp};
    iss >> result.first;
    if (!iss) {
        result.second = false;
    }
    else {
        result.second = true;
    }

    return result;
}

エラーが起きているなら、そのことを呼び出し元に教えなければならないので、入力された int型の値と、エラーの有無をあらわす bool型の値の2つを返す必要があります。方法はいくつかありえますが、ここでは std::pair を使ってペアにして返すことにします。

プログラム全体はこうなります。

#include <algorithm>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

constexpr auto booklist_path = "booklist.txt"; // 蔵書リストファイルのパス

struct Book {
    std::string    title;      // 書名
    std::string    author;     // 著者名
    int            price;      // 価格
    int            evaluate;   // 評価
};

// 標準入力から int型の値を受け取る。
// 戻り値: 入力された int型の値と、処理の成否を格納した std::pair。
std::pair<int, bool> get_int_from_stdin(void);

// 本のデータを標準出力へ書き出す。
// book: 本のデータ
void print_book_data(const Book& book);

// 「a: 本を登録する」
// books: 本のデータの配列
// 戻り値: 登録できたら true、失敗したら false
bool command_add(std::vector<Book>& books);

// 「d: 本を削除する」
// books: 本のデータの配列
// 戻り値: 削除できたら true、失敗したら false
bool command_delete(std::vector<Book>& books);

// 「c: すべての本を削除する」
// books: 本のデータの配列
void command_clear(std::vector<Book>& books);

// 「p: リストを確認する」
// books: 本のデータの配列
void command_print(const std::vector<Book>& books);

// 「st: 書名で検索する」
// books: 本のデータの配列
void command_search_by_title(const std::vector<Book>& books);

//「sa: 著者名で検索する」
// books: 本のデータの配列
void command_search_by_author(const std::vector<Book>& books);

// 「sp: 価格で検索する」
// books: 本のデータの配列
void command_search_by_price(const std::vector<Book>& books);

// 「se: 評価で検索する」
// books: 本のデータの配列
void command_search_by_evaluate(const std::vector<Book>& books);

// 「r: ファイルから読み込む」
// books: 本のデータの配列
// 戻り値: 成功したら true、失敗したら false
bool command_read_file(std::vector<Book>& books);

// 「w: ファイルに書き込む」
// books: 本のデータの配列
// 戻り値: 成功したら true、失敗したら false
bool command_write_file(const std::vector<Book>& books);

// 「e: 終了する」
// isChanged: 変更があるかどうか
// 戻り値: 終了できる場合は true、終了しない場合は false
bool command_exit(bool isChanged);


int main()
{
    std::vector<Book> books {};
    bool isChanged {false};

    while (true) {
        std::cout << "----------------------------\n"
                  << "コマンドを入力してください。\n"
                  << "a: 本を登録する。\n"
                  << "d: 本を削除する。\n"
                  << "c: すべての本を削除する。\n"
                  << "p: リストを確認する。\n"
                  << "st: 書名で検索する。\n"
                  << "sa: 著者名で検索する。\n"
                  << "sp: 価格で検索する。\n"
                  << "se: 評価で検索する。\n"
                  << "r: ファイルから読み込む。\n"
                  << "w: ファイルに書き込む。\n"
                  << "e: 終了する。\n"
                  << "----------------------------\n";

        std::string command {};
        std::getline(std::cin, command);
        if (command == "a") {
            if (command_add(books)) {
                isChanged = true;
            }
        }
        else if (command == "d") {
            if (command_delete(books)) {
                isChanged = true;
            }
        }
        else if (command == "c") {
            command_clear(books);
            isChanged = true;
        }
        else if (command == "p") {
            command_print(books);
        }
        else if (command == "st") {
            command_search_by_title(books);
        }
        else if (command == "sa") {
            command_search_by_author(books);
        }
        else if (command == "sp") {
            command_search_by_price(books);
        }
        else if (command == "se") {
            command_search_by_evaluate(books);
        }
        else if (command == "r") {
            if (command_read_file(books)) {
                isChanged = false;
            }
        }
        else if (command == "w") {
            if (command_write_file(books)) {
                isChanged = false;
            }
        }
        else if (command == "e") {
            if (command_exit(isChanged)) {
                return 0;
            }
        }
        else {
            // 無効なコマンド
            continue;
        }
    }
}

std::pair<int, bool> get_int_from_stdin(void)
{
    std::pair<int, bool> result {};

    std::string tmp {};
    std::getline(std::cin, tmp);
    std::istringstream iss {tmp};
    iss >> result.first;
    if (!iss) {
        result.second = false;
    }
    else {
        result.second = true;
    }

    return result;
}

void print_book_data(const Book& book)
{
    std::cout << "書名: " << book.title << "\n"
              << "著者名: " << book.author << "\n"
              << "価格: " << book.price << "\n"
              << "評価: " << book.evaluate << "\n"
              << "\n";
}

bool command_add(std::vector<Book>& books)
{
    Book book {};

    std::cout << "書名を入力してください。\n";
    std::getline(std::cin, book.title);

    std::cout << "著者名を入力してください。\n";
    std::getline(std::cin, book.author);

    std::cout << "価格を入力してください。\n";
    while (true) {
        auto price = get_int_from_stdin();
        if (!price.second || price.first < 0) {
            std::cout << "価格は 0以上の整数で入力してください。\n";
        }
        else {
            book.price = price.first;
            break;
        }
    }

    std::cout << "評価を入力してください。\n";
    auto evaluate = get_int_from_stdin();
    if (!evaluate.second) {
        std::cout << "入力内容に不備があります。\n";
        return false;
    }
    else {
        book.evaluate = evaluate.first;
        books.push_back(book);
        std::cout << "登録しました。\n";
        return true;
    }
}

bool command_delete(std::vector<Book>& books)
{
    std::cout << "書名を入力してください。\n";
    std::string title {};
    std::getline(std::cin, title);

    auto it = std::find_if(std::cbegin(books), std::cend(books), [title](Book e){ return e.title == title; });
    if (it == std::cend(books)) {
        std::cout << "指定された本は登録されていません。\n";
        return false;
    }
    else {
        books.erase(it);
        std::cout << "削除しました。\n";
        return true;
    }
}

void command_clear(std::vector<Book>& books)
{
    books.clear();
    std::cout << "削除しました。\n";
}

void command_print(const std::vector<Book>& books)
{
    for (auto& book : books) {
        print_book_data(book);
    }
}

void command_search_by_title(const std::vector<Book>& books)
{
    std::cout << "書名を入力してください。\n";
    std::string title {};
    std::getline(std::cin, title);

    for (auto& book : books) {
        if (book.title == title) {
            print_book_data(book);
        }
    }
}

void command_search_by_author(const std::vector<Book>& books)
{
    std::cout << "著者名を入力してください。\n";
    std::string author {};
    std::getline(std::cin, author);

    for (auto& book : books) {
        if (book.author == author) {
            print_book_data(book);
        }
    }
}

void command_search_by_price(const std::vector<Book>& books)
{
    std::cout << "価格を入力してください。\n";
    auto price = get_int_from_stdin();
    if (!price.second || price.first < 0) {
        std::cout << "入力内容に不備があります。\n";
    }
    else {
        for (auto& book : books) {
            if (book.price >= price.first) {
                print_book_data(book);
            }
        }
    }
}

void command_search_by_evaluate(const std::vector<Book>& books)
{
    std::cout << "評価を入力してください。\n";
    auto evaluate = get_int_from_stdin();
    if (!evaluate.second) {
        std::cout << "入力内容に不備があります。\n";
    }
    else {
        for (auto& book : books) {
            if (book.evaluate >= evaluate.first) {
                print_book_data(book);
            }
        }
    }
}

bool command_read_file(std::vector<Book>& books)
{
    std::ifstream ifs(booklist_path);
    if (!ifs) {
        std::cout << "ファイルが開けません。\n";
        return false;
    }

    // メモリ上の本のデータを消す
    books.clear();

    while (!ifs.eof()) {
        Book book {};
        std::istringstream iss {};
        std::string tmp {};

        std::getline(ifs, book.title);
        if (book.title.empty()) {
            break;
        }

        std::getline(ifs, book.author);

        std::getline(ifs, tmp);
        iss.str(tmp);
        iss >> book.price;
        iss.clear();

        std::getline(ifs, tmp);
        iss.str(tmp);
        iss >> book.evaluate;

        books.push_back(book);
    }

    std::cout << "読み込みました。\n";
    return true;
}

bool command_write_file(const std::vector<Book>& books)
{
    std::ofstream ofs(booklist_path);
    if (!ofs) {
        std::cout << "ファイルが開けません。\n";
        return false;
    }

    for (auto& book : books) {
        ofs << book.title << "\n"
            << book.author << "\n"
            << book.price << "\n"
            << book.evaluate << "\n";
    }

    ofs.close();
    if (!ofs) {
        std::cout << "書き込みに失敗しました。\n";
        return false;
    }

    std::cout << "書き込みました。\n";
    return true;
}

bool command_exit(bool isChanged)
{
    if (isChanged) {
        std::cout << "変更した内容をファイルに書き込んでいませんが、終了してよろしいですか? (Y/N)\n";
        std::string s {};
        std::cin >> s;
        if (s != "Y" && s != "y") {
            return false;
        }
    }

    std::cout << "終了します。\n";
    return true;
}

get_int_from_stdin関数からの戻り値を受け取り、まず second に入っている値が false でないかを確認します。false の場合はエラーが起きているため、受け取った int型の値は使わず、エラーメッセージを出力する処理を実行します。second が true なら、入力された値を first から取得します。

価格の入力では、int型の値が正常に受け取れたとしても、その値が 0未満の場合は不正な入力とみなすことにしているので、エラーチェックは次のようになります。

auto price = get_int_from_stdin();
if (!price.second || price.first < 0) {
    // エラー
}



参考リンク



更新履歴




はてなブックマーク に保存 Pocket に保存 Facebook でシェア
Twitter でツイート Twitter をフォロー LINE で送る
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー