ตัวชี้ฟังก์ชันการปิดและแลมด้า


86

ตอนนี้ฉันเพิ่งเรียนรู้เกี่ยวกับตัวชี้ฟังก์ชันและในขณะที่ฉันกำลังอ่านบท K&R ในเรื่องนี้สิ่งแรกที่โดนใจฉันคือ "เฮ้นี่มันเหมือนกับการปิด" ฉันรู้ว่าสมมติฐานนี้ผิดโดยพื้นฐานแล้วและหลังจากการค้นหาทางออนไลน์ฉันไม่พบการวิเคราะห์ใด ๆ เกี่ยวกับการเปรียบเทียบนี้เลย

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

เหตุใดการส่งผ่านฟังก์ชันไปยังฟังก์ชันจึงถูกมองว่ามีประสิทธิภาพมากกว่าในกรณีที่สองโดยที่ไม่มีชื่อมากกว่าฟังก์ชันแรกที่เป็นเพียงฟังก์ชันปกติในชีวิตประจำวันที่ถูกส่งผ่าน

โปรดบอกฉันว่าทำไมฉันถึงคิดผิดที่เปรียบเทียบทั้งสองอย่างใกล้ชิด

ขอบคุณ.

คำตอบ:


108

แลมบ์ดา (หรือปิด ) ห่อหุ้มทั้งตัวชี้ฟังก์ชันและตัวแปร นี่คือเหตุผลที่ใน C # คุณสามารถทำได้:

int lessThan = 100;
Func<int, bool> lessThanTest = delegate(int i) {
   return i < lessThan;
};

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

int lessThan = 100;
Func<int, bool> lessThanTest = delegate(int i) {
   return i < lessThan;
};

lessThanTest(99); // returns true
lessThan = 10;
lessThanTest(99); // returns false

ใน C สิ่งนี้จะผิดกฎหมาย:

BOOL (*lessThanTest)(int);
int lessThan = 100;

lessThanTest = &LessThan;

BOOL LessThan(int i) {
   return i < lessThan; // compile error - lessThan is not in scope
}

แม้ว่าฉันสามารถกำหนดตัวชี้ฟังก์ชันที่รับ 2 อาร์กิวเมนต์:

int lessThan = 100;
BOOL (*lessThanTest)(int, int);

lessThanTest = &LessThan;
lessThanTest(99, lessThan); // returns true
lessThan = 10;
lessThanTest(100, lessThan); // returns false

BOOL LessThan(int i, int lessThan) {
   return i < lessThan;
}

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

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

สรุป: การปิดเป็นการรวมกันของตัวชี้ฟังก์ชัน + ตัวแปรที่จับได้


ขอบคุณจริงๆคุณผลักดันให้คนอื่นกลับบ้านไปได้
ไม่มี

คุณอาจใช้ C เวอร์ชันเก่ากว่าเมื่อคุณเขียนสิ่งนี้หรือจำไม่ได้ว่าส่งต่อประกาศฟังก์ชัน แต่ฉันไม่สังเกตพฤติกรรมเดียวกันกับที่คุณพูดถึงเมื่อฉันทดสอบสิ่งนี้ ideone.com/JsDVBK
smac89

@ smac89 - คุณสร้างตัวแปร lessThan เป็น global - ฉันพูดถึงสิ่งนั้นอย่างชัดเจนเพื่อเป็นทางเลือก
Mark Brackett

42

ในฐานะคนที่เขียนคอมไพเลอร์สำหรับภาษาทั้งที่มีและไม่มีการปิด 'จริง' ฉันไม่เห็นด้วยกับคำตอบบางส่วนข้างต้น ชัด, โครงการ ML หรือปิด Haskell ไม่สร้างฟังก์ชั่นใหม่แบบไดนามิก แต่มันreuses ฟังก์ชั่นที่มีอยู่แต่ไม่ได้มีตัวแปรใหม่ฟรี การรวบรวมตัวแปรอิสระมักเรียกว่าสิ่งแวดล้อมอย่างน้อยโดยนักทฤษฎีภาษาโปรแกรม

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

