2.8 – Navngi kollisjoner og en introduksjon til navnerom

La oss si at du kjører til en venns hus for første gang, og adressen du får er 245 Front Street I Mill City. Når Du kommer Til Mill City, trekker du opp kartet ditt, bare for å oppdage At Mill City faktisk har to Forskjellige Frontgater på tvers av byen fra hverandre! Hvilken ville du gå til? Med mindre det var noen ekstra ledetråd for å hjelpe deg med å bestemme (f. eks. du husker huset hans er nær elven) du må ringe din venn og be om mer informasjon. Fordi dette ville være forvirrende og ineffektivt( spesielt for postmannen din), i de fleste land, er alle gatenavn og husadresser i en by pålagt å være unike.

På Samme Måte krever C++ at alle identifikatorer ikke er tvetydige. Hvis to identiske identifikatorer blir introdusert i det samme programmet på en måte som kompilatoren eller linker ikke kan skille dem fra hverandre, vil kompilatoren eller linker produsere en feil. Denne feilen er vanligvis referert til som en navnekollisjon (eller navnekonflikt).

et eksempel på en navngivningskollisjon

a.cpp:

1
2
3
4
5
6

#inkluder <iostream>
ugyldig myFcn(int x)
{
std:: cout < < x;
}

main.cpp:

1
2
3
4
5
6
7
8
9
10
11

#inkluder <iostream>
ugyldig myFcn(int x)
{
std:: cout < < 2 * x;
}
int main()
{
tilbake 0;
}

når kompilatoren kompilerer dette programmet, vil det kompilere a.cpp og main.cpp uavhengig, og hver fil vil kompilere uten problemer.

men når linkeren utfører, vil den koble alle definisjonene i a.cpp og main.cpp sammen, og oppdage motstridende definisjoner for funksjon myFcn. Linkeren vil da avbryte med en feil. Merk at denne feilen oppstår selv om myFcn aldri kalles!

de fleste navnekollisjoner forekommer i to tilfeller:
1) To (eller flere) definisjoner for en funksjon (eller global variabel) blir introdusert i separate filer som er kompilert i samme program. Dette vil resultere i en linkerfeil, som vist ovenfor.
2) To (eller flere) definisjoner for en funksjon (eller global variabel) blir introdusert i samme fil (ofte via en #include). Dette vil resultere i en kompilatorfeil.

ettersom programmer blir større og bruker flere identifikatorer, øker oddsene for en navngivningskollisjon betydelig. Den gode nyheten Er At C++ gir mange mekanismer for å unngå å navngi kollisjoner. Lokalt omfang, som holder lokale variabler definert inne funksjoner fra konflikt med hverandre, er en slik mekanisme. Men lokalt omfang fungerer ikke for funksjonsnavn. Så hvordan holder vi funksjonsnavn i konflikt med hverandre?

Hva er et navneområde?

Tilbake til vår adresseanalogi for et øyeblikk, å ha to Frontgater var bare problematisk fordi disse gatene eksisterte i samme by. På den annen side, hvis du måtte levere post til to adresser, en på 209 Front Street I Mill City, og en annen adresse på 417 Front Street I Jonesville, ville det ikke være forvirring om hvor du skal dra. Sagt på en annen måte, byer gir grupperinger som tillater oss å disambiguere adresser som ellers kan komme i konflikt med hverandre. Navnerom fungerer som byene gjør i denne analogien.

et navnerom er en region som lar deg deklarere navn inne i det med det formål å disambiguere. Navneområdet gir et omfang (kalt navneområdeomfang) til navnene som er erklært inne i det-noe som ganske enkelt betyr at et navn som er erklært inne i navneområdet, ikke vil forveksles med identiske navn i andre områder.

Nøkkelinnsikt

et navn deklarert i et navneområde vil ikke forveksles med et identisk navn deklarert i et annet område.

i et navneområde må alle navn være unike, ellers vil det oppstå en navnekollisjon.

Navnerom brukes ofte til å gruppere relaterte identifikatorer i et stort prosjekt for å sikre at De ikke utilsiktet kolliderer med andre identifikatorer. Hvis du for eksempel plasserer alle matematikkfunksjonene i et navneområde som heter math, vil ikke matematikkfunksjonene kollidere med funksjoner med identisk navn utenfor matematikknavneområdet.

vi snakker om hvordan du lager dine egne navnerom i en fremtidig leksjon.

det globale navneområdet

i c++ anses ethvert navn som ikke er definert i en klasse, funksjon eller et navneområde, å være en del av et implisitt definert navneområde kalt det globale navneområdet (noen ganger også kalt det globale omfanget).

i eksemplet øverst i leksjonen defineres hovedfunksjoner() og begge versjoner av myFcn() i det globale navneområdet. Navngivningskollisjonen som oppstår i eksemplet, skjer fordi begge versjoner av myFcn () ender opp i det globale navneområdet, som bryter med regelen om at alle navn i navneområdet må være unike.

std-navneområdet

da C++ ble opprinnelig utviklet, var alle identifikatorene I C++ standardbiblioteket (inkludert std::cin og std::cout) tilgjengelige for bruk uten std:: prefiks (de var en del av det globale navneområdet). Dette betydde imidlertid at alle identifikatorer i standardbiblioteket potensielt kunne komme i konflikt med alle navn du valgte for dine egne identifikatorer (også definert i det globale navneområdet). Kode som fungerte, kan plutselig ha en navnekonflikt når du # inkluderte en ny fil fra standardbiblioteket. Eller verre, programmer som ville kompilere under En versjon Av C++, kan ikke kompilere under en fremtidig versjon Av C++, da nye identifikatorer introdusert i standardbiblioteket kunne ha en navnekonflikt med allerede skrevet kode. Så c++ flyttet all funksjonalitet i standardbiblioteket til et navneområde som heter » std » (kort for standard).

Det viser seg at std:: cout navn er egentlig ikke std:: cout. Det er faktisk bare cout, og std er navnet på navneområdet som identifier cout er en del av. Fordi cout er definert i std-navneområdet, vil navnet cout ikke komme i konflikt med objekter eller funksjoner kalt cout som vi oppretter i det globale navneområdet.

På samme måte, når du åpner en identifikator som er definert i et navneområde (f.eks. std::cout) , må du fortelle kompilatoren at vi leter etter en identifikator definert i navneområdet (std).

nøkkelinnsikt

når du bruker en identifikator som er definert i et navneområde (for eksempel std-navneområdet), må du fortelle kompilatoren at identifikatoren lever i navneområdet.

det er noen forskjellige måter å gjøre dette på.

Explicit namespace qualifier std::

den enkleste måten å fortelle kompilatoren at vi vil bruke cout fra std-navneområdet er ved å eksplisitt bruke std:: prefiks. For eksempel:

1
2
3
4
5
6
7

#inkluder <iostream>
int hoved()
{
std:: cout < < » Hei, verden!»; // når vi sier cout, mener vi cout definert i std navneområdet
retur 0;
}

::- symbolet er en operator kalt scope resolution operator. Identifikatoren til venstre for:: – symbolet identifiserer navneområdet som navnet til høyre for:: – symbolet finnes i. Hvis ingen identifikator til venstre for:: – symbolet er angitt, antas det globale navneområdet.

Så når vi sier std:: cout, sier vi «cout som bor i namespace std».

Dette er den sikreste måten å bruke cout på, fordi det ikke er tvetydighet om hvilken cout vi refererer til (den i std-navneområdet).

Beste praksis

Bruk eksplisitte navneområdeprefikser for å få tilgang til identifikatorer som er definert i et navneområde.

bruke navneområde std (og hvorfor unngå det)

En annen måte å få tilgang til identifikatorer i et navneområde, er å bruke en brukerdirektiverklæring. Her er vårt opprinnelige «Hello world» – program med et bruksdirektiv:

1
2
3
4
5
6
7
8
9

#include <iostream>
bruke navneområde std; // dette er et bruksdirektiv som forteller kompilatoren å sjekke std-navneområdet når det løses identifikatorer uten prefiks
int main()
{
cout < < » Hei, verden!»; // cout har ingen prefiks, så kompilatoren vil sjekke om cout er definert lokalt eller i navnerom std
retur 0;
}

et brukerdirektiv ber kompilatoren om å kontrollere et angitt navneområde når man prøver å løse en identifikator som ikke har prefiks for navneområde. Så i eksemplet ovenfor, når kompilatoren går for å bestemme hvilken identifikator cout er, vil den sjekke både lokalt (hvor den er udefinert) og i std-navneområdet (hvor den vil matche til std::cout).

Mange tekster, opplæringsprogrammer og til og med noen kompilatorer anbefaler eller bruker et bruksdirektiv øverst i programmet. Men brukt på denne måten, er dette en dårlig praksis, og svært motløs.

Vurder følgende program:

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

#inkluder <iostream > / / importerer erklæringen av std:: cout
ved hjelp av navnerom std; / / gjør std:: cout tilgjengelig som «cout»
int cout () / / erklærer vår egen «cout» – funksjon
{
retur 5;
}
int main()
{
cout < < » Hei, verden !»; / / Kompilere feil! Hvilken cout vil vi ha her? Den i std-navneområdet eller den vi definerte ovenfor?
retur 0;
}

ovennevnte program kompilerer ikke, fordi kompilatoren nå ikke kan fortelle om vi vil ha cout-funksjonen som vi definerte, eller cout som er definert i std-navneområdet.

når du bruker et bruksdirektiv på denne måten, kan enhver identifikator vi definerer, komme i konflikt med en identifikator med identisk navn i std-navneområdet. Enda verre, mens et identifikatornavn ikke kan komme i konflikt i dag, kan det komme i konflikt med nye identifikatorer lagt til std-navneområdet i fremtidige språkrevisjoner. Dette var hele poenget med å flytte alle identifikatorene i standardbiblioteket til std-navneområdet i utgangspunktet!

Advarsel

Unngå å bruke direktiver (for eksempel navneområde std;) øverst i programmet. De bryter med grunnen til at navnerom ble lagt til i utgangspunktet.

Vi skal snakke mer om bruk av uttalelser (og hvordan du bruker dem på en ansvarlig måte) i leksjon 6.12-Bruk av uttalelser.

2.9 — Introduksjon til preprosessoren

Indeks

2.7 — Programmer med flere kodefiler

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert.