Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Info

Ciele cvičenia

Cvičenie je zamerané na prácu so smerníkmi, ktoré sú v programovaní C veľmi silným a užitočným nástrojom. Po osvojení si problematiky študent dokáže efektívne adresovať údaje v pamäti. Rozlišuje medzi adresou premennej a jej obsahom.

  • Hodnota, adresa a smerník
  • Smerník ako argument funkcie
  • Smerník a polia - reťazce
  • Vodiaca prezentácia
Info

Odporúčaná literatúra a dôležité odkazy

[1] KPI-FEI-TUKE: Prednáška č. 1

[2] KPI-FEI-TUKE:  Cvičenie č.

[3] Horovčák P.: Úvod do programovania v jazyku C- Smerníky

[4] Základy práce s knižnicou STDIO.H

[5] Reťazce v jazyku C

[6] Argumenty príkazového riadku

[7] CodeWithC.com: Understanding C Pointers: A Beginner’s Guide


Čo je to smerník?

So smerníkmi sme sa už pri programovaní (na ZAP) takmer určite stretli. Spomínate si na funkciu scanf? Táto funkcia slúži na načítanie znakov z terminálu. Ak si spomíname tak volanie tejto funkcie vyzeralo nejako takto: scanf("%c", &znak) alebo scanf("%s", retazec). Na ZAP sme len konštatovali, že Funkcia scanf() pracuje s "miestom" v pamäti, teda adresou premennej a nie s jej hodnotou [4] a preto, ak načítavame jeden znak musíme použiť &, ktorý hovorí o adrese premennej. Neskôr sme si tiež povedali, že ak načítavame reťazce tak & nepoužívame [5]. Z uvedeného je teda zrejmé, že ak pred názov premennej dáme znak & odkazujeme sa na adresu tejto premennej. V prípade polí (teda aj reťazcov), to ale neplatí! Pri poliach sa za adresu (ide o adresu prvého prvku poľa) považuje samotný názov tohto poľa a k jeho prvkom sa potom pristupuje cez indexáciu v hranatých zátvorkách.

Smerník je údajový typ, ktorý obsahuje adresu, ktorá ukazujeme na miesto v pamäti. Ak teda deklarujeme premennú, pred ktorú dáme znak*, dáme prekladaču najavo, že v tejto premennej budú uložené adresy premenných. 

Nasledujúcim kódom je demonštrovaný vzťah medzi adresou premennej, jej obsahom a smerníkom na túto premennú:

Code Block
languagecpp
#include <stdio.h>
int main()
{
  int a = 5;        // Priradenie hodnoty 5 do premennej a.
  int* ptr_a = &a;  // Priradenie adresy prem. a do ptr_a. 
					// (typ smernik)	
  int c = *ptr_a;   // Priradenie obsahu do prem c.
					// Tento obsah sa nachádza na adrese kam 
					// ukazuje smerník ptr_a.   
  printf(">> a = %d \n", a);  
  printf(">> *ptr_a = %d \n", *ptr_a);
  printf(">> ptr_a = %p \n", ptr_a);
  printf(">> &a = %p \n",  &a);
  printf(">> c = %d \n", c);
  return 0;
}



Pointers in C Programming with examples

Po kompilácii a spustení kódu:

Code Block
languagebash
ab123cd@zapfei:CV1$ gcc ptr.c -o PTR
ab123cd@zapfei:CV1$ ./PTR
>> a = 5
>> *ptr_a = 5
>> ptr_a = 0x7ffdebd89bf8
>> &a = 0x7ffdebd89bf8
>> c = 5



NULL vs 0 vs VOID

V jazyku C void znamená absenciu dátového typu, poznáme to napríklad z funkcii bez návratovej hodnoty. Aj smerníky môžu byť typu void a taký smerník môže obsahovať adresu premennej ľubovoľného typu. Môžeme ho použiť vtedy ak do neho počas behu programu, v rôznom čase, potrebujeme uložiť referenciu na rôzne údajové typy. Napr. najprv int a niekedy neskôr zasa char...  Smerník typu void má v jazyku C svoj význam, ale pre bežné potreby ho používať nebudeme. 

Smerník, ktorý obsahuje hodnotu NULL neukazuje nikam do pamäte. Občas budete tento smerník používať a overovať ako návratovú hodnotu funkcie. Napríklad vtedy ak sa nepodarí otvoriť súbor pre zápis alebo čítanie. V takom prípade vhodným testom predídeme chybe programu. Tiež je možné smerník inicializovať hodnotou NULL, teda vytvorená premenná je definovaná, ale neukazuje nikam do pamäte. 

