====== Lamda Expressions ======
Στη C++, τα Lambda Expressions εισήχθησαν στο πρότυπο C++11 και αποτελούν έναν σύντομο τρόπο για τη δημιουργία ανώνυμων συναρτήσεων (function objects) απευθείας στο σημείο όπου χρειάζονται. Είναι ιδιαίτερα χρήσιμα όταν θέλουμε να δηλώσουμε μια σύντομη λογική, χωρίς να ορίζουμε επιπλέον συναρτήσεις ή κλάσεις.
===== Σύνταξη των lamda expressions =====
Μια λάμδα έκφραση αποτελείται από τέσσερα βασικά μέρη:
''[capture clause] (parameters) -> return_type { body }''
* **[Capture Clause]:** Καθορίζει ποιες μεταβλητές από το εξωτερικό περιβάλλον είναι προσβάσιμες μέσα στη lamda.
* **Parameters():** Οι παράμετροι της συνάρτησης, όπως σε μια κανονική συνάρτηση περικλύονται σε παρένθεση.
* **Return Type (προαιρετικό):** Επιστρεφόμενος τύπος. Συνήθως ο compiler το συμπεραίνει αυτόματα.
* **{Body}:** Ο κώδικας που εκτελείται στο σώμα της συνάρτησης. Περικλύεται σε άγκιστρα.
=== Capture Clause (Δέσμευση Μεταβλητών από το εξωτερικό περιβάλλον της lamda) ===
Το //Capture Clause// επιτρέπει στη lamda να "βλέπει" μεταβλητές που ορίστηκαν έξω από αυτήν, χωρίς οι μεταβλητές αυτές να είναι παράμετροι της. Διακρίνουμε τους παρακάτω τύπους //Capture Clause//.
^ Capture ^ Περιγραφή ^
| [ ] | Καμία εξωτερική μεταβλητή δεν είναι προσβάσιμη. |
| [x] | Δέσμευση της μεταβλητής x με τιμή (αντίγραφο). |
| [&x] | Δέσμευση της μεταβλητής x με αναφορά (reference). |
| [=] | Δέσμευση όλων των τοπικών μεταβλητών με τιμή. |
| [&] | Δέσμευση όλων των τοπικών μεταβλητών με αναφορά. |
===== Παραδείγματα Lamda Expressions =====
==== Παράδειγμα 1ο - Φιλτράρισμα και εξαγωγή μέσου όρου ====
Στο παρακάτω παράδειγμα οι βαθμοί ενός φοιτητής, φιλτράρονται ως προς την προσβασιμοτητας τους και από αυτούς υπολογίζεται άθροισμα και πλήθος, ώστε να εξαχθεί ο μέσος όρος.
#include
#include
#include
int main() {
std::vector grades = {70, 85, 40, 92, 55, 30};
int sum = 0;
int count = 0;
int threshold = 50;
// Δέσμευση με αναφορά (&) για να τροποποιούμε τα sum και count
// Δέσμευση με τιμή (threshold) γιατί μόνο το διαβάζουμε
std::for_each(grades.begin(), grades.end(), [&sum, &count, threshold](int g) {
if (g >= threshold) {
sum += g;
count++;
}
});
if (count > 0) {
std::cout << "Average of passing grades: " << (double)sum / count << std::endl;
// Έξοδος: Average of passing grades: 75.5
}
}
Παρατηρούμε ότι το //Capture Clause// λαμβάνει τις μεταβλητές sum, count, threshold. Οι δύο πρώτες καλούνται με αναφορά και την ενημερώνουην μετά την έξοδο από τη lamda, ενώ η threshold καλείται με τιμή.
=== Πως διαχειρίζεται ο compiler το lambda expression; ===
Ο compiler μετατρέπει τη λάμδα σε μια ανώνυμη κλάση (anonymous functor). Για την παραπάνω έκφραση //lamda// ο compiler δημιουργεί την παρακάτω ανώνυμη κλάση.
class __lambda_unique_name {
private:
int& sum; // Capture by reference
int& count; // Capture by reference
const int threshold; // Capture by value
public:
// Constructor για την αρχικοποίηση των captures
__lambda_unique_name(int& _sum, int& _count, int _threshold)
: sum(_sum), count(_count), threshold(_threshold) {}
// Ο τελεστής κλήσης (το σώμα της lambda)
// Είναι const εξ ορισμού, εκτός αν χρησιμοποιηθεί η λέξη 'mutable'
void operator()(int g) const {
if (g >= threshold) {
sum += g;
count++;
}
}
// Ο compiler διαγράφει τον default constructor
__lambda_unique_name() = delete;
};
Παρατηρούμε ότι η μεταβλητή //threshold// δεν αλλάζει μέσα στην έκφραση //lambda//. Αν θέλαμε να αλλάζει σε διαφορετικές κλήσεις, θα έπρεπε να προσθέσουμε τη λέξη //mutable//.
==== Παράδειγμα 2ο - Αθροιστής ====
Ας δούμε το παρακάτω παράδειγμα που απεικόνίζει έναν αθροιστή και μεταβάλει μία μεταβλητή που έχει περαστεί με τιμή στο //Clause//.
#include
#include
int main() {
// Το [total = 0] δημιουργεί μια εσωτερική μεταβλητή (capture by value).
auto add_to_total = [total = 0](int amount) mutable {
total += amount; // Η τροποποίηση επιτρέπεται μόνο λόγω του 'mutable'
return total;
};
std::vector expenses = {10, 45, 5, 20};
std::cout << "Starting accumulation..." << std::endl;
for (int bill : expenses) {
std::cout << "Adding: " << bill
<< " | Current Total: " << add_to_total(bill)
<< std::endl;
}
}
Στο παράδειγμα, η μεταβλητή ''total'' μεταβάλει την τιμή της σε κάθε κλίση της έκφρασης //lamda//. Εδώ η προσθήκη της λέξης //mutable// είναι απαραίτητη και χωρίς αυτή το πρόγραμμα δεν μεταγλωττίζεται. H λέξη //mutable// σηματοδοτεί ότι η μεταβλητή στο //Capture Clause// μπορεί να αλλάξει τιμή. Ο λόγος που συμβαίνει αυτό είναι γιατί η λέξη //mutable// σηματοδοτεί ότι η συνάρτηση υπερφόρτωσης του τελεστή () δε θα είναι const. Παρακάτω δίνουμε την ανώνυμη κλάση (functor) που παράγεται για τη δεδομένη //lambda// έκφραση.
class __lambda_accumulator_unique {
private:
int total; // Το εσωτερικό state (capture by value)
public:
// Constructor που αρχικοποιεί το total (στην περίπτωσή μας με 0)
__lambda_accumulator_unique(int initial_val) : total(initial_val) {}
/**
* Ο operator() ΔΕΝ είναι const.
* Αυτό συμβαίνει επειδή χρησιμοποιήσαμε τη λέξη 'mutable'.
* Αν έλειπε το 'mutable', η μέθοδος θα ήταν:
* int operator()(int amount) const { ... }
* και η γραμμή total += amount θα έβγαζε error στη μεταγλώττιση.
*/
int operator()(int amount) {
total += amount; // Τροποποιούμε το data member της κλάσης
return total;
}
};
==== Παράδειγμα 3ο - Password Cheker ====
Παρακάτω δίνεται ένα πρόγραμμα που διαβάζει από ένα std::vector ένα σύνολο από passwords, εφαρμόζει κάποια κριτήρια για τον αν τα passwords είναι valid ή όχι και εκτυπώνει για κάθε passowrd τη θέση του στη λίστα και τόσα αστεράκια όσα είναι το μέγεθος του. Τα κριτήρια για το αν ένα passowrd είναι valid είναι α) μέγεθος τουλάχιστον 8 χαρκτήρες, β) πρέπει να περιέχει τουλάχιστον ένα πεζό, ένα κεφαλαίο, έναν αριθμό και ένα ειδικό χαρακτήρα.
#include
#include
#include
#include
#include
//using namespace std;
int main() {
// Λίστα με passwords για έλεγχο
std::vector passwords = {
"Pass123!", // Valid
"weak", // Invalid (μικρό μέγεθος)
"OnlyLetters", // Invalid (λείπουν αριθμοί/σύμβολα)
"NoSpecial12", // Invalid (λείπει ειδικός χαρακτήρας)
"Valid_Pass_2026", // Valid
"12345678", // Invalid (λείπουν γράμματα)
"Ab1!" // Invalid (πολύ μικρό)
};
// Λάμδα για τον έλεγχο εγκυρότητας
auto isValid = [specialChars = std::string("!@#$%^&*()-_=+[]{}|;:,.<>?/")](const std::string& p) -> bool {
if (p.length() < 8) return false;
bool hasUpper = false, hasLower = false, hasDigit = false, hasSpecial = false;
for (char c : p) {
if (std::isupper(c)) hasUpper = true;
else if (std::islower(c)) hasLower = true;
else if (std::isdigit(c)) hasDigit = true;
else if (specialChars.find(c) != std::string::npos) hasSpecial = true;
}
return hasUpper && hasLower && hasDigit && hasSpecial;
};
std::cout << "------- Checking Passwords -----------" << std::endl;
std::cout << "Position | Masked Password | Status" << std::endl;
std::cout << "--------------------------------------" << std::endl;
for (size_t i = 0; i < passwords.size(); ++i) {
bool valid = isValid(passwords[i]);
// Εκτύπωση θέσης (i + 1)
std::cout << "[" << i + 1 << "] ";
// Εκτύπωση αστερίσκων αντί για το password
std::string masked(passwords[i].length(), '*');
std::cout << masked;
// Ευθυγράμμιση εξόδου (padding)
int padding = 15 - (int)passwords[i].length();
for(int j=0; j < (padding > 0 ? padding : 1); ++j) std::cout << " ";
std::cout << "| " << (valid ? "VALID" : "INVALID") << std::endl;
}
return 0;
}
==== Παράδειγμα 4ο - Μετασχηματισμός ====
Ας υποθέσουμε ότι έχουμε ένα //std::vector// από αντικείμενα τύπου Student (βλέπε παρακάτω) και θέλουμε να μετασχήματίσουμε τα αντικείμενα αυτά ώστε να κρατήσουμε μόνο τους φοιτητές με ΑΕΜ από 1000 εως 2000 και το αποτέλεσμα να το εισάγουμε σε ένα νέο //std::vector// ή ένα νέο //std::set//.
#ifndef _STUDENT_HPP_
#define _STUDENT_HPP_
#include
#include
class Student {
private:
char *name;
int aem;
public:
Student();
Student(const char *name, int aem);
Student(const Student& );
~Student();
char* getName() const;
int getAEM() const;
void setName(char*);
void setAEM(int);
friend std::ostream& operator<<(std::ostream& out, const Student & st);
bool operator>(const Student& st) const;
bool operator<(const Student& st) const;
Student& operator=(const Student& st);
};
Student::Student(const char *name, int aem) {
this->name = new char [strlen(name) + 1];
strcpy(this->name, name);
this->aem = aem;
std::cerr << "Argu Construct : " << *this << std::endl;
}
Student::Student(const Student& st) {
name = new char [strlen(st.name) + 1];
strcpy(name, st.name);
aem = st.aem;
std::cerr << "Copy Construct : " << *this << std::endl;
}
Student::Student() {
this->name = nullptr;
this->aem = 0;
}
Student::~Student() {
if(name != nullptr) {
std::cerr << "Destruct: " << *this << std::endl;
delete []name;
}
}
char* Student::getName() const {
return name;
}
int Student::getAEM() const {
return aem;
}
void Student::setName(char* name) {
this->name != nullptr;
delete this->name;
this->name = new char [strlen(name) + 1];
strcpy(this->name, name);
}
void Student::setAEM(int aem) {
this->aem = aem;
}
Student& Student::operator=(const Student& st) {
if(name != nullptr)
delete name;
name = new char [strlen(st.name) + 1];
strcpy(name, st.name);
aem = st.aem;
std::cerr << "Copy : " << *this << std::endl;
return *this;
}
std::ostream& operator<<(std::ostream& out, const Student& st) {
if(st.name != nullptr)
out << st.name << " " << st.aem;
return out;
}
bool Student::operator>(const Student& st) const {
if(aem > st.aem)
return true;
return false;
}
bool Student::operator<(const Student& st) const {
if(aem < st.aem)
return true;
return false;
}
#endif
#include
#include
#include // Για το std::copy_if
#include // Για το std::back_inserter
#include "Student.hpp"
int main() {
// 1. Αρχικό vector με 5 αντικείμενα Student
std::vector students;
students.push_back(Student("Giannis", 1050));
students.push_back(Student("Maria", 950));
students.push_back(Student("Kostas", 1500));
students.push_back(Student("Eleni", 2500));
students.push_back(Student("Nikos", 1999));
// 3. Το ΝΕΟ vector όπου θα αποθηκευτούν οι φοιτητές που θα "επιζήσουν"
std::vector filteredStudents;
std::cout << "\n--- Φιλτράρισμα με Lambda σε νέο Vector ---\n" << std::endl;
// 2. Φιλτράρισμα και αποθήκευση στο νέο vector
std::copy_if(students.begin(), students.end(),
std::back_inserter(filteredStudents), // Εισαγωγή στο τέλος του νέου vector
[](const Student& st) {
return st.getAEM() >= 1000 && st.getAEM() <= 2000;
});
// Εκτύπωση των αποτελεσμάτων του νέου vector
std::cout << "\n--- Φοιτητές στο Νέο Vector ---" << std::endl;
for (const auto& student : filteredStudents) {
std::cout << student << std::endl;
}
std::cout << "\n--- Τέλος Προγράμματος ---" << std::endl;
return 0;
}
#include
#include
#include
#include // Απαραίτητο για το std::copy_if
#include // Απαραίτητο για το std::inserter
#include "Student.hpp"
int main() {
// 1. Δημιουργία vector με 5 αντικείμενα Student
std::vector students;
students.push_back(Student("Giannis", 1050));
students.push_back(Student("Maria", 950));
students.push_back(Student("Kostas", 1500));
students.push_back(Student("Eleni", 2500));
students.push_back(Student("Nikos", 1999));
// 3. Το set όπου θα αποθηκευτούν οι φοιτητές που θα "επιζήσουν"
std::set filteredStudents;
std::cout << "\n--- Φιλτράρισμα με Lambda και std::copy_if ---\n" << std::endl;
// 2. Φιλτράρισμα και αποθήκευση ταυτόχρονα
std::copy_if(students.begin(), students.end(),
std::inserter(filteredStudents, filteredStudents.end()),
[](const Student& st) {
return st.getAEM() >= 1000 && st.getAEM() <= 2000;
});
// Εκτύπωση των αποτελεσμάτων
std::cout << "\n--- Φοιτητές στο Set ---" << std::endl;
for (const auto& student : filteredStudents) {
std::cout << student << std::endl;
}
std::cout << "\n--- Τέλος Προγράμματος ---" << std::endl;
return 0;
}