C++コーディング演習(第9問)

PG

プチ総合問題のつもりが、大掛かりな仕上がりとなってしまったため独自の記事を立てた。
例によって、出題はChatGPTによる。

第9問 簡易楽譜マネージャを作ろう (3/23)

問題

【概要】

あなたは、楽譜上の要素(小節や音符)をテキストベースで管理し、次の機能をシミュレートするコンソールアプリケーションを作成してください。

  1. スタッフ(五線)の上下移動
    • 音符がある五線のインデックスを上下に動かす。
    • 五線インデックスが範囲外にならないようにガードする。
  2. MMRest(マルチメジャーレスト 注:長休符)を含む小節リストの表示
    • 小節ごとに「通常の小節」か「MMRest小節」か分かるように表示する。
    • 例えば、3つの連続した休符小節は “MMRest(3)” として表示するイメージ。
  3. 「スナップ」機能のトグル
    • 現在選択中の音符に対して「前の音符にスナップする/しない」を切り替える。
    • 切り替えの結果をコンソールに出力する。
  4. 単純な「スラー」の適用シミュレーション
    • 2つの音符を選択して「スラーを貼る」モードを作る。
    • 「成功/失敗」の結果を表示。

【詳細要件】

  1. スタッフ上下移動
    • ユーザーが「move-up」や「move-down」コマンドを入力したら、
      • 選択中の Note の staffIndex を +1/-1。
      • ただし 0 と (最大StaffIndex) の範囲内にクリップ。
    • 結果を「noteID: X の staffIndex を n へ変更」などコンソール出力。
  2. MMRest表示
    • 一覧表示するコマンド (show-measures) を実装し、
      • Measure を順番に見て
        • isMMRest == falseMeasure #<measureID>
        • isMMRest == trueMMRest(<mmRestCount>)
    • もし同じ measureID をまとめて扱うようにしたいなら、まとめ対象の先頭 measure にだけ isMMRest=true を立てるといった管理でもOK。
    • コンソール例:
      1: Measure #1
      2: MMRest(4) // ここは measureID=3 の小節がまとめ役
      3: ...
  3. スナップトグル
    • 「toggle-snap <noteID>」と入力すると、該当 Note を探し、snapToPrevious を反転する。
    • true -> false, false -> true
    • 結果を表示(「Note 10 snapToPrevious: OFF -> ON」など)。
  4. スラー適用シミュレーション
    • コマンド例:「apply-slur <startNoteID> <endNoteID>」
    • 判定条件(例)
      • 同じ measureID であること
      • 連符(tuplet)やトレモロなどはシミュレーション外なので今回は無視してOK
      • 休符でも良いとするか? それとも音符同士か? など簡単に決める
    • 成功の場合:「Slur applied from Note #X to Note #Y」
    • 失敗の場合:「Failed to apply slur (reason: … )」

【データ構造】

1. Staff
  • 目的
    「五線」を管理する要素。ここでは数が固定でも良いし、可変でも良いですが、最低限以下の情報を持つとしましょう。
  • メンバ
    • staffIndex (int)
      五線の番号。0ベースでも1ベースでもいいですが、同じプロジェクト内では統一してください。
    • name (std::string)
      五線の名前(例: “Piano RH”, “Piano LH”, “Violin 1” など)。省略可。
  • 備考
    • 実装のシンプルさ重視なら「スタッフのリスト」を用意するのではなく、音符側で staffIndex だけ持たせても問題ありません。
    • ユーザーが「move-up/down」コマンドで五線を切り替えようとするとき、最大 staffIndex(全五線数 – 1)と 0 の範囲内で上下するイメージです。
