C++ (発展編)演習問題

以下のプログラムをそれぞれ作成してみましょう

問題

1日目、2日目

  1. 商品ID、単価、在庫数を持つProductクラスを作成してみましょう
    メンバ関数は、商品IDの設定、取得、単価の設定、取得、在庫数の設定、取得を持ちます
  2. お小遣いの管理をするPocketmoneyクラスを作成してみましょう
    メンバ変数はmoneyを持ちます
    メンバ関数は、入金処理、出金処理、金額の表示を持ちます
    ただし、出金時、保持した金額よりも少ない場合はエラーを表示します

3日目

  1. 次のクラスを実装し以下の課題を確かめてみましょう
// 基底クラス
class Person {
  protected:
    string name;
    int age;
  public:
    Person(string n, int a) {
      name = n;
      age = a;
    }
    void show() {
      cout << “Name:<< name << endl;
      cout << “Age:<< age << endl;
    }
};

// 派生クラス
class Student: public Person {
  private:
    string school;
    int grade;
  public:
    Student(string n, int a, string s, int g): Person(n, a) {
      school = s;
      grade = g;
    }
    void show() {
      Person::show(); // 基底クラスのメンバー関数を呼び出す
      cout << “School:<< school << endl;
      cout << “Grade:<< grade << endl;
    }
};

課題:
(1) 基底クラス、派生クラスのコンストラクタ、デストラクタがどういう順番でよびだされているかを確認します
(2) PersonクラスからPatient(患者)クラスを派生クラスとして作成します
派生クラスには、身長、体重を持ち、適正体重を表示する関数と適正体重より大きいか小さいかを表示する関数をもちます
※適正体重=⾝⻑(m)×⾝⻑(m)×22

  1. 以下のプログラムをコンパイルするとエラーになります
    エラーがでないように、適切な関数などを追加して、修正してみましょう
#include <iostream>
#include <string>

class Animal {
private:
    std::string name;
protected:
    int age;
public:
    Animal(const std::string& n, int a){
      name = n;
      age = a;
    }
    void displayName() const { std::cout << "名前: " << name << std::endl; }
    int getAge() const { return age; }
};

class Dog : public Animal {
private:
    std::string breed;
public:
    Dog(const std::string& n, int a, const std::string& b) : Animal(n, a){
      breea = b;
    }
    void displayBreed() const { std::cout << "犬種: " << breed << std::endl; }
    void incrementAge() { age++; }
    void accessParentPrivate() { std::cout << name << std::endl; }
};

int main() {
    Animal animal("ネコ", 3);
    Dog dog("ポチ", 5, "柴犬");

    animal.displayName();
    std::cout << "年齢: " << animal.getAge() << "" << std::endl;
    std::cout << animal.name << std::endl; 
    animal.age = 4; 

    dog.displayName();
    std::cout << "年齢: " << dog.getAge() << "" << std::endl;
    dog.displayBreed();
    dog.incrementAge();
    std::cout << "年齢 (増加後): " << dog.getAge() << "" << std::endl;
    std::cout << dog.name << std::endl; 
    std::cout << dog.age << std::endl;
    dog.name = "ジョン"; 
    dog.age = 6; 

    return 0;
}

  1. 次の抽象クラスから二等辺三角形、長方形、円のクラスを作成してみましょう
class Shape {
    // 純粋仮想関数
    virtual double getArea() = 0; // 面積を計算する
    virtual double getAround() = 0; // 周囲長を計算する
};

  1. 次の性質をもった抽象クラス、および派生クラスを作成してみましょう
    抽象クラスShapeは、描画処理、図形の情報を表示する純粋仮想関数を持ちます
    派生クラスはLineとTextを作成します
    Lineは始点、終点の座標を表示し、Textは文字を表示します

4日目

  1. 時間、分、秒を保持するMyTimeクラスを作成しました
    以下のソースにメンバ関数の実体を作成して、動作するようにしてみましょう
#include <iostream>
#include <iomanip> // std::setw, std::setfill を使うため

class MyTime {
private: 
    int hour;
    int minute;
    int second;

    // 時間を正規化する
    void normalize() {
        if (second >60) {
            minute += second / 60;
            second %= 60;
        } else if (second < 0) {
            minute += (second - 59) / 60; // 負の場合の調整
            second = second % 60 + 60; // 負の場合の調整
        }

        if (minute >60) {
            hour += minute / 60;
            minute %= 60;
        } else if (minute < 0) {
            hour += (minute - 59) / 60;
            minute = minute % 60 + 60;
        }

        // 時間は24時間形式に正規化する
        hour %= 24;
        if (hour < 0) {
            hour += 24; // 負の場合の調整
        }
    }

public:
    // コンストラクタ
    MyTime(int h = 0, int m = 0, int s = 0) : hour(h), minute(m), second(s) {
        normalize(); // コンストラクタでも正規化を行う
    }

    // ゲッター関数
    int getHour() const { return hour; }
    int getMinute() const { return minute; }
    int getSecond() const { return second; }

