C++11 Zrozumieć Wyrażenia Regularne - Adam Majczak

-
Proszę czekać

Published: Dec, 2014

Language: Polish

Autor dołożył wszelkich starań, by informacje zawarte w niniejszej książce pozostawały poprawne, precyzyjne, aktualne i prawidłowe, Autor nie ponosi jednakże żadnego rodzaju odpowiedzialności, ani bezpośredniej, ani pośredniej, za jakiekolwiek szkody wynikłe w rezultacie wykorzystania informacji zawartych w niniejszej książce, bowiem ani nie może przewidzieć, ani nie może mieć wpływu na sposoby ich wykorzystania.

Niniejsza książka przeznaczona jest do osobistego użytku i wykorzystania przez Czytelnika i nie może być ponownie sprzedawana, ani oferowana w jakiejkolwiek innej formie osobom trzecim. Każdy Użytkownik proszony jest o uzyskanie własnej, legalnej kopii.

C. Żadna część niniejszej książki nie może być kopiowana, powielana, ani rozpowszechniana wtórnie bez pisemnej zgody Autora, gdyż stanowi to naruszenie Praw Autorskich.

Użyte w książce wyrażenia regularne nie są zastrzeżone i mogą być stosowane w wyszukiwarkach i aplikacjach bez ograniczeń. W razie wykorzystania cytatów, należy wskazać jednoznacznie źródło.

Adam Majczak

C++11:

Zrozumieć Wyrażenia Regularne

Edycja Polska

Adam Majczak

C++11: Zrozumieć wyrażenia regularne

SPIS TREŚCI:

Przedmowa do pierwszego wydania

SPIS LISTINGÓW:

Część I - Wyjaśnienie specyfiki składni i zapisu C++11

ROZDZIAŁ 1: Wprowadzenie

Kompilatory, konsolidatory, stadia tworzenia kodu wykonywalnego

Dostępne kompilatory C++ i kompilacja online

IDE dla C++11 online

Podsumowanie rozdziału

ROZDZIAŁ 2: Konstrukcja kodu źródłowego w C++11

Koncepcja przestrzeni nazw namespace

Funkcja main()

Komentarze w C++11 - bez zmian

Zmienne numeryczne w C++11 i słowo kluczowe auto

Standardowe typy zmiennych numerycznych, zakresy wartości, aliasy

Stałe, czyli konstanty

Typ porządkowy enum, inne kategorie stałych

ROZDZIAŁ 3: Tablice numeryczne i łańcuchy znaków

Obsługa łańcuchów znaków w formacie ASCIIZ

Tablice dynamiczne

Wskaźniki

Skalarny wymiar wskaźnika

Wskaźniki do struktur i do pól struktury

Makroinstrukcja assert()

Jak postępować z dynamicznymi tablicami?

ROZDZIAŁ 4: Rozszerzenia C++11, pętla for i funkcja for_each()

Identyfikacja typu w C++11: typeid()

Pętla for w C++11

Interesująca funkcja for_each()

Binarna postać danych w C++11

Zastosowanie kontenera std::array

Część II: Wyrażenia regularne w C++11

ROZDZIAŁ 5: Zrozumieć wyrażenia regularne

Bardzo krótka historia wyrażeń regularnych (GREP)

Nowe podejście C++11 do obsługi wyrażeń regularnych

Rozbudowa wzorca z uwzględnieniem znaków opcjonalnych

Budowanie wyrażeń regularnych krok po kroku

Wyrażenia regularne dla danych numerycznych w różnych formatach

Liczby szesnastkowe

Znaki specjalne w wyrażeniach regularnych - metacharacters

ROZDZIAŁ 6: Funkcje regex_search() i regex_match()

Funkcja regex_match()

Specyfika notacji C++

Wykorzystanie powtarzających się znaków

Grupy i odwołania do grup

Klasy znaków są wygodne w stosowaniu

ROZDZIAŁ 7: Więcej o maszynerii z biblioteki <regex>

Różne formaty wzorców

Jak zignorować wielkość liter - flaga icase

Pozycja w łańcuchu znaków

ROZDZIAŁ 8: Iteratory

Przydatne metody prefix() oraz suffix()

ROZDZIAŁ 9: Zastępowanie tekstów, funkcja regex_replace()

Kolejność automatycznej numeracji grup

ROZDZIAŁ 10: Podział tekstu na tokeny i obsługa wyjątków

Klasyczna funkcja strtok() z biblioteki <string.h>

Jak, zamiast funkcji, zastosować iterator?

Jak regex_token_iterator wykorzystuje grupy?

Dopasowania częściowe (sub-match)

Jak regex_token_iterator może zastąpić funkcjonalność regex_replace()?

Obsługa wyjątków

ROZDZIAŁ 11: Analizujemy kontekst

Technika "looking forward", czyli, co jest po?

Nie szafujmy nawiasami i grupami (zaskakujące efekty)

Wykrywanie słów i badanie kontekstu

Rozmiar ma (czasem) znaczenie

ROZDZIAŁ 12: Przykłady zastosowań wyrażeń regularnych

Walidacja adresów email

Walidacja danych na wejściu

Obsługa dat, znaczników XML, nieco szczegółów technicznych

Zastosowanie iteratora i funkcji for_each()

Selekcja negatywna (odrzucamy elementy tekstu)

Nakładamy dodatkowe warunki na hasło użytkownika

Zapamiętujemy lub pomijamy grupy znaków w dopasowaniach

Zakończenie

Przedmowa do pierwszego wydania

Wprowadzenie standardów C++11 i C++14 spowodowało, że obsługa wyrażeń regularnych weszła w skład standardowej biblioteki C++ (C++11 Standard Library). Dostępność biblioteki STL (Standard Template Library) oraz własnej, specjalnej biblioteki do obsługi wyrażeń regularnych <regex> spowodowała, że przetwarzanie wyrażeń regularnych w C++11 i C++14 stało się wygodniejsze i prostsze. Skoro jednakże zmienił się zestaw dostępnych narzędzi (rozszerzona również zestaw słów kluczowych języka), zmienił się także styl programowania.

Niniejsza książka zawiera ponad 140 listingów przykładowych programów i składa się z 2 części:

Część I - Wyjaśnienie specyfiki składni i zapisu C++11

Część II - Wyrażenia regularne w C++11

