CATEGORII DOCUMENTE |
DOCUMENTE SIMILARE |
|
TERMENI importanti pentru acest document |
|
Parametrii formali ai unei functii pot fi initializati in limbajul C++. Vom spune ca parametrii initializati au o valoare implicita. In lista parametrilor formali elementele initializate vor fi declarate in felul urmator:
tip nume = expresie
Totusi exista anumite restrictii in acest sens. Daca exista atat parametri formali initializati cat si neinitializati, atunci cei neinitializati trebuie sa fie inainte de cei initializati in lista parametrilor formali.
Prezentam un exemplu in continuare. Fisierul init1.cpp:
#include <iostream.h>
#include <conio.h>
void intilniri(char* nume, char* loc,
int ora = 12, int minut = 0)
int main()
Daca se executa programul se obtine:
Intilniri:
Nume: Daniel
Loc: Facultatea de Litere
Ora: 12
Minut: 0
Nume: Eva
Loc: Scoala Generala
Ora: 14
Minut: 0
Nume: bunica
Loc: acasa
Ora: 17
Minut: 20
Observam ca paramerii formali ora si minut sunt initializati cu valorile implicite 12, respectiv 0. Daca parametrii formali initializati nu au parametrii actuali corespunzatori, atunci se vor folosi valorile lor implicite, in caz contrar se va ignora valoarea implicita si se vor folosi parametrii actuali corespunzatori.
Este interesant de remarcat ca la initializare se poate folosi orice expresie, deci de exemplu si apeluri de functii. In urmatorul exemplu apar parametrii formali initializati in acest fel. Fisierul init2.cpp:
#include <iostream.h>
#include <conio.h>
#include <dos.h>
char ziua()
char luna()
int anul()
void scrie_data( char z = ziua(),
char l = luna(),
int a = anul() )
int main()
In exemplul de mai sus s-a folosit ca in fisierul dos.h este declarata o structura pentru memorarea datelor sub forma:
struct date ;
Functia getdate are un parametru, care este un pointer catre structura de mai sus, in care se va returna data curenta.
Fisierul se poate compila cu un compilator C++ sub sistemul de operare Dos sau Windows, si dupa executie se obtine un rezultat care poate varia in functie de data curenta. De exemplu (data curenta: 24 august 1998):
ziua: 24 luna: 8 anul: 1998
ziua: 11 luna: 8 anul: 1998
ziua: 18 luna: 4 anul: 1998
ziua: 27 luna: 7 anul: 1991
Din rezultate reiese ca in cazul in care nu s-a specificat parametru actual, s-a folosit valoarea implicita, deci data curenta.
In limbajul C++ tipul returnat de o functie poate fi si tip referinta. In acest caz antetul functiei va fi de forma:
tip& nume(lista_parametrilor_formali)
Functiile care returneaza tipuri referinta pot apare atat pe partea dreapta cat si pe partea stanga a operatorilor de atribuire. Se spune ca ele pot fi atat operanzi lvalue (left value) cat si rvalue (right value). Un operand lvalue poate sa apara pe partea stanga, iar unul rvalue pe partea dreapta a operatorilor de atribuire.
Sa consideram urmatorul exemplu. Intr-o urna se afla bile albe si negre. Se extrage un numar de bile din urna la intamplare. Sa se scrie un program pentru aflarea numarului de bile albe, respectiv negre extrase. Fisierul refer4.cpp:
#include <iostream.h>
#include <conio.h>
#include <stdlib.h>
#include <time.h>
long& alb_negru( long& ref_alb, long& ref_negru)
void main()
Dupa compilarea sub sistemul de operare Dos sau Windows cu Borland C++, si executarea programului, se obtine un rezultat care depinde de numarul total de bile, numar citit de la intrarea standard. Numarul de bile albe respectiv negre extrase se determina in mod aleator. Deci rezultatul poate fi:
Numarul de bile extrase: 50
Bile albe: 27
Bile negre: 23
Mentionam ca s-a evidentiat numarul citit. Observam ca functia alb_negru returneaza o referinta catre unul din parametrii nr_alb sau nr_negru in functie de faptul ca bila extrasa este alba sau neagra. Este important de mentionat ca parametrii formali ref_alb si ref_negru ai functiei alb_negru trebuie sa fie declarate ca si referinte la tipul long. In caz contrar, in cadrul functiei s-ar incerca returnarea unei referinte catre o variabila locala (unul din parametrii formali), ceea ce este nepermis.
In cadrul functiei main s-a apelat functia alb_negru si totodata s-a folosit si operatorul de incrementare. Acest lucru este posibil datorita faptului ca functia returneaza o referinta la o variabila declarata in functia main, si ca atare apelul functiei se comporta ca si un nume de variabila.
Bineinteles aceasta problema simpla s-ar fi putut rezolva si fara folosirea unei functii, care returneaza o referinta. In varianta fara functia care returneaza o referinta, operatorul de incrementare s-ar fi apelat de doua ori, pentru doua variabile distincte (nr_alb si nr_negru). In cazul unei probleme mai complexe, de exemplu daca operatorul de incrementare s-ar fi inlocuit cu operatii mai complicate, utilizarea unei functii, care returneaza o referinta, poate simplifica considerabil programul.
In capitolele care urmeaza, in cadrul definirii claselor, ne vom intalni cu situatii, in care apare necesitatea declararii unor functii, care returneaza o referinta catre clasa curenta.
In limbajul C numele functiilor distincte trebuie sa fie diferit. In limbajul C++ insa putem defini functii diferite avand numele identic. Aceasta proprietate se numeste supraincarcarea functiilor. De obicei functiile se vor supraincarca in cazul in care niste operatii asemanatoare sunt descrise de mai multe variante ale unor functii. In general aceste functii se refera la proprietati asemanatore, aplicate pentru parametrii formali diferiti.
Pentru a putea face distinctie intre functiile supraincarcate, antetul acestor functii trebuie sa fie diferit. Astfel tipul parametrilor actuali va determina functia care se va apela.
Prezentam un exemplu legat de variantele functiei trigonometrice atan, pentru calcularea numarului π. Fisierul sup_fun1.cpp:
#include <iostream.h>
#include <conio.h>
#include <math.h>
#include <complex.h>
/*
double atan(double x);
double atan2(double y, double x);
long double atanl(long double (x));
long double atan2l(long double (y), long double (x));
complex atan(complex x);
*/
int main()
In fisierul de mai sus, in forma de comentariu s-au specificat cele cinci variante ale functiei atan. Primele patru sunt declarate in fisierul math.h, ele au numele distincte si se refera la parametrii formali de tip real. In Borland C++ exista functia atan, cu acelasi nume ca si prima functie referitoare la parametrii de tip real. Aceasta functie este declarata in fisierul complex.h, si se apeleaza in cazul unui parametru actual de tip complex.
Dupa executarea programului obtinem:
3.141592653589793116
3.141592653589793238
3.141592653589793116
3.141592653589793238
3.141592653589793116
Observam ca in cazul in care nu s-au folosit variabile de tip long double (functiile atan cu parametrii de tip double respectiv complex, si functia atan2) primele 15 zecimale sunt corecte, iar pentru functiile atanl si atan2l toate cele 18 zecimale obtinute sunt corecte.
Exemplul de mai sus se poate modifica astfel incat toate cele cinci variante ale functiei atan sa aiba acelasi nume. Pentru a realiza acest lucru vom scrie trei functii cu numele atan, care vor apela functiile atan2, atanl respectiv atan2l. Deoarece antetul functiilor va fi diferit, se poate efectua supraincarcarea lor. Prezentam in continuare aceasta varianta modificata a programului. Fisierul sup_fun2.cpp:
#include <iostream.h>
#include <conio.h>
#include <math.h>
#include <complex.h>
//double atan(double x);
double atan(double y, double x)
long double atan(long double (x))
long double atan(long double (y), long double (x))
//complex atan(complex x);
int main()
Daca se executa programul, se obtine acelasi rezultat ca si pentru programul precedent. In acest caz numele tuturor functiilor este acelasi, deci functia care se va apela, se determina dupa numarul si tipul parametrilor actuali. De aceea trebuie specificat cu exactitate tipul parametrilor formali (de exemplu 1.0 este constanta de tip double, si 1.0L este constanta de tip long double).
In exemplul de mai sus se poate determina intotdeauna cu exactitate functia care se va apela, deoarece exista o functie pentru care atat numarul cat si tipul parametrilor formali este acelasi ca si pentru functia respectiva. Nu este insa intotdeauna in asa fel. De exemplu, daca in functia main din exemplul de mai sus am fi scris
cout << 4 * atan( 1 ) << endl;
atunci deoarece parametrul actual al functiei atan este de tip intreg, nici unul din cele cinci functii nu are un antet corespunzator. In astfel de cazuri se vor lua in considerare urmatoarele reguli. Etapele determinarii functiei care se va apela:
Daca exista o functie pentru care tipul parametrilor formali coincide cu tipul parametrilor actuali corespunzatoare (deci si numarul parametrilor formali coincide cu numarul parametrilor actuali), atunci aceasta functie se va apela.
Altfel, functia se va determina folosind conversia implicita pentru tipurile standard (de exemplu tipul int se va converti in double). In acest caz nu vor fi pierderi de informatii.
Daca nu s-a reusit determinarea unei functii, atunci se incearca o conversie pentru tipuri standard cu eventuale pierderi de informatii (de exemplu conversie din tipul long in tipul int).
Daca nici in acest fel nu s-a reusit determinarea functiei, atunci se incearca aplicarea unor conversii si tipurilor definite de programator.
Rezulta ca in cazul expresiei cout << 4 * atan( 1 ) << endl; se va incerca aplicarea etapei a doua a determinarii functiei, dar deoarece s-ar putea aplica atat o conversie de la tipul int la double, cat si la long double, apare o ambiguitate care nu poate fi rezolvata de compilator, si se va afisa un mesaj de eroare la compilare.
Daca insa revenim la exemplul din fisierul sup_fun1.cpp putem constata ca la compilarea expresiei amintite nu au aparut mesaje de eroare. Acest lucru se datoreaza faptului ca functia atan a fost supraincarcat in asa fel incat au existat numai doua variante ale ei (una cu parametru formal de tip double, iar alta de tip complex). In acest caz exista o singura conversie implicita posibila pentru tipuri standard, deci se va aplica conversia de la tipul int la double, si se va apela functia atan cu un singur parametru de tip double.
Apelarea unei functii se face printr-un salt la adresa unde este memorata corpul functiei, iar dupa executarea instructiunilor functiei, se face o revenire la locul apelarii. In cazul in care functia este foarte simpla operatiile necesare apelarii pot fi mai complicate decit instructiunile functiei. In limbajul C aceasta problema se poate rezolva prin folosirea unor macrouri. Ele se definesc in modul urmator:
#define nume(lista_de_parametrii) sir_de_caractere
In faza de preprocesare apelurile macrourilor vor fi inlocuite cu sirul de caractere specificat, in care parametrii formali vor fi inlocuiti cu parametrii actuali. Observam ca apelarea macrourilor seamana cu apelarea functiilor. Totusi exista multe diferente. De exemplu apelarea unei functii se face printr‑un salt la adresa corpului functiei dupa care se revine la locul apelarii, iar apelarea macrourilor se face prin inlocuirea cu expresia corespunzatoare in faza de preprocesare. Diferenta cea mai importanta este ca in cazul apelarii unei functii, se verifica corespondenta dintre tipurile parametrilor formali si cei actuali, iar daca este necesar se face si o conversie la tipul corespunzator. Astfel de verificari nu se fac in cazul macrourilor, ceea ce este un dezavantaj fiindca poate sa conduca la aparitia unor erori. Daca sunt folosite in mod corect, avantajul macrourilor este ca ele pot fi apelate pentru expresii de tipuri diferite.
In urmatorul exemplu vom defini trei macrouri pentru calculul valorii absolute a unui numar, si vom atrage atentia asupra posibilitatii aparitiei unor erori. Fisierul macro1.cpp
#include <iostream.h>
#include <conio.h>
#define Val_abs(x) ((x) > 0 ? (x) : -(x))
#define Val_abs_eronata_1(x) ( x > 0 ? x : -x )
#define Val_abs_eronata_2(x) (x) > 0 ? (x) : -(x)
int main()
Dupa executie se obtine:
10
50000
10000
-90000
25
5
11
y = 12
Daca parantezele ar lipsi din definirea macroului Val_abs(x), in unele expresii ar putea sa apara erori. Intr-adevar prin expresia
cout << Val_abs_eronata_1(4000L * y + z ) << endl;
se obtine rezultat necorespunzator (-90000 in loc de 10000), deoarece in faza de preprocesare macroul Val_abs_eronata_1 s-a inlocuit cu:
( 4000L * y + z > 0 ? 4000L * y + z : -4000L * y + z )
si rezultatul va fi: -4000L * y + z = -90000. Nici in cazul expresiei
cout << 15 + Val_abs_eronata_2( -y );
nu se obtine rezultat corect (5 in loc de 25), deoarece macroul Val_abs_eronata_2 se inlocuieste cu
(-y) > 0 ? (-y) : -(-y)
si expresia va deveni:
cout << 15 +(-y) > 0 ? (-y) : -(-y);
Operatorul de adunare fiind mai prioritar decat operatorul , se evalueaza mai intai expresia 15 + (-y), si se va afisa valoarea 5. In sfarsit, mentionam ca si in cazul in care macroul este definit corect pot sa apara situatii in care rezultatul nu va fi cel asteptat. Daca apelarea macroului s-ar face ca si in cazul functiilor, atunci prin evaluarea expresiei
cout << Val_abs( y++ ) << endl;
s-ar afisa valoarea 10, iar valoarea parametrului y dupa evaluarea expresiei ar fi 11. In cazul nostru insa s-a afisat valoarea 11, iar dupa evaluarea expresiei s‑a obtinut valoarea y = 12. Explicatia consta si de aceasta data in modul de apelare a macrourilor. In faza de preprocesare macroul Val_abs se inlocuieste cu urmatorul sir de caractere:
((y++) > 0 ? (y++) : -(y++))
Dupa evaluarea expresiei (y++) > 0, se incrementeaza valoarea lui y, deci rezultatul expresiei conditionale va fi egala cu 11, dupa care se incrementeaza inca o data valoarea variabilei y. In consecinta dupa evaluarea expresiei se obtine y = 12.
Aceste neajunsuri ale macrourilor pot fi inlaturate cu ajutorul functiilor inline. Apelarea functiilor inline se face in mod asemanator cu apelarea macrourilor. Apelul functiei se va inlocui cu corpul functiei, deci operatia de salt la adresa corpului functiei si revenirea la locul apelarii nu sunt necesare. Comparativ cu macrouri, functia inline are avantajul, ca se va verifica corespondenta dintre tipurile parametrilor formali si cei actuali, si se va face o conversie de tipuri, daca este necesar. Astfel posibilitatea aparitiei unor erori este diminuata. Declararea sau definirea unei functii inline se face in mod obisnuit, dar antetul functiei se va incepe cu cuvantul cheie inline.
Exemplul de mai sus se poate transcrie folosind o functie inline, in modul urmator. Fisierul inline1.cpp
#include <iostream.h>
#include <conio.h>
inline long Val_abs( long x )
int main()
Dupa executarea programului se obtine:
10
50000
10000
25
10
y = 11
Putem constata ca, in acest caz, toate rezultatele obtinute sunt corecte. Mentionam ca o functie inline nu se poate folosi numai intr-un singur modul, functia nu poate sa contina operatii mai sofisticate, de exemplu nici instructiuni de ciclare. In cazul definirii claselor ne vom intalni foarte des cu functii inline.
Politica de confidentialitate | Termeni si conditii de utilizare |
Vizualizari: 934
Importanta:
Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved