Programowanie w c

Pojęcia wstępne

 

Pojęcia wstępne, ułatwiające zrozumienie procesu tworzenia programu.

Kompilator - to program, który tłumaczy kod źródłowy na kod maszynowy - kod zrozumiały dla danego typu procesora. Jedna instrukcja kodu źródłowego odpowiada wielu instrukcjom kodu maszynowego. Niektóre kompilatory tłumaczą najpierw kod źródłowy na kod assemblera, a następnie ten jest tłumaczony na kod maszynowy. Język C++ jest językiem wysokiego poziomu, oznacza to, że jednej instrukcji w tym języku odpowiada do kilkuset instrukcji w języku maszynowym. Język assembler jest językiem niskiego poziomu. Jednej instrukcji w tym języku odpowiada kilka instrukcji w kodzie maszynowym.

Przykładowe kompilatory dla języka C++:

Kompilacja - jest to proces tłumaczenia danego kodu źródłowego na program wykonywalny (kod maszynowy).

Kod źródłowy - jest to tekst wpisywany do okienka kompilatora, charakterystyczny dla danego języka programowania np. C++, Pascal, Java itd.

W języku C++, kod źródłowy zapisujemy w plikach tekstowych o następujących rozszerzeniach:

Słowo kluczowe -  jest to słowo zarezerwowane przez dany język programowania, które można używać zgodnie z przeznaczeniem i w określonej sytuacji, np. w C++: auto, for, if, continue, break, case, switch, while itd.

Struktura programu w C++

powrót

Ogólna struktura programu w C++ (generowana automatycznie przy tworzeniu projektu przez kompilator Dev-C++) składa się z kilku części, które zostaną opisane poniżej:

 

część 1

#include <cstdlib>

#include <iostream>

część 2

using namespace std;

część 3

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

część 4

{

/* instrukcje funkcji main */

return 0;

}

 

 

Część I - trochę o bibliotekach

 

Ta część programu opisuje biblioteki jakie mają być dołączone do programu. Biblioteki to pliki, których zawartość jest dołączana do programu za pomocą dyrektywy preprocesora#include. Biblioteka to plik o nazwie podanej w nawiasie "< nazwa biblioteki >", który posiada między innymi definicje przydatnych funkcji.

Użycie funkcji pierwiastkującej zdefiniowanej w  bibliotece cmath

#include <cstdlib>

#include <iostream>

//dodanie biblioteki cmath, w której znajduje się

//definicja funkcji pierwiastkującej sqrt

#include <cmath>

 

using namespace std;

 

int main()

{

 

//użycie funkcji sqrt wyznaczającej

//pierwiastek z liczby 2

cout<<sqrt(2)<<endl;

 

system("pause");

 

return 0;

}

 

 

Np. biblioteka cstdlib zawiera funkcje ogólne takie jak konwersje, alokacja pamięci czy funkcje matematyczne. Biblioteka iostream (input/output stream) jest standardową biblioteką wejścia/wyjścia w C++. Jeśli chcemy coś wyświetlać na ekranie (za pomocą obiektu cout i przeciążonego operatora "<<"), lub pobierać dane z klawiatury (za pomocą obiektu cin i przeciążonego operatora ">>") musimy ją dodać do nagłówka programu.

 

Wcześniejsze wersje bibliotek miały rozszerzenie "*.h". Język C++ odchodzi od takiego nazewnictwa, gdyż pliki z tym rozszerzeniem były początkowo wykorzystywane w języku C (oczywiście C++ może nadal korzystać z tych bibliotek) i dla rozdzielenia bibliotek kojarzących się z danym językiem, C++ przyjął nazwy bez rozszerzenia. Niektóre biblioteki zostały przekształcone z C na C++, i w takich przypadkach pozbyto się rozszerzenia, ale dodano literkę "c" na początku takiego pliku np.:

 

stara nazwa pliku

nowa nazwa pliku

math.h

stdlib.h

...

cmath

cstdlib

...

Część II - przestrzenie nazw

Dyrektywę using namespace musimy użyć w przypadku, gdy zamiast pliku iostream.h, będziemy używać iostream, w celu udostępnienia definicji zawartej w tym pliku. Generalnie chodzi o to, żeby nie pisać za każdym razem wywołanie obiektu cout czy cin z przedrostkiem std::. Np.:

#include<iostream>

#include<cstdlib>

 

int main()

{

 

//bez użycia dyrektywy using namespace std musimy

//do każdej definicji z biblioteki

//iostream dodać przedrostek std::

 

std::cout<<"Ala ma kota"<<std::endl;

 

system("pause");

return 0;

}

 

 

Wyobraźmy sobie, że mamy dwa pakiety, w których zdefiniowana jest funkcja o nazwie wspaniala(). Pierwszy pakiet jest od producenta JANEK, a drugi odMARCIN. Jeśli chcemy używać funkcji wspaniala() od MARCIN, bo uważamy, że jest lepsza, udostępniamy definicję przestrzeni nazw MARCIN:

using namespace MARCIN;

i nie musimy za każdym wywołaniem funkcji wspaniala(), pisaćMARCIN::wspaniala().

Wiąże się to także z uproszczeniem uaktualnienia starszych wersji programu, dodając tylko odpowiednią dyrektywę bez konieczności dopisywania do każdego elementu przedrostka.

Część III - funkcja main

Funkcja main() jest charakterystyczną funkcją w C++, która musi występować w każdym konsolowym programie. Wszystko co zaczyna się dziać w danej aplikacji, jest określana w ciele właśnie tej funkcji. Oczywiście wszystkie inne funkcje mogą być wywoływane z wnętrza tej funkcji.

 

Funkcja main() ma kilka postaci:

 

 

Podczas uruchomienia programu za pomocą konsoli (w Windowsie: cmd.exe), oprócz podania nazwy programu, który chcemy uruchomić, możemy przekazać wstępne dane (parametry programu). Argument int argc, mówi ile tych danych jest, natomiast char *argv[] przechowuje te dane. Prześledźmy przykład programuprogram.exe, który wyświetli argumenty danego programu:

program.cpp

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

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

{

 

for(int i=1;i<argc;i++)

cout<<i<<" argument to "<<argv[i]<<endl;

 

system("pause");

 

return 0;

}

 

 

Przykładowe wywołanie programu program.exe i wynik działania:

 

Wywołana została aplikacja z argumentami: arg1arg2x oraz 8991. Zauważmy, że argumenty oddzielamy spacją.

Jeśli program wywołujemy bez dodatkowych parametrów, listę argumentów funkcji main() można pominąć.

Część IV - życie programu

W tej części programu zaczyna się życie naszej aplikacji. Wszystko to co tu napiszemy, będzie rzutowało na sposób zachowania się naszego programu. Oprócz słowa kluczowego return, które zostało wyjaśnione w części III, domyślnie dopisana zostaje przez Dev-C++instrukcja system("pause"). Powoduje ona zatrzymanie się programu w tym miejscu i wyświetlenie komunikatu: "Aby kontynuować, naciśnij dowolny klawisz . . .".

 

Zauważmy, że blok funkcji main(), zaczynamy nawiasem "{" i kończymy nawiasem "}".

Komentarze w C++

W trakcie pisania programu orientujemy się za co odpowiadają poszczególne części kodu źródłowego. Wracając po miesiącu ponownie do tego samego programu, autor potrzebuje znacznie więcej czasu aby zrozumieć ideę działania poszczególnych bloków kodu. Popatrzmy na inny przykład. Analiza nieswojego kodu źródłowego bez odpowiedniego opisu (bez komentarzy) jest bardzo trudna, czasami niemożliwa. Aby uniknąć tego typu sytuacji powinno się opisywać kluczowe elementy programu stosując właśnie komentarze. Są one ignorowane przez kompilator i pozostawiają tylko informację dla programisty.

 

C++ umożliwia wstawianie komentarzy na dwa sposoby. Pierwszy rodzaj to komentarz jednolinijkowy. Po wpisaniu "//" wszystko do końca linii jest traktowane jako komentarz np.:

 

int a; //stworzenie zmiennej typu całkowitego

 

 

Drugi rodzaj to komentarz wielolinijkowy. Wszystko co znajduje się między "/*" i"*/" jest traktowane jako komentarz np.:

/*

Ta linia jest komentarzem

Ta także

*/

 

 

Wprowadzanie i wyprowadzanie danych

powrót

Wypisywanie danych na ekranie monitora (standardowe wyjście)

Do wyświetlania danych na ekran służy obiekt zdefiniowany w bibliotece "iostreamcout (out jak wyjście, np. na ekran monitora). Jego konstrukcja jest następująca:

std::cout << "Jakiś ciąg znaków :)";

Jeśli do programu dołączymy linijkę

using namespace std;

która jest omówiona tutaj, zapis możemy skrócić do postaci:

cout << "Jakiś ciąg znaków :)";

Jak łatwo zauważyć, powyższa konstrukcja pozwala wyświetlać napis na ekranie monitora, który jest umieszczony w cudzysłowie. Dodatkowym elementem jest operator wyjścia <<, który pokazuje kierunek przekierowania strumienia znaków. Jest to przeciążony operator przesunięcia bitowego.

Przy wyświetlaniu zmiennych lub wartości działań, zapisujemy je bez użycia cudzysłowu, np.:

cout << b*34 - 45 + a;

Możemy łączyć wyświetlanie ciągów znaków z wartościami zmiennych (lub samymi wyrażeniami) oddzielając każde z nich operatorem << np.:

cout <<"Wartość wyrażenia "<<23*7655<<" jest większa niż "<<22*7654 ;

Na ekranie monitora pojawi się komunikat:

"Wartość wyrażenia 176065 jest większa niż 168388"

 

Prześledźmy przykład wyznaczania sumy dwóch liczb:

#include <iostream>

#include <cstdlib>

using namespace std;

 

int main()

{

int a = 67, b = 56;

 

cout <<"Suma liczb "<< a <<" i "<< b <<" wynosi "<< a+b << endl;

 

system("pause");

return 0;

}

 

 

Wstawianie znaku końca linii (enter) i innych znaków specjalnych

Do wstawienia znaku końca linii (znak enter), możemy wykorzystać dwie metody.

Pierwsza to wpisanie wewnątrz tekstu kombinacji: \n, natomiast druga polega na dopisaniu instrukcji endl (end line), którą wstawiając oddzielamy separatorem strumienia wyjścia. Prześledźmy przykład:

#include<iostream>

#include<cstdlib>

using namespace std;

 

int main()

{

//wstawienie trzech znaków enter

cout<<"Ala ma kota\n\na kot ma Ale :)\n";

 

//tak samo jak wyżej

cout<<"Ala ma kota"<<endl<<endl<<"a kot ma Ale :)"<<endl;

 

system("pause");

return 0;

}

 

 

W pierwszym i drugim przypadku zostanie wstawiony tekst "Ala ma kota", następnie dwa znaki enter, tekst "a kot ma Ale :)" i znak enter.

Innym znakiem specjalnym jest znak tabulacji, który możemy wstawić za pomocą kombinacji \t, lub sygnał z głośniczka dźwiękowego \a.

Do wstawiania znaków poprzez podanie kodów ASCII, możemy posłużyć się następującą kombinacją: \nr, gdzie nr to kod ASCII danego znaku zapisany w systemie ósemkowym.

Prześledźmy przykład:

#include<iostream>

#include<cstdlib>

using namespace std;

 

int main()

{

//wyświetlenie dziwnego znaczka

cout<<"To jest dziwny znaczek: \211"<<endl;

 

//wstawienie dwóch tabulatorów i

//dwóch sygnałów z głośniczka systemowego

cout<<"\t\t\a\a"<<endl;

 

system("pause");

return 0;

}

 

 

Pobieranie danych z klawiatury (standardowe wejście)Do pobierania danych z klawiatury służy obiekt cin (in jak wejście), który jest zdefiniowany w bibliotece "iostream" ("iostream.h").

Podobnie jak w przypadku obiektu cout mamy do dyspozycji dwie możliwości:

std::cin>>nazwa_zmiennej;

lub po dodaniu linijki

using namespace std;

mamy postać:

cin>>nazwa_zmiennej;

Zauważmy, że w tym przypadku stawiamy znaki przekierowania strumienia w odwrotną stronę ">>". Inaczej mówiąc, strzałeczki pokazują, w którą stronę mają być przekierowane wartości:  do zmiennych.

Jeśli chcemy podać wartości kilku zmiennych, można to zrobić oddzielając zmienne znakiem wejścia strumienia np.:

cin>>pierwsza_zmienna>>druga_zmienna>>trzecia_zmienna;

Prześledźmy przykład: Napisz program, który po podaniu dwóch liczb obliczy ich iloczyn. 

#include<iostream>

#include<cstdlib>

 

using namespace std;

 

int main()

{

int a, b;

cout<<"Podaj dwie liczby: ";

cin>>a>>b;

 

cout<<"Iloczyn liczby "<<a<<" i "<<b<<" wynosi: "<<a*b<<endl;

 

system("pause");

 

return 0;

}

Manipulatory

powrót

W tym artykule omówię krótko zasadę wykorzystania manipulatorów zdefiniowanych w standardowej bibliotece i w bibliotece iomanip, które formatują dane wyjściowe przy użyciu obiektu cout.

Zmiana systemu liczbowego  wyświetlanych liczb - manipulatory hex, oct i dec

Mamy do dyspozycji trzy systemy: szesnastkowy (hex), ósemkowy (oct) oraz dziesiętny (dec) - domyślny. Do zmiany systemu liczbowego dla wyświetlania liczb za pomocą obiektu cout, musimy podać jeden z trzech dostępnych manipulatorów przed wyświetlaną liczbą. System będzie obowiązywał do momentu zmiany na inny.  Domyślnie jest ustawiony dziesiętny.

Przykład

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

int main()

{

int liczba = 19880;

 

cout<<"Liczba w systemie dziesiętnym: "<<liczba<<endl;

 

cout<<"Ta sama liczba w systemie ósemkowym: "<<oct<<liczba<<endl;

 

cout<<"Ta sama liczba w systemie szesnastkowym: "<<hex<<liczba<<endl;

 

cout<<"I powrót do systemu dziesiętnego: "<<dec<<liczba<<endl;

 

system("pause");

return 0;

}

 

 

Szerokość pola

 Aby ustawić ilość miejsc dla wyswietlanej liczby lub ciągu znaków, wykorzystujemy metodę width(int). W przeciwieństwie do manipulatorów ustawiających system, metada ta działa tylko dla jednej (następnej) liczby lub ciągu. Możemy ją wykorzystać, jeśli zależy nam na równym wyświetlaniu danych. Zasadę działania pokażę na przykładzie:

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

int main()

{

int liczba = 43, a = 8, b = 10;

 

 

cout.width(30);

 

cout<<"Szerokość tego pola to 30"<<endl;

 

//wyrównanie liczb do prawej strony

//(na całą liczbę przeznaczamy siedem miejsc)

//puste miejsca będą wypełnione spacjami

 

cout.width(7);

cout<<liczba<<endl;

cout.width(7);

cout<<a<<endl;

cout.width(7);

cout<<b<<endl;

 

system("pause");

return 0;

}

 

 

