問題には2つの部分があります。可変数の引数を取り、便利な印刷関数が必要です。そして、任意のユーザー定義データ型を印刷するための良い方法を望んでいます。
最初の部分では、と同じ種類の「varargs」関数を記述できますprintf()
。しかし、問題があります。引数がいくつあるかを数える方法がなく、引数のタイプがわかりません。printf()
すべての引数は文字列へのポインタである必要があり、最後の引数は常にNULL
ポインタである必要があると言う場合よりも、関数を単純にすることができます。次に、に到達するまで続く単純なループを記述できますNULL
。詳細については、 Googleで検索しc varargs
てください。
これを行うとNULL
、最後の引数としてaを使用して関数の呼び出しに常に展開されるマクロを作成し、ユーザーがを置くのを忘れる可能性を低くすることができますNULL
。このようなもの:
#define CONVENIENT_PRINT(...) \
my_convenient_print_function(__VA_ARGS__, NULL)
もちろん、楽しい部分は、いくつかのコンパイラが上記でうまく機能することですが、いくつかはそうではありません。可変数の引数を使用してマクロを作成することは、Cの他の部分ほど移植性がありません。ただし、上記の形式はMicrosoft C、GCC、およびClangで機能するため、おそらく問題ありません。
2番目の部分では、Cはあまり役に立ちません。この問題に対する2つの可能な解決策を提案できます。1つは、厳密な命名規則を設定して、ユーザーがどの関数を呼び出すかを簡単に理解できるようにすることです。どのデータ型FOO
でも常に関数がStringFromFoo()
あり、ユーザーは正しい関数を呼び出すことを学ぶ必要があります。2つ目は、独自のオブジェクト指向型を実装し、各オブジェクトに、呼び出されるメソッド関数ToString()
などへのポインターがあることを確認することです。このようなもの:
typedef struct
{
char const *(*ToString)(void const *this);
} FOO_CLASS; // class struct
static FOO_CLASS _foo_class;
char const *FooToString(void const *this)
{
return NULL; // yeah I'm not really implementing this here
}
_foo_class.ToString = FooToString;
typedef struct
{
// member variables go here
int x;
float f;
// need a reference back to the class; just one
FOO_CLASS *pclass;
} FOO; // instance struct
int InitFoo(int x, float f, FOO **ppfooNew)
{
FOO *pfoo = malloc(sizeof(FOO));
if (!pfoo)
{
*ppfooNew = NULL;
return ENOMEM;
}
pfoo->x = x;
pfoo->f = f;
pfoo->pclass = &_foo_class;
*ppfooNew = pfoo;
return 0; // success!
}
// in your code:
FOO *pfoo;
char const *str;
if (InitFoo(x, f, &pfoo))
goto crash_and_burn;
str = pfoo->pclass->ToString(pfoo);
うわー、それはオブジェクト指向言語よりもはるかに厄介です!これはオブジェクト指向のコードですが、Cはまったく役に立ちません。あなたは自分ですべての仕事をしなければなりません。
pfoo
クラスを検索し、クラスでメソッド関数を見つけ、それでもpfoo
メソッド関数の引数(this
ポインター)として渡す必要があることに注意してください。C++はこれをすべて行います。
少なくとも、メソッド関数を呼び出す一般的な作業用のマクロを作成できます。
#define STR(instance) \
(instance)->pclass->ToString(instance)
str = STR(pfoo);
この簡単な例では、クラスに関数が1つだけあります。実際の例では、おそらくそれよりも多くの機能があります!単一の関数の場合、それをインスタンスに入れるだけで作業は少なくなりますが、それは拡張性がありません。これを行う場合は、一貫して行う必要があります。
ですから、ほとんどの人は命名規則の解決策を採用し、それで十分だと思います。部分的にオブジェクト指向のスタイルで大規模なプロジェクトを作成しましたが、それがコードをより管理しやすくするのに役立ったと思います。そして、オブジェクト指向のスタイルでさえ、私はまだ多くの命名規則を持っていました。
PS上記のコードは実際には実装されていないことに気付いたかもしれませんがToString()
、実際にはそれを実装するのはちょっと面倒です。文字列はどこに行きますか?必要性が非常に単純な場合は、ToString()
関数内で静的バッファーを使用することもできますが、それは良い解決策ではありません...それ以外の場合は、バッファーを渡すように変更するか、新しい文字列バッファーを取得するToString()
ために呼び出す必要があります。次に、 ;malloc()
の失敗の可能性を処理します。malloc()
次に、文字列が終わったらどういうわけか呼び出しfree()
ます(または恥ずかしそうにメモリをリークします!)。これは、C ++があなたのために行うさらに多くのことです...あなたはそれを作成しstd::string
、それを渡すことができ、それがスコープから外れると、デストラクタが呼び出されます。