C argumen default


279

Apakah ada cara untuk menentukan argumen default ke fungsi di C?


6
hanya ingin C sedikit lebih baik, bukan C ++. pikirkan C +. dengan berbagai perbaikan kecil yang diangkat dari C ++, tapi bukan kekacauan besar. Dan, tolong, tidak ada tautan-loader yang berbeda. seharusnya hanya langkah seperti preprosesor. terstandarisasi. di mana-mana ...
ivo Welch

Pertanyaan terkait yang tidak saya lihat tercantum di bilah samping.
Shelby Moore III

Saya akan mengatakan berhenti menjadi orang barbar dan belajar menggunakan C ++ (11, ...) dengan baik - jk! / aku memadamkan api ... tapi ... kamu akan menyukainya ... hahaha aku tidak bisa menahan diri, maaf
moodboom

Jawaban:


147

Tidak juga. Satu-satunya cara adalah dengan menulis fungsi varargs dan secara manual mengisi nilai default untuk argumen yang tidak dilewati pemanggil.


22
Saya benci kurangnya pengecekan saat menggunakan varargs.
dmckee --- ex-moderator kitten

16
Anda juga harus; Saya sebenarnya tidak merekomendasikan ini; Saya hanya ingin menyampaikan bahwa itu mungkin.
Eli Courtwright

4
Namun, bagaimana Anda ingin memeriksa apakah penelepon melewati argumen atau tidak? Saya pikir ini berfungsi, tidakkah Anda memiliki penelepon untuk memberi tahu Anda bahwa dia tidak lulus? Saya pikir ini membuat seluruh pendekatan agak kurang bermanfaat - penelepon juga dapat memanggil fungsi dengan nama lain.
Johannes Schaub - litb

3
The open(2)system call menggunakan ini untuk argumen opsional yang mungkin ada, tergantung pada argumen yang diperlukan, dan printf(3)membaca string format yang menetapkan berapa argumen akan ada. Keduanya menggunakan vararg dengan cukup aman dan efektif, dan meskipun Anda pasti dapat mengacaukannya, printf()terutama tampaknya cukup populer.
Chris Lutz

4
@ Eli: Tidak semua kompiler C adalah gcc. Ada beberapa keajaiban kompiler canggih yang terjadi untuk memperingatkan ketika printf Anda () args tidak cocok dengan string format Anda. Dan saya tidak berpikir itu mungkin untuk mendapatkan peringatan serupa untuk fungsi variadic Anda sendiri (kecuali jika mereka menggunakan gaya string format yang sama).
tomlogic

280

Wow, semua orang pesimis di sini. Jawabannya iya.

Ini bukan hal sepele: pada akhirnya, kita akan memiliki fungsi inti, struct pendukung, fungsi wrapper, dan makro di sekitar fungsi wrapper. Dalam pekerjaan saya, saya memiliki satu set makro untuk mengotomatisasi semua ini; setelah Anda memahami alurnya, akan mudah bagi Anda untuk melakukan hal yang sama.

Saya telah menulis ini di tempat lain, jadi di sini ada tautan eksternal terperinci untuk melengkapi ringkasan di sini: http://modelingwithdata.org/arch/00000022.htm

Kami ingin berbelok

double f(int i, double x)

menjadi fungsi yang menggunakan default (i = 8, x = 3.14). Tentukan struct pengiring:

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

Ganti nama fungsi Anda f_base , dan tentukan fungsi pembungkus yang menetapkan default dan memanggil basis:

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);
}

Sekarang tambahkan makro, menggunakan makro variadic C. Dengan cara ini pengguna tidak perlu tahu mereka sebenarnya sedang mengisi af_args struct dan berpikir mereka melakukan hal yang biasa:

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

Oke, sekarang semua hal berikut ini akan berfungsi:

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

Periksa aturan tentang bagaimana initializers majemuk menetapkan default untuk aturan yang tepat.

Satu hal yang tidak akan berhasil: f(0) karena kami tidak dapat membedakan antara nilai yang hilang dan nol. Dalam pengalaman saya, ini adalah sesuatu yang harus diwaspadai, tetapi dapat diatasi ketika kebutuhan muncul --- separuh waktu default Anda benar-benar nol.

Saya mengalami kesulitan menulis ini karena saya pikir argumen bernama dan default benar-benar membuat pengkodean dalam C lebih mudah dan bahkan lebih menyenangkan. Dan C luar biasa karena begitu sederhana dan masih memiliki cukup di sana untuk memungkinkan semua ini.


16
+1 kreatif! Ini memiliki keterbatasan tetapi juga membawa parameter bernama ke tabel. Perhatikan bahwa, {}(penginisialisasi kosong) adalah kesalahan C99.
u0b34a0f6ae

