Programarea Calculatoarelor, seria CC

Laborator 7

Funcții cu pointeri. Șiruri de caractere

În acest laborator veţi învăţa să:



Pointeri

Atunci când definiți o variablilă, compilatorul va fi informat despre 2 lucruri: numele variabilei si tipul ei, de exemplu int n. Astfel compilatorul va rezerva o zonă de memorie pentru aceasta variabilă (4 octeți pentru o arhitectură pe 32 biți).

Atunci când se face o atribuire n = 7, la zona de memorie rezervată variabilei n va fi scrisă noua valoare.

Pentru ca o funcție să poată modifica valoarea variabilei n ea va avea nevoie de adresa la care se află, de exemplu, pentru a citi variabila am scrie scanf("%d", &n);

Să presupunem că vrem să scrie o funcție care incrementează un întreg, va trebui deci să ținem cont că funcția va primi ca argument adresa întregului pe care vrem să-l incrementăm, deci un int *.

void inc(int * p_n)
{
    *p_n = *p_n + 1; // prin *p_n ne referim la valoarea de la respectiva adresa
}

Pentru a apela funcția pentru un întreg definit ca int n vom scrie inc(&n):

Greșit:

void inc_gresit(int n)
{
    n++; // n este doar o copie a valorii cu care a fost apelata functia
}

Funcția este greșită deoarece ea primește un întreg. Dacă ar fi apelată în modul următor:

...
int a = 5;
inc_gresit(a); // functia primeste doar valoarea variabilei
...
atunci va primi o copie a valorii lui a în momentul apelării, valoare ce va fi stocată într-o variabilă locală funcției. Toate modificările vor fi efectuate, de fapt, asupra acestei variabile locale. Pentru a înțelege mai ușor, gândiți-vă că putem apela inc_gresit(5) și este firesc că această funcție nu va modifica valoarea constantei 5.

Urmăriți următorul program care definește funcția inc și o apelează, dar mai afișează și alte informații legate de dispunerea datelor în memorie.

#include <stdio.h>

void inc(int * p_n)
{
    printf("incrementing value at address %p\n", p_n);
    printf("before: %d\n", *p_n);
    *p_n = *p_n + 1; // prin *p_n ne referim la valoarea de la respectiva adresa
    printf("after: %d\n", *p_n);
}

int main()
{
    int n = 0;
    int *pn = &n;               // pn va fi un pointer catre n
    printf("%p\t%p\n", &n, pn); // se observa ca reprezinta aceeasi adresa
    printf("%d\t%d\n", n, *pn); // si, prin urmare, indica catre aceeasi valoare
    inc(&n);                    // apelam functia pentru adresa lui n, pentru ca n sa fie modificat
    printf("%d\t%d\n", n, *pn); // valoarea lui n s-a modificat
    inc(pn);                    // pn indica tot catre adresa lui n
    printf("%d\t%d\n", n, *pn); // se va obtine deci acelasi comportament
    return 0;
}

Dimensiunea unui pointer, sizeof(int*) va fi 32b(= 4Bytes) (pentru un sistem pe 32b), deci adresele vor fi numere între 0 și 232 = 4GB. Observați în acest sens variabila pn ce are ca valoare adresa variabilei n.

Desenul următor prezintă o posibilă dispunere a variabilelor din programul nostru în memorie. Urmăriți desenul și cereți explicații dacă ceva nu este clar.

Șiruri de caractere

Un șir de caractere este un vector cu elemente de tip char, ultimul caracter din șir fiind '\0' (cod ASCII 0).

De exemplu, șirul "abc" va fi reprezentat prin vectorul {'a', 'b', 'c', '\0'}, deci va ocupa 4 octeți.

Funcțiile pentru prelucrarea șirurilor se găsesc în string.h.

Funcții din string.h:

Funcții pentru citirea și afișarea șirurilor de caractere


Problema 1 - problemă rezolvată

Scrieți o funcție pentru interschimbarea a 2 întregi.

Rezolvare:

#include <stdio.h>

void swap(int *pa, int *pb)
{
    int aux; // vom avea nevoie de un loc auxiliar de stocare
    aux = *pa; // aux este un intreg, nu un pointer, deci nu vom folosi "*"
    *pa = *pb; // pa si pb sunt pointeri, vom folosi "*" pentru a accesa valorile de la respectivele adrese
    *pb = aux;
}

int main()
{
    int a = 5, b = 7;
    printf("%d %d\n", a, b);
    swap(&a, &b);
    printf("%d %d\n", a, b);
    return 0;
}

Atenție! O greșeală frecventă este să scrieți:

...
int *aux;
*aux = *pa;
...

Problema este că aux este un pointer, care însă nu indică către o locație validă de memorie. Atunci când definiți un pointer, se alocă memorie doar pentru pointer, nu și pentru variabilă către care pointează, rezultând într-o eroare de acces la memorie la rularea programului.


Problema 2.

O funcție care primește un vector și numărul său de elemente și întoarce un pointer la elementul maxim din vector (dacă există mai multe, se poate întoarce un pointer la oricare dintre ele). Dacă vectorul nu are elemente, se va returna NULL.

int* get_max(int n, int *v);

Se va afișa adresa întoarsă de funcție, precum și valoarea aflată la această adresă.

Exemplu:

