TopC++ 入門 › 演習 › 練習問題集

C++ 練習問題集(全40問)

STEP 1〜STEP 12 の内容を 40 問で総復習。各問題に「難易度」と「解答例」を付けています。完了チェックを付けて進捗を管理してください(ブラウザに保存されます)。

進捗: 0 / 40
すべて
STL
クラス/OOP
所有権/RAII
テンプレート
アルゴリズム
例外
並行

01stringを逆順に

引数の std::string を逆順にした新しい string を返す関数 rev を書け。

入力: "hello" → 出力: "olleh"
a.cppANS
std::string rev(std::string s) { std::reverse(s.begin(), s.end()); return s; }

02vectorの最大・最小値

std::vector<int> を受け取り、最大値と最小値を pair で返す関数を書け。

a.cppANS
std::pair<int,int> minmax(const std::vector<int>& v) { auto [mn, mx] = std::minmax_element(v.begin(), v.end()); return {*mn, *mx}; }

03mapで頻度カウント

std::vector<std::string> から std::map<std::string, int> に単語頻度を数えろ。

a.cppANS
std::map<std::string,int> count(const std::vector<std::string>& v) { std::map<std::string,int> m; for (auto& s : v) ++m[s]; return m; }

04重複を削除しつつ順序維持

vector<int> から重複を削除(最初の出現順を維持)する関数を書け。set を使わずに。

a.cppANS
std::vector<int> dedup(std::vector<int> v) { std::unordered_set<int> seen; v.erase(std::remove_if(v.begin(), v.end(), [&](int x){ return !seen.insert(x).second; }), v.end()); return v; }

05string_view で部分一致カウント

大きなテキスト中に特定の単語が何回現れるか数える関数を、コピーせず(string_view)に書け。

a.cppANS
int countOf(std::string_view text, std::string_view word) { int cnt = 0; size_t p = 0; while ((p = text.find(word, p)) != std::string_view::npos) { ++cnt; p += word.size(); } return cnt; }

06Vec2 クラス

2D ベクトル Vec2(double x, y) を作り、+, -, 長さ len(), << をオーバーロードせよ。

a.cppANS
struct Vec2 { double x, y; Vec2 operator+(Vec2 o) const { return {x+o.x, y+o.y}; } Vec2 operator-(Vec2 o) const { return {x-o.x, y-o.y}; } double len() const { return std::sqrt(x*x+y*y); } }; std::ostream& operator<<(std::ostream& o, Vec2 v){ return o << "(" << v.x << "," << v.y << ")"; }

07アクセス制御のある BankAccount

BankAccount クラスに private の残高を持ち、deposit, withdraw(失敗時 false を返す)を実装せよ。balance() は const。

a.cppANS
class BankAccount { long long bal_ = 0; public: void deposit(long long v) { bal_ += v; } bool withdraw(long long v) { if (v > bal_) return false; bal_ -= v; return true; } long long balance() const { return bal_; } };

08Shape 階層 + 多態

抽象クラス Shape に area() を純粋仮想で定義し、Circle/Rect を作り、std::vector<std::unique_ptr<Shape>> から合計面積を求めよ。

a.cppANS
struct Shape { virtual double area() const = 0; virtual ~Shape() = default; }; struct Circle : Shape { double r; Circle(double r):r(r){} double area() const override { return 3.14159265*r*r; } }; struct Rect : Shape { double w,h; Rect(double w,double h):w(w),h(h){} double area() const override { return w*h; } }; double total(const std::vector<std::unique_ptr<Shape>>& v){ return std::accumulate(v.begin(), v.end(), 0.0, [](double a, const auto& p){ return a + p->area(); }); }

09RAII なファイルハンドラ

FILE* を持つ FileGuard クラスを作り、デストラクタで fclose せよ。コピー禁止・ムーブ可能にせよ。

a.cppANS
class FileGuard { FILE* f_; public: FileGuard(const char* p, const char* m) : f_(std::fopen(p,m)) {} ~FileGuard() { if (f_) std::fclose(f_); } FileGuard(const FileGuard&) = delete; FileGuard& operator=(const FileGuard&) = delete; FileGuard(FileGuard&& o) noexcept : f_(o.f_) { o.f_ = nullptr; } FILE* get() const { return f_; } };