    // 時刻を表示する関数
    void show() const {
        std::cout << std::setw(2) << std::setfill('0') << hour << ":"
                  << std::setw(2) << std::setfill('0') << minute << ":"
                  << std::setw(2) << std::setfill('0') << second
                  << std::endl;
    }

    // 加算演算子オーバーロード
    MyTime operator+(const MyTime& rhs) const ;

    // 減算演算子オーバーロード
    MyTime operator-(const MyTime& rhs) const ;

};

// ここにメンバ関数を記述する


int main() {
    MyTime t1(10, 30, 45);
    MyTime t2(2, 45, 20);
    MyTime t3(15, 0, 0);

    std::cout << "t1: ";
    t1.show(); 


    std::cout << "t2: " ;
    t2.show();

    MyTime t_sum = t1 + t2;
    std::cout << "t1 + t2: ";
    t_sum.show();

    MyTime t_diff = t1 - t2;
    std::cout << "t1 - t2: " ;
    t_diff.show();

    MyTime t_over_60(0, 0, 70); // 秒が60を超えるケース
    std::cout << "t_over_60 (normalized): " ;
    t_over_60.show();

    MyTime t_negative_sec(0, 0, -10); // 秒が負になるケース
    std::cout << "t_negative_sec (normalized): ";
    t_negative_sec.show();

    MyTime t_negative_time(5, 0, 0);
    MyTime t_sub_result = t_negative_time - MyTime(6, 0, 0);
    std::cout << "5:00:00 - 6:00:00: " ;//負の時間計算
    t_sub_result.show() ; 

    MyTime t_large_sum = MyTime(23, 50, 0) + MyTime(1, 20, 0);
    std::cout << "23:50:00 + 01:20:00: " ;// 24時間を超える計算
    t_large_sum.show() ; 

    return 0;
}

  1. 1で作ったMyClassに複合代入演算子(operator+=, operator-=)を追加してみましょう

5日目

関数テンプレート

  1. 関数テンプレートを使用して以下のプログラムを作成してみましょう
    任意の型の引数を受け取り、受け取った値をそのまま返す関数テンプレートfunc()関数を定義し、異なる型(例えば、int, double, std::string) で呼び出し、結果を標準出力に出力してみましょう
  2. 関数テンプレートを使用して以下のプログラムを作成してみましょう
    2つの任意の比較可能な型の引数を受け取る関数テンプレート compare() 関数を定義し、2つの引数が等しい場合は true を、そうでない場合はfalseを返すように実装します
    次に、例えば、同じ型の int 同士、同じ型の std::string 同士などでこの関数を呼び出し、結果を標準出力に出力してみましょう

クラステンプレート

  1. 次のソースに以下の(1),(2)を満たす関数を追加してみましょう
#include <iostream>

// クラステンプレートの定義
template <typename T, int N>
class Array {
private:
  T* ptr;
  int size;
public:
  Array(T arr[], int s); // コンストラクタ
  void print(); // 配列の要素を表示するメソッド
};

// クラステンプレートの実装
template <typename T, int N>
Array<T, N>::Array(T arr[], int s) {
  ptr = new T[N];
  size = s;
  for (int i = 0; i < size; i++) {
    ptr[i] = arr[i];
  }
}

template <typename T, int N>
void Array<T, N>::print() {
  for (int i = 0; i < size; i++) {
    std::cout << " " << *(ptr + i);
  }
  std::cout << std::endl;
}

int main(){
    // クラステンプレートのインスタンス化
    int arr[5] = {1, 2, 3, 4, 5};
    Array<int, 5> a(arr, 5); // 型と値を指定
    a.print();

    return 0;
}

(1) デストラクタを追加し、newで作成したptrを削除します
(2) 定義した配列に特定の値があるか検索します
※特定の値は、配列と同じ型であることとし、異なる型が指定されることはないものとします

可変長テンプレート

  1. ログを管理するクラスをLoggerを作成し、メンバ関数として可変長テンプレートを使用したlog関数を実装してみましょう
  • log():
    ログ出力関数の実装
    ※可変長テンプレートを使用し、入力された値をすべて空白区切りで1行に出力する
  • main関数
    以下の例のように呼び出して、動作を確認する
 <例>  
 logger.log("Info:", "Application started");  
       ⇒ Info: Application started  
 logger.log("Error:", "File not found", 404);  
       ⇒ Error: File not found 404  

6日目

  1. 配列を以下のように定義し、出現が最も多い値と出現回数を表示してみましょう
    ※std::unordered_mapを使用してみましょう
    array[20]={20, 5, 18, 12, 17, 14, 6, 17, 8, 1, 11, 18, 19, 10, 20, 18, 13, 3, 12, 18}
  2. 与えられた英文のテキストから、重複しない単語の数をカウントするプログラムを作成してみましょう
    ※プログラムは、以下の英文テキストを内部に保持していることとします
    A C++ data type determines the type and size of information that a variable can store, and there are several types of data types.
    ※句読点(ピリオド . など)は単語の一部として扱って構いません
    ※std::setを使用してみましょう
  3. std::list を使って、簡単なTODOリストを管理するプログラムを作成してみましょう
    以下の機能を持つようにしてください
    (1) add <タスク>と入力すると新しいタスクをリストの末尾に追加します
    (2) listと入力すると 現在のTODOリストのすべてのタスクを行番号付きで表示します
    (3) delete <行番号>と入力すると指定された行番号のタスクをリストから削除します
    (4) exitと入力するとプログラムを終了します

