Programowanie w Visual C++ 2010

Spis treści

Napisy i liczby zespolone

Na wstępie...

Znamy już podstawowe typy danych, jakimi są int, double, float oraz char. Pora poznać dwa nowe, bardzo ciekawe typy. W niniejszym samouczku dowiemy się nieco więcej o napisach i liczbach zespolonych.

Każdy podany fragment programu najpierw samodzielnie skompiluj i domyśl się, jaki będzie efekt działania poszczególnych instrukcji. Warto też będzie nieco poeksperymentować na własną rękę, aby wprawić się w używaniu konkretnych konstrukcji. Powodzenia!
Na razie nie znamy pojęcia funkcji. Przyjmijmy, że jest to taki rodzaj czarnej skrzynki, która wykonuje magiczne rzeczy.
Nie znamy też jeszcze pojęcia klasy. Roboczo możemy przyjąć, że każda klasa to nowy typ danych (być może dość złożony), który ma wbudowaną w sobie "samoobsługujące" go operacje. Nic strasznego, naukę o klasach zaczniemy od zapoznania się z nimi od strony użytkowej. Zobaczysz, jakie to proste i wygodne! Własne klasy zaczniesz tworzyć dopiero w II semestrze.

Biblioteka <string>

Wprowadzenie

Jeżeli chcesz się zaprzyjaźniać ze "stringami", w swoich programach załącz bibliotekę:

#include <string>

Czym tak właściwie jest klasa string? Po prostu nowym typem danych służącym do przechowywania napisów (i dokonywania na nich operacji). Biblioteka <string> została wprowadzona w języku C++, aby ułatwić pracę z tekstami. Pora zajrzeć do jej wnętrza.

Programiści bardzo lubią "stringi", choć nie dlatego, że kojarzą się z pewnym typem bielizny. Operuje się na nich znacznie wygodniej niż na własnoręcznie tworzonych łańcuchach znaków (string to wygodny "gotowiec", dzięki któremu nie musimy myśleć o licznych szczegółach technicznych).

Biblioteka <string> nie jest więc tym samym, co biblioteka <cstring>.

Omówienie zawartości <cstring> znajduje się w tym samouczku. Szczegółową wiedzę o własnoręcznie tworzonych łańcuchach znaków przyswoimy dopiero za jakiś czas na jednym z wykładów.

Tworzenie napisów

Zmienne (obiekty) typu string można definiować na przeróżne sposoby. Najlepiej będzie to obejrzeć na przykładzie (skopiuj go do nowoutworzonego projektu, np. o nazwie stringi):

#include <iostream>
#include <string>
using namespace std;

int main()
{
   string s1; //pusty napis, to samo co    string s1="";
   string s2 = "Jakis napis";
   string s3 = s2; // napis s2 zostanie skopiowany do s3

   // inne metody konstrukcji:
   string s4(s2); //napis s2 zostanie skopiowany do s4
   string s5("Stringi sa fajne");
   string s6("Ala ma kota", 6); //pobierz 6 znaków
   string s7(10, '@'); //pojedyncze znaki w pojedynczych cudzysłowach
   string s8(s5, 8, 2); //weź dwa znaki z s5, poczynając od 8. pozycji

   cout << s1 << endl;
   cout << s2 << endl;
   cout << s3 << endl;
   cout << s4 << endl;
   cout << s5 << endl;
   cout << s6 << endl;
   cout << s7 << endl;
   cout << s8 << endl;

   return 0;
}

Możliwości tworzenia (konstrukcji) napisów jest aż 17, lecz powyżej zostały ukazane te najpopularniejsze. Wszystkie powinny być zrozumiałe, komentarza potrzebuje raczej tylko sposób z linijki #16. Jednak wpierw należy wytłumaczyć budowę napisu. Przyjmijmy, że mamy napis s="oto string". Napis jako tablica cz.1