2. Measure
  • 目的
    楽譜の小節を管理する。
  • メンバ
    • measureID (int)
      小節番号。1小節目なら 1、など。
    • isMMRest (bool)
      マルチメジャーレストかどうか。
    • mmRestCount (int)
      その小節が MMRest である場合、何小節分の休符をまとめているかを示す。
      たとえば、小節 3 ~ 5 がまとめられていて小節 3 の位置で表示するなら、mmRestCount = 3 とか。
  • MMRestCount のイメージ
    • もし「3~6小節目が MMRest」に統合されたとしたら、小節 3 に相当する measure の
      • isMMRest = true
      • mmRestCount = 4 (3,4,5,6 の4小節分)
    • 中間小節 4,5,6 は実際には「表示されない/スキップ扱い」でもいいし、簡易シミュレーションなら isMMRest = false で保持したままでもOK。
    • どちらにしても、コンソール表示時に「MMRest(4)」のように表示し、その結果「3,4,5,6 がまとめられている」ことを示せば良い。
3. Note
  • 目的
    楽譜上の音符・休符を管理する。
  • メンバ
    • noteID (int)
      ユニークな ID(apply-slur のときに引数に使うなど)。
    • measureID (int)
      どの小節に属しているか(小節との関連付け)。
    • tick (int)
      小節内での位置(拍・タイムの概念)。簡単に「0, 120, 240」など適当な値でよい。
    • pitch (int)
      音程を示す数値(60=中央ドなど)。休符なら特別な値(-1)を入れるでも可。
    • staffIndex (int)
      どの五線に配置されているか。move-up/down で +1/-1。
    • snapToPrevious (bool)
      「前の音符にスナップするかどうか」のフラグ(トグル動作)。
  • 備考
    • スラー適用チェックをする際、measureID が同じかどうか、休符( pitch == -1 )でいいのか、など判定条件に使う。

【実装の流れ】

  1. データ作成
    • まずは std::vector<Measure>std::vector<Note> を適当に初期化(例: 5小節, 10ノートほど)
    • staffIndex の最大値は 3(4本の五線)など決めておく
    • MMRestの例として、小節 3 を isMMRest = true, mmRestCount=3 にしてみる
  2. メインループ
    • while(true) の中でユーザー入力を読み取り、コマンド名を if/elseswitch で分岐
    • show-measures → 上記のメソッドで表示
    • toggle-snap <noteID> → 該当Note検索&フラグ反転
    • move-up <noteID> / move-down <noteID> → staffIndex調整
    • apply-slur <startNoteID> <endNoteID> → 条件チェック → 成否表示
    • exit → ループ終了
  3. 関数設計
    • showMeasures(const std::vector<Measure>& measures)
    • toggleSnap(std::vector<Note>& notes, int noteID)
    • moveStaff(std::vector<Note>& notes, int noteID, bool up, int maxStaffIndex)
    • applySlur(const std::vector<Note>& notes, int startID, int endID)
      • 成功 or 失敗を bool などで返す
    • int main() でループ&入力解析

【操作イメージ】

  • 起動後、ユーザーにコマンド入力を促す。例:
コマンドを入力してください:
1) show-measures
2) move-up
3) move-down
4) toggle-snap
5) apply-slur
6) exit
  • show-measures
    • 登録してある小節のリストを表示
  • move-up
    • 選択中の音符の staffIndex を -1 して結果表示
  • move-down
    • staffIndex を +1
  • toggle-snap
    • snapToPrevious を false → true または true → false にして結果表示
  • apply-slur <startNoteId> <endNoteId>
    • 2つの音符IDを引数にとり、条件チェックして成功/失敗を表示

【提出物】

  • メイン関数main())を含む C++ プログラム
  • コンソール入力でコマンドを受け、上記機能を実装
  • 実行例のようにコンソール出力で操作の結果を表示

【ポイント】

  • データ構造同士の関連付け
    • Note が measureID を持つ
    • Measure は measureID で管理
    • staffIndex は Note が内包。スタッフの配列を作るかは自由
  • 安全な操作
    • move-up/down で staffIndex がマイナスにならない or 上限超えないようガード
    • toggleSnap で Note が見つからない場合はエラー表示
  • ユーザー体験
    • コンソールメッセージを工夫して、何が起きたか分かりやすく出力
  • 柔軟に簡略化して良い
    • pitch, tick はあまり深くやらず、形だけ用意でもOK
    • mmRestCount の運用は最低限で良い

この問題を解くと、これまで学んできた「五線インデックス」「スナップトグル」「連符や小節の整合性」「スラー適用条件」などの考え方をコンソールアプリで再現できるはずです。