7日目

1. 以下のようなクラスと関数を作成しました

#include <iostream> 
#include <memory>   
#include <string>   

// 簡単なクラスを定義します
// このクラスのオブジェクトが生成・破棄されるときにメッセージを表示します
class MyResource {
public:
    std::string name;

    // コンストラクタ
    MyResource(const std::string& n) : name(n) {
        std::cout << "  [MyResource] " << name << " が生成されました。" << std::endl;
    }

    // デストラクタ
    ~MyResource() {
        std::cout << "  [MyResource] " << name << " が破棄されました。" << std::endl;
    }

    void doSomething() {
        std::cout << "  [MyResource] " << name << " が何かをしています..." << std::endl;
    }
};

// unique_ptr を引数に取る関数
void processResource(std::unique_ptr<MyResource> res) {
    std::cout << "--- 関数 processResource 内 ---" << std::endl;
    if (res) { // res が有効なポインタを指しているか確認
        res->doSomething();
    } else {
        std::cout << "  [processResource] リソースはnullでした。" << std::endl;
    }
    std::cout << "--- 関数 processResource 終了 ---" << std::endl;
}

main()関数で次の検証を行ってください
(1) MyResourceクラスのunique_ptr ptr1を生成します
(2) ptr1を利用して、MyResourceクラスのdoSomething関数を呼び出します
(3) ptr2変数を定義し、ptr1の所有権をptr2へ移動します
(4) ptr2を利用して、MyResourceクラスのdoSomething関数を呼び出します
同時に、ptr1の状態を確認します
(5) ptr2を引数としてprocessResource()を呼び出します
(6) ptr2の状態を確認します

解答例

1日目、2日目

  1. 商品ID、単価、在庫数を持つProductクラスを作成してみましょう
#include <iostream>

class Product{
    int id; // 商品ID
    int price; // 単価
    int stock; // 在庫数

    public:
        Product();
        ~Product();

        int getID();  // 商品idの取得
        void setID(int new_id); //商品idの設定

        int getPrice(); // 単価の取得
        void setPrice(int new_price); // 単価の設定

        int getStock(); // 在庫数の取得
        void setStock(int stock); // 在庫数の設定
};

Product::Product(){
  id=0;
  price=0;
  stock=0;
}

Product::~Product(){}

int Product::getID(){
    return id;
}

void Product::setID(int new_id){
    id = new_id;
}

int Product::getPrice(){
    return price;
}

void Product::setPrice(int new_price){
    if(new_price<0){
        std::cout << "エラー:単価は0以上の値を設定してください" << std::endl;
        return;
    }
    price = new_price;
}

int Product::getStock(){
    return stock;
}

void Product::setStock(int new_stock){
    if(new_stock<0){
        std::cout << "エラー:在庫数は0以上の値を設定してください" << std::endl;
    }
    stock=new_stock;
}

int main(){
    Product note;

    note.setID(1);
    note.setPrice(300);
    note.setStock(10);

    std::cout << "商品ID=" << note.getID() << std::endl;
    std::cout << "価格=" << note.getPrice() << std::endl;
    std::cout << "在庫数=" << note.getStock() << std::endl;

    return 0;
}

  1. お小遣いの管理をするPocketmoneyクラスを作成してみましょう
#include <iostream>

class Pocketmoney{
    int money; // 保持金額

    public:
        Pocketmoney(); // コンストラクタ
        ~Pocketmoney(); // デストラクタ

        void deposit(int new_money); // 入金処理
        void withdrawal(int new_money); // 出金処理
        void showPocketmoney(); // 残金の表示
};

Pocketmoney::Pocketmoney(){
    money=0;
}
Pocketmoney::~Pocketmoney(){}

void Pocketmoney::deposit(int new_money){
    if(new_money<0){
        std::cout << "金額は0以上にしてください" << std::endl;
        return;
    }
    money += new_money;
}

void Pocketmoney::withdrawal(int new_money){
    if(new_money<0){
        std::cout << "金額は0以上にしてください" << std::endl;
        return;
    }
    if(money<new_money){
        std::cout << "残金が足りません" << std::endl;
        return;
    }
    money-=new_money;
}

void Pocketmoney::showPocketmoney(){
    std::cout << "現在の残金は" << money << "です" << std::endl;
}

int main(){
    Pocketmoney pock;

    pock.withdrawal(1000);
    pock.showPocketmoney();
    pock.deposit(10000);
    pock.showPocketmoney();
    pock.withdrawal(2000);
    pock.showPocketmoney();

    return 0;
}

3日目

  1. 次のクラスを実装し以下の課題を確かめてみましょう
#include <iostream>

using namespace std;

