2.8 – ütközések elnevezése és névterek bevezetése

tegyük fel, hogy először vezet egy barátja házához, és a megadott cím 245 Front Street Mill Cityben. Amikor elérte Mill City, húzza fel a térképet, csak felfedezni, hogy Mill City valójában két különböző Első utcák az egész városban egymástól! Melyikbe mennél? Hacsak nincs valamilyen további nyom, amely segít a döntésben (pl. emlékszel, hogy a háza a folyó közelében van) fel kell hívnia a barátját, és további információkat kell kérnie. Mivel ez zavaró és nem hatékony (különösen a postás számára), a legtöbb országban a városon belüli összes utcanévnek és házcímnek egyedinek kell lennie.

Hasonlóképpen, a C++ megköveteli, hogy minden azonosító ne legyen kétértelmű. Ha két azonos azonosítót vezetnek be ugyanabba a programba oly módon, hogy a fordító vagy a linker nem tudja megkülönböztetni őket, a fordító vagy a linker hibát fog okozni. Ezt a hibát általában elnevezési ütközésnek (vagy elnevezési ütközésnek) nevezik.

példa elnevezési ütközésre

a.cpp:

1
2
3
4
5
6

#include < iostream>
void myFcn (int x)
{
std:: cout < < x;
}

fő.cpp:

1
2
3
4
5
6
7
8
9
10
11

#include < iostream>
void myFcn (int x)
{
std:: cout < < 2 * x;
}
int main()
{
vissza 0;
}

amikor a fordító lefordítja ezt a programot, lefordítja a.cpp és fő.cpp függetlenül, és minden fájl lesz lefordítani gond nélkül.

azonban, amikor a linker végrehajtja, összekapcsolja az összes definíciót a.cpp és fő.cpp együtt, és fedezze ütköző definíciók funkció myFcn. A linker ezután hibával megszakítja. Vegye figyelembe, hogy ez a hiba akkor is előfordul, ha a myFcn-t soha nem hívják meg!

a legtöbb névütközés két esetben fordul elő:
1) egy függvény (vagy globális változó) két (vagy több) definícióját külön fájlokba vezetik be, amelyeket ugyanabba a programba fordítanak. Ez linker hibát eredményez, amint az fent látható.
2) egy függvény (vagy globális változó) két (vagy több) definícióját vezetik be ugyanabba a fájlba (gyakran egy #include segítségével). Ez fordító hibát eredményez.

ahogy a programok egyre nagyobbak és egyre több azonosítót használnak, az elnevezési ütközések bevezetésének esélye jelentősen megnő. A jó hír az, hogy a C++ rengeteg mechanizmust biztosít az ütközések elnevezésének elkerülésére. A helyi hatókör, amely megakadályozza, hogy a függvényeken belül definiált helyi változók ütközjenek egymással, az egyik ilyen mechanizmus. De a helyi hatókör nem működik a függvények neveinél. Tehát hogyan tartsuk meg a függvénynevek ütközését egymással?

mi a névtér?

vissza a cím analógiájához egy pillanatra, két elülső utca birtoklása csak azért volt problematikus, mert ezek az utcák ugyanabban a városban léteztek. Másrészt, ha két címre kellene kézbesítenie a leveleket, az egyik a Mill City Front Street 209.szám alatt, a másik pedig a Jonesville Front Street 417. szám alatt, akkor nem lenne zavar, hogy hová menjen. Másképp fogalmazva, a városok olyan csoportosulásokat biztosítanak, amelyek lehetővé teszik számunkra, hogy tisztázzuk azokat a címeket, amelyek egyébként ütközhetnek egymással. A névterek úgy viselkednek, mint a városok ebben az analógiában.

a névtér egy olyan régió, amely lehetővé teszi a nevek deklarálását benne a pontosítás céljából. A névtér hatókört (névtér hatókört) biztosít a benne deklarált nevekhez – ami egyszerűen azt jelenti, hogy a névtérben deklarált nevek nem téveszthetők össze más hatókörökben azonos nevekkel.

Key insight

a névtérben deklarált név nem tévesztendő össze egy másik hatókörben deklarált azonos névvel.

a névtérben minden névnek egyedinek kell lennie, különben névütközés következik be.

a névtereket gyakran használják a kapcsolódó azonosítók csoportosítására egy nagy projektben annak biztosítása érdekében, hogy véletlenül ne ütközzenek más azonosítókkal. Ha például az összes matematikai függvényt egy math nevű névtérbe helyezi, akkor a matematikai függvények nem ütköznek a matematikai névtéren kívüli azonos nevű függvényekkel.

arról fogunk beszélni, hogyan hozhat létre saját névtereket egy jövőbeli leckében.

a globális névtér

a C++ – ban minden olyan név, amely nincs definiálva egy osztályban, függvényben vagy névtérben, a globális névtérnek (néha globális hatókörnek is nevezik) nevezett implicit módon definiált névtér részének tekintendő.

a lecke tetején látható példában a main() függvény és a myFcn() mindkét verziója a globális névtérben van definiálva. A példában tapasztalt névütközés azért történik, mert a myFcn() mindkét verziója a globális névtérben végződik, ami sérti azt a szabályt, hogy a névtérben minden névnek egyedinek kell lennie.

az std névtér

amikor a C++ – t eredetileg tervezték, a C++ standard könyvtár összes azonosítója (beleértve az std::cin-t és az std::cout-ot) az std:: előtag nélkül is használható volt (a globális névtér részét képezték). Ez azonban azt jelentette, hogy a standard könyvtár bármely azonosítója ütközhet a saját azonosítóihoz kiválasztott (a globális névtérben is meghatározott) névvel. A működő kód hirtelen elnevezési ütközést okozhat ,ha # új fájlt tartalmaz a standard könyvtárból. Vagy ami még rosszabb, azok a programok, amelyek a C++ egyik verziója alatt fordítanának, nem biztos, hogy a C++ jövőbeli verziója alatt fordulnak elő, mivel a standard könyvtárba bevezetett új azonosítók elnevezési ütközést okozhatnak a már megírt kóddal. Tehát a C++ a standard könyvtár összes funkcióját áthelyezte egy “std” nevű névtérbe (a standard rövidítése).

kiderült, hogy az std::cout neve valójában nem std::cout. Valójában csak cout, az std pedig annak a névtérnek a neve, amelynek a cout azonosító része. Mivel a cout az std névtérben van definiálva, a cout név nem ütközik a globális névtérben létrehozott cout nevű objektumokkal vagy függvényekkel.

hasonlóképpen , amikor egy névtérben definiált azonosítót (például std::cout) érünk el, el kell mondanunk a fordítónak, hogy a névtérben (std) definiált azonosítót keresünk.

Key insight

ha egy névtérben (például az std névtérben) definiált azonosítót használ, el kell mondania a fordítónak, hogy az azonosító a névtérben található.

van néhány különböző módon, hogy ezt.

Explicit namespace qualifier std::

a legegyszerűbb módja annak, hogy elmondjuk a fordítónak, hogy a cout-ot az std névtérből akarjuk használni, az STD:: előtag explicit használata. Például:

1
2
3
4
5
6
7

#tartalmazza < iostream>
Int main()
{
std:: cout < < ” Helló világ!”; // amikor azt mondjuk, hogy cout, akkor az std névtérben definiált cout-ot értjük
visszatérés 0;
}

a:: szimbólum a scope resolution operator nevű operátor. A :: szimbólumtól balra található azonosító azonosítja azt a névteret, amelyben a :: szimbólumtól jobbra található név található. Ha a:: szimbólumtól balra nem található azonosító, akkor a globális névtér kerül feltételezésre.

tehát amikor azt mondjuk std::cout, azt mondjuk, hogy “az std névtérben élő cout”.

ez a legbiztonságosabb módja a cout használatának, mert nincs kétértelműség abban, hogy melyik cout-ra hivatkozunk (az std névtérben).

legjobb gyakorlat

explicit névtér előtagok használata a névtérben definiált azonosítók eléréséhez.

névtér használata std (és miért kerüljük el)

a névtérben lévő azonosítók elérésének másik módja a using directive utasítás használata. Itt van az eredeti “Hello world” programunk egy Használati irányelvvel:

1
2
3
4
5
6
7
8
9

#include < iostream>
STD névtér használata; / / ez egy olyan használati utasítás, amely azt mondja a fordítónak, hogy ellenőrizze az std névteret, amikor az azonosítókat előtag nélkül oldja meg
int main()
{
cout << “Helló világ!”; / a / cout-nak nincs előtagja, ezért a fordító ellenőrzi, hogy a cout lokálisan vagy névtérben van-e definiálva std
return 0;
}

A using direktíva azt mondja a fordítónak, hogy ellenőrizze a megadott névteret, amikor olyan azonosítót próbál megoldani, amelynek nincs névtér előtagja. Tehát a fenti példában, amikor a fordító meghatározza, hogy mi a cout azonosító, akkor mind helyben (ahol nincs meghatározva), mind az std névtérben ellenőrzi (ahol egyezik az std::cout-tal).

sok szöveg, oktatóprogram, sőt néhány fordító is javasolja vagy használja a Program tetején található using direktívát. Azonban ilyen módon használva ez egy rossz gyakorlat, és nagyon elriasztott.

Tekintsük a következő programot:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

#include <iostream> // importálja a nyilatkozatot std::cout
névtér használatával std; // teszi std:: cout elérhető “cout”
int cout () / / kijelenti saját “cout” funkció
{
visszatérés 5;
}
int main()
{
cout << “Helló, világ!”; / / Fordítási hiba! Melyik cout – ot akarjuk itt? Az std névtérben vagy a fent meghatározottban?
visszatérés 0;
}

a fenti program nem fordítja le, mert a fordító most nem tudja megmondani, hogy az általunk definiált cout függvényt akarjuk-e, vagy az std névtérben definiált cout-ot.

ha ilyen módon használ egy using direktívát, bármely általunk definiált azonosító ütközhet az std névtér bármely azonos nevű azonosítójával. Még rosszabb, bár az azonosító neve ma nem ütközhet, ütközhet az std névtérhez hozzáadott új azonosítókkal a jövőbeni nyelvi módosítások során. Ez volt a lényege, hogy a standard könyvtár összes azonosítóját elsősorban az std névtérbe helyezzük!

figyelem

kerülje az irányelvek használatát (például az std; névtér használatát) a program tetején. Megsértik a névterek hozzáadásának okát.

többet fogunk beszélni az állítások használatáról (és arról, hogyan használjuk őket felelősségteljesen) a 6.12 leckében-az állítások használata.

2.9 — Bevezetés Az előfeldolgozóba

Index

2.7 — programok több kódfájllal

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.