Powyższy napis składa się z 10 znaków, jednakże kolejne pozycje numeruje się od zera (podobnie jest w tablicach, które poznamy już niedługo na wykładzie). Zatem napis s5 z powyższego listingu ma pozycje numerowane w poniższy sposób: Napis jako tablica cz.2

W związku z tym, przy tworzeniu napisu s8 zwraca się uwagę na pozycję, a nie na to, który to z kolei znak. Napis ten będzie brzmiał więc "sa".

Porównywanie napisów

Czasem zachodzi potrzeba ustawienia napisów w porządku alfabetycznym, bądź sprawdzenie, czy napisy są identyczne. Prześledź działanie następującego fragmentu kodu.

   string s1="Abacki";
   string s2="Ababacki";
   string s3="Babacki";
   string s4="Acki";
   string s5="babacki";
   string s6="Babacki";

   if(s3==s5) //czy wielkość liter ma znaczenie?
      cout << "Te same nazwiska!" << endl;
   if(s3==s6)
      cout << "Te same nazwiska!" << endl;

   if(s1<s4)
      cout << s1 << " znajduje sie przed nazwiskiem " << s4 << endl;
   else
      cout << s4 << " znajduje sie przed nazwiskiem " << s1 << endl;

   if(s2<s1)
      cout << s2 << " znajduje sie przed nazwiskiem " << s1 << endl;
   else
      cout << s1 << " znajduje sie przed nazwiskiem " << s2 << endl;

Oczywiście, do porównywania możemy używać także operatorów <=, >, >=, !=.

Długość napisów

Jak sprawdzić, ile znaków ma napis? Można to zrobić na dwa sposoby:

cout << s.length();
//lub...
cout << s.size();

Pomimo że w języku angielskim słowa size (rozmiar) i length (długość) oznaczają co innego, to w tym przypadku obydwie metody zwracają tę samą wartość.

Metoda to specjalna funkcja, która jest "wpisana" w klasę. Metody wykonują różnego rodzaju operacje bezpośrednio na danym, konkretnym obiekcie. Na przykład zapis s.length() oznacza "wykonaj działanie length() na napisie s". Prawda, że proste?

Mamy także wpływ na długość naszego napisu. Możemy go np. skrócić lub wydłużyć. Potrzebna nam jest do tego funkcja resize():

   string s="Ala ma kota";
   cout << s << " ma dlugosc " << s.length() << endl;
   s.resize(3);
   cout << s << " ma dlugosc " << s.length() << endl;
   s.resize(11);
   cout << s << " ma dlugosc " << s.length() << endl;
   //------------------------------------------------
   s.resize(3);
   s.resize(11,'*');
   cout << s << " ma dlugosc " << s.length() << endl;

Powiększanie napisu w sposób taki jak w podświetlonej linii #5 może być jednak problematyczne. Wynikiem działania takiej operacji było wyświetlenie na moim komputerze w linijce #6 ciągu znaków Alaaaaaaaaa, z kolei na innym komputerze ukazało się Ala         (Ala + 8 spacji)

Na Twoim komputerze może się okazać nawet, że wyświetli się sam napis Ala (bez żadnych dodatkowych spacji), a mimo to wskazywana będzie długość 11.

Czy coś jest w środku?

Jeśli chcemy się dowiedzieć, czy napis jest pusty (nie zawiera żadnych znaków), możemy skorzystać z metody empty(). Jej użycie jest pokazane poniżej:

   string s;
   string s2="niepusty";

   if(s.empty()) //jeśli to prawda, że jest pusty
      cout << "Napis jest pusty" << endl;

   if(!s2.empty()) //jeśli to nieprawda, że jest pusty
      cout << "Napis jest " << s2;

Czyszczenie zawartości napisu

Jeżeli z jakiś powodów chcemy wyczyścić napis, to wystarczy, że wywołamy nań metodę clear():

   string s="napis";
   cout << s << " " << s.size() << endl;
   s.clear(); //równoważne z s=""
   cout << s << " " << s.size() << endl;

Operacje na poszczególnych pozycjach napisu