Znaki wiodące

Gdy szerokość pola danych jest ustawiona na większą niż same dane, puste miejsca są wypełniane spacjami. Można zmienić znak wypełniający na inny za pomocą metodyfill(char);

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

int main()

{

int a = 10, b = 100;

 

//puste miejsca z prawej strony będą wypełnione znakami "_"

 

cout.fill('_');

 

cout.width(10);

cout<<a<<endl;

 

cout.width(10);

cout<<b<<endl;

 

system("pause");

return 0;

}

 

 

Ustawianie precyzji liczb zmiennoprzecinkowych

Do ustawienia wyświetlenia ilości cyfr danej liczby zmiennoprzecinkowej służy metoda precision(precyzja). Popatrzmy na przykład:

 

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

int main()

{

float a = 3.12345;

 

cout<<a<<endl;

 

//wyświetlenie liczby której całkowita ilość cyfr jest równa 2

cout.precision(2);

cout<<a<<endl;

 

//ustawienie pola trzech cyfr (3.12)

cout.precision(3);

cout<<a<<endl;

 

system("pause");

return 0;

}

 

 

Aby wyświetlić liczbę z zadaną ilość miejsc po przecinku należy użyć następującej konstrukcji:

cout<<fixed<<setprecision(ilość miejsc po przecinku)<<liczba_zmiennoprzecinkowa;

gdzie fixed usuwa notację wykładniczą z zadanej liczby - potrzebna jest bibliotekaiomanip (iomanip.h).

Popatrzmy na przykład:

#include<iostream>

#include<iomanip>

#include<cstdlib>

using namespace std;

 

int main()

{

cout<<"Podaj liczbę zmiennoprzecinkową: ";

long double liczba;

cin>>liczba;

cout<<"Podaj precyzję: ";

int precyzja;

cin>>precyzja;

 

cout<<"Podałeś liczbę: "<<liczba<<endl;

cout<<"Po ustawieniu precyzji liczba wygląda tak: "<<fixed

<<setprecision(precyzja)<<liczba<<endl;

 

system("pause");

return 0;

}

 

 

Metoda setf() - zdefiniowana w bibliotece iostream

Metoda setf() pobiera argument, który ustawia odpowiedni format danych. Mamy do dyspozycji kilka flag formatujących, oto niektóre z nich:

Do unieważnienia flagi metody setf() służy metoda unsetf().

Popatrzmy na przykład:

 

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

int main()

{

int a = 3, b=6;

 

cout<<a;

 

//wymuszenie wstawienia przed liczbę dodatnią znaku "+"

 

cout.setf(ios_base::showpos);

 

cout<<b<<"=";

 

//unieważnienie działania metody setf()

 

cout.unsetf(ios_base::showpos);

 

cout<<a+b<<endl;

 

system("PAUSE");

return EXIT_SUCCESS;

}

 

 

Biblioteka iomanip

 Bibliotekę tą stworzono w celu uproszczenia stosowania manipulatorów. Oto przykładowe odpowiedniki dla manipulatorów z biblioteki standardowej:

#include <cstdlib>

#include <iostream>

#include <iomanip>

 

using namespace std;

 

int main()

{

float a = 3.141414;

 

cout<<setw(20); //ustawienie wielkości pola na dane - 20 miejsc

 

cout<<setfill('_'); //ustawienie znaków wiodących

 

cout<<setprecision(4); //precyzja - 4 cyfry na całą liczbę

//zmiennoprzecinkową

 

cout<<a<<endl;

 

system("pause");

return 0;

}

 

Operatory

 

powrót

Wyróżniamy kilka rodzajów operatorów. W zależności od sytuacji, w której mają zastosowanie dzielimy na kilka grup:

Dodatkowo w tym artykule przedstawię priorytety operatorów wykorzystywanych w C++.

Warto zauważyć, że operatory dzielimy także ze względu na ilość argumentów, jakie potrzebują do prawidłowego działania.  Operatory jednoargumentowe takie jak  "++" czy "--" nazywamy unarnymi np.:

int c = 0;

c++; //zauważmy, że do wykonania tej operacji potrzebny jest tylko jeden argument, w tym przypadku

//jest to zmienna o nazwie "c"

 

Operatory, które wykorzystują dwa argumenty nazywamy binarnymi np.:

int a = 9, b = 3;

int c = a + b;//operator + jest operatorem dwuargumentowym; argumentami są w tym przypadku zmienne "a" i "b".

 

Operatory arytmetyczne

Operatory arytmetyczne służą do wykonywania wszelkiego rodzaju działań na liczbach takich jak:

int a = 10, b = 3, wynik1;

wynik1 = a / b; //zmienna "wynik1" przechowa wartość 3, ponieważ 10 / 3 = 3 całe (dzielenie całkowite)

wynik1 = a % b; //zmienna "wynik1" w tym przypadku będzie miała nadaną wartość 1, ponieważ

// reszta z dzielenia liczby 10 przez 3 jest równa 1

wynik1 = b% a; //zmienna "wynik1" będzie równa 3, ponieważ tyle wynosi reszta z dzielenia liczby 3 przez 10

float c = 10, d = 3, wynik2;

wynik2 = c / d; //zmienna "wynik2" przechowa wartość 3.333..., ponieważ w tym przypadku zostało wykonane

//dzielenie całkowite

Operatory logiczne

 

Mają zastosowanie w miejscach, gdzie występują różnego rodzaju warunki - głównie w pętlach i instrukcjach warunkowych. Do operatorów logicznych zaliczamy:

 

 

Lub logiczne  zwraca prawdę, gdy przynajmniej jeden z warunków jest prawdziwy, w przeciwnym razie zwraca fałsz np.:

Załóżmy, że nasze zmienne przyjmują następujące wartości:

int n = 5; int k = 7;

bool warunek = n > 3 || k > 10; // wystarczy, aby jeden warunek był prawdziwy

// (w tym przypadku jest pierwszy) aby zmienna "warunek" otrzymała wartośćtrue

 

 

Logiczne zwraca prawdę w przypadku, gdy wszystkie warunki są prawdziwe, w przeciwnym razie zwraca fałsz np.:

int n = 5; int k = 7;

bool warunek = n > 3 && k > 10;// wystarczy, aby jeden warunek był fałszywy

// (w tym przypadku jest drugi) aby zmienna "warunek" otrzymała wartośćfalse

Logiczne nie zaprzecza otrzymaną wartość z true na false lub z false na true np.:

int n = 5; int k = 7;

bool warunek = !(n > 3 && k > 10); // wyrażenie w nawiasie ma wartość false,

// a więc zmienna "warunek" będzie miała wartość true

 

Operatory relacyjne

 

Operatory relacyjne stosujemy w sytuacje, gdzie jest potrzeba porównania dwóch elementów. Najczęściej w instrukcjach warunkowych i iteracyjnych. Wyróżniamy:

 

 

Przykładowe zastosowanie:

int a = 8, b = 7;

if(a==b) //sprawdzam czy wartość zmiennej "a" jest taka sama jak zmienne "b"

cout<<"a jest równe b"; //ten komunikat się nie wyświetli, ponieważ "a" nie jest równe "b"

else

cout<<"a nie jest równe b";

 

 

 

Operatory przypisania

Przypisanie polega na nadaniu wartości dla zmiennej znajdującej się po lewej stronie, wartości znajdującej się po stronie prawej. Dla takiej zmiennej można bezpośrednio nadać wartość,  przekazać wartość z innej zmiennej lub wartość może zostać nadana poprzez wcześniejsze wykonanie pewnych operacji. Podstawowym operatorem przypisania jest "=".

int a = 45; //przypisanie do zmienne "a" liczby 45, od tej chwili zmienna "a" przechowuje tą liczbę

int b = a; //przypisanie do zmiennej "b" wartości zmiennej "a", od tej chwili zmienna "b" ma taką

//samą wartość co zmienna "a".

int c = b * (a - b); //w tym przypadku wykonane zostaną operacje po prawej stronie operatora

// a następnie ten wynik zostanie przypisany do zmiennej "c" (czyli 0)

 

Częstą operacją w języku C++ jest zwiększenie lub zmniejszenie wartości zmiennej całkowitej o 1. Tą operację nazywamy odpowiednio inkrementacją idekrementacją. Przeanalizujmy przykład:

int c = 1;

c = c + 1; //zwiększenie wartości zmiennej "c" o 1, od tej pory ta zmienna ma wartość 2 - sposób pierwszy

c += 1;  //ponowne zwiększenie zmiennej "c" o 1, zmienna ta przyjmuje wartość 3 - drugi sposób inkrementacji

c ++; // najkrótszy i najczęstszy sposób inkrementujący zmienną "c". Od tej pory zmienna ta osiąga wartość 4

c --; // i analogicznie dekrementujemy naszą zmienną, a więc zmniejszamy wartość zmiennej o 1.

Pamiętaj!!! Inkrementacja i dekrementacja działa tylko na zmiennych typu całkowitego.

Jak już wspomniałem, najczęściej używaną opcją jest inkrementacja i dekrementacja zastosowana w trzecim przykładzie. Ten rodzaj inkrementacji dzielimy na dwa rodzaje: przyrostkowa (c++; c--;) oraz przedrostkowa (++c; --c;). Na pierwszy rzut oka, efekt działania będzie taki sam. Różnica jednak jest w priorytecie przy zastosowaniu z operatorem przypisania. Prześledźmy przykład:

int a, b;

a = 1;

b = a ++; // w tym przypadku najpierw zostanie przypisana wartość do zmiennej "b",

// a następnie zwiększona wartość zmiennej "a" o 1, czyli od tej chwili

// " a = 2 " natomiast " b =1 "

a = 1;

b = ++a; // w tym przypadku najpierw zadziała operator inkrementacji, a później przypisania

// a więc, najpierw zwiększamy "a", a następnie ten wynik przypisujemy do "b",

//z tego wynika, że " a = 2 " i " b = 2 ".

 

Operację przypisania połączoną z pewną operacją matematyczną na wartości zmiennej można przedstawić w następujący sposób:

zmienna [operator matematyczny][=] wartość;

Aby lepiej zrozumieć powyższą notację prześledźmy przykłady:

int a = 3;

a += 4; //działanie to oznacza, że do aktualnej wartości zmiennej "a" dodajemy 4, czyli nowa wartość tej zmiennej wynosi 7

 

int a = 3;

a -= 4; //działanie to oznacza, że od aktualnej wartości zmiennej "a" odejmujemy 4, czyli nowa wartość tej zmiennej wynosi -1

 

int a = 3;

a %= 2; //działanie to oznacza, że pod zmienną "a" zostanie zapisany wynik reszty z dzielenie aktualnej wartości tej danej

// i 2, czyli 1

 

int a = 3;

a *= 4; //działanie to oznacza, że aktualna wartość zmiennej "a" zostanie pomnożona przez 4 i wynik zostanie nadpisany w tej zmiennej

//wynikiem tym jest liczba 12

Nie będę opisywał wszystkich przypadków tego rodzaju przypisania. Wypiszę tylko operatory, które można zastosować analogicznie do powyższych przykładów:

 

Operatory bitowe

Operatory, o których mowa, wykonują operacje bezpośrednio na reprezentacji bitowej danej zmiennej.Załóżmy, że rozpatrujemy zmienną typu unsigned short, która zajmuje pole dwóch bajtów czyli 16 bitów. Teraz przypiszmy do tej zmiennej liczbę 19. Popatrzmy na tą liczbę w notacji binarnej:

 

19 = (00000000 00010011)2

Pierwszy operator bitowy to przesunięcie w prawo ">>". Przesuwa liczbę o bitów w prawą stronę, gdzie n jest liczbą całkowitą, uzupełniając z lewej strony zerami:

unsigned short a = 19;

a>>=3; //przesunięcie bitów w zmiennej "a" o 3 w prawo, powstała w ten sposób liczba 2

Gdy przesuniemy liczbę 19 o trzy w prawo, skasują się trzy bity z prawej strony, natomiast z lewej strony dopełnią się zerami:

19 = (00000000 00010011)2

przesuwamy 3 bity w prawo i otrzymujemy

2 = (00000000 00000010)2

Analogicznym operatorem jest operator przesunięcia w lewo. Popatrzmy na przykład:

unsigned short a = 19;

a<<=2; //przesunięcie bitów w zmiennej "a" o 2 w lewo, powstała w ten sposób liczba 76

19 = (00000000 00010011)2

przesuwamy 2 bity w lewo i otrzymujemy

76 = (00000000 01001100)2.

Następnym operatorem bitowym jest koniunkcja bitowa - w notacji C++ "&". Zanim przejdziemy do przykładu poparzmy jak zachowują się bity potraktowane tym operatorem:

0 & 0 = 0

0 & 1 = 0

1 & 0 = 0

1 & 1 = 1

Zauważmy, że tylko w jednym przypadku otrzymamy jedynkę - gdy oba bity będą jedynkami. Rozpatrzmy już znaną nam liczbę 19 zapisaną pod typem unsigned short. Wykonajmy następującą operację:

unsigned short a = 19;

unsigned short b = a & 34; //otrzymaliśmy w ten sposób wartość 2, którą od tej pory przechowuje zmienna "b"

 

 

19 =

(00000000 00010011)2

&

34 =

(00000000 00100010)2

 

2 =

(00000000 00000010)2

Podobnie działa alternatywa bitowa - w notacji C++ "|". W tym przypadku otrzymamy 0, gdy dwa bity są zerami, w przeciwnym wypadku będzie 1. Popatrzmy:

0 | 0 = 0

0 | 1 = 1

1 | 0 = 1

1 | 1 = 1

unsigned short a = 19;

unsigned short b = a | 34; //otrzymaliśmy w ten sposób wartość 51, którą od tej pory przechowuje zmienna "b"

 

 

19 =

(00000000 00010011)2

|

34 =

(00000000 00100010)2

 

51 =

(00000000 00110011)2

Następnym ciekawym operatorem jest operator różnicy symetrycznej, wykorzystywany często w szyfrowaniu symetrycznym danych. W notacji C++ operator zapisujemy "^". Traktując dwa bity tym operatorem otrzymujemy 0, gdy te bity są takie same, 1 w przeciwnym razie:

0 ^ 0 = 0

0 ^ 1 = 1

1 ^ 0 = 1

1 ^ 1 = 0

Rozpatrzmy przykład:

unsigned short a = 19;

unsigned short b = a ^ 34; //otrzymaliśmy w ten sposób wartość 49, którą od tej pory przechowuje zmienna "b"

 

 

19 =

(00000000 00010011)2

^

34 =

(00000000 00100010)2

 

49 =

(00000000 00110001)2

Ostatnim operatorem z tej serii jest jednoargumentowy operator negacji bitowej. W notacji C++ zapisujemy "~". Zasada działania jest prosta:

~ 0 = 1

~1 = 0.

Rozpatrzmy zmienną typu unsigned int znajdującą się na polu 4 bajtów, czyli 32 bitów. Przypiszmy wartość 19. Jaka będzie wartość tej zmienne po negacji? Zobaczmy:

unsigned int a = 19;

a = ~a; // w tym przypadku zmienna a po negacji przyjmie wartość 4294967276

 

 

