Podstawy informatyki Elektrotechnika I rok Język C++ Operacje na danych - wskaźniki Instrukcja do ćwiczenia Katedra Energoelektroniki i Automatyki Systemów Przetwarzania Energii AGH Kraków 2017
Tematyka i cel ćwiczenia Język C++. Operacje na danych - wskaźniki Celem ćwiczenia jest zapoznanie się z pojęciem wskaźnika, wykonanie prostych zadań mających ułatwić zrozumienie definiowanie i prace ze wskaźnikami. Omówione są także zastosowania wskaźników oraz ich wykorzystanie podczas wykonywania operacji na danych w języku C++. Wprowadzenie Program komputerowy składa się z algorytmów odpowiednio operujących na danych. Dane - zbiór jakiś wartości, np. liczb, tekstów, znaków, - to drugi filar programowania komputerów. Wszystkie dane muszą być zadeklarowane, powinny też być zainicjowane, natomiast rolą programisty jest wybór najodpowiedniejszego typu danych, mając na uwadze takie kryteria, jak zajętość pamięci, pojemność numeryczną, szybkość operowania, czytelność programu. Jednym z zespołów danych oprócz ostatnio poznanych są wskaźniki. Wskaźnik (ang. pointer) typ zmiennej odpowiedzialnej za przechowywanie adresu do innej zmiennej (innego miejsca w pamięci) w obrębie naszej aplikacji. Przykłady definicji wskaźnika: int *wsk; Pokazano definicję wskaźnika mogącego pokazywać na obiekt typu int. Definicję taką można odczytać: wsk jest wskaźnikiem do pokazywania na obiekty typu int. W tym wskaźniku wsk można przechowywać adres jakiegoś obiektu typu int. Zawartość wskaźnika informuje o tym gdzie wskazywany obiekt się znajduje, a nie co w tamtym obiekcie się znajduje. Znak * w definicji informuje, że używany będzie wskaźnik. Użyty w przykładzie wskaźnik nazywa się wsk bez gwiazdki *. Wskaźnik pokazuje na obiekty. Referencja nie jest obiektem, dlatego nie może być do niej wskaźnika. Ze wskaźnikami i adresami związane są dwa operatory: 1. operator referencji & zwracający adres zmiennej (zmiennej, tablicy, struktury itp.) podanej po prawej stronie tego operatora. 2. operator dereferencji (wyłuskania) identyfikujący obszar wskazywany przez wskaźnik podany po prawej stronie tego operatora. Przykład programu: int liczba ; int wsk ; wsk = &liczba; // przypisanie zmiennej wsk // adresu zmiennej liczba wsk = 10; // przypisanie 10 zawartości zmiennej // wskazywanej przez wskaźnik // tutaj równoważne liczba = 10 cout << *wsk << endl; // wyświetlenie wyłuskanej wartości wskaźnika (10) cout << wsk << endl; // wyświetlenie adresu zmiennej liczba cout << &wsk << endl; // wyświetlenie adresu wskaźnika cout << &liczba << endl; // wyświetlenie adresu zmiennej liczba W jednym programie można używać wskaźników różnych typów oraz więcej niż jednego wskaźnika danego typu. Dodatkowo wskaźnik nie pokazuje raz na zawsze na ten sam obiekt, można go łatwo przestawić i wskazywać nim inny obiekt. Wskaźnik służący do pokazywania na obiekt jednego typu nie nadaje się do pokazywania na obiekt innego typu, aby to umożliwić należy zastosować operator rzutowania reinterpreter_cast. Operator reinterpreter_cast służy do Katedra Energoelektroniki i Automatyki Systemów Przetwarzania Energii AGH, Kraków 2017 1
konwersji między typami, między którymi konwersja nie może zajść niejawnie. Najczęściej takie zamiany nie mają sensu. Wskaźniki typu void - zwykłe wskaźniki używane są aby wskazywać na dany obiekt określonego typu, np. zmienna tekstowa, zmienna liczbowa czy struktura. Wskaźniki typu void dają możliwość wskazywania na określony obszar pamięci aplikacji bez ustalania jakiego typu obiekt pokazuje się. Używanie takiego wskaźnika jest potrzebne w miejscach gdzie potrzebny jest tylko adres jakiegoś obiektu w pamięci. Wskaźnik każdego (niestałego) typu można przypisać wskaźnikowi typu void, natomiast nie można wykonać odwrotnej operacji. Aby tego dokonać należy posłużyć się operatorem rzutowania. Przykładowy program wykorzystujący wskaźniki: #include <iostream> using namespace std ; int obiekt_pierwszy = 2 ; int obiekt_drugi = 5000 ; int *wskaznik ; // wskaznik na obiekty typu int wskaznik = &obiekt_pierwszy ; // prosty wypis na ekran ; cout << "obiekt_pierwszy = " << obiekt_pierwszy << "\n Wskaznik natomiast odczytal nastepujaca wartosc = " << *wskaznik << endl ; obiekt_pierwszy = 20 ; // następuje zmiana wartości obiektu pierwszego cout << "obiekt_pierwszy = "<< obiekt_pierwszy << "\n Wskaznik natomiast odczytal nastepujaca wartosc = " << *wskaznik << endl ; *wskaznik = 55 ; // do tego na co pokazuje wskaznik wpisz 55 cout << "obiekt_pierwszy = " << obiekt_pierwszy << "\n Wskaznik natomiast odczytal nastepujaca wartosc = " << *wskaznik << endl ; wskaznik = &obiekt_drugi ; //przestawienie wskaznika na obiekt drugi cout << "obiekt_pierwszy = "<< obiekt_pierwszy << "\n Wskaznik natomiast odczytal nastepujaca wartosc = " << *wskaznik << endl ; Obszary zastosowania wskaźników: ulepszenie pracy z tablicami, funkcje mogące zmieniać wartość przysłanych do nich argumentów, dostęp do specjalnych komórek pamięci, rezerwacja obszarów pamięci. Jeśli mamy następujące definicje: int *wsk; int tab[10]; to instrukcja: wsk = &tab[n]; powoduje, że wskaźnik ustawi się na element tablicy o indeksie n. Natomiast instrukcja: wsk = &tab[0]; jest równoznaczny z: wsk = tab; Ponieważ nazwa tablicy jest równocześnie adresem jej początku. 2 Katedra Energoelektroniki i Automatyki Systemów Przetwarzania Energii AGH, Kraków 2017
Dodanie do wskaźnika jakiejś liczby całkowitej powoduje, że pokazuje on tyleż elementów tablicy dalej. Niezależnie od tego, jakie są to elementy. Ponadto możliwe jest wykonanie operacji odejmowania wskaźników od siebie. Odjęcie od siebie dwóch wskaźników pokazujących na różne elementy tej samej tablicy w rezultacie poinformuje o liczbie dzielących je elementów tablicy liczba ta może być dodatnia lub ujemna. Możliwe jest również porównywanie wskaźników, w tym zwykłego wskaźnika ze wskaźnikiem typu void. Przykład fragmentu programu: double x[5]; double *p; p = x; // tak jak p = &(x[0]); *p = 12; p++; // teraz p wskazuje na x[1] *p = 13; // tutaj x[0]=12, x[1]=13 Katedra Energoelektroniki i Automatyki Systemów Przetwarzania Energii AGH, Kraków 2017 3
Program ćwiczenia Przeanalizować kody zamieszczonych programów, następnie wykonać zadanie nr 1 i 2. Program 1: using namespace std; int *wsk,j,x; wsk=new int[10]; x=1; for(j=0;j<10;j++) *wsk=x; cout<<"tab["<<j<<"] jest w pamieci pod adresem:"<<wsk<< "\t i ma wartosc:"<< *wsk <<endl; wsk++; x++; delete [] wsk; błąd //w tym momencie użycie zapisanej tablicy komputer odczytałby jako Program 2: using namespace std; double *wsk; double zmienna; wsk=&zmienna; cout<<"podaj zmienna do spierwiastkowania!\n"; cin>>zmienna; *wsk=sqrt(zmienna); cout<<"zmienna="<<*wsk<<"\ni jest pod adresem:"<<wsk<<endl; 4 Katedra Energoelektroniki i Automatyki Systemów Przetwarzania Energii AGH, Kraków 2017
Program 3: using namespace std; int rozmiar; int i; cout<<"ile elementow ma miec tablica?\n"; cin>>rozmiar; int *wsk=new int[rozmiar]; for(i=0;i<rozmiar;i++) *wsk=i; cout<<"tab["<<i<<"]="<<*wsk<<"\tpod adresem:"<<wsk<<endl; wsk++; delete [] wsk; Katedra Energoelektroniki i Automatyki Systemów Przetwarzania Energii AGH, Kraków 2017 5
Program 4: int funkcja(); using namespace std; int (*wskfunkcji)(); wskfunkcji=funkcja; (*wskfunkcji)(); int funkcja() cout<< "Wyswietlam funkcje!"<<endl; 6 Katedra Energoelektroniki i Automatyki Systemów Przetwarzania Energii AGH, Kraków 2017
Zad 1: Należy napisać program wykorzystujący dwa wskaźniki: mały i duży. Na 10 elementowej tablicy należy ustawić wskaźnik mały w ustalonej pozycji (np. na 5 elemencie). Następnie program ma poprosić o ustawienie wskaźnika dużego (na dowolnym elemencie tablicy, czyli od 0-9). W kolejnym kroku następuje porównanie ustawień wskaźnika dużego względem małego. Należy sprawdzić czy: 1. Wskaźnik duży pokazuje na element bliżej początku tablicy. 2. Wskaźnik duży pokazuje element o wyższym indeksie. 3. Wskaźniki pokazują ten sam element. Po dokonaniu porównania należy wyświetlić komunikat informujący o zaistniałym przypadku. Np. Po ustawieniu wskaźnika małego na 5 element tablicy, a dużego na 3, powinien wyświetlić się komunikat nr. 1. Zad 2: Należy napisać program, który będzie używał wskaźników w pracy z tablicami. Należy zdefiniować dwie tablice, jedna dla typu int, a drugą dla typu double. Następnie ustawić wskaźnik na początkowym elemencie tablicy double. Zapełnić ją wartościami początkowymi (przy użyciu instrukcji for). Wypisać na ekran otrzymane wartości. W kolejnym kroku należy zmienić ustawienie wskaźników: jeden wskaźnik ustawić na piątym elemencie tablicy typu int, drugi wskaźnik ustawić na trzecim elemencie tablicy double. W nowym ustawieniu wskaźników należy zamienić wybrane dotychczasowe wartości w tablicy na nowe wartości. Następnie należy wypisać treść tablic po zastosowaniu zmian. Katedra Energoelektroniki i Automatyki Systemów Przetwarzania Energii AGH, Kraków 2017 7