Daniel Garcia

sábado, julio 17, 2010

SOBRECARGA DE OPERADORES EN C

 

Sobrecarga de operadores.

La sobrecarga de operadores permite redefinir ciertos operadores, como '+' y '-', para usarlos con las clases que hemos definido. Se llama sobrecarga de operadores porque estamos reutilizando el mismo operador con un número de usos diferentes, y el compilador decide cómo usar ese operador dependiendo sobre qué opera.

La sobrecarga de operadores sólo se puede utilizar con clases, no se pueden redefinir los operadores para los tipos simples predefinidos.

Los operadores lógicos && y || pueden ser sobrecargados para las clases definidas por el programador, pero no funcionarán como operadores de short circuit. Todos los miembros de la construcción lógica serán evaluados sin ning´n problema en lo que se refiere a la salida. Naturalmente los operadores lógicos predefinidos continuarán siendo operadores de short circuit como era de esperar, pero no los sobrecargados.

Los siguientes operadores no pueden ser sobrecargados:

    • El operador ternario ?.
    • El operador de acceso a una clase o estructura : .
    • El operador scope ::

Sobrecarga de operadores binarios.

Un operador binario es el que tiene dos operandos.

Ejemplo donde sobrecargamos el operador + para, a partir de dos cajas, crear otra de dimensiones igual a la suma de las dimensiones de las cajas dadas:

#include <iostream.h>

class Caja {

double longitud;

double anchura, altura;

public:

void set(int l, int w, int h) {longitud = l; anchura = w; altura = h;}

double volumen(void) {return longitud * anchura * altura;}

Caja operator+(Caja a);

};

Caja operator+(Caja a) {

Caja temp;

temp.longitud = longitud + a.longitud;

temp.anchura = anchura + a.anchura;

temp.altura = altura + a.altura;

return temp;

}

main() {

Caja pequeña, mediana, grande;

Caja temp;

pequeña.set(2, 4, 5);

mediana.set(5, 6, 8);

grande.set(8, 10, 12);

cout << 'El volumen es ' << pequeña.volumen() << '\n';

cout << 'El volumen es ' << mediana.volumen() << '\n';

cout << 'El volumen es ' << grande.volumen() << '\n';

temp = pequeña + mediana;

cout << 'El nuevo volumen es ' << temp.volumen() << '\n';

}

El resultado de la ejecución será:

El volumen es 40.

El volumen es 240.

El volumen es 960.

El volumen es 840.

Observamos que :

· El operador se llama desde la clase que precede al operador, y el objeto que le sigue es enviado como parámetro. Esto significa que el parámetro (a) es mediana.

· El operador puede acceder a los miembros privados del parámetro que es enviado. En el ejemplo, accede a a.longitud, a.anchura, a.altura.

Operadores amigos

#include <iostream.h>

class Caja {

double longitud;

double anchura, altura;

public:

void set(int l, int w, int h) {longitud = l; anchura = w; altura = h;}

int volumen(void) {return longitud * anchura * altura;}

friend Caja operator+(Caja a, Caja b); // Add two Cajas

friend Caja operator+(int a, Caja b); // Add a constant to a Caja

friend Caja operator*(int a, Caja b); // Multiply a Caja by a constant

};

Caja operator+(Caja a, Caja b) {

Caja temp;

temp.longitud = a.longitud + b.longitud;

temp.anchura = a.anchura + b.anchura;

temp.altura = a.altura + b.altura;

return temp;

}

Caja operator+(int a, Caja b) { // Add a constant to a Caja

Caja temp;

temp.longitud = a + b.longitud;

temp.anchura = a + b.anchura;

temp.altura = a + b.altura;

return temp;

}

Caja operator*(int a, Caja b) { // Multiply a Caja by a constant

Caja temp;

temp.longitud = a * b.longitud;

temp.anchura = a * b.anchura;

temp.altura = a * b.altura;

return temp;

}

main() {

Caja pequeña, mediana, grande;

Caja temp;

pequeña.set(2, 4, 5);

mediana.set(5, 6, 8);

grande.set(8, 10, 12);

cout << 'El volumen es ' << pequeña.volumen() << '\n';

cout << 'El volumen es ' << mediana.volumen() << '\n';

cout << 'El volumen es ' << grande.volumen() << '\n';

temp = pequeña + mediana;

cout << 'El nuevo volumen es ' << temp.volumen() << '\n';

temp = 10 + pequeña;

cout << 'El nuevo volumen es ' << temp.volumen() << '\n';

temp = 4 * grande;

cout << 'El nuevo volumen es ' << temp.volumen() << '\n';

}

El resultado de la ejecución es:

El volumen es 40

El volumen es 240.

El volumen es 960.

El nuevo volumen es 840.

El nuevo volumen es 2520.

El nuevo volumen es 61440.

· Hemos sobrecargado los operadores + y *, declarándolos como funciones amigas, de forma que podemos utilizar funciones con dos parámetros. Si no los hubiésemos utilizado la construcción friend, la función sería parte de uno de los objetos y ese objeto sería el objeto al que se le pasa el mensaje.

· No hay límite superior para el número de operadores o de funciones sobrecargadas. Se puede definir cualquier número de operadores sobrecargados, siempre que difieran en la lista de argumentos.

· Se observa que la implementación de las funciones amigas no es realmente parte de la clase porque el nombre de la clase no precede al de la función.

Sobrecarga de operadores unarios.

Un operador unario sólo tiene un operando. Ejemplos de operadores unitarios son ++ y --.

#include <iostream.h>

class Burrito {

private:

int amtbeef, amtbean;

public:

Burrito(int beef, int bean) {

amtbeef = beef;

amtbean = bean;

}

Burrito operator ++() {

amtbeef++; amtbean++;

return Burrito(amtbeef, amtbean);

}

int getBeef() { return amtbeef; }

};

main() {

Burrito b1(5,10);

cout << 'Burrito #1 has' << b1.getBeef() << 'ounces of beef.' << '\n';

b1++;

cout << 'Now Burrito #1 has' << b1.getBeef() << 'ounces of beef.'<< '\n';

}

Vemos que sobrecargar un operador unario es muy similar a la forma en que se hace para uno binario. De hecho, la única diferencia real es que ahora no se pasa ningún parámetro al operador, ya que ahora sólo hay un operando, que es el objeto cuyo operador se usa.

Hay si embargo una pequeña cuestión que debe tenerse en cuenta cuando se sobrecargan los operadores ++ y --. Sabemos que b = ++a; es diferente a b=a++;. La primera expresión equivale a a = a +1; b = a;, mientras que la segunda es : b = a; a = a +1;. Sin embargo, cuando se sobrecarga el operador ++ (o el --) no se puede hacer distinción entre estas dos situaciones. Los dos usos del operador tienen el mismo efecto.

Descomposicion en modulos EN C

 

Vamos a separar nuestro ejemplo en tres ficheros:

· Caja.h, que contiene la definición de la clase

· Caja.cxx, que contiene la implementación de los métodos declarados en la clase Caja

· main.cxx, la función principal.

Fichero Caja.h:

class Caja {

double longitud, anchura, altura;

public:

Caja (double dim1, double dim2, double dim3);

Caja (void);

void set (double nuevaLongitud, double nuevaAnchura, double nuevaAltura);

void print (void);

double getLongitud (void);

double volumen (void) {return longitud * anchura * altura};

~Caja (void);

};

En Caja.h hemos incluido sólo la definición de la clase. No se dan detalles sobre las diversas funciones. Es decir, tenemos la definición completa de cómo utilizar una clase sin detalles de implementación. Este fichero no puede ser compilado ni ejecutado.

Fichero Caja.cpp:

# include 'Caja.h'

Caja :: Caja (double dim1, double dim2, double dim3) {

longitud = dim1;

anchura = dim2;

altura = dim3;

};

Caja :: Caja (void) {

longitud = 8;

anchura = 8;

altura = 8;

};

void Caja :: set (double nuevaLongitud,nuevaLongitud, double nuevaAnchura, double nuevaAltura) {

longitud = nuevaLongitud;

anchura = nuevaAnchura;

altura = nuevaAltura;

}

void Caja :: print (void) {

cout << 'longitud = ' << longitud <<'\n';

cout << 'anchura = ' << anchura <<'\n';

cout << 'altura = ' << altura <<'\n';

};

double Caja:: getLongitud (void) {

return longitud;

};