「ソフトウェア設計の全体像に慣れてきたなら、こういう小さなシミュレーションを組んでみる」と、より深く身につくと思います。楽しんで取り組んでみてください!

最終解答

#include <iostream>
#include <vector>

struct Staff {
    int staffIndex; //0start
    std::string name; //part-name
};

struct Measure {
    int measureID;
    bool isMMrest; //長休符最初の小節だけtrue
    int mmRestCount;  //長休符最初の小節だけ数値変更
};

struct Note {
    int noteID;
    int measureID;
    int tick; //0~999 を想定
    int pitch; //-1は休符、0~127は音符
    int staffIndex;
    bool snapToPrevious;
};

//noteIDに対応するnotesのポインタを取得
Note* findNote(std::vector<Note>& notes, int noteID){
    for(auto& n : notes){
        if(n.noteID == noteID){
            return &n;
        }
    }
    
    return nullptr;
}

//不正でないnoteIDをユーザーから取得
int getNoteID(std::vector<Note>& notes){
    int id = 0;

    while(true){
        std::cout << "noteIDを入力...";
        std::cin >> id;
        
        if (findNote(notes, id)){
            break;
        }else{
            std::cout << "エラー:noteID が見つかりません。\n";
        }
    }

    return id;
}

void showMeasures(const std::vector<Measure>& measures){
    std::cout << "\n[measures List]" << std::endl;
    int restCnt = 0;
    
    for(auto& m : measures){
        if(m.isMMrest){
            std::cout << "MMRest(" << m.mmRestCount << ")" << std::endl;
            restCnt = m.mmRestCount - 1;
        }else{
            if(restCnt > 0){
                restCnt--;
            }else{
                std::cout << "Measure #" << m.measureID << std::endl;
            }
        }
    }
    
    std::cout << std::endl;
}

void moveStaff(std::vector<Note>& notes, int noteID, bool up, int maxStaffIndex){
    //noteIDは信頼できる
    Note* target = findNote(notes, noteID);
    if(!target){ //念のため
        std::cout << "[move up/down] エラー:noteID が見つかりません。" << std::endl;
        return;
    }
    
    int before = target->staffIndex; //元の段
    
    if(up){ //上へ移動、-1
        if(target->staffIndex > 0){
            target->staffIndex--;
        }else{
            std::cout << "[move-up] 移動不可" << std::endl;
        }
        
        std::cout << "[move-up] Note " << noteID << ": staffIndex " << before;
        std::cout << " -> " << target->staffIndex << std::endl;
    }else{
        if(target->staffIndex != maxStaffIndex-1){
            target->staffIndex++;
        }else{
            std::cout << "[move-down] 移動不可" << std::endl;
        }
        
        std::cout << "[move-down] Note " << noteID << ": staffIndex " << before;
        std::cout << " -> " << target->staffIndex << std::endl;
    }
}

void toggleSnap(std::vector<Note>& notes, int noteID){
    //noteIDは信頼できる
    Note* target = findNote(notes, noteID);
    if(!target){ //念のため
        std::cout << "[toggle-snap] エラー:noteID が見つかりません。" << std::endl;
        return;
    }
    
    target->snapToPrevious = !target->snapToPrevious;
    
    std::cout << "[toggle-snap] Note " << noteID << ": toggle-snap ";
    std::cout << (!target->snapToPrevious ? "ON" : "OFF") << " -> ";
    std::cout << (target->snapToPrevious ? "ON" : "OFF") << std::endl;
}