// 基底クラス
class Person {
    protected:
      string name;
      int age;
    public:
      Person(string n, int a) {
        cout << "in Person()" << endl;
        name = n;
        age = a;
      }
      ~Person(){
        cout << "in ~Person()" << endl;
      }
      void show() {
        cout << "Name: " << name << endl;
        cout << "Age: " << age << endl;
      }
  };

  // 派生クラス
  class Student: public Person {
    private:
      string school;
      int grade;
    public:
      Student(string n, int a, string s, int g): Person(n, a) {
        cout << "in Student()" << endl;
        school = s;
        grade = g;
      }
      ~Student(){
        cout << "in ~Student()" << endl;
      }
      void show() {
        Person::show(); // 基底クラスのメンバー関数を呼び出す
        cout << "School: " << school << endl;
        cout << "Grade: " << grade << endl;
      }
  };

  // 派生クラス
  class Patient : public Person{
    private:
        float height;
        float weight;
    public:
     Patient(string n, int a,float h,float w): Person(n,a){
            height = h;
            weight = w;
     }
     void show() {
        Person::show(); // 基底クラスのメンバー関数を呼び出す
        cout << "身長: " << height << endl;
        cout << "体重: " << weight << endl;
     }
     float calcIdealweight(){
        float bmi;
        bmi = height * height * 22 / 10000;
        return bmi;
     }
     bool isUnderIdealWeight(float bmi){
        // BMI以下の場合true、以上の場合false
        return (bmi>weight) ? true : false;
     }

  };

  int main(){
    Student st("Taro",18,"Abc school",1);
    st.show();

    Patient pt("Hanako",20,156.3,53.6);
    pt.show();
    float bmi = pt.calcIdealweight();
    cout << "BMI=" << bmi << endl;

    if(pt.isUnderIdealWeight(bmi)){
        cout << "BMI以下です" << endl;
    } else{
        cout << "BMI以上です" << endl;
    }
    return 0;
  }

  1. 以下のプログラムをコンパイルするとエラーになります
#include <iostream>
#include <string>

class Animal {
private:
    std::string name;
protected:
    int age;
public:
    Animal(const std::string n, int a){
        name = n;
        age = a;
    } 
    void displayName() const { std::cout << "名前: " << name << std::endl; }
    int getAge() const { return age; }
    void setAge(int n){
        age = n;
    }
    std::string getName() const {return name;}
    void setName(std::string str){
        name = str;
    }
};

class Dog : public Animal {
private:
    std::string breed;
public:
    Dog(const std::string n, int a, const std::string& b):Animal(n,a){
        breed = b;
    } 
    void displayBreed() const { std::cout << "犬種: " << breed << std::endl; }
    void incrementAge() { age++; }
    void accessParentPrivate() { std::cout << getName() << std::endl; }
};

int main() {
    Animal animal("ネコ", 3);
    Dog dog("ポチ", 5, "柴犬");

    animal.displayName();
    std::cout << "年齢: " << animal.getAge() << "" << std::endl;
    std::cout << animal.getName() << std::endl;
    animal.setAge(4); 

    dog.displayName();
    std::cout << "年齢: " << dog.getAge() << "" << std::endl;
    dog.displayBreed();
    dog.incrementAge();
    std::cout << "年齢 (増加後): " << dog.getAge() << "" << std::endl;
    std::cout << dog.getName() << std::endl; 
    std::cout << dog.getAge() << std::endl; 
    dog.setName("ジョン"); 
    dog.setAge(6); 

    return 0;
}

  1. 次の抽象クラスから二等辺三角形、長方形、円のクラスを作成してみましょう
#include <iostream>
#include <cmath>

class Shape{
    // 純粋仮想関数
    virtual double getArea() = 0; // 面積を計算する
    virtual double getAround() = 0; // 周囲長を計算する
};

class Triangle : public Shape{
private:
    double adjacent; // 底辺
    double opposite; // 高さ
public:
    Triangle(double b,double h){
        adjacent = b;
        opposite = h;
    }
    double getArea() override{ // 面積の計算をオーバーライド
        double area = adjacent * opposite / 2.0;
        return area;
    }

    double getAround() override{ // 周囲長を計算する
        double len;
        double hypotenuse = sqrt(opposite * opposite + (adjacent) * (adjacent) / 4.0);
        len = hypotenuse * 2 + adjacent;
        return len;
    }
};

class Rectangle : public Shape{
private:
    double adjacent; // 底辺
    double opposite; // 高さ
public:
    Rectangle(double w,double h){
        adjacent = w;
        opposite = h;
    }
    double getArea() override{ // 面積の計算をオーバーライド
        double area = adjacent * opposite;
        return area;
    }

    double getAround() override{ // 周囲長を計算する
        double len = opposite * 2 + adjacent * 2;
        return len;
    }
};

class Circle : public Shape{
private:
    double radius;
    const double PI = 3.14;
public:
    Circle(double r){
        radius = r;
    }

    double getArea() override{ // 面積の計算をオーバーライド
        double area = radius * radius * PI;
        return area;
    }

    double getAround() override{ // 周囲長を計算する
        double len = 2 * radius * PI;
        return len;
    }

};

