クラス 解答ページ | Programming Place Plus C++編【言語解説】 第11章

トップページC++編

C++編で扱っている C++ は 2003年に登場した C++03 という、とても古いバージョンのものです。C++ はその後、C++11 -> C++14 -> C++17 -> C++20 -> C++23 と更新されています。
なかでも C++11 での更新は非常に大きなものであり、これから C++ の学習を始めるのなら、C++11 よりも古いバージョンを対象にするべきではありません。特に事情がないなら、新しい C++ を学んでください。 当サイトでは、C++14 をベースにした新C++編を作成中です。

問題①

問題① Studentクラスを定義して、2人の生徒のオブジェクトをインスタンス化してください。Studentクラスは、student.cpp と student.h に分けて書くようにしてみましょう。


クラス定義はヘッダファイル側に、メンバ関数の定義はソースファイル側に記述します(インライン関数や、関数テンプレートの場合は、定義もヘッダファイル側に書きます)。

// student.h

#ifndef STUDENT_H_INCLUDED
#define STUDENT_H_INCLUDED

#include <string>

class Student {
    std::string  mName;   // 名前
    int          mGrade;  // 学年
    int          mScore;  // 得点

public:
    void SetData(std::string name, int grade, int score);
    void Print();
};

#endif
// student.cpp

#include "student.h"
#include <iostream>

void Student::SetData(std::string name, int grade, int score)
{
    mName = name;
    mGrade = grade;
    mScore = score;
}

void Student::Print()
{
    std::cout << "mName: " << mName << "\n"
              << "mGrade: " << mGrade << "\n"
              << "mScore: " << mScore << std::endl;
}
// main.cpp

#include "student.h"

int main()
{
    Student student1, student2;

    student1.SetData("Saitou Hiroyuki", 2, 80);
    student2.SetData("Inoue Megumi", 1, 77);

    student1.Print();
    student2.Print();
}

実行結果:

Saitou Hiroyuki 2 80
Inoue Megumi 1 77

問題②

問題② 2人の生徒の得点を比べて、点数が高い方の生徒の名前を出力する関数を作成してください。必要に応じて、Studentクラスを修正して構いません。


Studentクラス自身に、自分の得点と他の生徒の得点とを比較するメンバ関数を作ることも可能ですが、それが「生徒」の仕事・役割の一環と言えるかどうかが問題です。意見は分かれるところかもしれませんが、個人的にはこれは、Studentクラスの外ですることだと思います。そこで、独立した普通の関数として実装してみます。

// student.h

#ifndef STUDENT_H_INCLUDED
#define STUDENT_H_INCLUDED

#include <string>

class Student {
    std::string  mName;   // 名前
    int          mGrade;  // 学年
    int          mScore;  // 得点

public:
    void SetData(std::string name, int grade, int score);
    void Print();

    std::string GetName();
    int GetScore();
};

#endif
// student.cpp

#include "student.h"
#include <iostream>

void Student::SetData(std::string name, int grade, int score)
{
    mName = name;
    mGrade = grade;
    mScore = score;
}

void Student::Print()
{
    std::cout << "mName: " << mName << "\n"
              << "mGrade: " << mGrade << "\n"
              << "mScore: " << mScore << std::endl;
}

std::string Student::GetName()
{
    return mName;
}

int Student::GetScore()
{
    return mScore;
}
// main.cpp

#include <iostream>
#include "student.h"

namespace {
    void printStudentWithHigherScore(Student* s1, Student* s2)
    {
        const int s1score = s1->GetScore();
        const int s2score = s2->GetScore();

        if (s1score > s2score) {
            std::cout << s1->GetName() << std::endl;
        }
        else if (s1score < s2score) {
            std::cout << s2->GetName() << std::endl;
        }
        else {
            std::cout << s1->GetName() << " と " << s2->GetName() << " は同点です" << std::endl;
        }
    }
}

int main()
{
    Student students[3];

    students[0].SetData("Saitou Hiroyuki", 2, 80);
    students[1].SetData("Arakawa Yuuko", 2, 77);
    students[2].SetData("Yokota Ayumu", 2, 80);

    printStudentWithHigherScore(&students[0], &students[1]);
    printStudentWithHigherScore(&students[0], &students[2]);
}

実行結果:

Saitou Hiroyuki
Saitou Hiroyuki と Yokota Ayumu は同点です

printStudentWithHigherScore関数に、2つの Studentオブジェクトへのポインタを渡すと、得点を比較して、得点が高い方の生徒の名前を出力します。得点が同じなら、その旨を出力するようにしてあります。

この実装のために、Studentクラスに、GetNameメンバ関数と、GetScoreメンバ関数を追加しました。これは、メンバ変数^の値をそのまま返すだけです。次の章で詳しく解説しますが、メンバ変数に直接アクセスするのではなく、メンバ関数を経由させるようにすることが基本です。

printStudentWithHigherScore関数の中で、引数に渡した Studentオブジェクトを書き換えることはないので、仮引数の型に「const」を付けたいところです。しかし、それをすると、GetName や GetScore を呼び出しているところでコンパイルエラーになるはずです。これを解決するには、第12章で説明する constメンバ関数が必要です。

問題③

問題③ 以下の仕様で、「先生」を表すクラスを作ってください。


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

// teacher.h