Część pierwsza ma charakter wprowadzenia. Takie wprowadzenie wydaje się celowe, by ułatwić Czytelnikowi szybkie i dokładne zrozumienie przykładów zawartych w Części 2. Prawie pod wszystkimi przykładami dodano robocze wydruki, samodzielne uruchomienie przykładów (wszystkie można skompilować i uruchomić online) pozwoli jednakże Czytelnikom na wykonanie samodzielnych prób i eksperymentów.

Zalecany kompilator C++11 do uruchamiania przykładowych programów obsługujących wyrażenia regularne (Część II).

Poniżej umieszczono listę listingów z przykładowymi kodami C++11 ilustrujących kolejne omówione zagadnienia. Numer listingu zawiera numer Rozdziału oraz numer kolejny, co powinno ułatwić korzystanie z przykładów. Wszystkie listingi zawierają pełne, działające, gotowe do uruchomienia kody C++.

SPIS LISTINGÓW:

Listing 1.1a. "Hello World" niby w C, ale działa w C++11.

Listing 1.1b. Strumień cout zamiast funkcji printf().

Listing 1.2. Odczytujemy parametry środowiska na zdalnym serwerze.

Listing 2.3. Namespace po raz pierwszy (kod bez std::).

Listing 2.4. Każdy programista może mieć własną przestrzeń namespace.

Listing 2.5. Klauzuli using namespace wystarczy użyć raz.

Listing 2.6. Przykład komunikatu o błędzie Visual C++.

Listing 2.7. Operator sizeof() pozwala sprawdzić ile miejsca zajmuje zmienna w pamięci.

Listing 2.8. Słowo kluczowe auto (automatyczna dedukcja typu).

Listing 2.9. Domyślny typ double, rezultat dodawania.

Listing 2.10. Sprawdzamy wielkość podstawowych typów numerycznych.

Listing 2.11. Liczby ósemkowe i szesnastkowe, wymuszona konwersja typu.

Listing 2.12. Różne zastosowania typu char.

Listing 2.13. Liczby typu signed char i unsigned char.

Listing 2.14. Typ long long oraz long double w 32-bitowym środowisku operacyjnym.

Listing 2.15. Logiczna interpretacja wartości stało- i zmiennoprzecinkowych.

Listing 2.16. Kilka sposobów definiowania stałych numerycznych.

Listing 2.17. Jak zmienić dokładność (precyzję na) wydruku.

Listing 2.18. Zastosowanie metody cout.precision().

Listing 2.19. Jak uzyskać maksymalną dokładność dla typu double.

Listing 2.20. Zastosowanie numeric_limits.

Listing 2.21. Wyrażenie stałowartościowe constexpr.

Listing 2.22. Zastosowanie typu porządkowego enum.

Listing 2.23. Predefiniowane stałe matematyczne z grupy M_.

Listing 3.24. Jednowymiarowa statyczna tablica znakowa.

Listing 3.25. Uwzględnienie znaku końca łańcucha (format ASCIIZ).

Listing 3.26. Obsługa łańcuchów znaków w formacie ASCIIZ.

Listing 3.27. Kopiowanie i dodawanie (konkatenacja) łańcuchów znaków jak w języku C.

Listing 3.27b. Kopiowanie i dodawanie łańcuchów znaków typu string (C++11).

Listing 3.28. Zliczanie znaków w łańcuchu przy pomocy funkcji C.

Listing 3.29. C++11: zliczanie znaków w łańcuchu typu string przy pomocy metody length().

Listing 3.30. Zmienna typu string automatycznie dostosuje swoją wielkość.

Listing 3.31. Wektor i metoda push_back().

Listing 3.33. Wskaźnik (raw pointer) jako adres zmiennej.

Listing 3.34. Komunikat o błędzie, nie da się pobrać adresu R-wartości.

Listing 3.35. Nieprawidłowa deklaracja wskaźnika (typowy, częsty błąd).

Listing 3.36. Prawidłowa deklaracja wskaźnika.

Listing 3.37. Przestawienie wskaźnika tak, by wskazał inna zmienna.

Listing 3.38. Przestawienie wskaźnika o 1 bajt

Listing 3.39. Przestawienie wskaźnika o 4 bajty.

Listing 3.40. Obsługa wskaźnika - funkcja printf() i strumień cout.

Listing 3.41. Wskaźnik do struktury i do pól struktury.

Listing 3.42. Rozmiar wskaźnika.

Listing 3.43. Numeryczna tablica statyczna zgodna z konwencją C.

Listing 3.44. Odwołanie do elementów tablicy przy pomocy wskaźnika.

Listing 3.45. Błędne odwołanie do nieistniejących elementów tablicy.

Listing 3.46. Błędne odwołanie do tablicy poprzez wskaźnik.

Listing 3.47. Odwołanie do elementów dynamicznej tablicy o zmiennej wielkości.

Listing 3.48.Tworzymy tablicę o zadanej wielkości w ruchu programu (operator new).

Listing 4.49. Wyznaczamy rozmiar tablicy dla pętli for w ruchu programu.

Listing 4.50. Identyfikacja typu w ruchu programu, C++11: typeid().

Listing 4.51. Identyfikacja typu danych użytkownika w ruchu programu.

Listing 4.52. Inicjujemy tablicę w nagłówku sterującym pętli for.

Listing 4.53. C++11 liczy automatycznie powtórzenia pętli for.

Listing 4.54. Automatyczna adaptacja inteligentnej pętli orogramowej.

Listing 4.55.Funkcja for_each().

Listing 4.56. Zamiana liczby na postać binarną przy pomocy funkcji.

Listing 4.57. Zamiana liczby na postać binarną przy pomocy klasy.

Listing 4.58. Konwersja na postać dwójkową z wykorzystaniem STL (<bitset>).

Listing 4.59. Tablice statyczne typu array<int, n> oraz array<string, k>.

Listing 4.60. Dwubajtowe i czterobajtowe kodowanie znaków w C++11.

Listing 5.61. Pierwsze wyrażenie regularne szuka w tekście łańcucha znaków "kot".

Listing 5.62. Dopasowanie do wzorca i funkcja regex_search().

Listing 5.63. Klasyczna składnia języka C (PCRE, kompilator GNU GCC).

