C อาร์กิวเมนต์เริ่มต้น


279

มีวิธีการระบุอาร์กิวเมนต์เริ่มต้นกับฟังก์ชั่นใน C หรือไม่?


6
แค่อยากได้ C ที่ดีขึ้นเล็กน้อยไม่ใช่ C ++ คิดว่า C + ด้วยการปรับปรุงเล็ก ๆ หลายอย่างที่ยกมาจาก C ++ แต่ไม่ใช่เรื่องใหญ่ และโปรดอย่าเชื่อมโยงโหลดเดอร์ที่แตกต่างกัน ควรเป็นขั้นตอนเหมือน preprocessor อื่น มาตรฐาน ทุกที่ ...
ivo Welch

คำถามที่เกี่ยวข้องที่ฉันไม่เห็นในแถบด้านข้าง
Shelby Moore III

ฉันจะบอกว่าหยุดเป็นคนเถื่อนและเรียนรู้การใช้ C ++ (11, ... ) ได้ดี - jk! / ฉันดับไฟ ... แต่ ... คุณจะรักมัน ... ฮ่าฮ่าฮ่าฉันช่วยตัวเองไม่ได้ขอโทษ
moodboom

คำตอบ:


147

ไม่ได้จริงๆ วิธีเดียวที่จะเขียนฟังก์ชัน varargsและกรอกค่าเริ่มต้นด้วยตนเองสำหรับอาร์กิวเมนต์ที่โทรไม่ผ่าน


22
ฉันเกลียดการขาดการตรวจสอบเมื่อใช้ varargs
dmckee --- ผู้ดูแลอดีตแมว

16
เช่นกันคุณควร; ฉันไม่แนะนำสิ่งนี้จริงๆ ฉันแค่อยากจะบอกว่ามันเป็นไปได้
Eli Courtwright

4
อย่างไรก็ตามคุณต้องการตรวจสอบว่าผู้โทรผ่านการโต้แย้งหรือไม่? ฉันคิดว่ามันใช้งานได้คุณไม่มีผู้โทรมาบอกคุณว่าเขาไม่ผ่านมันเหรอ? ฉันคิดว่าวิธีนี้ทำให้วิธีการทั้งหมดค่อนข้างใช้งานได้น้อยผู้เรียกสามารถเรียกฟังก์ชันที่มีชื่ออื่นได้
Johannes Schaub - litb

3
การopen(2)เรียกระบบใช้สิ่งนี้สำหรับอาร์กิวเมนต์ที่เป็นทางเลือกซึ่งอาจมีอยู่ทั้งนี้ขึ้นอยู่กับอาร์กิวเมนต์ที่ต้องการและprintf(3)อ่านสตริงรูปแบบที่ระบุจำนวนอาร์กิวเมนต์ที่จะมี ทั้งสองใช้ varargs ค่อนข้างปลอดภัยและมีประสิทธิภาพและถึงแม้ว่าคุณจะสามารถทำให้พวกเขาสับสนได้printf()โดยเฉพาะดูเหมือนว่าจะเป็นที่นิยม
คริสลัทซ์

4
@Eli: คอมไพเลอร์ C ทั้งหมดไม่ใช่ gcc มีเวทมนตร์ของคอมไพเลอร์ขั้นสูงที่จะเตือนเมื่อ printf () ของคุณไม่ตรงกับสตริงรูปแบบของคุณ และฉันคิดว่ามันเป็นไปไม่ได้ที่จะได้รับคำเตือนที่คล้ายกันสำหรับฟังก์ชั่น variadic ของคุณเอง (เว้นแต่พวกเขาจะใช้สตริงรูปแบบลักษณะเดียวกัน)
tomlogic

280

ว้าวทุกคนเป็นคนมองโลกในแง่ร้ายอยู่แถวนี้ คำตอบคือใช่

มันไม่สำคัญ: ในตอนท้ายเราจะมีฟังก์ชั่นหลัก, โครงสร้างรองรับ, ฟังก์ชั่น wrapper และแมโครรอบฟังก์ชั่น wrapper ในงานของฉันฉันมีชุดมาโครเพื่อทำสิ่งเหล่านี้โดยอัตโนมัติ เมื่อคุณเข้าใจการไหลมันจะง่ายสำหรับคุณที่จะทำเช่นเดียวกัน

ฉันเขียนสิ่งนี้ไว้ที่อื่นดังนั้นนี่คือลิงก์ภายนอกที่มีรายละเอียดเพื่อเสริมการสรุปที่นี่: 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และกำหนดฟังก์ชั่น wrapper ที่ตั้งค่าเริ่มต้นและเรียกฐาน:

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

