Biểu thức Lambda trong C ++

Lambda Expressions C



Tại sao lại sử dụng Lambda Expression?

Hãy xem xét tuyên bố sau:

NSmyInt= 52;

Ở đây, myInt là một định danh, một giá trị. 52 là một nghĩa đen, một giá trị prvalue. Ngày nay, người ta có thể viết mã một hàm đặc biệt và đặt nó ở vị trí 52. Một hàm như vậy được gọi là biểu thức lambda. Cũng hãy xem xét chương trình ngắn sau:







#bao gồm

sử dụng không gian têngiờ;

NSfn(NSxuyên qua)

{

NSbài giải=xuyên qua+ 3;

trở lạibài giải;

}


NSchủ chốt()

{

fn(5);



trở lại 0;

}

Ngày nay, có thể viết mã một hàm đặc biệt và đặt nó vào vị trí của đối số là 5, của lời gọi hàm, fn (5). Một hàm như vậy được gọi là một biểu thức lambda. Biểu thức lambda (hàm) ở vị trí đó là một prvalue.



Bất kỳ ký tự nào ngoại trừ chuỗi ký tự là một giá trị prvalue. Biểu thức lambda là một thiết kế hàm đặc biệt sẽ phù hợp như một nghĩa đen trong mã. Nó là một chức năng ẩn danh (không được đặt tên). Bài viết này giải thích biểu thức chính C ++ mới, được gọi là biểu thức lambda. Kiến thức cơ bản trong C ++ là một yêu cầu để hiểu bài viết này.



Nội dung bài viết

Hình minh họa của Lambda Expression

Trong chương trình sau, một hàm, là một biểu thức lambda, được gán cho một biến:





#bao gồm

sử dụng không gian têngiờ;

tự độngfn= [](NSngừng lại)

{

NSbài giải=ngừng lại+ 3;

trở lạibài giải;

};


NSchủ chốt()

{

tự độngvariab=fn(2);

Giá cả <<variab<< ' ';


trở lại 0;

}

Đầu ra là:

5

Bên ngoài hàm main (), có một biến, fn. Loại của nó là tự động. Tự động trong trường hợp này có nghĩa là kiểu thực tế, chẳng hạn như int hoặc float, được xác định bởi toán hạng bên phải của toán tử gán (=). Ở bên phải của toán tử gán là một biểu thức lambda. Biểu thức lambda là một hàm không có kiểu trả về trước. Lưu ý việc sử dụng và vị trí của dấu ngoặc vuông, []. Hàm trả về 5, một int, sẽ xác định kiểu cho fn.



Trong hàm main (), có câu lệnh:

tự độngvariab=fn(2);

Điều này có nghĩa là, fn bên ngoài hàm main (), kết thúc là mã định danh cho một hàm. Các tham số ngầm định của nó là các tham số của biểu thức lambda. Loại cho variab là tự động.

Lưu ý rằng biểu thức lambda kết thúc bằng dấu chấm phẩy, giống như định nghĩa class hoặc struct, kết thúc bằng dấu chấm phẩy.

Trong chương trình sau, một hàm, là một biểu thức lambda trả về giá trị 5, là một đối số cho một hàm khác:

#bao gồm

sử dụng không gian têngiờ;

vô hiệuotherfn(NSno1,NS (*ptr)(NS))

{

NSno2= (*ptr)(2);

Giá cả <<no1<< '' <<no2<< ' ';

}


NSchủ chốt()

{

otherfn(4,[](NSngừng lại)

{

NSbài giải=ngừng lại+ 3;

trở lạibài giải;

});


trở lại 0;
}

Đầu ra là:

Bốn năm

Có hai hàm ở đây, biểu thức lambda và hàm otherfn (). Biểu thức lambda là đối số thứ hai của hàm otherfn (), được gọi trong hàm main (). Lưu ý rằng hàm lambda (biểu thức) không kết thúc bằng dấu chấm phẩy trong lệnh gọi này bởi vì, ở đây, nó là một đối số (không phải là một hàm độc lập).

Tham số hàm lambda trong định nghĩa của hàm otherfn () là một con trỏ đến một hàm. Con trỏ có tên, ptr. Tên, ptr, được sử dụng trong định nghĩa otherfn () để gọi hàm lambda.

Tuyên bố,

NSno2= (*ptr)(2);

Trong định nghĩa otherfn (), nó gọi hàm lambda với đối số là 2. Giá trị trả về của lệnh gọi, '(* ptr) (2)' từ hàm lambda, được gán cho no2.

