Kurs C++2, m 4, Instrukcja for


Ćwiczenie 4

Wskaźniki, tablice

4.1. Wskaźnik na zmienną

Deklaracja int *pa; oznacza, że pa jest wskaźnikiem na obiekt typu int. Zmienna pa zawiera adres pamięci, zarezerwowanej na zmienną typu int. Chcąc dostać się do zmiennej wskazywanej przez pa używamy operatora wyłuskania (dereferencji) *.

Tak więc wyrażenie *pa jest zmienną, która jest wskazywana przez wskaźnik pa.

Deklaracja int p, y; oznacza, że p jest zmienną typu int. Stosując operator adresu & można dowiedzieć się, jaki jest adres zmiennej p. Adres ten można przypisać zmiennej pa (która jest wskaźnikiem na obiekt typu int): pa = &p;

Teraz *pa i p są tym samym obiektem. Tak więc instrukcje

y = p + 1; i y = *pa + 1; są równoważne.

Wartości wskaźników można wyprowadzić w postaci szesnastkowych liczb o wartościach segmentu i offsetu przy użyciu kodu konwersji %p np. printf("%p", pa);

Na wskaźnikach tego samego typu - innego niż void - można wykonywać następujące operacje:

Program 4.1. //zmienna a wskaźnik

#include <stdio.h>

#include <conio.h>

int a, b; //zmienne globalne

int *pa, *pb; //wskaźniki globalne

void main(void)

{

clrscr();

printf("\n podaj wartosc liczby a\n");

scanf("%d",&a);

pa=&a; //podstawienie adresu pod wskaźnik

printf("\n a= %d",a); //wypisanie wartości zmiennej a

printf("jest pod adresem pa=%p", pa); //wypisanie adresu zmiennej a

*pa=*pa+2; //zwiększenie zmiennej a o 2

printf("\n a teraz a= %d",a);

printf("\n podaj wartosc liczby b\n");

scanf("%d",&b);

pb=&b; //podstawienie adresu pod wskaźnik

printf("\n b= %d",b);

printf(" jest pod adresem pb= %p",pb);

printf("\n pb-pa= %d",pb-pa); //odjęcie dwóch wskaźników od siebie

getch();

}

4.2. Wskaźnik na tekst

Stała łańcuchowa jest ciągiem znaków zawartych pomiędzy znakami cudzysłowu. Na jej końcu kompilator dodaje automatycznie znak '\0' oznaczający koniec tekstu. Stałe łańcuchowe mogą być definiowane za pomocą dyrektywy #define. Stałe łańcuchowe należą do klasy statycznej, tzn. są przechowywane przez cały czas działania programu i tylko w jednej kopii. Całe zdanie w cudzysłowie jest równocześnie stałym wskaźnikiem do miejsca, w którym jest zapisane. Można więc użyć zapisu wskaźnikowego char *tekst =" O co chodzi?" lub zadeklarować tablicę znakową zainicjowaną char tab[] = "O co chodzi?"

Do przeprowadzania operacji na tekstach można wykorzystywać następujące funkcje zawarte w pliku string.h:

strcpy(char *s1,char *s2) - kopiowanie tekstu z s2 do s1;

strcat(char *s1, char *s2) - dołączenie do tekstu z s1 tekstu z s2;

strcmp(char *s1,char *s2) - porównanie tekstów s1 i s2;

strchr(char *s1,char c)- odszukanie znaku c w tekście s1;

strstr(char *s1,char *s2)- odszukanie tekstu s2 w tekście s1;

strlen(char *s) - obliczenie długości tekstu s;

Program 4.2

#include <stdio.h>

#include <conio.h>

#include<string.h>

void main(void)

{ clrscr();

char pan[]="KAROL"; //deklaracja i inicjacja tablicy 6-znakowej

char *wsk="INKA"; // wskaźnik na tekst

puts(pan); //wypisanie tekstu z tablicy pan

puts(wsk); //wypisanie tekstu wskazywanego przez wsk

printf("\nrozmiar tablicy pan: %d\n", sizeof(pan));

printf("\ndlugosc tekstu wskazywanego przez wsk: %d\n", strlen(wsk));

printf("%c\n", pan[0]); //pierwszy znak w tekscie

printf("%s\n", &pan[0]); //tekst pod adresem &pan[0]

printf("adres poczatku tablicy pan %p\n", pan);

printf("%s\n", pan+3); //tekst pod adresem pan+3

printf("%c\n", wsk[2]); //trzeci znak w tekście

printf("%c\n", *(wsk+2)); //trzeci znak w tekście

printf("%s\n", wsk+2); //tekst pod adresem wsk+2

strcat(wsk, "OLA"); //dołączenie tekstu

printf("%s\n",wsk);

strcpy(wsk, "OLA"); //przekopiowanie tekstu

printf("%s\n",wsk);

strcpy(wsk, pan); //przekopiowanie tekstu

printf("%s\n",wsk);

strcpy(pan, "OLA"); //przekopiowanie tekstu

printf("%s\n",pan);

wsk=pan; //podstawienie adresu

puts (wsk);

puts(++wsk);

getch();

}

