libro de c++ (teria/practico)

243
i PROGRAMACION EN EL LENGUAJE C/C++

Upload: nicolas-palermo

Post on 13-Aug-2015

34 views

Category:

Documents


0 download

DESCRIPTION

Este libro posee ejemplos y es recomendado a todos aquellos principiantes que quieran aprender este maravilloso lenguaje de programación

TRANSCRIPT

Page 1: Libro de c++ (Teria/Practico)

i

PROGRAMACION EN EL LENGUAJE C/C++

Page 2: Libro de c++ (Teria/Practico)

CAPITULO 1

Proceso de creación del software.

Se define como proceso al conjunto ordenado de pasos a seguir para llegar a la

solución de un problema u obtención de un producto, en este caso particular, para

lograr un producto software que resuelva un problema específico.

El proceso de creación de software puede llegar a ser muy complejo,

dependiendo de su porte, características y criticidad del mismo. Por ejemplo la

creación de un sistema operativo es una tarea que requiere proyecto, gestión,

numerosos recursos y todo un equipo disciplinado de trabajo. En el otro extremo, si

se trata de un sencillo programa, éste puede ser realizado por un solo programador

fácilmente. Es así que normalmente se dividen en tres categorías según su tamaño

(líneas de código) o costo.

Considerando los de gran porte, es necesario realizar complejas tareas, tanto

técnicas como de gerencia, una fuerte gestión y análisis diversos, la complejidad de

ello ha llevado a que desarrolle una ingeniería específica para tratar su estudio y

realización: es conocida como Ingeniería de Software.

En tanto que en los de mediano porte, pequeños equipos de trabajo (incluso un

avezado analista-programador solitario) pueden realizar la tarea. Aunque, siempre en

casos de mediano y gran porte (y a veces también en algunos de pequeño porte, según

su complejidad), se deben seguir ciertas etapas que son necesarias para la

construcción del software. Tales etapas, si bien deben existir, son flexibles en su

forma de aplicación, de acuerdo a la metodología o proceso de desarrollo escogido y

utilizado por el equipo de desarrollo o por el analista-programador solitario (si fuere

el caso).

Los procesos de desarrollo de software poseen reglas preestablecidas, y deben

ser aplicados en la creación del software de mediano y gran porte, ya que en caso

Page 3: Libro de c++ (Teria/Practico)

contrario lo más seguro es que el proyecto o no logre concluir o termine sin cumplir

los objetivos previstos, y con variedad de fallos inaceptables. Entre tales «procesos»

los hay ágiles o livianos (ejemplo XP), pesados y lentos (ejemplo RUP), y variantes

intermedias. Normalmente se aplican de acuerdo al tipo y porte del software a

desarrollar, a criterio del líder (si lo hay) del equipo de desarrollo. Algunos de esos

procesos son Programación Extrema (en inglés eXtreme Programming o XP),

Proceso Unificado de Rational (en inglés Rational Unified Process o RUP), Feature

Driven Development (FDD), etc.

Cualquiera sea el proceso utilizado y aplicado al desarrollo del software, y casi

independientemente de él, siempre se debe aplicar un modelo de ciclo de vida.

Cuando un proyecto fracasa, rara vez es debido a fallas técnicas, la principal

causa de fallos y fracasos es la falta de aplicación de una buena metodología o

proceso de desarrollo. Entre otras, una fuerte tendencia, desde hace pocas décadas, es

mejorar las metodologías o procesos de desarrollo, o crear nuevas y concientizar a los

profesionales de la informática a su utilización adecuada. Normalmente los

especialistas en el estudio y desarrollo de estas áreas y afines son los ingenieros en

software, es su orientación. Los especialistas en cualquier otra área de desarrollo

informático normalmente aplican sus conocimientos especializados pero utilizando

modelos, paradigmas y procesos ya elaborados.

Es común para el desarrollo de software de mediano porte que los equipos

humanos involucrados apliquen metodologías propias, normalmente un híbrido de los

procesos anteriores y a veces con criterios propios.

El proceso de desarrollo puede involucrar numerosas y variadas tareas , desde

lo administrativo, pasando por lo técnico y hasta la gestión y el gerenciamiento. Pero,

casi rigurosamente, siempre se cumplen ciertas etapas mínimas; las que se pueden

resumir como sigue:

• Captura, elicitación, especificación y análisis de requisitos (ERS)

• Diseño

Page 4: Libro de c++ (Teria/Practico)

• Codificación

• Pruebas (unitarias y de integración)

• Instalación y paso a producción

• Mantenimiento

En las anteriores etapas pueden variar ligeramente sus nombres, o ser más

globales, o contrariamente, ser más refinadas; por ejemplo indicar como una única

fase (a los fines documentales e interpretativos) de análisis y diseño; o indicar como

implementación lo que está dicho como codificación; pero en rigor, todas existen e

incluyen, básicamente, las mismas tareas específicas.

Lenguajes de programación.

Definición.

Un lenguaje de programación es un conjunto de símbolos y reglas sintácticas

y semánticas que definen su estructura y el significado de sus elementos y

expresiones. Es utilizado para controlar el comportamiento físico y lógico de una

máquina.

Un lenguaje de programación es un lenguaje diseñado para describir el

conjunto de acciones que un equipo debe ejecutar. Por lo tanto, un lenguaje de

programación es un modo práctico para que los seres humanos puedan dar

instrucciones a un equipo.

Los primeros lenguajes de programación surgieron de la idea de Charles

Babagge, la cual se le ocurrió a este hombre a mediados del siglo XIX, predijo

muchas de las teorías en que se basan los actuales ordenadores. Consistía en lo que él

denominaba la maquina analítica, pero que por motivos técnicos no pudo construirse

hasta mediados del siglo XX. Con él colaboro Ada Lovedby, la cual es considerada

como la primera programadora de la historia, pues realizo programas para aquélla

supuesta maquina de Babagge, en tarjetas perforadas.

Page 5: Libro de c++ (Teria/Practico)

Los lenguajes de programación son los medios de comunicación entre los

programadores o usuarios y la computadora. Con ellos se construyen los programas

que después serán ejecutados por la computadora.

Clasificacion de los lenguajes de programación.

Un lenguaje de programación es un conjunto limitado de palabras y de símbolos

que representan procedimientos, cálculos, decisiones y otras operaciones que pueden

ejecutar una computadora.

Lenguajes de Programacion según su nivel.

Calsificamos los lenguajes según su nivel de programación en:

• Lenguajes de máquina

El lenguaje máquina de una computadora consta de cadenas de números

binarios (ceros y unos) y es el único que "entienden" directamente los

procesadores. Todas las instrucciones preparadas en cualquier lenguaje de

máquina tienen por lo menos dos partes. La primera es el comando u

operación, que dice a la computadora cuál es la función que va a realizar.

Todas las computadoras tienen un código de operación para cada una de

sus funciones. La segunda parte de la instrucción es el operando, que

indica a la computadora dónde hallar o almacenar los datos y otras

instrucciones que se van a manipular; el número de operandos de una

instrucción varía en las distintas computadoras.

Según los estándares actuales, las primeras computadoras eran poco

tolerantes. Los programadores tenían que traducir las instrucciones de

manera directa a la forma de lenguaje de máquina que comprendían las

Page 6: Libro de c++ (Teria/Practico)

computadoras. Por ejemplo, un programador que escribiera la instrucción

"SUMAR 0814" para una de las primeras máquinas IBM hubiera escrito:

000100000000000000000000000010111000

Además de recordar las docenas de códigos numéricos para los comandos

del conjunto de instrucciones de la máquina, el programador tenía que

conocer las posiciones donde se almacenan los datos y las instrucciones.

La codificación inicial muchas veces requería meses, por lo que era

costosa y era frecuente que originara errores.

• Lenguajes ensambladores

A principios de la década de 1950, y con el fin de facilitar la labor de los

programadores, se desarrollaron códigos nemotécnicos para las

operaciones y direcciones simbólicas. La palabra nemotécnico se refiere a

una ayuda para la memorización. Uno de los primeros pasos para mejorar

el proceso de preparación de programas fue sustituir los códigos de

operaciones numéricos del lenguaje de máquina por símbolos alfabéticos,

que son los códigos nemotécnicos. Todas las computadoras actuales tienen

códigos nemotécnicos aunque, naturalmente, los símbolos que se usan

varían en las diferentes marcas y modelos. La computadora sigue

utilizando el lenguaje de máquina para procesar los datos, pero los

programas ensambladores traducen antes los símbolos de código de

operación especificados a sus equivalentes en lenguaje de máquina. Este

procedimiento preparó avances posteriores. La técnica de

direccionamiento simbólico permite expresar una dirección no en términos

de su localización numérica absoluta, sino en términos de símbolos

convenientes para el programador.

Durante las primeras etapas del direccionamiento simbólico, el

programador asigna un nombre simbólico y una dirección real a un dato.

Así, durante el resto del programa, el programador se referirá a los

Page 7: Libro de c++ (Teria/Practico)

nombres simbólicos, más que a las direcciones, cuando fuera preciso

procesar estos datos. Más adelante se hizo otra mejora. Se dejó a la

computadora la tarea de asignar y recordar las direcciones de las

instrucciones. Lo único que tenía que hacer el programador era indicar a la

computadora la dirección de la primera instrucción, y el programa

ensamblador se encargaba de almacenar, de manera automática, todas las

demás en forma secuencial a partir de ese punto. Así, si se agregaba más

tarde otra instrucción al programa, no era necesario modificar las

direcciones de todas las instrucciones que seguían al punto de inserción.

En vez de ello, el procesador ajustaba automáticamente las localidades de

memoria la próxima vez que se ejecutaba el programa.En la actualidad, los

programadores no asignan números de dirección reales a los datos

simbólicos, simplemente especifican dónde quieren que se coloque la

primera localidad del programa, y el programa ensamblador se encarga de

lo demás: asigna localidades tanto para las instrucciones como para los

datos.Estos programas de ensamble, o ensamblador, también permite a la

computadora convertir las instrucciones en lenguaje ensamblador del

programador en su propio código de máquina. Un programa de

instrucciones escrito en lenguaje ensamblador por un programador se

llama programa fuente. Después de que el ensamblador convierte el

programa fuente en código de máquina a éste se le denomina programa

objeto. Para los programadores es más fácil escribir instrucciones en un

lenguaje ensamblador que en códigos de lenguajes de máquina, pero es

posible que se requieran dos corridas de computadora antes de que se

puedan utilizar las instrucciones del programa fuente para producir las

salidas deseadas.

Los lenguajes ensambladores tienen ventajas sobre los lenguajes de

máquina. Ahorran tiempo y requieren menos atención a detalles. Se

Page 8: Libro de c++ (Teria/Practico)

incurren en menos errores y los que se cometen son más fáciles de

localizar.

• Lenguajes de alto nivel

Los primeros programas ensambladores producían sólo una instrucción en

lenguaje de máquina por cada instrucción del programa fuente. Para

agilizar la codificación, se desarrollaron programas ensambladores que

podían producir una cantidad variable de instrucciones en lenguaje de

máquina por cada instrucción del programa fuente. Dicho de otra manera,

un sola macroinstrucción podía producir varias líneas de código en

lenguaje de máquina. Por ejemplo, el programador podría escribir "LEER

ARCHIVO", y el programa traductor produciría una serie detallada de

instrucciones al lenguaje de máquina previamente preparada, con lo que se

copiaría un registro del archivo que estuviera leyendo el dispositivo de

entrada a la memoria principal. Así, el programador no se tenía que ocupar

de escribir una instrucción por cada operación de máquina realizada.

El desarrollo de las técnicas nemotécnicas y las macroinstrucciones

condujo, a su vez, al desarrollo de lenguajes de alto nivel que a menudo

están orientados hacia una clase determinada de problemas de proceso. A

diferencia de los programas de ensamble, los programas en lenguaje de

alto nivel se pueden utilizar con diferentes marcas de computadores sin

tener que hacer modificaciones considerables. Esto permite reducir

sustancialmente el costo de la reprogramación cuando se adquiere equipo

nuevo. Otras ventajas de los lenguajes de alto nivel son:

o Son más fáciles de aprender que los lenguajes ensambladores.

o Se pueden escribir más rápidamente.

o Permiten tener mejor documentación.

o Son más fáciles de mantener.

Page 9: Libro de c++ (Teria/Practico)

o Un programador que sepa escribir programas en uno de estos

lenguajes no está limitado a utilizar un solo tipo de máquina.

Lenguajes compilados.

Naturalmente, un programa que se escribe en un lenguaje de alto nivel también

tiene que traducirse a un código que pueda utilizar la máquina. Los programas

traductores que pueden realizar esta operación se llaman compiladores. Éstos, como

los programas ensambladores avanzados, pueden generar muchas líneas de código de

máquina por cada proposición del programa fuente. Se requiere una corrida de

compilación antes de procesar los datos de un problema.

Los compiladores son aquellos cuya función es traducir un programa escrito en

un determinado lenguaje a un idioma que la computadora entienda (lenguaje máquina

con código binario).

Al usar un lenguaje compilado, el programa desarrollado nunca se ejecuta

mientras haya errores, sino hasta que luego de haber compilado el programa, ya no

aparecen errores en el código.

Lenguajes interpretados.

Se puede también utilizar una alternativa diferente de los compiladores para

traducir lenguajes de alto nivel. En vez de traducir el programa fuente y grabar en

forma permanente el código objeto que se produce durante la corrida de compilación

para utilizarlo en una corrida de producción futura, el programador sólo carga el

programa fuente en la computadora junto con los datos que se van a procesar. A

continuación, un programa intérprete, almacenado en el sistema operativo del disco, o

incluido de manera permanente dentro de la máquina, convierte cada proposición del

programa fuente en lenguaje de máquina conforme vaya siendo necesario durante el

proceso de los datos. No se graba el código objeto para utilizarlo posteriormente.

Page 10: Libro de c++ (Teria/Practico)

La siguiente vez que se utilice una instrucción, se le debe interpretar otra vez y

traducir a lenguaje máquina. Por ejemplo, durante el procesamiento repetitivo de los

pasos de un ciclo, cada instrucción del ciclo tendrá que volver a ser interpretado cada

vez que se ejecute el ciclo, lo cual hace que el programa sea más lento en tiempo de

ejecución (porque se va revisando el código en tiempo de ejecución) pero más rápido

en tiempo de diseño (porque no se tiene que estar compilando a cada momento el

código completo). El intérprete elimina la necesidad de realizar una corrida de

compilación después de cada modificación del programa cuando se quiere agregar

funciones o corregir errores; pero es obvio que un programa objeto compilado con

antelación deberá ejecutarse con mucha mayor rapidez que uno que se debe

interpretar a cada paso durante una corrida de producción.

Lenguajes de programación declarativos.

Se les conoce como lenguajes declarativos en ciencias computacionales a

aquellos lenguajes de programación en los cuales se le indica a la computadora qué es

lo que se desea obtener o qué es lo que se esta buscando.

La programación declarativa es una forma de programación que implica la

descripción de un problema dado en lugar de proveer una solución para dicho

problema, dejando la interpretación de los pasos específicos para llegar a dicha

solución a un intérprete no especificado. La programación declarativa adopta, por lo

tanto, un enfoque diferente al de la programación imperativa tradicional.

En otras palabras, la programación declarativa provee el "qué", pero deja el

"cómo" liberado a la implementación particular del intérprete. Por lo tanto se puede

ver que la programación declarativa tiene dos fases bien diferenciadas, la declaración

y la interpretación.

Los lenguajes declarativos están orientados a buscar la solución del problema,

sin preocuparse por la forma de llegar a ello; es decir, el programador debe

concentrarse en la lógica del algoritmo, más que en el control de la secuencia. Los

Page 11: Libro de c++ (Teria/Practico)

programas están formados por un conjunto de definiciones o ecuaciones, las cuales

describen lo que debe ser calculado, no en sí la forma de hacerlo. Las variables sólo

pueden tener asignado un solo valor a lo largo de la ejecución del programa, lo cual

implica que no puede existir asignación destructiva. Debido a esto, cobra especial

importancia el uso del anidamiento y la recursividad.

Las listas representan la estructura fundamental de datos.

El orden de la ejecución no resulta importante debido a que no existen efectos

colaterales; es decir, que al calcular un valor, resulta imposible afectar el cálculo de

otros y con esto se puede afirmar que cualquier secuencia de ejecución deberá

conducir al mismo resultado.

Las expresiones o definiciones pueden ser usadas como valores y por lo tanto se

pueden tratar como argumentos de otras definiciones.El control de la ejecución no es

responsabilidad del programador.

Entre los lenguajes declarativos podemos clasificar:

• Programación lógica

La idea fundamental de la programación lógica consiste en emplear la lógica

como lenguaje de programación. La lógica no es imperativa porque no sirve

para indicar cómo resolver un problema. La lógica es declarativa porque sirve

para especificar qué problema resolver.

En la programación lógica, se especifican las condiciones que satisfacen las

soluciones, se deducen las soluciones a partir de las condiciones y el énfasis

de todo está en qué problema resolver. El problema se describe especificando

qué caracteriza a sus posibles soluciones.

La programación lógica, junto con la funcional, forma parte de lo que se

conoce como programación declarativa. En los lenguajes tradicionales, la

programación consiste en indicar cómo resolver un problema mediante

sentencias; en la programación lógica, se trabaja de forma descriptiva,

estableciendo relaciones entre entidades, indicando no cómo, sino qué hacer.

Page 12: Libro de c++ (Teria/Practico)

Se establece entonces que la idea esencial de la programación lógica es:

algoritmos = lógica + control. Es decir, un algoritmo se construye

especificando conocimiento en un lenguaje formal y el problema se resuelve

mediante un mecanismo de inferencia que actúa sobre aquél.

Al hacer un recorrido por la programación lógica, aparece como uno de sus

lenguajes más representativos, Prolog, que es un clásico de la inteligencia

artificial y que se aplica de múltiples formas en el desarrollo de software

comercial.

• Programación funcional

La programación funcional es un paradigma de programación declarativa

basado en la utilización de funciones matemáticas. El objetivo de la

programación funcional es conseguir lenguajes expresivos y matemáticamente

elegantes, en los que no sea necesario bajar al nivel de la máquina para

describir el proceso llevado a cabo por el programa.

Los programas escritos en un lenguaje funcional están constituidos

únicamente por definiciones de funciones, entendiendo éstas no como

subprogramas clásicos de un lenguaje imperativo, sino como funciones

puramente matemáticas, en las que se verifican ciertas propiedades como la

transparencia referencial, y por tanto, la carencia total de efectos laterales.

Otras características propias de estos lenguajes son la no existencia de

asignaciones de variables y la falta de construcciones estructuradas como la

secuencia o la iteración. Existen dos grandes categorías de lenguajes

funcionales: los funcionales puros y los híbridos. La diferencia entre ambos

estriba en que los lenguajes funcionales híbridos son menos dogmáticos que

los puros, al permitir conceptos tomados de los lenguajes imperativos, como

las secuencias de instrucciones o la asignación de variables. En contraste, los

lenguajes funcionales puros tienen una mayor potencia expresiva,

Page 13: Libro de c++ (Teria/Practico)

conservando a la vez su transparencia referencial, algo que no se cumple

siempre con un lenguaje híbrido.

• Programación orientada a bases de datos

Las bases de datos son programas que administran información y hacen más

ordenada la información, aparte de hacer la fácil de buscar y por supuesto de

encontrar.

Las características de las bases de datos pueden ser ventajosas o

desventajosas: pueden ayudar a almacenar, organizar, recuperar, comunicar y

manejar información en formas que serían imposibles sin las computadoras,

pero también afecta de alguna manera ya que existen enormes cantidades de

información en bases de datos de las que no se tiene control del acceso.

Las bases de datos tienen muchos usos: facilitan el almacenamiento de

grandes cantidades de información; permiten la recuperación rápida y flexible

de información, con ellas se puede organizar y reorganizar la información, así

como imprimirla o distribuirla en formas diversas.

Es claro que los lenguajes orientados a bases de datos son declarativos y no

imperativos, pues el problema es "qué" se quiere hacer o "qué" se necesita

buscar y encontrar en la base de datos, y no se enfatiza el "cómo".

Una base de datos también se puede definir como un banco de datos o

conjunto de datos que pertenecen al mismo contexto, almacenados

sistemáticamente para su posterior uso. Los sistemas gestores de bases de

datos (SGBD) permiten almacenar y posteriormente acceder a los datos de

forma rápida y estructurada.

Lenguajes de programación imperativos.

Se llama lenguajes imperativos a aquellos en los cuales se le ordena a la

computadora cómo realizar una tarea siguiendo una serie de pasos o instrucciones. El

Page 14: Libro de c++ (Teria/Practico)

proceso anterior se puede realizar con un lenguaje imperativo como por ejemplo

BASIC, C, C++, Java, Clipper, Dbase, C#, PHP, Perl, etc.

Dentro de la programación imperativa, se tiene un conjunto de instrucciones

que le indican al computador cómo realizar una tarea.

Los lenguajes imperativos se basan en comandos u órdenes que se le dan a la

computadora para que haga algo, con el fin de organizar o cambiar valores en ciertas

partes de la memoria.

La ejecución de estos comandos se realiza, en la mayor parte de ellos,

secuencialmente, es decir, hasta que un comando no ha sido ejecutado no se lee el

siguiente. Según el dominio, o mejor dicho con el propósito que se utiliza el

programa, se puede hablar de lenguajes de dominio específico y de dominio general.

Lenguajes imperativos procedurales es la aplicación quien controla qué

porciones de código se ejecuta, y la secuencia en que este se ejecuta. La ejecución de

la aplicación se inicia con la primera línea de código, y sigue una ruta predefinida a

través de la aplicación, llamando procedimientos según sea necesario. Los lenguajes

procedurales están fundamentados en la utilización de variables para almacenar

valores y en la realización de operaciones con los datos almacenados.

En este tipo de lenguajes, la arquitectura consta de una secuencia de celdas,

llamadas memoria, en las cuales se pueden guardar en forma codificada, lo mismo

datos que instrucciones; y de un procesador, el cual es capaz de ejecutar de manera

secuencial una serie de operaciones, principalmente aritméticas y booleanas, llamadas

comandos. En general, un lenguaje procedural ofrece al programador conceptos que

se traducen de forma natural al modelo de la máquina. El programador tiene que

traducir la solución abstracta del problema a términos muy primitivos, cercanos a la

máquina.

Con un lenguaje procedural el usuario (normalmente será un programador)

especifica qué datos se necesitan y cómo obtenerlos. Esto quiere decir que el usuario

debe especificar todas las operaciones de acceso a datos llamando a los

procedimientos necesarios para obtener la información requerida. Estos lenguajes

Page 15: Libro de c++ (Teria/Practico)

acceden a un registro, lo procesan y basándose en los resultados obtenidos, acceden a

otro registro, que también deben procesar. Así se va accediendo a registros y se van

procesando hasta que se obtienen los datos deseados. Las sentencias de un lenguaje

procedural deben estar embebidas en un lenguaje de alto nivel, ya que se necesitan

sus estructuras (bucles, condicionales, etc.) para obtener y procesar cada registro

individual.

Lenguajes de programación orientados a objetos

En la Programación Orientada a Objetos se definen los programas en términos

de "clases de objetos", objetos que son entidades que combinan estado (es decir,

datos) comportamiento (esto es, procedimientos o métodos) e identidad (propiedad

del objeto que lo diferencia del resto). La programación orientada a objetos expresa

un programa como un conjunto de estos objetos, que colaboran entre ellos para

realizar tareas. Esto permite hacer los programas módulos más fáciles de escribir,

mantener y reutilizar.

De esta forma, un objeto contiene toda la información, (los denominados

atributos) que permite definirlo e identificarlo frente a otros objetos pertenecientes a

otras clases (e incluso entre objetos de la misma clase, al poder tener valores bien

diferenciados en sus atributos). A su vez, dispone de mecanismos de interacción (los

llamados métodos) que favorecen la comunicación entre objetos (de una misma clase

o de distintas), y en consecuencia, el cambio de estado en los propios objetos. Esta

característica lleva a tratarlos como unidades indivisibles, en las que no se separan (ni

deben separarse) información (datos) y procesamiento (métodos).

Las principales diferencias entre la programación imperativa y la programación

orientada a objetos:

Page 16: Libro de c++ (Teria/Practico)

• La programación orientada a objetos es más moderna, es una evolución de la

programación imperativa plasmada en el diseño de una familia de lenguajes

conceptos que existían previamente, con algunos nuevos.

• La programación orientada a objetos se basa en lenguajes que soportan

sintáctica y semánticamente la unión entre los tipos abstractos de datos y sus

operaciones (a esta unión se la suele llamar clase).

• La programación orientada a objetos incorpora en su entorno de ejecución

mecanismos tales como el polimorfismo y el envío de mensajes entre objetos.

Traductores.

Definición.

Un traductor (compilador o intérprete) es un software que lee un programa

escrito en un lenguaje (lenguaje fuente) y lo traduce a un programa equivalente en

otro lenguaje (lenguaje objeto). Como parte importante de este proceso de

traducción, el traductor informa a su usuario de la presencia de errores en el programa

fuente. (Fig. 1.4)

Figura 1. 1. Traductor.

Etapas del proceso de traducción.

El proceso de traducción se divide en dos fases o etapas:

Page 17: Libro de c++ (Teria/Practico)

• Fase de análisis.- La parte del análisis divide al programa fuente en sus

elementos componentes y crea una representación intermedia del programa

fuente.

• Fase de síntesis.- La parte de la síntesis construye el programa objeto deseado

a partir de la representación intermedia.

De las dos partes, la síntesis es la que requiere las técnicas más especializadas.

Además de un traductor, se pueden necesitar otros programas para crear un

programa objeto ejecutable. Un programa fuente se puede dividir en módulos

almacenados en archivos distintos. La tarea de reunir el programa fuente a menudo se

confía a un programa distinto, llamado preprocesador. El preprocesador también

puede expandir abreviaturas, llamadas a macros, a proposiciones del lenguaje fuente.

Es útil pensar en estas fases como en piezas separadas dentro del traductor, y

pueden en realidad escribirse como operaciones codificadas separadamente aunque en

la práctica a menudo se integren juntas.

Fase de Análisis

Análisis léxico

El análisis léxico constituye la primera fase, aquí se lee el programa fuente de

izquierda a derecha y se agrupa en componentes léxicos (tokens), que son secuencias

de caracteres que tienen un significado. Además, todos los espacios en blanco, líneas

en blanco, comentarios y demás información innecesaria se elimina del programa

fuente. También se comprueba que los símbolos del lenguaje (palabras clave,

operadores,...) se han escrito correctamente.

Como la tarea que realiza el analizador léxico es un caso especial de

coincidencia de patrones, se necesitan los métodos de especificación y

Page 18: Libro de c++ (Teria/Practico)

reconocimiento de patrones, y estos métodos son principalmente las expresiones

regulares y los autómatas finitos. Sin embargo, un analizador léxico también es la

parte del traductor que maneja la entrada del código fuente, y puesto que esta entrada

a menudo involucra un importante gasto de tiempo, el analizador léxico debe

funcionar de manera tan eficiente como sea posible.

Análisis sintáctico

En esta fase los caracteres o componentes léxicos se agrupan jerárquicamente

en frases gramaticales que el compilador utiliza para sintetizar la salida. Se

comprueba si lo obtenido de la fase anterior es sintácticamente correcto (obedece a la

gramática del lenguaje). Por lo general, las frases gramaticales del programa fuente se

representan mediante un árbol de análisis sintáctico.

La estructura jerárquica de un programa normalmente se expresa utilizando

reglas recursivas. Por ejemplo, se pueden dar las siguientes reglas como parte de la

definición de expresiones:

1. Cualquier identificador es una expresión.

2. Cualquier número es una expresión.

3. Si expresión1 y expresión2 son expresiones, entonces también lo son:

• expresión1 + expresión2

• expresión1 * expresión2

• ( expresión1 )

Las reglas 1 y 2 son reglas básicas (no recursivas), en tanto que la regla 3 define

expresiones en función de operadores aplicados a otras expresiones.

La división entre análisis léxico y análisis sintáctico es algo arbitraria. Un factor

para determinar la división es si una construcción del lenguaje fuente es

inherentemente recursiva o no. Las construcciones léxicas no requieren recursión,

Page 19: Libro de c++ (Teria/Practico)

mientras que las construcciones sintácticas suelen requerirla. No se requiere recursión

para reconocer los identificadores, que suelen ser cadenas de letras y dígitos que

comienzan con una letra. Normalmente, se reconocen los identificadores por el

simple examen del flujo de entrada, esperando hasta encontrar un carácter que no sea

ni letra ni dígito, y agrupando después todas las letras y dígitos encontrados hasta ese

punto en un componente léxico llamado identificador. Por otra parte, esta clase de

análisis no es suficientemente poderoso para analizar expresiones o proposiciones.

Por ejemplo, no podemos emparejar de manera apropiada los paréntesis de las

expresiones, o las palabras begin y end en proposiciones sin imponer alguna clase de

estructura jerárquica o de anidamiento a la entrada.

Análisis semántico

La fase de análisis semántico revisa el programa fuente para tratar de encontrar

errores semánticos y reúne la información sobre los tipos para la fase posterior de

generación de código. En ella se utiliza la estructura jerárquica determinada por la

fase de análisis sintáctico para identificar los operadores y operandos de expresiones

y proposiciones.

Un componente importante del análisis semántico es la verificación de tipos.

Aquí, el compilador verifica si cada operador tiene operandos permitidos por la

especificación del lenguaje fuente. Por ejemplo, las definiciones de muchos lenguajes

de programación requieren que el compilador indique un error cada vez que se use un

número real como índice de una matriz. Sin embargo, la especificación del lenguaje

puede imponer restricciones a los operandos, por ejemplo, cuando un operador

aritmético binario se aplica a un número entero y a un número real.

Fase de síntesis

Page 20: Libro de c++ (Teria/Practico)

Consiste en generar el código objeto equivalente al programa fuente. Sólo se

genera código objeto cuando el programa fuente está libre de errores de análisis, lo

cual no quiere decir que el programa se ejecute correctamente, ya que un programa

puede tener errores de concepto o expresiones mal calculadas. Por lo general el

código objeto es código de máquina relocalizable o código ensamblador. Las

posiciones de memoria se seleccionan para cada una de las variables usadas por el

programa. Después, cada una de las instrucciones intermedias se traduce a una

secuencia de instrucciones de máquina que ejecuta la misma tarea. Un aspecto

decisivo es la asignación de variables a registros.

Generación de código intermedio

Después de los análisis sintáctico y semántico, algunos compiladores generan

una representación intermedia explícita del programa fuente. Se puede considerar esta

representación intermedia como un programa para una máquina abstracta. Esta

representación intermedia debe tener dos propiedades importantes; debe ser fácil de

producir y fácil de traducir al programa objeto.

La representación intermedia puede tener diversas formas. Existe una forma

intermedia llamada "código de tres direcciones" que es como el lenguaje ensamblador

de una máquina en la que cada posición de memoria puede actuar como un registro.

El código de tres direcciones consiste en una secuencia de instrucciones, cada una de

las cuales tiene como máximo tres operandos. Esta representación intermedia tiene

varias propiedades:

• Primera.- Cada instrucción de tres direcciones tiene a lo sumo un

operador, además de la asignación, por tanto, cuando se generan estas

instrucciones, el traductor tiene que decidir el orden en que deben

efectuarse las operaciones.

Page 21: Libro de c++ (Teria/Practico)

• Segunda.- El traductor debe generar un nombre temporal para guardar los

valores calculados por cada instrucción.

• Tercera.- Algunas instrucciones de "tres direcciones" tienen menos de tres

operandos, por ejemplo, la asignación.

Optimización de código

La fase de optimización de código consiste en mejorar el código intermedio, de

modo que resulte un código máquina más rápido de ejecutar. Esta fase de la etapa de

síntesis es posible sobre todo si el traductor es un compilador (difícilmente un

interprete puede optimizar el código objeto). Hay mucha variación en la cantidad de

optimización de código que ejecutan los distintos compiladores. En los que hacen

mucha optimización, llamados "compiladores optimizadores", una parte significativa

del tiempo del compilador se ocupa en esta fase. Sin embargo, hay optimizaciones

sencillas que mejoran sensiblemente el tiempo de ejecución del programa objeto sin

retardar demasiado la compilación.

Tipos de traductores

Existen dos tipos importantes de traductores:

• Los que van adaptando las instrucciones conforme son encontradas. A este

proceso se lo llama interpretar y a los programas que lo hacen se los conoce

como intérpretes. La traducción es simultánea y se produce de forma

dialogada con el programador.

• Los que convierten el conjunto de instrucciones en lenguaje de programación,

al programa equivalente escrito en lenguaje de máquina. A ese proceso se lo

llama compilar y al programa traductor se le denomina compilador. No se

Page 22: Libro de c++ (Teria/Practico)

realiza simultáneamente y no hay un dialogo con el programador durante la

programación.

Intérprete

Un intérprete es un programa informático capaz de analizar y ejecutar otros

programas, escritos en un lenguaje de alto nivel. Los intérpretes se diferencian de los

compiladores en que mientras estos traducen un programa desde su descripción en un

lenguaje de programación al código máquina del sistema destino, los primeros (los

intérpretes) sólo realizan la traducción a medida que sea necesario, típicamente,

instrucción por instrucción, y normalmente no guardan el resultado de dicha

traducción.

Los programas interpretados suelen ser más lentos que los compilados debido a

la necesidad de traducir el programa mientras se ejecuta, pero a cambio son más

flexibles como entornos de programación y depuración (lo que se traduce, por

ejemplo, en una mayor facilidad para reemplazar partes enteras del programa o añadir

módulos completamente nuevos), y permiten ofrecer al programa interpretado un

entorno no dependiente de la máquina donde se ejecuta el intérprete, sino del propio

intérprete (lo que se conoce comúnmente como máquina virtual).

Comparando su actuación con la de un ser humano, un compilador equivale a

un traductor profesional que, a partir de un texto, prepara otro independiente

traducido a otra lengua, mientras que un intérprete corresponde al intérprete humano,

que traduce de viva voz las palabras que oye, sin dejar constancia por escrito.

En la actualidad, uno de los entornos más comunes de uso de los intérpretes

informáticos es Internet, debido a la posibilidad que estos tienen de ejecutarse

independientemente de la plataforma.

Compilador

Page 23: Libro de c++ (Teria/Practico)

Un compilador es un programa informático que traduce un programa escrito en

un lenguaje de programación a otro lenguaje de programación, generando un

programa equivalente que la máquina será capaz de interpretar. Usualmente el

segundo lenguaje es código máquina, pero también puede ser simplemente texto. Este

proceso de traducción se conoce como compilación.

Un compilador es un programa que permite traducir el código fuente de un

programa en lenguaje de alto nivel, a otro lenguaje de nivel inferior (típicamente

lenguaje máquina). De esta manera un programador puede diseñar un programa en un

lenguaje mucho más cercano a cómo piensa un ser humano, para luego compilarlo a

un programa más manejable por una computadora.

Normalmente los compiladores están divididos en dos partes (Fig. 1.5):

• Front End: es la parte que analiza el código fuente, comprueba su validez,

genera el árbol de derivación y rellena los valores de la tabla de símbolos.

Esta parte suele ser independiente de la plataforma o sistema para el cual

se vaya a compilar.

• Back End: es la parte que genera el código máquina, específico de una

plataforma, a partir de los resultados de la fase de análisis, realizada por el

Front End.

Page 24: Libro de c++ (Teria/Practico)

