• Nem Talált Eredményt

Több modulból felépülő C++ programok

II. fejezet - Moduláris programozás C++ nyelven

3. Névterek és tárolási osztályok

3.3. Több modulból felépülő C++ programok

Ha egy C-könyvtár függvényeinek leírását külön deklarációs állomány tartalmazza, akkor az alábbi megoldás a célravezető:

extern "C" {

#include <rs232.h>

}

Ugyancsak az extern "C" deklarációt kell alkalmaznunk akkor is, ha C++ nyelven megírt és lefordított függvényeket C programból kívánjuk elérni.

Megjegyezzük, hogy a main() fügvény szintén extern "C" kapcsolódású.

3.3. Több modulból felépülő C++ programok

Az előző rész példaprogramjának szerkezete sokkal áttekinthetőbbé válik, ha a MatModul.cpp fájlhoz elkészítjük a modul interfészét leíró MatModul.h fejállományt. Ugyancsak egyszerűbbé válik a konstansok alkalmazása, ha fájl szintű hatókörbe (static) helyezzük őket. Az elmondottak alapján a MatModul.h állomány tartalma:

// MatModul.h

#ifndef _MATMODUL_H_

#define _MATMODUL_H_

#include <cmath>

using namespace std;

// fájl szintű definíciók/deklarációk const double pi = asin(1)*2;

const double e = exp(1);

static double Fok2Radian(double);

// program szintű deklarációk int Random(int n);

double Sin(double fok);

#endif

A zöld színnel megvastagított sorok olyan előfeldolgozó utasításokat tartalmaznak, amelyek garantálják, hogy a MatModul.h állomány tartalma pontosan egyszer épül be az

#include "MatModul.h"

utasítást tartalmazó modulba. (A 4. szakasz - A C++ előfeldolgozó utasításai további információkat tár az Olvasó elé az előfeldolgozó használatáról.)

A fejállomány alkalmazásával a C++ modulok tartalma is megváltozott:

// FoModul.cpp // MatModul.cpp

static bool elsornd = true;

return sin(Fok2Radian(fok));

}

static double Fok2Radian(double fok) { return fok/180*pi;

}

A C++ fordítóprogram minden programmodult önállóan fordít, külön tárgymodulokat előállítva. Ezért szükséges, hogy minden modulban szereplő azonosító ismert legyen a fordító számára. Ezt a deklarációk/prototípusok és a definíciók megfelelő elhelyezésével biztosíthatjuk. A fordítást követően a szerkesztőprogram feladata a tárgymodulok összeépítése egyetlen futtatható programmá, a külső kapcsolódások feloldásával magukból a tárgymodulokból vagy a szabványos könyvtárakból.

Az integrált fejlesztői környezetek projektek létrehozásával automatizálják a fordítás és a szerkesztés menetét.

A programozó egyetlen feladata: a projekthez hozzáadni az elkészített C++ forrásfájlokat. Általában a Build menüparancs kiválasztásával a teljes fordítási folyamat végbe megy.

A GNU C++ fordítót az alábbi parancssorral aktivizálhatjuk a fordítás és a szerkesztés elvégzésére, valamint a Projekt.exe futtatható állomány létrehozására:

g++ -o Projekt FoModul.cpp MatModul.cpp

3.4. Névterek

A C++ fordító a programban használt neveket (azok felhasználási módjától függően) különböző területeken (névtér, névterület – namespace) tárolja. Egy adott névtéren belül tárolt neveknek egyedieknek kell lenniük, azonban a különböző névterekben azonos nevek is szerepelhetnek. Két azonos név, amelyek azonos hatókörben helyezkednek el, de nincsenek azonos névterületen, különböző azonosítókat jelölnek. A C++ fordító az alábbi névterületeket különbözteti meg:

3.4.1. A C++ nyelv alapértelmezett névterei és a hatókör operátor

A C nyelven a fájl szinten definiált változók és függvények (a szabványos könyvtár függvényei is) egyetlen közös névtérben helyezkednek el. A C++ nyelv a szabványos könyvtár elemeit az std névtérbe zárja, míg a többi fájl szinten definiált elemet a globális névtér tartalmazza.

A névterek elemeire való hivatkozáshoz meg kell ismerkednünk a hatókör operátorral (::). A hatókör operátor többször is előfordul könyvünk további fejezeteiben, most csak kétféle alkalmazására mutatunk példát.

A :: operátor segítségével a program tetszőleges blokkjából hivatkozhatunk a fájl- és program szintű hatókörrel rendelkező nevekre, vagyis a globális névtér azonosítóira.

#include <iostream>

using namespace std;

Ugyancsak a hatókör operátort alkalmazzuk, ha az std névtérben definiált neveket közvetlenül kívánjuk elérni:

#include <iostream>

int main() {

std::cout<<"C++ nyelv"<<std::endl;

std::cin.get();

}

3.4.2. Saját névterek kialakítása és használata

A nagyobb C++ programokat általában több programozó fejleszti. A C++ nyelv eddig bemutatott eszközeivel elkerülhetetlen a globális (program szintű) nevek ütközése, keveredése. A saját névvel ellátott névterek bevezetése megoldást kínál az ilyen problémákra.

