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

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

Lambdas ถูกกำหนดโดยใช้ไวยากรณ์ต่อไปนี้:

[ capture list ] ( argument list ) -> return type { function body }
  • capture list ใช้เพื่อระบุตัวแปรจากขอบเขตโดยรอบที่จะสามารถเข้าถึงได้ภายในแลมบ์ดา คุณสามารถบันทึกตัวแปรตามค่า โดยการอ้างอิง หรือใช้ this
  • argument listระบุพารามิเตอร์ที่จะส่งผ่านไปยังแลมบ์ดา
  • return type ระบุประเภทของค่าที่ lambda จะส่งกลับ หากไม่ได้ระบุไว้ คอมไพลเลอร์จะพยายามสรุป
  • function body ระบุรหัสที่จะดำเนินการเมื่อมีการเรียกแลมบ์ดา

ต่อไปนี้เป็นวิธีต่างๆ บางประการในการใช้ lambdas ใน C++:

  1. ฟังก์ชั่นการโทรกลับ
  2. กำลังบันทึกค่าเริ่มต้น
  3. การจับตามมูลค่า
  4. การจับโดยการอ้างอิง
  5. แลมบ์ดาที่ไม่แน่นอน

{1} การเรียกกลับฟังก์ชัน

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

ตัวอย่าง:

#include <iostream>
#include <algorithm>
#include <vector>

int main() {
  std::vector<int> numbers = {1, 2, 3, 4, 5};

  // Lambda expression to find the sum of two numbers
  auto sum = [](int a, int b) { return a + b; };

  int result = std::accumulate(numbers.begin(), numbers.end(), 0, sum);
  std::cout << "Sum of elements in the vector: " << result << std::endl;

  return 0;
}

ในตัวอย่างนี้ ตัวแปร sum คือนิพจน์ Lambda ที่รับพารามิเตอร์ 2 ตัว a และ b แล้วส่งคืนผลรวม ฟังก์ชัน std::accumulate รับเวกเตอร์ของตัวเลข ค่าเริ่มต้นของผลลัพธ์ และฟังก์ชันผลรวม (นิพจน์ Lambda) ฟังก์ชันจะคำนวณผลรวมขององค์ประกอบทั้งหมดในเวกเตอร์และส่งกลับผลลัพธ์ที่พิมพ์บนหน้าจอ

ตัวอย่างอื่น:

#include <iostream>
#include <algorithm>
#include <vector>

int main()
{
    std::vector<int> vec = { 1, 2, 3, 4, 5 };
    int sum = 0;
    std::for_each(vec.begin(), vec.end(), [&sum](int x) { sum += x; });
    std::cout << "The sum is: " << sum << std::endl;
    return 0;
}

ในกรณีนี้ นิพจน์แลมบ์ดา [&sum](int x) { sum += x; } จะถูกส่งผ่านเป็นฟังก์ชันที่จะใช้กับแต่ละองค์ประกอบ แลมบ์ดาจับตัวแปร sum โดยการอ้างอิง & เพื่อให้สามารถแก้ไขได้ภายในเนื้อความแลมบ์ดา

ทั้งสองตัวอย่างได้ผลลัพธ์เดียวกัน แต่ตัวอย่างที่สองใช้อัลกอริธึม std::for_each และนิพจน์แลมบ์ดา ซึ่งเป็นเทคนิคที่ทันสมัยและกระชับมากกว่าใน C++

{2} การบันทึกค่าเริ่มต้น

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

ตัวอย่าง:

#include <iostream>

int main() {
  int x = 42;
  auto f = [ ]() { std::cout << x << std::endl; };
  f();
  return 0;
}
#include <iostream>

int main()
{
    auto square = [](int x) { return x * x; };
    std::cout << "The square of 5 is: " << square(5) << std::endl;
    return 0;
}

ในตัวอย่างที่สอง นิพจน์แลมบ์ดาถูกกำหนดและจัดเก็บไว้ในตัวแปรชื่อ square นิพจน์แลมบ์ดานี้รับอาร์กิวเมนต์ int x และส่งกลับค่า x * x ซึ่งเป็นกำลังสองของอาร์กิวเมนต์