Figura 1. 2. Partes de un compilador

Esta división permite que el mismo Back End se utilice para generar el código

máquina de varios lenguajes de programación distintos y que el mismo Front End que

sirve para analizar el código fuente de un lenguaje de programación concreto sirva

para generar código máquina en varias plataformas distintas.

El código que genera el Back End normalmente no se puede ejecutar

directamente, sino que necesita ser enlazado por un programa enlazador (linker).

Page 25: Libro de c++ (Teria/Practico)

CAPITULO 2

INTRODUCCIÓN A LA PROGRAMACIÓN ORIENTADA A

OBJETOS

2.1. Introducción a la programación orientada a objetos.

Desde principios de la era computacional se crearon diversas técnicas de

programación que a medida han evolucionado para poder adaptarse a nuevos retos y

poder crear soluciones más realistas que se amolden a el entorno real.

Podemos notar que a inicios la programación era no estructurada. Este estilo de

Programación No Estructurada, consistía en un solo programa principal, el cual se

establece como una secuencia de comandos o instrucciones que modifican datos que

son a su vez globales en el transcurso de todo el programa.

Esta técnica de programación no estructurada ofrece tremendas desventajas una

vez que el programa se hace suficientemente grande. Por ejemplo, si la misma

secuencia de instrucciones se necesita en diferentes situaciones dentro del programa,

la secuencia debe ser repetida. Esto ha llevado a la idea de extraer estas secuencias,

dando origen a nuevas técnicas como lo son la programación procedimental y

modular, conduciéndonos a un estilo de programación estructurada.

La Programación Estructurada es un método de programación basado sobre

el concepto de la unidad y del alcance. La programación estructurada ofrece muchas

ventajas sobre la programación secuencial, es más fácil de leer y más conservable;

siendo muy flexible, facilitando el buen diseño de programas.

La programación estructurada, es un estilo de programación con el cual el

programador elabora programas, cuya estructura es la más clara posible, mediante el

uso de tres estructuras básicas de control lógico: secuencia, selección e iteración.

Page 26: Libro de c++ (Teria/Practico)

Los programas son más fáciles de entender. Un programa estructurado puede

ser leído en secuencia, de arriba hacia abajo, sin necesidad de estar saltando de un

sitio a otro en la lógica, lo cual es típico de otros estilos de programación. La

estructura del programa es más clara puesto que las instrucciones están más ligadas o

relacionadas entre si, por lo que es más fácil comprender lo que hace cada función.

Reducción del esfuerzo en las pruebas. El programa se puede tener listo para

producción normal en un tiempo menor del tradicional; por otro lado, el seguimiento

de las fallas o depuración se facilita debido a la lógica más visible, de tal forma que

los errores se pueden detectar y corregir más fácilmente. Los programas quedan

mejor documentados internamente.

A pesar de las múltiples ventajas que ofrecen la programación estructurada,

surge necesidades y problemas complejos los cuales necesitan recrear o representar

mundos reales, lo cual, se dificulta con la técnicas de programación estructurada.

La Programación Orientada a Objetos (POO) aporta un nuevo enfoque a los

retos que se plantean en la programación estructurada cuando los problemas a

resolver son complejos. Al contrario que la programación procedimental que enfatiza

en los algoritmos, la POO enfatiza en los datos. En lugar de intentar ajustar un

problema al enfoque procedimental de un lenguaje, POO intenta ajustar el lenguaje al

problema. La idea es diseñar formatos de datos que se correspondan con las

características esenciales de un problema. Los lenguajes orientados combinan en una

única unidad o módulo, tanto los datos como las funciones que operan sobre esos

datos. Tal unidad se llama objeto. Si se desea modificar los datos de un objeto, hay

que realizarlo mediante las funciones miembro del objeto. Ninguna otra función

puede acceder a los datos. Esto simplifica la escritura, depuración y mantenimiento

del programa.

La tecnología orientada a objetos se define como una metodología de diseño de

software que modela las características de objetos reales o abstractos por medio del

uso de clases y objetos. Hoy en día, la orientación a objetos es fundamental en el

desarrollo de software, sin embargo, esta tecnología no es nueva, sus orígenes se

Page 27: Libro de c++ (Teria/Practico)

remontan a la década de los años sesenta (Simula, uno de los lenguajes de

programación orientados a objetos más antiguos, fue desarrollado en 1967).

La Programación Orientada a Objetos, la podemos definir como una técnica

o estilo de programación que utiliza objetos como bloque esencial de construcción.

ASCUI

El objeto, es el concepto principal sobre el cual se fundamenta la programación

orientada a objetos, el cual puede ser visto como una entidad que posee atributos y

efectúa acciones. En el mundo real podemos encontrar cientos de ejemplos que

cumplen con ésta definición, algunos de ellos son: una bicicleta, un automóvil, una

persona, una computadora, etc.

Los programas creados con la POO, se organizan como un conjunto finito de

objetos que contienen datos y operaciones (funciones miembro en C++) que llaman a

esos datos y que se comunican entre sí mediante mensajes.(Figura 2.1).

Figura 2. 1. Representación POO.

Un programa orientado a objetos es una colección de clases.

Necesita de una función principal que cree objetos y comience la ejecución

mediante la invocación de sus funciones o métodos.

En primer lugar, se crean los objetos. Segundo, los mensajes se envían desde

unos objetos y se reciben en otros a medida que el programa se ejecuta. Y tercero, se

borran los objetos cuando ya no son necesarios y se recupera la memoria ocupada por

ellos.

Page 28: Libro de c++ (Teria/Practico)

Los objetos son tipos de datos abstractos definidos por el programador. En

realidad son unidades que contienen datos y funciones que operan sobre esos datos.

A los objetos también se les conoce como instancias de clase. A los elementos

de un objeto se les conoce como miembros (datos miembros y funciones miembro).

2.1.1. Características de la POO

Las características más importantes que debe soportar un lenguaje que lo

definen como "orientado a objetos", son:

• Abstracción.

• Encapsulamiento.

• Principio de ocultación.

• Polimorfismo.

• Herencia.

• Recolección de basura.

2.1.1.1. Abstracción.

Denota las características esenciales de un objeto, donde se capturan sus

comportamientos. Cada objeto en el sistema sirve como modelo de un "agente"

abstracto que puede realizar trabajo, informar y cambiar su estado, y "comunicarse"

con otros objetos en el sistema sin revelar cómo se implementan estas características.

Los procesos, las funciones o los métodos pueden también ser abstraídos y cuando lo

están, una variedad de técnicas son requeridas para ampliar una abstracción.

Un ejemplo de esto puede identificarse en un automóvil. Aunque se observa un

automóvil como un único ente, este estará compuesto por: características y métodos o

funciones. (Figura 2.2).

Page 29: Libro de c++ (Teria/Practico)

Figura 2. 2. Objeto abstracto: automóvil.

2.1.1.2. Encapsulamiento.

Significa reunir a todos los elementos que pueden considerarse pertenecientes a

una misma entidad, al mismo nivel de abstracción. Esto permite aumentar la cohesión

de los componentes del sistema.

La encapsulación es un mecanismo que consiste en organizar datos y métodos

de una estructura, conciliando el modo en que el objeto se implementa, es decir,

evitando el acceso a datos por cualquier otro medio distinto a los especificados. Por lo

tanto, la encapsulación garantiza la integridad de los datos que contiene un objeto.

El encapsulamiento nos permite considerar a los objetos como cajas negras:

como objetos que podemos utilizar sin enfocarnos en la forma en que trabajan.

Ejemplo: en un automóvil, un mecánico debe saber cómo trabaja el motor, la

transmisión, etc., pero un conductor, puede usar estos componentes sin preocuparse

por estos detalles, el automóvil encapsula todos los detalles de las partes que lo

constituyen, por lo que un conductor tan solo necesita conocer su interfaz: el

acelerador, el freno y el volante.

2.1.1.3. Principio de ocultación.

Page 30: Libro de c++ (Teria/Practico)

Cada objeto está aislado del exterior, es un módulo natural, y cada tipo de

objeto expone una interfaz a otros objetos que específica cómo pueden interactuar

con los objetos de la clase. El aislamiento protege a las propiedades de un objeto

contra su modificación por quien no tenga derecho a acceder a ellas, solamente los

propios métodos internos del objeto pueden acceder a su estado. Esto asegura que

otros objetos no pueden cambiar el estado interno de un objeto de maneras

inesperadas, eliminando efectos secundarios e interacciones inesperadas. Algunos

lenguajes relajan esto, permitiendo un acceso directo a los datos internos del objeto

de una manera controlada y limitando el grado de abstracción. La aplicación entera se

reduce a un agregado o rompecabezas de objetos.

El usuario de una clase en particular no necesita saber cómo están estructurados

los datos dentro de ese objeto, es decir, un usuario no necesita conocer la

implementación Al evitar que el usuario modifique los atributos directamente y

forzándolo a utilizar funciones definidas para modificarlos (llamadas interfaces), se

garantiza la integridad de los datos (por ejemplo, uno puede asegurarse de que el tipo

de datos suministrados cumple con nuestras expectativas bien que los se encuentran

dentro del periodo de tiempo esperado).

La encapsulación define los niveles de acceso para elementos de esa clase.

Estos niveles de acceso definen los derechos de acceso para los datos, permitiéndonos

el acceso a datos a través de un método de esa clase en particular, desde una clase

heredada o incluso desde cualquier otra clase. Existen tres niveles de acceso:

• Público: funciones de toda clase pueden acceder a los datos o métodos de una

clase que se define con el nivel de acceso público. Este es el nivel de

protección de datos más bajo

• Privado: el acceso a los datos está restringido a los métodos de esa clase en

particular. Este es nivel más alto de protección de datos

Page 31: Libro de c++ (Teria/Practico)

• Protegido: el acceso a los datos está restringido a las funciones de clases

heredadas, es decir, las funciones miembro de esa clase y todas las subclases

2.1.1.4. Polimorfismo

Comportamientos diferentes, asociados a objetos distintos, pueden compartir el

mismo nombre, al llamarlos por ese nombre se utilizará el comportamiento

correspondiente al objeto que se esté usando. O dicho de otro modo, las referencias y

las colecciones de objetos pueden contener objetos de diferentes tipos, y la

invocación de un comportamiento en una referencia producirá el comportamiento

correcto para el tipo real del objeto referenciado. Cuando esto ocurre en "tiempo de

ejecución", esta última característica se llama asignación tardía o asignación