Intrare Ieşire
5
2 5 6 1 4
0xcfde121e
6

Problema 3.

O funcție care numără câte elemente pozitive, câte elemente negative și câte elemente nule se află într-un vector. Antetul funcției va fi:

void count(int n, int *v, int *zero, int *poz, int *neg);

Exemplu:

Intrare Ieşire
5
0 1 -2 3 0 -5 6
zero : 2
poz : 3
neg : 2

Problema 4.

A.

O funcție pentru ștergerea a n caractere dintr-o poziție p dată a unui șir. Se va folosi funcția strcpy.

Funcția primește adresa de unde se șterge și numărul de caractere șterse și are ca rezultat adresa p.

char *strdel(char *p, int n); // sterge n caractere din pozitia poz

Pentru a șterge 5 caractere începând cu poziția 3 (al 4-lea caracter) din șirul s va trebui să apelați cu parametrii: strdel(s + 3, 5).

B.

O funcție pentru inserarea unui șir s într-o poziție dată p dintr-un alt șir, folosind funcția strcpy.

char *strins (char *p, char *s); // insereaza la p, sirul s

C.

Un program care va căuta și înlocui un șir dat cu alt șir (de lungime diferită) într-un text. Vor fi înlocuite toate aparițiile șirului dat.

Se vor folosi funcțiile definite anterior și funcții de bibliotecă.

Textul se va introduce în program ca un șir constant. Șirul căutat și cel cu care va fi înlocuit se citesc de la consolă.

Exemplu:

Pentru șirul "ala bala portocala".

Intrare Ieşire
ala
lala
lala blala portoclala

Problema 5.

O funcție care extrage un subșir de lungime dată dintr-o poziție dată a unui șir:

char *substr(char *src, int n, char *dest); // extrage de la pozitia src in dest n caractere

Funcția are ca rezultat adresa șirului extras.

Pentru a extrage 5 caractere începând cu poziția 3 (al 4-lea caracter) din șirul s în șirul p va trebui să apelați cu parametrii: substr(s + 3, 5, p).

Exemplu:

Intrare Ieşire
abcdefg
2 3
cde

Problema 6.

A.

O funcție de comparare a două șiruri de cifre zecimale ce reprezintă numere naturale (fără semn), cu rezultat -1, 0 sau 1:

int strdcmp(char *s1, char *s2);

B.

O funcție de citire a unui întreg pozitiv cu verificare depășire valoare maximă posibilă. Se va citi numărul ca șir de caractere (cu scanf).

Șirul se va compara șirul citit cu șirul corespunzator valorii maxime admise (INT_MAX). Constanta INT_MAX este definită in limits.h.

Pentru comparare folosiți funcția definită anterior.

În caz de introducere a unui număr prea mare funcția are ca rezultat pe - INT_MAX.

int readint();

Pentru a converti un întreg n într-un șir de caracter s puteți folosi: sprintf(s, "%d", n) (afișează în s pe n, ca întreg).

Pentru a converti din șir în întreg puteți folosi int atoi(char *s).

Exemplu:

Intrare Ieşire
1
1
2147483648
-2147483647

Problema 7.

A.

Funcție care extrage următorul cuvânt format numai din litere mici începând de la o adresă dată.

Funcția are ca rezultat adresa imediat următoare cuvântului extras (în al doilea argument al funcției):

char *next (char *from, char *word);

B.

Program pentru citirea unei linii ce conține cuvinte formate din litere mici și alte șiruri și afișarea cuvintelor formate din litere mici (separate între ele prin orice alte caractere).

Exemplu:

Intrare Ieşire
( unu, 1 doi DOI trei; "patru" ) unu doi trei patru

Problema 8.

O funcție care calculează panta și ordonata pentru o dreaptă dată prin 2 puncte.

Ecuația dreptei dată prin pantă și ordonată este y = m * x + n

void panta(int x1, int y1, int x2, int y2, float *m, float *n);

Dacă dreapta este verticală m va avea valoarea INT_MAX (definită in limits.h).

Programul va citi coordonate a 2 puncte ce determină dreapta și încă un punct pentru care se va verifica dacă aparține dreptei, folosind funcția de mai sus. Atenție la cazul când dreapta e verticală.

Panta unei drepte date prin coordonatele (x1, y1) și (x2, y2) este: m = (y2 - y1) / (x2 - x1), dacă x1 != x2.

Ordonata va fi n = y1 - m * x1.

Folosiți ecuația y = m * x + n ca să aflați dacă cel de-al treilea punct aparține dreptei. Dacă dreapta este verticală, trebuie să verificați egalitatea pentru coordonatele x.

Exemplu:

Intrare Ieşire
1 1
2 2
3 3
DA
2 1
2 3
3 3
NU

Problema 9.

Funcție pentru incrementarea unui moment de timp, dat ca oră, minut și secunda și perioada zilei (AM sau PM). Funcția va trebui să modifice 3 numere întregi și un vector de caractere și va primi 3 argumente pointer și un argument vector.

Exemplu:

Intrare Ieşire
09 09 09 AM 09:09:10 AM
10 25 59 PM 10:26:00 PM
11 59 59 PM 00:00:00 AM
<<<<<<< HEAD
======= >>>>>>> 741b20e58add22f5ff7f102122ab3e5d7fbad0c0