Listing 5.64. Wyrażenie regularne: regex r("(kot)(ek)?");

Listing 5.65. Wyrażenie regularne: regex r("....(ko)(t)?(cisko)?(ek)?.");

Listing 5.66. Demonstracja działania wyrażenia regex r("[b-z]+\\d{3}");

Listing 5.66. Jak realizować funkcję sumy logicznej (OR) w wyrażeniach regularnych.

Listing 5.67. Określamy długość łańcucha znakowego w dopasowaniu.

Listing 5.68. Wyrażenie regex r("[a-z]+\\s[a-z]\\s\\W\\s\\d+\\W");

Listing 5.69. Wykluczamy znaki niepożądane.

Listing 5.70. Wyrażenie "((...4)|(.9..)|(..8.)).+)".

Listing 5.70b. Porównanie działania znaków [?] oraz [*].

Listing 5.71. Wyrażenie my_regex = "^-?\d+(\\.\\d+)?((e|E)-?\\d+)?$";

Listing 5.71. Kod PHP wychwytujący liczby szesnastkowe:

$my_regex = '/(0x)?#?[A-Fa-f0-9]+/';

Listing 5.72. Dzia.lanie wyrażenia my_regex = "^(0x)?#?([A-Fa-f0-9]\\s?)+$";

Listing 5.73. Działanie znaków [^] oraz [$].

Listing 5.73b. Działanie znaków [\\b].

Listing 5.74. Działanie wyrażeń regex r1("l+o\\b"), r2("\\s.+o.+[^!]");

Listing 6.75. Działanie wyrażenia reg1("[[:digit:]]+");

Listing 6.76. Porównanie dzialania funkcji regex_search() oraz regex_mach().

Listing 6.77. Działanie wyrażenia "(\\+|-)?[[:digit:]]+(\\.)?[[:digit:]]*".

Listing 6.78. Działanie wyrażenia regex r("\\++");.

Listing 6.79. Działanie wyrażenia wzorzec = "\\\\";,

Listing 6.80. Znaki Unicode, wyrażenie: wzorzec = "\\u0050";.

Listing 6.81. Działanie wyrażenia: "\\b(k|s)[a-zA-Z]*t\\b".

Listing 6.81b. Zastosowanie wyrażenia: wzorzec = "(a|b|c)\\1";.

Listing 6.82. Podwójne wsteczne odwołanie do grupy (backreference):

wzorzec = "([a-c])X\\1Y\\1";.

Listing 6.83. Zastosowanie wyrażenia: r("(A\\d{2})|(\\b\\d{3})");.

Listing 6.84. Zastosowanie tablicy wzorców:

{"[abc]{2}","(a|b|c){2}","(a|b|c)\\1"};.

Listing 6.85. Wykrywanie powtarzajacych się słów: wzorzec = "([a-zA-Z]+)\\s\\1";

Listing 6.86. Działanie wyrażenia: wzorzec = "\\b(\\w)?(\\w)\\w?\\2\\1";.

Listing 6.87. Wyrażenie: r("^((0x)|#)?([[:xdigit:]]\\s?)+$");

Listing 7.88. Działanie wyrażenia: "\\b(\\w)?(\\w)\\w?\\2\\1";

Listing 7.89. Działanie wyrażenia: "([A-Za-z]+) \\1" == "(\\w+)\\s\\1";.

Listing 7.90. Działanie wyrażenia: wzorzec = "(\\w+)\\s?\\1";.

Listing 7.91. Wystarczy, jeśli funkcja regex_search() znajdzie pierwsze dopasowanie.

Listing 7.92. Określamy pozycję w przeszulkiwanym łańcuchu znaków. Obiekt smatch.

Listing 7.93. Określamy pozycję w przeszulkiwanym łańcuchu znaków ASCIIZ.

Listing 7.94. Działanie wyrażenia: wzorzec = "\\b(\\w)?(\\w)(\\w)?\\2\\1";.

Listing 8.95. Działanie iteratora (dwa iteratory).

Listing 8.95b. Działanie iteratora (pojedynczy iterator).

Listing 8.96. Działanie iteratora i wyrażenia: "\\b(\\w)?(\\w)\\w?\\2\\1";.

Listing 8.97. Działanie iteratora i wyrażenia: R ("\\b([a|A]l)([^ ]*)");

Listing 8.98. Zastosowanie metody suffix().

Listing 9.99. Zastępowanie - funkcja regex_replace().

Listing 9.100. Działanie wyrażenia: string replace = "$1";.

Listing 9.101. Działanie wyrażeń:

wzorzec = "(\\w+)\\s[i]\\s(\\w+)"; replace = "$2 i $1";.

Listing 9.102. Zastępowanie wybranych znaków w słowach, wyrażenie: "a|e|i|o|u|y".

Listing 9.103. Wyszukiwanie i zastępowanie przy pomocy wzorców: [B+], [$'], [$'].

Listing 10.104. Podziuał na tokeny, funkcja strtok().

Listing 10.105. Zastosowanie regex_token_iterator z separatorem "[-\\s,\\.]+";.

Listing 10.106. Podział na słowa przy pomocy iteratora.

Listing 10.107. Zastosowanie wzorca: wzorzec = "\\b(pod)([^\\s]*)";

Listing 10.108. Wykorzystujemy podział na grupy: \\1: (pod) oraz \\2: ([^\\s]*)

Listing 10.109. Dopasowania częściowe.

Listing 10.110. Wychwytywanie separatorów (split).

Listing 10.111. Podział na tokeny przy pomocy funkcji regerx_search().

Listing 10.112. Zastosowanie wzorca: "(\\w+)?(\\.)?(\\w+)".

Listing 10.113. Typowa obsługa wyjątków w C++11.

Listing 10.114. Schemat obsługi wyjątków dla wyrażeń regularnych C++11.

Listing 10.115. Obsługa wyjątków w działaniu.

Listing 10.116. Reakcja Visual C++ na błąd formalny w wyrażeniu regularnym.

Listing 11.117. Działanie wyrażenia: "\\d+[,\\.]?(\\d)*((?=\\sdol)|(?=\\seur))".

Listing 11.118. Działanie wyrażenia: R ("(\\w+)[,]\\s");.

Listing 11.119. Działanie wyrażenia: R ("(\\w+)[,]\\s\\1");.