28
Namun, di sini ada sesuatu yang bagus untuk Anda: Standar memungkinkan menentukan anggota bernama beberapa kali, yang kemudian ditimpa. Jadi hanya untuk parameter bernama Anda dapat menyelesaikan masalah default dan memungkinkan panggilan kosong. #define vrange(...) CALL(range,(param){.from=1, .to=100, .step=1, __VA_ARGS__})
u0b34a0f6ae

3
Saya harap kesalahan kompiler dapat dibaca, tapi ini adalah teknik yang hebat! Hampir terlihat seperti python kwargs.
totowtwo

6
@RunHolt Meskipun tentu saja lebih sederhana, itu tidak lebih baik secara objektif; parameter bernama datang dengan manfaat seperti kemudahan keterbacaan panggilan (dengan mengorbankan keterbacaan kode sumber). Satu lebih baik untuk pengembang sumber, yang lain lebih baik untuk pengguna fungsi. Agak terburu-buru membuang "yang ini lebih baik!"
Alice

3
@DawidPi: C11 6.7.9 (19), pada inisialisasi agregat: "semua sub-objek yang tidak diinisialisasi secara eksplisit harus diinisialisasi secara implisit sama dengan objek yang memiliki durasi penyimpanan statis" Dan seperti yang Anda ketahui, elemen durasi statis diinisialisasi ke nol | NULL | \ 0. [Ini juga di c99.]
bk.

161

Iya. :-) Tapi tidak dengan cara yang Anda harapkan.

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");
}

Sayangnya, C tidak memungkinkan Anda untuk membebani metode sehingga Anda akan memiliki dua fungsi yang berbeda. Namun, dengan memanggil f2, Anda sebenarnya akan memanggil f1 dengan nilai default. Ini adalah solusi "Don't Repeat Yourself", yang membantu Anda menghindari menyalin / menempel kode yang ada.


24
FWIW, saya lebih suka menggunakan nomor di akhir fungsi untuk menunjukkan jumlah argumen yang dibutuhkan. Membuatnya lebih mudah daripada hanya menggunakan nomor acak. :)
Macke

4
Sejauh ini, inilah jawaban terbaik karena ini menunjukkan cara sederhana untuk mencapai tujuan yang sama. Saya memiliki fungsi yang merupakan bagian dari API tetap yang tidak ingin saya ubah, tetapi saya membutuhkannya untuk mengambil param baru. Tentu saja, sangat jelas bahwa saya merindukannya (terjebak pada memikirkan param default!)
RunHolt

4
f2 juga bisa menjadi makro preprosesor
osvein

41

Kita dapat membuat fungsi yang menggunakan parameter bernama (hanya) untuk nilai default. Ini adalah kelanjutan dari jawaban 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);                                                             
}    

Standar C99 mendefinisikan bahwa nama-nama yang belakangan dalam inisialisasi menimpa item sebelumnya. Kami juga dapat memiliki beberapa parameter posisi standar, cukup ubah tanda tangan makro dan fungsinya. Parameter nilai default hanya dapat digunakan dalam gaya parameter bernama.

Output program:

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

2
Ini tampaknya seperti implementasi yang lebih mudah dan lebih lurus daripada solusi Wim ten Brink atau BK. Apakah ada kelemahan pada implementasi ini yang tidak dimiliki oleh yang lain?
stephenmm

24

OpenCV menggunakan sesuatu seperti:

