เราสามารถมีฟังก์ชั่นภายในฟังก์ชั่นใน C ++ ได้ไหม?


226

ฉันหมายถึงสิ่งที่ชอบ:

int main() 
{
  void a() 
  {
      // code
  }
  a();

  return 0;
}

1
ทำไมคุณถึงพยายามทำเช่นนี้? การอธิบายจุดประสงค์ของคุณอาจอนุญาตให้บางคนบอกวิธีที่ถูกต้องในการบรรลุเป้าหมายของคุณ
โธมัสโอเวนส์

3
gcc รองรับฟังก์ชั่นที่ซ้อนกันเป็นส่วนขยายที่ไม่ได้มาตรฐาน แต่ดีกว่าอย่าใช้มันแม้ว่าคุณจะใช้ gcc และในโหมด C ++ จะไม่สามารถใช้งานได้
Sven Marnach

27
@ โทมัส: เพราะมันจะดีที่จะลดขอบเขตของ? ฟังก์ชั่นในฟังก์ชั่นเป็นคุณสมบัติปกติในภาษาอื่น
Johan Kotlinski

64
เขากำลังพูดถึงฟังก์ชั่นที่ซ้อนกัน ในทำนองเดียวกันกับความสามารถในการเรียนต่อไปในชั้นเรียนเขาต้องการที่จะซ้อนฟังก์ชั่นภายในฟังก์ชั่น ที่จริงแล้วฉันมีสถานการณ์ที่ฉันจะต้องทำเช่นกันถ้าเป็นไปได้ มีภาษา (เช่น F #) ที่อนุญาตสิ่งนี้และฉันสามารถบอกคุณได้ว่าสามารถสร้างโค้ดที่ชัดเจนอ่านและบำรุงรักษาได้ดีขึ้นโดยไม่ทำให้ห้องสมุดที่มีฟังก์ชั่นผู้ช่วยเหลือนับสิบที่ไร้ประโยชน์นอกบริบทที่เฉพาะเจาะจงมาก ;)
Mephane

16
@Thomas - ฟังก์ชั่นที่ซ้อนกันอาจเป็นกลไกที่ยอดเยี่ยมสำหรับการทำลายฟังก์ชั่น / อัลกอริทึมที่ซับซ้อนโดยไม่ต้องเติมขอบเขตปัจจุบันด้วยฟังก์ชั่นที่ไม่ได้ใช้งานทั่วไปภายในขอบเขตการปิดล้อม Pascal และ Ada มีการสนับสนุนที่น่ารัก (IMO) สำหรับพวกเขา เช่นเดียวกับ Scala และภาษาเก่า / ใหม่ที่เคารพนับถืออื่น ๆ อีกมากมาย เช่นเดียวกับคุณสมบัติอื่น ๆ พวกเขายังสามารถถูกทารุณกรรมได้ แต่นั่นเป็นหน้าที่ของนักพัฒนา IMO พวกเขามีประโยชน์มากกว่าที่เป็นอันตราย
luis.espinal

คำตอบ:


272

Modern C ++ - ใช่กับ lambdas!

ในรุ่นปัจจุบันของ c ++ (C ++ 11, C ++ 14 และ C ++ 17) คุณสามารถมีฟังก์ชั่นภายในฟังก์ชั่นในรูปแบบของแลมบ์ดา:

int main() {
    // This declares a lambda, which can be called just like a function
    auto print_message = [](std::string message) 
    { 
        std::cout << message << "\n"; 
    };

    // Prints "Hello!" 10 times
    for(int i = 0; i < 10; i++) {
        print_message("Hello!"); 
    }
}

แลมบ์ดายังสามารถปรับเปลี่ยนตัวแปรในท้องถิ่นผ่านทาง ** การจับภาพโดยการอ้างอิง * ด้วยการอ้างอิงโดยการจับแลมบ์ดามีการเข้าถึงตัวแปรท้องถิ่นทั้งหมดที่ประกาศในขอบเขตของแลมบ์ดา สามารถแก้ไขและเปลี่ยนแปลงได้ตามปกติ

int main() {
    int i = 0;
    // Captures i by reference; increments it by one
    auto addOne = [&] () {
        i++; 
    };

    while(i < 10) {
        addOne(); //Add 1 to i
        std::cout << i << "\n";
    }
}

C ++ 98 และ C ++ 03 - ไม่โดยตรง แต่ใช่ด้วยฟังก์ชั่นคงที่ภายในคลาสท้องถิ่น

C ++ ไม่รองรับสิ่งนั้นโดยตรง

ที่กล่าวว่าคุณสามารถมีคลาสท้องถิ่นและพวกเขาสามารถมีฟังก์ชั่น (ไม่ใช่ - staticหรือstatic) เพื่อให้คุณสามารถขยายนี้แม้ว่าจะเป็น kludge:

int main() // it's int, dammit!
{
  struct X { // struct's as good as class
    static void a()
    {
    }
  };

  X::a();

  return 0;
}

อย่างไรก็ตามฉันจะถามแพรคซิส ทุกคนรู้ (ดีตอนนี้ที่คุณทำอยู่แล้ว:)) C ++ ไม่รองรับฟังก์ชั่นในท้องถิ่นดังนั้นพวกเขาจึงคุ้นเคยกับการไม่มี อย่างไรก็ตามพวกมันไม่ได้ใช้กับกระบองนั้น ฉันจะใช้เวลาสักครู่ในรหัสนี้เพื่อให้แน่ใจว่าเป็นจริงเท่านั้นที่มีการอนุญาตฟังก์ชั่นในท้องถิ่น ไม่ดี.


3
หลักใช้เวลาสอง args ถ้าคุณจะอวดความรู้เกี่ยวกับประเภทกลับ :) (หรือว่าเป็นตัวเลือก แต่ไม่ใช่คืนวันนี้หรือไม่ฉันไม่สามารถรักษาได้)
Leo Davidson

3
นี่เป็นสิ่งที่ไม่ดี - มันทำลายการประชุมที่ดีและสะอาดทุกรหัส ฉันไม่สามารถนึกถึงอินสแตนซ์เดียวที่นี่เป็นความคิดที่ดี
โธมัสโอเวนส์

19
@Thomas Owens: มันจะดีถ้าคุณต้องการฟังก์ชั่นการโทรกลับและไม่ต้องการที่จะสร้างมลภาวะเนมสเปซอื่นด้วย
Leo Davidson

9
@Leo: มาตรฐานบอกว่ามีสองรูปแบบที่อนุญาตสำหรับหลัก: int main()และint main(int argc, char* argv[])
จอห์น Dibling

8
มาตรฐานบอกว่าint main()และint main(int argc, char* argv[])ต้องได้รับการสนับสนุนและอื่น ๆ อาจได้รับการสนับสนุน
JoeG

261

สำหรับทุกเจตนาและวัตถุประสงค์ C ++ สนับสนุนสิ่งนี้ผ่านlambdas : 1

int main() {
    auto f = []() { return 42; };
    std::cout << "f() = " << f() << std::endl;
}

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

ด้านหลังของฉากfเป็นวัตถุฟังก์ชั่น (เช่นวัตถุประเภทที่ให้operator()) ประเภทฟังก์ชั่นวัตถุที่ถูกสร้างขึ้นโดยคอมไพเลอร์ขึ้นอยู่กับแลมบ์ดา


1ตั้งแต่ C ++ 11


5
อ่าเรียบร้อยแล้ว! ฉันไม่คิดอย่างนั้น มันดีกว่าความคิดของฉันมาก+1จากฉัน
sbi

1
@sbi: จริง ๆ แล้วฉันเคยใช้โครงสร้างในท้องถิ่นเพื่อจำลองสิ่งนี้ในอดีต (ใช่ฉันละอายอย่างเหมาะสมกับตัวเอง) แต่ประโยชน์ถูก จำกัด ด้วยข้อเท็จจริงที่ว่าโครงสร้างในท้องถิ่นไม่ได้สร้างการปิดเช่นคุณไม่สามารถเข้าถึงตัวแปรในตัวเครื่องได้ คุณต้องผ่านและเก็บมันไว้อย่างชัดเจนผ่านตัวสร้าง
Konrad Rudolph

1
@ Konrad: ปัญหาอีกประการหนึ่งของพวกเขาคือใน C ++ 98 คุณจะต้องไม่ใช้ชนิดโลคัลเป็นพารามิเตอร์เทมเพลต ฉันคิดว่า C ++ 1x ได้ยกเลิกข้อ จำกัด ดังกล่าวแล้ว (หรือว่า C ++ 03)
sbi

3
@luis: ฉันต้องเห็นด้วยกับเฟร็ด คุณกำลังแนบความหมายกับ lambdas ที่พวกเขาไม่มี (ทั้งใน C ++ หรือในภาษาอื่น ๆ ที่ฉันเคยทำงานด้วย - ซึ่งไม่รวม Python และ Ada สำหรับบันทึก) นอกจากนี้การทำให้ความแตกต่างนั้นไม่ได้มีความหมายใน C ++ เพราะ C ++ ไม่มีฟังก์ชั่นท้องถิ่น, จุด มันมีเพียง lambdas หากคุณต้องการ จำกัด ขอบเขตของสิ่งที่เหมือนฟังก์ชั่นให้กับฟังก์ชั่นตัวเลือกเดียวของคุณคือ lambdas หรือโครงสร้างภายในที่กล่าวถึงในคำตอบอื่น ฉันจะบอกว่าหลังค่อนข้างซับซ้อนเกินไปที่จะเป็นประโยชน์ใด ๆ
Konrad Rudolph

2
@AustinWBryan ไม่ lambdas ใน C ++ เป็นเพียงน้ำตาล syntactic สำหรับ functors และมีค่าใช้จ่ายเป็นศูนย์ มีคำถามที่มีรายละเอียดเพิ่มเติมบางแห่งในเว็บไซต์นี้
Konrad Rudolph

43

คลาสของโลคัลได้ถูกกล่าวถึงแล้ว แต่นี่เป็นวิธีที่จะทำให้พวกมันปรากฏมากขึ้นในฐานะที่เป็นฟังก์ชั่นท้องถิ่นโดยใช้โอเปอเรเตอร์ () โอเวอร์โหลดและคลาสที่ไม่ระบุชื่อ:

int main() {
    struct {
        unsigned int operator() (unsigned int val) const {
            return val<=1 ? 1 : val*(*this)(val-1);
        }
    } fac;

    std::cout << fac(5) << '\n';
}

ฉันไม่แนะนำให้ใช้สิ่งนี้เป็นเพียงกลลวงตลก (ทำได้ แต่ไม่ควรทำ)


อัปเดต 2014:

ด้วยการเพิ่มขึ้นของ C ++ 11 ในขณะนี้คุณสามารถมีฟังก์ชั่นท้องถิ่นที่มีไวยากรณ์เป็นความทรงจำเล็กน้อยของ JavaScript:

auto fac = [] (unsigned int val) {
    return val*42;
};

1
ควรจะoperator () (unsigned int val)เป็นชุดวงเล็บที่ขาดหายไปของคุณ
Joe D

1
ที่จริงแล้วนี่คือสิ่งที่ดีที่สุดที่เหมาะสมจะทำอย่างไรถ้าคุณต้องผ่าน functor นี้เพื่อฟังก์ชั่น STL หรือขั้นตอนวิธีการเหมือนหรือstd::sort() std::for_each()
Dima

1
@Dima: น่าเสียดายที่ใน C ++ 03 ประเภทที่กำหนดไว้ในเครื่องไม่สามารถใช้เป็นอาร์กิวเมนต์เทมเพลตได้ C ++ 0x แก้ไขสิ่งนี้ แต่ยังมอบโซลูชัน lambdas ที่ดีกว่าดังนั้นคุณยังคงไม่ทำเช่นนั้น
Ben Voigt

โอ๊ะคุณพูดถูก ความผิดฉันเอง. แต่ถึงกระนั้นนี่ไม่ใช่แค่เคล็ดลับตลก มันจะเป็นสิ่งที่มีประโยชน์ถ้ามันได้รับอนุญาต :)
Dima

3
รองรับการเรียกซ้ำ อย่างไรก็ตามคุณไม่สามารถใช้autoประกาศตัวแปรได้ Stroustrup ให้ตัวอย่าง: function<void(char*b, char*e)> rev=[](char*b, char*e) { if( 1<e-b ) { swap( *b, *--e); rev(++b,e); } };สำหรับการย้อนกลับสตริงที่กำหนดพอยน์เตอร์เริ่มต้นและสิ้นสุด
Eponymous

17

เลขที่

คุณพยายามจะทำอะไร?

วิธีแก้ปัญหา:

int main(void)
{
  struct foo
  {
    void operator()() { int a = 1; }
  };

  foo b;
  b(); // call the operator()

}

2
โปรดทราบว่าวิธีการสร้างอินสแตนซ์ของคลาสมาพร้อมกับการจัดสรรหน่วยความจำและถูกครอบงำโดยวิธีการคงที่
ManuelSchneid3r

14

เริ่มต้นด้วย C ++ 11 คุณสามารถใช้ที่เหมาะสมlambdas ดูคำตอบอื่น ๆ สำหรับรายละเอียดเพิ่มเติม


คำตอบเก่า: คุณสามารถเรียงลำดับของ แต่คุณต้องโกงและใช้ชั้นเรียนหุ่น:

void moo()
{
    class dummy
    {
    public:
         static void a() { printf("I'm in a!\n"); }
    };

    dummy::a();
    dummy::a();
}

ไม่แน่ใจว่าคุณสามารถทำได้ยกเว้นโดยการสร้างวัตถุแทน (ซึ่งเพิ่มเพียงเสียงรบกวน IMO) เว้นแต่จะมีบางสิ่งที่ฉลาดที่คุณสามารถทำกับ namespaces ได้ แต่ฉันไม่สามารถคิดถึงมันได้และอาจไม่ใช่ความคิดที่ดีที่จะใช้ภาษาในทางที่ผิดมากกว่าสิ่งที่เราเป็นอยู่แล้ว :)
Leo Davidson

การกำจัดหุ่นจำลอง :: เป็นอีกหนึ่งคำตอบ
Sebastian Mach

8

ดังที่คนอื่น ๆ พูดถึงคุณสามารถใช้ฟังก์ชั่นที่ซ้อนกันได้โดยใช้นามสกุลภาษา gnu เป็น gcc หากคุณ (หรือโครงการของคุณ) ยึดติดกับ gcc toolchain โค้ดของคุณจะเป็นแบบพกพาส่วนใหญ่ในสถาปัตยกรรมที่แตกต่างกันที่กำหนดเป้าหมายโดยคอมไพเลอร์ gcc

อย่างไรก็ตามหากมีความต้องการที่เป็นไปได้ที่คุณอาจต้องรวบรวมรหัสด้วย toolchain ที่แตกต่างกันแล้วฉันจะอยู่ห่างจากส่วนขยายดังกล่าว


ฉันจะใช้อย่างระมัดระวังด้วยการใช้ฟังก์ชันซ้อนกัน พวกเขาเป็นทางออกที่สวยงามสำหรับการจัดการโครงสร้างของความซับซ้อน แต่บล็อกของรหัสที่เหนียวแน่น (ชิ้นส่วนที่ไม่ได้มีไว้สำหรับการใช้งานภายนอก / ทั่วไป) พวกเขายังมีประโยชน์มากในการควบคุมมลภาวะเนมสเปซ คลาสยาวในภาษา verbose)

แต่พวกเขาสามารถเปิดรับการละเมิดได้

เป็นเรื่องน่าเศร้าที่ C / C ++ ไม่รองรับคุณสมบัติเช่นมาตรฐาน ปาสกาลสายพันธุ์ส่วนใหญ่และ Ada ทำ (เกือบทุกภาษาที่ใช้ Algol ทำ) เหมือนกันกับ JavaScript เช่นเดียวกันกับภาษาสมัยใหม่เช่น Scala เช่นเดียวกันกับภาษาที่น่านับถือเช่น Erlang, Lisp หรือ Python

และเช่นเดียวกับ C / C ++ แต่น่าเสียดายที่ Java (ซึ่งฉันได้รับส่วนใหญ่ของชีวิตของฉัน) ไม่ได้

ฉันพูดถึง Java ที่นี่เพราะฉันเห็นหลายโปสเตอร์แนะนำให้ใช้วิธีการเรียนและชั้นเรียนเป็นทางเลือกแทนฟังก์ชั่นที่ซ้อนกัน และนั่นก็เป็นวิธีแก้ปัญหาทั่วไปใน Java

คำตอบสั้น ๆ : ไม่

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

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