3.4.2.1. Névterek készítése

A névtereket a forrásfájlokban a függvényeken kívül, illetve a fejállományokban egyaránt létrehozhatjuk.

Amennyiben a fordító több azonos nevű névteret talál, azok tartalmát egyesíti, mintegy kiterjesztve a már korábban definiált névteret. A névtér kijelöléséhez a namespace kulcsszót használjuk:

namespace nevter {

deklarációk és definíciók }

Program szintű névterek tervezése során érdemes különválasztani a fejállományba helyezhető, csak deklarációkat tartalmazó névteret, a definíciók névterétől.

#include <iostream>

void nspelda::Beolvas(int & a, std::string & s) { std::cout << "nev: "; getline(std::cin, s);

std::cout << "ertek: "; std::cin >> a;

}

Amennyiben egy névtérben deklarált függvényt a névtéren kívül hozunk létre, a függvény nevét minősíteni kell a névtér nevével (példánkban nspelda::).

3.4.2.2. Névtér azonosítóinak elérése

Az nspelda névtérben megadott azonosítók minden olyan modulból elérhetők, amelybe beépítjük a névtér deklarációkat tartalmazó változatát. A névtér azonosítóit többféleképpen is elérhetjük.

Közvetlenül a hatókör operátor felhasználásával:

int main() {

nspelda::nev = "Osszeg";

nspelda::ertek = 123;

nspelda::Kiir();

nspelda::Beolvas(nspelda::ertek, nspelda::nev);

nspelda::Kiir();

std::cin.get();

}

A using namespace direktíva alkalmazásával:

Természetesen sokkal kényelmesebb megoldáshoz jutunk, ha az összes nevet elérhetővé tesszük a programunk számára a using namespace direktívával:

using namespace nspelda;

int main() {

nev = "Osszeg";

ertek = 123;

Kiir();

Beolvas(ertek, nev);

Kiir();

std::cin.get();

}

A using deklarációk megadásával

A using deklarációk segítségével a névtér elemei közül csak a számunkra szükségeseket tesszük egyszerűen elérhetővé. Ezt a megoldást kell használnunk akkor is, ha a névtér bizonyos azonosítóval névütközés áll fent.

int Kiir() {

std::cout << "Nevutkozes" << std::endl;

return 1;

}

int main() {

using nspelda::nev;

using nspelda::ertek;

using nspelda::Beolvas;

nev = "Osszeg";

ertek = 123;

nspelda::Kiir();

Beolvas(ertek, nev);

Kiir();

std::cin.get();

}

3.4.2.3. Névterek egymásba ágyazása, névtér álnevek

A C++ lehetőséget ad arra, hogy a névterekben újabb névtereket hozzunk létre, vagyis egymásba ágyazzuk őket (nested namespaces). Ezzel a megoldással a globális neveinket strukturált rendszerbe szervezhetjük. Példaként tekintsük a Projekt névteret, amelyben processzorcsaládonként különböző függvényeket definiálunk!

namespace Projekt {

Az Projekt névtér elemeit többféleképpen is elérhetjük:

using namespace Projekt::Intel;

using Projekt::Motorola::ToWord;

cout << hex; cout << Motorola::ToWord(0xab,0x12)<< endl; // ab12 }

// --- int main() {

cout<<hex;

cout<<Projekt::Intel::ToWord(0xab,0x12)<< endl; // 12ab cout<<Projekt::Motorola::ToWord(0xab,0x12)<<endl; // ab12 }

A bonyolult, sokszorosan egymásba ágyazott névterek elemeinek elérésékor használt névtérnevek hatókör operátorral összekapcsolt láncolata igencsak rontja a forráskód olvashatóságát. Erre a problémára megoldást ad a névtér álnevek (alias) bevezetése:

namespace alnev =kulsoNS::belsoNS … ::legbelsoNS;

Az álnevek segítségével a fenti példa sokkal egyszerűbb formát ölt:

int main() {

namespace ProcI = Projekt::Intel;

namespace ProcM = Projekt::Motorola;

cout << hex;

függvényeket, amelyek a fordítási egységen kívülről nem érhetők el. Azonban a modulon belül minden korlátozás nélkül felhasználhatók névtelen névterek azonosítói.

Egyetlen modulon belül több névtelen névteret is megadhatunk:

#include <iostream>

#include <string>

using namespace std;

namespace {

string jelszo;

}

namespace {

bool JelszoCsere() { bool sikeres = false;

string regi, uj1, uj2;

cout << "Regi jelszo: "; getline(cin, regi);

if (jelszo == regi) {

cout << "Uj jelszo: "; getline(cin, uj1);

cout << "Uj jelszo: "; getline(cin, uj2);

if (uj1==uj2) { jelszo = uj1;

sikeres = true;

} }

return sikeres;

} // JelszoCsere() }

int main() {

jelszo = "qwerty";

if (JelszoCsere())

cout << "Sikeres jelszocsere!" << endl;

else

cout << "Sikertelen jelszocsere!" << endl;

}