Cデフォルト引数


279

Cの関数にデフォルトの引数を指定する方法はありますか?


6
C ++ではなく、少し優れたCが欲しいだけです。C +だと思う。C ++からさまざまな小さな改善が行われましたが、大きな混乱はありませんでした。そして、どうか、別のリンクローダーはありません。プリプロセッサのようなもう1つの手順にすぎません。標準化された。everywhere ...
ivo Welch、2015

サイドバーにリストされていない関連質問
シェルビームーアIII

私は野蛮人であるのをやめ、C ++(11、...)を上手に使う方法を学びます-jk!/私は炎を消します...しかし...あなたはそれを愛するようになります...ははは私は自分を助けることができません
moodboom 2017年

回答:


147

あんまり。唯一の方法は、varargs関数記述し、呼び出し元が渡さない引数のデフォルト値を手動で入力することです。


22
varargsを使用する場合のチェックの欠如が嫌いです。
dmckee ---元モデレーターの子猫

16
同様にあなたがする必要があります。これはお勧めしません。それが可能であることを伝えたかっただけです。
Eli Courtwright、2009

4
ただし、呼び出し元が引数を渡すかどうかをどのように確認しますか?私はこれがうまくいくと思います、あなたは彼がそれをパスしなかったことをあなたに言う発信者がいますか?これにより、アプローチ全体がやや使いづらくなると思います。呼び出し元は別の名前の関数を呼び出すこともできます。
ヨハネスシャウブ-litb 2009

3
open(2)システムコールは、必要な引数に応じて存在することができるオプションの引数のためにこれを使用し、printf(3)どのように多くの引数を指定があるだろうというフォーマット文字列を読み込みます。どちらも非常に安全かつ効果的にvarargを使用しますが、間違いなくそれらを台無しにすることはできますが、printf()特に非常に人気があるようです。
Chris Lutz、

4
@Eli:すべてのCコンパイラがgccであるとは限りません。printf()引数がフォーマット文字列と一致しない場合に警告するために、いくつかの高度なコンパイラマジックが進行中です。また、独自の可変関数に対して同様の警告が表示される可能性はないと思います(同じ形式のフォーマット文字列を使用しない限り)。
tomlogic

280

うわぁ、ここはみんな悲観論者です。答えはイエスです。

ささいなことではありません。最後に、コア関数、サポートする構造体、ラッパー関数、およびラッパー関数の周りのマクロを用意します。私の仕事では、これをすべて自動化するための一連のマクロを用意しています。フローを理解したら、同じことを簡単に実行できます。

私はこれを他の場所で書いたので、ここに要約を補足する詳細な外部リンクがあります:http : //modelingwithdata.org/arch/00000022.htm

向きを変えたい

double f(int i, double x)

デフォルト(i = 8、x = 3.14)を取る関数に。コンパニオン構造体を定義します。

typedef struct {
    int i;
    double x;
} f_args;

関数の名前を変更し、f_baseデフォルトを設定してベースを呼び出すラッパー関数を定義します。

double var_f(f_args in){
    int i_out = in.i ? in.i : 8;
    double x_out = in.x ? in.x : 3.14;
    return f_base(i_out, x_out);
}

次に、Cの可変個マクロを使用してマクロを追加します。このように、ユーザーは実際にf_args構造体にデータを入力していることを知り、通常どおりに実行していると考える必要はありません。

#define f(...) var_f((f_args){__VA_ARGS__});

OK、これで次のすべてが機能します:

f(3, 8);      //i=3, x=8
f(.i=1, 2.3); //i=1, x=2.3
f(2);         //i=2, x=3.14
f(.x=9.2);    //i=8, x=9.2

複合イニシャライザが正確なルールのデフォルトを設定する方法に関するルールを確認してください。

機能しないことが1つf(0)あります。欠損値とゼロを区別できないためです。私の経験では、これは注意する必要がありますが、必要に応じて対処できます。つまり、デフォルトが実際にはゼロの半分の時間です。

名前付き引数とデフォルトは本当にCでのコーディングをより簡単に、そしてもっと楽しいものにするので、私はこれを書くのに苦労しました。そして、Cはとてもシンプルでありながら、これをすべて可能にするのに十分なほど優れているという点で素晴らしいです。


16
+1クリエイティブ!これには制限がありますが、名前付きパラメーターをテーブルにもたらします。{}(空の初期化子)はエラーC99であることに注意してください。
u0b34a0f6ae 2011年

28
しかし、これはあなたにとって素晴らしいことです:標準では名前付きメンバーを複数回指定でき、後でオーバーライドされます。したがって、名前付きパラメータの場合のみ、デフォルトの問題を解決して空の呼び出しを許可できます。#define vrange(...) CALL(range,(param){.from=1, .to=100, .step=1, __VA_ARGS__})
u0b34a0f6ae 2011年

3
コンパイラのエラーが読みやすいといいのですが、これは素晴らしいテクニックです。ほぼpython kwargsのように見えます。
totowtwo 2012年

6
@RunHolt確かに単純ですが、客観的に優れているわけではありません。名前付きパラメーターには、呼び出しの読みやすさ(ソースコードの読みやすさを犠牲にする)などの利点があります。1つはソースの開発者に適しており、もう1つは関数のユーザーに適しています。「これがいい!」と捨てるのはちょっと急いで
アリス

3
@DawidPi:C11 6.7.9(19)、集約の初期化時:「明示的に初期化されていないすべてのサブオブジェクトは、静的ストレージ期間を持つオブジェクトと同じように暗黙的に初期化される」そして、ご存知のとおり、静的期間要素はゼロに初期化されます。 | NULL | \ 0。[これはc99にもありました。]
bk。

161

はい。:-)しかし、あなたが期待する方法ではありません。

int f1(int arg1, double arg2, char* name, char *opt);

int f2(int arg1, double arg2, char* name)
{
  return f1(arg1, arg2, name, "Some option");
}

残念ながら、Cではメソッドをオーバーロードできないため、2つの異なる関数が作成されます。それでも、f2を呼び出すと、実際にはデフォルト値でf1を呼び出すことになります。これは「Do n't Repeat Yourself」ソリューションであり、既存のコードのコピー/貼り付けを回避するのに役立ちます。


24
FWIW、関数の最後にある数を使用して、必要な引数の数を示します。任意の数値を使用するよりも簡単になります。:)
Macke、2011年

4
同じ目標を達成するための簡単な方法を示しているため、これは断然最良の答えです。変更したくない修正済みAPIの一部である関数がありますが、新しいパラメーターを取得するために必要です。もちろん、私はそれを逃したほど盲目的に明白です(デフォルトの
パラメーターの

4
f2はプリプロセッサマクロにすることもできます
osvein

41

デフォルト値に名前付きパラメーター(のみ)を使用する関数を作成できます。これはbk。の回答の続きです。

#include <stdio.h>                                                               

struct range { int from; int to; int step; };
#define range(...) range((struct range){.from=1,.to=10,.step=1, __VA_ARGS__})   

/* use parentheses to avoid macro subst */             
void (range)(struct range r) {                                                     
    for (int i = r.from; i <= r.to; i += r.step)                                 
        printf("%d ", i);                                                        
    puts("");                                                                    
}                                                                                

int main() {                                                                     
    range();                                                                    
    range(.from=2, .to=4);                                                      
    range(.step=2);                                                             
}    

C99標準では、初期化における後の名前が以前の項目をオーバーライドすることを定義しています。また、いくつかの標準的な位置パラメータも使用できます。それに応じてマクロと関数のシグネチャを変更するだけです。デフォルト値パラメーターは、名前付きパラメータースタイルでのみ使用できます。

プログラム出力:

1 2 3 4 5 6 7 8 9 10 
2 3 4 
1 3 5 7 9

2
これは、Wim ten BrinkまたはBKソリューションよりも簡単で簡単な実装のようです。他の人も持っていないこの実装の欠点はありますか?
stephenmm 2014

24

OpenCVは次のようなものを使用します。

/* in the header file */

#ifdef __cplusplus
    /* in case the compiler is a C++ compiler */
    #define DEFAULT_VALUE(value) = value
#else
    /* otherwise, C compiler, do nothing */
    #define DEFAULT_VALUE(value)
#endif

void window_set_size(unsigned int width  DEFAULT_VALUE(640),
                     unsigned int height DEFAULT_VALUE(400));

ユーザーが何を書くべきかわからない場合は、このトリックが役立ちます。

使用例


19

番号。

最新のC99標準でさえこれをサポートしていません。


単純ないいえはさらに優れていたでしょう;)
KevinDTimm