void applySlur(std::vector<Note>& notes, int startID, int endID){
    Note* target1 = findNote(notes, startID);
    Note* target2 = findNote(notes, endID);
    if(!target1 || !target2){ //念のため
        std::cout << "[apply-slur] エラー:noteID が見つかりません。" << std::endl;
        return;
    }
    
    //target1,2の前後関係チェック
    if(startID == endID){
        std::cout << "[apply-slur] エラー:スラーの始点と終点が同じです。" << std::endl;
        return;
    }else if(startID > endID){
        std::swap(startID, endID);
        std::swap(target1, target2);
    }
    
    //スラーしてよいかチェック(両方音符、同じstaffIdx)
    if(target1->pitch == -1 || target2->pitch == -1){
        std::cout << "[apply-slur] エラー:スラーは休符に適用できません。" << std::endl;
        return;
    }else if(target1->staffIndex != target2->staffIndex){
        std::cout << "[apply-slur] エラー:スラーは別の五線の音符に適用できません。" << std::endl;
        return;
    }
    
    std::cout << "[apply-slur] Note " << startID << " -> " << endID << " (表示のみ)" << std::endl;
}

int main()
{
    //テストデータ設定 後々初期化拡張も検討
    std::vector<Staff> staffs = {
        Staff{0, "Violin 1"},
        Staff{1, "Violin 2"},
        Staff{2, "Viola"},
        Staff{3, "Cello"}
    };

    std::vector<Measure> measures = {
        Measure{1, false, 0},
        Measure{2, false, 0},
        Measure{3, true, 4}, // MMRest(4): 3-6小節
        Measure{4, false, 0},
        Measure{5, false, 0},
        Measure{6, false, 0},
        Measure{7, false, 0}
    };

    std::vector<Note> notes = {
        Note{1, 1, 0, 60, 0, false},
        Note{2, 1, 240, 62, 0, false},
        Note{3, 2, 0, 64, 1, true},
        Note{4, 2, 240, 65, 1, false},
        Note{5, 3, 0, -1, 2, true}, // rest
        Note{6, 5, 0, 67, 2, false}
    };

/*
    //テストデータ確認出力
    std::cout << "staffs" << std::endl;
    for(auto& s: staffs){
        std::cout << s.staffIndex << " " << s.name << std::endl;
    }
    
    std::cout << "measures" << std::endl;
    for(auto& m : measures){
        std::cout << m.measureID << " " << m.isMMrest << " " << m.mmRestCount << std::endl;
    }
    
    std::cout << "notes" << std::endl;
    for(auto& n : notes){
        std::cout << n.noteID << " " << n.measureID << " " << 
        n.tick << " " << n.pitch << " " << n.staffIndex << " " << n.snapToPrevious << std::endl;
    }
*/

    //コンソール入力受付
    std::cout << "コマンドを入力...\n";
    std::cout << "1) show-measures\n2) move-up\n3) move-down\n";
    std::cout << "4) toggle-snap\n5) apply-slur\n6) exit" << std::endl;

    while(true){
        std::string cmd;
        std::cin >> cmd;
        
        if (cmd == "show-measures"){
            showMeasures(measures);
        }else if(cmd == "move-up"){
            moveStaff(notes,getNoteID(notes),true,4);
        }else if(cmd == "move-down"){
            moveStaff(notes,getNoteID(notes),false,4);
        }else if(cmd == "toggle-snap"){
            toggleSnap(notes,getNoteID(notes));
        }else if(cmd == "apply-slur"){
            int start;
            int end;
            std::cout << "[apply-slur] Start: ";
            start = getNoteID(notes);
            std::cout << "[apply-slur] End: ";
            end = getNoteID(notes);
            applySlur(notes,start,end);
        }else if(cmd == "exit"){
            break;
        }else{
            std::cout << "正しいコマンドを入力してください。" << std::endl;
        }
        
    }
    
    std::cout << "終了" << std::endl;
    return 0;
}

最終解答(2回目、4/7)

#include <iostream>
#include <vector>
#include <sstream>

struct Staff{
  int staffIndex;  //0 start
  std::string name;
};

struct Measure{
    int measureID; //1 start
    bool ismmRest;
    int mmRestCount; //最初の長休符小節のみ格納
};

struct Note{
  int noteID;
  int tick;
  int pitch; //休符 -1
  bool snapToPrevious;
  const Staff* staff;
  const Measure* measure;
};

//Note構造体 の noteID と 添え字対応づけ
Note* findNote(std::vector<Note>& notes, int noteID){
    for(auto& n : notes){
        if(n.noteID == noteID){
            return &n;
        }
    }
    return nullptr;
}

