C++/Przestrzenie nazw

Z Wikibooks, biblioteki wolnych podręczników.

< C++

Jeśli użyjemy dowolnej wyszukiwarki internetowej to powinniśmy bez problemu znaleźć prosty, szablonowy kod napisany w C++, który wyświetla napis „Hello World!”, w tłumaczeniu na polski „Witaj Świecie!”. Spójrzmy na niego:

#include <iostream>
 
using namespace std;
 
int main()
{
   cout << "Hello World!" << endl;
   return 0;
}
Crystal ktip.png

Zaleca się używanie znaku nowej linii (\n) zamiast funkcji "endl". Chyba że jest to uzasadnione: endl powoduje opróżnienie bufora, ale na przykład przy wielokrotnym zapisie na dysk może to obciążyć jego pracę.

Osoby, które już znają C, na pewno się domyślą, co mniej więcej się dzieje w tym kodzie. Nasuwa się tylko jedno pytanie -- po co jest ta wstawka using namespace std ? O tym za chwilę. Najpierw, pokrótce omówimy, co ten program właściwie robi.

Za pomocą #include<iostream> dołączyliśmy odpowiedni, tak zwany plik nagłówkowy, który umożliwia nam między innymi wypisywanie pewnych informacji na ekran (ściślej na standardowe wyjście).

Dzięki int main( ) {...} mogliśmy otworzyć funkcję główną, która jest zawsze uruchomiana podczas startu naszego programu.

Operacja cout << umożliwia nam wypisywanie pewnych informacji. W naszym przypadku wypisaliśmy napis „Hello World!”, a następnie „przedłużyliśmy” to polecenie za pomocą znaku << i wstawiliśmy nową linię, która jest zdefiniowana przez endl.

Za pomocą return 0 informujemy system, że program działał prawidłowo i nie wystąpiły żadne problemy.

Powróćmy teraz do tematu zadanego na początku, czyli po co wstawiliśmy linię z kodem using namespace std. W tym celu musimy omówić, czym są przestrzenie nazw.

Spis treści

[edytuj] Przestrzenie nazw

Spójrzmy jeszcze raz na nasz pierwszy program, tylko w trochę zmienionym wydaniu:

#include <iostream>
 
int main( )
{
   std::cout << "Hello World!" << std::endl;
   return 0;
}

Łatwo zauważyć, że usunęliśmy linię using namespace std, kosztem tego, że cout i endl musieliśmy poprzedzić nazwą std i operatorem ::. Właśnie std jest nazwą pewnej przestrzeni nazw. W przestrzeni nazw std znajdziemy mnóstwo, a wręcz cały arsenał różnych narzędzi, począwszy od pewnych bardzo przydatnych funkcji np. sortowania, wyszukiwania, a kończywszy na tak zwanych pojemnikach, które pozwalają nam w łatwy sposób przechowywać pewne wartości. Oczywiście, aby mieć dostęp do tych narzędzi musimy dołączyć odpowiedni plik nagłówkowy, używając do tego dyrektywy #include.

Pisząc std::cout << informujemy kompilator, że chcemy wejść do przestrzeni nazw std i skorzystać ze znajdującej się tam operacji cout <<.

Poprzedni przykład pokazał nam, że nie musimy za każdym razem odwoływać się do przestrzeni nazw, kiedy chcemy wywołać pewną, należącą do niej funkcję. Używając using namespace przestrzenNazw, powiadamiamy kompilator, aby wszystko co się znajduje w przestrzeni nazw przestrzenNazw przeniósł do „naszego pokoju”. Dzięki temu nie musimy się martwić, w którym „pokoju” (czyli przestrzeni nazw) się co znajduje, ponieważ wszystko mamy pod ręką.

Oczywiście nie musimy naraz „przenosić” wszystkiego, co jest w danej przestrzeni nazw, możemy wykorzystać także pewne wybrane elementy. Używamy do tego operacji using przestrzenNazw::element. Zobaczmy przykład użycia tej operacji:

#include <iostream>
 
using std::endl;
 
int main( )
{
   std::cout << "Hello World!" << endl;
   return 0;
}

Za pomocą using std::endl poinformowaliśmy kompilator, że używany przez nas znak końca linii (czyli endl), znajduje się w przestrzeni nazw std. Dzięki temu możemy zamiast std::endl spokojnie pisać endl (nie powinno być problemów także wtedy, gdybyśmy napisali std::endl). Nie wykonaliśmy tej analogicznej operacji na elemencie cout (ściślej nie wstawiliśmy instrukcji using std::cout), więc musimy go dalej poprzedzać nazwą tej przestrzeni.

Ale właściwie do czego są używane przestrzenie nazw? Przede wszystkim zapewniają pewien komfort programiście. Pisząc np. jakąś funkcję, nie musimy się martwić jak się ją nazwie, ponieważ raczej nie spowoduje to żadnych kolizji nazw z nazwami innych funkcji, które na przykład znajdują się w innych bibliotekach (no chyba, że dana nazwa jest już zajęta w używanej przestrzeni nazw). Możemy w obrębie różnych przestrzeni nazw tworzyć funkcje o takich samych nazwach i deklaracjach, ale robiące zupełnie inne operacje!

[edytuj] Tworzenie własnej przestrzeni nazw

Przestrzeń nazw tworzymy za pomocą słowa kluczowego namespace, ograniczając zawartość klamrami. Możemy na przykład stworzyć przestrzeń nazw HelloWorld zawierającą funkcję hello( ):

#include <iostream>
 
namespace HelloWorld
{
   void hello( )
   {
      std::cout << "Hello World!" << std::endl;
   }
}
 
int main( )
{
   HelloWorld::hello( );
   return 0;
}

Oczywiście, gdybyśmy wstawili using namespace HelloWorld przed funkcją main (a nawet wewnątrz tej funkcji), nie musielibyśmy odwoływać się bezpośrednio do HelloWorld, wystarczyłoby samo hello( ).

Nie musimy zamieszczać od razu wszystkiego, co chcielibyśmy, aby się znalazło w naszej przestrzeni nazw. Możemy rozbić to na wiele części:

namespace Matematyka
{
   int dodaj( int a, int b )
   {
       return a+b;
   }
 
   int odejmij( int a, int b )
   {
       return a-b;
   }
}
 
namespace Matematyka
{
   int pomnoz( int a, int b )
   {
       return a*b;
   }
 
   int podziel( int a, int b )
   {
       return a/b;
   }
}

Wówczas wewnątrz przestrzeni nazw Matematyka znajdziemy wszystkie stworzone przez nas funkcje.

Tworząc funkcję w pewnej przestrzeni nazw możemy wstawić samą deklarację, a potem w innym miejscu podać pełną definicję tej funkcji. Możemy na co najmniej dwa sposoby podać definicję pewnej funkcji -- wewnątrz definicji przestrzeni nazw lub pisząc typ_zwracany przestrzenNazw::nazwaFunkcji( typ0 arg0, typ1 arg1, ... ) na przykład:

#include <iostream>
 
namespace Matematyka
{
   int dodaj( int a, int b );
   int odejmij( int a, int b );
}
 
using namespace std;
int main( )
{ 
   cout << Matematyka::dodaj( 10, 20 ) << endl;
   return 0;
}
 
namespace Matematyka
{
   int dodaj( int a, int b )
   {
       return a+b;
   }
 
   int odejmij( int a, int b )
   {
       return a-b;
   }
}

Ostatnie 12 linii moglibyśmy zapisać także w ten sposób:

int Matematyka::dodaj( int a, int b )
{
   return a+b;
}
 
int Matematyka::odejmij( int a, int b )
{
   return a-b;
}

Zależy to wyłącznie od nas, który sposób będziemy chcieli wykorzystać.

[edytuj] Przestrzeń nazw std

Wróćmy ponownie do standardowej przestrzeni nazw, jaką jest std. Dzięki plikowi nagłówkowemu iostream możemy operować na standardowym wejściu i wyjściu. Poprzednio dowiedzieliśmy się, jak wypisać pewien napis. Teraz zobaczymy jak wczytywać pewne wartości do zmiennych, używając do tego cin.Zobaczmy na przykład:

#include <iostream>
 
int main( )
{
   std::cout << "Podaj dwie liczby a i b" << std::endl;
   int a, b;
 
   // wypisujemy "a:" na wyjście i czekamy na wpisanie liczby a
   std::cout << "a:";
   std::cin >> a;
 
   // wypisujemy "b:" na wyjście i czekamy na wpisanie liczby b
   std::cout << "b:";
   std::cin >> b;
 
   // wypisujemy sumę tych dwóch liczb
   std::cout << "a+b=" << a+b << std::endl;
   return 0;
}

Dzięki std::cin >> możemy wczytać pewną wartość do dowolnej zmiennej. Zmienna ta nie musi być liczbą, może być także pewien tekst. W C++ bardzo często pewien tekst (łańcuch znaków) przechowuje się w zmiennej o typie string (który także znajduje się w std). Aby można było utworzyć taką zmienną trzeba najpierw dołączyć plik string. Zobaczmy na przykład:

#include <iostream>
#include <string>
 
using namespace std;
 
int main( )
{
   string imie;
   string email;
   string informacja;
   // wczytujemy imię
   cout << "Podaj swoje imie: "; 
   cin >> imie;
 
   // wczytujemy email
   cout << "Podaj swój email: ";
   cin >> email;
 
   informacja=imie+" ("+email+")"; // suma napisów
   cout << "Witaj " << informacja << endl;
 
   informacja+=" czyta ten napis";
   cout << informacja << endl;
   return 0;
}

Zauważmy, jak prosto się korzysta zmienną typu string (dla wtajemniczonych jest to pewna klasa). Jeśli chcemy dodać dwa napisy, wystarczy wykorzystać operator +. Możemy także wykorzystywać operator +=, jeśli chcemy dokleić do tekstu dodatkowy napis.

Podając swoje imię jako Zdzichu, a e-mail jako zdzichu@zdzichowo.mars, zobaczymy wynik:

Podaj swoje imie: Zdzichu
Podaj swój email: zdzichu@zdzichowo.mars
Witaj Zdzichu (zdzichu@zdzichowo.mars)
Zdzichu (zdzichu@zdzichowo.mars) czyta ten napis

Więcej o stringach można przeczytać w dodatku opisującym bibliotekę STL.

[edytuj] Korzystanie z biblioteki standardowej C

Ponieważ język C++ jest (w pewnym uproszczeniu) rozwinięciem C, w dalszym ciągu można korzystać z biblioteki standardowej C (tzw. libc). Ze względu na zachowanie wstecznej kompatybilności umożliwiono korzystanie z niej tak jak wcześniej w C.

#include <string.h>
 
int main(int argc, char **argv)
{
   if( argc < 2 )
      return -1;
   return strcmp(argv[0], argv[1]);
}

Jednak dostępna jest też wersja libc przygotowana specjalnie dla C++. Pliki nagłówkowe są w niej inaczej nazywane, wszystkie funkcje znajdują się dodatkowo w przestrzeni nazw std. Tak więc powyższy program napisany w sposób właściwy dla C++ wyglądałby następująco:

#include <cstring> // zamiast <string.h> wykorzystujemy <cstring>
using namespace std;
 
int main(int argc, char **argv)
{
   if( argc < 2 )
      return -1;
   return strcmp(argv[0], argv[1]);
}

Zauważmy, że:

  1. dołączany plik nagłówkowy ma dodaną na początku literę c
  2. nie jest używane rozszerzenie .h

Reguła ta dotyczy wszystkich plików, z których składa się biblioteka standardowa C.


W swoich programach lepiej jest używać wersji przygotowanej dla C++. Po pierwsze, dzięki przestrzeniom nazw unikniemy kolizji nazw z własnymi funkcjami. Po drugie, wersja ta ma wbudowaną obsługę wyjątków. Po trzecie, czasami libc przygotowana dla C wywołuje ostrzeżenia lub błędy kompilacji w kompilatorach C++.