Índex
Miguel A. Almarza
Departament d'Informàtica
IES Mare de Deu de la Merce

       

 
Capítol 4

Sobrecàrrega d'operadors

  1. Sobrecàrrega d'operadors.
  2. Sobrecàrrega d'operadors binaris.
    1. L'operador binari =
    2. Sobrecàrrega de l'operador = en cas d'assignació dinàmica de memòria.
    3. Sobrecàrrega de l'operador + en cas d'assignació dinàmica de memòria.
  3. Sobrecàrrega d'operadors monaris.
    1. Els operadors increment i decrement.
    2. L'operador monari -
  4. Sobrecàrrega d'operadors lògics i relacionals.
  5. Sobrecàrrega d'operadors amb funcions amigues.
    1. L'operador + amb funcions amigues. Classe Complex.
    2. L'operador + amb funcions amigues. Classe String.
  6. Sobrecàrrega de l'operador [ ].
  7. Sobrecàrrega dels operadors new i delete.
  8. Conversió de tipus de definits pel usuri.
  9. Exercicis.

4.1. Sobrecàrrega d'operadors.

En el llenguatge de programació C++ existeixen gran quantitat d'operadors, aritmètics, lògics, d'assignació etc.

Quan fem una operació suma entre dos números enters estem fent ús de l'operador aritmètic binari +. Però això vol dir en programació que existeix una funció associada a aquest operador que es capaç de veure aquest dos números enters, els suma i torna el resultat.

Ara nosaltres estem fent classes noves i objectes d'aquestes classes. Segons el tipus d'objecte voldrem que aquests operadors funcionin i que funcionin de la manera apropiada a aquest objectes.

Així, si volem sumar do números complexos l'operador + haurà de sumar la part real i la part imaginària d'aquests dos números i tornar l'objecte resultant que serà el complexo suma. També podem fer que l'operador + faci la tasca de concatenació de dos objectes de tipus String.

Per tant, nosaltres haurem de fer les funcions corresponents a l'operador suma per a aquestes dues classes, com a funcions membre de la classe. També podem, com veurem més endavant, fer-ho amb funcions amigues.

Es poden sobrecarregar tots els operadors de C++ menys els sis operadors següents:

.   ::   .*   ?:, #, ##



Operadors que es poden sobrecarregar
+-*/%^&|
~!,=<><=>=
++--<<>>==!=&&||
+=-=*=/=%=^=&=|=
<<=>>=[ ]( )->->*newdelete

4.2. Sobrecàrrega d'operadors binaris.


La forma de sobrecarregar un operador binari la veiem a continuació amb l'exemple dels nombres complexos.

Sobrecàrrega d'operadors binaris.
#include <iostream.h>

class Complex
{
    float a,b;
public:
    Complex(){a=b=0;};
    Complex(float x, float y) {a=x;b=y;}
    void Imprimeix();

    Complex operator+(Complex z2);
};

void Complex::Imprimeix()
{
    cout << a;
    if (b>0) cout << " + ";
    cout << b << "i";
}

Complex Complex::operator+(Complex z2)
{
    Complex z;
    z.a = a + z2.a;
    z.b = b + z2.b;

    return z;
}

main()
{
    Complex z1(2,5),z2(4,8), z;

    z = z1 + z2;
    z.Imprimeix();
}

Fem una classe de nombres complexos. Recordem que un número complex té la forma general a + bi on: Aquesta classe té com a propietats els valors float a i b (nombres reials, que també poden ser double o long double).

Fem només dos constructors, el constructor per defecte i el constructor que rep dos valors. També fem una petita funció Imprimeix.

Després declarem la funció + com a funció sobrecarregada per a la classe complex. Ens fixem que té una sintaxi especial car al nom de la funció (+) li precedeix la paraula operator.

La forma de cridar aquesta funció amb els números complexos z, z1 i z2 és la següent:

z=z1+z2;

