構造体とポインタ 解答ページ | Programming Place Plus 新C++編

トップページ新C++編構造体とポインタ

このページの概要

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



解答・解説

問題1 (確認★)

以下の変数のうち、ヌルポインタとして初期化されるものをすべて選んでください。

int* p1 {nullptr};
int* p2 {0};
int* p3 {NULL};
int* p4 {};
int* p5 {p1};
int* p6 {0L};
int* p7;


p1 は nullptr で初期化しています。nullptr はヌルポインタを表すリテラルなので(本編解説)、p1 はヌルポインタになります。

p2 は 0 で初期化しています。整数リテラルの 0 はヌルポインタ定数であるとみなされるため(本編解説)、p2 はヌルポインタになります。このルールは、0 という整数リテラルでなければならないことに注意してください。これは古い方法であって、今の C++ では nullptr を使ったほうがいいです。

p3 は NULL で初期化しています。NULL は標準ライブラリで定義されているマクロです(本編解説)。NULLマクロの置換結果は処理系定義ですが、ヌルポインタ定数を表現する何かに置換されることになっています。したがって、p3 はヌルポインタになります。これは古い方法であって、今の C++ では nullptr を使ったほうがいいです。

p4 は {} で初期化しています。初期化子を空の {} にした場合は値初期化というルールで初期化されますが、ポインタ型の場合のそれはヌルポインタによる初期化です(本編解説)。したがって、p4 はヌルポインタになります。

p5 は p1 で初期化しています。p1 はヌルポインタなので、p5 もまたヌルポインタになります。

p6 は 0L で初期化しています。p2 と同様の理由から、p6 はヌルポインタになります。整数リテラルでさえあればいいので、0 でも 0L でも 0UL であっても同じことです。

p7 は初期化子がありません。p7 が自動ストレージ期間を持つ場合(静的でないローカル変数など)、p7 は未初期化状態になります。未初期化であることと、ヌルポインタであることは違うので注意してください。p7 が静的ストレージ期間を持つ場合(静的ローカル変数など)、ゼロ初期化されます。ゼロ初期化は 0 による初期化なので、p2 のルールと同様で、ヌルポインタになります(本編解説)。したがって、p7 が自動ストレージ期間を持つ場合はヌルポインタになる保証はなく、静的ストレージ期間を持つ場合はヌルポインタになります。


ということで、p7 が限定的な保証にとどまることを除いて、ほかはすべてヌルポインタになります。

問題2 (基本★)

以下のように定義された構造体型の変数2つの値が一致しているかどうかを判定する関数を作成してください。引数はポインタで渡すものとします。

struct Book {
    std::string    title;
    std::string    author;
    int            price;
    int            evaluate;
};


たとえば次のように作成できます。

#include <cassert>
#include <iostream>

struct Book {
    std::string    title;
    std::string    author;
    int            price;
    int            evaluate;
};

bool is_equal_book(const Book* book1, const Book* book2)
{
    assert(book1);
    assert(book2);

    return book1->title == book2->title
        && book1->author == book2->author
        && book1->price == book2->price
        && book1->evaluate == book2->evaluate
        ;
}

int main()
{
    Book book1 {"C++ Programming Guide Book", "Thomas Murray", 3500, 5};
    Book book2 = book1;

    std::cout << std::boolalpha
              << is_equal_book(&book1, &book2) << "\n";
}

実行結果:

true

構造体を指し示すポインタを使ってデータメンバにアクセスするとき、アロー演算子を使うと簡潔に書けます。book1->title(*(book1)).title と同じ意味です(本編解説)。

ポインタを受け取る関数は、渡されてきたものがヌルポインタである可能性を考慮して実装してください(本編解説)。このサンプルプログラムでは assert で確認するようにしていますが、関数の内容によっては、ヌルポインタの場合だけ異なる処理をさせたほうがよかったり、警告メッセージのようなものを出すに留めるほうがよかったりするかもしれません。

なお、assert(book1)assert(book1 != nullptr) を簡潔に書いたものです。ポインタ型から bool型には暗黙的に型変換できるため、!= nullptr の部分を省略できます(本編解説)。