Jak już zostało wspomniane, każdy znak w napisie ma swoją określoną pozycję. Jeśli nam się zamarzy, to możemy sprawdzić, jaki znak znajduje się na danym miejscu. A gdy coś nam się nie spodoba, to możemy podmienić zawartość. :-)

   string s="Ola ma kota";
   //odczytujemy trzy pierwsze znaki
   cout << s[0] << s[1] << s[2] << endl;
   cout << s.at(0) << s.at(1) << s.at(2) << endl;
   //przecież każdy wie, że to Ala ma kota!
   s[0]='A';
   cout << s << endl;
   //A tam, Ula!
   s.at(0)='U';
   cout << s << endl;

Niestety, kiedy działamy na napisie za pomocą operatora [ ], program nie sprawdza, czy liczba w tych nawiasach faktycznie należy do przedziału [0, s.length()-1] (brak tzw. sprawdzania zakresu, ang. range check). W przypadku błędu, pojawi się odpowiedni komunikat.

Z kolei, gdy używamy funkcji at(), aplikacja sprawdza, czy numer pozycji się zgadza. Jeżeli będzie coś nie w porządku, również ukaże się informacja o błędzie , jednakże bardziej zaawansowany programista może zapobiec pojawieniu się tego alertu i kontynuować działanie programu. Poznamy tę możliwość na drugim semestrze, gdy dowiemy się nieco więcej o tzw. wyjątkach.

Przesadziłam z tym zaawansowaniem. ;-) Ty także już teraz możesz zapobiec pojawianiu się powyższych błędów (choć nieco w mniej wygodny sposób). Wystarczy, że zastosujesz instrukcję warunkową:

if(pozycja>=0 && pozycja<s.length())
    cout << s[pozycja]; //bądź s.at(pozycja)

Łączenie napisów

Mamy kilka sposobów łączenia (dodawania, konkatenacji) napisów. Poznamy dwa najpopularniejsze sposoby dodawania do siebie napisów.

   string s1, s2; //s1="", s2=""
   string s3 = "Bzyczy bzyg znad Bzury zbzikowane bzdury, bzyczy bzdury, bzdurstwa bzdurzy ";
   string s4 = "i nad Bzura w bzach bajdurzy, bzyczy bzdury, bzdurnie bzyka, ";
   string s5 = "bo zbzikowal i ma bzika!";
    //można łączyć napisy tak:
   s1 = s3 + s4 + s5;
   cout << s1 << endl;
    //...lub w ten sposób:
   s2 += s3;
   cout << s2 << endl << endl;
   s2 += s4;
   cout << s2 << endl << endl;
   s2 += s5;
   cout << s2 << endl << endl;

Operacje na podciągach

Umiemy już działać na pojedynczych znakach napisu, jednakże są sytuacje, kiedy potrzebujemy uzyskać dostęp do konkretnego fragmentu danego napisu. Przydaje się do tego metoda substr(), której działanie możesz poznać wykonując poniższy fragment kodu:

   string s="Tom i Jerry";
   string ss=s.substr(6,5); //od 6. pozycji pobierz pięć znaków
   cout << ss << endl;
Uważajmy znowu na to, by "nie wyjść" poza napis! Zabezpieczajmy się przed takimi ewentualnościami.

Możemy również odszukać jedynie pozycji, od której zaczyna się jakiś podciąg. Służy do tego metoda find() – korzystanie z niej jest trochę nietypowe. Spójrz:

   string tekst = "A moja papuga mieszka u mnie juz ponad rok!";
   string wzorzec1 = "papuga";
   string wzorzec2 = "nie";
   string wzorzec3 = "nigdzie";

   int pozycja;
   pozycja = tekst.find(wzorzec1);
   cout << pozycja << endl; //7
   pozycja = tekst.find(wzorzec2);
   cout << pozycja << endl; //25
   pozycja = tekst.find(wzorzec3);
   cout << pozycja << endl; //a tu wyświetli się coś dziwnego
Popraw powyższy kod, aby zamiast dziwnej liczby w ostatniej linijce pojawiał się odpowiedni, czytelny komunikat.