Chương trình trên cũng chỉ ra cách hàm lambda có thể được sử dụng trong lược đồ hàm gọi lại C ++.

Các phần của Lambda Expression

Các phần của một hàm lambda điển hình như sau:

[] () {}
  • [] là mệnh đề nắm bắt. Nó có thể có các mục.
  • () dành cho danh sách tham số.
  • {} dành cho thân hàm. Nếu hàm đứng một mình, thì nó phải kết thúc bằng dấu chấm phẩy.

Chụp

Định nghĩa hàm lambda có thể được gán cho một biến hoặc được sử dụng làm đối số cho một lệnh gọi hàm khác. Định nghĩa cho một lời gọi hàm như vậy phải có dưới dạng một tham số, một con trỏ đến một hàm, tương ứng với định nghĩa hàm lambda.

Định nghĩa hàm lambda khác với định nghĩa hàm thông thường. Nó có thể được gán cho một biến trong phạm vi toàn cục; hàm được gán cho biến này cũng có thể được mã hóa bên trong một hàm khác. Khi được gán cho một biến phạm vi toàn cục, phần thân của nó có thể thấy các biến khác trong phạm vi toàn cục. Khi được gán cho một biến bên trong định nghĩa hàm thông thường, phần thân của nó có thể thấy các biến khác trong phạm vi hàm chỉ với sự trợ giúp của mệnh đề nắm bắt, [].

Mệnh đề capture [], còn được gọi là lambda-Introductioner, cho phép các biến được gửi từ phạm vi (hàm) xung quanh vào thân hàm của biểu thức lambda. Phần thân hàm của biểu thức lambda được cho là nắm bắt biến khi nó nhận đối tượng. Nếu không có mệnh đề nắm bắt [], một biến không thể được gửi từ phạm vi xung quanh vào thân hàm của biểu thức lambda. Chương trình sau minh họa điều này, với phạm vi hàm main (), là phạm vi xung quanh:

#bao gồm

sử dụng không gian têngiờ;

NSchủ chốt()

{

NSTôi= 5;


tự độngfn= [Tôi]()

{

Giá cả <<Tôi<< ' ';

};

fn();


trở lại 0;

}

Đầu ra là 5 . Nếu không có tên, id, bên trong [], biểu thức lambda sẽ không thấy id biến của phạm vi hàm main ().

Chụp bằng cách tham khảo

Ví dụ ở trên sử dụng mệnh đề nắm bắt là nắm bắt theo giá trị (xem chi tiết bên dưới). Khi nắm bắt bằng tham chiếu, vị trí (lưu trữ) của biến, ví dụ: id ở trên, của phạm vi xung quanh, được tạo sẵn bên trong thân hàm lambda. Vì vậy, việc thay đổi giá trị của biến bên trong thân hàm lambda sẽ thay đổi giá trị của cùng một biến đó trong phạm vi xung quanh. Mỗi biến được lặp lại trong mệnh đề nắm bắt được đặt trước dấu và (&) để đạt được điều này. Chương trình sau minh họa điều này:

#bao gồm

sử dụng không gian têngiờ;

NSchủ chốt()

{

NSTôi= 5; trôi nổift= 2.3; charch= 'ĐẾN';

tự độngfn= [&Tôi,&ft,&ch]()

{

Tôi= 6;ft= 3,4;ch= 'NS';

};

fn();

Giá cả <<Tôi<< ',' <<ft<< ',' <<ch<< ' ';

trở lại 0;

}

Đầu ra là:

6, 3,4, B

Xác nhận rằng các tên biến bên trong thân hàm của biểu thức lambda dành cho các biến giống nhau bên ngoài biểu thức lambda.

Nắm bắt theo giá trị

Khi nắm bắt theo giá trị, một bản sao của vị trí của biến, của phạm vi xung quanh, được tạo sẵn bên trong thân hàm lambda. Mặc dù biến bên trong thân hàm lambda là một bản sao, nhưng giá trị của nó không thể thay đổi bên trong thân hàm hiện tại. Để đạt được việc nắm bắt theo giá trị, mỗi biến được lặp lại trong mệnh đề bắt không được đặt trước bất kỳ biến nào. Chương trình sau minh họa điều này:

#bao gồm

sử dụng không gian têngiờ;

NSchủ chốt()

{

NSTôi= 5; trôi nổift= 2.3; charch= 'ĐẾN';

tự độngfn= [id, ft, ch]()

{

// id = 6; ft = 3,4; ch = 'B';

Giá cả <<Tôi<< ',' <<ft<< ',' <<ch<< ' ';

};

fn();

Tôi= 6;ft= 3,4;ch= 'NS';

Giá cả <<Tôi<< ',' <<ft<< ',' <<ch<< ' ';

trở lại 0;

}

Đầu ra là:

5, 2,3, A

6, 3,4, B

Nếu chỉ báo bình luận bị loại bỏ, chương trình sẽ không biên dịch. Trình biên dịch sẽ đưa ra thông báo lỗi rằng không thể thay đổi các biến bên trong định nghĩa của thân hàm về biểu thức lambda. Mặc dù không thể thay đổi các biến bên trong hàm lambda, chúng có thể được thay đổi bên ngoài hàm lambda, như đầu ra của chương trình ở trên cho thấy.

Trộn các hình chụp

Chụp theo tham chiếu và chụp theo giá trị có thể được trộn lẫn, như chương trình sau cho thấy:

#bao gồm

sử dụng không gian têngiờ;

NSchủ chốt()

{

NSTôi= 5; trôi nổift= 2.3; charch= 'ĐẾN'; boolbl= thật;


tự độngfn= [id, ft,&ch,&bl]()

{

ch= 'NS';bl= sai;

Giá cả <<Tôi<< ',' <<ft<< ',' <<ch<< ',' <<bl<< ' ';

};

fn();


trở lại 0;

}

Đầu ra là:

5, 2,3, B, 0

Khi tất cả được nắm bắt, là bằng tham chiếu:

Nếu tất cả các biến cần nắm bắt đều được ghi lại bằng tham chiếu, thì chỉ cần một & sẽ đủ trong mệnh đề nắm bắt. Chương trình sau minh họa điều này:

#bao gồm

sử dụng không gian têngiờ;

NSchủ chốt()

{

NSTôi= 5; trôi nổift= 2.3; charch= 'ĐẾN'; boolbl= thật;


tự độngfn= [&]()

{

Tôi= 6;ft= 3,4;ch= 'NS';bl= sai;

};

fn();

Giá cả <<Tôi<< ',' <<ft<< ',' <<ch<< ',' <<bl<< ' ';


trở lại 0;

}

Đầu ra là:

6, 3,4, B, 0

Nếu một số biến được ghi lại bằng tham chiếu và những biến khác theo giá trị, thì một & sẽ đại diện cho tất cả các tham chiếu và phần còn lại sẽ không được đặt trước bất kỳ thứ gì, như chương trình sau cho thấy:

sử dụng không gian têngiờ;

NSchủ chốt()

{

NSTôi= 5; trôi nổift= 2.3; charch= 'ĐẾN'; boolbl= thật;


tự độngfn= [&, id, ft]()

{

ch= 'NS';bl= sai;

Giá cả <<Tôi<< ',' <<ft<< ',' <<ch<< ',' <<bl<< ' ';

};

fn();


trở lại 0;

}

Đầu ra là:

5, 2,3, B, 0

Lưu ý rằng & một mình (tức là & không theo sau bởi một định danh) phải là ký tự đầu tiên trong mệnh đề nắm bắt.

Khi tất cả được nắm bắt, theo giá trị:

Nếu tất cả các biến cần bắt đều được bắt theo giá trị, thì chỉ cần một = là đủ trong mệnh đề bắt. Chương trình sau minh họa điều này:

#bao gồm

sử dụng không gian têngiờ;

NSchủ chốt()
{

NSTôi= 5; trôi nổift= 2.3; charch= 'ĐẾN'; boolbl= thật;


tự độngfn= [=]()

{

Giá cả <<Tôi<< ',' <<ft<< ',' <<ch<< ',' <<bl<< ' ';

};

fn();


trở lại 0;


}

Đầu ra là:

5, 2,3, A, 1

Ghi chú : = là chỉ đọc, kể từ bây giờ.

Nếu một số biến được ghi lại theo giá trị và những biến khác bằng tham chiếu, thì một = sẽ đại diện cho tất cả các biến được sao chép chỉ đọc và phần còn lại mỗi biến sẽ có &, như chương trình sau đây hiển thị:

#bao gồm

sử dụng không gian têngiờ;

NSchủ chốt()