Listing 11.120. Działanie wyrażenia: R ("(\\b\\w+)(?=\\s(\\w+\\s)+\\1)");.

Listing 11.121. Zastosowanie metody swap().

Listing 11.122. Zastosowanie metody compare().

Listing 11.123. Zastosowanie wyrażenia w = "^(?!.*vat).*";.

Listing 11.124. Działanie wyrażenia:

w = "^([^V]|V[^A]|VA[^T])*$"; (odpowiednik: "^(?:[^V]|f(?!AT))*$").

Listing 11.125. Zastosowanie metody size() do zliczania znaków.

Listing 11.126. Zastosowanie metody size() do automatyzacji pętli programowej.

Listing 11.127. Metoda empty() oraz wyrażenia: R( "([abc]*)[1234]" );.

Listing 12.128. Walidacja adresów email:

R( "(\\w+)(.*)(\\w*)@(\\w+)(\\.(\\w+))+", icase );

alternatywnie:

r = "\\b([A-Z0-9\\._%+-]+)(.*)(\\w*)@(\\w+)(\\.(\\w+))+$";

Listing 12.129. Jednoczesne wykorzystanie obiektów typu cmatch oraz smatch.

Wyrażenie: R ("(sub)(.*)");

Listing 12.130. Walidacja na wejściu: R("(\\+|-)?\\d+");

Listing 12.131. Wyrażenia: r1("\\w{10,15}"), r2("\\w*\\d+\\w*\\d+\\w*");.

Listing 12.132. Wyrażenia:

r1("\\w{10,15}"), r2("\\w*\\d+\\w*\\d+\\w*"), r3("\\w*[A-Z]\\w*");

Listing 12.133. Wyrażenia: r1("\\w{10,15}"), r2("\\d(?=\\d)"), r3("[A-Z]");.

Listing 12.134. Wyrażenie: r1("([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})");

Listing 12.135. Wyrażenie: "<(.*)>(.*)</(\\1)>".

Listing 12.136. Wyrażenie: "<(.+)>(.*)</(\\1)>" (XML).

Listing 12.137. Wyrażenia: "(\\w+)\\s[i]\\s(\\w+)"; oraz "$2 oraz $1; ";.

Listing 12.137. Wyrażenie:"<(.+)>(.*)</(\\1)>" (HTML).

Listing 12.138. Wyrażenie: "<.+>[\n]?<(.+)>(.*)</(\\1)>" (HTML).

Listing 12.139. Usuwanie niepożądanych znaków: "\\b[[:alpha:]]+\\d\\,?" .

Listing 12.140. Wyrażenie:

"((?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#_$%]).{6,20})";.

Listing 12.141. Przy formatowaniu metodą format() stosujemy takie same wzorce, jak przy zamianie regex_replace().

Listing 12.142. Zliczanie słów i znaków. Wyrażenia: <.+>, (\\S+), (\\w{7,}).

Listing 12.143. Dopasowania grup smatch M[n] (sub-match).

Listing 12.144. Działanie wyrażenia R("(T.*)\\s(?:T.*)\\s(H.*)\\s(?:W.*)");.

Część I - Wyjaśnienie specyfiki składni i zapisu C++11

ROZDZIAŁ 1: Wprowadzenie

Lapidarne stwierdzenie, że nowoczesna informatyka zaczęła się w 1969 roku, gdy wymyślono język C, jest pewnym uproszczeniem. Było jednakże na tej drodze kilka znaczących, milowych kroków. W 1978 roku Brian Kernighan i Dennis Ritchie opublikowali "Biblię Programistów" (The C programming language, wydaną po polsku jako "Język ANSI C"). Panowie K&R opisywali w swojej książce tzw. klasyczny styl programowania w języku C, nazywany od ich inicjałów "The K&R style". Instytut ANSI wziął się za C nieco później i pojawiły się kolejne standardy języka:

1989 - ANSI X3.159-1989 - nazywany w skrócie ANSI C (lub C89)

1990 - ISO/IEC 9899:1990 - nazywany w skrócie C90

1999 - ISO/IEC 9899:1999 - nazywany w skrócie C99

2011 - ISO/IEC 9899:2011 - nazywany w skrócie C11

Podobnie język C++, który pojawił się jako rozszerzenie C w 1979 roku, podlegał rozwojowi, rozszerzeniom i kolejnym standardom.

1998 - ISO/IEC 14882:1998 - nazywany w skrócie C++98

2003 - ISO/IEC 14882:2003 - nazywany w skrócie C++03

2011 - ISO/IEC 14882:2011 - nazywany w skrócie C++11

sierpień 2014 - ISO/IEC 9899:2014 - nazywany w skrócie C++14

Wbrew początkowym ambitnym założeniom (a tych najistotniejszych było 3):

1. Kolejne wersje C++ będą w pełni kompatybilne z C i z poprzednimi wersjami C++,

2. C (a później C++) stanie się "Lingua Franca", czyli językiem uniwersalnym, w pełni przenośnym pomiędzy różnymi komputerami i różnymi środowiskami operacyjnymi,

3. Kody źródłowe C/C++ będą poprawnie kompilowane przez wszystkie kompilatory spełniające wymagania standardów C/C++,

ciągle nie jest tak do końca. Niniejsza książka nie koncentruje się na specyfice żadnego konkretnego kompilatora C++, dotyczy natomiast konstrukcji samego języka oraz obsługi wyrażeń regularnych w wersji C++11. Książka rozpoczyna się od wyjaśnienia (Część I) najistotniejszych (z punktu widzenia obsługi wyrażeń regularnych) zmian wprowadzonych do C++ wraz ze standardem C++11, by ułatwić Czytelnikom zrozumienie notacji i dalszych przykładów. Kolejne rozdziały (Część II) poświęcone są już meritum zagadnienia, czyli różnym aspektom konstruowania i obsługi wyrażeń regularnych w C++11 / C++14.

Do kompilacji przykładowych programów przy przygotowaniu książki wykorzystano dostępne online kompilatory

Visual C++:

(http://webcompiler.cloudapp.net/)

oraz GNU GCC v. 4.8.3:

(http://www.compileonline.com/compile_cpp11_online.php).

Kompilator GCC wykorzystano w pierwszej części książki, natomiast wszystkie przykłady dotyczące wyrażeń regularnych w Części II były kompilowane przez VC++ v. 19.00.22318(x86), Nov. 18, 2014.

Kompilatory, konsolidatory, stadia tworzenia kodu wykonywalnego

Programiści, przy pomocy dowolnych "czystych" edytorów tekstowych (w Windows to np. Notatnik / Notepad, w systemach Linuksowych, powiedzmy Vi, Emacs, itp.), mogą tworzyć pliki tekstowe zawierające kody źródłowe w językach C i C++. Kody źródłowe można tworzyć również w zintegrowanych środowiskach programistycznych (IDE = Integrated Development Environment). W C/C++ można tworzyć projekty (ang. poroject-s) składające się w wielu plików źródłowych. W uproszczeniu, w typowej sytuacji, kolejność przetwarzania plików źródłowych jest następująca:

1. Preprocesor przetwarza plik źródłowy (source code) na plik tekstowy gotowy do kompilacji.

2. Kompilator przetwarza plik tekstowy na plik w języku maszynowym (object file).

2.a Przy zastosowaniu opcji Compile via Assembly może zostać wygenerowany plik ASM (asemblera). Wtedy to odrębny asembler tworzy plik OBJ.

3. Konsolidator (linker) dołącza biblioteki (LIB-raries) i generuje plik wykonywalny (w Windows EXE).

To bardzo uproszczony schemat, ponieważ dla różnych środowisk operacyjnych mogą dochodzić kompilatory zasobów i linkowanie zasobów (ang. resource compiler, resource linking), nie dotyczy to jednak samego języka C++11, więc tu jedynie wspominam, że istnieje taki obszar zagadnień i spotkamy się z nimi, gdy zechcemy dostosować nasze aplikacje do określonego środowiska operacyjnego.

Dostępne kompilatory C++ i kompilacja online

Wiele kompilatorów i wiele środowisk programistycznych jest dostępne bezpłatnie. Podaję tu kilka przydatnych adresów w sieci www, decyzja i wybór należy jednakże do Czytelnika, a zależy od platformy systemowej (własnego i docelowego środowiska operacyjnego), preferencji uczelni, czy firmy.

http://www.visualstudio.com/pl-pl/products/visual-studio-express-vs

http://gcc.gnu.org/

http://www.embarcadero.com.pl/produkty/cbuilder/

https://apps.ubuntu.com/cat/applications/raring/g++/

http://www.codeblocks.org

Przystępując do lektury niniejszego podręcznika nie zapominajmy, że język nie jest celem samym w sobie. Jest jedynie narzędziem. "Być, albo nie być" wcale nie brzmi mniej frapująco, niż "To be or not to be". Bowiem nie to jest najważniejsze, jakim językiem się posługujemy. Najważniejsze jest to, co mamy do powiedzenia (z Mądrości Mojego Dziadka). Czemu zatem służy rozbudowa i unowocześnianie języka C i C++? Chodzi o to, by różne algorytmy numeryczne zapisywać szybciej, wygodniej i tak, by generowany przez kompilator kod maszynowy był coraz bardziej efektywny.

Równie wiele jest kompilatorów C++ dostępnych online. Wymienię kilka. Polecam wykonywanie ćwiczeń ze wspomnianym powyżej Visual C++, Czytelnik zechce jednakże samodzielnie wybrać najwygodniejszy w stosowaniu.

http://www.compileonline.com/compile_cpp11_online.php

https://ideone.com/

http://codepad.org/

https://isocpp.org/blog/2013/01/online-c-compilers

http://webcompiler.cloudapp.net/

Posługiwanie się kompilatorem online ma istotne zalety:

1. Nie musimy "obciążać" własnego komputera, ani zmieniać jego konfiguracji.

2. Z kompilatorów C++ online możemy korzystać z dowolnego komputera, nawet wtedy, gdy nie mamy uprawnień administratora (do instalowania własnego oprogramowania).

Najsłynniejszy chyba program świata, napisany pierwotnie w C, będzie poprawnie kompilowany i poprawnie wykonany w C++11.

Listing 1.1a. "Hello World" niby w C, ale działa w C++11.

#include <stdio.h> int main() { printf("Hello World! \n"); }

// wydruk: Hello World!

By było jasne, czym różni się C++11, pokażę najpierw - od czego. Zmodyfikuję nieco powyższy kod, zamiast funkcji bibliotecznej C printf(), posłużę się obiektem cout.

Listing 1.1b. Strumień cout zamiast funkcji printf().

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

// wydruk: Hello World!

Zwróćmy uwagę na zmianę w pisowni nazw plików nagłówkowych. W nowej notacji C++ znikają rozszerzenia *.h oraz *.hpp, natomiast w nazwach plików nagłówkowych C pojawia się dodatkowa pierwsza litera "c". Zmiany nazw są następujące:

<iostream.h> ? <iostream>

<stdio.h> ? <cstdio>

<stdlib.h> ? <cstdlib>

...itp.

Rys. 1.1. Kompilacja i uruchamianie kodów online.

UWAGA TECHNICZNA:

W trakcie pracy nad tekstem tej książki portal www.compileonline.com został przebudowany. Poniżej, na Rysunku 1.1b nowsza wersja (grudzień 2014).

Rys. 1.1b. Nowsza wersja kompilatora C++11 online.

Funkcja biblioteczna C getenv() ma jedną istotną wadę. Jeśli chcemy, by funkcja podała nam stan zmiennej środowiskowej (ang. environment variable settings), musimy wiedzieć, o jaką zmienną nam chodzi i podać jej dokładną nazwę. Jeśli nie jesteśmy pewni, przy pomocy prostego kodu C możemy odczytać wszystkie zmienne środowiskowe.

Listing 1.2. Odczytujemy parametry środowiska na zdalnym serwerze.

#include <stdio.h>

#include <stdlib.h>

main( int argc , char *argv[] , char *env[] )

{

int i = 0;

printf( "Parametry srodowiska operacyjnego: \n" );

do

{

printf("%s \n", env[i]);

i++;

} while (env[i] != NULL);

printf( "Nazwa pliku programu: \n" );

printf( "%s" , argv[0] );

return 0;

}

// wydruk: zależy od zastosowanego kompilatora i od środowiska operacyjnego

Na powyższym rysunku zwróćmy uwagę, że na ekranie widać, która wersja kompilatora i z jakimi parametrami jest stosowana przy kompilacji naszych programów. Możemy uruchamiać program z parametrami (Command Line Arguments) i wczytywać dane w ruchu programu (STDIN Input). W nowszej wersji (Rys. 1.1b, w prawym panelu można wpisywać dane w sposób interaktywny, w ruchu programu).

IDE dla C++11 online

Postawmy sobie przykładowe, proste zadanie. Znana anegdotka głosi, że obliczenie sumy liczb naturalnych od 1 do 100 było pierwszym zadaniem, przy którym ujawnił się geniusz matematyczny kilkuletniego wtedy Gaussa. Gauss szybko zauważył, że suma każdej z par liczb:

100 + 1 = 101

99 + 2 = 101

98 + 3 = 101

...

51 + 50 = 101

Wystarczyło teraz policzyć ile jest takich par. Po chwili zastanowienia łatwo obliczyć, że po podzieleniu 100 liczb na pary mamy dokładnie 50 par. Jeśli par jest 50, a każda z nich daje w sumie 101, to suma wszystkich liczb wynosi 50 * 101 = 5050. Znamy już zatem końcowy wynik. Teraz przez chwilę nie myślmy tak szybko, jak geniusz Gauss, mamy przecież komputery, które mogą liczyć "mechanicznie" i całkowicie bezmyślnie, czyli tak:

suma = 1 + 2 + 3 + ... + 98 + 99 + 100;

Nie chce nam się tyle pisać, prawda? Więc może nie aż tak bezmyślnie. W klasycznym C możemy to zapisać np. tak:

Rys. 1.2. Pętla programowa, C++11.

Na powyższym rysunku widać, jak pojawiła się opcja "std=C++11", mimo to, archaiczny kod jest poprawnie kompilowany i wykonywany. Może zatem z tą kompatybilnością wsteczną nie jest aż tak źle? Przecież to taki styl programowania, który już w latach 70-tych ubiegłego wieku (już na etapie "K&R Style") był uważany za nieelegancki. Prowokacja intelektualna jest tu zamierzona. Skoki bezwarunkowe (goto) i warunkowe (if-goto) to technika, która od zarania dziejów C i C++ uważana była za już archaiczną i zdecydowanie nieelegancką, mimo to C++11 nadal ją akceptuje. Co prawda w C skok goto może następować w dowolne miejsce kodu, w C++ już nie. Jest pewne ograniczenie. C++ nie pozwoli skoczyć do wnętrza bloku instrukcji (blok instrukcji ujmuje się w nawiasy klamrowe {}). Dla przykładu, taki skok w C++11 jest niedozwolony:

// UWAGA: w starszych wersjach OK, ale w C++11 to błąd!

goto Etykieta;

{

// blok instrukcji

Etykieta:

// reszta instrukcji bloku

}

Jeśli etykieta jest wewnątrz (lub na zewnątrz) bloku instrukcji, taki skok jest w C++11 niedozwolony.

Zapiszmy to teraz zgodnie z zasadami programowania strukturalnego. Zamiast instrukcji skoku goto zastosujmy pętlę programową while (wykonuj-dopóki). Dodatkowo zmienimy plik nagłówkowy i zastąpimy funkcję biblioteczną C obiektem C++. Skrócimy również zapis inkrementacji.

zapis i = i + 1; zastąpimy przez i++;

Po tych kilku zmianach nasz kod C++11 wygląda i działa tak, jak pokazano na rysunku poniżej.

Rys. 1.3. Ta sama pętla programowa, ale bardziej elegancko.

Podsumowanie rozdziału

Standard C++11 został przyjęty przez ISO i uznany przez producentów kompilatorów języka C++. Kompilatory C++11 są dostępne dla popularnych środowisk operacyjnych (Windows, Linux, itd.), w tym niektóre wersje (np. Visual C++ Express Edition) są dostępne bezpłatnie i/lub online. C++11 jest w znacznym stopniu wstecznie kompatybilny z wcześniejszymi wersjami C i C++, jednakże kompilatory różnych producentów i dla różnych środowisk operacyjnych zachowują pewną specyfikę. Obsługa wyrażeń regularnych długo pozostawała nieobjęta standardem i w różnych wersjach C/C++ z różnymi bibliotekami była obsługiwana różnie. Standard C++11 ujednolicił obsługę wyrażeń regularnych w C++, a biblioteka <regex> stała się częścią standardowej biblioteki C++ (C++11 Standard Library).

Niniejsza książka koncentruje się na wykorzystaniu do nauki dostępnych online kompilatorów

C++11 - gcc (Linux) i vc++ (Windows). Obydwa te kompilatory są również dostępne bezpłatnie i mogą być, zgodnie z wyborem i decyzją użytkownika, zainstalowane na własnym komputerze.

ROZDZIAŁ 2: Konstrukcja kodu źródłowego w C++11

Wróćmy od najsłynniejszego programu świata napisanego zgodnie z konwencją C++11 i przyjrzyjmy się uważniej na poszczególne elementy konstrukcji kodu źródłowego.

Listing 2.3. Namespace po raz pierwszy (kod bez std::).

#include <iostream> // wiersz 1 using namespace std; // wiersz 2int main() // wiersz 4 { cout << "Hello World" << endl; // wiersz 6 return 0; }

Program rozpoczyna się w wierszu 1 dyrektywą preprocesora. Dyrektywa #include dołącza plik nagłówkowy (ang. header file) o nazwie <iostream>. Pierwsze litery "io" to skrót od "Input / Output", czyli "obsługa Wejścia / Wyjścia", stream to strumień (danych). Wprowadzenie nowego standardu stało się okazją do ujednolicenia rozszerzeń w nazwach plików nagłówkowych. Wcześniej (od słowa "header" - nagłówek) pliki nagłówkowe miały zwykle rozszerzenie .h w przypadku C i *.h, *.hxx lub *.hpp w przypadku C++. Zgodnie ze standardem C++11 pliki nagłówkowe nie mają rozszerzeń. Pliki nagłówkowe C zamiast rozszerzenia mają dodaną na początku nazwy literę "c", zatem, zgodnie z tą konwencją,

#include <cstring> zastępuje stare oznaczenie #include <string.h>

natomiast

#include <cstring> // to prototypy funkcji C obsługujących łańcuchy znaków

#include <string> // to klasa string C++

Ze względu na zachowanie wstecznej kompatybilności C++11 rozpoznaje i obsługuje stary format, ale zalecane jest stosowanie nazw według nowego standardu. Usunięcie rozszerzeń z dyrektyw #include niekoniecznie oznacza, że pliki dyskowe (z punktu widzenia systemu operacyjnego, zazwyczaj w podfolderze ..\INCLUDE) nie mogą mieć rozszerzeń. Tu standard C++11 pozostawia swobodę wyboru na poziomie konkretnej implementacji. Standard dopuszcza także, jak w poprzednich wersjach, stosowanie cudzysłowów (zamiast pary nawiasów <...>) i względnych (relatywnych) ścieżek dostępu do plików nagłówkowych.

Koncepcja przestrzeni nazw namespace

Operator dostępności / widoczności nazw nazywa się "scope operator" i oznaczany jest podwójnym dwukropkiem [::]. C i C++ od zarania zawierają operator widoczności / dostępności nazw (identyfikatorów). W wielkim uproszczeniu wyobraźmy sobie, że mamy duży zespół programistów, którzy budują wspólny kod C++. Jak to zrobić, by wykluczyć konflikty nazw? Co zrobić by zmienna, powiedzmy "x" programisty A nie kolidowała ze zmienną "x" programisty B? Jednym z możliwych rozwiązań jest przydzielenie każdemu programiście jego własnej, odrębnej przestrzeni nazw przy pomocy słowa namespace. Jeśli teraz obydwaj programiści A i B zechcą nazwać jakieś swoje zmienne tak samo, kompilator C++ rozróżni je poprawnie dzięki przynależności do różnych przestrzeni nazw. Oto prosty przykład.

Listing 2.4. Każdy programista może mieć własną przestrzeń namespace.

#include <iostream>

using namespace std;

// przestrzen programisty A:

namespace A

{

int x = 1;

}

// przestrzeń programisty B:

namespace B

{

int x = 3;

}

int main ()

{

cout << "Programista A ma x = " << A::x << endl;

cout << "Programista B ma x = " << B::x << endl;

}

// wydruk: pokazano na rysunku poniżej

Rys. 2.1. Każdy programista może mieć odrębną przestrzeń namespace.

Jak widać w przykładzie, po słowie namespace następuje nazwa przestrzeni nazw (tu są dwie: A i B) i para nawiasów klamrowych. Odwołanie się do zmiennej o tej samej nazwie "x" z konkretnej przestrzeni namespace określa operator widoczności:

A::x // zmienna "x" z przestrzeni A, lub

B::x // z przestrzeni B

To z tego właśnie powodu w powyższych (i dalszych) przykładach występują alternatywnie dwie składnie. Pierwsza zawiera włączenie całej standardowej przestrzeni nazw std i potem odwoływanie się bez operatora [::], np.:

using namespace std; int main() {cout << "Hello World" << endl; //...

Druga składnia, bez dołączenia przestrzeni std posługuje się operatorem [::]:

int main() {std::cout << "Hello World" << std::endl; //...

Zwróćmy uwagę, że klauzula using namespace ...; może występować także wewnątrz funkcji main(). Od momentu zastosowania tej deklaracji można zrezygnować z operatora [::] w odwołaniach do symboli zdefiniowanych w obrębie danej przestrzeni nazw. Dla przykładu, zastosowanie deklaracji using namespace A; pozwala nam wszędzie poniżej pisać "x" zamiast "A::x".

Listing 2.5. Klauzuli using namespace wystarczy użyć raz.

namespace A

{

int x = 1;

}

namespace B

{

int x = 3;

}

int main ()

{

using namespace A;

cout << "Programista A ma x = " << x << endl;

cout << "Programista B ma x = " << B::x << endl;

}

Funkcja main()

Jak wspomniałem wcześniej, kody źródłowe C++11 mogą być rozmieszczone w wielu plikach (tworząc projekty, wykorzystując dołączanie plików nagłówkowych, itp.). Mogą być podzielone na wiele przestrzeni nazw, wiele klas i wiele funkcji. Każdy program musi jednakże zawierać dokładnie jedną funkcję główną main(). W C++11 można pominąć wartości domyślne (pokazane w następnym przykładzie). Kod:

main()

{

cout << "W C++11 można pominąć int oraz return";

}

zostanie domyślnie zinterpretowany jako:

int main(void)

{

cout << "W C++11 mozna pominac int oraz return";

return 0;

}

Funkcja main() stanowi (jak i w poprzednich wersjach) "punkt wejścia do programu" (ang. program entry point), czyli to miejsce, od którego rozpoczyna się wykonanie programu (listy instrukcji ujętej w parę nawiasów klamrowych { ... }).

UWAGA TECHNICZNA:

Stosowany w Części II poświęconej wyrażeniom regularnym VC++ nie pozwala na pominięcie słowa int i na jego brak reaguje komunikatem o błędzie:

Listing 2.6. Przykład komunikatu o błędzie Visual C++.

// reakcja Visual C++ na brak int przed main()

# include <iostream> main() { std::cout << "Hello World!" << std::endl; }// wydruk: error C4430: missing type specifier - int assumed. Note: C++ does not support default-int

Rys. 2.2. Stosowany w tej książce Visual C++ nie pozwala na pominięcie słowa int.

Komentarze w C++11 - bez zmian

Nic się nie zmieniło w komentarzach. Pojedynczy wiersz komentarza poprzedzany jest podwójnym odwrotnym ukośnikiem (backslash):

// pojedynczy wiersz komentarza kończy się wraz z końcem wiersza

Zgodnie z konwencją C, można w kodach źródłowych umieszczać komentarze składające się z wielu wierszy tekstu.

/*

taki komentarz może składać się

z wielu (dowolnej liczby wierszy)

*/

Komentarze pojawiły się już nieco powyżej, w poprzednich przykładach:

// przestrzen programisty A: ? to jest komentarz

namespace A

{

int x = 1; // ? to jest deklaracja i zainicjowanie zmiennej x

}

// przestrzeń programisty B: ? to też jest komentarz

namespace B

Pod tym względem standard C++11 nie wprowadza nic nowego.

Zmienne numeryczne w C++11 i słowo kluczowe auto

Zmienne (ang. variables, vars) służą w językach programowania do zapamiętywania danych. Jeśli przypomnimy sobie z dzieciństwa zagadki typu:

[..] + 3 = 5

albo ze szkoły zadania

x + 3 = 5

to możemy w uproszczeniu uznać, że te puste miejsca [..] do wpisania danych i te symbole x (nazywane wtedy "niewiadomymi"), były to nasze pierwsze zmienne. Jak widać z przykładu, zmienna - to pewna ilość miejsca na pewne dane (tu na liczbę całkowitą 2). Zmienna może mieć również swoją nazwę (identyfikator, tu: x). W C++ można to zapisać tak:

int x = 5 - 3;

To jednocześnie deklaracja zmiennej x oraz przypisanie [=] zmiennej jej początkowej wartości obliczonej przy pomocy wyrażenia (ang. expression) 5 - 3. Te znaki [=] i [-] też mają swoją nazwę. Z punktu widzenia języka C++ to operatory (ang. operators), czyli polecenia wykonania pewnych operacji. Słowo int (skrót od "integer" - całkowita) jest słowem kluczowym języka (ang. keyword) i oznacza, że w danej zmiennej x można przechowywać liczby całkowite.

Potrzebna ilość miejsca (i na papierze, i w pamięci komputera) na różne dane może być różna.

[.......] + 12.3456 = 13.00001

y + 12.3456 = 13.00001

Jak to zapisać w C++? By komputer wiedział, że w pamięci trzeba wykorzystać więcej miejsca i zapamiętać także to, co jest po przecinku, możemy to zapisać np. tak:

float y = 13.0001 - 12.3456;

Słowo kluczowe float (skrót od "floating point number" - liczba zmiennoprzecinkowa, rzeczywista, a bardziej dosłownie, "liczba z pływającym przecinkiem") spowoduje, że miejsca w pamięci będzie więcej. Do tej pory w C i w C++ typ float był domyślnym typem zmiennoprzecinkowym. Zapis:

cout << 2.0 + 3.1;

powodował automatyczną konwersję typu na:

cout << (float) ((float) 2.0 + (float) 3.1);

Jeśli chcemy, by dokładność obliczeń zmiennoprzecinkowych była jeszcze większa, możemy użyć słowa kluczowego double (podwójna).

double y = 13.0001 - 12.3456;

Tak było do tej pory. Chociaż standardowe funkcje matematyczne operowały domyślnie na liczbach podwójnej precyzji (double), typ float pozostawał w C i w C++ domyślnym typem zmiennoprzecinkowym. W C++11 jest inaczej. Wpisanie liczby zmiennoprzecinkowej powoduje jej automatyczną konwersję na typ double:

(double) 2.0 + (double) 3.1

Możemy to sprawdzić przy pomocy operatora sizeof(). Dowiemy się w ten sposób ile bajtów w pamięci zajmuje x, a ile y.

Listing 2.7. Operator sizeof() pozwala sprawdzić ile miejsca zajmuje zmienna w pamięci.

#include <iostream> using namespace std; int main() { int x = 5 - 3; double y = 13.0001 - 12.3456; cout << "Zmienna x zajmuje " << sizeof(x) << " bajty." << endl; cout << "Zmienna y zajmuje " << sizeof(y) << " bajtow." << endl; cout << "X = " << x << endl; cout << "Y = " << y; }

// wydruk:

Zmienna x zajmuje 4 bajty.

Zmienna y zajmuje 8 bajtow.

X = 2

Y = 0.6545

Użyte tu słowa kluczowe int, double określają typ danych lub inaczej typ zmiennej. Obydwa typy dotyczą zmiennych numerycznych (danych liczbowych). Uogólniając, deklaracja zmiennej w C++ (konieczna przed pierwszym użyciem zmiennej) może mieć postać:

typ nazwa_zmiennej; // np. int x;

typ nazwa = wartość; // np. int x = 5;

typ nazwa = wyrażenie; // np. float y = 2.0 + 5.123;

W pierwszym przypadku miejsce w pamięci zostanie zarezerwowane, ale wartość takiej zmiennej może być przypadkowa (kompilator C++ automatycznie zeruje zmienne statyczne i globalne, ale zmienne lokalne mają wartość przypadkową, aż do pierwszego przypisania). W roli przecinka rozdzielającego część całkowitą i część ułamkową liczby stosujemy kropkę [.]. Omyłkowe postawienie przecinka spowoduje komunikat o będzie (error) i wstrzymanie kompilacji. W razie wystąpienia błędów, program wykonywalny nie powstanie i, oczywiście, nie wykona się.

W C++11 można utworzyć zmienną w jeszcze jeden, szczególny sposób. Przy pomocy słowa kluczowego auto (zamiast deklaracji typu, np. int) tworzymy w poniższym przykładzie zmienne z1 i z2. Kompilator C++11 sam "domyśla się" jakiego typu powinny być te zmienne i, jak widać, wyniki dodawania są obliczane i drukowane poprawnie.

Listing 2.8. Słowo kluczowe auto (automatyczna dedukcja typu).

#include <iostream>

using namespace std; int main() { int x = 2, y = 3; auto z1 = x + y; cout << z1 << endl; float a = 1.111, b = 2.2222; auto z2 = a + b; cout << z2;}

// wydruk: 5 3.3332

Słowo kluczowe auto możemy stosować tam, gdzie można liczyć na "domyślność" kompilatora C++11. Ta "domyślność" bywa nazywana automatyczna dedukcją typu (ang. deduction). Oto jeszcze jeden przykład. "Na oko" niełatwo się zorientować, czy mamy do czynienia z typem danych float, czy double. Można to sprawdzić przy pomocy operatora sizeof().

Listing 2.9. Domyślny typ double, rezultat dodawania.

#include <iostream> using namespace std; auto x = 2; auto y = 1.234; int main() { cout << x + y << " Rozmiar: " << sizeof(x+y); }// wydruk: 3.234 Rozmiar: 8

Zmienna x ma oczywiście typ int, ale dodawanie musi odbywać się zmiennoprzecinkowo, więc C++ wykona sam konwersję zmiennej x na typ double.

Visit: http://www.smashwords.com/books/view/502815 to purchase this book to continue reading. Show the author you appreciate their work!