21
@kevindtimm:それは不可能です。SOは回答の最小長を義務付けています。私は試した。:)
アンワインド

2
私の答えを参照してください。:)
混乱


14

短い答え:いいえ。

少し長めの答え:昔、ある古いあなたがいることを文字列渡し回避策解析オプションの引数のためには:

int f(int arg1, double arg2, char* name, char *opt);

ここで、optには "name = value"のペアなどを含めることができます。

n = f(2,3.0,"foo","plot=yes save=no");

明らかに、これはたまにしか役に立ちません。一般に、機能のファミリーへの単一のインターフェースが必要な場合。


このアプローチは、c ++の専門的なプログラム(たとえばROOTなど)によって記述された素粒子物理学コードでも見られます。主な利点は、互換性を維持しながらほぼ無制限に拡張できることです。


2
これを可変引数と組み合わせると、あらゆる種類の楽しみがあります!
David Thornley、

5
私はカスタムstructを使用して、発信者にカスタムを作成させ、さまざまなオプションのフィールドに入力してから、アドレスで渡すかNULL、デフォルトのオプションを渡します。
Chris Lutz、

ROOTからコードパターンをコピーするのはひどい考えです!
innisfree 2017

13

おそらくこれを行うための最良の方法(状況によっては、状況によっては可能かどうかはわかりません)は、C ++に移動して「より優れたC」として使用することです。クラス、テンプレート、演算子のオーバーロード、その他の高度な機能を使用せずにC ++を使用できます。

これにより、関数のオーバーロードとデフォルトのパラメーター(および使用することを選択したその他の機能)を備えたCのバリアントが提供されます。C ++の制限されたサブセットのみを使用することを本当に真剣に考えている場合は、少し訓練する必要があります。

多くの人がC ++をこのように使用するのはひどい考えだと言うでしょう、そして彼らはポイントを持っているかもしれません。しかし、それは単なる意見です。全体を購入することなく快適なC ++の機能を使用することは妥当だと思います。C ++が成功した理由の重要な部分は、C ++が初期の頃に非常に多くのプログラマーによってまさにこの方法で使用されていたことにあると思います。


13

さらに別のオプションではstructsを使用します。

struct func_opts {
  int    arg1;
  char * arg2;
  int    arg3;
};

void func(int arg, struct func_opts *opts)
{
    int arg1 = 0, arg3 = 0;
    char *arg2 = "Default";
    if(opts)
      {
        if(opts->arg1)
            arg1 = opts->arg1;
        if(opts->arg2)
            arg2 = opts->arg2;
        if(opts->arg3)
            arg3 = opts->arg3;
      }
    // do stuff
}

// call with defaults
func(3, NULL);

// also call with defaults
struct func_opts opts = {0};
func(3, &opts);

// set some arguments
opts.arg3 = 3;
opts.arg2 = "Yes";
func(3, &opts);

9

番号。


2
回避策は何ですか?私はそれがあることがわかります20202020六角ではなく、どのように私はそれを入力するのですか?
Lazer、

5

マクロを使用する別のトリック:

#include <stdio.h>

#define func(...) FUNC(__VA_ARGS__, 15, 0)
#define FUNC(a, b, ...) func(a, b)

int (func)(int a, int b)
{
    return a + b;
}

int main(void)
{
    printf("%d\n", func(1));
    printf("%d\n", func(1, 2));
    return 0;
}

引数が1つだけ渡さbれた場合、デフォルト値(この場合は15)を受け取ります


4

いいえ。ただし、デフォルトの引数を使用して概算するために、関数(またはマクロ)のセットの使用を検討する場合があります。

// No default args
int foo3(int a, int b, int c)
{
    return ...;
}

// Default 3rd arg
int foo2(int a, int b)
{
    return foo3(a, b, 0);  // default c
}

// Default 2nd and 3rd args
int foo1(int a)
{
    return foo3(a, 1, 0);  // default b and c
}

4

はい、C99の機能でこれを行うことができます。これは、新しいデータ構造などを定義することなく、また実行時に関数がどのように呼び出されたかを決定する必要がなく、計算のオーバーヘッドなしで機能します。

詳細な説明については、私の投稿を参照してください

http://gustedt.wordpress.com/2010/06/03/default-arguments-for-c99/

イェンス


Seelはまたあなたから派生した私の答えです。
シェルビームーアIII

1
例をインライン化します。
user877329

3

通常はありませんが、gccでは、マクロを使用してfuncA()の最後のパラメーターをオプションにすることができます。

funcB()では、特別な値(-1)を使用して、「b」パラメーターのデフォルト値が必要であることを通知します。

#include <stdio.h> 

int funcA( int a, int b, ... ){ return a+b; }
#define funcA( a, ... ) funcA( a, ##__VA_ARGS__, 8 ) 


int funcB( int a, int b ){
  if( b == -1 ) b = 8;
  return a+b;
}

int main(void){
  printf("funcA(1,2): %i\n", funcA(1,2) );
  printf("funcA(1):   %i\n", funcA(1)   );

  printf("funcB(1, 2): %i\n", funcB(1, 2) );
  printf("funcB(1,-1): %i\n", funcB(1,-1) );
}

1

イェンス・ガステッドの答えを改善して、次のようにしました。

  1. インライン関数は採用されていません
  2. デフォルトは前処理中に計算されます
  3. モジュール式の再利用可能なマクロ
  4. 許可されたデフォルトの引数が不十分な場合に意味のあるコンパイラエラーを設定することが可能
  5. 引数の型が明確なままである場合、デフォルトはパラメータリストの末尾を形成する必要はありません。
  6. C11 _Genericと相互運用する
  7. 引数の数によって関数名を変えます!

variadic.h:

#ifndef VARIADIC

#define _NARG2(_0, _1, _2, ...) _2
#define NUMARG2(...) _NARG2(__VA_ARGS__, 2, 1, 0)
#define _NARG3(_0, _1, _2, _3, ...) _3
#define NUMARG3(...) _NARG3(__VA_ARGS__, 3, 2, 1, 0)
#define _NARG4(_0, _1, _2, _3, _4, ...) _4
#define NUMARG4(...) _NARG4(__VA_ARGS__, 4, 3, 2, 1, 0)
#define _NARG5(_0, _1, _2, _3, _4, _5, ...) _5
#define NUMARG5(...) _NARG5(__VA_ARGS__, 5, 4, 3, 2, 1, 0)
#define _NARG6(_0, _1, _2, _3, _4, _5, _6, ...) _6
#define NUMARG6(...) _NARG6(__VA_ARGS__, 6, 5, 4, 3, 2, 1, 0)
#define _NARG7(_0, _1, _2, _3, _4, _5, _6, _7, ...) _7
#define NUMARG7(...) _NARG7(__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0)
#define _NARG8(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) _8
#define NUMARG8(...) _NARG8(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define _NARG9(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, ...) _9
#define NUMARG9(...) _NARG9(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define __VARIADIC(name, num_args, ...) name ## _ ## num_args (__VA_ARGS__)
#define _VARIADIC(name, num_args, ...) name (__VARIADIC(name, num_args, __VA_ARGS__))
#define VARIADIC(name, num_args, ...) _VARIADIC(name, num_args, __VA_ARGS__)
#define VARIADIC2(name, num_args, ...) __VARIADIC(name, num_args, __VA_ARGS__)

