Cartilla PROGRAMACION - 1º Año Diseño de Software
Cartilla PROGRAMACION - 1º Año Diseño de Software
Cartilla PROGRAMACION - 1º Año Diseño de Software
Cartilla PROGRAMACION
Carrera: Diseño de Software
Prof. Sergio V. Orgás
28/04/2016
Cartilla teórica para el espacio curricular Programación de 1º Año de la carrera Diseño de Software
RESOLUCION DE PROBLEMAS
El proceso de resolución de problemas
Se entiende como problema a una situación que plantea dificultades para las que no se poseen soluciones
conocidas.
Para su resolución se requiere de la consecución de ciertos procedimientos que refieren a complejos procesos
intelectuales y operativos semejantes a los que se siguen en una investigación científica. Éstos procedimientos
básicamente son la descripción y análisis del problema, la síntesis de la solución y la evaluación de la solución.
Modelo polya
Para George Polya (1945), la resolución de un problema consiste a grandes rasgos, en cuatro fases o etapas
bien definidas:
2
Nos podemos ayudar con la formulación de preguntas tales como: “¿Me ha encontrado con un problema
semejante? ¿Conozco un problema relacionado con este? ¿Podría enunciar el problema de otra forma? ¿He
empleado todos los datos?”
1. Analogía o semejanza.
2. Simplificar, particularizar.
3. Organización, codificación:
4. Técnicas asociadas: Esquema, Notación, Lenguaje, Figura, Diagrama, Gráfico, Modelos manipulativos.
5. Ensayo y error.
6. Trabajar marcha atrás o considerar el problema resuelto.
7. EXPERIMENTACIÓN: Sacar pautas, regularidades y leyes.
8. MODIFICAR EL PROBLEMA. Descomposición en problemas más pequeños.
a. Proponer subproblemas, submetas.
b. Utilizar menor número de variables, datos, etc.
9. CONJETURAR.
a. Empezar por casos sencillos
b. Intentar llevar adelante las conjeturas.
10. HAZ RECUENTO.
a. Realiza un conteo parcial.
b. Practica los recuentos exhaustivos.
11. EXPLORACIÓN.
a. Saca partido a la simetría.
b. Analiza los casos límite.
12. TÉCNICAS GENERALES.
a. Supón que no....., REDUCCIÓN AL ABSURDO O CONTRADICCIÓN.
b. Método de INDUCCIÓN MATEMÁTICA
c. Principio del PALOMAR DE DIRICHLET
3
A continuación vamos a describir de forma detenida alguna de estas estrategias, resolviendo además un
problema que ejemplifique dicha estrategia.
Se debe tener en cuenta que muy pocos problemas se resuelven utilizando una única estrategia, en general se
necesitará la utilización de varias.
Es muy bueno, a fin de encontrar un buen asidero que nos proporcione confianza, buscar situaciones
semejantes a la propuesta. Al hacerlo, probablemente, surgirán procedimientos de ataque de dichas situaciones
semejantes, que nos proporcionarán estrategias válidas para la que nos ocupa.
Esta búsqueda, será más fácil cuanta más experiencia tengamos en la resolución de problemas.
Problema.- Calcular el área lateral del tronco de cono que aparece en la figura
Solución:
h=
Luego:
4
Consiste en pasar de la consideración de un conjunto de objetos dado a considerar un conjunto más pequeño
(o incluso un solo objeto) contenido en el conjunto dado.
Particularizar, significa simplificar el problema haciéndolo más concreto y específico, hasta que sea posible
hacer algún progreso.
A veces te encuentras con un problema que resulta difícil por su tamaño, por tener demasiados elementos que
lo hacen enrevesado y oscuro. En este caso se puede empezar construyendo un problema semejante más
sencillo, tratar de resolverlo y luego proceder a complicarlo hasta llegar al propuesto inicialmente.
Otras veces el problema visto en su conjunto resulta inabordable, entonces para empezar se puede abordar
una parte de él que parezca más simple.
Es una de las mejores estrategias para los principiantes, pues sirve para adquirir confianza y en otros casos
proporciona ayuda en los atascos y bloqueos y nos permite manipulando los datos entrar en materia.
La particularización puede hacerse al azar para entender el significado del problema o de forma sistemática
para preparar el terreno hacia la generalización
Acude a ésta estrategia cuando no poseas ninguna idea que te haga prosperar, ya que en múltiples ocasiones
te permitirá lograr un avance.
Veamos un ejemplo.-
16 jugadores de tenis participan en un sorteo para emparejarse entre sí en la primera ronda. ¿De cuántas
maneras se pueden hacer los emparejamientos?
Solución:
Como el número de jugadores es elevado, comenzamos con dos jugadores; claramente hay una sola forma. Si
el número de jugadores es 3, tenemos 3 emparejamientos. Si los jugadores son 4, tenemos los siguientes 6
grupos: (1,2); (1,3); (1,4); (2,3); (2,4) y (3,4). Si los jugadores son 6, aparecen 15 grupos (compruébalo)
¿Serías capaz de encontrar una ley y deducir cuántos emparejamientos hay con 16 jugadores? Otra forma de
resolver el problema es visualizar las diversas situaciones en diagramas y sacar conclusiones.
1 2 1 2 3 4
1 NO SÍ 1 NO SÍ SÍ SÍ
2 NO NO 2 NO NO SÍ SÍ
2 jugadores; un 3 NO NO NO SÍ
emparejamiento
4 NO NO NO NO
4 jugadores; 6 emparejamientos
5
3.- ORGANIZACIÓN, CODIFICACIÓN
La organización, en general, consiste en adoptar un enfoque sistemático del problema. Suele ser de gran
ayuda enfocar el problema en términos de tres componentes fundamentales: antecedentes (origen y datos), el
objetivo y las operaciones que pueden realizarse en el ámbito del problema.
Las técnicas asociadas a la organización, pasan por realizar: símbolos apropiados, croquis, gráficos,
figuras, diagramas y esquemas. Estos símbolos o dibujos no se reservan al uso exclusivo de la Geometría;
una figura o gráfico puede ayudar considerablemente en todo tipo de problemas, que nada tienen de
geométrico, ya que las figuras trazadas sobre el papel son fáciles de hacer, fáciles de conocer y fáciles de
recordar.
Las figuras que te fabriques del problema deben incorporar, de alguna forma sencilla, los datos relevantes y
suprimir los superfluos que pueden conducir a confusión. De ésta forma pueden quedar resaltadas visualmente
las relaciones entre los aspectos más importantes del problema y de ahí muy a menudo se desprenden luces
que clarifican sustancialmente la situación.
Una buena organización suele ir asociada con la elección de una notación o código que organice la
búsqueda de posibles caminos hacia la solución.
Las diferentes notaciones y códigos nos conducen a utilizar un determinado lenguaje.- Los lenguajes que
resultan útiles en la resolución de problemas son: El lenguaje de la lógica, el de las Matemáticas
(geométrico, algebraico, analítico, probabilístico etc.), el analógico (modelos, manipulaciones etc. ) y el
imaginativo o pictórico (figuras, esquemas, diagramas etc.).
Una buena organización es un buen punto de arranque y a veces allí se encuentra la clave del éxito.
Veámoslo en el siguiente ejemplo:
Hay varias formas de sumar 10, mediante números impares y con cuatro sumandos; tenemos: 10
=1+1+1+7; 10 = 1+1+3+5; 10 = 1+3+3+3; tenemos tres formas (los cambios de orden en los números no
cuentan como nuevas soluciones)
Para obtener 20 con 8 sumandos impares ¿Cuántas formas hay?
Desde luego hay que organizarse un poco y ser sistemático: 20= 1+1+1+1+1+1+1+13;
20=1+1+1+1+1+1+7+7; 20 = 1+1+1+1+1+1+3+11; así llegamos hasta 11 combinaciones posibles.
Codificación. Ejemplo.- Tenemos 3 cajas iguales y 5 guantes de la mano izquierda, todos ellos iguales
¿De cuántas maneras se pueden distribuir en las tres cajas?
Después de jugar un poco con el problema se puede llegar a definir un código que nos organice la
búsqueda. Así si los guantes los representamos por A y las cajas por B, la secuencia BAA BA BAA nos indica
que en la 1ª caja hay dos guantes, en la 2ª un guante y en la 3ª dos guantes. Quizás este código nos resulte
más fácil de manejar y así resolver el problema.
Esquema. Ejemplo.- En tu bolsillo tienes 5 monedas: 1 Euro, 2 Euros, 5 Euros, 10 Euros y 20 Euros.
¿Cuántas cantidades distintas puedes formar?
Si empezamos una búsqueda poco organizada, seguramente nos liaremos, así 1+2=3; 1+10=11; ........
10+20=30 etc.
¿Cuántas combinaciones hay? Un esquema como el siguiente nos lleva a la solución.
6
4.- Problema.- Aquí aparece el plano de un solar. Un
gato quiere llegar a la posición de salida.
4.-ENSAYO Y ERROR
Consiste en realizar los siguientes pasos:
1.-Elegir un valor (resultado, operación o propiedad) posible.
2.-Llevar a cabo con éste valor las condiciones indicadas por el problema.
3.-Probar si hemos alcanzado el objetivo buscado.
Veamos un ejemplo.-Calcular un número tal que al elevarlo al cuadrado y sumarle el número buscado,
obtenemos 132
Solución:
1.-Elegimos un valor: el 10
2
2.-LLevamos a cabo con éste valor las condiciones del problema 10 +10 =110
3.-Probar si hemos logrado el objetivo: 110 es menor de 132
2
Volvemos a empezar con otro número :14; 14 +14 =210 ; 210 es mayor de 132 luego será 11, 12 ó 13.
Esta estrategia puede ser puesta en práctica de formas diferentes, estas son:
Ejemplo. - Judit y Teodoro fueron de visita a la granja de su abuelo. Durante su estancia vieron un corral con
cerdos y gallinas. Teodoro dijo haber contado 18 animales en total. Judit afirma haber contado un total de 50
patas ¿Cuántos cerdos había? (sin utilizar ecuaciones).
Solución:
1.- Ensayo y error fortuito. Damos valores al azar.
Cerdos Gallinas Patas
14 4 64
12 6 60
10 8
7
Etc.
Aquí tienes dos discos circulares. En la cara superior de cada uno de ellos hay escrito un número. En la
otra cara tiene escrito otro número. Si lanzamos los dos discos al aire y sumamos los dos números, podemos
obtener estos resultados: 11,12,16 y 17. Investiga qué números están escritos en la cara oculta de cada disco
Prueba ahora con estos tres discos sabiendo que los resultados que se obtienen son :
15,16,17,19,20,21,22,23.
¿Y si los resultados obtenidos fuesen 12,13,15,16,17,18,20,21, qué números estarían escritos en la cara
oculta de cada disco?
2.- Los huevos de gallina y pata.- El huevero tiene ante sí seis cestas con huevos. Cada una tiene huevos
de una clase, de gallina o de pata. Cada cesta tiene el número de huevos que se indica:
6 15 29
12 14 23
El huevero dice señalando una cesta que no acierto a ver cual es exactamente: “si vendo esta cesta,
me quedará el doble de huevos de gallina que de pata”.
3.- Números.-Obtener todos los números del 1 al 10, utilizando solamente 4 cuatros y los signos de las
operaciones.
ABSTRACCION
8
La abstracción es una actividad cerebral que permite aislar, a nivel conceptual, una cierta cualidad de algo
con la intención de entregarse a una reflexión sobre la misma sin considerar el resto de las propiedades del
objeto en cuestión.
La abstracción es el filtro utilizado para quedarse con lo que se considera esencial, eliminando toda la
complejidad innecesaria.
Un ejemplo de la utilidad de la abstracción es la proporcionada por un mapa. La aportación de los mapas es
que resaltan únicamente la esencia de la información de interés y se eliminan detalles innecesarios como los
árboles, los edificios, los automóviles, el ganado, entre muchos otros elementos del entorno.
Se hace evidente que el concepto de abstracción es una de las principales ideas del pensamiento
computacional en el proceso de solución de problemas. Entre los objetivos operativos del pensamiento
computacional que se relacionan estrechamente con la abstracción son los siguientes:
Representar datos mediante abstracciones, como modelos y simulaciones.
Formular problemas de manera que permitan usar computadores y otras herramientas para
solucionarlos
Modelizacion
La construcción de un modelo de representación de una situación problemática es uno de los procesos más
fundamentales en resolución de problemas. Muchos problemas pueden ser resueltos representando
directamente los rasgos críticos de la situación problemática con una ecuación, un programa de ordenador, o
una representación física o pictórica.
La abstracción es una habilidad esencial para la construcción de modelos y la descomposición de
problemas. El humano ha podido plantear leyes o modelos que representan la esencia de los fenómenos y que
tienen como finalidad simplificar el fenómeno real para poder analizarlo, comprenderlo, predecirlo o controlarlo.
Por ejemplo, las leyes de Newton que modelan el movimiento de los objetos permitieron comprender y
explicar el comportamiento del sistema solar. El planeta Urano fue descubierto en la década de 1840, pero en
ese entonces no se podían explicar las desviaciones de su ´orbita considerando las perturbaciones
ocasionadas por todos los demás planetas conocidos. Por lo tanto, o la ley de la gravitación universal fallaba a
esa gran distancia del sol o había un octavo planeta desconocido que perturbaba la órbita de Urano. Los
astrónomos Adams y Le Verrier supusieron que la ley universal de Newton es válida y calcularon de manera
independiente donde debería estar el octavo planeta. Sugiriendo que se debería buscar un nuevo planeta en
determinada zona del cielo. ¡La misma noche que se recibieron los cálculos en el observatorio de Berlín fue
descubierto el planeta Neptuno! Newton calculó la rapidez para tener en órbita circular un objeto, y como en
aquel tiempo (siglo XVII) era claramente imposible alcanzar esa velocidad inicial, no previó que los seres
humanos lanzaran satélites. No obstante, el modelo de movimiento que Newton desarrolló con base en sus
abstracciones del movimiento permitió al hombre llegar a la Luna. En la actualidad los modelos nos permiten
simular fenómenos en dispositivos de cómputo y la creación de aplicaciones. Por ejemplo, el modelo de
movimiento de la mecánica clásica es el mismo utilizado en los videojuegos para simular el movimiento de los
objetos.
9
Las características más sobresalientes de la resolución de problemas son:
Análisis. El problema se analiza teniendo presente la especificación de los requisitos dados por el
cliente de la empresa o por la persona que encarga el programa.
Diseño. Una vez analizado el problema, se diseña una solución que conducirá a un algoritmo que
resuelva el problema.
Codificación (implementación). La solución se escribe en la sintaxis del lenguaje de alto nivel
(por ejemplo, c++) y se obtiene un programa fuente que se compila a continuación.
Ejecución, verificación y depuración. El programa se ejecuta, se comprueba rigurosamente y se
eliminan todos los errores (denominados “bugs”, en inglés) que puedan aparecer.
Documentación. Escritura de las diferentes fases del ciclo de vida del software, esencialmente el
análisis, diseño y codificación, unidos a manuales de usuario y de referencia, así como normas
para el mantenimiento.
Mantenimiento. El programa se actualiza y modifica, cada vez que sea necesario, de modo que se
cumplan todas las necesidades de cambio de sus usuarios.
Las dos primeras fases conducen a un diseño detallado escrito en forma de algoritmo. Durante la tercera
fase (codificación) se implementa el algoritmo en un código escrito en un lenguaje de programación, reflejando
las ideas desarrolladas en las fases de análisis y diseño.
Las fases de compilación y ejecución traducen y ejecutan el programa. En las fases de verificación y
depuración el programador busca errores de las etapas anteriores y los elimina. Comprobará que mientras más
tiempo se gaste en la fase de análisis y diseño, menos se gastará en la depuración del programa. Por último,
se debe realizar la documentación del programa.
Antes de conocer las tareas a realizar en cada fase, se considera el concepto y significado de la palabra
algoritmo.
La palabra algoritmo se deriva de la traducción al latín algorithmus y éste a su vez de Al-Juarismi, nombre
de un matemático y astrónomo árabe que escribió un tratado sobre manipulación de números y ecuaciones en
el siglo IX. Un algoritmo es un método para resolver un problema mediante una serie de pasos precisos,
definidos y finitos.
Un algoritmo debe producir un resultado en un tiempo finito. Los métodos que utilizan algoritmos se
denominan métodos algorítmicos, en oposición a los métodos que implican algún juicio o interpretación que se
denominan métodos heurísticos. Los métodos algorítmicos se pueden implementar en computadoras; sin
embargo, los procesos heurísticos no han sido convertidos fácilmente en las computadoras. En los últimos
años las técnicas de inteligencia artificial han hecho posible la implementación del proceso heurístico en
computadoras.
Ejemplos de algoritmos son: instrucciones para montar en una bicicleta, hacer una receta de cocina, obtener
el máximo común divisor de dos números, etc. Los algoritmos se pueden expresar por fórmulas, diagramas de
flujo y pseudocódigos. Esta última representación es la más utilizada para su uso con lenguajes estructurados.
10
Dado que se busca una solución por computadora, se precisan especificaciones detalladas de entrada y
salida.
La siguiente figura muestra los requisitos que se deben definir en el análisis.
Para poder identificar y definir bien un problema es conveniente responder a las siguientes preguntas:
• ¿Qué entradas se requieren? (tipo de datos con los cuales se trabaja y cantidad).
• ¿Cuál es la salida deseada? (tipo de datos de los resultados y cantidad).
• ¿Qué método produce la salida deseada?
• Requisitos o requerimientos adicionales y restricciones a la solución.
Problema ejemplo
Calcular la paga neta de un trabajador conociendo el número de horas trabajadas, la tarifa horaria y el
porcentaje de descuentos.
Entrada:
número de horas trabajadas
tarifa horaria
porcentaje de descuentos
Salida:
paga neta
Proceso:
cálculo de la paga bruta
cálculo de los descuentos
cálculo de la paga neta
Ejemplo de salida solicitada:
Paga bruta: $ 7.800
Descuentos: $ 780
Paga neta: $ 7.020
11
Cada subprograma es resuelto mediante un módulo (subprograma) que tiene un solo punto de entrada y un
solo punto de salida.
Cualquier programa bien diseñado consta de un programa principal (el módulo de nivel más alto) que llama
a subprogramas (módulos de nivel más bajo) que a su vez pueden llamar a otros subprogramas. Los
programas estructurados de esta forma se dice que tienen un diseño modular y el método de romper el
programa en módulos más pequeños se llama programación modular. Los módulos pueden ser planeados,
codificados, comprobados y depurados independientemente (incluso por diferentes programadores) y a
continuación combinarlos entre sí. El proceso implica la ejecución de los siguientes pasos hasta que el
programa se termina:
1. Programar un módulo.
2. Comprobar el módulo.
3. Si es necesario, depurar el módulo.
4. Combinar el módulo con los módulos anteriores.
El proceso que convierte los resultados del análisis del problema en un diseño modular con refinamientos
sucesivos que permitan una posterior traducción a un lenguaje se denomina diseño del algoritmo.
El diseño del algoritmo es independiente del lenguaje de programación en el que se vaya a codificar
posteriormente.
Herramientas de programación
Las dos herramientas más utilizadas comúnmente para diseñar algoritmos son: diagramas de flujo y
pseudocódigos.
Un diagrama de flujo (flowchart) es una representación gráfica de un algoritmo. Los símbolos utilizados han
sido normalizados por el Instituto Norteamericano de Normalización (ANSI), y los más frecuentemente
empleados son:
12
Aunque no existen reglas para escritura del pseudocódigo en español, se ha recogido una notación
estándar que utilizaremos y que ya es muy empleada. Las palabras reservadas básicas se representarán en
letras negritas minúsculas. Estas palabras son traducción libre de palabras reservadas de lenguajes como C,
Pascal, etc.
El pseudocódigo que resuelve el problema expuesto anteriormente es:
1. Leer Horas, Tarifa, Porcentaje
2. PagaBruta = Horas * Tarifa
3. Descuentos = PagaBruta * Porcentaje / 100
4. PagaNeta = PagaBruta – Descuentos
5. Visualizar PagaBruta, Descuentos, PagaNeta
Inicio
Horas,
Tarifa,
Porcentaje
PagaBruta,
Descuentos,
PagaNeta
Fin
Codificación de un programa
La codificación es la escritura en un lenguaje de programación de la representación del algoritmo
desarrollada en las etapas precedentes. Dado que el diseño de un algoritmo es independiente del lenguaje de
programación utilizado para su implementación, el código puede ser escrito con igual facilidad en un lenguaje o
en otro.
Para realizar la conversión del algoritmo en programa se deben sustituir las operaciones/instrucciones
indicadas en lenguaje natural por el lenguaje de programación correspondiente.
#include "iostream"
int main() {
int horas;
float tarifa, porcentaje, pagaBruta, descuentos, pagaNeta;
Documentación interna
Como se verá más tarde, la documentación de un programa se clasifica en interna y externa. La
documentación interna es la que se incluye dentro del código del programa fuente mediante comentarios que
ayudan a la comprensión del código. Todas las líneas de programas que se encuentren entre los símbolos /* y
*/ son comentarios. El programa no los necesita y la computadora los ignora. Estas líneas de comentarios sólo
sirven para hacer los programas más fáciles de comprender. El objetivo del programador debe ser escribir
códigos sencillos y limpios.
Debido a que las máquinas actuales soportan grandes memorias (512 Mb o 1.024 Mb de memoria central
mínima en computadoras personales) no es necesario recurrir a técnicas de ahorro de memoria, por lo que es
recomendable que se incluya el mayor número de comentarios posibles, pero eso sí, que sean significativos.
14
Una vez que el programa ejecutable se ha creado, ya se puede ejecutar (correr o rodar) desde el sistema
operativo con sólo teclear su nombre (en el caso de DOS). Suponiendo que no existen errores durante la
ejecución (llamados errores en tiempo de ejecución), se obtendrá la salida de resultados del programa.
Las instrucciones u órdenes para compilar y ejecutar un programa en C, C++,... o cualquier otro lenguaje
dependerá de su entorno de programación y del sistema operativo en que se ejecute Windows, Linux, Unix,
etc.
Documentación y mantenimiento
Programas pobremente documentados son difíciles de leer, más difíciles de depurar y casi imposibles de
mantener y modificar.
15
La documentación de un programa puede ser interna y externa. La documentación interna es la contenida
en líneas de comentarios. La documentación externa incluye análisis, diagramas de flujo y/o pseudocódigos,
manuales de usuario con instrucciones para ejecutar el programa y para interpretar los resultados.
La documentación es vital cuando se desea corregir posibles errores futuros o bien cambiar el programa.
Tales cambios se denominan mantenimiento del programa. Después de cada cambio la documentación debe
ser actualizada para facilitar cambios posteriores. Es práctica frecuente numerar las sucesivas versiones de los
programas 1.0, 1.1, 2.0, 2.1, etc. (Si los cambios introducidos son importantes, se varía el primer dígito [1.0,
2.0,...]; en caso de pequeños cambios sólo se varía el segundo dígito [2.0, 2.1…].)
Acciones simples
Las acciones simples, también denominadas instrucciones primitivas, son aquellas que el procesador
ejecuta de forma inmediata.
Asignación. Almacena en una variable el resultado de evaluar una expresión:
variable = expresión
Sentencias de control
También se llaman sentencias estructuradas y controlan el flujo de ejecución de otras instrucciones.
Secuencia. Se ejecutan instrucciones de i1, i2, …, iN en el mismo orden en el que
aparecen.
i1, i2,…,iN
o Si (condición)
Entonces i1, i2,…,iN
Sino j1, j2,…,jN
Fin-Si
o Opción (expresión) de
v1 hacer i1, i2,…, iN
v2 hacer j1, j2,…, jN
v3 hacer k1, k2,…, kN
…………………………
vN hacer l1, l2,…, lN
Otro hacer m1, m2,…, mN
Fin-Opción
16
Repetición o bucles. En un bucle hay una o varias acciones que se han de repetir y
una condición que determina el número de veces que se repiten las instrucciones.
o Mientras
Mientras (condición) hacer
i1, i2,…, iN
Fin-Mientras
o Repetir
Repetir
i1, i2,…, iN
Hasta (condición)
o Para
Para Variable desde Valor_inicial
hasta Valor_final
incrementando según Valor_de_incremento hacer
I1, i2,…, iN
Fin-Para
Subrprogramas
17
Conector dentro de pagina. Representa la
continuidad del diagrama dentro de la misma
pagina.
Comentarios.
18
Ciclo repetitivo Repetir.
NO SI
La función main( )
19
Todo programa C++, desde el más pequeño hasta el más complejo, tiene un programa principal que es con
el que se comienza la ejecución del programa. Este programa principal es también una función, pero una
función que está por encima de todas las demás. Esta función se llama main() y tiene la forma siguiente:
int main(int argc, char *argv[])
{
sentencia_1
sentencia_2 ...
}
Tokens
Existen seis clases de componentes sintácticos o tokens en el vocabulario del lenguaje C++: palabras clave,
identificadores, constantes, cadenas de caracteres, operadores y separadores. Los separadores –uno o varios
espacios en blanco, tabuladores, caracteres de nueva línea (denominados "espacios en blanco" en conjunto), y
también los comentarios escritos por el programador– se emplean para separar los demás tokens; por lo
demás son ignorados por el compilador. El compilador descompone el texto fuente o programa en cada uno
de sus tokens, y a partir de esta descomposición genera el código objeto correspondiente. El compilador
ignora también los sangrados al comienzo de las líneas.
Identificadores
Un identificador es un nombre con el que se hace referencia a una función o al contenido de una zona de la
memoria (variable). Cada lenguaje tiene sus propias reglas respecto a las posibilidades de elección de nombres
para las funciones y variables. En C++ estas reglas son las siguientes:
En general es muy aconsejable elegir los nombres de las funciones y las variables de forma que permitan
conocer a simple vista qué tipo de variable o función representan, utilizando para ello tantos caracteres como
sean necesarios. Esto simplifica enormemente la tarea de programación y – sobre todo– de corrección y
mantenimiento de los programas. Es cierto que los nombres largos son más laboriosos de teclear, pero en
general resulta rentable tomarse esa pequeña molestia.
Constantes
Las variables pueden cambiar de valor a lo largo de la ejecución de un programa, o bien en ejecuciones
distintas de un mismo programa. Además de variables, un programa utiliza también constantes, es decir,
valores que siempre son los mismos. En C++ existen distintos tipos de constantes:
Constantes numéricas, constantes carácter, cadenas de caracteres y de tipo enumeración.
Operadores
Los operadores son signos especiales –a veces, conjuntos de dos caracteres– que indican determinadas
operaciones a realizar con las variables y/o constantes sobre las que actúan en el programa. El lenguaje C++ es
particularmente rico en distintos tipos de operadores: aritméticos (+, -, *, /, %), de asignación (=, +=, -=, ++, --,
*=, /=), relacionales (==, , <=, >=, !=), lógicos (&&, ||, !) y otros. También los operadores serán vistos con
mucho más detalle en apartados posteriores.
Separadores
Como ya se ha comentado, los separadores están constituidos por uno o varios espacios en blanco,
tabuladores, y caracteres de nueva línea. Su papel es ayudar al compilador a descomponer el programa fuente
en cada uno de sus tokens. Es conveniente introducir espacios en blanco incluso cuando no son estrictamente
necesarios, con objeto de mejorar la legibilidad de los programas.
Comentarios
El lenguaje C++ permite que el programador introduzca comentarios en los ficheros fuente que contienen
el código de su programa. La misión de los comentarios es servir de explicación o aclaración sobre cómo está
hecho el programa, de forma que pueda ser entendido por una persona diferente (o por el propio
programador algún tiempo después). El compilador ignora por completo los comentarios.
Los caracteres (/*) se emplean para iniciar un comentario introducido entre el código del programa; el
comentario termina con los caracteres (*/). Todo texto introducido entre los símbolos de comienzo (/*) y final
(*/) de comentario son siempre ignorados por el compilador. Por ejemplo:
21
/* Esto es un comentario simple. */
/* Esto es un comentario más largo,
distribuido en varias líneas. El
texto se suele alinear por la izquierda. */
Los comentarios pueden actuar también como separadores de otros tokens propios del lenguaje C++. Una
fuente frecuente de errores –no especialmente difíciles de detectar– al programar en C++, es el olvidarse de
cerrar un comentario que se ha abierto previamente.
Además se considera que son comentarios todo aquel texto que está desde dos barras consecutivas (//)
hasta el fin de la línea. Las dos barras marcan el comienzo del comentario y el fin de la línea, el final. Si se
desea poner comentarios de varias líneas, hay que colocar la doble barra al comienzo de cada línea. Los
ejemplos anteriores se podrían escribir del siguiente modo:
COMPILADOR
El compilador es el elemento más característico del lenguaje C++. Como ya se ha dicho anteriormente, su
misión consiste en traducir a lenguaje de máquina el programa C++ contenido en uno o más ficheros fuente. El
compilador es capaz de detectar ciertos errores durante el proceso de compilación, enviando al usuario el
correspondiente mensaje de error.
PREPROCESADOR
El preprocesador es un componente característico de C++, que no existe en otros lenguajes de
programación. El preprocesador actúa sobre el programa fuente, antes de que empiece la compilación
propiamente dicha, para realizar ciertas operaciones. Una de estas operaciones es, por ejemplo, la sustitución
de constantes simbólicas. Así, es posible que un programa haga uso repetidas veces del valor 3.141592654,
correspondiente al número p. Es posible definir una constante simbólica llamada PI que se define como
3.141592654 al comienzo del programa y se introduce luego en el código cada vez que hace falta. En realidad
PI no es una variable con un determinado valor: el preprocesador chequea todo el programa antes de
comenzar la compilación y sustituye el texto PI por el texto 3.141592654 cada vez que lo encuentra. Las
constantes simbólicas suelen escribirse completamente con mayúsculas, para distinguirlas de las variables.
El preprocesador realiza muchas otras funciones que se irán viendo a medida que se vaya explicando el
lenguaje. Lo importante es recordar que actúa siempre por delante del compilador (de ahí su nombre),
facilitando su tarea y la del programador.
LIBRERÍA ESTÁNDAR
Con objeto de mantener el lenguaje lo más sencillo posible, muchas sentencias que existen en otros
lenguajes, no tienen su correspondiente contrapartida en C++. Por ejemplo, en C++ no hay sentencias para
22
entrada y salida de datos. Es evidente, sin embargo, que esta es una funcionalidad que hay que cubrir de
alguna manera. El lenguaje C++ lo hace por medio de funciones preprogramadas que se venden o se entregan
junto con el compilador. Estas funciones están agrupadas en un conjunto de librerías de código objeto, que
constituyen la llamada librería estándar del lenguaje. La llamada a dichas funciones se hace como a otras
funciones cualesquiera, y deben ser declaradas antes de ser llamadas por el programa (más adelante se verá
cómo se hace esto por medio de la directiva del preprocesador #include).
Ficheros
El código de cualquier programa escrito en C++ se almacena en uno o más ficheros, en el disco del
ordenador. La magnitud del programa y su estructura interna determina o aconseja sobre el número de
ficheros a utilizar. Como se verá más adelante, la división de un programa en varios ficheros es una forma de
controlar su manejo y su modularidad. Cuando los programas son pequeños (hasta 50»100 líneas de código),
un solo fichero suele bastar. Para programas más grandes, y cuando se quiere mantener más independencia
entre los distintos subprogramas, es conveniente repartir el código entre varios ficheros.
Recuérdese además que cada vez que se introduce un cambio en el programa hay que volver a compilarlo.
La compilación se realiza a nivel de fichero, por lo que sólo los ficheros modificados deben ser compilados de
nuevo. Si el programa está repartido entre varios ficheros pequeños esta operación se realiza mucho más
rápidamente.
Recuérdese que en C++ es necesario declarar todas las variables que se vayan a utilizar. Una variable no
declarada produce un mensaje de error en la compilación. Cuando una variable es declarada se le reserva
memoria de acuerdo con el tipo incluido en la declaración. Es posible inicializar –dar un valor inicial– las
variables en el momento de la declaración; ya se verá que en ciertas ocasiones el compilador da un valor inicial
por defecto, mientras que en otros casos no se realiza esta inicialización y la memoria asociada con la variable
correspondiente contiene basura informática (combinaciones sin sentido de unos y ceros, resultado de
operaciones anteriores con esa zona de la memoria, para otros fines).
23
Las variables carácter (tipo char) contienen un único carácter y se almacenan en un byte de memoria (8
bits). En un bit se pueden almacenar dos valores (0 y 1); con dos bits se pueden almacenar 22 = 4 valores (00,
01, 10, 11 en binario; 0, 1, 2, 3 en decimal). Con 8 bits se podrán almacenar 28 = 256 valores diferentes
(normalmente entre 0 y 255; con ciertos compiladores entre - 128 y 127).
La declaración de variables tipo carácter puede tener la forma:
char nombre;
char nombre1, nombre2, nombre3;
Se puede declarar más de una variable de un tipo determinado en una sola sentencia. Se puede también
inicializar la variable en la declaración. Por ejemplo, para definir la variable carácter letra y asignarle el valor a,
se puede escribir:
A partir de ese momento queda definida la variable letra con el valor correspondiente a la letra a.
Recuérdese que el valor 'a' utilizado para inicializar la variable letra es una constante carácter. En realidad,
letra se guarda en un solo byte como un número entero, el correspondiente a la letra a en el código ASCII, que
se muestra en la siguiente tabla para los caracteres estándar (existe un código ASCII extendido que utiliza los
256 valores y que contiene caracteres especiales y caracteres específicos de los alfabetos de diversos países,
como por ejemplo las vocales acentuadas y la letra ñ para el castellano).
0 1 2 3 4 5 6 7 8 9
0 Nul Soh Stx Etx Eot Enq Ack Bel Bs Ht
1 Nl Vt Np Cr So Si Del Dcl Dc2 Dc3
2 Dc4 Nak Syn Etb Can Em Sub Esc Fs Gs
3 Rs Us Sp ¡ “ # $ % & ‘
4 ( ) * + , - . / 0 1
5 2 3 4 5 6 7 8 9 : ;
6 < = > ? @ A B C D E
7 F G H I J K L M N O
8 P Q R S T U V W X Y
9 Z [ \ ] ^ ` a b C
10 d e f g h i j k l m
11 n o p q r s t u v w
12 x y z { | } ~ del
La tabla se utiliza de la siguiente forma. La(s) primera(s) cifra(s) del número ASCII correspondiente a un
carácter determinado figura en la primera columna de la Tabla, y la última cifra en la primera fila de la Tabla.
Sabiendo la fila y la columna en la que está un determinado carácter puede componerse el número
correspondiente. Por ejemplo, la letra A está en la fila 6 y la columna 5. Su número ASCII es por tanto el 65. El
carácter % está en la fila 3 y la columna 7, por lo que su representación ASCII será el 37. Obsérvese que el
código ASCII asocia números consecutivos con las letras mayúsculas y minúsculas ordenadas alfabéticamente.
Esto simplifica notablemente ciertas operaciones de ordenación alfabética de nombres.
En la Tabla aparecen muchos caracteres no imprimibles (todos aquellos que se expresan con 2 ó 3 letras).
Por ejemplo, el ht es el tabulador horizontal, el nl es el carácter nueva línea, etc.
Volviendo al ejemplo de la variable letra, su contenido puede ser variado cuando se desee por medio de
una sentencia que le asigne otro valor, por ejemplo:
letra = 'z';
24
También puede utilizarse una variable char para dar valor a otra variable de tipo char:
Como una variable tipo char es un número entero pequeño (entre 0 y 255), se puede utilizar el contenido
de una variable char de la misma forma que se utiliza un entero, por lo que están permitidas operaciones
como:
letra = letra + 1;
letra_min = letra_may + ('a' - 'A');
En el primer ejemplo, si el contenido de letra era una a, al incrementarse en una unidad pasa a contener
una b. El segundo ejemplo es interesante: puesto que la diferencia numérica entre las letras minúsculas y
mayúsculas es siempre la misma (según el código ASCII), la segunda sentencia pasa una letra mayúscula a la
correspondiente letra minúscula sumándole dicha diferencia numérica.
Recuérdese para concluir que las variables tipo char son y se almacenan como números enteros pequeños.
Ya se verá más adelante que se pueden escribir como caracteres o como números según que formato de
conversión se utilice en la llamada a la función de escritura.
También se puede reservar 1 byte de memoria mediante variables tipo bool. Aunque realmente no sean un
tipo de variable, C++ permite declararlas porque se les da un uso muy específico.
En una variable tipo bool se almacena un único valor en ese byte de memoria. Este valor puede ser 0 ó 1,
donde el 0 corresponde a la sentencia false y el uno corresponde a la sentencia true.
Estas variables no se utilizan para almacenar datos, sino para controlar el flujo de ejecución, es decir, para
dirigir las bifurcaciones y los bucles.
Una variable bool se declara e inicializa de la siguiente manera:
En este caso numero podrá estar entre 0 y 4294967296, mientras que nota deberá estar comprendida
entre -2147483648 y 2147483647. Cuando a una variable int se le asigna en tiempo de ejecución un valor que
queda fuera del rango permitido (situación de overflow o valor excesivo), se produce un error en el resultado
de consecuencias tanto más imprevisibles cuanto que de ordinario el programa no avisa al usuario de dicha
circunstancia.
25
Aunque lo habitual es que una variable tipo int ocupe 4 bytes, en algunos compiladores no se le asignan
más que 2. Para evitar este problema se puede anteponer la palabra long, que asegura que la variable alcance
esos 4 bytes.
De esta manera el rango de la variable será el correspondiente al tipo int habitual.
short numero_pequeño;
El rango de un entero short puede variar según el computador o el compilador que se utilice, pero de
ordinario se utilizan 2 bytes (16 bits) para almacenarlos, por lo que se pueden representar 2 16 = 65.536
números enteros diferentes. Si se utilizan números con signo, podrán representarse números entre –32.768 y
32.767. También se pueden declarar enteros short que sean siempre positivos con la palabra unsigned:
En algunos computadores una variable int ocupa 2 bytes (coincidiendo con short) y en otros 4 bytes
(coincidiendo con long). Lo que se garantiza es que el rango de int no es menor que el de short ni mayor que el
de long.
2 23 = 8.388.608
lo cual quiere decir que se pueden representar todos los números decimales de 6 cifras y la mayor parte –
aunque no todos– de los de 7 cifras (por ejemplo, el número 9.213.456 no se puede representar con 23 bits).
Por eso se dice que las variables tipo float tienen entre 6 y 7 cifras decimales equivalentes de precisión.
Respecto al exponente de dos por el que hay que multiplicar la mantisa en base 2, con 7 bits el número más
grande que se puede representar es 128. El rango vendrá definido por la potencia,
26
lo cual indica el número más grande representable de esta forma. El número más pequeño en valor
absoluto será del orden de
float numero_real;
Las variables tipo float pueden ser inicializadas en el momento de la declaración, de forma análoga a las
variables tipo int.
2 52 = 4.503.599.627.370.496
lo cual representa entre 15 y 16 cifras decimales equivalentes. Con respecto al rango, con un exponente de
10 bits el número más grande que se puede representar será del orden de 2 elevado a 2 elevado a 10 (que es
1024):
double real_grande;
Por último, existe la posibilidad de declarar una variable como long double, aunque el C++ no garantiza un
rango y una precisión mayores que las de double. Eso depende del compilador y del tipo de computador. Estas
variables se declaran en la forma:
CONSTANTES
Se entiende por constantes aquel tipo de información numérica o alfanumérica que no puede cambiar más
que con una nueva compilación del programa. Como ya se ha dicho anteriormente, en el código de un
programa en C++ pueden aparecer diversos tipos de constantes que se van a explicar a continuación.
Constantes numéricas
CONSTANTES ENTERAS
27
Una constante entera decimal está formada por una secuencia de dígitos del 0 al 9, constituyendo un
número entero. Las constantes enteras decimales están sujetas a las mismas restricciones de rango que las
variables tipo int y long, pudiendo también ser unsigned. El tipo de una constante se puede determinar
automáticamente según su magnitud, o de modo explícito postponiendo ciertos caracteres, como en los
ejemplos que siguen:
En C++ se pueden definir también constantes enteras octales, esto es, expresadas en base 8 con dígitos del
0 al 7. Se considera que una constante está expresada en base 8 si el primer dígito por la izquierda es un cero
(0). Análogamente, una secuencia de dígitos (del 0 al 9) y de letras (A, B, C, D, E y F o minúsculas) precedida
por 0x o por 0X, se interpreta como una constante entera hexadecimal, esto es, en base 16. Por ejemplo:
Es probable que no haya necesidad de utilizar constantes octales y hexadecimales, pero conviene conocer
su existencia y saber interpretarlas por si hiciera falta. La ventaja de los números expresados en base 8 y base
16 proviene de su estrecha relación con la base 2 ( 8 y 16 son potencias de 2), que es la forma en la que el
ordenador almacena la información.
28
1,23 error: la coma no esta permitida
23963f error: no hay punto decimal ni carácter e ó E
.e4 error: no hay ni parte entera ni fraccionaria
Constantes carácter
Una constante carácter es un carácter cualquiera encerrado entre apóstrofos (tal como 'x' o 't'). El valor de
una constante carácter es el valor numérico asignado a ese carácter según el código ASCII (ver Tabla 2.3).
Conviene indicar que en C++ no existen constantes tipo char; lo que se llama aquí constantes carácter son en
realidad constantes enteras (equivalente ASCII).
Hay que señalar que el valor ASCII de los números no coincide con el propio valor numérico. Por ejemplo, el
valor ASCII de la constante carácter '7' es 55.
Ciertos caracteres no representables gráficamente, el apóstrofo (') y la barra invertida (\) y otros caracteres,
se representan mediante la siguiente tabla de secuencias de escape, con ayuda de la barra invertida (\)(Una
secuencia de escape está constituida por la barra invertida (\) seguida de otro carácter. La finalidad de la
secuencia de escape es cambiar el significado habitual del carácter que sigue a la barra invertida).
Los caracteres ASCII pueden ser también representados mediante el número octal correspondiente (hasta
tres dígitos), encerrado entre apóstrofos y precedido por la barra invertida. Por ejemplo, '\07' y '\7'
representan el número 7 del código ASCII ('\007' es la representación octal del carácter '7'), que es el sonido
de alerta. El C++ también admite secuencias de escape hexadecimales (sin límite de dígitos), por ejemplo
'\x1a'.
Cadenas de caracteres
Una cadena de caracteres es una secuencia de caracteres delimitada por comillas ("), como por ejemplo:
"Esto es una cadena de caracteres". Dentro de la cadena, pueden aparecer caracteres en blanco y se pueden
emplear las mismas secuencias de escape válidas para las constantes carácter.
Por ejemplo, las comillas (") deben estar precedidas por (\), para no ser interpretadas como fin de la
cadena; también la propia barra invertida (\). Es muy importante señalar que el compilador sitúa siempre un
byte nulo (\0) adicional al final de cada cadena de caracteres para señalar el final de esta.
Así, la cadena "mesa" no ocupa 4 bytes, sino 5 bytes. A continuación se muestran algunos ejemplos de
cadenas de caracteres:
29
"Informática I"
"'A'"
" cadena con espacios en blanco "
"Esto es una \"cadena de caracteres\".\n"
OPERADORES Y EXPRESIONES
Operadores
Un operador es un carácter o grupo de caracteres que actúa sobre una, dos o más variables para realizar
una determinada operación con un determinado resultado. Ejemplos típicos de operadores son la suma (+), la
diferencia (-), el producto (*), etc. Los operadores pueden ser unarios, binarios y ternarios, según actúen sobre
uno, dos o tres operandos, respectivamente. En C++ existen muchos operadores de diversos tipos (éste es uno
de los puntos fuertes del lenguaje), que se verán a continuación.
OPERADORES ARITMÉTICOS
Los operadores aritméticos son los más sencillos de entender y de utilizar. Todos ellos son operadores
binarios. En C++ se utilizan los cinco operadores siguientes:
– Suma: +
– Resta: -
– Multiplicación: *
– División: /
– Resto: %
Todos estos operadores se pueden aplicar a constantes, variables y expresiones. El resultado es el que se
obtiene de aplicar la operación correspondiente entre los dos operandos.
El único operador que requiere una explicación adicional es el operador resto %. En realidad su nombre
completo es resto de la división entera. Este operador se aplica solamente a constantes, variables o
expresiones de tipo int. Aclarado esto, su significado es evidente: 23%4 es 3, puesto que el resto de dividir 23
por 4 es 3. Si a%b es cero, a es múltiplo de b.
Como se verá más adelante, una expresión es un conjunto de variables y constantes –y también de otras
expresiones más sencillas– relacionadas mediante distintos operadores. Un ejemplo de expresión en la que
intervienen operadores aritméticos es el siguiente polinomio de grado 2 en la variable x:
Las expresiones pueden contener paréntesis (...) que agrupan a algunos de sus términos.
Puede haber paréntesis contenidos dentro de otros paréntesis. El significado de los paréntesis coincide con
el habitual en las expresiones matemáticas, con algunas características importantes que se verán más
adelante. En ocasiones, la introducción de espacios en blanco mejora la legibilidad de las expresiones.
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 el valor de otra variable (en realidad, una
variable es un caso particular de una expresión).
El operador de asignación más utilizado es el operador de igualdad (=), que no debe ser confundido con la
igualdad matemática. Su forma general es:
30
nombre_de_variable = expresion;
Desde el punto de vista matemático este ejemplo no tiene sentido (¡Equivale a 0 = 1!), pero sí lo tiene
considerando que en realidad el operador de asignación (=) representa una sustitución; en efecto, se toma el
valor de variable contenido en la memoria, se le suma una unidad y el valor resultante vuelve a depositarse en
memoria en la zona correspondiente al identificador variable, sustituyendo al valor que había anteriormente.
El resultado ha sido incrementar el valor de variable en una unidad.
Así pues, una variable puede aparecer a la izquierda y a la derecha del operador (=). Sin embargo, a la
izquierda de (=) no puede haber una expresión: tiene que ser necesariamente el nombre de una variable. Es
incorrecto, por tanto, escribir algo así como:
a + b = c;
Existen otros diez operadores de asignación (+=, -=, *=, /=, %=, <<=, >>=, &=, |= y ^=). Estos operadores
simplifican algunas operaciones recurrentes sobre una misma variable. Su forma general es:
OPERADORES INCREMENTALES
Los operadores incrementales (++) y (--) son operadores unarios que incrementan o disminuyen en una
unidad el valor de la variable a la que afectan. Estos operadores pueden ir inmediatamente delante o detrás de
la variable. Si preceden a la variable, ésta es incrementada antes de que el valor de dicha variable sea utilizado
en la expresión en la que aparece. Si es la variable la que precede al operador, la variable es incrementada
después de ser utilizada en la expresión. A continuación se presenta un ejemplo de estos operadores:
i = 2;
j = 2;
m = i++; /* después de ejecutarse esta sentencia m=2 e i=3 */
n = ++j; /* después de ejecutarse esta sentencia n=3 y j=3 */
Estos operadores son muy utilizados. Es importante entender muy bien por qué los resultados m y n del
ejemplo anterior son diferentes.
OPERADORES RELACIONALES
31
Este es un apartado especialmente importante para todas aquellas personas sin experiencia en
programación. Una característica imprescindible de cualquier lenguaje de programación es la de considerar
alternativas, esto es, la de proceder de un modo u otro según se cumplan o no ciertas condiciones. Los
operadores relacionales permiten estudiar si se cumplen o no esas condiciones.
Así pues, estos operadores producen un resultado u otro según se cumplan o no algunas condiciones que
se verán a continuación.
En el lenguaje natural, existen varias palabras o formas de indicar si se cumple o no una determinada
condición. En inglés estas formas son (yes, no), (on, off), (true, false), etc. En Informática se ha hecho bastante
general el utilizar la última de las formas citadas: (true, false). Si una condición se cumple, el resultado es true;
en caso contrario, el resultado es false.
En C++, un 0 representa la condición de false, y cualquier número distinto de 0 equivale a la condición true.
Cuando el resultado de una expresión es true y hay que asignar un valor concreto distinto de cero, por defecto
se toma un valor unidad. Los operadores relacionales de C++ son los siguientes:
– Igual que: ==
– Menor que: <
– Mayor que: >
– Menor o igual que: <=
– Mayor o igual que: >=
– Distinto que: !=
Todos los operadores relacionales son operadores binarios (tienen dos operandos), y su forma general es la
siguiente:
expresion1 op expresion2
donde op es uno de los operadores (==, <, >, <=, >=, !=). El funcionamiento de estos operadores es el
siguiente: se evalúan expresion1 y expresion2, y se comparan los valores resultantes. Si la condición
representada por el operador relacional se cumple, el resultado es 1; si la condición no se cumple, el resultado
es 0.
A continuación se incluyen algunos ejemplos de estos operadores aplicados a constantes:
(2==1) /* resultado=0 porque la condición no se cumple */
(3<=3) /* resultado=1 porque la condición se cumple */
(3<3) /* resultado=0 porque la condición no se cumple */
(1!=1) /* resultado=0 porque la condición no se cumple */
OPERADORES LÓGICOS
Los operadores lógicos son operadores binarios que permiten combinar los resultados de los operadores
relacionales, comprobando que se cumplen simultáneamente varias condiciones, que se cumple una u otra,
etc. El lenguaje C++ tiene dos operadores lógicos: el operador Y (&&) y el operador O (||). En inglés son los
operadores and y or. Su forma general es la siguiente:
expresion1 || expresion2
expresion1 && expresion2
El operador && devuelve un 1 si tanto expresion1 como expresion2 son verdaderas (o iguales a 1), y 0 en
caso contrario, es decir si una de las dos expresiones o las dos son falsas (iguales a 0); por otra parte, el
operador || devuelve 1 si al menos una de las expresiones es cierta. Es importante tener en cuenta que los
compiladores de C++ tratan de optimizar la ejecución de estas expresiones, lo cual puede tener a veces efectos
no deseados. Por ejemplo: para que el resultado del operador && sea verdadero, ambas expresiones tienen
que ser verdaderas; si se evalúa expresion1 y es falsa, ya no hace falta evaluar expresion2, y de hecho no se
evalúa. Algo parecido pasa con el operador ||: si expresion1 es verdadera, ya no hace falta evaluar expresion2.
32
Los operadores && y || se pueden combinar entre sí, agrupados entre paréntesis, dando a veces un código
de más difícil interpretación. Por ejemplo:
OTROS OPERADORES
Operador negación lógica (!):
Este operador devuelve un cero (false) si se aplica a un valor distinto de cero (true), y devuelve un 1 (true)
si se aplica a un valor cero (false). Su forma general es:
!expresion
Expresiones
Una expresión es una combinación de variables y/o constantes, y operadores. La expresión es equivalente
al resultado que proporciona al aplicar sus operadores a sus operandos. Por ejemplo, 1+5 es una expresión
formada por dos valores (1 y 5) y un operador (el +); esta expresión es equivalente al valor 6, lo cual quiere
decir que allí donde esta expresión aparece en el programa, en el momento de la ejecución es evaluada y
sustituida por su resultado. Una expresión puede estar formada por otras expresiones más sencillas, y puede
contener paréntesis de varios niveles agrupando distintos términos. En C++ existen distintos tipos de
expresiones.
EXPRESIONES ARITMÉTICAS
Están formadas por variables y/o constantes, y distintos operadores aritméticos e incrementales (+, -, *, /,
%, ++, --). Como se ha dicho, también se pueden emplear paréntesis de tantos niveles como se desee, y su
interpretación sigue las normas aritméticas convencionales. Por ejemplo, la solución de la ecuación de
segundo grado:
x=(-b+sqrt((b*b)-(4*a*c)))/(2*a);
donde, estrictamente hablando, sólo lo que está a la derecha del operador de asignación (=) es una
expresión aritmética. El conjunto de la variable que está a la izquierda del signo (=), el operador de asignación,
la expresión aritmética y el carácter (;) constituyen una sentencia. En la expresión anterior aparece la llamada
a la función de librería sqrt(), que tiene como valor de retorno la raíz cuadrada de su único argumento. En las
expresiones se pueden introducir espacios en blanco entre operandos y operadores; por ejemplo, la expresión
anterior se puede escribir también de la forma:
EXPRESIONES LÓGICAS
Los elementos con los que se forman estas expresiones son valores lógicos; verdaderos (true, o distintos de
0) y falsos (false, o iguales a 0), y los operadores lógicos ||, && y !. También se pueden emplear los operadores
33
relacionales (<, >, <=, >=, ==, !=) para producir estos valores lógicos a partir de valores numéricos. Estas
expresiones equivalen siempre a un valor 1 (true) o a un valor 0 (false). Por ejemplo:
a = ((b>c)&&(c>d))||((c==e)||(e==b));
donde de nuevo la expresión lógica es lo que está entre el operador de asignación (=) y el (;). La variable a
valdrá 1 si b es mayor que c y c mayor que d, ó si c es igual a e ó e es igual a b.
Bifurcaciones
OPERADOR CONDICIONAL
El operador condicional es un operador con tres operandos (ternario) que tiene la siguiente forma general:
Explicación: Se evalúa expresion_1. Si el resultado de dicha evaluación es true (!=0), se ejecuta expresion_2;
si el resultado es false (=0), se ejecuta expresion_3.
SENTENCIA IF
Esta sentencia de control permite ejecutar o no una sentencia según se cumpla o no una determinada
condición. Esta sentencia tiene la siguiente forma general:
if (expresion)
sentencia
Explicación: Se evalúa expresion. Si el resultado es true (!=0), se ejecuta sentencia; si el resultado es false
(=0), se salta sentencia y se prosigue en la línea siguiente. Hay que recordar que sentencia puede ser una
sentencia simple o compuesta.
if (expresion)
sentencia_1
else
sentencia_2
if (expresion_1)
sentencia_1
else if (expresion_2)
sentencia_2
else if (expresion_3)
sentencia_3
else if (...)
...
[else
sentencia_n]
SENTENCIA SWITCH
La sentencia que se va a describir a continuación desarrolla una función similar a la de la sentencia if ... else
con múltiples ramificaciones, aunque como se puede ver presenta también importantes diferencias. La forma
general de la sentencia switch es la siguiente:
switch (expresion) {
case expresion_cte_1:
sentencia_1
case expresion_cte_2:
sentencia_2
...
case expresion_cte_n:
sentencia_n
[default:
sentencia]
}
Explicación: Se evalúa expresion y se considera el resultado de dicha evaluación. Si dicho resultado coincide
con el valor constante expresion_cte_1, se ejecuta sentencia_1 seguida de sentencia_2, sentencia_3, ...,
sentencia. Si el resultado coincide con el valor constante expresion_cte_2, se ejecuta sentencia_2 seguida de
sentencia_3, ..., sentencia. En general, se ejecutan todas aquellas sentencias que están a continuación de la
expresion_cte cuyo valor coincide con el resultado calculado al principio. Si ninguna expresion_cte coincide se
ejecuta la sentencia que está a continuación de default. Si se desea ejecutar únicamente una sentencia_i (y no
todo un conjunto de ellas), basta poner una sentencia break a continuación (en algunos casos puede utilizarse
la sentencia return o la función exit()). El efecto de la sentencia break es dar por terminada la ejecución de la
35
sentencia switch. Existe también la posibilidad de ejecutar la misma sentencia_i para varios valores del
resultado de expresion, poniendo varios case expresion_cte seguidos.
El siguiente ejemplo ilustra las posibilidades citadas:
int opc;
cout <<"Introduzca la opcion deseada: ";
cin>>opc;
switch (opc){
case 1 : cout << "caso 1\n" ; break;
case 2 : cout << "caso 2\n" ; break;
case 3 : cout << "caso 3\n" ; break;
case 4 : cout << "caso 4\n" ; break;
default : cout << "caso por defecto";
}
SENTENCIAS IF ANIDADAS
Una sentencia if puede incluir otros if dentro de la parte correspondiente a su sentencia, A estas sentencias
se les llama sentencias anidadas (una dentro de otra), por ejemplo,
if (a >= b)
if (b != 0.0)
c = a/b;
En ocasiones pueden aparecer dificultades de interpretación con sentencias if...else anidadas, como en el
caso siguiente:
if (a >= b)
if (b != 0.0)
c = a/b;
else
c = 0.0;
En principio se podría plantear la duda de a cuál de los dos if corresponde la parte else del programa. Los
espacios en blanco –las indentaciones de las líneas– parecen indicar que la sentencia que sigue a else
corresponde al segundo de los if, y así es en realidad, pues la regla es que el else pertenece al if más cercano.
Sin embargo, no se olvide que el compilador de C++ no considera los espacios en blanco (aunque sea muy
conveniente introducirlos para hacer más claro y legible el programa), y que si se quisiera que el else
perteneciera al primero de los if no bastaría cambiar los espacios en blanco, sino que habría que utilizar llaves,
en la forma:
if (a >= b) {
if (b != 0.0)
c = a/b;
}
else
c = 0.0;
Recuérdese que todas las sentencias if e if...else, equivalen a una única sentencia por la posición que
ocupan en el programa.
Bucles
36
Además de bifurcaciones, en el lenguaje C++ existen también varias sentencias que permiten repetir una
serie de veces la ejecución de unas líneas de código. Esta repetición se realiza, bien un número determinado
de veces, bien hasta que se cumpla una determinada condición de tipo lógico o aritmético. De modo genérico,
a estas sentencias se les denomina bucles. Las tres construcciones del lenguaje C++ para realizar bucles son el
while, el for y el do...while.
SENTENCIA WHILE
Esta sentencia permite ejecutar repetidamente, mientras se cumpla una determinada condición, una
sentencia o bloque de sentencias. La forma general es como sigue:
while (expresion_de_control)
sentencia
SENTENCIA FOR
For es quizás el tipo de bucle mas versátil y utilizado del lenguaje C++. Su forma general es la siguiente:
Explicación: Posiblemente la forma más sencilla de explicar la sentencia for sea utilizando la construcción
while que sería equivalente. Dicha construcción es la siguiente:
inicializacion;
while (expresion_de_control) {
sentencia;
actualizacion;
}
donde sentencia puede ser una única sentencia terminada con (;), otra sentencia de control ocupando
varias líneas (if, while, for, ...), o una sentencia compuesta. Antes de iniciarse el bucle se ejecuta inicializacion,
que es una o más sentencias que asignan valores iniciales a ciertas variables o contadores. A continuación se
evalúa expresion_de_control y si es false se prosigue en la sentencia siguiente a la construcción for; si es true
se ejecutan sentencia y actualizacion, y se vuelve a evaluar expresion_de_control. El proceso prosigue hasta
que expresion_de_control sea false. La parte de actualizacion sirve para actualizar variables o incrementar
contadores. Un ejemplo típico puede ser el producto escalar de dos vectores a y b de dimensión n:
for (pe =0.0, i=0; i<n; i++){ /*i empieza en 0, porque los
vectores empiezan a contar desde
la posición cero*/
pe += a[i]*b[i];
}
37
Primeramente se inicializa la variable pe a cero y la variable i a 0; el ciclo se repetirá mientras que i sea
menor que n, y al final de cada ciclo el valor de i se incrementará en una unidad. En total, el bucle se repetirá n
veces. La ventaja de la construcción for sobre la construcción while equivalente está en que en la cabecera de
la construcción for se tiene toda la información sobre como se inicializan, controlan y actualizan las variables
del bucle. Obsérvese que la inicialización consta de dos sentencias separadas por el operador (,).
do
sentencia;
while(expresion_de_control);
donde sentencia puede ser una única sentencia o un bloque, y en la que debe observarse que hay que
poner (;) a continuación del paréntesis que encierra a expresion_de_control, entre otros motivos para que esa
línea se distinga de una sentencia while ordinaria.
sentencias ...
...
if (condicion)
goto otro_lugar;
sentencia_1;
sentencia_2;
...
otro_lugar:
sentencia_3;
...
Obsérvese que la etiqueta termina con el carácter (:). La sentencia goto no es una sentencia muy
prestigiada en el mundo de los programadores de C++, pues disminuye la claridad y legibilidad del código. Fue
introducida en el lenguaje por motivos de compatibilidad con antiguos hábitos de programación, y siempre
puede ser sustituida por otras construcciones más claras y estructuradas.
38
double a[10];
se reserva espacio para 10 variables de tipo double. Las 10 variables se llaman a y se accede a una u otra
por medio de un subíndice, que es una expresión entera escrita a continuación del nombre entre corchetes
[...]. La forma general de la declaración de un vector es la siguiente:
tipo nombre[numero_elementos];
Los elementos se numeran desde 0 hasta (numero_elementos - 1). El tamaño de un vector puede definirse
con cualquier expresión constante entera. Para definir tamaños son particularmente útiles las constantes
simbólicas. En C++ no se puede operar con todo un vector o toda una matriz como una única entidad, sino que
hay que tratar sus elementos uno a uno. Los vectores (mejor dicho, los elementos de un vector) se utilizan en
las expresiones de C++ como cualquier otra variable. Ejemplos de uso de vectores son los siguientes:
a[5] = 0.8;
a[9] = 30. * a[5];
a[0] = 3. * a[9] - a[5]/a[9];
a[3] = (a[0] + a[9])/a[3];
Una cadena de caracteres no es sino un vector de tipo char, con alguna particularidad que conviene
resaltar. Las cadenas suelen contener texto (nombres, frases, etc.), y éste se almacena en la parte inicial de la
cadena (a partir de la posición cero del vector). Para separar la parte que contiene texto de la parte no
utilizada, se utiliza un carácter fin de texto que es el carácter nulo ('\0') según el código ASCII. Este carácter se
introduce automáticamente al leer o inicializar las cadenas de caracteres, como en el siguiente ejemplo:
donde a los 13 caracteres del nombre de esta ciudad se añade un decimocuarto: el '\0'. El resto del espacio
reservado –hasta la posición ciudad[19]– no se utiliza. De modo análogo, una cadena constante tal como
"mar" ocupa 4 bytes (para las 3 letras y el '\0').
Las matrices se declaran de forma análoga, con corchetes independientes para cada subíndice. La forma
general de la declaración es:
tipo nombre[numero_filas][numero_columnas];
donde tanto las filas como las columnas se numeran también a partir de 0. La forma de acceder a los
elementos de la matriz es utilizando su nombre, seguido de las expresiones enteras correspondientes a los dos
subíndices, entre corchetes.
En C++ tanto los vectores como las matrices admiten los tipos de las variables escalares (char, int, long,
float, double, etc.), y los modos de almacenamiento auto, extern y static, con las mismas características que
las variables normales (escalares). No se admite el modo register.
Los arrays static y extern se inicializan a cero por defecto. Los arrays auto no se inicializan, contienen basura
informática.
Las matrices en C++ se almacenan por filas, en posiciones consecutivas de memoria. En cierta forma, una
matriz se puede ver como un vector de vectores-fila. Si una matriz tiene N filas (numeradas de 0 a N-1) y M
columnas (numeradas de 0 a la M-1), el elemento (i, j) ocupa el lugar:
posición_elemento(0, 0) + i * M + j
declara una hipermatriz con tres subíndices, que podría verse como un conjunto de 3 matrices de
dimensión (5x7). En la fórmula de direccionamiento correspondiente, el último subíndice es el que varía más
rápidamente.
FUNCIONES
Una función es una parte de código independiente del programa principal y de otras funciones, que puede
ser llamada enviándole unos datos o no, para que realice una determinada tarea y/o proporcione unos
resultados. Las funciones son una parte muy importante del lenguaje C++. Seguidamente se describirán los
aspectos más importantes de las funciones.
La primera línea recibe el nombre de encabezamiento (header) y el resto de la definición – encerrado entre
llaves– es el cuerpo de la función. Cada función puede disponer de sus propias variables, declaradas al
comienzo de su código. Estas variables, por defecto, son de tipo auto, es decir, sólo son visibles dentro del
bloque en el que han sido definidas y permanecen ocultas para el resto del programa. También pueden
hacerse visibles a la función variables globales definidas en otro fichero (o en el mismo fichero, si la definición
está por debajo de donde se utilizan), declarándolas con la palabra clave extern.
El código ejecutable es el conjunto de instrucciones que deben ejecutarse cada vez que la función es
llamada. La lista de parámetros, también llamados argumentos formales, es una lista de declaraciones de
variables, precedidas por su tipo correspondiente y separadas por comas (,).
Los argumentos formales son la forma más natural y directa para que la función reciba valores desde el
programa que la llama, correspondiéndose en número y tipo con otra lista de argumentos – los argumentos
actuales- en el programa que realiza la llamada a la función. Los argumentos formales son declarados en el
encabezamiento de la función, pero no pueden ser inicializados en él.
40
Cuando una función es ejecutada, puede devolver al programa que la ha llamado un valor (el valor de
retorno), cuyo tipo debe ser especificado en el encabezamiento de la función (si no se especifica, se supone
por defecto el tipo int). Si no se desea que la función devuelva ningún valor, el tipo del valor de retorno deberá
ser void.
La sentencia return permite devolver el control al programa que llama. Puede haber varias sentencias
return en una misma función. Si no hay ningún return, el control se devuelve cuando se llega al final del
cuerpo de la función. Le palabra clave return puede ir seguida de una expresión, en cuyo caso ésta es evaluada
y el valor resultante devuelto al programa que llama como valor de retorno (si hace falta, con una conversión
previa al tipo declarado en el encabezamiento). Los paréntesis que engloban a la expresión que sigue a return
son optativos. El valor de retorno es un valor único: no puede ser un vector o una matriz, aunque sí un puntero
a un vector o a una matriz.
Sin embargo, el valor de retorno sí puede ser una estructura, que a su vez puede contener vectores y
matrices como elementos miembros.
Como ejemplo supóngase que se va a calcular a menudo el valor absoluto de variables de tipo double. Una
solución es definir una función que reciba como argumento el valor de la variable y devuelva ese valor
absoluto como valor de retorno. La definición de esta función podría ser como sigue:
double valor_abs(double x)
{
if (x < 0.0)
return -x;
else
return x;
}
tipo_valor_de_retorno nombre_funcion(lista_de_tipos_de_parámetros);
Esta forma general coincide sustancialmente con la primera línea de la definición –el encabezamiento-, con
dos pequeñas diferencias: en vez de la lista de argumentos formales o parámetros, en el prototipo basta incluir
los tipos de dichos argumentos. Se pueden incluir también identificadores a continuación de los tipos, pero
son ignorados por el compilador. Además, una segunda diferencia es que el prototipo termina con un carácter
(;). Cuando no hay argumentos formales, se pone entre los paréntesis la palabra void, y se pone también void
precediendo al nombre de la función cuando no hay valor de retorno.
41
Los prototipos permiten que el compilador realice correctamente la conversión del tipo del valor de
retorno, y de los argumentos actuales a los tipos de los argumentos formales. La declaración de las funciones
mediante los prototipos suele hacerse al comienzo del fichero, después de los #define e #include. En muchos
casos –particularmente en programas grandes, con muchos ficheros y muchas funciones–, se puede crear un
fichero (con la extensión .h) con todos los prototipos de las funciones utilizadas en un programa, e incluirlo con
un #include “nombre_del_fichero.h” en todos los ficheros en que se utilicen dichas funciones.
a) Existe la posibilidad de definir funciones con un número variable o indeterminado de argumentos. Para
definir estas funciones se utilizan los puntos suspensivos (...), que representan los argumentos desconocidos
que puede haber. Un ejemplo de función de este tipo es el siguiente:
donde los argumentos i y a tendrían que estar siempre presentes. Para conocer con más detalle cómo se crean
estas funciones se recomienda acudir a alguno de los textos de C++ recomendados en la Bibliografía.
b) En C++ se pueden definir valores por defecto para todos o algunos de los argumentos formales. Después,
en la llamada, en el caso de que algún argumento esté ausente de la lista de argumentos actuales, se toma el
valor asignado por defecto a ese argumento. Por ejemplo, la función modulo() podía haberse declarado del
siguiente modo:
42
normal es que sea llamada incluyendo su nombre seguido de los argumentos actuales en una expresión
aritmética o de otro tipo. En este caso, la llamada a la función hace el papel de un operando más de la
expresión.
En otros casos, no existirá valor de retorno y la llamada a la función se hará incluyendo en el programa una
sentencia que contenga solamente el nombre de la función, siempre seguido por los argumentos actuales
entre paréntesis y terminando con un carácter (;).
La declaración y la llamada de la función valor_abs() antes definida, se podría realizar de la forma siguiente:
La función valor_abs() recibe un valor de tipo double. El valor de retorno de dicha función (el valor absoluto
de y), es introducido en la expresión aritmética que calcula z.
La declaración (double valor_abs(double)) no es estrictamente necesaria cuando la definición de la función
está en el mismo archivo que main() y está definida antes que la llamada.
FLUJOS DE ENTRADA/SALIDA
El lenguaje C++ no dispone de sentencias de entrada/salida. En su lugar se utilizan operadores contenidos
en la librería estándar y que forman parte integrante del lenguaje.
La librería de C++ proporciona algunas herramientas para la entrada y salida de datos que la hacen más
versátil, y más complicada en ocasiones, que la de C. En C++ las entradas son leídas desde streams y las salidas
son escritas en streams. La palabra stream quiere decir algo así como canal, flujo o corriente.
Los streams más utilizados para introducir y sacar datos son cin y cout respectivamente.
Para la utilización de dichos streams es necesario incluir, al comienzo del programa, el archivo iostream.h
en el que están definidos sus prototipos.:
#include <iostream.h>
Salida de datos
El stream o flujo de salida cout imprime en la unidad de salida (el monitor, por defecto), el texto, y las
constantes y variables que se indiquen. Para poder insertar datos en un stream es necesario utilizar el
operador de inserción (<<). Así, la forma general de utilizar el flujo de salida cout se puede estudiar viendo su
prototipo:
Explicación: El objeto cout imprime el texto contenido entre las comillas tal y como está escrito.
Entre las comillas se pueden incluir palabras reservadas (sin que C++ las interprete como tales) y las
secuencias de escape, las cuales C++ ejecuta y no escribe. Separados por el operador (<<) se pueden incluir
variables y/o expresiones (incluso las creadas por el usuario si el operador (<<) ha sido sobrecargado) que cout
es capaz de intepretar correctamente e imprimirá en pantalla su valor.
43
Considérese el ejemplo siguiente,
int i = 1;
double tiempo = 10;
float acel = 9.8;
const float Vo = 5;
cout << “Resultado numero: “ << i << “ En el instante “ << tiempo << “
la velocidad vale “ << Vo + acel * tiempo << “\n”;
en el que se imprimen 2 variables (i y tiempo) y una expresión (el cálculo de la velocidad). Esto será lo que
se imprimirá en pantalla:
Entrada de datos
El stream o flujo cin es análoga en muchos aspectos a cout, y se utiliza para leer datos de la entrada
estándar (que por defecto es el teclado). Junto con cin, también es necesario utilizar el operador de extracción
(>>). Así, la forma general de utilizar este flujo es la siguiente:
Explicación: El flujo cin almacena en las variables los datos introducidos por el teclado, interpretando el
final de cada una al encontrar un espacio o un final de línea.
Se puede ver la ventaja de los operadores propios de C++ para la extracción de datos de consola, y es el
hecho de que se evita el chequeo de compatibilidad de entre las variables. Es decir que la variable1 puede ser
tipo int y la variable2 tipo double.
Al trabajar con variables tipo char[ ], el flujo cin sólo acepta texto sin espacios. Si lo que se desea es
introducir en la misma variable varias palabras (texto con espacios incluidos, por ejemplo un nombre y
apellidos), se debe utilizar una función del flujo cin , de la siguiente forma:
donde límite_de_longitud indica que en la variable no se almacenarán más caracteres que los señalados y
carácter_delimitador precisa que si antes del límite se hallara el carácter señalado no se almacenaría nada más
en la variable.
Comando #include
Cuando en un archivo .cpp se encuentra una línea con un #include seguido de un nombre de archivo, el
preprocesador la sustituye por el contenido de ese archivo.
La sintaxis de este comando es la siguiente:
#include "nombre_del_archivo"
#include <nombre_del_archivo>
La diferencia entre la primera forma (con comillas "...") y la segunda forma (con los símbolos <...>) estriba
en el directorio de búsqueda de dichos archivos. En la forma con comillas se busca el archivo en el directorio
44
actual y posteriormente en el directorio estándar de librerías (definido normalmente con una variable de
entorno del MS-DOS llamada INCLUDE, en el caso de los compiladores de Microsoft). En la forma que utiliza
los símbolos <...> se busca directamente en el directorio estándar de librerías. En la práctica los archivos del
sistema (iostream.h, math.h, etc.) se incluyen con la segunda forma, mientras que los archivos hechos por el
propio programador se incluyen con la primera.
Este comando del preprocesador se utiliza normalmente para incluir archivos con los prototipos
(declaraciones) de las funciones de librería, o con módulos de programación y prototipos de las funciones del
propio usuario. Estos archivos suelen tener la extensión .h, aunque puede incluirse cualquier tipo de archivo
de texto.
45
strcmp(s1,s2) int Compara dos cadenas string.h
lexicográficamente.
strcomp(s1,s2) int Compara dos cadenas string.h
lexicográficamente, sin considerar
mayúsculas o minúsculas.
strcpy(s1,s2) char * Copia la cadena s2 en la cadena s1 string.h
strlen(s1) int Retorna el número de caracteres string.h
en la cadena s.
system(s) int Pasa la orden s al sistema stdlib.h
operativo.
tan(d) double Retorna la tangente de d math.h
time(p) long int Retorna el número de segundos time.h
transcurridos después de un
tiempo base designado.
toupper(c) int convierte una letra a mayúscula stdlib.h o ctype.h
Nota: La columna tipo se refiere al tipo de la cantidad devuelta por la función. Un asterisco denota puntero,
y los argumentos que aparecen en la tabla tienen el significado siguiente:
46