Caja::~Caja (void) {

longitud = 0; anchura = 0; altura = 0;

}

Vemos que:

· Se incluye el fichero cabecera Caja.h, que contiene los prototipos de las funciones.

· Caja.cpp contiene la implementación de las funciones declaradas en la clase Caja.

Este fichero puede ser compilado, pero no ejecutado, porque no tiene función main. Cuando sea compilado, el código objeto será almacenado, y estará disponible para usarlo por otros programas.

La separación de la definición y la implementación es un paso importante hacia la ingeniería de software. El fichero que contiene la definición es todo lo que el usuario necesita para utilizar la clase en un programa. No necesita conocer la implementación real de las funciones.

Fichero main.cpp:

#include <iostream.h>

#include 'Caja.h'

main () {

Caja pequeña(5, 4, 10), mediana (10, 6, 20), grande(20, 10, 30);

cout << 'El volumen de la caja grande es ' << grande.volumen() << '\n';

}

 

ARRAY DE OBJETOS EN C

Un array de objetos.

De la misma forma que declaramos vectores cuyos elementos son los tipos definidos en C++ (int, float, double, ...) podemos definir vectores formados por objetos definidos por el usuario. Veamos un ejemplo, partiendo de la clase Caja.

# include <iostream.h>

# include ' Caja.h'

main () {

Caja pequeña(5, 4, 10), mediana(10, 6, 20), grande, varias [4];

grande.set (20, 10, 30);

for (int indice = 1; indice < 4; indice ++)

varias [indice].set(indice + 10, 10, 10);

cout << ' El volumen de la caja pequeña es ' << pequeña.volumen() <<'\n';

cout << ' El volumen de la caja mediana es ' << mediana.volumen() <<'\n';

cout << ' El volumen de la caja grande es ' << grande.volumen() <<'\n';

for (indice = 0; indice < 4; indice ++)

cout << ' El volumen del array de cajas es' << varias[indice].volumen() <<'\n';

}

El resultado de la ejecución de este programa será:

El volumen de la caja pequeña es 200

El volumen de la caja mediana es 1200

El volumen de la caja grande es 6000

El volumen del array de cajas es 512

El volumen del array de cajas es 1100

El volumen del array de cajas es 1200

El volumen del array de cajas es 1300

Declaramos varias, un array formado por cuatro objetos del tipo Caja. Al hacer esta declaración, estamos llamando al constructor para cada uno de los cuatro objetos. Para declarar un array de objetos, debe existir un constructor para ese objeto que no reciba parámetros.

El contador del bucle for, indice, toma 1 como valor inicial, dejando que el primer objeto, varias [0], tome los valores por defecto (todas las dimensiones iguales a 8). Dentro del bucle, se llama a la función set para dar valor a las dimensiones de cada objeto. Esta construcción es similar a la de los objetos normales.

La variable indice se declara en el primer bucle, y está todavía disponible para su uso en el bucle de impresión, ya que no hemos salido del bloque en el que se declaró, la función main.

CLASES STATIC EN C

Ejemplo:

# include <iostream.h>

class Ejemplo {

int ejemplo1;

static int ejemplo2;

public:

Ejemplo (void);

void print(void);

};

int Ejemplo :: ejemplo2;

Ejemplo :: Ejemplo (void) {

ejemplo1 = 1;

ejemplo2 = 1;

}

void Ejemplo :: print (void) {

ejemplo1++;

ejemplo2++;

cout << 'ejemplo1 = ' << ejemplo1 << '\n';

cout << 'ejemplo2 = ' << ejemplo2 << '\n'; ;

}

main() {

Ejemplo primero, segundo;

primero.print();

segundo.print();

}

La salida de este programa es:

ejemplo1 = 2

ejemplo2 = 2

ejemplo1 = 2

ejemplo2 = 3

Una variable declarada static (ejemplo2) es una variable externa y sólo puede existir una copia de esa variable. Todos los objetos de esta clase (en este caso, primero y segundo) comparten una misma copia de esta variable, que es global a estos objetos.

En la definición de clase, la variable sólo es declarada. La declaración dice que la variable existirá y le da un nombre, pero la definición es la que realmente define un lugar para guardarla en la memoria del ordenador. Por definición, una variable puede ser declarada en la cabecera del fichero, pero no definida allí, sino fuera de ella, normalmente en el fichero de implementación.

El constructor inicializa las dos variables internas a 1 cada vez que se crea un objeto. Para mostrar que ejemplo2 es compartida por todos los objetos de esta clase, definimos una función, print, que incrementa el valor de las variables internas y a continuación las imprime.

Un objeto con un puntero interno

Modifiquemos la clase Caja para que incluya un puntero a una variable entera como atributo privado:

#include <iostream.h>

class Caja {

double longitud, anchura, altura;

int *point;

public:

Caja(double dim1, double dim2, double dim3, int valorAlmacenado);

~Caja();

double volumen(void) {return longitud * anchura * altura;} // Inline

int getValor(void) {return *point;} // Inline

};

//implementación del constructor

Caja::Caja(double dim1, double dim2, double dim3, int valorAlmacenado) {

longitud = dim1;

anchura = dim2;

altura = dim3;

point = new int;

*point = valorAlmacenado;

}

Caja::~Caja(void) { //Destructor

delete point;

}

main() {

Caja pequeña(5, 4, 10, 1), mediana (10, 6, 20, 2), grande(20, 10, 30, 3);

cout << 'El volumen de la caja pequeña es ' << pequeña.volumen() << '\n';

cout << 'El volumen de la caja mediana es' << mediana.volumen() << '\n';

cout << 'El volumen de la caja grande es' << grande.volumen() << '\n';

cout << 'El valor almacenado en la caja pequeña es ' << pequeña.getValor() << '\n';

cout << 'El valor almacenado en la caja mediana es' << mediana.get_valor() << '\n';

cout << 'El valor almacenado en la caja grande es ' << grande.get_valor() << '\n';

}

El resultado de la ejecución será:

El volumen de la caja pequeña es 200

El volumen de la caja mediana es 1200

El volumen de la caja grande es 6000

El valor almacenado en la caja pequeña es 1

El valor almacenado en la caja mediana es 2

El valor almacenado en la caja grande es 3

· Declaramos como atributo privado de la clase point, un puntero a un entero. Pero en la declaración no estamos asociando memoria a este puntero. Es en el constructor donde dinámicamente reservamos memoria para un entero, utilizando el operador new.

· Declaramos tres objetos, pequeña, mediana, y grande, del tipo Caja. Cada uno de ellos contiene un puntero que apunta a tres localizaciones de memoria diferentes. Cada objeto tiene su propia variable dinámicamente reservada para su uso privado. Además, en esta variable dinámicamente reservada se almacena el valor entero pasado al constructor.

· En un programa pequeño como éste, no se agotará la memoria, por tanto no es necesario comprobar que existe memoria disponible. En programas largos, sería conveniente comprobar que el valor del puntero devuelto no es NULL, para asegurar que los datos están realmente reservados.

· Hemos definido un destructor que borra con delete la memoria reservada dinámicamente con new. El destructor es llamado cuando los objetos que hemos definido (pequeña, mediana y grande) abandonan el bloque en el que han sido definidos. Si no hubiésemos definido el destructor, al salir de la función, quedarían en la memoria las tres variables reservadas dinámicamente sin nada apuntando a ellas. Por esta razón, el destructor se utiliza para borrar la variable a la que el puntero apunta cuando cada objeto sale de su ámbito de definición.

· En este caso particular, las variables serían automáticamente liberadas al volver al sistema operativo. Pero si se tratara de una función que llama a otra función, estaríamos llenando la memoria.

· Recordemos que si hubiésemos reservado memoria para más de un entero, por ejemplo:

p = new int [4];

en el destructor la liberaríamos de la siguiente forma:

delete [] p;

· Mencionemos de nuevo que las funciones declaradas inline deben ser utilizadas cuando la rapidez es lo más importante en el programa, ya que el código de la función se reescribe en el lugar en que se utiliza, y no se produce una llamada a una función definida de forma independiente. Definimos entonces el código como parte de la declaración de la clase, no en la implementación de las funciones. Si el código de la función es demasiado largo, el compilador puede ignorar el requisito de inline y tratarla como un método implementado independientemente, pero lo hará de forma invisible al usuario. Las funciones inline violan el principio de protección de la información, ya que el se hace el código de la función visible al usuario.

 