// Vary function name by number of arguments supplied
#define VARIADIC_NAME(name, num_args) name ## _ ## num_args ## _name ()
#define NVARIADIC(name, num_args, ...) _VARIADIC(VARIADIC_NAME(name, num_args), num_args, __VA_ARGS__)

#endif

シンプルな使用シナリオ:

const uint32*
uint32_frombytes(uint32* out, const uint8* in, size_t bytes);

/*
The output buffer defaults to NULL if not provided.
*/

#include "variadic.h"

#define uint32_frombytes_2(   b, c) NULL, b, c
#define uint32_frombytes_3(a, b, c)    a, b, c
#define uint32_frombytes(...) VARIADIC(uint32_frombytes, NUMARG3(__VA_ARGS__), __VA_ARGS__)

そして、_Generic:

const uint8*
uint16_tobytes(const uint16* in, uint8* out, size_t bytes);

const uint16*
uint16_frombytes(uint16* out, const uint8* in, size_t bytes);

const uint8*
uint32_tobytes(const uint32* in, uint8* out, size_t bytes);

const uint32*
uint32_frombytes(uint32* out, const uint8* in, size_t bytes);

/*
The output buffer defaults to NULL if not provided.
Generic function name supported on the non-uint8 type, except where said type
is unavailable because the argument for output buffer was not provided.
*/

#include "variadic.h"

#define   uint16_tobytes_2(a,    c) a, NULL, c
#define   uint16_tobytes_3(a, b, c) a,    b, c
#define   uint16_tobytes(...) VARIADIC(  uint16_tobytes, NUMARG3(__VA_ARGS__), __VA_ARGS__)

#define uint16_frombytes_2(   b, c) NULL, b, c
#define uint16_frombytes_3(a, b, c)    a, b, c
#define uint16_frombytes(...) VARIADIC(uint16_frombytes, NUMARG3(__VA_ARGS__), __VA_ARGS__)

#define   uint32_tobytes_2(a,    c) a, NULL, c
#define   uint32_tobytes_3(a, b, c) a,    b, c
#define   uint32_tobytes(...) VARIADIC(  uint32_tobytes, NUMARG3(__VA_ARGS__), __VA_ARGS__)

#define uint32_frombytes_2(   b, c) NULL, b, c
#define uint32_frombytes_3(a, b, c)    a, b, c
#define uint32_frombytes(...) VARIADIC(uint32_frombytes, NUMARG3(__VA_ARGS__), __VA_ARGS__)

#define   tobytes(a, ...) _Generic((a),                                                                                                 \
                                   const uint16*: uint16_tobytes,                                                                       \
                                   const uint32*: uint32_tobytes)  (VARIADIC2(  uint32_tobytes, NUMARG3(a, __VA_ARGS__), a, __VA_ARGS__))

#define frombytes(a, ...) _Generic((a),                                                                                                 \
                                         uint16*: uint16_frombytes,                                                                     \
                                         uint32*: uint32_frombytes)(VARIADIC2(uint32_frombytes, NUMARG3(a, __VA_ARGS__), a, __VA_ARGS__))

そして、_Genericと組み合わせることのできない可変関数名の選択を使用します。

// winternitz() with 5 arguments is replaced with merkle_lamport() on those 5 arguments.

