2016. december 6., kedd

Kifejezések

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 <regex> könyvtár révén. A reguláris kifejezések szabályai 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:
  1. Legyen egy program, ami számokat fogad el bemenetnek. Az ECMAScript a számokat a [:digit:] vagy [:d:] szintaxissal azonosítja: regex r("[[:digit:]]");
  2. 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:]]+");
  3. 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:]]+");
  4. 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:]]+");
  5. 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 <iostream>
#include <regex>
#include <string>

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!" << endl;
            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 <iostream>
#include <string>
#include <regex>

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: " << m[0] << '\n';
    std::regex_search(str, m, re2);
    std::cout << " POSIX grep: " << m[0] << '\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 <iostream>
#include <string>

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<<normal_str<<endl;
    cout<<raw_str<<endl;
    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 << result << endl;

Kicsit hasznosabb a következő:

int result = [](int input) -> int { return 2 * input; }(10);
cout << result << endl;

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 << result << endl;

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 << result << endl;

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 << func(2, 3) << endl;

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 <iostream>
#include <vector>
#include <algorithm>

bool compare(int i,int j)
{
      return (i<j);
}

using namespace std;

int main()
{
      vector<int> v {3,1,7,4,8,9,5,6,2,10};
      for(int i = 0; i < v.size(); i++) cout << v[i] << " "; cout << endl;
      sort(v.begin(), v.end(), compare); //rendezés
      for(int i = 0; i < v.size(); i++) cout << v[i] << " "; cout << endl;

      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: " << result << endl;
}

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:

#include <iostream>
using namespace std;

class MyClass
{
      public:
            int TagOsszeg(char a, char b) //tagfüggvény
            {
                  return a+b;
            }
            int TagKulonbseg(char a, char b) //tagfüggvény
            {
                  return a-b;
            }
};

int Osszeg (char a, char b) //sima függvény
{
      return a+b;
}

int Kulonbseg (char a, char b) //sima függvény
{
      return a-b;
}

void PassPtr(int (*funcptr)(char, char))
{
   int result = (*funcptr)(20,3);
   cout << "PassPtr(20,3): " << result << endl;
}

int (*ReturnPtr(char muvelet))(char, char)
{
   if(muvelet == '+')
      return &Osszeg;
   else
      return &Kulonbseg;
}


int main()
{
      int (*func)(char, char) = NULL; //függvénymutató egy sima függvényre
      int (MyClass::*Classfunc)(char, char) = NULL; //függvénymutató a tagfüggvényre
      int result;
     
      func = &Osszeg;
      result = func(12,3);
      cout << "Osszeg(12,3): " << result << endl;
     
      func = &Kulonbseg;
      result = (*func)(12,3);
      cout << "Kulonbseg(12,3): " << result << endl;
     
      MyClass obj1;
      Classfunc = &MyClass::TagOsszeg;
      result = (obj1.*Classfunc)(10,2);
      cout << "TagOsszeg(10,2): " << result << endl;
     
      MyClass *obj2 = new MyClass;
      Classfunc = &MyClass::TagKulonbseg;
      result = (obj2->*Classfunc)(10,2);
      cout << "TagKulonbseg(10,2): " << result << endl;
     
      PassPtr(func);
     
      result = (*ReturnPtr('+'))(3,7);
      cout << "(*ReturnPtr('+'))(3,7): " << result << endl;
     
     
      return 0;
}

A kimenet:

Osszeg(12,3): 15
Kulonbseg(12,3): 9
TagOsszeg(10,2): 12
TagKulonbseg(10,2): 8
PassPtr(20,3): 17
(*ReturnPtr('+'))(3,7): 10

A függvénymutatók legnagyobb hasznát igazából a tömbben való alkalmazáskor lehet érezni. Ilyenkor ugyanis a függvényeket indexelni lehet.

#include <iostream>
using namespace std;

int Osszeg (char a, char b)
{
      return a+b;
}

int Kulonbseg (char a, char b)
{
      return a-b;
}

int Szorzat (char a, char b)
{
      return a*b;
}

int Hatvany (char a, char b)
{
      int h=1;
      for(int i=1; i<=b; i++)
            h=h*a;
      return h;
}

int main()
{
   int (*tomb[4])(char, char) = {NULL}; //10 darab függvénymutató

   tomb[0]=&Osszeg;
   tomb[1]=&Kulonbseg;
   tomb[2]=&Szorzat;
   tomb[3]=&Hatvany;

   for(int i=0; i<4; i++)
            cout << tomb[i](6,2) << endl;
  
   return 0;
}

A kimenet:

8
4
12
36


Nincsenek megjegyzések:

Megjegyzés küldése