19 =

(00000000 00000000 00000000 00010011)2

~ 19 =

4294967276 =

(11111111 11111111 11111111 11101100)2

Priorytety operatorów

W tym podrozdziale będziemy rozpatrywać kolejność wykonywanych operacji przez operatory. Przyjrzyjmy się następującemu działaniu matematycznemu:

343 · 5 - 6 = …

Wiadomo, że w tym przypadku kolejność jest następująca:

Najpierw potęgowanie, potem mnożenie, a na końcu odejmowanie. Aby zmienić kolejność działań możemy posłużyć się nawiasami np.:

343 · (5 - 6) = …

W tym przypadku zmieniliśmy priorytety wykonywanych działań. Najpierw zostanie wykonane działanie w nawiasie, następnie potęgowanie (lub na odwrót), potem dopiero mnożenie.

Tak samo dzieje się z operatorami. Jedne działają wcześniej (mają wyższy priorytet), inne później. Oto tabelka z operatorami i ich priorytetami.

 

 

Priorytet Ilość arg. Operator Opis Przykład

1

2

::

zakres, przestrzeń nazw

std::cout

2

2

() []
. ->
++ --
dynamic_cast
static_cast
reinterpret_cast
const_cast
typeid

nawiasy, inkrementacja postfiksowa(przyrostkowa), odwołanie do metod obiektów, odwołania do pól struktur...

i++,

obiekt->metoda()

3

1

++ -- ~ !
sizeof
new delete
* & + -

inkrementacja przedrostkowa,

prefiksowa, referencja, wskaźnik

++i, +k, -k, & ref, * wsk

4

1

(typ)

rzutowanie

(double) a

5

2

.* ->*

 

 

6

2

* / %

mnożenie, dzielenie, modulo

a / b

7

2

+ -

dodawanie, odejmowanie

a + b

8

2

<< >>

przesunięcie bitów

a << 2

9

2

< > <= >=

porównywanie

a < b

10

2

== !=

porównywanie

a == b

11

2

&

bitowy iloczyn

 

12

2

^

różnica symetryczna XOR

 

13

2

|

bitowa suma

 

14

2

&&

iloczyn logiczny

(warunek1) && (warunek2)

15

2

||

suma logiczna

(warunek1) || (warunek2)

16

2

x ? y : z

operator warunkowy – zwraca y, gdy x ma wartość niezerową, z w przeciwnym wypadku

 

17

2

= *=
/= %=
+= -=
>>= <<=
&= ^= !=

przypisanie

a %= b

18

2

,

operator przecinkowy, służący np. do grupowania wyrażeń podczas inicjalizowania pętli

for(i = 1, j = 1;;i++,j--)

Warunkowy trójargumentowy operator ? :

powrót

W C++ i wielu innych językach programowania istnieje operator, który potrzebuje trzech argumentów do poprawnego działania. Tym operatorem jest warunkowy operator:

arg1 ? arg2 : arg3

arg1 - tu znajduje się warunek lub warunki

arg2 - tu znajduje się instrukcja, która wykona się, gdy warunek jest prawdziwy

arg3 - tu znajduje się instrukcja, która wykona się, gdy warunek będzie fałszywy.

W wielu sytuacjach stosowanie tego operatora skraca i upraszcza kod programu. Prześledźmy kilka przykładów.

Zadanie 1. Napisz program, który sprawdzi, czy dana liczba jest parzysta.

Rozwiązanie

#include<iostream>

using namespace std;

 

int main()

{

int a;

 

cin>>a;

 

cout<<(a%2?"Liczba jest nieparzysta":"Liczba jest nieparzysta");

 

return 0;

}

 

 

Zadanie 2. Napisz program, który przypisze do zmiennej max większą wartość z dwóch podanych liczb.

Rozwiązanie

#include<iostream>

using namespace std;

 

int main()

{

int a, b;

 

cin>>a>>b;

//jeśli a>b do zmiennej max zostanie przypisana wartość zmiennej a

// w przeciwnym wypadku wartość zmiennej b

int max = a>b?a:b;

 

cout<<max;

return 0;

}

 

Zmienne w C++

 powrót

Spis treści

Podstawowe pojęcia

zmienna - to obiekt w programowaniu, który przechowuje różnego rodzaju dane niezbędne do działania programu. Zmienna podczas działania programu może zmieniać swoje wartości (jak wskazuje nazwa). Tworząc zmienną musimy nadać jej nazwę oraz typ, który określa co nasza zmienna będzie przechowywać. Nadając nazwę trzymamy się następujących reguł:

typ zmiennej - tworząc zmienną musimy się zastanowić, jakie będzie jej zastosowanie. Zmienne mogą przechowywać znaki, liczby całkowite, liczby rzeczywiste, ciągi znaków lub wartość logiczną true lub false. W dalszej części dokumentu zostaną zilustrowane podstawowe typy zmiennych, ich rozmiar, zakres i zastosowanie.

Inicjacja zmiennych

Ogólna zasada tworzenia zmiennych jest następująca:

typ_zmiennej nazwa_zmiennej;

np.

int a - zmienna o nazwie "a" mająca typ całkowity int

char b - zmienna o nazwie "b" mająca typ znakowy char.

Prześledźmy przykłady:

int a; //stworzenie zmiennej typu całkowitego o nazwie "a"

int b = 8; //stworzenie zmiennej typu całkowitego o nazwie "b" i nadanie jej wartości 8

int x, y = 80, z, k =7; //stworzenie kilku zmiennych typu całkowitego, nadając niektórym

//zmiennym wartości

 

Typy zmiennych

Pierwsza grupa to zmienne typu całkowitego. Jak sama nazwa mówi, przechowują tylko liczby całkowite. Różnią się one rozmiarem, czyli zakresem przechowywanych liczb. Im większy rozmiar, tym większe liczby mogą być przechowane.

Typy całkowite

Nazwa

Wielkość (bajty)

Zakres

short

2

-215÷ 215 - 1, czyli przedział [-32768, 32767]

int

4

-231÷ 231 - 1, czyli przedział [-2147483648, 2147483647]

long

4

-231÷ 231 - 1, czyli przedział [-2147483648, 2147483647]

long long

8

-263÷ 263 - 1, czyli przedział [-9223372036854775808, 9223372036854775807]

unsigned short

2

0 ÷ 216 - 1, czyli przedział [0, 65535]

unsigned int

4

0 ÷ 232 - 1, czyli przedział [0, 4294967295]

unsigned long

4

0 ÷ 232 - 1, czyli przedział [0, 4294967295]

unsigned long long

8

0 ÷ 264 - 1, czyli przedział [0, 18446744073709551615]

 

Warto zauważyć, że po dodaniu słowa kluczowego unsigned (bez znaku), wartości zmiennych stają się nieujemne i podwojony zostaje prawy zakres.

 

Uwaga!!! Dane w tabeli określają typy zdefiniowane w kompilatorze 32 bitowym Dev C++. W kompilatorach 64 bitowych, zakresy niektórych zmiennych będą większe.

 

Patrząc na dane w tabeli, łatwo jest dostosować dany typ do potrzeb programu. Gdy orientujemy się jakich wielkości będziemy używać (jak duże będą liczby w naszym programie), dobieramy optymalny typ.

Typ rzeczywisty - przechowuje liczby zmiennoprzecinkowe. Gdy mamy zamiar w naszym programie wykorzystać ułamki, ten typ będzie najbardziej odpowiedni. Wyróżniamy następujące typy:

 

Typy rzeczywiste

Nazwa

float

double

long double

Uwaga!!! Dane w tabeli określają typy zdefiniowane w kompilatorze 32 bitowym Dev C++. W kompilatorach 64 bitowych, zakresy niektórych zmiennych będą większe.

 

Typ znakowy - przechowuje znaki, które są kodowane kodem ASCII. Tzn. znak w pamięci nie może być przechowany jako znak, tylko jako pewna liczba. Dlatego każdy znak ma swój odpowiednik liczbowy z zakresu [0, 255], który nazywamykodem ASCII. I na przykład litera "d" ma wartość 100, "!" = 33, itd.:

 

Typ znakowy

Nazwa

char

unsigned char

 

Popatrzmy jeszcze na operację przypisania stosowaną na zmiennych typu znakowego:

 

char znak;//stworzenie zmiennej znakowej o nazwie "znak"

char litera = 'w'; //stworzenie zmiennej znakowej o nazwie "litera" i przypisanie

//do niej znaku "w". Zauważmy, że znaki wpisujemy w pojedynczym

// apostrofie.

char q = 'q'; // jak wyżej

char p = q; // przypisanie do zmiennej znakowej "q" wartość zmiennej "p", czyli znak "q"

 

 

Typ logiczny - przechowuje jedną z dwóch wartości - true (prawda) albo false (fałsz). Wartość logiczna true jest równa 1, natomiast false ma wartość 0.

 

Typ logiczny

Nazwa

bool

 

Dla zmiennych tego typu  możemy realizować przypisanie na dwa sposoby, podając wartość true lub fałsz, albo 1 lub 0.

 

bool a = true; //nadanie wartości true dla zmiennej "a"

bool b = 1; //przypisanie wartości 1 (czyli true) dla zmiennej "b"

int x = 3, y = 11;

bool c = x == y; //zmiennej "c" zostanie nadana wartość false, ponieważ warunek jest fałszywy

// wartość zmiennej "x" nie jest przecież równa wartości zmiennej "y"

bool d = x <= y; //zmiennej "d" zostanie nadana wartość true, ponieważ warunek jest prawdziwy

// wartość zmiennej "x" jest przecież mniejsza od wartości zmiennej "y"

 

Zmienne lokalne i zmienne globalne

Programowanie w języku C++ opiera się na idei korzystania ze zmiennych lokalnych. Zmienne tego typy "widoczne" są tylko w określonym bloku. Dzięki temu nie ma niebezpieczeństwa omyłkowego przypisania wartości zmiennej, która jest wykorzystywana w innym miejscu. Prześledźmy przykłady:

 

Przykład 1

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

void funkcja()

{

int a = 3; //zmienna lokalna "widoczna" tylko w funkcji funkcja(), ale nie w main()

}

 

int main()

{

 

int a = 4; //zmienna lokalna "widoczna" w całej funkcji main()

 

system("pause");

return 0;

 

}

 

 

Zmienne lokalne można także tworzyć w mniejszych blokach ograniczonych nawiasami klamrowymi:

 

Przykład 2

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

int main()

{

 

int a = 3;

 

{ //początek widoczności zmiennej "b"

 

int b = 4; //zmienna lokalna widoczna tylko w bloku ograniczonym klamrami

 

} //koniec widoczności zmiennej "b"

 

if(a < b)

{

 

int s; //zmienna s jest widoczna tylko w bloku instrukcji if

 

}

 

for(int i = 1/*zmienna i widoczna tylko w instrukcji for*/;i < b;i++)

{

 

int s = i; //zmienna s widoczna tylko w bloku instrukcji for

cout<<i<<" ";

 

}

 

system("pause");

return 0;

}

 

 

Ważność zmiennych o tych samych nazwach jest nadpisywana zgodnie z następującą zasadą:

 

Przykład 3

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

int main()

{

 

int a = 5 /*zmienna "a" jest zmienną lokalną w całej funkcji main()

ale nie jest widoczna w bloku należącym do if*/

 

, b = 4;

 

if(b == 4)

{

 

int a = 4; // zmienna ta jest "widoczna" w bloku if, ale

// nie jest widoczna w podbloku

 

{ //podblok

 

int a = 3; //zmienna ta jest widoczna tylko w tym podbloku,

//w tym miejscu tracą ważność wszystkie zmienne lokalne o nazwie "a", które zostały

//stworzone powyżej

cout<<a; //wyświetlenie wartości 3

 

} //koniec podbloku

 

cout<<a; //wyświetlenie wartości 4

 

}

 

 

cout<<a; // wyświetlenie wartości 5

 

system("PAUSE");

return EXIT_SUCCESS;

}

 

 

Zmienne globalne widoczne są w każdym "zakątku" programu. Oznacza to, że można z niej korzystać w każdym miejscu programu. Trzeba jedna zwrócić uwagę, że przy tworzeniu tego typu zmiennych, istnieje niebezpieczeństwo przypadkowego nadpisania jej wartości, co może spowodować nieprawidłowe działanie programu. Dlatego zaleca się korzystanie ze zmiennych lokalnych.

Zmienne globalne deklarujemy przed blokiem funkcji main():

 

Przykład 1

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

int globalna = 3; //zmienna globalna

 

void f()

{

cout<<globalna; //zmienna globalna

}

 

int main()

{

cout<<globalna; //zmienna globalna

 

f(); //wywołanie funkcji f()

 

cout<<endl;

 

system("pause");

return 0;

}

 

 

Można nadpisywać zmienne globalne, tworząc zmienną lokalną o takiej samej nazwie:

Przykład 2

#include <cstdlib>

#include <iostream>

using namespace std;

 

int globalna = 3; //zmienna globalna

 

void f()

{

int globalna = 5; //nadpisanie zmiennej globalnej i stworzenie lokalnej o takiej samej nazwie

cout<<globalna<<endl; //zmienna lokalna "widoczna" tylko w funkcji f()

}

 

int main()

{

 

f();

cout<<globalna; //zmienna globalna

cout<<ndl;

 

system("pause");

return 0;

}

 

 

Rzutowanie typów

Rzutowanie typów polega na konwersji jednego typu na drugi. W przypadku konwersji liczby typu rzeczywistego na całkowitą, obetniemy część ułamkową liczby rzeczywistej, w przypadku konwersji char -> int, zamiast znaku będziemy mieli do dyspozycji liczbę (czyli kod ASCII), pod jaką kryje się dany znak itd..

Konwersję typów wykonuje się według dwóch równoważnych zasad:

Przykład 1

float a = 3.34;

//sposób pierwszy

int b = (int) a; //rzutowanie zmiennej rzeczywistej na typ całkowity - zmienna "b" przyjmie wartość 3

//zauważmy, że rzutowanie wykonujemy poprzez wpisanie w nawiasie typu na który

//chcemy przekonwertować

//sposób drugi

int c = int (a); //zauważmy, że rzutowanie wykonujemy poprzez wpisanie w nawiasie zmiennej,

//a przed nią typ, na który rzutujemy

char znak = 'q';

cout<<znak<<": "<<(int)znak; //wyświetlenie kodu ASCII znaku q

Stałe w C++

powrót

W przypadku zmiennych (jak sama nazwa wskazuje), wartości mogą się zmieniać podczas działania programu. Gdy tworzymy stałą, musimy jej nadać wartość początkową, która przez cały okres działania programu nie może zmienić swojej wartości. W C++ mamy do dyspozycji dwie metody tworzenia zmiennych:

Pierwsza przestarzała metoda wykorzystuje preprocesor:

Przykład 1

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

#define moja_stala 24

 

int main()

{

int a = 3*moja_stala;

cout<<a<<endl;

cout<<moja_stala<<endl;

 

system("PAUSE");

return EXIT_SUCCESS;

}

 

