Reguláris kifejezések
A reguláris vagy szabályos kifejezések bizonyos szabályokat követő
karaktersorozatok. Arra szolgálnak, hogy megkönnyítsék a szöveggel való munkát,
mint a keresés, helyettesítés, ellenőrzés, stb. Az ilyen kifejezések sok programozási
nyelvbe be vannak építve, főleg a szövegek feldolgozására specializálódott
nyelvek, mint az XML vagy a HTML. A C++11 is szabványosította ezeket a
kifejezéseket a
eltérőek lehetnek, ezért több szabályzat
(szintaxis) sorolható fel. Ezek közül a C++11 az ECMAScript, az alap és
bővített POSIX, az awk, a grep és az egrep szabálygyűjteményeit támogatja. A
következő példákban az ECMAScript szintaxisát fogjuk használni.
A reguláris kifejezéseket fokozatosan kell felépíteni, akár egy
programot. Először az alap dolgok működjenek, aztán jöhetnek a különböző
megszorítások:
- Legyen
egy program, ami számokat fogad el bemenetnek. Az ECMAScript a számokat a
[:digit:] vagy [:d:] szintaxissal azonosítja: regex r("[[:digit:]]"); - A
fenti kifejezés csak egyjegyű számokat fogad el. Több számjegy esetén a végére
kell írni egy + jelt: regex r("[[:digit:]]+"); - Ha
tesztelni szeretnénk, hogy a beolvasott szám nem-e negatív, akkor a kifejezés
elejébe a –? kombinációt kell tenni: regex r("-?[[:digit:]]+"); - Ha a
felhasználó netán a + karaktert is használja a számok előtt, akkor erre is fel
kell készíteni a fordítót, ugyanis ez egy speciális karakter az ECMAScript
szintaxisában. Hogy figyelmen kívül hagyja ezt a karaktert, a \\+? jelt kell a
kifejezés elejébe írni: regex r("(\\+?[[:digit:]]+"); - A
kifejezés előtt lévő ellenőrzéseke kombinálni kell egy „vagy” kapuval, mert
vagy az egyik, vagy a másik fordulhat elő: regex r("(\\+|-)?[[:digit:]]+");
Ezt a felépített kifejezést alkalmazzuk a
következő programban. Ha a bevitt számjegy nem felel meg a reguláris kifejezés
szabályainak, akkor a „Nem helyes!” üzenet jelenik meg. A program a q billentyű
beviteléig fut.
#include
#include
#include
using namespace std;
int main()
{
string input;
regex r("(\\+|-)?[[:digit:]]+");
while(true) //végtelen ciklus
{
cout
"Irj be egy egesz szamot: ";
cin >>
input;
if(input=="q")
break; // q esetén
kilép
if(regex_match(input,r))
cout
"Helyes!"
else
cout
"Nem helyes!" endl;
}
}
A reguláris kifejezés helyességét az std::regex_match() függvény ellenőrzi. A kimenet:
Irj be egy egesz
szamot: 23
Helyes!
Irj be egy egesz
szamot: +51
Helyes!
Irj be egy egesz
szamot: -79
Helyes!
Irj be egy egesz
szamot: 6.7
Nem helyes!
Irj be egy egesz
szamot: +-41
Nem helyes!
Irj be egy egesz
szamot: abc
Nem helyes!
Irj be egy egesz
szamot: q
Ha valós számokra is hasonlóképp kell
felépíteni a reguláris kifejezést. Ott a tizedespont utáni rész opcionális,
ezért a ([[:digit:]]+)? kifejezéssel jelöljük. Ugyanígy a tizedespont is
opcionális ha nincs utána semmi, így az egész második részt (\\.)? közé lehet
tenni: regex rr("((\\+|-)?[[:digit:]]+)(\\.(([[:digit:]]+)?))?");
Ha a feladat, hogy tudományos formátumú
számokat lehessen beírni, mint pl. -1.23e+06 vagy 1E3, akkor azzal bővítjük az
előző kifejezést, hogy az exponenciális tag is opcionális részese lehet a
számnak. Az exponenciális részt a ((e|E)((\\+|-)?)[[:digit:]]+)? kifejezés
jelenti: regex rrr("((\\+|-)?[[:digit:]]+)(\\.(([[:digit:]]+)?))?((e|E)((\\+|-)?)[[:digit:]]+)?");
Az alapértelmezettként használt ECMAScript
logikája hamar elsajátítható, de beállítható más szintaxis is, például a POSIX
grep:
#include
#include
#include
int main()
{
std::string str = "zzxayyzz";
std::regex re1(".*(xay)"); //
ECMAScript
std::regex re2(".*(xay)", std::regex::grep); // POSIX grep
std::cout "Kereses 'xay' utan az 'zzxayyzz' szovegben:\n";
std::smatch m;
std::regex_search(str, m, re1);
std::cout " ECMAScript: " '\n';
std::regex_search(str, m, re2);
std::cout "
POSIX grep: " '\n';
}
A fenti program egy keresést végez az str stringben. A keresést az std::regex_search() függvény végzi, melynek első paramétere egy string amiben a keresés
zajlik, a második egy konténer, ami a találatokat tartalmazza, a harmadik pedig
a keresés reguláris kifejezése. Az eredmény:
Kereses 'xayy'
utan az 'zzxayyzz' szovegben:
ECMAScript: zzxay
POSIX grep: zzxay
Raw stringek
A raw stringek olyan stringek, melyekben a
feloldójelek (pl. \n, \t, \) nem érvényesek. Szintaxisa: R”(szoveg)”. A
következő program jól szemlélteti a hagyományos és a raw string közti
különbséget:
#include
#include
using namespace std;
int main()
{
string normal_str="Elso sor.\nMasodik sor.\nUzenet vege.\n";
string raw_str=R"(Elso sor.\nMasodik sor.\nUzenet vege.\n)";
cout
cout
return(0);
}
A kimenet:
Elso sor.
Masodik sor.
Uzenet vege.
Elso
sor.\nMasodik sor.\nUzenet vege.\n
A raw string egyik célja, hogy valamennyire
egyszerűsítse a reguláris kifejezések használatát. Ezzel közvetlen bevihetők az
ECMAScript kifejezések anélkül, hogy a feloldójeleket a fordító felismerné. Egy
korábbi példában a bemenő számok helyességét a következőképp ellenőriztük:
regex r("(\\+|-)?[[:digit:]]+");
Ugyanez raw stringgel:
regex integer(R"((\+|-)?[[:digit:]]+)");
Lambda függvények és
kifejezések
A reguláris kifejezések és a raw stringek
mellett egy másik hasznos nyelvezet a lambda. A lambda kifejezések lehetővé
teszik, hogy a név nélkül definiáljunk és használjunk függvényeket. Használható
a függvényobjektumok helyett, így nem kell külön osztályt és tagfüggvényt
definiálni. A következő lambda kifejezés egy olyan függvényt definiál, amelyik
egy számot térít vissza:
[]() -> int
{ return 4; }();
A fenti függvény igazából semmi hasznosat
nem tesz, csupán visszatéríti a 4-et, ami például kiírásra használható:
int result = []()
-> int { return
4; }();
cout
Kicsit hasznosabb a következő:
int result = [](int input) -> int
{ return 2 * input; }(10);
cout
A fenti kifejezésnek van agy bemenő paramétere
(input) és visszatéríti ennek dupláját. A függvény rögtön a deklarálás után
megkapja a 10 paramétert, így a kiírt érték 20. Hasonlóképp felírható két szám
összege:
int result = [](int a,int b) -> int { return a + b;
}(2,4);
cout
Akár a reguláris kifejezések, a lambda
kifejezések is kombinálhatók, egymásba építhetők:
int result = [](int m, int n) -> int { return m + n; }
([](int a) -> int
{ return a; }(2),[](int
a) -> int { return
a; }(3));
cout
A fenti lambda függvényben két másik lambda
függvény szerepel, egyik 2-t, másik 3-at térít vissza, a fő függvény pedig
összeadja ezeket, mint bemenő paramétereket. A függvénymutatók működnek a
lambda függvényekre is:
auto func = [](int a, int b) -> int { return a+b; };
cout
Az auto típus a func változót a lambda
kifejezésre mutató mutatóként fogja deklarálni. Tegyük fel, hogy egész
számokkal megpakolt vektor tartalmát kell rendezni a sort() algoritmussal. Ez a
hagyományos C++98 fordítóval a következőképp néz ki:
#include
#include
#include
bool compare(int i,int j)
{
return (i
}
using namespace std;
int main()
{
vectorint> v {3,1,7,4,8,9,5,6,2,10};
for(int i = 0; i v.size(); i++) cout "
"; cout
sort(v.begin(),
v.end(), compare); //rendezés
for(int i = 0; i v.size(); i++) cout "
"; cout
return 0;
}
Lambda kifejezéssel ugyanez elérhető,
megspórolva a compare() függvény deklarálását:
sort(v.begin(), v.end(), [](int i, int j) -> bool{ return (i j);}); //rendezés
Függvénymutatók
A függvénymutatók is a programozás
egyszerűsítésére szolgálnak. Egy függvény nem más, mint egy memóriacím, ahonnan
az utasítások kezdődnek. A függvény hívásához elegendő ezt a címet ismerni.
A következő függvénymutató olyan
függvényre mutat, melynek paramétere két char, visszatérített típusa pedig int. A mutató
neve func, amit bármilyen
függvényhez lehet használni, melynek ilyen a felépítése.
int (*func)(char,char) = NULL;
A függvénymutatók az osztályok
tagfüggvényeire is mutathatnak.
int (MyClass::*Classfunc)(char,char) = NULL;
Mindkét mutató NULL mutatónak van
inicializálva, de lehet rögtön a függvény címét is adni (pl. &fuggveny). Ha a mutató megkapta a függvény címét,
a függvény rajta keresztül hívható:
int result = func(10,2);
int result =
(obj.*Classfunc)(10,2);
Mivel mutatóról van szó, ez paraméterként
is átadható egy másik függvénynek.
void PassPtr(int (*funcptr)(char, char))
{
int result =
(*funcptr)(20,3); // call using function pointer
cout "PassPtr:
"
}
A függvényt a következőképp lehet
meghívni: PassPtr(func);
A függvénymutató egy függvény
visszatérített értéke is lehet:
int
(*ReturnPtr(char muvelet))(char, char)
{
if(muvelet
== '+')
return
&Plus;
else
return
&Minus;
}
Ebben az esetben a Plus() és Minus() két hasonló függvény. A következő példában
összesítve vannak az eddig felsorolt példák: