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;
}