![]() |
Índex |
Miguel A. Almarza
Departament d'Informàtica IES Mare de Deu de la Merce |
class <identificador> { <Secció privada> public: <Secció pública> private: <Secció privada> proctected: <secció protegida> } <llista d'objectes de la classe> |
Fem notar que les instruccions escrites abans d'escriure cap de les tres etiquetes és una secció d'element privats de la classe.
Podem escriure més d'una secció private, public o protected, però és convenient ordenar les propietats i mètodes d'una classe escrivint només una secció de cada una d'elles.
No importa l'ordre en el que estan escrites les secciones de les classes.
Però també és convenient escriure primer la secció
privada, després la secció pública i en darrer lloc
la protegida. Simplement és una qüestió d'estètica
del programador i que ajuda molt a l'hora de llegir i entendre una declaració
d'una classe.
struct <identificador> { <Secció pública> public: <Secció pública> private: <Secció privada> proctected: <secció protegida> } <llista d'objectes de la classe> |
Allò que és una estructura normal en C és en realitat un objecte en C++ i així la paraula clau struct ha canviat totalment els seu significat. Ara quan declarem una estructura en C++ li podem posar també funcions i una estructura serà conceptualment equivalent a una classe.
La diferència entre una estructura i una classe en C++ és que per defecte les instrucció escrites abans de cap etiqueta són elements públics de l'estructura.
Penso que és molt convenient pels programadors fer ús
de les estructures en el sentit clàssic de C encara que estiguem
escrivint en C++ i reservar totes les qüestions d'objectes per a les
classes.
Quan volem fer assignació dinàmica de memòria hem de crear un punter a un objecte i després assignar-li memòria mitjançant l'operador new. Per alliberar la memòria assignada mitjançant l'operador new es fa ús de l'operador delete.
Es clar, igual que amb les estructures del C, quan accedim a un objecte
mitjançant un punter hem de fer ús de l'operador -> per tal
de arribar als seus membres (propietats i mètodes) públics.
Ús de punters i reserva dinàmica de memòria | |
#include <iostream.h> #include <conio.h> #include <stdlib.h> class Punt { int x; int y; char Caracter; public: void PosaCaracter(char c); void PosaCoordenades(int n1, int n2); void Dibuixat(); int TornaX(); int TornaY(); char TornaCaracter(); }; main() { Punt * pA; pA = new Punt; if (!pA) exit(1); pA->PosaCoordenades(10,10); pA->PosaCaracter('O'); pA->Dibuixat(); Punt * pB if (! (pB = new Punt)) exit(1); pB->PosaCoordenades(15,10); pB->PosaCaracter('G'); pB->Dibuixat(); gotoxy(1,1); cout << pA->TornaX(); cout << pA->TornaY(); cout << pA->TornaCaracter(); delete pA; delete pB; } |
La instrucció Punt * pA; crea una variable punter
a un objecte de tipus Punt, però no fa la reserva de memòria
necessària per a les dades de l'objecte.
Per tant pA és un punter a un objecte de tipus Punt. La instrucció pA = new Punt; busca memòria lliure, en tota la memòria a la que pot accedir el programa, per posar-hi les dades d'un objecte de tipus punt apuntat pel punter pA. Si troba memòria suficient, l'operador new fa la reserva d'aquesta memòria i torna l'adreça del lloc on comencen les dades de l'objecte punt adreça de memòria que és assignada al punter pA. Cas que no es trobés memòria suficient l'operador new torna un NULL i el punter pA pren aquest valor NULL. Veiem que totes les instruccions per accedir als membres públics a través del punter pA ara fan ús de l'operador -> en la forma següent: pA->TornaX();
La instrucció if (! pA) exit(1); s'escriu pel cas que l'operador new no hagués trobat memòria suficient pel punt i evitar accedir a memòria no reservada pel nostre programa. |
Així si afegim les següents instruccions al programa anterior
Punt Centre; Punt * O; O = &Centre; O->PosaCoordenades(20,10); O->PosaCaracter('C'); O->Dibuixat(); |
tenim un nou punt que es diu Centre. Fem un punter a punt que anomenem O i la instrucció O = &Centre assigna l'adreça de memòria del punt Centre al punter O. Així tenim que O és una referència al punt Centre.
Hem de pensar que com que O és un punter hem d'accedir als elements de l'objecte mitjançant l'operador ->.
És normal fer ús del pas d'objectes a funcions per referències i no per valor quan hem d'estalviar memòria o volem més rapidesa en l'execució dels nostres programes. El mecanisme de pas d'objectes per referència és s'explica en el paràgraf degüent.
Pas de paràmetres per referència a funcions. | |
Programa fet en C | Programa fet en C++ |
#include <stdio.h> void Duplica(int * Numero); main() { int Diners=230; Duplica(&Diners); printf("La variable Diners té com a dada %d\n",Diners); } void Duplica(int * Numero) { *Numero = *Numero * 2; } | #include <iostream.h> void Duplica(int &Numero); main() { int Diners=230; Duplica(Diners); cout << "La variable Diners té com a dada " << Diners; } void Duplica(int &Numero) { Numero = Numero * 2; } |
Vectors d'objectes amb new. |
main() { Punt * Rectangle; Rectangle = new Punt [4]; Rectangle[0].PosaCoordenades(1,1); Rectangle[0].PosaCaracter('O'); Rectangle[1].PosaCoordenades(79,1); Rectangle[1].PosaCaracter('O'); Rectangle[2].PosaCoordenades(1,24); Rectangle[2].PosaCaracter('O'); Rectangle[3].PosaCoordenades(79,24); Rectangle[3].PosaCaracter('O'); for(int i=0;i<4;i++) Rectangle[i].Dibuixat(); delete Rectangle; } |
Els vectors de tipus elementals de dades amb new. |
#include <iostream.h> #include <string.h> #include <stdlib.h> class String { char * Cadena; int Longitut; public: String(char * Punter); ~String(); void Posa(char * Punter); void ImprimeixDades(); } String::String(char * Punter) { if ((Cadena = new char [strlen(Punter)+1]) == NULL) { cout << "Falta mem•ria per a aquest string\n"; exit(1); } strcpy(Cadena,Punter); Longitut=strlen(Cadena); } String::~String() { cout << "Desassignat mem•ria de dades\n"; delete Cadena; } void String::Posa(char * Punter) { delete Cadena; if ((Cadena = new char [strlen(Punter)+1]) == NULL) { cout << "Falta mem•ria per a aquest string\n"; exit(1); } strcpy(Cadena,Punter); Longitut=strlen(Cadena); } void String::ImprimeixDades() { cout << "Objecte string.\n"; cout << "Cadena: " << Cadena << "\n"; cout << "Longitut: " << Longitut << "\n"; } main() { String Nom("Pere Lopez Perez"); Nom.ImprimeixDades(); Nom.Posa("Adela Lugano Luengo"); Nom.ImprimeixDades(); } |
Això permet fer més ràpid la crida d'aquestes funcions. És clar que el codi d'aquestes funcions no ha de ser gran, car el codi del programa sencer hauria de ser molt gran. També és impropi fer ús dins aquestes funcions de estructures iteratives o de la recursivitat, i de fet hi ha alguns compiladors que no permeten el seu ús.
En el programa següent demostrem com es fa ús d'aquesta
paraula clau:
Ús de funcions inline. | |
#include <iostream.h> #include <stdlib.h> class Dau { int Numero; int NumeroCaras; public: Dau(); Dau(int NumeroC, int Num); void FesSorteig(); int MostraNumero(); }; inline Dau::Dau() { Numero = 1; NumeroCaras = 6; } inline Dau::Dau(int NumeroC, int Num) { NumeroCaras = NumeroC; Numero = Num; } inline void Dau::FesSorteig() { Numero = rand() % NumeroCaras +1; } inline int Dau::MostraNumero() { return Numero; } main() { randomize(); Dau ElMeuDau; cout << ElMeuDau.MostraNumero() << '\n'; for(int i=0;i<10;i++) { ElMeuDau.FesSorteig(); cout << ElMeuDau.MostraNumero() << '\n'; } } |
Aprofitem per crear una classe anomenada Dau. En aquesta classe posem
dues propietats, el número de cares i el número (la cara
visible).
Els dos constructors i els dos mètodes (FesSorteig i MostraNumero) són totes funcions molt petites i que tenen codi sense crides recursives ni instruccions iteratives. Això fa possible declarar-les totes com a funcions inline en el nostre codi. |
Quan fem una classe també podem escriure el codi de les funcions dins la declaració de la classe. Això és una altre manera de dir-li al compilador que aquestes funcions són funcions inline.
L'aspecte típic d'una classe amb funcions inline de petit codi
és la del programa següent:
La classe Punt amb funcions inline. |
#include <iostream.h> #include <conio.h> class Punt { int x; int y; char Caracter; public: void PosaCaracter(char c) { Caracter = c;} void PosaCoordenades(int n1, int n2) {x = n1;y = n2;} void Dibuixat() {gotoxy(x,y); cout << Caracter;} int TornaX() { return x;} int TornaY() { return y;} char TornaCaracter() {return Caracter;} }; main() { Punt A; A.PosaCoordenades(10,10); A.PosaCaracter('O'); A.Dibuixat(); Punt B; B.PosaCoordenades(15,10); B.PosaCaracter('G'); B.Dibuixat(); gotoxy(1,1); cout << A.TornaX(); cout << A.TornaY(); cout << A.TornaCaracter(); } |
Si estem programant en el llenguatge C haurem de fer funcions diferents amb noms també diferents cadascuna amb els seus paràmetres.
Si estem programant en C++ haurem de fer funcions diferents però podrem donar el mateix nom a totes elles, i es diferenciaran en el tipus de paràmetres que rebran.
Això es diu sobrecàrrega de funcions. Una funció sobrecarregada és aquella que té diverses definicions, totes elles amb el mateix nom de funció, de manera que una definició és diferencia de les altres en el numero de paràmetres que rep o en el tipus d'aquests paràmetres.
Quan una funció està sobrecarregada el compilador tria una de les definicions examinant els paràmetres que li passem quan la cridem.
No és suficient que el tipus de paràmetre que torna la funció sigui diferent per tal de considerar la funció sobrecarregada. De fet si escrivim dues definicions d'una funció sobrecarregada amb els mateixos paràmetres d'entrada però tornant diferent tipus de paràmetres el compilador donarà errors d'ambigüitat ja que quan la cridarem no sabrà quina definició triar.
Ja hem vist que els constructors són funcions que es sobrecarreguen normalment.
La sobrecàrrega és un dels mètodes que utilitza el C++ per aconseguir el polimorfisme, ja que amb el mateix nom de funció podem fer coses diferents.
La funció ValorAbsolut sobrecarregada. | |
#include <iostream.h> int ValorAbsolut(int a) { cout << "Valor absolut amb enters "; if (a > 0) return a; else return - a; } long ValorAbsolut(long a) { cout << "Valor absolut amb enters llargs "; if (a > 0) return a; else return - a; } float ValorAbsolut(float a) { cout << "Valor absolut amb reals (floats) "; if (a > 0) return a; else return - a; } main() { int x = -5; long y = -23L; float z =-10.4; cout << ValorAbsolut(x) << "\n"; cout << ValorAbsolut(y) << "\n"; cout << ValorAbsolut(z) << "\n"; } |
Una funció que calculi el valor absolut d'un número és
necessària. En C tenim les funcions abs per a enters, fabs per a
floats, i labs per a long.
Nosaltres fem tres funcions, totes anomenades ValorAbsolut, una per a enters, altra per a floats i una tercera per a longs. En cadascuna d'aquestes funcions hem posat una instrucció cout per tal d'entendre que en el moment de cridar-les el compilador tria una d'elles segons el tipus de paràmetre que li passem. El resultat d'executar aquest programa és el següent: És obvi que si estem fent un objecte per a una llibreria no posarem les instruccions cout de les funcions ValorAbsolut. |
long SumaQuadrats(int a1=0, int a2=0, int a3=0)
Podem cridar-la de quatre formes diferents de manera que e cadascuna els paràmetres prenen els valors del quadre:
Ens adonem que podem cridar la funció sense especificar tots
els paràmetres, però han d'omplir-se des de el primer fins
a l'últim que s'especifica. No podem fer una crida a la funció
especificant el primer i el tercer paràmetre, per exemple.
Crida de la funció | Valor dels paràmetres | ||
a1 | a2 | a3 | |
SumaQuadrats() | 0 | 0 | 0 |
SumaQuadrats(3) | 3 | 0 | 0 |
SumaQuadrats(4,7) | 4 | 7 | 0 |
SumaQuadrats(23,12,13) | 23 | 12 | 13 |
D'una certa manera podem dir que aquesta és una altra forma de
polimorfisme, ja que tenim diferents formes de cridar una mateix funció.
Exemple 1 de pas de paràmetres per defecte. | |
#include <iostream.h> long SumaQuadrats(int a1=0, int a2=0, int a3=0) { return a1*a1 + a2*a2 + a3*a3; } main() { cout << SumaQuadrats() << "\n"; cout << SumaQuadrats(1) << "\n"; cout << SumaQuadrats(1,2) << "\n"; cout << SumaQuadrats(1,2,3) << "\n"; } |
Sortida per pantalla:
0
|
També es poden posar paràmetres "normals" i després
paràmetres per defecte. En aquest cas sempre han d'estar escrits
els paràmetres per defecte després dels paràmetres
normals. És típic fer ús d'aquesta forma per fer
funcions que actuïn de manera diferent segons el paràmetres
per defecte.
Exemple 2 de pas de paràmetres per defecte. | |
#include <iostream.h> float Calcula(int a, int b, char Operador='+') { float resultat=0; switch (Operador) { case '+' : resultat = a + b; break; case '-' : resultat = a - b; break; case '*' : resultat = a * b; break; case '/' : if (b!=0) resultat = (float)a/(float)b; break; } return resultat; } main() { cout << Calcula(3,6) << '\n'; cout << Calcula(3,6,'-') << '\n'; cout << Calcula(3,6,'*') << '\n'; cout << Calcula(3,6,'/') << '\n'; } |
Si volem fer una funció que serveixi normalment per sumar
dos números, però que també volem fer-la més
flexible i que ens serveixi en altres moments per restar, multiplicar
o dividir podem fer la funció següent:
float Calcula(int a, int b, char Operador='+'); Veiem que quan cridarem la funció serà obligatori escriure els números que han de sumar-se i no haurem d'especificar l'operand suma. Així, per defecte, la funció sumarà els dos números que rebrà com a paràmetres. Només si especifiquen el paràmetre corresponent a l'operand farà una altra operació. |
Constructor amb pas de paràmetre per defecte. | |
#include <iostream.h> class Punt { int x,y; char Caracter; public: Punt(int n1=0, int n2=0, char C = ' ') { x=n1; y=n2; Caracter = C; }; void ImprimeixDades(void); }; void Punt::ImprimeixDades(void) { cout << "x = " << x << " y = " << y << "\n"; cout << "Carcter " << Caracter << "\n"; } main() { Punt A; Punt B(1); Punt C(1,3); Punt D(1,3,'z'); A.ImprimeixDades(); B.ImprimeixDades(); C.ImprimeixDades(); D.ImprimeixDades(); } |
Així suposem que tenim les definicions següents de la ValorAbsolut
Programa amb ambigüitat |
#include <iostream.h> long ValorAbsolut(long a) { cout << "Valor absolut amb enters llargs "; if (a h> 0) return a; else return - a; } float ValorAbsolut(float a) { cout << "Valor absolut amb reals (floats) "; if (a h> 0) return a; else return - a; } main() { cout << ValorAbsolut(10) << "\n"; } |
Quan fem la crida ValorAbsolut(10) el compilador ha de fer la tria entre una de les dues funcións ValorAbsolut. Llavors, com el compilador, que pot interpretar el número 10, que és un enter, com un float o com un long indistintament, no sap quina de les dues funcions ha de triar, cosa que produeix un error d'ambigüitat en el moment de la compilació del programa.
Demo punter this | |
#include <iostream.h> class Complex { float a,b; public: Complex(float x, float y) { this->a=x; this->b=y; } void Imprimeix() { cout << this->a << " + " << this->b << "i"; } } main() { Complex z(2,5); z.Imprimeix(); } |
La instrucció Complex z(2,5); declara un objecte, anomenat z, de la classe Complex. Les dades, (2,5), es
posaran en una certa adreça de memòria. La instrucció z.Imprimeix() s'executa i rep l'adreça de l'objecte z per mitja del punter this, punter que es crea i passa de forma automàtica. Segons veiem a les dues funcions membre, la funció constructora Complex i la funció Imprimeix, hem fet ús del punter this per accedir a les dades a i b dels objectes de la classe Complex. Cal dir que escriure this->a = x; és igual que escriure a=x;. Es a dir, que podem suprimir l'escriptura del punter this, encara que de fet es fa ús d'ell encara que no l'escriguem. Aquest punter apareix també en llenguatges de programació posteriors a C++ com és el llenguatge Java on adquireix molta més importància. |
![]() |
int func(int); //Funció 1 int func(float); //Funció 2 void func(int, float); //Funció 3 void func(float, int); //Funció 4Declarem les variables següents:
int n,p; float x, y; char c; double z;Decideix de les crides següents quines són correctes i si ho són quina funció s'executa i quines conversions de dades es produeixen.
func(n); func(x); func(n,x); func(x,n); func(c); func(n,p); func(n,c); func(n,z); func(z,z);
Segons(hores, minuts) Segons(hores, minuts, segons) //Totes tres variables sons enters Segons("hh:mm:ss") //La funció rep un string amb separador : Segons("hh/mm/ss") //La funció rep un string amb separador /Pensa quantes funcions has de fer i els paràmetres que han de rebre.