4.3. Tablice jednowymiarowe

Tablice jednowymiarowe deklaruje się podając typ elementów, nazwę tablicy i liczbę elementów ujętą w nawiasy kwadratowe: np. char t[10]; double tab[2]; Elementy tablicy numeruje się zaczynając od zera.

Tablica może być zainicjowana podczas deklaracji:

char t[10] = {'a','b','c','d','e','\o'};.

Para nawiasów [] jest dwuargumentowym, lewostronnie łącznym operatorem indeksacji, którego wynikiem jest element tablicy.

Program 4.3 //deklaracje i inicjacje tablic

#include <stdio.h>

#include <conio.h>

#define r 10

void main()

{ char c1[r] = {'b','i','a','l','y','\0'}; // lub {"zielony"};

int i1[r] = {1,2,3,4,5,4,5,4,6}; //tablica będzie uzupełniona zerami

float f1[r] = {1.25,0,0,23,-3.33,333,34.23,1};

clrscr();

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

printf("\nc1[%d] = %c i1[%d] = %d f1[%d] = %8.2f",i,c1[i],i,i1[i],i,f1[i]);

getch();

}

4.4. Wskaźnik a tablica

W C istnieje ścisła zależność między wskaźnikami a tablicami. Każda operacja, która może być przeprowadzona przez indeksowanie tablicy, może być również dokonana za pomocą wskaźnika.

Deklaracja int A[10]; definiuje tablicę dziesięciu liczb typu int, a więc blok 10 kolejnych obiektów nazywających się A[0], A[1], ..., A[9]. Zapis A[i] oznacza element tablicy oddalony o i pozycji od jej początku.

Jeżeli pa będzie wskaźnikiem do obiektów całkowitych zadeklarowanym jako int *pa; wówczas przypisania takie, jak

pa = &A[0]; lub pa = A;

ustawiają pa tak, aby wskazywał na zerowy element tablicy A; to znaczy pa będzie zawierać adres elementu A[0].

Instrukcje *pa = 10; i A[0] = 10; są równoważne.

Jeśli pa wskazuje na pewien element tablicy A, to z definicji pa+1 wskazuje na element następny. Ogólnie pa-i wskazuje na i-ty element przed pa, a pa+i na i-ty element po pa. Zatem, jeżeli pa wskazuje na A[0], to *(pa+1) odnosi się do zawartości A[1]; pa+i jest adresem A[i], a *(pa+i) jest wartością A[i].

Istnieje jednak istotna różnica między nazwą tablicy a wskaźnikiem. Wskaźnik jest zmienną, wiec operacje pa=A i pa++ mają sens. Natomiast nazwa tablicy jest stałą wskaźnikową, a nie zmienną czyli konstrukcje takie, jak A = pa, A++ lub pa=&A są niedopuszczalne!!!

Jeśli uzupełnimy program 4.1 instrukcjami:

int *pa;

pa=i1;

puts("wyświetlenie zawartości tablicy i1 ");

for (i=0;i<10;i++) printf(" %d",*(pa+i));

to wyświetlimy zawartość tablicy i1 używając notacji wskaźnikowej.

4.5. Tablice wielowymiarowe

Tablice wielowymiarowe są reprezentowane jako tablice tablic. Np. deklaracja

char A[2][5];

wprowadza tablicę A z dwoma elementami, z których każdy jest tablicą typu char [5]. Tablica taka może być zainicjowana podczas deklaracji:

char A[2][5] = { {'a','b','c' }, {'0', '1', '2', '3', '4'}};

Nazwa tablicy A jest stałą wskazującą na pierwszy wiersz tablicy, czyli jej pierwszy element A[0].