ในฟังก์ชัน main นิพจน์แลมบ์ดานี้จะใช้เป็นออบเจ็กต์ฟังก์ชัน มันถูกเรียกโดยการส่งอาร์กิวเมนต์ 5 และผลลัพธ์จะแสดงโดยใช้สตรีม cout

{3} การจับตามค่า

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

ตัวอย่าง

#include <iostream>

int main() {
  int x = 42;
  auto f = [x]() { std::cout << x << std::endl; };
  f();
  return 0;
}
#include <iostream>

int main() {
    int x = 42;
    auto f = [x](int y) { std::cout << x+y << std::endl;};
    f(1);
    return 0;
}

{4} การจับโดยการอ้างอิง

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

ตัวอย่าง

#include <iostream>

int main() {
  int x = 42;
  auto f = [&x]() { std::cout << x << std::endl; };
  f();
  return 0;
}
#include <iostream>

int main() {
  int x = 10;

  auto add_one = [&x]() { ++x; };
  add_one();
  std::cout << x << "\n";

  return 0;
}
#include <iostream>

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

ในตัวอย่างสุดท้าย ตัวแปร x ถูกจับโดยการอ้างอิง และ lambda add สามารถแก้ไขค่าของมันได้

{5} แลมบ์ดาที่ไม่แน่นอน

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

ตัวอย่าง

#include <iostream>

int main() {
  int x = 42;
  auto f = [x]() mutable { std::cout << ++x << std::endl; };
  f();
  return 0;
}

{บทสรุป}

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

Lambdas มีประโยชน์ด้านประสิทธิภาพมากกว่าฟังก์ชันแบบเดิม

  1. ฟังก์ชันอินไลน์: Lambdas จะถูกอินไลน์โดยอัตโนมัติโดยคอมไพเลอร์ ซึ่งหมายความว่าโค้ดของแลมบ์ดาจะถูกแทรกลงในฟังก์ชันการเรียกโดยตรง ซึ่งสามารถลดค่าใช้จ่ายในการเรียกใช้ฟังก์ชันและปรับปรุงประสิทธิภาพได้
  2. การหลีกเลี่ยงโอเวอร์เฮดของฟังก์ชันที่มีชื่อ: Lambdas ไม่มีชื่อ ดังนั้นจึงไม่จำเป็นต้องประกาศและจัดเก็บไว้ในตารางสัญลักษณ์ ซึ่งสามารถลดโอเวอร์เฮดและปรับปรุงประสิทธิภาพได้
  3. ปรับปรุงตำแหน่งแคช: สามารถกำหนดและใช้งาน Lambdas ภายในฟังก์ชันเดียวกันได้ ซึ่งหมายความว่ารหัสและข้อมูลที่ใช้โดย lambda จะถูกจัดเก็บไว้ในบรรทัดแคชเดียวกันกับรหัสการเรียก วิธีนี้สามารถปรับปรุงตำแหน่งแคชและลดต้นทุนการพลาดแคชได้
  4. ขนาดโค้ดที่ลดลง: โดยปกติแล้ว Lambdas จะมีขนาดเล็กกว่าฟังก์ชันที่มีชื่อ และไม่จำเป็นต้องมีการเรียกใช้ฟังก์ชันภายนอก ซึ่งสามารถลดขนาดของโค้ดที่คอมไพล์และปรับปรุงประสิทธิภาพได้
  5. ความยืดหยุ่นที่เพิ่มขึ้น: Lambdas สามารถใช้เพื่อส่งฟังก์ชันเป็นอาร์กิวเมนต์ไปยังฟังก์ชันอื่นๆ ได้ ซึ่งให้ความยืดหยุ่นมากขึ้นในการนำโค้ดกลับมาใช้ซ้ำและจัดระเบียบได้ วิธีนี้สามารถปรับปรุงประสิทธิภาพได้โดยลดความจำเป็นในการใช้โค้ดที่ซ้ำกัน
  6. ปรับปรุงความสามารถในการอ่าน: Lambdas สามารถทำให้โค้ดอ่านง่ายขึ้นโดยการห่อหุ้มตรรกะที่ซับซ้อนในลักษณะที่กะทัดรัดและครบถ้วนในตัวเอง สิ่งนี้สามารถปรับปรุงประสิทธิภาพโดยทำให้ง่ายต่อการเข้าใจและบำรุงรักษาโค้ด

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

ดีที่สุด