最近、私は世界で最高の競争力のあるプログラマーのソリューションのいくつかを経験していました。私はそれらの人々がプログラムを書くときに、できればC++でテンプレートを使用していることを知りました。
最初に_#define
_とtypedef
を使用して独自のキーワードを作成するため、これらのプログラムを理解するのは困難です。これにより、コードがよりコンパクトになり、より速く記述できるようになります。
for()
ループのように、コードの記述中に何度も繰り返される多くの命令があります。したがって、長い命令を書く代わりに、マクロを作成できます。コードをよりコンパクトにするために、他のステートメントでも同じことができます。
_#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#define SC1(x) scanf("%lld",&x)
#define SC2(x,y) scanf("%lld%lld",&x,&y)
#define SC3(x,y,z) scanf("%lld%lld%lld",&x,&y,&z)
#define PF1(x) printf("%lld\n",x)
#define PF2(x,y) printf("%lld %lld\n",x,y)
#define PF3(x,y,z) printf("%lld %lld %lld\n",x,y,z)
#define REP(i,n) for(long long i=0;i<(n);i++)
#define FOR(i,a,b) for(long long i=(a);i<=(b);i++)
#define FORD(i,a,b) for(long long i=(a);i>=(b);i--)
#define WHILE(n) while(n--)
#define MEM(a, b) memset(a, (b), sizeof(a))
#define ITOC(c) ((char)(((int)'0')+c))
#define MID(s,e) (s+(e-s)/2)
#define SZ(a) strlen(a)
#define MOD 1000000007
#define MAX 10000000005
#define MIN -10000000005
#define PI 3.1415926535897932384626433832795
#define TEST(x) printf("The value of \"%s\" is: %d\n",#x,x)
const int INF = 1<<29;
typedef long long ll;
typedef unsigned long long ull;
#define FILEIO(name) \ freopen(name".in", "r", stdin); \ freopen(name".out", "w", stdout);
_
C/C++コンパイラのプリプロセッサがこれらのマクロをすべて処理する方法に興味があります。私は次のようなマクロを使用して定義されている定数を理解しています:
_#define MOD 10000000007
#define MAX 10000000005
#define MIN -10000000005
#define PI 3.1415926535897932384626433832795
_
しかし、次のように、命令を参照できる関数マクロを作成するにはどうすればよいですか。
_#define SC1(x) scanf("%lld",&x)
#define SC2(x,y) scanf("%lld%lld",&x,&y)
#define SC3(x,y,z) scanf("%lld%lld%lld",&x,&y,&z)
#define PF1(x) printf("%lld\n",x)
#define PF2(x,y) printf("%lld %lld\n",x,y)
#define PF3(x,y,z) printf("%lld %lld %lld\n",x,y,z)
#define REP(i,n) for(long long i=0;i<(n);i++)
#define FOR(i,a,b) for(long long i=(a);i<=(b);i++)
#define FORD(i,a,b) for(long long i=(a);i>=(b);i--)
#define WHILE(n) while(n--)
#define MEM(a, b) memset(a, (b), sizeof(a))
#define ITOC(c) ((char)(((int)'0')+c))
#define MID(s,e) (s+(e-s)/2)
#define SZ(a) strlen(a)
#define TEST(x) printf("The value of \"%s\" is: %d\n",#x,x)
_
上記のようなマクロを定義することで、プログラマーの生産性、効率、速度を向上させることができますよね?
今私の質問は:
_#define SC1(x) scanf("%lld",&x)
_
コンパイラーがこのマクロを処理する方法。より一般的には、プログラムのコンパイル/実行時にこの命令がプリプロセッサによってどのように理解されるか。
_#define REP(i,n) for(long long i=0;i<(n);i++)
#define FOR(i,a,b) for(long long i=(a);i<=(b);i++)
#define FORD(i,a,b) for(long long i=(a);i>=(b);i--)
_
この命令では、最初に計算する必要がある変数値を括弧_()
_で囲みます。マクロを操作するときの括弧の意味は何ですか?
\
_の意味:_#define FILEIO(name) \ freopen(name".in", "r", stdin); \ freopen(name".out", "w", stdout);
_
私はC/C++でのマクロの使用に関連する幅広い質問を知っています。誰かがソースコードで遭遇したときにこれらのマクロ命令を解釈/理解する方法について正しい方向を私に指摘できれば、私ができるように、それを本当に感謝します独自のマクロを変更して記述し、より高速で効率的なコーディングを行います。
コードの文字数がコード品質の最も重要なメトリックである、またはテンプレートがコードをより速く書くための最良の方法であるという根本的な概念に同意しないので、これはフレームチャレンジになります。
免責事項:これは(主に)C#開発者の観点から書かれていますが、私がしている議論は言語にとらわれません。
これらのプログラムは、最初に#defineとtypedefを使用して独自のキーワードを作成するため、理解が困難です。
これがexact理由で、これらのテンプレートが嫌いです。それらは、それらのテンプレートを作り出した開発者以外の開発者の可読性を大幅に損なう。基本的な言語のキーワードをより好きな名前に書き換えることは、何の価値もない主観的な聖なる戦争です。
その上、短縮すると、構成可能性が低下します。例えば。 for
ループは、提供することを目的とする機能セットを提供するために必要なだけ冗長です。冗長にしない場合は、機能セットの一部を省略します。
for
ループは、外部カウンターを定義し、そのカウンターを使用して終了条件をチェックするwhile
ループの圧縮バージョンです。それは合理的に可能な限り簡潔です。
これは、コードをよりコンパクトにするために行われます...
コードの記述に必要な文字数でコード品質を測定しようとすることは、自動車の構造に組み込まれた部品の量で自動車の性能を測定することに似ています。
私はこれを十分に強調することはできません:読みやすさは毎回簡潔さよりも優先されます。 KLOC(または文字数)は、コード品質の有効な尺度ではありません。コンパクトなコードは、開発者が理解しづらくする一方で、コンパイラーにまったく影響を与えない(パフォーマンスの向上はない)ため、一般的に悪いコードです。
これは、コードの長さやコースの長さに上限がないという意味ではありません。しかし、言語構造を圧縮しようとすることは合理的ではありません。
読みにくいコードのバリアントは、読みやすいコードよりもパフォーマンスが向上する可能性のある例外があります。しかし、基本的な言語構造の使用例は、これらの周辺の例の1つではありません。
...そしてより速く書くことができます。
これを処理する方法は他にもあります。 IDEを使用すると、言語のキーワードを再定義する必要なしに、コードの記述を迅速化できます。
Visual Studioの例として、for
と入力してTabキーを2回押すと、すべてが書き込まれます。
さらにタブ補完を使用すると、3つの操作(初期化、終了条件、増分条件)をすばやく書き込むことができます。
これは良いですか?どれどれ:
私は、任意に決定された定義ではなく、タブ補完などのIDEツールを使用することに関して、大きな勝利としてそれを呼びかけています。
また、付記として:
#define SC1(x) scanf("%lld",&x)
#define PF3(x,y,z) printf("%lld %lld %lld\n",x,y,z)
//...
これらのほとんどは通常のメソッドとして記述でき、ここでテンプレートを使用する理由はありません。単純なメソッドを記述しないようにテンプレートを誤用する領域にあなたは流れ込んでいるようです。
あなたのコメントに応じて
私はプログラミングコンテストについて言及しています。正しい心の誰もが業界でそのようなコードを望まない理由を教えてください
同じ理由で、食料品の買い物をするためにレースカーを運転する人はいません。レーサーカーは、レーサーと自動車メーカーの間の競争のために構築されていますが、レーサーカーは、大多数の自動車所有者にとって有用な日常の自動車ではありません。
同様に、業界コードは開発者間の課題として書かれているのではなく、タスクを実行し、必要に応じて適応できるように保守可能に書かれています。
テンプレート化されたコードから得られる限界速度の向上(秒の端数)は、何時間もの無駄な作業につながる可能性があるコードの可読性の低下によって相殺されます。
さらに、テンプレートは、1人の開発者が1回限りのコードベースを作成するシナリオで機能する場合があります。しかし、複数の開発者が同じコードベースに再度アクセスする必要がある場合、これは耐えられません。
上記のようなマクロを定義することにより、プログラマーの生産性、効率、速度を向上させることができます。
それは誤りです。あなたは自分で書いた「それらのプログラムを理解するのが難しい」-したがって、これらのマクロはコードを読んで理解するための生産性を向上させませんでした。
でも、直接質問にお答えしましょう。
#define SC1(x) scanf("%lld",&x)
のようなマクロを定義すると、コンパイラはこのマクロをどのように処理しますか?
実際には、処理を行うのはCまたはC++コンパイラ(少なくともその主要部分ではありません)ではなく、これを行う preprocessor というコンポーネントです。簡単に言えば、ここのプリプロセッサは、実際のコンパイルが始まる前に、いくつかのテキスト置換を行います。たとえば、上記のマクロ定義の後、プリプロセッサは次のようなコード行を置き換えます
_ SC1(foo);
_
沿って
_ scanf("%lld",&foo);
_
これが、メインコンパイラが最終的に表示するコードです。
2つ目の質問は次のとおりです。
マクロを書くときの括弧の意味は何ですか?
プリプロセッサはテキスト処理のみを行うため、マクロの変数が評価される前に、マクロが展開されます。つまり、引数が最初に評価され、結果が関数内で処理される通常の関数呼び出しのように結果が意味的に動作することを確認するには、括弧が必要です。
たとえば、これらの余分な括弧がない場合、次のような行
_ `REP(i, 1|2)`
_
に拡張されます
_ for(long long i=0;i< 1|2 ;i++)
_
これはfor(long long i=0;i< (1|2) ;i++)
とは異なるセマンティクスを持っています。実際、括弧がないと、これは無限ループを導入する難読化された方法になります。なぜなら、_i< 1|2
_は、 iの値。これは、「<」の前に「|」よりも高い演算子があるためです。 CおよびC++。
そしてあなたの3番目の質問へ:
次の説明での\の意味:
_#define
_構文では、通常、マクロ定義全体が同じ行にあると想定しています。複数行にまたがるには、行の最後にバックスラッシュ「\」を追加する必要があります。しかし、あなたの例では、同じ行のそれらのステートメント間のバックスラッシュはまったく効果がなく、おそらく省略できます。
このようなマクロは、コードが記述されているが、誰もコードを読み取ったり維持したりすることのない競争力のあるプログラミングに役立ちます。それは書かれ、それが機能することを証明するために実行され、それから(本質的に)捨てられます。コードが維持されないという事実を考えると、初期開発の速度のみを考慮して最適化するのが妥当です。
ただし、通常のコードは、何年も維持されます。その間、通常は実際にコードを書き込むよりも、コードを読み取るのに(かなり)多くの時間を費やします。これらのようなマクロは、読み取りの(非常に)費用で書き込み用に最適化されるため、競合プログラミング以外では、ほぼ間違いなく正味の損失です(そして、それはかなり大きな損失です)。
マクロを使用すると、コードがエラーが発生しやすくなり、判読不能になり、保守が困難になります。
言語の専門家や開発者からの標準的な推奨はマクロを避けることであり、ほとんどの人はそれに同意しています。
マクロでできることは、コードを密に(=読み取り不可能)にすることです。そのため、ターゲットがソースファイルのディスク領域を節約する(->コードゴルフを参照)場合、または特定のコンテストに勝つ場合、マクロは役立ちますが、fastおよびefficientの場合、影響はありません。コードが適切に記述されていて、コンパイラーが優れている場合、コードは高速です。マクロはプリコンパイラによって展開され、これにはまったく影響しません。
コードwritingは、マクロを使用するとより高速に見えるかもしれませんが、特定のレベルの複雑さ、つまり約1画面分に達するまでのみです。
リストしたマクロの多くは数十年前のコーディングスタイルであるため、ソースが15年古くなっている可能性があります。たとえばprintf
には、C++ 07以降の推奨オプションがあります。