C++ 練習問題集(全40問)
STEP 1〜STEP 12 の内容を 40 問で総復習。各問題に「難易度」と「解答例」を付けています。完了チェックを付けて進捗を管理してください(ブラウザに保存されます)。
01 易 stringを逆順に
引数の std::string を逆順にした新しい string を返す関数 rev を書け。
入力: "hello" → 出力: "olleh"
解答を見る
a.cpp ANS
std::string rev (std::string s) {
std::reverse (s.begin (), s.end ());
return s;
}
02 易 vectorの最大・最小値
std::vector<int> を受け取り、最大値と最小値を pair で返す関数を書け。
解答を見る
a.cpp ANS
std::pair<int ,int > minmax (const std::vector<int >& v) {
auto [mn, mx] = std::minmax_element (v.begin (), v.end ());
return {*mn, *mx};
}
03 易 mapで頻度カウント
std::vector<std::string> から std::map<std::string, int> に単語頻度を数えろ。
解答
a.cpp ANS
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.cpp ANS
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;
}
05 中 string_view で部分一致カウント
大きなテキスト中に特定の単語が何回現れるか数える関数を、コピーせず(string_view)に書け。
解答
a.cpp ANS
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;
}
06 易 Vec2 クラス
2D ベクトル Vec2(double x, y) を作り、+, -, 長さ len(), << をオーバーロードせよ。
解答
a.cpp ANS
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.cpp ANS
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_; }
};
08 難 Shape 階層 + 多態
抽象クラス Shape に area() を純粋仮想で定義し、Circle/Rect を作り、std::vector<std::unique_ptr<Shape>> から合計面積を求めよ。
解答
a.cpp ANS
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 (); });
}
09 易 RAII なファイルハンドラ
FILE* を持つ FileGuard クラスを作り、デストラクタで fclose せよ。コピー禁止・ムーブ可能にせよ。
解答
a.cpp ANS
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_; }
};
10 中 shared_ptr と循環参照
Node が shared_ptr<Node> next; を持つ循環リストで、Node が解放されない現象を再現し、weak_ptr で修正せよ。
解答
circular を std::weak_ptr<Node> next; に変えることで use_count が 1 のまま解放される。対象を「所有しない参照」として持つ時に weak_ptr を使うのが鉄則。
11 中 unique_ptr で木
子ノードを std::vector<std::unique_ptr<TreeNode>> で持つ Tree を実装し、深さを再帰で計算せよ。
解答
a.cpp ANS
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 ;
}
12 難 Rule 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.cpp ANS
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.cpp ANS
template <class ... Args>
auto sum (Args... xs) { return (xs + ... + 0 ); }
15 中 Box<T> with CTAD
1 つの値を保持する Box<T> を作り、Box(42) で T を推論できるようにせよ。
解答
a.cpp ANS
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;
});
18 中 erase-remove イディオム
vector から負の要素を全て削除せよ(標準アルゴリズムで)。
解答
v.erase (std::remove_if (v.begin (), v.end (), [](int x){ return x<0 ; }), v.end ());
19 中 2 つの 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 ()));
21 易 throw する 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;
}
22 中 optional で安全な除算
同じ 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 を明示。
24 難 C の 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 ; }
}
25 易 hello 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 ();
26 中 mutex で安全なカウンタ
mutex + lock_guard で SafeCounter クラスを作り、inc / value を提供せよ。
解答
第 59 回 3-2 の Counter クラス。単一変数なので std::atomic<int> でも可で、そちらの方が速い。
27 中 async で並列合計
大きな vector<int> を 4 分割して async で並列合計せよ。
解答
第 60 回 4-1 に完全実装。launch::async の明示と、std::cref で参照を渡す点に注意。
28 難 atomic でスピンロック
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); }
};
29 中 map の要素を値でソート
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; });
30 中 enum 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 "?" ;
}
31 中 Strategy パターン
std::function を使って、ソート比較関数を差し替え可能なクラスを作れ。
解答
第 67 回 cpp-design-patterns にパターン別の例がある。
32 難 Observer パターン
イベント登録/通知を行う Signal<Args...> クラスを実装せよ。
解答
vector<std::function<void(Args...)>> を保持し、emit で全員に呼び出す。弱参照管理が本格派。
33 易 transform で 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 ; });
34 中 accumulate で文字列連結
vector<string> を「, 」区切りで 1 つの string にせよ(先頭は区切りなし)。
解答
std::string s = v.empty () ? "" : v[0 ];
for (size_t i=1 ; i<v.size (); ++i) s += ", " + v[i];
35 中 CSV パーサ(簡易)
"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;
}
37 中 2D 配列の転置
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;
}
38 難 Tokenizer テンプレート
イテレータ対から token を取り出す generic なクラステンプレートを設計せよ。
解答
概念的にはイテレータを受けて operator*/operator++ で次トークンを提供。詳細は STL の istream_iterator を参考に。
39 難 SFINAE で 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 );