Mamy również możliwość wstawiania znaków do napisów. Przyjrzyjmy się działaniu metody insert().

   string s1 = "niebo";
   string s2 = "ladne ";
   string s3 = "i bezchmurne ";
   string s4 = "i po";
   string s5 = "gwiazdki  na niebie";

   s1.insert(0, s2); //wstaw na początek napisu
   cout << s1 << endl;
   s1.insert(6, s3); //wstaw na 6. pozycji
   cout << s1 << endl;
   s1 = "nie"+s2+"niebo";
   cout << s1 << endl;
   s1.insert(9, s4);
   s1.insert(13, s3, 5, 8); /*od 13. pozycji wstaw z s3 osiem znaków,
                      zaczynając od 5. pozycji s3 */
   cout << s1 << endl;

   s5.insert(9, 6, '*'); //od 9. pozycji wstaw sześć gwiazdek
   cout << s5 << endl;

Twórcy biblioteki string zadbali również o to, byśmy mogli zamieniać część znaków w naszych napisach. Służy do tego metoda replace().

   string s1 = "Ten ptak nazywa sie Tweety";
   string s2 = "Faflum";

   cout << s1 << endl;
   s1.replace(20,s2.length(),s2); /*zacznij zamieniac od 20. pozycji
                            wstaw wszystkie znaki z s2 */
   cout << s1 << endl;

   s1.replace(20, 6, 3, '*'); /*zacznij zamieniać od 20. pozycji,
                        usuń sześć znaków, w to miejsce wstaw trzy * */
   cout << s1 << endl;

Wczytywanie napisów z klawiatury

Dobrze by było, gdyby dało się wczytywać napisy z klawiatury... Na szczęście, tak jest! Mam nadzieję, że pamiętasz jeszcze instrukcję:

cin >> x;

Dzięki niej można wczytywać przeróżne informacje m.in. do zmiennych typu int czy double. Do napisów też się da!

string s;
cin >> s;

Wadę tego rozwiązania jest to, że można w ten sposób wczytać jedynie pojedyncze słowo – bez żadnych białych znaków. Jednakże jest i na to sposób!

string s;
getline(cin, s); //jeśli w s coś było, to ta wartość jest zastępowana nową

Get line nie oznacza pobierz linię. Można sprawić, aby znaki były wczytywane nie do napotkania znaku przejścia do nowej linii, lecz do dowolnie innego!

getline(cin, s, '*');

Należy być bardzo ostrożnym, jeżeli w programie używa się jednocześnie cin >> oraz getline().

string s1;
cin >> s1;

string s2;
getline(cin, s2);

cout << "Zawartosc s1: " << s1 << endl;
cout << "Zawartosc s2: " << s2 << endl;

Cóż się ukazało w drugim przypadku? Nawet nie mogliśmy go wczytać! Dzieje się tak dlatego, iż cin >> wczytuje wyłącznie do wciśnięcia klawisza Enter – przez to znak nowej linii zostaje w pamięci i wczytywany jest do s2.

Więcej informacji o napisach możesz zasięgnąć w angielskojęzycznej dokumentacji biblioteki.

Biblioteka <complex>

Wprowadzenie

Podczas studiów matematycznych czasem będziesz mieć do czynienia z liczbami zespolonymi. Warto uławić sobie życie, pisząc programy, dzięki którym można łatwiej i szybciej coś obliczyć. Na szczęście, programiści wzbogacili język C++ o bibliotekę zarządzającą liczbami zespolonymi.

Aby móc korzystać z dobrodziejstw tej biblioteki, musisz użyć dyrektywy:

#include <complex>

Tworzenie liczb zespolonych

Jak może już wiesz (albo dopiero się dowiesz), liczby zespolone możemy przedstawiać w postaci algebraicznej \(z=a+bi\) (gdzie \(a\) to część rzeczywista, \(b\) to część urojona oraz \(i=\sqrt{-1}\)).

