domingo, 7 de septiembre de 2014

Apuntes de C++ : Arrays y punteros

Continuando con los resúmenes de mis apuntes del libro "C++ guia de autoenseñanza" de Herbert Schildt, en este post trataré los Arrays, los punteros y las referencias.

Cómo crear un array

int lista[20];
Esta línea crea un array de 20 elementos de tipo entero.

Para crear un array de objetos de una clase concreta, se emplea la misma sintaxis:

class Nave_espacial{
    int r;
    int g;
    int b;
public:
    Nave_espacial(int r, int g, int b);
    void setColor(int r, int g, int b);
};

void Nave_espacial::setColor(int r, int g, int b){...}

main(){
    Nave_espacial naves[20];
    naves[0].setColor(255, 0 , 0);  
}

Creamos un array para 20 objetos de tipo Nave_espacial y modificamos el color de la primera nave de la lista.

Nave_espacial naves[2] = { Nave_espacial(1,2,3) ,  Nave_espacial(4,5,6) };

Este tipo de inicialización del array, permite llamar a las funciones constructoras de cada uno de los objetos del array pasándoles los parámetros que sean necesarios.

Punteros a objetos


class Muestra{
     int id, valor;
public:
     Muestra(int _id, int _valor){ id = _id; valor = _valor; }
     int getId(){ return id; }
     int getValor(){ return valor; }
};

main(){
     int i;
     Muestra muestras[4] = {
          Muestra(0, 20),
          Muestra(1, 23),
          Muestra(2, 21),
          Muestra(3, 23)
     };
     Muestra *m;
     m = muestras;

     for(i=0; i<4; i++){
          cout << m->getValor() << " , ";
          m++; 
     }
}

Creamos un puntero m para apuntar a objetos de la clase Muestra, y cogemos la dirección del array.
Cuando se incrementa el puntero, este apunta al próximo objeto del array.
Para emplear las funciones miembro de los objetos se emplea el operador ->.

Uso de new y delete

En C la asignación de memoria dinámica se realizaba mediante malloc() y la memoria asignada se liberaba utilizando free(). Estas funciones estándar siguen siendo válidas en C++.
C++ proporciona un método más seguro para asignar memoria y liberar memoria, new y delete.

Muestra *m;
m = new Muestra(2, 40);
delete m;

Creamos un puntero para objetos de una clase en concreto.
new nos devuelve un puntero a la memoria asignada dinámicamente, que es lo suficientemente grande como para contener el objeto de la clase.
delete devuelve la memoria cuando ya no se necesita.

Si no hay memoria disponible para asignar, new devuelve un puntero nulo.
Solamente debe llamarse delete con punteros obtenidos previamente mediante new. En caso contrario, se bloquea el sistema de asignación, interrumpiéndose el programa.

new asigna automáticamente la memoria necesaria para albergar un objeto del tipo especificado, así que no es necesario emplear sizeof para calcular el número de bytes requeridos.
Tanto new como delete pueden sobrecargarse permitiendo que el programador cree a su medida su propio sistema de asignación de memoria.

m = new Muestra[10];

El puntero m apuntará a la primera posición de un array de 10 objetos de clase Muestra.
Para eliminar un array asignado dinámicamente, se emplea la siguiente instrucción:

delete []m;

Así el compilador llama a la función destructora para cada elemento del array, y m no se libera en múltiples ocasiones, se libera sólo una vez.

Referencias: pasar parámetros por referencia a una función

K = 10;
void sumarK(int *n);

void sumarK(int *n){
    *n = *n + K;
}

main(){
    int i = 5;
    sumarK(&i);
    cout << "Valor de i = " << i;
}

Por defecto, C++ pasa los parámetros por valor, lo que significa que realiza una copia de los mismos y que si alteramos su valor dentro de las funciones, la variable original que se ha pasado a la función no se ve afectada.
En este ejemplo la función sumarK(int *n) recibe como parámetro un puntero a un entero, lo que significa que cuando realizamos la llamada debemos pasar como parámetro la dirección de la variable entera, para coger dicha dirección o referencia, empleamos el operador &i.

Al hacer esto la función f trabaja directamente con la variable original, ya que altera el valor contenido en la dirección apuntada por el puntero.

En C la definición de paso por referencia sólo puede hacerse de la anterior forma, pero en C++ puede automatizarse para simplificarse el paso por referencia:


K = 10;
void sumarK(int &n);

void sumarK(int &n){
    n = n + K;
}

main(){
    int i = 5;
    sumarK(i);
    cout << "Valor de i = " << i;
}

Simplemente es necesario añadir el operador de dirección al declarar la función, &n.
Hecho esto el compilador ya sabe que realmente cuando se haga la llamada sumarK(i) lo que está pasando como parámetro es la dirección de la variable i.
Y del mismo modo tampoco hace falta indicar dentro de la implementación de la función, que n es un puntero.

Importante: no se pueden modificar las direcciones a las que apunta una referencia.

Pasar parámetros por referencia es la mejor forma de evitar los problemas asociados con la copia de argumentos, que al llamar a sus funciones destructoras pueden perjudicar partes del programa.

Una referencia no es un puntero. Cuando se pasa un objeto por referencia su operador de acceso a atributos y funciones miembro, es el punto "." y no la flecha "->".

Devolución de referencias

Una función puede devolver una referencia. Y podemos asignar directamente valores a la dirección de dicha referencia, es decir, una función puede estar en el lado izquierdo de una asignación.
El siguiente ejemplo es una locura, para los que nunca han trabajado con punteros o están muy acostumbrados a lenguajes como Java.
Vamos a crear una clase Array, para almacenar arrays de enteros.
Y en la función main vamos a hacer que las posiciones 2 y 3 del array contengan los valores 4 y 30:


class Array(){
     int size;
     int *set;
public:
     Array(int _size);
     int &put(int i);
     int get(int i);
};

Array::Array(int _size){
     set = new int[_size];
     size = _size;
}

int &Array::put(int i){
     if(!(i<0 || i>=size)) return set[i]; //devuelve una referencia a set[i]
}

int Array::get(int i){
     if(!(i<0 || i>=size)) return set[i]; //devuelve el entero contenido en set[i]
}

main(){
     Array a(20);
     a.put(2) = 4;
     a.put(3) = 30;
}     

Para que una función devuelva una referencia debemos poner el operador & delante del nombre de función.
Una vez hemos indicado esto automáticamente el compilador sabe que debe devolver la dirección de set[i] y no el valor que contiene.

Una referencia no puede ser referenciada.
No pueden crearse arrays de referencias.
Las referencias son similares a los punteros, pero no son punteros.

Hasta aquí la segunda parte de los apuntes de C++.
Espero que estos resúmenes os sirvan de "referencia" y os sean de utilidad!
Un saludo a todos!!

No hay comentarios:

Publicar un comentario