void showMeasures(const std::vector<Measure>& measures){
    int idx = 1;
    for(int i=0; i<measures.size(); ++i){
        if(!measures[i].ismmRest){
            std::cout << idx << ": Measure #" << measures[i].measureID << std::endl;
            ++idx;
        }else{
            std::cout << idx << ": MMRest(" << measures[i].mmRestCount << ")" << std::endl;
            i += measures[i].mmRestCount - 1;
            ++idx;
        }
    }
}

bool moveStaff(std::vector<Note>& notes, int noteID, bool up, const std::vector<Staff>& staffs){
    Note* target = findNote(notes, noteID);
    if(!target){
        std::cout << "[Error] noteIDが存在しません。" << std::endl;
        return false;
    }
    
    int beforeIndex = target->staff->staffIndex;
    
    if(up && beforeIndex == 0){
        std::cout << "[Error] noteID: " << noteID << " は最上段です。" << std::endl;
        return false;
    }
    
    if(!up && beforeIndex == staffs.size() - 1){
        std::cout << "[Error] noteID: " << noteID << " は最下段です。" << std::endl;
        return false;
    }
    
    target->staff = up ? &staffs[beforeIndex - 1] : &staffs[beforeIndex + 1];
    std::cout << "[Done] noteID:" << noteID << " staff:" <<
                beforeIndex << " -> " << target->staff->staffIndex << std::endl;
    
    return true;
}

void toggleSnap(std::vector<Note>& notes, int noteID){
    Note* target = findNote(notes, noteID);
    if(!target){
        std::cout << "[Error] noteIDが存在しません。" << std::endl;
        return;
    }
    
    target->snapToPrevious = !target->snapToPrevious;
    std::cout << std::boolalpha <<
                "[toggleSnap] NoteID: " << noteID << " , snapToPrevious: " <<
                !target->snapToPrevious << " -> " << target->snapToPrevious << std::endl;
}

bool applySlur(std::vector<Note>& notes, int startID, int endID){
    Note* target1 = findNote(notes, startID);
    Note* target2 = findNote(notes, endID);
    
    if(!target1 || !target2){
        std::cout << "[Error] 存在しないnoteIDが入力されました。" << std::endl;
        return false;
    }
    
    if(target1 == target2){
        std::cout << "[Error] 同じnoteIDを指定しています。" << std::endl;
        return false;
    }
    
    if(target1->pitch == -1 || target2->pitch == -1){
        std::cout << "[Error] 休符にスラーを適用できません。" << std::endl;
        return false;        
    }
    
    if(target1->staff != target2->staff){
        std::cout << "[Error] 違う段の音符にスラーを適用できません。" << std::endl;
        return false;        
    }
    
    std::cout << "[done] スラー (noteID: " << startID << " - " << endID << ")" << std::endl;
    return true;
}