/* 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));

Jika pengguna tidak tahu apa yang harus ia tulis, trik ini dapat membantu:

contoh penggunaan


19

Tidak.

Bahkan standar C99 terbaru tidak mendukung hal ini.


tidak sederhana akan lebih baik;)
KevinDTimm

21
@kevindtimm: Itu tidak mungkin, SO mengamanatkan panjang minimum jawaban. Saya mencoba. :)
bersantai

2
Silakan merujuk jawaban saya. :)
chaos


14

Jawaban singkat: Tidak.

Jawaban yang sedikit lebih lama: Ada solusi lama, lama tempat Anda meneruskan string yang Anda uraikan untuk argumen opsional:

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

di mana opt dapat menyertakan pasangan "name = value" atau sesuatu, dan yang Anda inginkan

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

Jelas ini hanya berguna sesekali. Umumnya ketika Anda ingin antarmuka tunggal ke keluarga fungsi.


Anda masih menemukan pendekatan ini dalam kode fisika partikel yang ditulis oleh program profesional dalam c ++ (seperti misalnya ROOT ). Keuntungan utamanya adalah dapat diperpanjang hampir tanpa batas dengan tetap mempertahankan kompatibilitas.


2
Kombinasikan ini dengan varargs dan Anda memiliki semua jenis kesenangan!
David Thornley

5
Saya akan menggunakan custom structdan membuat penelepon membuat, mengisi kolom untuk opsi yang berbeda, dan kemudian meneruskannya dengan alamat, atau meneruskan NULLuntuk opsi default.
Chris Lutz

Menyalin pola kode dari ROOT adalah ide yang buruk!
innisfree

13

Mungkin cara terbaik untuk melakukan ini (yang mungkin atau mungkin tidak mungkin dalam kasus Anda tergantung pada situasi Anda) adalah pindah ke C ++ dan menggunakannya sebagai 'C yang lebih baik'. Anda dapat menggunakan C ++ tanpa menggunakan kelas, templat, overloading operator atau fitur canggih lainnya.

Ini akan memberi Anda varian C dengan kelebihan fungsi dan parameter default (dan fitur apa pun yang Anda pilih untuk digunakan). Anda hanya perlu sedikit disiplin jika Anda benar-benar serius hanya menggunakan subset terbatas dari C ++.

Banyak orang akan mengatakan itu ide yang buruk untuk menggunakan C ++ dengan cara ini, dan mereka mungkin ada benarnya. Tapi itu hanya sebuah opini; Saya pikir itu sah untuk menggunakan fitur C ++ yang Anda merasa nyaman tanpa harus membeli semuanya. Saya pikir bagian penting dari alasan keberhasilan C ++ adalah bahwa itu digunakan oleh banyak programmer di hari-hari awal persis seperti ini.


13

Namun opsi lain menggunakan 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

Tidak.


2
apa solusinya? Saya bisa melihat bahwa itu 20202020dalam hex, tapi bagaimana cara mengetiknya?
Lazer

5

Trik lain menggunakan makro:

#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;
}

Jika hanya satu argumen yang diteruskan, bterima nilai default (dalam hal ini 15)


4

Tidak, tetapi Anda mungkin mempertimbangkan untuk menggunakan serangkaian fungsi (atau makro) untuk memperkirakan menggunakan argumen default:

// 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
}


3

Umumnya tidak, tetapi dalam gcc Anda dapat membuat parameter terakhir dari funcA () opsional dengan makro.

Dalam funcB () saya menggunakan nilai khusus (-1) untuk memberi sinyal bahwa saya memerlukan nilai default untuk parameter '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

Saya meningkatkan jawaban Jens Gustedt sehingga:

  1. fungsi sebaris tidak digunakan
  2. default dihitung selama preprocessing
  3. makro dapat digunakan kembali modular
  4. mungkin untuk mengatur kesalahan kompiler yang bermakna sesuai dengan kasus argumen tidak mencukupi untuk default yang diizinkan
  5. default tidak diperlukan untuk membentuk ekor dari daftar parameter jika tipe argumen akan tetap tidak ambigu
  6. interopts dengan C11 _Generic
  7. variasikan nama fungsinya dengan jumlah argumen!

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

Skenario penggunaan yang disederhanakan:

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__)

Dan dengan _Generik:

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__))

Dan dengan pemilihan nama fungsi variadic, yang tidak dapat digabungkan dengan _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

IYA

Melalui makro

3 parameter:

#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);
}

Jika Anda menginginkan argumen ke-4, maka my_func3 tambahan perlu ditambahkan. Perhatikan perubahan dalam VAR_FUNC, my_func2 dan my_func

4 Parameter:

#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);
}

Hanya pengecualian bahwa variabel float tidak dapat diberi nilai default ( kecuali jika itu adalah argumen terakhir seperti dalam kasus 3 parameter ), karena mereka memerlukan periode ('.'), Yang tidak diterima dalam argumen makro. Tapi bisa mencari cara kerja seperti yang terlihat di makro my_func2 ( dari 4 parameter kasus )

Program

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;
}

Keluaran:

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

Ya, Anda dapat melakukan sesuatu secara bersamaan, di sini Anda harus mengetahui daftar argumen yang berbeda yang bisa Anda dapatkan tetapi Anda memiliki fungsi yang sama untuk menangani semuanya.

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

Kenapa kita tidak bisa melakukan ini.

Berikan nilai opsional pada argumen opsional. Dengan cara itu, pemanggil fungsi tidak perlu melewati nilai argumen. Argumen mengambil nilai default. Dan dengan mudah argumen itu menjadi opsional untuk klien.

Untuk misalnya

membatalkan foo (int a, int b = 0);

Di sini b adalah argumen opsional.


10
Wawasan yang menakjubkan, masalahnya adalah bahwa C tidak mendukung argumen opsional atau fungsi kelebihan beban, sehingga solusi langsung tidak dapat dikompilasi.
Thomas
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.