เป็นไปได้ไหมที่จะมีอินพุตตัวแปรในฟังก์ชัน C

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

ฉันต้องการป้อนข้อมูลอย่างใดอย่างหนึ่ง:

จำนวนเต็มสองตัว : ส่งออกจำนวนเต็มหนึ่งตัว

สองคู่ : ส่งออกหนึ่งคู่

ฉันได้ลองค้นหาการประกาศอินพุตทั่วโลกบางประเภททางออนไลน์แล้ว แต่หาไม่พบ

ฉันไม่ต้องการมีสองฟังก์ชัน แค่ฟังก์ชันธรรมดาอันเดียว

ตัวอย่างโค้ดส่วนหัว: int diff(int a, int b); ต่างกันสองเท่า (ดับเบิล a, ดับเบิล b);

ขอบคุณสำหรับความช่วยเหลือ!


person PathToLife    schedule 21.10.2014    source แหล่งที่มา
comment
เพิ่มพารามิเตอร์ตัวที่สามที่ระบุว่าอาร์กิวเมนต์และเอาต์พุตเป็นประเภทใด จากนั้นให้โค้ดตรวจสอบพารามิเตอร์ตัวที่สามนั้นและดำเนินการตามนั้น   -  person user3629249    schedule 21.10.2014


คำตอบ (3)


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

คุณ สามารถ ทำมันได้ด้วยตัวช่วยสร้าง C ทุกประเภท (คุณสามารถ อะไรก็ได้ ด้วยตัวช่วยสร้าง C ที่เพียงพอ) แต่โค้ดที่ได้ออกมาอาจจะดูน่าเกลียดจนไม่สามารถบำรุงรักษาได้ : -)

ตัวอย่างเช่น C11 แนะนำการเลือกทั่วไปด้วยนิพจน์หลัก _Generic และสิ่งนี้ทำให้คุณสามารถเรียกใช้ฟังก์ชันต่างๆ ตามประเภทอาร์กิวเมนต์อินพุต จริงๆ แล้วมันไม่ได้ทำอะไรมากกว่านั้นอีกสักหน่อย แต่นั่นคือสิ่งที่คุณสนใจตามคำถามของคุณ

ตัวอย่างเช่น สมมติว่าคุณกำหนดฟังก์ชันสองฟังก์ชันดังนี้:

int     diffi (int    a, int    b) { return a - b; }
double  diffd (double a, double b) { return a - b; }

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

#define diff(a,b)        \
    _Generic((a),        \
        double:  diffd,  \
        int:     diffi,  \
        default: diffX   \
    )(a,b)

และสิ่งนี้ทำโดยพื้นฐานในการค้นหามาโคร diff(x,y) ในซอร์สโค้ด:

  • กำหนดประเภทของนิพจน์ (a) โดยไม่ต้องประเมิน
  • ฉีดโทเค็นที่ตรงกับประเภทนั้นลงในสตรีมต้นทาง (หรือค่าเริ่มต้นหากไม่พบรายการที่ตรงกัน)
  • แทรกข้อความ (a,b) ลงในสตรีมต้นทางในตอนท้าย

ดังนั้น หากไฟล์ต้นฉบับของคุณมีบรรทัด:

x = diff (1,   2);
y = diff (1.0, 2);

สิ่งนี้จะถูกแปลเป็น:

x = diffi (1  , 2);
y = diffd (1.0, 2);

ให้คุณโอเวอร์โหลดได้อย่างมีประสิทธิภาพ

ตอนนี้เป็นกรณีที่ง่ายพอสมควรเนื่องจากอาศัยเฉพาะอาร์กิวเมนต์ประเภทแรกเท่านั้น - คุณจะเห็นช่องโหว่หากคุณพยายามทำ:

z = diff (1, 2.0);

โดยที่ประเภทของอาร์กิวเมนต์ แรก คือ int ดังนั้นคุณจะได้รับ:

z = diffi (1, 2.0);

ซึ่งจะไม่ใช่สิ่งที่คุณต้องการทำจริงๆ นี่คือที่มาของความซับซ้อน เนื่องจากคุณต้องครอบคลุมความเป็นไปได้ สี่: {int/int, int/double, double/int, double/double} และจะซับซ้อน มากขึ้น ขึ้นอยู่กับจำนวนอาร์กิวเมนต์และประเภทที่เป็นไปได้สำหรับแต่ละอาร์กิวเมนต์

อย่างไรก็ตาม กรณีทั้งหมดของคุณนั้นสามารถทำได้โดยใช้ค่าเริ่มต้นอย่างรอบคอบและการเลือกทั่วไปที่ซ้อนกัน เช่น:

#define diff(a,b)              \
    _Generic((a),              \
        double:  diffd,        \
        default: _Generic((b), \
            double:  diffd,    \
            default: diffi     \
        )                      \
    )(a,b)

และสิ่งนี้สามารถอ่านได้เป็น:

  • ถ้าประเภทของ a คือ double ให้ใช้ diffd;
  • มิฉะนั้น หากประเภทของ b คือ double ให้ใช้ diffd;
  • มิฉะนั้นให้ใช้ diffi
  • อย่าลืมใส่ข้อโต้แย้งด้วย

โปรแกรมที่สมบูรณ์ต่อไปนี้ (คอมไพล์ด้วย clang 3.0) จะแสดงคุณลักษณะนี้ในการทำงาน:

#include <stdio.h>

int diffi (int a, int b) {
    printf ("diffi %d %d", a, b);
    return a - b;
}
double diffd (double a, double b) {
    printf ("diffd %f %f", a, b);
    return a - b;
}

#define diff(a,b)              \
    _Generic((a),              \
        double:  diffd,        \
        default: _Generic((b), \
            double:  diffd,    \
            default: diffi     \
        )                      \
    )(a,b)