10shared_ptr と循環参照

Node が shared_ptr<Node> next; を持つ循環リストで、Node が解放されない現象を再現し、weak_ptr で修正せよ。

circular を std::weak_ptr<Node> next; に変えることで use_count が 1 のまま解放される。対象を「所有しない参照」として持つ時に weak_ptr を使うのが鉄則。

11unique_ptr で木

子ノードを std::vector<std::unique_ptr<TreeNode>> で持つ Tree を実装し、深さを再帰で計算せよ。

a.cppANS
struct TreeNode { int v; std::vector<std::unique_ptr<TreeNode>> ch; }; int depth(const TreeNode& n){ int d = 0; for (auto& c : n.ch) d = std::max(d, depth(*c)); return d + 1; }

12Rule of 5 の手書き

ポインタで配列を保持するクラス Vec を、copy / move / dtor を全て自分で書け(noexcept付き)。

第 24 回 cpp-rule-of-035 に完全な実装例。copy は deep、move は所有権奪取 + nullptr 化、dtor で delete[]。

13型安全 max

2 引数 max のテンプレート関数を書き、int と double を同時に渡せるようにせよ(共通型に揃える)。

a.cppANS
template<class A, class B> auto max2(A a, B b) -> std::common_type_t<A,B> { return a > b ? a : b; }

14可変引数 sum

任意個数の数値を合計する可変引数テンプレートを書け(fold expression)。

a.cppANS
template<class... Args> auto sum(Args... xs) { return (xs + ... + 0); }

15Box<T> with CTAD

1 つの値を保持する Box<T> を作り、Box(42) で T を推論できるようにせよ。

a.cppANS
template<class T> struct Box { T v; }; // C++17 は暗黙 CTAD、明示なら: template<class T> Box(T) -> Box<T>; auto b = Box{42}; // Box<int>

16偶数の数え上げ

vector<int> 中の偶数の個数を count_if で求めよ。

std::count_if(v.begin(), v.end(), [](int x){ return x%2==0; });

17文字列ソート(長さ優先)

vector<string> を「短い順、同じ長さなら辞書順」でソート。

std::sort(v.begin(), v.end(), [](auto& a, auto& b){ if (a.size() != b.size()) return a.size() < b.size(); return a < b; });

18erase-remove イディオム

vector から負の要素を全て削除せよ(標準アルゴリズムで)。

v.erase(std::remove_if(v.begin(), v.end(), [](int x){ return x<0; }), v.end());

192 つの sorted 配列の集合演算

set_intersection / set_union / set_difference のうち 1 つを使って、2 つの sorted vector の共通要素を求めよ。

std::vector<int> out; std::set_intersection(a.begin(), a.end(), b.begin(), b.end(), std::back_inserter(out));

20次の並び順を生成

文字列 "abc" から始めて、すべての順列を next_permutation で列挙せよ。

std::string s = "abc"; do { std::cout << s << '\n'; } while (std::next_permutation(s.begin(), s.end()));

21throw する divide

int divide(int, int) を、b==0 で std::invalid_argument を投げるようにし、main で catch して表示せよ。

int divide(int a, int b) { if (b == 0) throw std::invalid_argument("b==0"); return a / b; }

22optional で安全な除算

同じ divide を、例外ではなく std::optional<int> を返す形に書き換えよ。

std::optional<int> safeDiv(int a, int b) { if (b == 0) return std::nullopt; return a / b; }

23例外安全な push

std::vector<T> の push_back が strong guarantee を持つために、T には何が必要か?

noexcept な move。なければ vector は再配置時に copy にフォールバックし、性能が落ちる。自作型は T(T&&) noexcept を明示。

24C の extern "C" ブリッジ

C 側から呼ばれる extern "C" 関数 int my_init() を書け。内部で C++ の例外が出てもコードに変換して返せ。

extern "C" int my_init() noexcept { try { Engine::init(); return 0; } catch (const std::bad_alloc&) { return -2; } catch (const std::exception&) { return -1; } catch (...) { return -99; } }

25hello from thread

std::thread で 3 つのラムダを並行起動し、全て join せよ。