Skoro #define jest dyrektywą preprocesora, więc tworzenie stałej jest wykonane jeszcze przed kompilacją. Oznacza to, że każdy ciąg tekstu moja_stala zostanie zamieniona na liczbę 24,  a więc kompilator nie widzi już ciągu tekstu tylko samą liczbę. Warto także zauważyć, że nie podajemy typu stałej.

Definiowanie stałych za pomocą słowa kluczowego const wygląda następująco:

const [typ_stałejnazwa_stałej = wartość;

 

Przykład 2

const int a = 34; //definicja stałej całkowitej o nazwie "a"

const float pi = 3.14;

const unsigned long long b = 9090000000;

const char znak = 't'; //definicja stałej znakowej

 

 

Pamiętajmy, że stała musi mieć nadaną wartość i nie wolno tej wartości nadpisywać. Tworząc stałą mamy pewność, że przez przypadek nie zmienimy jej wartości.

Kody ASCII

ASCII (American Standard Code for Information Interchange ) to kod liczbowy, który jest przyporządkowany każdemu znaku. W C++ litery, cyfry, znaki przystankowe, znaki niedrukowalne takie jak spacja czy enter przechowujemy w zmiennej typu char, mamy wtedy do dyspozycji znaki z przedziału [0; 127], oraz w rozszerzonym typie unsigned char, tu znaki zawierają się w przedziale[0; 255].

Operator sizeof

W sytuacji, gdy nie jesteśmy pewni ile pamięci zajmuje typ wbudowany w strukturę C++ lub typ stworzony przez użytkownika (struktura, klasa), możemy skorzystać z operatora sizeof. Wyraz ten jest słowem kluczowym w C++, a więc nie może być używany jako nazwa zmiennych.

Prześledźmy kilka przykładów.

Sprawdźmy, ile pamięci zajmują podstawowe typy zmiennych w moim kompilatorze:

 

#include<iostream>

#include<cstdlib>

using namespace std;

 

int main()

{

int a;

 

cout<<"Typ int zajmuje "<<sizeof(int)<<"b\n";

cout<<"Można też sprawdzić podając nazwę zmiennej "<<sizeof(a)<<"b\n";

 

int tab[1000];

cout<<"Ta tablica zajmuje "<<sizeof(tab)<<"b\n";

 

system("pause");

return 0;

}

 

 

Out:

Typ int zajmuje 4b
Można też sprawdzić podając nazwę zmiennej 4b
Ta tablica zajmuje 4000b
Aby kontynuować, naciśnij dowolny klawisz . . .

 

 Możemy sprawdzić wielkość obiektów strukturalnych i klas:

#include<iostream>

#include<cstdlib>

using namespace std;

 

struct struktura{

int x;

char tab[100];

bool b;

};

 

class klasa{

public:

int metoda(int x)

{

return x+x*x;

}

private:

int a, b;

};

 

int main()

{

struktura moja;

klasa twoja;

 

cout<<"Powyższa struktura zajmuje "<<sizeof(moja)<<"b\n";

cout<<"Powyższa klasa zajmuje "<<sizeof(twoja)<<"b\n";

 

system("pause");

return 0;

}

 

 

Out: 

Powyższa struktura zajmuje 108b
Powyższa klasa zajmuje 8b
Aby kontynuować, naciśnij dowolny klawisz . . .

 

Operator staje się przydatny w sytuacjach, w których należy przydzielić pamięć dynamicznie za pomocą funkcji, w których wymagane jest podanie wielkości obiektu. 

Instrukcja warunkowa (if-else)

Instrukcja warunkowa bez alternatywy

Instrukcja warunkowa bez alternatywy ma postać:

if(warunek (warunki))

{ //początek bloku należącego do if

//instrukcje zostaną wykonane, gdy warunek (warunki) jest prawdziwy

 

//w przeciwnym wypadku ta część kodu zostanie pominięta

} //koniec bloku należącego do if

Przykład 1

#include <iostream>

using namespace std;

 

int main()

{

int a = 3, b = 6;

 

if(a > b) //warunek jest fałszywy, a więc poniższa instrukcja

//zostanie pominięta

cout<<a<<" jest większe od "<<b<<endl;

 

if(b > a) //warunek jest prawdziwy, a więc wykona się instrukcja

//należąca do bloku if

cout<<b<<" jest większe od "<<a<<endl;

 

return 0;

}

 

 

Zasada działania instrukcji warunkowej if opiera się na wartości, jaką przyjmuje warunek. W przypadku true (1), instrukcje dla bloku if zostaną wykonane. To oznacza, że w przypadku zmiennych logicznych (i w niektórych przypadkach) można użyć skróconej konstrukcji warunku:

 

Przykład 2

bool zmienna = true;

 

//zamiast pisać

if(zmienna == true) //w tym przypadku wartość warunku

//jest równa true, czyli wykona

cout<<"Wartość zmiennej jest równa true !";

//się instrukcja należąca do bloku if

 

//można skrócić zapis

 

if(zmienna) //w tym przypadku wartość nawiasu

//też jest równa true, czyli tu także zostanie wykonana instrukcja poniżej

cout<<"Wartość zmiennej jest równa true !";

 

 

 

Gdy do bloku if należy tylko jedna instrukcja, klamrę otwierającą blok i klamrę zamykającą blok można opuścić.

 

Instrukcja warunkowa z alternatywą

W Instrukcja warunkowa z alternatywą pojawia się drugi blok instrukcji rozpoczynany słowem kluczowym else, wykonany w przypadku, gdy warunek (warunki) jest fałszywy. Warto zauważyć, że w tym przypadku wykona się zawsze jeden z dwóch bloków:

if(warunek (warunki))

{ //początek bloku należącego do if

 

//instrukcje zostaną wykonane, gdy warunek (warunki) jest prawdziwy

 

//w przeciwnym wypadku część kodu należąca do bloku ifzostanie pominięta

} //koniec bloku należącego do if

else //do tego bloku należą instrukcje, które zostaną wywołane w przypadku, gdy warunek dla if będzie fałszywy

{ //początek bloku należącego do else

 

//instrukcje zostaną wykonane, gdy warunek (warunki) jest fałszywy

 

//w przeciwnym wypadku część kodu należąca do bloku elsezostanie pominięta

} //koniec bloku należącego do else

Prześledźmy przykład, który sprawdzi, czy podana osoba jest pełnoletnia:

Przykład 3

#include <iostream>

using namespace std;

 

int main()

{

unsigned int wiek;

 

cout<<"Podaj wiek: ";

cin>>wiek;

 

if(wiek>=18)

//ta część wykona się gdy warunek będzie prawdziwy

cout<<"Podana osoba jest pełnoletnia"<<endl;

else

//ta część wykona się gdy warunek będzie fałszywy

cout<<"Podana osoba jest nieletnia"<<endl;

 

return 0;

}

 

Warunki proste i złożone

Konstrukcja warunków instrukcji if może być prosta - złożona z jednego warunku, lub złożona z kilku warunków połączonych operatorami logicznymi. Rozpatrzmy przykład, który sprawdzi, czy dana liczba naturalna jest podzielna przez 3 lub reszta z dzielenia tej liczby przez 7 jest równa 2:

Przykład 1 - realizacja zadania z wykorzystaniem  warunków prostych

#include <iostream>

using namespace std;

 

int main()

{

unsigned int liczba;

 

cout<<"Podaj liczbę: ";

cin>>liczba;

 

if(liczba%3==0) // warunek prosty

if(liczba%7==2) // warunek prosty

cout<<"Liczba "<<liczba<<" spełnia warunki zadania."<<endl;

else

cout<<"Liczba "<<liczba<<" nie spełnia warunki zadania."<<endl;

else

cout<<"Liczba "<<liczba<<" nie spełnia warunki zadania."<<endl;

 

return 0;

}

 

 

To samo zadanie zrealizowane warunkami złożonymi:

Przykład 2

#include <iostream>

using namespace std;

 

int main()

{

unsigned int liczba;

 

cout<<"Podaj liczbę: ";

cin>>liczba;

 

// warunek złożony z dwóch warunków prostych

if(liczba%3==0 &amp;&amp; liczba%7==2)

cout<<"Liczba "<<liczba<<" spełnia warunki zadania."<<endl;

else

cout<<"Liczba "<<liczba<<" nie spełnia warunki zadania."<<endl;

 

return 0;

}

 

 

Zagnieżdżona instrukcja warunkowa

Zagnieżdżenie instrukcji warunkowej polega na wywołaniu jej wewnątrz innej instrukcji warunkowej. W C++ można dowolnie zagnieżdżać if - else, pamiętając o tym, aby kod był czytelny. Stosuje się tu wcięcia określający kolejne poziomy zagnieżdżenia.

 Dla przykładu rozwiążmy zadanie:

Zadanie. Tabela poniżej określa taryfikator mandatów w pewnym państwie:

Wykroczenie

Wysokość mandatu

Przekroczenie prędkości do 10 km

100

Przekroczenie prędkości z przedziału [11, 30]

200

Przekroczenie prędkości > 30

400

 Użytkownik podaje liczbę km, jaką przekroczył kierowca. Zadaniem programu jest określenie wysokości mandatu.

Rozwiązanie:

#include <iostream>

using namespace std;

 

int main()

{

cout<<"Podaj wartość przekroczonej predkości: ";

unsigned int km;

cin>>km;

 

if(km<=10)

cout<<"Wartość mandatu wynosi 100";

else //dla czytelności kodu stosujemy odpowiednie wcięcia

 

if(km<=30) //zagnieżdżona instrukcja if

cout<<"Wartość madatu wynosi 200";

else

cout<<"Wartość mandatu wynosi 400";

 

cout<<endl;

return 0;

}

 

Instrukcja wielokrotnego wyboru switch case

Wyobraźmy sobie sytuację, że mamy do napisania program, który będzie wykonywał pewną czynność  zależną od wybranej opcji. Zadanie to można zrealizować za pomocą instrukcji warunkowej, ale może to być dość uciążliwe.  Z pomocą idzie nam instrukcja wielokrotnego wyboru. Konstrukcja jest następująca:

switch(opcja)  // ta część przełącza nas w odpowiednie miejsce,

// w zależności jaką wartość ma zmienna opcja

{ //klamra otwierająca blok switch

case etykieta1:

instrukcje dla opcji o wartości etykieta1

break; // to słowo kluczowe przerywa dalsze wykonywanie instrukcji i wychodzi z bloku switch

case etykieta2:

instrukcje dla opcji o wartości etykieta2

break;

......................

case etykietan:

instrukcje dla opcji o wartości etykietan

break;

default: // ta część instrukcji switch jest opcjonalna

instrukcje, które wykonają się w przypadku, gdy podana opcja nie istnieje

 

} //klamra zamykająca blok switch

Zasada działania jest bardzo prosta. Tworzymy zmienną (w przykładzie powyżej jest to zmienna opcja), której nadajemy wartość. Instrukcja switch przekieruje nas w odpowiednie miejsce, gdzie wartość zmiennej jest równa wartości etykiety. Można opcjonalnie dodać alternatywę w postaci słowa kluczowego default. Gdy dana etykieta nie istnieje, program przeskoczy właśnie w to miejsce.

Jeśli chcemy zmienić zasięg zmiennych, można wstawić nawiasy klamrowe:

........

case etykietan:

{

instrukcje dla opcji o wartości etykietan

break;

}

........

Słowo kluczowe break przerywa działanie instrukcji switch. Jeśli jednak chcemy, aby kilka etykiet zostało podpiętych pod jeden ciąg instrukcji, wtedy omijamy słowobreak. Prześledźmy przykład:

 

Zadanie. Podajemy dzień tygodnia, program określa, czy jest to dzień roboczy, czy weekend.

Rozwiązanie:

#include <iostream>

using namespace std;

 

int main()

{

unsigned short dzien;

 

cout<<"Podaj nr dnia tygodnia: ";

cin>>dzien;

 

switch(dzien)

{

case 1:

case 2:

case 3:

case 4:

case 5:

cout<<"Podany dzień jest dniem roboczym";

break;

case 6:

case 7:

cout<<"Mamy weekend";

break;

default:

cout<<"Nie ma takiego dnia";

}

 

return 0;

}

 

 

Przeanalizujmy powyższy program. Gdy np. zmienna dzien przyjmie wartość 2, to instrukcja switch przełączy nas w miejsce "case 2:", a następnie będzie przeskakiwać w dół do momentu napotkania słowa kluczowego break. W tym przypadku zatrzyma się dopiero po instrukcjach dla "case 5:".

 

Instrukcja iteracyjna (pętle)

Zacznijmy od wyjaśnienia pojęcia instrukcji iteracyjnej, nazywanej także pętlą. Instrukcja ta polega na powtarzaniu pewnego ciągu instrukcji skończoną ilość razy. Oczywiście dany ciąg instrukcji można powtórzyć, bez korzystania z pętli, ale wyobraźmy sobie sytuacje, w której chcemy wyświetlić milion liczb. Wypisanie instrukcji, które wyświetliły by te liczby zajęłoby nam bardzo dużo czasu:

 

załóżmy, że na stworzenie instrukcji wypisującej 10 liczb, potrzebujemy 10 sekund, a więc dla miliona liczb potrzebujemy 100 000 sekund, czyli około 1667 minut = około 28 godzin, czyli nieco ponad doba. No cóż, trochę żmudne zadanie.

 

Dzięki instrukcjom iteracyjnym wykorzystujemy moc obliczeniową procesora, dzięki czemu na wykonanie powyższego zadania potrzebujemy kilkadziesiąt sekund.

 

Licznik pętli to pewna zmienna, która kontroluje zachowanie się pętli, określa ona ile razy zostanie powtórzony dany ciąg instrukcji. Nazwy liczników pętli najczęściej oznaczamy małymi literami: "i", "j", "k", .....

Pamiętaj, że licznik określa przejścia pętli, czyli musi to być liczba całkowita. Pętla nie może przecież wykonać się np. 2,2 razy!

 

Instrukcja iteracyjna for

Instrukcja iteracyjna for jest bardzo elastyczną instrukcją (tak jak cała struktura języka C++). Złożona jest  z trzech części oddzielonych średnikami:

for(część a; część b; część c)

{

//blok instrukcji, który będzie powtarzany

}

W części pierwszej najczęściej inicjujemy licznik lub liczniki pętli for. Inicjacja licznika, w tym przypadku polega na stworzeniu i nadaniu wartości początkowej. Popatrzmy na przykład, gdzie "i" jest licznikiem:

//inicjalizajca jednego licznika

for(int i = 0; i < 100 ; i++)

{

//ciąg instrukcji

}

//inicjalizacja dwóch liczników - separatorem jest znak przecinka

for(int i = 0, j = 10; i < j; i ++, j --)

{

//ciąg instrukcji

}

 

W części drugiej definiujemy warunek, lub warunki, które wpływają na ilość powtórzeń pętli. Instrukcja for będzie powtarzać ciąg instrukcji tak długo, jak długo zdefiniowane warunki będą  prawdziwe.

Źle sformułowane warunki mogą spowodować zapętlenie instrukcji for, to znaczy, że pętla będzie wykonywać się w nieskończoność. Np:

for(int i = 5; i > 0; i ++ )

{

//ta pętla nigdy się nie zakończy, ponieważ warunek będzie zawsze prawdziwy

}

 

Część c określa operacje (najczęściej na liczniku), od których zależy zachowanie się pętli. Jeśli chcemy, aby licznik zwiększał się o jeden, wtedy ustawiamy operację w trzeciej części na i++.

 

Dla przykładu zrealizujmy następujące zadanie:

 

Przykład. Wyświetl n kolejnych liczb parzystych, gdzie n podajemy z klawiatury.

Rozwiązanie - sposób pierwszy

#include <cstdlib>
#include <iostream>

using namespace std;

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

unsigned

int n;
cout<<"Podaj ile liczb parzystych chcesz wyświetlić: ";
cin>>n;

//licznik "i" kontroluje ilość wykonań pętli - pętla musi wyświetlić "n" liczb parzystych,

//przy każdym przejściu pętli zwiększamy "i" o 1 (i++),

//licznik "j" przechowuje kolejne liczby parzyste, dlatego w tym przypadku zwiększenie

//jest o 2 (i+=2)

for(int i = 0, j=0; i < n; i++, j+=2)

cout<<j<<" "; //przy każdym przejściu pętli wyświetlamy wartość licznika "j"

cout<<endl; 

system("PAUSE");
return EXIT_SUCCESS;
}

