1

関数を記述して、任意の数の引数をフォーマットしてコンソールに出力しようとしています。関数に次の署名を持たせたい

void formatted_text(...);

そして、この関数を次のように呼び出したいと思います。

formatted_text("String 1", 1, "String 2", 2.2);

問題は、関数内で、どの引数がどのデータ型のものかわからないことです。1つの解決策は、すべての引数を文字列に変換してから、このように関数に渡すようにユーザーに依頼することです

formatted_text("String 1", "1", "String 2", "2.2");

しかし、それはそれほど便利ではありません。私ができるもう1つのことは、ユーザーに各引数のデータ型を提供するように依頼することですが、これもあまり良い解決策ではありません。

データ型を知らなくてもデータ型を文字列に変換する方法はありますか。私は答えを知っています……しかし、あなたは私が知らない何かを知っているかもしれません.

この王様の要件を処理する最善の方法は何でしょうか。

4

2 に答える 2

2

Cデータ型は、Java、Python、PHPなどの他の言語でよく見られるように、本質的に自己記述型ではありませんformatted_text(...)スタックに渡すバイトを解釈する方法を知ることができるように、メタデータを提供する必要があります。コメントで述べられているように、渡すフォーマット文字列(つまり最初のパラメータ)printfは、メタデータがとることができるそのような形式の1つです。

于 2013-02-01T01:41:17.987 に答える
1

問題には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、それを渡すことができ、それがスコープから外れると、デストラクタが呼び出されます。

于 2013-02-01T02:41:43.357 に答える