Deklaracja zmiennej typu complex jest dosyć nietypowa. Nie wystarczy podać typu i nazwy zmiennej. Jej konstrukcja wygląda tak:

complex<typ> nazwa(a,b);

Jako typ możemy podać double, float lub long double, a w miejsce a i b wstawiamy dwie liczby zmiennoprzecinkowe. Przykładowo:

complex<double> z1(2.0, 5.0); //z=2+5i

Stworzyliśmy tym samym liczbę zespoloną "opartą" o typ double. Jeżeli mamy w kodzie już zdefiniowaną jedną liczbę zespoloną, to możemy się pokusić o skopiowanie jej zawartości nowej zmiennej. Można zrobić to tak:

complex<double> z2=z1;

Można też utworzyć nową zmienną i ustalić jej wartość w następujący sposób (wykorzystujemy tu tzw. konstruktor kopiujący):

complex<double> z2(z1);

Zmienną tego typu wyświetlamy tak jak każdą inną, czyli za pomocą "wysłania" jej na strumień cout.

Utwórz nowy projekt i wklej do niego poniższy kod:

#include <iostream>
#include <complex>
using namespace std;

int main()
{
	complex<double> z1(2.0, 5.0); //z=2+5i
	complex<double> z2=z1;
	cout << z2 << endl;
	return 0;
}

Skompiluj program i sprawdź, w jaki sposób wyświetli się liczba zespolona z2

Wyświetlanie części rzeczywistej i urojonej

Chcąc wyświetlić część rzeczywistą w naszym programie, piszemy:

cout << z.real(); // real = rzeczywisty

Z kolei część urojoną wyświetlamy ją za pomocą poniższej instrukcji:

cout << z.imag(); // imag (skrót od imaginary) = urojony

Wartość bezwględna

Wartość bezwględna (moduł) liczby zespolonej \(z=a+bi\) określona jest wzorem \[|z|=\sqrt{a^2+b^2}.\] W naszych programach możemy ją obliczyć, korzystając z funkcji abs():

cout << abs(z); //wyświetl moduł liczby z

Argument

Liczbę zespoloną można zapisać też w postaci \(z=|z|(\cos\phi + i\sin\phi)\), gdzie \(\phi\) nazywamy argumentem (kątem fazowym). Biblioteka complex umożliwia obliczenie argumentu danej liczby za pomocą funkcji arg():

cout << arg(z); //wyświetl argument liczby z
Wyświetlana wartość argumentu podana jest w radianach a nie w stopniach!

Operacje na liczbach zespolonych

Jakie znasz podstawowe działania matematyczne? Dodawanie, odejmowanie, mnożenie i dzielenie? Tak? Bardzo dobra odpowiedź! Wymienione działania można bez problemów stosować także dla liczb zespolonych. Zatem w naszym kodzie dozwolone są operatory: +, -, *, /, a także +=, -=, *=, /=

/* Działania matematyczne */
#include <iostream>
#include <complex>
using namespace std;

int main()
{
	complex<double> z1(2.0, 4.0);
	complex<double> z2(5.0, 4.0);
	complex<double> z3;

	cout << z3 << endl << endl; //co się wyświetli?

	cout << "Podstawowe dzialania matematyczne:" << endl;
	cout << z1+z2 << endl;
	cout << z1-z2 << endl;
	cout << z1*z2 << endl;
	cout << z1/z2 << endl << endl;

	cout << "Operatory @=:" << endl;
	z3+=z1; //z3=z3+z1
	cout << z3 << endl;
	z3-=z2;
	cout << z3 << endl;
	z3*=z1;
	cout << z3 << endl;
	z3/=z2;
	cout << z3 << endl << endl;

	return 0;
}
Przypomnij sobie, jak wyglądają definicje mnożenia i dzielenia liczb zespolonych.

Ponadto, zmienne typu complex możemy "porównywać", ale jedynie pod kątem sprawdzenia, czy jedna zmienna jest równa drugiej. Mam nadzieję, że wiesz, iż w przeciwieństwie do liczb rzeczywistych, zespolonych nie da się uporządkować wprost?