int main()
{
    //テストデータ設定
    std::vector<Staff> staffs = {
        Staff{0, "Violin 1"},
        Staff{1, "Violin 2"},
        Staff{2, "Viola"},
        Staff{3, "Cello"}
    };

    std::vector<Measure> measures = {
        Measure{1, false, 0},
        Measure{2, false, 0},
        Measure{3, true, 4}, // MMRest(4): 3-6小節
        Measure{4, false, 0},
        Measure{5, false, 0},
        Measure{6, false, 0},
        Measure{7, false, 0}
    };

    std::vector<Note> notes = {
        Note{1, 0, 60, false, &staffs[0], &measures[0]},   // Violin 1, Measure 1
        Note{2, 240, 62, false, &staffs[0], &measures[0]}, // Violin 1, Measure 1
        Note{3, 0, 64, true, &staffs[1], &measures[1]},    // Violin 2, Measure 2
        Note{4, 240, 65, false, &staffs[1], &measures[1]}, // Violin 2, Measure 2
        Note{5, 0, -1, true, &staffs[2], &measures[2]},    // Viola (休符), Measure 3
        Note{6, 0, 67, false, &staffs[2], &measures[4]}    // Viola, Measure 5
    };

    // コマンド受け取り
    std::string line, cmd;

    while(true){
        std::cout << "コマンドを入力してください。[h]コマンド一覧表示...";
        
        std::getline(std::cin, line);
        std::istringstream iss(line);
        
        std::vector<std::string> tokens;
        std::string token;
        
        while(iss >> token){
            tokens.push_back(token);
        }
        
        if (tokens.empty()) continue;
        
        cmd = tokens[0];
        
        if (cmd == "h"){
            std::cout << "(1) show-measures [s]\n(2) move-up [mu] <noteID>\n" <<
                        "(3) move-down [md] <noteID>\n(4) toggle-snap [t] <noteID>\n" <<
                        "(5) apply-slur [a] <startNoteID> <endNoteID>\n(6) exit [e]" << std::endl;
        }
        else if (cmd == "show-measures" || cmd == "s"){
            showMeasures(measures);
        }
        else if ((cmd == "move-up" || cmd == "mu") && tokens.size() == 2){
            int arg1 = std::stoi(tokens[1]);
            bool success = moveStaff(notes, arg1, true, staffs);
            std::cout << "[move-up] " << (success ? "成功" : "失敗") << std::endl;
        }
        else if ((cmd == "move-down" || cmd == "md") && tokens.size() == 2){
            int arg1 = std::stoi(tokens[1]);
            bool success = moveStaff(notes, arg1, false, staffs);
            std::cout << "[move-down] " << (success ? "成功" : "失敗") << std::endl;
        }
        else if ((cmd == "toggle-snap" || cmd == "t") && tokens.size() == 2){
            int arg1 = std::stoi(tokens[1]);
            toggleSnap(notes, arg1);
        }
        else if ((cmd == "apply-slur" || cmd == "a") && tokens.size() == 3){
            int arg1 = std::stoi(tokens[1]);
            int arg2 = std::stoi(tokens[2]);
            bool success = applySlur(notes, arg1, arg2);
            std::cout << "[apply-slur] " << (success ? "成功" : "失敗") << std::endl;
        }
        else if (cmd == "exit" || cmd == "e"){
            std::cout << "プログラムを終了します。" << std::endl;
            break;
        }
        else{
            std::cout << "[Error] 不正なコマンドか、引数の数が正しくありません。" << std::endl;
        }
    }

    return 0;
}

ふりかえり

  • 完成に4時間半かかってしまった。
    • 仕様を読んだ時点では、「基本的なC++構文の組み合わせでできそう、でも面倒くさそう」という感想。でも手をつけてみるとそんな簡単ではなく…。
    • 以下のとおり、実践的な1問を徹底的に解くと大きな収穫がある。自分のできなさに直面する。
  • クラス構造体の区別
    • メソッドがなくてデータ構造として扱うだけなら構造体
    • 構造体はデフォルトでpublic、クラスはデフォルトでprivate
    • データを隠したい、継承やポリモーフィズムを使いたいならクラスにするとよい
  • どこまで汎用性を意識するか? 要求に忠実になるか? に悩む。
    • この仕様の前提として、小節数、五線数の初期化をすべきでは?とまず考える。
    • しかし、まずは求められたことだけを作る姿勢も大事。
  • switch文の条件に文字列 std::string は使えない。
  • 関数の責任分担、変数の宣言位置の悩み
    • この変数をどこで宣言してどのスコープで使うべき?
    • 実装したい機能をどの関数、位置で組み込めば良い? と悩んで時間が掛かる。
    • ここでこうしたら全体の整合性は? ここで変数作ってスコープミスらない? この処理をどの関数の中に入れると後で困らない?
    • 関数を複数作ると、役割が重複していないかが気になる。
    • 例:①NoteIDの入力、②入力を受けたNoteIDが存在するかのチェック、③NoteIDとそれを指すポインタの紐付け、④Noteの中身を編集する機能の実装 →それぞれの役割をどの関数に割り振るか、また処理の階層構造を考える
  • 引数の扱い(定数として扱うか変数として扱うか)
    • 関数Aで引数Xをconstで取ったのに、その関数Aの中で別の関数Bを呼び出し、Bにその引数Xを非constで渡そうとするとエラーになる(定数の中身を書き換えうる関数に渡しているので)
    • 関数Aで引数Xをconstで取ったのに、Xに含まれる一部の値を非constでreturnしようとするとエラーになる→戻り値もconstとなるように、関数の定義でconst void... とすべき
  • ポインタとconst
     const Note* p :pが指すNoteの書き換え不可、pの付け替え(別のNoteに充てる)は可
     Note* const p:pが指すNoteの書き換え可、pの付け替え不可
  • vectorのインデックスと人間が設定したIDは一致しない
    • ここを勘違いするとバグの温床
    • 例:notes.noteID == 6notes[6] は示すものが違う
  • 複数の引数を入力させる場合の処理
    • 各引数(個別)のチェックは専用の関数を作るとよい
    • 引数同士の論理的な関係性は処理関数のなかで判断する
    • 例:スラー適用で両端のNoteID(x,y)をあわせて2つ取る場合
      • x, y がそれぞれNoteIDに属する値か? →専用の関数でチェック
      • x, y の値の大小関係や不一致であるか? →スラー適用関数でチェック