Pri smerníkoch je možné inicializovať aj hodnotou 0 to sa tiež považuje za NULL. Je potrebné mať na pamäti, že pri normálnej premennej napr. int 0 znamená hodnotu 0. Pre lepšiu predstavu si to môžete zapamätať pomocou obr. vpravo. 

Ak hodnotu premennej alebo smerníka nedefinujeme (napr. int a), nevieme čo sa v nej nachádza alebo kam smerník ukazuje. Je to náhodná hodnota, ktorá sa v daný moment na danom pamäťovom mieste nachádzala.  Na toto je potrebné dávať pozor!

Čo sa so smerníkom smie a čo sa nedá

  • Smerník môže obsahovať adresu premennej ľubovoľného typu

  • Smerník môže byť inicializovaný hodnotou NULL
  • K smerníku (adrese) je možné pripočítať alebo od neho odpočítať celočíselnú hodnotu
  • Smerník (adresu) nie je možné vynásobiť alebo deliť
  • Smerník nemožno uložiť do iného údajového typu ako smerník [7]



Pointers in C


Smerník ako argument funkcie

Na ZAP sme sa naučili, že výsledok vykonania ľubovoľnej funkcie môže byť vrátený pomocou jej návratovej hodnoty. K tomuto účelu slúži príkaz return, ktorý je spravidla posledným príkazom vykonaným volanou funkciou. Mnoho funkcií ale je typu void, teda bez návratovej hodnoty a stále dokážu "vrátiť" výsledok, napr. tak ako už spomínaná funkcia scanf.

Funkciu bez návratovej hodnoty, ktorej výsledky potrebujeme ďalej používať musí teda pracovať s premennými v pamäti  presne tam, kde ich používa aj funkcia (napr. main), ktorá danú funkciu zavolala. Toto zabezpečíme tak, že vstupnými argumentom funkcie bude priamo adresa premennej, do ktorej sa má výsledok uložiť.

V nasledujúcom príklade si ukážeme jednoduchú funkciu, ktorá vynásobí číslo, ktoré jej bude zaslané hodnotou "-1". Teda v prípade kladného čísla dostaneme číslo záporné a naopak v prípade záporného čísla zasa vo výsledku dostaneme číslo kladné. Takúto funkciu si ukážeme v dvoch prevedeniach - s návratovou hodnotou a bez nej. 


Funkcia invertovania s návratovou hodnotou 

Code Block
languagecpp
#include <stdio.h>
int invert(int cislo); // funkcia s návratovou hodnotou int a vstup. argumentom int

int main()
{

  int a = 5;         
  int b = invert(a); // Do b je uložená návratová hodnota funkcie.
  printf(" >> b = -1 x a = -1 x %d = %d\n", a, b); 
  return 0;
}

int invert(int cislo)
{
  return -1*cislo;  
}

Funkcia invertovania bez návratovej hodnoty (s použitím smerníka)

Code Block
languagecpp
#include <stdio.h>
void invert(int* cislo); // funkcia bez návratovej hodnoty a 
						 // so vstup. argumentom smerník na int
int main()
{
  int a = 5;
  int b = a;  // Do b je skopírovaná hodnota z premennej a
  invert(&b); // Funkcii invert sa pošle iba adresa premennej b
  printf(" >> b = -1 x a = -1 x %d = %d\n", a, b); 
  return 0;
}
void invert(int* cislo)
{
  *cislo = *cislo * (-1);  // Cez * pred premenou cislo sa dostaneme na obsah,
						   // ktorý sa nachádza na adrese, ktorú funkcia dostala.
}

V oboch prípadoch bude po spustení programu výsledok rovnaký:

Code Block
languagebash
ab123cd@zapfei:CV1$ gcc invert.c -o INV
ab123cd@zapfei:CV1$ ./INV
 >> b = -1 x a = -1 x 5 = -5

Úloha 1. 

Napíšte program (aritmetika.c) pre výpočet základných aritmetických operácií "+", "-",  "*"  a "/"

  • Operácia bude zadaná ako argument príkazového riadku! Zopakovať si [6].
  • Vstupné čísla a, b budú vyžiadané od používateľa programom. (printf → scanf)
  • Všetky funkcie okrem main budú typu void.
  • Kostra kódu je zobrazená v bloku vpravo.
    • Odporúčaný postup je taký, že
      • najprv si sfunkčnite funkcie,
      • potom si spravíte zadávanie čísel,
      • nakoniec vyriešite zadávanie operácie z príkazového riadku!
  • Úloha je postavená tak, že musíte doplniť kostru a nesmiete nič čo je v nej uvedené zmeniť alebo vymazať!