PUNTEROS A CLASES EN C

Punteros a clases.

Como cualquier otro tipo de dato, podemos tener punteros a clases, punteros a punteros a clases, punteros a punteros a punteros a clases, etc. Veamos un ejemplo:

# include <iostream.h>

# include 'Caja.h'

main() {

Caja grande (20, 10, 30);

Caja *punteroACaja1;

Caja *punteorACaja2;

punteroACaja1 = new Caja;

punteroACaja2 = new Caja(1, 2, 3);

cout << 'El volumen de la caja grande es ' << grande.volumen() << '\n';

cout << 'El nuevo volumen 2 es ' << punteroACaja2 -> volumen() << '\n';

punteroACaja1 -> set (2, 4, 6);

cout << 'El nuevo volumen 1 es ' << punteroACaja1 -> volumen() << '\n';

delete punteroACaja1, punteroACaja2;

}

La salida de este programa será:

El volumen de la caja grande es 6000

El nuevo volumen 2 es 6

El nuevo volumen 1 es 48

En este ejemplo hemos declarado dos punteros, punteroACaja1 y punteroACaja2, a objetos del tipo Caja. Hemos reservado memoria para la clase utilizando new, y al final de la función la hemos liberado utilizando delete.

Cuando declaramos punteroACaja1 y punteroACaja2 no se llama al constructor de Caja. Esto se hace cuando reservamos memoria para un objeto de ese tipo.

El acceso a los componentes del objeto se hace a través del operador ->

Las siguientes sentencias son equivalentes:

punteroACaja1 -> volumen()

(*punteroACaja1).volumen()

pero la notación -> es la que se suele utilizar.

Si la clase tuviese un destructor, éste sería llamado automáticamente cuando liberamos la memoria reservada dinámicamente para punteroACaja1 y punteroACaja2

OBJETOS ENCADENADOS EN C

Un objeto con un puntero a otro objeto: Objetos encadenados.

Añadamos a la clase Caja como atributo privado un puntero a otro objeto de la misma clase:

#include <iostream.h>

class Caja {

double longitud, anchura, altura;

Caja *otraCaja;

public:

Caja(double dim1, double dim2, double dim3); //Constructor

double volumen(void);

void point_at_next(Caja *where_to_point);

Caja *get_next(void);

};

Caja::Caja(double dim1, double dim2, double dim3) { //implementación del constructor

longitud = dim1;

anchura = dim2;

altura = dim3;

otraCaja = NULL;

}

double Caja::volumen(void) {

return (longitud * anchura * altura);

}

void Caja::point_at_next(Caja *where_to_point) {

otraCaja = where_to_point;

}

Caja *Caja::get_next(void) { // Este método devuelve la caja a la que apunta la caja actual

return otraCaja;

}

main() {

Caja pequeña(5, 4, 10), mediana (10, 6, 20), grande(20, 10, 30);

Caja *CajaPointer;

cout << 'El volumen de la Caja pequeña es ' << pequeña.volumen() << '\n';

cout << 'El volumen de la Caja mediana es ' << mediana.volumen() << '\n';

cout << 'El volumen de la Caja grande es' << grande.volumen() << '\n';

pequeña.point_at_next(&mediana);

mediana.point_at_next(&grande);

CajaPointer = &pequeña;

CajaPointer = CajaPointer->get_next();

cout << 'La Caja apuntada tiene volumen = ' << CajaPointer->volumen() << '\n';

}

El resultado de la ejecución será :

El volumen de la Caja pequeña es 200

El volumen de la Caja mediana es 1200

El volumen de la Caja grande es 6000

La Caja apuntada tiene volumen = 1200

En este programa:

· Hemos definido, en la parte privada de la clase Caja, un puntero a un objeto de la misma Clase. Esta es la estructura utilizada para la construcción de listas encadenadas.

· El constructor asigna el valor NULL al puntero. Esta es una buena idea, inicializar siempre los punteros. Haciendo esta asignación en el constructor, se garantiza que cada objeto de esta clase tendrá automáticamente su puntero inicializado.

· Hemos añadido dos funciones: point_at_next y get_next. Esta última devuelve un puntero a un objeto de la clase Caja.

· En el programa principal declaramos un puntero a un objeto del tipo Caja, CajaPointer.

· Hacemos que el puntero embebido en la caja pequeña apunte a la caja mediana, y que el puntero embebido en la caja mediana apunte a la caja grande. Hemos generado una lista encadenada con tres elementos.

· A continuación hacemos que el puntero CajaPointer apunte a la caja pequeña y lo utilizamos para referenciar a la caja pequeña y actualizarlo al valor contenido en la caja pequeña que es la dirección de la caja mediana. Hemos pasado, pues, de un elemento de la lista al siguiente llamando a una función de uno de los objetos.

Un objeto embebido en otro objeto. El puntero this.

En C++ se define this dentro de un objeto como un puntero al objeto en que está contenido. Se declara implícitamente como:

class_name *this;

y se inicializa para apuntar al objeto para el cual se llama a la función miembro. Este puntero es muy útil cuando se trabaja con punteros y especialmente en listas encadenadas cuando se necesita referenciar un puntero al objeto que se está insertando en la lista. La palabra this está disponible para este propósito y puede ser utilizada en cualquier objeto. Realmente la forma apropiada de referenciar a cualquier variable en una lista es a través del uso del puntero predefinido this, escribiendo this -> nombre_variable, pero el compilador supone que se está usando, y podemos omitir el puntero.

Funciones amigas.

Una función fuera de una clase puede definirse como función amiga por la clase que le da libre acceso a los miembros privados de la clase. Hay casos en que esto ayuda a hacer más legible un programa, y permite el acceso controlado a los datos.

Una función aislada puede ser declarada como amiga, así como miembro de otras clases, e incluso se le puede dar el status de amiga a clases enteras, si es necesario.

No pueden ser funciones amigas los constructores ni los destructores.

ESTRUCTURAS EN LENGUAJE C

Las estructuras ya estaban presentes en C. Hay quien las ve como una clase, pero sin métodos (sólo almacena datos).

Supongamos que queremos hacer una agenda con los números de teléfono de nuestros amigos. Necesitaríamos un array de Cadenas para almacenar sus nombres, otro para sus apellidos y otro para sus números de teléfono. Esto puede hacer que el programa quede desordenado y difícil de seguir. Y aquí es donde vienen en nuestro auxilio las estructuras.

Para definir una estructura usamos el siguiente formato:

struct nombre_de_la_estructura {
campos de estructura;
};



NOTA: Es importante no olvidar el ';' del final.



Vamos a crear una declaración de estructura llamada amigo:



struct estructura_amigo {
char nombre[30];
char apellido[40];
char telefono[10];
char edad;
};



A cada elemento de esta estructura (nombre, apellido, teléfono) se le llama campo o miembro.



Una vez definida la estructura, podemos usarla declarando una variable con esa estructura:



struct estructura_amigo amigo;



Ahora la variable amigo es de tipo estructura_amigo. Para acceder al nombre de amigo usamos: amigo.nombre.



Arrays de estructuras



Supongamos ahora que queremos guardar la información de varios amigos. Con una variable de estructura sólo podemos guardar los datos de uno. Necesitamos declarar arrays de estructuras:



struct estructura_amigo amigo[10];



Ahora necesitamos saber cómo acceder a cada elemento del array. La variable definida es amigo, por lo tanto para acceder al primer elemento usaremos amigo[0] y a su miembro nombre: amigo[0].nombre.



Inicializar una estructura



Primero se define la estructura y luego al declarar una variable como estructura le damos el valor inicial que queramos. Ejemplo:



struct estructura_amigo amigo = {
'Juanjo',
'Lopez',
'592-0483',
30
};



Por supuesto hemos de meter en cada campo el tipo de datos correcto.



Punteros a estructuras



Primero hay que definir la estructura igual que antes, pero al declarar la variable de tipo estructura debemos ponerle el operador '*' para indicarle que es un puntero.



Es importante recordar que un puntero no debe apuntar a un lugar cualquiera, debemos darle una dirección válida donde apuntar. No podemos por ejemplo crear un puntero a estructura y meter los datos directamente mediante ese puntero, no sabemos dónde apunta el puntero y los datos se almacenarían en un lugar cualquiera.