คุณสามารถจำลองทั้งหมดนี้ในภาษา C แต่มันเป็นความเจ็บปวดที่ตูด สองเทคนิคยอดนิยม:

  1. ส่งตัวชี้ไปยังฟังก์ชัน (รหัส) และตัวชี้แยกไปยังตัวแปรอิสระเพื่อให้การปิดถูกแบ่งข้ามตัวแปร C สองตัว

  2. ส่งตัวชี้ไปยังโครงสร้างโดยที่โครงสร้างประกอบด้วยค่าของตัวแปรอิสระและตัวชี้ไปยังรหัส

เทคนิค # 1 เหมาะอย่างยิ่งเมื่อคุณพยายามจำลองความหลากหลายของความหลากหลายในภาษา C และคุณไม่ต้องการเปิดเผยประเภทของสภาพแวดล้อมคุณใช้ตัวชี้โมฆะ * เพื่อแสดงสภาพแวดล้อม ตัวอย่างเช่นดูที่C Interfaces and Implementationsของ Dave Hansonการเชื่อมต่อและการใช้งาน เทคนิค # 2 ซึ่งคล้ายกับสิ่งที่เกิดขึ้นในคอมไพเลอร์โค้ดเนทีฟสำหรับภาษาที่ใช้งานได้มากขึ้นก็คล้ายกับเทคนิคอื่นที่คุ้นเคย ... วัตถุ C ++ ที่มีฟังก์ชันสมาชิกเสมือน การใช้งานเกือบจะเหมือนกัน

ข้อสังเกตนี้นำไปสู่ความฉลาดจาก Henry Baker:

ผู้คนในโลก Algol / Fortran บ่นมาหลายปีแล้วว่าพวกเขาไม่เข้าใจว่าการปิดฟังก์ชันการใช้งานที่เป็นไปได้จะมีอะไรบ้างในการเขียนโปรแกรมที่มีประสิทธิภาพในอนาคต จากนั้นการปฏิวัติ `` การเขียนโปรแกรมเชิงวัตถุ 'ก็เกิดขึ้นและตอนนี้ทุกคนตั้งโปรแกรมโดยใช้การปิดฟังก์ชันยกเว้นว่าพวกเขายังปฏิเสธที่จะเรียกสิ่งนั้น


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

8

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

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

แก้ไข: การเพิ่มตัวอย่าง (ฉันจะใช้ไวยากรณ์ Actionscript-ish เพราะนั่นคือสิ่งที่ฉันคิดในตอนนี้):

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

function runLater(f:Function):Void {
  sleep(100);
  f();
}

ตอนนี้บอกว่าคุณต้องการให้ผู้ใช้ runLater () ชะลอการประมวลผลของวัตถุ:

function objectProcessor(o:Object):Void {
  /* Do something cool with the object! */
}

function process(o:Object):Void {
  runLater(function() { objectProcessor(o); });
}

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

ฉันหวังว่ามันจะสมเหตุสมผล


ฉันปรับเปลี่ยนคำตอบตามความคิดเห็นของคุณ ฉันยังไม่ชัดเจน 100% เกี่ยวกับข้อกำหนดเฉพาะของข้อกำหนดดังนั้นฉันจึงขออ้างถึงคุณโดยตรง :)
สมุนไพร

ความสามารถแบบอินไลน์ของฟังก์ชันที่ไม่ระบุตัวตนเป็นรายละเอียดการใช้งานของภาษาโปรแกรมหลัก (ส่วนใหญ่?) - ไม่ใช่ข้อกำหนดสำหรับการปิด
Mark Brackett

6

การปิด = ตรรกะ + สภาพแวดล้อม

ตัวอย่างเช่นพิจารณาวิธี C # 3 นี้:

public Person FindPerson(IEnumerable<Person> people, string name)
{
    return people.Where(person => person.Name == name);
}

นิพจน์แลมบ์ดาไม่เพียงห่อหุ้มตรรกะ ("เปรียบเทียบชื่อ") แต่ยังรวมถึงสภาพแวดล้อมด้วยรวมถึงพารามิเตอร์ (เช่นตัวแปรโลคัล) "ชื่อ"

สำหรับข้อมูลเพิ่มเติมโปรดดูบทความของฉันเกี่ยวกับการปิดซึ่งจะนำคุณไปสู่ ​​C # 1, 2 และ 3 ซึ่งแสดงให้เห็นว่าการปิดทำให้สิ่งต่างๆง่ายขึ้นได้อย่างไร


พิจารณาแทนที่โมฆะด้วย IEnumerable <Person>
Amy B

1
@David B: ไชโยเสร็จแล้ว @edg: ฉันคิดว่ามันเป็นมากกว่าเพียงแค่รัฐเพราะมันไม่แน่นอนของรัฐ กล่าวอีกนัยหนึ่งคือหากคุณดำเนินการปิดซึ่งเปลี่ยนตัวแปรโลคัล (ในขณะที่ยังอยู่ในเมธอด) ตัวแปรโลคัลนั้นก็เปลี่ยนไปเช่นกัน "สิ่งแวดล้อม" ดูเหมือนจะสื่อถึงสิ่งนี้ได้ดีกว่าสำหรับฉัน แต่มันเป็นขนสัตว์
Jon Skeet

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

ใช่มันกำลังเรียกใช้เมธอดอยู่ แต่พารามิเตอร์ที่ส่งผ่านคือการปิด
Jon Skeet

4

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

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

ปัญหา funargs ด้านล่างกล่าวคือการส่งตัวชี้ฟังก์ชัน "ลงสแต็ก" เป็นอาร์กิวเมนต์ไปยังฟังก์ชันที่คุณเรียกใช้จริงไม่เข้ากันกับ C และ GCC สนับสนุนฟังก์ชันที่ซ้อนกันเป็นฟังก์ชันลง ใน GCC เมื่อคุณสร้างตัวชี้ไปยังฟังก์ชันที่ซ้อนกันคุณจะได้ตัวชี้ไปที่แทรมโพลีนจริงๆซึ่งเป็นส่วนของโค้ดที่สร้างขึ้นแบบไดนามิกที่ตั้งค่าตัวชี้ลิงก์แบบคงที่แล้วเรียกฟังก์ชันจริงซึ่งใช้ตัวชี้ลิงก์แบบคงที่เพื่อเข้าถึง ตัวแปรของฟังก์ชันภายนอก

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

Lambdas ( ฟังก์ชันที่ไม่ระบุตัวตน ) เป็นปัญหาที่แยกจากกัน แต่โดยปกติแล้วภาษาที่ให้คุณกำหนดฟังก์ชันที่ไม่ระบุตัวตนได้ทันทีจะให้คุณคืนค่าเป็นค่าฟังก์ชันดังนั้นจึงต้องปิดลง


3

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

(defun get-counter (n-start +-number)
     "Returns a function that returns a number incremented
      by +-number every time it is called"
    (lambda () (setf n-start (+ +-number n-start))))

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

[1]> (defun get-counter (n-start +-number)
         "Returns a function that returns a number incremented
          by +-number every time it is called"
        (lambda () (setf n-start (+ +-number n-start))))
GET-COUNTER
[2]> (defvar x (get-counter 2 3))
X
[3]> (funcall x)
5
[4]> (funcall x)
8
[5]> (funcall x)
11
[6]> (funcall x)
14
[7]> (funcall x)
17
[8]> (funcall x)
20
[9]> 

2

การปิดหมายถึงตัวแปรบางตัวจากจุดนิยามฟังก์ชันถูกผูกไว้ด้วยตรรกะของฟังก์ชันเช่นความสามารถในการประกาศมินิอ็อบเจกต์ได้ทันที

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

ฉันไม่สะดวกที่จะเทียบค่าแลมด้าด้วยการปิดเพราะฉันไม่แน่ใจว่าแลมบ์ดาในทุกภาษานั้นเป็นการปิดในบางครั้งฉันคิดว่าแลมบ์ดาเป็นฟังก์ชันที่ไม่ระบุตัวตนที่กำหนดไว้ในเครื่องโดยไม่มีการผูกตัวแปร (Python pre 2.1?)


2

ใน GCC สามารถจำลองฟังก์ชันแลมด้าโดยใช้มาโครต่อไปนี้:

#define lambda(l_ret_type, l_arguments, l_body)       \
({                                                    \
    l_ret_type l_anonymous_functions_name l_arguments \
    l_body                                            \
    &l_anonymous_functions_name;                      \
})

ตัวอย่างจากแหล่งที่มา :

qsort (array, sizeof (array) / sizeof (array[0]), sizeof (array[0]),
     lambda (int, (const void *a, const void *b),
             {
               dump ();
               printf ("Comparison %d: %d and %d\n",
                       ++ comparison, *(const int *) a, *(const int *) b);
               return *(const int *) a - *(const int *) b;
             }));

แน่นอนว่าการใช้เทคนิคนี้จะช่วยขจัดความเป็นไปได้ที่แอปพลิเคชันของคุณจะทำงานร่วมกับคอมไพเลอร์อื่น ๆ และเห็นได้ชัดว่ามีพฤติกรรม "ไม่ได้กำหนด" ดังนั้น YMMV


2

การปิดจับตัวแปรอิสระในสภาพแวดล้อมสภาพแวดล้อมสภาพแวดล้อมจะยังคงมีอยู่แม้ว่าโค้ดโดยรอบอาจไม่ทำงานอีกต่อไป

ตัวอย่างใน Common Lisp ที่MAKE-ADDERส่งกลับการปิดใหม่

CL-USER 53 > (defun make-adder (start delta) (lambda () (incf start delta)))
MAKE-ADDER

CL-USER 54 > (compile *)
MAKE-ADDER
NIL
NIL

การใช้ฟังก์ชันข้างต้น:

CL-USER 55 > (let ((adder1 (make-adder 0 10))
                   (adder2 (make-adder 17 20)))
               (print (funcall adder1))
               (print (funcall adder1))
               (print (funcall adder1))
               (print (funcall adder1))
               (print (funcall adder2))
               (print (funcall adder2))
               (print (funcall adder2))
               (print (funcall adder1))
               (print (funcall adder1))
               (describe adder1)
               (describe adder2)
               (values))

10 
20 
30 
40 
37 
57 
77 
50 
60 
#<Closure 1 subfunction of MAKE-ADDER 4060001ED4> is a CLOSURE
Function         #<Function 1 subfunction of MAKE-ADDER 4060001CAC>
Environment      #(60 10)
#<Closure 1 subfunction of MAKE-ADDER 4060001EFC> is a CLOSURE
Function         #<Function 1 subfunction of MAKE-ADDER 4060001CAC>
Environment      #(77 20)

โปรดสังเกตว่าDESCRIBEฟังก์ชันแสดงให้เห็นว่าอ็อบเจ็กต์ของฟังก์ชันสำหรับการปิดทั้งสองเหมือนกัน แต่สภาพแวดล้อมต่างกัน

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


1

ความแตกต่างที่สำคัญเกิดจากการขาดการกำหนดขอบเขตคำศัพท์ใน C.

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

การปิด OTOH มีสถานะของตัวเองในรูปแบบของ 'ตัวแปรภายนอก' หรือ 'ค่าที่เพิ่มขึ้น' สามารถเป็นแบบส่วนตัวหรือแชร์ได้ตามที่คุณต้องการโดยใช้การกำหนดขอบเขตคำศัพท์ คุณสามารถสร้างการปิดจำนวนมากโดยใช้รหัสฟังก์ชันเดียวกัน แต่มีอินสแตนซ์ตัวแปรต่างกัน

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

ในระยะสั้นการปิดเป็นตัวชี้ฟังก์ชันบวกสถานะบางอย่าง มันเป็นการสร้างระดับที่สูงขึ้น


2
WTF? C มีขอบเขตศัพท์แน่นอน
Luís Oliveira

1
มันมี 'ขอบเขตคงที่' ตามที่ฉันเข้าใจแล้วการกำหนดขอบเขตคำศัพท์เป็นคุณลักษณะที่ซับซ้อนกว่าในการรักษาความหมายที่คล้ายกันในภาษาที่มีฟังก์ชันที่สร้างขึ้นแบบไดนามิกซึ่งเรียกว่าการปิด
Javier

1

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

{
    my $count;
    sub increment { return $count++ }
}

การปิดคือสภาพแวดล้อมที่กำหนด$countตัวแปร พร้อมใช้งานสำหรับincrementรูทีนย่อยเท่านั้นและยังคงอยู่ระหว่างการโทร


0

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

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