Rozwiązanie - sposób drugi

#include <cstdlib>
#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
unsigned int n;
cout<<"Podaj ile liczb parzystych chcesz wyświetlić: ";
cin>>n;

for(int i = 0; i<2*n; i++) // gdybyśmy sformułowali warunek i<2, to
//program wyświetliłby o połowę mniej liczb 
if(i%2==0) //lub if(!(i%2))

cout<<i<<" ";

cout<<endl;
system("PAUSE");
return EXIT_SUCCESS;
}

 

Konstrukcja pętli for dopuszcza sytuację, w której, jakaś część (a, b lub c) pętli może być pusta np.:

 

int i = 0;

for(; i < 10; ) // w tym przypadku część a i c są puste

{

i+=2;

cout<<i-1;

}

 

 

Zauważmy, że inicjacja licznika następuje przed pętlą for (wtedy ma ona większy zasięg - patrz. zasięg zmiennych), natomiast operacje na liczniku wykonywane są w bloku należącym do for.

Więcej przykładów w części - zadania.

Pętla while

Konstrukcja instrukcji iteracyjnej while jest znacznie prostsza od pętli for. Wyróżniamy tu tylko jedną część, w której definiujemy warunek lub warunki, od których zależy wykonywanie się pętli.

Pętla while wykonuje się tak długo, jak długo prawdziwy jest warunek (warunki)

 

Część inicjacyjną licznika tworzy się najczęściej przed blokiem pętli, natomiast część operacyjna jest wykonywana wewnątrz pętli. Popatrzmy na konstrukcję:

while(warunki)

{ //początek bloku pętli while

//instrukcje powtarzane przez pętlę

} //koniec bloku pętli while

 

Warto także zauważyć, że pętla może nie uruchomić się ani razu (gdy od samego początku warunek (warunki) będzie fałszywy).

Należy także zwrócić uwagę, że niepoprawna konstrukcja pętli może spowodować jej zapętlenie (wykonywanie bez końca).

Prześledźmy przykład ilustrujący działanie pętli while:

Przykład. Napisz program, który wyświetli n początkowych liczb naturalnych.

Rozwiązanie

#include <cstdlib>
#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
int n;
cout<<"Podaj ilość kolejnych liczb naturalnych do wyświetlenia: ";
cin>>n;
int i=0; //inicjacjalicznika

while(i<n)
{

cout<<i<<" ";
i++; //operacja na liczniku

}

cout<<endl;

system("PAUSE");
return EXIT_SUCCESS;
}

Instrukcja do..while

Konstrukcja pętli do..while jest podobna do pętli while. Instrukcja ta wykonuje się tak długo, jak długo prawdziwy będzie warunek (warunki). Różnica jest taka, że ta pętla zawsze wykona się co najmniej raz, ponieważ warunek jest sprawdzany po wykonaniu instrukcji należących do bloku do..while.

Konstrukcja pętli do..while

do{

//instrukcje powtarzane przez pętlę

}while(warunki);

 

Przykład. Napisz program, który pobiera liczby rzeczywiste tak długo, jak długo są one dodatnie, oraz wyznacza ich sumę.

Rozwiązanie

#include<cstdlib>
#include<iostream>

using namespace std;

int main(int argc, char *argv[])
{
float liczba,suma = 0;
do{

cout<<"Podaj liczbę: ";
cin>>liczba;

if(liczba>0)

suma+=liczba;

 

}while(liczba>0); //pętla się wykonuje, dopóki zmienna "liczba" będzie miała dodatnią wartość

cout<<"Suma podanych liczb wynosi "<<suma<<"."<<endl; 

system("PAUSE");
return EXIT_SUCCESS;
}

Zagnieżdżone instrukcje iteracyjne

powrót

Zagnieżdżenie pętli, podobnie jak instrukcji warunkowej, polega na wywołaniu jednej pętli wewnątrz drugiej. Oznacza to, że na jedną iterację pętli zewnętrznej, zostanie wykonany cały przebieg pętli wewnętrznej. Instrukcje iteracyjne w C++ można dowolnie zagnieżdżać. Im więcej zagnieżdżeń tym większa złożoność obliczeniowa algorytmu.

Zagnieżdżenie pętli for

for(inicjacja; warunki; operacje) // pętla zewnętrzna

{

// instrukcje pętli zewnętrznej

for(inicjacja; warunki; operacje) // pętla wewnętrzna

{

//instrukcje powtarzane przez pętlę wewnętrzną

}

// instrukcje pętli zewnętrznej

}

 

 

Przykład. Napisz program, który wyświetli wszystkie liczby trzycyfrowe o niepowtarzających się cyfrach oraz określi ilość takich liczb.

Rozwiązanie z wykorzystaniem zagnieżdżenia pętli for

#include<cstdlib>
#include<iostream>

using namespace std;

int main(int argc, char *argv[])
{
int ile = 0; //zmienna podliczający ilość liczb

for(int i=1; i<=9; i++) //pętla odpowiedzialna za cyfrę setek

for(int j=0; j<=9; j++) //pętla odpowiedzialna za cyfrę dziesiatek

for(int k=0; k<=9; k++) //pętla odpowiedzialna za cyfrę jedności

if(i!=j && i!=k && j!=k)
{

cout<<i<<j<<k<<"_";
ile++;

}

cout<<endl<<"Liczb trzycyfrowych o niepowtarzających się cyfrach jest "<<ile<<endl; 
system("pause");

return 0;
}

 

Rozwiązanie z wykorzystaniem zagnieżdżenia pętli while

#include <cstdlib>
#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
int ile = 0, i = 1, j = 0, k = 0; //zmienna podliczający ilość liczb

while(i<=9) //pętla odpowiedzialna za cyfrę setek
{

while(j<=9) //pętla odpowiedzialna za cyfrę dziesiatek
{

while(k<=9) //pętla odpowiedzialna za cyfrę jedności
{

if(i!=j && i!=k && j!=k)
{

cout<<i<<j<<k<<" ";
ile++;

}
k++;

}
k=0;
j++;

}
j=0;
i++;

}
cout<<endl<<"Liczb trzycyfrowych o niepowtarzających się cyfrach jest "<<ile<<endl; 
system("pause");

return 0;
}

Skok bezwarunkowy goto

powrót

Skok bezwarunkowy goto jest najstarszym rodzajem pętli, który został wyparty przez pętlę for i pętlę while. Do konstrukcji pętli goto potrzebna jest etykieta, czyli takie słowo, które jest zakończone znakiem dwukropka (:). Etykieta musi występować w linii jako pierwsza. Program gdy napotka słowo kluczowe goto i nazwę tejże etykiety, przemieszcza się bezwarunkowo w miejsce gdzie  została ona zadeklarowana. 

Nie jest wskazane stosowanie tego rodzaju pętli, ponieważ kod staje się nieczytelny, trudny do analizy, oraz łamie ideę programowania strukturalnego.

Przykład. Napisz program, który  wyznaczy sumę n kolejnych liczb naturalnych.

Rozwiązanie

#include<cstdlib>
#include<iostream>

usingnamespace std;

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

unsigned int i = 0, suma = 0,n;

cout<<"Podaj ilość liczb do policzenia: ";
cin>>n;

licz:

suma+=i;

if(i==n)

goto koniec;

else
{

i++; 
goto licz;

}

koniec:

cout<<"Suma "<<n<<"-kolejnych liczb naturalnych wynosi: "<<suma<<endl;

system("PAUSE");
return EXIT_SUCCESS;
}

Break i continue

powrót

W instrukcjach iteracyjnych słowo kluczowe break bezwarunkowo przerywa działanie pętli (wychodzi z tej pętli). Jeśli dalsze wykonywanie pętli nie ma sensu, przerywamy jej działanie właśnie tą instrukcją. Prześledźmy przykład.

Przykład. Napisz program, który stwierdzi, czy podana liczba naturalna dodatnia n posiada co najmniej jeden dzielnik z przedziału domkniętego

[2;n−−√]

Rozwiązanie

#include <cstdlib>
#include <iostream>
#include <math.h>

using namespace std;

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

unsigned int n, i;
cout<<"Podaj liczbę: ";
cin>>n;

int pierw = (int) sqrt(n);

for(i = 2; i<=pierw; i++)

if(n%i==0) //jeśli znajdziemy dzielnik, to nie ma sensu dalszego wykonywania działania pętli

break; //przerwanie działania pętli for

if(i>pierw) //jesli licznik "i" przekroczył wartość zmiennej "pierw", tzn., że nie znalazł pierwiastka

cout<<"Liczba "<<n<<" nie posiada dzielników z zadanego przedziału"<<endl;

else

cout<<"Liczba "<<n<<" posiada dzielniki z zadanego przedziału"<<endl;

system("PAUSE");
return EXIT_SUCCESS;
}

 

Słowo kluczowe continue, powoduje wznowienie działania pętli i zaprzestanie wykonywania dalszych instrukcji dla danego powtórzenia. Stosuje się je w przypadku, gdy w danej iteracji pętli nie ma konieczności wykonania dalszych instrukcji pętli. Popatrzmy na przykład:

Przykład. Napisz program, który wyświetli wszystkie trzycyfrowe palindromy, w których środkowa cyfra jest równa zero lub pięć.

 

Rozwiązanie

#include <cstdlib>

#include <iostream>

using namespace std;

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

for(int i = 100; i<1000; i++)

{

if((i/10)%10 != 0 && (i/10)%10 != 5) //sprawdzenie czy środkowa cyfra jest różna od 0 i od 5

continue; // jeśli tak, to nie ma sensu wykonywania dalszych instrukcji dla danej iteracji

if(i%10 == i/100)

cout<<i<<" ";

}
cout<<endl;

system("PAUSE");
return EXIT_SUCCESS;
}

Tworzenie i wywoływanie funkcji w C++

powrót

Funkcje w C++ możemy tworzyć na kilka sposobów:

W artykule dodatkowo zostanie opisany sposób wywoływania funkcji oraz przykłady wywołania.

Między bibliotekami a funkcją main()

Rozpatrzmy pierwszy sposób tworzenia funkcji:

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

//definicja funkcji wraz z jej ciałem

 

typ_funkcji nazwa_funkcji(lista argumentów)

{

//ciało funkcji

return wartość;

}

 

 

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

{

//instrukcje funkcji main

system("PAUSE");

return EXIT_SUCCESS;

}

 

 

Całą funkcję wraz z jej instrukcjami tworzymy przed główną funkcją main().

 

Deklaracja samych prototypów przed funkcją main()

Drugi sposób polega na wstawieniu przed funkcją main() prototypów funkcji, natomiast ciała tych funkcji są zaimplementowane poniżej funkcji main():

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

//definicja funkcji wraz z jej ciałem

 

typ_funkcji1 nazwa_funkcji(lista argumentów);

//przy tworzeniu prototypów wstawiamy

typ_funkcji2 nazwa_funkcji_drugiej(lista argumentów);

...

//średnik na końcu funkcji

 

 

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

{

 

//instrukcje funkcji main

system("PAUSE");

return EXIT_SUCCESS;

}

 

//implementacje funkcji zadeklarowanych powyżej

typ_funkcji1 nazwa_funkcji(lista argumentów)

{

 

//instrukcje funkcji

return wartość;

}

 

//implementacja funkcji zadeklarowanej powyżej

typ_funkcji2 nazwa_funkcji_drugiej(lista argumentów)

{

 

//instrukcje funkcji

return wartość;

}

 

 

Taki sposób tworzenia funkcji jest wygodny, w przypadku gdy w programie mamy ich większą ilość. Aby sterować naszą aplikacją nie musimy przedostawać się przez gąszcz funkcji, tylko przez same ich prototypy.

W oddzielnej bibliotece

Ostatni sposób polega na implementacji naszych funkcji w oddzielnym pliku nazywanym biblioteką. Plik ten posiada rozszerzenie *.h Aby korzystać z napisanych funkcji, trzeba dodać napisaną bibliotekę do nagłówka programu. Prześledźmy przykład:

Załóżmy, że nasza biblioteka ma nazwę biblioteka.h

biblioteka.h

#ifndef biblioteka_h

#define biblioteka_h

//w tej części definiujemy i implementujemy nasze funkcje

typ_funkcji nazwa(lista argumentów)

{

return wartość;

}

 

typ_funkcji druga_funkcja(lista argumentów)

{

//instrukcje funkcji

return wartość;

}

...

#endif

 

 

 

Aby można było korzystać z własnej biblioteki, musisz ją dodać do projektu programu. Gdy dodajesz swoja bibliotekę do programu, musisz zapisać jej ścieżkę względną w nagłowku programu w podójnym cudzysłowiu np.:

#include "biblioteka.h"

 

Zauważ, że pojawiły się dodatkowe dyrektywy:

#ifndef biblioteka_h
#define biblioteka_h

...

#endif

Podczas konsolidacji kodu obiektowego, do naszego programu dołączane są pliki zwane bibliotekami. Aby zapobiec wielokrotnemu dodaniu naszej biblioteki do programu, zabezpieczamy się dyrektywą, która sprawdza, czy dana biblioteka nie została już wcześniej dołączona. #ifndef biblioteka_hmożna przetłumaczyć: "jeśli nie zdefiniowano ciągu znaków biblioteka_h", #define biblioteka_h - "zdefiniuj ciąg znaków biblioteka_h" i dołącz nagłówek pliku biblioteka.h do programu, #endif -koniec bloku #ifndef.

Przykład. Napiszmy dwie funkcje:

sposobami omówionymi powyżej.

Sposób 1.

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

float max(float a,float b)

{

if(a>b)

return a;

else

return b;

}

 

void hello()

{

cout<<"Witaj programisto!!!";

}

 

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

{

//wywołanie funkcji max

cout<<"Wartość maksymalna z liczb 5 i 7 wynosi: "<<max(5,7)<<endl;

 

//wywołanie funkcji hello

hello();

 

cout<<endl;

 

system("pause");

return 0;

}

 

 

