Labor, 6. hét: pointerek, sztringek
Pohl László, Czirkos Zoltán · 2024.10.04.
Pointerek (mutatók), tömbök átadása függvényeknek. Sztringek alacsony szintű kezelése és a C sztringkezelő függvényei.
A mai labor feladatai pointerekkel dolgoznak; hol indirekt paraméterátadás, hol tömbök miatt.
Ha egy függvénynek átadunk egy tömböt, akkor csak a kezdőcíme adódik át, mintha egy
&t[0]
alakú kifejezéssel lekérdeznénk azt. Emiatt általában a
tömb méretét is át kell adnunk a függvénynek. A sztringeknél ezzel szemben nincs így; mivel a
sztringeknél a tömb tartalma alapján kiderül, hol van a vége (a lezáró nullánál), ezért a
méretet legtöbbször felesleges átadni paraméterként. A tömb mérete és a benne lévő sztring
hossza amúgy sem egyezik meg. Gondold át minden feladatnál, átadod-e a méretet!
Felkészülés a laborra:
- A pointerekről és sztringekről szóló előadás átismétlése.
Alább egy kis programot látsz, benne néhány változóval – egy valóssal és pointerekkel. A rajz a program futásának 1-essel jelölt helyén mutatja, hogy milyen változók léteznek, és azon belül a pointerek hova mutatnak.
int main(void) {
double d1 = 3.14;
double *p1, *p2;
p1 = &d1;
p2 = NULL; // 1
}
Rajzold le papíron az alábbi program futásának buborékkal jelölt helyein, hogy milyen változók léteznek, és hogy hova mutatnak a pointerek! A buborékok, ahogy látod, szándékosan időrendben vannak. Határozd meg, mit ír ki a program – annak futtatása nélkül! Aztán végül egy futtatással ellenőrizd az eredményt.
A c11 szabvány szerint a printf
függvény void*
típusú paramétert
vár a %p
-hez. A (void*)
explicit típuskonverzió azért szükséges,
mert az újabb fordítók figyelmeztető üzenetet adnak, ha automatikus konverzióra támaszkodik a kódunk.
#include <stdio.h>
void func1(int i2) {
i2 *= 2;
printf("func1()... i2 = %d\n", i2); // 4
}
void func2(int *p3) {
*p3 *= 2;
printf("func2()... *p3 = %d\n", *p3);
p3 = NULL; // 5, 6
printf("func2()... p3 = %p\n", (void*)p3);
}
int main(void) {
int i1 = 2;
int *p1 = NULL;
int *p2 = NULL;
printf("Hova mutat p1 es p2?\n"); // 1
p1 = &i1;
printf("&i1 = %p, p1 = %p\n", (void*)&i1, (void*)p1);
printf("i1 = %d, *p1 = %d\n", i1, *p1); // 2
i1 = 3; printf("*p1 = %d\n", *p1);
*p1 = 4; printf("i1 = %d\n", i1);
p2 = p1;
*p2 = 5;
printf("i1 = %d, p2 = %p\n", i1, (void*)p2); // 3
printf("-----\n");
func1(i1);
printf("main()... i1 = %d - de miert?\n", i1);
printf("-----\n");
func2(&i1);
printf("main()... i1 = %d - miert?\n", i1);
printf("-----\n");
func2(p2);
printf("main()... i1 = %d - miert?\n", i1);
printf("main()... p2 = %p - miert?\n", (void*)p2);
return 0;
}
Írj egyetlen void
visszatérési típusú függvényt, amely paraméterként kapja egy kocka oldalhosszúságát (valós szám), majd cím
szerint átvett paraméterekben kiszámítja (visszaadja) a kocka felületét és térfogatát! Használd ezt a függvényt
a főprogramban egy 2.7
oldalhosszúságú kocka felületének és térfogatának kiszámításához!
Emlékeztető
A cím szerint átvett paraméter esetén a függvény módosítani is tudja a neki átadott változó
értékét. Gyakran ezt úgy hívjuk, hogy „paramétersoron adja vissza az eredményt.” Gondolj a printf()
–scanf()
függvénypárra: a scanf()
megváltoztatja a neki paraméterként átadott változót, a printf()
nem.
Ezért a scanf()
cím szerint veszi át azt, a printf()
-nek elég az értéke:
int x;
scanf("%d", &x);
printf("%d", x);
Megoldás
#include <stdio.h>
#include <math.h>
void kocka(double oldal, double *pfelszin, double *pterfogat) {
*pfelszin = pow(oldal, 2) * 6;
*pterfogat = pow(oldal, 3);
}
int main(void) {
double a, v;
kocka(2.7, &a, &v);
printf("V=%f A=%f\n", v, a);
return 0;
}
Készíts függvényt, amely paraméterként vesz át egy egész számokból álló tömböt, továbbá egy számot, amit meg kell keresnie a tömbben! Térjen vissza a megtalált számnak az indexével, vagy −1-gyel, ha nem találta meg! (Ha több előfordulás is van, akkor mindegy, melyik sorszámával tér vissza.)
Írasd ki a tömböt sorszámozott elemekkel, és ellenőrizd a függvény működését! Írd ki az indexet, vagy a „nincs találat” szöveget!
Emlékeztető
Amikor függvénynek tömböt adunk át, a tömb nem másolódik le! A függvény mindig
csak a tömb elejére mutató pointert kapja meg, akár int t[]
formában, akár
int *t
formában definiáltuk a fejlécében a formális paramétert. Így tetszés
és ízlés szerint mindkét forma használható. De sosem szabad elfelejteni, hogy a függvényparaméter
kontextusban mindkettő pointert jelent! Ezért kell mindig átadni a tömb méretét is a függvénynek,
ha máshonnan nem tudná.
Alakítsd át a programot úgy, hogy ne a találat indexét, hanem annak memóriacímét adja vissza! Mit tud visszaadni ilyenkor a függvény, ha nem talált semmit? Ha ezt a függvényt kell használnod, hogyan tudod segítségével az indexet meghatározni, egy újabb keresés nélkül?
Emlékeztető
p1 + integer = p2
: mutató a tömb elejére + egész szám → mutató a tömb valahányadik elemére.
Emiatt p2 - p1 = integer
is igaz kell legyen – és tényleg, ki lehet számolni, hányat kell ugrani az
egyik memóriacímtől a másikig.
Alakítsd át úgy is a programot, hogy a ciklusok tömbindexek helyett pointerekkel dolgozzanak, az előadáson bemutatott módon.
Emlékeztető
Ha p
pointer típusú változó esetén p + 1
leírható, akkor p = p+1
is. Akkor pedig p += 1
is és ++p
is.
Így lehet lépkedni a tömbben.
Megoldás
#include <stdio.h>
int keres(int *tomb, int meret, int keresett) {
for (int i = 0; i != meret; ++i)
if (tomb[i] == keresett)
return i;
return -1;
}
int *keres_ptr(int *tomb, int meret, int keresett) {
for (int i = 0; i != meret; ++i)
if (tomb[i] == keresett)
return tomb+i;
return NULL;
}
int *keres_ptr_aritmetika(int *tomb, int meret, int keresett) {
for (int *p = tomb; p != tomb+meret; ++p)
if (*p == keresett)
return p;
return NULL;
}
int main(void) {
int szamok[10] = { 5, 8, 3, 6, 9, -2, 4, 6, 7, 1 };
int keresett = -2;
int index = keres(szamok, 10, keresett);
if (index != -1) {
printf("A(z) %d tömbbeli indexe: %d\n", keresett, index);
} else {
printf("A(z) %d nincs a tömbben\n", keresett);
}
int *p1 = keres_ptr(szamok, 10, keresett);
if (p1 != NULL) {
printf("A(z) %d tömbbeli memóriacíme: %p, indexe: %ld\n", keresett, (void*)p1, p1-szamok);
} else {
printf("A(z) %d nincs a tömbben\n", keresett);
}
int *p2 = keres_ptr_aritmetika(szamok, 10, keresett);
if (p2 != NULL) {
printf("A(z) %d tömbbeli memóriacíme: %p, indexe: %ld\n", keresett, (void*)p2, p2-szamok);
} else {
printf("A(z) %d nincs a tömbben\n", keresett);
}
}
Hasonló feladatok
Ha ez a feladat nehezen ment, példatárban itt találsz hasonlókat, amiken a labor után gyakorolhatsz otthon.
Készíts programot, mely bekér és eltárol egy keresztnevet, majd azt betűnként függőlegesen lefelé kiírja. Például ha a név „Imre”, akkor az eredmény:
I m r e
Tipp
Hogy lehet beolvasni egy szót a billentyűzetről? Mekkora karaktertömbre van ehhez szükség? Mi történik akkor, ha túl kicsi a karaktertömb, ahhoz képest túl hosszú a felhasználó neve?
Megoldás
#include <stdio.h>
int main(void) {
char nev[100];
printf("Írd be a keresztneved!\n");
scanf("%s", nev);
for (int i = 0; nev[i] != '\0'; ++i)
printf("%c\n", nev[i]);
return 0;
}
Hasonló feladatok
Ha ez a feladat nehezen ment, megoldhatsz pár hasonló feladatot a példatárból, mielőtt a következő feladatra rátérsz.
Vigyázz, a laborfeladatokat erősen ajánlott az utolsó feladatig megoldani, hogy a jövő hétre felkészült legyél. Ha nem sikerül, fejezd be őket otthon!
Gyakori feladat, hogy egy sztring elejéről és végéről el kell távolítani a szóközöket.
Ezt a függvényt gyakran trim()
-nek szokták hívni.
Írj függvényt, amelyik egy paraméterként kapott sztring elejéről és végéről eltávolítja a
szóköz karaktereket (a többi maradjon)! Pl. ha a bemenet
" helló, mizu? "
, akkor az új sztring
"helló, mizu?"
legyen. A függvény vegyen át két paramétert: egy forrás tömböt (ebben van a szóközös sztring) és
egy cél tömböt (ebbe kerül a szóköz nélküli).
Meg kell ennek a függvénynek kapnia a tömb méretét? Miért?
Megoldás
Részfeladatok: 1) előbb megkeresi az első nem szóköz karaktert a sztring elején, 2) aztán megkeresi a sztring végét, 3) és onnan visszafelé indulva megkeresi az első nem szóköz karaktert. Így megkapja a kivágandó darabot. Ezt a darabot a cél tömbbe másolja, és lezárja nullával.
Egyik művelet sem a tömb méretére, hanem a végjeles tartalmára támaszkodik.
#include <stdio.h>
void trim(char *forras, char *cel) {
int eleje = 0, vege = 0;
/* elején a szóközök? */
while (forras[eleje] != '\0' && forras[eleje] == ' ')
eleje++;
/* hol a vége? utána visszafelé a szóközökön */
while (forras[vege] != '\0')
vege++;
vege--;
while (vege >= 0 && forras[vege] == ' ')
vege--;
/* másolás az elejére, és lezárás */
int i;
for (i = 0; i <= vege-eleje; i++)
cel[i] = forras[eleje+i];
cel[i] = '\0';
}
int main(void) {
char s1[100] = " Hello mizu? ";
char s2[100];
trim(s1, s2);
printf("[%s] [%s]\n", s1, s2);
return 0;
}