Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Programacionen C
Programacionen C
con
C++Builder
Versión de borrador N°3
Año 2004
Angel A. Zeitoune
(azeitoune@yahoo.com.ar)
y
Ricardo A. Rettore
(ricadorettore@yahoo.com.ar)
* Esta es una versión borrador, cualquier duda o sugerencia por favor escribir a cualquiera
de los autores.
Creando un proyecto.
Al iniciarse, C++Builder, esta listo para comenzar un nuevo proyecto. Si estabamos
en el entrono y queremos crear uno nuevo existen dos formas de hacerlo:
• En el menú principal File/New Application.
• En el menú principal File/New, donde se nos presenta una ventana de “new item”,
donde seleccionamos Application.
Un nuevo proyecto esta formado (por defecto) por los archivos de proyecto, y un
formulario con su respectiva unit.
También podremos incluir a nuestro proyecto, nuevos formularios, units, módulos
de datos, archivos de texto, etc. con la opción File/New.
Lo primero que realizaremos, es grabar nuestro proyecto con nombres significativos
al programa que estemos realizando en una carpeta especialmente destinada a nuestro
proyecto.
Extensión
Contenido
Eventos:
OnCreate: este evento ocurre cuando el formulario se crea.
OnShow: ocurre cuando el formulario es mostrado (cuando el propiedad Visible es
True).
OnActivate: ocurre cuando se activa el formulario (cuando el formulario recibe el
foco).
OnPaint: ocurre cuando el formulario es redibujado (redraw).
Eventos:
OnClick: ocurre cuando se hace click sobre el botón.
OnMouseMove: ocurre cuando se mueve el mouse sobre el botón.
Métodos:
SetFocus: coloca el foco en el botón.
Label:
Componente que permite mostrar texto en un formulario. Es usado para mostrar
resultados e información al usuario, debido a que él no puede editarlo. No puede contener
el foco en una aplicación.
Propiedades:
Name: Representa el nombre lógico con el que se referencia al componente.
Caption: permite modificar el texto del label (etiqueta).
Font: modifica el tipo de letra (fuente, estilo de fuente, tamaño, etc.) del caption.
Alignment: permite especificar la alineación del texto. Puede ser hacia la derecha,
izquierda o centrada.
CheckBox y RadioButton:
Son componentes de selección. Se diferencian en que el primero permite seleccionar
varias opciones simultáneamente, mientras el segundo sólo permite la selección de un único
elemento dentro de un mismo grupo.
De ahora en adelante sólo veremos las propiedades, métodos y eventos que
caracterizan a los componentes.
Propiedades:
Checked: especifica cuando el componente está seleccionado.
ListBox:
Es un componente que permite visualizar y manipular una lista de elementos
(items).
Propiedades:
Items: contiene los elementos del ListBox; es del tipo TStrings (lista de strings).
Esta clase, a su vez, contiene métodos que permiten manipular elementos como agregar
(Add), insertar (insert), eliminar (delete) y mover (move).
Columns: especifica el número de columnas.
MultiSelect: permite seleccionar varios elementos al mismo tiempo.
Sorted: ordena automáticamente los elementos alfabéticamente.
Memo:
Es un componente estándar de Windows, que permite manipular texto multilínea,
tanto para el ingreso por parte del usuario como informar textos de gran longitud.
Propiedades:
Lines: contiene los líneas de texto que están contenida en el Memo; es del tipo
TStrings (lista de strings), al igual que los Ítems del ListBox.
ScrollBars: determina cuales barras de desplazamientos se van a mostrar.
Métodos:
Clear: Borra el contenido del Memo.
Otros:
Una vez experimentado con estos componentes, se sugiere continuar investigando,
otros como:
MainMenu.
ComboBox.
Panel.
StringGrid.
Image.
StatusBar.
Timer.
Clases (class).
Una clase es una abstracción, que modela las características y comportamientos de
un objeto, la cual debe incluir funcionalidades que permitan informar o modificar el estado
de esa entidad, como así también, realizar diversas acciones para las cual fue diseñado.
Para el modelaje de esta clase, se debe determinar un conjunto de atributos que la
definen, y un conjunto de funcionalidades que representan sus posibilidades de interacción.
void TRegtangulo::CalcularSup(void)
{
Superficie = Ancho * Alto;
}
float TRegtangulo::InformarSup(void)
{
return Superficie;
}
Para el método CalcularSup se observa que se realiza la asignación de las
variables recibidas como parámetro a los atributos de la clase.
Diagrama de clase.
Un modelo sencillo, que nos ayuda a esquematizar una clase para acelerar su diseño,
es el diagrama de clase. A su vez, nos permite interpretar rápidamente el diseño de una
clase con una rápida lectura, con la particularidad de ser independiente del lenguaje con que
se implemente la clase.
Constructor.
El método constructor es un método especial de la clase que permite inicializar
atributos u otras variables necesarias de la clase. Este método es invocado automáticamente
cuando se crea un objeto de esta clase. Posee algunas características importantes como:
• No retorna ningún valor (ni siquiera void).
• Puede recibir parámetros de cualquier tipo con excepción de la misma clase (si un
puntero).
• Para su declaración se utiliza el mismo nombre que la clase.
Para continuar con el ejemplo anterior, supongamos que deseamos inicializar los
valores del ancho y alto del rectángulo con los valores 10 y 20 respectivamente, y que
realice el cálculo de la superficie para estos valores.
La declaración del constructor se realiza en la parte pública, como se observa:
class TRegtangulo {
private:
float Ancho, Alto;
float Superficie;
public:
TRectangulo(void);
Destructor.
También se trata de un método especial de una clase, pero que es invocado cuando
se destruye un objeto de esta clase. En él se debe liberar toda memoria o recurso especial
que se halla pedido la clase. Posee algunas características importantes como:
• No retorna ningún valor (ni siquiera void).
• No recibe parámetros.
• Para su declaración se utiliza el símbolo ~ seguido por el mismo nombre que la clase.
La declaración del constructor se realiza en la parte pública, como se observa:
class TRegtangulo {
private:
float Ancho, Alto;
float Superficie;
public:
TRectangulo(void);
~TRectangulo(void);
void IngresarAncho(float Anchoi);
void IngresarAlto (float Altoi);
void CalcularSup(void);
float InformarSup(void);
Encapsulamiento
Como definimos antes, el encapsulamiento es el agrupamiento de atributos y
servicios dentro de una clase. Esto significa, que podemos comunicarnos con una clase a
través de sus interfaces bien definidas, pero no conocer como se encuentran implementadas.
Aunque podríamos conocer los detalles de su implementación de una clase, no debe
escribirse código que dependa de ello, esto significa que la implementación de una clase en
particular puede ser modificada o reemplazada sin afectar al resto del sistema, siempre y
cuando no cambie la interfaz public y published;
Herencia.
Una de las relaciones más importantes entre clases es la herencia. Es uno de los
pilares fundamentales de la POO, mediante la cual se produce la transmisión de atributos y
funcionalidades de una clase a otra, lo cual trae aparejado grandes ventajas como la de
reutilización de código en la que se crean nuevas clases a partir de clases ya existentes por
medio de la absorción de sus atributos y comportamientos, sobreponiéndolos o
mejorándolos con las capacidades que las nuevas clases requieran.
La herencia, nos permite definir una clase modificando una o más clases ya
existentes. Estas modificaciones pueden consistir en añadir nuevos atributos y
funcionalidades a la clase que se está definiendo, aunque también se pueden redefinir
funcionalidades ya existentes.
A partir de lo anterior, se deduce que existe una clase primitiva (ya existente) de la
que partimos, a la cual llamaremos clase base o clase padre, y una nueva clase que
definiremos, a la cual llamaremos clase derivada o clase hija.
Esta clase hija, puede ser, a su vez, la clase padre de una o más nuevas clases
derivadas. Creándose de esta manera, una jerarquía de clases.
Para especificar el uso de la herencia, después del nombre de la clase hija, se agrega
el operados dos puntos (:), seguido por un especificador de acceso y luego el nombre de la
clase padre, como se observa:
class NombreClaseHija: public NombreClasePadre {
private:
// …
Nota: Cabe destacar que la clase base no debe ser modificada y esta debe modelar
el objeto del problema para el cual fue diseñada.
Los miembros private de la clase base son siempre inaccesibles para los métodos
de la clase derivada a menos que se declare explícitamente que es un miembro friend en
la clase base. No se tratará sobre miembros friend en este texto.
Vamos a diseñar una clase que modele el volumen de un prisma a la cual
llamaremos TPrisma. Para esta modelización recurriremos a la herencia que nos permitirá
reutilizar código fuete ya existente.
void TPrisma::CalcularVolumen(void);
{
CalcularSup(); // Aquí llamamos al método que calcula
// la superficie de la base
// perteneciente a la clase padre
Volumen = InformarSup() * Altura;
}
float TPrisma::InformarVolumen(void);
{
return Volumen;
}
Clases contenedoras.
El uso de clases contenedoras se centra en la idea que los objetos pueden estar
formados (o contienen) a otros objetos, llamados objetos miembro. Los objetos miembro se
convierten en atributos de nuestra nueva clase. Esta capacidad de contener a otros objetos
también es llamada composición.
Llevando este concepto a la vida real, podemos pensar a los objetos como formados
por piezas de distinta naturaleza que contribuyen a un mismo fin. Este es el caso del objeto
auto, el cual esta compuesto por otros objetos que son parte integra de él, como son el
objeto motor, rueda, volante, etc.
Imaginemos ahora, un péndulo de un reloj
bidimensional, como la conjunción de un rectángulo (brazo
del péndulo) y un círculo (peso del péndulo), al cual
queremos calcular el área.
La clase contenedora TPendulo, contendrá a las
clases miembro TRectangulo y TCirculo. Para
continuar con el concepto de reutilización de código,
utilizaremos a la clase TRectangulo antes definida y Modelo bidimensional
sólo diseñaremos a la clase TCirculo y modelaremos la del péndulo de un reloj
clase TPendulo.
class TCirculo {
private:
float Radio;
float Superficie;
public:
void IngresarRadio(float Radioi);
void CalcularSup(void);
float InformarSup(void);
};
void TCirculo::CalcularSup(void)
{
Superficie = M_PI * pow(Radio,2);
}
float TCirculo::InformarSup(void)
{
return Superficie;
}
void TPendulo::CalcularSup(void)
{
Rect.CalcularSup();
Circ.CalcularSup();
Superficie = Rect.InformarSup() + Circ.InformarSup();
}
float TPendulo::InformarSup(void)
{
return Superficie;
}
Registros (struct).
Los registros son los predecesores de las clases. Permiten definir tipos de datos
agregados que se construyen empleando elementos de otros tipos, es decir, una estructura
es un conjunto de diferentes datos agrupados bajo una única declaración. Un ejemplo de
esta definición:
struct Tiempo {
int hora;
int minutos;
Tiempo TInicial;
Tiempo TFinal;
TInicial.hora = 10;
TInicial.minutos = 35;
TInicial.segundos = 21;
TFinal.hora = 15;
TFinal.minutos = 10;
TFinal.segundos = 59;
En el caso de C++, los struct siguen existiendo, pero en ves de ser estructuras de
datos o registros, fueron implementadas como clases, las cuales permiten ser instanciadas
para crear objetos distintos partiendo de la declaración inicial. El ejemplo es el mismo que
para el struct de C, sólo con una diferencia de concepto, es decir en C es un registro de
datos y en C++ es una clase en donde todos sus elementos son de uso público (public).
Expresión lógica.
Una expresión lógica es una combinación de constantes, variables y funciones
lógicas, con operadores lógicos y relacionales.
Para comenzar a entender su uso, podemos definir una variables lógicas como una
variable que puede contener sólo dos valores posibles: verdadero (trae o 1) o falso (false o
0). En C++ este tipo de variable se llama bool.
Los operadores lógicos son aquellos que nos permiten concatenar o modificar
expresiones lógicas, resultando un valor lógico. Ellos son:
Operador Nombre Operación lógica
! not negación
&& and y lógico
|| or o lógico
Para entender mejor su uso, vamos a ver como trabajan a traves de un ejemplo.
Supongamos que tenemos dos variables lógicas A y B.
A !A
0 1
1 0
A B A && B
0 0 0
0 1 0
1 0 0
1 1 1
A B A || B
Observación: No hay que confundir el operador ==, con el operador =, dado que
el primero significa comparación, mientras el segundo asignación. declaran.
Vamos a ver como trabajan a través de un ejemplo. Supongamos que tenemos tres
variables A=5, B=5 y C=7.
Expresión Resultado
A>B 0
A>=B 1
A<=C 1
A!=B 0
A!=C 1
A==B 1
A==C 0
Estructura condicional “ if ”
Es una estructura simple que permite ejecutar una instrucción o un bloque de
instrucciones sólo si se cumple una expresión lógica, es decir, se ejecuta sólo si el resultado
de la expresión lógica es verdadero.
if (expresión_lógica)
{acción;};
El selector debe ser una variable ordinal, es decir, una variable que posea una
secuencia definida (ordenada) y acotada (finitas posibilidades), como puede ser una
variable del tipo int, bool, cualquier tipo definido por el usuario, o el resultado de una
expresión; siempre y cuando cumplan con la condición.
Cada uno de los valores de los casos para los cuales hemos definido una acción,
deben corresponder a un valor que pertenece al tipo de dato del selector.
Esta estructura también permite la definición de una acción que se ejecutará por
defecto si ninguno de los casos anteriores se cumple. Pero su definición es opcional.
En el caso de que un caso se cumpla, se ejecuta la acción definida para este caso
hasta que se encuentra con la instrucción break, que es una instrucción que permite salir
del bloque de código que se esta ejecutando, en este caso del bloque switch. Si no lo
encuentra, se seguirán ejecutando las acciones de los casos siguientes hasta terminar todos
los casos o hasta encontrarse con un break.
Debe notarse que la instrucción switch sólo funciona cuado existe una igualdad
entre el selector y alguno de los case, por lo que no será de utilidad en el caso de situación
Se analizan solamente los casos 0 y 1 dado a que el resto de la división por 2 sólo
puede tomar estos valores.
El mismo ejemplo se podría haber resuelto de tres formas más sencillas para
analizar:
if (num%2 == 0) if (!num%2)
par = true; par = true;
par = !bool(num%2)
else else
par = false; par = false;
switch (caracter) {
case ‘a’: {Resultado = “es la vocal a”; break;}
case ‘b’: {Resultado = “es la vocal b”; break;}
case ‘c’: {Resultado = “es la vocal c”; break;}
case ‘d’: {Resultado = “es la vocal d”; break;}
case ‘e’: {Resultado = “es la vocal e”; break;}
default:
{Resultado = “no es vocal”;}
}
Ciclo for.
La estructura for (“durante”), se utiliza para realizar, generalmente, una acción
cierta cantidad determinada y definida de veces. Para ello, consta de tres parámetros que
debemos definir:
• Inicialización,
• Condición de salida
• Incremento
Ciclo while
El ciclo while ("mientras") difiere del ciclo for en que sólo contiene una condición
de prueba, que se verifica al principio de cada iteración. Mientras la condición sea true el
ciclo continúa funcionando. La sintáxis correspondiente es:
while (expresión_lógica) {acción_1;
acción_2;
.
.
acción_n;};
Ciclo do-while.
Este ciclo es prácticamente igual al while. Sin embargo, la diferencia entre los dos
es importante, ya que el ciclo while evalúa la expresión condicional al principio del ciclo;
en el caso de do-while ("hacer – mientras"), la expresión se evalúa al final del
propio ciclo. La sintaxis de este bucle es:
int x = 1024;
int n;
do {x = x / 2;
n++;}
while (x > 1);
float x = 0;
float y;
do {y = 512 / x; //error al tratar de dividir por cero!!!
x = x + 2.0;} //acumulo en x el valor anterior de x más 2
//x en este caso es un acumulador
while (y != 1);
En este caso, el for se utiliza a modo de delay o retardo hasta que llegue la
confirmación de lectura del puerto.
En el caso que consideremos usar un bit de status para que haga comenzar o detenga la
adquisición del dato, según sea true o false.
int x=0;
for(;;){
if(LeerBitStatus())break;
for(;;){
if(LeerBitControl())break;
}
x += LeerDato();
}
int x=0;
while(!detener){
if(LeerBitStatus())continue;
for(;;){
if(!LeerBitControl())break;
}
x += LeerDato();
}
int vector[5];
vector[5]=31;
Este es un error que se comete frecuentemente, dado que los arreglos tienen base 0.
Podríamos pensar que el último elemento del arreglo es 5, cuando en realidad es 4.
Es posible solicitar que, automáticamente, se verifique que los índices se encuentren
dentro del rango de la definición del arreglo, activando la directiva de compilación $R,
accediendo a Project options/Pascal/Range checking.
Hay una diferencia notable entre el índice de un elemento de un vector (que es
siempre de tipo entero), y el valor contenido en la posición del vector marcada por el
índice, que puede ser de cualquier tipo (entero, flotante, booleano, etc.).
char
Es un tipo de dato que permite definir un carácter de la tabla ASCII (Anexo I). Esta
tabla es una tabla de correspondencia entre un caracter y un número asociado al mismo
entre 0 y 255.
En este ejemplo definimos un variable llamada Caracter a la cual le asignamos
el mismo caracter “A” de dos maneras diferentes:
char Caracter = ‘A’;
char Caracter = 65;
c-string
Es un arreglo de caracteres, es decir, es una cadena de caracteres que se define en
forma de un vector de caracteres. Esta definición es heredada del lenguaje “c” aunque su
uso ha disminuido. Pero conserva algunas ventajas como son su simpleza y el menor
recurso ocupado. Se definición tiene la forma:
Char Nombre[longitud];
c++ string
Es una clase asociada a un arreglo de caracteres que posee métodos para su
manipulación. Para su utilización no es necesario especificar la longitud en la definición,
dado que se puede modificar en forma dinámica con la asignación de una nueva cadena.
string Cadena = “Bioingenieria”;
Esta clase es muy flexible y práctica aunque no la vamos a desarrollar dado que
C++Buider posee su propia definición de cadena de caracteres que veremos a
continuación.
AnsiString
Es una clase especialmente diseñada para la manipulación de cadenas de caracteres
definida por Borland®. Su definición tiene la forma:
AnsiString Cadena = “Bioingenieria”;
Esta clase define varios métodos que facilitan la manipulación de las cadenas, entre
las cuales se destacan (los ejemplos son siempre sobre la cadena original “Bioingenieria”):
Delete(pos, cant)
Permite borrar una cantidad (cant) de caracteres de la propia cadena a partir de
una posición (pos)
Cadena.Delete(1, 3); // Cadena = “ingenieria”
SubString(pos, cant)
Devuelve una nueva cadena que es una subcadena de la propia. La subcadena
contiene cant caracteres y comienza desde pos.
Sub = Cadena.SubString(6, 2); // Sub = “ge”
Insert(subcadena, pos)
Inserta una subcadena en nuestra cadena en la posición pos.
SubCad = “Super”;
Cadena.Insert(Subcad, 1); // Cadena = “SuperBioingenieria”
SetLength(cant)
Cambia la longitud de la cadena a la especificada por cant. Si la cant es menor
que la longitud de la cadena, entonces la trunca, es decir, borra todos los caracteres desde la
posición cant+1 en adelante. Si cant es mayor a la longitud, el contenido de los
caracteres restantes es incierto.
Cadena.SetLength(10); // Cadena = “Bioingenie”
Pos(subcadena)
Devuelve la posición del inicio donde se encuentra la subcadena dentro de la cadena
original. Si la cadena no posee la subcadena retorna el valor 0 (cero). Si la cadena posee
repetida la subcadena dentro de ella, devuelve la posición del primero.
int posicion = Candena.Pos(“in”); // posicion = 4
LowerCase()
Devuelve la cadena en minúscula.
AnsiString cad = Cadena.LowerCase(); // cad = “bioingenieria”
UpperCase();
Devuelve la cadena en mayúscula.
AnsiString cad = Cadena.UpperCase(); // cad = “BIOINGENIERIA”
Ahora vemos que podemos también realizar la inserción del contenido una variable
entera, cuyo contenido se convierte de forma automática en una cadena de caracteres. A
demás se puede observar otra forma de insertar el caracter de fin de línea agregando \n
int num = 1; 1Hola
ArchivoSal << num << “Hola\n”; 2 Hola
Num++;
ArchivoSal << num << “ Hola\n”;
Contenido archivo
Código ejemplo
variables
En este primer ejemplo vemos como extrae una simple cadena de caracteres de un
archivo de texto tipo ifstream llamado ArchivoEnt.
string Texto; alumno
ArchivoEnt >> Texto; Texto = “alumno”
En este par de ejemplos, vemos como el mismo código permite leer cadenas de
caracteres sucesivos independientes que estén separadas por un caracter de espacio o de fin
de línea.
string Texto1, Texto2; alumno1 alumno2
ArchivoSal >> Texto1 >> Texto2; Texto1 = “alumno1”
Texto2 = “alumno2”
string Texto1, Texto2; alumno1
ArchivoSal >> Texto1 >> Texto2; alumno2
Texto1 = “alumno1”
Texto2 = “alumno2”
Métodos especiales
• eof()
Es uno de los métodos más importantes. Devuelve true si se llegó al final de
archivo. Encontró el carácter EOF.
• is_open()
Este método devuelve true si se abrió correctamente el archivo.
• good()
Este método devuelve true si no ocurrió ningún error.
• bad()
Este método devuelve true si ocurrió algún error con el buffer.
• fail()
Este método devuelve true si ocurrió algún error que no afecte al buffer.