Y para comprender cómo funcionan nada mejor que un ejemplo. Este programa utiliza un puntero para acceder a la información de la estructura:



#include <stdio.h>
struct estructura_amigo {
char nombre[30];
char apellido[40];
char telefono[10];
int edad;
};
struct estructura_amigo amigo = {
'Juanjo',
'Lopez',
'592-0483',
30
};
struct estructura_amigo *p_amigo;
int main()
{
p_amigo = &amigo;
printf( '%s tiene ', p_amigo->apellido );
printf( '%i años ', p_amigo->edad );
printf( 'y su teléfono es el %s.\n' , p_amigo->telefono );
}


p_amigo es un puntero a la estructura estructura_amigo. Dado que es un puntero tenemos que indicarle dónde debe apuntar, en este caso vamos a hacer que apunte a la variable amigo:



 p_amigo = &amigo;


El operador & que significa 'dame la dirección donde está almacenado...'.



Para acceder a cada campo de la estructura antes lo hacíamos usando el operador '.', pero, como muestra el ejemplo, si se trabaja con punteros se debe usar el operador '->'.




Punteros a arrays de estructuras



Por supuesto también podemos usar punteros con arrays de estructuras.



Paso de estructuras a funciones



Las estructuras se pueden pasar directamente a una función igual que hacíamos con las variables. Ejemplo:



int suma( struct estructura_amigo arg_amigo ) {



return arg_amigo.edad+20;



}



CLASES



Terminología.



· Una clase es un grupo de datos y métodos (funciones). Es sólo un patrón que será usado para crear una variable que pueda ser manipulada en el programa.



· Un objeto es un ejemplo de una clase, lo que es similar a decir que una variable que hemos definido como un ejemplo de un tipo. Un objeto es lo que realmente se utiliza en un programa, ya que tiene valores que pueden ser cambiados.



· Un método es una función contenida en una clase.



· Un mensaje es lo mismo que una llamada a una función. En programación orientada a objetos se envían mensajes en lugar de llamar a funciones. En principio se puede pensar en ambas cosas como equivalentes




Clases.



El principal objetivo en un lenguaje orientado a objeto es que se pueden crear objetos que contienen los datos y los métodos para manipularlos. Además, el programador puede decidir si los datos y los métodos son visibles o no al resto del programa. En C++, estos objetos se implementan mediante clases:



class Caja {



// Datos y Métodos



};




La declaración de clase está encerrada entre { y }, como cualquier otro bloque en C++. Es importante el punto y coma que sigue a }. Utilizaremos para los nombres de las clases una letra mayúscula como primer carácter, y las demás minúsculas.



Veamos una clase muy simple, que contiene sólo datos. Hagámoslos públicos, para que puedan ser accedidos desde el programa:



# include <iostream.h>



class Caja {



public:



double longitud, anchura, altura;



};



main () {



Caja pequeña, mediana;



class Caja grande;



pequeña.longitud = 5;



mediana.longitud = 10;



grande.longitud = 20;



pequeña.anchura = 4;



mediana.anchura = 6;



grande.anchura = 10;



pequeña.altura = 10;



mediana.altura = 20;



grande.altura = 30;



cout << ' La longitud de la caja grande es ' << grande.longitud <<'\n';



cout << ' La anchura de la caja pequeña es ' << pequeña.anchura <<'\n';



cout << ' La altura de la caja mediana es ' << mediana.longitud <<'\n';



}




Vemos que:



· En primer lugar, definimos una clase llamada Caja, formada por tres variables: longitud, anchura y altura.



· En la función principal, declaramos tres variables de este tipo (tres objetos), pequeña, mediana y grande, para lo cual podemos utilizar o no la palabra class antes del nombre de la clase. Cada una de estas cajas contiene tres números reales, sus dimensiones.



· La palabra public en la definición de la clase es en este caso necesaria porque las variables en una clase son por defecto privadas, y no se puede acceder a ellas directamente desde el programa principal. En este caso, las tres variables reales están disponibles desde cualquier parte del programa principal. Cada variable puede ser inicializada, incrementada, leída, modificada, o sufrir cualquier operación que definamos sobre ella. Como ejemplo, en el programa hemos inicializado las dimensiones de cada objeto y a continuación las hemos imprimido.



· Los datos miembro de una clase son accedidos como los de una estructura de C: el nombre de la variable estructura/clase seguido por el dato miembro, separados ambos por un '.'




 



clases y encapsulacion en C



Encapsulación.



Los métodos son una interface a través de la cual se manipulan los datos almacenados en una clase. Usando estos métodos, podemos manipular y extraer datos de un objeto Caja sin saber qué tipos de datos se usan para almacenar los datos. Esto se conoce como encapsulación de datos, y es un concepto muy importante en la programación orientada a objetos. Encapsulación es la habilidad de una parte de un programa para ocultar sus datos al resto del código, impidiendo así accesos incorrectos o conflictos con los nombres de otras variables.



En el ejemplo anterior, cada clase tenía sólo una sección, etiquetada como public. Pero podemos utilizar también la etiqueta private. Así, la palabra public hace que los campos (variables o funciones) que le siguen en la clase puedan ser accedidas desde cualquier parte del programa, mientras que la palabra private indica que los campos que le siguen sólo son accesibles por el código que forma parte de la misma clase.



Si no se indica lo contrario, por defecto en una clase todos los elementos son privados. Veamos un ejemplo:



# include <iostream.h>



class Caja {



double longitud, anchura, altura;



public:



void set (double dim1, double dim2, double dim3);



void print (void);



double getLongitud (void);



};



void Caja :: set (double dim1, double dim2, double dim3) {



longitud = dim1;



anchura = dim2;



altura = dim3;



};



void Caja :: print (void) {



cout << 'longitud = ' << longitud <<'\n';



cout << 'anchura = ' << anchura <<'\n';



cout << 'altura = ' << altura <<'\n';



};



double Caja:: getLongitud (void) {



return longitud;



};



main () {



double longitud;



Caja pequeña, mediana, grande;



pequeña.set(5, 4, 10);



mediana.set (10, 6, 20);



grande.set (20, 10, 30);



pequeña.print();



mediana.print();



grande.print();



longitud = grande.getLongitud();



cout << 'longitud de la caja grande = ' << longitud <<'\n';



}




La ventaja de las funciones miembro es que la función llamada puede automáticamente acceder a los datos del objeto para el cual fue llamado. Así, en grande.print(), el objeto grande es el 'substrato': las variables longitud, altura y anchura que son utilizadas en el código de la función se refieren al objeto grande.



La sección privada de una clase es la parte de los datos que no puede ser accedida desde fuera de la clase, está escondida para cualquier acceso externo. Las variables longitud, anchura y altura del ejemplo, que son parte del objeto grande, no están disponibles para su uso en cualquier lugar del programa principal.



Se introduce la palabra public para indicar que puede accederse a todo lo que le sigue desde fuera de esta clase. En este caso, hemos declarado tres funciones públicas, que pueden ser llamadas desde la función main. Por supuesto, estas funciones, llamadas funciones miembro, tienen acceso a la parte privada de la clase. Por tanto, desde el programa principal sólo se puede acceder a las dimensiones de una caja llamando a las estas funciones.



Una vez declaradas las funciones dentro de la clase, debemos definirlas para establecer qué acción realizan. Esto se hace de la forma habitual, salvo que el nombre de la clase se antepone, seguido del operador ::, al nombre de la función. La definición de una función se denomina la implementación de la función. Es necesario especificar el nombre de la clase porque se puede utilizar el mismo nombre para funciones de otras clases, y el compilador debe saber a qué clase corresponde cada implementación.



Los datos privados de la clase son accesibles para las funciones miembro, y en la implementación de éstas pueden ser modificados y leídos en la forma habitual. Esto se puede hacer con los datos privados que forman parte de la clase a la que pertenece la función, pero los datos privados de otras clases están escondidos y no se puede acceder a ellos a través de funciones miembro de esta clase. Esta es la razón por la que se antepone el nombre de la clase al nombre de la función cuando se define ésta.



En la parte privada de una clase se pueden incluir variables y funciones, y otras variables y funciones en la parte públicas. En la mayor parte de las situaciones prácticas, las variables se incluyen en la parte privada, y las funciones sólo en la parte pública de la clase.



