Escolar Documentos
Profissional Documentos
Cultura Documentos
Existen muchos lenguajes de programación de entre los que se destacan los siguientes:
1. C ▶
2. C++ ▶
3. Basic
4. Ada ▶
5. Java ▶
6. Pascal ▶
7. Python □
8. Fortran
9. Smalltalk
Historia de C++
C++ es un lenguaje de programación creado por Bjarne Stroustrup en los laboratorios de
At&T en 1983. Stroustrup tomó como base un lenguaje de programación popular en aquella
época el cual era C.
El C++ es un derivado del mítico lenguaje C, el cual fue creado en la década de los 70 por
la mano del finado Dennis Ritchie para la programación del sistema operativo [1] (un
sistema parecido a Unix es GNU/Linux), el cual surgió como un lenguaje orientado a la
programación de sistemas (System Programming) y de herramientas (Utilities)
recomendado sobre todo para programadores expertos, y que no llevaba implementadas
muchas funciones [¿cómo cuáles?] que hacen a un lenguaje más comprensible.
Qué es C++
C++ es un lenguaje de programación orientado a objetos que toma la base del lenguaje C y
le agrega la capacidad de abstraer tipos como en Smalltalk.
Herramientas Necesarias
Las principales herramientas necesarias para escribir un programa en C++ son las
siguientes:
Adicional
1. Inglés (Recomendado)
2. Estar familiarizado con C u otro lenguaje derivado (PHP, Python, etc).
Es recomendable tener conocimientos de C, debido a que C++ es una mejora de C, tener los
conocimientos sobre este te permitira avanzar mas rapido y comprender aun mas. Tambien,
hay que recordar que C++, admite C, por lo que se puede programar (reutilizar), funciones
de C que se puedan usar en C++.
También es muy recomendable hacer uso de comentarios (comenta todo lo que puedas, hay
veces que lo que parece obvio para ti, no lo es para los demás) y tratar de hacer un código
limpio y comprensible, especificando detalles y haciendo tabulaciones, aunque te tome un
poco mas de tiempo, es posible que mas adelante lo agradezcas tu mismo.
Estructura de un programa
En general, los programas escritos en C++ tienen una estructura (básica) compuesta por tres
secciones:
1. Directivas de preprocesador
2. declaraciones globales
3. declaración de funciones
Directivas de preprocesador
#include <iostream>
Macros de preprocesador
Cuando el procesador encuentra esta directiva, este reemplaza todas las ocurrencias del
identificador y son sustituidas por reemplazo. Cuando se requiere desactivar una macro, a
partir de cierta parte del código, se utiliza la directiva #undef. La sintaxis es:
#undef nombre_macro
Inclusiones condicionales
#ifdef: Permite que una seccion del programa sea compilada solo si la macro especificada
como parametro ha sido definida, sin importar el valor de esta. Por ejemplo:
#ifdef TABLE_SIZE
int table[TABLE_SIZE];
#endif
#ifndef: Permite realizar exactamente todo lo contrario a #ifdef. La lineas de código que se
encuentren entre #ifndef y #endif, serán ejecutadas siempre y cuando la macro señalada
como parámetro de #ifndef no se encuentre definida aun. Por ejemplo:
#ifndef TABLE_SIZE
#define TABLE_SIZE 100
#endif
int table[TABLE_SIZE];
#if, #else y #elif (o else if): Estas directivas tienen el mismo significado que los comandos
condicionales de cualquier lenguaje de programacion. Por ejemplo:
#if TABLE_SIZE>200
#undef TABLE_SIZE
#define TABLE_SIZE 200
#elif TABLE_SIZE<50
#undef TABLE_SIZE
#define TABLE_SIZE 50
#else
#undef TABLE_SIZE
#define TABLE_SIZE 100
#endif
int table[TABLE_SIZE];
En el ejemplo anterior, se muestra el uso de todas las directivas condicionales del
preprocesador.
Control
Donde número es el nuevo número de línea que se asignará a la siguiente línea de código.
Los números de línea de las líneas sucesivas se incrementarán uno por uno desde este punto
en adelante."nombre_de_archivo" es un parámetro opcional que permite redefinir el
nombre del archivo que se mostrará. Por ejemplo:
Este código generará un error que se mostrará como un error en el archivo de "asignación
de variable", línea 20.
Error
#ifndefcplusplus
#error A Se requiere compilador de C++
#endif
Inclusión de fichero
#include <cabecera>: Es usado para incluir las cabeceras proporcionadas por defecto, por
ejemplo, la librería estándar (string, iostream, etc.).
#include "archivo" : Es usado para incluir archivos.
Pragma
La directiva #pragma es utilizada para especificar las distintas opciones del compilador.
Estas opciones son especificadas dependiendo del compilador que se utilice. Si el
compilador no permite un argumento para #pragma, esto es ignorado y no genera un error
de sintaxis.
Declaraciones globales
En esta seccion se declaran todas variables y cabeceras de funciones que seran vistas de
manera global, es decir, que su alcance es total en el programa. Por ejemplo:
#include <iostream>
#define PI 3.1415
Declaración de funciones
#include <iostream>
#define PI 3.1415
Los parámetros de entrada de la función main es algo que se abordará mas adelante.
Este último paso es el más costoso, por que en programas grandes, averiguar si hay o no un
fallo prácticamente puede ser una tarea totémica.
// Aquí se sitúan todas las librerias que se vayan a usar con #include,
// que se verá posteriormente
// Las librerias del sistema son las siguientes
#include <iostream>
// Función main
// Recibe: void
// Devuelve: int
// Función principal, encargada de mostrar "Hola Mundo",por pantalla
int main(void)
{
// Este tipo de líneas de código que comienzan por '//' son
comentarios
// El compilador los omite, y sirven para ayudar a otros programadores
o
// a uno mismo en caso de volver a revisar el código
// Es una práctica sana poner comentarios donde se necesiten,
return 0;
// se devuelve un 0.
//que en este caso quiere decir que la salida se ha efectuado con
éxito.
}
Mediante simple inspección, el código parece enorme, pero el compilador lo único que
leerá para la creación del programa es lo siguiente:
Ejemplo
#include <iostream>
int main(void){ std::cout << "Hola Mundo" << std::endl; return 0; }
Como se puede observar, este código y el original no difieren en mucho salvo en los saltos
de línea y que los comentarios, de los que se detallan posteriormente, están omitidos y tan
sólo ha quedado "el esqueleto" del código legible para el compilador. Para el compilador,
todo lo demás, sobra.
O este otro, que es, en parte, como el lenguaje C, en su versión C99, es:
Ejemplo
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
printf( "Hola Mundo\n" );
return EXIT_SUCCESS;
// 'EXIT_SUCCESS' es una definición que está dentro de 'stdlib.h'
// tambien funciona return 0
}
Nota: si se usa Windows, el código es el mismo, pero debemos agregar un metodo mas para
que el programa se mantenga abierto y no se cierre la consola, cosa que en GNU, no es
necesaria por que la consola ya esta abierta (al mandar a ejecutar).
Para esto podemos usar cin.get() que nos permitira leer del teclado, por lo que el programa
no finalizara, hasta que el usuario pulse enter.
Ejemplo
#include <iostream>
int main(void)
{
std::cout << "Hola Mundo" << std::endl;
std::cin.get();
//con 'std::cin.get();' lo que se hace es esperar hasta que el
usuario pulse enter.
return 0;
}
Los pasos siguientes son para una compilación en GNU o sistema operativo Unix. En
Windows tampoco es aplicable.
Con ctrl-x ctrl-s se guarda el archivo. Ahora para generar el ejecutable del programa se
compila con g++ de la siguiente forma:
Para poder ver los resultados del programa en acción, se ejecuta el programa de la siguiente
forma:
$./hola
Hola Mundo
Comentarios
Cuando se escriben programas es muy útil agregar comentarios que ayuden a explicar lo
que realiza un programa. En C++ se pueden utilizar tres tipos de comentarios: al estilo C, al
estilo C++ y usando preprocesador.
Ej:
/*
*/
Si se usan este tipo de etiquetas de comentarios, hay que tener cuidado con el cierre (*/),
por que el compilador puede tomar todo el texto como comentario, o cerrar antes de lo
deseado.
Usando el estilo de código de C++ sólo pueden ocupar una línea como en el siguiente
código:
Una buena práctica de programación es pensar que se programa a sabiendas de que otro
programador, tal vez el lector mismo en un futuro, tenga que "desencriptar" qué quiso hacer
ahí y por qué.
Otra posible forma de comentar código es usando el preprocesador. Esto se detallará más
adelante en profundidad, por ahora la parte útil del preprocesador que interesa es la
siguiente:
#if 0
Comentarios sobre el programa /parte del programa.
Pueden ocupar múltiples líneas.
Este tipo de comentarios se usan rara vez. Generalmente son difíciles de localizar, incluso
para programadores experimentados que trabajan en papel, y son fáciles de distinguir en
casi cualquier IDE. Es recomendable indicar que se tratan de comentarios, o directamente
no usarlos, salvo si son grandes cantidades de comentarios. Se verá más adelante que
también puede tener otros usos.
Donde ALGO puede ser lo que sea que 'std::cout' sea capaz de mostrar por consola. Más
adelante se trata más sobre ello en detalle y aclarando posibles dudas que ahora puedan
surgir. También se utilizó 'std::endl', esto permite que el texto se escriba en una nueva línea.
// Función: main
// Recibe: void
// Devuelve: int
// Es la función principal encargada de mostrar por consola diferentes
textos
int main(void)
{
// Ejemplo con una única línea, se muestra el uso de std::cout y
std::endl
std::cout << "Bienvenido. Soy un programa. Estoy en una linea de
codigo." << std::endl;
Se reta a compilar este código y a observar sus resultados. En este momento el lector está
capacitado para escribir programas que impriman por pantalla el mensaje que se quiera.
Con algunos compiladores, verá 'programación', pero con otros puede ver incluso
'programaci n'.
Advertencia: cout puede ser utilizado sin tener std:: de forma previa porque se puede introducir
una directiva, denominada 'using', que acomoda todos los cout. De otro modo habría un error de
compilador. Este tema se trata en detalle más adelante.
Sintaxis
Sintaxis es la forma correcta en que se deben escribir las instrucciones para el computador
en un lenguaje de programación específico. C++ hereda la sintaxis de C estándar, es decir,
la mayoría de programas escritos para el C estándar pueden ser compilados en C++.
El punto y coma
El punto y coma es uno de los simbólos más usados en C, C++; y se usa con el fin de
indicar el final de una línea de instrucción. El punto y coma es de uso obligatorio.
ejemplo
´´´´´´
ejemplo
Espacios y tabuladores
Tipos primitivos
En un lenguaje de programación es indispensable poder almacenar información, para esto
en C++ están disponibles los siguientes tipos que permiten almacenar información
numérica de tipo entero o real:
double De punto flotante de doble precisión 8bytes 1.7e +/- 308 (15 digitos)
long double Long de punto flotante de doble precisión 8bytes 1.7e +/- 308 (15 digitos)
Los valores dependen de la arquitectura utilizada. Los mostrados son los que
generalmente se encuentran en una máquina típica de arquitectura 32 bits.
El modificador long
El modificador long le indica al compilador que el tipo debe utilizar más bits que los
normalmente utilizados por ejemplo si tenemos en una maquina de 32 bits como un
Pentium de Intel, normalmente de un int ocupara 32 bits, pero si al declarar un entero le
antecedemos long, este entero ocupa 64 bits, el siguiente código muestra como utilizar este
modificador:
El Modificador short
Similar al anterior, pero indica que se deben utilizar menos bits. Por ejemplo, en un
computador de 32 bits, un short int ocupa 16 bits. c++ o java
El Modificador unsigned
El Modificador register
Este modificador sobre una variable le indica al compilador que la variable debe
almacenarse en un registro en el compilador, que para el caso de los IA32, es un registro
real de la propia CPU, y por tanto el tiempo de acceso es más rápido respecto a la memoria
RAM. Hoy en día apenas se utiliza este modificador, ya que los compiladores son capaces
de determinar de manera óptima la asignación de registros a variables del programa.
El Modificador volatile
Dependiendo del entorno donde se declare la variable que la modifiquemos como static,
puede significar dos cosas muy distintas:
1. Si declaramos una variable static dentro del cuerpo de una función, lo que estamos
indicándole al compilador es que dicha variable sea inicializada solo una vez (la primera
vez que se llama a la función), y el resto de veces que se llame a la función, la variable
contendrá el último valor asignado. Esta variable sólo podrá ser visible desde la función
que declara dicha variable. Por ejemplo:
void mifuncion(){
static int i=0;
cout<<"En la entrada i vale "<<i<<endl;
for(int j=0;j<10;j++)
i++;
cout<<"En la salida i vale "<<i<<endl;
}
1. Si declaramos una variable static fuera del cuerpo de una función, lo que le estamos
indicando al compilador, es que dicha variable es privada para el modulo donde se
implementa el código del contexto de la variable, es decir, que otro fichero objeto binario,
no podrá tener acceso a dicha variable utilizando el modificador extern. Ejemplo:
#import "prueba.h"
void mifuncion(){
cout<<"En la entrada i vale "<<i<<endl;
for(int j=0;j<10;j++)
i++;
cout<<"En la salida i vale "<<i<<endl;
}
Tenga en cuenta que para este último caso, usted podrá acceder a la variable y desde
cualquier función que defina dentro de prueba.cpp, pero no tendrá acceso desde cualquier
fichero objeto o fuente que no sea prueba.cpp
El espacio en bits que ocupan en la computadora una variable de este tipo se puede ver en
la siguiente tabla:
char 8
short 16
int 32
long int 64
float 32
double 64
El rango que puede almacenar los tipos primitivos en C++ es muy importante, para poder
saber cual es el rango de valores que puede almacenar un tipo es necesario conocer el
número de bits del tipo. El caso para enteros y para flotantes es distinto. Para enteros se
debe saber si el tipo es con signo o sin signo. si es sin signo el rango de valores que puede
almacenar es el siguiente:
Para ilustrar lo anterior supongamos que tenemos un entero de 16 bits sin signo, entonces el
rango de valores que puede almacenar es el siguiente:
Para obtener el rango de un entero de 32 bits con signo se puede realizar el siguiente
calculo:
El caso de los números flotantes es distinto y depende en gran manera del compilador y el
procesador que este utilizando. Sin embargo hoy en día la mayoría de los compiladores y
los procesadores utilizan en estándar de la IEEE para representación en coma flotante. Para
saber mas al respecto ver IEEE floating-point standard.
Ejemplo 1
En el ejemplo anterior se define por medio de enum el tipo enumerado dias, en el mismo
ejemplo se debe observar que dentro de la construcción se definen las constantes
simbólicas: domingo, lunes, ... sabado; y que a las mismas el compilador les asignará
respectivamente y por defecto los valores: 0, 1, 2, 3, 4, 5, 6. Es decir, las constantes
mencionadas pueden usarse dentro del programa y este sabrá a que valor hace referencia
cada una de las mismas. Por ejemplo, la instrucción: cout << domingo; desplegará 0 en la
pantalla. El siguiente ejemplo muestra como usar las constantes enumeradas en un ciclo for.
Ejemplo 2
Luego, podremos declarar variables, constantes y funciones del tipo entero. Por ejemplo,
Un uso más útil y común es el empleo de typedef para definir datos estructurados. Por
ejemplo, supongamos que deseamos definir un tipo estructurado llamado persona y que
contenga nombre, edad y sexo. Entonces podemos definir persona como:
Una vez que un tipo ya se ha definido, el mismo puede emplearse para declarar variables y
constantes de este. Por ejemplo, con la instrucción:
persona hombre;
se está declarando la variable hombre del tipo persona. Luego, para acceder a cada
elemento de la variable hombre usaremos un mecanismo conocido como direccionamiento
directo por medio del carácter de punto ( . ). Por ejemplo, la edad de hombre se debe
indicar como:
hombre.edad
Variables y constantes
Una variable, como su nombre lo indica, es un determinado objeto cuyo valor puede
cambiar durante el proceso de una tarea específica. Contrario a una variable, una constante
es un determinado objeto cuyo valor no puede ser alterado durante el proceso de una tarea
específica. En C, C++ para declarar variables no existe una palabra especial, es decir, las
variables se declarán escribiendo el tipo seguido de uno o más identificadores o nombres de
variables. Por otro lado, para declarar constantes existe la palabra reservada const, así como
la directiva #define. A continuación se muestran ejemplos de declaración de variables y
constantes.
#define a
int a; const int a = 100;
100
Notas:
A diferencia de las constantes declaradas con la palabra const los símbolos definidos con
#define no ocupan espacio en la memoria del código ejecutable resultante.
El tipo de la variable o constante puede ser cualquiera de los listados en Tipos primitivos, o
bien de un tipo definido por el usuario.
Las constantes son usadas a menudo con un doble propósito, el primero es con el fin de
hacer más legible el código del programa, es decir, si se tiene (por ejemplo) la constante
numerica 3.1416 y esta representa al número pi, entonces podemos hacer declaraciones
tales como:
#define pi 3.1416
o bien,
const pi = 3.1416;
Sentencias de decisión
Estructura de control IF
DEFINICIÓN
Sentencia if
La instrucción if es, por excelencia, la más utilizada para construir estructuras de control de
flujo.
SINTAXIS
Primera Forma
if (condicion)
{
Set de instrucciones
}
siendo "condicion" el lugar donde se pondrá la condición que se tiene que cumplir para que
sea verdadera la sentencia y así proceder a realizar el "set de instrucciones" o código
contenido dentro de la sentencia.
Segunda Forma
Ahora veremos la misma sintaxis pero ahora le añadiremos la parte "Falsa" de la sentencia:
if (condicion)
{
Set de instrucciones //PARTE VERDADERA
}
else
{
Set de instrucciones 2 //Parte FALSA
}
else
{
Set de instrucciones 2 //Parte FALSA
}
Ejemplo 1:
if(numero == 0) //La condicion indica que tiene que ser igual a Cero
{
cout<<"El Numero Ingresado es Igual a Cero";
}
Ejemplo 2:
if(numero > 0) // la condicion indica que tiene que ser mayor a Cero
{
cout<<"El Numero Ingresado es Mayor a Cero";
}
Ejemplo 3:
if(numero < 0) // la condicion indica que tiene que ser menor a Cero
{
cout<<"El Numero Ingresado es Menor a Cero";
}
Ahora uniremos todos estos ejemplos para formar un solo programa mediante la utilización
de la sentencia "Else" e introduciremos el hecho de que se puede escribir en este espacio
una sentencia if ya que podemos ingresar cualquier tipo de código dentro de la sentencia
escrita después de un Else.
Ejemplo 4:
if(numero == 0) //La condicion indica que tiene que ser igual a Cero
{
cout<<"El Numero Ingresado es Igual a Cero";
}
else
{
if(numero > 0) // la condicion indica que tiene que ser mayor a
Cero
{
cout<<"El Numero Ingresado es Mayor a Cero";
}
else
{
if(numero < 0) // la condicion indica que tiene que ser menor a
Cero
{
cout<<"El Numero Ingresado es Menor a Cero";
}
}
}
Sentencia switch
Notas: cualquier número de casos a evaluar por switch así como la sentencia default son
opcionales. La sentencia switch es muy útil en los casos de presentación de menus.
Sintaxis:
switch (condición)
{
case primer_caso:
bloque de instrucciones 1
break;
case segundo_caso:
bloque de instrucciones 2
break;
case caso_n:
bloque de instrucciones n
break;
Ejemplo 1
switch (numero)
{
case 0: cout << "numero es cero";
}
Ejemplo 2
switch (opcion)
{
case 0: cout << "Su opcion es cero"; break;
case 1: cout << "Su opcion es uno"; break;
case 2: cout << "Su opcion es dos";
}
Ejemplo 3
switch (opcion)
{
case 1: cout << "Su opcion es 1"; break;
case 2: cout << "Su opcion es 2"; break;
case 3: cout << "Su opcion es 3"; break;
default: cout << "Elija una opcion entre 1 y 3";
}
Si se cumple tal condición entonces haz esto, de lo contrario haz esto otro.
Sintaxis:
Ejemplo 1
int edad;
cout << "Cual es tu edad: ";
cin >> edad;
cout << ( (edad < 18) ? "Eres joven aun" : "Ya tienes la mayoría de edad"
);
int edad;
cout << "Cual es tu edad: ";
cin >> edad;
if (edad < 18) cout << "Eres joven aun";
else cout << "Ya tienes la mayoría de edad";
Ejemplo 2
Vamos a suponer que deseamos escribir una función que opere sobre dos valores numéricos
y que la misma ha de regresar 1 (true) en caso de que el primer valor pasado sea igual al
segundo valor; en caso contrario la función debe retornar 0 (false).
Sentencias de iteración
DEFINICIÓN
Las Sentencias de Iteración o Ciclos son estructuras de control que repiten la ejecución de
un grupo de instrucciones. Básicamente, una sentencia de iteración es una estructura de
control condicional, ya que dentro de la misma se repite la ejecución de una o más
instrucciones mientras que una a condición especifica se cumpla. Muchas veces tenemos
que repetir un número definido o indefinido de veces un grupo de instrucciones por lo que
en estos casos utilizamos este tipo de sentencias. en C++ los ciclos o bucles se construyen
por medio de las sentencias for, while y do - while. La sentencia for es útil para los casos
en donde se conoce de antemano el número de veces que una o más sentencias han de
repetirse. Por otro lado, la sentencia while es útil en aquellos casos en donde no se conoce
de antemano el número de veces que una o más sentencias se tienen que repetir.
Sentencias For
for(contador; final; incremento)
{
Codigo a Repetir;
}
donde:
1. contador es una variable numérica
2. final es la condición que se evalua para finalizar el ciclo (puede ser independiente
del contador)
3. incremento es el valor que se suma o resta al contador
Hay que tener en cuenta que el "for" evalua la condición de finalización igual que el while,
es decir, mientras esta se cumpla continuaran las repeticiones.
Ejemplo 1:
Esto indica que el contador "i" inicia desde 1 y continuará iterando mientras i sea menor o
igual a 10 ( en este caso llegará hasta 10) e "i++" realiza la sumatoria por unidad lo que
hace que el for y el contador se sumen. repitiendo 10 veces "HOLA MUNDO" en pantalla.
Ejemplo 2:
Este ejemplo hace lo mismo que el primero, salvo que el contador se inicializa a 10 en lugar
de 1; y por ello cambia la condición que se evalua así como que el contador se decrementa
en lugar de ser incrementado.
Ejemplo 3:
int j = 20;
for(int i=0; j>0; i++){
cout<<"Hola"<<i<<" - "<<j<<endl;
j--;
}
En este ejemplo las iteraciones continuaran mientras j sea mayor que 0, sin tener en cuenta
el valor que pueda tener i.
Sentencia while
while(condicion)
{
código a Repetir
}
donde:
Ejemplo 1:
int contador = 0;
while(contador<=10)
{
contador=contador+1;
cout<<"Hola Mundo";
}
El contador Indica que hasta que este llegue a el total de 10 entonces se detendrá y ya no se
realizará el código contenido dentro de la sentencia while, de lo contrario mientras el
"contador" sea menor o igual a 10 entonces el código contenido se ejecutará desplegando
hasta 11 veces "Hola Mundo" en pantalla.
Sentencia do - while
La sentencia do es usada generalmente en cooperación con while para garantizar que una o
más instrucciones se ejecuten al menos una vez. Por ejemplo, en la siguiente construcción
no se ejecuta nada dentro del ciclo while, el hecho es que el contador inicialmente vale
cero y la condición para que se ejecute lo que está dentro del while es "mientras el contador
sea mayor que diez". Es evidente que a la primera evaluación hecha por while la condición
deja de cumplirse.
int contador = 0;
int contador = 0;
do
{
contador ++;
cout<<"Hola Mundo";
}
while(contador > 10);
Observe cómo en el caso de do la condición es evaluada al final en lugar de al principio del
bloque de instrucciones y, por lo tanto, el código que le sigue al do se ejecuta al menos la
primera vez.
break
La sentencia break se usa para forzar un salto hacia el final de un ciclo controlado por for o
por while.
Ejemplo:
En el siguiente fragmento de código la sentencia break cierra el ciclo for cuando la variable
( i ) es igual a 5. La salida para el mismo será:
01234
for (int i=0; i<10; i++) {
if (i == 5) break;
cout << i << " ";
}
continue
La sentencia continue se usa para ignorar una iteración dentro de un ciclo controlado por
for o por while.
Ejemplo:
012346789
for (int i=0; i<10; i++) {
if (i == 5) continue;
cout << i << " ";
}
Los dos ejemplos anteriores se presentan en seguida, salvo que en lugar de for se hace uso
de while.
Nota: no deje de observar que la construcción del ciclo while para el caso de la sentencia
continue es diferente, esto para garantizar que el ciclo no vaya a caer en una iteración
infinita.
break
int i = 0;
while (i<10) {
if (i == 5) break;
cout << i << " ";
i++;
}
continue
int i = -1;
while (i<10) {
i++;
if (i == 5) continue;
cout << i << " ";
}
Estructuras de datos
Las estructuras de datos se emplean con el objetivo principal de organizar los datos
contenidos dentro de la memoria del ordenador. Así, nuestra primera experiencia con
estructuras comienza desde el momento mismo en que usamos en nuestros programas
variables de tipos primitivos (char, short, int, float, etc). A la memoria del ordenador se le
puede considerar como un gran bloque compuesto por una serie de BYTES dispuestos
secuencialmente uno detrás de otro. por ejemplo, si un ordenador posee una memoria de
128MB (128 megas) entonces se le puede leer o escribir desde el BYTE 0 hasta el BYTE
128MB - 1 ( 0000000H .. 7FFFFFFH ).
La idea de ver la memoria como un serie de bytes es buena, sin embargo no es suficiente ya
que en la misma podemos guardar números, cadenas de caracteres, funciones, objetos, etc.
de tal manera que surge la necesidad de establecer los mecanismos adecuados para dar
cuenta de la forma, tamaño y objetivo de los datos almacenados. Según el tipo de
microprocesador, estos tienen la capacidad para manipular o direccionar estructuras
compuestas por uno, dos, cuatro, etc, bytes; de donde se derivan los tipos que comunmente
se conocen como: BYTE, WORD, DWORD, QWORD y TWORD.
En C,C++ existe una serie de estructuras básicas o tipos primitivos, los cuales pueden ser
usados por el programador para declarar variables, y también son el fundamento sobre el
cual se crean estructuras complejas. El tamaño de los tipos primitivos no es estándar ya que
los mismos dependen de factores tales como:
Nota: en el lenguaje C,C++ existe el operador sizeof(), con el cual se puede obtener el
tamaño (número de bytes) ocupados por un tipo específico. Por ejemplo, sizeof(int) regresa
el número de bytes ocupados por los datos de tipo int.
variables
En C, C++ la sintaxis para declarar variables es:
donde, tipo se refiere a uno de los tipos mostrados en la tabla anterior; id1 es el nombre con
el cual se identificará a la variable. Observe que si se quiere declarar más de una variable en
una línea de instrucción, las mismas deben separarse por medio de una coma.
Ejemplos:
De acuerdo con la tabla anterior y según las instrucciones anteriores, con la primera, o sea (
char c;), se le está indicando al ordenador que reserve en la memoria un espacio de tipo
char (8 bits) y que el mismo será identificado bajo el nombre de c. La segunda instrucción (
int i;) le indica al ordenador que reserve en la memoria un espacio de tipo int (16 bits) y que
el mismo será identificado bajo el nombre de i. Luego, la instrucción ( float f;) le indica al
ordenador que reserve en la memoria un espacio de tipo float (32 bits) y que el mismo será
identificado bajo el nombre de f. Por último, se le indica al compilador que reserve espacio
para otras tres variables enteras identificadas como: x, y, z. Así, se puede dar cuenta cómo
los tipos primitivos sirven con el propósito de estructurar los datos dentro de la memoria y
con la idea de referirnos a los mismos mediante nombres usamos identificadores de
variables.
Matrices o Arreglos
Una Matriz (en inglés, array, también denominado arreglo) es una
estructura usada para agrupar bajo un mismo nombre
listas de datos de un mismo tipo.
El tipo de matriz puede ser cualquiera, sin embargo cada componente tiene que ser del
mismo tipo. En C estándar solamente da soporte para matrices estáticas, mientras que con
C++ se pueden crear matrices dinámicas pudiendo usar la librería estándar de plantillas
(STL).
Matrices estáticas
donde,
tipo se refiere al tipo de datos que contendrá la matriz. El tipo puede ser cualquiera de los
tipos estándar (char, int, float, etc.) o un tipo definido por el usuario. Es más, el tipo de la
matriz puede ser de una estructura creada con: struct, union y class.
lista de inicialización es opcional y se usa para establecer valores para cada uno de los
componentes de la matriz. Si la matriz es declarada con un tamaño especifíco, el número
de valores inicializados no podrá ser mayor a dicho tamaño.
Ejemplos:
int intA[5];
long longA[5] = { 1, 2, 3, 4, 5 };
char charA[] = { 'a', 'b', 'c' };
En orden de acceder a los miembros de una matriz se debe indicar el nombre de la matriz
seguido de dos corchetes, dentro de los cuales se debe especificar el índice del elemento
deseado. Se debe aclarar que los índices son números o expresiones enteras y que en C,
C++ estos tienen un rango permitido de 0 a T-1 ( T = tamaño de la matriz ).
Matrices dinámicas
Una matriz dinámica es una estructura compleja y, ya que C estándar no da el soporte para
operar con estos tipos de estructuras, le corresponde al programador crear los algoritmos
necesarios para su implementación. Crear lista dinámicas de datos en C estándar no es una
tarea para programadores inexpertos, ya que para lograr tal objetivo se necesita tener
conocimentos solidos acerca de los punteros y el comportamiento de los mismos. Los
usuarios de C++ pueden auxiliarse de la librería estándar de plantillas, conocidas por sus
siglas en ingles como STL.
struct: esta orden se emplea para agrupar variables de tipos iguales o diferentes en un
solo registro, con la misma se pueden crear estructuras anónimas, estructuras con
nombre, y un tipo especial de estructura conocida como bit-fields ( banderas o campos de
bits ).
union: una union es similar a una struct, salvo que en una estructura creada con union los
campos o variables comparten una dirección de almacenamiento común.
class: una clase es una estructura en donde se agrupan variables y funciones, la misma es
usada en Programación Orientada al Objeto. Las clases no son soportadas por el C
estándar.
Nota: tanto las estructuras como las uniones y las clases pueden ser
anónimas, pero lo más
recomendable es darle a las mismas un nombre. Si una estructura, union o
clase posee
nombre, esta pueden ser empleadas para declarar variables de la misma y,
lo más importante,
puede ser usada para el paso de parámetros a funciones.
De acuerdo con la sintaxis general de la orden struct es posible crear estructuras de datos
anónimas. Solamente hay que tener en cuenta que en una declaración anónima se debe
definir al menos una variable al final de la declaración. Por ejemplo, con el siguiente
fragmento de código:
se declara y define la variable estructurada p1, misma que se compone por los miembros a
y b; ambos del tipo int. Ahora bien, la sintaxis mostrada arriba no es tan común ni
conveniente, ya que con la misma solamente se esta creando una variable estructurada pero
no un nuevo tipo. Es decir, si desearamos tener otra variable que tuviera las mismas
caracteristicas que posee la variable p1, necesitariamos escribir exactamente la misma
instrucción, salvo que cambiando el nombre de la variable. Por ejemplo:
Por supuesto, en una misma línea de instrucción podemos definir más de una variable.
Ejemplo:
Entonces, para crear nuevos tipos con struct deberemos de modificar la sintaxis mostrada
en los ejemplos anteriores.
Ejemplos:
Una vez que una estructura con nombre ha sido creada, la misma puede ser usada para
declarar cualquier número de variables. Por ejemplo, en el siguiente fragmento de código se
crea la estructura tiempo compuesta por los miembros hora, minuto y segundo; todos del
tipo int. En el mismo ejemplo, se declaran las variables t1 y t2.
Nota: en C++ puede obviarse la palabra struct a la hora de declarar variables. Así, en C++
la línea de instrución struct tiempo t1, t2; ( del ejemplo anterior) puede escibirse como:
tiempo t1, t2;
En orden de poder leer o escribir uno de los miembros de una variable estructurada, se debe
usar el operador de acceso ( . ); o sea, el nombre de la variable seguida por un punto
seguido por el nombre del miembro o componente deseado de la estructura. Por ejemplo,
para acceder a los miembros de la variable t1 (mostrada arriba) podemos hacerlo de la
siguiente manera:
t1.hora = 12;
t1.minuto = 0;
t1.segundo = 0;
Estructuras anidadas
Los miembros de una estructura pueden ser ellos mismos, otra estructura previamente
identificada, o bien una estructura anónima. Por ejemplo, en el siguiente fragmento de
código, se crean las estructuras pareja y pareja2. Obsérvese cómo dentro de los miembros
de pareja2, se declara el miembro X, que a su vez es una estructura del tipo pareja. Luego,
las variables declaradas a raíz de la estructura pareja2 poseerán los miembros variables a y
b heredados de pareja, y c.
Ahora bien, para acceder a los miembros de una estructura dentro de otra estructura se
emplea el mismo mecanismo de acceso (el punto). Por ejemplo, para desplegar el miembro
a de la variable P3 declarada en el ejemplo anterior, lo haremos más o menos así:
Herencia
Por ejemplo, en la definición de las estructuras pareja y pareja2 del ejemplo anterior, se
dice que pareja2 hereda por composición todos los miembros de pareja. Ahora, en el
siguiente ejemplo se usa la sintaxis para que la estructura pareja2 herede por extensión los
miembros de pareja:
// solo C++
struct pareja { int a, b ; };
struct pareja2 : pareja { int c; } P3;
Con esta forma de herencia, la estructura pareja2 hereda de pareja los miembros a y b, y
además agrega un miembro c. Y a diferencia del ejemplo anterior, para acceder a alguno de
sus miembros heredados, basta con utilizar el mecanismo de acceso (el punto).
// solo C++
cout << P3.a << P3.b ;
Antes de ver un ejemplo del uso de struct para crear estructuras de campos de bits,
consideremos el caso en donde se tiene una variable del tipo short (16 bits) y que para la
misma se desea que los bits tengan significados específicos. Digamos que el primer bit
servirá para controlar alguna condición; los siguientes cuatro bits, o sea del segundo al
quinto bit, controlarán otra condición; el bit 6 tendrá otra función; y el resto, o sea del
séptimo al decimosexto bit se emplearán para controlar otra condición. De tal manera que si
queremos, por ejemplo, saber si el primer bit de la variable tiene almacenado un 1 o un 0,
podemos emplear la siguiente sintaxis:
int X = 123;
int r = X & 1;
la cosa parece sencilla, pero ahora consideremos el caso en el cual deseamos saber cual es
el valor contenido por el grupo de bits ( segundo al quinto ), entonces nos daremos cuenta
que no basta con una prueba mediante AND ( X & 1 ), sino que hay que realizar otros
pasos.
Precisamente, para problemas como el planteado arriba es por los que los lenguajes C y
C++ soportan las estructuras de campos de bits. Por ejemplo, la estructura
struct campo_de_bit {
int bit_1 : 1;
int bits_2_a_5 : 4;
int bit_6 : 1;
int bits_7_a_16 : 10;
} bit_var;
bit_var.bit_1 = 1;
printf("%i\n", bit_var.bit_1 );
Nota: acerca de las estructuras de campos de bits hay que aclarar que, aunque cada uno de
los campos de la estructura pueden declararse como enteros con signo o enteros sin signo,
la misma no tendrá una longitud mayor a un entero largo.
De la misma manera que con la orden struct, con la orden union se pueden crear
estructuras con nombre y estructuras sin nombre.
En el ejemplo anterior se declara la variable u1, la cual es una estructura tipo union. El
espacio de almacenamiento para la variable a es compartido por la variable b, en
consecuencia, al escribir sobre cualquiera de estas dos variables se altera el contenido de
ambas.
En el ejemplo anterior se declara la variable u1, la cual es una estructura tipo union. El
espacio de almacenamiento para la variable a es compartido por la variable b. Es decir, el
compilador reservará espacio en la memoria para la variable de mayor tamaño (que para
éste caso es b ). Ahora bién, suponiendo que en su equipo el tipo long ocupa 32 bits y que
el tipo short ocupa 16 bits, entonces la variable a ocupará solamente los 16 bits menos
significativos, mientras que la variable b ocupará todo el espacio, o sea los 32 bits; Observe
que en la sintaxis se ha especificado el nombre ux, mismo que puede ser empleado para
declarar cualquier número de variables de la union. Por ejemplo, a continuación se declaran
las variables u2 y u3 del tipo union ux creado en el ejemplo anterior.
class: sintaxis
<classkey> <classname> [<:baselist>] { <member list> } [lista de
variables] ;
El mecanismo para acceder a los miembros de una class es igual que aquel utilizado para
acceder a los miembros de una struct
Las clases son algo así como "super" estructuras capaces de agrupar no solo datos
miembros sino también funciones miembros. En el lenguaje común a los datos miembros
de una clase se les conoce como atributos; mientras que a las funciones miembros de una
clase se les llama métodos. Normalmente, los métodos se emplean para leer o escribir los
atributos. Es decir, la norma general es no permitir el acceso directo a los atributos de una
clase, con la idea de aumentar la seguridad de los datos.
En seguida se mostrará el código para crear la clase Pareja, misma que poseerá los atributos
a y b, y los métodos setA(), setB(); getA(), getB(), y mostrar();
class Pareja {
int a, b;
public:
void setA(int n) { a = n; }
void setB(int n) { b = n; }
int getA() { return a; }
int getB() { return b; }
void mostrar() {
cout << "a = " << a << "; b = " << b << endl;
}
} p1;
Nota: por omisión, los miembros de una clase son privados, lo cual significa que los
objetos instanciados de dicha clase no tendrán acceso a los mismos. Así, en el ejemplo
anterior se está creando la clase Pareja, y al mismo tiempo el objeto p1. Luego, para leer o
escribir los atributos de p1 se debe hacer a traves de los métodos definidos con tal objetivo.
Por ejemplo, con el siguiente fragmento de código se establecen respectivamente a 100 y a
200 los atributos a y b; y posteriormente se despliegan por medio del método mostrar().
p1.setA(100);
p1.setB(200);
p1.mostrar();
Los miembros de una struct son públicos por defecto, mientras que los miembros de una
class son privados por defecto.
Los parámetros-argumentos struct se pasan normalmente por copia, los parámetros-
argumentos class se pasan normalmente por referencia.
// Este programa ha sido probado en Dev-C++, Borland C++ y Code::Blocks
#include <iostream>
int a, b;
// constructor base
Par() { a = b = 0; }
// destructor base
~Par() { cout << "hecho..." << endl; }
void setA(int n) { a = n; }
void setB(int n) { b = n; }
void mostrar() {
cout << "a = " << a << ", b = " << b << "; suma = " << a+b <<
endl;
}
};
// prueba
void test00() {
ParHijo p1(100, 200); // p1 es instancia de ParHijo
p1.mostrar(); // se envía mensaje al método mostrar() de p1
}
// funcion principal
int main()
{
test00();
cin.get();
return 0;
}
Funciones
Una función es un conjunto de líneas de código que realizan una tarea específica y puede
retornar un valor. Las funciones pueden tomar parámetros que modifiquen su
funcionamiento. Las funciones son utilizadas para descomponer grandes problemas en
tareas simples y para implementar operaciones que son comúnmente utilizadas durante un
programa y de esta manera reducir la cantidad de código. Cuando una función es invocada
se le pasa el control a la misma, una vez que esta finalizó con su tarea el control es devuelto
al punto desde el cual la función fue llamada.
Para comenzar, vamos a considerar el caso en el cual se desea crear la función cuadrado(),
que deberá devolver el cuadrado de un número real (de punto flotante), es decir, cuadrado()
aceptará números de punto flotante y regresará una respuesta como número flotante.
Nota: aunque para la función que veremos el tipo de retorno coincide con el tipo de
parámetro pasado, algunas veces las cosas pueden cambiar, es decir, no es obligatorio que
una función reciba un parámetro de un tipo y que tenga que regresar una respuesta de dicho
tipo.
// regresar el cuadrado de un número
double Cuadrado(double n)
{
return n*n;
}
Parámetros
Normalmente, las funciones operan sobre ciertos valores pasados a las mismas ya sea como
constantes literales o como variables, aunque se pueden definir funciones que reciban
parámetros. Existen dos formas en C++ de pasar parámetros a una función; por referencia o
por valor. El hecho es que si en una declaración de función se declaran parámetros por
referencia, a los mismos no se les podrá pasar valores literales ya que las referencias
apuntan a objetos (variables o funciones) residentes en la memoria; por otro lado, si un
parámetro es declarado para ser pasado por valor, el mismo puede pasarse como una
constante literal o como una variable. Los parámetros pasados por referencia pueden ser
alterados por la función que los reciba, mientras que los parámetros pasados por valor o
copia no pueden ser alterados por la función que los recibe, es decir, la función puede
manipular a su antojo al parámetro, pero ningún cambio hecho sobre este se reflejará en el
parámetro original.
Para mostrar un ejemplo del paso de parámetros por referencia, vamos a retomar el caso de
la función cuadrado, salvo que en esta ocasión cambiaremos ligeramente la sintaxis para
definir la misma. Veamos:
Al poner a prueba las funciones cuadrado() y cuadrado2() se podrá verificar que la primera
de estas no cambia el valor del parámetro original, mientras que la segunda sí lo hace.
Funciones void
Bajo ciertas circunstancias se deseará escribir funciones que no regresen valor alguno (esto
sería algo parecido a escribir procedures en Pascal) y para ello podemos declarar a la
función como void. La palabra reservada void es utilizada para declarar funciones sin valor
de retorno y también para indicar que una función específica no requiere de parámetros. Por
ejemplo, la función pausa() que se verá en seguida, no devolverá valor alguno y la misma
no requiere de parámetros.
== Funciones anidadas ==
A diferencia de '''Pascal''', el lenguaje C, C++ no permite anidar
funciones, sin embargo, dentro de una funcíon puede existir la llamada a
una o más funciones declaradas previamente, que determinara en cierto
punto un resultado para que sea ejecutado cuando el programador lo desee.
<source lang="cpp">
#include <iostream>
#include <string.h>
// declaración de prototipo
char *binstr(unsigned int);
// punto de prueba
int main()
{
int n = 128;
cout << "decimal = " << n << ", binario = " << binstr(n) << endl;
cin.get();
}
strcpy(buffer, "0");
if (n > 0) {
while (n > 0) {
buffer[i] = ( n & 1 ) + '0';
i++;
n >>= 1;
}
buffer[i] = '\0';
strrev(buffer);
} // fin (n > 0)
return buffer;
}
Parámetros constantes
Los parámetros usados por una función pueden declararse como constantes ( const ) al
momento de la declaración de la función. Un parámetro que ha sido declarado como
constante significa que la función no podrá cambiar el valor del mismo ( sin importar si
dicho parámetro se recibe por valor o por referencia ).
Ejemplo:
Para ver un ejemplo más, vamos a considerar el caso de la función binstr() del programa
funciones01. Ahora, vamos modificar dicha función, salvo que esta ocasión nos interesa
que la misma sirva para convertir números decimales en cadenas numéricas y cuya base de
conversión sea pasada como parámetro. Es decir, la función de la que estamos hablando
podrá convertir números decimales a: binario, octal, decimal, hexadecimal, etc.; y la única
condición será que la base indicada esté entre el 2 y el 36, inclusive.
#include <iostream>
#include <stdlib.h>
// declaración de prototipo
char *numstr(unsigned int, const int base = 10);
// punto de prueba
int main()
{
int n = 128;
cout << "decimal = " << n << ", binario = " << numstr(n, 2) << endl;
cout << "decimal = " << n << ", octal.. = " << numstr(n, 8) << endl;
cin.get();
}
Parámetros estructurados
Al igual que cualquier otro tipo los parámetros de tipo estruturado pueden pasarse por valor
o por referencia, sin embargo, podría ser que si una estructura es pasada por valor el
compilador mostrara una advertencia ( warning ) indicando que se pasado por valor una
estructura, puesto que el paso de estructuras por valor es permitido usted puede ignorar la
advertencia, pero lo mejor es pasar estructuras por referencia. Si una estructura es pasada
por valor y si esta es muy grande podria ser que se agotara la memoria en el segmento de
pila ( Stack Segment ), aparte de que la llamada a la función sería más lenta.
struct empleado {
char nombre[32];
int edad;
char sexo;
};
Ahora, pensemos que deseamos escribir una función para imprimir variables del tipo
empleado. Así, la función puede escribirse de las tres maneras siguientes:
Funciones sobrecargadas
C++, a diferencia del C estándar, permite declarar funciones con el mismo nombre y a esto
se conoce como sobrecarga de funciones. Las funciones sobrecargadas pueden coincidir
en tipo, pero al menos uno de sus parámetros tiene que ser diferente. En todo caso, si usted
trata de declarar funciones sobrecargadas que coincidan en tipo y número de parámetros el
compilador no se lo permitirá. Para poner un ejemplo vamos a considerar el caso de dos
funciones cuyo nombre será divide, ambas regresarán el cociente de dos números, salvo
que una de ellas operará sobre números enteros y la otra lo hará sobre números reales ( de
punto flotante ).
Nota: cuando en los programas se hace una llamada a una función sobrecargada, el
compilador determina a cual de las funciones invocar en base al tipo y número de
parámetros pasados a la función.
#include <iostream.h>
#include <stdlib.h>
// divide enteros
int divide(int a, int b)
{
cout << "división entera" << endl;
return ( (b != 0) ? a/b : 0);
}
// divide reales
double divide(double a, double b)
{
cout << "división real" << endl;
return ( (b != 0) ? a/b : 0);
}
// punto de prueba
int main()
{
cout << divide(10, 3) << endl;
cout << divide(10.0, 3.0) << endl;
cin.get();
}
Número variable de parámetros
En C,C++ se pueden crear funciones que operen sobre una lista variable de parámetros, es
decir, en donde el número de parámetros es indeterminado. En esta sección se mostrará un
ejemplo de la manera en que podemos crear funciones para manejar tales asuntos, y para
ello haremos uso de tres macros soportadas por C++:
La sintaxis que usaremos para declarar funciones con lista de parámetros variables es:
1) tipo nombrefuncion(...)
2) tipo nombrefuncion(int num, ...)
donde:
En el siguiente programa, por ejemplo, se define una función ( printstr ) que despliega una
lista variable de cadenas de caracteres.
#include <iostream.h>
#include <stdarg.h>
int main()
{
printstr("Hola, ", "Esta es\n", "una prueba\n", NULL);
cin.get();
return 0;
}
En el programa que se listará en seguida, se define la función suma(), misma que operará
sobre listas de números enteros, la función devolverá la suma de dichos números.
va_end( argptr );
return( total );
}
int main()
{
cout << suma(4, 100, 200, 300, 400) << endl;
cin.get();
return 0;
}
o
C estandar C++
stdin cin
stdout cout
stderr cerr
--- clog
La iostream
La iostream es la biblioteca estándar en C++ para poder tener acceso a los dispositivos
estándar de entrada y/o salida. En sus programas, si usted desea hacer uso de los objetos
cin, cout, cerr y clog tendrá que incluir ( por medio de la directiva #include ) el uso de la
biblioteca iostream. En la iostream se encuentran definidas las clases ios ( misma que es la
base para las clases que implementen operaciones de entrada y/o salida de datos ), istream
( para operaciones de entrada ) y ostream ( para operaciones de salida ). Aparte de las
clases mencionadas, en la iostream se encuentra una lista de variables y constantes (
atributos ) que son accesibles por el usuario a través del operador de ámbito ( :: ).
Streams automáticos
Si usted usa la directiva #include <iostream.h> o #include <iostream> en sus programas,
automáticamente la iostream pone a su disposición los objetos cin, cout, clog y cerr en el
ámbito estándar (std), de tal manera que usted puede comenzar a enviar o recibir
información a través de los mismos sin siquiera preocuparse de su creación. Asi, un sencillo
ejemplo del uso de los objetos mencionados se muestra en seguida.
Operadores de direccionamiento
Los operadores de direccionamiento son los encargados de manipular el flujo de datos
desde o hacia el dispositivo referenciado por un stream específico. El operador de
direccionamiento para salidas es una pareja de símbolos de "menor que" <<, y el operador
de direccionamiento para entradas es una pareja de símbolos de "mayor que" >>. Los
operadores de direccionamiento se colocan entre dos operandos, el primero es el Stream y
el segundo es una variable o constante que proporciona o recibe los datos de la operación.
Por ejemplo, en el siguiente programa y en la instrucción cout << "Entre su nombre: "; la
constante "Entre su nombre: " es la fuente o quien proporciona los datos para el objeto
cout. Mientras que en la instrucción cin >> nombre la variable nombre es el destino o quien
recibe los datos provenientes del objeto cin.
Observe que si en una misma línea de comando se desea leer o escribir sobre varios campos
a la vez, no es necesario nombrar más de una vez al stream. Ejemplos:
Banderas de I/O
En esta sección abordaremos de manera más directa el tema sobre el control de formato
para los stream de C++. Específicamente, veremos las tres diferentes formas que existen en
C++ para manipular las banderas relacionadas a los stream y que nos permitirán gobernar
de una manera más precisa la forma para representar datos de salida y/o entrada. En ese
sentido, veremos que la primera de las forma que nos permitirá el formateo sera a través de
las funciones flags(), setf() y unsetf(), la segunda y la tercera forma las encontraremos en
ciertos manipuladores directos definidos en las bibliotecas <iostream> y <iomanip>.
Banderas de formato:
C++ define algunas banderas de formato para entradas y salidas estándar, las cuales pueden
ser manipuladas a través de la funciones (métodos) flags(), setf(), y unsetf(). Por ejemplo,
cout.setf(ios::left);
activa la justificación a la izquierda para todas las salidas dirigidas hacia cout.
Bandera Descripción
boolalpha Los valores booleanos pueden ser leídos/escritos usando las palabras "true" y "false"
Manipulando la lista de banderas de I/O de C++ (mostrada arriba) se pueden controlar los
aspectos relacionados a la forma con la cual se desean presentar los datos en la salida. Por
ejemplo, el programa que se muestra en seguida, activa la bandera boolalpha para mostrar
los resultados de operaciones booleanas como "true" o "false" en lugar de "0" o "1" como
es lo normal.
#include <iostream>
cin.get();
return 0;
}
Manipuladores
#include <iostream>
cin.get();
return 0;
}
Por último, y para terminar esta sección, hablaremos de los manipuladores definidos en la
biblioteca <iomanip>. Estos operan directamente igual que los que se vierón
anteriormente, salvo que son parametrizados, es decir, operan en línea de salida mediante el
operador <<, pero los mismos operan a manera de funciones, o sea con parámetros.
Atendiendo a las tablas de banderas, así como a las tablas de manipuladores para las
mismas mostradas arriba, usted puede (con practica y perceverancia) lograr salidas con
muy buena presentación hacia los dispositivos estándar. Por ejemplo, el programa que se
mostrará a continuación muestra una de las formas de emplear manipuladores para
formatear números de punto flotante, en el programa se despliega una lista de valores
numéricos y se establece el campo de salida a una longitud de 12 caracteres y dos
decimales.
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
double data[] = { 347.25, 45.75, 124.50, 456.80, 1500.90 };
double total = 0;
int ancho = 12;
cout.precision(2);
cout.setf(ios::fixed);
Por definición, un archivo es una colección de datos almacenados en algún lugar. En C++,
el programador puede considerar que un archivo es un stream, de tal manera que los
objetos vistos en la sección anterior ( cin, cout, cerr y clog ) son una especie de archivo
manipulados por streams. Este punto es de suma importancia, ya que todo lo que se ha
mencionado acerca de los atributos, banderas y manipuladores; que son miembros de los
objetos ( stream ) para entrada y salida estándar, son aplicables a los streams que usemos
para trabajar con archivos en disco y/o cualquier otro dispositivo de almacenamiento. Hasta
este momento, solamente se ha mencionado que los streams poseen banderas y que las
mismas pueden alterarse a traves de los manipuladores, sin embargo, hace falta hablar
acerca de los métodos o funciones que son miembros de dichos streams. Así, antes de ver
un ejemplo para mostrar el uso de archivos en disco listaremos una tabla más, o sea, la de
los métodos aplicables a los streams.
Función Descripción
Observe que la sintaxis para crear objetos de las tres clases mostradas arriba es la misma.
Es decir,
1. El primer método constructor crea un objeto que no está (aún) asociado a un archivo en
disco, en estos casos, se tendrá que usar el método stream.open("nombre de archivo");
para establecer la conexión entre el stream y el archivo en disco.
2. El segundo método constructor crea un objeto asociado a un archivo en disco, en estos
casos, el archivo en el disco queda abierto y asociado al stream. En este, el parámetro char
* apunta a una cadena de caracteres con el nombre del archivo.
3. El tecer método crea un stream a raiz de un identificador de archivo.
4. El cuarto método crea un stream a raiz de un identificador de archivo, un buffer y tamaño
de buffer específicos.
Para nuestro primer ejemplo usaremos el segundo de los constructores mencionados. Así, el
programa que se muestra en seguida realiza las siguientes tareas:
#include <iostream>
#include <fstream>
#include <string.h>
fichero.close();
cout << "archivo creado exitosamente" << endl;
return 0;
}
// lectura de datos
while ( ! fichero.eof() ) cout << (char)fichero.get();
fichero.close();
cout << endl << "archivo leido exitosamente" << endl;
return 0;
}
int main()
{
crearArchivo(filename);
leerArchivo(filename);
cout << endl << "Presione <Enter>...";
cin.get();
return 0;
}
No hay que olvidar que si un archivo es asociado a un stream las operaciones de entrada y/o
salida para dicho archivo se hacen a travez del stream y, por lo tanto, se puede usar, no solo
los operadores de redirección, sino también todos los metodos, manipuladores y atributos
que se ha mencionado hasta este momento. Así, el objetivo del siguiente programa es
demostrar como hacer uso del operador << sobre un archivo asociado a un stream de salida,
y del operador >> sobre un archivo asociado a un stream de entrada. El ejemplo que se
mostrará no es realmente útil, sin embargo cumple con su cometido, es decir, el punto de
interes del mismo está en las líneas de código:
while ( ! fin.eof() ) {
fin >> temp;
fout << temp ;
}
Encargadas de leer datos del archivo de entrada y de escribir los mismos en el archivo de
salida.
#include <iostream.h>
#include <fstream.h>
// lectura de datos
while ( ! fichero.eof() ) cout << (char)fichero.get();
fichero.close();
cout << endl << "archivo leido exitosamente" << endl;
return 0;
}
char temp;
while ( ! fin.eof() ) {
fin >> temp;
fout << temp ;
}
fin.close();
fout.close();
return 0;
}
int main()
{
backup(filename);
leerArchivo("test.bak");
cout << endl << "Presione <Enter>...";
cin.get();
return 0;
}
Array:
Un array es un conjunto de datos del mismo tipo ordenados de forma líneal uno después de
otro. Los componentes de un array se han de referenciar por medio del nombre del array y
un índice de desplazamiento para indicar el componente deseado.
Indices de un array
Los índices son números que se utilizan para identificar a cada uno de los componentes de
un array. A modo de ejemplo, podemos pensar que los índices son como los números de
habitaciones de un hotel, es decir, para poder dirigirnos a un hotel específico es necesario
saber el nombre del mismo, luego, si queremos llegar a una habitación específica de dicho
hotel necesitaremos, además del nombre del hotel, el número de habitación deseado.
Dimensiones de un array
De acuerdo a la forma en que se construye o declara un array, éste puede ser clasificado
como: unidimensional, bidimensional y multidimensional. Los arrays que se emplean con
mucha más frecuencia son los estructurados a manera de vector ( array unidimensional ) y
los estructurados a manera de matriz ( array bidimensional ), así, aunque en C++ se pueden
crear estructuras multidimensionales, en este capítulo solo trataremos con vectores y
matrices.
Array unidimensional
Una array uni-dimensional es aquel en donde los componentes son accesibles por medio de
uno y solamente un índice que apunte al componente requerido. Los arrays de este tipo son
conocidos también con el nombre de vectores. Conceptualmente, podemos pensar en un
array unidimensional como una lista compuesta de datos, donde para referirnos a uno de
ellos emplearemos un número para indicar la posición del mismo dentro de la lista. Por
ejemplo, consideremos el caso de la tabla o array VentaSemanal, la cual está pensada para
registrar las ventas de cada uno de los días de la semana. De manera conceptual podemos
ver el array como se muestra a continuación:
Nota: en C++ los arrays están basados en 0 ( cero ), es decir, el primer elemento de un
array se indexa mediante el 0, y el índice para el último de los elementos es igual al número
de componentes menos uno.
array: VentaSemanal
+------+
| dato | <-- componente 0, ( fila 0 )
|------|
| dato | <-- componente 1, ( fila 1 )
|------|
| dato | ...
|------|
| dato | ...
|------|
| dato | ...
|------|
| dato | ...
|------|
| dato | <-- componente 6, ( fila 6 )
|------|
array: VentaSemanal
+------+
| dato |
|------|
| dato |
|------|
| dato |
|------|
| dato |
|------|
| 8987 | <--- componente 4
|------|
| dato |
|------|
| dato |
|------|
Array bidimensional
Un array bi-dimensional es aquel en donde los componentes son accesibles por medio de
una pareja de índices que apunten a la fila y a la columna del componente requerido. Los
arrays de este tipo son conocidos también con el nombre de matrices. Conceptualmente,
podemos pensar en un array bidimensional como en una lista compuesta de filas y
columnas, en donde para referirnos a una de ellas emplearemos un número para indicar la
posición de fila y otro número para indicar la posición de la columna del componente
deseado. Por ejemplo, consideremos el caso de la tabla o array VentaSemanaQ, la cual está
pensada para registrar las ventas de cada uno de los días de la semana por cuatro semanas, o
sea, una tabla de 7 x 4 elementos. De manera conceptual podemos ver el array como se
muestra a continuación:
array: VentaSemanaQ
C O L U M N A S
+--- componente ( 0, 0 )
|
+------+------+------+------+
| dato | dato | dato | dato |
|------|------|------|------|
F | dato | dato | dato | dato |
|------|------|------|------|
I | dato | dato | dato | dato |
|------|------|------|------|
L | dato | dato | dato | dato |
|------|------|------|------|
A | dato | dato | dato | dato |
|------|------|------|------|
S | dato | dato | dato | dato |
|------|------|------|------|
| dato | dato | dato | dato |
+------+------+------+------+
|
+---- componente ( 6, 3 )
array: VentaSemanaQ
+--- componente ( 0, 0 )
|
+------+------+------+------+
| dato | dato | dato | dato |
|------|------|------|------|
| dato | dato | dato | dato |
|------|------|------|------|
| dato | dato | dato | dato |
|------|------|------|------|
| dato | dato | dato | dato |
|------|------|------|------|
| dato | dato | dato | 5000 | <-- componente ( 4, 3 )
|------|------|------|------|
| dato | dato | dato | dato |
|------|------|------|------|
| dato | dato | dato | dato |
+------+------+------+------+
|
+---- componente ( 6, 3 )
donde,
tipo se refiere al tipo de datos que contendrá el array. El tipo puede ser cualquiera
de los tipos estándar (char, int, float, etc.) o un tipo definido por el usuario. Es más,
el tipo del array puede ser de una estructura creada con: struct, union y class.
lista de inicialización es opcional y se usa para establecer valores para cada uno de
los componentes del array. Si el array es declarado con un tamaño específico, el
número de valores inicializados no podrá ser mayor a dicho tamaño.
Ejemplos:
int intA[5];
long longA[5] = { 1, 2, 3, 4, 5 };
char charA[3] = { 'a', 'b', 'c' };
El termino Iterar se refiere al hecho de acceder (con el fin de leer o escribir) sobre cada uno
de los componentes de un array. Así, para poner un ejemplo reconsideremos el caso de la
tabla VentaSemanal (vista en una sección anterior), y que dicho sea de paso es un array de
7 elementos de tipo double. Luego, vamos a mostrar como ejemplo un programa completo
en el cual se declara el array mencionado con valores inicializados, que serán mostrados en
pantalla y al final la suma de estos. Observe que la variable i usada para iterar dentro del
array va desde 0 hasta FILAS - 1 ( FILAS es el tamaño del array ).
Nota: por motivos de simplificación el programa está escrito al estilo de C estándar. Sin
embargo puede ser compilado y ejecutado en un compilador de C++.
#include <stdio.h>
#include <stdlib.h>
#define FILAS 7
int main()
{
float ventas[FILAS] = {
123.50, 234.60, 345.45, 321.40, 345.00, 456.65, 0.0 };
float total = 0;
int i;
puts("Ventas de la semana");
puts("-------------------");
puts("--------");
printf("%8.2f\n", total );
system("pause");
return 0;
}
Con el fin de leer o escribir sobre cada uno de los componentes de una matriz se deben
crear dos ciclos de iteración. Así, para poner un ejemplo reconsideremos el caso de la tabla
VentaSemanaQ (vista en una sección anterior), y que dicho sea de paso es un array de 4 x
4 elementos de tipo double. Luego, vamos a mostrar como ejemplo un programa completo
en el cual se declara el array mencionado con valores inicializados, que serán mostrados en
pantalla y al final la suma de estos. Observe que en este caso se utilizan dos variables, una
para iterar sobre las filas y otra para iterar sobre las columnas de la matriz.
#include <stdio.h>
#include <stdlib.h>
#define FILAS 7
#define COLS 4
int main()
{
float VentaSemanaQ[FILAS][COLS] = {
123.50, 234.60, 345.45, 321.40,
345.00, 456.65, 123.50, 234.60,
345.45, 321.40, 345.00, 456.65,
123.50, 234.60, 345.45, 321.40,
345.00, 456.65, 123.50, 234.60,
345.45, 321.40, 345.00, 456.65,
0.0, 0.0, 0.0, 0.0 };
puts("--------------------------------------");
for (t=0; t<COLS; t++) {
printf("%8.2f ", totales[t] );
grantotal += totales[t];
}
Para comenzar y antes de ver algunas de las funciones de la mencionada librería, tenemos
los siguientes ejemplos:
En ambos casos el resultado es el mismo, es decir, al final se obtiene la misma cadena, pero
usted debe poner atención al hecho de que toda cadena de caracteres en C, C++ debe
terminar con el carácter NULL, que normalmente es igual a cero y se puede escribir como
'\0'. Ahora bien, cuando usted usa la sintaxis mostrada en el ejemplo 1 no tiene que
preocuparse por agregar el caracter NULL, ya que esto lo hace el compilador
automáticamente.
La biblioteca string
Ejemplo:
Ejemplo:
Ejemplo:
Ejemplo:
Ejemplo:
p = strchr(nombre, 'E');
if (p) {
cout << "nombre contiene a E" << endl;
cout << "indice = " << (p - nombre) << endl;
}
else cout << "E no está en nombre" << endl;
Ejemplo:
p = strrchr(nombre, 'E');
if (p) {
cout << "nombre contiene a E" << endl;
cout << "indice = " << (p - nombre) << endl;
}
else cout << "E no está en nombre" << endl;
Ejemplo:
Cadenas en C++
En la sección anterior descubrimos algunas funciones para trabajar con cadenas de
caracteres al estilo de C estándar, si bien no está de más tener tal conocimiento, también es
cierto que C++ es un lenguaje de programacíón orientado a objetos, de tal manera que
ciertos compiladores ( como el gcc, utilzado por Bloodshed Dev-C++ y otros tantos
entornos de desarrolo ) dan soporte a la clase cstring, que no debe confundirse con la
<string.h>.
Una de las ventajas que ofrece la clase cstring es que, a diferencia de las cadenas estándar,
ésta posee la capacidad de crecer o disminuir su tamaño en tiempo de ejecución. Además,
entre otras caracteristicas destacables, la clase string soporta operaciones de asignación
tales como: =, +, +=, etc.; y de comparación tales como: ==, <=, etc.
Para tener una idea básica sobre las cadenas en C++ veamos el siguiente programa:
Terminología
Propiedad. Una característica asociada a una clase. Ejemplo, todos los autos tienen
un color, o un registro aunque esta propiedad varíe entre objetos.
Método. Posible acción que los objetos de una clase pueden realizar. En términos
informáticos, un algoritmo que pueden ejecutar. Ejemplo: todos los autos pueden
acelerar, frenar, etc.
Herencia. La clase auto es una clase muy general; se puede hacer un desglose más
pormenorizado, por ejemplo, si hacemos subclases de autos deportivos, utilitarios,
compactos, manuales, automáticos, de lujo, etc. Cada subclase toma de su clase
base (o superclase, en la jerga informática) las propiedades y métodos más
generales y agrega las propias. Así, como todos los autos, tienen color y registro, así
que éstas estarían contenidas en la superclase auto. Propiedades como Capacidad en
toneladas, sistema estéreo o cantidad de velocidades, estarían delegadas a las
subclases más particulares.
Clases y Objetos
Este capítulo introduce a las clases en C++. La clase es la fundación de C++ para el soporte
de la programación orientada a objetos, y se encuentra en el núcleo de muchas de sus más
avanzadas características. La clase es la unidad básica de C++ de la encapsulación y esta
provee el mecanismo por el cual los objetos son creados.
Fundamentos de Clases
Vamos a comenzar definiendo los términos de clase y objeto. Una clase define un nuevo
tipo de dato que especifica la forma de un objeto. Una clase incluye los datos y el código
que operará sobre esos datos. Además, una clase enlaza datos y código. C++ usa una
especificación de una clase para construir objetos. Los objetos son instancias de una clase.
Además, una clase es esencialmente una serie de planes que especifican cómo construir un
objeto. Es importante tener claro esto: Una clase es una abstracción lógica.
No es sino hasta que un objeto de esa clase sea creado que la representación física de la
clase existe en la memoria. Cuando se define una clase, se declaran los datos que ésta
contiene y el código que opera en esos datos. Aunque clases muy simples pueden contener
sólo código o sólo datos, la mayoría de las clases contienen ambos. En conjunto, los datos
se almacenan en las variables y el código en las funciones. Colectivamente, las funciones y
variables que constituyen una clase son llamados 'miembros' de la clase. Una variable
declarada dentro de una clase es llamada 'variable miembro', y una función declarada en
una clase es llamada 'función miembro'. En ocasiones el término 'variable de instancia' es
usado en lugar de variable miembro.
Una clase es creada con la palabra clave class. La declaración de una clase es similar
sintácticamente a una estructura (y tienen muchísimo que ver). Aquí tenemos un ejemplo.
La siguiente clase define un tipo llamado CRender, el cual es usado para implementar
operaciones de renderizado en este caso.
Para hacer pública una parte de la clase (accesible a otras partes del programa), se debe
declarar con la palabra clave public. Todas las variables o funciones definidas después de
la declaración pública son accesibles por todas las demás funciones en el programa. En
nuestra clase CRender, la función m_Renderizar() es pública. Típicamente, su programa
accederá a los miembros privados de una clase a través de sus funciones públicas. Note que
la palabra clave public es seguida con : . Mantenga en mente que un objeto forma una
relación entre código y datos. Una función miembro tiene acceso a los elementos privados
de su clase. Esto significa que m_Renderizar tiene acceso a buffer en nuestro ejemplo. Para
añadir una función miembro a la clase, debe especificar su prototipo en la definición de la
misma.
Una vez que se ha definido una clase, se puede crear un objeto de ese tipo usando el
nombre de la clase. El nombre de la clase se convierte en un especificador del nuevo tipo.
Por ejemplo la siguiente declaración crea 2 objetos llamados render1 y render2 del tipo
CRender.
Cuando un objeto de la clase es creado, éste tendrá su propia copia de las variables
miembros que contiene la clase. Esto significa que render1 y render2 tendrán su propia e
independiente copia de buffer. Los datos asociados con render1 son distintos y separados de
los datos asociados con render2.
Recordemos: En C++, una clase es un nuevo tipo de dato que puede ser usado para crear
objetos. Específicamente, una clase crea una consistencia lógica que define una relación
entre sus miembros. Cuando se declara una variable de una clase, se está creando un objeto.
Un objeto tiene existencia física, y es una instancia específica de una clase. ( Esto es, un
objeto ocupa espacio de memoria, pero una definición de tipo no ). Además, cada objeto de
una clase tiene su propia copia de los datos definidos dentro de esa clase.
Para implementar una función que es un miembro de una clase, debe indicarle al
compilador a cual clase pertenece la función calificando el nombre de la función con el
nombre de la clase. Por ejemplo, esta es una manera de codificar la función
m_Renderizar().
void CRender::m_Renderizar()
{
strcpy(buffer, "C++ en wikibooks");
return;
}
Resolución de ámbito
Las funciones miembros de una clase sólo pueden ser llamadas relativas a un objeto
específico. Para llamar a una función miembro desde alguna parte del programa que se
encuentre fuera de la clase, se debe usar el nombre del objeto y el operador de
direcionamiento '.' ( punto ). Por ejemplo, lo siguiente llama a m_Renderizar() en el objeto
objeto1.
objeto1.m_Renderizar();
Cuando una función miembro llama a otra función miembro de la misma clase, puede
hacerlo directamente, sin usar un objeto y el operador '.' En este caso, el compilador ya
conoce en cuál objeto se está operando. Solamente cuando una función miembro es llamada
por código que se encuentra fuera de la clase es cuando debe utilizarse el nombre del objeto
y el operador '.' Por la misma razón, una función miembro puede referirse directamente a
una variable miembro, pero código fuera de la clase debe referenciarse a la variable a través
de un objeto y el operador '.'
El programa siguiente muestra aquí todas las piezas juntas y detalles perdidos, e ilustra la
clase CRender.
Ejemplo
// Programa OPP01.CPP
#include <iostream>
#include <cstring>
using std::cout;
using std::endl;
return (0);
}
Métodos:
En comparación con la programación tradicional, un método es lo mismo que una
función cualquiera, salvo que como los métodos se declaran para pertenecer a una
clase específica, se dice que todos los métodos de dicha clase son miembros de la
misma. Por lo demás, la declaración y definición de los métodos es exactamente
igual que declarar y definir cualquier otra función.
Atributos:
Miembros:
class CRender {
public:
char buffer[256]; // atributo
void m_Renderizar(const char *cadena); // método
};
Un miembro público significa que el acceso al mismo puede darse dentro del
interior de la clase, dentro de una subclase, y desde un objeto instanciado de
cualquiera de estas. Por ejemplo, los miembros de la clase CRender son accesibles
dentro de la misma y podrán accederse desde cualquier otra clase que se derive de
CRender, así como desde cualquier objeto instanciado de estas.
Un miembro privado significa que el acceso al mismo puede darse solamente dentro
del interior de la clase que lo posee. Normalmente, el programador creador de una
clase declara a los atributos de la clase como privados y a los métodos como
públicos, esto con la idea de que el usuario de la clase no pueda tener acceso a los
atributos sino es a traves de los métodos definidos para el caso.
Un miembro protegido se comporta de manera parecida a un miembro privado,
salvo que estos son accesibles dentro de la clase que lo posee y desde las clases
derivadas, pero no desde los objetos instanciados a raiz de dichas clases.
En la clase Pareja que se verá en seguida, se declaran dos atributos y cuatro métodos para
la manipulación de dichos atributos. Observe que los atributos son privados(por defecto),
mientras que los métodos se declaran públicos.
class Pareja
{
// atributos
double a, b;
public:
// métodos
double getA();
double getB();
void setA(double n);
void setB(double n);
};
Subclases
Una subclase es una clase que se deriva de otra. La clase que sirve de base suele conocerse
como parent (padre), y a la subclase se le llama child (hija). En C++ cada clase que es
creada se convierte en candidata para servir de base de donde se deriven otras. Por ejemplo,
la clase Pareja es candidata para convertirse en la base para las subclases Suma, Resta,
Multiplica, Divide, y otras posibles subclases en donde se utilice un par de valores
numéricos.
Para poner un ejemplo, pensemos en que deseamos crear la clase Suma, misma que será
utilizada para obtener la suma de dos números. Puesto que la clase Pareja posee dos
atributos númericos puede ser usada como base para la clase que estamos proyectando. Así,
el siguiente ejemplo se constituye en un caso de clases derivadas.
public:
// métodos de Suma
double calcular();
};
// implementación de Suma
//
double Suma::calcular() { return getA() + getB(); }
#include <iostream.h>
int main()
{
Suma s;
s.setA(80);
s.setB(100);
cout << s.getA() << " + " << s.getB() << " = " << s.calcular() <<
endl;
cin.get();
return 0;
}
Herencia
La herencia es uno de los mecanismos más útiles de la programación orientada al objeto,
ya que por medio de la misma se puede llevar a cabo la reutilización de código. Es decir,
puesto que toda clase definida se convierte en candidata para ser usada como base de donde
se deriven otras, esto da como resultado que las clases derivadas hereden todos los
miembros de la clase base. Por ejemplo, la clase Suma vista en la sección anterior, hereda
todos los miembros de la clase Pareja puesto que Suma es una extensión de Pareja. En ese
sentido, podemos decir que existen dos tipos de herencia, por extensión y por agregación o
composición. En el caso de las clases Pareja y Suma, se dice que Suma es una extensión de
Pareja. Vista gráficamente, la herencia por extensión se puede representar así:
Al tipo de diagrama mostrado arriba (Herencia por extensión) se le conoce como UML [1]
y es utilizado para mostrar de forma grafica la relación existente entre una clase hija con la
clase padre. En el caso del ejemplo, se muestra que la clase Suma es una extensión de la
clase Pareja y, en consecuencia, Suma posee a los miembros { a, b, getA(), getB(), setA(),
setB() } heredados de la clase Pareja. Observe como la clase Suma posee otros dos
miembros no heredados, { resultado, y calcular() }, y es precisamente a este tipo de
situación por lo que se dice que Suma es una extensión de Pareja, ya que Suma, además de
poseer a todos los miembros de Pareja, se extiende para poseer o adquirir otros dos
miembros.
Agregacion o composición
La composición se da en los casos en donde una clase posee un objeto que es una instancia
de otra clase. Por ejemplo, la clase Suma podría escribirse de la siguiente forma:
class Suma
{
// atributo privado
double resultado;
public:
// método público
double calcular();
// atributo público
Pareja p;
};
// implementación del metodo calcular de la clase Suma.
double Suma::calcular() { return p.getA() + p.getB(); }
Luego, si usted presta atención, notará que el miembro p de la clase Suma es un objeto o
instancia de la clase Pareja, en consecuencia, la clase Suma puede acceder a los miembros
de la clase Pareja a través de la variable p. También se debe observar que la
implementación del método calcular() es diferente que el mismo de la clase Suma original.
Si usted desea poner a prueba a la nueva clase Suma, compile y ejecute el siguiente
programa.
// programa herencia_por_composicion.CPP
#include <iostream>
using namespace std;
class Pareja
{
// atributos
double a, b;
public:
// métodos
double getA();
double getB();
void setA(double n);
void setB(double n);
};
class Suma
{
// atributo privado
double resultado;
public:
// método público
double calcular();
// atributo público
Pareja p;
};
// implementación del metodo calcular de la clase Suma.
double Suma::calcular() { return p.getA() + p.getB(); }
int main()
{
Suma s;
s.p.setA(80);
s.p.setB(100);
cout << s.p.getA() << " + " << s.p.getB() << " = " << s.calcular() <<
endl;
cin.get();
return 0;
}
////SALIDA////
80 + 100 = 180
Constructores
Un constructor es un método que pertenece a una clase y el cual (en C++) debe tener el
mismo nombre de la clase a la que pertenece. A diferencia de los otros métodos de la clase,
un constructor deberá ser del tipo void, es decir, el mismo no regresará valor alguno. Una
clase puede tener más de un método constructor. Cada clase debe tener al menos un
constructor, tanto así que si el programador creador de una clase no define un método
constructor para la misma, el sistema, o sea el compilador, creará de manera automática un
constructor nulo.
Los constructores suelen usarse para la inicialización de los atributos de los objetos
instanciados. Por ejemplo, con las instrucciones:
Suma s;
s.setA(80);
s.setB(100);
del programa OOP02.CPP, se declara el objeto s de la clase Suma y luego se inicializan los
atributos del objeto por medio de los métodos setA() y setB(). En este caso, es necesario
hacer uso de dichos métodos debido al hecho de que no se ha definido un constructor para
la clase Suma. Ahora bien, para evitar este tipo de situaciones podemos definir un método
constructor para Suma y que este se encargue de inicializar los atributos. Veamos.
// programa OOP04.CPP
// Ejemplo: clases Pareja y Suma, ambas con constructor
#include <iostream>
using namespace std;
//------------------------
class Pareja
{
// atributos
double a, b;
public:
// constructor de base (null)
Pareja() {}
// constructror parametrizado
Pareja(double x, double y) : a(x), b(y) {}
// métodos
double getA();
double getB();
void setA(double n);
void setB(double n);
};
//------------------------
class Suma : public Pareja
{
// atributos de Suma
double resultado;
public:
// constructor
Suma(double a, double b) : Pareja(a, b) {}
// métodos de Suma
double calcular();
};
// implementación de Suma
//
double Suma::calcular() { return getA() + getB(); }
//------------------------
int main()
{
Suma s(80, 100);
cout << s.getA() << " + " << s.getB() << " = " << s.calcular() <<
endl;
cin.get();
return 0;
}
Destructores
Un destructor es un método que pertenece a una clase y el cual (en C++) debe tener el
mismo nombre de la clase a la que pertenece. A diferencia de los otros métodos de la clase,
un destructor deberá ser del tipo void, es decir, el mismo no regresará valor alguno. Para
diferenciar a un método destructor de un método constructor, al nombre del destructor se le
debe anteponer el caracter ~ (Alt + 126).
Los destructores suelen usarse para liberar memoria que haya sido solicitada por el objeto a
través de las ordenes malloc(), new, etc. En tales casos se deberá incluir dentro del método
destructor la orden free, delete, etc., según sea el caso.
public:
// constructor de base (nulo)
Pareja() {}
// constructor parametrizado
Pareja(double x, double y) : a(x), b(y) {}
// destructor
~Pareja() {}
// métodos
double getA();
double getB();
void setA(double n);
void setB(double n);
};
Sobrecarga de operadores
La sobrecarga de operadores es uno de los mecanismos que nos permite ampliar las
capacidades de los lenguajes de programación orientados a objetos. En C++, la declaración
y definición de una sobrecarga de operador es muy similar a la declaración y definición de
una función cualquiera. El ejemplo más sencillo de una sobrecarga de operadores nos lo da
el lenguaje mismo, es decir, en una operación aritmética (por ejemplo, una suma ) el
compilador determina el tipo de operación requerida de acuerdo con el tipo de datos
involucrados. Vamos a suponer que se tienen las variables: int A, B; double X, Y; int R; y
las siguientes instrucciones:
R = A + B;
R = A + X;
R = X + Y;
Ahora bien:
Mi primera sobrecarga
Para poner un ejemplo práctico de sobrecarga del operador de suma ( + ) vamos a
considerar el caso de la clase pareja mostrada enseguida:
class Pareja {
public:
double a, b;
// constructor parametrizado
Pareja(const double a,const double b)
{
this->a = a;
this->b = b;
}
};
int main()
{
Pareja A(50, 75 );
Pareja B(150, 175 );
Pareja C = A + B;
return 0;
}
class Pareja {
public:
double a, b;
// constructor parametrizado
Pareja(const double a,const double b)
{
this->a = a;
this->b = b;
}
};
int main()
{
Pareja A(50, 75 );
Pareja B(150, 175 );
Pareja C = A + B;
cout << "A = " << A.a << ',' << A.b << "\n";
cout << "B = " << B.a << ',' << B.b << "\n";
cout << "C = " << C.a << ',' << C.b << "\n";
return 0;
}
El ejemplo anterior tiene un problema al reservar memoria dinámica que no es liberada
posteriormente. El código, por tanto, tiene pérdidas de espacio de almacenamiento. La
implementación correcta en C++ de este operador sería:
// Sobrecarga del operador +
Pareja operator +(const Pareja &p1,const Pareja &p2)
{
Pareja res( p1.a + p2.a, p1.b + p2.b );
return res;
}
Sintaxis general
Tal y como usted lo habrá notado, la declaración de sobrecarga en el programa anterior es
lo más parecido a una función, es decir, la sintaxis general para sobrecargar un operador
cualquiera es:
en donde,
Nota: en el caso de que el operador sobrecargado se hace para una clase específica la
sintaxis se extiende de la siguiente manera:
cout << "A = " << A.a << ',' << A.b << "\n";
cout << "B = " << B.a << ',' << B.b << "\n";
cout << "C = " << C.a << ',' << C.b << "\n";
Usted puede ver cómo para cada uno de los miembros de los objetos se debe de usar el
operador de dirección ( . ), pues bien, nuestro objetivo es lograr que dado un objeto de la
clase Pareja éste pueda ser desplegado por cout y para ello haremos la sobrecarga de
operador << con el fin de que pueda operar con objetos de la clase mencionada. Veamos:
class Pareja {
public:
double a, b;
// constructor parametrizado
Pareja(const double a,const double b)
{
this->a = a;
this->b = b;
}
};
int main()
{
Pareja A(50, 75 );
Pareja B(150, 175 );
Pareja C = A + B;
int main()
{
Pareja A(50, 75 );
Pareja B;
cin >> B;
Pareja C = A + B;
return 0;
}
Operadores amigos ( friend )
Un operador amigo, al igual que una función amiga, es aquel que aún cuando no es
miembro de una clase tiene todos los privilegios de acceso a los miembros de dicha clase.
En la sobrecarga de los operadores +, << y >> para la clase Pareja de los programas
anteriores, se puede notar que dichos operadores no son parte de la clase Pareja, sino que
más bien éstos operan sobre objetos de dicha clase y es a través de dichos objetos que
pueden manipular a los miembros de la clase. Ahora bien, no hemos tenido ningún
problema debido a que todos los miembros de Pareja han sido declarados como públicos
(public:), pero ¿qué sucede si un operador o una función que no sea parte de la clase trata
de acceder a los miembros privados o protegidos de ésta? En estos casos, el compilador
reportaría el error indicando que tal o cual miembro es privado dentro de cierto contexto.
Si queremos que un operador o función que no es miembro de una clase pueda acceder a
los miembros públicos, privados o protegidos deberemos declarar a dicho operador o
función como amigo (friend) dentro de la clase específica. Para mostrar un ejemplo
modificaremos la clase Pareja, donde sus atributos serán privados.
class Pareja {
private:
double a, b;
public:
// constructor parametrizado
Pareja(const double a,const double b)
{
this->a = a;
this->b = b;
}
int main()
{
Pareja A(50, 75 );
Pareja B(150, 175 );
Pareja C = A + B;
return 0;
}
Nota:
Los operadores binarios son aquellos que poseen dos partes ( izquierda y
derecha),
por ejemplo, una operación de suma requiere dos operandos ( o1 + o2 ).
Los operadores unarios son aquellos que poseen solo una parte, por
ejemplo, una operación
de incremento ( o1 ++ ).
Con el propósito de ver un ejemplo práctico vamos a retomar una vez más la tan famosa
clase Pareja, salvo que en esta ocasión vamos a sobrecargar dentro de la misma a los
operadores binarios: + (suma), - (resta), * (multiplicación) y / (división); el operador de
asignación (=); el operador de incremento (++) y el operador de comparación (==).
// Programa: sobrecarga05.cpp
// Objetivo: mostrar sobrecarga de operadores
// Autor...: Oscar Edmundo Palacios.
#include <iostream>
class Pareja {
private:
int a, b;
public:
// constructor base
Pareja() : a(0), b(0) {}
// constructor parametrizado
Pareja(const int a,const int b) {
this->a = a;
this->b = b;
}
// constructor de copia
Pareja(const Pareja&);
// operadores miembros
Pareja& operator + (const Pareja &p);
Pareja& operator - (const Pareja &p);
Pareja& operator * (const Pareja &p);
Pareja& operator / (const Pareja &p);
Pareja& operator = (const Pareja &p);
Pareja& operator ++();
bool operator ==(const Pareja &p) const;
// operadores no miembros
friend ostream& operator << (ostream &o,const Pareja &p);
friend istream& operator >> (istream &o, Pareja &p);
};
//....................................
Pareja& Pareja::operator ++ ()
{
this->a ++;
this->b ++;
return *this;
}
//....................................
bool Pareja::operator == (const Pareja &p) const
{
return this->a == p.a && this->b == p.b;
}
++C;
cout << "C = " << C << endl;
cout << "A == B " << ( (A==B) ? "Si": "No" );
cin.get();
return 0;
}