Sposób 2.

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

//definicja prototypów funkcji max i hello

 

//zauważmy, że wystarczy podać typy argumentów bez ich nazw

float max(float,float);

void hello();

 

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

{

//wywołanie funkcji max

cout<<"Wartość maksymalna z liczb 5 i 7 wynosi: "<<max(5,7)<<endl;

 

//wywołanie funkcji hello

hello();

 

cout<<endl;

 

system("pause");

return 0;

}

 

//pełne definicje funkcji

float max(float a,float b)

{

if(a>b)

return a;

else

return b;

 

}

 

void hello()

{

cout<<"Witaj programisto!!!";

}

 

 

Sposób 3.

biblioteka.h

#ifndef biblioteka_h

#define biblioteka_h

 

float max(float a,float b)

{

if(a>b)

return a;

else

return b;

}

 

void hello()

{

cout<<"Witaj programisto!!!";

}

#endif

 

biblioteka.cpp

#include <cstdlib>

#include <iostream>

using namespace std;

 

#include "biblioteka.h"

 

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

{

float a, b;

cout<<"Podaj dwie liczby: ";

cin>>a>>b;

 

//wywołanie funkcji max, która jest zdefiniowana w bibliotece biblioteka.h

cout<<"Wartość maksymalna liczb "<<a<<" i "<<b<<" wynosi: "<<max(a,b)<<endl;

 

system("pause");

return 0;

}

 

 

Wywoływanie funkcji

Aby wywołać funkcję w C++, należy podać jej nazwę, oraz argumenty (jeśli takie posiada), zwracając uwagę, na zgodność typów argumentów:

jeśli funkcja o nazwie "f" pobiera następujące typy f(int a, char b), oznacza to, że przy uruchomieniu tej funkcji pierwszym argumentem musi być liczba typu int, a drugim argumentem musi być znak np.:

//wywołanie funkcji f

f(123,'a');

lub

int a = 3;

char znak = 'p';

//wywołanie funkcji f

f(a,znak);

 

 

Funkcje niezwracające wartości wywołujemy podając tylko ich nazwę, natomiast funkcje z wartością zwrotną przypisujemy do odpowiednich zmiennych (do zmiennej typu zwracanej wartości), lub kierujemy na wyjście do pliku lub na ekran.

Podprogramy (funkcje) możemy wywoływać w głównej funkcji main() lub z wnętrza innych funkcji, metod, itd..

Prześledźmy przykłady:

Stworzymy dwie funkcje:

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

void sumaA(int a, int b)

{

cout<<"Suma liczb "<<a<<" i "<<b<<" wynosi: "<<a+b<<endl;

}

 

int sumaB(int a,int b)

{

return a+b;

}

 

 

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

{

//wywołanie funkcji typu void - bez zwróconej wartosci

sumaA(2,3);

 

//wywołanie funkcji z informacją zwrotna zapisaną w zmiennej suma

int suma = sumaB(3,6);

 

cout<<"Suma liczb 3 i 6 wynosi: "<<suma<<endl;

 

system("pause");

return 0;

}

 

Funkcja typu void

Funkcja typu void (odpowiednik procedury w PASCALU) nie zwraca żadnych danych, które można byłoby poddać dalszej obróbce. Funkcja tego typu może wykonywać pewne czynności, ale nie przekazuje informacji zwrotnej. Słowo kluczowe void oznacza "pusty", informuje, że nic nie będzie zwracane.

 

Wyobraźmy sobie, że tworzymy funkcję, która będzie czyścić ekran. Zauważmy, że oprócz czyszczenia, żadne dane nie będą przekazywane dalej, czyli ten typ jak najbardziej sie do tego nadaje.

 

Weźmy przykład funkcji wyznaczającej ilość cyfr podanej liczby całkowitej. W tym przypadku tym void się nie sprawdzi, ponieważ informacją zwrotną w tym przypadku będzie ilość cyfr danej liczby.

 

Prześledźmy przykład:

 

Przykład. Napisz funkcję, która jako argument pobierze znak i powieli go 20 razy.

Rozwiązanie

#include <cstdlib>

#include <iostream>

using namespace std;

 

void powiel(char znak)

{

for(int i=0; i<20; i++)

cout<<znak;

 

cout<<endl;

}

 

int main()

{

 

char znak;

 

cout<<"Podaj znak, który chcesz powielić 20 razy: ";

cin>>znak;

 

//wywołanie funkcji powiel(char)

powiel(znak);

 

system("pause");

return 0;

}

 

 

 

 

Zauważmy, że funcje tego typu wywołujemy wypisując tylko jej nazwę z ewentualnymi argumentami (gdy tych argumentów nie ma, podajemy nazwę wywoływanej funkcji z pustym nawiasem np. powiel()).

Funkcja z wartością zwrotną

Wartością zwrotną funkcji jest wyliczona dana, która jest przekazywana do dalszej obróbki. Typ tej danej wypisujemy przed definicją naszej funkcji i jest ona zwracana za pomocą słowa kluczowego return:

typ_wyniku_funkcji nazwa(argumenty)

{

 

//instrukcje funkcji

//..........

 

return wynik;

}

 

 

Rozpatrzmy funkcję, która wyznaczy sumę cyfr zadanej liczby całkowitej. Zwracaną wartością będzie własnie ta suma. Informację tą w postaci wyniku można wykorzystać w dalszej części programu, np. wyświetlić ją na ekranie, sprawdzić czy ta suma jest parzysta lub czy mieści się w danym zakresie.

Przykład. Dla przykładu napiszemy program, który zwróci prawdę, jeśli dana osoba jest pełnoletnia, w przeciwnym razie zwróci fałsz.

Rozwiązanie

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

bool czy_peln(unsigned short wiek)

{

if(wiek<18)

return false;

else

return true;

}

 

int main()

{

 

unsigned short wiek;

cout<<"Podaj wiek: ";

cin>>wiek;

 

//funkcja czy_peln zwróci prawdę lub fałsz,

//równoważny zapis warunku czy_peln(wiel)==true

if(czy_peln(wiek))

cout<<"Jesteś osobą pełnoletnią!"<<endl;

else

cout<<"Nie jesteś osobą pełnoletnią!"<<endl;

 

//wartość zwracanej wartosci można na przykład wyświetlić,

//lub przypisać do zmiennej

cout<<"Funkcja zwróciła wartość: "<<czy_peln(wiek)<<endl;

 

//przypisanie zwróconej wartości do zmiennej "x"

bool x = czy_peln(wiek);

 

system("pause");

return 0;

}

 

 

Argumenty funkcji

Argumenty funkcji to dane, które są przekazywane dla funkcji, na podstawie których wykonywane są instrukcje. Np. dla funkcji sumującej dwie liczby rzeczywiste argumentami będą te dwie liczby:

 

float funkcja(float argument1, float argument2)

 

Definiując funkcję, musimy zdefiniować listę argumentów, nadając im typy oddzielając je przecinkami. Gdy definiujemy tylko prototyp funkcji, wystarczy nadać  same nazwy typówi:

 

float funkcja(floatfloat);

 

Pamiętaj, że kolejność argumentów ma znaczenie, tzn. jeśli pierwszym argumentem jest liczba całkowita, a drugim znak, to w takiej kolejności musisz wpisać dane przekazywane dla funkcji:

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

void funkcja(float liczba, char znak)

{

cout<<liczba<<" "<<znak;

}

 

int main()

{

float l = 3.14;

char zn = 'a';

 

cout<<"Podałeś następujące wartości argumentów: ";

 

//wywołanie funkcji przekazując dwa argumenty: liczbę "l" i znak "zn"

funkcja(l, zn);

 

system("PAUSE");

return EXIT_SUCCESS;

}

 

 

Ważną rzeczą jest zrozumienie, że w momencie przekazywania zmiennych jako argumenty, funkcja tworzy ich kopie w pamięci. Oznacza to, że wszelkie zmiany na wartościach zmiennych nie będą widoczne w miejscu, gdzie zostały one przekazane do funkcji.

Dla lepszego zobrazowania problemu przeanalizujmy przykład zamiany wartości dwóch zmiennych.

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

void zamiana(int a, int b)

{

int temp = a;

a = b;

b = temp;

}

 

int main()

{

int a, b; //zauważ, że nazwy zmiennych mogą być (ale nie muszą)

//takie same jak nazwy argumentów funkcji

 

cout<<"Podaj dwie liczby: ";

cin>>a>>b;

cout<<"Przed zamianą: a = "<<a<<", b = "<<b<<endl;

 

//wywołanie funkcji

zamiana(a,b);

 

cout<<"Po zamianie: a = "<<a<<", b = "<<b<<endl;

 

//oczywiście w pierwszym i drugim przypadku zostanie

//wyświetlone dokładnie to samo czyli

 

//np., gdy podamy a = 9, b = 0

//to ujżymy komunikat

//Przed zamianą: a = 9, b = 0

//Po zamianie: a = 9, b = 0

 

system("PAUSE");

return EXIT_SUCCESS;

}

 

Funkcje a referencje

Jak już wspominałem w poprzednich artykułach, funkcje w C++ mogą zwracać co najwyżej jedną wartość, która może posłużyć dalszej obróbce (wartość zwrotna funkcji), ale co jeśli tych wartości potrzebujemy więcej? Jednym ze sposobów uporania się z tym problemem jest wykorzystanie referencji.

Pamiętamy, że w chwili przykazywania danych do funkcji tworzone są ich kopie w pamięci, czego konsekwencją jest to, że każda zmiana na wartościach tych zmiennych nie będzie "widziana" w miejscu, w którym tą funkcję wywołaliśmy.

Referencje powodują, że w chwili przekazywania argumentów,  pracujemy na oryginałach zmiennych, ponieważ przekazujemy adresy naszych argumentów.

Aby przekazać referencję, należy dodać po nazwie typu znak "&" np.:

void funkcja(char &a, int &b);

Rozpatrzmy zadanie polegające na zamianie wartości dwóch zmiennych.

 

Zróbmy najpierw to zadanie bez referencji:

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

void zamiana(int a,int b)

{

int temp = a;

a = b;

b = temp;

}

 

int main()

{

int a, b;

 

cout<<"Podaj dwie liczby: ";

cin>>a>>b;

cout<<"Przed zamiana: a = "<<a<<", b = "<<b<<endl;

 

zamiana(a,b);

 

cout<<"Po zamianie: a = "<<a<<", b = "<<b<<endl;

 

system("pause");

return 0;

}

 

Oczywiście efekt nie będzie zadowalający:

 

No i program z wykorzystaniem referencji:

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

void zamiana(int &amp;a,int &amp;b)

{

int temp = a;

a = b;

b = temp;

}

 

 

int main()

{

int a, b;

 

cout<<"Podaj dwie liczby: ";

cin>>a>>b;

cout<<"Przed zamiana: a = "<<a<<", b = "<<b<<endl;

 

zamiana(a,b);

cout<<"Po zamianie: a = "<<a<<", b = "<<b<<endl;

 

system("pause");

return 0;

}

 

 

Tu widzimy, że funkcja spełnia kryteria zadania.

Warto wspomnieć, że podobne działanie uzyskamy w przypadku przekazania argumentów funkcji za pomocą wskaźników.

 

Funkcje inline

Tworzenie funkcji w C++ wiąże się z przydzieleniem pamięci w innym segmencie niż program główny. Oznacza to, że podczas wywoływania funkcji, program musi przeskoczyć w to miejsce, czego konsekwencją jest nieznaczne opóźnienie programu. Jeśli programiście zależy, aby program był szybki, można stworzyć funkcję inline (w lini).

Funkcje inline możemy rozumieć w ten sposób, że kod tej funkcji jest "wklejany" w miejsce, gdzie została ona wykonana (pracujemy wtedy na tym samym segmencie pamięci, oszczędzając czas). Funkcje tego typu tworzy się zazwyczaj dla krótkich kilku linijkowych funkcji. Jeśli kompilator uzna, że dany podprogram nie kwalifikuje się jako inline, to słowo inline zostanie zignorowane.

Słowo kluczowe inline wstawiamy przed typem funkcji:

inline int funkcja(argumenty);

inline void innafunkcja(argumenty);

itd.

Dla przykładu napiszemy funkcję sumującą dwie liczby:

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

inline float suma(float a,float b)

{

return a+b;

}

 

int main()

{

float a,b;

 

cout<<"Podaj dwie liczby: ";

cin>>a>>b;

 

cout<<"Suma liczb "<<a<<" i "<<b<<" wynosi: "<<suma(a,b)<<endl;

 

//w tym przypadku wywołanie funkcji suma jest równoważne z zapisem

//cout<<"Suma liczb "<<a<<" i "<<b<<" wynosi: "<<a+b<<endl;

 

system("pause");

return 0;

}

Przeciążanie nazw funkcji

Przeciążanie nazw funkcji polega na wielokrotnym wykorzystaniu takiej samej jej nazwy, różniącej się tylko typem i ilością argumentów (przeciążanie funkcji można zaliczyć do elementów polimorfizmu).

Rozpatrzmy funkcję, która będzie liczyła pole pewnej figury. Pole figury jakiej ma liczyć uzależnimy od ilości argumentów i typów argumentów. Nasza funkcja będzie nosiła nazwę Pole.

#include <cstdlib>

#include <iostream>

#include <fstream>

 

using namespace std;

 

float Pole(float a)

{

return a*a;

}

 

double Pole(double r)

{

return 3.14*r*r;

}

float Pole(float a,float b)

{

return a*b;

}

 

int main()

{

float a,b;

double r;

char opcja;

 

cout<<"Wybierz opcję: "<<endl;

 

cout<<"1 - pole kwadratu "<<endl;

cout<<"2 - pole prostokąta "<<endl;

cout<<"3 - pole koła "<<endl;

 

cin>>opcja;

 

switch(opcja)

{

case '1':

 

cout<<"Podaj długość boku: "; cin>>a;

cout<<"Pole kwadratu wynosi "<<Pole(a)<<endl;

 

break;

case '2':

 

cout<<"Podaj długość boku a: "; cin>>a;

cout<<"Podaj długość boku b: "; cin>>b;

cout<<"Pole prostokąta wynosi "<<Pole(a,b)<<endl;

 

break;

case '3':

 

cout<<"Podaj długość promienia: "; cin>>r;

cout<<"Pole koła wynosi "<<Pole(r)<<endl;

 

break;

 

default: cout<<"Wybrałeś nieprawidłową opcję!"<<endl;

}

 

system("pause");

return 0;

}

 

Kiedy nie wolno przeciążać funkcji?

 Do przeciążenia funkcji nie wystarczy  różny tym zwracanych wartości np.:

 

int funkcja(int a)

char funkcja(int a)

 

W przykładzie powyżej, funkcje mają różne zwracane wartości, natomiast takie same argumenty - kompilator wyrzuci błąd!

Rekurencja w C++

Rekurencja zwana rekursją, polega na wywołaniu przez funkcję samej siebie. Algorytmy rekurencyjne zastępują w pewnym sensie iteracje. Zazwyczaj zadania rozwiązywane tą techniką są wolniejsze  od iteracyjnego odpowiednika, natomiast rozwiązanie niektórych problemów jest znacznie wygodniejsze.