int main(){
    Triangle tri = Triangle(2,2);
    double area = tri.getArea();
    double len = tri.getAround();
    std::cout << "面積=" << area << std::endl;
    std::cout << "周囲長=" << len << std::endl;

    Rectangle rec = Rectangle(2,2);
    area = rec.getArea();
    len = rec.getAround();
    std::cout << "面積=" << area << std::endl;
    std::cout << "周囲長=" << len << std::endl;

    Circle cir = Circle(3);
    area = cir.getArea();
    len = cir.getAround();
    std::cout << "面積=" << area << std::endl;
    std::cout << "周囲長=" << len << std::endl;

    return 0;
}

  1. 次の性質をもった抽象クラス、および派生クラスを作成してみましょう
#include <iostream>

class Shape{
    // 純粋仮想関数
    virtual void draw() const = 0; // 描画する
    virtual void info() const { std::cout << "図形"; } // 図形の情報を表示する
};

class Line : public Shape{
private:
    double sx; // 開始点
    double sy;
    double ex; // 終了点
    double ey;

    void drawPoint(double x,double y) const{
        std::cout << "(" << x << "," << y << ")" << std::endl;
    }

public:
    Line(double x1,double y1,double x2,double y2){
        sx = x1;
        sy = y1;
        ex = x2;
        ey = y2;
    }
    void draw() const override{
        std::cout << "開始点:";
        drawPoint(sx,sy);
        std::cout << "終了点:";
        drawPoint(ex,ey);
    }
    void info() const override{
        std::cout << "" << std::endl;
    }
};

class Text : public Shape{
private:
    std::string str;

public:
    Text(std::string s){
        str = s;
    }
    void draw() const override{
        std::cout << str << std::endl;
    }
    void info() const override{
        std::cout << "文字" << std::endl;
    }
};

int main(){
    Line l = Line(1,1,2,2);
    l.info();
    l.draw();

    Text txt = Text("Hello");
    txt.info();
    txt.draw();

    return 0;
}

4日目

1. 時間、分、秒を保持するMyTimeクラスを作成しました
以下のソースにメンバ関数の実体を作成して、動作するようにしてみましょう

#include <iostream>
#include <iomanip> // std::setw, std::setfill を使うため

class MyTime {
private: 
    int hour;
    int minute;
    int second;

    // 時間を正規化する
    void normalize() {
        if (second >60) {
            minute += second / 60;
            second %= 60;
        } else if (second < 0) {
            minute += (second - 59) / 60; // 負の場合の調整
            second = second % 60 + 60; // 負の場合の調整
        }

        if (minute >60) {
            hour += minute / 60;
            minute %= 60;
        } else if (minute < 0) {
            hour += (minute - 59) / 60;
            minute = minute % 60 + 60;
        }

        // 時間は24時間形式に正規化する
        hour %= 24;
        if (hour < 0) {
            hour += 24; // 負の場合の調整
        }
    }

public:
    // コンストラクタ
    MyTime(int h = 0, int m = 0, int s = 0) : hour(h), minute(m), second(s) {
        normalize(); // コンストラクタでも正規化を行う
    }

    // ゲッター関数
    int getHour() const { return hour; }
    int getMinute() const { return minute; }
    int getSecond() const { return second; }

    // 時刻を表示する関数
    void show() const {
        std::cout << std::setw(2) << std::setfill('0') << hour << ":"
                  << std::setw(2) << std::setfill('0') << minute << ":"
                  << std::setw(2) << std::setfill('0') << second
                  << std::endl;
    }

    // 加算演算子オーバーロード
    MyTime operator+(const MyTime& rhs) const ;

    // 減算演算子オーバーロード
    MyTime operator-(const MyTime& rhs) const ;

};

// ここにメンバ関数を記述する
// 加算演算子オーバーロード
MyTime MyTime::operator+(const MyTime& rhs) const
{
    // 新しい MyTime オブジェクトを作成し、加算結果を格納
    MyTime result(hour + rhs.hour, minute + rhs.minute, second + rhs.second);
    result.normalize(); // 加算後も正規化
    return result;
}

// 減算演算子オーバーロード
MyTime MyTime::operator-(const MyTime& rhs) const
{
    // 新しい MyTime オブジェクトを作成し、減算結果を格納
    MyTime result(hour - rhs.hour, minute - rhs.minute, second - rhs.second);
    result.normalize(); // 減算後も正規化
    return result;
}

int main() {
    MyTime t1(10, 30, 45);
    MyTime t2(2, 45, 20);
    MyTime t3(15, 0, 0);

    std::cout << "t1: ";
    t1.show(); 


    std::cout << "t2: " ;
    t2.show();

    MyTime t_sum = t1 + t2;
    std::cout << "t1 + t2: ";
    t_sum.show();

    MyTime t_diff = t1 - t2;
    std::cout << "t1 - t2: " ;
    t_diff.show();

    MyTime t_over_60(0, 0, 70); // 秒が60を超えるケース
    std::cout << "t_over_60 (normalized): " ;
    t_over_60.show();

    MyTime t_negative_sec(0, 0, -10); // 秒が負になるケース
    std::cout << "t_negative_sec (normalized): ";
    t_negative_sec.show();

    MyTime t_negative_time(5, 0, 0);
    MyTime t_sub_result = t_negative_time - MyTime(6, 0, 0);
    std::cout << "5:00:00 - 6:00:00: " ;//負の時間計算
    t_sub_result.show() ; 

    MyTime t_large_sum = MyTime(23, 50, 0) + MyTime(1, 20, 0);
    std::cout << "23:50:00 + 01:20:00: " ;// 24時間を超える計算
    t_large_sum.show() ; 

    return 0;
}

  1. 1で作ったMyClassに複合代入演算子()を追加してみましょう