ในซอฟต์แวร์ (และในสาขาวิศวกรรมใด ๆ ) การสร้างแบบจำลองเป็นเรื่องของการแลกเปลี่ยน ดังนั้นในชีวิตจริงจะมีการยกเว้นข้อยกเว้นสำหรับกฎเหล่านั้น (หรือแนวทาง) ดำเนินการต่อด้วยความระมัดระวัง


8

คุณไม่สามารถมีฟังก์ชั่นท้องถิ่นใน C ++ อย่างไรก็ตาม, C ++ 11 มีlambdas แลมบ์ดานั้นเป็นตัวแปรที่ทำงานเหมือนฟังก์ชั่น

แลมบ์ดามีประเภทstd::function(ที่จริงแล้วมันไม่ได้เป็นความจริงแต่โดยส่วนใหญ่คุณสามารถสมมติได้) #include <functional>หากต้องการใช้ประเภทนี้คุณจะต้อง คือแม่แบบเอาเป็นอาร์กิวเมนต์แม่แบบชนิดกลับและประเภทการโต้แย้งกับไวยากรณ์std::function std::function<ReturnType(ArgumentTypes)ยกตัวอย่างเช่นstd::function<int(std::string, float)>เป็นแลมบ์ดาส่งคืนintและการใช้สองอาร์กิวเมนต์หนึ่งและเป็นหนึ่งในstd::string floatสิ่งที่พบได้บ่อยที่สุดคือstd::function<void()>สิ่งที่คืนกลับมาและไม่ขัดแย้งกัน

lambda(arguments)เมื่อแลมบ์ดาประกาศจะเรียกว่าเช่นเดียวกับการทำงานตามปกติโดยใช้ไวยากรณ์

ในการกำหนดแลมบ์ดาใช้ไวยากรณ์[captures](arguments){code}(มีวิธีอื่นในการทำ แต่ฉันจะไม่พูดถึงพวกเขาที่นี่) argumentsเป็นข้อโต้แย้งที่แลมบ์ดาใช้และcodeเป็นรหัสที่ควรเรียกใช้เมื่อแลมบ์ดาถูกเรียก โดยปกติคุณใส่[=]หรือ[&]ถูกจับ [=]หมายความว่าคุณจับตัวแปรทั้งหมดในขอบเขตที่ค่าถูกกำหนดโดยค่าซึ่งหมายความว่าพวกเขาจะเก็บค่าที่พวกเขามีเมื่อแลมบ์ดาถูกประกาศ [&]หมายความว่าคุณจับตัวแปรทั้งหมดในขอบเขตโดยอ้างอิงซึ่งหมายความว่าพวกเขาจะมีค่าปัจจุบันเสมอ แต่ถ้าพวกเขาถูกลบออกจากหน่วยความจำโปรแกรมจะพัง นี่คือตัวอย่างบางส่วน:

#include <functional>
#include <iostream>

int main(){
    int x = 1;

    std::function<void()> lambda1 = [=](){
        std::cout << x << std::endl;
    };
    std::function<void()> lambda2 = [&](){
        std::cout << x << std::endl;
    };

    x = 2;
    lambda1();    //Prints 1 since that was the value of x when it was captured and x was captured by value with [=]
    lambda2();    //Prints 2 since that's the current value of x and x was captured by value with [&]

    std::function<void()> lambda3 = [](){}, lambda4 = [](){};    //I prefer to initialize these since calling an uninitialized lambda is undefined behavior.
                                                                 //[](){} is the empty lambda.

    {
        int y = 3;    //y will be deleted from the memory at the end of this scope
        lambda3 = [=](){
            std::cout << y << endl;
        };
        lambda4 = [&](){
            std::cout << y << endl;
        };
    }

    lambda3();    //Prints 3, since that's the value y had when it was captured

    lambda4();    //Causes the program to crash, since y was captured by reference and y doesn't exist anymore.
                  //This is a bit like if you had a pointer to y which now points nowhere because y has been deleted from the memory.
                  //This is why you should be careful when capturing by reference.

    return 0;
}

