C および C++ プログラミングの壮大な世界に参加する準備はできていますか? 数行の簡単な C++ を学習した後、自分の存在に疑問を感じてみませんか?
あなたの答えが「うん!」「そうだね」「なぜダメなの?」なら、あなたの知識をテストしましょう。C または C++ に関連する複数の質問が出題されます。
正解と解説は物語の最後にあります。頑張ってください!
1. 最小のプログラム
main;
このプログラムを C コンパイラを使用してコンパイルしようとすると何が起こるでしょうか?
- コンパイルされない
- コンパイルはされるが、リンクはされない
- コンパイルしてリンクします
2. フォーク
#include <iostream> #include <unistd.h> int main() { for(auto i = 0; i < 1000; i++) std::cout << "Hello world!\n"; fork(); }
このプログラムは何行印刷しますか?
- 1000
- 1000未満
- 1000以上
3. 必要なのはインデックスだけ
#include <iostream> int main() { int array[] = { 1, 2, 3 }; std::cout << (4, (1, 2)[array]) << std::endl; }
このプログラムは何を印刷しますか?
- 1
- 2
- 3
- 4
- コンパイルされない
- 未定義
4. 正規表現
#include <regex> #include <iostream> int main() { std::regex re("(.*|.*)*O"); std::string str("0123456789"); std::cout << std::regex_match(str, re); return 0; }
この正規表現がこの入力文字列と一致するまでどのくらいの時間がかかりますか?
- 1ミリ秒
- 1秒
- 1分
- 1時間
- 1年
- 永遠に
5. 移動とラムダ
#include <iostream> struct Foo { Foo() { std::cout << "Foo()\n"; } Foo(Foo&&) { std::cout << "Foo(Foo&&)\n"; } Foo(const Foo&) { std::cout << "Foo(const Foo&)\n"; } }; int main() { Foo f; auto a = [f = std::move(f)]() { return std::move(f); }; Foo f2(a()); return 0; }
このプログラムによって印刷される最後の行は…
-
Foo()
-
Foo(Foo&&)
-
Foo(const Foo&)
6. Xとバー
#include <iostream> int x = 0; int bar(int(x)); int main() { std::cout << bar; }
このプログラムは何を印刷しますか?
-
0
-
1
-
0x0
- コンパイルされない
- リンクしません
7. コンストラクター
#include <iostream> struct Foo { Foo() { std::cout << "Foo()\n"; } Foo(const Foo&) { std::cout << "Foo(const Foo&)\n"; } Foo(int) { std::cout << "Foo(int)\n"; } Foo(int, int) { std::cout << "Foo(int, int)\n"; } Foo(const Foo&, int) { std::cout << "Foo(const Foo&, int)\n"; } Foo(int, const Foo&) { std::cout << "Foo(int, const Foo&)\n"; } }; void f(Foo) {} struct Bar { int i, j; Bar() { f(Foo(i, j)); f(Foo(i)); Foo(i, j); Foo(i); Foo(i, j); } }; int main() { Bar(); }
このプログラムによって印刷される最後の行は…
-
Foo(int, int)
-
Foo(const Foo&, int)
-
Foo(int, const Foo&)
-
Foo(int)
結論の代わりに
この奇妙な断片が世の中で見つかることが決してないことを願います。
回答
最小のプログラム
これは正当な C コードです。コンパイルとリンクは正常に行われます。実行しようとするとクラッシュします。main
main;
- はグローバル変数です。
C コードでは、多くのものを省略できます。たとえば、グローバル変数の型を省略できます。デフォルトでは、コンパイラはこの型が
int
であると想定します。また、C では名前のマングリングが行われないため (C++ とは異なります)、リンク時に変数main
と関数main
を区別する方法はありません。
したがって、コンパイラは有効なコードをコンパイルし、リンカーはオブジェクト ファイル内で
main
という名前のものを見つけてプログラムをリンクします。
フォーク
これは、C や C++ の機能というよりは、POSIX の機能です。IO 操作の実装では、パフォーマンスを最適化するためにバッファを使用します。fork
fork
呼び出すと、OS はプロセス メモリのコピーオンライト複製を作成し、IO バッファも複製される可能性が高く、バッファリングされた文字列は 1000 回以上印刷される可能性があります。
必要なのはインデックスだけ
答えは3です
このコードを理解するために、C および C++ でのインデックスの動作を詳しく見てみましょう。array
array[index]
は*(array + index)
と同じであり、(index + array)
と同じであり、index[array
と同じです。2 番目のヒントは演算子
,
です。これは二項演算子であり、左の引数を破棄して右の引数を返します。
正規表現
何が起こるかを予測することは不可能です。動作は実装次第です。
私の環境では、このプログラムは例外を発生させます
The complexity of an attempted match against a regular expression exceeded a pre-set level.
他の可能性のあるオプションは、驚くほど長い時間がかかるか、期待どおりに動作するかです。これは、正規表現を実装するための 2 つのアプローチが考えられるためです。
まず、正規表現を有限オートマトンに変換します
O(n**2)
(n - パターンの長さ))。文字列を一致させるにはO(m)
(m - 文字列の長さ) が必要です。このアプローチではバックトラッキングはサポートされません。
2 番目 - 貪欲なアプローチ + DFS は、バックトラッキングをサポートしますが、特定のパターンでは指数関数的な時間複雑性が発生する傾向があります。
移動とラムダ
答えは
Foo(const Foo&)
です。ラムダはデフォルトで不変であり、[]
を使用してラムダにキャプチャされたすべての値は暗黙的にconst
です。これにより、ラムダのべき等動作が解除されます。
f
を移動するとconst Foo&&
が作成されます。constconst Foo&&
は奇妙な型なので、コンパイラはFoo
をコピーするだけです。
これを修正するには 2 つの方法があります。
可変ラムダを作成する
auto a = [f = std::move(f)]() mutable { return std::move(f); };
コンストラクタ
Foo(const Foo&&)
を宣言します。
Xとバー
プログラムは
1
出力します。
int bar(int(x));
— は関数を宣言する奇妙な方法ですが、int bar(int x);
と同じです。型キャストと混同している場合は、
int bar((int(x)));
- これは型キャストです。
関数アドレスを
bool
に暗黙的にキャストしようとすると、そのようなキャストの結果は常にtrue
なります。
関数
bar()
一度も使用されていないため、リンク時に参照されていないシンボルのエラーを回避できます。
コンストラクター
最後の行は
Foo(const Foo&, int)
です。
Foo(i)
は変数宣言であり、Foo i
と同じです。したがって、i
という名前のクラス メンバーはこのスコープ内で隠されています。