#include <iostream>
#include <iomanip> // std::setw, std::setfill を使うため

class MyTime {
private: 
    int hour;
    int minute;
    int second;

    // 時間を正規化する
    void normalize() {
        if (second >60) {
            minute += second / 60;
            second %= 60;
        } else if (second < 0) {
            minute += (second - 59) / 60; // 負の場合の調整
            second = second % 60 + 60; // 負の場合の調整
        }

        if (minute >60) {
            hour += minute / 60;
            minute %= 60;
        } else if (minute < 0) {
            hour += (minute - 59) / 60;
            minute = minute % 60 + 60;
        }

        // 時間は24時間形式に正規化する
        hour %= 24;
        if (hour < 0) {
            hour += 24; // 負の場合の調整
        }
    }

public:
    // コンストラクタ
    MyTime(int h = 0, int m = 0, int s = 0) : hour(h), minute(m), second(s) {
        normalize(); // コンストラクタでも正規化を行う
    }

    // ゲッター関数
    int getHour() const { return hour; }
    int getMinute() const { return minute; }
    int getSecond() const { return second; }

    // 時刻を表示する関数
    void show() const {
        std::cout << std::setw(2) << std::setfill('0') << hour << ":"
                  << std::setw(2) << std::setfill('0') << minute << ":"
                  << std::setw(2) << std::setfill('0') << second
                  << std::endl;
    }

    // 加算演算子オーバーロード
    MyTime operator+(const MyTime& rhs) const ;

    // 減算演算子オーバーロード
    MyTime operator-(const MyTime& rhs) const ;

    // 複合代入演算子オーバーロード
    MyTime& operator+=(const MyTime& rhs) ;

    // 複合代入演算子オーバーロード
    MyTime& operator-=(const MyTime& rhs) ;

};

// 加算演算子オーバーロード
MyTime MyTime::operator+(const MyTime& rhs) const
{
    // 新しい MyTime オブジェクトを作成し、加算結果を格納
    MyTime result(hour + rhs.hour, minute + rhs.minute, second + rhs.second);
    result.normalize(); // 加算後も正規化
    return result;
}

// 減算演算子オーバーロード
MyTime MyTime::operator-(const MyTime& rhs) const
{
    // 新しい MyTime オブジェクトを作成し、減算結果を格納
    MyTime result(hour - rhs.hour, minute - rhs.minute, second - rhs.second);
    result.normalize(); // 減算後も正規化
    return result;
}

// 複合代入演算子オーバーロード
MyTime& MyTime::operator+=(const MyTime& rhs) 
{
    // 自分自身に加算結果を格納
    hour += rhs.hour;
    minute += rhs.minute;
    second += rhs.second;
    this->normalize(); // 正規化
    return *this;
}

// 複合演算子オーバーロード
MyTime& MyTime::operator-=(const MyTime& rhs) 
{
    // 自分自身に減算結果を格納
    hour -= rhs.hour;
    minute -= rhs.minute;
    second -= rhs.second;
    this->normalize(); // 正規化
    return *this;
}

int main() {
    MyTime t1(10, 30, 45);
    MyTime t2(2, 45, 20);
    MyTime t3(15, 0, 0);

    std::cout << "t1: ";
    t1.show(); 

    MyTime t_sum = t1 + t2;
    std::cout << "t1 + t2: ";
    t_sum.show();

    MyTime t_diff = t2 - t3;
    std::cout << "t2 - t3: " ;
    t_diff.show();

    // 複合演算子、加算結果の秒が60を超えるケース
    t1 += t2;
    std::cout << "t1 += t2:";
    t1.show(); 

    // 複合演算子、減算結果の秒が負になるケース
    t2-=t3;
    std::cout << "t2 -= t3:";
    t2.show();

    return 0;
}

5日目

関数テンプレート

  1. 関数テンプレートを使用して以下のプログラムを作成してみましょう
#include <iostream>

template <typename T>
T id(const T &a){
    return a;
}

int main(){
    int a = 5;
    float b = 1.2;
    double c = 1.5;
    char d = 'd';
    std::string str="Hello";

    a = id<int>(a);
    std::cout << a << std::endl;

    b = id<float>(b);
    std::cout << b << std::endl;

    c = id<double>(c);
    std::cout << c << std::endl;

    d = id<char>(d);
    std::cout << d << std::endl;

    str = id<std::string>(str);
    std::cout << str << std::endl;

    return 0;
}

  1. 関数テンプレートを使用して以下のプログラムを作成してみましょう
