LEKCJA 12. Wskaźniki i tablice w C i C++. W czasie tej lekcji: 1. Dowiesz się więcej o zastosowaniu wskaźników. 2. Zrozumiesz, co mają wspólnego wskaźniki i tablice w języku C/C++. WSKAŹNIKI I TABLICE W C i C++. W języku C/C++ pomiędzy wskaźnikami a tablicami istnieje bardzo ścisły związek. Do onumerowania elementów w tablicy służą tzw. INDEKSY. W języku C/C++ * KAŻDA OPERACJA korzystająca z indeksów może zostać wykonana przy pomocy wskaźników; * posługiwanie się wskaźnikiem zamiast indeksu na ogół przyspiesza operację. Tablice, podobnie jak zmienne i funkcje wymagają przed użyciem DEKLARACJI. Upraszczając problem - komputer musi wiedzieć ile miejsca zarezerwować w pamięci i w jaki sposób rozmieścić kolejne OBIEKTY, czyli kolejne elementy tablicy. [???] CO Z TYMI OBIEKTAMI? OBIEKTEM w szerokim znaczeniu tego słowa jest każda liczba, znak, łańcuch znaków itp.. Takimi klasycznymi obiektami języki programowania operowały już od dawien dawna. Prawdziwe programowanie obiektowe w dzisiejszym, węższym znaczeniu rozpoczyna się jednak tam, gdzie obiektem może stać się także coś "nietypowego" - np. rysunek. Jest to jednak właściwy chyba moment, by zwrócić Ci uwagę, że z punktu widzenia komputera obiekt to coś, co zajmuje pewien obszar pamięci i z czym wiadomo jak postępować. Deklaracja: int A[12]; oznacza że należy zarezerwować 12 KOLEJNYCH komórek pamięci dla 12 liczb całkowitych typu int (po 2 bajty każda). Jednowymiarowa tablica (wektor) będzie się nazywać "A", a jej kolejne elementy zostaną ponumerowane przy pomocy indeksu: - zwróć uwagę, że w C zaczynamy liczyć OD ZERA A NIE OD JEDYNKI; A[0], A[1], A[2], A[3],... A[11]. Jeśli chcemy zadeklarować: - indeks i; - wskaźnik, wskazujący nam początek (pierwszy, czyli zerowy element) tablicy; - samą tablicę; to takie deklaracje powinny wyglądać następująco: int i; int *pa; int A[12]; Aby wskaźnik wskazywał na początek tablicy A[12], musimy go jeszcze zainicjować:
pa = &A[0]; Jeśli poszczególne elementy tablicy są zawsze rozmieszczane KOLEJNO, to: *pa[0] oznacza: "wyłuskaj zawartość komórki pamięci wskazanej przez wskaźnik", czyli inaczej - pobierz z pamięci pierwszy (zerowy!) element tablicy A[]. Jeśli deklaracja typów elementów tablicy i deklaracja typu wskaźnika są zgodne i poprawne, nie musimy się dalej martwić ile bajtów zajmuje dany obiekt - element tablicy. Jeśli tablica jest dwu- lub trójwymiarowa, początek tablicy oznacza zapis: A[0][0]; A[0][0][0]; itd. Zwróć uwagę, że wskaźnik do tablicy *pa oznacza praktycznie wskaźnik do POCZĄTKOWEGO ELEMENTU TABLICY: *pa == *pa[0] To samo można zapisać w języku C++ w jeszcze inny sposób. Jeśli A jest nazwą tablicy, to zapis: *A oznacza wskazanie do początku tablicy A. Należy jednak podkreślić, że pomiędzy nazwami tablic (w naszym przykładzie A) a wskaźnikami istnieje zasadnicza różnica. Wskaźnik jest ZMIENNĄ, zatem operacje: pa = A; pa++; są dopuszczalne i sensowne. Nazwa tablicy natomiast jest STAŁĄ, zatem operacje: A = pa; ŹLE! A++; ŹLE! są niedopuszczalne i próba ich wykonania spowoduje błędy! DEKLAROWANIE I INICJOWANIE TABLIC. Dla przykładu, skompiluj program przykładowy. Zwróć uwagę na sposób zainicjowania wskaźnika. [P023.CPP] # include <iostream.h> int a[][2]= 1,2,3,4,5,6,7,8,9,10,11,12 ; char b[]= "Poniedzialek" ; int i; int *pa; char *pb; pa = &a[0][0]; pb = b; for (i=0; i<12; i++) cout << *(pa+i) << << *(pb+i)) << \n ; system( Pause ); Zwróć uwagę, że w C++ każdy wymiar tablicy musi mieć swoją parę nawiasów kwadratowych. Dla przykładu, tablicę trójwymiarową należy deklarować nie tak TAB3D[i,j,k] lecz tak: int i, j, k; TAB3D[i][j][k];
PRZYKŁADY TABLIC WIELOWYMIAROWYCH. Spróbujmy prześledzić rozmieszczenie elementów tablicy w pamięci i odwołać się do tablicy na kilka sposobów. [P026.CPP] int TABLICA[4][6]=11,12,13,14,15,16, 21,22,23,24,25,26, 31,32,33,34,35,36, 41,42,43,44,45,46; int i, j; cout << "OTO NASZA TABLICA \n"; for (i=0; i<=3; i++) for (j=0; j<=5; j++) cout << TABLICA[i][j] << "\n"; cout << "\n\inicjujemy wskaźnik na poczatek tablicy\n"; cout << "i INKREMENTUJEMY wskaźnik *pt++ \n"; pt=&tablica[0][0]; for (i=0; i<4*6; i++) cout << *(pt+i); system( pause ); Zwróć uwagę, że jeśli tablica ma wymiary A * B (np. 3 * 4) i składa się z k=a*b elementów, to w C++ zakres indeksów wynosi zawsze 0, 1, 2,...A*B-2, A*B-1. Tak więc tablica 10 x 10 (stuelementowa) będzie składać się z elementów o numerach 0...99, a nie 1...100. RĘCZNE I AUTOMATYCZNE GENEROWANIE TABLIC WIELOWYMIAROWYCH. Aby nabrać wprawy, spróbujmy pomanipulować inną tablicą, znaną Ci prawie "od urodzenia" - tabliczką mnożenia. Jest to kwadratowa tablica 10 x 10, której każdy wyraz opisuje się prostą zależnością T(i,j)=i*j. Jeśli przypomnimy sobie, że indeksy w C++ zaczną się nie od jedynki a od zera, zapis ten przybierze następującą formę: int T[10][10]; T[i][j] = (i+1)*(j+1); Do pełni szczęścia brak jeszcze wskaźnika do tablicy: i jego zainicjowania pt = &T[0][0]; I już możemy zaczynać. Moglibyśmy oczywiście zainicjować tablicę "na piechotę", ale to i nieeleganckie, i pracochłonne, i o pomyłkę łatwiej. Pamiętaj, że komputer myli się rzadziej niż programista, więc zawsze lepiej jemu zostawić możliwie jak najwięcej roboty. [P028.CPP] int T[10][10];
int i, j, k; char spacja = ' '; for (i=0; i<10; i++) for (j=0; j<10; j++) T[i][j] = (i+1)*(j+1); if (T[i][j]<10) cout << T[i][j] << spacja); else cout << T[i][j]; cout << \n ; cout << \n Inicjujemy i INKREMENTUJEMY wskaźnik *pt++ \n\n"; pt=&t[0][0]; for (k=0; k<10*10; k++) if (*(pt+k) < 10) cout << *(pt+k) << spacja; else cout << *(pt+k)); if ((k+1)%10 == 0) system( pause ); Po wynikach jednocyfrowych dodajemy trzy spacje a po dwucyfrowych dwie spacje. Po dziesięciu kolejnych wynikach trzeba wstawić znak nowego wiersza. Sprawdzamy te warunki: if (*(pt+k) < 10) - jeśli wynik jest mniejszy niż 10... lub if (T[i][j] < 10); if ((k+1) % 10 == 0) - jeśli k jest całkowitą wielokrotnością 10, czyli - jeśli reszta z dzielenia równa się zero... Tablice mogą zawierać liczby, ale mogą zawierać także znaki. Przykład prostej tablicy znakowej zawiera następny program przykładowy. [P029.CPP] char T[7][12]="Poniedzialek", "Wtorek", "Sroda", "Czwartek", "Piatek", "Sobota", "Niedziela"; char *pt; int i, j, k; char spacja=' ';
pt =&T[0][0]; cout << "\t TABLICA znakowa (ineksy)\n\n"; for (i=0; i<7; i++) for (j=0; j<12; j++) cout << T[i][j] ; cout << "\n\t Przy pomocy wskaźnika \n\n"; for (k=0; k<7*12; k++) cout << *(pt+k) ; if ((k+1)%12 == 0) system( Pause ); Nazwy dni mają różną długość, czym więc wypełniane są puste miejsca w tablicy? TABLICA znakowa (ineksy) : P o n i e d z i a l e k W t o r e k S r o d a C z w a r t e k P i a t e k S o b o t a N i e d z i e l a Przy pomocy wskaźnika: 80 111 110 105 101 100 122 105 97 108 101 107 87 116 111 114 101 107 0 0 0 0 0 0 83 114 111 100 97 0 0 0 0 0 0 0 67 122 119 97 114 116 101 107 0 0 0 0 80 105 97 116 101 107 0 0 0 0 0 0 83 111 98 111 116 97 0 0 0 0 0 0 78 105 101 100 122 105 101 108 97 0 0 0 Okaże się, że puste miejsca zostały wypełnione zerami. Zero w kodzie ASCII - NUL - '\0' jest znakiem niewidocznym, nie było więc widoczne na wydruku w formie znakowej. [Zadania] 1. Posługując się wskaźnikiem i inkrementując wskaźnik z różnym krokiem - np. pt += 2; pt += 3 itp., zmodyfikuj programy przykładowe tak, by uzyskać wydruk tylko części tablicy. 2. Wydrukuj nazwy dni tygodnia pionowo i wspak. 3. Napisz definicję dziesięcioelementowej tablicy wskaźników do liczb całkowitych. Napisz program, który wykorzystując zadeklarowaną tablicę wczytuje z klawiatury dziesięć liczb, a następnie drukuje wczytane liczby na ekranie. 4. Napisz funkcję WymianaInt. Parametrami funkcji mają być dwa wskaźniki do zmiennych typu int. Zadaniem funkcji jest wymiana wartości zmiennych.