/* Porównywanie liczb */
#include <iostream>
#include <complex>
using namespace std;

int main()
{
	complex<double> z1(2.0, 4.0);
	complex<double> z2(5.0, 4.0);
	complex<double> z3(4.0, 0.0);
	complex<double> z4=4.0; //jakiś nowy sposób tworzenia liczby zespolonej?
	double r=4.0; //liczba rzeczywista

	if(z1==z2)
		cout << "Rowne!" << endl;
	else //tzn. if(z1!=z2)
		cout << "Nierowne!" << endl;

	if(z3==r) //porownanie liczby zespolonej z rzeczywistą
		cout << "Rowne!" << endl;
	else //tzn. if(z3!=r)
		cout << "Nierowne!" << endl;

	if(z4==r)
		cout << "Rowne!" << endl;
	else //tzn. if(z4!=r)
		cout << "Nierowne!" << endl;

	return 0;
}
Sprawdź działanie dwóch powyższych listingów.
Więcej działań na liczbach zespolonych możesz znaleźć w angielskojęzycznej dokumentacji. Znajdują się tu m.in. opisy funkcji trygonometrycznych.

Podsumowanie

Nauczyliśmy się korzystać z dwóch nowych typów. Gdy będziemy chcieli je wykorzystywać w codziennej pracy, warto mieć pod ręką ściągawki:

Biblioteka string

Operacja Działanie
string s("napis") konstruktor: utworzenie napisu z zawartością "napis"
string s1(s2) konstruktor: skopiowanie zawartości s2 do s1
string s1(s2, n) konstruktor: skopiowanie n-pierwszych znaków z s2 do s1
string s(n, znak) konstruktor: utworzenie napisu składającego się z n znaków
string s1(s2, n, ile) konstruktor: utworzenie napisu składającego się z ile znaków s2, poczynając od n-tej pozycji
==, !=, <, <=, >, >= operatory do porównywania napisów w kolejności alfabetycznej
s.length()
s.size()
długość napisu
s.resize(x) skracanie/wydłużanie napisu
s.resize(x, znak) zwiększanie długości napisu; puste miejsce uzupełnione przez znak
s.empty() sprawdza, czy napis jest pusty
s.clear() czyszczenie napisu
s[x]
s.at(x)
obsługa x-tej pozycji napisu
+, += operatory służące do łączenia napisów
s.substr(n,ile) pobranie od n-tej pozycji ile znaków
s1.find(s2) szukanie pozycji s2 w s1
s1.insert(n, s2) wstawienie do s1 na pozycji n napisu s2
s1.insert(n1, s2, n2, ile) wstawienie do s1 na pozycji n1 ile znaków z s2, poczynając od pozycji n2
s1.replace(n, ile, s2) podmiana od n-tej pozycji w s1 ile znaków z s2
s1.replace(n, ile1, ile2, znak) usunięcie ile1 znaków od n-tej pozycji w s1,
wstawienie w to miejsce ile2 znaków
cin >> s wczytanie pojedynczego słowa
getline(cin, s) wczytanie do s całej linii
getline(cin, s, znak) wczytanie do s całej linii aż do napotkania znak

Biblioteka complex

Operacja Działanie
complex<typ> nazwa(a,b) konstruktor: tworzy liczbę zespoloną \(a+bi\), gdzie typ: double, float, long double
z.real() część rzeczywista
z.imag() część urojona
abs(z) wartość bezwzględna
arg(z) argument liczby zespolonej
+, -, *, / podstawowe działania matematyczne na liczbach zespolonych
+=, -=, *=, /= działania matematyczne z możliwością przypisywania
==, != umożliwiają porównywanie liczb zespolonych (w sensie, czy obie liczby są równe)
CC By 3.0
Copyright © 2011-2016 by Katarzyna Fokow [Last update: 2017-02-01 17:17:11]
This work is licensed under a Creative Commons Attribution 3.0 Unported License