{

NSTôi= 5; trôi nổift= 2.3; charch= 'ĐẾN'; boolbl= thật;


tự độngfn= [=,&ch,&bl]()

{

ch= 'NS';bl= sai;

Giá cả <<Tôi<< ',' <<ft<< ',' <<ch<< ',' <<bl<< ' ';

};

fn();


trở lại 0;

}

Đầu ra là:

5, 2,3, B, 0

Lưu ý rằng = một mình phải là ký tự đầu tiên trong mệnh đề nắm bắt.

Lược đồ hàm gọi lại cổ điển với biểu thức Lambda

Chương trình sau đây cho thấy cách một lược đồ hàm gọi lại cổ điển có thể được thực hiện với biểu thức lambda:

#bao gồm

sử dụng không gian têngiờ;

char *đầu ra;


tự độngcba= [](charngoài[])

{

đầu ra=ngoài;

};



vô hiệumainFunc(charđầu vào[],vô hiệu (*)(char[]))

{

(*)(đầu vào);

Giá cả<<'cho chức năng chính'<<' ';

}


vô hiệufn()

{

Giá cả<<'Bây giờ'<<' ';

}


NSchủ chốt()

{

charđầu vào[] = 'cho chức năng gọi lại';

mainFunc(đầu vào, cba);

fn();

Giá cả<<đầu ra<<' ';



trở lại 0;

}

Đầu ra là:

cho chức năng chính

Bây giờ

cho chức năng gọi lại

Nhớ lại rằng khi một định nghĩa biểu thức lambda được gán cho một biến trong phạm vi toàn cục, thân hàm của nó có thể thấy các biến toàn cục mà không cần sử dụng mệnh đề nắm bắt.

Kiểu trả về sau

Kiểu trả về của biểu thức lambda là tự động, có nghĩa là trình biên dịch xác định kiểu trả về từ biểu thức trả về (nếu có). Nếu người lập trình thực sự muốn chỉ ra kiểu trả về, thì anh ta sẽ làm điều đó như trong chương trình sau:

#bao gồm

sử dụng không gian têngiờ;

tự độngfn= [](NSngừng lại) -> NS

{

NSbài giải=ngừng lại+ 3;

trở lạibài giải;

};


NSchủ chốt()

{

tự độngvariab=fn(2);

Giá cả <<variab<< ' ';


trở lại 0;

}

Đầu ra là 5. Sau danh sách tham số, toán tử mũi tên được nhập. Tiếp theo là kiểu trả về (trong trường hợp này là int).

Khép kín

Hãy xem xét đoạn mã sau:

cấu trúcCla

{

NSTôi= 5;

charch= 'đến';

}obj1, obj2;

Ở đây, Cla là tên của lớp struct. Obj1 và obj2 là hai đối tượng sẽ được khởi tạo từ lớp struct. Biểu thức Lambda cũng tương tự trong việc triển khai. Định nghĩa hàm lambda là một loại lớp. Khi hàm lambda được gọi (được gọi), một đối tượng sẽ được khởi tạo từ định nghĩa của nó. Đối tượng này được gọi là bao đóng. Việc đóng cửa sẽ thực hiện công việc mà lambda dự kiến ​​sẽ làm.

Tuy nhiên, mã hóa biểu thức lambda như struct ở trên sẽ có obj1 và obj2 được thay thế bằng các đối số của tham số tương ứng. Chương trình sau minh họa điều này:

#bao gồm

sử dụng không gian têngiờ;

tự độngfn= [](NSparam1,NSparam2)

{

NSbài giải=param1+param2;

trở lạibài giải;

} (2,3);


NSchủ chốt()

{

tự độngở đâu=fn;

Giá cả <<ở đâu<< ' ';


trở lại 0;

}

Kết quả là 5. Các đối số là 2 và 3 trong ngoặc đơn. Lưu ý rằng lệnh gọi hàm biểu thức lambda, fn, không nhận bất kỳ đối số nào vì các đối số đã được mã hóa ở cuối định nghĩa hàm lambda.

Phần kết luận

Biểu thức lambda là một hàm ẩn danh. Nó có hai phần: lớp và đối tượng. Định nghĩa của nó là một loại lớp. Khi biểu thức được gọi, một đối tượng được hình thành từ định nghĩa. Đối tượng này được gọi là bao đóng. Việc đóng cửa sẽ thực hiện công việc mà lambda dự kiến ​​sẽ làm.

Để biểu thức lambda nhận một biến từ một phạm vi hàm bên ngoài, nó cần một mệnh đề nắm bắt không rỗng vào thân hàm của nó.