Prześledźmy program wyznaczający sumę kolejnych liczb naturalnych.

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

long long suma(int n)

{

if(n<1)

return 0;

 

return n+suma(n-1);

}

 

int main()

{

int n;

 

cout<<"Podaj liczbę: ";

cin>>n;

 

cout<<"Suma "<<n<<" kolejnych liczb naturalnych wynosi "

<<suma(n)<<endl;

 

system("pause");

return 0;

}

 

 

Załóżmy, że na wejściu podaliśmy liczbę 5 (program ma wyznaczyć sumę 1+ 2+ 3 + 4 + 5).

wynik = suma(5);

Funkcja suma(n), wywołała się z argumentem równym 5. Najpierw sprawdzamy, czy n< 1 (5 < 1). Warunek jest fałszywy, przechodzimy więc do następnej linijkireturn 5 + suma(5 - 1) . Funkcja suma wywołana została przez samą siebie z argumentem równym 4, a więc mamy:

wynik = suma(5) = 5 + suma(4),

daną czynność powtarzamy do momentu, gdy argument osiągnie wartość 0, wtedy funkcja zwróci 0 (0 < 1prawda).

wynik = suma(5) = 5 + suma(4) = 5 + 4 + suma(3) = 5 + 4 + 3 + suma(2) = 5 + 4 + 3 + 2 + suma(1) =

5 + 4 + 3 + 2 + 1 + suma(0) = 5 + 4 + 3 + 2 + 1 + 0 = 15.

 

Tablice w C++

Definicja: Tablica to struktura danych, która przechowuje wiele elementów tego samego typu. Np., gdy potrzebujemy przechowywać 1000 liczb całkowitych, to możemy stworzyć tysiąc zmiennych (szkoda czasu), albo jedną tablicę typu całkowitego, składającą się z 1000 komórek.

Możemy tworzyć tablice dowolnego typu wbudowanego w strukturę C++, oraz tablice własnych typów takich jak struktury czy klasy.

Tablice jednowymiarowe w C++

Inicjacja tablicy jednowymiarowej

Tablice jednowymiarowe można porównać do osi liczbowej. Aby odwołać się do jakiegoś elementu, wystarczy podać jedną współrzędną.

Tablice tego typu deklarujemy następująco:

typ_komórek_tablicy nazwa_tablicy [ ilość elementów tablicy ] ;

 

np.:

 

int tablica[1000];

 

czyli tablica, która może przechowywać tysiąc elementów typu całkowitego,

 

char znaki[1001];

 

czyli tablica, która może przechowywać 1000 znaków + znak końca tablicy.

 

Nadanie wartości początkowych tablicy

Operację tą można zrobić tylko przy deklaracji tablicy (przy tworzeniu tablicy). Operację nadania wartości tablicy przedstawię na przykładzie pięcio-elementowej tablicy liczb całkowitych:

 

int tab[5] = {1, 3, 90, 100000, 9};

 

Oznacza to, że w pierwszej komórce przechowywana jest liczba 1, w drugiej 3 itd..

Jeśli przypiszemy do tablicy mniejszą ilość elementów niż wielkość tablicy, to pozostałe komórki będą miały wartość zero:

 

int tab[5] = {1, 3};

 

Powyższy zapis jest równoważny z zapisem:

 

int tab[5] = {1, 3, 0, 0, 0};

 

Przypisanie do tablicy pustego nawiasu oznaczać będzie, że wszystkie komórki będą miały wartość zero.

 

Warto wiedzieć, że jeśli tablicę zadeklarujemy jako globalną, to elementy tablicy zostaną także wyzerowane.

 

Odwoływanie się do komórek tablicy

Najważniejszą regułą jest to, że komórki numerujemy od zera. Numery te nazywamy indeksami tablicy. Oznacza to, że jeśli stworzyliśmy tablicę 10-elementową, to numer pierwszej komórki jest równy zero, drugiej jeden, ..., no i ostatniej dziewięć:

[0][1][2]⋯[n−1]indeksy komorek n−elementowej tablicy

 

Odwołujemy się do komórek tablicy podając jej indeks w nawiasie kwadratowym, np:

int tab[10]; //deklaracja tablicy

 

tab[0] = 34; //przypisanie do pierwszej komórki wartość 34

 

tab[9] = 100; //przypisanie do ostatniej komórki wartość 100

 

cout<<tab[0]; //wyświetlenie zawartości pierwszej komórki

//tablicy (czyli 34)

 

Pamiętaj!!! Gdy odwołujesz się do ostatniej komórki tablicy n-elementowej, to jej indeks jest równy n-1.

 

Przykładowe zadanie

Zadanie. Napisz program, który nada  następujące wartości początkowe tablicy 5-elementowej: 1, 2, 5, 0, 0, a następnie wyświetli najpierw wartości parzyste tej tablicy, a następnie nieparzyste.

Rozwiązanie:

#include <iostream>

#include <cstdlib>

 

using namespace std;

 

int main()

{

int tab[5] = {1, 2, 5};

 

for(int i=0;i<5;i++)

//jeśli wartość tablicy jest parzysta

if(tab[i]%2==0)

cout<<tab[i]<<" ";

 

for(int i=0;i<5;i++)

//jeśli wartość tablicy jest nieparzysta

if(tab[i]%2)

cout<<tab[i]<<" ";

 

cout<<endl;

 

system("pause");

return 0;

}

 Warto zauważyć, że przeszukujemy tablicę o indeksach od 0 od 4, a więc w nawiasie kwadratowym tablicy wstawiliśm

 

Tablice wielowymiarowe w C++

 

powrót

Inicjacja tablicy dwuwymiarowej

Jako tablicę dwuwymiarową możemy sobie wyobrazić planszę prostokątną składającą się z pewnej liczby wierszy i kolumn (numerowanie zaczynamy od zera). Aby przypisać (pobrać) wartość do danej komórki, należy podać jej obie współrzędne.

Inicjacja tablicy polega na podaniu ilości wierszy i kolumn:

typ_elementów_tablicy nazwa_tablicy [ ilość wierszy][ilość kolumn ];

np.:

//stworzenie dwuwymiarowej tablicy liczb całkowitych, w sumie 100 komórek: 10x10.

int tab [ 10 ][ 10 ];

 

Nadanie wartości początkowych tablicy

Wartości początkowe tablicy możemy nadać przy jej deklaracji. Popatrzmy na przykład oparty na tablicy dwuwymiarowej liczb całkowitych:

 

//zauważmy, że mamy trzy wiersze, i dwie kolumny

int tab[3][2] = {{1,3},{4,5},{0,-1}};

 

Przy nadawaniu wartości tablicy można pominąć wartość w pierwszym nawiasie kwadratowym:

 

//wartość pierwszego nawiasu została pominięta

int tab[][2] = {{1,3},{4,5},{0,-1}};

 

Odwoływanie się do komórek w tablicy dwuwymiarowej 

Aby odwołać się do każdej z komórek należy w nawiasach kwadratowych podać numer wiersza i kolumny komórki, do której się odwołujemy, pamiętając o tym, żenumerujemy je od zera:

 

int tab[][2] = {{1,3},{4,5},{0,-1}}; //deklaracja i inicjacja tablicy dwuwymiarowej

cout<<tab[2][1]; //wyświetlenie wartości komórki znajdującej się w trzecim wierszu i w drugiej kolumnie (-1)

tab[0][0] = -100; //przypisanie do pierwszej komórki wartości -100

 

Zad. Napisz program, który wykona transpozycję macierzy 4x5. Liczby generujemy losowo z przedziału [-9; 9]. Elementami macierzy są liczby całkowite.

 

Rozwiązanie:

Macierz to obiekt, który doskonale nadaje się do przechowywania w tablicach dwuwymiarowych. Każda macierz składa się z pewnej ilości wierszy i kolumn. Przykładowa macierz spełniająca warunki zadania:

⎡⎣⎢⎢⎢12321234−506580−6−9254−8⎤⎦⎥⎥⎥

 

Transpozycja macierzy polega na zamianie wierszy z kolumnami. Powyższa macierz powinna wyglądać następująco:

 

⎡⎣⎢⎢⎢⎢⎢⎢11−58222005336−64245−9−8⎤⎦⎥⎥⎥⎥⎥⎥

 

#include<iostream>

using namespace std;

 

int main()

{

 

int tab[4][5]

 

cout<<"Przed transpozycją:\n ";

 

for(int i=0;i<4;i++)

{

for(int j=0;j<5;j++)

{

//wygenerowanie liczb z zakresu [-9; 9]

tab[i][j]=rand()%19-9;

//wyświetlenie wylosowanej liczby

cout<<tab[i][j]<<" ";

}

cout<<endl;

}

 

//transpozycja macierzy

cout<<"Po transpozycji: "<<endl;

for(int i=0;i<5;i++)

{

for(int j=0;j<4;j++)

cout<<tab[j][i]<<" ";

 

cout<<endl;

}

 

system("pause");

return 0;

}

 

Tablice wielowymiarowe

Tablice o większej liczbie wymiarów rzadko się stosuje. Sposób inicjacji, oraz operowania na tablicach tego typu jest analogiczny jak w przypadku tablic dwuwymiarowych.

Prześledźmy przykład tworzenia tablicy trójwymiarowej:

 

#include

#include<cstdlib>

using namespace std;

 

int main()