#include <iostream>

template <typename T>
bool compare(const T &a,const T &b){
    return (a==b) ? true : false;
}

int main(){
    int a=5,b=6;
    double x=7.5,y=7.5;
    std::string str1="Hello",str2="Hello ";
    bool rtn;

    rtn = compare<int>(a,b);
    std::cout << rtn << std::endl;

    rtn = compare<double>(x,y);
    std::cout << rtn << std::endl;

    rtn = compare<std::string>(str1,str2);
    std::cout << rtn << std::endl;

    return 0;
}

クラステンプレート

  1. 次のソースに以下の(1),(2)を満たす関数を追加してみましょう
#include <iostream>

// クラステンプレートの定義
template <typename T, int N>
class Array {
private:
  T* ptr;
  int size;
public:
  Array(T arr[], int s); // コンストラクタ
  ~Array();             // デストラクタ
  void print(); // 配列の要素を表示するメソッド
  bool contains(T value); // 特定の値が配列中に存在するか
};

// クラステンプレートの実装
template <typename T, int N>
Array<T, N>::Array(T arr[], int s) {
  ptr = new T[N];
  size = s;
  for (int i = 0; i < size; i++) {
    ptr[i] = arr[i];
  }
}

template <typename T, int N>
Array<T, N>::~Array() {
    delete[] ptr;
}

template <typename T, int N>
void Array<T, N>::print() {
  for (int i = 0; i < size; i++) {
    std::cout << " " << *(ptr + i);
  }
  std::cout << std::endl;
}

template <typename T,int N>
bool Array<T, N>::contains(T value){
  for (int i = 0; i < size; ++i) {
    if (ptr[i] == value) {
        return true;
    }
}
return false;
}

int main(){
    // クラステンプレートのインスタンス化
    int arr[5] = {1, 2, 3, 4, 5};
    Array<int, 5> a(arr, 5); // 型と値を指定
    a.print();

    bool rtn = a.contains(9);
    std::cout << rtn << std::endl;

    rtn = a.contains(1);
    std::cout << rtn << std::endl;

    return 0;
}

可変長テンプレート

  1. ログを管理するクラスをLoggerを作成し、メンバ関数として可変長テンプレートを使用したlog関数を実装してみましょう
#include <iostream>

class Logger {
public:
    Logger() {} // コンストラクタ

    // ログ出力関数 (可変長テンプレート)
    template <typename... Args>
    void log(Args... args){
        printLog(args...);
    }

private:
    // 引数がなくなった場合の処理
    void printLog() {
        std::cout << std::endl;
    }

    // 実際の出力を行う再帰関数 (再帰ステップ)
    template <typename T,typename... Args>
    void printLog(T first,Args... rest){
        std::cout << first << " ";  // はじめの文字を出力
        printLog(rest...); // 残りの引数でprintLogを呼び出す
    }

};

int main() {
    Logger logger;

    logger.log("Info:", "Application started");
    logger.log("Warning:", 10, "errors occurred");
    logger.log("Error:", "File not found", 404);
    logger.log("Debug:", true);
    logger.log("Single argument");

    return 0;
}

6日目

  1. 配列を以下のように定義し、出現が最も多い値と出現回数を表示してみましょう
#include <iostream>
#include <unordered_map>

int main() {
    int array[20] = {20, 5, 18, 12, 17, 14, 6, 17, 8, 1, 11, 18, 19, 10, 20, 18, 13, 3, 12, 18};
    std::unordered_map<int, int> counts;
    int mostFrequentValue;
    int maxCount = 0;

    for(int i=0;i<sizeof(array)/sizeof(int);i++){
        int value = array[i];
        counts[value]++;
    }

    // 最も出現回数の多い値とその回数を探す
    for (auto itr = counts.begin(); itr != counts.end();++itr) {
        if (itr->second > maxCount) {
            maxCount = itr->second;
            mostFrequentValue = itr->first;
        }
    }

    std::cout << "最も出現回数が多い値: " << mostFrequentValue << std::endl;
    std::cout << "出現回数: " << maxCount << std::endl;

    return 0;
}

  1. 与えられた英文のテキストから、重複しない単語の数をカウントするプログラムを作成してみましょう
#include <iostream>
#include <sstream>
#include <set>

int main(){
    std::string text = "A C++ data type determines the type and size of information that a variable can store, and there are several types of data types.";

    std::set<std::string> data;
    std::stringstream ss(text);
    std::string s;
    while (getline(ss, s, ' ')) {
        data.insert(s);
    }

    for (auto it = data.begin(); it != data.end(); ++it) {
        std::cout << *it << " "; 
    }
    std::cout << std::endl;

    std::cout << data.size() << std::endl;

    return 0;
}

  1. std::list を使って、簡単なTODOリストを管理するプログラムを作成してみましょう
#include <iostream>
#include <list>
#include <string>
#include <sstream> // getlineのために必要

class Todo {
    std::list<std::string> list;

public:
    void addList(const std::string& str) {
        list.push_back(str);
    }