問題3 (応用★★)

タイトルあるいは著者名を使って本を検索する関数を、次のように宣言しました。中身を作成してください。

Book* search_book(std::vector<Book>& books, std::string Book::* item, const std::string& name)


第2引数の型に注目しましょう。これはメンバポインタです(本編解説)。メンバポインタは、指し示すデータメンバの構造体型の中での位置情報を持ちます。

今回は、Book構造体の titleデータメンバあるいは、authorデータメンバのいずれかを指すメンバポインタを渡します。第1引数は、すべての本の情報が詰まった std::vector、第3引数は、検索したい文字列(タイトルで探すならタイトル名、著者で探すなら著者名)です。

search_book関数は次のように実装できるでしょう。

// タイトルか著者名を使って本を探す
//
// books: 本の配列
// item: タイトルか著者名を指すメンバポインタ
// name: 検索文字列
// 戻り値: 発見できた本を指すポインタ。発見できなかった場合はヌルポインタ
Book* search_book(std::vector<Book>& books, std::string Book::* item, const std::string& name)
{
    assert(item);

    for (auto& book : books) {
        if (book.*item == name) {
            return &book;
        }
    }
    return nullptr;
}

books を範囲for文で回しながら、メンバポインタitem を books の要素とともに使ってデータメンバにアクセスします。item が titleデータメンバを指すなら、各要素の titleデータメンバにアクセスできますし、item が authorデータメンバを指すなら、各要素の authorデータメンバにアクセスできます。一致する本を見つけたら、その本を指すポインタを返します。最後まで見つからなければ nullptr を返しておきます。

プログラム全体としては次のようになります。

#include <cassert>
#include <iostream>
#include <string>
#include <vector>

struct Book {
    std::string    title;
    std::string    author;
    int            price;
    int            evaluate;
};

// タイトルか著者名を使って本を探す
//
// books: 本の配列
// item: タイトルか著者名を指すメンバポインタ
// name: 検索文字列
// 戻り値: 発見できた本を指すポインタ。発見できなかった場合はヌルポインタ
Book* search_book(std::vector<Book>& books, std::string Book::* item, const std::string& name)
{
    assert(item);

    for (auto& book : books) {
        if (book.*item == name) {
            return &book;
        }
    }
    return nullptr;
}

// 本の情報を標準出力へ出力する
//
// book: 対象の本。ヌルポインタの場合は何も出力しない
void print_book(const Book* book)
{
    if (!book) {
        return;
    }

    std::cout << "title: " << book->title << "\n"
              << "author: " << book->author << "\n"
              << "price: " << book->price << "\n"
              << "evaluate: " << book->evaluate << "\n";
}

int main()
{
    std::vector<Book> books {
        {"C++ Programming Guide Book", "Thomas Murray", 3500, 5},
        {"C++ Programming Primer", "Sonia Elis", 2200, 3},
        {"C++ Tutorial", "Jack Simon", 2800, 4},
        {"C++ Game Programming", "Richard Bill", 5800, 5},
        {"C++ Reference Book", "Jack Simon", 4000, 2},
    };

    print_book(search_book(books, &Book::title, "C++ Tutorial"));
    print_book(search_book(books, &Book::author, "Richard Bill"));
}

実行結果:

title: C++ Tutorial
author: Jack Simon
price: 2800
evaluate: 4
title: C++ Game Programming
author: Richard Bill
price: 5800
evaluate: 5

このように、メンバポインタを使うと、処理対象の構造体オブジェクトと、アクセスするデータメンバをそれぞれ実行中に切り替えることが可能になります。とはいえ、これが分かりやすく良いプログラムであるかといえば、そうとも言いがたいところです。メンバポインタ自体があまり知られた機能ではないことも問題でしょう。

このサンプルプログラムの場合なら、search_book_by_title関数と search_book_by_author関数を用意したほうが、意図も明確であり、関数を使う側にとっても分かりやすく、自然だと思います。


参考リンク



更新履歴




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