นอกจากนี้คุณยังสามารถจับตัวแปรเฉพาะโดยระบุชื่อของพวกเขา เพียงแค่ระบุชื่อของพวกเขาจะจับพวกเขาด้วยค่าการระบุชื่อของพวกเขาด้วย&ก่อนจะจับพวกเขาโดยการอ้างอิง ตัวอย่างเช่น[=, &foo]จะจับตัวแปรทั้งหมดตามค่ายกเว้นfooซึ่งจะถูกบันทึกโดยการอ้างอิงและ[&, foo]จะจับตัวแปรทั้งหมดโดยการอ้างอิงยกเว้นfooซึ่งจะถูกดักจับด้วยค่า คุณสามารถจับเฉพาะตัวแปรที่เฉพาะเจาะจงเช่น[&foo]จะจับภาพfooโดยการอ้างอิงและจะไม่จับตัวแปรอื่น ๆ []นอกจากนี้คุณยังสามารถจับภาพตัวแปรที่ไม่ทั้งหมดโดยใช้ หากคุณพยายามใช้ตัวแปรในแลมบ์ดาที่คุณไม่ได้จับมันจะไม่รวบรวม นี่คือตัวอย่าง:

#include <functional>

int main(){
    int x = 4, y = 5;

    std::function<void(int)> myLambda = [y](int z){
        int xSquare = x * x;    //Compiler error because x wasn't captured
        int ySquare = y * y;    //OK because y was captured
        int zSquare = z * z;    //OK because z is an argument of the lambda
    };

    return 0;
}

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

#include <functional>

int main(){
    int x = 3, y = 5;
    std::function<void()> myLambda = [x, &y](){
        x = 2;    //Compiler error because x is captured by value and so it's of type const int inside the lambda
        y = 2;    //OK because y is captured by reference
    };
    x = 2;    //This is of course OK because we're not inside the lambda
    return 0;
}

นอกจากนี้การเรียก lambdas ที่ไม่ได้เตรียมการนั้นเป็นพฤติกรรมที่ไม่ได้กำหนดและมักจะทำให้โปรแกรมหยุดทำงาน ตัวอย่างเช่นอย่าทำสิ่งนี้:

std::function<void()> lambda;
lambda();    //Undefined behavior because lambda is uninitialized

ตัวอย่าง

นี่คือรหัสสำหรับสิ่งที่คุณต้องการทำในคำถามของคุณโดยใช้ lambdas:

#include <functional>    //Don't forget this, otherwise you won't be able to use the std::function type

int main(){
    std::function<void()> a = [](){
        // code
    }
    a();
    return 0;
}

นี่คือตัวอย่างขั้นสูงของแลมบ์ดา:

#include <functional>    //For std::function
#include <iostream>      //For std::cout

int main(){
    int x = 4;
    std::function<float(int)> divideByX = [x](int y){
        return (float)y / (float)x;    //x is a captured variable, y is an argument
    }
    std::cout << divideByX(3) << std::endl;    //Prints 0.75
    return 0;
}

7

ไม่ไม่อนุญาต C หรือ C ++ ไม่รองรับคุณสมบัตินี้ตามค่าเริ่มต้นอย่างไรก็ตาม TonyK ชี้ให้เห็น (ในความคิดเห็น) ว่ามีส่วนขยายของคอมไพเลอร์ GNU C ที่เปิดใช้งานลักษณะการทำงานนี้ใน C


2
ได้รับการสนับสนุนโดยคอมไพเลอร์ GNU C เป็นส่วนขยายพิเศษ แต่สำหรับ C เท่านั้นไม่ใช่ C ++
TonyK

อา. ฉันไม่มีส่วนขยายพิเศษในคอมไพเลอร์ C ของฉัน แม้ว่าจะเป็นเรื่องดีที่รู้ ฉันจะเพิ่ม titbit นั้นให้กับคำตอบของฉัน
Thomas Owens

ฉันใช้ส่วนขยาย gcc เพื่อรองรับฟังก์ชั่นที่ซ้อนกัน (ใน C แม้ว่าไม่ใช่ C ++) ฟังก์ชั่นที่ซ้อนกันเป็นสิ่งที่ดี (เหมือนใน Pascal และ Ada) สำหรับการจัดการที่ซับซ้อน ตราบใดที่หนึ่งการนำมาใช้ประโยชน์ toolchain gcc ก็จะมั่นใจได้ว่าจะเป็นส่วนใหญ่พกพาไปสถาปัตยกรรมที่กำหนดเป้าหมายทั้งหมด แต่ถ้ามีการเปลี่ยนแปลงในการคอมไพล์โค้ดผลลัพธ์ด้วยคอมไพเลอร์ที่ไม่ใช่ gcc ดังนั้นจึงเป็นการดีที่สุดที่จะหลีกเลี่ยงส่วนขยายดังกล่าวและติดให้ใกล้ที่สุดกับ ansi / posix มนต์
luis.espinal