int main (void) {
    int i; double d;
    i = diff (1  , 2  ); printf (" --> %d\n", i);
    d = diff (1.0, 2  ); printf (" --> %f\n", d);
    d = diff (1  , 2.0); printf (" --> %f\n", d);
    d = diff (1.0, 2.0); printf (" --> %f\n", d);
    return 0;
}

ผลลัพธ์ของโปรแกรมนั้นคือ:

diffi 1 2 --> -1
diffd 1.000000 2.000000 --> -1.000000
diffd 1.000000 2.000000 --> -1.000000
diffd 1.000000 2.000000 --> -1.000000

แสดงว่ามีการเรียกฟังก์ชันที่ถูกต้องสำหรับความเป็นไปได้ทั้งสี่ประการ


และในความเป็นจริง ตามที่ rici ชี้ให้เห็นในความคิดเห็น คุณสามารถพึ่งพากฎการส่งเสริมของ C โดยที่การเพิ่ม double และ int (ตามลำดับใดๆ ก็ตาม) จะให้ double ในขณะที่การเพิ่มตัวแปร int สองตัวจะให้ int:

#define diff(a,b) _Generic((a+b), double:diffd, default:diffi)(a,b)
person paxdiablo    schedule 21.10.2014
comment
แล้วฟีเจอร์ C11 _Generic ล่ะ? (ดูส่วนที่ 6.5.1.1 ของ open-std.org /jtc1/sc22/wg14/www/docs/n1570.pdf) มันไม่สวยเท่า C++ แต่ก็ไม่ได้ ขนาดนั้น น่าเกลียด - person rici; 21.10.2014
comment
@rici คุณควรลองใช้ฟังก์ชันสี่อาร์กิวเมนต์ ซึ่งแต่ละฟังก์ชันสามารถเป็นหนึ่งในสามประเภทได้ จากนั้นคุณจะประทับใจกับความน่าเกลียดของมัน :-) อย่างไรก็ตาม มันใช้ได้สำหรับกรณีการใช้งานขนาดเล็ก ดังนั้นฉันจะเพิ่มมันเป็น ตัวเลือก. - person paxdiablo; 22.10.2014
comment
@paxdiablo: ฉันใช้ C++ ตัวเอง :-) แต่ _Generic นั้นดีพอสำหรับคำถามนี้อย่างแน่นอน - person rici; 22.10.2014
comment
@rici เอาล่ะคุณพูดถูก มันไม่ได้น่าเกลียดอย่างที่ฉันคิดไว้ในตอนแรก (อย่างน้อยก็สำหรับกรณีการใช้งานเฉพาะ) - person paxdiablo; 22.10.2014
comment
@rici มีไม่บ่อยหรอกที่ฉันจะประทับใจกับความลับๆล่อๆของใครบางคน - โทรดีๆ :-) - person paxdiablo; 22.10.2014
comment
ฉันได้โหวตให้คุณแล้ว ไม่เช่นนั้นการแก้ไขของคุณจะมีมูลค่ามากกว่า (+1) - person Mohit Jain; 22.10.2014
comment
_ทั่วไป ค่อนข้างมีเวทมนตร์บางอย่างจริงๆ ขอบคุณสำหรับคำตอบที่ครอบคลุม paxdiablo และ rici :7) - person PathToLife; 22.10.2014

ฟังก์ชันโอเวอร์โหลด ไม่สามารถใช้งานได้ในภาษา C วิธีแก้ปัญหาที่เป็นไปได้บางประการ

  1. ใช้ 2 ฟังก์ชันที่มีชื่อต่างกัน เช่น diff_i และ diff_d (แนะนำ)
  2. ใช้ varargs (ไม่แนะนำ) ซึ่งจะทำให้โค้ดยากต่อการดูแลรักษาตามเวลา
  3. ใช้ _Generic
person Mohit Jain    schedule 21.10.2014

IEEE754 double มีความแม่นยำมากกว่า 32 บิต ดังนั้นเพียงแค่เขียนเวอร์ชัน double แล้วปล่อยให้การแปลงอัตโนมัติจัดการส่วนที่เหลือ

แน่นอน หากคุณใช้ระบบที่ sizeof(int)>4 หรือ char มีมากกว่า 8 บิต วิธีที่ดีที่สุดคือเขียนรูปแบบต่างๆ สำหรับแต่ละประเภท และใช้รูปแบบการตั้งชื่อภาษาฮังการีบางประเภทสำหรับสิ่งเหล่านั้น จากนั้นคุณสามารถเขียนได้บางที:

int diffi(int, int);
double diffd(double, double);
ssize_t diffz(size_t, size_t);

ฯลฯ โดยพื้นฐานแล้ว นี่คือ ด้วยตนเอง name-mangling ซึ่งเป็นเทคนิคเดียวกับที่คอมไพเลอร์ C++ ใช้เพื่อสร้างชื่อสัญลักษณ์ที่แตกต่างกันสำหรับตารางส่งออกของไฟล์อ็อบเจ็กต์

person luser droog    schedule 21.10.2014
comment
AC int มีช่วง ขั้นต่ำ ไม่ใช่ช่วงสูงสุด ดังนั้นอาจมีการใช้งานที่ int ต้องการมากกว่าความแม่นยำของ double :-) อย่างไรก็ตาม มันก็ไม่ใช่ความคิดที่แย่ถ้าคุณรู้ข้อจำกัด - person paxdiablo; 21.10.2014
comment
จริง. ขอบคุณ! นั่นทำให้ฉันมีบางอย่างที่จะเพิ่มให้กับคำตอบ - person luser droog; 21.10.2014