std::vector<std::thread> ts; for (int i=0; i<3; ++i) ts.emplace_back([i]{ std::cout << "hello " << i << '\n'; }); for (auto& t : ts) t.join();

26mutex で安全なカウンタ

mutex + lock_guard で SafeCounter クラスを作り、inc / value を提供せよ。

第 59 回 3-2 の Counter クラス。単一変数なので std::atomic<int> でも可で、そちらの方が速い。

27async で並列合計

大きな vector<int> を 4 分割して async で並列合計せよ。

第 60 回 4-1 に完全実装。launch::async の明示と、std::cref で参照を渡す点に注意。

28atomic でスピンロック

std::atomic<bool> だけで簡単なスピンロックを書け(test_and_set 相当)。

class SpinLock { std::atomic<bool> f{false}; public: void lock() { while (f.exchange(true, std::memory_order_acquire)) {} } void unlock() { f.store(false, std::memory_order_release); } };

29map の要素を値でソート

map<string,int> を value 降順に並べた vector<pair> を作れ。

std::vector<std::pair<std::string,int>> v(m.begin(), m.end()); std::sort(v.begin(), v.end(), [](auto& a, auto& b){ return a.second > b.second; });

30enum class の stringify

enum class Color { Red, Green, Blue } の値を string に変換する関数を書け。

const char* toStr(Color c) { switch (c) { case Color::Red: return "Red"; case Color::Green: return "Green"; case Color::Blue: return "Blue"; } return "?"; }

31Strategy パターン

std::function を使って、ソート比較関数を差し替え可能なクラスを作れ。

第 67 回 cpp-design-patterns にパターン別の例がある。

32Observer パターン

イベント登録/通知を行う Signal<Args...> クラスを実装せよ。

vector<std::function<void(Args...)>> を保持し、emit で全員に呼び出す。弱参照管理が本格派。

33transform で 2 倍

vector<int> の各要素を 2 倍した新しい vector を transform で作れ。

std::vector<int> out(v.size()); std::transform(v.begin(), v.end(), out.begin(), [](int x){ return x*2; });

34accumulate で文字列連結

vector<string> を「, 」区切りで 1 つの string にせよ(先頭は区切りなし)。

std::string s = v.empty() ? "" : v[0]; for (size_t i=1; i<v.size(); ++i) s += ", " + v[i];

35CSV パーサ(簡易)

"a,b,c" → vector<string>{"a","b","c"}。istringstream + getline で。

std::vector<std::string> split(const std::string& s, char d){ std::vector<std::string> out; std::string cur; std::istringstream is(s); while (std::getline(is, cur, d)) out.push_back(cur); return out; }

36ファイル全行を vector に

std::ifstream で全行を vector<string> に読み込む関数を書け。

std::vector<std::string> lines(const std::string& p){ std::ifstream f(p); if (!f) throw std::runtime_error("open"); std::vector<std::string> out; for (std::string l; std::getline(f, l); ) out.push_back(std::move(l)); return out; }

372D 配列の転置

vector<vector<int>> (r×c) を転置した (c×r) を返す関数を書け。

auto transpose(const std::vector<std::vector<int>>& m){ if (m.empty()) return m; std::vector<std::vector<int>> r(m[0].size(), std::vector<int>(m.size())); for (size_t i=0; i<m.size(); ++i) for (size_t j=0; j<m[0].size(); ++j) r[j][i] = m[i][j]; return r; }

38Tokenizer テンプレート

イテレータ対から token を取り出す generic なクラステンプレートを設計せよ。

概念的にはイテレータを受けて operator*/operator++ で次トークンを提供。詳細は STL の istream_iterator を参考に。

39SFINAE で vector 判定

is_vector<T>::value が std::vector<*> のとき true になる trait を書け。

template<class> struct is_vector : std::false_type {}; template<class T, class A> struct is_vector<std::vector<T,A>> : std::true_type {};

40コンパイル時 fibonacci

constexpr 関数で fib(n) を書き、static_assert(fib(10)==55) を通せ。

constexpr int fib(int n) { if (n < 2) return n; int a = 0, b = 1; for (int i=2; i<=n; ++i) { int c = a+b; a = b; b = c; } return b; } static_assert(fib(10) == 55);