En C++ tenemos tres ámbitos de validez para las variables: local, file y clase. Las variables locales están localizadas en una única función, y las variables de fichero se pueden utilizar en cualquier lugar del fichero que sigue a su definición. Una variable cuyo ámbito es una clase se puede utilizar en el ámbito de utilización de la clase y en ningún otro sitio.



Más funciones.



La idea básica de la programación orientada a objetos es definir tipos de dato abstractos, y las operaciones o métodos que pueden actuar sobre ellos. Añadamos al ejemplo anterior una función que calcule el volumen de un objeto de la clase Caja:



# include <iostream.h>



class Caja {



double longitud, anchura, altura;



public:



void set (double dim1, double dim2, double dim3);



double volumen (void);



};



void Caja :: set (double dim1, double dim2, double dim3) {



longitud = dim1;



anchura = dim2;



altura = dim3;



};



double Caja:: volumen (void) {



return longitud * anchura * altura;



};



main () {



double longitud; Caja pequeña, mediana, grande;



pequeña.set(5, 4, 10);



mediana.set (10, 6, 20);



grande.set (20, 10, 30);



cout << 'El volumen de la caja grande es ' << grande.volumen() << '\n';



}




Esto impide que al calcular el volumen de una caja mezclemos las dimensiones de dos objetos distintos.



CLASES CONSTRUCTORES Y DESCTRUCTORES EN C



Constructores.



Cuando creamos un objeto de una clase, siempre seguimos los mismos pasos para llamar a un conjunto de métodos que inicializen los datos del objeto. En C++, se define una función especial, el constructor, que es llamada cuando se crea un nuevo objeto de la clase. Este constructor puede recibir parámetros, como cualquier otra función. Veamos un ejemplo de clase sin constructor y con constructor:



# include <iostream.h>



class Caja {



double longitud, anchura, altura;



public:



Caja (double dim1, double dim2, double dim3);



double volumen (void);



};



Caja :: Caja (double dim1, double dim2, double dim3) {



longitud = dim1;



anchura = dim2;



altura = dim3;



};



double Caja:: volumen (void) {



return longitud * anchura * altura;



};



main () {



Caja pequeña(5, 4, 10), mediana (10, 6, 20), grande(20, 10, 30);



cout << 'El volumen de la caja grande es ' << grande.volumen() << '\n';



}




En este sencillo ejemplo vemos las características más importantes de un constructor:



· El constructor tiene el mismo nombre que la clase a la que pertenece. Por ejemplo, el constructor de la clase Caja se llama Caja.



· El constructor no devuelve nada (ni siquiera void).



· Los argumentos se pasan al constructor en la declaración de un objeto. Así, Caja pequeña(5, 4, 10) crea un nuevo objeto de la clase Caja y llama al constructor con los parámetros 5, 4 y 10.




Cuando se crea un objeto de una clase, se llama automáticamente al constructor. Si tuviésemos un puntero a una clase, su constructor no sería llamado a menos que reserváramos memoria para lo que apunta el puntero, utilizando new.



Un constructor es una función, y por tanto podemos aplicarle todo lo que hemos visto que las funciones en C++ pueden hacer. Por ejemplo, podemos sobrecargarlo:



# include <iostream.h>



class Caja {



double longitud, anchura, altura;



public:



Caja (double dim1, double dim2, double dim3);



Caja (void);



double volumen (void);



};



Caja :: Caja (void) {



longitud = 8;



anchura = 8;



altura = 8;



};




Hemos creado una función constructora que inicializa las dimensiones de la caja a 8. Esta será llamada cuando creemos un objeto del tipo Caja sin ningún parámetro.



Destructores.



El destructor es muy similar al constructor, excepto que es llamado automáticamente cuando cada objeto sale de su ámbito de validez. Recordemos que las variables automáticas tienen un tiempo de vida limitado, ya que dejan de existir cuando se sale del bloque en que han sido declaradas. Cuando un objeto es liberado automáticamente, su destructor, si existe, es llamado automáticamente.



Un destructor tiene el mismo nombre que la clase a la que pertenece, pero precedido con una tilde (~). Igual que el constructor, un destructor no devuelve nada.



Si algún bloque de memoria fue reservado dinámicamente en un objeto, se puede utilizar el destructor para liberarlo antes de que se pierdan los punteros a esas variables.



#include <iostream.h>



class Taco {



public:



Taco (int hard) {



dureza = new int;



*dureza = hard;



}



~Taco() {



cout << 'Destruyendo taco con dureza ' ;



cout << *dureza <<;\n';



delete dureza;



}



private:



int *dureza;



};



main () {



Taco hard(10);



Taco *soft = new Taco (0);



delete soft;



};




En este ejemplo, vemos que el destructor tiene el mismo nombre que la clase, con un ~ delante. Cuando se crean punteros a clases, como soft en el ejemplo, se llama al destructor cuando se libera la memoria del puntero. Si esto no se hace, nunca se llamará al destructor.



