Documente Academic
Documente Profesional
Documente Cultură
C/C++
i
CAPITULO 1
Lenguajes de programación.
Definición.
Lenguajes compilados.
Lenguajes interpretados.
Traductores.
Definición.
Figura 1. 1. Traductor.
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
Análisis sintáctico
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,
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
Fase de síntesis
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.
Optimización de código
Tipos de traductores
Intérprete
Compilador
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.
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).
CAPITULO 2
2.1.1.1. Abstracción.
2.1.1.2. Encapsulamiento.
2.1.1.4. Polimorfismo
2.1.1.5. Herencia.
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.
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;
...
}
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
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).
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:
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:
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.
void main()
{ Instrucción_a;
{ Instrucción_b;
Instrucción_c;
}
}
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
"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:
2.3.1.2. Directivas
#define, sirve para definir macros. Las macros suministran un sistema para la
sustitución de palabras, con y sin parámetros.
2.3.1.4. Identificadores.
/*
Este es un programa simple en C++, escribe una frase
en la pantalla
*/
#include <iostream.h>
int main( )
{
cout << "Hola mundo\n"; // imprime en la pantalla la frase "hola mundo"
return 0;
}
CAPITULO 3
TIPOS DE DATOS
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:
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:
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:
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:
Hemos definido las constantes CERO, UNO y DOS con los valores 0, 1 y 2.
3.1.3. Tipos derivados
* 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.
[] 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:
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.
Figura 3. 2. Variable.
tipodato nombrevariable;
Ejemplos:
int a;
float d;
char e;
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)
Ejemplos:
int a = 5;
int a = 2, b = 4, c = 6;
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.
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
…
}
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
}
ejemplo( )
{
static int x=0;
x++;
...
}
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
(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 ));
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
#include <stdio.h>
#define PI 3.1415926 // constante definida PI
int main()
{
printf("Pi vale %f", PI);
return 0;
}
Pi vale 3.1415926
#define X 2.4
#define Y 9.2
#define Z X + Y
#include <stdio.h>
#define escribe printf
main()
{
int r;
escribe("Ingrese un numero: ");
scanf("%d",&r);
escribe("El cuadrado del numero es: %d",r*r);
}
Hemos definido las constantes MALO, BUENO y REGULAR con los valores
0, 1 y 2.
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:
Ejemplo:
3.3.1. Operadores
+ <expresión>
- <expresión>
<variable> ++ // (post-incremento)
++ <variable> // (pre-incremento)
<variable>-- // (post-decremento)
-- <variable> // (pre-decremento)
c = a + ++b;
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++.
Expresión1 op Expresión2
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ón !Expresión
false true
true false
Figura 3. 7. Tabla de verdad del operador "!".
sizeof (<expresión>)
sizeof (nombre_de_tipo)
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)
^
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 = a >> 2;
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)
int edad;
cout << "Cual es tu edad: ";
cin >> edad;
cout << ( (edad < 18) ? "Eres joven aun" : "Ya tienes la mayoría de edad" );
3.4. Expresiones.
a + 5*b
(a >= 0) && ((b+5) > 10)
a
-a * 2 + b
--b + (- 4*a*c)
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)
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:
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)
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
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
{ int i = 1, j = 2;
double peso;
peso = 5.5;
j = i + j;
}
#include <nombrelibreria>
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.
cout<<variable1<<variable2<<...<<variablen;
Las cadenas de texto son variables y se ponen entre " " (comillas dobles).
printf("Color %s, num1 %d, num2 %5d, real %5.2f.\n", "rojo", 12, 8, 3.4);
Carácter Descripción
Esto imprime un número como un carácter ASCII. Por lo que, `printf
‘c’ "%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.
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
‘e’
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.
Esto imprime en notación científica o en notación punto flotante, la que
‘g’
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.
Esto imprime un entero hexadecimal sin signo. Sin embargo, para los
‘X’ 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
int edad = 5;
printf("edad2: %d, edad1: %d, edad inicial: %d", edad, ++edad, edad+
+);
puts(direccion_cadena_caracteres);
ó
puts(“literales”);
Ejemplo:
char cadena[25]=”Casa”;
// se declara una cadena de caracteres {‘C’,’a’,’s’,’a’,’\0’}
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
putchar (variable_caracter);
ó
putchar (valor_caracter);
ó
putchar (‘simbolo_caracter’);
Ejemplo:
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;
#include <iostream.h>
void main ()
{
int i;
cout<<"Introduce un número";
cin>>i; // atrapa un valor entero ingresado por teclado
}
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
}
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
char letra;
letra=getchar( );
// atrapa un carácter y lo retorna después del carácter intro.
char letra;
letra=getche( );
// atrapa un carácter al momento de presionarlo y lo muestra
CAPITULO 4
ESTRUCTURAS DE CONTROL
4.1. Concepto.
if (expresión)
{
//sentencias
}
if (expresión)
sentencia_unica;
int x=6;
if(x>5)
k++;
#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
{
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";
}
}
#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";
}
if(num!=-1)
cout<<"El Numero es ="<<num;
if(num==-1)
cout<<"Carácter no es un símbolo numérico";
}
if (expresión)
{
//sentencias condición verdadera
}
else
{
// sentencias condición falsa
}
if(expresión)
sentencia_unica1;
else
sentencia_unica2;
x=0;
if( x ==5)
{ k++;
}
else
{ k--;
}
#include <iostream.h>
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";
}
}
#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";
}
#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";
}
}
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
}
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
}
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;
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";
}
}
int c;
...
scanf ("%d", &c);
switch (c) { case 1:
case 2: Funcion2 ();
case 3: Funcion3 ();
break;
case 4: Funcion4_1 ();
Funcion4_2 ();
break;
case 5: Funcion_5 ();
default: FuncionX ();
}
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:
int i;
for (i = 1; i <= 10; i++)
{
printf ("\nValor de i: %d", i);
printf ("|tValor de i2: %d|n", i * i);
}
char letra;
for (letra = 'A'; letra <= 'Z'; letra++)
cout<< letra<<endl;
int i;
for (i = 100; i >= 1; i = i - 2)
cout << i <<"\t ";
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;
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 ();
}
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 ( ; ; )
{
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);
}
}
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.
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 .......... ;
}
char c;
while (c != 's' && c != 'n')
c = getche ();
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;
while ( contador < 100 )
{ contador++;
cout<<"\t "<<contador;
}
cout<<"Presione Enter para salir";
}
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:
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.
#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));
#include <iostream.h>
void main ()
{
char tecla;
do {
cout<<”Pulse S o N: "<<endl;
tecla = getch ();
} while (tecla != 'S' && tecla != 'N');
}
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.
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.
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}};
int listanum[10];
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];
}
int i;
cout<<”Los elementos del arreglo:”<<endl;
for ( i = 0; i < 10; i++)
{
cout<<A[i]<<”\t”;
}
#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.
// Bidimensional
tipo_de_dato identificador[tamaño1][tamaño2];
// Tridimensional
tipo_de_dato identificador[tamaño1][tamaño2][tamaño3];
dónde:
tipo_de_dato nomb_matriz[fila][columnas];
tipo_de_dato nomb_arreglo[fila][columnas][elementos];
float matriz[10][10];
a = matriz [2][1];
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
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:
Por ejemplo:
#include <conio.h>
#include <iostream.h>
#define max 100
void main()
{ int num[max][max];
int i, j, n, div, contprimos;
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;
}
#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;
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;
}
#include <conio.h>
#include <iostream.h>
#define max 20
void main()
{ int num[max][max];
int i, j, k, n, aux, auxA[max*max];
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];
#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];
}
char cadena[8];
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:
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();
#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];
}
}
Es equivalente a:
char nombres[3][5];
nombres[0] = "HUGO" ; // no aplicable
nombres[1] = "PACO" ; // no aplicable
nombres[2] = "LUIS" ; // no aplicable
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' ;
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' ;
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.
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.
Compara parte de una cadena con parte de otra, sin distinguir entre mayúsculas
strncmpi
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.
Busca la primera aparición, en una cadena, de cualquier carácter de un
strpbrk
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.
Busca en una cadena el primer segmento que es un subconjunto de un conjunto
strspn
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.
#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;
}
#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:”
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;
#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<< "] = " ;
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;
APUNTADORES
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:
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’;
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;
}
6.2. Apuntadores.
6.2.1. Concepto.
Ejemplo:
int *pint ;
double *pfloat ;
char *letra , *codigo , *caracter ;
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:
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) ;
#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
for (var1 = 'a'; var1 <= 'z'; var1++)
printf("%c", *pchar) ; // imprimimos el valor de la variable apuntada
}
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 ; // ??????
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
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.
6.2.6. Aritmética de referencia
int *p , a[] = { 0 , 10 , 20 , 30 , 40 , 50 , 60 , 70 , 80 , 90 } ;
int var ;
p=a;
*p = 27 ;
var = *p ;
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.
En cambio la expresión:
var = *( p++ ) ;
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:
#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 ;
cout<< palabra ;
cout<< 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;
}
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;
char *flecha[5];
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];
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:
tipo_de_variable_apuntada **nomb_var_puntero_a_puntero ;
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
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].
double *p ;
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) ;
p = malloc( sizeof(double) ) ;
if( p == NULL )
rutina_de_error() ;
char * r;
r = (char *) malloc (10);
int * b;
b = (int *) malloc (5 * sizeof(int));
int *b;
b = (int *) calloc (5, sizeof(int));
Otra diferencia entre malloc y calloc es que calloc inicializa todos sus
elementos a 0.
Esta función debe ser usada solamente para liberar memoria asignada con las
funciones malloc, calloc y realloc.
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:
int *b;
b = new int [5];
int *b;
b = new int [5];
if (bobby == NULL)
{
// error asignando memoria.
};
6.3.6. Operador delete
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.
#include<stdlib.h>
#include<iostream.h>
void main()
{
int *dato_simple;
dato_simple = (int *) malloc (3*sizeof(int));
}
#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];
}
cout<<”Los números ingresados:”;
for(i=0; i<n; i++)
cout<<datos[i]<<endl;
}
CAPITULO 7
FUNCIONES
7.1. Concepto.
[extern|static]<tipovalor_retorno> <identificador>(<lista_parámetros>);
Por ejemplo:
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:
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:
// 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();
}
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.
#include <iostream.h>
void precio(double);
void main(void)
{
double costo;
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;
}
#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;
}
#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;
}
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.
#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:”
cin>>n;
cout<<”Ingrese los nombres:”<<endl;
Leer(nom, n);
for(i = 0; i<n ; i++)
Mayuscula( nom[i]);
Mostrar(a[i],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 main()
{ 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;
}
#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;
}
long int factorial(unsigned short int n)
{ if(n==0) return 1;
else return n*(factorial(n-1)) ;
}
Numero de
Valor de n Resultado
invocación
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
2 * (factorial(1)) = 2 * 1 = 2
3 * (factorial(2)) = 3 * 2 = 6
4 * (factorial(3)) = 4 * 6 = 24
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
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.
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.
Nombre_variable_estructura.dato_miembro;
Una vez declarada una estructura, las variables permiten las siguientes
operaciones:
#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;
}
Datos Ordenados:
10988888 Juana Ruiz
12924345 Maria Lopez
nombre_nuevo_tipo nombre_coleccion[Cantidad_elementos];
#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"
};
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);" }
puntero_estructura->dato_miembro;
apd->ingl;
apd->espa;
#include <iostream.h>
#include <conio.h>
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
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:
Se pueden accesar los miembros de varnums a través del operador punto, por
ejemplo:
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
};
8.3.1.1. Atributos.
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
void fun() { return y; } // Funciones Miembros o Métodos
…
};
8.3.1.2. Métodos.
class punto
{ int x,y ;
public:
int dax() { return x ; } // Define el método dentro de la clase
};
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 ;
}
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");
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; }
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 )
CAPITULO 9
referencia.metodo (parametros);
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.
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 NomClase { …
NombClase() // Constructor por defecto
{ // inicializaciones de atributos
}
};
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 ;
}
};
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
class NomClase { …
~NombClase() // destructor
{ }
};
class punto
{ int x,y ;
public:
int dax() { return x ; }
int day() { return y ; }
punto(int nx, int nx) ;
~punto() ; // Declaracion del Destructor
};
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;
...
};
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;
...
};
void main()
{ punto a, b(10,10);
a = b;
}
#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);
cout << "La suma es: " << c->getReal() << "+" <<
c>getComp() << "i" << endl;
delete c;
}
CAPITULO 10
HERENCIA
10.1. Herencia.
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:
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"; }
};
#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
};
#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; };
int getj() { return j; }
int getk() { return k; }
};
void main()
{
derivada obj;
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:
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):
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 B { ... };
class C1 : public B { ... };
class C2 : public B { ... };
class D : public C1, C2 { ... };
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();
};
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;
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();
}