Práce s ukazateli



Práce s ukazateli, základní náhled.



Zadání

Napište funkci, která vymění obsah (jednotlivé prvky) dvou polí, která jsou funkci předána jako parametry. Funkci realizujte tak aby byla co nejrychlejší. Pokud je to možné, realizujte ji tak, aby byla (univerzálně) použitelná pro výměnu prvků polí libovolného typu.



0.krok: založte si nový projekt. Zpočátku pracujte se soubory .c, po dokončení je přejmenujte na .cpp a odstraňte chyby plynoucí z přísnějšího přístupu ke konverzi ukazatelů.


1.krok: Zkuste si prohlédnout následující obrázek, udělat si v něm jasno a zkusit se orientovat v pojmech: Hodnota, Paměť, Paměťové místo (buňka), Adresa, Ukazatel, Proměnná, Název proměnné, Přístup k proměnné (nastavení/čtení hodnoty), Paměť na které leží proměnná (adresa/ukazatel), Proměnná jejíž hodnota je adresa/ukazatel, Získání adresy proměnné, Přístup k proměnné na adrese, Definice proměnné, Deklarace proměnné. Inicializace proměnné.



V případě ukazatelů (či jiného programování) je výhodné „čmárat“. Získáte tak lepší představu o dané situaci. Obrázek znázorňuje ve střední části místa v paměti a jejich názvy (i, pi …). Vpravo jsou příslušné definice proměnných, které zajišťují názvy pro proměnné a příslušné místo v paměti, společné s inicializací. V levé části jsou odpovídající zápisy pro přístup k daným proměnným podle definicí z pravé části.

O předávání parametrů více zde





2. krok: Pro zadání existuje celá řada řešení. Jedno z elegantních (rychlé a univerzální) je to, při kterém nedojde přímo k výměně prvků polí, ale využije se toho, že pole je interně reprezentováno adresou počátku zabraného datového prostoru. Proto lze (pro pole stejného typu) pouze vyměnit adresy počátku polí a úkol změny hodnot v polích je splněn. Toto řešení nelze provést u statických polí, protože ty jsou (z hlediska svého počátku = názvu) konstantní (kapitola 5.2.15, 5.2.17). Výhodou tohoto řešení také je, že pracujeme/měníme pouze počátky polí (adresy) a nepotřebujeme znát typ dat pole (ukazatel) se kterými nepracujeme. Proto můžeme funkci napsat univerzálně = předávat funkci počátek pole pomocí ukazatele typu void.
Prostudujte si následující obrázek, který situaci popisuje (postupujeme-li do funkce (vlevo), musíme získat adresu pomocí &. Postupujeme-li k původním proměnným (vpravo) používáme pro přístup k prvku dereferenci *).



V místě mezi P2 a P_Y (a P1 a P_X) je hranice mezi volající a volanou funkcí
Vpravo jsou naznačená pole, uprostřed jsou proměnné ve kterých jsou adresy polí a naznačené kam ukazují před a po výměně.
Vlevo jsou proměnné ve funkci, mezi levým a středním sloupcem jsou naznačeny „změny“ proměnných mezi funkcí volající a volanou.



3. krok: Na základě vzoru zdrojového kódu, který je přiložen na konci této stránky realizujte dané zadání. Vyberte pouze jednu z funkcí exchange (tak aby splňovala požadavky zadání). Promyslete si zdůvodnění vhodnosti/nevhodnosti, výhod/nevýhod jednotlivých řešení a případně navrhněte změny v hlavičce tak, aby byly univerzálnější (=lepší).


4. krok: naprogramujte funkci, která provede výměnu prvků po jednom a srovnejte rychlost jednotlivých řešení pro 10, 100, 1000 prvků.









Nápověda:

Na začátku programu předpokládáme naalokování dvou polí (p_x a p_y jsou ukazatele na int, tj. Adresy počátku pole intů, které dá malloc. V případě použití *p_x se přistupuje na hodnotu uloženou na adrese p_x, tj. K prvnímu prvku pole. Adresa, na které leží proměnná p_x se získá pomocí &p_x.)

int * pole intů

p_x -> int 10, int11, int12, int13 (p_x ukazuje na pole proměnných typu int)

p_y -> int 20, int 21, int 22, int 23

Naším úkolem je, aby (výměnou hodnot pole1 a pole2 ve funkci) došlo k následujícímu

int * pole intů

p_x -> int 20, int 21, int 22, int 23

p_y -> int 10, int11, int12, int13

Předáme-li do funkce hodnotu pole1 a pole2, budou ve funkci pouze lokální kopie (ukazatelů na počátek pole) a jejich výměna se neprojeví vně.

Proto je nutné do funkce dát adresu, na které leží proměnné (p_x, p_y), jejichž obsah chceme měnit. Výměnou obsahu (adres polí) vlastně vyměníme pole.

Typ void ve funkci používáme proto, že není důležité (z hlediska algoritmu) na co je ukazatel (typ pole), protože se mění pouze ukazatele ( a ty jsou pro všechny typy stejné). Funkce je tedy použitelná pro pole libovolného typu. (void se tedy dá chápat nejen jako ukazatel na „nic“ ale také jako ukazatel na „cokoli“ neboli univerzální ukazatel)

Sled proměnných a jejich obsahů je tedy ve funkci tento:

void ** (int **)

void* (int *)

(int)

p1 (&p_x)

*p1 (p_x)

(p_x[ i ])

adresa na níž je měněná proměnná

Obsah měněné proměnné (adresa počátku pole)

(prvky) pole



=================== vzor ====================

// Tento program vytvori dve pole v dynamicke pameti
// Vytvori na ne ukazatele a ty pomoci funkce exchange
// prehodi.


#include <stdio.h>
#include <stdlib.h>

#define POCET 50

// vyberte nejlepsi prototyp a ostatní smazte
void exchange (void **p1,void **p2)
 {

 }

void exchange (void *p1,void *p2)
 {

 }

void exchange (int **p1,int **p2)
 {

 }

void exchange (int *p1,int *p2)
 {

 }

void exchange (int **p1,int **p2, int delka1, int delka2)
 {

 }

void exchange (int *p1,int *p2, int delka1, int delka2)
 {

 }

void exchange (float  **p1,float **p2)
 {

 }

void exchange (float  *p1,float *p2)
 {

 }

void exchange (float  **p1,float **p2, int delka)
 {

 }

void exchange (float  *p1,float *p2, int delka)
 {

 }



int main ( )
 {
  int *p_x,*p_y;
  p_x=malloc(sizeof(int)*POCET);
  p_y=malloc(sizeof(int)*POCET);
// tady muzete cvicne naplnit pole ruznymi hodnotami
  printf("Alokovano......\n");
  printf(" px= %p , py= %p\n",p_x,p_y);
// pro lepsi prehled muzete vzdy po ukazatelich vytisknout i hodnoty
  printf("Prehazuju......\n");

//  exchange (); // zavolejte tu spravnou funkci se spravnymi parametry
//  Semhle prijde napsat volani nejvhodnejsi funkce Exchange se vstupnimi parametry.

  printf(" px= %p , py= %p\n",p_x,p_y);
  free (p_x);
// tady neco chybi
  free (p_y);
// tady neco chybi 
  printf("Dealokovano......\n");
  printf(" px= %p , py= %p\n",p_x,p_y); // tohle uz nedava moc smysl pokud se to vyse neupravi
  printf("Konec.\n");
 return 0;
 }

================ konec vzoru ==================





Poslední změna 2011-09-19