Con clases declaradas estáticamente, como Taco hard, el destructor se llama al final de la función donde se declara el objeto (en el ejemplo, al final de la función main.



Incluso cuando se interrumpe un programa usando una llamada a exit(), se llama a los destructores de los objetos que existen en ese momento.

ARRAYS Y PUNTEROS EN C

 

Arrays.

Un array es una colección ordenada de objetos, llamados elementos del array, todos del mismo tipo. Un array de 10 elementos se declara de la siguiente forma:

float a[100];

El primer elemento de este array es a[0], y el último a[9].

Un array se puede inicializar así:

float a[3] = {10.1,10.2,10.3};

Si no se indica el tamaño del array, éste será igual al número de elementos que se indiquen:

float x[] = {1.3, 2.4};

crea un vector x de tamaño 2.

Arrays multidimensionales

Los arrays multidimensionales se declaran:

int a[3][3], b[2][3];

En este ejemplo, a es una matriz 3x3, y b una matriz 2x3. Los elementos se almacenan por filas, al contrario de lo que sucedía en FORTRAN. Así, podemos inicializar b de la siguiente forma:

int b[2][3] = { {1,2,3}, {4,5,6} };

Punteros.

El concepto de puntero está unido a la forma en que los tipos de datos son almacenados en la memoria de un ordenador, ya que denotan la dirección de una variable determinada. El nombre de la variable determina el tipo (char, int, float o double) y su dirección determina dónde está almacenada. Conocer la dirección de una variable es importante porque:

Permite que las funciones cambien el valor de sus argumentos, como veremos en el capítulo siguiente.

Permite pasar vectores de forma eficiente entre funciones: en lugar de copiar cada elemento del vector, se copia la dirección del primer elemento.

Permite reservar memoria en tiempo de ejecución en lugar de en tiempo de compilación, lo que significa que el tamaño de un vector puede ser determinado por el usuario en lugar de por el programador.

El nombre de un array es un puntero al array. Por tanto, los punteros y los arrays están íntimamente ligados en C y en C++.

Terminología básica.

Para entender los punteros y los vectores, es necesario conocer primero cómo se almacenan los números en la memoria del ordenador. El valor de cada variable en un programa de ordenador se guarda en una sección de memoria cuyo tamaño está determinado por el tipo de dato de la variable. La localización de esta sección de memoria es almacenada en la dirección de la variable. Por tanto, es como si cada variable estuviera compuesta de dos partes: su valor y su dirección. Cada celda de memoria se puede considerar compuesta de una parte con el contenido, y otra en la que se almacena la dirección. Esto es análogo a una fila de casas: cada casa tiene diferentes contenidos, y para mirarla necesitamos conocer su dirección.

Las direcciones se representan normalmente por un número hexadecimal, pudiendo contener un carácter, un entero o un real (aunque en realidad todos son almacenados como números binarios).

Para obtener la dirección de una variable se utiliza el operador dirección &:

#include <iostream.h>

main() {

double d = 2.7183;

cout << 'numero = ' << d << '\tdirección = ' << &d << '\n';

}

El valor de d es 2.7183. El valor de &d es una dirección (donde 2.7183 está almacenado). La dirección es imprimida en formato hexadecimal.

Direcciones y punteros

Un puntero guarda la dirección de un objeto en memoria, y como tal un puntero es también una variable. Puede parecer algo confuso, es como decir que el contenido de una casa es la dirección de otra vivienda. Las direcciones se guardan como números hexadecimales, por lo que no hay ninguna razón por la que no podamos definir otro tipo de dato, similar a un entero, pero a través del cual se puede modificar el valor almacenado en esa dirección. Es importante entender la relación entre punteros y direcciones:

Cada variable tiene su dirección, que puede ser obtenida mediante el operador unario &.

La dirección de una variable puede ser almacenada en un tipo de dato llamado puntero.

Un puntero en C o en C++ se declara anteponiendo un * al nombre de la variable, que es el operador inverso a &. El puntero apunta entonces a una variable del tipo especificado, y no debe ser usado con variables de otros tipos. Un experto en C podría forzar la utilización de un puntero con un tipo distinto del que se ha declarado, pero no es recomendable, ya que podría conducir a un uso erróneo.

Ejemplos de declaración de punteros:

int *puntero1;

int *puntero2, *puntero3;

char variable, *punteroCaracter;

float *punteroReal, real;

En la primera línea, declaramos un puntero a un entero. En la segunda, dos punteros a entero. En la tercera, un carácter ( variable) y un puntero a carácter (punteroCaracter). Por último, punteroReal es un puntero a un real, y real es declarado como un número real.

Ejemplo de uso del operador * y del operador de dirección (&):

#include <iostream.h>

main() {

double d, *dp;

d = 2.7183;

dp = &d;

cout << 'numero = ' << d << '\tdirección = ' << &d << '\n';

}

Operaciones con punteros.

Un puntero es un tipo de dato similar a un entero, y hay un conjunto de operaciones definidas para punteros:

La suma o resta de un entero produce una nueva localización de memoria.

Se pueden comparar punteros, utilizando expresiones lógicas, para ver si están apuntando o no a la misma dirección de memoria.

La resta de dos punteros da como resultado el número de variables entre las dos direcciones.

Siempre que se realiza una operación aritmética sobre un puntero, sumando o restando un entero, el puntero se incrementa o decrementa un número apropiado de sitios tal que el nuevo valor apunta a la variable que está n elementos (no n bytes) antes o después que el dado. De la misma forma, al restar dos punteros se obtiene el número de objetos entre las dos localizaciones. Finalmente, dos punteros son iguales si y sólo si apuntan a la misma variable (el valor de las direcciones es el mismo). No son necesariamente iguales si sus valores indirectos son los mismos, ya que estas variables podrían estar en diferentes localizaciones de memoria.

La siguiente tabla resume los operadores que manipulan punteros:

RESERVA DINAMICA DE MEMORIA EN C

Los operadores new y delete se utilizan para reservar y liberar memoria dinámicamente. new y delete son parte del lenguaje C++ y no parte de una librería como sucedía con las funciones equivalentes malloc() y free() de C. Ahora los operadores new y delete.

El propósito de new es crear arrays cuyo tamaño pueda ser determinado mientras el programa se ejecuta.

delete funciona igual que free() en C. La memoria a la que apunta el puntero es liberado, pero no el puntero en sí.

A continuación se presenta a modo de ejemplo un programa que reserva memoria de modo

dinámico para un vector de caracteres:

#include <iostream.h>

#include <string.h>

void main() {

char Nombre[50];

cout << 'Introduzca su Nombre:';

cin >> Nombre;

char *CopiaNombre = new char[strlen(Nombre)+1];

strcpy(CopiaNombre, Nombre); //copio Nombre en la variable CopiaNombre

cout << CopiaNombre;

delete [] CopiaNombre; //libero memoria

}

Se puede utilizar el operador new para crear variables de cualquier tipo. New devuelve, en todos los casos, un puntero a la variable creada. También se pueden crear variables de tipos definidos por el usuario.

struct usuario {

..........

};

usuario* Un_Usuario;

Un_Usuario = new usuario;

Cuando una variable ya no es necesaria se destruye con el operador delete para poder utilizar

la memoria que estaba ocupando, mediante una instrucción del tipo:

FUNCIONES EN LENGUAJE C

Las funciones se declaran y se definen exactamente igual que en C, y, al igual que en éste, se puede utilizar prototipo (prototype).

Prototipos

La declaración de una función es el prototipo. El prototipo da un modelo de la interface a la función. Veamos un ejemplo:

# include <iostream.h>

void haz_algo (int alas, float pies, char ojos);

main() {

int ala = 2;

float pie = 1000.0;

char ojo = 2;

haz_algo (3, 12.0, 4);

haz_algo (ala, pie, ojo);

}

void haz_algo (int alas, float pies, char ojos) {

cout << 'Hay ' << alas << 'alas.' << '\n';

cout << 'Hay ' << pies << 'pies. ' << '\n';

cout << 'Hay ' << int(ojos) << 'ojos.' << '\n';

}

La salida de este programa será:

Hay 3 alas.

Hay 12 pies.

Hay 4 ojos.

Hay 2 alas.

Hay 1000 pies.

Hay 2 ojos.

Cada llamada a la función haz_algo() debe verificar:

El número de parámetros debe ser exactamente tres.

Los tipos deben ser compatibles con los de la declaración.

Nótese que cuando llamamos a la función, la comprobación de tipo la hace el compilador basándose en el prototipo (en la declaración) puesto que la función todavía no ha sido definida.

Los nombres de variables que aparecen en el prototipo son opcionales y actúan casi como comentarios al lector del programa, ya que son completamente ignorados por el compilador.

Tipos compatibles

Son compatibles cualquiera de los tipos simples (definidos en C++) que pueden ser convertidos de uno a otro de manera significativa. Por ejemplo, si llamamos con un entero a una función que está esperando un número real como parámetro, el sistema lo convertirá automáticamente, sin mencionarlo al usuario. Esto también es cierto de float a char, o de char a int.

En cambio, si pasamos un puntero a un entero a una función que estaba esperando un entero, no habrá conversión de tipo, ya que son dos variables completamente distintas. De la misma forma, un tipo definido por el usuario (estructura o clase) no puede ser convertido automáticamente a un long float, a un array o incluso a otra estructura o clase diferente, porque son tipos incompatibles y no puede realizarse la conversión de manera significativa.

Sin embargo, el tipo devuelto por la función, void en el ejemplo anterior, debe ser compatible con el tipo que se espera que devuelva en la función de llamada, o el compilador dará un warning.

El uso de prototipos no supone coste alguno en tiempo ni en velocidad de ejecución. El prototipo se verifica durante la compilación.

Funciones con void como argumento.

Una función sin lista de argumentos como

void func ();

significa en C que no se ha declarado el tipo de la lista de argumentos que recibe la función, por lo que el compilador no producirá errores respecto al uso impropio de los argumentos. Cuando en C se declara una función que no tiene argumentos se utiliza el tipo void:

void func (void);

En C++, ambas expresiones son equivalentes

Pasar punteros a funciones.(paso por valor y por referencia)

Cuando se llama a una función, todos los parámetros con los que la llamamos son copiados y pasados a la función (paso por valor). Esto significa que si la función cambia el valor de los parámetros, sólo lo hace dentro del ámbito de la función. Por ejemplo:

#include <iostream.h>

void change_values(int a,int b) {

a=4;

b=5;

}

main() {

int a, b;

a=1;

b=2;

change_values(a,b);

cout << 'A is ' << a <<',B is' << b <<'\n';

}

La salida de programa es: A is 1, B is 2

La llamada a la función no ha cambiado el valor de las variables que se le han pasado. La función cambia las copias de lo que se le ha pasado.

Si queremos pasar parámetros por referencia hay que pasar punteros a los datos. Para hacer esto, utilizamos el operador &, que da la dirección de una variable:

#include <iostream.h>

void change_values(int *a,int *b) {

*a=4;

*b=5;

}

main() {

int a, b;

a=1;

b=2;

change_values(&a,&b);

cout << 'A is ' << a <<',B is' << b <<'\n';

}

Ahora la salida del programa es:

A is 4, B is 5

La función main pasa la dirección de a y b, por lo que a la función change_values se le pasa una copia de las direcciones. Utilizando las direcciones de a y b, la función puede acceder a los datos directamente.

Polimorfismo.

En C++ es posible declarar dos funciones diferentes que tengan el mismo nombre. Las funciones deben diferir en la lista de argumentos, bien en el número de variables que se pasan a la función, bien en el tipo de argumentos que recibe. Así, por ejemplo, se puede definir una función que trabaje, bien con enteros, bien con strings; sólo hay que definir dos funciones separadas con el mismo nombre:

#include <iostream.h>

void show(int val) {

cout <<' Es un entero :'<< val << '\n';

}

void show(char *val) {

cout <<'Es un carácter: '<< val << '\n';

}

main() {

show (42);

show ('A');

show (452.2);

}

En la primera llamada a la función show, se le pasa un entero, por tanto se llama a la primera copia de la función show. La segunda vez, el argumento es un carácter, por tanto se utiliza la segunda definición, aquella que utiliza un carácter. Ahora bien, la tercera llamada utiliza un número real, y no existe una definición de la función para este caso. El compilador utiliza la primer definición. La salida del programa es:

Es un entero :42

Es un carácter: A

Es un entero :452

Comentarios sobre la sobrecarga de funciones:

· El uso de más de una función con el mismo nombre pero acciones diferentes debe ser evitado. En el ejemplo anterior, las funciones show() están relacionadas: imprimen información en la pantalla.

· C++ no permite que varias funciones difieran sólo en su valor devuelto. Dos funciones de este tipo no podrían ser distinguidas por el compilador.

Parámetros por defecto

Es una forma de indicar qué valor debe ser pasado a una función en el caso en que en la llamada no se pase nada, o se pasen menos argumentos de los definidos. Un ejemplo de definición de una función que tiene parámetros por defecto en su lista de argumentos es:

void funcion (int y = 2)

En este caso, estamos definiendo un valor, 2, que tomará la variable y en caso de que no se pase nada en la llamada a la función:

funcion ();

ENTRADA/SALIDA EN C

 

Función printf()

Sirve para imprimir por pantalla. Supongamos que queremos mostrar el contenido de la variable "x" por pantalla:

printf( '%i', x );

Suponiendo que x valga 10 (x=10) en la pantalla tendríamos:

10

Para ver el contenido de dos variables, por ejemplo x e y, podemos hacer:

printf( '%i %i', x, y );

resultado (suponiendo x=10, y=20):

10 20

También podemos mezclar texto con enteros:

printf( 'El valor de x es %i, ¡que bien!\n', x );

que quedará como:

El valor de x es 10, ¡que bien!

Como vemos %i al imprimir se sustituye por el valor de la variable.

Salida por pantalla y entrada por teclado.

En C++ además de las funciones printf() y scanf(), que siguen estando vigentes, se pueden utilizar los operadores cin y cout. Para utilizar estos nuevos operadores es necesario incluir la librería iostream.h con la instrucción #include <iostream.h>. Así en un programa en C habría que hacer algo de este estilo:

char nombre;

int num=2;

printf ('Introduzca el nombre del fichero %d: ', num);

scanf (' %s', nombre)

En C++ podría escribirse así:

char nombre;

int num=2;

cout << 'Introduzca el nombre del fichero ' << num << ': ';

cin >> nombre;

Es importante darse cuenta de que ahora ya no hace falta especificar el tipo de dato que va a ser impreso o leído, asociándolo con un formato determinado. Es el propio programa el que decide el tipo de dato en tiempo de ejecución gracias a que estos operadores están sobrecargados de tal manera que admiten tanto los tipos predefinidos como aquellos tipos de datos definidos por el usuario.

Códigos de escape.

Se utilizan para producir un tabulador, retorno de carro, movimiento del cursor hacia atrás, incluso un pitido. Siempre comienzan por un ' \ ' seguido de una letra. Algunos son:

\n Newline
\r Retorno de carro
\t Tabulador horizontal
\v Tabulador vertical
\b Espacio hacia atrás

ESTRUCTURAS DE CONTROL EN C

Bloques if

La sintaxis general de un bloque if es:

if( expresión ) {

statement ;

...

}

La expresión debe ir entre paréntesis y dar un valor numérico. Si el valor es no cero, las expresiones que van entre llaves son ejecutadas.

También se puede utilizar un bloque if-else:

if( expresión ) {

contenidos bloque 1 ;

}

else { contenidos bloque 2 ;

}

Bucles

En C++ hay tres clases de bucles:

Bucle while

while( expresión ) {

statement ; // cuerpo del bucle

...

}

El bucle while ejecuta el cuerpo del bucle repetidamente mientras la expresión sea distinta de cero (sea verdadera). El test se hace antes de ejecutar el cuerpo del bucle, lo que significa que se éste se ejecuta cero o más veces.

Se debe utilizar un bucle while cuando es posible que el cuerpo del bucle no sea ejecutado. Por ejemplo, para leer y procesar el contenido de un fichero de tamaño desconocido.

Bucle do-while

do {

statement ; // cuerpo del bucle do-while

...

} while ( expresión );

El cuerpo del bucle se ejecuta repetidamente mientras la expresión es distinta de cero (verdadera). El test se hace después de ejecutar el cuerpo del bucle, por lo que éste se ejecuta al menos una vez.

Debe utilizarse este tipo de bucles cuando el cuerpo debe ser ejecutado al menos una vez. En particular, en aquellos casos en que el bucle calcula un valor que es necesario para la condición de terminación. Por ejemplo, los cálculos iterativos que terminan cuando se da una condición de convergencia, cuando una expresión calculada dentro del bucle es menor que un determinado valor.

Bucle for

for ( init-statement; expresión de continuación; expresión de incremento ) {

statement ; // cuerpo del bucle for

...

}

break y continue

break termina la ejecución del bucle en que se encuentra. continue hace que el bucle pase directamente a la siguiente iteración.

Ambos comandos deben utilizarse lo menos posible

VARIABLES Y CONSTANTES EN C

 

Nombres de las variables

Sólo están permitidas letras de la 'a' a la 'z' (la ñ no vale), números y el símbolo '_', puede contener números, pero no en el primer carácter.

Ejemplos de nombres válidos:

	camiones
numero
buffer
a1
j10hola29
num_alumnos


Ejemplos de nombres no válidos:



	1abc
nombre?
num/alumnos


Tampoco valen como nombres de variable las palabras reservadas que usa el compilador. Por ejemplo:for, main, do, while.



C distingue entre mayúsculas y minúsculas. Por lo tanto:



	Nombre
nombre
NOMBRE
serían tres variables distintas.



¿Dónde se declaran las variables?



Tenemos dos posibilidades, una es declararla como global y otra como local. Es global aquella variable que se declara fuera de la función main y local la que se declara dentro:




Variable Global



Variable Local



#include <stdio.h>

int x;

int main() {
}


#include <stdio.h>

int main() {
int x;
}


Las variables globales se pueden usar en cualquier procedimiento y las locales sólo pueden usarse en el procedimiento en el que se declaran. Es buena costumbre usar variables locales en vez de globales.



Podemos declarar más de una variable en una sola línea:



int x, y;



En C++, las variables pueden ser declaradas en cualquier lugar dentro de un programa. No es necesario, como en C, que sean declaradas al comienzo de una función o de un bloque. Esto puede ser útil en códigos grandes, cuando una variable se utiliza en un trozo de código lejano al comienzo del bloque. En este caso, el declarar el tipo de la variable cerca del lugar donde se va a utilizar puede hacer que el programa sea más fácil de leer.



Un ejemplo de esta posibilidad de C++ es la declaración del contador dentro de un bucle. Por ejemplo:



# include



main () {



for (int i=0; i < 10; i++) {



cout << 'hola' << '\n';



}



}




El índice i se ha definido dentro del bucle for. En algunos compiladores, su validez se extiende hasta el final del bloque donde han sido definidos



Constantes.



Las constantes se declaran, como en C, igual que una variable normal, pero añadiendo la palabra constdelante. Por ejemplo, para declarar una constante con valor 14:



const int numero = 14;



Estas constantes no pueden ser modificadas a lo largo del programa. Por eso deben ser definidas al mismo tiempo que declaradas.




Enumeraciones.



C++ permite definir nombres mnemotécnicos para enteros agrupados en conjuntos. Una enumeración es un conjunto ordenado de nombres:



enum Color {rojo, verde, azul, negro};




Esta declaración define un nuevo tipo, Color, y cuatro variables inalterables de ese tipo. Por ejemplo,



Color c=verde;




declara la variable c del tipo Color con valor inicial verde. Cada nombre en la enumeración se inicializa con un número entero, empezando en 0 e incrementándose en 1 al ir de izquierda a derecha. Se pueden especificar los valores enteros asociados a cada identificador en la enumeración:



enum Poligono {triángulo = 3, cuadrado = 4, pentágono =5};




Declaraciones typedef.



La declaración typedef da un nombre adicional a un tipo ya existente. Por ejemplo:



typedef float temperatura;




hace que temperatura sea un sinónimo de float, pero no define un nuevo tipo, y por tanto no afecta a las reglas de conversión de tipo. y p como variables de distinto tipo, ambas son en realidad de tipo float .



Conversiones de tipo.



Conversiones aritméticas usuales


Si los operandos de un operador aritmético binario son del mismo tipo, ese tipo es el tipo del resultado. Pero si no es así uno de los operandos es convertido al tipo del otro, y el resultado de la operación tiene ese tipo común. El tipo común es el tipo del operando que aparece primero en la lista long double, double, float, long int, int. Losoperandos de tipo char y short son tratados como si fueran de tipo int



Conversiones forzadas por el programador


Las conversiones de tipo se hacen en C++ igual que en C:



c = (int)a;




En C++ las conversiones de tipo se pueden escribir también como una llamada a función:



c = int(a);











































































Operación Acción
x++ Postincremento
++x Preincremento
x-- Postdecremento
--x Predecremento
+x + unario
-x - unario
x*y Multiplicación
x/y División
x%y Módulo
x+y Suma
x-y Resta

Todas Las Librerías y Funciones Standard Del Lenguaje C

Etiquetas de Technorati:
Para quienes estén interesados, les dejo una lista con el nombre de todas las librerías estándar de C y las funciones que contiene cada una.
Dejé los títulos de las librerías y las variables en inglés ya que quienes programamos nos llevamos mejor con ese idioma (al menos en el momento de la programación en si) que con nuestro español de nacimiento.

Input and Output: <stdio.h>

  • FILE *fopen(const char *filename, const char *mode)
  • FILE *freopen(const char *filename, const char *mode, FILE *stream)
  • int fflush(FILE *stream)
  • int fclose(FILE *stream)
  • int remove(const char *filename)
  • int rename(const char *oldname, const char *newname)
  • FILE *tmpfile(void)
  • char *tmpnam(char s[L_tmpnam])
  • int setvbuf(FILE *stream, char *buf, int mode, size_t size)
  • void setbuf(FILE *stream, char *buf)
  • int fprint(FILE *stream, const char *format, …)
  • int sprintf(char *s, const char *format, …)
  • vprintf(const char *format, va_list arg)
  • vfprintf(FILE *stream, const char *format, va_list arg)
  • vsprintf(char *s, const char *format, va_list arg)
  • int fscanf(FILE *stream, const char *format, …)
  • int scanf(const char *format, …)
  • int sscanf(char *s, const char *format, …)
  • int fgetc(FILE *stream)
  • char *fgets(char *s, int n, FILE *stream)
  • int fputc(int c, FILE *stream)
  • int fputs(const char *s, FILE *stream)
  • int getc(FILE *stream)
  • int getchar(void)
  • char *gets(char *s)
  • int putc(int c, FILE *stream)
  • int putchar(int c)
  • int ungetc(int c, FILE *stream)
  • size_t fread(void *ptr, size_t size, size_t nobj, FILE *stream)
  • size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *stream)
  • int fseek(FILE *stream, long offset, int orogin)
  • long ftell(FILE *stream)
  • void rewind(FILE *stream)
  • int fgetpos(FILE *stream, fpos_t *ptr)
  • int fsetpos(FILE *stream, const fpos_t *ptr)
  • void clearerr(FILE *stream)
  • int feof(FILE *stream)
  • int ferror(FILE *stream)
  • void perror(const char *s)