7

เทคนิคทั้งหมดนี้แค่มอง (มากหรือน้อย) เป็นฟังก์ชั่นในท้องถิ่น แต่มันไม่ทำงานอย่างนั้น ในฟังก์ชั่นท้องถิ่นคุณสามารถใช้ตัวแปรท้องถิ่นของมันเป็นฟังก์ชั่นสุดยอด มันเป็นครึ่งวงกลม ไม่ใช่เทคนิคเหล่านี้สามารถทำได้ ที่ใกล้ที่สุดคือเคล็ดลับแลมบ์ดาจาก c ++ 0x แต่การปิดนั้นถูกผูกไว้ในเวลาที่กำหนดไม่ใช่เวลาที่ใช้


ตอนนี้ฉันคิดว่านี่เป็นคำตอบที่ดีที่สุด แม้ว่ามันจะเป็นไปได้ที่จะประกาศฟังก์ชั่นภายในฟังก์ชั่น (ซึ่งฉันใช้ตลอดเวลา) มันไม่ได้เป็นฟังก์ชั่นท้องถิ่นตามที่กำหนดไว้ในภาษาอื่น ๆ มันยังดีที่ได้รู้ถึงความเป็นไปได้
Alexis Wilke

6

คุณไม่สามารถกำหนดฟังก์ชั่นฟรีได้ใน C ++


1
ไม่ได้อยู่กับ ansi / posix แต่คุณสามารถใช้นามสกุล gnu ได้
luis.espinal

4

ให้ฉันโพสต์วิธีแก้ปัญหาที่นี่สำหรับ C ++ 03 ที่ฉันคิดว่าเป็นไปได้ที่สะอาดที่สุด *

#define DECLARE_LAMBDA(NAME, RETURN_TYPE, FUNCTION) \
    struct { RETURN_TYPE operator () FUNCTION } NAME;

...

int main(){
  DECLARE_LAMBDA(demoLambda, void, (){ cout<<"I'm a lambda!"<<endl; });
  demoLambda();

  DECLARE_LAMBDA(plus, int, (int i, int j){
    return i+j;
  });
  cout << "plus(1,2)=" << plus(1,2) << endl;
  return 0;
}

(*) ในโลก C ++ ที่ใช้มาโครนั้นไม่ถือเป็นความสะอาด


อเล็กซิสคุณมีสิทธิ์ที่จะบอกว่าไม่สะอาดอย่างสมบูรณ์ มันยังคงใกล้เคียงกับความสะอาดเพราะมันแสดงให้เห็นถึงสิ่งที่โปรแกรมเมอร์ตั้งใจจะทำโดยไม่มีผลข้างเคียง ฉันคิดว่าศิลปะของการเขียนโปรแกรมคือการเขียนการแสดงออกของมนุษย์ที่อ่านง่ายเหมือนนิยาย
Barney

2

แต่เราสามารถประกาศฟังก์ชั่นภายใน main ():

int main()
{
    void a();
}

แม้ว่าไวยากรณ์จะถูกต้องบางครั้งก็สามารถนำไปสู่ ​​"การแยกส่วนใหญ่รบกวน":

#include <iostream>


struct U
{
    U() : val(0) {}
    U(int val) : val(val) {}

    int val;
};

struct V
{
    V(U a, U b)
    {
        std::cout << "V(" << a.val << ", " << b.val << ");\n";
    }
    ~V()
    {
        std::cout << "~V();\n";
    }
};

int main()
{
    int five = 5;
    V v(U(five), U());
}

=> ไม่มีเอาต์พุตโปรแกรม

(เฉพาะเสียงเตือนดังหลังจากรวบรวม)

การแยกวิเคราะห์ที่น่ารำคาญที่สุดของ C ++ อีกครั้ง

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