La funció + ha de treballar amb els dos complexos z1 i z2. Per tant ha de tenir accés a les seves dades. El C++ fa, que en la sobrecàrrega d'operadors binaris, z2 sigui un paràmetre passat per valor a la funció + i que z1 sigui l'objecte que cridi a aquesta funció i per tant la funció + accedirà a les dades de z1 amb el punter this (recordem que this pot ometre's en escriure la funció).

Així tenim una nova notació, especial per als operadors, de la notació que haguès estat logic fer z=z1.+(z2), peró és més lògica la primera.

4.2.1 L'operador binari =

El C++ proveeix d'un operador = per a l'assignació entre objectes que fa una còpia bit a bit de les dades de l'objecte. Però l'operador = es pot sobrecarregar com qualsevol operador binari quan vulguem que aquest operador faci alguna tasca especial.

Posarem una exemple de com es fa la sobrecàrrega d'aquest operador en el cas senzill de la classe complex i, més endavant, farem una explicació de la sobrecàrrega de l'operador = en el cas de necessitar assignació dinàmica de memòria.

L'operador binari =
class Complex
{
    float a,b;
public:
    Complex(){a=b=0;};
    Complex(float x, float y) {a=x;b=y;}

    void PosaA(float x){a=x;}
    void PosaB(float y){b=y;}
    float TornaA() {return a;}
    float TornaB() {return b;}

    void Imprimeix();
    
    Complex operator+(Complex z2);
    Complex operator=(Complex z2);
};

Complex Complex::operator=(Complex z2)
{
    a = z2.a;
    b = z2.b;

    return *this;
}

main()
{
    clrscr();
    Complex z1(2,5), z2;

    z2 = z1;
    z2.Imprimeix();
}
Veiem que quan cridem z2 = z1 estem passant z1 per valor a la funció de l'operador = i l'objecte que crida la funció és l'objecte z2 amb el punter this. Es a dir que com que l'objecte que treballa és z2 les instruccions

a=z2.a;
b=z2.b;

copien les dades de z1 a z2.

Ara pensem en una instrucció del tipus z3 = z2 = z1; Aquesta instrucció s'executa de dreta a esquerra. Es a dir que primer s'executa z2 = z1 i després z3 = objecte tornat per la primera crida.

Tenim doncs, que com que la crida z2 = z1 torna l'objecte z2, les dades de z2 seran assignades a z3.

4.2.2 Sobrecàrrega de l'operador = en cas d'assignació dinàmica de memòria.

Una de les tasques serà fer una sobrecàrrega en el cas que els objectes a assignar tinguin memòria dinàmica associada. Recordem que havia casos semblants, de pas de paràmetres d'objectes entre funcions amb memòria dinàmica assignada, que es resolien amb els constructors de còpia.

L'operador binari =, assignació dinàmica de memòria
#include <iostream.h>
#include <string.h>
#include <stdlib.h>

class String
{
    char * Cadena;
    int Longitud;
public:
    String(){Cadena = NULL;Longitud=-1;};
    String(char * C);
    String(const String &ObString);

    ~String() {delete Cadena;};

    void Posa(char * Punter);
    void ImprimeixDades();
    char * TornaPunter(){return Cadena;}
    int TornaLongitud(){return Longitud;};

    String operator=(String &ObString);
};

String String::operator=(String &ObString)
{
    String ObTemp(ObString);
    int n = ObString.Longitud;

    delete Cadena;
    Cadena = new char [n+1];
    if (!Cadena)
    {
        cout << "Falta memòria per a String";
        exit(1);
    }
    strcpy(Cadena,ObString.Cadena);
    Longitud = n;

    return ObTemp;
}

main()
{
    String Tira1;
    String Tira2("Hola Pedro");

    Tira1 = Tira2;
    Tira1.ImprimeixDades();
}
Igual que en el constructor de còpia fem una declaració per a la sobrecàrrega de l'operador igual amb una referència a l'objecte passat a la funció, en aquest cas l'objecte String que està situat a la dreta de l'igual:

String operator=(String &ObString);

La funció que es crida amb la instrucció Tira2 = Tira1 és cridat pel objecte Tira2 o el que es trobi a l'esquerra de l'operador =.

Així la funció sobrecarregada = actua sobre Tira2 directament i crea la memòria adequada per posar-hi la mateixa cadena de Tira1.

Veiem que, com aquesta funció a de tornar les mateixes dades per a una possible nova assignació es crea l'objecte ObTemp que és l'objecte tornat.

És necessari fer-ho d'aquesta manera ja que es farà ús del constructor de còpia en el moment de tornar aquest objecte que té assignació dinàmica de memòria.

4.2.3 Sobrecàrrega de l'operador + en cas d'assignació dinàmica de memòria.

L'operador binari +, assignació dinàmica de memòria
#include <iostream.h>
#include <string.h>
#include <stdlib.h>

class String
{
    char * Cadena;
    int Longitud;
public:
    String(){Cadena = NULL;Longitud=-1;};
    String(char * C);
    String(const String &ObString);
    ~String() {delete Cadena;};

    void Posa(char * Punter);
    void ImprimeixDades();
    char * TornaPunter(){return Cadena;}
    int TornaLongitud(){return Longitud;};

    String operator=(String &ObString);
    String operator+(String &ObString);
};

String String::operator+(String &ObString)
{
    String ObTemp;
    int n = Longitud+ObString.Longitud;

    ObTemp.Cadena = new char [n+1];
    if (!ObTemp.Cadena)
    {
        cout << "Falta memòria per a String";
        exit(1);
    }
    strcpy(ObTemp.Cadena,Cadena);
    strcat(ObTemp.Cadena,ObString.Cadena);
    ObTemp.Longitud = n;

    return ObTemp;
}

main()
{
    String Tira1;
    String Tira2("Hola Pedro");
    String Tira3("No se si lo conseguiremos");

    Tira1 = Tira2+Tira3;
    Tira1.ImprimeixDades();
}
Veiem que la declaració de la funció membre operador+ és fa igual que la declaració de l'operador =

String operator+(String &ObString);

Per executar aquesta funció es dona una instrucció com la següent:

Tira1 = Tira2+Tira3;

Per tant l'objecte que crida la funció operador és l'objecte Tira2 i l'objecte Tira3 es passa per referència a l'operador.

Es construeix un objecte provisional ObString que conté les dades i aquest objecte és tornat per la funció.

Per tant aquesta funció necessita el constructor de còpia, i també el destructor adient, per tornar aquest objecte ObString.

Hem de pensar que l'objecte tornat ha d'assignar-se a Tira1 mitjançant l'operador =, per tant la instrucció Tira1 = Tira2+Tira3; necessita que la sobrecàrrega de l'operador = anterior també estigui feta.

4.3 Sobrecàrrega d'operadors monaris.

Els operadors monaris es poden sobrecarregar de forma anàloga a la dels operadors binaris.

Ara es farà la crida a una funció sense paràmetres i per tant, l'objecte al que li apliquem l'operador serà l'objecte que crida la funció que ho reconeix amb el punter this.

4.3.1 Els operadors increment i decrement.

Els operadors increment i decrement són els operadors ++ i --. Recordem que la seva tasca és incrementar o decrementar en 1 l'objecte al que els apliquem.

També hem de recordar que existeixen dues formes de fer ús d'aquest operadors segons la seva precedència:

Preincrement i predecrement++Ob, --Ob
Postincrement i postdecrement Ob++, Ob--

Això fa que haguem d'escriure dues funcions diferents per a cadascun d'ells.

Encara que no té sentit fer la sobrecàrrega d'aquests operadors en els números complexos, la farem per tal d'il·lustrar la manera de fer-la.

L'operador monari increment ++
class Complex
{
    float a,b;
public:
    Complex(){a=b=0;};
    Complex(float x, float y) {a=x;b=y;}

    void PosaA(float x){a=x;}
    void PosaB(float y){b=y;}
    float TornaA() {return a;}
    float TornaB() {return b;}

    void Imprimeix();

    //Sobrecàrrega d'operadors binaris
    Complex operator+(Complex z2);
    Complex operator-(Complex z2);
    Complex operator=(Complex z2);

    //Sobrecàrrega d'operadors monaris
    Complex operator++();       //Operador preincrement
    Complex operator++(int x);  //Operador postincrement
    Complex operator-();
};

Complex Complex::operator++()
{
    a++;
    b++;
    return *this;
}

Complex Complex::operator++(int x)
{
    a++;
    b++;
    return *this;
}
Segons veiem tenim dues declaracions per a l'operador increment ++. La primera és la declaració de la sobrecàrrega per a l'operador preincrement:

Complex operator++();

La segona és la declaració per a l'operador postincrement, i ens fixem que aquesta declaració porta un paràmetre (int x), paràmetre mut, del que només fa ús el compilador per distingir aquesta funció de l'altra.

Complex operator++(int x)

Veiem que la implementació d'aquestes funcions és la mateixa i que incrementa en 1 les propietats de l'objecte que la crida.

Recordem que a les instruccions següents l'objecte que crida la funció és l'objecte Z.

Complex Z(3,6);
Z++;   //Crida a Postincrement
++Z;   //Crida a Preincrement
També ens fixem que a la definició de les funcions apareix la instrucció >return *this molt important per a que les instruccions com Z2 = ++Z; puguin executar-se. Amb aquest return aconseguirem que s'incrementi Z i també que el resultat sigui assignat a Z2.

4.3.2 L'operador monari -

L'operador menys - és un operador binari i un operador monari. Quan actua com a operador binari calcula la diferència entre dos objectes. Quan actua com a operador monari canvia el signe de l'objecte que ho crida.

A la declaració de la classe Complex d'amunt veiem que hi ha les dues declaracions per a l'operador menys, la declaració com a operador binari i com a operador monari.

La implementació de la funció operador monari es fa a continuació:

L'operador monari -
Complex Complex::operator-()
{
    a=-a;
    b=-b;
    return *this;
}
La implementació d'aquest operador canvia el signe de les propietats del complex que ho crida.

Ens fixem que també torna l'adreça de l'objecte per a que la instrucció que fa la crida pugui fer-ne ús.

Podem fer ús doncs de la forma següent:

Complex Z(2,5);
-Z;
Z2 = -Z;

4.4 Sobrecàrrega d'operadors lògics i relacionals.

Els operadors lògics i els relacionals són operadors que també es poden sobrecarregar.

Així en el nostre exemple de números complexos ens interessarà saber si dos d'ells són iguals o no. Tenim doncs que la expressió z1==z2 ens ha de tornar cert o fals, que serà un enter amb valor 0 o 1. És el primer operador que torna un objecte diferent dels objectes que ho criden.

L'operador == i l'operador &&
class Complex
{
     float a,b;
public:
     Complex(){a=b=0;};
     Complex(float x, float y) {a=x;b=y;}

     void PosaA(float x){a=x;}
     void PosaB(float y){b=y;}
     float TornaA() {return a;}
     float TornaB() {return b;}

     void Imprimeix();

     //Sobrec…rrega d'operadors binaris
     Complex operator+(Complex z2);
     Complex operator-(Complex z2);
     Complex operator=(Complex z2);

     //Sobrec…rrega d'operadors monaris
     Complex operator++();       //Operador preincrement
     Complex operator++(int x);  //Operador postincrement
     Complex operator-();

     //Sobrec…rrega d'operadors relacionals i lògics
     int operator==(Complex z2);
     int operator&&(Complex z2);
};

int Complex::operator==(Complex z2)
{
     return ((a==z2.a)&&(b==z2.b));
}

int Complex::operator&&(Complex z2)
{
     return ((a&&z2.a)&&(b&&z2.b));
}

main()
{
     clrscr();
     Complex z1(2,5), z2(2,5);

     cout << (z1==z2);
}

4.5 Sobrecàrrega d'operadors amb funcions amigues.

Totes les sobrecàrregues d'operadors es poden fer amb funcions amigues. La notació general per fer una d'aquestes sobrecàrregues és

friend Class1 operator x(Class2 ob1, Class3 ob2);

on Class1 Class2 i Class3 poden ser totes iguals o totes diferents.

Aquesta declaració serà per a una funció amiga de Class2 o de Class3. Fins i tot poden ser un tipus de dades primari, com un enter, etc.

És evident que una de les principals utilitats d'aquest tipus de sobrecàrrega serà poder escriure a l'esquerra d'un operador un objecte que no sigui de la classe a la que pertany l'operador sobrecarregat.

Ens fixem que ara els dos operands es passen per valor a l'operador i no és l'objecte de l'esquerra el que crida a la funció amb l'us del punter this.

4.5.1 L'operador + amb funcions amigues. Classe Complex.

Quan fem la suma de dos números complexos no pensem en l'ordre que escrivim els operands ja que té la suma de complexos té la propietat commutativa.

També és normal fer sumes de enters o reials amb complexos.

Així tenim que són molt naturals les tres expressions següents:
z = z1 + z2;
z = z1 + 4;
z = 4 + z2;
Això planteja tres problemes que es resolen a continuació:

L'operador + amb funcions amigues. Classe Complex
class Complex
{
    float a,b;
public:
    Complex(){a=b=0;};
    Complex(float x, float y) {a=x;b=y;}

    //Sobrecàrrega d'operadors binaris
    Complex operator+(Complex z2);
    Complex operator+(float n);

    //Sobrecàrrega amb funcions amigues
    friend Complex operator+(int n, Complex z2);
};
Complex Complex::operator+(Complex z2)
{
    Complex z;
    z.a = a + z2.a;
    z.b = b + z2.b;

    return z;
}
A la esquerra tenim l'operador + per tal de fer crides del tipus z=z1+z2 com a funció membre de la classe complex.
Podíem haver declarat la sobrecàrrega am funcions amigues de la manera següent
friend Complex operator+(Complex z1,Complex z2);
i implementar aquest operador amb la funció
Complex operator+(Complex z1,Complex z2)
{
    Complex z;
    z.a=z1.a+z2.a;
    z.b=z1.b+z2.b;
    return z;
}
Hem de dir que en aquest cas no és necessari fer ús de les funcions amigues ja que amb una funció membre tenim resolt el problema d'una manera molt més lògica.
Complex Complex::operator+(float n)
{
    Complex z;
    z.a = a + n;
    z.b = b;
    return z;
}
Aquesta sobrecàrrega correspon a una crida com la de la funció main següent:
main()
{
    Complex z1(3,3),z;
    z =  z1 + 4;
    z.Imprimeix();
}
Veiem que la crida z = z1 + 4 es fa mitjançant dos objectes de tipus diferents. Lobjecte que fa la crida, z1, és un Complex i per tant l'operador + serà una funció membre que rep com a paràmetre l'objecte que està a la dreta del signe mas en el moment de la crida, en aquest cas el número 4.
Complex operator+(int n, Complex z2)
{
    Complex z=z2;
    z.a += n;
    return z;
}
Ens falta fer una crida del tipus z = 4 + z1; com a la funció main següent
main()
{
    Complex z1(2,8),Z;
    z =  4 + z1;
    z.Imprimeix();
}
Tenim doncs que ara no hi ha cap objecte que cridi a la funció més. Aixa vol dir que hem de fer una funció amiga de la classe Complex que rebi els dos paràmetres de tipus float i Complex, i en aquest ordre.

Aquesta funció amiga serà la sobrecàrrega de l'operador + de la classe Complex per a aquest cas.

4.5.2 L'operador + amb funcions amigues. Classe String.

Amb la classe String es planteja un problema similar al de la classe Complex quan volem concatenar amb l'operador + diferents tipus d'objectes. Així serà normal fer crides del tipus següent:
String Tira;
String Tira1("Hola Maria");
String Tira2("Hola Pedro");

Tira = Tira1 + Tira2;
Tira = Tira1 +  " CARAMBA";
Tira = "CARAMBA " + Tira2;
Veiem que la primera concatena dos objectes de la classe String, la segona concatena un String amb un vector de caràcters i la tercera concatena un vector de caràcters amb un objecte de tipus String.

Les tres crides necessiten una sobrecàrrega diferent de l'operador +, que es resolen de manera similar a la de la classe Complex.

Hem de dir que ara tenim assignació dinàmica de memòria i per tant el pas d'objectes als operadors sobrecarregats es fa mitjançant referències.

L'operador + amb funcions amigues. Classe String.
class String
{
    char * Cadena;
    int Longitud;
public:
    String(){Cadena = NULL;Longitud=-1;};
    String(char * C);
    String(const String &ObString);
    ~String() {delete Cadena;};

    void Posa(char * Punter);
    void ImprimeixDades();
    char * TornaPunter(){return Cadena;}
    int TornaLongitud(){return Longitud;};

    String operator=(String &ObString);

    String operator+(String &ObString);
    String operator+(char * C);
    friend String operator+(char * C,String &ObString);
};

String String::operator+(String &ObString)
{
    String ObTemp;
    int n = Longitud+ObString.Longitud;

    ObTemp.Cadena = new char [n+1];
    if (!ObTemp.Cadena)
    {
        cout << "Falta mem•ria per a String";
        exit(1);
    }
    strcpy(ObTemp.Cadena,Cadena);
    strcat(ObTemp.Cadena,ObString.Cadena);
    ObTemp.Longitud = n;

    return ObTemp;
}

String String::operator+(char * C)
{
    String ObTemp;
    int n = Longitud+strlen(C);

    ObTemp.Cadena = new char [n+1];
    if (!ObTemp.Cadena)
    {
        cout << "Falta mem•ria per a String";
        exit(1);
    }
    strcpy(ObTemp.Cadena,Cadena);
    strcat(ObTemp.Cadena,C);
    ObTemp.Longitud = n;

    return ObTemp;
}

String operator+(char * C,String &ObString)
{
    String ObTemp;
    int n = strlen(C)+ObString.Longitud;

    ObTemp.Cadena = new char [n+1];
    if (!ObTemp.Cadena)
    {
        cout << "Falta memòria per a String";
        exit(1);
    }
    strcpy(ObTemp.Cadena,C);
    strcat(ObTemp.Cadena,ObString.Cadena);
    ObTemp.Longitud = n;

    return ObTemp;
}

4.6 Sobrecàrrega de l'operador [ ].

Els símbols [ ] utilitzats per escriure els índex d'un vector es consideren un operador binari que es pot sobrecarregar.

Per entendre bé aquesta sobrecàrrega farem un programa com el del quadre següent en el que definim un objecte que es diu VectorSegur.

Tenim les dades d'aquest objecte són un vector v de nombres enters pel que reservem 10 enters de dimensió.

Si ens fixem a la funció main hi ha declarat el vector vDades del tipus VectorSegur. Seria bo poder fer les dues instruccions següents:
cout << vDades[5];
vDades[3]=88;

Podrem escriure aquestes instruccions si definim una funció membre de la classe VectorSegur que sobrecarregui l'operador [ ].

La funció es declara en la forma següent:

int &operator[](int i);

La instrucció cout << vDades[5] fa una crida a la funció operator[ ](5) que com veiem a la definició de la funció torna el valor que es troba a la posició 5 del vector v de l'objecte vDades.

Cal dir que l'índex 2 es passa com a paràmetre a la funció operator[ ] i que l'objecte que fa la crida a aquesta funció és l'objecte vDades i que per tant és accessible des de la funció per mitjà del punter this.

L'altra forma de cridar la funció és vDades[3]=88; Per poder fer ús d'aquesta forma es fa la declaració de la sobrecàrrega de manera que la funció operator [ ] torni una referència i d'aquesta manera es fa l'assignació.

Es clar que quan farem aquesta funció posarem les instruccions adients pel control de l'índex. Així la instrucció cout << vDades[25]; ens donarà una errada per pantalla.

Nota: El constructor que hem posat és solament un constructor per a fer l'exercici. Possiblement no sigui el més adequat en el cas que programéssim un objecte d'aquests tipus.

L'operador [ ]
#include <iostream.h>
#include <stdlib.h>

const int DIMENSIO = 10;

class VectorSegur
{
    int v[DIMENSIO];
public:
    VectorSegur();
    int &operator[](int i);
};

VectorSegur::VectorSegur()
{
    for (int i=0; i<DIMENSIO; i++) v[i]=i;
}

int &VectorSegur::operator[](int i)
{
    if ( (i<0) || (i>DIMENSIO-1) )
    {
        cout << "Índex " << i <<" del vector fora de dimensió";
        exit(1);
    }
    return v[i];
}

main()
{
    VectorSegur vDades;

    cout << vDades[5];
    vDades[3]=88;
    cout << vDades[3];
    cout << vDades[25];
}

4.7 Sobrecàrrega dels operadors new i delete.

Els operadors new i delete també es consideren com a operadors que es poden sobrecarregar. Pot ser necessari que la funció new hagi de fer alguna tasca especial en el moment que realitzarem la sobrecàrrega.

A l'exemple següent tenim una classe anomenada DadesPersonals que fa aquesta sobrecàrrega. La tasca especial que ens hem inventat per a aquesta sobrecàrrega serà omplir amb lletres a els bytes dels dos camps de la classe.

Observem la declaració de la classe

void * operator new(size_t Mida); //Declaració

i també el seu ús

void * pTemporal = malloc(Mida); //Instrucció

Quan fem des de programa la crida a la funció new sobrecarregada amb la instrucció anterior, la funció new rep la mida en bytes de l'objecte que es declara. Fa la petició de memòria (malloc) i cas que la obtingui posa les lletres a. Després retorna el punter a la memòria assignada.

Hem de dir que una vegada executada la funció new s'executa el constructor i per tant tindrem inicialitzat l'objecte com en una declaració normal.

Es clar que si fem una funció new hem de fer la sobrecàrrega de la funció delete. Aquesta funció no pot tenir més d'una sobrecàrrega, encara que la funció ni si que en pot tenir totes les sobrecàrregues necessàries.

Els operadors new i delete
#include <iostream.h>
#include <stdlib.h>

class DadesPersonals
{
    char Nom[45];
    char NIF[12];

public:
    DadesPersonals() {Nom[0]='\0';NIF[0]='\0';}
    void * operator new(size_t Mida);
    void operator delete(void * punter){free(punter);}
};

void * DadesPersonals::operator new(size_t Mida)
{
    void * pTemporal = malloc(Mida);
    if (!pTemporal)
    {
        cout << "Falta mem•ria per a DadesPersonals";
        exit(1);
    }

    memset(pTemporal,'a', Mida);
    return pTemporal;
}

main()
{
    DadesPersonals * Fitxa1 = new DadesPersonals; //Operador new creat
    DadesPersonals * Agenda = new DadesPersonals[200]; //Funció new del C++
}

4.8 Conversió de tipus de definits pel usuri.

Ja sabem que quan assignem variables numèriques de diferents tipus o quan fem operacions entre variables numèriques, també de diferents tipus el C++ porta unes funcions que converteixen, només per aquella operació o assignació, les dades als tipus necessaris.

Aquestes conversions es fan mitjançant unes regles de promoció entre tipus molt específiques.

També existeix la conversió forçada de tipus, (float) x converteix la variable entera x a float, que en anglès es diu fer un cast.

Ara pensem que seria bo definir aquestes conversions pels nostres objectes. Això es fa mitjançant una nova sobrecàrrega d'operadors en aquest cas els operadors de conversió int, float, ...

Existeixen dues formes de crear aquestes conversions de tipus:

  1. Redefinint els operadors int, float ... coma a funció membre de la classe. Això convertirà objectes de la classe que estem escrivint a d'altres tipus.


  2. Conversió de tipus 1
    #include <iostream.h>
    
    class Punt
    {
        int x,y;
    public:
        Punt(int x=0, int y=0)
        {
            this->x=x;
            this->y=y;
        }
        operator int() {return x;}
    };
    
    void Func(int x)
    {
        cout << x;
    }
    
    main()
    {
        Punt A(5,10);
        int x, y;
        x = A;
        y = x + A;
        Func(A);
    }
    
    Veiem la classe Punt que té una nova funció membre operator int() que és la sobrecàrrega d'aquest operador.

    En aquest cas estem definint la forma de convertir un punt a enter fent que sigui la abscissa el resultat de la conversió.

    Així la instrucció x = A de la funció main assigna el valor 5 a x.

    La instrucció y = x + A converteix A a enter amb valor 5 i ho suma a x. Resultat y val 10.

    Quan passem el punt A com a paràmetre a una funció que ha de rebre un enter en lloc d'un punt tenim que es fa us de la conversió definida i la crida Func(A) fa que a la funció arribi un enter de valor 5.


  3. Mitjançant un constructor que té només un paràmetre. Així fem que un altre objecte promocioni el seu tipus a element de la classe que té el constructor.

    Conversió de tipus 2
    #include <iostream.h>
    
    class Punt
    {
        int x,y;
    public:
        Punt(int x=0, int y=0)
        {
            this->x=x;
            this->y=y;
            cout << "Executat constructor pel ";
            cout << "punt: " << x << "," << y << endl;
        }
    
        friend Punt operator+(Punt A,Punt B)
        {
            Punt S;
            S.x = A.x + B.x;
            S.y = B.y + B.y;
            return S;
        }
    
        void Imprimeix()
        {
            cout << "Punt: " << x << "," << y << endl;
        }
    };
    
    main()
    {
        Punt A, B(5,7);
        A = B + 6;
        A.Imprimeix();
        A = 6 + B;
        B.Imprimeix();
    }
    
    Ara a la classe Punt l'hem dotada d'una sobrecàrrega de l'operador + amb una funció amiga.

    Si examinem les instruccions de la funció main veiem que s'executa el constructor pels objectes A, B en la primera línia.

    En la segona instrucció A = B + 6; primer es fa una conversió del numero enter 6 a punt, abans d'executar la suma. Aquesta conversió es fa executant el constructor pel punt 6,0.

    De forma similar s'executa també el constructor quan fem la instrucció A = 6 + B.

    Així tenim que en pantalla apareix:

    Resultat per pantalla


    Es clar que també s'executa dues vegades el constructor pel punt S de la funció operator+ ja que la cridem dues vegades.


Es poden escriure més d'una sobrecàrrega d'aquest tipus, però hem de tenir cura de nom provocar errades d'ambigüitat.

4.9 Exercicis.

  1. En la classe Complex de l'apartat 4.2 tenim fetes les sobrecàrregues de l'operador +. Fes la sobrecàrrega pels operadors -, * i /.

    Recorda que si tenim dos complexos z1=a+bi, z2=c+di llavors

  2. Afegeix a la teva classe Complex la sobrecàrrega de l'operador = de l'apartat 4.2.1.
  3. Tenim una classe VectorEnters que té com a dades un punter a enter i un enter per a la longitud del vector.

    També té un constructor que rep el número d'elements del vector, una funció per posar una dada en una posició determinada i una altra que ens retorna la dada d'una posició dada.

    Fes la sobrecàrrega de l'operador + de manera que ens sumi dos vectors si tenen la mateixa longitud i no faci res en cas contrari.

    Classe VectorEnters
    #include <iostream.h>
    #include <stdlib.h>
    
    class VectorEnters
    {
        int * V;
        int Longitud;
    public:
        VectorEnters(int L);
        int Posa(int Dada, int i);
        int Extreu(int i);
    	~VectorEnters(){delete V;}
    };
    
    int VectorEnters::Posa(int Dada, int i)
    {
        if ((0<=i) && (i<Longitud))
        {
            V[i]=Dada;
            return 1;
        }
        else return 0;
    }
    
    int VectorEnters::Extreu(int i)
    {
        if ((0<=i) && (i<Longitud)) return V[i];
        else return NULL;
    }
    
    VectorEnters::VectorEnters(int L)
    {
        V = new int[L];
        if (!V) exit(1);
        for(int i=0; i<L; i++) V[i]=0;
        Longitud=L;
    }
    
  4. Fes la sobrecàrrega de l'operador d'assignació = per la classe anterior VectorEnters.
  5. Fes la sobrecàrrega dels operadors monaris increment i decrement per a la classe VectorEnters. Aquests operadors han de incrementar o decrementar en 1 tots els elements del vector.
  6. Fes la sobrecàrrega de l'operador monari - per a la teva classe VectorEnters. Aquesta operació canvia el signe de tots els elements del vector.
  7. Si vols multiplicar un número per un vector és lògic multiplicar tots els elements del vector per aquest número.

    Fes dues sobrecàrregus amb funcions amigues de la teva classe VectorEnters per tal que puguis multiplicar un enter per un vector i un vector per un enter.
  8. Fes la sobrecàrrega del l'operador [ ] per a la teva classe VectorSegur.
  9. Fes la sobrecàrrega de les funcions new i delete per a la teva classe VectorEnters.