Character Class Tests: <ctype.h>

  • isalnum(c)
  • isalpha(c)
  • iscntrl(c)
  • isdigit(c)
  • isgraph(c)
  • islower(c)
  • isprint(c)
  • ispunct(c)
  • isspace(c)
  • isupper(c)
  • isxdigit(c)

String Functions: <string.h>

  • char *strcpy(s , ct)
  • char *strncpy(s , ct , n)
  • char *strcat(s , ct)
  • char *strncat(s , ct , n)
  • int strcmp(cs , ct)
  • int strncmp(cs , ct ,n)
  • char *strchr(cs , c)
  • char *strrchr(cs , c)
  • size_t strspn(cs , ct)
  • size_t strcspn(cs , ct)
  • char *strstr(cs , ct)
  • size_t strlen(cs)
  • char *strerror(n)
  • char *strtok(s , ct)

Mathematical Functions: <math.h>

  • sin(x)
  • cos(x)
  • tan(x)
  • asin(x)
  • acos(x)
  • atan(x)
  • atan2(x)
  • sinh(x)
  • cosh(x)
  • tanh(x)
  • exp(x)
  • log(x)
  • log10(x)
  • pow(x,y)
  • sqrt(x)
  • ceil(x)
  • floor(x)
  • fabs(x)
  • ldexp(x)
  • frexp(x,double *ip)
  • modf(x,double *ip)
  • fmod(x,y)

Utility Functions: <stdlib.h>

  • double atof(const char *s)
  • int atoi(const char *s
  • long atol(const char *s)
  • double strrod(const char *s, char **endp)
  • long strtol(const char *s, char **endp, int base)
  • unsigned long strtoul(const char *s, char **endp, int base)
  • int rand(void)
  • void srand(unsigned int seed)
  • void *calloc(size_t nobj, size_t size)
  • void *malloc(size_t size)
  • void *realloc(void *p, size_t size)
  • void free(void *p)
  • void abort(void)
  • void exit(int status)
  • int atexit(void (*fcn)(void))
  • int system(const char *s)
  • char *getenv(const char *name)
  • void *bsearch(const void *key, const void *base, size_t n, size_t size, int (*cmp)(const void *keyval, const void *datum))
  • void qsort(void *base, size_t n, size_t size, int (*cmp)(const void *, const void *))
  • int abs(int n)
  • long labs(long n)
  • div_t div(int num, int denom)
  • ldiv_t ldiv(long num , long denom)

Diagnostics: <assert.h>

  • void assert(int expression)

Non-local Jumps: <setjmp.h>

  • int setjmp(jmp_buf env)
  • void longjmp(jmp_buf env, int val)

Signals: <signal.h>

  • void (*signal(int sig, void (*handler)(int)))(int)

Data and Time Functions: <time.h>

  • clock_t clock(void)
  • time_t time(time_t , *tp)
  • double difftime(time_t time2 , time_t time1)
  • time_t mktime(struct tm *tp)
  • char *asctime(const time_t *tp)
  • char *ctime(const time_t *tp)
  • struct tm *gmtime(const time_t *tp)
  • struct tm *localtime(const time_t *tp)
  • size_t strftime(char *s, size_t smax, const char *fmt, const struct tm *tp)

RESUMEN DE LIBRERIAS

Ø  assert.h Contiene una macro para el diagnóstico dentro de los programas.

Ø  ctype.h Contiene varias funciones para comprobación de tipos y transformación de caracteres.

Ø  errno.h Contiene varias macros usadas para informar de errores.

Ø  limits.h Contienen varias macros que definen constantes para el tamaño de tipo enteros.

Ø  float.h Contienen varias macros que definen constantes para el tamaño de tipo flotante.

Ø  locale.h Contienen varias macros, funciones y tipos para unidades locales, como unidad monetaria, tiempo, dígitos, etc.

Ø  math.h Contiene una macro y varias funciones matemáticas.

Ø  setjmp.h Contienen declaraciones que proporcionan una forma de evitar la secuencia normal de llamada y regreso de funciones.

Ø  signal.h Contiene un tipo, dos funciones y varias macros para manejar condiciones excepcionales que aparecen durante la ejecución, tal como una señal de interrupción de una fuente externa o un error en la ejecución.

Ø  stdarg.h Contiene un tipo y tres macros que proporcionan recursos para recorrer una lista de argumentos de función de tamaño y tipo desconocido.

Ø  stddef.h Contiene varios tipos y macros que también están definidas en otras librerías, como size_t.

Ø  stdio.h Contiene tipos, macros y funciones para la realización de tareas de E/S.

Ø  stdlib.h Contiene tipos, macros y funciones para la conversión numérica, generación de números aleatorios, búsquedas y ordenación, gestión de memoria y tareas similares.

Ø string.h Contiene tipos, macros y funciones para la manipulación de cadenas de caracteres.

Ø  time.h Contiene tipos, macros y funciones para la la manipulación de información sobre fechas y horas.