#ifndef TEACHER_H_INCLUDED
#define TEACHER_H_INCLUDED

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

#define CHARGE_STUDENT_MAX  (40)  // 担当する生徒の最大数

class Teacher {
    std::string  mName;   // 名前
    int          mGrade;  // 担当学年
    Student*     mChargeStudents[CHARGE_STUDENT_MAX];  // 担当する生徒
    int          mChargeStudentsCount;  // 担当する生徒の数

public:
    void SetData(std::string name, int grade);

    void AddChargeStudent(Student* student);
    void PrintChargeStudentsList();

    int GetGrade();
};

#endif
// teacher.cpp

#include "teacher.h"
#include <iostream>
#include <cassert>

#define SIZE_OF_ARRAY(array)    (sizeof(array)/sizeof(array[0]))

void Teacher::SetData(std::string name, int grade)
{
    mName = name;
    mGrade = grade;
    for (int i = 0; i < SIZE_OF_ARRAY(mChargeStudents); ++i) {
        mChargeStudents[i] = NULL;
    }
    mChargeStudentsCount = 0;
}

void Teacher::AddChargeStudent(Student* student)
{
    assert(mChargeStudentsCount < CHARGE_STUDENT_MAX);  // 担当数オーバー
    assert(student != NULL);
    assert(student->GetGrade() == mGrade);  // 担当学年と一致しない

    mChargeStudents[mChargeStudentsCount] = student;
    mChargeStudentsCount++;
}

void Teacher::PrintChargeStudentsList()
{
    for (int i = 0; i < mChargeStudentsCount; ++i) {
        std::cout << mChargeStudents[i]->GetName() << " (" << mChargeStudents[i]->GetScore() << ")\n";
    }
    std::cout << std::endl;
}

int Teacher::GetGrade()
{
    return mGrade;
}

メンバ変数として、Studentクラスのオブジェクトを持てば、「生徒」を管理下に置くことができます。このとき、Student型で持つか、Student*型で持つかを選択することも、重要な検討課題です。

前者の場合、プログラムの他の部分では Student の実体を扱わず、Teacherクラスが独占的に管理することを意味します。後者の場合、プログラムの他の部分に Student の実体があり、Teacher はポインタを通じて間接的に参照することを意味します。

この練習問題では、どちらが良いと言える程、複雑でも大きくもないプログラムなので、どちらでも構わないですが、設計の観点では重要な問題なので、よく意識するようにしてください。

Studentクラスは、学年を知るために、GetGradeメンバ関数を追加する程度で、大きな変更はありません。

// student.h

#ifndef STUDENT_H_INCLUDED
#define STUDENT_H_INCLUDED

#include <string>

class Student {
    std::string  mName;   // 名前
    int          mGrade;  // 学年
    int          mScore;  // 得点

public:
    void SetData(std::string name, int grade, int score);
    void Print();

    std::string GetName();
    int GetGrade();
    int GetScore();
};

#endif
// student.cpp

#include "student.h"
#include <iostream>

void Student::SetData(std::string name, int grade, int score)
{
    mName = name;
    mGrade = grade;
    mScore = score;
}

void Student::Print()
{
    std::cout << "mName: " << mName << "\n"
              << "mGrade: " << mGrade << "\n"
              << "mScore: " << mScore << std::endl;
}

std::string Student::GetName()
{
    return mName;
}

int Student::GetGrade()
{
    return mGrade;
}

int Student::GetScore()
{
    return mScore;
}

最後に、main関数です。多少、リアリティを持たせるために、関係がない生徒の情報も用意して、学年を調べながら、担当する生徒を決めるような形にしてみました。

// main.cpp

#include "student.h"
#include "teacher.h"

#define SIZE_OF_ARRAY(array)    (sizeof(array)/sizeof(array[0]))

namespace {

    // teacher の担当学年と一致する生徒を、担当登録
    void ChargeStudents(Teacher* teacher, Student* students, int studentNum)
    {
        for (int i = 0; i < studentNum; ++i) {
            if (teacher->GetGrade() == students[i].GetGrade()) {
                teacher->AddChargeStudent(&students[i]);
            }
        }
    }

}

int main()
{
    Teacher teacher;
    teacher.SetData("Yamada Shinya", 2);

    Student students[7];
    students[0].SetData("Saitou Hiroyuki", 2, 80);
    students[1].SetData("Arakawa Yuuko", 2, 77);
    students[2].SetData("Yokota Ayumu", 2, 80);
    students[3].SetData("Takeda Satoshi", 3, 85);
    students[4].SetData("Katou Kouji", 3, 59);
    students[5].SetData("Ikeda Yuri", 1, 96);
    students[6].SetData("Hayakawa Hiroshi", 2, 72);

    ChargeStudents(&teacher, students, SIZE_OF_ARRAY(students));

    teacher.PrintChargeStudentsList();
}

実行結果:

Saitou Hiroyuki (80)
Arakawa Yuuko (77)
Yokota Ayumu (80)
Hayakawa Hiroshi (72)


参考リンク


更新履歴

’2018/8/27 全体的に見直し修正。

’2015/12/27 SIZE_OF_ARRAYマクロの定義を修正。

’2014/4/19 新規作成。



第11章のメインページへ

C++編のトップページへ

Programming Place Plus のトップページへ



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