ตอนนี้เพิ่มแมโครโดยใช้มาโคร variadic ของ C วิธีนี้ผู้ใช้ไม่จำเป็นต้องรู้ว่าพวกเขากำลังวางโครงสร้างจริงf_argsและคิดว่าพวกเขากำลังทำตามปกติ:

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

ตกลงตอนนี้สิ่งต่อไปนี้จะได้ผล:

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

ตรวจสอบกฎเกี่ยวกับวิธีการตั้งค่าเริ่มต้นของสารประกอบเริ่มต้นสำหรับกฎที่แน่นอน

สิ่งหนึ่งที่ใช้งานไม่ได้: f(0)เนื่องจากเราไม่สามารถแยกความแตกต่างระหว่างค่าที่หายไปและศูนย์ จากประสบการณ์ของฉันนี่คือสิ่งที่ต้องระวัง แต่สามารถดูแลได้ตามความต้องการที่เกิดขึ้น --- ครึ่งหนึ่งของค่าเริ่มต้นของคุณคือศูนย์

ฉันเจอปัญหาในการเขียนเรื่องนี้เพราะฉันคิดว่าการตั้งชื่อข้อโต้แย้งและค่าเริ่มต้นทำให้การเข้ารหัสใน C ง่ายขึ้นและสนุกยิ่งขึ้น และ C นั้นยอดเยี่ยมมากสำหรับการเป็นคนเรียบง่ายและยังมีเพียงพอที่จะทำให้สิ่งนี้เป็นไปได้ทั้งหมด


16
+1 โฆษณา! มันมีข้อ จำกัด แต่ยังนำพารามิเตอร์ที่มีชื่อไปยังตาราง โปรดทราบว่า{}(initializer ว่างเปล่า) เป็นข้อผิดพลาด C99
u0b34a0f6ae

28
อย่างไรก็ตามนี่เป็นสิ่งที่ดีสำหรับคุณ: มาตรฐานอนุญาตให้ระบุสมาชิกที่มีชื่อหลาย ๆ ครั้งการแทนที่ในภายหลัง ดังนั้นสำหรับ named-parameters เพียงคุณเท่านั้นที่สามารถแก้ปัญหาค่าเริ่มต้นและอนุญาตให้มีการโทรที่ว่างเปล่า #define vrange(...) CALL(range,(param){.from=1, .to=100, .step=1, __VA_ARGS__})
u0b34a0f6ae

3
ฉันหวังว่าข้อผิดพลาดของคอมไพเลอร์สามารถอ่านได้ แต่นี่เป็นเทคนิคที่ยอดเยี่ยม! เกือบจะดูเหมือนกับ python kwargs
totowtwo

6
@ RunHolt แม้ว่าจะง่ายกว่า แต่ก็ไม่ได้ดีกว่า พารามิเตอร์ที่กำหนดชื่อมาพร้อมกับประโยชน์ต่าง ๆ เช่นความง่ายในการอ่านของการโทร (โดยเสียค่าใช้จ่ายในการอ่านรหัสต้นฉบับ) หนึ่งคือดีกว่าสำหรับนักพัฒนาของแหล่งอื่น ๆ ดีกว่าสำหรับผู้ใช้ฟังก์ชั่น มันค่อนข้างรีบร้อนที่จะทิ้ง "อันนี้ดีกว่า!"
อลิซ

3
@DawidPi: C11 6.7.9 (19) ในการเริ่มต้นการรวม: "subobjects ทั้งหมดที่ไม่ได้เริ่มต้นอย่างชัดเจนจะต้องเริ่มต้นโดยปริยายเช่นเดียวกับวัตถุที่มีระยะเวลาการจัดเก็บแบบคงที่" และที่คุณรู้ว่าองค์ประกอบระยะเวลาคงที่จะเริ่มต้นเป็นศูนย์ | โมฆะ | \ 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 ไม่อนุญาตให้คุณโหลดเมธอดมากเกินไปดังนั้นคุณจึงมีสองฟังก์ชันที่แตกต่างกัน แต่ถึงกระนั้นเมื่อคุณเรียก f2 คุณจะต้องโทรหา f1 ด้วยค่าเริ่มต้น นี่เป็นโซลูชัน "อย่าทำซ้ำตัวเอง" ซึ่งช่วยให้คุณหลีกเลี่ยงการคัดลอก / วางรหัสที่มีอยู่


24
FWIW ฉันต้องการใช้หมายเลขท้ายฟังก์ชันเพื่อระบุจำนวน args ที่ใช้ ทำให้ง่ายกว่าการใช้หมายเลขใดก็ได้ :)
Macke