    void showList() {
        int no = 1;
        for(auto it = list.begin();it != list.end(); ++it){
            std::cout << no << ":" << task << std::endl;
            no++;
        }
    }

    void deleteList(int num) {
        int no = 1;
        for (auto it = list.begin(); it != list.end(); ++it) {
            if (no == num) {
                it = list.erase(it); // eraseは次の要素を指すイテレータを返す
                std::cout << "タスク " << num << " を削除しました。" << std::endl;
                return; // 削除したらループを抜ける
            }
            no++;
        }
        std::cout << "指定された番号のタスクは見つかりませんでした。" << std::endl;
    }
};

int main() {
    std::string line;
    Todo todoList;

    while (true) {
        std::cout << "入力してください (add <タスク>, list, delete <番号>, exit):";
        std::getline(std::cin, line); // 行全体を読み取る
        std::istringstream iss(line); // 読み込んだ文字列すべてからstd::istringstream(文字列ベースのストリーム)を生成
        std::string command;
        iss >> command; // はじめの空白の前まで取得

        if (command == "exit") {
            std::cout << "終了します" << std::endl;
            break;
        } else if (command == "add") {
            std::string task;
            // 残りの行をタスクとして取得
            std::getline(iss >> std::ws, task); // 先頭の空白を読み飛ばす
            if (!task.empty()) {
                todoList.addList(task);
                std::cout << "\"" << task << "\" を追加しました。" << std::endl;
            } else {
                std::cout << "追加するタスクを入力してください。" << std::endl;
            }
        } else if (command == "delete") {
            std::string arg;
            iss >> arg;
            try {
                int num = std::stoi(arg);
                todoList.deleteList(num);
            } catch (const std::invalid_argument& e) {
                std::cout << "削除するタスク番号を正しく入力してください。" << std::endl;
            } catch (const std::out_of_range& e) {
                std::cout << "削除するタスク番号が範囲外です。" << std::endl;
            }
        } else if (command == "list") {
            std::cout << "TODOリスト:" << std::endl;
            todoList.showList();
        } else {
            std::cout << "無効なコマンドです。'add', 'list', 'delete', 'exit' のいずれかを入力してください。" << std::endl;
        }
    }

    return 0;
}

7日目

  1. 以下のようなクラスと関数を作成しました
    main()関数で次の検証を行ってください
#include <iostream> 
#include <memory>
#include <string> 

// 簡単なクラスを定義します
// このクラスのオブジェクトが生成・破棄されるときにメッセージを表示します
class MyResource {
public:
    std::string name;

    // コンストラクタ
    MyResource(const std::string& n) : name(n) {
        std::cout << "  [MyResource] " << name << " が生成されました。" << std::endl;
    }

    // デストラクタ
    ~MyResource() {
        std::cout << "  [MyResource] " << name << " が破棄されました。" << std::endl;
    }

    void doSomething() {
        std::cout << "  [MyResource] " << name << " が何かをしています..." << std::endl;
    }
};

// unique_ptr を引数に取る関数
void processResource(std::unique_ptr<MyResource> res) {
    std::cout << "--- 関数 processResource 内 ---" << std::endl;
    if (res) { // res が有効なポインタを指しているか確認
        res->doSomething();
    } else {
        std::cout << "  [processResource] リソースはnullでした。" << std::endl;
    }
    std::cout << "--- 関数 processResource 終了 ---" << std::endl;
}

int main() {
    std::cout << "==== unique_ptr の基本的な使い方 ====" << std::endl;

    // (1) unique_ptr の生成
     std::unique_ptr<MyResource> ptr1(new MyResource("リソースA")); 
     // C+;14以降は以下のように記述します
     // std::unique_ptr<MyResource> ptr1 = std::make_unique<MyResource>("リソースA");

    // (2) ptr1 を使って MyResource のメソッドを呼び出す
    if (ptr1) { // ポインタが有効か確認
        ptr1->doSomething();
    }

    std::cout << "\n==== unique_ptr の所有権の移動 (ムーブ) ====" << std::endl;

    // (3) unique_ptr の所有権の移動 (コピーは不可)
    std::unique_ptr<MyResource> ptr2 = std::move(ptr1);

    // (4) ptr2 を使って MyResource のメソッドを呼び出す
    if (ptr2) {
        ptr2->doSomething();
    }

    // (4) ptr1の状態の確認
    std::cout << "ptr1が有効か? " << (ptr1 ? "はい" : "いいえ") << std::endl; // "いいえ" と表示されるはず

    std::cout << "\n==== 関数への unique_ptr の受け渡し ====" << std::endl;

    // (5) ptr2 を関数に渡す
    processResource(std::move(ptr2));

    // (6) ptr2の状態の確認
    std::cout << "ptr2が有効か? " << (ptr2 ? "はい" : "いいえ") << std::endl; // "いいえ" と表示されるはず

    std::cout << "\n==== main 関数終了 ====" << std::endl;

    return 0;
}

コメント

この記事へのコメントはありません。

関連記事

C++ 8日目:小数点の入力

C++ 基礎編 2日目

C++ 3日目:イベントハンドラの理解

PAGE TOP