{

//deklaracja tablicy trójwymiarowej

//taka tablica posiada 3*4*5 = 60 komórek

int tab[3][4][5];

//przypisanie wartości 23 do pierwszej komórki

tab[0][0][0] = 23;

 

cout<<tab[0][0][0]<<endl;

 

system("pause");

return 0;

Tablice znaków

 

powrót

Tablice znaków służą do przechowywania ciągów znaków, czyli tekstu. Przy deklaracji musimy pamiętać, żeby podać o jedną komórkę więcej niż potrzebujemy, ponieważ ciąg znaków musi być zakończony specjalnym znakiem '\0' (kod ASCII = 0). Dzięki temu "zakończeniu", między innymi program wie kiedy zakończyć wypisywanie tekstu.

Poniżej przedstawiony jest schemat przechowywania tekstu "Ala ma kota". W tym przypadku potrzebujemy co najmniej dwunastu komórek tablicy (pamiętajmy, że numerowanie zaczynamy od zera):

0′A′−− 1′l′−− 2′a′−− 3′ ′−− 4′m′−− 5′a′−− 6′ ′−− 7′k′−− 8′o′−− 9′t′−− 10′a′−− 11\'∖0\'−−−−

Wypełnianie tablicy znakami

Tablicę można wypełnić przy deklaracji (podobnie jak inne obiekty), pamiętając o tym szczególnym znaku na końca tablicy:

#include <iostream>

#include <cstdlib>

using namespace std;

 

int main()

{

//pierwszy sposób

char tab[5] = {'a','b','\0'}; //tworzymy tablicę 5-elementową, która może

 

//przechować do 4 znaków

 

cout<< tab<< endl; //program wypisze ab

 

//drugi sposób - nie podając wielkości tablicy - program sam dopasuje jej wielkość

char tab2[] = {'a','b','\0'}; //tym razem tworzymy tablicę 3-elementową

 

cout<< tab2<< endl;

 

//trzeci sposób - podajemy ciąg znaków pamiętając o podwójnym cudzysłowie

char tab3[] = "Ala ma kota :)";

 

cout<< tab3<< endl;

system("pause");

return 0;

}

 

 

Wypełnianie tablicy poprzez przypisanie do danych komórek odbywa się tak samo jak na innych typach.

Zauważmy, że przy wyświetlaniu tablicy znaków podajemy tylko nazwę tablicy.

 

Wprowadzanie ciągów znaków

Podobnie jak przy wypisywaniu tekstu, do wprowadzania posługujemy się tylko nazwą tablicy. W tym miejscu należy zwrócić uwagę na działanie obiektu "cin". Dane zostaną wczytane do napotkania pierwszej spacji lub znaku końca linii. Oznacza to, że tym sposobem możemy wczytać tylko jeden wyraz.

#include<iostream>

#include<cstdlib>

using namespace std;

 

int main()

{

char tablica[100];

cout<<"Podaj imie i nazwisko: ";

cin>>tablica;

cout<<"Twoje dane osobowe: "<<tablica<<endl; //dane zostaną zredukowane

//do imienia

system("pause");

return 0;

}

 

 

Zrzut:

 

Metoda getline().

Drugim sposobem, jaki można tu zastosować jest wykorzystanie metody getline() obiektu cin. Funkcja ta jest ukierunkowana na wczytywanie całych wierszy. Konstrukcja wygląda następująco:

 

cin.getline(tab, bufor);

 

gdzie tab to tablica znaków, a bufor to wielkość tej tablicy (tablica może przechować bufor - 1 znaków + znak końca tablicy).

Przeanalizujmy jeszcze raz nasz program:

#include<iostream>

#include<cstdlib>

using namespace std;

 

int main()

{

char tablica[100];

cout<<"Podaj imie i nazwisko: ";

cin.getline(tablica,100); //wykorzystanie metody getline()

cout<<"Twoje dane osobowe: "<<tablica<<endl;

system("pause");

return 0;

}

 

 

Zrzut:

 

Następną dostępną metodą jest funkcja o nazwie get() występująca w kilku wariantach. Metoda ta może działać podobnie jak getline, z tą różnicą, że znak nowego wiersza nie jest odrzucany (jak w przypadku getline()), tylko pozostaje w kolejce wejściowej. Oznacza to, że ponowne użycie  get() nie pobierze ciągu znaków, ponieważ zakłada, że nastąpił już koniec wiersza. Aby zaradzić temu problemowi można użyć innej postaci get, a mianowicie metody get() bez argumentów. Pobiera ona następny znak, a wiec znak końca linii zostanie usunięty i można ponownie wczytywać dane:

#include<iostream>

#include<cstdlib>

 

using namespace std;

 

int main()

{

char tablica[100];

cout<<"Podaj imie i nazwisko: ";

 

//wykorzystanie metody getline()

cin.get(tablica,100); //pobranie wiersza danych

cout<<"Twoje dane osobowe: "<<tablica<<endl;

cin.get(); //usunięcie znaku końca linii

cout<<"Ponownie podaj imię i nazwisko: ";

cin.get(tablica,100); //pobranie następnego wiersza danych

cout<<"Twoje dane osobowe: "<<tablica<<endl;

system("pause");

return 0;

}

 

 

Dokładnie ten sam efekt można osiągnąć wywołując na przemian metodę get z argumentami i bez argumentów:

#include<iostream>

#include<iostream>

#include<cstdlib>

 

using namespace std;

 

int main()

{

char tablica[100];

cout<<"Podaj imie i nazwisko: ";

 

//wykorzystanie metody getline()

cin.get(tablica,100).get(); //pobranie wiersza danych

cout<<"Twoje dane osobowe: "<<tablica<<endl;

 

cout<<"Ponownie podaj imię i nazwisko: ";

cin.get(tablica,100).get(); //pobranie następnego wiersza danych

cout<<"Twoje dane osobowe: "<<tablica<<endl;

system("pause");

return 0;

}

 

 

Zadanie. Napisz program, który pobierze ze standardowego wejścia trzy zdania oraz wyświetli je w odwrotnej kolejności, zamieniając wszystkie male litery (tylko angielskie) na duże.

Rozwiązanie. Warto zauważyć, że numery ASCII małych liter mieszczą się w przedziale [97; 122]. Różnica między małymi i dużymi literami wynosi 32. Będziemy sprawdzać każdy znak, czy jest to mała litera. Gdy będzie spełniony warunek przesuniemy ją o 32 numery w dół aby przeskoczyć na dużą literę. Do przechowania zdań użyjemy tablicy dwuwymiarowej.

#include<iostream>

#include<cstdlib>

using namespace std;

 

int main()

{

char zdanie[3][100];

cout<<"Podaj pierwsze zdanie: ";

cin.getline(zdanie[0],100);

cout<<"Podaj drugie zdanie: ";

cin.getline(zdanie[1],100);

cout<<"Podaj trzecie zdanie: ";

cin.getline(zdanie[2],100);

 

for(int i=2;i>=0;i--) //pętla odpowiedzialna

{ //za wypisanie w odwrotnej kolejności

int k = 0; //ustawienie na czytanie pierwszegj komórki tablicy

while(zdanie[i][k]) //dopóki nie napotkamy znak końca linii,

{ //pętla będzie sie wykonywać

if(zdanie[i][k]>=97&amp;&amp;zdanie[i][k]<=122) //sprawdzenie czy

//znak jest małą literą

cout<<char(zdanie[i][k]-32); //jeśli tak to przesuwamy

//ją do dużej litery

else

cout<<(char)zdanie[i][k];

++k; //przeskoczenie do następnej komórki tablicy

}

cout<<endl;

}

system("pause");

return 0;

}

 

Tablice znaków - przydatne funkcje

W tym artykule opiszę kilka podstawowych funkcji operujących na tablicach znaków:

Tablica jako argument funkcji

W tym artykule opiszę w jaki sposób przekazujemy tablicę do wnętrza funkcji poprzez jej argumenty. Omówię tablice jedno i wielowymiarowe:

Na wstępie pragnę zauważyć, że nazwa tablicy jest wskaźnikiem na jej pierwszy element. Oznacza to, że przekazując tablicę będziemy pracować na jej oryginalnym adresie (wszelkie zmiany w modyfikacji tablicy będą widoczne w miejscu, gdzie ją stworzyliśmy).

 

Konstrukcja nagłówka funkcji dla tablicy jednowymiarowej
sposób 1:

typ_funkcji nazwa(typ_elementów_tablicy nazwa_tablicy[]);

 

Można opcjonalnie w nawiasie podać ilość elementów tablicy.

Przykładowa funkcja może wyglądać następująco:

void funkcja(int tablica[10]);

Zadanie. 1. Napisz program, który zamieni wartości tablicy 10 elementowej na ich kwadraty.

#include<iostream>

#include<cstdlib>

using namespace std;

 

void zamiana(int tab[])

{

for(int i=0; i<10; i++)

tab[i]*=tab[i]; //lub tab[i] = tab[i] * tab[i];

}

 

int main()

{

//inicjacja tablicy

int tablica[10] = {0, 3, 4, 3, 6, 7, 11, -5, -10, 87};

 

//wypisanie elementów tablicy

for(int i=0;i<10;i++)

cout<<tablica[i]<<" ";

 

cout<<endl; //wstawienie znaku końca linii (enter)

 

//wykonanie polecenia

zamiana(tablica); //przekazując tablicę, podajemy tylko jej nazwę

 

//ponowne wypisanie elementów tablicy

for(int i=0;i<10;i++)

cout<<tablica[i]<<" ";

 

cout<<endl;

 

system("pause");

 

return 0;

}

 

 

Wyście:

0 3 4 3 6 7 11 -5 -10 87

0 9 16 9 36 49 121 25 100 7569

 

Uwaga!!!. Przekazując tablicę wpisujemy tylko jej nazwę bez nawiasu kwadratowego.

 

Konstrukcja nagłówka funkcji dla tablicy jednowymiarowej sposób 2:

typ_funkcji nazwa(typ_elementów_tablicy *nazwa_tablicy);

 

Drugim sposobem jest przekazanie tablicy poprzez wskaźnik na pierwszy element tej tablicy.

Program będzie się różnił tylko nagłówkiem funkcji:

 

void zamiana(int *tab)

{

for(int i=0; i<10; i++)

tab[i]*=tab[i]; //lub tab[i] = tab[i] * tab[i];

}

 

 

 

Konstrukcja nagłówka funkcji dla tablicy wielowymiarowej 
sposób 1:

Pierwszy sposób jest podobny jak w przypadku tablicy jednowymiarowe. Przykład dotyczy tablicy dwuwymiarowej. Tablice o większej liczbie wymiarów przekazujemy podobnie. Przeanalizujmy zadanie:

Zadanie. 2. Napisz program, który zamieni wartości tablicy dwuwymiarowej o wymiarach 3x3, na ich kwadraty. Tablicę wypełniamy liczbami pseudolosowymi z przedzialu [0..9].

#include<iostream>

#include<cstdlib>

 

using namespace std;

 

void zamiana(int tab[3][3])

{

for(int i=0;i<3;i++)

for(int j=0;j<3;j++)

tab[i][j]*=tab[i][j]; //lub tablica[i][j] = tablica[i][j]*tablica[i][j];

}

 

int main()

{

//inicjacja tablicy dwuwymiarowej

int tablica[3][3] = {{1, 2, 3}, {4, 5, 6}, {-1, -2, -3}};

 

//wypisanie elementów tablicy

for(int i=0;i<3;i++)

for(int j=0;j<3;j++)

cout<<tablica[i][j]<<" "; //lub tablica[i][j] = tablica[i][j]*tablica[i][j];

 

cout<<endl; //wstawienie znaku końca linii (enter)

 

//wykonanie polecenia

zamiana(tablica); //przekazując tablicę, podajemy tylko jej nazwę

 

//ponowne wypisanie elementów tablicy

for(int i=0;i<3;i++)

for(int j=0;j<3;j++)

cout<<tablica[i][j]<<" "; //lub tablica[i][j] = tablica[i][j]*tablica[i][j];

 

cout<<endl;

 

system("pause");

 

return 0;

}

 

 

Przykładowe wyjście:

1 2 3 4 5 6 -1 -2 -3

1 4 9 16 25 36 1 4 9

 

Konstrukcja nagłówka funkcji dla tablicy wielowymiarowej 
sposób 2:

Drugi sposób opiera się na utworzeniu tablicy dynamicznej postaci:

typ_tablicy **nazwa_tablicy;

 

Przeanalizujmy powyższe zadanie rozwiązane tym sposobem:

 

#include<iostream>

#include<cstdlib>

using namespace std;

 

void zamiana(int **tab)

{

for(int i=0;i<3;i++)

for(int j=0;j<3;j++)

tab[i][j]*=tab[i][j]; //lub tab[i][j] = tab[i][j]*tab[i][j];

}

 

int main()

{

srand(time(NULL)); //ustawienie ziarna

 

//deklarujemy tablicę dynamicznie

int **tablica = new int *[3];

for(int i=0;i<3;i++)

tablica[i] = new int [3];

 

//przypisanie do tablicy losowych liczb z przedziału [0..9]

for(int i=0;i<3;i++)

for(int j=0;j<3;j++)

tablica[i][j] = rand()%10;

 

//wypisanie elementów tablicy

for(int i=0;i<3;i++)

for(int j=0;j<3;j++)

cout<<tablica[i][j]<<" ";

 

cout<<endl; //wstawienie znaku końca linii (enter)

 

//wykonanie polecenia

zamiana(tablica); //przekazując tablicę, podajemy tylko jej nazwę

 

//ponowne wypisanie elementów tablicy

for(int i=0;i<3;i++)

for(int j=0;j<3;j++)

cout<<tablica[i][j]<<" ";

 

cout<<endl;

 

system("pause");

 

return 0;

}

 

 

Tablice przydzielane dynamicznie opisane są tutaj.

Tablica a wskaźnik

powrót

Na wstępie przypomnę, że nazwa tablicy jest wskaźnikiem na jej pierwszy element. Oznacza to, że możemy odnieść się to tego elementu za pomocą nazwy tablicy i indeksu o wartości 0 lub poprzez ten wskaźnik. Popatrzmy na przykład:

 

int tablica[5] = {1 ,2, 3 , 4 ,5}; //inicjacja tablicy

//odwołanie się za pomocą indeksu

cout<<"Pierwszy element tej tablicy to: "<<tab[0]<<endl;

//odwołanie się za pomocą wskaźnika

cout<<"to także pierwszy element tej tablicy: "<<*tab;

 

 

Po elementach tablicy możemy poruszać się przy użyciu indeksów podając kolejne numery komórek począwszy od 0, jak również przeskakując kolejne komórki tablicy z wykorzystaniem wskaźnika.

Warto zauważyć, że w przypadku elementów, które zajmują np. 4 bajty pamięci (liczby typu int), będziemy przy każdym zwiększeniu wskaźnika przeskakiwać o te cztery bajty, żeby dostać się do następnej wartości tablicy. W przypadku tablicy typu char przeskok będzie o jeden bajt - tyle ile zajmuje typ char. Reguła ta dotyczy każdego innego typu.

 

Przeanalizujmy program, który pokazuje sposób poruszania się po elementach tablicy z wykorzystaniem wskaźników:

 

#include<iostream>

#include<cstdlib>

 

using namespace std;

 

int main()

{

int tab[10] = {1, 2 ,4, 5, 6, 4, 3, 2, 1, 0};

 

cout<<*tab<<endl; //wypisanie 1 elementu tablicy

 

cout<<*(tab+3)<<endl; //wypisanie 4 elementu tablicy

 

for(int i=0;i<10;i++)

cout<<*(tab+i)<<" "; //wypisanie elementów tablicy

//poruszając się po niej wskaźnikiem

cout<<endl;

 

system("pause");

 

return 0;

}

 

 

Wyjście:

1

5

1 2 4 5 6 4 3 2 1 0

Wskaźniki

Definicja. Wskaźnik to zmienna przechowująca adres innej zmiennej. Inaczej, jest to zmienna wskazująca na adres innej zmiennej. Skoro jest zmienną więc posiada także swój adres.

Wskaźniki mają szerokie zastosowanie w C++ co daje temu językowi ogromną elastyczność.

Tworzenie wskaźnika

Zmienną wskaźnikową tworzymy podobnie jak zwykłe zmienne pamiętając tylko, aby przed nazwą wpisać operator "*". Operator ten służy także do wyłuskania wartości ze zmiennej wskaźnikowej (wyciągnięcia wartości ze zmiennej, na którą wskazuje).

int *a, *b; //stworzenie dwóch zmiennych wskaźnikowych typu int

 

Przypisywanie wartości

Aby przypisać wartość należy użyć nazwy zmiennej z operatorem "*" (operator wyłuskania). Tak samo postępujemy w sytuacji, gdy wartość tej zmiennej chcemy przypisać do innej zmiennej lub ją wyświetlić.

int *a, b; //stworzenie zmiennej wskaźnikowej a i zwykłej zmiennej b

*a = 5; //przypisanie wartości do zmiennej a

b = *a; //przypisanie wartości znajdującej się w zmiennej wskaźnikowej a do zmiennej b

cout<<*a<<" "<<b; //zostanie wypisana dwa razy liczba 5

 

Przypisywanie adresu drugiej zmiennej

Przypisując adres zmiennej należy wymusić aby przekazała swój adres a nie wartość. W takim przypadku należy użyć operatora "&".

Przypisując do zmiennej wskaźnikowej adres drugiej zmiennej wypisujemy tylko nazwę tej zmiennej, bez operatora "*":

int *liczba, b;

b = 6;

liczba = &b; //przypisanie adresu zmiennej b

cout<<*liczba; //wypisanie wartości przechowującej przez b

 

Zastosowanie

Za pomocą wskaźników można kontrolować wartości innych zmiennych:

#include<iostream>

using namespace std;

 

int main()

{

int *a, *b, c;

 

c = 5;

a = &c; //zmienna a wskazuje na adres zmiennej c

b = &c; //zmienna b wskazuje na adres zmiennej c

 

cout<<*a<<" "<<*b<<endl; //wypisanie wartości, którą przechowuje zmienna c (2 razy 5)

 

*a = 7; //zmiana wartosci zmiennej c

 

cout<<*a<<" "<<*b<<" "<<c; //zostanie wypisana trzy razy wartość 7

 

return 0;

}

 

Wskaźniki jako argumenty funkcji. Przekazując wskaźniki jako argumenty mamy zapewnione, że każda zmiana wartości zmiennej wewnątrz funkcji będzie "widoczna" w miejscu, w którym została wywołana:

#include<iostream>

using namespace std;

 

void zamien(int *a, int *b)

{

int pom = *a;

*a = *b;

*b = pom;

}

 

int main()

{

int a, b;

cin>>a>>b;

 

zamien(&a,&b); //przekazujemy adresy zmiennych

 

cout<<a<<" "<<b; //wartości zmiennych zostały zamienione

 

return 0;

}

 

Przykładowe wejście:

2 3

Wyjście:

3 2

Użycie zwykłych zmiennych nie spowoduje zamiany wartości, ponieważ funkcja będzie pracować na kopiach tych zmiennych.

Przydzielanie dynamiczne pamięci dla tablic, obiektów, struktur itp.

Gdy nie znamy wielkości tablicy lub tablica jest bardzo duża - większa niż stos programu, należy ją przydzielić dynamicznie.

#include<iostream>

using namespace std;

 

class klasa

{

public:

void f()

{

cout<<"metoda klasy";

}

private:

int a;

};

 

int main()

{

int *tab, n;

 

klasa * obiekt = new klasa; //przydzielenie pamięci na obiekt klasy klasa

 

cout<<"Podaj ilość elementów tablicy: ";

cin>>n;

 

tab = new int [n]; //przydzielenie pamięci na n elementów tablicy

 

/*******************************

operacje na tej tablicy

*******************************/

 

delete [] tab; //zwolnienie pamięci dla tablicy

delete obiekt; //zwolnienie pamięci dla objektu

 

return 0;

}

 

Przy tworzeniu dynamicznych struktur danych takich jak drzewa, kolejki, listyi stosy.

Struktury, unie, pola bitowe, wyliczenia


Wyszukiwarka

Podobne podstrony:
Nowy Prezentacja programu Microsoft PowerPoint 5
Charakterystyka programu
1 treści programoweid 8801 ppt
Programowanie rehabilitacji 2
Rola rynku i instytucji finansowych INowy Prezentacja programu Microsoft PowerPoint
Nowy Prezentacja programu Microsoft PowerPoint ppt
Szkoła i jej program
wykluczenie społ program przeciwdział
ProgrammingJavaLecture9
Nowa podstawa programowa WF (1)
Programowanie robotów przemysłowych FANUC
A3 Silnik indukcyjny pierscieniowy program
instrukcja programu wsjt222
Program 7
13 programowalny kontroler przerwan 8259

więcej podobnych podstron