©2008 Jerzy Kluczewski
PROGRAMOWANIE OBIEKTOWE
Ćwiczenia podstawowe Cz. I
•
Kompilacja i linkowanie
•
Pierwszy program w C++
•
Rozpoznawanie błędów
•
Zmienne
•
Instrukcja warunkowa
•
Rodzaje zmiennych
•
Wskaźniki
•
Iteracje
•
Macierze
•
Tablice i wskaźniki
•
Dynamiczny przydział pamięci
2
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I.
______________________________________________________________________
______________________________________________________________________
©2008 Jerzy Kluczewski
I.
Kompilacja i linkowanie
Za pomocą środowiska Borland C++ Builder, będziemy kodować, kompilować i
likować programy (Rys .1).
Rys. 1. Proces tworzenia pliku wykonywalnego.
Najprostszy program
Najprostszy program w C++ zawiera funkcję main, która zawsze zwraca wartość
całkowitą. Nawiasy okrągłe oznaczają, ze mamy do czynienia funkcją. Nawiasy
klamrowe do wpisywania miedzy nimi, treści funkcji (wpisujemy tam instrukcje języka
C++).
Program ten nic nie robi, więc nie ma celu praktycznego.
kod źródłowy
Polecenie w C++ Builder: Make lub Build
powoduj,e że kod źródłowy będzie poddany
kompilacji, i jeśli nie będzie błędów to nastąpi
linkowanie bibliotek i tworzenie pliku
wykonywalnego.
kompilacja
linkowanie
plik wykonywalny
int main()
{
}
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I. 3
______________________________________________________________________
©2008 Jerzy Kluczewski
Prawdziwy program
Praktyczny, prawdziwy program w C++ będzie wyświetlał na ekranie napis Moj drugi
program. Aby operator cout był właściwie zinterpretowany, należy na początku wpisać
linię #include <iostream.h>. iostream to biblioteka języka C++ służąca do obsługi
klawiatury i ekranu. Operator << służy do skierowania napisu do ekranu. Ekranem jest
tu symbol cout. Funkcja system z parametrem "pause" służy do zatrzymania pracy
programy i wyczekiwaniem przez niego na naciśnięcie dowolnego klawisza.
Standard opisu języka C++ umożliwia pisanie programów z użyciem tzw. przestrzeni
nazw, program powyższy będzie wyglądał następująco (dwie wersje jednakowo
działające):
II.
Rozpoznawanie błędów
Kompilacja to proces tłumaczenia kodu źródłowego na kod zrozumiały dla danego
procesora. Powstaje plik pośredni (OBJ). Konsolidacja (linkowanie) to utworzenie z
pliku pośredniego programu wynikowego, który będzie można uruchamiać jako
aplikację. W tracie linkowania plik pośredni jest łączony z dodatkowymi bibliotekami
(modułami, pakietami).
Każdy programista musi pogodzić się z faktem, że pisząc programy, będzie
jednocześnie popełniał błędy. Szczególnie narażone na pomyłki są osoby początkujące.
#include <iostream.h>
int main()
{
cout << "Moj drugi program";
system("pause");
}
#include <iostream>
using namespace std;
int main()
{
cout << "Moj drugi program";
system("pause");
}
#include <iostream>
#include <stdlib>
int main()
{
std::cout << "Moj drugi program";
system("pause");
}
4
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I.
______________________________________________________________________
______________________________________________________________________
©2008 Jerzy Kluczewski
Błędy można podzielić na trzy grupy:
•
występujące na etapie kompilacji i sygnalizowane przez kompilator (Rys. 2),
•
występujące w trakcie działania programu, powodujące jego zatrzymanie,
•
błędy logiczne nie powodujące komunikatów kompilatora, ani zatrzymania
programu, ale powodujące błędne jego działanie (na przykład program
produkuje błędne wyniki).
Rys. 2. Błędy kompilacji
Dziwny błąd linkera
Początkujący użytkownicy środowiska Borland C++ często spotykają się z błędem,
który pojawia się podczas kompilacji projektu. Objawia się on dziwnym komunikatem
w postaci:
[Linker Fatal Error] Fatal: Expected a file name:
Jak widać ta linia komunikatu informuje programistę, że błąd nie wystąpił w etapie
kompilacji, lecz linkowania, czyli konsolidacji. Przyczyna tego błędu jest bardzo prosta
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I. 5
______________________________________________________________________
©2008 Jerzy Kluczewski
Otóż programista zapisał pliki projektu w katalogu o ścieżce w której znajdują się znaki
niedozwolone dla środowiska Borland C++. Na ścieżce dostępu do projektu
C:\Program Files\Borland\CBuilder6\Projects\c++ znajdują się znaki c++, które
zawierają ++, a to przez linker środowiska Borland C++ zostało zrozumiane jako część
komendy, a nie katalog. Rozwiązaniem naprawczym jest po prostu zmiana nazwy
katalogu na taką aby zawierały wyłącznie znaki alfabetu łacińskiego (ewentualnie znaki
podkreślenia i cyfry).
Niezadeklarowana zmienna
Każdą zmienną przed użyciem należy zadeklarować. W przeciwnym razie wystąpi błąd
kompilacji. Na przykład w programie errory.cpp, w linii 5 wystąpi błąd:
[C++ Error] terrory.cpp (5): E2451 Undefined symbol ‘z’.
Program errory.cpp
Przyczyna: brak deklaracji zmiennej z.
Literówki
Literówki popełniają nawet zaawansowani programiści, na szczęście te błędy są łatwe
do wykrycia. W programie trzeci.cpp, w linii 5 wystąpi błąd:
[C++ Error] trzeci.cpp (5): E2335 Overloaded ‘count’ ambiguous in this context.
Program trzeci.cpp
Przyczyna: programista zamiast słowa cout użył count.
int main()
{
for (int i =0; i < 5; i++)
{
z = i * 5;
}
return 0;
}
#include <iostream>
using namespace std;
int main()
{
count << "Moj trzeci program";
system("pause");
return 0;
}
6
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I.
______________________________________________________________________
______________________________________________________________________
©2008 Jerzy Kluczewski
Pliki nagłówkowe
W następnym programie czwarty.cpp, programista zapomniał dołączyć plik
nagłówkowy iostream, dlatego kompilator zgłosił błąd:
[C++ Error] czwarty.cpp (3): E2451 Undefined symbol ‘cout’.
Program czwarty.cpp
Przestrzenie nazw
W następnym programie piaty.cpp, programista zapomniał użyć dyrektywy using
namespace std (standardowej przestrzeni nazw), aby zapewnić dostęp do nazw
zdefiniowanych w bibliotece iostream. W takim przypadku kompilator zgłosił błąd:
[C++ Error] piaty.cpp (4): E2451 Undefined symbol ‘cout’.
Program piaty.cpp
Koniec instrukcji
W kolejnym przykładzie szosty.cpp, popełniono banalny błąd, polegający na braku
ś
rednika na końcu instrukcji cout << "Moj szosty program" (każda instrukcja musi
kończyć się średnikiem). W takim przypadku kompilator zgłosił błąd:
[C++ Error] szosty.cpp (6): E2379 Statement missing ;.
Program szosty.cpp
int main()
{
cout << "Moj czwarty program";
return 0;
}
#include <iostream>
int main()
{
cout << "Moj piaty program";
return 0;
}
#include <iostream>
using namespace std;
int main()
{
cout << "Moj szosty program"
return 0;
}
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I. 7
______________________________________________________________________
©2008 Jerzy Kluczewski
Cudzysłów
W programie siodmy.cpp, wprowadzono drobny błąd, polegający na braku średnika na
końcu instrukcji cout << "Moj szosty program". W takim przypadku kompilator
zgłosił błąd: [C++ Error] siodmy.cpp (3): E2034 Cannot convert ‘char*’ to ‘char’.
Zadeklarowano zmienną dana o typie char. Ten typ pozwala na przechowywanie
znaków oraz niewielkich liczb całkowitych. Programista chciał przypisać tej zmiennej
jeden znak a. Aby to zrobić należy znak ująć w parę apostrofów. Prawidłowym zapisem
jest char dana = ‘a’;
Program siodmy.cpp
Argumenty funkcji
Kolejny błąd często popełniany przez początkujących programistów wiąże się z
przekazywaniem argumentów do funkcji. Pokazuje to przykładowy program
errory2.cpp, którego zadaniem jest obliczenie drugiej potęgi wprowadzonej przez
użytkownika liczby. Za obliczenia odpowiada funkcja drugaPotega. Jaki będzie wynik
działania takiego programu, gdy podamy mu liczbę np. 5 ?
Podaj liczbe 5
Druga potega tej liczby to 5
Program errory2.cpp
int main()
{
char dana = "a";
return 0;
}
#include <iostream>
using namespace std;
void drugaPotega(double liczba)
{
liczba = liczba * liczba;
}
int main()
{
double liczba;
cout << "Podaj liczbe ";
cin >> liczba;
drugaPotega(liczba);
cout << "Druga potega tej liczby to "<< liczba;
system("pause");
return 0;
}
8
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I.
______________________________________________________________________
______________________________________________________________________
©2008 Jerzy Kluczewski
Program nie działa prawidłowo. Stan zmiennej liczba nie zmienił się, wygląda to tak,
jakby funkcja drugaPotega nie została wykonana. Błąd jest w sposobie przekazywania
argumentu do funkcji. Aby na zewnątrz funkcji nastąpiła zmiana argumentu, należy
użyć innego sposobu przekazywania argumentów. Wyróżniamy tutaj dwa sposoby:
•
przekazywanie przez wskaźnik,
•
przekazywanie przez referencję
Pierwszy sposób (program argumenty1.cpp)
Drugi sposób (program argumenty2.cpp)
#include <iostream>
using namespace std;
void drugaPotega(double* liczba)
{
*liczba = (*liczba) * (*liczba);
}
int main()
{
double liczba;
cout << "Podaj liczbe ";
cin >> liczba;
drugaPotega(&liczba);
cout << "Druga potega tej liczby to "<< liczba;
system("pause");
return 0;
}
#include <iostream>
using namespace std;
void drugaPotega(double& liczba)
{
liczba = liczba * liczba;
}
int main()
{
double liczba;
cout << "Podaj liczbe ";
cin >> liczba;
drugaPotega(liczba);
cout << "Druga potega tej liczby to "<< liczba;
system("pause");
return 0;
}
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I. 9
______________________________________________________________________
©2008 Jerzy Kluczewski
III.
Zmienne
Zmienne pozwalają na przechowywanie w programie danych. Każda zmienna ma swój
typ, który określa, jakiego rodzaju dane może ona przechowywać. Typy występujące w
C++ możemy podzielić na następujące główne rodzaje (patrz rysunek Rys. 3):
•
typy arytmetyczne,
•
typ logiczny,
•
typy specjalne.
Rys. 3. Podział typów
Każda zmienna, zanim zaczniemy jej używać, musi zostać wcześniej zadeklarowana.
Deklaracja polega na podaniu typu oraz nazwy zmiennej (kończymy ją średnikiem,
bowiem w C++ deklaracja jest tzw. instrukcją deklarującą).
Została tu zadeklarowana zmienna o nazwie liczba, której typem jest int.
Oznacza to, że będzie ona mogła przechowywać liczby całkowite. W platformach 32-
bitowych liczba ta jest reprezentowana jako słowo 32-bitowe. Zakres wartości takich
liczb całkowitych mieści się w przedziale od -2 147 483 648 do 2 147 483 647
Inicjowanie zmiennych
Pierwsze przypisanie wartości do zmiennej nazywamy jej inicjacją.
Takie przypisanie może odbywać się zarówno po jej deklaracji, jak i w jej deklaracji.
Oto równoważny kod inicjowania zmiennej.
int liczba;
int liczba;
liczba = 100;
int liczba = 100;
10
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I.
______________________________________________________________________
______________________________________________________________________
©2008 Jerzy Kluczewski
Jak nazywać zmienne?
Obowiązują tutaj pewne zasady. Nazwa zmiennej:
•
może składać się z dużych i małych liter oraz cyfr,
•
nie może rozpoczynać się od cyfry,
•
nie można stosować w niej polskich znaków diakrytycznych,
Nazwa zmiennej powinna odzwierciedlać funkcję pełnioną w programie, co bardzo
poprawia czytelność kodu oraz ułatwia jego analizę.
Typ znakowy
Typ znakowy ma nazwę char i służy do reprezentowania znaków, ze zbioru znaków
ASCII. Zmienna typu char zajmuje w pamięci 8 bitów. Za jej pomocą można
przedstawić 256 znaków. Jeśli chcemy przypisać do zmiennej jakiś znak, musimy ująć
go w apostrofy.
Ten typ jest zaliczony do typów arytmetycznych, bo zmienne takie możemy traktować
jako liczby, na przykład znak ‘z’ ma kod ASCII 122. Zmienna typu char, w naszym
przykładzie, w rzeczywistości przechowuje liczbę 122. Zatem na takich zmiennych
można wykonywać proste operacje arytmetyczne (dodawanie i odejmowanie).
Program zmienne1.cpp pokazuje, że jest to możliwe; wyświetli on na ekranie literę ‘y’.
Program zmienne1.cpp
Modyfikatory signed i unsigned
Zakres liczb jaki reprezentują typy całkowite i znakowe można modyfikować. W tym
celu używamy modyfikatorów signed i unsigned.
char zmienna = ‘z’;
#include <iostream>
using namespace std;
int main()
{
char zmienna = 'z';
zmienna = zmienna – 1;
cout << zmienna;
system("pause");
return 0;
}
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I. 11
______________________________________________________________________
©2008 Jerzy Kluczewski
Program zmienne2.cpp
Do zmiennej typu signed int można przypisywać liczby całkowite ujemne i dodatnie.
Do zmiennej typu unsigned int można przypisywać tylko liczby całkowite dodatnie, ale
o większej wartości. Dlatego że w przypadku typu signed int ostatni (31 bit) jest
zarezerwowany do sygnalizowania, czy jest to liczba ujemna, czy dodatnia (czyli
określa znak liczby). W przypadku typu bezznakowego unsigned int, wszystkie bity
reprezentują wartość liczby.
typ
liczba bitów
Zakres
unsigned int X
32
0 <= X <= 4 294 967 295
signed int X
32
-2 147 483 648 <= X <= 2 147 483 647
Uwaga: Liczba kombinacji dla signed int, 2
31
=2 147 483 648, dla unsigned int
2
32
=4 294 967 296.
Liczby zmiennoprzecinkowe
Do reprezentacji liczb zmiennoprzecinkowych, czyli takich z częścią ułamkową służą
typy float, double i long double.
typ
liczba bitów
Zakres
float Y
32
3.4 * (10**-38) <= Y <= 3.4 * (10**+38)
double Y
64
1.7 * (10**-308) <= Y <= 1.7 * (10**+308)
long double Y
80
3.4 * (10**-4932) <= Y <= 3.4 * (10**+4932)
Istnieją dwa sposobu przypisywania wartości takim zmiennych. Pierwszy polega na że
część ułamkową przedstawiamy po kropce dziesiętnej 1023.4. Drugi sposób to zapis
wykładniczy. 10.234e2 oznacza 10,234 razy 10 do potęgi 2.
#include <iostream>
using namespace std;
int main()
{
signed zmienna1 = – 2147483648;
unsigned zmienna2 = 4294967295;
cout << zmienna1 << " , " << zmienna2;
system("pause");
return 0;
}
y = 1023.4;
z = 10.234e2;
12
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I.
______________________________________________________________________
______________________________________________________________________
©2008 Jerzy Kluczewski
if (warunek) instrukcja;
IV.
Instrukcja warunkowa
Instrukcje warunkowe są niezbędne do działania każdego bardziej rozbudowanego
programu. Podstawowa instrukcja warunkowa w C++ to instrukcja if. W najprostszej
postaci można ją zapisać w jednej linii kodu:
Należy ją rozumieć następująco: Jeżeli
warunek jest prawdziwy, to wykonaj
instrukcję. Jako warunek najczęściej wykorzystujemy wyrażenie zawierające
instrukcję porównania, korzystające z jednego z operatorów porównania. Operatory
opisano na rysunku Rys. 4.
Rys. 4. Operatory porównania.
Operator
Przykład
Znaczenie
= =
a = = b
a równe b
! =
a ! = b
a różne od b
<
a < b
a mniejsze od b
>
a > b
a większe od b
< =
a < = b
a mniejsze lub równe b
> =
a > = b
a większe lub równe b
OPERATORY PORÓWNANIA
#include <iostream>
using namespace std;
int main()
{
int a = 5;
int b = 6;
if (a < b) cout << "a jest mniejsze od b" << endl;
system("pause");
return 0;
}
#include <iostream>
using namespace std;
int main()
{
int a = 5;
int b = 6;
if (a > b) cout << "a jest wieksze od b" << endl;
else cout << "a jest mniejsze lub rowne b" << endl;
system("pause");
return 0;
}
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I. 13
______________________________________________________________________
©2008 Jerzy Kluczewski
if (warunek) {
instrukcja1;
instrukcja2;
instrukcja3;
instrukcja4;
}
if (warunek) {
instrukcja1;
}
else {
instrukcja2;
}
int a, b;
cout << "Podaj pierwsza liczbe:" << endl;
cin >> a;
cout << "Podaj druga liczbe:" << endl;
cin >> b;
cout << "a=" << a << ", " << "b=" << b << endl;
if (a = = b) {
cout << "a jest rowne b" << endl;
}
else if (a < b) {
cout << "a jest mniejsze od b" << endl;
}
else if (a > b) {
cout << "a jest wieksze od b" << endl;
}
Gdy po radzeniu warunku, gdy jest on prawdziwy, wystąpi potrzeba wykonania kilku
instrukcji, należy je ująć w nawiasy klamrowe (tzw. blok instrukcji).
Kolejną instrukcją formą instrukcji warunkowej jest instrukcja if…else. Jej ogólna
postać jest następująca:
Instrukcja warunkowa zagnieżdżona
Bardzo często w programach zachodzi konieczność sprawdzania wielu warunków w
jednej instrukcji if. Nazywamy ją zagnieżdżoną instrukcją warunkową. Przykładowy
kod wykorzystujący tą postać instrukcji pokazany jest następujący:
14
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I.
______________________________________________________________________
______________________________________________________________________
©2008 Jerzy Kluczewski
V.
Rodzaje zmiennych
Korzystając ze zmiennych, należy przestrzegać ważnych zasad. Inaczej program będzie
działał losowo. Zasady te, to świadomy wybór deklarowania zmiennych jako lokalnych
lub globalnych.
Zmienne lokalne
W programie zmienne2.cpp, zmienne zadeklarowane w ciele funkcji nazywamy
zmiennymi lokalnymi. Nazwa wzięła się stąd, że są one dostępne jedynie w funkcji, w
której zostały zadeklarowane i żadna inna funkcja nie ma do nich dostępu. Można je
zatem traktować jako prywatną własność danej funkcji.
Program zmienne2.cpp.
Każda próba użycia zmiennej lokalnej poza jej zasięgiem (poza nawiasami
klamrowymi wewnątrz których została zadeklarowana) skutkuje błędem kompilatora
Undefined symbol.
#include <iostream>
using namespace std;
void funkcja1()
{
int liczba1;
int liczba2;
liczba1 = 100;
liczba2 = 200;
cout << "Liczba1=" << liczba1 << endl;
cout << "Liczba2=" << liczba2 << endl;
}
int main()
{
funkcja1();
system("pause");
return 0;
}
Zasięg zmiennych
lokalnych
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I. 15
______________________________________________________________________
©2008 Jerzy Kluczewski
#include <iostream>
using namespace std;
int liczba1;
void funkcja3()
{
liczba1 = 200;
}
int main()
{
liczba1 = 100;
cout << "liczba1 przed wywolaniem fukcji funkcja3: ";
cout << liczba1 << endl;
funkcja3();
cout << "liczba1 po wywolaniu fukcji funkcja3: ";
cout << liczba1 << endl;
system("pause");
return 0;
}
Zmienne globalne
Zmienne globalne, jak łatwo się domyślić są przeciwieństwem zmiennych lokalnych.
Nie zależą one do żadnej funkcji, mogą być za to wykorzystywane we wszystkich
funkcjach. Zmienna globalna musi zostać zadeklarowana przed definicjami funkcji.
Najlepiej to zobaczyć na konkretnych przykładach.
W programie zmienne3.cpp została zadeklarowana jedna zmienna globalna o nazwie
liczba1 oraz dwie funkcje main i funkcja3. Ponieważ deklaracja zmiennej liczba1
znajduje się przed definicjami funkcji, zmienna ta jest widoczna w obu funkcjach.
Program zmienne3.cpp.
Program wygeneruje na ekranie następujące wiersze:
liczba1 przed wywolaniem fukcji funkcja3: 100
liczba1 po wywolaniu fukcji funkcja3: 200
Zmienne modułowe
Zmienne zadeklarowane na początku kodu źródłowego w rzeczywistości są globalne
tylko obrębie jednego pliku z kodem źródłowym. W przypadku prostych programów,
składających się z jednego tylko pliku, są oczywiście globalne dla całego programu. W
przypadku bardziej skomplikowanych aplikacji wieloplikowych, niestety nie. Dlatego
zmienne te nazywa się również zmiennymi modułowymi lub zmiennymi o zasięgu
modułowym.
16
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I.
______________________________________________________________________
______________________________________________________________________
©2008 Jerzy Kluczewski
#include <iostream>
using namespace std;
int liczba1 = 200;
int main()
{
int liczba1 = 100;
cout << "zmienna lokalna liczba1 ma wartosc: " << liczba1 << endl;
cout << "zmienna globalna liczba1 ma wartosc: " << ::liczba1 << endl;
::liczba1 = liczba1;
cout << "zmienna globalna liczba1 ma wartosc: " << ::liczba1 << endl;
system("pause");
return 0;
}
Przesłanianie zmiennych globalnych
W programie zmienne4.cpp mamy zadeklarowane dwie zmienne: liczba1 i liczba1.
Wydawałoby się że występuje tu konflikt nazw, bowiem obie mają tą są nazwę. Jest to
jednak konflikt pozorny – wszystko jest w porządku, dlatego że to kompilator decyduje
która zmienna jest zmienną globalną a która lokalną. Poza funkcją main program widzi
tylko zmienną globalną równą 200. Funkcja main rozpoczynając swoje działanie, widzi
tylko zmienną lokalną o wartości 100. Po prostu wewnątrz funkcji zmienna globalna
zostaje przesłonięta przez lokalną.
Program zmienne4.cpp.
Wynik działania programu:
zmienna lokalna liczba1 ma wartosc: 100
zmienna globalna liczba1 ma wartosc: 200
zmienna globalna liczba1 ma wartosc: 100
Na szczęście język C++ posiada sposób na uzyskiwanie dostępu do zmiennych
globalnych w momentach w których są one przesłonięte. Służy do tego operator
zasięgu (podwójny dwukropek). W instrukcji ::liczba1 = liczba1; następuje
przekazanie wartości zmiennej lokalnej do zmiennej globalnej. Zmienna globalna
otrzymuje wartość100, co potwierdza wyniki działania przykładowego programu.
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I. 17
______________________________________________________________________
©2008 Jerzy Kluczewski
int* liczba1;
int* liczba2;
int* liczba3;
int liczba1;
int liczba2;
int liczba3;
int* liczba1, liczba2, liczba3;
int liczba1, liczba2, liczba3;
VI.
Wskaźniki
Znajomość wskaźników jest potrzebna każdemu programiście, jak człowiekowi tlen do
oddychania. Wskaźnik to specjalny typ danych, który wskazuje na inne dane.
Najczęściej wskaźnik traktuje się jako wskazanie pewnego miejsca pamięci komputera.
W miejscu tym może znajdować się zmienna, funkcja czy inny obiekt. Praktycznie
można skonstruować wskaźnik do dowolnego typu danych, czy to do typu int, char,
czy też stworzonego przez programistę.
Typ wskaźnikowy
Typ wskaźnikowy konstruuje się w bardzo prosty sposób – do nazwy typu
(podstawowego) należy dodać znak * (gwiazdka). Oznacza to że jeśli typem
podstawowym jest int, to wskaźnik do int zapisywany jest jako int*.
Znak * jest w C++ wykorzystywany jako operator mnożenia, ale kompilator bez trudu
poradzi sobie z tą sytuacją, odczytując właściwe znaczenie znaku * z kontekstu.
Przykład deklaracji zmiennych typu int*:
Zadeklarowano trzy wskaźniki (trzy zmienne wskaźnikowe) wskazujące typ int.
Przykład deklaracji zmiennych typu int:
Zadeklarowano trzy zmienne typu int.
UWAGA: Przykład nieprawidłowej deklaracji zmiennych typu int*:
Zadeklarowano jeden wskaźnik liczba1 wskazujący typ int oraz dwie zmienne typu
int. Należy starać się unikać takiego stylu deklarowania zmiennych wskaźnikowych!
Przykład prawidłowej deklaracji zmiennych typu int:
Zadeklarowano trzy zmienne typu int.
18
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I.
______________________________________________________________________
______________________________________________________________________
©2008 Jerzy Kluczewski
#include <iostream>
using namespace std;
int* liczba1;
int* liczba2;
int* liczba3;
int liczba4;
int main()
{
liczba4 = 100;
liczba1 = &liczba4;
cout << "zmienna liczba4 ma wartosc: " << liczba4 << endl;
cout << "wskaznik liczba1 ma wartosc: " << liczba1 << endl;
system("pause");
return 0;
}
cout << *liczba1 << endl;
W przykładowym programie pointer1.cpp mamy zadeklarowane trzy wskaźniki
(zmienne wskaźnikowe): liczba1, liczba2, liczba3. oraz jedną zmienną liczba4 typu int.
Zmiennej liczba4 przypisujemy wartość 100, następnie zmiennej wskaźnikowej
liczba1, przypisujemy adres zmiennej liczba4. Znak & (ampersand) jest tzw.
operatorem pobrania adresu (operator adresowy). Po wyświetleniu wartości zmiennych
liczba4 i liczba1, otrzymujemy następujący wyniki:
zmienna liczba4 ma wartosc: 100
wskaznik liczba1 ma wartosc: 4203528
Drugą linię należy interpretować jako adres w pamięci komputera. Przypomnijmy sobie
ż
e zmienna wskaźnikowa to taka sobie prosta zmienna, tylko adres jakiegoś miejsca w
pamięci. Zmienna liczba4 znajduje się pod adresem 4203528 w pamięci .
Program pointer1.cpp
W języku C++ istnieje też operator odwrotny do operatora & a mianowicie operator
wyłuskania wartości (dereferencji) spod adresu pamięci. I o dziwo jest to znak
gwiazdka *, ale użyta nie w deklaracji, tylko w instrukcji.
Jeśli do programu pinter1.cpp dopiszemy instrukcję
to uzyskamy następujący wynik:
100
Co należy zinterpretować jako „wartość 100 jest przechowywana w komórce pamięci o
adresie liczba1”.
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I. 19
______________________________________________________________________
©2008 Jerzy Kluczewski
typ_zwracany (*nazwa_wskaźnika) (argumenty funkcji);
void (*pfun) ();
pfun = &fun();
(*pfun)();
fun ();
#include <iostream>
using namespace std;
void fun()
{
cout << "Wykonanie funkcji..." << endl;
}
int main()
{
void (*pfun)();
// deklaracja wskaźnika do funkcji
pfun = &fun;
// przypisanie adresu funkcji do wskaźnika
(*pfun)();
// wywołanie funkcji za pomocą wskaźnika pfun
fun();
// wywołanie bezpośrednie funkcji.
system("pause");
return 0;
}
Wskaźniki do funkcji
W C++ można tworzyć wskaźniki do funkcji, ponieważ kod funkcji znajduje się
przecież w pamięci, a zatem ma adres. Konstrukcja wskaźnika jest trudniejsza niż
wskaźnika do zmiennej. Schemat definicji wskaźnika do funkcji jest następujący:
Konkretny przykład deklaracji wskaźnika do funkcji:
Potem należy przypisać adres funkcji do wskaźnika.
A na koniec można wywołać funkcję za pomocą wskaźnika pfun.
Ostatnia instrukcja jest równoważna następującej instrukcji (wywołanie bezpośrednie
funkcji):
Kod programu pointer2.cpp pokazuje wykorzystanie wskaźnika do wywołania funkcji
oraz operatora adresowego & do pobrania adresu funkcji.
Program pointer2.cpp
20
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I.
______________________________________________________________________
______________________________________________________________________
©2008 Jerzy Kluczewski
#include <iostream>
using namespace std;
class Tosoba
{
public:
char* nazwisko;
char* imie;
};
int main()
{
TOsoba x;
x.nazwisko = "Kowalski";
x.imie = "Jan";
cout << x.imie << " " << x.nazwisko << endl;
system("pause");
return 0;
}
Wskaźniki do funkcji na skróty
Zamiast pisać:
pfun = &fun;
można zastosować równoważną konstrukcję:
pfun = fun;
Podobnie zamiast:
(*pfun)();
można napisać:
pfun();
ale taki zapis może wprowadzić programistę w błąd, sugeruje bowiem, że w kodzie
programu istnieje funkcja o nazwie pfun, której w rzeczywistości nie ma.
Wskaźniki do klas
W odniesieniu do klas w C++ można używać wskaźników w takim sam sposób, jak
używaliśmy dotychczas (za pomocą operatorów * i &), lecz z użyciem specjalnego
symbolu dwuznakowego –>.
Najlepiej będzie zaobserwować sposoby dostępu do składowych klasy na następującym
przykładzie klasy Tosoba:. Operacje będą wykonywane na obiekcie x klasy Tosoba.
Program pointer3.cpp – dostęp do klasy bez użycia wskaźników.
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I. 21
______________________________________________________________________
©2008 Jerzy Kluczewski
#include <iostream>
using namespace std;
class Tosoba
{
public:
char* nazwisko;
char* imie;
int wiek;
};
int main()
{
TOsoba* pointerx;
pointerx–>nazwisko = "Kowalski";
pointerx–>imie = "Jan";
pointerx–>wiek = 25;
cout << pointerx–>imie << " " << pointerx–>nazwisko << endl;
cout << "Wiek: " << pointerx–>wiek << endl;
system("pause");
return 0;
}
#include <iostream>
using namespace std;
class Tosoba
{
public:
char* nazwisko;
char* imie;
int wiek;
};
Drugi przykład (pointer4.cpp) zamiast deklaracji obiektu x klasy Tosoba zawiera
deklarację wskaźnika pointerx do klasy Tosoba.
Program pointer4.cpp – dostęp do klasy za pomocą wskaźników.
Wskaźnik do składowej klasy
Język C++ dostarcza mechanizm specjalnego dostępu do składowych klasy; jest to
alternatywny typ wskaźnika, a mianowicie wskaźnik do składowej klasy. Oto ten sam
przykład z uwzględnieniem nowego wskaźnika (program pointer5.cpp).
22
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I.
______________________________________________________________________
______________________________________________________________________
©2008 Jerzy Kluczewski
// ciąg dalszy programu
int main()
{
Tosoba x;
char* Tosoba::*wsk1;
char* Tosoba::*wsk2;
int Tosoba::*wsk3;
x.nazwisko = "Nowak";
x.imie = "Jan";
x.wiek = 25;
wsk1 = &Tosoba::nazwisko;
wsk2 = &Tosoba::imie;
wsk3 = &Tosoba::wiek;
cout << x.*wsk2 << " " << x.*wsk1 << endl;
cout << x.*wsk3 << endl;
system("pause");
return 0;
}
for (wyrażenie początkowe; wyrażenie warunkowe; wyrażenie modyfikujące)
{
instrukcje do wykonania;
}
Program pointer5.cpp – dostęp do składników klasy za pomocą operatora zasięgu :: oraz
wskaźników.
VII.
Iteracje czyli pętle
Aby fragment kodu wykonać wielokrotnie należy wykorzystać pętle. Pętla to instrukcja
wykonująca pewne, te same czynności wiele razy. W C++ występują dwie główne
odmiany pętli : instrukcja for i instrukcja for.
Pętla for
Ogólna postać instrukcji for to:
Wyrażenie początkowe służy do zainicjowania zmiennej używanej jako licznik pętli.
Wyrażenie warunkowe określa warunek, jaki musi być spełnionym, aby wykonać
kolejne przejście w pętli (następną iterację).
Wyrażenie modyfikujące służy zwykle do modyfikacji zmiennej będącej licznikiem.
Działanie pętli for zostanie pokazane na przykładzie programu for1.cpp, którego
zadaniem jest 10-krotne wyświetlenie napisu „C++ Language”.
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I. 23
______________________________________________________________________
©2008 Jerzy Kluczewski
#include <iostream>
using namespace std;
int main()
{
for (int i = 1; i <= 10; i++)
{
cout << "C++ Language" << endl;
}
system("pause");
return 0;
}
#include <iostream>
using namespace std;
int main()
{
for (int i = 0; i < 10; i++) { cout << i << endl; }
system("pause");
return 0;
}
Program for1.cpp.
Objaśnienie działania instrukcji for:
1.
Do zmiennej i postaw 1
2.
Jeżeli zmienna i jest większa od 10, to skocz do kroku 6
3.
Wykonaj instrukcje w nawiasach klamrowych (cout << "C++ Language" <<
endl;)
4.
Zwiększ wartość zmiennej i o 1
5.
Przejdź do kroku 2
6.
Zakończ instrukcję for.
Następny przykład (program for2cpp) pokazuje typowe wykorzystanie licznika pętli:
Program for2.cpp.
Objaśnienie działania instrukcji for:
1.
Do zmiennej i postaw 0
2.
Jeżeli zmienna i jest większa lub równa 10, to skocz do kroku 6
3.
Wyświetl wartość zmiennej i przejdź do nowego wiesza
4.
Zwiększ wartość zmiennej i o 1
5.
Przejdź do kroku 2
6.
Zakończ instrukcję for.
24
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I.
______________________________________________________________________
______________________________________________________________________
©2008 Jerzy Kluczewski
while (wyrażenie warunkowe)
{
instrukcje do wykonania;
}
#include <iostream>
using namespace std;
int main()
{
int i = 1; // tutaj trzeba zainicjowac wartosc i
while (i <= 10)
{
cout << "C++ Language" << endl;
i++;
}
system("pause");
return 0;
}
Pętla while
Pętla while służy do tego samego celu co pętla for, lecz stosuje się ją, gdy liczba
kroków pętli nie jest z góry znana.
Ogólna postać instrukcji while to:
Wyrażenie warunkowe określa warunek, jaki musi być spełnionym, aby wykonać
instrukcje zawarte w pętli (w nawiasach klamrowych).
Działanie pętli while zostanie pokazane na przykładzie programu while1.cpp, którego
zadaniem jest 10-krotne wyświetlenie napisu „C++ Language”.
Program while1.cpp
Objaśnienie działania instrukcji while:
1.
Do zmiennej i postaw 1
2.
Jeżeli zmienna i jest większa od 10, to skocz do kroku 6
3.
Wykonaj instrukcje w nawiasach klamrowych:
a.
cout << "C++ Language" << endl;
b.
zwiększ wartość zmiennej i o 1
4.
Przejdź do kroku 2
5.
Zakończ instrukcję while.
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I. 25
______________________________________________________________________
©2008 Jerzy Kluczewski
do {
instrukcje do wykonania;
} while (wyrażenie warunkowe)
#include <iostream>
using namespace std;
int main()
{
int i = 0; // tutaj trzeba zainicjowac wartosc i
do
{
cout << "C++ Language" << endl;
i++;
} while (i < 10);
system("pause");
return 0;
}
Pętla do … while
Pętla do …while jest odmianą pętli while, która różni się od niej tym, że
zawsze
bezwarunkowo
wykonowany jest jej pierwszy krok, a potem, sprawdzany warunek
(warunek jest sprawdzany na jej końcu).
Ogólna postać instrukcji do … while to:
Wyrażenie warunkowe określa warunek, jaki musi być spełnionym, aby przejść do
następnego kroku pętli (następnej iteracji).
Działanie pętli do … while zostanie pokazane na przykładzie programu while2.cpp,
którego zadaniem jest 10-krotne wyświetlenie napisu „C++ Language”.
Program while2.cpp
Objaśnienie działania instrukcji do … while:
1.
Do zmiennej i postaw 0
2.
Wykonaj instrukcje w nawiasach klamrowych:
a.
cout << "C++ Language" << endl;
b.
zwiększ wartość zmiennej i o 1
3.
Jeżeli zmienna i jest mniejsza od 10, to skocz do kroku 2
4.
Zakończ instrukcję while.
26
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I.
______________________________________________________________________
______________________________________________________________________
©2008 Jerzy Kluczewski
#include <iostream>
using namespace std;
int main()
{
int i = 0;
do {
cout << i << endl;
i++;
} while (true);
system("pause");
return 0;
}
#include <iostream>
using namespace std;
int main()
{
int i = 0;
do {
cout << i << endl;
i++;
} while (false);
system("pause");
return 0;
}
Zad. 1. Zadanie do samodzielnego rozwiązania:
Ile razy wykona się następująca pętla?
Zad. 2. Zadanie do samodzielnego rozwiązania:
Ile razy wykona się następująca pętla?
Instrukcja break
Jak przerwać pętlę? Służy do tego instrukcja break. Wyjaśnienie jak ona działa
najlepiej przedstawić na przykładzie (patrz program break1.cpp) .
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I. 27
______________________________________________________________________
©2008 Jerzy Kluczewski
#include <iostream>
using namespace std;
int main()
{
int i = 1;
while (i <= 10) {
if (i == 6) break;
else cout << i << endl;
i++;
}
system("pause");
return 0;
}
#include <iostream>
using namespace std;
int main()
{
for (int i=1; i <= 20; i++)
{
if (i % 2 != 0) continue;
cout << i << endl;
i++;
}
system("pause");
return 0;
}
Program break1.cpp.
Zad. 3. Zadanie do samodzielnego rozwiązania:
Jakie liczby wyświetli program break1.cpp?
Instrukcja continue
Instrukcja break powodowała przerwanie pętli. Użycie wewnątrz pętli instrukcji
continue spowoduje że bieżący krok zostanie przerwany i nastąpi przejście do
kolejnego kroku (iteracji), co obrazuje poniższy przykład (patrz program continue1.cpp)
Program continue1.cpp.
Operator % służy do obliczenia reszty z dzielenia dwóch liczb całkowitych.
28
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I.
______________________________________________________________________
______________________________________________________________________
©2008 Jerzy Kluczewski
#include <iostream>
using namespace std;
int main()
{
for (int i=1; i <= 10; i++)
{
if (i % 2 == 0) continue;
cout << " " << i;
i++;
}
cout << endl;
system("pause");
return 0;
}
Objaśnienie działania instrukcji for i continue w programie continue1.cpp:
1.
Do zmiennej i postaw 1
2.
Jeżeli wartość zmiennej i jest większa od 20, to skocz do kroku 6
3.
Jeżeli wartość zmiennej jest niepodzielna przez 2 to skocz do kroku 2
4.
Wyświetl wartość zmiennej i
5.
Zwiększ wartość zmiennej i o 1
6.
Przejdź do kroku 2
7.
Zakończ instrukcję for.
Zad. 4. Zadanie do samodzielnego rozwiązania:
Jakie liczby wyświetli program continue2.cpp?
Program continue2.cpp.
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I. 29
______________________________________________________________________
©2008 Jerzy Kluczewski
VIII.
Macierze w życiu - wprowadzenie
W naszym codziennym życiu bardzo często posługujemy się tabelkami, różnego
rodzaju siatkami, zeszytami w kratkę, w grach planszowych i komputerowych
szachownicami, planszami, zonami, strefami a w geografii siatkami kartograficznymi.
Do tworzenia obrazów graficznych stosujemy tzw. rastry. Obrazy edytowane za
pomocą programu Paint są nazywane rastrami.
Szachownica jest przykładem macierzy składającej się z 64 pól (8 wierszy x 8 kolumn).
A B C D E F G H
1
2
3
4
5
6
7
8
Gdy chcemy określić położenie jakiejś figury, podajemy współrzędne pola na którym
ona stoi, np. 3D.
Co to jest rozmiar macierzy?
W przypadku naszej szachownicy jest to liczba jej wierszy i kolumn, czyli 8 x 8.
Wiersze są ponumerowane kolejno od 1 do 8, a kolumny są oznaczone literami od A do
H.
Na pewno każdy z nas zna grę w statki. W tej grze też posługujemy się planszami
(macierzami) o rozmiarze 10 x 10. Wiersze są ponumerowane od 1 do 10, kolumny
literami od A do J. Za pomocą krzyżyków oznaczamy maszty.
A B C D E F G H I J
1
2 x x x x x
3
4 x x x x x
5 x x
6 x
7 x
8 x
9 x x x x
10 x
Podobnie jak w grze w szachy, pozycje masztów statków określamy za pomocą
współrzędnych np. maszty czteromasztowca znajdują się w polach: 4A, 4B, 5B, 5C.
Ta plansza jest macierzą o rozmiarze 10 x 10 (czyt. dziesięć na dziesięć).
30
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I.
______________________________________________________________________
______________________________________________________________________
©2008 Jerzy Kluczewski
Aby zbliżyć się do języka matematyków, zamieńmy oznaczenia masztów w ten sposób,
ż
e liczba 1 oznacza maszt, a liczba 0 oznacza brak masztu (puste pole). Można tak
zrobić ponieważ nie zmieni to zasad gry.
Macierz o 10 wierszach i 10 kolumnach
A B C D E F G H I J
1 0 0 0 0 0 0
0 0 0 0
2 0 1 0 0 1 1
1 0 0 1
3 0 0 0 0 0 0
0 0 0 0
4 1 1 0 0 0 0
1 1 1 0
5 0 1 1 0 0 0
0 0 0 0
6 0 0 0 0 0 0
0 0 1 0
7 0 0 0 0 0 0
0 0 1 0
8 0 0 1 0 0 0
0 0 0 0
9 0 0 1 0 1 1
0 0 0 1
10 0 0 0 0 0 0
0 1 0 0
Nadal mamy macierz o wymiarach 10 x 10. Także kolumny są oznaczone nadal literami
od A do J. Gdybyśmy zmienili numerację kolumn na liczbową od 1 do 10, to
uzyskalibyśmy prawdziwą macierz, taką jaką posługują się matematycy.
1 2 3 4 5 6 7 8 9 10
1 0 0 0 0 0 0 0 0 0 0
2 0 1 0 0 1 1 1 0 0 1
3 0 0 0 0 0 0 0 0 0 0
4 1 1 0 0 0 0 1 1 1 0
5 0 1 1 0 0 0 0 0 0 0
6 0 0 0 0 0 0 0 0 1 0
7 0 0 0 0 0 0 0 0 1 0
8 0 0 1 0 0 0 0 0 0 0
9 0 0 1 0 1 1 0 0 0 1
10 0 0 0 0 0 0 0 1 0 0
Teraz wiersze i kolumny ponumerowane są jednolicie, czyli od 1 d o10. W
prawdziwych macierzach posługujemy się właśnie liczbami.
W matematyce pola w macierzy nazywamy elementami (komórkami). Z kolei
numerowanie wierszy i kolumn nazywamy indeksowaniem elementów (pól).
Oto przykład innej macierzy:
Macierz A o 5 wierszach i 8 kolumnach
1 2 3 4 5 6 7 8
1 3 5 9 2 6 8 2 9
2 5 6 4 3 1 2 3 4
3 1 1 3 7 2 4 0 9
4 4 7 5 8 2 1 9 0
5 6 2 7 3 6 2 1 8
Oznaczymy ją dużą literą A.
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I. 31
______________________________________________________________________
©2008 Jerzy Kluczewski
Indeksowanie elementów macierzy
Element lub jak kto woli pole o współrzędnych [1, 3] przechowuje wartość 9. Najpierw
podajemy numer wiersza a potem numer kolumny. Mówimy, że indeksujemy
elementy macierzy – pierwszym indeksem i = 1 do10 oznaczamy wiersze, a drugim
indeksem j = 1 do 10 oznaczamy kolumny.
j= j= j= j= j= j= j= j=
1 2 3 4 5 6 7 8
i= 1 3 5 9 2 6 8 2 9
i= 2 5 6 4 3 1 2 3 4
i= 3 1 1 3 7 2 4 0 9
i= 4 4 7 5 8 2 1 9 0
i= 5 6 2 7 3 6 2 1 8
Jak oznaczamy elementy macierzy w matematyce?
Element znajdujący się w miejscu przecięcia się wiersza 1 z kolumną 3 oznaczamy
A[1,3] albo a
13
– co czytamy następująco: „element macierzy A o indeksie (wierszu)
równym 1 i indeksie (kolumnie) równym 3”. Element A[1,3] równa się 9, co
zapisujemy następująco:
A[1,3] = 9
W matematyce posługujemy się oznaczeniami ogólnymi, szczególnie we wzorach.
Dlatego ogólne oznaczenie jakiegoś dowolnego elementu w macierzy A wygląda
następująco: A[i,j] albo a
ij
– co czytamy następująco: ”element macierzy A o wierszu i-
tym oraz kolumnie j-tej”. Element A[i,j] znajduje się na przecięciu wiersza i-tego z
kolumną j-tą. Symbole i , j są tzw. indeksami macierzy.
Macierze które mają tylko jeden wiersz nazywamy macierzami jednowymiarowymi, a
macierze mające wiele wierszy i wiele kolumn nazywamy macierzami
dwuwymiarowymi.
Macierz jednowymiarowa
1 2 3 4 5 6 7 8
1
3 5 9 2 6 8 2 9
Macierz dwuwymiarowa
1 2 3 4 5 6 7 8
1 3 5 9 2 6 8 2 9
2 5 6 4 3 1 2 3 4
3 1 1 3 7 2 4 0 9
4 4 7 5 8 2 1 9 0
5 6 2 7 3 6 2 1 8
32
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I.
______________________________________________________________________
______________________________________________________________________
©2008 Jerzy Kluczewski
Macierze w informatyce
Macierze w informatyce też istnieją, tylko inaczej się nazywają – nazywamy je
tablicami. Tablice są bardzo przydatne, bo jakbyśmy napisali program komputerowy
symulujący np. grę w statki, czy w szachy.
Wyobraźmy sobie taką macierz, która przechowuje litery alfabetu łacińskiego oraz ma 6
wierszy i 9 kolumn. Macierz tą oznaczmy literą B.
Macierz B (o rozmiarach 6 x 9)
0 1 2 3 4 5 6 7 8
0 a j v n r l n r l
1 x b k s m i s m i
2 u t c n l p n l p
3 e f o d m o d m o
4 d p g h e n h e n
5 r c y a g f a g f
O ile w podręcznikach matematycznych nie spotkamy takiego przykładu, to w
informatyce występuje on często i jest jak najbardziej prawidłowy. Dzieje się tak
dlatego że każdą literę komputer przechowuje jako kod liczbowy (tzw. kod ASCII).
W języku C++ macierz B będzie nazywana tablicą B, a nawet można ją nazwać
dwuwymiarową tablicą znakową.
Postać macierzy B w matematyce:
=
69
68
67
66
65
64
63
62
61
59
58
57
56
55
54
53
52
51
49
48
47
46
45
44
43
42
41
39
38
37
36
35
34
33
32
31
29
28
27
26
25
24
23
22
21
19
18
17
16
15
14
13
12
11
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
b
B
Postać tablicy B w informatyce (jej odwzorowanie w języku C++):
char B [6] [9];
W języku C++ macierz B jest tablicą dwuwymiarową o indeksach 0..5 (czyt. od 0 do
5) oraz 0..8 (czyt. od 0 do 8) – zawiera 6 wierszy i 9 kolumn. Każdy element tablicy B
przechowuje wartość typu znakowego char (litery też są znakami).
Aby wyświetlić zawartość elementu tablicy B z 0 wiersza i 1 kolumny, należy
posłużyć się instrukcją cout << B [ 0 ][ 1 ].
Natomiast żeby wstawić jakiś znak do tablicy B w 0 wierszu i 1 kolumnie, użyjemy
instrukcji B [ 0 ][ 1 ] = ‘@’.
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I. 33
______________________________________________________________________
©2008 Jerzy Kluczewski
Wyobraźmy sobie taką macierz, która przechowuje liczby rzeczywiste oraz ma 4
wiersze i 2 kolumny. Macierz tą oznaczmy literą C.
Macierz C (o rozmiarach 4 x 2)
0
1
0
1.25 34.30
1
-0.45 63.70
2
45.90 14.60
3
23.00 -3.33
Postać tablicy C w języku C++:
float C [4] [2];
Tablica C jest tablicą dwuwymiarową o indeksach 0..3 (czyt. od 0 do 3) oraz 0..1 (czyt.
od 0 do 1), czyli zawiera 4 wiersze i 2 kolumny. Każdy element tablicy C przechowuje
wartość typu rzeczywistego float.
Aby wyświetlić liczbę z tablicy C (np. z 3 wiersza i 1 kolumny), należy posłużyć się
instrukcją cout << C [ 3 ][ 1] << endl;
Natomiast żeby wstawić liczbę do tablicy C w 3 wierszu i 1 kolumnie, użyjemy
instrukcji
C [ 3 ][ 1 ] = 1.20;
Uwaga: w językach programowania zamiast przecinka oddzielającego część całkowitą
od ułamkowej (liczba 1,20) stosuje się kropkę (1.20). W arkuszu kalkulacyjnym (Excel
w wersji polskiej) stosuje się przecinek. Warto o tym pamiętać, żeby uniknąć błędów
podczas tworzenia programów w językach C++, Pascal, Delphi i innych.
Jak wyzerować macierz (tablicę) ?
Wyzerowanie tablicy polega na wstawieniu zer do jej wszystkich elementów. Należy
użyć dwóch zagnieżdżonych pętli for:
for (int i = 0; i<4; i++)
for (int j = 0; j<2) C[ i ][ j ] = 0.0;
Po wykonaniu tych pętli, zawartość tablicy C będzie wyglądała następująco:
0
1
0
0.00
0.00
1
0.00
0.00
2
0.00
0.00
3
0.00
0.00
34
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I.
______________________________________________________________________
______________________________________________________________________
©2008 Jerzy Kluczewski
int tab[10];
int liczba;
tab[0] = 100;
liczba = *tab;
cout << "pierwszy element tablicy: "<< *tab << endl;
cout << "zawartosc zmiennej liczba: "<< liczba << endl;
#include <iostream>
using namespace std;
int main()
{
int tab[10];
int* ptab1; // wskaźnik;
int* ptab2; // wskaźnik
ptab1 = tab;
ptab2 = &tab[0];
cout << "zawartosc zmiennej ptab1: "<< ptab1 << endl;
cout << "zawartosc zmiennej ptab2: "<< ptab2 << endl;
system("pause");
return 0;
}
IX.
Tablice i wskaźniki
Może to dziwne, ale tak to już jest w języku C++ , że nazwa tablicy jest wskaźnikiem.
Wskaźnik ten wskazuje na pierwszy jej element (jest to adres pierwszego elementu
tablicy).
Oznacza to, że jeśli zastosujemy operator wyłuskania (dereferencji) * w stosunku do
nazwy tablicy, to otrzymamy wartość jej pierwszego elementu. Pokazuje to następujący
fragment kodu:
Ponieważ nazwa tablicy jest wskaźnikiem do jej pierwszego elementu , to można ją
przypisywać do zmiennej wskaźnikowej. Patrz program tablice2.cpp.
Program tablice2.cpp.
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I. 35
______________________________________________________________________
©2008 Jerzy Kluczewski
#include <iostream>
using namespace std;
int main()
{
char tab[12] = {'C','+','+',' ','L','a','n','g','u','a','g','e'};
for (int i=0; i<=11; i++)
{
cout << *(tab+i);
}
cout << endl;
system("pause");
return 0;
}
#include <iostream>
using namespace std;
int main()
{
char tab[12] = {'C','+','+',' ','L','a','n','g','u','a','g','e'};
char* ptab = tab;
for (int i=0; i<=11; i++) {
cout << *(ptab++);
}
cout << endl;
system("pause");
return 0;
}
Arytmetyka wskaźników
Na wskaźnikach można wykonywać operacje arytmetyczne: dodawanie, odejmowanie,
inkremencję (zwiększanie o 1), dekremencję (zmniejszanie o 1).
Pozwala to na swobodny dostęp (adresowanie pamięci komputera) do elementów
tablicy. W poniższym programie (tablice3.cpp) instrukcja tab+i oznacza przesuniecie
adresu od wskaźnika o liczbę i, a instrukcja *(tab+i) oznacza element tablicy
wskazywany przez adres tab+i. Sam wskaźnik nie jest modyfikowany.
Program tablice3.cpp.
W następnym programie (tablice4.cpp) nastąpi modyfikacja samego wskaźnika. Musi
on być jednak formalnie zadeklarowany jako typ wskazujący na inny typ. Nie może to
być nazwa tablicy. Instrukcja char* ptab = tab; kopiuje adres pierwszego elementu
tablicy do wskaźnika ptab. Instrukcja *(ptab++) oznacza element tablicy wskazywany
przez adres ptab. ptab++ oznacza przesuniecie wskaźnika o 1.
Program tablice4.cpp.
36
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I.
______________________________________________________________________
______________________________________________________________________
©2008 Jerzy Kluczewski
#include <iostream>
using namespace std;
int main()
{
char tab[12] = {'C','+','+',' ','L','a','n','g','u','a','g','e'};
for (int i=0; i<=11; i++)
{
cout << tab[i];
}
cout << endl;
system("pause");
return 0;
}
#include <iostream>
using namespace std;
int main()
{
char tab[12] = {'C','+','+',' ','L','a','n','g','u','a','g','e'};
char* ptab = tab;
for (int i=0; i<=12; i++) {
cout << *(ptab++);
}
cout << endl;
system("pause");
return 0;
}
Program równoważny bez użycia wskaźników, to program tablice5.cpp. Program ten
używa indeksowania elementów tablicy.
Program tablice5.cpp.
Niebezpieczne operacje
Podczas wykonywania operacji na wskaźnikach należy bardzo uważać, aby nie
przekroczyć dopuszczalnego zakresu pamięci. Bardzo łatwo wyjść poza obszar
zadeklarowanej tablicy.
Program tablice6cpp.
Program tablice6cpp wyświetli na ekranie o jeden znak za dużo.
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I. 37
______________________________________________________________________
©2008 Jerzy Kluczewski
#include <iostream>
using namespace std;
int main()
{
char* text = "To jest trudne";
cout << text << endl;
text = text + 3;
cout << text << endl;
text = text + 5;
cout << text << endl;
system("pause");
return 0;
}
Przechowywanie napisów
Do przechowywania ciągów znaków (czyli przeważnie tekstów, napisów), służą
zmienne wskaźnikowe typu char*. Przechowują one ciąg znaków ASCII zakończony
znakiem specjalnym ‘\0’. Natomiast zmienna wskaźnikowa przechowuje adres
(wskaźnik) który wskazuje na pierwszy znak tego ciągu.
Tak jak w przypadku tablic, w przypadku tekstów, można też posługiwać się
wskaźnikami i je modyfikować (z należytą ostrożnością). Pokazuje to niniejszy
przykład – program tablice7.cpp.
Program tablice7cpp.
Program wyświetli na ekranie:
To jest trudne
jest trudne
trudne
38
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I.
______________________________________________________________________
______________________________________________________________________
©2008 Jerzy Kluczewski
new typ_danych;
new int;
int* pInt = new int;
#include <iostream>
using namespace std;
int main()
{
int* pInt = new int;
*pInt = 100;
cout << *pInt << endl;
delete pInt; // zwolnienie obszaru pamięci
}
X.
Dynamiczny przydział pamięci
W poprzednich rozdziałach występowały wyłącznie zmienne statyczne, czyli takie,
które istnieją przez cały czas życia programu (od jego uruchomienia do zakończenia).
Tworzenie zmiennych dynamicznych polega na dynamicznym przydziale pamięci dla
zmiennych. Kasowanie zmiennych dynamicznych polega na dynamicznym
zwalnianiu pamięci.
W celu utworzenia zmiennej dynamicznej należy użyć operatora new. Jego wywołanie
ma ogólną postać:
Po wykonaniu takiego polecenia w pamięci powstanie zarezerwowanie obszaru pamięci
niezbędnego do przechowywania danych typu typ_danych (tzw. dynamiczna
zmienna typu typ_danych., a wywołanie operatora zwróci wskaźnik do tego obszaru
(tej zmiennej). Na przykład, dla większości systemów 32-bitowych, wywołanie
new int;
powoduje zarezerwowanie w pamięci 4 bajtów i zwrócenie wskaźnika do nich.
W przypadku typów obiektowych, oprócz zarezerwowania pamięci dla obiektu,
wywoływany jest dodatkowo jawny lub domyślny konstruktor tego obiektu.
Aby programista miał dostęp do zmiennej dynamicznej, nie wystarczy instrukcja
należy zastosować następującą konstrukcję:
Tak skonstruowana deklaracja zmiennej wskaźnikowej oraz powołanie zmiennej
dynamicznej do życia można już wykorzystywać w programie:
Program dynam1.cpp
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I. 39
______________________________________________________________________
©2008 Jerzy Kluczewski
#include <iostream>
using namespace std;
int main()
{
int* pInt = new int;
char* pChar = (char*) pInt;
*pInt = 100;
cout << *pInt << endl;
*pChar = 'a';
cout << *pChar << endl;
cout << *pInt << endl;
*(pChar + 1) = 'b';
*(pChar + 2) = 'c';
*(pChar + 3) = 0;
cout << pChar << endl;
cout << *pInt << endl;
delete pInt;
system("pause");
return 0;
}
W linii 5 wywołanie new int zwróciło wskaźnik do nowo zarezerwowanego obszaru
pamięci, który będzie mógł przechowywać wartości typu int. Wskaźnik został
przypisany zmiennej wskaźnikowej pInt.
W linii 6, za pomocą operatora * do obszaru wskazywanego przez pInt wstawiana jest
wartość 100.
W linii 7 wyświetlana jest wartość zmiennej dynamicznej pInt.
W linii 8 za pomocą operatora delete, następuje zwolnienie obszaru pamięci,
zarezerwowanego operatorem new.
Uwaga: Zawsze należy zwalniać pamięć, po tym jak już nie jest potrzebna, bo
w przeciwnym razie nastąpi tzw. wyciek pamięci (memory leaks), powodując
zmniejszenie ogólnej ilości pamięci dostępnej w systemie i wiele problemów
związanych z tym faktem.
Sztuczki ze wskaźnikami
Czy można przewidzieć, co wypisze na ekranie poniższy program dynam2.cpp?
Program dynam2.cpp
Język C++ jest bardzo elastyczny, bowiem pozwala na swobodną interpretację
wskazywanego fragmentu pamięci.
40
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I.
______________________________________________________________________
______________________________________________________________________
©2008 Jerzy Kluczewski
W programie dynamic2.cpp w linii 5, instrukcja int* pInt = new int;
Powoduje tworzenie wskaźnika do danej typu int. Następna instrukcja tworzy nowy
wskaźnik pChar, który wskazuje na dane typu char. Jednak operator = powoduje
przypisanie mu tego samego adresu, co wskaźnik pInt (patrz Rys. 5).
Rys. 5. Wskaźniki i obszar wskazywany
Po wykonaniu instrukcji w linii 6, oba wskaźniki wskazują początek tego samego
obszaru. Różnica między nimi polega tylko na tym, że odwołując się do tego obszaru,
inaczej traktujemy dane przechowywane w tym obszarze. W linii 7, obszar ten
potraktowano jako 4-bajtową liczbę równa 100.
Natomiast w linii 9, (instrukcja *pChar = 'a';), obszar zarezerwowany traktowany
jest jak ciąg 4 znaków. W pierwszy bajt tego obszaru jest wpisywany znak ‘a’.
W linii 12, (instrukcja *(pChar + 1) = 'b';) następuje wpisanie znaku ‘b’ pod adres
o 1 większy od adresu znaku ‘a’.
W linii 13, (instrukcja *(pChar + 2) = 'c';) następuje wpisanie znaku ‘c’ w następny
bajt obszaru. Na koniec do czwartego bajtu wpisywana jest liczba 0. Oznacza on
koniec ciągu znaków. Mamy więc do czynienia z podwójną interpretacją obszaru
danych (liczba – kolor czerwony, znaki – kolor niebieski), występuje on raz jako typ
int, a raz jako ciąg znaków zakończonych zerem (patrz Rys. 6).
Rys. 6. Obszar 4 bajtów jako typ int (czerwony) i jako typ char (niebieski)
Znaki przechowywane są w postaci kodów ASCII, dlatego gdy w linii 16 (instrukcja
cout << *pInt << endl;) wyświetlamy wartość typu int, wydaje się ona dziwna.
Wynosi ona 6513249.
97
100
98
0
99
0
0
0
Koniec ciągu znaków
obszar
zarezerwowany
pInt
pChar
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I. 41
______________________________________________________________________
©2008 Jerzy Kluczewski
Wydaje się ona dziwna, ale wszystko jest w porządku. To tylko mały trik
programistyczny, który został już wyjaśniony.
Stos i sterta
Wszystkie programy korzystają z segmentów pamięci: są to m. in. pamięć kodu (CODE
SEGMENT), pamięć stosu (STACK SEGMENT), pamięć sterty (HEAP MEMORY).
Zmienne statyczne rezerwowane są na tzw. stosie (ang. stack) – Rys. 7. Stos ma stały
rozmiar i nie może być zwiększany w trakcie działania programu. Zazwyczaj jego
wielkość jest regulowana w opcjach kompilatora. W przypadku zmiennych
dynamicznych pamięć rezerwowana na dane znajduje się w obszarze tzw. sterty (ang.
heap). Wielkość sterty zależy od tego, ile pamięci system operacyjny jest w stanie
przydzielić jednemu procesowi.
Rys. 7. Obszary pamięci i ich wykorzystanie.
Wskaźniki są przechowywane na stosie, dane wskazywane na stercie. Powoływanie do
ż
ycia oraz usuwanie zmiennej dynamicznej odbywa się na stercie, a wskaźniki po
usunięciu zmiennej dynamicznej, nadal istnieją, tylko, można powiedzieć, że wskazują
na przypadkowe dane. Dlatego używanie wskaźnika po usunięciu obszaru ze sterty, jest
bardzo nieodpowiedzialne.
pamięć
stos
Wskaźnik2
Wskaźnik1
sterta
Obszar X
Obszar Y
42
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I.
______________________________________________________________________
______________________________________________________________________
©2008 Jerzy Kluczewski
#include <iostream>
using namespace std;
int main()
{
int* pTab = new int[10]; // 10 elementów
for (int i = 0; i < 10; i++)
{
pTab[i] = i + 1;
}
for (int i = 0; i < 10; i++)
{
cout << pTab[i] << " ";
}
cout << endl;
delete pTab;
system("pause");
return 0;
}
Dynamiczne tablice
Za pomocą operatora new można też tworzyć dynamiczne tablice. Przykład tworzenia
takiej tablicy zaprezentowano w programie dynam3.cpp.
Program dynam3.cpp.
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I. 43
______________________________________________________________________
©2008 Jerzy Kluczewski
XI.
Literatura
A. Majczak. C++ Przykłady praktyczne. Wydawnictwo Mikom, Warszawa 2003.
J. Liberty. C++ dla każdego. Wydawnictwo Helion, Gliwice 2002.
K. Loudon. C++ Leksykon kieszonkowy. Wydawnictwo Helion, Gliwice 2003.
A. Stasiewicz. C++ Ćwiczenia praktyczne. Wydawnictwo Helion, Gliwice 2004.
44
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I.
______________________________________________________________________
______________________________________________________________________
©2008 Jerzy Kluczewski
Spis treści
I.
Kompilacja i linkowanie ........................................................................................... 2
Najprostszy program..................................................................................................... 2
Prawdziwy program ...................................................................................................... 3
II. Rozpoznawanie błędów ............................................................................................ 3
Dziwny błąd linkera...................................................................................................... 4
Niezadeklarowana zmienna .......................................................................................... 5
Literówki....................................................................................................................... 5
Pliki nagłówkowe ......................................................................................................... 6
Przestrzenie nazw ......................................................................................................... 6
Koniec instrukcji........................................................................................................... 6
Cudzysłów .................................................................................................................... 7
Argumenty funkcji ........................................................................................................ 7
III.
Zmienne ................................................................................................................ 9
Inicjowanie zmiennych ................................................................................................. 9
Jak nazywać zmienne?................................................................................................ 10
Typ znakowy............................................................................................................... 10
Modyfikatory signed i unsigned ................................................................................. 10
Liczby zmiennoprzecinkowe ...................................................................................... 11
IV.
Instrukcja warunkowa......................................................................................... 12
Instrukcja warunkowa zagnieżdżona .......................................................................... 13
V. Rodzaje zmiennych................................................................................................. 14
Zmienne lokalne ......................................................................................................... 14
Zmienne globalne ....................................................................................................... 15
Zmienne modułowe ................................................................................................ 15
Przesłanianie zmiennych globalnych...................................................................... 16
VI.
Wskaźniki ........................................................................................................... 17
Typ wskaźnikowy ................................................................................................... 17
Wskaźniki do funkcji .............................................................................................. 19
Wskaźniki do funkcji na skróty .............................................................................. 20
Wskaźniki do klas ....................................................................................................... 20
Wskaźnik do składowej klasy................................................................................. 21
VII.
Iteracje czyli pętle ............................................................................................... 22
Pętla for....................................................................................................................... 22
Pętla while................................................................................................................... 24
Pętla do … while......................................................................................................... 25
Instrukcja break....................................................................................................... 26
Instrukcja continue.................................................................................................. 27
VIII. Macierze w życiu - wprowadzenie ..................................................................... 29
Co to jest rozmiar macierzy? .................................................................................. 29
Indeksowanie elementów macierzy ........................................................................ 31
Jak oznaczamy elementy macierzy w matematyce?............................................... 31
C++. Programowanie obiektowe. Ćwiczenia podstawowe. Cz. I. 45
______________________________________________________________________
©2008 Jerzy Kluczewski
Macierze w informatyce ......................................................................................... 32
Jak wyzerować macierz (tablicę) ? ......................................................................... 33
IX.
Tablice i wskaźniki ............................................................................................. 34
Arytmetyka wskaźników ............................................................................................ 35
Niebezpieczne operacje .............................................................................................. 36
Przechowywanie napisów........................................................................................... 37
X. Dynamiczny przydział pamięci .............................................................................. 38
Sztuczki ze wskaźnikami ........................................................................................ 39
Stos i sterta.............................................................................................................. 41
Dynamiczne tablice..................................................................................................... 42
XI.
Literatura............................................................................................................. 43