dinámica. Algunos lenguajes proporcionan medios más estáticos (en "tiempo de

compilación") de polimorfismo, tales como las plantillas y la sobrecarga de

operadores de C++.

Polimorfismo de sobrecarga. El polimorfismo de sobrecarga ocurre cuando

las funciones del mismo nombre existen, con funcionalidad similar, en clases que son

completamente independientes una de otra (éstas no tienen que ser clases secundarias

de la clase objeto). Por ejemplo, la clase complex, la clase image y la clase link

pueden todas tener la función "display". Esto significa que no necesitamos

preocuparnos sobre el tipo de objeto con el que estamos trabajando si todo lo que

deseamos es verlo en la pantalla. Por lo tanto, el polimorfismo de sobrecarga nos

permite definir operadores cuyos comportamientos varían de acuerdo a los

parámetros que se les aplican. Así es posible, por ejemplo, agregar el operador + y

hacer que se comporte de manera distinta cuando está haciendo referencia a una

operación entre dos números enteros (suma) o bien cuando se encuentra entre dos

cadenas de caracteres (concatenación).

Page 32: Libro de c++ (Teria/Practico)

Polimorfismo paramétrico (también llamado polimorfismo de plantillas). El

polimorfismo paramétrico es la capacidad para definir varias funciones utilizando el

mismo nombre, pero usando parámetros diferentes (nombre y/o tipo). El

polimorfismo paramétrico selecciona automáticamente el método correcto a aplicar

en función del tipo de datos pasados en el parámetro. Por lo tanto, podemos por

ejemplo, definir varios métodos homónimos de addition() efectuando una suma de

valores.

• El método int addition(int, int) devolvería la suma de dos enteros.

• float addition(float, float) devolvería la suma de dos flotantes.

• char addition(char, char) daría por resultado la suma de dos caracteres.

Polimorfismo de inclusión (también llamado redefinición o subtipado). La

habilidad para redefinir un método en clases que se hereda de una clase base se llama

especialización. Por lo tanto, se puede llamar un método de objeto sin tener que

conocer su tipo intrínseco: esto es polimorfismo de subtipado. Permite no tomar en

cuenta detalles de las clases especializadas de una familia de objetos,

enmascarándolos con una interfaz común (siendo esta la clase básica). Imagine un

juego de ajedrez con los objetos rey, reina, alfil, caballo, torre y peón, cada uno

heredando el objeto pieza. El método movimiento podría, usando polimorfismo de

subtipado, hacer el movimiento correspondiente de acuerdo a la clase objeto que se

llama. Esto permite al programa realizar el movimiento de pieza sin tener que verse

conectado con cada tipo de pieza en particular.

2.1.1.5. Herencia.

Por herencia se entiende la capacidad de poder crear nuevas clases a partir de

alguna anterior, de forma que las nuevas "heredan" las características de sus ancestros

(propiedades y métodos). Se trata por tanto de la capacidad de crear nuevos tipos de

Page 33: Libro de c++ (Teria/Practico)

datos a partir de los anteriores. Una característica especial de la herencia es que si se

cambia el comportamiento de la clase antecesora (también llamada padre, base o

super), también cambiará el comportamiento de las clases derivadas de ella

(descendientes). Como puede deducirse fácilmente, la herencia establece lo que se

llama una jerarquía de clases del mismo aspecto que el árbol genealógico de una

familia. Se entiende también que estos conceptos representan niveles de abstracción

que permiten acercar la programación a la realidad del mundo físico tal como lo

concebimos. Por ejemplo, todas las guitarras, ya sean Eléctricas, ElectroAcústicas o

normales, cumplen con características similares como número de cuerdas (6), método

de afinación, cambio de tono, cambio de postura y rasgueo entre otras. Si no se

siguiera con una metodología de herencia, por cada clase (Guitarra Electrica y

ElectroAcústica) se tendría que repetir la definición de todos los atributos y métodos

que pertenecen a la clase padre Guitarra, que corresponde a la abstracción más amplia

del concepto "Guitarra". Mediante el uso de la herencia, se puede definir una clase

"Guitarra" que cumpla con todas las características generales del concepto guitarra y

sus evoluciones, con el fin de acotar el número de especificaciones de las clases

"GuitarraElectrica" y "ElectroAcústica", y permitir la herencia a éstas últimas clases

con las características del objeto padre. (Figura 2.3).

Page 34: Libro de c++ (Teria/Practico)

Figura 2. 3. Herencia.

2.1.1.6. Recolección de basura.

La Recolección de basura o Garbage Collection es la técnica por la cual el

ambiente de Objetos se encarga de destruir automáticamente, y por tanto desasignar

de la memoria, los Objetos que hayan quedado sin ninguna referencia a ellos. Esto

significa que el programador no debe preocuparse por la asignación o liberación de

memoria, ya que el entorno la asignará al crear un nuevo Objeto y la liberará cuando

nadie lo esté usando. En la mayoría de los lenguajes híbridos que se extendieron para

soportar el Paradigma de Programación Orientada a Objetos como C++ u Object

Pascal, esta característica no existe y la memoria debe desasignarse manualmente.

2.1.2. Uso de los lenguajes de POO

Actualmente una de las áreas más candentes en la industria y en el ámbito

académico es la orientación a objetos. La orientación a objetos promete mejoras de

Page 35: Libro de c++ (Teria/Practico)

amplio alcance en la forma de diseño, desarrollo y mantenimiento del software

ofreciendo una solución a largo plazo a los problemas y preocupaciones que han

existido desde el comienzo en el desarrollo de software: la falta de portabilidad del

código y reusabilidad, código que es difícil de modificar, ciclos de desarrollo largos y

técnicas de codificación no intuitivas.

La introducción de tecnología de objetos como una herramienta conceptual para

analizar, diseñar e implementar aplicaciones permite obtener aplicaciones más

modificables, fácilmente extensibles y a partir de componentes reusables. Esta

reusabilidad del código disminuye el tiempo que se utiliza en el desarrollo y hace que

el desarrollo del software sea más intuitivo porque se piensa naturalmente en

términos de objetos más que en términos de algoritmos de software.

La Programación Orientada a Objetos ofrece algunas de ventajas respecto a

otros paradigmas de la programación:

• Fomenta la reutilización y extensión del código.

• Facilita el mantenimiento del software.

• Permite crear sistemas más complejos.

• Agiliza el desarrollo de software.

• Facilita la creación de programas visuales.

• Facilita el trabajo en equipo relacionar el sistema al mundo real.

2.2. Introducción a las Clases y Objetos.

2.2.1. Conceptos Clases y Objetos

Un objeto se define como la unidad que en tiempo de ejecución realiza las

tareas de un programa. También a un nivel más básico se define como la instancia de

una clase.

Page 36: Libro de c++ (Teria/Practico)

Estos objetos interactúan unos con otros, en contraposición a la visión

tradicional en la cual un programa es una colección de subrutinas (funciones o

procedimientos), o simplemente una lista de instrucciones para el computador. Cada

objeto es capaz de recibir mensajes, procesar datos y enviar mensajes a otros objetos

de manera similar a un servicio. (Figura 2.4).

Un objeto es una abstracción encapsulada que tiene un estado interno como

dado por una lista de atributos cuyos valores son únicos al objeto. El objeto también

conoce la lista de mensajes al que puede responder y cómo responderá en cada uno.

Un objeto está constituido por:

• Atributos: estos son los datos que caracterizan al objeto. Son variables que

almacenan datos relacionados al estado de un objeto.

• Métodos (usualmente llamados funciones miembro): Los métodos de un

objeto caracterizan su comportamiento, es decir, son todas las acciones

(denominadas operaciones) que el objeto puede realizar por sí mismo. Estas

operaciones hacen posible que el objeto responda a las solicitudes externas (o

que actúe sobre otros objetos). Además, las operaciones están estrechamente

ligadas a los atributos, ya que sus acciones pueden depender de, o modificar,

los valores de un atributo.

Figura 2. 4. Constitución de objetos.

Una clase es la estructura de un objeto, es decir, la definición de todos los

elementos de que está hecho un objeto. Un objeto es, por lo tanto, el "resultado" de

Page 37: Libro de c++ (Teria/Practico)

una clase. En realidad, un objeto es una instancia de una clase, por lo que se pueden

intercambiar los términos objeto o instancia (o incluso evento).

Los objetos son miembros o pertenecen a una clase. Una clase es un tipo

definido por el usuario que determina las estructuras de datos y las operaciones

asociadas con ese tipo. Las clases son como plantillas o modelos que describen cómo

se construyen ciertos tipos de objetos. Incluye en su descripción un nombre para el

tipo de objeto, una lista de atributos (y sus tipos), y una lista de mensajes con

métodos correspondientes al que un objeto de la clase puede responder. Cada vez que

se construye un objeto de una clase, se crea una instancia de esa clase.

Una clase se compone de dos partes:

• Atributos (denominados, por lo general, datos miembros): esto es, los datos

que se refieren al estado del objeto

• Métodos (denominados, por lo general, funciones miembros): son funciones

que pueden aplicarse a objetos

2.2.2. Sintaxis de la definición de clases

Una clase define la estructura que tendrán los objetos que se creen a partir de

esta, la cual contiene las características de los objetos o atributos y los métodos o

funciones miembros necesarios para modificar y evaluar los atributos. Se describen

también el tipo de ocultamiento que posee cada atributo y método.

Si tenemos una clase llamada auto, los objetos Peugeot y Renault serán

instancias de esa clase. También puede haber otros objetos Peugeot 406,

diferenciados por su número de modelo. Asimismo, dos instancias de una clase

pueden tener los mismos atributos, pero considerarse objetos distintos independientes.

En un contexto real: dos camisas pueden ser idénticas, pero no obstante, también ser

diferentes de alguna manera. Sin embargo, si las mezclamos es imposible distinguir

una de la otra.

Page 38: Libro de c++ (Teria/Practico)

La sintaxis típica de una clase es:

clase Nombre { // Variables miembro (habitualmente privadas) miembro_1; //lista de miembros miembro_2; miembro_3;

... // Funciones o métodos (habitualmente públicas) funcion_miembro_1( ); //funciones miembro conocidas funcion_miembro_2 ( ); // funciones como métodos

... // Propiedades (habitualmente públicas) propiedad_1; propiedad_2; propiedad_3;

... }

2.2.3. Mensajes y métodos

Los objetos, las clases y sus instancias se comunican a través del paso de

mensajes. Esto elimina la duplicación de datos y garantiza que no se propaguen los

efectos de los cambios en las estructuras de datos encapsuladas dentro de objetos

sobre otras partes del sistema. A menudo los mensajes se realizan como llamadas a

funciones.

El mensaje es la acción que realiza un objeto sobre otro, la petición de servicio.

Se considera al mensaje como una llamada, posiblemente comprendido en el

tiempo de ejecución, al objeto o clase que pide un servicio o la notificación de un

suceso específico.

Un mensaje es representado por un identificador, o combinación de

identificadores que implican una acción para ser tomada por un objeto. Los mensajes

pueden ser simples o pueden incluir parámetros que afectan como el objeto receptor

Page 39: Libro de c++ (Teria/Practico)

responde. La manera en que un objeto responde al mensaje puede también ser

afectado por los valores de sus atributos.

El método es la respuesta al mensaje, la forma de proporcionar el servicio. Un

método consiste en la implementación en una clase de un protocolo de respuesta a los

mensajes dirigidos a los objetos de la misma. La respuesta a tales mensajes puede

incluir el envío por el método de mensajes al propio objeto y aun a otros, también

como el cambio del estado interno del objeto.(Figura 2.5).

Figura 2. 5. Comunicación entre objetos.

2.2.4. Métodos constructores y destructores

Un método constructor es una función miembro especial que lleva a cabo la

inicialización automática de cada objeto de la clase en el momento en que se declara.

Un constructor es una función miembro pública con el mismo nombre de la clase, sin

indicación de tipo devuelto y se ejecuta automáticamente al crearse un objeto de la

clase.

Cuando un objeto no va a ser utilizado, el espacio de memoria de dinámica que

utiliza ha de ser liberado, así como los recursos que poseía, permitiendo al programa

disponer de todos los recursos posibles. A esta acción se la da el nombre de

destrucción del objeto.

El sistema de recogida de basura se ejecuta periódicamente, buscando objetos

que ya no estén referenciados.

Todas las clases tienen uno y sólo un destructor. Si no se indica lo contrario, el

compilador genera un destructor por defecto cuya única función es liberar el espacio

que ocupan los miembros estáticos (no reservados dinámicamente) del objeto que se

Page 40: Libro de c++ (Teria/Practico)

destruye. El destructor es una función miembro que realiza la tarea de “limpieza”. Un

destructor es una función miembro pública con el mismo nombre de la clase pero

precedido por el símbolo (~), sin indicación de tipo devuelto, que se ejecuta

automáticamente cuando se destruye un objeto.

2.2.5. Definición de datos miembro

Un dato miembro representan los datos internos del objeto, se define fuera de

cualquier método pero dentro de la clase, es una buena costumbre definirlos

inmediatamente después de la llave que abre la clase, es decir antes de la definición

de cualquier método.

Para definir un dato miembro:

Modificador_de_visibilidad tipo nombre;

El tipo de un dato miembro puede ser un tipo de datos primitivo o un objeto.

Los modificadores de visibilidad controlan el acceso al dato miembro de la clase.

Pueden ser aplicados tanto a métodos como a datos miembro y son:

• Público

• Privado

• Protegidos

Si un método o dato tiene visibilidad pública entonces puede ser referenciado

directamente desde fuera del objeto y si tiene visibilidad privada no puede ser

referenciada externamente solo puede ser usada dentro de la clase, si la visibilidad es

protegida solo podrá ser referenciada a través de métodos dentro de la misma clase

únicamente. En general, si un método ofrece servicios de una clase, puede ser

declarado con visibilidad pública. La encapsulación evita que los valores sean

cambiados por cualquiera. Por defecto la visibilidad es privada. Los datos miembros

Page 41: Libro de c++ (Teria/Practico)

de una clase tienen que ser privados, no públicos ni protegidos. De esta manera

aseguramos la encapsulación de la clase.

2.2.6. Definición de funciones miembro.

Son funciones que pueden aplicarse a los objetos, son las acciones o

operaciones que puede realizar un objeto. Las funciones pueden ser: estáticas, no

estáticas, constructoras, etc. Para definir una función:

Modificador_Visibilidad modificador_tipoRegreso nombre (parámetros)

{ //cuerpo

}

El modificador de visibilidad puede ser público o privado. El tipo de regreso

puede ser: un tipo primitivo, una clase o bien sin retorno. Los nombres de las

funciones miembro (métodos) de una clase tiene que ser autoexplicativos de la

finalidad de dichos métodos. Como ocurría con las funciones.

Las funciones miembros de una clase que no modifiquen los datos miembros ni

llamen a funciones que modifiquen los datos miembros de la misma deberían

declararse como constantes. De otra forma los objetos creados como constantes no

tendrían acceso a las funciones miembro de la clase.

2.3. Estructura de un Programa Orientado a Objetos.

La programación orientada a objetos (POO) es una de las técnicas más

modernas de desarrollo que trata de disminuir el coste del software, aumentando la

eficiencia y reduciendo el tiempo de espera para la puesta en escena de una nueva

aplicación. Por eso, donde la POO toma verdadera ventaja es en poder compartir y

Page 42: Libro de c++ (Teria/Practico)

reutilizar el código. Existen varios lenguajes que permiten escribir un programa

orientado a objetos y entre ellos se encuentra C++. Se trata de un lenguaje de

programación basado en el lenguaje C, compilado, estandarizado, ampliamente

difundido, y con una biblioteca estándar C++ que lo ha convertido en un lenguaje

universal, de propósito general, y ampliamente utilizado tanto en el ámbito

profesional como en el educativo.

El lenguaje C++ es tomado como lenguaje de aprendizaje en este material de

apoyo por todas las características que nos brinda

C++ es un lenguaje de programación diseñado a mediados de los años 1980 por

Bjarne Stroustrup. La intención de su creación fue el extender al exitoso lenguaje de

programación C con mecanismos que permitan la manipulación de objetos. En ese

sentido, desde el punto de vista de los lenguajes orientados a objetos, el C++ es un

lenguaje híbrido. Posteriormente se añadieron facilidades de programación genérica,

que se sumó a los otros dos paradigmas que ya estaban admitidos (programación

estructurada y la programación orientada a objetos). Por esto se suele decir que el

C++ es un lenguaje de programación multiparadigma.

Una particularidad del C++ es la posibilidad de redefinir los operadores

(sobrecarga de operadores), y de poder crear nuevos tipos que se comporten como

tipos fundamentales.

La meta de C++ es mejorar la productividad. Ésta viene por muchos caminos,

pero el lenguaje está diseñado para ayudarle todo lo posible, y al mismo tiempo

dificultarle lo menos posible con reglas arbitrarias o algún requisito que use un

conjunto particular de características. C++ está diseñado para ser práctico; las

decisiones de diseño del lenguaje C++ estaban basadas en proveer los beneficios

máximos al programador.

2.3.1. Estructura de un programa en C ++.

Page 43: Libro de c++ (Teria/Practico)

El lenguaje de programación C++ nos expresa un conjunto le reglas semánticas,

sintácticas, léxico, necesarias para que la computadora pueda entender lo que se pide

o quiere, de no ser adecuada una expresión introducida no podrá ser entendida.

Existen variedad de errores sintácticos que la maquina es capaz de discernir y

denunciar, para mayor facilidad del usuario.

Un programa en el lenguaje C++ estará compuesto por:

• Comentarios: Muestra información en cuanto a que realiza el programa, la

utilidad de las funciones, variables y objetos, estos no serán procesados por el

compilador.

• Encabezados: se realizan las llamadas y accesos a archivos de Biblioteca de

funciones estándar y diseñadas por los usuarios.

• Definiciones de prototipos: se definen las estructuras de nuevos tipos de

datos como lo son las clases, estructuras, etc., se implementan las funciones

que cumplirán un objetivo determinado.

• Declaraciones globales: Se manifiestan el uso de variables, objetos,

constantes, etc., el ámbito de estas se extiende desde el punto en el que se

definen hasta el final del programa, proporcionan un mecanismo de

intercambio de información entre funciones sin necesidad de utilizar

argumentos. Además se declaran las existencias de funciones si no han sido

implementadas.

• Declaraciones locales: Se manifiesta la existencia de variables, objetos,

constantes, etc., que serán utilizadas en una función determinada o cuerpo, la

cual no podrá extender su ámbito de existencia fuera de la función.

• Declaración de la función principal main( ): La función main es

imprescindible en cualquier programa C/C++ representa el punto de inicio de

su ejecución. Es la función de control principal de un programa, reflejándose

como el cuerpo del mismo programa.

Page 44: Libro de c++ (Teria/Practico)

• Implementación y cuerpo de funciones: Se define la forma o cuerpo de una

función para resolver determinada tarea.

Ejemplo:

#include <iostream.h> // Encabezados permite inclusión de biblioteca

class Punto { // Definición de prototipo

public: int x, y;

public:

Punto(int x, int y) {x=0; y=0;}

int extraer_x( )

{ return x; }

int extraer_y( )

{ return y; }

};

Punto aux; //Objeto aux declarado global void main( ) //Cuerpo de Programa

{

Punto punto; // Objeto punto declarado local

cout << "Coordenada X:" << punto.extraer_x( ) << endl;

cout << "Coordenada Y:" << punto.extraer_y( )<< endl;

}

Como observamos en el ejemplo, un programa será una secuencia de líneas que

contendrán instrucciones, expresiones, sentencias, directivas de compilación,

comentarios, símbolos, etc.

2.3.1.1. Operadores de línea

En el lenguaje se utilizaran conjuntos de símbolos denominados operadores de

línea que permiten dar orden y control en las líneas de código. Entre estos tenemos:

Page 45: Libro de c++ (Teria/Practico)

operador de separación, operador de fin de instrucción, operadores de comentarios,

operadores de agrupación o bloque.

2.3.1.1.1. Operadores de comentarios ( //, /* */)

Estos símbolos nos permiten indicar la presencia de un comentario, el cual no

es más que la documentación de algún propósito a cumplir en el programa, solo será

de carácter informativo a los usuarios. Es decir, lo encontrado dentro de estos no será

procesado por el compilador, por lo tanto la maquina no notara la existencia del

contenido en la ejecución del programa.

Los comentarios se introducirán en el programa separados por /* y */

(comentario de bloque) o comenzándolos con // (comentario de línea). Los

comentarios entre /* y */ pueden tener la longitud que queramos, pero no se anidan,

es decir, si escribimos /* hola /* amigo */ mío */, el compilador interpretará que el

comentario termina antes de mío, y dará un error. Los comentarios que comienzan

por // sólo son válidos hasta el final de la línea en la que aparecen. El uso de los

comentarios son opcionales ya que no intervienen directamente sobre el programa.

2.3.1.1.2. Operador de fin de instrucción (;)

El punto y coma es uno de los operadores más usados en C++; este se usa con

el fin de indicar el final de una línea de instrucción. El punto y coma es de uso

obligatorio, toda instrucción deberá ser finalizada por este operador sino se generara

un error, excepto en el uso de directivas y definición de funciones.

También es conocido como sentencia nula al ser controlada por una sentencia

cíclica o al usarse innecesariamente de forma repetida.

instrucción1; // el operador ; indica el fin de la instrucción1

Page 46: Libro de c++ (Teria/Practico)

instruccion2;;; // el primer operador ; indica el fin de la instrucción2

// los siguiente operadores ; indica sentencias nulas

El punto y coma se usa también separador de contadores, condicionales e

incrementadores dentro de un sentencia for.

2.3.1.1.3. Operadores de agrupación o bloque ({ })

Un bloque es un grupo de instrucciones contenidas entre los símbolos de llave

izquierda '{' (inicio de bloque) y llave derecha '}' (fin de bloque). Las sentencias

compuestas se agrupan en bloques que pueden contener combinaciones de sentencias

elementales (denominadas sentencias de expresión) y otras sentencias compuestas.

Así las sentencias compuestas pueden estar anidadas, una dentro de otra. Cada inicio

de bloque debe tener un fin de bloque.

Su uso es obligatorio en la definición de funciones, prototipos, sentencias que

controles más de una instrucción y opcionalmente pueden aparecer en cualquier otra

parte del programa.

void main()

{ Instrucción_a;

{ Instrucción_b;

Instrucción_c;

}

}

2.3.1.1.4. Operador separación (,)

La coma tiene una doble función, por una parte separa elementos de una lista de

argumentos de una función. Por otra, puede ser usado como separador en expresiones

Page 47: Libro de c++ (Teria/Practico)

"de coma". Ambas funciones pueden ser mezcladas, pero hay que añadir paréntesis

para resolver las ambigüedades.

E1, E2, ..., En ;

En una expresión "de coma", cada operando es evaluado como una expresión,

pero los resultados obtenidos anteriormente se tienen en cuenta en las subsiguientes

evaluaciones. Por ejemplo:

func(i, (j = 1, j + 4), k);

Llamará a la función con tres argumentos: (i, 5, k). La expresión de coma (j = 1,

j+4), se evalúa de izquierda a derecha, y el resultado se pasará como argumento a la

función.

2.3.1.2. Directivas

El preprocesador analiza el fichero fuente antes de la fase de compilación real,

y realiza las sustituciones de macros y procesa las directivas de compilación (C++ es

un lenguaje compilado).

Una directiva de compilación o preprocesador es una línea cuyo primer carácter

es un #. Las directivas serán instrucciones que le daremos al compilador para

indicarle que realice alguna operación antes de compilar nuestro programa, como el

incluir funciones de alguna biblioteca como son los encabezados.

Algunas de las directivas tenemos:

• #define, sirve para definir macros. Las macros suministran un sistema para la

sustitución de palabras, con y sin parámetros.

Page 48: Libro de c++ (Teria/Practico)

#define identificador_de_macro <secuencia>

El preprocesador sustituirá cada ocurrencia del identificador_de_macro

en el fichero fuente, por la secuencia. Cada sustitución se conoce como una

expansión de la macro. La secuencia es llamada a menudo cuerpo de la macro.

• #include, permite insertar ficheros externos dentro de nuestro fichero de

código fuente. Estos ficheros son conocidos como ficheros incluidos, ficheros

de cabecera o "headers".

#include <nombre de fichero cabecera>

#include "nombre de fichero de cabecera"

#include identificador_de_macro

El preprocesador elimina la línea #include y, conceptualmente, la

sustituye por el fichero especificado. El tercer caso haya el nombre del fichero

como resultado de aplicar la macro.El código fuente en si no cambia, pero el

compilador observa el fichero incluido.

La diferencia entre escribir el nombre del fichero entre < > o " ", está en

el algoritmo usado para encontrar los ficheros a incluir. En el primer caso el

preprocesador buscará en los directorios "include" definidos en el compilador

(librerías estándar). En el segundo, se buscará primero en el directorio actual, es

decir, en el que se encuentre el fichero fuente, si no existe en ese directorio, se

trabajará como el primer caso (librerías creadas por el usuario).

2.3.1.3. Palabras reservadas

Page 49: Libro de c++ (Teria/Practico)

El lenguaje C++ está compuesto por un conjunto de palabras reservadas que

tiene un significado determinado para el compilador, de manera tal que cuando las

encuentra en el programa sabe que hay que llevar a cabo una determinada acción.

Las variables que se declaren a lo largo de todo el programa, así como las

funciones y demás no pueden llevar el nombre de estas palabras reservadas, ya que

son de uso por parte del compilador, ya que si se hace esto, se producirían errores, por

lo que es importante tener una buena familiarización con las mismas, a fin de no

cometer errores.

Algunas de las palabras reservadas del lenguaje se muestran en la tabla

siguiente (Figura 2.6.):

Palabras reservadas lenguaje C++ asm auto bool break case

catch char class const continue

default delete do double else

enum explicit extern false float

for friend goto if inline

int long mutable namespace new

operator private protected public register

return short signed sizeof static

struct switch template this throw

true try typedef typename union

unsigned using virtual void volatile

while

Figura 2. 6. Tabla de palabras reservadas en C++.

• asm. Medio definido por la puesta en práctica de utilización de lenguaje

de ensamblaje a lo largo de C++ .

Page 50: Libro de c++ (Teria/Practico)

• break. La declaración de pausa o descanso manda pasar el argumento al

partidario de la declaración, es utilizada en las llamadas a do, while, for o

switch.

• case. Se define dentro de una estructura switch, case, y default.

• const. Variable contante cuyo valor no puede ser alterado.

• continue. Envío de los pasos a seguir sin detenerse como es caso siguiente.

• catch. Maneja una excepción generado por un throw.

• class. Define una nueva clase. Pueden crearse objetos de esta clase.

• delete. Destruye un objeto de memoria creado con new.

• new. Asigna dinámicamente un objeto de memoria libre. Determina

automáticamente el tamaño del objeto.

• friend. Declara una función o una clase que sea un “friend (amigo)” de otra

clase. Los amigos pueden tener acceso a todos los miembros de datos y

a todas las funciones miembro de una clase.

• operador. Declara un operador “homónimo”.

• private. Un miembro de clase accesible a funciones miembro y a funciones

friend de la

• clase de miembros private.

• protected. Una forma extendida de acceso private; tambièn se puede tener

acceso a los miembros protected por funciones miembros de clases derivadas

y amigos de clases derivadas.

• public. Un miembro de clase accesible a cualquier función.

• template. Declara como construir una clase o una función, usando una

variedad de tipos.

• this. Un apuntador declarado en forma implícita en toda función de miembro

no static de una clase. Señales al objeto al cual esta función miembro ha sido

invocada.

Page 51: Libro de c++ (Teria/Practico)

• throw. Transfiere control a un manejador de excepción o termina la ejecución

del programa si no puede ser localizado un manejador apropiado.

• virtual. Declara una función virtual.

2.3.1.4. Identificadores.

Los identificadores son palabras no reservadas correspondientes a nombres de

declaraciones de variables, constantes, prototipos, funciones, etc.

Debemos seguir ciertas reglas al nombrar tipos de datos, variables, funciones,

etc. Los identificadores válidos del C++ son los formados a partir de los caracteres

del alfabeto (el inglés, no podemos usar ni la ñ ni palabras acentuadas), los dígitos

(0...9) y el subrayado (_), la única restricción es que no podemos comenzar un

identificador con un dígito (es así porque se podrían confundir con literales

numéricos).

Hay que señalar que el C++ distingue entre mayúsculas y minúsculas, por lo

que Hola y hola representan dos cosas diferentes. Hay que evitar el uso de

identificadores que sólo difieran en letras mayúsculas y minúsculas, porque inducen a

error.

2.3.1.5. Ejemplo de un programa en C++.

Un programa simple puede ser el siguiente:

/*

Este es un programa simple en C++, escribe una frase

en la pantalla

*/

#include <iostream.h>

Page 52: Libro de c++ (Teria/Practico)

int main( )

{

cout << "Hola mundo\n"; // imprime en la pantalla la frase "hola mundo"

return 0;

}

Analizando el código del ejemplo observamos:

La primera parte separada entre /* y */ es un comentario. Es recomendable que

se comenten los programas, explicando que es lo que estamos haciendo en cada caso,

para que cuando se lean sean más comprensibles.

La línea que empieza por # es una directiva. En este caso indica que se incluya

el fichero "iostream.h", que contiene las definiciones para entrada/salida de datos en

C++.

En la declaración de main ( ) hemos incluido la palabra reservada int, que

indica que la función devuelve un entero al finalizar su propósito.

La llave izquierda nos indica el inicio del cuerpo de la función main ( ).

La sentencia separada entre llaves indica que se escriba la frase "Hola mundo".

El operador << escribe (inserta) el segundo argumento en el primero. En este caso la

cadena "Hola mundo\n" se escribe en la salida estándar (cout). El carácter \ seguido

de otro carácter indica un solo carácter especial, en este caso un salto de línea (\n).

La palabra reservada return nos indica retornar un valor de la mismo tipo que

el definido en la función. En este caso retornamos el valor numérico entero 0.

La llave derecha nos indica el fin del cuerpo de la función main ( ).

Page 53: Libro de c++ (Teria/Practico)

CAPITULO 3

TIPOS DE DATOS

3.1. Tipos de datos.

C++ no soporta un gran número de tipos de datos predefinidos, pero tiene la

capacidad para crear sus propios tipos de datos. Posee un conjunto de tipos simples

correspondientes a las unidades de almacenamiento típicas de un computador y a las

distintas maneras de utilizarlos. Todos los tipos de datos simples o básicos de C/C++

son, esencialmente, números. Los tres tipos de datos básicos son:

• Enteros: int • Números flotantes (reales): float, double • Caracteres: char

Los tipos simples de datos admitidos se muestran en la siguiente tabla de rango

de valores. (Figura 3.1)

Denominación Tipo de datos Tamaño en bits Rango de valores

char Carácter 8 de 0 a 255

int Número entero 16 de –32768 a 32767

float Número real de

precisión simple 32 de 3.4 x 10-38 a 3.4 x 1038

double Número real de

precisión doble 64 de 1.7 x 10-308 a 1.7 x 10308

void Tipo vacío 0 sin valor

Figura 3. 1. Tabla de los tipos de datos simples en C++.

Page 54: Libro de c++ (Teria/Practico)

Los tamaños en bits pueden variar dependiendo del compilador empleado. Por

ejemplo, gcc interpreta que el entero es de 32 bits, y para usar enteros de 16 bits hay

que indicarlo expresamente. Por tanto, no debe usted presuponer ningún tamaño

concreto para los tipos si quiere escribir programas portables.

El tipo char se usa normalmente para variables que guardan un único carácter,

aunque lo que en realidad guardan es un código ASCII, es decir, un número entero de

8 bits sin signo (de 0 a 255). Los caracteres se escriben siempre entre comillas

simples (‘…’). Por lo tanto, si suponemos que x es una variable de tipo char, estas dos

asignaciones tienen exactamente el mismo efecto, ya que 65 es el código ASCII de la

letra A:

x = 'A';

x = 65;

A diferencia de las comillas simples de los caracteres sueltos cadenas de

caracteres se escriben con comillas dobles (”…”).

El tipo int se usa para números enteros, mientras que los tipos float y double

sirven para números reales. El segundo permite representar números mayores, a costa

de consumir más espacio en memoria.

Un tipo especial del C++ es el denominado void (vacío). Este tipo tiene

características muy peculiares, ya que es sintácticamente igual a los tipos elementales

pero sólo se emplea junto a los derivados, es decir, no hay objetos del tipo void. Se

emplea para especificar que una función no devuelve nada, para declarar funciones

sin argumentos; o como base para punteros a objetos de tipo desconocido. Por

ejemplo:

void BoraPrantalla (void);

indica que la función BorraPantalla no tiene parámetros y no retorna nada.

Page 55: Libro de c++ (Teria/Practico)

3.1.1. Modificadores de tipo.

Existen, además, unos modificadores de tipo que pueden preceder a los tipos de

datos. Dichos modificadores son:

• signed: obliga a que los datos se almacenen con signo. • unsigned: los datos se almacenan sin signo. • long: los datos ocuparán el doble de espacio en bits del habitual, y, por lo

tanto, aumentará su rango de valores. • short: los datos ocuparán la mitad del espacio habitual, y, por lo tanto,

disminuirá su rango de valores..

De este modo, nos podemos encontrar, por ejemplo, con estos tipos de datos:

• unsigned int: Número entero de 16 bits sin signo. Rango: de 0 a 65535. • signed int: Número entero de 16 bits con signo. No tiene sentido, porque el

tipo int ya es con signo por definición, pero es sintácticamente correcto. • signed char: Carácter (8 bits) con signo. Rango: de –128 a 127 • long int: Número entero de 32 bits. Rango: de –2147483648 a 2147483647

Incluso podemos encontrar combinaciones de varios modificadores. Por

ejemplo:

• unsigned long int: Número entero de 32 bits sin signo. Rango: de 0 a 4294967295

3.1.2. Tipos enumerados.

Un tipo especial de tipos enteros son los tipos enumerados. Estos tipos sirven

para definir un tipo que sólo puede tomar valores dentro de un conjunto limitado de

valores. Estos valores tienen nombre, luego se da una lista de constantes asociadas a

este tipo. La sintaxis es:

enum booleano {FALSE, TRUE}; // definimos el tipo booleano

Page 56: Libro de c++ (Teria/Practico)

Aquí hemos definido el tipo booleano que puede tomar los valores FALSE o

TRUE. En realidad hemos asociado la constante FALSE con el número 0, la

constante TRUE con 1, y si hubiera más constantes seguiríamos con 2, 3, etc.

Si por alguna razón nos interesa dar un número concreto a cada valor podemos

hacerlo en la declaración:

enum colores {rojo = 4, azul, verde = 3, negro = 1};

El azul tomará el valor 5 (4+1), ya que no hemos puesto nada. También se

pueden usar números negativos o constantes ya definidas.

Si al definir un tipo enumerado no se le da nombre al tipo declaramos una serie

de constantes:

enum { CERO, UNO, DOS };

Hemos definido las constantes CERO, UNO y DOS con los valores 0, 1 y 2.

3.1.3. Tipos derivados

De los tipos fundamentales podemos derivar otros mediante el uso de los

siguientes operadores de declaración:

* Puntero: Para cualquier tipo T, el puntero a ese tipo es T*. Una variable de

tipo T* contendrá la dirección de un valor de tipo T.

& Referencia: Una referencia es un nombre alternativo a un objeto, se emplea

para el paso de argumentos y el retorno de funciones por referencia. T& significa

referencia a tipo T.

Page 57: Libro de c++ (Teria/Practico)

[] Arreglos: Para un tipo T, T[n] indica un tipo arreglo con n elementos. Los

índices del arreglo empiezan en 0, luego llegan hasta n-1. Podemos definir arreglos

multidimensionales como arreglos de arreglos.

() Función: La declaración de una función nos da el nombre de la función, el

tipo del valor que retorna y el número y tipo de parámetros que deben pasársele.

Ejemplos:

int *n; // puntero a un entero

int v[20]; // arreglo de 20 enteros

int *c[20]; // arreglo de 20 punteros a entero

void f(int j); // función con un parámetro entero

3.1.4. Tipos compuestos

Los tipos de datos compuestos en C++ son:

• Estructuras. Las estructuras son el tipo equivalente a los registros de otros

lenguajes, se definen poniendo la palabra struct delante del nombre del tipo y

colocando entre llaves los tipos y nombres de sus campos. Si después de

cerrar la llave ponemos una lista de variables las declaramos a la vez que

definimos la estructura.

• Uniones. Las uniones son idénticas a las estructuras en su declaración, con la

particularidad de que todos sus campos comparten la misma memoria (el

tamaño de la unión será el del campo con un tipo mayor).

• Clases. Las clases son estructuras con una serie de características especiales.

3.1.5. Datos estáticos y datos dinámicos

Page 58: Libro de c++ (Teria/Practico)

Otra forma de clasificar los tipos de datos:

• Datos estáticos: su tamaño y forma es constante durante la ejecución de un

programa y, por tanto, se determinan en tiempo de compilación. El ejemplo

típico son los arreglos. Tienen el problema de que hay que dimensionar la

estructura de antemano, lo que puede conllevar desperdicio o falta de

memoria.

• Datos dinámicos: su tamaño y forma es variable (o puede serlo) a lo largo de

un programa, por lo que se crean y destruyen en tiempo de ejecución. Esto

permite dimensionar la estructura de datos de una forma precisa: se va

asignando memoria en tiempo de ejecución según se va necesitando.

3.2. Variables y constantes.

3.2.1. Variable.

En C/C++ una variable es una posición con nombre en memoria donde se

almacena un valor de un cierto tipo de dato y puede ser modificado. Las variables

pueden almacenar todo tipo de datos: Caracteres, números, estructuras.

Las variables están compuestas por dos partes importantes que son:

• El contenido, que es el valor que se almacena o está almacenado en la

variable y está directamente relacionada al mismo nombre de la variable.

• La dirección de memoria, que nos indica la posición o dirección donde se

almacena el contenido de la variable en la memoria de ejecución. Esta se

representa mediante el operador de dirección & acompañada del nombre de la

variable.

Page 59: Libro de c++ (Teria/Practico)

Sea una variable con el nombre A. Se representa de esta: su contenido es A, y

su dirección es &A. (Figura 3.2)

Figura 3. 2. Variable.

3.2.1.1. Declaración de las variables

Una variable típicamente tiene un nombre (un identificador) que describe su

propósito. Toda variable utilizada en un programa debe ser declarada previamente. La

definición utilizada en un programa en cualquier parte del programa. Una definición

reserva un espacio de almacenamiento en memoria. El procedimiento para definir

(crear) una variable es escribir el tipo de dato, el identificador o nombre de la variable

y, en ocasiones, el valor inicial que tomará.

La declaración de variables puede considerarse como una sentencia. Desde este

punto de vista, la declaración terminará con un ";".

Sintaxis de declaración de una variable:

tipodato nombrevariable;

Ejemplos:

Page 60: Libro de c++ (Teria/Practico)

int a;

float d;

char e;

Sintaxis de declaración de n variables:

tipodato nomvar1, nomvar2, nomvar3,…, nomvarn;

Ejemplo:

int a, b, c;

Cuando declaramos más de una variable usando el separador (,), estas reservan

posiciones de memorias consecutivas del tamaño indicado por el tipo de dato.

Observando el ejemplo anterior se notara que las variables a, b y c reservan

posiciones de memoria del tamaño del tipo de dato int que es de 2 bytes. Es decir que

si la dirección de la variable a (&a) es 0x1000, entonces la dirección de la variable b

(&b) será la igual a la siguiente posición de memoria del tamaño del tipo int, 0x1002

(las posiciones de memoria del computador se representan en notación hexadecimal y

su tamaño será de 1 byte). (Figura 3.3)

Page 61: Libro de c++ (Teria/Practico)

Figura 3. 3. Declaración de variables consecutivas.

También es posible inicializar las variables dentro de la misma declaración.

Sintaxis de declaración e inicialización de variables:

tipodato nombrevariable = inicialización;

ó

tipodato nomvar1=inicial1, nomvar2=inicial2, … , nomvarn= inicialn;

Ejemplos:

int a = 5;

int a = 2, b = 4, c = 6;

Las variables no inicializadas tienen un valor, este valor lo conocemos como

"basura", ya que el valor es desconocido que puede interferir en un programa.

Las variables no pueden tener el mismo nombre que una “palabra reservada”

del lenguaje. No se les pueden colocar espacios en blanco. Los nombres de variables

solo pueden tener letras, dígitos numéricos y el guión bajo ó subguión (_).

Page 62: Libro de c++ (Teria/Practico)

Los nombres de variables no pueden llevar caracteres especiales, ejemplo:

caracteres acentuados, ñ, *, /, -, etc.

Deben comenzar por un carácter (letra no numero) o también pueden comenzar

con un guión bajo (_), ejemplo: _costo.

3.2.1.2. Ámbito de las variables

Dependiendo de dónde se declaren las variables, podrán o no ser accesibles

desde distintas partes del programa. Las variables declaradas dentro de un bucle,

serán accesibles sólo desde el propio bucle, serán de ámbito local del bucle.

Las variables declaradas dentro de una función, sólo serán accesibles desde esa

función. Esas variables son variables locales o de ámbito local de esa función.

Las variables declaradas fuera de las funciones, normalmente antes de definir

las funciones, en la zona donde se declaran los prototipos, serán accesibles desde

todas las funciones. Estas variables serán globales o de ámbito global.

3.2.1.2.1. Variables Locales

Las variables locales son aquellas definidas en el interior de una función y son

visibles sólo en esta función específica. Las reglas por las que se rigen las variables

locales son:

• En el interior de una función, una variable local no puede ser modificada por

ninguna sentencia externa a la función.

• Los nombres de las variables locales no han de ser únicos. Dos, tres o más

funciones - por ejemplo: pueden definir variables de nombre Interruptor. Cada

variable es distinta y pertenece a la función en que está declarada.

Page 63: Libro de c++ (Teria/Practico)

• Las variables locales de las funciones no existen en memoria hasta que se

ejecuta la función. Esta propiedad permite ahorrar memoria, ya que permite

que varias funciones compartan la misma memoria para sus variables locales

(pero no a la vez).

Ejemplo:

#include <iostream.h> #include <conio.h> void main() { //declaración de variables locales de la función main() int x, y, a; //variables locales x, y, a … } void suma() { //declaración de variables locales de la función suma() int a, b; //variable locales a, b … }

3.2.1.2.2. Variables Globales

Las Variables Globales son variables que se declaran fuera de la función y por

defecto (omisión) son visibles a cualquier función incluyendo main ().

Ejemplo:

#include <iostream.h> #include <conio.h> int a, c, b, x; //declaración de variables globales main() { //declaración de variables locales

Page 64: Libro de c++ (Teria/Practico)

}

3.2.1.3. Tipos de Almacenamientos

Las variables por su parte pueden tener distinto tipo de almacenamiento,

dependiendo éste de las partes del código en el que van a ser utilizadas.

• Static

Una variable estática existe desde que el programa comienza su ejecución y

dura hasta que el programa termina. Esta característica permite retener el

valor de una variable incluso aunque la ejecución del programa salga fuera del

ámbito en el que ha sido declarada. Se declara anteponiendo la palabra

reservada static a la declaración de la variable. Considere el siguiente

fragmento de código:

ejemplo( ) { static int x=0; x++; ... }

En este caso, static modifica la declaración de la variable x, que por defecto

es local a la función, de manera que si bien el ámbito de x sigue siendo la

función (no se puede utilizar fuera de ella), ésta no se destruye una vez

ejecutada la función, sino que sigue en memoria conservando su valor, y si

la función es llamada por segunda vez, el valor de la variable x ya no será 0

sino 1. Las variables globales que se declaran en cada archivo son por

defecto static.

• Extern

La palabra reservada extern sirve para declarar un nombre de función o

variable como externa, y permite referencia una declaración que se encuentra

Page 65: Libro de c++ (Teria/Practico)

en otro archivo. Esta característica fue diseñada originalmente para facilitar la

compilación separada de archivos, en este curso, no la utilizaremos de

momento.

• Auto

Es la declaración de una variable local. Se usa para definir el ámbito temporal

de una variable local, es utilizada por defecto en las funciones.

• Register

Cuando a la declaración de una variable le antecede la palabra reservada

register se indica al compilador que la variable se almacenará en uno de los

registros del hardware del microprocesador. La palabra clave register, en una

sugerencia, no un mandato, al compilador. Una variable register debe ser local

a una función. La razón de utilizar variables register reside en que las

operaciones sobre los valores situadas en los registros son normalmente más

rápidas que las realizadas sobre valores situados en memoria, por lo que se

aumente la eficacia y velocidad del programa.

Una aplicación típica es el uso de una variable register como variable de

control de un bucle; de este modo se reduce el tiempo en el la CPU requiere

para buscar el valor de la variable en memoria. Por ejemplo:

register int i; for (i=1; i<10000; i++) { ... }

3.2.1.4. Conversión explícita de tipos de datos

En C++ está permitida una conversión explícita del tipo de una expresión

mediante una construcción que tiene la forma nombre_del_tipo (expresión). La

expresión es convertida al tipo especificado. Por ejemplo la función raíz cuadrada

Page 66: Libro de c++ (Teria/Practico)

(sqrt) devuelve un resultado de tipo double. Para poder asignar este resultado a una

variable de otro tipo, por ejemplo de tipo int, tendremos que escribir:

int a; a = int (sqrt ( 2 ));

Una variable de un determinado tipo no siempre puede ser convertida

explícitamente a otro tipo.

3.2.2. Constantes

Las constantes son tipos de datos (con valores numéricos o de cadena) que

permanecen invariables, sin posibilidad de cambiar el valor que tienen durante el

curso del programa. Una constante corresponde a una longitud fija de un área

reservada en la memoria principal del ordenador, donde el programa almacena

valores fijos. Una constante también puede ser definida, como una variable cuyo

valor o contenido no puede ser modificado.

Por ejemplo: El valor de pi = 3.141592

3.2.2.1. Constantes definidas.

Las constantes pueden recibir nombres simbólicos mediante la directiva

#define, esto significa que esa constante tendrá el mismo valor a lo largo de todo el

programa. El identificador de una constante así definida será una cadena de caracteres

que deberá cumplir los mismos requisitos que el de una variable (sin espacios en

blanco, no empezar por un dígito numérico, etc.).

Ejemplo:

#include <stdio.h>

Page 67: Libro de c++ (Teria/Practico)

#define PI 3.1415926 // constante definida PI

int main()

{

printf("Pi vale %f", PI);

return 0;

}

Lo cual mostrará por pantalla:

Pi vale 3.1415926

Es decir, PI es una constante a la que le hemos asignado el valor 3.1415926

mediante la directiva #define. La directiva #define también se puede utilizar para

definir expresiones más elaboradas con operadores (suma, resta, multiplicación, etc.)

y otras constantes que hayan sido definidas previamente, por ejemplo:

#define X 2.4

#define Y 9.2

#define Z X + Y

Otro ejemplo del uso de las constantes definidas:

#include <stdio.h>

#define escribe printf

main()

{

Page 68: Libro de c++ (Teria/Practico)

int r;

escribe("Ingrese un numero: ");

scanf("%d",&r);

escribe("El cuadrado del numero es: %d",r*r);

}

3.2.2.2. Constantes de enumeración.

Se caracterizan por poder adoptar valores entre una selección de constantes

enteras denominadas enumeradores; estos valores son establecidos en el momento

de la declaración del nuevo tipo. Como se ha señalado, son enteros y (una vez

establecidos) de valor constante.

Ejemplo:

enum { MALO, BUENO, REGULAR };

Hemos definido las constantes MALO, BUENO y REGULAR con los valores

0, 1 y 2.

enum { PRIMERO=1, SEGUNDO, TERCERO= 5 };

Hemos definido las constantes PRIMERO, SEGUNDO y TERCERO con los

valores 1, 2 (si no se establece asume el siguiente valor numérico) y 5.

3.2.2.3. Constantes declaradas

El especificador constante (const), permite crear o declarar entidades cuyo

valor no se puede modificar. Una vez que una constante se declara no se puede

Page 69: Libro de c++ (Teria/Practico)

modificar dentro del programa. Las constantes deben ser inicializadas cuando se

declaran.

La palabra clave const se utiliza para hacer que un objeto-dato, señalado por un

identificador, no pueda ser modificado a lo largo del programa (sea constante). El

especificador const se puede aplicar a cualquier objeto de cualquier tipo, dando lugar

a un nuevo tipo con idénticas propiedades que el original pero que no puede ser

cambiado después de su inicialización (se trata pues de un verdadero especificador de

tipo).

Cuando se utiliza en la definición de parámetros de funciones o con miembros

de clases, tiene significados adicionales especiales.

Sintaxis:

const [<tipo-de-variable>] <nombre-de-variable> [ = <valor> ];

Ejemplo:

const int longitud = 20;

char array[longitud];

La palabra clave const declara que un valor no es modificable por el programa.

Puesto que no puede hacerse ninguna asignación posterior a un identificador

especificado como const, esta debe hacerse inevitablemente en el momento de la

declaración, a menos que se trate de una declaración extern.

Las constantes se pueden utilizar para sustituir a #define.

Ejemplo:

const PI = 3.141592 // sustituye a #define PI 3.141592 const long = 128 // sustituye a #define long 128

Page 70: Libro de c++ (Teria/Practico)

3.3. Operadores y operaciones.

3.3.1. Operadores

Un operador es un carácter o grupo de caracteres que actúa sobre una, dos o

más variables u operandos o expresiones que se encuentran en una operación para

obtener un resultado.

Ejemplo típicos de operadores son la suma (+), la diferencia (-), el producto

(*), etc. Los operadores pueden ser unarios, binarios y terciarios, según actúen sobre

uno, dos o tres operandos, respectivamente. Hay varios tipos de operadores,

clasificados según el tipo de objetos sobre los que actúan.

3.3.1.1. Operadores aritméticos

Son usados para crear expresiones matemáticas. Existen dos operadores

aritméticos unitarios, '+' y '-' que tienen la siguiente sintaxis:

+ <expresión>

- <expresión>

Asignan valores positivos o negativos a la expresión a la que se aplican.

En cuanto a los operadores binarios existen varios. '+', '-', '*' y '/', tienen un

comportamiento análogo, en cuanto a los operandos, ya que admiten enteros y de

coma flotante. Se trata de las conocidísimas operaciones aritméticas de suma, resta,

multiplicación y división.

Sintaxis:

<expresión> + <expresión>

Page 71: Libro de c++ (Teria/Practico)

<expresión> - <expresión>

<expresión> * <expresión>

<expresión> / <expresión>

<expresión> % <expresión>

El operador de módulo '%', devuelve el resto de la división entera del primer

operando entre el segundo. Por esta razón no puede ser aplicado a operandos en coma

flotante.

Cuando las expresiones que intervienen en una de estas operaciones sean

enteras, el resultado también será entero. Por otro lado si las expresiones son en punto

flotantes, con decimales, el resultado será en punto flotante.

3.3.1.2. Operadores incrementales

Son dos operadores unitarios, se trata de operadores un tanto especiales, ya que

sólo pueden trabajar sobre variables, pues implican una asignación. Se trata de los

operadores '++' y '--'. El primero incrementa el valor del operando y el segundo lo

decrementa, ambos en una unidad. Existen dos modalidades, dependiendo de que se

use el operador en la forma de prefijo o de sufijo.

Sintaxis:

<variable> ++ // (post-incremento)

++ <variable> // (pre-incremento)

<variable>-- // (post-decremento)

-- <variable> // (pre-decremento)

En su forma de prefijo, el operador es aplicado antes de que se evalúe el resto

de la expresión; en la forma de sufijo, se aplica después de que se evalúe el resto de la

Page 72: Libro de c++ (Teria/Practico)

expresión. Veamos un ejemplo, en las siguientes expresiones "a" vale 100 y "b" vale

10:

c = a + ++b;

En este primer ejemplo primero se aplica el pre-incremento, y b valdrá 11 a

continuación se evalúa la expresión "a+b", que dará como resultado 111, y por último

se asignará este valor a c, que valdrá 111.

c = a + b++;

En este segundo ejemplo primero se avalúa la expresión "a+b", que dará como

resultado 110, y se asignará este valor a c, que valdrá 110. Finalmente se aplica en

post-incremento, y b valdrá 11.

Los operadores unitarios sufijos (post-incremento y post-decremento) se

evalúan después de que se han evaluado el resto de las expresiones. En el primer

ejemplo primero se evalúa ++b, después a+b y finalmente c =<resultado>. En el

segundo ejemplo, primero se evalúa a+b, después c = <resultado> y finalmente b++.

3.3.1.3. Operadores de asignación

Los operadores de asignación atribuyen a una variable, es decir, depositan en la

zona de memoria correspondiente a dicha variable, el resultado de una expresión o

valor de otra variable.

Existen varios operadores de asignación, el más evidente y el más usado es el

"=", pero no es el único.

Entre los operadores de asignación tenemos: "=", "*=", "/=", "%=", "+=", "-=".

Y la sintaxis es:

Page 73: Libro de c++ (Teria/Practico)

<variable> <operador de asignación> <expresión>

En general, para todos los operadores mixtos la expresión:

Expresion1 op= Expresion2

Tiene el mismo efecto que la expresión:

Expresion1 = Expresion1 op Expresion2

El funcionamiento es siempre el mismo, primero se evalúa la expresión de la

derecha, se aplica el operador mixto, si existe y se asigna el valor obtenido a la

variable de la izquierda. En la siguiente tabla se muestra estas operaciones y sus

equivalencias. (Figura 3.4).

Operador Sentencia Abreviada Sentencia no Abreviada

+ = m = + n m = m + n;

- = m - = n m = m - n;

* = m * = n m = m * n;

/ = m / = n m = m / n;

% = m = % n m = m % n

Figura 3. 4. Tabla de Equivalencia de Operadores de Asignación

3.3.1.4. Operadores relacionales

Page 74: Libro de c++ (Teria/Practico)

Los operadores relacionales comprueban la igualdad o desigualdad entre dos

valores o expresiones.

Sintaxis:

<expresión1> > <expresión2>

<expresión1> < <expresión2>

<expresión1> <= <expresión2>

<expresión1> >= <expresión2>

<expresión1> == <expresión2>

<expresión1> != <expresión2>

El resultado de cualquier evaluación de este tipo, es un valor verdadero (true) o

falso (false). Siendo verdadero cualquier valor distinto de 0, aunque por lo general se

usa el valor 1 y falso representado por el valor 0.

El significado de cada operador es evidente:

> mayor que

< menor que

>= mayor o igual que

<= menor o igual que

== igual a

!= distinto a

Todos los operadores relacionales son operadores binarios (tienen 2 operandos),

y su forma general es:

Expresión1 op Expresión2

Page 75: Libro de c++ (Teria/Practico)

3.3.1.5. Operadores lógicos

Los operadores "&&", "||" y "!" relacionan expresiones lógicas, formando a su

vez nuevas expresiones lógicas.

Sintaxis:

<expresión1> && <expresión2>

<expresión1> || <expresión2>

!<expresión>

El operador "&&" equivale al "AND" o "Y"; devuelve "true" sólo si las dos

expresiones evaluadas son "true" o distintas de cero, en caso contrario devuelve

"false" o cero. Si la primera expresión evaluada es "false", la segunda no se evalúa.

Generalizando, con expresiones AND con más de dos expresiones, la primera

expresión falsa interrumpe el proceso e impide que se continúe la evaluación del resto

de las expresiones. Esto es lo que se conoce como "cortocircuito", y es muy

importante. A continuación se muestra la tabla de verdad del operador && (Figura

3.5):

Expresión1 Expresión2 Expresión1 && Expresión2

false ignorada false

true false false

true true true

Figura 3. 5. Tabla de verdad del operador "&&":

El operador "||" equivale al "OR" u "O inclusivo"; devuelve "true" si cualquiera

de las expresiones evaluadas es "true" o distinta de cero, en caso contrario devuelve

Page 76: Libro de c++ (Teria/Practico)

"false" o cero. Si la primera expresión evaluada es "true", la segunda no se evalúa. A

continuación se muestra la tabla de verdad del operador "||". (Figura 3.6):

Expresión1 Expresión2 Expresión1 || Expresión2

false false false

false true true

true ignorada true

Figura 3. 6. Tabla de verdad del operador "||" .

El operador "!" es equivalente al "NOT", o "NO", y devuelve "true" sólo si la

expresión evaluada es "false" o cero, en caso contrario devuelve "false".

La expresión "!E" es equivalente a (0 == E). A continuación se muestra la tabla

de verdad del operador "!" (Figura 3.7):

Expresión !Expresión

false true

true false

Figura 3. 7. Tabla de verdad del operador "!".

3.3.1.6. Operador "sizeof"

Este operador tiene dos usos diferentes.

Sintaxis:

sizeof (<expresión>)

sizeof (nombre_de_tipo)

Page 77: Libro de c++ (Teria/Practico)

En ambos casos el resultado es una constante entera que da el tamaño en bytes

del espacio de memoria usada por el operando, que es determinado por su tipo. El

espacio reservado por cada tipo depende de la plataforma.

En el primer caso, el tipo del operando es determinado sin evaluar la expresión,

y por lo tanto sin efectos secundarios. Si el operando es de tipo "char", el resultado es

1. A pesar de su apariencia, sizeof() NO es una función, sino un OPERADOR.

3.3.1.7. Operadores a nivel de bit enteros

Los operadores a nivel de bit operan independientemente sobre cada uno de los

bits de un valor.

NOT: El operador NOT unario, ~, invierte todos los bits de su operando. Por

ejemplo, en número 42, que tiene el siguiente patrón de bits 00101010 se convierte en

11010101 después de aplicar el operador NOT.

AND: El operador AND, &, combina los bits de manera que se obtiene un 1 si

ambos operandos son 1, obteniendo 0 en cualquier otro caso.

00101010 (representación en byte del numero 42)

&

00001111 (representación en byte del numero 15)

= 00001010 (representación en byte del numero 10)

OR: El operador OR, |, combina los bits de manera que se obtiene un 1 si

cualquiera de los operandos es un 1.

00101010 (representación en byte del numero 42)

|

00001111 (representación en byte del numero 15)

= 00101111 (representación en byte del numero 47)

XOR: El operador XOR, ^, combina los bits de manera que se obtiene un 1 si

cualquiera de los operandos es un 1, pero no ambos, y cero en caso contrario.

00101010 (representación en byte del numero 42)

Page 78: Libro de c++ (Teria/Practico)

^

00001111 (representación en byte del numero 15)

= 00100101 (representación en byte del numero 37)

La tabla siguiente muestra cómo actúa cada operador a nivel de bit sobre cada

combinación de bits de operando (Figura 3.8):

A B OR AND XOR NOT

0 0 0 0 0 1

1 0 1 0 1 1

0 1 1 0 1 1

1 1 1 1 0 0

Figura 3. 8. Tabla de los operadores a nivel de bit.

Desplazamiento a la izquierda: El operador desplazamiento a la izquierda,

<<, mueve hacia la izquierda todos los bits del operando de la izquierda un número de

posiciones de bit especificado en el operando de la derecha. Al realizarse el

desplazamiento se pierden por el extremo izquierdo del operando el número de bits

desplazados y se rellena el operando con ceros por la derecha el mismo número de

bits.

Desplazamiento a la derecha: El operador desplazamiento a la derecha, >>,

mueve hacia la derecha todos los bits del operando de la izquierda un número de

posiciones de bit especificado por el operando de la derecha.

Ejemplo: Si se desplaza el valor 35 a la derecha dos posiciones de bit, se

obtiene como resultado que el valor 8.

a = a >> 2;

Page 79: Libro de c++ (Teria/Practico)

int a = 35;

Cuando un valor tiene bits que se desplazan fuera por la parte izquierda o

derecha de una palabra, esos bits se pierden. Si se estudian en binario estas

operaciones se observa con mayor claridad.

00100011 (representación en byte del numero 35)

>> 2

= 00001000 (representación en byte del numero 8)

3.3.1.8. Operador condicional ternario ?:

El operador condicional (?:) el cual es conocido por su estructura como

ternario. Este operador permite controlar el flujo de ejecución del programa. Permite

evaluar situaciones tales como: Si se cumple tal condición entonces haz esto, de lo

contrario haz esto otro.

Sintaxis:

( (condición) ? proceso1 : proceso2 )

En donde, condición es la expresión que se evalúa, proceso1 es la tarea a

realizar en el caso de que la evaluación resulte verdadera, y proceso2 es la tarea a

realizar en el caso de que la evaluación resulte falsa.

Ejemplo:

int edad;

cout << "Cual es tu edad: ";

cin >> edad;

cout << ( (edad < 18) ? "Eres joven aun" : "Ya tienes la mayoría de edad" );

Page 80: Libro de c++ (Teria/Practico)

3.4. Expresiones.

Una expresión es una combinación de operadores y operandos de cuya

evaluación se obtiene un valor. Los operandos pueden ser nombres que denoten

objetos variables o constantes, funciones, literales de cualquier tipo adecuado de

acuerdo con los operadores u otras expresiones más simples. La evaluación de una

expresión da lugar a un valor de algún tipo, una expresión se dice que es del tipo de

su resultado.

Ejemplos de expresiones:

a + 5*b

(a >= 0) && ((b+5) > 10)

a

-a * 2 + b

--b + (- 4*a*c)

Las expresiones se evalúan de acuerdo con la precedencia de los operadores.

Ante una secuencia de operadores de igual precedencia, la evaluación se realiza según

el orden de escritura, de izquierda a derecha. El orden de evaluación puede

modificarse usando operadores que denoten precedencia como los paréntesis.

3.4.1. Reglas de precedencias

El resultado de una expresión depende del orden en que se ejecutan las

operaciones. Por ejemplo considere la siguiente expresión: 3 + 4 * 2. Si se resuelve

primero la suma y luego la multiplicación el resultado será 14. Pero si se realiza

primero la multiplicación y luego la suma el resultado es 11. Con el objeto de que el

Page 81: Libro de c++ (Teria/Practico)

resultado de una expresión sea claro e inequívoco, es necesario crear reglas que

definan el orden de ejecución.

La interpretación de cualquier expresión en C++ está determinada por la

precedencia y asociatividad de los operadores en dicha expresión. Cada operador

tiene una precedencia, y los operadores en una expresión se evalúan en orden de

mayor a menor precedencia. La evaluación de operadores con la misma precedencia

viene determinada por su asociatividad. Los paréntesis anulan las reglas de

precedencia.

En la siguiente tabla se listan los operadores en C++, su precedencia y su

asociatividad. Los operadores se listan en orden de prioridad decreciente (los situados

más arriba tienen mayor prioridad). Los operadores en la misma línea horizontal

tienen la misma precedencia.

3.4.2. Tabla de precedencia de los operadores en C++.

En la siguiente tabla se muestran las prioridades de los operadores en el

lenguaje C++. (Figura 3.9):

Page 82: Libro de c++ (Teria/Practico)

Operador Propósito Asociatividad

( ) Denota precedencia en una

expresión

De izquierda a derecha

sizeof Tamaño de un objeto De derecha a izquierda

++ -- Incremento y decremento prefijo De derecha a izquierda

! ~ + - Operadores Unario De derecha a izquierda

* / % Operaciones aritméticas

multiplicación, división y modulo

De izquierda a derecha

+ - Operaciones aritméticas adicción y

sustracción

De izquierda a derecha

<< >> Desplazamiento binario De izquierda a derecha

< > <= >= Operadores de relación De izquierda a derecha

== != Operadores de igualdad De izquierda a derecha

& Y binario De izquierda a derecha

^ O exclusivo binario De izquierda a derecha

| O inclusivo binario De izquierda a derecha

&& Y lógico De izquierda a derecha

|| O lógico De izquierda a derecha

?: Operador condicional De izquierda a derecha

= *= /= += -

= &= ^= |=

%= <<=

Operadores de asignación De derecha a izquierda

++ -- Incremento y decremento sufijo De derecha a izquierda

, Separador coma De izquierda a derecha Figura 3. 9. Tabla de prioridades de los operadores.

Page 83: Libro de c++ (Teria/Practico)

3.4.3. Ejemplo de expresiones.

Ejemplo 1:

Se tiene la siguiente expresión y=2*5*5+3*5+7, se desea evaluarla y encontrar

el resultado.

Solución:

Se resuelve tomando encuentra la precedencia de los operadores y su

asociatividad.

y = 2 * 5 * 5 + 3 * 5 + 7;

2 * 5 = 10 (multiplicación más a la izquierda primero)

y = 10 * 5 + 3 * 5 + 7;

10 * 5 = 50 (Multiplicación más a la izquierda)

y = 50 + 3 * 5 + 7;

3 * 5 = 15 (Multiplicación antes de la suma)

y = 50 + 15 + 7;

50 + 15 = 65 (Suma más a la izquierda)

y = 65 + 7;

65 + 7 = 72 (Se resuelve la última operación aritmética)

y = 72 (por último se realiza la asignación a la variable y)

Ejemplo 2:

Dados a=1, b=2 y c=3 efectúe la siguiente expresión:

d = 10 * a > c * 10 + b

Solución:

Se resuelve tomando encuentra la precedencia de los operadores y su

asociatividad.

d = 10 * 1 > 3 * 10 + 2

(Se realiza primero la operación aritmética de mayor precedencia más a la

izquierda 10*1 y luego 3*10)

Page 84: Libro de c++ (Teria/Practico)

d = 10 > 30 + 2

(Se realiza primero la operación aritmética 30 + 2)

d = 10 > 32

(Se resuelve la operación relacional 10>32 dando como resultado un valor

falso. Todo valor falso es igual a 0)

d = 0 (por último se realiza la asignación a la variable d)

Ejemplo 3:

Efectúe la siguiente expresión: d = 10 > 5 && 3 * 10

Solución:

Se resuelve tomando encuentra la precedencia de los operadores y su

asociatividad. A pesar de que el operador lógico && tiene menor prioridad obliga a

resolver la expresión más a la izquierda 10 > 5, aunque el operador aritmético * tenga

mayor prioridad.

d = 10 > 5 && 3 * 10

(Se separa la expresión debido a la presencia del operador lógico y se

resuelve la primera expresión 10 > 5, siendo su resultado verdadero, todo valor

verdadero será representado por el valor 1)

d = 1 && 3 * 10

(Dado un resultado verdadero en la primera expresión se resuelve la

siguiente 3 * 10, de haber resultado falsa toda la expresión seria falsa y no

realizaría dicha operación. Al resolver la operación 3 * 10 el resultado es 30,

donde todo valor diferente a cero es un valor verdadero.)

d = 1 && 1

(El resultado final de la expresión será un valor verdadero)

d = 1 (por último se realiza la asignación a la variable d)

Ejemplo 4:

Dado a = 1, b = 2 efectúe la siguiente expresión:

Page 85: Libro de c++ (Teria/Practico)

d = a++ > 5 / 2 || 3 * --b < ( 3 + a)

Solución:

Se resuelve tomando encuentra la precedencia de los operadores y su

asociatividad. A pesar de que el operador lógico || tiene menor prioridad obliga a

resolver la expresión mas a la izquierda a++ > 5 / 2, aunque el las operación

decremento prefijo tenga mayor prioridad.

d = a++ > 5 / 2 || 3 * --b < (3 + a)

(Resolvemos la expresión a++ > 5 / 2)

a++ > 5 / 2

(La operación incremento sufijo por tener menor prioridad se resuelve

después de todas las operaciones siendo resuelta después de finalizar toda la

expresión a++ > 5 / 2, se asigna el valor de la variable a)

1 > 5 / 2

(se resulte la operación aritmética de mayor precedencia 5 / 2 y luego la

operación relacional 1 > 2, siendo falsa)

d = 0 || 3 * --b < (3 + a)

(Antes de resolver la próxima expresión primero se realiza el incremento

de sufijo, dando como resultado el valor a en 2)

(Resolvemos la expresión a++ > 5 / 2)

3 * --b < (3 + a)

(El paréntesis denota prioridad en la expresión por lo cual se deberá

resolver la operación 3 + a antes, sustituyendo la variable por su valor 2 el

resultado es 5)

3 * --b < 5

(Se resuelve la operación de mayor prioridad –b el cual decrementará en

memoria inmediatamente el valor de la variable b)

3 * 1 < 5

(Resolvemos la operación aritmética y luego la operación relacional

dando como resultado un valor verdadero)

Page 86: Libro de c++ (Teria/Practico)

d = 0 || 1

(El resultado final de la expresión será un valor verdadero)

d = 1 (por último se realiza la asignación a la variable d)

3.5. Sentencias

Las sentencias son unidades completas, ejecutables en si mismas. Existen

muchos tipos de sentencias que incorporan expresiones aritméticas, lógicas o

generales como componentes de dichas sentencias.

Las sentencias simples se separan por punto y coma y las compuestas se

agrupan en bloques mediante llaves.

3.5.1. Sentencias Simple

Una sentencia simple es una expresión de algún tipo terminada por un carácter

(;). Por ejemplo las declaraciones o las sentencias aritméticas.

flota real; // declaración de variable

area = base * altura; // expresión aritmética

3.5.2. Sentencia vacía o nula

En algunas ocasiones es necesario introducir en el programa una sentencia que

ocupe un lugar, pero que no realice ninguna tarea. A esta sentencia se le denomina

sentencia vacia y consta de un simple carácter (;). Por ejemplo:

;

Page 87: Libro de c++ (Teria/Practico)

3.5.3. Sentencia compuesta

Es un conjunto de declaraciones y de sentencias agrupadas dentro de llaves

{…}. También conocido como bloques. Una sentencia compuesta puede incluir otras

sentencias, simples y compuestas. Ejemplo:

{ int i = 1, j = 2;

double peso;

peso = 5.5;

j = i + j;

}

3.6. Operaciones de entrada y salida estándar.

Cuando nos referimos a entrada/salida estándar (E/S estándar) queremos decir

que los datos o bien se están leyendo del teclado, ó bien se están escribiendo en el

monitor de video. Como se utilizan muy frecuentemente se consideran como los

dispositivos de E/S por defecto y no necesitan ser nombrados en las instrucciones de

E/S.

Las operaciones de entrada y salida no forman parte del conjunto de sentencias

de C++, sino que pertenecen al conjunto de funciones y clases de la biblioteca

estándar de C++. Ellas se incluyen en los archivos de cabecera mediante la directiva

#include por lo que siempre que queramos utilizarlas deberemos introducir la línea

de código:

#include <nombrelibreria>

Page 88: Libro de c++ (Teria/Practico)

En el lenguaje C++ tenemos varias alternativas para ingresar y/o mostrar datos,

dependiendo de la librería que vamos a utilizar para desarrollar el programa, entre

estas están: iostream.h, stdio.h, conio.h.

<STDIO.H>

Esta librería incorpora las sentencias de entrada y salida básicas entre ellas

tenemos la sentencia scanf y la sentencia printf, denominadas sentencias de entrada y

salida con formato, ya que se les indica el tipo de conversión de tipo a realizar.

< IOSTREAM.H>

Esta biblioteca es una implementación orientada a objetos y está basada en el

concepto de flujos. A nivel abstracto un flujo es un medio de describir la secuencia de

datos de una fuente a un destino o sumidero. Así, por ejemplo, cuando se introducen

caracteres desde el teclado, se puede pensar en caracteres que fluyen o se trasladan

desde el teclado a las estructuras de datos del programa.

Entre los objetos de flujo que vienen predefinidos tenemos:

cin, que toma caracteres de la entrada estándar (teclado);

cout, pone caracteres en la salida estándar (pantalla);

cerr y clog, ponen mensajes de error en la salida estándar.

Estos objetos se utilizan mediante los operadores << y >>.

<CONIO.H>

Esta librería incorpora las sentencias de entrada y salida simples de carácter o

cadenas como lo son: getch, getche, getchar, gets, putchar, puts.

3.6.1. Sentencias de Salida Estándar.

El dispositivo de salida estándar como podemos suponer es el monitor o

pantalla de nuestro ordenador. Las sentencias de salida envían datos al puerto de

video para ser visualizados. Entre estas sentencias tenemos:

Page 89: Libro de c++ (Teria/Practico)

3.6.1.1. Sentencia de Salida cout

La salida estándar en C++ es la pantalla de nuestro ordenador. El objeto

asociado con dicha salida estándar es cout.

El objeto cout emplea al operador de inserción "<<" y apunta al objeto donde

tiene que enviar la información. Por lo tanto la sintaxis de cout será:

cout<<variable1<<variable2<<...<<variablen;

Las cadenas de texto son variables y se ponen entre " " (comillas dobles).

cout << "Hola";

cout << 489;

cout << 13.69;

cout << x;

cout << "El valor de pi es = " << 3.1416;

Si queremos incluir saltos de línea podemos emplear el código de escape '\n' o

bien el manipulador "endl".

cout << "Una linea.\n ";

cout << "segunda linea.\n";

cout << "tercera linea." << endl;

cout << "cuarta linea." << endl;

Page 90: Libro de c++ (Teria/Practico)

3.6.1.2. Sentencia de Salida printf

La rutina printf permite la aparición de valores numéricos, caracteres y cadenas

de texto por pantalla. El prototipo de la sentencia printf es el siguiente:

printf (control, arg1,arg2...);

En la cadena de control indicamos la forma en que se mostraran los argumentos

posteriores, también podemos introducir una cadena de texto (sin necesidad de

argumentos) o combinar ambas posibilidades, así como secuencia de escape. En el

caso de que utilicemos argumentos deberemos indicar en la cadena de control tanto

modificadores como argumentos se vayan indicar. El modificador esta compuesto por

el carácter % seguido por un carácter de conversión indica de que tipo de dato se

trata.

Ejemplo:

printf("Color %s, num1 %d, num2 %5d, real %5.2f.\n", "rojo", 12, 8, 3.4);

Imprimirá la siguiente línea en la pantalla (incluyendo el carácter de nueva línea

\n):

Color rojo, num1 123, num2 00089, real 3.14.

Page 91: Libro de c++ (Teria/Practico)

3.6.1.3. Tabla de caracteres de formato salida

Las letras de control de formato más usadas son (Figura 3.10):

Carácter Descripción

‘c’

Esto imprime un número como un carácter ASCII. Por lo que, `printf

"%c", 65' imprimiría la letra ‘A’. La salida para un valor cadena es el

primer carácter de la cadena.

‘d’ Esto imprime un entero decimal.

‘i’ Esto también imprime un entero decimal.

‘e’

Esto imprime un número en notación científica (exponencial). Por

ejemplo, printf "%4.3e", 1950 imprime ‘1.950e+03’, con un total de 4

cifras significativas de las cuales 3 siguen al punto decimal. Los

modificadores ‘4.3’ son descritos más abajo.

‘f’ Esto imprime un número en notación punto flotante.

‘g’ Esto imprime en notación científica o en notación punto flotante, la que

quiera que sea más corta.

‘o’ Esto imprime un entero octal sin signo.

‘s’ Esto imprime una cadena.

‘x’ Esto imprime un entero hexadecimal sin signo.

‘X’

Esto imprime un entero hexadecimal sin signo. Sin embargo, para los

valores entre 10 y 15, utiliza las letras desde la ‘A’ a la ‘F’ en lugar de

esas mismas letras pero en minúsculas.

‘%’

Esta no es realmente una letra de control de formato. Pero tiene un

significado especial cuando se usa después de un ‘%’: la secuencia ‘%%’

imprime el carácter ‘%’. No consume ni necesita ningún item o argumento

correspondiente.

Figura 3. 10. Tabla de caracteres de formato salida

El formato completo de los modificadores es el siguiente:

Page 92: Libro de c++ (Teria/Practico)

% [signo][longitud][.][precisión] carácter de conversión.

Signo: indicamos si el valor se ajustara a la izquierda, en cuyo caso

utilizaremos el signo menos, o a la derecha (por defecto).

Longitud: Especifica la longitud máxima del valor que aparece en por pantalla.

Si la longitud máxima del valor que aparece por pantalla. Si la longitud es menos que

el número de dígitos del valor, este aparecerán ajustado a la izquierda.

Precisión: Indicamos el numero máximo de decimales que tendrá el valor.

La sentencia printf puede incluir caracteres especiales o secuencias de escape

que permitan modificar la impresión de los datos.

Caracteres especiales más utilizados:

\n // salto de línea

\t // tabulador horizontal

\v // tabulador vertical

\b // retroceso

\r // retorno de carro

\f // salto de página

\a // alerta (campana)

\\ // carácter \

\? // interrogante

\' // comilla simple

\" // comilla doble

\ooo // carácter ASCII dado por tres dígitos octales (ooo serán dígitos)

\xhh // carácter ASCII dado por dos dígitos hexadecimales (hh serán dígitos)

\0 // carácter nulo

Los argumentos de la sentencia de entrada pueden estar representados por

operaciones o sentencias. La sentencia printf resulte estas operaciones antes de

Page 93: Libro de c++ (Teria/Practico)

ejecutar la etapa de control. Se resolverán los argumentos de derecha a izquierda unas

vez finalizados se resuelve la etapa de control de izquierda a derecha.

Ejemplo:

int edad = 5;

printf("edad2: %d, edad1: %d, edad inicial: %d", edad, ++edad,

edad++);

Muestra por salida:

edad2: 7, edad1: 7, edad inicial: 5

3.6.1.4. Sentencia de Salida puts

Esta sentencia muestra por la salida estándar una cadena de caracteres o

literales y salta a la próxima línea, su sintaxis:

puts(direccion_cadena_caracteres);

ó

puts(“literales”);

Ejemplo:

char cadena[25]=”Casa”;

// se declara una cadena de caracteres {‘C’,’a’,’s’,’a’,’\0’}

Page 94: Libro de c++ (Teria/Practico)

puts(cadena);

// Muestra los caracteres de cadena hasta encontrar el fin de cadena

puts(100);

// Muestra los caracteres desde la posición de memoria 100 hasta

// encontrar el carácter de fin de cadena

puts(“cadena”);

// Muestra la palabra: cadena por pantalla

3.6.1.5. Sentencia de Salida putchar

Esta sentencia muestra por la salida estándar un carácter, su sintaxis:

putchar (variable_caracter);

ó

putchar (valor_caracter);

ó

putchar (‘simbolo_caracter’);

Ejemplo:

char car = ‘65’;

putchar (car); // Muestra por pantalla el carácter ‘A’

putchar (65); // Muestra por pantalla el carácter ‘A’

putchar (‘A’); // Muestra por pantalla el carácter ‘A’

Page 95: Libro de c++ (Teria/Practico)

3.6.2. Sentencias de Entradas Estandar

El dispositivo de entrada estándar como podemos suponer es el teclado de

nuestro ordenador. Las sentencias de entrada escanean este puerto para atrapar datos

y ser utilizados. Entre estas sentencias tenemos:

3.6.2.1. Sentencia de Entrada cin

Para extraer datos desde el teclado empleamos el objeto "cin", que se utiliza en

conjunción con el operador de extracción representado como ">>", lee información

del flujo cin (a la izquierda del operador) y las almacena en las variables indicadas a

la derecha). La sintaxis sería la siguiente:

cin>>variable1>>...>>variablen;

Un ejemplo de código utilizando ambos objetos podría ser el siguiente:

#include <iostream.h>

void main ()

{

int i;

cout<<"Introduce un número";

cin>>i; // atrapa un valor entero ingresado por teclado

}

Muestra por pantalla la frase "Introduce un número" y posteriormente

almacenaría el valor introducido por teclado en la variable i.

Page 96: Libro de c++ (Teria/Practico)

3.6.2.2. Sentencia de Entrada scanf

La rutina scanf permite entrar datos en la memoria del ordenador a través del

teclado. El prototipo de la sentencia scanf es el siguiente:

scanf(control, arg1, arg2, ...);

En la cadena de control indicaremos por regla general, los modificadores que

harán referencia al tipo de dato de los argumentos. Al igual que en la sentencia printf

los modificadores, estarán formado por el carácter % seguido de un carácter de

conversión. Los argumentos indicados serán, nuevamente las variables. La principal

característica de la sentencia scanf es que necesita saber la posición de la memoria del

ordenador en que se encuentra la variable para poder almacenar la información

obtenida, para indicarle está posición utilizamos el símbolo ampersand (&), aunque

colocaremos delante del nombre de cada variable (esto no es necesario en las cadenas

de caracteres o punteros).

Ejemplo:

include<stdio.h>

void main( ) /*solicita dos datos*/

{

char nombre[10];

int edad;

printf ("introduce tu edad:");

scanf("%d", &edad);

printf ("introduce tu nombre:");

scanf("%s", nombre); // nombre no lleva & por ser una referencia

}

Page 97: Libro de c++ (Teria/Practico)

3.6.2.3. Sentencia de Entrada gets

Esta sentencia lee y guarda una cadena introducida por la entrada estándar, su

sintaxis:

char cadena[25];

puts(“Ingrese un nombre:”);

gets(cadena);

// Envía los caracteres a partir de la posición de memoria de la cadena

// y transforma el carácter intro (enter) en un carácter fin de cadena

puts(cadena);

// Muestra lo que se ingreso por la entrada en pantalla

3.6.2.4. Sentencia de Entrada getchar

Esta sentencia lee y retorna cuando se presione la tecla intro, un único

carácter introducido mediante el teclado y salta a la próxima línea. Muestra el

carácter por la pantalla. Su sintaxis:

char letra;

letra=getchar( );

// atrapa un carácter y lo retorna después del carácter intro.

3.6.2.5. Sentencia de Entrada getch

Page 98: Libro de c++ (Teria/Practico)

Lee y retorna un único carácter al presionar una tecla. No muestra el

carácter por pantalla. Su sintaxis:

char letra;

letra=getch( );

// atrapa un carácter al momento de presionarlo y no lo muestra

3.6.2.6. Sentencia de Entrada getche

Esta sentencia lee y retorna un único carácter introducido mediante el

teclado por el usuario al igual que la sentencia getch pero a diferencia esta si

muestra el carácter por pantalla. Su sintaxis:

char letra;

letra=getche( );

// atrapa un carácter al momento de presionarlo y lo muestra

Page 99: Libro de c++ (Teria/Practico)

CAPITULO 4

ESTRUCTURAS DE CONTROL

4.1. Concepto.

Para existir un control de un proceso o situación, se deben dar condiciones

necesarias para ello, es decir que las condiciones son el punto de partida principal de

una sentencia de control.

Podemos definir las estructuras de control como sentencias condicionales

compuesta, que al cumplirse la condición necesaria, se realicen las sentencias simples

contenidas en esta.

4.2. Tipos de estructuras de control

El C++, como todo lenguaje de programación basado en la algorítmica, posee

una serie de estructuras de control para gobernar el flujo de los programas.

Debemos recordar que la evaluación de una condición producirá como

resultado un cero si es falsa y un número cualquiera distinto de cero si es cierta, este

hecho es importante a la hora de leer los programas, ya que una operación

matemática, por ejemplo, es una condición válida en una estructura de control.

Las estructuras de control básicas las podemos dividir en: sentencias de control

selectivas y las sentencias de control iterativo o ciclos.

4.2.1. Estructuras de control selectivas.

Page 100: Libro de c++ (Teria/Practico)

Las sentencias de control selectivas son estructuras de control que funciona de

esta manera: Las instrucciones comienzan a ejecutarse de forma secuencial (en orden)

y cuando se llega a una estructura condicional, la cual está asociada a una condición,

se decide que camino tomar dependiendo siempre del resultado de la condición

siendo esta falsa o verdadera. Cuando se termina de ejecutar este bloque de

instrucciones se reanuda la ejecución en la instrucción siguiente a la de la

condicional.

Dentro de las estructuras de selección encontramos en el C++, las de

condición simple (sentencia if), bicondicionales (sentencia if-else) y las de condición

múltiple (switch).

4.2.1.1. La sentencia if o condicional simple

Es una sentencia condicional simple cuya traducción es: si la expresión a

evaluar es verdadera realiza las instrucciones que controle dicha sentencia. Si el

resultado de la expresión es falso el flujo del programa continuará con las sentencias

debajo del final del if. Su sintaxis es:

if (expresión)

{

//sentencias

}

Si solo controla una sentencia no es necesario usar los operadores de bloque.

if (expresión)

sentencia_unica;

Page 101: Libro de c++ (Teria/Practico)

Podemos representar esta sentencia con el siguiente diagrama (Figura 4.1),

Fiugra 4. 1. Diagrama de flujo sentencia if.

donde se muestra que solo se realizara la sentencia 1 si el resultado lógico de la

expresión es verdadera de no serlo continúa el flujo del programa.

Ejemplo:

int x=6;

if(x>5)

k++;

Como se ve x tiene un valor y el if evalúa su estatus, como el resultado es

verdadero k incrementara su valor una sola vez y el flujo de programa continuará.

Ejemplo 1: Determine si un número es igual mayor o menor que cero.

#include <iostream.h>

void main()

{ int numero;

cout<<"Ingrese un numero:";

cin>>numero;

if(numero == 0) //La condición indica que tiene que ser igual a Cero

Page 102: Libro de c++ (Teria/Practico)

{

cout<<"El Numero Ingresado es Igual a Cero";

}

if(numero > 0) // la condición indica que tiene que ser mayor a Cero

{

cout<<"El Numero Ingresado es Mayor a Cero";

}

if(numero < 0) // la condicion indica que tiene que ser menor a Cero

{

cout<<"El Numero Ingresado es Menor a Cero";

}

}

Ejemplo 2: Determine si un número entero es par o impar.

#include <iostream.h>

void main()

{ int num;

cout<<"Ingrese un numero:";

cin>>num;

//Un numero será par sí es divisible por 2

if(num %2==0) //La condición indica que se cumpla si es divisible

cout<<"El numero ingresado es par";

//Un numero será impar sí no es divisible por 2

if(num %2!=0) //La condición indica que se cumpla si no es divisible

cout<<"El numero ingresado es impar";

}

Page 103: Libro de c++ (Teria/Practico)

Ejemplo 3: Convertir un carácter atrapado en un número entre [0,9].

#include <iostream.h>

#include <stdio.h>

#include <conio.h>

void main()

{ int num=-1;

char car;

cout<<"Ingrese un carácter:";

car=getche(); //atrapa un símbolo

//La condición se cumple se asignara el número correspondiente

if(car==’0’) num=0;

if(car==’1’) num=1;

if(car==’2’) num=2;

if(car==’3’) num=3;

if(car==’4’) num=4;

if(car==’5’) num=5;

if(car==’6’) num=6;

if(car==’7’) num=7;

if(car==’8’) num=8;

if(car==’9’) num=9;

if(num!=-1)

cout<<"El Numero es ="<<num;

if(num==-1)

cout<<"Carácter no es un símbolo numérico";

}

Page 104: Libro de c++ (Teria/Practico)

4.2.1.2. La sentencia if-else o bicondicional

Esta estructura cumple las mismas características de la sentencia if, cuando la

condición es verdadera realizara una sentencia o conjunto de sentencias verdaderas,

pero si esta es falsa realizara una sentencia o conjunto de sentencias falsas.

if (expresión)

{

//sentencias condición verdadera

}

else

{

// sentencias condición falsa

}

Si solo controla una sentencia no es necesario usar los operadores de bloque.

if(expresión)

sentencia_unica1;

else

sentencia_unica2;

Podemos representar esta sentencia con el siguiente diagrama (Figura 4.2),

Page 105: Libro de c++ (Teria/Practico)

Fiugra 4. 2. Diagrama de flujo sentencia if-else.

donde se muestra que solo se realizara la sentencia 1 si el resultado lógico de la

expresión es verdadera, pero si esta no se cumple realiza la sentencia 2, una vez

realice alguna de las sentencias dado el resultado de la expresión continúa el flujo del

progama.

De esta manera podemos mostrar el siguiente ejemplo:

x=0;

if( x ==5)

{ k++;

}

else

{ k--;

}

Como la evaluación de x es falsa, k decrementará en uno su valor y continuará

con el flujo del programa.

Ejemplo 1: Determine si un numero entero es positivo o negativo.

#include <iostream.h>

Page 106: Libro de c++ (Teria/Practico)

void main()

{ int num;

cout<<"Ingrese un numero:";

cin>>num;

if(num>= 0) //La condición indica que tiene mayor o igual a cero

{

cout<<"El numero ingresado es positivo";

}

else // de no cumplirse, entonces debe ser menor que cero

{

cout<<"El numero ingresado es negativo";

}

}

Ejemplo 2: Determine si un número entero es par o impar.

#include <iostream.h>

void main()

{ int num;

cout<<"Ingrese un numero:";

cin>>num;

if(num %2==0) //La condición indica que se cumpla si es divisible

cout<<"El numero ingresado es par";

else //si la condición no se cumple entonces no es divisible

cout<<"El numero ingresado es impar";

}

Page 107: Libro de c++ (Teria/Practico)

Las sentencias pueden controlar otras sentencias de control secuencial

permitiendo evaluar los procesos solo cuando sean necesarios conocidas como

sentencias secuenciales anidadas. Por ejemplo si queremos determinar si un número

es igual mayor o menor que cero, usando sentencias anidadas if-else resulta:

#include <iostream.h>

void main()

{

int numero;

cout<<"Ingrese un numero:";

cin>>numero;

if(numero == 0)

{ cout<<"El Numero Ingresado es Igual a Cero";

}

else if(numero > 0)

{ cout<<"El Numero Ingresado es Mayor a Cero";

}

else {

cout<<"El Numero Ingresado es Menor a Cero";

}

}

4.2.1.3. La sentencia switch.

Sirve para seleccionar una de entre múltiples alternativas de acuerdo al valor de

una expresión. La sentencia switch es específicamente útil cuando la selección se

Page 108: Libro de c++ (Teria/Practico)

basa en el valor de una variable simple o de una expresión simple denominada

expresión de control múltiple o selector.

Su sintaxis:

switch (expresión)

{

case valor_expresion_1:

//sentencias 1

break;

case valor_expresion_2:

//sentencias 2

break;

....

case valor_expresion_n:

//sentencias n

break;

default:

//sentencias x

}

Podemos representar esta sentencia mediante el diagrama siguiente (Figura

4.3),

Fiugra 4. 3. Diagrama de flujo sentencia switch.

Page 109: Libro de c++ (Teria/Practico)

donde dado el valor de la expresión realizara las operaciones y continuara con

el flujo del programa.

Esta sentencia la podemos representar usando sentencias anidadas if…else, si la

sentencia break no fuese utilizada entonces se representaría por un conjunto de

sentencias if una tras otra.

Como verán dependiendo de valor se selecciona el caso por ejemplo:

x=3;

switch (x)

{

case 1: k= 20*2;

break;

case 2: k=20/2;

break;

case 3: k=20+2;

//no es necesario usar break

}

Como x es igual a 3 el case efectuará la operación k=20+2, el uso de default es

opcional.

Ejemplo: Escribir un programa que lea por teclado las notas desde la A-H, y

muestre por pantalla el rendimiento académico del alumno.

#include <conio.h>

#include <iostream.h>

main()

{ char letra;

Page 110: Libro de c++ (Teria/Practico)

cout<<"Ingrese la Calificación y presione Enter: ";

cin>>letra;

switch (letra)

{

case 'A': cout<<"Excelente";

break;

case 'B': cout<<"Notable";

break;

case 'C': cout<<"Aprobado";

break;

case 'D':

case 'E':

case 'F': cout<<"Desaprobado";

break;

default: cout<<"No esposible esta nota";

}

}

La sentencia break es opcional. Cuando se encuentra, provoca la salida de

switch. En caso contrario continua la siguiente secuencia case o default aunque no se

cumpla la condición. Para aclarar esto, tomemos el siguiente ejemplo:

int c;

...

scanf ("%d", &c);

switch (c) { case 1:

case 2: Funcion2 ();

case 3: Funcion3 ();

Page 111: Libro de c++ (Teria/Practico)

break;

case 4: Funcion4_1 ();

Funcion4_2 ();

break;

case 5: Funcion_5 ();

default: FuncionX ();

}

La siguiente tabla indica qué función se ejecuta dependiendo del valor de c

(Figura 4.4).

Si se pulsa Se ejecuta las funciones

1 Funcion2() y Funcion3()

2 Funcion2() y Funcion3()

3 Funcion3()

4 Funcion4_1() y Funcion4_2()

5 Funcion5() y FuncionX()

Otra cosa FuncionX() Fiugra 4. 4. Tabla de ejecución de funciones.

4.2.2. Estructuras de control iterativas.

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 o hasta que una a condición

específica 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

Page 112: Libro de c++ (Teria/Practico)

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.

4.2.2.1. Sentencia for

Es un bucle o sentencia repetitiva que, permite realizar un proceso de manera

cíclica, desde un punto partida inicial, hasta un punto final, atreves de un incremento

o contador de actividad. La sentencia for:

1. Ejecuta la sentencia de inicializaciones.

2. Verifica la expresión booleana de condición de término:

a. si es cierta, ejecuta la sentencia entre llaves y la sentencia de iteración para

volver a verificar la expresión booleana de condición de término.

b. si es falsa, sale del bucle.

Podemos representar el flujo de la sentencia en el siguiente diagrama (Figura

4.5):

Fiugra 4. 5. Diagrama de flujo sentencia for.

Page 113: Libro de c++ (Teria/Practico)

Sintaxis:

for (inicio; condición; iteración)

sentencia;

o si se desean repetir varias sentencias:

for (inicio; condición; iteración)

{

sentencia_1;

sentencia_2;

sentencia_n;

}

Vemos que la sentencia for tiene tres secciones: inicio, en dónde se da un valor

inicial a una variable o variables de control del bucle; condición, que es una

expresión que devuelve un valor verdadero o falso, y hace que el bucle se repita

mientras sea cierta y salga de este solo cuando la condición sea falsa; e iteración, en

dónde se determina el cantidad del incremento o decremento de la variable o

variables de control. Las tres secciones están separadas por punto y coma. El cuerpo

del bucle puede estar formado por una o por varias sentencias. En este último caso

deben encerrarse entre llaves {}. Las llaves sólo son necesarias si se quieren repetir

varias sentencias, aunque se recomienda su uso porque facilita la lectura del código

fuente y ayuda a evitar errores al modificarlo.

Habitualmente, en la expresión lógica de condición de término se verifica que

la variable de control alcance un determinado valor. Por ejemplo:

for (i = valor_inicial; i <= valor_final; i++)

Page 114: Libro de c++ (Teria/Practico)

{ sentencia;

}

Algunos ejemplos de la sentencia for:

En la siguiente secuencia se muestran en pantalla los números del 1 al 10 y sus

cuadrados.

int i;

for (i = 1; i <= 10; i++)

{

printf ("\nValor de i: %d", i);

printf ("|tValor de i2: %d|n", i * i);

}

Esta secuencia, se muestran en pantalla las letras mayúsculas de la A a la Z.

char letra;

for (letra = 'A'; letra <= 'Z'; letra++)

cout<< letra<<endl;

El valor de incremento/decremento de las variables de control puede ser

diferente de 1. El siguiente ejemplo muestra en pantalla los números pares

comprendidos entre 1 y 100, descendentemente:

int i;

for (i = 100; i >= 1; i = i - 2)

cout << i <<"\t ";

Page 115: Libro de c++ (Teria/Practico)

Es posible tener más de una variable de control del bucle. En el bucle for las

secciones de inicialización e incremento pueden tener, a su vez, subsecciones, en

cuyo caso van separadas por el operador secuencial (,). Un ejemplo es:

int i, j;

for (i = 0, j = 1; i + j < N; i++, j++)

cout<< i + j<<endl;

que visualiza los N primeros números impares.

No debe confundirse esta sentencia con un anidamiento de bucles for. Un

anidamiento tiene el siguiente aspecto:

int i, j;

for (i = 0; i <= 100; i++)

{ //cuerpo_del_bucle_externo;

for (j = 0; j <= 100; j++)

{ //cuerpo_del_bucle_interno;

}

}

La condición de salida del bucle no tiene por qué referirse a la variable de

control. Esto queda ilustrado en el siguiente ejemplo:

char a;

int i;

for (i = 1; a != 's'; i++)

{ printf ("\n%d", i);

a = getch ();

Page 116: Libro de c++ (Teria/Practico)

}

En este ejemplo se van mostrando en pantalla los números 1, 2,...mientras

presionemos cualquier carácter hasta que se teclee el carácter ‘s’.

El bucle for puede no tener cuerpo. Esta característica permite crear retardos en

un programa.

int i;

for (i = 0; i < 100; i++);

El bucle provoca un retardo de 100 ciclos, saldrá del bucle cuando la variable

sea mayor o igual a100.

El bucle for puede tener vacía cualquier sección. En un bucle for puede faltar

una, dos o las tres secciones. Por ejemplo, es correcto escribir

register int i;

for (i = 0; i != 10; ) /* Falta la 3ª sección (incremento) */

{

scanf ("%d", &i);

printf ("\n%d", i);

}

que va mostrando en pantalla los valores que se ingresen, finalizando al

ingresar el número 10 (que también se visualiza).

También podemos escribir un bucle como:

for ( ; ; )

{

Page 117: Libro de c++ (Teria/Practico)

cuerpo_del_bucle;

}

que es un bucle sin fin, debe haber en el cuerpo del bucle una sentencia que

rompa el ciclo del programa como lo es una sentencia break. Ejemplo:

void main () { int n;

for ( ; ; ) { printf ("\nTeclee un número: ");

scanf ("%d", &n); if (!n) break; printf ("\nEl cuadrado es %d", n * n);

} }

4.2.2.2. Sentencia while

La sentencia while tiene una condición del bucle (una expresión lógica) que

controla la secuencia de repetición. La sentencia evalúa la condición antes de que se

ejecute el cuerpo del bucle si se cumple la condición, realiza los procesos que

controla la sentencia, sino sale de esta y continúa con el flujo del programa. Como

podemos observar en la figura 4.6:

Fiugra 4. 6. Diagrama de flujo sentencia while.

Page 118: Libro de c++ (Teria/Practico)

El cuerpo del bucle no se ejecutará nunca si la primera vez no se cumple la

condición. El bucle puede ser infinito si no se modifican adecuadamente las variables

de la condición dentro del bucle.

Sintaxis:

while ( condición )

{

sentencias .......... ;

}

Veamos algunos ejemplos.

En esta sentencia se solicita un carácter del teclado mientras no se teclee el

carácter ‘n’ ni el carácter ‘s’. Cuando se teclea alguno de estos caracteres, se

almacena en c y se abandona el bucle.

char c; while (c != 's' && c != 'n')

c = getche ();

El siguiente ejemplo es un caso de bucle while sin cuerpo. Se mantendrá en el

ciclo hasta que se teclea el carácter ‘s’.

while (getch () !=’s’);

El siguiente programa utiliza un bucle while para solicitar del usuario que

adivine un número. Realizar un programa que muestre los número del 1 al 100.

#include <conio.h> #include <iostream.h> main() { int contador=0;

Page 119: Libro de c++ (Teria/Practico)

while ( contador < 100 ) { contador++;

cout<<"\t "<<contador; } cout<<"Presione Enter para salir";

}

4.2.2.3. Equivalencias ente las sentencias for y while.

Las sentencias for y while son equivalentes entre si ya que se pueden emular

entre sí, aunque podríamos usar ambas sin problemas, se recomienda el uso de

sentencias for para recorridos y la sentencia while cuando dependa del resultado de

un proceso:

Emulando un while mediante una sentencia for:

for ( ; condición ; ) // while carece de inicio y de iteraciones {

sentencias; }

Emulando un for mediante una sentencia while:

inicio; // la sentencia for posee valores de inicio while ( condición ) {

sentencias; iteraciones; // la sentencia for posee valores de iteraciones

}

Ejemplo 1: Determine si un numero es capicúo (se lee igual en ambos sentidos).

Usando la sentencia while Usando la sentencia for

#include <conio.h> #include <conio.h>

Page 120: Libro de c++ (Teria/Practico)

#include <iostream.h> void main() { int a, inv, aux; cout<<”Ingrese un numero:”; cin>>a; aux = a; inv = 0; while ( aux > 0 ) { // separar dígitos e invertir

inv = inv * 10 + aux % 10; aux = aux / 10;

} if(a == inv) cout<<"El numero es capicúo”; else cout<<"El numero no es capicúo”; }

#include <iostream.h> void main() { int a, inv, aux; cout<<”Ingrese un numero:”; cin>>a; for(aux=a, inv=0; aux>0; aux/=10) { // separar dígitos e invertir

inv = inv * 10 + aux % 10; } if(a == inv) cout<<"El numero es capicúo”; else cout<<"El numero no es capicúo”; }

Ejemplo 2: Muestre un cuadro usando el carácter ’x’ y una dimensión n. Sera

necesario usar sentencia anidadas para desplazar el cursor de manera horizontal y

vertical.

Usando la sentencia while Usando la sentencia for

#include <conio.h> #include <iostream.h> void main() { int i, j, n; cout<<”Ingrese la dimensión:”; cin>>num; i = 0; while(i < n ) { j=0; while( j < n) { if(i==0||i==n-1||j==0||j==n-1) cout<<"x”; else cout<<” ”; j++; }

#include <conio.h> #include <iostream.h> void main() { int i, j, n; cout<<”Ingrese la dimensión:”; cin>>num; for(i = 0; i < n ; i++) { for(j = 0; j < n; j++) if(i==0 || i==n-1|| j==0|| j==n-1) cout<<"x”; else cout<<” ”; cout<<endl; } }

Page 121: Libro de c++ (Teria/Practico)

cout<<endl; i++; } }

Ejemplo 2: Muestre una X usando el carácter ’x’ y una dimensión n.

Usando la sentencia while Usando la sentencia for

#include <conio.h> #include <iostream.h> void main() { int i, j, n; cout<<”Ingrese la dimensión:”; cin>>num; i = 0; while(i < n ) { j=0; while( j < n) { if(i==j || i+j==n-1) cout<<"x”; else cout<<” ”; j++; } cout<<endl; i++; } }

#include <conio.h> #include <iostream.h> void main() { int i, j, n; cout<<”Ingrese la dimensión:”; cin>>num; for(i = 0; i < n ; i++) { for(j = 0; j < n; j++) if(i==j || i+j==n-1) cout<<"x”; else cout<<” ”; cout<<endl; } }

4.2.2.4. Sentencia do-while

La sentencia do - while se utiliza para especificar un bucle condicional que se

ejecuta al menos una vez. Esta situación se suele dar en algunas circunstancias en las

que se ha de tener la seguridad de que una determina acción se ejecutara una o varias

Page 122: Libro de c++ (Teria/Practico)

veces, pero al menos un vez. El flujo de ejecución podemos obsérvala en la siguiente

figura 4.7:

Fiugra 4. 7. Diagrama de flujo sentencia do-while.

En esta sentencia a diferencia de la sentencia for y while, primero realizara las

sentencias que controle una vez realizada evaluara la condición, si esta es verdadera,

volverá a realizar las sentencias hasta que la condición en algún momento sea falsa.

Las sentencias se ejecuta al menos una vez, incluso aunque la expresión se

evalúe como falsa, puesto que la evaluación se hace al final, a diferencia de la

sentencia while y for, en el que la evaluación de la condición se hace al principio.

Sintaxis:

do{ sentencias; }while(condición);

Esta sentencia por su conformación la hacen preferibles en problemas que se

espere un valor antes de poder ejecutar procesos, como por ejemplo: validaciones,

menús de espera, ingreso de datos con clave única, etc.

Ejemplo: Realizar un programa que convalide el ingreso de dos notas con rango

[0 – 20] y halle el promedio de ambas notas.

Page 123: Libro de c++ (Teria/Practico)

#include <conio.h> #include <iostream.h> void main() { int nota1, nota2; do{ //termina del ciclo solo si el valor nota1 está en el rango

cout << "Ingrese la primera nota: "; cin>>nota1;

if(nota1<0 || nota1>20) cout << "Nota fuera de los rangos[0-20]"<<endl; }while(nota1<0 || nota1>20)); do{ //termina del ciclo solo si el valor nota2 está en el rango

cout << "Ingrese la segunda nota: "; cin>>nota2;

if(nota2<0 || nota2>20) cout << "Nota fuera de los rangos[0-20]"<<endl; }while(nota2<0 || nota2>20));

cout<<"El promedio de las nontas es:"<<(nota1+nota2)/2; }

En el siguiente ejemplo se solicita un carácter del teclado hasta que se pulse

cualquiera de los caracteres 'S' o 'N'.

#include <iostream.h> void main () { char tecla; do {

cout<<”Pulse S o N: "<<endl; tecla = getch ();

} while (tecla != 'S' && tecla != 'N'); }

4.2.2.5. La sentencia break

Es una sentencia de ruptura de secuencia, permite cortar ciclos de programas.

La sentencia break se puede colocar dentro de un bucle o bucles anidados. Cuando se

Page 124: Libro de c++ (Teria/Practico)

ejecuta la sentencia break se abandona el bucle más interno. A todos los efectos la

sentencia break actúa como un salto a la instrucción siguiente al bucle en el que se

ejecuta.

4.2.2.6. La sentencia continue

La sentencia continue, no abandona el bucle si no hace que se ejecute la

siguiente iteración. En el bucle while la ejecución del continue hace que el flujo del

programa salte a la condición. En el bucle for la ejecución del continue hace que la

expresión de incremento, para después continuar normalmente con la condición. Es

decir, la ejecución del continue evita que se ejecute el resto del cuerpo del bucle.

Esta sentencia se utiliza en los bucles for, while y do/while. Cuando se ejecuta

fuerza un nuevo ciclo del bucle, saltándose cualquier sentencia posterior. Por

ejemplo,

int i, n; for (i = 1; i <= 100; i++) { n = i / 2; if (i == 2 * n) continue; printf ("\n%d", i); }

el bucle :muestra en pantalla sólo los números impares, puesto que para los

números pares la expresión i == 2 * n se evalúa como cierta, ejecutándose la

sentencia continue que fuerza de inmediato un nuevo ciclo del bucle.

Page 125: Libro de c++ (Teria/Practico)

CAPITULO 5

ARREGLOS

5.1. Definición

Los arreglos son usados extensamente por los programadores para contener

listas de datos en la memoria, por ejemplo, los datos almacenados en un disco suelen

leerse y ponerse dentro de un arreglo con el objetivo de facilitar la manipulación de

dichos datos, ya que los datos en memoria pueden ser modificados, clasificados,

marcados para su eliminación, etc. para luego ser reescritos al disco.

Un arreglo (array) es una colección de datos del mismo tipo, que se almacenan

en posiciones consecutivas de memoria y reciben un nombre común. Para diferenciar

cada elemento de un arreglo se utiliza un índice, que especifique su posición relativa

en el arreglo.

Los índices son números que se utilizan para identificar a cada uno de los

componentes de un arreglo. Por ejemplo podemos pensar en los casilleros, así que si

deseamos guardar o retirar un paquete nos dirigimos al casillero el cual sería el

arreglo; y dado el número específico el cual representa el índice para identificar el

lugar del casillero en donde quedó guardado el paquete.

Un arreglo es una colección finita, homogénea y ordenada de elementos.

• Finita: Todo arreglo tiene un límite; es decir, debe determinarse cuál será el

número máximo de elementos que podrán formar parte del arreglo.

• Homogénea: Todos los elementos del arreglo deben ser del mismo tipo.

• Ordenada: Se puede determinar cuál es el primer elemento, el segundo, el

tercero,.... y el n-ésimo elmento.

Page 126: Libro de c++ (Teria/Practico)

5.2. Declaración de arreglos

Para declarar un arreglo se emplea la sintaxis:

tipo identificador[ [tamaño] ] [ = { lista de inicialización } ] ;

donde,

• tipo se refiere al tipo de datos que contendrá el arreglo. 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 arreglo puede ser de una estructura creada

con: struct, class.

• identificador se refiere al nombre que le daremos al arreglo.

• tamaño es opcional e indica el número de elementos que contendrá el

arreglo. Si un arreglo se declara sin tamaño, el mismo no podrá contener

elemento alguno a menos que en la declaración se emplee una lista de

inicialización.

• lista de inicialización es opcional y se usa para establecer valores para

cada uno de los componentes del arreglo. Si el arreglo es declarado con un

tamaño especifico el número de valores inicializados no podrá ser mayor a

dicho tamaño.

Ejemplos:

int int_A[5]; long long_A[5] = { 1, 2, 3, 4, 5 }; char char_A[] = { 'a', 'b', 'c' ); int bidim_A[4][4]={{1,1},{2,3}};

Page 127: Libro de c++ (Teria/Practico)

5.3. Clasificación de los arreglos

Los arreglos los podemos clasificar de acuerdo con el número de dimensiones

que tienen. Así se tienen los:

• Unidimensionales (vectores): representados por una colección de elementos

con un mismo nombre y diferenciados por un índice.

• Multidimensionales: Bidimensionales (tablas o matrices) y

Tridimensionales. Representados por colecciones de arreglos

unidimensionales (matrices) o múltiples matrices.

5.3.1. Arreglos unidimensionales.

Los arreglos unidimensionales pueden ser apreciados en la vida cotidiana como

por ejemplo un armario el cual está dividido por gavetas, una cola de personas que

realizan un mismo proceso como la compra de un mismo artículo, etc.

Podemos definir los arreglos unidimensionales como una colección de variables

del mismo tipo, con un mismo nombre y diferenciadas a través de un índice. También

podríamos representarlo como un conjunto compuesto por elementos internos con un

universo limitado. (Figura 5.1)

Figura 5. 1.Representación de un arreglo unidimensional.

El formato para declarar un arreglo unidimensional es:

Page 128: Libro de c++ (Teria/Practico)

tipo nomb_arr[tamaño] = { lista de inicialización } ; //La lista es opcional

donde, el tamaño representa la dimensión o cantidad de elementos o variables

del mismo tipo que componen al arreglo.

El nombre de arreglo representa la dirección de todos los elementos declarado

en el. Donde cada variable declarada en el arreglo está compuesta por una dirección y

contenido.

Por ejemplo, para declarar un arreglo de enteros llamado listanum con diez

elementos se hace de la siguiente forma:

int listanum[10];

El ejemplo declara un arreglo de enteros con diez elementos o variables enteras

desde listanum[0] hasta listanum[9].

Cada una de las variables declaradas puede ser utilizada referenciando su

índice. La forma como pueden ser accesados los elementos de un arreglo, es de la

siguiente forma:

listanum[2] = 15; /* Asigna 15 al 3er elemento del arreglo */ num = listanum[2]; // Asigna el contenido del 3er elemento a la variable num

Para evaluar, leer, modificar un arreglo de elementos es necesario realizar

recorridos a través de este, es decir si se quiere realizar alguna operación sobre el

arreglo es necesario utilizar sentencias iterativas que permitan recorrer el arreglo

desde una posición a otra.

Sea la declaración de los arreglos A y B:

Page 129: Libro de c++ (Teria/Practico)

int A[10], B[10];

Si se desea leer o asignar a los 10 elementos del arreglo, es necesario

desplazarnos por cada una de las variables desde la posición 0 hasta la posición 9.

Si se desea leer los 10 elementos del arreglo A.

int i; for (i = 0; i < 10; i++) { cout<<”Ingrese el elemento A[”<<i<<”]:”; cin>>A[i]; }

Para asignar a los 10 elementos del arreglo B el mismo valor del arreglo A.

int i; for (i = 0; i < 10; i++) { B[i] = A[i]; }

Si se desea buscar un elemento en el arreglo A, es necesario comparar el valor

buscado con cada elemento del arreglo.

int i, encontrado; for (encontrado=0, i = 0; encontrado==0 && i < 10; i++) { if ( buscado == A[i]) encontrado = 1; } if ( encontrado==1) cout<<”El elemento buscado fue encontrado”; else cout<<”El elemento no fue localizado”;

Page 130: Libro de c++ (Teria/Practico)

Si se desea ordenar los elementos del arreglo A, es necesario comparar cada

valor del arreglo y ubicarlo en la posición correspondiente, en la siguiente sentencia

se utilizara el método de la burbuja el cual consiste en comparar un elemento con el

siguiente y dependiendo del criterio de comparación y valor de peso este será

cambiado o no.

int i, cambio, aux, n=10; do{ // nos indica la cantidad de veces necesarias para ordenar cambio = 0; //si su valor se mantiene en cero el arreglo estará

ordenado for (i = 0; i < n-1; i++) // el último elemento no será comparado if ( A[i] <criterio> A[i+1]) // criterio (>) ordena de menor a mayor

{ cambio = 1; // criterio (<) ordena de mayor a menor aux = A[i]; // intercambia los valores A[i] con A[i+1] A[i] = A[i+1]; A[i+1] = aux; }

} }while( cambio!=0);

El ejemplo anterior usando el método de ordenamiento por selección seria:

int i, j, aux, n=10; for (i = 0; i < n-1; i++) // selecciona la posición del elemento a ordenar for (j = i+1; i < n; j++) // busca si hay un elemento que cumpla criterio if ( A[i] <criterio> A[j]) // criterio (>) ordena de menor a mayor

{ // intercambia los valores A[i] con A[j] aux = A[i]; A[i] = A[j]; A[j] = aux; }

Si desea mostrar los elementos del arreglo A:

int i; cout<<”Los elementos del arreglo:”<<endl;

Page 131: Libro de c++ (Teria/Practico)

for ( i = 0; i < 10; i++) { cout<<A[i]<<”\t”; }

Ejemplo: Programa para determinar el mayor y menor de n números enteros.

#include <conio.h> #include <iostream.h> #define max 100 void main() { int num[max];

int i, n, mayor, menor; do { cout<<”Ingrese la cantidad de numeros a ingresar:”;

cin>>n; }while( n<1 || n>100); for(i = 0; i < n ; i++) // ingresa los n números { cout<<”Ingrese el numero[”<<i+1<<”]:”; cin>>num[i]; } for(i = 1, mayor= num[0], menor=num[0]; i < n; i++) { // compara los números y los guarda sea el resultado if(mayor < num[i]) mayor = num[i]; if(menor > num[i]) menor = num[i]; } cout<<"El mayor: ”<<mayor<<” y el menor: ”<<menor<<endl;

}

5.3.2. Arreglos multidimensionales.

Se podrán declarar arreglos de mayor dimensión, ya sean arreglos

bidimensionales y tridimensionales como sigue:

// Bidimensional tipo_de_dato identificador[tamaño1][tamaño2]; // Tridimensional

Page 132: Libro de c++ (Teria/Practico)

tipo_de_dato identificador[tamaño1][tamaño2][tamaño3];

dónde:

• tipo_de_dato: Es el tipo de datos que contendrá la matriz. Hasta ahora sólo

conocemos los tipos básicos de datos; int, float, double, char. Posteriormente

veremos cómo definir nuestros propios tipos de datos.

• identificador: Es el nombre que le damos a la variable matriz y por el cual la

referenciaremos en un programa.

• [tamaño]: Indica la dimensión o el número de elementos de tipo

tipo_de_datos contendrá la matriz identificada. Si se definen 2 dimensiones

(arreglo bidimensional), la cantidad total será igual al producto de ambas

dimensiones o tamaños.

5.3.2.1. Arreglo bidimensional.

Un arreglo bidimensional es aquel en donde los componentes son accesados por

medio de una pareja de índices que apunten a la fila y columna del componente

requerido. Los arreglos de este tipo son conocidos también con el nombre de

matrices o tablas. Conceptualmente, podemos pensar en un arreglo 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.(Figura 5.2)

Page 133: Libro de c++ (Teria/Practico)

Figura 5. 2. Representación de un arreglo bidimensional.

La primera dimensión establece la cantidad de filas. La segunda dimensión

establece la cantidad de columnas. Al igual que en los arreglos de una dimensión,

todos los datos serán del mismo tipo y nombre y diferenciada por dos índices.

También podemos decir que representa un conjunto compuesto por

subconjuntos interno, donde la primera dimensión representa la cantidad de estos

conjuntos los cuales tienen la misma cantidades de elementos todos del tipo declarado

representado por la segunda dimensión. Es decir lo representamos como un arreglo de

arreglos unidimensionales.

5.3.2.2. Arreglo Tridimensional

Los arreglos tridimensionales están compuestos por tres dimensiones donde la

primera dimensión representa la cantidad de arreglos bidimensionales, la segunda

dimensión representa la cantidad de arreglos unidimensionales y la tercera dimensión

representa la cantidad de elementos que contiene cada arreglo unidimensional.

Permite realizar representaciones en tres planos. (Figura 5.3)

Page 134: Libro de c++ (Teria/Practico)

Figura 5. 3. Representación de un arreglo tridimensional.

5.3.2.3. Declaración de los arreglos bidimensionales y tridimensionales

Para declarar un arreglo bidimensional, matriz o tabla:

tipo_de_dato nomb_matriz[fila][columnas];

Para declarar un arreglo tridimensional:

tipo_de_dato nomb_arreglo[fila][columnas][elementos];

Algunas declaraciones de matrices y arreglos tridimensionales.

Matriz de números reales de 10x10:

float matriz[10][10];

Matriz tridimensional de números enteros 20x20x10:

Page 135: Libro de c++ (Teria/Practico)

int Tridimensional[20][20][10];

Como ya se supondrá el acceso a cada elemento de la matriz se realiza

especificando su posición, pero ésta comienza a contarse desde el valor 0, es decir,

la matriz que hemos declarado (matriz) tendrá elementos desde el [0][0] al [9][9].

Esto puede causar algunos mal entendidos cuando se trabaja con matrices

estáticas. Por ejemplo:

a = matriz [2][1];

Al tomar el valor del elemento (2,1) comenzando a contar desde 0, es decir

que el elemento se ubica en la tercera fila (fila 2) de la segunda columna (columna 1).

tridimensional [5][16][1] = 67;

Introduce el valor 67 en la entrada de la matriz especificada

Las variables de tipo matriz como el resto de las declaraciones, se

pueden inicializar en el momento de su declaración, ayudándose de las llaves

({}) para la inclusión de inicializaciones múltiples.

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

Estas líneas nos declararían una matriz llamada "matriz" de 2x3 elementos

inicializada con los valores indicados. Las matrices son extremadamente útiles para

trabajar con multitud de problemas matemáticos que se formulan de esta forma o

Page 136: Libro de c++ (Teria/Practico)

para mantener tablas de datos a los que se accede con frecuencia y por tanto su

referencia tiene que ser muy rápida.

Se permite la inicialización de arreglos, debiendo seguir el siguiente formato:

tipo nombre_arr[ n] = {lista- nvalores}; tipo nombre_arr[ n ][ m ] = {{lista-0},…, {lista-n-1}}; tipo nombre_arr[ n][m][l] = {{{lista0-0},…,{lista0-m-1}},

… {{listan-1-0},…,{listan-1-m-1}};

};

Por ejemplo:

int i[10] = {1,2,3,4,5,6,7,8,9,10}; int num[3][4]= { {0,1,2,3}, {4,5,6,7}, {8,9,10,11} }; int num[2][2][2]={{{0,1}, {2,3}}, {{4,5}, {6,7}}};

A igual que los arreglos unidimensionales, los arreglos multidimensionales

utilizaran sentencias interactivas anidadas para cada una de las dimensiones.

5.3.2.4. Ejemplo de arreglos multidimensionales

Ejemplo 1: Realice un programa que lea una matriz de n * n elementos enteros

y muestre cuántos de estos elementos son primos.

#include <conio.h>

#include <iostream.h>

#define max 100

void main()

{ int num[max][max];

int i, j, n, div, contprimos;

Page 137: Libro de c++ (Teria/Practico)

do { cout<<”Ingrese la cantidad de números a ingresar:”;

cin>>n;

}while( n<1 || n>100);

for(i = 0; i < n ; i++) // recorre las filas

for(j = 0; j < n ; j++) // recorre las columnas

{ cout<<”Ingrese el numero[”<<i+1<<”][”<<j+1<<”]:”;

cin>>num[i][j];

}

for(contprimos=0, i = 0; i < n ; i++) // recorre las filas

for(j = 0; j < n ; j++) // recorre las columnas

{

for(div=2;div<num[i][j] && num[i][j]%div!=0; div++);

if(div==num[i][j]) contprimos++;

}

cout<<"Existen : ”<<contprimos<<” numeros primos”<<endl;

}

Ejemplo 2: calcular el determinante de una matriz de n * n elementos enteros.

#include<iostream.h>

void main()

{

int i,j,k,l,m,n ;

float a[50][50];

float det;

cout << "Introducir el ORDEN DE LA MATRIZ : N = " << endl;

cin >> n;

m=n-1;

Page 138: Libro de c++ (Teria/Practico)

cout << "Introducir los elementos" << endl;

cout << "------------------------" << endl;;

for(i=0; i<n; i++)

for(j=0; j<=n; j++)

cin >> a[i][j];

det=a[0][0];

for(k=0;k=m;k++)

{ l=k+1;

for(i=l;i<n;i++)

{ for(j=l;j<n;j++)

a[i][j] = ( a[k][k]*a[i][j]-a[k][j]*a[i][k] )/a[k][k];

}

det=det*a[k+1][k+1];

}

cout << endl;

cout << "DETERMINANTE = " << det << endl;

cout << "------------------------" << endl;

}

Ejemplo 3: Realice un programa que lea una matriz de n * n elementos enteros

y muestre el arreglo solo ordenando los números pares.

#include <conio.h>

#include <iostream.h>

#define max 20

void main()

{ int num[max][max];

int i, j, k, n, aux, auxA[max*max];

Page 139: Libro de c++ (Teria/Practico)

do { cout<<”Ingrese la cantidad de números a ingresar:”;

cin>>n;

}while( n<1 || n>100);

for(i = 0; i < n ; i++)

for(j = 0; j < n ; j++)

{ cout<<”Ingrese el numero[”<<i+1<<”][”<<j+1<<”]:”;

cin>>num[i][j];

}

for(i = 0,k=0; i < n ; i++)

for(j = 0; j < n ; j++)

auxA[k++]=num[i][j];

for(i = 0; i < k-1 ; i++)

if(auxA[i]%2==0)

for(j = i+1; j < k ; j++)

if(auxA[j]%2==0 && auxA[i] >auxA[j])

{ aux=auxA[i];

auxA[i]=auxA[j];

auxA[j]=aux;

}

cout<<"La matriz ordenado solo sus valores pares es:”<<endl;

for(i = 0,k=0; i < n ; i++)

{ for(j = 0; j < n ; j++)

{ num[i][j]= auxA[k++];

cout<<num[i][j]<<” ”;

}

cout<<endl;

Page 140: Libro de c++ (Teria/Practico)

}

}

Ejemplo 4: Realice un programa que lea una matriz de n * n elementos enteros

y muestre si la diagonal principal es igual a la secundaria. Además muestre cuales

filas son iguales a su columna.

#include <conio.h>

#include <iostream.h>

#define max 20

void main()

{ int num[max][max];

int i, j, k, n, v;

do { cout<<”Ingrese la cantidad de números a ingresar:”;

cin>>n;

}while( n<1 || n>100);

for(i = 0; i < n ; i++)

for(j = 0; j < n ; j++)

{ cout<<”Ingrese el numero[”<<i+1<<”][”<<j+1<<”]:”;

cin>>num[i][j];

}

for(i = 0; i < n ; i++)

if(num[i]i] != num[i][n-1-i])

break;

if(i==n)

cout<<”la diagonal principal es igual a la secundaria”<<endl;

else

Page 141: Libro de c++ (Teria/Practico)

cout<<”las diagonal principal y secundaria son distintas”<<endl;

for(i = 0; i < n ; i++)

for(k=0; k<n; k++)

{ for(j = 0; j< n ; j++)

if(num[i]j] != num[k][j])

break;

if(j==n)

cout<<”la Fila:”<<i+1<<” es igual a la Columna:”<<k+1<<endl;

}

}

Además de clasificar los arreglos según su dimensión, también los podemos

declarar según sea su tipo de datos: En arreglos numéricos y arreglos alfanuméricos o

de caracteres. Tomando como diferencia primordial el contenido que puede

almacenar sus variables, notaremos que en los numéricos puede existir diversidad de

símbolos (como se ha reflejado en los apartados anteriores) y en los de carácter un

único símbolo, por lo que son llamadas cadenas de caracteres.

5.3.3. Cadenas de caracteres.

Una cadena es un conjunto de caracteres, o valores de tipo "char", terminados

con el carácter nulo, es decir el valor numérico 0. Internamente se almacenan en

posiciones consecutivas de memoria. Este tipo de estructuras recibe un tratamiento

especial, y es de gran utilidad y de uso continuo.

Lo que distingue a una cadena de caracteres, con respecto a un arreglo

numérico, es que la cadena de caracteres tiene como último carácter al carácter nulo

‘\0’. Esto permite conocer hasta donde evaluaremos la secuencia de caracteres.

La manera de definir una cadena es la siguiente:

Page 142: Libro de c++ (Teria/Practico)

char nombre_identificador [<longitud máxima>];

Por ejemplo, si se declara el arreglo:

char cadena[8];

Se podrá asignar los siguientes valores a cada uno de sus elementos:

cadena[0] = 'A' ;

cadena[1] = 'R' ;

cadena[2] = 'R' ;

cadena[3] = 'E' ;

cadena[4] = 'G' ;

cadena[5] = 'L' ;

cadena[6] = 'O' ;

cadena[7] = '\0';

Al contener el carácter nulo, el arreglo cadena será reconocido por las funciones

y objetos diseñados para manipular cadenas de caracteres. Entre estas las sentencias

de salida cout, printf, puts, etc.

Para manejar y realizar operaciones con las cadenas de caracteres es necesario

realizar sentencias de recorrido hasta encontrar el carácter fin de cadena. Lo que lo

diferencia de los arreglos numéricos que es necesario conocer la cantidad de

elementos a evaluar.

Un arreglo es necesario leer y mostrar cada elemento uno a uno en el caso de

las cadenas solo es necesario usar una función que atrape cadenas de caracteres o las

muestre, ejemplo:

Page 143: Libro de c++ (Teria/Practico)

char cadena[10];

get ( cadena); // Lee una cadena de caracteres

cin>>cadena; // Lee la cadena y transforma el carácter intro en’\0’

scanf(”%s”, cadena); // Lee la cadena y transforma el carácter intro en’\0’

cout<<cadena; // muestra todos los caracteres hasta el carácter final

Para evaluar una cadena siempre sera necesario al menos una sentencia de

recorrido.

Ejemplo: realice un programa que lea una frase y cambie un carácter de la frase

por un nuevo carácter.

#include <conio.h>

#include <iostream.h>

#define max 100

void main()

{ char frase[max];

char carV, carN;

cout<<”Ingrese una frase:”

gets(frase);

cout<<”Ingrese el carácter a modificar:”

carV = getch();

cout<<”Ingrese el nuevo nuevo carácter:”

carN = getch();

for(i = 0; frase[i] !=’\0’ ; i++)

if( frase[i] ==carV)

frase[i] = carN ;

Page 144: Libro de c++ (Teria/Practico)

cout<<"La nueva frase es : ”<<frase<<endl;

}

Para manejar un arreglo de cadenas de caracteres se debe declarar como un

arreglo bidimensional de elementos de tipo char, como por ejemplo: Leer 10 nombres

y una calificación.

#include <iostream.h>

#include <conio.h>

void main()

{

unsigned short int calif[10];

char nombre[10][20]; // Se declara un arreglo bidimensional

// para 10 nombres de 20 caracteres por

// nombre más un carácter para el nulo.

for( int x=0 ; x < 10 ; x++)

{

cout << "NOMBRE [" << x +1<< "] = " ;

cin >> nombre[x];

cout << "CALIFICACION [" << x+1 << "] = " ;

cin >> calif[x];

}

}

Cuando se tiene un arreglo de cadenas de caracteres, se puede utilizar para

asignar valores en las declaraciones de cadena.

Por Ejemplo:

Page 145: Libro de c++ (Teria/Practico)

char nombres[][5] = { "HUGO", "PACO", "LUIS" } ;

Es equivalente a:

char nombres[3][5];

nombres[0] = "HUGO" ; // no aplicable

nombres[1] = "PACO" ; // no aplicable

nombres[2] = "LUIS" ; // no aplicable

Esto también puede manejarse a nivel de caracteres, así:

char nombres[][5] = { 'H','U','G','O','\0',

'P','A','C','O','\0',

'L','U','I','S','\0' };

o así:

char nombres[3][5];

nombres[0][0] = 'H' ;

nombres[0][1] = 'U' ;

nombres[0][2] = 'G' ;

nombres[0][3] = 'O' ;

nombres[0][4] = '\0' ;

nombres[1][0] = 'P' ;

nombres[1][1] = 'A' ;

nombres[1][2] = 'C' ;

Page 146: Libro de c++ (Teria/Practico)

nombres[1][3] = 'O' ;

nombres[1][4] = '\0' ;

nombres[2][0] = 'L' ;

nombres[2][1] = 'U' ;

nombres[2][2] = 'I' ;

nombres[2][3] = 'S' ;

nombres[2][4] = '\0' ;

Al manejar arreglos bidimensionales de cadenas, no es necesario escribir el

valor de la primera dimensión de los arreglos cuando, en su declaración, se asignan

valores constantes a los elementos. La ventaja que tiene es que, al no especificar una

de las dimensiones, la cantidad de cadenas a manejar puede variarse con sólo

agregarlas a la lista o eliminarlas de ella.

Además de las funciones gets() y puts(), existe otro grupo de funciones para el manejo

de cadenas de caracteres, como strlen() y strupr(). Los prototipos de estas funciones se

encuentran declarados en la libreria STRING. En la tabla (Figura 5.4) siguiente se describen

brevemente algunas de las funciones para el manejo de cadenas de caracteres, cuyos

prototipos se encuentran declarados en STRING.H.

FUNCION DESCRIPCION

stpcpy Copia una cadena de caracteres en otra.

strcat Añade una cadena de caracteres a otra.

strchr Busca, en una cadena, un carácter dado.

strcmp Compara dos cadenas.

strcmpi Macro que compara dos cadenas sin distinguir entre mayúsculas y minúsculas.

strcpy Copia una cadena.

strdup Copia una cadena a una nueva localidad.

_strerror Genera un mensaje de error definido por el programador.

strerror Retorna el apuntador al mensaje asociado con el valor del error.

Page 147: Libro de c++ (Teria/Practico)

stricmp Compara dos cadenas sin diferenciar entre mayúsculas y minúsculas

strlen Determina la longitud de una cadena.

strlwr Convierte las mayúsculas de una cadena en minúsculas.

strncat Añade el contenido de una cadena al final de otra.

strncmp Compara parte de una cadena con parte de otra.

strncmpi Compara parte de una cadena con parte de otra, sin distinguir entre mayúsculas

y minúsculas.

strncpy Copia un número de bytes dados, desde una cadena hacia otra.

strnset Hace que un grupo de elementos de una cadena tengan un valor dado.

strpbrk Busca la primera aparición, en una cadena, de cualquier carácter de un

conjunto dado.

strrchr Busca la última aparición de un carácter en una cadena.

strrev Invierte el orden de los caracteres de una cadena.

strset Hace que los elementos de una cadena tengan un valor dado.

strspn Busca en una cadena el primer segmento que es un subconjunto de un conjunto

de caracteres dado.

strstr Busca en una cadena la aparición de una subcadena dada.

_strtime Convierte la hora actual a una cadena.

strtod Convierte una cadena a un valor double ó long double.

strtol Convierte una cadena a un valor long.

strtoul Convierte una cadena a un valor unsigned long.

strupr Convierte las minúsculas de una cadena a mayúsculas.

Figura 5. 4. Tabla de funciones para el manejo de cadenas de caracteres.

5.3.3.1. Ejemplo de cadenas de caracteres

Ejemplo1: Realice un programa que lea una frase y muestre si es palindrome.

Page 148: Libro de c++ (Teria/Practico)

#include <conio.h>

#include <iostream.h>

#define max 100

void main()

{ char frase[max];

int i, j;

cout<<”Ingrese una frase:”

gets(frase);

for(i = 0; frase[i] !=’\0’ ; i++);

for(i=i-1, j = 0; frase[i] == frase[j] && i > j ; j++);

if( i<=j)

cout<<"La frase : ”<<frase<< “ es palindrome”<<endl;

else

cout<<"La frase : ”<<frase<< “ no es palindrome”<<endl;

}

Ejemplo2: Realice un programa que lea n nombres, convierta los caracteres a

mayúscula y los muestre ordenados alfabéticamente.

#include <conio.h>

#include <iostream.h>

#define max 25

#define max2 100

void main()

{ char nom[max2][max], aux[max];

int i, j, k, v, n, cambio;

cout<<”Ingrese cantidad de nombres:”

Page 149: Libro de c++ (Teria/Practico)

cin>>n;

cout<<”Ingrese los nombres:”<<endl;

for( i=0 ; i < n ; i++)

{ cout << "|tNombre: [" << i +1<< "] = " ;

cin >> nom[i];

}

// transforma los n nombres a mayúscula

for(i = 0; i<n ; i++)

for(j = 0; nom[i][j] !=’\0’ ; j++)

if(nom[i][j]>=’a’ && nom[i][j]<=’z’)

nom[i][j]=nom[i][j] – 32;

// ordena los n nombres método burbuja

do{ cambio=0;

for(i = 0; i<n-1 ; i++)

{ v=0;

for (j=0; v==0 && nom[i][j]!=’\0’&& nom[i+1][j]!=’\0’; j++)

if(nom[i][j] > nom[i+1][j]) v=1;

else if(nom[i][j] < nom[i+1][j]) v=2;

if(v==0){ if(nom[i][j]!=’\0’) v=1;

if(nom[i+1][j]!=’\0’) v=2;

}

if( v==1){ cambio=1;

for(j = 0; nom[i][j] !=’\0’ ; j++)

aux[j]=nom[i][j];

aux[j]=nom[i][j];

for(j = 0; nom[i+1][j] !=’\0’ ; j++)

nom[i][j]=nom[i+1][j];

Page 150: Libro de c++ (Teria/Practico)

nom[i][j]=nom[i+1][j];

for(j = 0; aux[j] !=’\0’ ; j++)

nom[i+1][j]=aux[j];

nom[i+1][j]=aux[j];

}

}

}while(cambio==1);

cout<<"Los nombres ordenados alfabéticamente:”<<endl;

for(i = 0; i<n ; i++)

cout<< nom[i]<< endl;

}

Ejemplo 3: Realice un programa que lea n nombres, convierta a mayúscula y

elimine los nombres repetidos.

#include <conio.h>

#include <iostream.h>

#define max 25

#define max2 100

void main()

{ char nom[max2][max], aux[max];

int i, j, k, l, v, n;

cout<<”Ingrese cantidad de nombres:”

cin>>n;

cout<<”Ingrese los nombres:”<<endl;

for( i=0 ; i < n ; i++)

{ cout << "|tNombre: [" << i +1<< "] = " ;

Page 151: Libro de c++ (Teria/Practico)

cin >> nom[i];

}

// transforma los n nombres a mayúscula

for(i = 0; i<n ; i++)

for(j = 0; nom[i][j] !=’\0’ ; j++)

if(nom[i][j]>=’a’ && nom[i][j]<=’z’)

nom[i][j]=nom[i][j] – 32;

// busca las frases repetidas y las elimina

for(k=0;k<n-1; k++)

for(i = k; i<n ; i++)

{ v=0;

for (j=0; v==0 && nom[i][j]!=’\0’&& nom[i+1][j]!=’\0’; j++)

if(nom[i][j] > nom[i+1][j]) v=1;

else if(nom[i][j] < nom[i+1][j]) v=2;

if(v==0) { if(nom[i][j]!=’\0’) v=1;

if(nom[i+1][j]!=’\0’) v=2;

}

if( v==0){

for(l = i; l<n ; l++)

{ for(j = 0; nom[l+1][j] !=’\0’ ; j++)

nom[l][j]=nom[l+1][j];

nom[l][j]=nom[l+1][j];

}

i--;

n--;

}

}

Page 152: Libro de c++ (Teria/Practico)

cout<<"Los nombres sin repetir son:”<<endl;

for(i = 0; i<n ; i++)

cout<< nom[i]<< endl;

}

Page 153: Libro de c++ (Teria/Practico)

CAPITULO 6

APUNTADORES

6.1. Referencias de memoria.

Las referencias son novedades absolutas del C++ (no se encuentran disponibles

en C). Una referencia es un nuevo tipo de datos que nos va a permitir utilizar las

características de los punteros pero tratándolos como variables ordinarias. Podéis

imaginaros una referencia como un "alias" de una variable o, mejor dicho, como la

misma variable disponible pero con un nombre distinto.

La inicialización de una referencia es simple, sólo tendremos que asociar una

referencia a una variable que ya esté creada. Una vez realizado la inicialización, la

referencia va a estar continuamente asociada con la variable correspondiente. Si

quisiéramos hacer que la referencia fuese el "alias" de otra variable o referenciar a

otra variable no podríamos, ocurre un error de compilación, solo podrá realizarse en

el momento de su declaración.

La declaración de una referencia:

Tipo_dato &nombre_referencia = variable_declarada;

Ejemplo:

// dato es una variable definida y es de tipo entero. int dato;

// referenciaDato es la referencia creada de dato. int &referenciaDato = dato;

Page 154: Libro de c++ (Teria/Practico)

Como se observa para crear una referencia no necesitamos más que la variable

a la que queremos referenciar, que en el ejemplo es dato, junto la referencia en sí, que

se va a definir con el símbolo &. De este modo, referenciaDato es la referencia o

alias de la variable dato.

Una vez realizada, cualquier cambio que hagamos sobre dato se verá reflejado

en referenciaDato y viceversa, es decir, si realizamos una modificación en

referenciaDato, esta también se va a ver reflejada en la variable dato.

Ejemplo:

#include <iostream.h> void main() { int dato = 50; int& refDato = dato; cout << "La variable dato vale " << dato << ’\n’; cout << "La variable refDato vale " << refDato << ’\n’;

// multiplicamos la variable dato por 2, ahora dato = 100 dato *= 2; cout << "La variable dato vale " << dato << ’/n’; cout << "La variable refDato vale " << refDato << ’\n’; // incrementamos el valor de refDato = 101; refDato ++; cout << "La variable dato vale " << dato << ’\n’; cout << "La variable refDato vale " << refDato; }

Si se observa los resultados en la salida, tenemos que:

La variable dato vale 50

La variable refDato vale 50

La variable dato vale 100

Page 155: Libro de c++ (Teria/Practico)

La variable refDato vale 100

La variable dato vale 101

La variable refDato vale 101

Los cambios efectuados en dato y refDato se ven involucrados. Debido a que

dato y refDato comparten la misma dirección de memoria y por eso, cualquier

cambio que efectuemos sobre dato afectará a refDato y viceversa. (Figura 6.1)

Figura 6. 1. Referencia de memoria.

Para comprobar que las direcciones de dato y refDato son las misma,

realizamos:

void main() { int dato = 50; int& refDato = dato; cout << "La dirección de la variable dato es " << &dato << endl; cout << "La dirección de la referencia refDato es " << &refDato; }

Page 156: Libro de c++ (Teria/Practico)

6.2. Apuntadores.

6.2.1. Concepto.

Los apuntadores o punteros se definen como una variable que contiene la

dirección de memoria de un dato o de otra variable que contiene al dato. El puntero

apunta al espacio físico donde está el dato o la variable. Pueden apuntar a un objeto

de cualquier tipo, como una estructura o una función. Se utilizan para referenciar y

manipular estructuras de datos, bloques de memoria, paso de argumentos.

Los punteros o apuntadores podemos conceptualizarlos como referencias de

memoria variables, las cuales permiten realizar operaciones con las posiciones de

memorias, permitiendo el desplazamiento entre las variables y además con la

posibilidad de modificar el contenido de estas.

Es una herramienta cómoda, poderosa y directa para el manejo de variables

complejas, argumentos, parámetros, etc.

6.2.2. Declaración de un apuntador.

Para declarar un puntero, este deberá tener un tamaño de memoria a la cual se

pueda ubicar y un nombre el cual se identifica su forma de referenciar y el operar ‘*’

el cual nos indica la presencia del puntero:

tipo_de_variable_apuntada *nombre_del_puntero ;

Ejemplo:

int *pint ; double *pfloat ; char *letra , *codigo , *caracter ;

Page 157: Libro de c++ (Teria/Practico)

En estas declaraciones indicamos al compilador que reserve una posición de

memoria para albergar la dirección de una variable, del tipo indicado, la cual será

referenciada con el nombre que hayamos dado al puntero. El tipo de dato del puntero

indica la capacidad del puntero de ubicarse en una variable o estructura de dato, la

cual deberá ser del mismo tamaño.

Cuando declaramos un puntero como:

int *a;

Indicamos que:

• a representa la dirección de memoria que apunta.

y

• *a representa el contenido de la dirección de memoria apuntada.

Obviamente, un puntero debe ser inicializado antes de usarse, y una de las eventuales

formas de hacerlo es la siguiente:

int var1 ; // declaro ( y creo en memoria ) una variable entera ) int *pint ; // un puntero que contendrá la dirección de una variable entera

pint = &var1 ; //escribo en la dirección del puntero la de la variable

"&nombre_de_una_variable " implica la dirección de la misma.

Esquemáticamente, lo que hemos hecho se puede simbolizar como se muestra

en la Figura 6.2:

Page 158: Libro de c++ (Teria/Practico)

Figura 6. 2. Representación de un puntero.

La variable var1 reserva una dirección de memoria de 2 bytes solo para ella, el

puntero apunta a esta posición permitiendo utilizar el contenido de esta.

En la declaración del puntero, está implícita otra información: el tamaño (en

bytes) de la variable apuntada.

El símbolo &, ó dirección, puede aplicarse a variables, funciones, etc., pero no

a constantes ó expresiones, ya que éstas no tienen una posición de memoria asignada.

La operación inversa a la asignación de un puntero, de referenciación del

mismo, se puede utilizar para hallar el valor contenido por la variable apuntada. Así

por ejemplo serán expresiones equivalentes:

y = var1 ; y = *pint ; printf("%d" , var1 ) ; printf("%d" , *pint) ;

En estos casos, la expresión " *nombre_del_puntero ", implica " contenido de la

variable apuntada por el mismo”. Ejemplo de esto:

#include <stdio.h> void main() { char var1 ; //una variable del tipo carácter char *pchar; // un puntero a una variable del tipo carácter pchar = &var1 ; //*asignamos al puntero la dirección de la variable

Page 159: Libro de c++ (Teria/Practico)

for (var1 = 'a'; var1 <= 'z'; var1++) printf("%c", *pchar) ; // imprimimos el valor de la variable apuntada }

Vemos acá, que en el for se incrementa el valor de la variable, y luego para

imprimirla usamos la de referenciación de su puntero.

El programa imprimirá las letras del abecedario de la misma manera que lo

habría hecho si la sentencia del printf hubiera sido, printf("%c" , var1 ). Hay un

error, que se comete con bastante frecuencia, y es cargar en la dirección apuntada por

un puntero a un tipo dado de variable, el contenido de otro tipo de las mismas, por

ejemplo:

double d = 10.0 ; int i = 7 , *pint ; pint = &i ; *pint = 10 ; // correcto, equivale a asignar a i el valor 10 *pint = d ; // ERROR se pretende cargar en una variable entera un

valor double pint = &d ; // INCORRECTO se pretende apuntar a una variable double con un puntero declarado como apuntador a int pint = 4358 ; // ??????

El primer error, la asignación de un double, produce la pérdida de información

dada por la conversión automática de tipo de variable, el segundo produce un llamado

de atención rotulado como " asignación sospechosa de un pointer”. Resumiendo, las

variables ó constantes cargadas por de referenciación de un puntero, deben coincidir

en tipo con la declaración de aquel.

La asignación de una constante a un pointer, y no a la variable apuntada por él,

es un serio error, ya que debe ser el compilador, el encargado de poner en él el valor

de la dirección, aquel así lo declara dando un mensaje de "conversión de puntero no

transportable" . Si bien lo compila, ejecutar un programa que ha tenido esta

Page 160: Libro de c++ (Teria/Practico)

advertencia es similar a jugar a la ruleta rusa, puede "colgarse" la máquina ó lo que es

peor destruirse involuntariamente información contenida en un disco, etc.

Hay un sólo caso en el que esta asignación de una constante a un puntero es

permitida, muchas funciones para indicar que no pueden realizar una acción ó que se

ha producido un error de algún tipo, devuelven un puntero llamado "Null Pointer", lo

que significa que no apunta a ningún lado válido, dicho puntero ha sido cargado con

la dirección NULL (por lo general en valor 0), así la asignación: pint = NULL; es

válida y permite luego operaciones relacionales del tipo if( pint ) ..... ó if( pint !=

NULL ) para convalidar la validez del resultado devuelto por una función.

Una advertencia seria que se debe tener en cuenta que los punteros no son

enteros, como parecería a primera vista, ya que el número que representa a una

posición de memoria, sí lo es. Debido al corto alcance de este tipo de variable,

algunos compiladores pueden, para apuntar a una variable muy lejana, usar cualquier

otro tipo, con mayor alcance que el antedicho.

6.2.3. Apuntadores y arreglos

Hay una relación muy cercana entre los punteros y los arreglos. El nombre de

un arreglo es equivalente a la dirección del elemento[0] del mismo.

int a[10]; // La dirección del primer elemento: &a[0] // La dirección del arreglo se refleja como su mismo nombre: a // entonces a == &a[0], ambas direcciones son equivalentes

El nombre de un arreglo, para el compilador, es un apuntador inicializado con

la dirección del primer elemento del arreglo. Sin embargo hay diferencias entre

ambos.

Page 161: Libro de c++ (Teria/Practico)

6.2.4. Asignación de los apuntadores.

Observemos las líneas siguientes:

float var1 , conjunto[] = { 9.0 , 8.0 , 7.0 , 6.0 , 5.0 }; float *punt ; punt = conjunto ; // equivalente a hacer : punt = &conjunto[0] var1 = *punt ; *punt = 25.1 ; Es válido asignar a un puntero el valor de otro, el resultado de ésta operación es

cargar en el puntero punt la dirección del elemento[0] del arreglo, y posteriormente

en la variable var1 el valor del mismo (9.0) y para luego cambiar el valor de dicho

primer elemento a 25.1.

La diferencia entre un puntero y el denominador de un arreglo, el primero es

variable, es decir que puedo asignarlo, incrementarlo, etc, en cambio el arreglo es una

constante, que apunta siempre al primer elemento del arreglo con que fue declarado,

por lo que su contenido no puede ser variado. Si lo piensa un poco, es lógico, ya que

un "conjunto" implica la dirección del elemento conjunto[0], por lo que, si yo

cambiara su valor, apuntaría a otro lado dejando de ser un "conjunto”.

El siguiente ejemplo nos muestra un tipo de error frecuente:

int conjunto[5] , lista[] = { 5 , 6 , 7 , 8 , 0 ) ; int *apuntador ; apuntador = lista ; // correcto conjunto = apuntador; // ERROR (conjunto es una dirección constante) lista = conjunto ; // ERROR ( son direcciones constantes ) apuntador = &conjunto;

//ERROR no se puede aplicar el operador & a una constante

Page 162: Libro de c++ (Teria/Practico)

6.2.5. Incremento y decremento de un apuntador.

Dado las siguientes instrucciones podemos analizar:

int *pint , arreglo_int[5] ; double *pdou , arreglo_dou[6] ; pint = arreglo_int ; // pint apunta a arreglo_int[0] pdou = arreglo_dou ; // pdou apunta a arreglo_dou[0] pint += 1 ; // pint apunta a arreglo_int[1] pdou += 1 ; // pdou apunta a arreglo_dou[1] pint++ ; // pint apunta a arreglo_int[2] pdou++ ; // pdou apunta a arreglo_dou[2]

Hemos declarado y asignado dos punteros, uno a int y otro a doublé, con las

direcciones de dos arreglos. Ambos estarán ahora apuntando a los elementos[0] de los

arreglos. En las dos instrucciones siguientes incrementamos en uno dichos punteros.

En el compilador, estas sentencias se leen como: incremente el contenido del

puntero (dirección del primer elemento del arreglo) en un número igual a la cantidad

de bytes que tiene la variable con que fue declarado. Es decir que el contenido de pint

es incrementado en dos bytes (un int tiene 2 bytes) mientras que pdou es

incrementado 8 bytes (por ser un puntero a double), el resultado entonces es el mismo

para ambos, ya que luego de la operación quedan apuntando al elemento siguiente del

arreglo, arreglo_int[1] y arreglo_dou[1].

Vemos que de ésta manera será fácil recorrer un arreglo, independientemente del

tamaño de variables que lo compongan, permitiendo por otro lado que el programa

sea transportable a distintos hardwares sin preocuparnos de la diferente cantidad de

bytes que pueden asignar los mismos, a un dado tipo de variable.

Page 163: Libro de c++ (Teria/Practico)

6.2.6. Aritmética de referencia

Debido a que los operadores * y ++ ó -- tienen la misma precedencia y se

evalúan de derecha a izquierda, y los paréntesis tienen mayor precedencia que ambos,

muchas operaciones que los utilizan en conjunto a todos estos operadores, pueden

parecer poco claras y dar origen a un sin número de errores, analicémoslas

detalladamente, partiendo de:

int *p , a[] = { 0 , 10 , 20 , 30 , 40 , 50 , 60 , 70 , 80 , 90 } ; int var ; p = a ;

El puntero está apuntando a a[0]. Si:

*p = 27 ;

Se asignamos al elemento apuntado por p (que seria a[0]) un valor constante.

De forma inversa:

var = *p ;

var sería asignada al valor contenido de a[0], y p seguiría apuntando al mismo

elemento. Si fuese de la forma:

var = *( p + 1 ) ;

Carga a var con el contenido del elemento siguiente al apuntado por p (el cual

seria a[1]). El puntero p, no varía sigue apuntando a a[0]. De la misma forma: var =

*( p + 3 ) asignará 30 a var, sin modificar el contenido de p.

Page 164: Libro de c++ (Teria/Practico)

En cambio la expresión:

var = *( p++ ) ;

Se asigne a var el valor de lo apuntado por p e incremente luego éste para

que apunte al próximo elemento. Así en var quedaría con el valor de a[0] y p

apuntaría a a[1]. Si en vez de esto hubiéramos preincrementado a p tendríamos:

var = *( ++p ) ;

Apunte con p al próximo elemento y asigne a var con el valor de éste. En este

caso var sería igualada al valor de a[1] y p quedaría apuntando al mismo .

En las dos operaciones anteriores los paréntesis son superfluos ya que al analizarse

los operadores de derecha a izquierda, daría lo mismo escribir:

var = *p++ ; // sintácticamente igual a var = *(p++) var = *++p ; // " " " var = *(++p)

6.2.7. Aritmética de punteros

La aritmética más frecuentemente usada con punteros son las sencillas

operaciones de asignación, incremento ó decremento y de referenciación. Todo otro

tipo de aritmética con ellos está prohibida ó es de uso peligroso ó poco transportable.

Por ejemplo no está permitido, sumar, restar, dividir, multiplicar, etc., dos

apuntadores entre sí. Por ejemplo.

int *p, *q;

Page 165: Libro de c++ (Teria/Practico)

p = p + q; // no tendría caso realizar // le asignamos a p una dirección que no conocemos

Otras operaciones están permitidas, como la comparación de dos punteros, por

ejemplo (punt1 == punt2) ó (punt1 < punt2), sin embargo este tipo de operaciones

son potencialmente peligrosas, ya que con algunos modelos de pointers pueden

funcionar correctamente y con otros no.

6.2.8. Los apuntadores y las cadenas de caracteres

No hay gran diferencia entre el trato de punteros a arreglos, y a cadenas de

caracteres, ya que estos dos últimos son entidades de la misma clase. Sin embargo

analicemos algunas particularidades.

Los punteros prestan un mejor beneficio a la hora de

Así como inicializamos una cadena con un grupo de caracteres terminados en

'\0', podemos asignar al mismo un puntero:

p = "Esto es una cadena constante ";

Esta operación no implica haber copiado el texto, sino sólo que a p se le ha

asignado la dirección de memoria donde reside la "E" del texto. A partir de ello

podemos manejar a p como lo hemos hecho hasta ahora. Veamos un ejemplo:

#include <iostream.h> #define TEXTO1 "¿ Hola , como " #define TEXTO2 "le va a Ud. ? " void main() { char palabra[20] , *p ;

int i ; p = TEXTO1 ; for( i = 0 ; ( palabra[i] = *p++ ) != '\0' ; i++ ) ;

p = TEXTO2 ;

Page 166: Libro de c++ (Teria/Practico)

cout<< palabra ; cout<< p;

}

Definimos primero dos cadenas constantes TEXTO1 y TEXTO2, luego

asignamos al puntero p la dirección del primero, y seguidamente en el for copiamos el

contenido de éste en el arreglo palabra, observe que dicha operación termina cuando

el contenido de lo apuntado por p es el terminador de la cadena, luego asignamos a p

la dirección de TEXTO2 y finalmente se muestra ambas cadenas, obteniendo una

salida del tipo: " ¿ Hola , como le va a UD. ? ".

Reconozcamos que esto se podría haber escrito más compacto, ya que palabra

también es un puntero y NULL es cero, cambiamos el for:

while( *palabra++ = *p++ ) ;

Un programa ejemplo del uso de puntero en cadena podría ser el modificar una

frase dada a mayúscula como se muestra a continuación:

#include <iostream.h> #include <stdio.h> #include <conio.h> #define max 100 void main()

{ char frase [max]; char *p; p=frase; cout<<”Ingrese una frase:”; gets(p); for( ; *p!=’|0’ ; *p++) if(*p>=’a’ && *p<=’z’)

*p=*p-32; // como los caracteres difieren en 32 //de mayúscula a minúscula

cout<<”La frase ingresada en mayúscula:”<<frase<<endl;

Page 167: Libro de c++ (Teria/Practico)

}

6.2.9. Arreglos de apuntadores

Es una práctica muy habitual, sobre todo cuando se tiene que tratar con cadenas

de distinta longitud, generar arreglos cuyos elementos son punteros, que albergarán

las direcciones de dichas cadenas. Si imaginamos a un puntero como una flecha, un

arreglo de ellos equivaldría a un conjunto de flechas.

Así como:

char *flecha;

Se declara un puntero a un carácter. Entonces la declaración:

char *flecha[5];

implica un arreglo de 5 punteros a caracteres.

Los arreglos de punteros pueden ser inicializados de la misma forma que un

arreglo común, es decir dando los valores de sus elementos, durante su declaración,

por ejemplo si quisiéramos tener un arreglo donde el subíndice de los elementos

coincidiera con el nombre de los días de la semana, podríamos escribir:

char *dias[] = { "número de día no válido" , "lunes" , "martes" , "miercoles" , "jueves" , "viernes" , "sabado" , "domingo"

Page 168: Libro de c++ (Teria/Practico)

};

Igual que antes, no es necesario en este caso indicar la cantidad de elementos,

ya que el compilador los calcula por la cantidad de términos dados en la

inicialización. Así el elemento dias[0] será un puntero con la dirección de la primera

cadena, dias[1], la del segundo, etc.

Sea A una matriz de enteros (arreglo de 2 dimensiones) y P un arreglo de

punteros enteros:

int A[5][5]; // Matriz de dos dimensiones

int *P[5]; //arreglo de punteros

Las declaraciones anteriores nos indican la declaración de 25 elementos de tipo

entero para la matriz A y de 5 elementos declarados como punteros de tipo int para P

Como se muestra la Figura 6.3.

Figura 6. 3. Representación de una matriz entera y un arreglo de punteros

Si queremos que cada puntero en el arreglo de punteros, apunte a cada arreglo

de la matriz, entonces asignaremos a cada puntero con las filas de la matriz A:

int i; int A[5][5]; // Matriz o arreglo de dos dimensiones int *P[5]; // arreglo de punteros for (i=0; i<5; i++) P[i]=A[i];

Page 169: Libro de c++ (Teria/Practico)

Como ese muestra en la Figura 6.4.

Figura 6. 4. Arreglo de punteros apunta a cada fila de la matriz.

Aunque podríamos pensar que el bucle que asigna a los elementos del arreglo

de punteros P las filas de A (P[i]=A[i]), puede sustituirse por P=A, no se puede ya

que los niveles de dirección son diferentes. Expresados de otra forma, los tipos de P y

A no son iguales ni admiten una conversión entre ellos:

El tipo de P int *[5] ( arreglo de 5 elementos tipo puntero a int)

El tipo de P[i] int * (puntero a int)

El tipo de A int (*)[5] ( puntero a una arreglo de 5 elementos tipo int)

El tipo de A[i] int * (puntero a int)

6.2.10. Apuntador a apuntador.

Para especificar que una variable es un puntero a un puntero, la sintaxis

utilizada es la siguiente:

tipo_de_variable_apuntada **nomb_var_puntero_a_puntero ;

donde tipo_de_variable_apuntada especifica el tipo del apuntador la doble

dirección ** indica un puntero que apunta a un puntero y

Page 170: Libro de c++ (Teria/Practico)

nomb_var_puntero_a_puntero es el identificador de la variable puntero a puntero.

Por ejemplo:

int A, *P, **PP; A = 10; // dato P = &A; //puntero que apunta al dato PP = &P; // puntero que apunta al puntero que apunta al dato

Gráficamente en memoria esto sería como se muestra en la figura 6.5.

Figura 6. 5. Apuntador a apuntador.

Un ejemplo utilizando un puntero a puntero seria el código siguiente:

void main() { int i, j; int A[5][5]={{1,2,3,4,5},

{5,4,3,2,1},{0,1,2,3,4},{4,3,2,1,0},{0,1,0,1,0}}; int *p[5] ; // arreglo de punteros int **q ; //puntero a puntero entero for(i=0; i<5; i++)

p[i] =A[i]; q = p; for(i=0; i<5; i++) { for(j=0; j<5; j++) cout<<q[i][j];

cout<<endl; }

}

Como podemos observar con q[i][j] podemos recorrer elementos del arreglo

podemos notar, q[i] es misma dirección que A[i], si q siendo la dirección de A que es

Page 171: Libro de c++ (Teria/Practico)

la misma dirección de A[0], entonces q+1 es la posición de A[1] y q+i es la dirección

q+i. De esta observación concluimos que las representaciones siguientes son iguales:

q[i][j], *(q[i]+j), *(*(q+i)+j)

Según lo expuesto, las direcciones q+1 y *(q+1) tienen significados diferentes,

por ejemplo:

q+1+2 es la dirección del elemento q[3] de la matriz de punteros.

*(q+1)+2 es la dirección del elemento q[1][2].

*(*(q+1)+2) es el valor del elemento q[1][2].

6.3. Asignación de memoria dinámica.

Cuando se habla de asignación dinámica de memoria se hace referencia al

hecho de crear variables que reserven espacio en memoria en tiempo de ejecución

del programa, así como liberar el espacio reservado para dichas variables, cuando ya

no son necesarias, también durante el tiempo de ejecución.

Supongamos que debemos recibir una serie de datos de entrada, digamos del

tipo double, y debemos procesar según un determinado algoritmo a aquellos que

aparecen una ó más veces con el mismo valor.

Si no estamos seguros de cuantos datos van a ingresar a nuestro programa,

pondremos alguna limitación, suficientemente grande a los efectos de la precisión

requerida por el problema, digamos 10000 valores como máximo, debemos definir

entonces un arreglo de tipo double capaz de albergar a diez mil de ellos, por lo que el

mismo ocupará del orden de los 80Kb de memoria.

Si definimos este en main(), ese espacio de memoria permanecerá ocupado

hasta el fin del programa, aunque luego de aplicarle el algoritmo de cálculo ya no lo

necesitemos más, comprometiendo seriamente nuestra disponibilidad de memoria

para albergar a otras variables. Una solución posible sería definirlo en una función

llamada en main (), que se ocupara de llenar el arreglo con los datos, procesarlos y

Page 172: Libro de c++ (Teria/Practico)

finalmente devolviera algún tipo de resultado, borrando con su retorno a la masiva

variable de la memoria.

Los programas ejecutables creados dividen la memoria disponible en varios

segmentos, uno para el código (en lenguaje máquina), otro para albergar las variables

globales, otro para el stack (a través del cual se pasan argumentos y donde residen las

variables locales) y finalmente un último segmento llamado memoria de apilamiento

ó amontonamiento.(Figura 6.6).

Figura 6. 6. Esquema de asignación de memoria.

La memoria de apilamiento es la zona destinada a albergar a las variables

dinámicas, es decir aquellas que crecen (en el sentido de ocupación de memoria) y

decrecen a lo largo del programa, pudiéndose crear y desaparecer (desalojando la

memoria que ocupaban) en cualquier momento de la ejecución.

Supongamos que queremos ubicar un único dato, declaramos un puntero al tipo

de la variable, ejemplo:

double *p ;

Page 173: Libro de c++ (Teria/Practico)

notemos que ésta declaración no crea lugar para la variable, sino que asigna un

lugar en la memoria para que posteriormente se guarde ahí la dirección de aquella.

Para reservar una cantidad dada de byte, se efectúa una llamada a alguna de las

funciones dedicadas al manejo del mismo. La más tradicional es malloc() (su nombre

deriva de memory allocation), a esta función se le da como argumento la cantidad de

bytes que se quiere reservar, y nos devuelve un pointer apuntando a la primer

posición de la "pila" reservada. En caso que la función falle en su cometido (la

memoria está llena) devolverá un puntero inicializado con NULL.

p = malloc(8) ;

hemos pedido 8 bytes (los necesarios para albergar un double) y hemos

asignado a p el retorno de la función, es decir la dirección de la memoria reservada.

Como es algo engorroso recordar el tamaño de cada tipo variable, agravado por

el hecho de que, si reservamos memoria de esta forma, el programa no se ejecutará

correctamente, si es compilado con otro compilador que asigne una cantidad distinta

de bytes a dicha variable, es más usual utilizar sizeof, para indicar la cantidad de

bytes requerida:

p = malloc( sizeof(double) ) ;

Se comprueba si se reservo con éxito mediante:

if( p == NULL )

rutina_de_error() ;

Page 174: Libro de c++ (Teria/Practico)

si no lo fue, estas sentencias me derivan a la ejecución de una rutina de error

que tomará cuenta de este caso. Por supuesto podría combinar ambas operaciones en

una sola, como:

if( ( p = malloc( sizeof(double) ) ) == NULL )

{ cout<<"no hay mas lugar en memoria!") ;

exit(1) ;

}

se ha reemplazado aquí la rutina de error, por un mensaje y la terminación del

programa, por medio de exit() retornando un código de error .

6.3.1. La función malloc

Es la función genérica para asignar memoria dinámica a apuntadores. Su

prototipo es:

void * malloc (size_t nbytes);

donde nbytes es el número de bytes que queremos que sean asignados al

apuntador. La función retorna un apuntador de tipo void*, por lo que tenemos que

hacer type cast al valor al tipo del apuntador destino, por ejemplo:

char * r;

r = (char *) malloc (10);

Esto asigna a r un apuntador a un bloque de 10 bytes. Cuando queremos asignar

un bloque de data a un tipo diferente a char (diferente a 1 byte) debemos multiplicar

Page 175: Libro de c++ (Teria/Practico)

el número de elementos deseados por el tamaño de cada elemento. Tenemos a

disposición el operador sizeof, que retorna el tamaño del tipo de un dato en concreto.

int * b;

b = (int *) malloc (5 * sizeof(int));

Este código asigna a b un apuntador a un bloque de 5 enteros de tipo int, este

tamaño puede ser igual a 2, 4 o más bytes de acuerdo al sistema donde el programa es

compilado.

6.3.2. La función calloc.

calloc es muy similar a malloc en su funcionamiento, su principal diferencia es

en su prototipo:

void * calloc (size_t nelements, size_t size);

ya que admite 2 parámetros en vez de 1. Estos dos parámetros se multiplican

para obtener el tamaño total del bloque de memoria a ser asignado. Usualmente el

primer parámetros (nelements) es el número de elementos y el segundo (size) sirve

para especificar el tamaño de cada elemento. Por ejemplo, podríamos definir a b con

calloc así:

int *b;

b = (int *) calloc (5, sizeof(int));

Otra diferencia entre malloc y calloc es que calloc inicializa todos sus

elementos a 0.

Page 176: Libro de c++ (Teria/Practico)

6.3.3. La función realloc

Cambia el tamaño de un bloque de memoria ya asignado a un apuntador.

void * realloc (void * pointer, size_t size);

El parámetro pointer recibe un apuntador a un bloque de memoria ya asignado

o un apuntador nulo, y size especifica el nuevo tamaño que el bloque de memoria

deberá tener. La función asigna size bytes de memoria al apuntador. La función

podría necesitar cambiar la localidad del bloque de memoria para que el nuevo

tamaño pueda encajar, en ese caso el contenido actual del bloque es copiado al nuevo

para garantizar que la data existente no se pierda. La función retorna el nuevo

apuntador. Si no ha sido posible asignar el bloque de memoria con el nuevo tamaño

retorna un apuntador nulo, pero el pointer especificado como parámetro y su

contenido permanecen sin cambios.

6.3.4. La función free

Libera un bloque de memoria dinámica previamente asignado usando malloc,

calloc o realloc.

void free (void * pointer);

Esta función debe ser usada solamente para liberar memoria asignada con las

funciones malloc, calloc y realloc.

Page 177: Libro de c++ (Teria/Practico)

6.3.5. Operadores new

Este operador permite solicitar memoria dinámica. new es seguido por un tipo

de dato y opcionalmente el número de elementos requeridos entre corchetes [].

Retorna un apuntador al comienzo del nuevo bloque de memoria asignada. Su forma

es:

pointer = new type ó

pointer = new type [elementos]

La primera expresión es usada para asignar memoria para contener un solo

elemento de tipo type. La segunda se usa para asignar un bloque (un arreglo) de

elementos de tipo type. Por ejemplo:

int *b; b = new int [5];

En este caso, se ha asignado espacio para 5 elementos de tipo int en un heap y

ha retornado un apuntador a su comienzo que ha sido asignado a b. Por lo tanto,

ahora, b apunta a un bloque de memoria válido con espacio para 5 elementos int.

Podría preguntarse cuál es la diferencia entre declarar un arreglo normal y

asignar memoria a un apuntador como hemos hecho. La más importante es que el

tamaño de un arreglo debe ser un valor constante, el cual limita su tamaño a lo que

decidamos al momento de designar el programa antes de su ejecución, mientras que

la asignación dinámica de memoria permite asignar memoria durante la ejecución del

programa usando cualquier variable, constante o combinación de ambas como

tamaño.

Page 178: Libro de c++ (Teria/Practico)

La memoria dinámica es generalmente administrada por el sistema operativo, y

en interfaces multitarea puede ser compartida entre varias aplicaciones, por lo que

existe la posibilidad de que la memoria se acabe. Si esto ocurre y el sistema operativo

no puede asignar la memoria que solicitamos con el operador new, se retorna un

apuntador nulo. Por esta razón se recomienda siempre chequear si el apuntador

retornado es nulo, luego de una llamada a new.

int *b; b = new int [5]; if (bobby == NULL) { // error asignando memoria. };

6.3.6. Operador delete

Ya que la necesidad de memoria dinámica está limitada a momentos concretos

dentro de un programa, una vez que no se necesita más debería ser liberada para que

se convierta en disponible para futuras solicitudes de memoria dinámica. Para este

propósito existe el operador delete, cuya forma es:

delete pointer; ó

delete [] pointer;

La primera expresión debería ser utilizada para borrar memoria asignada para

un único elemento, y la segunda para memoria asignada para múltiples elementos

(arreglos). En la mayoría de los compiladores ambas expresiones son equivalentes y

pueden ser utilizadas sin distinción, aunque en realidad son dos operadores diferentes

y así deben ser considerados para sobrecarga de operadores.

Page 179: Libro de c++ (Teria/Practico)

6.3.7. Ejemplos de asignación de memoria dinámica

Ejemplo1: Reserva de memoria de varios datos simples.

#include<stdlib.h>

#include<iostream.h>

void main()

{

int *dato_simple;

dato_simple = (int *) malloc (3*sizeof(int));

}

Ejemplo2: Realice un programa que lea n enteros y los muestre.

#include<stdlib.h>

#include<iostream.h>

void main()

{ int n, i;

int *datos;

cout<<”Ingrese la cantidad de datos:”;

cin>>n;

datos = (int *) malloc (n*sizeof(int));

for(i=0; i<n; i++)

{ cout<<”Ingrese el numero#”<<i+1<<”:”;

cin>>datos[i];

}

Page 180: Libro de c++ (Teria/Practico)

cout<<”Los números ingresados:”;

for(i=0; i<n; i++)

cout<<datos[i]<<endl;

}

Page 181: Libro de c++ (Teria/Practico)

CAPITULO 7

FUNCIONES

7.1. Concepto.

Las funciones son un conjunto de instrucciones que realizan una tarea

específica. En general toman ciertos valores de entrada, llamados parámetros y

proporcionan un valor de salida o valor de retorno; aunque en C++, tanto unos como

el otro son opcionales, y pueden no estar presentes.

Desde un punto de vista práctico, podemos decir que una función es una parte

de un programa (subrutina) con un nombre, que puede ser invocada (llamada a

ejecución) desde otras partes tantas veces como se desee. Una función en un método

de organización, creando una visión clara del código de un programa, además que

permite su reutilización.

Si observamos detalladamente notaremos que el cuerpo principal de un

programa es una función, lo que cabe destacar que las funciones son la base del

lenguaje.

7.2. Prototipos de funciones

Un prototipo es una declaración de una función. Consiste en una presentación

de la función, exactamente con la misma estructura que la definición, pero sin cuerpo

y terminada con un ";". En C++ es necesario en muchos casos el uso de los

prototipos. Esto permite identificar o reconocer la existencia de la función en el

programa.

La estructura de un prototipo es:

Page 182: Libro de c++ (Teria/Practico)

[extern|static]<tipovalor_retorno> <identificador>(<lista_parámetros>);

En general, el prototipo de una función se compone de las siguientes secciones:

• Opcionalmente, una palabra que especifique el tipo de almacenamiento, puede

ser extern o static. Si no se especifica ninguna, por defecto será extern.

• El tipo del valor de retorno, que puede ser void, si no necesitamos valor de

retorno. En C, si no se establece, será int por defecto, aunque en general se

considera una mala técnica de programación omitir el tipo de valor de retorno

de una función. En C++ es obligatorio indicar el tipo del valor de retorno.

• El identificador de la función. Es costumbre, muy útil y muy recomendable,

poner nombres que indiquen, lo más claramente posible, qué es lo que hace la

función, y que permitan interpretar qué hace el programa con sólo leerlos.

Cuando se precisen varias palabras para conseguir este efecto se puede usar

alguna de las reglas más usuales. Una consiste en separar cada palabra con un

"_". Por ejemplo, si hacemos una función que busque el número de teléfono

de una persona en una base de datos, podríamos llamarla "busca_telefono" o

"BuscaTelefono".

• Una lista de declaraciones de parámetros entre paréntesis. Los parámetros de

una función son los valores de entrada (y en ocasiones también de salida).

Para la función se comportan exactamente igual que variables, y de hecho

cada parámetro se declara igual que una variable. Una lista de parámetros es

un conjunto de declaraciones de parámetros separados con comas. Puede

tratarse de una lista vacía. En C es preferible usar la forma "func(void)" para

listas de parámetros vacías. En C++ este procedimiento se considera obsoleto,

se usa simplemente "func()".

Por ejemplo:

Page 183: Libro de c++ (Teria/Practico)

int Mayor(int a, int b);

Un prototipo sirve para indicar al compilador los tipos de retorno y los de los

parámetros de una función, de modo que compruebe si son del tipo correcto cada vez

que se use esta función dentro del programa, o para hacer las conversiones de tipo

cuando sea necesario.

En el prototipo, los nombres de los parámetros son opcionales, y si se incluyen

suele ser como documentación y ayuda en la interpretación y comprensión del

programa. El ejemplo de prototipo anterior sería igualmente válido si se escribiera

como:

int Mayor(int, int);

Esto sólo indica que en algún lugar del programa se definirá una función

"Mayor" que admite dos parámetros de tipo int y que devolverá un valor de tipo int.

No es necesario escribir nombres para los parámetros, ya que el prototipo no los usa.

En otro lugar del programa habrá una definición completa de la función.

Normalmente, los prototipos de las funciones se declaran dentro del fichero del

programa, o bien se incluyen desde un fichero externo, llamado fichero de cabecera.

Las funciones son extern por defecto. Esto quiere decir que son accesibles

desde cualquier punto del programa, aunque se encuentren en otros ficheros fuente

del mismo programa.

En contraposición las funciones declaradas static sólo son accesibles dentro del

fichero fuente donde se definen.

7.3. Definición de funciones

Page 184: Libro de c++ (Teria/Practico)

Al igual que hemos visto con las variables, las funciones deben declararse, para

lo que usaremos los prototipos, pero también deben definirse.

Una definición contiene además las instrucciones con las que la función

realizará su trabajo, es decir, su cuerpo o código.

La sintaxis de una definición de función es:

[extern|static] <tipo_valor_retorno> <identificador>(<lista_parámetros>) { [sentencias] //cuerpo de la función return valor; //debe ser del mismo valor que tipo_valor_retorno }

Como vemos, la sintaxis es idéntica a la del prototipo, salvo que se elimina el

punto y coma final, y se añade el cuerpo de función que representa el código que será

ejecutado cuando se llame a la función. El cuerpo de la función se encierra entre

llaves "{}".

La definición de la función se hace más adelante o más abajo, según se mire, es

decir, se hace después que el prototipo y cuerpo principal main(). Lo correcto es

hacerlo después de la función main() si existe el prototipo, de lo contrario solo se

define por encima del cuerpo principal ya que la misma función sirve como prototipo

en su definición.

Una función muy especial es la función main(). Se trata de la función de

entrada, y debe existir siempre, ya será la que tome el control cuando se ejecute el

programa.

El valor de retorno es el valor de respuesta que da la función una vez culminada

mediante el uso de la palabra reservada return, este deberá ser del mismo tipo que la

función definida, cualquier otro valor de retorno será erróneo. Si la función está

definida con la palabra void, se indica que no deberá retornar valor de respuesta, es

decir que en el cuerpo de la función esta la respuesta de esta de forma implícita.

Page 185: Libro de c++ (Teria/Practico)

7.4. Invocación a funciones

Una invocación ó llamada a una función implica pasarle el control de la

ejecución del programa, así como los argumentos ó parámetros que requiere para

realizar su tarea.

Por ejemplo se tienen las líneas:

saludo(); //invocación a la función saludo() precio = calcula(costo); //invocación a la función calcula()

En la primera, se invoca a la función saludo() y no se le pasa ningún argumento.

En la segunda, se invoca a la función calcula(), pasándosele como argumento una

copia del valor que tiene la variable costo. El valor retornado por calcula() se asigna a

la variable precio.

El programa seria:

#include <iostream.h> #include <conio.h>

// Declaración de funciones void saludo(); float calcula(float);

// Función principal void main() { float costo, precio; clrscr();

cout << "COSTO : $ "; cin>> costo; saludo(); // invocación a la función saludo() precio = calcula(costo); // invocación a la función calcula() cout << "PRECIO : $ " << precio; getch(); }

// Definición de la función saludo()

Page 186: Libro de c++ (Teria/Practico)

void saludo() { cout << "!! BIENVENIDO A LA VENTA ESPECIAL !!"; }

// Definición de la función calcula() float calcula(float x) { return( x * 1.6); }

7.5. Parámetros y Argumentos

Son el medio a partir del cual podemos expandir el ámbito de variables locales

de funciones, hacia otras funciones y además quienes nos permiten establecer

comunicaciones entre funciones. Si nos vemos ante la necesidad de visualizar o

modificar el valor de una variable local en otra función que llamaremos, debemos

invocar a dicha función haciendo referencia de su nombre, seguido de los parámetros

o nombres de variables para las cuales, ampliaríamos su ámbito.

void funcion_llamada(int x) // función que recibe un argumento { … } void una_funcion(void) // variables de ámbito local { int a,b;

… funcion_llamada(a); // llamada de la función con un parámetro …

}

Desde luego que, sobre la base de la comunicación entre funciones y la teoría

del paradigma procedimental donde aplicamos la disgregación de procesos, nos

podemos encontrar con las siguientes variantes:

• Llamado de funciones sin pasar parámetros.

• Llamado de funciones pasando parámetros.

Page 187: Libro de c++ (Teria/Practico)

Está claro que dichas funciones pueden o no devolvernos valores hacia el

origen de su llamado.

7.5.1. Paso de parámetros

Cuando se invoca a una función, en ocasiones es necesario enviarle algunos

elementos indispensables para realizar su tarea. Estos elementos, enviados en la

invocación, se llaman parámetros actuales. Dadas las dos necesidades básicas en una

función, de poder obtener el contenido de una variable o el de modificar el contenido

de una variable, clasificaremos el paso de ellos en:

7.5.1.1. Paso de parámetros por valor o contenido

Cuando surge la necesidad de obtener el valor o contenido de una variable

original o local a una función, en otra función, se utiliza paso de parámetros por valor

cuando se envía una copia de los valores de los parámetros actuales. En este caso, la

función invocada no podrá modificar los valores de las variables utilizadas como

parámetros actuales, sino que trabajará con las copias que se le envían. Estas copias

son recibidas en los parámetros formales, los cuales se comportan como variables

locales pertenecientes a la función invocada.

Al igual que las variables locales, una vez que termina la ejecución de la

función invocada, las variables pertenecientes a los parámetros formales se destruyen;

y por lo tanto, se pierden los valores que envió el módulo invocador.

El siguiente programa se muestra el paso de parámetros por valor:

#include <iostream.h> void precio(double); void main(void) { double costo;

Page 188: Libro de c++ (Teria/Practico)

cout << "COSTO : "; cin>> costo; precio(costo); // Se invoco a precio() y se le envía una // copia del valor de costo cout << "\n" cout << costo; // El valor de costo se conserva despues // de la invocación de precio() } void precio(double recibido) { recibido=recibido * 1.5; // Se modifica el valor de la copia // guardada en recibido cout << "\n" cout << recibido; }

Supongamos que se desea intercambiar los valores enteros de a y b mediante

una función intercambio con paso por valor:

#include <iostream.h> void intercambiar(int, int); void main(void) { int a = 5, b = 7; cout << "a= "<< a<< " , b= "<< b<<endl; intercambiar (a, b); // se envía el contenido de a y b cout << "a= "<< a<< " , b= "<< b<<endl; } void intercambiar(int a, int b) //asigna los contenidos a las variables a y b { int aux; aux = a; a = b; b = aux; }

Aunque podemos pensar que en el programa anterior intercambia los valores de

a y b, en la función nunca modifica a los valores y que recibe una copia de sus

Page 189: Libro de c++ (Teria/Practico)

contenidos. Las variables a y b locales de la función main() son diferentes a las

variables locales de a y b en la función intercambiar().

7.5.1.2. Paso de parámetros por referencia

La referencia indica trabajar sobre la dirección de memoria que ocupa el

parámetro o variable original.

A diferencia del paso por valor, el paso por referencia permite que la función

invocada modifique el valor original de las variables usadas como argumentos.

En C++, el paso de parámetros por referencia se realiza creando un alias del

identificador de la variable que forma el parámetro actual, como se muestra en el

siguiente programa.

#include <iostream.h> #include <conio.h> void oferta(float &); void main(void) { float precio;

cout << "CUAL ES EL PRECIO :"<<endl ; cin>> precio; oferta(precio); cout << " DIJO USTED :" << precio << endl; cout << " ESO ES CON EL 20 % DE DESCUENTO"<<endl;

} void oferta(float &bajo) {

bajo *=0.8 ; cout << "PRECIO REBAJADO: " << bajo;

}

En el programa se puede decir que bajo es otro identificador asociado a la

variable precio, por lo que la función oferta() realmente modifica el valor contenido

en dicha variable. Según lo anterior, podemos decir que es preferible utilizar una

Page 190: Libro de c++ (Teria/Practico)

variable existente en lugar crear una nueva, y eliminar el paso de parámetros por

valor.

Supongamos que se desea intercambiar los valores enteros de a y b mediante

una función intercambio pero esta vez usamos paso por referencia:

#include <iostream.h> void intercambiar(int, int); void main(void) { int a = 5, b = 7;

cout << "a= "<< a<< " , b= "<< b<<endl; intercambiar (a, b); // se envía el contenido de a y b cout << "a= "<< a<< " , b= "<< b<<endl;

} void intercambiar(int &a, int &b)

//asigna un alias a las variables a y b de main() { int aux=a;

a = b; b = aux;

}

Salida:

a = 5 , b = 7

a = 7 , b = 5

En este caso mostrara por salida los cambios de las variables, ya que en la

función se crea un alias de las mismas variables paramétricas.

Si al argumento por referencia lo precedemos de la palabra const, dicha variable

no podrá ser alterada en el ámbito de la función receptora, por lo tanto nunca afectará

a la variable original.

Page 191: Libro de c++ (Teria/Practico)

7.5.1.3. Arreglos como parámetros de función

Al pasar un arreglo como parámetro de una función en su invocación, esta no

envía el contenido de los elementos del arreglo sino que, pasara la dirección de

comienzo del arreglo. El argumento de arreglo de la función invocada tomara la

misma dirección que el del arreglo enviado como parámetro.

Lo que modifiquemos en el arreglo dentro de la función también se verá

afectado en el arreglo paramétrico envido.

En el siguiente ejemplo se leen n nombres, convirtiendo a mayúscula y se

muestran:

#include <conio.h> #include <iostream.h> #define max 25 void Leer( char m[][], int n) { int i;

for(i=0; i<n; i++) { cout<<”Ingrese el nombre#”<<i+1<<”:”<<endl; cin>>m[i]; }

} void Mostrar( char m[][], int n) { int i; cout<<”Los nombres:”<< endl; for(i=0; i<n; i++)

cout<<m[i]<<endl; } void Mayuscula( char m[]) { int i; for(i=0; m[i]!=’|0’; i++)

if (m[i] >=’a’ && m[i]<=’z’) m[i]=m[i] - 32;

} void main() { char nom[max][max]; int n;

cout<<”Ingrese cantidad de nombres:”

Page 192: Libro de c++ (Teria/Practico)

cin>>n; cout<<”Ingrese los nombres:”<<endl; Leer(nom, n); for(i = 0; i<n ; i++) Mayuscula( nom[i]); Mostrar(a[i],n));

}

7.5.1.4. Puntero como parámetros de una función

Los apuntadores y arreglos nos hacen notar que la definición de los argumentos

tipodato s[]; y tipodato *s; son completamente equivalentes cuando se pasa un

nombre de un arreglo a una función. Ya que al pasar un arreglo como parámetro, lo

que en realidad se está pasando es la dirección del comienzo del mismo.

En el siguiente ejemplo se lee una frase y muestra la muestra en minúscula y

en mayúscula:

#include <conio.h> #include <iostream.h> #define max 100 void mayuscula( char *m) { for(; *m!=’|0’; m++) if (*m >=’a’ && *m<=’z’) *m = *m - 32; } void minuscula( char *m) { for(; *m!=’|0’; m++) if (*m >=’A’ && *m<=’Z’) *m = *m + 32; } void main()

Page 193: Libro de c++ (Teria/Practico)

{ char frase [max],*p; p=frase; cout<<”Ingrese la frase:”

gets(p);

mayuscula( p);

cout<<”La frase en mayuscula”<<frase<<endl;

minuscula( p);

cout<<”La frase en minuscula”<<frase<<endl;

}

7.6. Funciones recursivas

Se dice que una función es recursiva cuando puede invocarse a sí misma. En

cada invocación se crean las variables locales, por lo que es indispensable incluir una

condición de salida, ya que de no hacerlo se agotará la memoria disponible en la pila.

El cálculo del factorial de un número es un problema clásico en la aplicación de

las funciones recursivas. En el listado se presenta un programa en C++ que calcula el

factorial de un número dado.

#include <iostream.h> #include <conio.h> long int factorial(unsigned short int); void main() { unsigned short int num;

long int result; do { cout << "El FACTORIAL del número: " ;

cin>> num ; } while(num <0 || num> 19 ); result = factorial(num); cout << " es : " << result;

}

Page 194: Libro de c++ (Teria/Practico)

long int factorial(unsigned short int n) { if(n==0) return 1; else return n*(factorial(n-1)) ; }

En el código anterior, si el número dado por el usuario es 4, el proceso

realizado por el programa se podría representar de la manera siguiente.

Numero de

invocación Valor de n Resultado

1 4 4*(factorial(3))

2 3 3*(factorial(2))

3 2 2*(factorial(1))

4 1 1*(factorial(0))

5 0 1

Figura 7. 1. Tabla de resultado de una función recursiva.

Resultados de invocar a la función factorial() pasándole como parámetro el

número 4.

En cada invocación, se crea en la pila una variable cuyo identificador es n y su

valor cambiará como se muestra en la tabla (Figura 7.1). Como en las invocaciones 1,

2, 3 y 4 el valor de n es diferente de 0, la función vuelve a invocarse a sí misma,

quedando sin resolver el resultado.

Cuando el valor de n es igual a 0 (invocación 5), la función retorna el valor 1 la

la invocación 4, por lo que el resultado en ésta se calcula así:

1 * (factorial(0)) = 1 * 1 = 1

Page 195: Libro de c++ (Teria/Practico)

La invocación 4 retorna a la invocación 3 el valor 1 y el resultado en la

invocación 4 es:

2 * (factorial(1)) = 2 * 1 = 2

A su vez, la invocación 3 retorna a la invocación 2 el valor 2. El resultado en la

invocación 2 es:

3 * (factorial(2)) = 3 * 2 = 6

Posteriormente, la invocación 2 retorna el valor 6 a la invocación 1. El

resultado en la invocación 1 es:

4 * (factorial(3)) = 4 * 6 = 24

Finalmente, la invocación 1 retorna el valor 24 a la función invocadora main(),

la cual asigna a la variable result el valor recibido ( 24 ).

Page 196: Libro de c++ (Teria/Practico)

CAPITULO 8

ESTRUCTURAS, CLASES Y OBJETOS

8.1. Estructuras.

8.1.1. Concepto.

Todas las variables que hemos utilizado hasta ahora permiten almacenar un

dato y de un único tipo, excepto los arreglos que almacenas varios datos pero de un

mismo tipo.

En ocasiones es necesario contar con tipos de datos que estén compuestos por

conjuntos de elementos de diferentes tipos entre sí. Por ejemplo si se quiere

representar datos de personas esta puede estar asociada a mucha información de la

persona, es decir cantidades matrices de variables, las cuales para poder dar relación

entre ellas tendríamos que hacerlo por su posición pero aun así siguen siendo datos

desasociados. Lo que dificulta su visión y uso. (Figura 8.1)

Nombres Apellidos Dirección … etc.

Persona0 { “José” “Vallejo” “Caracas” … xxx

Persona1 { “María” “García” “Maturín” … xxx

… … … … … … …

PersonaN { Ashlie “Sánchez” “Cumana” … xxx

Figura 8. 1.Datos de personas sin el uso de estructuras.

Las estructuras se crean con la finalidad de agrupar una o más variables,

generalmente de diferentes tipos, bajó un mismo nombre para hacer más fácil su

Page 197: Libro de c++ (Teria/Practico)

manejo y representación. Las estructuras son tipos derivados definidos por el usuario

que representan colecciones de elementos.

Ahora sería más fácil de expresar por ejemplo el conjunto de datos de una

persona como una ficha de datos como por ejemplo nombre, apellido, dirección, etc.

Donde algunos de estos datos podrían a su vez ser representados por estructuras como

por ejemplo fechas que estarían compuestos por los datos días, mes, año. (Figura 8.2) PersonaN

Persona2

Persona1

Persona0

Nombre “Rosmil”

Apellido “Infante”

… …

Fecha 20 02 1985

Figura 8. 2. Datos de personas usando estructuras.

Una estructura es un nuevo tipo de dato compuesto creado por el usuario con el

fin de agrupar datos con una relación en común, estos grupos de datos serán los

conocidos como: datos miembros o campos o registros del nuevo tipo.

8.1.2. Definición de tipos de estructuras

La palabra struct se utiliza para definir un tipo compuesto o agrupación de

datos (simples y/o compuestos).

Para definir una estructura usaremos el siguiente formato:

struct nombre_nuevo_tipo { // Composición de Campos o registros tipo_dato_1 nombre_campo_1;

Page 198: Libro de c++ (Teria/Practico)

tipo_dato_n nombre_campo_n; };

Después de definir un tipo de estructura, podemos declarar variables de ese tipo

así:

nombre_nuevo_tipo nombre_variable;

Se podrán declarar variables derivadas o compuestas, arreglos, punteros,

referencias, con este nuevo tipo de dato definido.

Por ejemplo, si se requiere definir un nuevo tipo llamado persona cuyos

elementos sean:

• cedula número entero largo.

• nombre cadena de caracteres.

La estructura a definir seria:

struct persona { long int cedula ; char nombre[50] ; } ;

En este caso, persona es un nuevo tipo que puede utilizarse para declarar una

variable de la estructura como:

persona estudiante ;

8.1.3. Acceso a los miembros de una estructura.

Para acceder a los datos miembros o campos de una estructura utilizaremos el

operador de contenido (.), de la forma:

Page 199: Libro de c++ (Teria/Practico)

Nombre_variable_estructura.dato_miembro;

Una vez declarada una estructura, las variables permiten las siguientes

operaciones:

• Inicializar la variable estructura, ya sea al declararla o después de ello (mediante

una asignación), como se muestra en:

// inicialización al declarar estud persona estud={ 11232425, “Luisa Pera” }; persona estud1;

// inicialización por asignación después de la declaración estud1.cedula = 12185312 ; strcpy(estud1.nombre, “Alexander Blanco”) ;

• Obtener su dirección mediante el operador dirección &, como se muestra en:

persona estud, estud1; persona *p_estud = &estud; p_estud = &estud1;

• Acceder a sus miembros, como se muestra:

long int cedula; persona estud={ 12345678, “Armando Paredes” }; cedula = estud1.cedula;

• Asignar una estructura a otra utilizando el operador asignación:

persona estud_a={ 23456789, “Ashlie Dorta” }; persona estud_b={ 14000001, “Valentina Vallejo” }; persona aux;

Page 200: Libro de c++ (Teria/Practico)

aux = estud_a; estud_a = estud_b; estud_b = aux;

En el siguiente ejemplo, lee datos de 2 personas y las muestra ordenadas por el

dato miembro cedula.

#include <iostream.h> struct persona { long int cedula ; char nombre[50] ; } ; void main() { persona per1, per2, aux; //declara las variables estructuras

// Lee los datos de personas cout<<”Datos primera persona:”<<endl; cout<<” Nombre:”<<endl; gets(per1.nombre); cout<<” Cedula:”<<endl; cin>>per1.cedula; cout<<”Datos segunda persona:”<<endl; cout<<” Nombre:”<<endl; gets(per2.nombre); cout<<” Cedula:”<<endl; cin>>per2.cedula; cout<<endl;

// Ordena los datos de personas por cedula if(per1.cedula > per2.cedula) { aux = estud_a;

estud_a = estud_b; estud_b = aux;

} // Muestra los datos de personas ordenados por cedula

cout<<”Datos Ordenados:”<<endl; cout<<per1.cedula<<” ”<<per1.nombre<<endl; cout<<per2.cedula<<” ”<<per2.nombre<<endl;

}

Se muestra en salida al ejecular:

Page 201: Libro de c++ (Teria/Practico)

Datos primera persona:

Nombre:Maria Lopez

Cedula:12924345

Datos primera persona:

Nombre:Juana Ruiz

Cedula:10988888

Datos Ordenados:

10988888 Juana Ruiz

12924345 Maria Lopez

8.1.4. Arreglos de Estructuras

Los arreglos se expresan como colecciones de variables de un mismo tipo el

cual se diferencian cada una de estas variables a través de un índice. Las estructuras

son variables compuestas, al igual que las variables primitivas, pueden agruparse en

arreglos y ser tratados de la misma forma que colecciones primitivas.

Después de definir un tipo de estructura, podemos declarar arreglos de la forma:

nombre_nuevo_tipo nombre_coleccion[Cantidad_elementos];

Para matrices o arreglos bidimensionales:

nombre_nuevo_tipo nombre_matriz[cant_filas][cant_columnas];

Un ejemplo de ello se muestra en el código siguiente de búsqueda binaria de

una palabra en ingles para mostrar su expresión en español:

Page 202: Libro de c++ (Teria/Practico)

#include <iostream.h> #include <string.h> // Define la estructura dicc. struct dicc {

char *ingl ; char *espa ;

} ; // Declaramos un arreglo de estructura

dicc tabla[] = { "absolute","absoluto", "append","anexar", "begin","iniciar,principiar,comenzar", "close","cerrar",

"do","hacer", "file","archivo", "while","mientras"

}; #define NUMESTR (sizeof(tabla)/sizeof(dicc)) int busqbin(char * , dicc * , int) ; void main() { char pal[80]; int nret ; do{ cout << "\n\nPara finalizar, escriba FIN en : \n";

cout << "\n Palabra en inglés : "; cin>> pal; nret = busqbin(pal,tabla,NUMESTR);

if(nret == -1) { if(strcmp(strupr(pal),"FIN")==0) cout << "\n\n!! Finalizado !!\n\n"; else cout << "\n\nPalabra no registrada\n\n"; } else cout << "\n" << pal << "=" << tabla[nret].espa ;

}while(strcmp(strupr(pal)," FIN")!="0);" }

// Busca una palabra en ingles int busqbin(char *pal, dicc *t, int N) { int bajo=0, alto=N-1, medio, cond; while(bajo <=alto)

Page 203: Libro de c++ (Teria/Practico)

{ medio=(bajo+ alto) / 2 ; if((cond==strcmp(pal, t[medio].ingl)) < 0) alto=medio-1 ; else if(cond> 0) bajo = medio + 1 ; else return (medio) ; } return(-1) ;

}

8.1.5. Apuntadores a estructuras

Al declarar una estructura se le asocia un bloque de memoria. La dirección

inicial del bloque puede asignarse a un apuntador, por lo que la estructura puede

manejarse a través de éste.

Un apuntador a una estructura se declara de la misma forma en que se declara

cualquier apuntador.

Para evaluar el contenido de los datos miembros de la dirección de memoria

asignada al puntero, es necesario un nuevo operador (->), usado de la forma:

puntero_estructura->dato_miembro;

Por ejemplo, si usamos el tipo dicc definido anteriormente, podemos escribir la

línea:

dicc *apd ;

que declara a apd como un apuntador a objetos de tipo dicc.

En este momento, se puede declarar una variable dinámica utilizando el

apuntador apd, como se muestra a continuación:

Page 204: Libro de c++ (Teria/Practico)

apd = new dicc;

El acceso a los elementos de la estructura apuntada por apd se logra a través:

apd->ingl;

apd->espa;

En el listado siguiente se muestra el manejo de una lista del tipo de estructura

alumnos.

#include <iostream.h> #include <conio.h> void inserta(), elimina(), despliega();

// Define a alumno como un tipo de estructura struct alumno { char nombre[25];

int calif; struct alumno *sig; };

alumno *primero, *actual; void main() { char opcion; primero = actual = NULL ; do { clrscr();

cout << "LISTA :"<<endl; cout << "1.- Ingresar alumno"<<endl; cout << "2.- Mostrar lista alumnos"<<endl; cout << "0.- Salir"<<endl; cout << "Escriba el número de su opción : "; opcion=getche(); switch(opcion) { case '1' : inserta(); break; case '2' : despliega(); break; }

} while(opcion !='0'); }

Page 205: Libro de c++ (Teria/Practico)

void inserta() { alumno *nuevo; nuevo = new alumno; if(!nuevo)

{ cout << “No hay memoria”<<endl; getch(); }

else { cout << "Ingrese alumno: "<<endl; cout << "Nombre: "; gets(nuevo->nombre); cout << "Calificacion:"; cin>> nuevo->calif;

if(!primero) { nuevo->sig = primero; primero->sig = nuevo; actual = nuevo; }

else { nuevo->sig = actual->sig; actual->sig = nuevo ; }

} void despliega() { actual=primero;

clrscr(); cout<<”Listado de estudiantes:”<<endl; if(!actual) cout << “Lista vacia”<<endl;

while(actual) { cout << actual->nombre<< " " << actual->calif<<endl; actual = actual->sig; }

getch(); }

8.2. Uniones

Los tipos union comparten muchas de las características sintácticas y

funcionales de los tipos struct, sin embargo existen algunas diferencias; la principal

es que la union permite que solamente uno de sus miembros esté activo a la vez,

mientras que en la estructura todos los miembros están activos al mismo tiempo. Otra

Page 206: Libro de c++ (Teria/Practico)

diferencia es que el tamaño de la union es el tamaño del miembro más grande,

mientras que en la estructura su tamaño es la suma de los tamaños de sus miembros.

Las uniones son recomendables cuando se van a manejar variables que pueden

compartir espacio en la memoria, debido a que sus valores van a requerirse en

momentos diferentes.

Por ejemplo, sean tres variables: una de tipo int, otra de tipo float y otra de tipo

double.

Si no van a manejarse simultáneamente, puede definirse un nuevo tipo union

que las abarque de la forma:

union nums { int x ; // 2 bytes float y ; // 4 bytes double z ; // 8 bytes } ;

En este momento se cuenta con un nuevo tipo llamado nums, así que podemos

utilizarlo para efectuar la siguiente declaración:

nums varnums ; // Se declara la variable varnums // que ocupa 8 bytes de memoria.

Se pueden accesar los miembros de varnums a través del operador punto, por

ejemplo:

varnums.x = 10 ; // Se asigna el valor 10 al miembro // x de la union varnums.

Page 207: Libro de c++ (Teria/Practico)

8.3. Clases y objetos

8.3.1. Definición de una clase

Una clase es muy parecido a una estructura, ya que la misma es una estructura

de tipo especial que permite representar características reales de los objetos o cosas,

que no podemos realizar con las estructura, dando una perspectiva real en un

programa.

Una clase representa al conjunto de objetos que comparten una estructura y

comportamientos comunes.

Una clase es un tipo definido por el usuario que describe los atributos y

métodos de los objetos que se crearan a partir de la misma. Los atributos definen el

estado de un determinado objeto y los métodos son las operaciones que definen su

comportamiento.

La definición de una clase consta de dos partes: el nombre de la clase

precedido por la palabra reservada class, y el cuerpo de la clase encerrado entre

llaves y seguido de un punto y coma. Esto es:

class nombre_clase {

cuerpo_de_la_clase

};

El cuerpo de la clase en general consta de modificadores de acceso (public,

protected y private), atributos, mensajes y métodos. Un método implícitamente define

un mensaje (el nombre del método es el mensaje).

Un ejemplo de la definición de una clase, seria la clase punto que puede tomar

la siguiente forma:

Page 208: Libro de c++ (Teria/Practico)

class punto { prívate: //acceso privado de los datos miembros int x,y ; // Miembros Datos o Atributos public: //acceso publico de los metodos void fun() { return y; } // Funciones Miembros o Metodos …

} ;

8.3.1.1. Atributos.

Son componentes de un objeto que almacenan datos. También se les denomina

variables miembro. Estos datos pueden ser de tipo primitivo (int, double, char...) o, a

su vez, de otro tipo de objeto (lo que se denomina agregación o composición de

objetos). La idea es que un atributo representa una propiedad determinada de un

objeto.

Un atributo es lo mismo que una variable cualquiera, salvo que como los

atributos se declaran para pertenecer a una clase específica, se dice que todos los

atributos de dicha clase son miembros de la misma. La declaración de los atributos es

exactamente igual que declarar cualquier otra variable. Aunque con la diferencia de

que estos se les adiciona el tipo de acceso, lo cual pueden ser manejados como

variables comunes (public) o mediante el uso de métodos para el caso de las privadas

(prívate).

Ejemplo de la clase punto podemos representarlo como un conjunto de datos

miembros o atributos visibles y no visibles como lo son x, y, color.

class punto { public: //acceso público de los métodos

char color[25]; prívate: //acceso privado de los datos miembros int x,y ; // Miembros Datos o Atributos privados public: //acceso público de los métodos

Page 209: Libro de c++ (Teria/Practico)

void fun() { return y; } // Funciones Miembros o Métodos …

} ;

En el ejemplo color representa un dato miembro del objeto que es visible de

manera externa, es decir puede ser usada de la forma tradicional. A diferencia de los

datos miembro x y y los cuales no podrán ser visibles de forma externa lo cual

implicaría que no existe para las funciones y deben ser manejadas a través del uso de

funciones miembros o métodos de la clase.

8.3.1.2. Métodos.

Son un componente de un objeto que lleva a cabo una determinada acción o

tarea con los atributos.

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.

Las funciones miembro o métodos se incluyen en la definición de una clase, de

dos formas:

• Al definir el método dentro de la definición de la clase.

Por ejemplo:

class punto { int x,y ;

public: int dax() { return x ; } // Define el método dentro de la clase

};

Page 210: Libro de c++ (Teria/Practico)

• Declarar el metodo dentro de la definición de la clase y escribir la definición de la

función fuera de la definición de la clase.

Por ejemplo:

class punto { int x,y ; public: int dax() ; // Declaración del método dentro de la clase }; int punto::dax() // Definición del método fuera de la clase { return x ; }

En la línea de cabecera de la definición de la función miembro dax() se utiliza

el llamado operador de resolución de ámbito (::). Este operador indica que la función

dax() es una función miembro de la clase punto.

Varias clases diferentes pueden usar los mismos nombres de función. El

compilador sabe cuál función pertenece a cuál clase y esto es posible por el operador

de resolución de ámbito y el nombre de la clase.

8.3.1.3. Tipos de acceso.

Una de las características fundamentales de una clase es ocultar tanta

información como sea posible. Por consiguiente, es necesario imponer ciertas

restricciones en el modo en que se puede manipular una clase y de cómo se puede

utilizar los datos y el código dentro de una clase.

Las funciones miembros o dato miembro de una clase sólo pueden ser llamadas

relativas a un objeto específico. Para llamar a una función miembro o dato miembro

visible desde alguna parte del programa que se encuentre fuera de la clase, se debe

usar el nombre del objeto y el operador de direccionamiento (.) punto.

Page 211: Libro de c++ (Teria/Practico)

//Para acceder a un atributo visible de la clase Nombre_objeto.nombre_atributo;

//Para acceder a un método visible de la clase Nombre_objeto.nombre_metodo(parámetro_del_metodo);

Ejemplo:

#include <iostream.h> // Esto define la clase CRender

class CRender { public: char buffer[256]; void m_Renderizar(const char *cadena);

}; // implementar m_Renderizar() para la clase

void CRender::m_Renderizar(const char *cadena) { strcpy(buffer, cadena); //copia la cadena } void main () { CRender render1, render2; // crear 2 objetos clase render1.m_Renderizar("Inicializando el objeto render1"); render2.m_Renderizar("Inicializando el objeto render2"); cout << "buffer en render1: "; cout << render1.buffer << endl; // tenemos acceso a buffer es público cout << "buffer en render2: "; cout << render2.buffer << endl; }

Por visibilidad se entiende al acto de acceder a los miembros de una clase. En

este sentido, los miembros de una clase pueden ser: públicos, privados y protegidos.

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.

Page 212: Libro de c++ (Teria/Practico)

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 través 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 raíz de dichas clases.

Por defecto al crear miembros de una clase (atributos o métodos) si no se les

indica el tipo de accesibilidad por defecto, serán privados. Son necesarios métodos

públicos para poder acceder a las variables o métodos que no sean públicos.

class Par { // atributos por defecto privados double a, b; public: // métodos de visibilidad publica double exta() { return a; } double extb() { return b; }

};

8.3.2. Definición de Objetos

Un objeto representa alguna entidad de la vida real, es decir, alguno de los

objetos que pertenecen a una clasificación con los que podemos interactuar. A través

del estudio de ellos se adquiere el conocimiento necesario para, mediante la

abstracción y la generalización, agruparlos según sus características en conjuntos,

estos conjuntos determinan las clases de objetos a utilizar. Primero existen los

objetos, luego aparecen las clases en función de la solución que estemos buscando.

Ésta es la forma más común de adquirir conocimiento aunque no es la única. En

ocasiones el proceso puede ser a la inversa y comenzar el análisis en una base teórica

Page 213: Libro de c++ (Teria/Practico)

abstracta, sustentada por el conocimiento previo que da lugar primeramente a clases

de objetos que satisfagan las necesidades de la solución.

Los objetos tienen características fundamentales que nos permiten conocerlos

mediante la observación, identificación y el estudio posterior de su comportamiento;

estas características son:

• Identidad. Es la propiedad que permite a un objeto diferenciarse de otros.

• Comportamiento. El comportamiento de un objeto está directamente relacionado

con su funcionalidad y determina las operaciones que este puede realizar o a las

que puede responder ante mensajes enviados por otros objetos.

• Estado. El estado de un objeto se refiere al conjunto de los valores de sus

atributos en un instante de tiempo dado.

Se define a un objeto como la instancia de una clase.

Una instancia es un elemento tangible (ocupa memoria durante la ejecución del

programa) generado a partir de una definición de clase. Todos los objetos empleados

en un programa han de pertenecer a una clase determinada. De la forma:

class Tipo_clase { //atributos //métodos }; Tipo_clase nombre_objeto_Tipo_clase;

Ejemplo:

class punto { int x,y ; public: int extx(){ return x ; } int exty(){ return y ; } void asigx() { x = a; }

Page 214: Libro de c++ (Teria/Practico)

void asigy() { y = b; } void mostrar(){ cout<<”( ”<<x<<” , ”<<y<<” )” ; } }; void main() { punto a; //Objeto a del tipo punto

a.asigx(10); a.asigy(15); a.mostrar();

}

Salida:

( 10 , 15 )

Page 215: Libro de c++ (Teria/Practico)

CAPITULO 9

PASO DE MENSAJES: MÉTODOS, PASO DE PARÁMETROS

9.1. Control de acceso a los miembros de una clase.

El control de acceso se suele llamar también ocultación de la implementación.

Incluir funciones dentro de las estructuras (a menudo llamado encapsulación) produce

tipos de dato con características y comportamiento, pero el control de acceso pone

fronteras en esos tipos.

Una clase puede contener partes públicas y partes privadas. Por defecto, todos

los miembros definidos en la clase son privados. Para hacer las partes de una clase

públicas (esto es, accesible desde cualquier parte de su programa) deben declararse

después de la palabra reservada public. Todas las variables o funciones definidas

después de public son accesibles a las restantes funciones del programa.

Especialmente, el resto de su programa accede a un objeto a través de sus funciones y

datos públicos.

Dado que una característica clave de la POO es la ocultación de datos, debe

tener presente que aunque puede tener variables públicas, desde un punto de vista

conceptual debe tratar de limitar o eliminar su uso. En su lugar, debe hacer todos los

datos privados y controlar el acceso a ellos, a través de funciones públicas. El

mecanismo para hacer privados datos o funciones es anteponerle la palabra reservada

private.

Por defecto, una clase es privada, aunque es conveniente especificar la

visibilidad expresamente, por legibilidad y por compatibilidad con versiones

posteriores que pueden recomendar su uso obligatorio. (Figura 9.1)

Existen tres clases de usuarios de una clase:

Page 216: Libro de c++ (Teria/Practico)

• La propia clase

• Usuarios genéricos

• Clases derivadas

Cada usuario tiene diferentes privilegios o niveles de acceso. Cada nivel de

privilegio de acceso se asocia con una palabra reservada:

• private. Por defecto todo lo declarado dentro de una clase es privado (private),

y sólo se puede acceder a ella con las funciones miembros declaradas en el

interior de la clase,

• public. Los miembros que se declaran en la región pública (public) se puede

acceder a través de cualquier objeto de la clase de igual modo que se accede a

los miembros de una estructura.

• Protected. Los miembros que se declaran en la región protegida (protected)

sólo se pueden acceder por funciones miembros declaradas dentro de la clase,

por funciones miembro de clases derivadas de esta clase.

Figura 9. 1. Visibilidad de una clase.

Ejemplo de visibilidad de miembros de una clase:

class punto { int x, y ;

Page 217: Libro de c++ (Teria/Practico)

public: int color;

void asigx() { x = a; } void asigy() { y = b; } void mostrar2(){ cout<<”( ”<<x<<” , ”<<y<<” )” ; } private: void mostrar(){ mostrar2(); } }; void main() { punto a; //Objeto a del tipo punto

a.color = 1; //asignación del atributo publico a.x = 20; //ERROR!!! atributo x de a no es visible a.asigx(10); //asignación del atributo x de a

// usando un método publico a.mostrar2(); //ERROR!!! método privado de no visible a.mostrar(); //llamada al método privado a través de uno publico

}

Es necesaria la existencia de un método público para acceder a atributos y

métodos privados.

9.2. Métodos de una clase.

Un método como ya ha visto es una función miembro la cual cumple un

propósito especifico al igual como una función, solo que esta es parte de la clase con

el propósito adicional de poder interactuar con los atributos de la clase.

Para declarar un método su estructura general consta de dos partes, la

declaración y el cuerpo del método.

Declaracion_ del_ método { Cuerpo_del_metodo

};

La Declaracion_del_metodo proporciona información sobre su nombre, la

accesibilidad del método, el número de parámetros que recibe, etc.

Page 218: Libro de c++ (Teria/Practico)

El Cuerpo_del_metodo contiene el conjunto de sentencias que manipula los

datos de cada objeto.

9.2.1. Pasos de mensajes.

Los métodos de una clase constituyen la lógica de la clase, es decir, contienen

el código que manipula el estado del objeto. Constituyen el mecanismo utilizado para

implementar los mensajes entre objetos. Quiere decir, cuando un objeto se comunica

con otro por un mensaje lo hace por medio de la invocación al método

correspondiente del objeto.

Un paso de mensaje no es más que el mecanismo de invocara un método por

medio de las referencias de la siguiente forma:

referencia.metodo (parametros);

Existen diversos tipos de paso de mensaje según sea la definición de sus

prototipos y que parámetros recibe.

9.2.1.1. Paso de Parámetros.

Son los valores paramétricos que se envía a un método en el momento de su

invocación. Estos permiten diferenciar prototipos o funciones que cumplen un mismo

propósito pero con la diferencia de los datos enviados en el momento de su

invocación son de tipos diferentes y/o con uno o más parámetros.

//sin parámetros o defecto referencia.metodo ();

//por parámetros referencia.metodo (parámetro_tipo1);

Page 219: Libro de c++ (Teria/Practico)

referencia.metodo (parámetro_tipo2); referencia.metodo (parametro1, parametro2,..,parametron);

Al declara métodos de una clase con un mismo nombre, se diferenciaran los

métodos por los parámetros que recibe.

Según sean sus parámetros los métodos pueden ser métodos por defecto

aquellos que no reciben datos y métodos paramétricos los que reciben datos

adicionales para su funcionalidad.

9.2.1.2. El puntero this.

En uno de los puntos anteriores comentábamos que un método perteneciente a

una clase tenía acceso a los miembros de su propia clase sin necesidad de pasar como

parámetro el objeto con el que se estaba trabajando. Esto no es tan sencillo, puesto

que es lógico pensar que los atributos (datos) contenidos en la clase son diferentes

para cada objeto de la clase, es decir, se reserva memoria para los miembros de datos,

pero no es lógico que cada objeto ocupe memoria con una copia de los métodos, ya

que replicaríamos mucho código.

En realidad, los objetos de una clase tienen un atributo específico asociado, su

dirección. La dirección del objeto nos permitirá saber que variables debemos

modificar cuando accedemos a un miembro de datos. Esta dirección se pasa como

parámetro (implícito) a todas las funciones miembro de la clase y se llama this.

Si en alguna función miembro queremos utilizar nuestra propia dirección

podemos utilizar el puntero como si lo hubiéramos recibido como parámetro. Por

ejemplo, para retornar el valor de un atributo escribimos:

float empleado::cuanto_cobra () { return sueldo;

}

Page 220: Libro de c++ (Teria/Practico)

Pero también podríamos haber hecho lo siguiente:

float empleado::cuanto_cobra () { return this->sueldo; }

Utilizar el puntero dentro de una clase suele ser redundante, aunque a veces es

útil cuando trabajamos con punteros directamente.

9.2.2. Métodos sobrecargados.

Cada método tiene una forma, que son su nombre, el tipo y número de sus

parámetros. Existe una característica para tener dos métodos con el mismo nombre.

Esta característica se denomina sobrecarga de métodos.

El concepto de sobrecarga de métodos se puede aplicar siempre que los

parámetros sean diferentes, bien por su tipo, bien porque el número de parámetros de

un método u otro es diferente. De la forma:

class Nom_clase{ void nom_metodo() {… } void nom_metodo( tipoa parametrox) { … } void nom_metodo(tipob parametroy) { … } void nom_metodo(tipo parámetro1, tipo parametro2) { … }

…. };

Ejempló:

class Articulo { private: float precio;

public: void setPrecio() { precio = 3.50;

Page 221: Libro de c++ (Teria/Practico)

} void setPrecio(float newPrecio) {

precio = nuevoPrecio; }

};

Entonces cuando hacemos un llamado a este método, el compilador hace

referencia al tipo de parámetro. La sobrecarga seria redefinir cualquiera de estos

métodos utilizando los mismos parámetros pero para un proceso distinto.

9.2.3. Constructores y destructores.

Los constructores y los destructores son un tipo de sobrecarga de funciones

miembros especiales.

Podemos clasificar los objetos en cuatro tipos diferentes según la forma en que

se crean:

• Objetos automáticos: son los que se crean al encontrar la declaración del

objeto y se destruyen al salir del ámbito en que se declaran.

• Objetos estáticos: se crean al empezar la ejecución del programa y se

destruyen al terminar la ejecución.

• Objetos dinámicos: son los que se crean empleando el operador new y se

destruyen con el operador delete.

• Objetos miembro: se crean como miembros de otra clase o como un elemento

de un arreglo.

Los objetos que se crean con el uso explícito del constructor son objetos

automáticos.

9.2.3.1. Constructores

Page 222: Libro de c++ (Teria/Practico)

Un constructor especifica la manera en que será creado e inicializado un nuevo

objeto de cierta clase. Los constructores en C++ pueden ser definidos por el usuario

ó generados por el lenguaje. El compilador de C++ invoca automáticamente al

constructor apropiado cada vez que se defina un nuevo objeto.

Esto puede ocurrir en una declaración de datos, cuando se copia un objeto o a

través de la asignación dinámica de memoria a un nuevo objeto por medio del

operador new.

Los constructores se pueden considerar como funciones de inicialización, y

como tales pueden tomar cualquier tipo de parámetros, incluso por defecto. Los

constructores se pueden sobrecargar, por lo que podemos tener muchos constructores

para una misma clase (como ya sabemos, cada constructor debe tomar parámetros

distintos).

Existe un constructor especial que podemos definir o no definir que tiene una

función muy específica: copiar atributos entre objetos de una misma clase. Si no lo

definimos se usa uno por defecto que copia todos los atributos de los objetos, pero si

lo definimos se usa el nuestro.

Este constructor se usa cuando inicializamos un objeto por asignación de otro

objeto.

Podemos definir un constructor por defecto de la forma:

class NomClase { … NombClase() // Constructor por defecto

{ // inicializaciones de atributos }

};

Podemos definir un constructor por argumento paramétricos de la forma:

class NomClase { … NombClase(tipo parametrico)// Constructor por parámetros

{ // inicializaciones por parametros

Page 223: Libro de c++ (Teria/Practico)

} };

Para añadir un constructor a la clase punto escribimos:

class punto { int x,y ;

public: int dax() { return x ; } int day() { return y ; }

punto() // constructor por defecto { x = 0 ; y = 0 ; } punto(int nx, int ny) // constructor por parámetro { x = nx ; y = ny ; }

};

En el momento de instanciar o crear un objeto se hace llamado al constructor

inicializando al objeto. De la forma:

void main() { punto a; //Inicialización punto a constructor por defecto punto b(10,10); //Inicialización punto b con constructor por parámetro }

9.2.3.2. Destructores

Los destructores destruyen a los objetos creados, liberando la memoria

asignada. Pueden ser invocados explícitamente por medio del operador delete.

Podemos definir un destructor usando la negación implícita del constructor

mediante el operador (~) de la forma:

Page 224: Libro de c++ (Teria/Practico)

class NomClase { … ~NombClase() // destructor { }

};

Siguiendo con el ejemplo de la clase punto:

class punto { int x,y ;

public: int dax() { return x ; } int day() { return y ; } punto(int nx, int nx) ; ~punto() ; // Declaracion del Destructor

}; punto::punto(int nx, int nx) { x = nx ; y = ny ; } punto::~punto() // Definicion del Destructor { delete x ; delete y ; }

9.2.4. Métodos estáticos y funciones amigas

Dentro de las peculiaridades de las clases encontramos dos tipos de funciones

especiales: los métodos estáticos y las funciones amigas. Los comentamos separados

del bloque relativo a clases y miembros por su similitud y por la importancia de las

funciones amigas en la sobrecarga de operadores.

Su característica común es que no poseen parámetro implícito this.

Page 225: Libro de c++ (Teria/Practico)

9.2.4.1. Métodos estáticos

Al igual que los atributos estáticos mencionados en el punto anterior, las

funciones miembro estáticas son globales para los miembros de la clase y deben ser

definidas fuera del ámbito de la declaración de la clase.

Estos métodos son siempre públicos, se declaren donde se declaren. Al no tener

parámetro this no pueden acceder a los miembros no estáticos de la clase (al menos

directamente, ya que se le podría pasar un puntero al objeto para que modificara lo

que fuera).

9.2.4.2. Funciones amigas (friend)

Son funciones que tienen acceso a los miembros privados de una clase sin ser

miembros de la misma. Se emplean para evitar la ineficiencia que supone el tener que

acceder a los miembros privados de una clase a través de métodos.

Como son funciones independientes de la clase no tienen parámetro this, por lo

que el acceso a objetos de una clase se consigue pasándoles como parámetro una

referencia al objeto (una referencia como tipo implica pasar el objeto sin copiar,

aunque se trata como si fuera el objeto y no un puntero), un puntero o el mismo

objeto. Por la misma razón, no tienen limitación de acceso, ya que se definen fuera de

la clase.

Para hacer amiga de una clase a una función debemos declararla dentro de la

declaración de la clase precedida de la palabra friend, como se muestra en el

siguiente código:

class X { private: int i; ... friend int f(X&, int);

Page 226: Libro de c++ (Teria/Practico)

// función amiga que toma como parámetros una referencia a // un objeto del tipo X y un entero y retorna un entero

};

En la definición de la función (que se hace fuera de la clase como las funciones

normales) podremos usar y modificar los miembros privados de la clase amiga sin

ningún problema:

int f(X& objeto, int i) { int j = objeto.i; objeto.i = i; return j;

}

Es importante ver que aunque las funciones amigas no pertenecen a la clase se

declaran explícitamente en la misma, por lo que forman parte de la interface de la

clase.

Una función miembro de una clase puede ser amiga de otra:

class X { ... void f(); ...

};

class Y { ... friend void X::f();

};

Si queremos que todas las funciones de una clase sean amigas de una clase

podemos poner:

class X { friend class Y;

Page 227: Libro de c++ (Teria/Practico)

... };

En el ejemplo todas las funciones de la clase Y son amigas de la clase X, es

decir, todos los métodos de Y tienen acceso a los miembros privados de X.

9.3. Asignación de objetos

La forma de inicializar un objeto es mediante el uso del operador de asignación

(=). Por ejemplo:

class punto { int x,y ; public:

int dax() { return x ; } int day() { return y ; } punto() { x = 0 ; y = 0 ; } punto(int nx, int ny) { x = nx ; y = ny ; }

};

void main() { punto a, b(10,10); a = b; }

Cuando se realiza la operación asignación, ambos objetos existen. En C++ el

operador de asignación, por omisión de la clase seria:

punto& punto::operator=(const punto& pun) { x = pun.x;

y = pun.y; return *this;

}

9.4. Ejemplo de Programa usando clases

Page 228: Libro de c++ (Teria/Practico)

Programa que suma números complejos:

#include <iostream.h> class NumComp { private:

double real; double comp;

public: NumComp(); NumComp(double); NumComp(double, double); NumComp* suma(NumComp&); double getReal(); double getComp();

}; NumComp::NumComp() {

real = 0; comp = 0; }

NumComp::NumComp(double real) { this->real = real; comp = 0; }

NumComp::NumComp(double real, double comp) { this->real = real; this->comp = comp; }

NumComp* NumComp::suma(NumComp& otro) { return new NumComp(this->real + otro.real, this->comp + otro.comp); } double NumComp::getReal() {

return this->real; }

double NumComp::getComp() { return this->comp; }

void main() {

NumComp a(23, 1); NumComp b(-1, 5); NumComp* c = a.suma(b);

Page 229: Libro de c++ (Teria/Practico)

cout << "La suma es: " << c->getReal() << "+" << c>getComp() << "i" << endl; delete c;

}

Page 230: Libro de c++ (Teria/Practico)

CAPITULO 10

HERENCIA

10.1. Herencia.

La herencia es una propiedad esencial de la Programación Orientada a Objetos

que consiste en la creación de nuevas clases a partir de otras ya existentes. Este

término ha sido tomado prestado de la herencia biológica, donde un hijo ciertas

facetas físicas o del comportamiento de sus progenitores.

La herencia en C++ es un mecanismo de abstracción creado para poder facilitar

y mejorar el diseño de las clases de un programa. Con ella se pueden crear nuevas

clases a partir de clases ya hechas, siempre y cuando tengan un tipo de relación

especial.

Las clases que heredan de clases base se denominan derivadas, estas a su vez

pueden ser clases bases para otras clases derivadas. Se establece así una clasificación

jerárquica.

La herencia es una forma de reutilización del software, en la cual se crean

clases nuevas a partir de clases existentes, mediante la absorción de sus atributos y

comportamientos, y enriqueciendo éstos con las capacidades que las clases nuevas

requieren.

10.2. Jerarquía de clases.

Cada nueva clase obtenida mediante herencia se conoce como clase derivada, y

las clases a partir de las cuales se deriva, clases base. Además, cada clase derivada

puede usarse como clase base para obtener una nueva clase derivada. Y cada clase

Page 231: Libro de c++ (Teria/Practico)

derivada puede serlo de una o más clases base. En este último caso hablaremos de

derivación múltiple.

Esto nos permite crear una jerarquía de clases tan compleja como sea necesario.

Ese es el principio de la programación orientada a objetos. Esta propiedad nos

permite encapsular diferentes partes de cualquier objeto real o imaginario, y

vincularlo con objetos más elaborados del mismo tipo básico, que heredarán todas sus

características. Lo veremos mejor con un ejemplo.

Un ejemplo muy socorrido es de las personas. Supongamos que nuestra clase

base para clasificar a las personas en función de su profesión sea "Persona". Presta

especial atención a la palabra "clasificar", es el punto de partida para buscar la

solución de cualquier problema que se pretenda resolver usando POO. Lo primero

que debemos hacer es buscar categorías, propiedades comunes y distintas que nos

permitan clasificar los objetos, y crear lo que después serán las clases de nuestro

programa. Es muy importante dedicar el tiempo y atención necesarios a esta tarea, de

ello dependerá la flexibilidad, reutilización y eficacia de nuestro programa.

Ten en cuenta que las jerarquías de clases se usan especialmente en la

resolución de problemas complejos, es difícil que tengas que recurrir a ellas para

resolver problemas sencillos.

Siguiendo con el ejemplo, partiremos de la clase "Persona". (Figura 10.1)

Independientemente de la profesión, todas las personas tienen propiedades comunes,

nombre, fecha de nacimiento, género, estado civil, etc.

Page 232: Libro de c++ (Teria/Practico)

Figura 10. 1. Jerarquía de clases para Persona

La siguiente clasificación debe ser menos general, supongamos que dividimos a

todas las personas en dos grandes clases: empleados y estudiantes. Lo importante es

decidir qué propiedades que no hemos incluido en la clase "Persona" son exclusivas

de los empleados y de los estudiantes. Por ejemplo, los ingresos por nómina son

exclusivos de los empleados, la nota media del curso, es exclusiva de los estudiantes.

Una vez hecho eso crearemos dos clases derivadas de Persona: "Empleado" y

"Estudiante".

Haremos una nueva clasificación, ahora de los empleados. Podemos clasificar a

los empleados en ejecutivos y comerciales. De nuevo estableceremos propiedades

exclusivas de cada clase y crearemos dos nuevas clases derivadas de "Empleado":

"Ejecutivo" y "Comercial".

Ahora veremos las ventajas de disponer de una jerarquía completa de clases.

Cada vez que creemos un objeto de cualquier tipo derivado, por ejemplo de tipo

Comercial, estaremos creando en un sólo objeto un Comercial, un Empleado y una

Persona. Nuestro programa puede tratar a ese objeto como si fuera cualquiera de esos

tres tipos. Es decir, nuestro comercial tendrá, además de sus propiedades, como

comercial tambien, su nómina como empleado, y su nombre, edad y género como

persona.

Siempre podremos crear nuevas clases para resolver nuevas situaciones.

Consideremos el caso de que en nuestra clasificación queremos incluir una nueva

Page 233: Libro de c++ (Teria/Practico)

clase "Becario", que no es un empleado, ni tampoco un estudiante; la derivaríamos de

Persona. También podemos considerar que un becario es ambas cosas. Sería un

ejemplo de derivación múltiple, podríamos hacer que la clase derivada Becario, lo

fuera de Empleado y Estudiante.

Podemos aplicar procedimientos genéricos a una clase en concreto, por

ejemplo, podemos aplicar una subida general del salario a todos los empleados,

independientemente de su profesión, si hemos diseñado un procedimiento en la clase

Empleado para ello.

10.3. Clases bases y derivadas.

Las clases creadas pueden ser reutilizadas en nuevos programas donde sus

definiciones requieran las mismas funcionalidades. Una clase utilizada para derivar

nuevas clases se denomina clase base (padre, superclase o ascendiente).

Una clase creada de otra clase, donde permita utilizar las funciones necesarias

de la clase (clase base) se denomina clase derivada o subclase. La terminología

supone una clase base o clase padre, y una clase derivada o clase hija. Esta relación

supone un orden de jerarquía simple. A su vez, una clase derivada puede ser utilizada

como una clase base para derivar más clases. Por consiguiente se puede construir

jerarquías de clases, en las que cada clase sirve como padre o raíz de una nueva clase.

Las clases nuevas se denominan clases derivadas, en donde cada clase derivada se

convierte en candidata a clase base para alguna clase futura.

Cada clase derivada se debe referir a una clase base declarada anteriormente. La

declaración de una clase derivada tiene la siguiente sintaxis:

Class clase_derivada: <especificadores_de_acceso> clase_base {

... };

Page 234: Libro de c++ (Teria/Practico)

El operador (:) nos indica que se herede .Los especificadores de acceso pueden

ser: public, protected o private.

10.3.1. Clases de derivación

Los especificadores de acceso a las clases base definen los posibles tipos de

derivación: public, protected y private. El tipo de acceso a la clase base específica

cómo recibirá la clase derivada a los miembros de la clase base. Si no se especifica un

acceso a la clase base, C++ supone que su tipo de herencia es privado.

• Derivación pública (public). Todos los miembros public y protected de la

clase base son accesibles en la clase derivada, mientras que los miembros

private de la clase base son siempre inaccesibles en la clase derivada.

#include <iostream.h> class base { int i, j;

public: void set(int a, int b) { i = a; j = b; } void mostrar() { cout << i << " " << j << "\n"; }

}; class derivada : public base { int k;

public: derivada(int x) { k = x; }

void mostrar_k() { cout << k << "\n"; }

}; void main() { derivada obj(3); obj.set(1, 2); // accesar a miembro de base obj.mostrar(); // accesar a miembro de base obj.mostrar_k(); // usa miembro de la clase derivada };

Page 235: Libro de c++ (Teria/Practico)

• Derivación privada (private). Todos los miembros de la clase base se

comportan como miembros privados de la clase derivada. Esto significa que

los miembros public y protected de la clase base no son accesibles más que

por las funciones miembro de la clase derivada. Los miembros privados de la

clase siguen siendo inaccesibles desde la clase derivada.

#include <iostream.h> class base { int i, j;

public: void set(int a, int b) { i = a; j = b; } void mostrar() { cout << i << " " << j << "\n"; }

}; // Miembros públicos de 'base' son privados en 'derivada'

class derivada : privatec base { int k; public: derivada(int x) { k = x; }

void mostrar_k() { cout << k << "\n"; }

}; void main() { derivada obj(3); obj.set(1, 2); // Error!!!, no se puede acceder a set() obj.mostrar(); // Error!!!, no se puede acceder a set() obj.mostrar_k(); // usa miembro de la clase derivada };

• Derivación protegida (protected). Todos los miembros public y protected de

la clase base se comportan como miembros protected de la clase derivada.

Estos miembros no son, pues, accesibles al programa exterior, pero las clases

que se deriven a continuación podrán acceder normalmente a estos miembros

(datos o funciones).

Usando miembros protegidos:

Page 236: Libro de c++ (Teria/Practico)

#include <iostream.h> class base { protected: int i, j; // privados base, pero accesibles a derivada

public: void set(int a, int b) { i = a; j = b; } void mostrar() { cout << i << " " << j << "\n"; }

}; class derivada : public base { int k;

public: // derivada puede accesar en base a 'j' e 'i'

void set_k() { k = i * j; } void mostrar_k() { cout << k << "\n"; }

}; void main() {

derivada obj(3); obj.set(1, 2); // Ok, conocido por derivada obj.mostrar(); // Ok, conocido por derivada obj.set_k(); obj.mostrar_k();

};

Usando protected para clase base:

#include <iostream.h> class base { int i;

protected: int j;

public: int k; void seti(int a) { i = a; } int geti() { return i; }

}; // Heredar 'base' como protected.

class derivada : protected base { public:

void setj(int a) { j = a; }; // j es protected aqui. void setk(int a) // k es tambien protected. { k = a; };

Page 237: Libro de c++ (Teria/Practico)

int getj() { return j; } int getk() { return k; }

}; void main() {

derivada obj; /* La proxima linea es ilegal porque seti() es un miembro protegido de derivada, lo cual lo hace inaccesible fuera de derivada. */ // obj.seti(10); // cout << obj.geti(); // ilegal -- geti() es protected. // obj.k = 10; // tambien ilegal porque k es protected. // estas declaraciones son correctas obj.setk(10); cout << obj.getk() << " "; obj.setj(12); cout << obj.getj() << " ";

}

10.4. Tipos de herencia.

En C++ existen los tipos de herencia: simple y múltiple.

10.4.1. Herencia simple

La herencia simple es aquella en la que cada clase derivada hereda de una

única clase. Cada clase tiene un solo ascendiente, y puede tener muchos

descendientes.

En la herencia, las clases derivadas "heredan" los datos y las funciones

miembro de las clases base, pudiendo las clases derivadas redefinir estos

comportamientos y añadir comportamientos nuevos propios de las clases derivadas.

Para no romper el principio de encapsulamiento (ocultar datos cuyo conocimiento no

Page 238: Libro de c++ (Teria/Practico)

es necesario para el uso de las clases), se proporciona un nuevo modo de visibilidad

de los datos/funciones: "protected". Cualquier cosa que tenga visibilidad protected se

comportará como pública en la clase Base y en las que componen la jerarquía de

herencia, y como privada en las clases que no sean de la jerarquía de la herencia.

Antes de utilizar la herencia, nos tenemos que hacer una pregunta, y si tiene

sentido, podemos intentar usar esta jerarquía: Si la frase <claseB> ES-UN <claseA>

tiene sentido, entonces estamos ante un posible caso de herencia donde clase A será la

clase base y clase B la derivada.

Ejemplo: clases Barco, Acorazado, Carguero, etc. un Acorazado ES-UN Barco,

un Carguero ES-UN Barco, un Trasatlántico ES-UN Barco, etc.

En este ejemplo tendríamos las cosas generales de un Barco (en C++).

class Barco { protected:

char *nombre; float peso;

public: //Constructores y demás funciones básicas de barco

};

y ahora las características de las clases derivadas, podrían (a la vez que heredan

las de barco) añadir cosas propias del subtipo de barco que vamos a crear, por

ejemplo:

class Carguero: public Barco { private: float carga; //El resto de cosas };

class Acorazado: public Barco { private: int numeroArmas;

Page 239: Libro de c++ (Teria/Practico)

int Soldados; // El resto de cosas

};

Como vimos existen 3 clases de derivación o herencia que se diferencian en el

modo de manejar la visibilidad de los componentes de la clase resultante:

Herencia publica (class Derivada: public Base ): Con este tipo de herencia se

respetan los comportamientos originales de las visibilidades de la clase Base en la

clase Derivada.

Herencia privada (clase Derivada: private Base): Con este tipo de herencia

todo componente de la clase Base, será privado en la clase Derivada (las propiedades

heredadas serán privadas aunque estas sean públicas en la clase Base)

Herencia protegida (clase Derivada: protected Base): Con este tipo de

herencia, todo componente publico y protegido de la clase Base, será protegido en la

clase Derivada, y los componentes privados, siguen siendo privados.

10.4.2. Herencia múltiple.

La herencia múltiple es aquella en la cual una clase derivada tiene más de una

clase base.

La herencia múltiple es el mecanismo que permite al programador hacer clases

derivadas a partir, no de una sola clase base, sino de varias. Para entender esto mejor,

pongamos un ejemplo: Cuando ves a quien te atiende en una tienda, como persona

que es, podrás suponer que puede hablar, comer, andar, pero, por otro lado, como

empleado que es, también podrás suponer que tiene un jefe, que puede cobrarte

dinero por la compra, que puede devolverte el cambio, etc.

Si esto lo trasladamos a la programación sería herencia múltiple (clase

empleado_tienda):

Page 240: Libro de c++ (Teria/Practico)

class Persona { ... Hablar(); Caminar(); ...

};

class Empleado { Persona jefe; int sueldo; Cobrar(); ... };

class empleado_tienda: public Persona, Empleado { ...

AlmacenarStock(); ComprobarExistencias(); ...

};

Por tanto, es posible utilizar más de una clase para que otra herede sus

características.

class clase_derivada: <especificadores_de_acceso1> clase_base1, <especificadores_de_acceso2> clase_base2, … ,

<especificadores_de_acceson> clase_basen {

... };

Una declaración multiple seria la clase derivada D:

class B { ... }; class C1 : public B { ... }; class C2 : public B { ... }; class D : public C1, C2 { ... };

Page 241: Libro de c++ (Teria/Practico)

Ejemplo Programa banco usando herencia multiple.

#include<iostream.h> #include<cstdlib.h> #include<conio.h> class valor { protected: double cap,cap1,cap2,monto,capital; public: valor(); }; valor::valor() { capital=capital; } class ctacte { protected: string num_cuenta; public: ctacte(); }; ctacte::ctacte() { num_cuenta=num_cuenta; } class accion { protected: char operacion; public: accion(); }; accion::accion() { operacion=operacion; } class propiedad : public valor,ctacte,accion { protected: char rpta; double monto,t,deposito,retiro; public: propiedad(); void calcular(); void mostrar(); };

Page 242: Libro de c++ (Teria/Practico)

propiedad::propiedad() { valor::capital=capital; ctacte::num_cuenta=num_cuenta; accion::operacion=operacion; } void propiedad::calcular() { cout<<"nn"; cout<<"ttt INGRESAR NUMERO DE CUENTA: "<<endl; cin>>num_cuenta; system("cls"); cout<<"nn"; cout<<"ttt INGRESAR CAPITAL: "<<endl; cin>>capital; system("cls"); t=capital; deposito=0; retiro=0; do { cout<<"nn" <<"ttt QUE OPERACION DESEA REALIZARn" <<"ttt === ========= ===== ========nnn" <<"tþ (1) DEPOSITO "<<"nn" <<"tþ (2) RETIRO "<<"nn"; cin>>operacion; switch(operacion) { case'1': { cout<<"nn"; cout<<"tttINGRESAR DEPOSITO"<<endl; cin>>cap1; deposito=deposito+cap1; monto=capital+cap1; capital=monto; }break; case'2': { cout<<"nn"; cout<<"tttINGRESAR RETIRO"<<endl; cin>>cap2; if (cap2 >capital) cout<<"No puede retirar mas dinero"<< endl; else{ retiro=retiro+cap2;

Page 243: Libro de c++ (Teria/Practico)

monto=capital-cap2; capital=monto;} }break; } system("cls"); cout<<"DESEA CONTINUAR S/N : "<<endl; cin>>rpta; }while(rpta=='S'||rpta=='s'); system("cls"); } void propiedad::mostrar() { calcular(); cout<<"nn" <<"tttMOSTRANDO DATOS LA CUENTA BANCARIAn" <<"ttt========= ===== == ====== ========nnn" <<"tþ Su capital inicial es : $ "<<t<<"nn" <<"tþ El deposito total es : $ "<<deposito<<"nn" <<"tþ El retiro total es : $ "<<retiro<<"nn" <<"tþ Su actual capital es : $ "<<capital<<"nn"; } void main() { propiedad a; a.mostrar(); getch(); }