ふりかえり (2回目)

  • 所要時間:2時間程度。前回と同じレベルのたたき台コードを書き切った。
  • コマンドによって可変の引数を受け取りたい
    • 前回は、コマンド受付→分岐させて必要数の引数を受け取った。今回は コマンド+NoteID といった形式で一気に受け取りたい。
    • std::getline() + std::istringstream
    • std::getline(): 1行の入力をまるごと文字列として受け取る。
    • cf. std::cin は空白で区切られて1単語しか読めない
    • std::istringstream: 文字列をストリーム化(?)、わからん
    • istringstream は stringのような型になるの?
    • この場合、不要な引数が残るケースがある。
      • →毎回コマンド入力を受けるたびに、引数の数をチェックする仕組みを作る。
  • if(A){…}; if(B){…}; を上手に使い、ダメな例を次々弾いて最後に本命処理。
    • 92~110行目の処理が流れるようにキレイになった。
  • 変数名 count vs. num
    • count: いまの状態を測るイメージ(vectorのサイズ、ループの終了条件)
    • num: 与えられた定数・仕様としての数値というイメージ
  • 設計の整合性を考える
    • applySlur() を bool型にして、成功失敗を明示するようになった
    • moveStaff() も bool型にしたほうが整合的では?
  • Note構造体で、Staff(五線位置)とMeasure(小節)をポインタにすることで、Staff構造体とMeasure構造体との整合性を取る。データとしての一貫性を意識した設計をする。
    • データ同士を繋げられるようになると、一気に意味を帯びる。
    • 他の構造体をポインタで扱うようになると、実装部分で比較したいときに、「何を比較するか」が変わる。
    • 具体的な値で比較 vs. 指している構造体の実体が同じかをポインタで比較。→ポインタ比較が安全。
  • あるNoteのstaffIndexを変更したい(その音符を上の五線に移動させたい) → staff->staffIndexの値を書き換えてしまうミス
    • これをすると、Staff 構造体の要素の値を書き換えてしまう。
    • 例えば、上から3番目の五線のstaffIndexの値を2→1に書き換えてしまう。当該 Note の所属する五線が3番目から2番目に移るわけではない。
    • 構文上の誤りはなくコンパイルが通ってしまうので怖い。
  • そうすると、「&staffs が今のスコープに存在しない」とのコンパイルエラーが出る。
    • ポインタを使うようになると、外から引数で渡す必要が出る。
  • そうすると、staffconstにしたほうが安全になる。意図せず書き換えてしまうバグを防げるから。
  • そうすると、処理の内部でconst Staff*Staff* に代入することになり、コンパイルエラー。
    • 今の処理で書き換えなかったとしても、「書き換える可能性がある」だけでC++はエラーを吐く。
    • そのため、Note構造体の定義から、Staff, Measureconst 指定しないといけない。C++はこういうところにとても厳しいのだ。
  • C++は本気で信頼されるシステムを書くための言語。責任の重さ=自由の重さ(byAI MEMちょ)

タイトルとURLをコピーしました