A+i wskazuje na i-ty wiersz tablicy, czyli A[i]. Natomiast A[i]+j wskazuje na j-ty element i-tego wiersza, czyli A[i][j].

Program 4.5.

#include <stdio.h>

#include <conio.h>

#define M 10

void main ()

{

int A[M][M], i, j, w,k;

clrscr();

printf("Podaj ilosc wierszy ");

scanf("%d",&w);

printf("Podaj ilosc kolumn ");

scanf("%d",&k);

for(i=0;i<w;i++)

{

for (j=0;j<k;j++)

{

printf("A[%d",i);

printf(",%d",j);

printf("]=");

scanf("%d",&A[i][j]);

}

}

clrscr();

for(i=0;i<w;i++)

{

for (j=0;j<k;j++)

{

gotoxy(5+4*j,10+i);

printf("%d",A[i][j]);

}

}

getch();

}

Sprawdzić, że poniższe zapisy są równoważne:

A[0][0] i *A[0] i **A

A[1][2] i *(A[1]+2) i *(*(A+1)+2)

&A[1][2] i A[1]+2 i *(A+1)+2

4.6. Alokacja (przydział) pamięci na tablice dynamiczne

Wszystkie dane w programie należą do jednej z trzech klas: statycznych, automatycznych lub dynamicznych. Dane statyczne są definiowane w czasie kompilacji. Pamięć dla danych automatycznych jest przydzielana, niewidocznie dla użytkownika, w chwili ich utworzenia. Tylko dane dynamiczne mają przydzielaną pamięć (na stercie) na żądanie programisty. Stosuje się je głównie wtedy, gdy ich rozmiar nie jest znany w chwili pisania programu.

Przydziału pamięci na tablicę n liczb całkowitych dokonuje się za pomocą funkcji

void * malloc(n*sizeof(int)), lub void *calloc(n, sizeof(int)), zdefiniowanych w pliku nagłówkowym alloc.h, które zwracają wskaźnik do początku przydzielonego obszaru lub wskazanie puste NULL, gdy alokacja się nie powiodła. Ponadto funkcja calloc zeruje przydzieloną pamięć. Do zwalniania wcześniej przydzielonej pamięci służy funkcja free(wsk). Zwalnianie pamięci nie przydzielonej jest błędem.

Program 4.6.1 /*dynamiczne przydzielanie pamięci na tablicę jednowymiarową */

#include <stdio.h>

#include <conio.h>

#include <stdlib.h>

#include <time.h>

#include <alloc.h>

main()

{

int n,i, *wsk;

randomize();

clrscr();

printf("liczba elementow tablicy n=");

scanf("%d",&n);

wsk=(int*)calloc(n,sizeof(int));

if (wsk == NULL)

{

printf("brak miejsca w pamieci\n");

exit (1);

}

else

for (i=0;i<n;i++)

{

*(wsk+i)=random(100);

printf("%5d",*(wsk+i));

}

getch();

free(wsk);

return 0;

}

Program 4.6.2 /*dynamiczne przydzielanie pamięci na tablicę dwuwymiarową */

# include <stdio.h>

# include <conio.h>

# include <alloc.h>

void tworz_tab(int *a[10],int *w,int *k);

void wydruk_tab(int *a[10], int *w, int *k);

void main()

{

int w,k, nr;

int *tab[10]; //deklaracja tablicy zawierającej wskaźniki do początków wierszy

clrscr();

printf("liczba wierszy=");scanf("%d",&w);

printf("liczba kolumn=");scanf("%d",&k);

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

tab[i]=(int *)calloc(k,2); //alokacja wierszy

tworz_tab(tab,&w,&k);

clrscr();

wydruk_tab(tab,&w,&k);

for (i=0;i<w;i++) free(*(tab+i)); //zwolnienie pamięci

getch();

}

void tworz_tab(int *a[10], int *w, int *k)

{

int i,j;

for (i=0;i<*w;i++)

for (j=0;j<*k;j++)

{

printf("tab[%d,%d]=",i,j);

scanf("%d",*(a+i)+j); // lub *(*(a+i)+j)=random(100);

}

}

void wydruk_tab(int *a[10], int *w, int *k)

{

int i, j;

for (i=0;i<*w;i++)

{

for(j=0;j<*k;j++)

printf("%5d",*(*(a+i)+j)); // *(a+i) - adres pocz. i-tego wiersza

printf("\n"); // *(a+i)+j - adres j-tego elementu i-tego wiersza

} // *(*(a+i)+j) - wartość pod powyższym adresem

}