#define   merkle_lamport_5(a, b, c, d, e) a, b, c, d, e
#define   winternitz_7(a, b, c, d, e, f, g) a, b, c, d, e, f, g
#define   winternitz_5_name() merkle_lamport
#define   winternitz_7_name() winternitz
#define   winternitz(...) NVARIADIC(winternitz, NUMARG7(__VA_ARGS__), __VA_ARGS__)

1

はい

マクロを通して

3パラメータ:

#define my_func2(...) my_func3(__VA_ARGS__, 0.5)
#define my_func1(...) my_func2(__VA_ARGS__, 10)
#define VAR_FUNC(_1, _2, _3, NAME, ...) NAME
#define my_func(...) VAR_FUNC(__VA_ARGS__, my_func3, my_func2, my_func1)(__VA_ARGS__)

void my_func3(char a, int b, float c) // b=10, c=0.5
{
    printf("a=%c; b=%d; c=%f\n", a, b, c);
}

4番目の引数が必要な場合は、追加のmy_func3を追加する必要があります。VAR_FUNC、my_func2、my_funcの変更に注意してください

4パラメータ:

#define my_func3(...) my_func4(__VA_ARGS__, "default") // <== New function added
#define my_func2(...) my_func3(__VA_ARGS__, (float)1/2)
#define my_func1(...) my_func2(__VA_ARGS__, 10)
#define VAR_FUNC(_1, _2, _3, _4, NAME, ...) NAME
#define my_func(...) VAR_FUNC(__VA_ARGS__, my_func4, my_func3, my_func2, my_func1)(__VA_ARGS__)

void my_func4(char a, int b, float c, const char* d) // b=10, c=0.5, d="default"
{
    printf("a=%c; b=%d; c=%f; d=%s\n", a, b, c, d);
}

唯一の例外は、フロート変数にはデフォルト値を与えることができないことです(3つのパラメーターの場合のように最後の引数である場合を除き)。これは、マクロ引数内では受け入れられないピリオド( '。')を必要とするためです。しかし、my_func2マクロ(4つのパラメーターの場合)に見られるような回避策を理解できます)に

プログラム

int main(void)
{
    my_func('a');
    my_func('b', 20);
    my_func('c', 200, 10.5);
    my_func('d', 2000, 100.5, "hello");

    return 0;
}

出力:

a=a; b=10; c=0.500000; d=default                                                                                                                                                  
a=b; b=20; c=0.500000; d=default                                                                                                                                                  
a=c; b=200; c=10.500000; d=default                                                                                                                                                
a=d; b=2000; c=100.500000; d=hello  

0

はい、あなたは同じように何かをすることができます、ここであなたはあなたが得ることができる異なる引数リストを知る必要がありますが、あなたはすべてを処理するために同じ関数を持っています。

typedef enum { my_input_set1 = 0, my_input_set2, my_input_set3} INPUT_SET;

typedef struct{
    INPUT_SET type;
    char* text;
} input_set1;

typedef struct{
    INPUT_SET type;
    char* text;
    int var;
} input_set2;

typedef struct{
    INPUT_SET type;
    int text;
} input_set3;

typedef union
{
    INPUT_SET type;
    input_set1 set1;
    input_set2 set2;
    input_set3 set3;
} MY_INPUT;

void my_func(MY_INPUT input)
{
    switch(input.type)
    {
        case my_input_set1:
        break;
        case my_input_set2:
        break;
        case my_input_set3:
        break;
        default:
        // unknown input
        break;
    }
}

-6

なぜこれができないのか。

オプションの引数にデフォルト値を指定します。このように、関数の呼び出し元は必ずしも引数の値を渡す必要はありません。引数はデフォルト値を取ります。そして簡単に、その引数はクライアントのオプションになります。

例えば

void foo(int a、int b = 0);

ここでbはオプションの引数です。


10
驚くべき洞察、問題は、Cがオプションの引数またはオーバーロードされた関数をサポートしていないため、直接的なソリューションがコンパイルされないことです。
トーマス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.