4
นี่คือคำตอบที่ดีที่สุดเพราะมันแสดงให้เห็นวิธีง่ายๆในการบรรลุเป้าหมายเดียวกัน ฉันมีฟังก์ชั่นที่เป็นส่วนหนึ่งของ API แบบคงที่ซึ่งฉันไม่ต้องการเปลี่ยน แต่ฉันต้องการให้มันใช้พารามิเตอร์ใหม่ แน่นอนว่ามันชัดเจนมากจนทำให้ฉันพลาด (ติดอยู่กับการคิดค่าเริ่มต้น!)
RunHolt

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

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: นั่นเป็นไปไม่ได้ดังนั้น mandates ความยาวขั้นต่ำในคำตอบ ฉันเหนื่อย. :)
คลาย

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
รวมสิ่งนี้เข้ากับ varargs และคุณจะได้สนุกไปกับมัน!
David Thornley

5
ฉันจะใช้แบบกำหนดเองstructและให้ผู้โทรทำการกรอกข้อมูลลงในฟิลด์สำหรับตัวเลือกต่าง ๆ จากนั้นส่งผ่านตามที่อยู่หรือส่งผ่านNULLตัวเลือกเริ่มต้น
คริสลัทซ์

การคัดลอกรูปแบบรหัสจาก ROOT เป็นความคิดที่น่ากลัว!
innisfree

13

อาจเป็นวิธีที่ดีที่สุดในการทำเช่นนี้ (ซึ่งอาจเป็นไปได้หรือไม่อาจเป็นไปได้ในกรณีของคุณขึ้นอยู่กับสถานการณ์ของคุณ) คือการย้ายไปที่ C ++ และใช้มันเป็น 'a 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);


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

ถ้ามีเพียงอาร์กิวเมนต์เดียวเท่านั้นที่bได้รับค่าเริ่มต้น (ในกรณีนี้ 15)


4

ไม่ แต่คุณอาจลองใช้ชุดฟังก์ชัน (หรือแมโคร) เพื่อประมาณค่าโดยใช้ args เริ่มต้น:

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

Jens


จงตอบคำตอบของฉันที่มาจากคุณด้วย
Shelby Moore 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

ฉันปรับปรุงคำตอบของ Jens Gustedt เพื่อที่:

  1. ไม่ใช้ฟังก์ชันอินไลน์
  2. ค่าเริ่มต้นจะถูกคำนวณในระหว่างการประมวลผลล่วงหน้า
  3. มาโครที่นำกลับมาใช้ใหม่ได้แบบแยกส่วน
  4. เป็นไปได้ในการตั้งข้อผิดพลาดคอมไพเลอร์ที่มีความหมายตรงกับกรณีของการขัดแย้งไม่เพียงพอสำหรับค่าเริ่มต้นที่ได้รับอนุญาต
  5. ไม่จำเป็นต้องใช้ค่าเริ่มต้นในการสร้างส่วนท้ายของรายการพารามิเตอร์หากประเภทอาร์กิวเมนต์จะยังคงชัดเจน
  6. interopts กับ 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__))

และด้วยการเลือกชื่อฟังก์ชัน Variadic ซึ่งไม่สามารถใช้ร่วมกับ _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

ใช่คุณสามารถทำสิ่งต่าง ๆ simulair ที่นี่คุณต้องรู้รายการอาร์กิวเมนต์ที่แตกต่างที่คุณจะได้รับ แต่คุณมีฟังก์ชั่นเดียวกันในการจัดการทั้งหมด

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

ทำไมเราทำเช่นนี้ไม่ได้

ตั้งค่าเริ่มต้นให้อาร์กิวเมนต์ที่เป็นตัวเลือก ด้วยวิธีนี้ผู้เรียกฟังก์ชันไม่จำเป็นต้องส่งค่าของอาร์กิวเมนต์ อาร์กิวเมนต์ใช้ค่าเริ่มต้น และง่ายต่อการโต้แย้งว่าจะเป็นตัวเลือกสำหรับลูกค้า

สำหรับเช่น

โมฆะ foo (int a, int b = 0);

นี่คือ b เป็นอาร์กิวเมนต์ที่เป็นทางเลือก


10
ข้อมูลเชิงลึกที่น่าทึ่งปัญหาคือ C ไม่สนับสนุนอาร์กิวเมนต์ที่เป็นตัวเลือกหรือฟังก์ชันที่มีการโอเวอร์โหลดดังนั้นโซลูชันโดยตรงจึงไม่ได้รวบรวม
โทมัส
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.