4.7. Wskaźnik a funkcja

Argumenty wywołania funkcji (parametry aktualne) są przekazywane przez wartość. Jeśli argument ma być wykorzystany do wyprowadzenia wyniku, to ten argument musi być typu wskaźnikowego np. funkcja void fun(int *a, int*b) {int t=*a; *a=*b; *b=t;} wykonuje zamianę wartości wskazywanych przez wskaźniki będące jej argumentami.

Nazwa funkcji jest stałą wskaźnikową do tej funkcji. Jeśli funkcja ma prototyp

void fun(int*t, int r), to wskaźnik do niej musi mieć ten sam typ i identyczną listę argumentów: void (*wsk_fun)(int *t1, int r1). Możliwe jest wtedy przypisanie wsk_fun=fun. Zapisując wywołanie funkcji określonej przez wskaźnik, nie trzeba poprzedzać wskaźnika operatorem wyłuskania.

Program 4.7.

#include <stdio.h>

#include <conio.h>

int minimum(int *t, int n) //tablicę przekazuje się jako wskaźnik do zerowego elementu tablicy

{ int min=t[0];

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

if(min>t[i]) min=t[i];

return min;

}

const int r=6;

int (*wsk_min)(int*,int) = minimum; //deklaracja wskaźnika do funkcji i zainicjowanie go

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

void main()

{ clrscr();

printf("\n 1 wywolanie: %d",minimum(tab,r));

printf("\n 2 wywolanie: %d",wsk_min(tab,r));

printf("\n 3 wywolanie: %d",(*wsk_min)(tab,r));

getch();

}

Wskaźniki do funkcji można przekazywać jako argumenty innym funkcjom, zwracać jako wyniki funkcji, porównywać ze wskazaniem pustym NULL. Nie wolno wykonywać na nich operacji arytmetycznych.

Stosuje się także tablice wskaźników do funkcji np. int(*rodzaje_Testów[10])(); jest deklaracją tablicy zawierającej 10 elementów, z których każdy jest wskaźnikiem do funkcji.

Programy do samodzielnego napisania:

  1. Wskaźnik t wskazuje na tekst. Napisać funkcję hist(char *t, int *litery, int *cyfry), która policzy histogramy (liczbę wystąpień) wszystkich małych liter samogłosek i cyfr w podanym tekście. Funkcja jako pierwszy parametr dostaje wskaźnik do tekstu, który ma być analizowany, a jako drugi i trzeci wskaźniki do tablic, w których należy umieścić liczebności wystąpień liter i cyfr.

  2. Dana jest tablica A zawierająca n liczb całkowitych. Do tablicy B przepisać z tablicy A elementy o indeksach parzystych. Do tablicy C przepisać z A elementy parzyste.

  3. Dany jest ciąg n liczb rzeczywistych. Obliczyć wartość średnią jego elementów i wstawić ją na początek tego ciągu.

  4. Zdefiniować funkcję znajdującą pozycję i wartość maksymalnego elementu danego ciągu liczbowego. Napisać program sprawdzający jej działanie.

  5. Uporządkować rosnąco ciąg n liczb całkowitych używając notacji wskaźnikowej. Wskazania do trzech największych elementów umieścić w tablicy wskaźników.

  6. Dwa uporządkowane rosnąco ciągi liczb scalić w jeden ciąg uporządkowany rosnąco.

  7. Utworzyć tablicę dwuwymiarową o wymiarze n × n wypełnioną kolejnymi liczbami naturalnymi w następujący sposób:

0x01 graphic

Następnie wyzerować jej elementy leżące poniżej głównej przekątnej.

  1. Napisać funkcję sklej, której parametrami są dwa wskaźniki na tekst s i t. Funkcja ma dopisać do tekstu s tekst t, inaczej mówiąc ma je połączyć. Funkcja ma operować na wskaźnikach do tekstu, a nie na tablicach i indeksowaniu.

  2. Do programu 4.6.2 dopisać:

a) funkcję usuwającą wiersz z tablicy

b) funkcję dodającą jako ostatni wiersz zawierający sumy elementów poszczególnych kolumn

Sprawdzić działanie tych funkcji.

10. Napisać (przy użyciu wskaźników) program wykonujący operacje dodawania, odejmowania i mnożenia dwóch macierzy dwuwymiarowych.

Laboratorium Podstawy Programowania

5



Wyszukiwarka