Code Block
languagebash
ab123cd@zapfei:CV1$ gcc aritmetika.c -o ARITMETIKA
ab123cd@zapfei:CV1$ ./ARITMETIKA +

 >> Zadaj čslo a = 5

 >> Zadaj čislo b = 3

 >> c = a + b = 5 + 3 = 8

Code Block
languagecpp
#include <stdio.h>
#include <string.h>

// Tu sú deklarácie funkcii
void sucet(int* cislo1, int* cislo2, int* ans);
void rozdiel(int* cislo1, int* cislo2, int* ans);
void sucin(int* cislo1, int* cislo2, int* ans);
void podiel(int* cislo1, int* cislo2, int* ans);

int main(int argc, char *argv[])
{
	// Nezabudnúť na overenie toho či používateľ zadal správny počet argumentov
	// Nezabudnuť na deklarácie premenných
	// Tu bude kód pre zadávanie hodnoty vstupných premenných 
    
   if( strcmp(argv[1],"+") == 0 )
    {
      sucet(&a, &b, &c);
      printf("\n >> c = a + b = %d + %d = %d\n", a,b,c);
    }
    // ... tu budú ďalšie možnosti      
 return 0;   
}
	//  Tu by  mali byť definície funkcií

Smerník a polia - reťazce

Ako už bolo uvedené vyššie, v prípade polí (teda aj reťazcov)  sa za adresu (ide o adresu prvého prvku poľa) považuje samotný názov tohto poľa. Na ZAP sme sa tiež naučili, že reťazec je 1R pole, ktoré je zakončené terminátorom '\0' [5]. Preto keď sme reťazce posúvali iným funkciám stačilo, aby sme funkcii poslali iba názov poľa a už sme nemuseli ďalej zasielať aj jeho dĺžku. V bloku kódu vpravo je zobrazený jednoduchý kód na zámenu veľkých písmen za malé a naopak (medzery a iné znaky okrem písmen nie sú ošetrené). Výsledkom tohto kódu je nasledovný výpis z príkazového riadku:

Code Block
languagebash
ab123cd@zapfei:CV1$ gcc retazec.c -o RETAZEC
ab123cd@zapfei:CV1$ ./RETAZEC

>> Vstupny retazec --> AhoJSveT

>> Vystupny retazec --> aHOjsVEt

V prípade klasických 1R polí je potrebné funkcii spoločne s adresou zaslať aj veľkosť poľa. To je dané tým, že obyčajné 1R pole na svojom konci nemá ukončovací znak - terminátor.  

Code Block
languagecpp
void funkcia_nad_1R_polom(int* pole, int dlzka); 
Code Block
languagecpp
#include <stdio.h>
#include <string.h>
#include <ctype.h>
void change(char*); 

int main()
{
  char Retazec[100] = "AhoJSveT";  
  printf("\n>> Vstupny retazec --> %s\n",Retazec);
  change(Retazec);
  printf("\n>> Vystupny retazec --> %s\n",Retazec);   
}
void change(char* retazec)
{
  int d = strlen(retazec);
  for (int i = 0; i<d; i++)
  { 
   if (islower(retazec[i]))
        retazec[i] = retazec[i] + ('A'-'a');
   else
        retazec[i] = retazec[i] - ('A'-'a');
  }
}

Úloha 2.

Vytvorte vlastnú funkciu pre porovnávanie dvoch reťazcov. 

  • Reťazce budú funkcii odovzdané cez smerník (teda adresa ako to je v príklade vyššie). 
  • Funkcia vráti hodnotu 1 ak budú reťazce zhodné a hodnotu 0 v opačnom prípade.
  • Funkcia vráti hodnotu -1 ak je niektorý z reťazcov NULL alebo prázdny
  • Funkcia bude mať názov StrCmp.
  • Kostra programu je vpravo.
  • Okrem stdio.h sa zakazuje použitie iných .h súborov


Code Block
languagecpp
#include <stdio.h>
int StrCmp(char*, char*);

int main()
{
	char Retazec1[10], Retazec2[10];
	// tu bude rutina pre zadávanie vstupných reťazcov

	printf("\n>> Vstupny retazec --> %s\n",Retazec);
	int ans = StrCmp(Retazec1,Retazec2)
	// tu bude vyhodnotenie a opoznamenia použivateľa o výsledku
}
//  Tu by mala byť definícia funkcie