PROGRAMACIÓN ORIENTADA A OBJETOS (072-2103)
Unidad I: Introducción
1.1 Historia del Computador
1.2 Lenguajes de Programación
1.3 Compilación de Programas
1.4 Paradigmas de Programación
Unidad II: Paradigma Orientado a Objetos
2.1 Generalidades
2.2 Clases y Objetos
2.3 Programación en Consola
Unidad III: Datos Simples
3.1 Variables
3.2 Tipos de Datos
3.3 Expresiones, Operadores y Asignaciones
Unidad IV: Estructuras de Control
4.1 Estructuras de Control Selectivas
4.2 Estructuras de Control Iterativas
Unidad V: Datos Compuestos
5.1 Arreglos
5.2 Cadenas
5.3 Estructuras y Enumeraciones
Unidad VI: Fundamentos Avanzados de Programación
6.1 Propiedades de uso, modificadores de parámetros y regiones
6.2 Recursividad
6.3 Apuntadores y clases autoreferenciadas
6.4 Herencia
6.5 Interfaces
Programación Orientada a Objetos
2
TABLA DE CONTENIDO
Unidad I: Introducción
1.1 Historia de la computación............................................................................................ 5
1.1.1 La computadora ............................................................................................................. 5
1.1.2 Prehistoria de la computación ....................................................................................... 5
1.1.3 Generaciones de la computadora .................................................................................. 6
1.2 Lenguajes de programación .......................................................................................... 9
1.2.1 Tipos de lenguajes ......................................................................................................... 9
1.2.2 Criterios del lenguaje .................................................................................................. 10
1.3 Compilación de programas ......................................................................................... 11
1.3.1 Tipos de compiladores ................................................................................................ 12
1.3.2 Proceso de compilación............................................................................................... 12
1.3.3 Etapas de la compilación ............................................................................................. 13
1.3.4 Otras herramientas de programación .......................................................................... 13
1.4 Paradigmas de programación ...................................................................................... 14
Unidad II: Paradigma orientado a objetos
2.1 Generalidades .............................................................................................................. 16
2.1.1 Conceptos dentro de la POO ....................................................................................... 16
2.1.2 Ventajas ....................................................................................................................... 17
2.2 Clases y objetos ........................................................................................................... 17
2.2.1 Acceso a los miembros de una clase ........................................................................... 19
2.2.2 Constructores y destructores ....................................................................................... 21
2.2.3 Ventajas del uso de clases ........................................................................................... 23
2.3 Programación en consola ............................................................................................ 24
2.3.1 Buffer de pantalla y ventana de la consola.................................................................. 24
2.3.2 La clase console .......................................................................................................... 25
2.3.3 Entrada y salida por consola ....................................................................................... 25
2.3.4 Métodos para personalizar la consola ......................................................................... 28
2.4 Ejercicios propuestos .................................................................................................. 30
Programación Orientada a Objetos
3
Unidad III: Datos Simples
3.1 Variables ..................................................................................................................... 31
3.1.1 Reglas para asignar nombres de variables .................................................................. 31
3.1.2 Tipos de identificadores .............................................................................................. 32
3.2 Tipos de datos ............................................................................................................. 32
3.3 Expresiones, operadores y asignaciones ..................................................................... 33
3.3.1 Operadores aritméticos................................................................................................ 34
3.3.2 Expresiones y operadores relacionales........................................................................ 35
3.3.3 Expresiones y operadores lógicos ............................................................................... 35
3.3.4 Operadores incrementales y decrementales ................................................................ 36
3.3.5 Operadores compuestos de asignación........................................................................ 36
3.3.6 Operador condicional .................................................................................................. 37
3.3.7 Precedencia de operadores .......................................................................................... 37
3.4 Ejercicios propuestos .................................................................................................. 39
Unidad IV: Estructuras de control
4.1 Estructuras de control selectivas ................................................................................. 40
4.1.1 La estructura de selección simple: if ........................................................................... 40
4.1.2 La estructura de selección doble: if/else ..................................................................... 41
4.1.3 La estructura de selección múltiple: switch/case ........................................................ 47
4.2 Estructuras de control iterativas .................................................................................. 48
4.2.1 La estructura iterativa for ............................................................................................ 49
4.2.2 La estructura iterativa while ........................................................................................ 51
4.2.3 La estructura iterativa do-while .................................................................................. 53
4.2.4 Sentencias break y continue ........................................................................................ 54
4.3 Ejercicios propuestos: ................................................................................................. 55
Unidad V: Datos compuestos
5.1 Arreglos ....................................................................................................................... 57
5.1.1 Arreglo unidimensional ............................................................................................... 57
5.1.2 Arreglos anidados........................................................................................................ 63
Programación Orientada a Objetos
4
5.1.3 Arreglos multidimensionales ...................................................................................... 64
5.1.4 La clase System.array.................................................................................................. 67
5.2 Cadenas ....................................................................................................................... 68
5.2.1 Operadores de igualdad y concatenación .................................................................... 69
5.2.2 Métodos de manejo de cadenas ................................................................................... 70
5.3 Estructuras y Enumeraciones ...................................................................................... 72
5.3.1 Estructuras ................................................................................................................... 72
5.3.2 Enumeraciones ............................................................................................................ 74
5.4 Ejercicios propuestos .................................................................................................. 75
Unidad VI: Fundamentos avanzados de programación
6.1 Propiedades de uso, modificadores de parámetros y regiones .................................... 77
6.1.1 Propiedades de uso: get y set ...................................................................................... 77
6.1.2 Modificadores de parámetros: ref, in y out ................................................................. 79
6.1.3 Regiones ...................................................................................................................... 83
6.2 Recursividad ................................................................................................................ 84
6.3 Referencias internas .................................................................................................... 89
6.3.1 Clases autoreferenciadas ............................................................................................. 92
6.3.2 Código inseguro y apuntadores ................................................................................... 93
6.4 Herencia ...................................................................................................................... 95
6.5 Interfaces ..................................................................................................................... 97
6.6 Ejercicios propuestos .................................................................................................. 99
Programación Orientada a Objetos
5
UNIDAD I: INTRODUCCIÓN
1.1 Historia de la computación
1.1.1 La computadora
Es una colección de circuitos integrados y otros componentes relacionados capaz de efectuar
una secuencia de operaciones mediante un programa, de tal manera que recibe y procesa un
conjunto de datos de entrada para convertirlos en información útil.
1.1.2 Prehistoria de la computación
Uno de los primeros dispositivos mecánicos para contar fue el ábaco, cuya historia se
remonta a las antiguas civilizaciones griega y romana, el cual tenía cuentas ensartadas en
varillas que, a su vez, estaban montadas en un marco rectangular. Al desplazar las cuentas
sobre las varillas, sus posiciones representaban valores determinados. Por supuesto, este
dispositivo no podía considerarse como una computadora, ya que carecía de un elemento
fundamental llamado programa.
Otros dispositivos mecánicos fueron la Pascalina, inventada por el francés Blaise
Pascal (1623 – 1662), y la máquina inventada por el alemán Gottfried Wilhelm Von Leibniz
(1646 – 1716), con las cuales los datos se representaban mediante las posiciones de los
engranajes, y los mismos se introducían manualmente estableciendo las posiciones finales de
la rueda, de forma similar a cómo se cuentan los kilómetros recorridos en el cuentakilómetros
de un automóvil.
En el siglo XIX Charles Babbage trabajaba en el proyecto de la “máquina de
diferencias”, es decir, aquella que realizaba una serie de sumas sucesivas, con la cual buscaba
solucionar el problema que implica la elaboración de tablas matemáticas ya que era una tarea
tediosa y propensa a errores. Sin embargo, en el año 1823 Charles Jaquard había inventado
un telar que podía reproducir patrones de tejidos leyendo la información codificada en
Programación Orientada a Objetos
6
patrones de agujeros perforados en papel rígido. Esto sirvió de inspiración a Babbage para
que abandonase la máquina de diferencias y se dedicase a la “máquina analítica”, un
dispositivo que podría ser programado con tarjetas perforadas para efectuar cálculos con una
precisión de hasta 20 dígitos. Desafortunadamente, la tecnología de la época no bastaba para
hacer realidad sus ideas.
Unos 100 años después, concretamente en el año 1944, se construyó en la Universidad
de Harvard la MARK I, diseñada por un equipo encabezado por Howard E. Aiken, cuyo
funcionamiento estaba basado en dispositivos electromecánicos llamados relevadores.
En 1947 se construyó en la Universidad de Pensilvania la ENIAC (Electronic
Numerical Integrator And Calculator) por el equipo de diseño dirigido por John Mauchly y
John Eckert, la cual ocupaba todo un sótano de la universidad, tenía más de 18 mil tubos de
vacío, consumía 200 KW de energía y requería todo un sistema de aire acondicionado, la
cual tenía capacidad para realizar 5 mil operaciones aritméticas por segundo. Esta máquina
marcó el inicio de las generaciones de la computadora.
1.1.3 Generaciones de la computadora
Se definen tomando en cuenta la forma en que fueron construidas, y la manera en que el ser
humano se comunica con ellas, además de las tecnologías empleadas.
1ª Generación: Tubos de Vacío (1947 – 1958)
• Utilizaban tubos de vacío para procesar la información.
• Empleaban tarjetas perforadas para realizar programas.
• La memoria principal consistía en un tambor magnético.
• Eran de un inmenso tamaño y gran peso.
• Costaban alrededor de US$ 10.000
• Surge el lenguaje máquina (código binario)
• Máquinas representativas: ENIAC y UNIVAC.
Programación Orientada a Objetos
7
2ª Generación: Transistores (1958 – 1964)
• Uso de transistores para procesar la información.
• Reducción del tamaño y emisión de calor.
• Menor consumo eléctrico.
• Eran comercialmente accesibles.
• Las computadoras de ese período eran muy avanzadas para la época.
• Se define la programación de sistemas.
• Máquinas representativas: PHILCO 212 y UNIVAC M460.
3ª Generación: Circuitos Integrados (1964 – 1971)
• Uso de circuitos integrados (inventados en 1959) para procesar la información.
• Las computadoras son mucho más pequeñas y producen menos calor.
• Son enérgicamente más eficientes.
• Surge la multiprogramación.
• Máquina representativa: IBM 360.
4ª Generación: Microprocesadores (1971 – 1981)
• Uso del microprocesador (inventado en 1971) para procesar información.
• Desarrollo de las microcomputadoras y supercomputadoras.
• Reemplazo de las memorias con núcleos magnéticos por chips de silicio.
• Uso de los Sistemas Operativos.
5ª Generación: Inteligencia Artificial (1981 – 1990)
• Avances en el desarrollo del software y la robótica.
• Se crea la primera súper computadora con capacidad de proceso en paralelo.
• Desarrollo de sistemas expertos e inteligencia artificial.
• Uso del lenguaje natural.
• Almacenamiento en dispositivos magnético-ópticos extraíbles.
Programación Orientada a Objetos
8
6ª Generación: Arquitectura Paralelo/Vectorial (1990 – 1999)
• Utilizan cientos de microprocesadores vectoriales trabajando en paralelo.
• Se crearon computadoras capaces de realizar millones de operaciones aritméticas en
punto flotante (teraflops) por segundo.
• Uso de las redes WWW y WAN para comunicar las computadoras entre sí.
• Uso de fibra óptica con gran ancho de banda y satélites para las comunicaciones.
• Desarrollo de tecnologías con inteligencia artificial distribuida, teoría de caos, holografía,
transistores ópticos, etc.
7ª Generación: LCD y Dispositivos Inteligentes (1999 – 2011)
• Aparecen las pantallas planas LCD2 y se hacen a un lado las de rayos catódicos.
• Se crea el Blu-Ray Disc, reemplazando al DVD.
• Almacenamiento de datos de alta densidad.
• Reemplazo de TV’s y equipos de sonido por dispositivos digitales.
• Aparecen las computadoras portátiles (laptops), dispositivos de bolsillo (PDA’s),
computadoras ópticas y cuánticas.
• Uso masivo de mensajería y correo electrónico.
• Tecnología de reconocimiento de voz y escritura, además de la realidad virtual y
dispositivos inalámbricos.
• Súper cómputo (procesadores paralelos masivos).
• Proliferación de los Smartphone y memorias compactas.
8ª Generación: Nuevas Tecnologías (2011 – Presente)
• Se inicia con el lanzamiento del Nintendo 3DS (25 de febrero de 2011), seguido por la
Sony PlayStation Vita (17 de diciembre de 2011).
• Empleo de tecnologías de última generación, como la estereoscopía y detección del
movimiento corporal.
• Mayor capacidad de almacenamiento en los dispositivos móviles.
• A futuro se plantea la desaparición de dispositivos físicos y de almacenamiento
mecánicos (disco duro, tarjeta madre), además del uso de la nanotecnología.
Programación Orientada a Objetos
9
1.2 Lenguajes de programación
Un programa es una secuencia de instrucciones mediante las cuales se ejecutan diferentes
acciones de acuerdo con los datos que se estén procesando, el cual está desarrollado para ser
utilizado por la computadora.
Por su parte, la programación es el proceso de diseñar, escribir, probar, depurar y
mantener el código fuente de programas computacionales. El código fuente es escrito en un
lenguaje de programación. El propósito de la programación es crear programas que exhiban
un comportamiento deseado. El proceso de escribir código requiere frecuentemente
conocimientos en varias áreas distintas, además del dominio del lenguaje a utilizar,
algoritmos especializados y lógica formal.
Programar no involucra necesariamente otras tareas tales como el análisis y diseño de
la aplicación (pero si el diseño del código), aunque si suelen estar fusionadas en el desarrollo
de pequeñas aplicaciones.
Obviamente, para llevar a cabo esta tarea se requiere de un lenguaje de programación,
el cual no es más que un sistema de símbolos y reglas que permite la construcción de
programas con los que la computadora puede operar así como resolver problemas de manera
eficaz. Estos contienen un conjunto de instrucciones que permiten realizar operaciones de
entrada / salida, calculo, manipulación de textos, lógica / comparación y almacenamiento /
recuperación.
1.2.1 Tipos de lenguajes
• Lenguaje Máquina: Son aquellos cuyas instrucciones son directamente entendibles por
la computadora y no necesitan traducción posterior para que la CPU pueda comprender
y ejecutar el programa. Las instrucciones en lenguaje maquina se expresan en términos
de la unidad de memoria más pequeña el bit (dígito binario 0 ó 1).
Programación Orientada a Objetos
10
• Lenguaje de Bajo Nivel (Ensamblador): En este lenguaje las instrucciones se escriben
en códigos alfabéticos conocidos como mnemotécnicos para las operaciones y
direcciones simbólicas. Los lenguajes de bajo nivel permiten crear programas muy
rápidos, pero que a menudo son difíciles de entender. Más importante es el hecho de que
los programas escritos en bajo nivel son prácticamente específicos para cada procesador.
• Lenguaje de Alto Nivel: Los lenguajes de programación de alto nivel (BASIC, pascal,
cobol, fortran, etc.) son aquellos en los que las instrucciones o sentencias a la
computadora son escritas con palabras similares a los lenguajes humanos (en general en
inglés), lo que facilita la escritura y comprensión del programa. Éstos son normalmente
fáciles de aprender porque están formados por objetos de lenguajes naturales, sin
embargo, para muchas personas esta forma de trabajar es un poco frustrante dado que, en
realidad, las computadoras suelen operar de forma rígida y sistemática.
1.2.2 Criterios del lenguaje
Son características interrelacionadas empleadas para considerar si un lenguaje tiene méritos,
es decir, si es eficiente, confiable y es capaz de proporcionar los mejores resultados.
• Sintaxis: Es la forma del lenguaje, es decir, una colección de instrucciones formadas al
seguir un conjunto de reglas que diferencian los programas válidos de nos no válidos.
• Semántica: Describe de manera precisa lo que significa una construcción particular, ya
que cada lenguaje de programación podría interpretar de manera distinta las instrucciones
escritas exactamente igual.
• Comprobabilidad:
o El programa cumple con la intención del programador.
o El compilador traduce de manera correcta a código de máquina la sintaxis y
semántica del programa en el lenguaje empleado.
o La máquina misma funciona correctamente.
Programación Orientada a Objetos
11
• Confiabilidad: El programa se considera confiable si se comporta como es anunciado y
produce los resultados que el usuario espera.
• Traducción Rápida: La traducción del código fuente a un lenguaje que la máquina
pueda reconocer, y luego a un código máquina que la misma pueda ejecutar, debe ser lo
más rápida y óptima posible.
• Corrección: Un programa es correcto si hace lo que debe hacer tal y como se estableció
en las fases previas a su desarrollo.
• Claridad: Es muy importante que el programa sea lo más claro y legible posible, para
facilitar así su desarrollo y posterior mantenimiento. Al elaborar un programa, se debe
intentar que su estructura sea sencilla y coherente, así como cuidar el estilo de la edición.
• Eficiencia: Se trata de que el programa, además de realizar aquello para lo que fue
creado, lo haga gestionando de la mejor forma los recursos que utiliza, para lo cual es
necesario que exista un balance comparativo entre el trabajo que el programador debe
hacer y el trabajo que el compilador puede hacer.
• Ortogonalidad: Las variables aleatorias se consideran ortogonales si son independientes
entre sí. De esta forma los componentes son independientes entre sí y se comportan de la
misma manera bajo cualquier circunstancia.
• Portabilidad: Cuando el programa puede compilarse y ejecutarse en una plataforma
(Hardware o Software) diferente a aquella en la cual se elaboró.
1.3 Compilación de programas
Un compilador es un programa que permite traducir el código fuente (conjunto de
instrucciones que describe el funcionamiento de un programa determinado), realizado en
lenguaje de alto nivel, a otro de nivel inferior (típicamente a lenguaje máquina). De esta
Programación Orientada a Objetos
12
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.
1.3.1 Tipos de compiladores
• Cruzados: Generan código para un sistema distinto del que están funcionando.
• Optimizadores: Realizan cambios en el código para mejorar su eficiencia, manteniendo
la funcionalidad del programa original.
• De una sola pasada: Generan el código máquina a partir de una única lectura del código
fuente.
• De varias pasadas: Necesitan leer el código fuente varias veces antes de poder producir
el código máquina.
• JIT (Just In Time): Forma parte de un intérprete y compilan muestras del código según
sus necesidades.
1.3.2 Proceso de compilación
Normalmente, la creación de un programa ejecutable conlleva dos pasos:
• Compilación: Traduce el código fuente escrito en un lenguaje de alto nivel a bajo nivel,
creando un código objeto que puede ser en lenguaje máquina o bytecode (es más
abstracto que el código máquina) y distribuirse en varios ficheros.
• Enlazado: Se enlaza el código de bajo nivel generado de todos los ficheros y
subprogramas que se han mandado a compilar, y se añade el código de las funciones que
hay en las bibliotecas del compilador, traduciendo así el código objeto a lenguaje
máquina, generando un módulo ejecutable.
Programación Orientada a Objetos
13
1.3.3 Etapas de la compilación
• Fase de Análisis:
o Análisis Léxico: Se lee el programa fuente de izquierda a derecha y se agrupa en
componentes léxicos llamados tokens (secuencias de caracteres que tienen un
significado determinado).
o Análisis Sintáctico: Los tokens se agrupan jerárquicamente en frases
gramaticales que el compilador utiliza para sintetizar la salida.
o Análisis Semántico: Se revisa el programa fuente para tratar de encontrar errores
semánticos, reuniendo la información sobre los tipos para la fase posterior de
generación de código.
• Fase de Síntesis: Consiste en generar el código objeto equivalente al programa fuente, y
sólo se realiza esta acción cuando el programa fuente está libre de errores de análisis
(aunque esto no implica que el programa corra bien).
o Generación de código intermedio: Algunos compiladores generan una
representación intermedia explícita del programa fuente, el cual debe ser fácil de
producir y fácil de traducir al programa objeto.
• Fase de Optimización del Código: Mejora el código intermedio, de modo que resulte
un código máquina más rápido de ejecutar.
1.3.4 Otras herramientas de programación
Además del compilador, existen otras herramientas de programación con funciones muy
similares, las cuales son:
• Ensamblador: Traduce un fichero fuente en lenguaje ensamblador (bajo nivel) a código
máquina.
• Enlazador: Toma los objetos generados en los primeros pasos de compilación y la
información de biblioteca, (salvo lo que no necesita), y crea un archivo ejecutable.
Programación Orientada a Objetos
14
• Intérprete: Es un programa informático capaz de analizar y ejecutar otros programas. Se
diferencia del compilador en que, mientras éste traduce un programa desde su descripción
en un lenguaje de programación al código máquina, los intérpretes sólo la traducción a
medida que sea necesaria, típicamente, instrucción por instrucción, por lo que
normalmente no guardan los resultados de dicha traducción, lo que los hace mucho más
lentos, pero a su vez menos susceptibles de provocar fallos.
1.4 Paradigmas de programación
Son colecciones de características abstractas que categorizan un grupo de lenguajes que son
aceptados y utilizados por un grupo de profesionales, representando un enfoque particular o
filosofía para diseñar soluciones. Éstos están delimitados en el tiempo en cuanto a aceptación
y uso, ya que nuevos paradigmas aportan nuevas o mejores soluciones que los sustituyen
parcial o totalmente.
Algunos tipos de paradigmas conocidos en la actualidad son:
• Imperativos: Facilitan cálculos por medio de cambios de estados, pues se basan en dar
instrucciones al ordenador de cómo hacer las cosas en forma de algoritmo. El ejemplo
principal de este tipo de paradigmas es el lenguaje de máquina.
• De Procedimientos: Constituidos por bloques de programas (secciones de código con
una o más declaraciones y sentencias), que forman un archivo plano, en donde cada
bloque sigue a su predecesor, constituyéndose así un programa lineal. Ejemplo:
FORTRAN.
• Estructurados en Bloques: Están constituidos por ámbitos anidados, es decir, los
bloques pueden estar dentro de otros bloques e incluso tener sus propias variables.
Ejemplo: Basic, Pascal, C.
• Basados en Objetos: Describen a los lenguajes que soportan objetos en interacción,
además de la abstracción, encapsulación y polimorfismo. Ejemplo: ADA, Modula,
SmallTalk.
Programación Orientada a Objetos
15
• Orientados a Objetos: Incluyen a los lenguajes que están basados en objetos y, además,
soportan clases de objetos y herencia de atributos. Ejemplo: C++, C#, Object Pascal
(Delphi) y Java.
• De Procedimiento en Paralelo: Describen los lenguajes que soportan el uso de
procesadores múltiples, la cooperación entre procesos y el potencial para falla parcial
(esto último implica que algún proceso puede fallar sin peligrar la ejecución de los
demás). Ejemplo: Concurrent C, Pascal S, C-Linda.
• De Programación Lógica: Están basados en un subconjunto del cálculo de predicados
o relación de elementos, proporcionando axiomas y reglas de inferencia que permiten
inferir nuevos hechos a partir de hechos conocidos. Ejemplo: Prolog.
• Funcionales: Es cuando el lenguaje opera solamente a través de funciones. Ejemplo:
Scheme y Haskell.
• Declarativos: Se basan en el desarrollo de programas declarando propiedades y reglas
que deben cumplirse, en lugar de instrucciones. Ejemplo: Maude y ML.
• Dirigidos por Eventos: Tanto la estructura como la ejecución de los programas son
determinados por los sucesos que ocurran en el sistema, ya sean definidos por el usuario
o provocados por ellos mismos. Ejemplo: Lexico y Visual Basic.
• Específicos del Dominio: Conocidos como DSL, se denomina así a los lenguajes
desarrollados para resolver un problema en específico. Puede pertenecer a cualquiera de
los paradigmas antes descritos. Ejemplo: SQL (declarativo) y LOGO (imperativo).
• Multiparadigma: Es el uso de dos o más paradigmas dentro de un programa. Ejemplo:
Lisp y Python.
Programación Orientada a Objetos
16
UNIDAD II: PARADIGMA ORIENTADO A OBJETOS
2.1 Generalidades
La programación orientada a objetos ha tomado las mejores ideas de la programación
estructurada y los ha combinado con varios conceptos nuevos y potentes que incitan a
contemplar las tareas de programación desde un nuevo punto de vista.
El término Programación Orientada a Objetos (POO), hoy en día ampliamente
utilizado, es difícil de definir debido a que no es un concepto nuevo, sino que ha sido el
desarrollo de técnicas de programación desde principios de la década de los 70’s, cuando ha
aumentado su difusión, uso y popularidad. No obstante, se puede definir como una técnica o
estilo de programación que utiliza objetos como bloque especial de construcción.
2.1.1 Conceptos dentro de la POO
• Objeto: Entidad de programa que consiste en datos, y todos aquellos procedimientos que
pueden manipularlos.
• Clase: Colección de objetos que poseen características comunes, la cual contiene toda la
información necesaria para crear nuevos objetos. En otras palabras, se definen las
características concretas de un determinado tipo de objetos, es decir, de cuáles son los
datos y los métodos de los que van a disponer todas las instancias de ese tipo.
• Encapsulación: Técnica que permite localizar y ocultar los detalles de un objeto,
previniendo que el mismo sea manipulado por operaciones distintas de las definidas. Ésta
se puede comparar con el funcionamiento de una caja negra.
• Abstracción: Representación concisa de una idea u objeto complicado, localizando y
ocultando los detalles de un modelo o diseño para generar y manipular objetos.
• Polimorfismo: Se refiere al hecho de que un nombre se puede utilizar para especificar
una clase genérica de acciones.
• Herencia: Proceso mediante el cual un objeto (hijo) puede adquirir las propiedades de
otro objeto (padre).
Programación Orientada a Objetos
17
2.1.2 Ventajas
• Uniformidad: La representación de los objetos implica tanto el análisis como el diseño
y la codificación de los mismos.
• Comprensión: Los datos y los procedimientos que los manipulan se pueden agrupar en
clases.
• Flexibilidad: Cualquier cambio en los datos (o sus respectivas funciones) quedará
reflejado automáticamente en cualquier lugar donde éstos aparezcan.
• Estabilidad: Permite aislar las partes del programa que permanecen inalterables con el
tiempo.
• Reusabilidad: Se pueden reutilizar las definiciones de objetos empleadas en otros
programas, o incluso los procedimientos que los manipulan.
2.2 Clases y objetos
Una clase es un tipo definido por el usuario que determina la estructura de los datos y las
operaciones asociadas a este tipo, es decir, son modelos o plantillas que describen cómo se
construyen ciertos tipos de objetos.
La sintaxis básica para definir una clase es la mostrada a continuación:
class nombre_clase {
miembros;
}
Donde los miembros son datos y funciones pertenecientes a la clase definida, las cuales
están a disposición de todos los objetos o instancias de la misma. Obviamente hay muchos
tipos de miembros distintos, pero por ahora sólo se considerarán los siguientes:
• Campo o Atributo: Es un dato común a todos los objetos de una determinada clase. Su
sintaxis es como sigue: <modificador_de_acceso> tipo_de_campo nombre_campo;
Programación Orientada a Objetos
18
o <modificador_de_acceso>: Es una palabra reservada que permite restringir el
acceso a un atributo o método de un objeto en particular, es decir, que con ella se
decide si un miembro perteneciente a una clase puede ser visto o no desde
cualquier punto del programa. Puede ser:
▪ Private: Implica que sólo permite el acceso a los miembros de la clase
donde está definido. En caso de omitirse el modificador de acceso, el
miembro es private por defecto.
▪ Protected: Sólo tienen acceso la clase donde está definido y sus
herederas.
▪ Public: Se puede acceder desde cualquier parte del programa.
o tipo_de_campo: Establece el tipo de formato (o categoría) del dato. Puede ser
definido dentro del sistema (int, float, string, etc.) o por el usuario.
• Método: Es un conjunto de instrucciones a las que se les asocia un nombre, con las cuales
se manipulan los datos asociados a la clase. Su sintaxis es como sigue:
<modificador_de_acceso> <tipo_de_retorno> nombre_método(parámetros) {
sentencias;
}
o tipo_de_retorno: Es el tipo del dato u objeto que se devuelve como resultado de
la ejecución de las sentencias (instrucciones) pertenecientes a dicho método: Si
no se indica nada, el retorno es de tipo void (vacío), y si se devuelve algo, se debe
terminar la ejecución de las sentencias con la instrucción return <objeto>
o parámetros: Son los objetos o atributos que recibe el método al ser invocado,
especificando allí el tipo de dato y el nombre con el que se referencia de manera
interna. Si no se recibe nada, sólo se colocan los paréntesis, asumiendo que los
parámetros son de tipo void.
Ejemplo 2.1: Cree una clase llamada empleado, cuyos atributos son nombre, edad,
departamento y salario. Incluya los métodos necesarios para agregar un empleado y
visualizar sus datos.
Programación Orientada a Objetos
19
En este ejemplo, se agregan además de los atributos señalados, un método que reciba
los datos, y otro para visualizarlos, quedando de la siguiente manera:
class empleado {
private string nombre, dpto;
private int edad;
private float salario;
public void agregar_datos() {
//Sentencias para agregar datos
}
public void mostrar_datos() {
//Sentencias para mostrar datos
}
}
Como se pudo observar en el ejemplo, los atributos o campos son private, mientras
ambos métodos son de tipo public, lo que indica que estos últimos podrán ser vistos desde
cualquier parte del programa. Esto es así debido al principio de encapsulación, ya que de
esta manera se puede proteger la información interno, evitando así una errónea manipulación
de los datos en cualquier parte del programa, Por supuesto se pueden usar otros
modificadores, con funciones privadas y atributos públicos, pero esta manera es la más
recomendada (sobre todo para los principiantes).
2.2.1 Acceso a los miembros de una clase
Ejemplo 2.2: Tratar de cargar los datos del empleado, usando el método definido en el
ejemplo anterior.
Aquí hay que llamar al método “cargar_datos”, el cual ha sido definido como public,
lo que indica que puede ser visto por el método Main (éste es el método principal, que se
encarga de ejecutar el programa). Obsérvese el siguiente código:
class Program {
static void Main(string[] args) {
cargar_datos();
}
}
Programación Orientada a Objetos
20
En este caso el programa da error, ya que “cargar_datos”, aun siendo visible en
cualquier parte del programa, no está definido dentro del método Main, sino dentro de la
clase empleado, lo que indica que hace falta algo para acceder a dicho método, un enlace
entre ambas clases. Por lo tanto… ¿Cómo se accede a un método de la clase? La respuesta:
Declarar un objeto.
Como se ha dicho, los objetos son instancias de una clase, es decir, cada objeto definido
trabaja con los mismos atributos y posee los mismos métodos de la clase a la que pertenece,
Éste se declara de la siguiente manera:
nombre_clase nombre_objeto = new nombre_clase();
Donde nombre_clase hace referencia a la clase definida por el usuario, es decir, es el
“tipo de dato” del objeto; new es un operador que reserva espacio de memoria para almacenar
un objeto de tipo clase, y nombre_clase( ) es el constructor, es decir, un método especial que
se encarga de inicializar dicho objeto.
Ejemplo 2.3: Cree un objeto de tipo empleado.
class Program {
static void Main(string[] args) {
empleado e = new empleado();
}
}
Con esto se ha creado un objeto llamado “e” el cual tiene todas las características de la
clase a la cual instancia (empleado), es decir, posee la capacidad de almacenar dentro de sí
un nombre de empleado, el departamento al cual pertenece, su edad y su salario, además de
tener la capacidad de invocar métodos para la carga e impresión de dichos datos.
Pero sigue la pregunta… ¿Cómo acceder a los miembros de una clase? Ahora que se
tiene el objeto definido, la manera de acceder a los mismos se realiza utilizando la siguiente
sintaxis:
nombre_objeto.miembro
Programación Orientada a Objetos
21
Aquí, el operador “.” (Punto) se utiliza como operador de acceso a los miembros de
una clase, los cuales pueden ser atributos o métodos, siempre y cuando su nivel de acceso lo
permita, es decir, este método no funciona, por ejemplo, para miembros de tipo private
porque se generaría un error, ya que el acceso a los mismos está restringido sólo y
exclusivamente para la clase donde son definidos.
A raíz de lo anteriormente dicho, para resolver el problema planteado en el Ejemplo 2
se procede de la siguiente manera:
class Program {
static void Main(string[] args) {
empleado e = new empleado();
e.cargar_datos();
}
}
2.2.2 Constructores y destructores
Como se mencionó, el constructor se trata de un método especial, cuya principal
característica es que tiene el mismo nombre de la clase. Se define de forma similar a un
método cualquiera, salvo que a éste se le omite el tipo de retorno (de hecho, muchos autores
ni siquiera emplean la palabra void). En cuanto a su acceso, aunque pueden ser private o
protected, éstos se recomiendan que sean de tipo public (por defecto).
Cuando el usuario no define ningún constructor, se emplea el constructor por defecto,
definido dentro del sistema con las operaciones básicas que éste necesita para la construcción
de cualquier objeto. Sin embargo, si el usuario define al menos un constructor, cada vez que
se crea el objeto su constructor debe coincidir con lo que definió dicho usuario.
Además de los constructores, también se tienen los destructores, los cuales se utilizan
para realizar ciertas operaciones que son necesarias cuando ya no se utiliza un objeto. Su
definición es similar a la de un constructor, sólo que se coloca el carácter “~” (virgulilla)
antes del nombre. Su uso es poco frecuente, y cuando no se define se suele utilizar un
destructor por defecto que se encarga de liberar el espacio en memoria.
Programación Orientada a Objetos
22
Ejemplo 2.4: Incluya un constructor y un destructor para la clase empleado.
class empleado {
//Atributos y métodos internos
public empleado() {
//Sentencias para construir el objeto
}
public ~empleado() {
//Sentencias para destruir el objeto
}
}
Ejemplo 2.5: Desarrolle un proyecto que posea una clase llamada empleado, cuyos atributos
son los siguientes: nombre, edad, departamento y salario; y posea los métodos necesarios
para agregar un nuevo empleado y mostrar sus datos.
Compilando todo lo desarrollado en los ejemplos anteriores, el programa queda de la
siguiente manera:
class empleado {
private string nombre, dpto;
private int edad;
private float salario;
public void agregar_datos() {
//Sentencias para agregar datos
}
public void mostrar_datos() {
//Sentencias para mostrar datos
}
public empleado() {
//Sentencias para construir el objeto
}
}
class Program {
static void Main(string[] args) {
empleado e = new empleado();
e.cargar_datos();
e.mostrar_datos();
}
}
Programación Orientada a Objetos
23
2.2.3 Ventajas del uso de clases
El programa del Ejemplo 5 muestra la misma salida que uno donde se trabaje sólo con el
método Main, así:
class Program {
static void Main(string[] args) {
string nombre, dpto;
int edad;
float salario;
// Instrucciones para cargar datos del empleado
// Instrucciones para mostrar datos del empleado
}
}
Ahora, imagínese por un momento que se desean cargar los datos de 3 empleados. Se
podría realizar con un ciclo, pero en este caso los datos no serían reutilizables, ya que los
mismos se reemplazarían en cada iteración (lo que impediría su uso en sentencias
posteriores); y si se hace de manera lineal resulta tedioso, puesto que se estarían declarando
muchas variables, con sus instrucciones de entrada y salida correspondientes, eso sin
mencionar que, al tener que escribir mucho código, se está más propenso a cometer errores.
En cambio, si la clase está adecuadamente definida, sólo harían falta 3 objetos, y pocas líneas
de código para llamar a los métodos correspondientes:
class Program {
static void Main(string[] args) {
empleado a = new empleado();
empleado b = new empleado();
empleado c = new empleado();
// Cargar datos de 3 empleados
a.cargar_datos();
b.cargar_datos();
c.cargar_datos();
// Mostrar datos de 3 empleados
a.mostrar_datos();
b.mostrar_datos();
c.mostrar_datos();
}
}
Programación Orientada a Objetos
24
2.3 Programación en consola
La consola es una ventana del Sistema Operativo en la cual los usuarios interactúan con el
S.O., o con una aplicación de consola basada en texto. Este tipo de aplicaciones utilizan como
entrada y salida líneas de comandos, lo que permite probar rápidamente las características
del lenguaje y escribir utilidades en modo carácter.
2.3.1 Buffer de pantalla y ventana de la consola
La consola posee dos características que están estrechamente relacionadas entre sí:
• Buffer de Pantalla: Atributo de la consola que está organizado como una cuadrícula
rectangular de filas y columnas. Cada intersección (llamada celda de carácter) puede
contener un carácter. Cada carácter tiene su propio color de primer plano y cada celda de
carácter tiene su propio color de fondo.
• Ventana de Consola: Es otro atributo de la consola, una ventana del Sistema Operativo
que también está organizada en filas y columnas. Su tamaño es menor o igual al Buffer
de Pantalla, se puede mover para permitir la visibilidad de secciones del buffer de
pantalla, y cuando su tamaño es menor a este último, crea automáticamente barras de
desplazamiento.
Ejemplo de Consola: El área negra es el Buffer de Pantalla, La Ventana de Consola es el marco que la rodea
Programación Orientada a Objetos
25
2.3.2 La clase console
La consola posee dos características que están estrechamente relacionadas entre sí:
• Representa las secuencias de entrada, salida y error estándar para las aplicaciones de
consola.
• No se puede heredar.
Su sintaxis se define de la siguiente forma: public static class Console; lo que
indica que esta clase es de dominio público (accesible por todos los objetos) y a su vez es
estática (lo que implica que, una vez definida, no se puede modificar). Nótese que luego de
la instrucción se coloca un “;” (punto y coma), el cual es el operador de fin de sentencia.
Cuando se inicia una aplicación de consola, el Sistema Operativo asocia
automáticamente a la misma tres secuencias de Entrada y Salida: Entrada Estándar, Salida
Estándar y Error Estándar, las cuales se representan a la aplicación como los valores de las
propiedades In (entrada), Out (salida) y Error. Éstas se encuentran contenidas en:
• System.IO.TextReader (entrada)
• System.IO.TextWriter (salida y error)
La clase Console contiene métodos de lectura y escritura de caracteres individuales o
líneas enteras de la consola; métodos de conversión de una instancia de un tipo de valor,
matriz de caracteres o conjunto de objetos en una cadena con o sin formato; métodos y
propiedades para cambiar la posición del cursor, los colores de primer plano y de fondo, para
reproducir un “bip”, entre otros.
2.3.3 Entrada y salida por consola
WriteLine
Es un método estándar para mostrar información a través del monitor de la consola donde se
esté ejecutando el programa en C#. Tiene 3 variantes fundamentales:
Programación Orientada a Objetos
26
• Mostrar mensaje: Es una de las formas más comunes de usar el método y consiste en
mostrar por pantalla una cadena de texto (escrita entre comillas dobles “0”). Sintaxis:
Console.WriteLine("Este es un mensaje");
• Mostrar el contenido de un atributo: Otra de las formas más comunes de usar el
método, y consiste en mostrar en pantalla el valor almacenado dentro de una variable.
Sintaxis: Console.WriteLine(atributo);
• Mostrar mensaje y el valor de 1 o más atributos y/u operaciones: La forma más
completa de usar el método, el cual permite mostrar no sólo un mensaje, sino que éste
puede ir acompañado de 1 o más valores. Una sintaxis puede ser como la siguiente:
Console.WriteLine("Texto "+ atributo);
Nótese el uso del operador “+”, el cual en este caso se emplea como concatenación
(unión) de cadenas de texto y atributos. Como ya se mencionó, el texto debe estar entre
comillas dobles “0”, los atributos no. Si por ejemplo se escribe esto:
Console.WriteLine(Texto + atributo);
Se muestra en pantalla el contenido de una variable Texto y de una variable atributo.
Si por el contrario se escribe esto: Console.WriteLine("Texto"+"atributo"); se muestra
en pantalla la palabra Textoatributo.
Otra manera de mostrar este tipo de salidas es la siguiente:
Console.WriteLine("La suma de {0} y {1} es {2}", a, b, a+b);
En este caso se escribe dentro del mensaje un identificador de posición de la variable.
En C#, la posición de una variable o atributo dentro del método WriteLine se cuenta de
izquierda a derecha a partir de la posición 0 (0 – 1 – 2 – 3 …). En el ejemplo, la variable a se
encuentra en la posición 0, por eso se escribe {0}, la variable b está en la posición 1, así que
es {1}, mientras que la operación a+b se encuentra en la posición, 2, entonces se coloca {2}.
Si se escribe algo como esto:
Programación Orientada a Objetos
27
Console.WriteLine("La suma de {0} y {0} es {2}", a, b, a+b);
Se mostraría el valor de a de manera duplicada, y el valor de a+b. En este caso, el valor
de b no se mostraría, dado que no está indicado en el método. Esta modalidad también
permite definir la cantidad de espacios previos a la impresión de algún valor, por ejemplo:
Console.WriteLine("Número {0,5}", num);
Imprime el valor de num predecedido por 5 espacios en blanco. Esto es muy útil
cuando se desea tabular la salida. Cabe destacar que si el valor a imprimir excede la cantidad
de caracteres señalada, símplemente se tratará como una impresión normal, y no lo truncará.
Por último, hay que señalar que cualquiera de las dos formas de mostrar la salida (con
cocatenación o indicador de posición) es totalmente válida. Incluso, una combinación de
ambas formas es igualmente aplicable:
Console.WriteLine("La suma de {0} y {1} es "+(a+b), a, b);
Write
Se trata de un método muy similar a WriteLine, con sintaxis casi idéntica y mismas
variantes:
Console.Write("Hola ");
Console.Write(a);
Console.Write("La suma de a+b es "+(a+b));
La única diferencia es que mientras WriteLine imprime una línea de texto y/o variable
y coloca el cursor en la siguiente línea, Write imprime la línea y coloca el cursor al final de
la misma.
ReadLine
Es el método estándar para leer información a través del teclado de la consola donde se esté
ejecutando el programa en C#. Es importante saber que cuando se emplea éste para la entrada
de datos, los mismos se leen en forma de cadena de caracteres, por lo que aunque se
Programación Orientada a Objetos
28
introduzca un valor numérico por teclado NO se podrá operar matemáticamente este valor
hasta tanto no se transforme adecuadamente al tipo indicado. Su sintaxis es como sigue:
lector = Console.ReadLine(); donde lector es una cadena de caracteres con capacidad
de almacenar cualquier cosa que el usuario introduzca por el teclado.
Si se desea emplear la información de entrada para realizar cálculos matemáticos, se
puede realizar una conversión de tipo, lo cual no es más que llevar el contenido de una
cadena a un tipo de dato específico. La sintaxis para tal conversión es como sigue:
variable_real = tipo_de_dato_a_convertir.Parse(cadena_a_transformar)
Por ejemplo, si se quiere transformar la entrada de datos a un número entero y
almacenarlo en una variable num, se realiza como sigue:
num = int.Parse(cadena);
Ahora, para transformar una variable de cualqueir tipo a cadena, existe el método
ToString, cuya sintaxis es: cadena = variable.ToString();
Al ejecutar un programa en C#, éste lo hace tán rápidamente que finaliza y se cierra
impidiendo ver los resultados. Para evitar esto, es recomendable escribir al final del código
la siguiente línea: Console.ReadLine(); ya que obliga al programa a esperar la entrada del
usuario, permitiendo ver la ejecución del mismo.
2.3.4 Métodos para personalizar la consola
Como ya se ha dicho, la clase Console no sólo permite la entrada y salida de datos, sino que
también permite la personalización de la pantalla. Dos de los métodos más populares son los
siguientes:
• BackgroundColor: Permite cambiar el color de fondo de las celdas de carácter. Su
sintaxis es como sigue:
Console.BackgroundColor = ConsoleColor.DarkBlue;
Programación Orientada a Objetos
29
De acuerdo al ejemplo, se asigna como color de fondo uno de los colores disponibles para
consola (en este caso el azul oscuro).
• ForegroundColor: Permite cambiar el color en primer plano, es decir, cambia el color
de las letras. Su sintaxis es muy parecida a la del método anterior:
Console.ForegroundColor = ConsoleColor.Yellow;
Para activar ambos métodos debe escribirse lo siguiente: Console.Clear(); la cual,
como su nombre lo indica, realiza una limpieza de pantalla. Cabe destacar que es
recomendable escribir estos métodos ANTES de escribir el resto del programa.
Ejemplo 2.6: Escriba un programa que lea 2 números enteros e imprima la suma:
using System;
using System.Collections.Generic;
using System.Text;
namespace Ejemplo2_6
{
class Sumador
{
private int a, b, suma;
private string temp;
public void ejercicio()
{
Console.WriteLine("Ingrese 2 números");
temp = Console.ReadLine(); // Lectura de una cadena de texto
a = int.Parse(temp); // Conversión a entero
// Lectura y conversión directa:
b = int.Parse(Console.ReadLine());
suma = a + b;
// Impresión de texto y variable:
Console.WriteLine("La suma es: " + suma);
}
}
class Program
{
static void Main(string[] args)
{
// Personalización de la Consola:
Console.BackgroundColor = ConsoleColor.DarkBlue;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Clear();
Programación Orientada a Objetos
30
// Aquí inicia el programa como tal:
Sumador sum = new Sumador(); // Declaración del objeto
sum.ejercicio() // Ejecución del método interno
Console.ReadLine();
}
}
}
Nótese las líneas precedidas por la doble barra “//”. Estos son los comentarios de una
línea, los cuales no son más que línea no ejecutables que se emplean para la documentación
del código fuente. Si se quieren usar comentarios de líneas múltiples, se emplea la
combinación de /* y */, así:
/* Este es un comentario de múltiples lineas
Y estará inactivo hasta el cierre del mismo */
La combinación anterior suele ser empleada para la inhabilitación de código ejecutable,
esto con el fin de facilitar la revisión y detección de errores de código, sin necesidad de borrar
el contenido.
2.4 Ejercicios propuestos
1) Escriba un programa que lea tres palabras e imprima una frase completa con ellas. Por
ejemplo, si se reciben las cadenas “Bienvenido”, “a” y “C#” se imprima la línea
“Bienvenido a C#”.
2) Modifique el programa anterior para imprimir texto adicional, el cual debe ir entre las
palabras recibidas desde el teclado. Por ejemplo, al recibir “Bienvenido”, “a” y “C#”, se
imprima algo como “Sean todos Bienvenidos a las clases de C#”.
3) Escriba un programa que lea un número entero e imprima su cuadrado.
4) Construya una clase que sea capaz de realizar las operaciones aritméticas fundamentales
entre dos números (suma, resta, producto y división).
Programación Orientada a Objetos
31
UNIDAD III: DATOS SIMPLES
3.1 Variables
La variable es una posición de memoria donde se puede almacenar un valor para uso de un
programa, ya sea entero, número real, carácter, entre otros. Este valor es temporal, pues puede
ser reemplazado por otro a medida que se van ejecutando las instrucciones. La manera de
informar a la computadora que se crea una variable es a través de una sentencia de
declaración, la cual ayuda a definir lo que se puede almacenar en un nombre de variable.
Por su parte están las constantes, cuyo valor es invariable durante la ejecución del
algoritmo. En ambos casos se requiere del uso de un identificador, el cual consiste en una
secuencia de caracteres alfanuméricos que permite asignar un nombre (preferiblemente
significativo) al referido espacio de memoria, ya que de no hacerlo se requeriría conocer la
dirección específica de la variable o constante, lo cual resulta muy complicado.
3.1.1 Reglas para asignar nombres de variables
La sintaxis del lenguaje de programación C# impone algunas reglas en la construcción de los
nombres de variables. Estos se listan a continuación:
• Puede ir formado por una o más letras, o en combinación con números.
• Puede tener cualquier longitud.
• No se permite el uso de caracteres especiales, excepto el underscope “_”.
• Siempre deberá comenzar con una letra o por “_”. Sin embargo, es buena práctica de
programación tener nombres de variables que empiecen con letras minúsculas (a - z).
• Preferiblemente, que sea mnemotécnico.
• Todas las variables deben declararse con un nombre y un tipo de datos. Por ejemplo,
string cad;
• La declaración de una variable se puede realizar en cualquier parte del programa, siempre
y cuando sea antes de su primer uso
Programación Orientada a Objetos
32
• No puede ser una palabra reservada del lenguaje (se trata en este caso de un
identificador predefinido que tiene un significado especial).
Algunas palabras reservadas por C#
bool break case char const
continue decimal default do double
else enum extern float for
goto if int long object
return short sizeof static string
struct switch void volatile while
3.1.2 Tipos de identificadores
Los identificadores (variables o constantes) pueden ser:
• Numéricos: Almacenan sólo números.
o Acumulador: Es aquel que se incrementa en forma no definida por la suma o
producto con otro valor a dicho campo. Su valor inicial suele ser 0 (suma) o 1
(producto).
• Alfanuméricos: Almacenan cualquier carácter.
• Lógicos: Almacenan los estados true (verdadero) y false (falso).
• Compuestos: Combinación de varios identificadores de uno o más tipos.
3.2 Tipos de datos
Se define como un conjunto de valores y operaciones definidas para las variables de ese tipo
en particular. Los tipos de datos básicos en C# son: Byte, Int, Single, Double, Decimal,
Boolean, Char, String y Object.
Por ejemplo, int es un tipo de dato y conjunto de valores que una variable de tipo int
puede almacenar, y también el conjunto de operaciones que se pueden usar con los operandos
enteros.
Programación Orientada a Objetos
33
Cada uno de los tipos de datos básicos se almacena en forma diferente en la memoria
de la computadora:
Tipo Descripción Bits Rango de Valores Alias
Sbyte Bytes con signo 8 -128 a 127 sbyte
Byte Bytes sin signo 8 0 a 255 byte
Int16 Enteros cortos con signo 16 -32.768 a 32.767 short
UInt16 Enteros cortos sin signo 16 0 a 65.535 ushort
Int32 Enteros normales con signo 32 -2.147.483.648 a 2.145.483.647 int
UInt32 Enteros normales sin signo 32 0 a 4.294.697.295 uint
Int64 Enteros largos con signo 64 -9.223.372.036.854.775.808 a
9.223.372.036.854.775.807 long
UInt64 Enteros largos sin signo 64 0 a 18.446.744.073.709.551.615 ulong
Single Reales con 7 dígitos de precisión 32 1,5x10-45 a 3,4x1018 float
Double Reales con 15-16 dígitos de precisión 64 1,0x10-28 a 7,9x1028 double
Decimal Reales con 28-29 dígitos de precisión 128 5,0x10-324 a 1,7x10308 decimal
Boolean Valores Lógicos 32 True, false bool
Char Caracteres Unicode 16 ‘\u0000’ a ‘\uFFFF’ char
String Cadenas de Caracteres Variable El permitido por la memoria string
Object Cualquier Objeto Variable Cualquier objeto object
Como se puede observar, además de los datos básicos existen los calificadores de
datos signed y unsigned, los cuales modifican las propiedades de los tipos (es decir,
determina si tienen o no tienen signo). También puede apreciarse un “alias” o nombre
alternativo en el que pueden ser declarados los tipos de datos.
Por ejemplo: long a = 123456789;
Int64 b = 987654321;
En ambos casos se declaran variables de tipo entero largo (o de 64 bits). Ambas
declaraciones son totalmente válidas.
3.3 Expresiones, operadores y asignaciones
El operador es el símbolo utilizado en matemáticas o computación para indicar la operación
que se realiza entre los elementos que une o la relación que existe entre ellos.
Programación Orientada a Objetos
34
3.3.1 Operadores aritméticos
Los operadores aritméticos de C# se muestran en la siguiente tabla:
Operación Operador Expresión Algebraica Expresión en C#
Suma + Z + 7 Z + 7
Resta – A – B A – B
Producto * XY X * Y
División / j/k ó j÷k j / k
Módulo % r mod s r % s
Advierta el uso de varios símbolos especiales, no utilizados en álgebra. El asterisco (*)
indica producto, y el signo de porcentaje (%) denota el operador módulo. Los operadores
aritméticos son operadores binarios. La combinación de operandos por medio de cualquiera
de estos operadores se llama expresión algebraica o aritmética.
La división de enteros da como resultado también un entero. Por ejemplo, la expresión
7/4 da como resultado 1, y la expresión 17/5 da como resultado 3. Se debe tener mucho
cuidado de asegurar que al realizar la división, el denominador sea distinto a 0. Si se intenta
dividir entre 0, el programa no funcionará.
C# tiene el operador de módulo %, que proporciona el residuo después de una división
de enteros, por lo tanto, 7%4 da como resultado 3, y 17%5 da como resultado 2. El módulo
es un operador entero, es decir, que sólo puede ser utilizado con operandos de tipo int.
Cuando uno de los operandos es negativo, el resultado de % no está definido.
Las expresiones aritméticas deben ser escritas en una línea continua para facilitar la
escritura de programas en la computadora. Entonces, expresiones tales como “a dividido
entre b” deben ser escritas como a/b, y no como 𝒂
𝒃. Por su parte, los paréntesis se utilizan en
las expresiones de C# de manera muy similar a como se usan en las expresiones algebraicas.
Por ejemplo, para multiplicar a por la cantidad b+c se escribe lo siguiente: a * (b + c).
Programación Orientada a Objetos
35
3.3.2 Expresiones y operadores relacionales
Las expresiones relacionales son aquellas que se forman con una combinación de
identificadores, constantes y expresiones aritméticas, todas enlazadas a través de un conjunto
de operadores relaciónales. Los siguientes, por ejemplo, son todas expresiones relacionales
válidas:
• 10
• (a-25) <= max
• b<=0
• (b*b -2*a) < (c*e)/2
Operadores relacionales
Algunos de los operadores relacionales son: < (menor que), <= (menor o igual que), > (mayor
que) y >= (mayor o igual que). La asociatividad de la evaluación es de izquierda a derecha.
Los operadores relacionales se usan para formar expresiones relacionales. Las expresiones
relacionales siempre producen un valor de verdadero o falso.
Operadores de igualdad
Los operadores de igualdad son: == (igual a) y != (diferente a). La asociatividad de los
operadores es de izquierda a derecha. Éstos se pueden usar en expresiones relacionales.
Algunos ejemplos válidos del uso de los operadores de igualdad se presentan a continuación:
a == b n != 10 b + c == c - d
3.3.3 Expresiones y operadores lógicos
Las expresiones relacionales conectadas por operadores lógicos se denominan expresiones
lógicas, ya que siempre producen un valor de verdadero o falso. El valor que retorna una
expresión lógica es de tipo Boolean, a saber: true y false. C# también provee operadores
lógicos, llamados conectores lógicos, los cuales se listan a continuación:
Programación Orientada a Objetos
36
• && Y (AND)
• | | O (OR)
• ! NO (NOT)
Se pueden usar los operadores lógicos para combinar expresiones lógicas. Algunos
ejemplos se dan a continuación:
(a + 2) == max && (n != 0)
(a == 3) && (max != -1) | | (i % 2 != 0)
El operador lógico && retorna el valor true sólo cuando ambos operandos son
verdaderos. El operador lógico | | retorna el valor false sólo si ambos operandos son falsos.
El operador lógico ! retorna el opuesto del operando, es decir, retorna true si el operando es
false, y viceversa.
3.3.4 Operadores incrementales y decrementales
C# también tiene el operador incremental unario (++) y el operador decremental unario (--),
los cuales permiten incrementar o decrementar en 1 el valor de una variable. Por ejemplo,
X++ es el equivalente de X = X + 1.
Operador Ejemplo Descripción
++ ++ a Se incrementa a en 1 y a continuación se utiliza el
nuevo valor de a en la expresión en la cual resida a
a ++ Utiliza el valor actual de a en la expresión en la cual
resida a, y después se incrementa a en 1
-- -- b Se decrementa b en 1 y a continuación se utiliza el
nuevo valor de b en la expresión en la cual resida b
b -- Utiliza el valor actual de b en la expresión en la cual
resida b, y después se decrementa b en 1
3.3.5 Operadores compuestos de asignación
C# proporciona operadores compuestos de asignación, los cuales pueden ser utilizados como
atajos al escribir las declaraciones de asignación.
Programación Orientada a Objetos
37
Un operador de asignación compuesto consiste generalmente de un operador aritmético
binario y un operador de asignación simple. Éste ejecuta la operación de un operador binario
en ambos operandos y da el resultado de esa operación al operando izquierdo. Por ejemplo,
la declaración: X = X + 2 es equivalente a: X += 2. Los operadores compuestos más usados
son: += (suma), –= (resta), *= (producto), /= (división) y %= (módulo).
3.3.6 Operador condicional
El operador condicional “?:” retorna uno de dos valores de una expresión booleana. Su
sintaxis es como sigue:
Condición ? instrucción1 : instrucción2;
Como se puede observar, se trata de un operador ternario, ya que involucra 3
operandos. Aquí la condición se evalúa y, si el resultado es true se lleva a cabo la primera
instrucción, si es false ejecuta la segunda.
3.3.7 Precedencia de operadores
Las reglas de precedencia de operadores son guías de acción que permiten a C# calcular
expresiones en una secuencia precisa, es decir, permite establecer qué operaciones se
ejecutan primero, qué otras operaciones se ejecutan posteriormente.
Categoría Operadores Ejemplo de Uso Asociatividad
Acceso a Datos ( ) [ ] . a * (b + c) De izquierda a derecha
Unarios ++ -- + - (signo) -5 De derecha a izquierda
NOT Lógico ! !(k>f) De izquierda a derecha
Aritméticos * / % x % y De izquierda a derecha
+ - 3 + 2 De izquierda a derecha
Relacionales < <= > >= rel1 < rel2 De izquierda a derecha
Igualdad == != rel3 != rel4 De izquierda a derecha
AND Lógico && b<c && d>e De izquierda a derecha
OR Lógico | | b<c || d>e De izquierda a derecha
Condicional ?: i>j ? k=1 : k=0 De izquierda a derecha
Asignación = += -= *= /= %= a += 5 De derecha a izquierda
Programación Orientada a Objetos
38
Ejemplo 3.1: Escriba un programa que entre 3 enteros diferentes del teclado, y a continuación
imprima la suma, el producto y el promedio de estos números.
using System;
using System.Collections.Generic;
using System.Text;
namespace Ejemplo3_1
{
class Operaciones
{
private int a, b, c, prod=1; // Declaración de variables
public void lector()
{
Console.WriteLine("Ingrese 3 números enteros:");
a = int.Parse(Console.ReadLine());
b = int.Parse(Console.ReadLine());
c = int.Parse(Console.ReadLine());
}
public void ejemplo()
{
/* Declaración y asignación de variable interna. Nótese que
se declara SIEMPRE antes de usarla por primera vez */
int suma = a + b + c;
// Ejemplo de uso de operador compuesto de asignación:
prod *= a * b * c; // Es lo mismo que prod = prod * a * b * c
/* Ejemplo de uso de ley de precedencia: Dado que la división
tiene precedencia mayor que la suma, se fuerza a realizar
la suma primero con el uso de paréntesis (de precedencia
mayor) */
int prom = (a + b + c) / 3;
Console.WriteLine("La suma de los números es: "+suma);
Console.WriteLine("El producto de los números es: " + prod);
Console.WriteLine("El promedio de los números es: " + prom);
}
}
class Program
{
static void Main(string[] args)
{
Operaciones Ope = new Operaciones();
Ope.lector();
Ope.ejemplo();
Console.ReadLine();
}
}
}
Programación Orientada a Objetos
39
3.4 Ejercicios propuestos
1) Escriba un programa que lea el radio de un círculo y que imprima el diámetro del mismo,
su circunsferencia y su área. Utilice el valor constante 3.14159 para Pi, y el tipo de
atributo double.
2) Escriba un programa que reciba un número de cinco dígitos, separe el número en sus
dígitos individuales e imprima los dígitos separados unos de otros mediante tres espacios.
Utilice los operadores / (división) y % (módulo).
Programación Orientada a Objetos
40
UNIDAD IV: ESTRUCTURAS DE CONTROL
En un programa, por lo general, los enunciados son ejecutados uno después del otro, en el
orden en que aparecen escritos. Esto se conoce como ejecución secuencial.
Sin embargo, hay ocasiones en que se requiere que ciertas condiciones se verifiquen y
se tomen decisiones de acuerdo a éstas, es decir, realizar acciones específicas que bien
pudieran cambiar la secuencia de ejecución de un programa. Esto se denomina transferencia
de control, la cual puede realizarse mediante el uso de estructuras de selección o iterativas.
4.1 Estructuras de control selectivas
El control de decisión abarca desde verificar condiciones muy simples hasta estructuras muy
complejas, tomando decisiones basadas en ellas. C# proporciona 3 estructuras de selección,
a saber:
• if simple: Estructura de una sola selección. Ejecuta una acción si una condición es
verdadera, de ser falsa la ignora.
• if/else: Ejecuta una acción si una condición es verdadera, de ser falsa ejecuta una
acción distinta.
• switch/case: Ejecuta una entre muchas acciones diferentes, dependiendo del valor de
una expresión.
4.1.1 La estructura de selección simple: if
IF significa SI (condicional) en español, y consiste en una estructura de selección empleada
para elegir entre cursos alternativos de acción. Su funcionamiento es simple: Se evalúa una
condición, si es verdadera ejecuta un código, si es falsa, continúa con la ejecución del
programa.
Programación Orientada a Objetos
41
Diagrama de Flujo Sintaxis
…
if(condición)
{
<instrucciones>;
}
…
Ejemplo 4.1: Escribir un método que tome como entrada la calificación de un alumno y
determinar si aprobó la asignatura.
void ejemplo4_1(int nota)
{
if(nota >= 5)
{ Console.WriteLine("Aprobado"); }
}
En este ejemplo, la condición que se verifica es nota >= 5, la cual es una expresión
relacional. Si el resultado es true, se imprime la palabra “Aprobado”, de lo contrario no
ejecuta ninguna acción. Nótese el uso de las llaves “{ }”: En este caso se emplearon
simplemente para evitar confusión, ya que al haber una única instrucción éstas pueden ser
omitidas. De haber 2 o más sentencias dentro del bloque if, dichas llaves son de uso
obligatorio.
4.1.2 La estructura de selección doble: if/else
ELSE significa SINO (condicional) en español. La estructura if/else se le llama estructura
de doble selección, porque elige entre dos opciones distintas: Ejecuta una acción si la
condición es verdadera o ejecuta una acción diferente si la condición es falsa.
Diagrama de Flujo Sintaxis
…
if(condición)
{ <instr_ni;> }
else
{ <instr_no;> }
…
instrucciones condición Sí
No
No Sí condición
instr_si instruc_no
Programación Orientada a Objetos
42
Ejemplo 4.2: Modificar el método anterior, de tal manera que indique si un alumno aprobó
o reprobó la asignatura.
void ejemplo4_2(int nota)
{
if(nota >= 5)
{
Console.WriteLine("Aprobado");
}
else
{
Console.WriteLine("Reprobado");
}
}
Como pudo observarse en el ejemplo, en esta oportunidad se realizan 2 acciones
distintas, dependiendo de la condición: Si el resultado es true, se imprime la palabra
“Aprobado”, de lo contrario se imprime Reprobado. Tal como en if simple, si hay una única
sentencia relacionada con if y/o con else, las llaves pueden ser omitidas. Es de resaltar que
ambas acciones son excluyentes entre sí, es decir, si se ejecuta una de ellas no puede
ejecutarse la otra.
Debe recordarse que el bloque else se ejecuta solamente si el resultado de la
condición es falso, en otras palabras, es una acción automática que no necesita de una nueva
verificación, por lo que una instrucción de tipo “else(nota < 5)” es totalmente incorrecta.
Las estructuras if/else anidadas
Las estructuras anidadas son aquellas que se encuentran dentro de otras estructuras
de control. En el caso de un if/else anidado, su forma es como sigue:
if(condicion)
sentencia1;
else
if(condicion)
sentencia2;
else
if(condicion)
sentencia3;
else ...
Programación Orientada a Objetos
43
Esto tipo de estructuras resulta útil cuando existen relaciones que involucren al menos
3 posibles respuestas, ya que los operadores en programación son de tipo binario (es decir,
de la forma operando1 – operador – operando2), lo que implica que solamente se puede elegir
entre 2 alternativas, pudiéndose de esta forma realizar el descarte entre pares de opciones.
Por supuesto, se debe tener cuidado con los niveles de anidamiento para no elaborar un
programa demasiado confuso.
Ejemplo 4.3: Modificar el método anterior, de tal manera que indique si un alumno aprobó
la asignatura, sino, que indique si tiene o no derecho a examen final o reparación.
void ejemplo4_3(int nota)
{
if(nota >= 5)
Console.WriteLine("Aprobado");
else
if(nota >= 3.5)
Console.WriteLine("Puede presentar examen final");
else
if(nota >= 2)
Console.WriteLine("Puede ir a reparación");
else
Console.WriteLine("Reprobado");
}
En este caso se estudian 3 condiciones: Si la nota es mayor o igual a 5, si es mayor o
igual a 3.5 y si es mayor o igual a 2. Si se cumple la primea condición, se imprime la palabra
Aprobado, de lo contrario se pasa a la siguiente sentencia if/else, hasta que se ejecute
cualquiera de las sentencias involucradas.
Problema del else colgante
El compilador de C# siempre asocia un else con el if anterior, a menos que se indique lo
contrario mediante el uso de las llaves { }. En vista de que a primera vista el programador no
estaría seguro de cuál if coincide con qué else, esto se conoce como el problema del else
colgante.
Programación Orientada a Objetos
44
Ejemplo 4.4: Determine la salida para el siguiente bloque de código, cuando a es 9 y b es 11;
y cuando a es 11 y b es 9.
if(a < 10)
if(b > 10)
Console.WriteLine("*****");
else
Console.WriteLine("#####");
Console.WriteLine("$$$$$");
Se puede observar que el código ni está tabulado, ni incluye llaves. Un programador
sin experiencia puede confundirse al tratar de resolver este problema, sobretodo porque surge
la pregunta: ¿A quién le pertenece el else?
En este caso se debe tomar en cuenta lo siguiente:
• Un else nunca puede estar sin su correspondiente if, por lo tanto, cada instrucción else
dentro del código estará automáticamente asociada con el if inmediatamente anterior.
• Recuérdese que, cuando la instrucción asociada a un if o else es única, las llaves pueden
ser omitidas, pues funcionará de la misma manera.
• Es recomendable aplicar la indentación (o tabulado) en el código, para una correcta
identificación de los bloques.
Entonces, para resolver esto, lo primero que hay que hacer es tabular el código,
quedando como sigue:
if(a < 10)
if(b > 10)
Console.WriteLine("*****");
else
Console.WriteLine("#####");
Console.WriteLine("$$$$$");
Nótese que la última instrucción queda al mismo nivel del primer if. Esto es así porque,
como ya se ha mencionado, al no haber llaves se toma como parte de la estructura la
instrucción inmediatamente inferior al if o else. Si se desea que esta línea pertenezca al else,
se deben encerrar ambas instrucciones entre llaves.
Programación Orientada a Objetos
45
Ahora que se tiene ordenado el código es visualmente más sencillo identificar la
secuencia de instrucciones, de acuerdo a las condiciones; por lo tanto, para a = 9 y b = 11 la
salida es:
*****
$$$$$
Por su parte, para a = 11 y b = 9, la salida es:
$$$$$
Hay que ser cuidadosos en el uso de las llaves, sobre todo cuando se desea agrupar
varias instrucciones en un mismo bloque, pues se podría encerrar a una instrucción else sin
su correspondiente if, lo que generaría un error de código. Esto se ilustra en el siguiente
ejemplo:
Ejemplo 4.5: Aplique la indentación y las llaves en el código que sigue para producir la salida
deseada. No puede hacer ningún otro cambio.
if(i == 5)
if(j == 8)
Console.WriteLine("@@@@@");
else
Console.WriteLine("#####");
Console.WriteLine("$$$$$");
Console.WriteLine("*****");
a) Suponiendo que i = 5 y j = 8, se genera la siguiente salida: @@@@@
*****
b) Suponiendo que i = 5 y j = 8, se genera la siguiente salida: @@@@@
Este tipo de ejercicios es un poco más complicado de resolver, pero con un análisis
detallado y cierta habilidad en el uso de una estructura doble se puede dar con una solución.
En el caso de la salida a, se nota que debe imprimirse la línea de arrobas. Al haber dos
instrucciones if consecutivas, es evidente que una está anidada dentro de la otra, y que ambas
se deben cumplir (y lo hacen, según los valores de i y j), para que dicha salida se ejecute.
Programación Orientada a Objetos
46
Con respecto a las dos siguientes líneas, éstas no deben ser impresas. Se observa que
ambas siguen a una instrucción else, y como se mencionó antes, los if deben ejecutarse, por
lo tanto, estas líneas deben ser agrupadas en el else, ya que el mismo no ejecuta según la
condición. Ahora, ¿a cuál if pertenece? A cualquiera de los dos, pues el resultado es el mismo.
En este caso se decide que éste pertenezca al if más interno.
La última línea (asteriscos) debe imprimirse, entonces la misma puede, bien pertenecer
al if externo, o ser una instrucción independiente, ay que da el mismo resultado. Para este
ejercicio se opta por incluirlo en dicho if. Entonces, el código queda como sigue:
if(a == 5)
{
if(b == 8)
Console.WriteLine("@@@@@");
else
{
Console.WriteLine("#####");
Console.WriteLine("$$$$$");
}
Console.WriteLine("*****");
}
En el caso de b, solamente se requiere que se imprima la línea de arrobas. Aquí se
procede igual que con a, solo que ahora las tres últimas líneas deben incluirse en el else, ya
que, según los valores de i y j, esta serie de instrucciones no se ejecuta, ya que ambas
condiciones se cumplen. Por lo tanto se tiene lo siguiente:
if(a == 5)
{
if(b == 8)
Console.WriteLine("@@@@@");
else
{
Console.WriteLine("#####");
Console.WriteLine("$$$$$");
Console.WriteLine("*****");
}
}
Recuérdese que, al trabajar con las llaves, no debe haber una instrucción de tipo
“if(condicion){ else }”, ya que allí hay un error de sintaxis.
Programación Orientada a Objetos
47
4.1.3 La estructura de selección múltiple: switch/case
Una construcción anidada if/else puede llegar a ser difícil de entender, sobre todo cuando un
número de condiciones alternas deben ser evaluadas. C# provee la estructura switch/case,
que prueba una variable o expresión por separado contra cada uno de los valores constantes
enteros que pude asumir, lo que conduce a tomar distintas acciones.
Diagrama de Flujo Sintaxis
…
switch(variable o expresión)
{
case resp1: <instr_1>;
break;
case resp2: <instr_2>;
break;
…
case respN: <instr_N>;
break;
[default : <instr_D>;
break;]
}
Aquí pueden observarse varios aspectos:
• La condición a evaluar puede ser una variable, constante o expresiones algebraicas.
Sin embargo, el uso de expresiones relacionales en una selección múltiple puede verse
limitado por el lenguaje (por ejemplo, en C# no está permitido, pero en Pascal sí).
• Cada case debe culminar en una sentencia break (ruptura); la cual evita que, por
lógica, las otras sentencias case se ejecuten, de hecho, si se omite esa instrucción C#
muestra ese error.
• Default es una sentencia por defecto, la cual se ejecuta si no se cumplen los case.
Ésta, sin embargo, puede ser omitida. Si se incluye, al igual que los case debe
culminar en una sentencia break.
resp1
var o exp
resp2 respN
Por defecto
instr_1 instr_D instr_2 instr_N
…
…
Programación Orientada a Objetos
48
Ejemplo 4.6: Escriba un método que tome como entrada un valor del 1 al 7 e imprima el día
de la semana que corresponde al número ingresado (Domingo = 1, Lunes = 2, etc.). Si la
entrada no es válida se debe mostrar un mensaje de error.
public void ejemplo4_6(int dia)
{
switch (dia)
{
case 1: Console.WriteLine("Domingo");
break;
case 2: Console.WriteLine("Lunes");
break;
case 3: Console.WriteLine("Martes");
break;
case 4: Console.WriteLine("Miércoles");
break;
case 5: Console.WriteLine("Jueves");
break;
case 6: Console.WriteLine("Viernes");
break;
case 7: Console.WriteLine("Sábado");
break;
default: Console.WriteLine("Error!");
break;
}
}
Se debe prestar atención en el uso de esta estructura de control. Ya se ha mencionado
que un error común es la omisión de la instrucción break, sin embargo, existe otro error
bastante frecuente, conocido como código inalcanzable, debido a que el mismo se coloca
entre un break y el case siguiente (o el default), así:
case x: <instrucción_x>
break;
<alguna_instrucción> // <- Este es un código inalcanzable
case y: <instrucción_y>
break;
4.2 Estructuras de control iterativas
Muchos problemas en la vida real son una ejecución repetida de un conjunto de tareas, es
decir, realiza una serie de iteraciones, ciclos o bucles (loops). En programación existen las
Programación Orientada a Objetos
49
estructuras de control iterativas, las cuales permiten repetir porciones de algoritmo un
determinado número de veces. Este tipo de estructuras involucra tres partes básicas, a saber:
• Inicialización de la variable de control: La variable de control es aquella que, como su
nombre lo indica, lleva el control de la ejecución de un ciclo. Ésta debe tener siempre un
valor inicial con el que inicia la ejecución del primer bucle.
• Condición de repetición: Es la expresión relacional (o variable lógica) que define en
qué momento va a finalizar el ciclo.
• Actualización de la variable de control: Instrucción que permite modificar el valor de
la variable de control, de forma tal que en algún momento alcance aquel que indique el
fin de ejecución de los bucles.
4.2.1 La estructura iterativa for
Un contador es una variable especial que incrementa o decrementa, contando el número de
veces que el proceso ha detectado una ocurrencia determinada. Esto es importante saberlo,
puesto que el ciclo for (en español “para”) maneja de manera automática todos los detalles
de la repetición controlada por contador, ya que incorpora los componentes del ciclo en
una misma instrucción, y se emplea cuando se conoce con certeza el número de iteraciones
que deben realizarse.
Diagrama de Flujo Sintaxis
…
for(inicio;condición;actualización)
{
<instrucciones>;
}
…
El funcionamiento es como sigue: Cuando la estructura for inicia su ejecución, la
variable de control se inicializa a un valor dado. A continuación, se verifica si cumple con la
condición. Si se satisface, se ejecuta el conjunto de instrucciones contenidas dentro del ciclo.
final_de_ciclo
ini; cond; act instrucciones
condición_cierta
Programación Orientada a Objetos
50
A continuación, la variable de control se actualiza en forma automática, y el ciclo inicia otra
vez hasta que la condición sea false. Es allí cuando se termina la ejecución del bucle y se
continúa ejecutando el resto del programa.
Observaciones:
1. Tanto la inicialización, como la condición de continuación de ciclo, y la actualización
pueden contener expresiones aritméticas.
2. Si la condición de continuación de ciclo resulta falsa al inicio, la porción del cuerpo del
bucle no se ejecutará. En vez de ello, la ejecución seguirá adelante con el enunciado que
siga a la estructura for.
3. La variable de control con frecuencia se imprime o se utiliza en cálculos en el cuerpo de
un ciclo, pero esto no es necesario. Es común utilizar dicha variable para controlar la
repetición, aunque jamás se mencione la misma dentro del bucle.
4. En la sentencia for, se puede omitir cualquiera de los tres componentes del ciclo,
(inicialización, condición o actualización). En este caso, éstos deben colocarse antes o
dentro del mismo ciclo. Sin embargo, se requiere mucho dominio del manejo de ciclos
para realizar este tipo de cambios (sobre todo la omisión de la condición, la cual no es
recomendada).
5. No todos los lenguajes permiten definir la manera en que se actualiza la variable de
control. Por ejemplo, esta opción es inexistente en Pascal, puesto que allí el incremento
(o decremento) es por unidad. Por fortuna, este no es el caso de C#, el cual ofrece esta
facilidad.
Ejemplo 4.7: Escriba un método que imprima los números del 1 al 10.
public void ejemplo4_7()
{
for(int n = 0; n <=10; n++)
{
Console.WriteLine(n);
}
} /* Nótese que "n" se ha inicializado dentro del ciclo FOR. Esto
es totalmente válido, ya que se declara antes de su uso.
Una vez que n > 10, se culmina el ciclo. */
Programación Orientada a Objetos
51
Esta estructura permite ahorrar muchas líneas de código, ya que engloba todos los
elementos de un ciclo en una sencilla sentencia. Esto es sumamente importante, dado que
esta estructura for, es muy usada al trabajar con los arreglos (los cuales serán explicados en
la unidad V).
4.2.2 La estructura iterativa while
La estructura while (en español “mientras”), permite al programador repetir una acción, en
tanto cierta condición se mantenga verdadera. En otras palabras, realiza básicamente la
misma función que el ciclo for, sólo que aquí los componentes del ciclo son instrucciones
separadas.
Diagrama de Flujo Sintaxis
…
<valor_inicial>;
while(condición)
{
<instrucciones>;
}
…
Aquí se muestra con claridad el flujo de control en la estructura de repetición mientras:
La línea de flujo que parte del bloque instrucciones retoma a la decisión misma, que es
probada cada vez dentro del ciclo, hasta que de forma eventual la decisión se convierte en
falsa. Llegado este momento, se sale de la estructura y el control pasa al siguiente enunciado
del algoritmo.
Ejemplo 4.8: Modifique el algoritmo anterior para que use la estructura while.
public void ejemplo4_8()
{
int n = 0;
while(n <=10)
{
Console.WriteLine(n);
n++;
}
Verdadero
Falso
valor_inicial
condición instrucciones
Programación Orientada a Objetos
52
}
Obsérvese que, entre las sentencias dentro del cuerpo debe haber una que actualice la
variable de control, de forma tal que modifique la condición de iteración del bucle,
asegurando así la ejecución finita del mismo. De no haberla, o de estar mal formulada, podría
generarse lo que se llama un bucle infinito. Igualmente, una incorrecta formulación de la
variable de control (valor inicial inadecuado, inicialización dentro del bucle) o su completa
omisión, puede derivar en un error de sintaxis o ejecución.
En ocasiones, no se conoce con certeza el número total de ciclos, por lo que se debe
llevar a cabo una repetición controlada por centinela, ya que usa una variable que, al recibir
un valor específico, determina el final del ciclo.
Ejemplo 4.9: Elabore un método que permita leer una serie de números enteros positivos y
calcule su promedio. Dicho conteo deberá terminar cuando se introduzca el 0.
public void ejemplo4_9()
{
int n=1, suma = 0, cont=0;
while(n > 0)
{
Console.Write("Ingrese un número: ");
n = int.Parse(Console.ReadLine());
if(n > 0)
{
suma += n; // <- Esta variable es un acumulador
cont++; // <- Y esta variable es un contador
}
}
// En este caso, "0" es el valor centinela
if(cont > 0) // Se verifica si se insertaron valores
{
double prom = suma/cont;
Console.WriteLine("El promedio es: " + prom);
}
}
Aquí se declara una variable n que, en este caso, es el controlador del ciclo de
repetición, de tal manera que el mismo se ejecute hasta que el usuario ingrese el 0, que es el
Programación Orientada a Objetos
53
valor centinela, ya que permite que la relación n > 0 de un resultado falso, lo que culmina
el ciclo.
Como se ha dicho, si no hay una sentencia de control de repetición del ciclo, o si la
misma está mal formulada, el programa generaría un error de sintaxis (por ejemplo, si n no
está inicializada) o incluso un ciclo infinito (si se elimina la lectura del valor de n).
4.2.3 La estructura iterativa do-while
La sentencia do-while (“hacer-mientras”) es otra construcción iterativa que se usa cuando el
cuerpo de un bucle se debe ejecutar al menos una vez. En los bucles for y while, el cuerpo
del bucle se ejecuta sólo cuando la condición es verdadera. En el do-while la condición es
verificada al final, de este modo el cuerpo del bucle se ejecuta al menos una vez. Cuando
termina do/while, la ejecución continuará con el enunciado que aparezca después de la
cláusula while.
Diagrama de Flujo Sintaxis
…
<valor_inicial>
do
{
<instrucciones>;
} while(condición);
…
En otros lenguajes existen variantes de este bucle, por ejemplo, en Pascal no existe el
do-while, sino el repeat-until (“repetir-hasta”), con una estructura idéntica a la mostrada en
la tabla, salvo que allí el ciclo se ejecuta mientras la condición sea falsa.
Ejemplo 4.10: Modifique el programa anterior para que utilice una estructura do-while.
Verdadero
Falso
condición
instrucciones
valor_inicial
Programación Orientada a Objetos
54
public void ejemplo4_10()
{
int n, suma = 0, cont=0;
do
{
Console.Write("Ingrese un número: ");
n = int.Parse(Console.ReadLine());
if(n > 0)
{
suma += n;
cont++;
}
}while(n > 0);
/* Como puede observarse, el valor inicial se puede
obtener incluso dentro del cuerpo del bucle */
if(cont > 0)
{
double prom = suma/cont;
Console.WriteLine("El promedio es: " + prom);
}
}
4.2.4 Sentencias break y continue
Las sentencias break (romper) y continue (continuar) se utilizan para modificar el flujo de
control. El enunciado break, al ser ejecutado en una estructura while, for, do/while, o
switch, causa la salida inmediata de la misma. La ejecución del programa continúa con la
primera instrucción después de la estructura. Los usos comunes de esta sentencia son para
escapar en forma prematura de un ciclo, o para saltar el resto de una estructura switch (ver
4.1.3).
Ejemplo 4.11: Modifique el método mostrado en el ejemplo 4.7, para que utilice la sentencia
break cuando n = 5.
public void ejemplo4_11()
{
for(int n = 0; n <=10; n++)
{
if (x == 5)
break;
Console.WriteLine(x);
}
Console.WriteLine("El ciclo se rompe cuando X = 5");
}
Programación Orientada a Objetos
55
La salida generada es la siguiente:
Por su parte, el enunciado continue, cuando se ejecuta dentro de un ciclo, salta los
enunciados restantes del cuerpo de dicha estructura, y ejecuta la siguiente iteración del bucle.
En estructuras while y do/while, la prueba de continuación de ciclo se valúa inmediatamente
después de que se haya ejecutado el enunciado continue. En la estructura for, se ejecuta la
actualización de la variable de control, y a continuación se valúa la prueba de continuación
de ciclo.
Ejemplo 4.12: Modifique el método anterior, para que utilice la sentencia continue cuando
n = 5.
public void ejemplo4_12()
{
for(int n = 0; n <=10; n++)
{
if (x == 5)
continue;
Console.WriteLine(x);
}
Console.WriteLine("El ciclo continúa cuando X = 5");
}
En este programa, la salida generada es la que sigue:
4.3 Ejercicios propuestos:
1) Escriba un programa que lea 3 valores enteros positivos (no ceros) y que determine e
imprima si pueden ser los lados de un triángulo rectángulo.
Programación Orientada a Objetos
56
2) Escriba un programa que lea 5 números enteros y a continuación determine e imprima
cuál es el mayor y cuál es el menor del grupo.
3) Realizar un programa que sume todos los números impares comprendidos entre 1 y 1000.
4) Escriba un programa que lea un valor y a continuación imprima la figura mostrada. Éste
debería poder funcionar para todos los tamaños entre 5 y 30. Por ejemplo, si se lee un
tamaño de 5, debería imprimir:
*****
*****
*****
*****
*****
5) Escriba un programa que imprima los siguientes patrones. Cada * deberá ser impreso con
un enunciado Console.Write( ) (o WriteLine). Utilice ciclos for:
(a) (b) (c) (d)
* ********** ********** **********
** ********* ********** **********
*** ******** ********** **********
**** ******* ********** **********
***** ****** ********** **********
****** ***** ********** **********
******* **** ********** **********
******** *** ********** **********
********* ** ********** **********
********** * ********** **********
Programación Orientada a Objetos
57
UNIDAD V: DATOS COMPUESTOS
5.1 Arreglos
Muchas aplicaciones requieren el uso de múltiples elementos de datos que tienen una
característica en común. Hasta ahora, el único método que conoce para almacenar los es en
una variable. Pero, ¿cómo se almacenan 100 elementos de un mismo dato? Una forma de
hacerlo es tener 100 variables, como por ejemplo: var1, var2, …, var100. Sin embargo, sería
más simple referirse a una colección de datos con un solo identificador (var), y aún ser capaz
de poder referirse a las cada uno de ellos.
Esto se hace usando una estructura llamada arreglo, la cual se define como un grupo de
posiciones en memoria relacionadas entre sí, por el hecho de que todas tienen el mismo
nombre y son del mismo tipo. Para referirse a una posición en particular o elemento dentro
del arreglo, especificamos el nombre del arreglo y el número de posición del elemento
particular dentro del mismo.
5.1.1 Arreglo unidimensional
En la siguiente figura se muestra un arreglo de enteros:
C[0] C[1] C[2] C[3] C[4] C[5] C[6] C[7] C[8] C[9]
20 62 125 -5 0 74 3 10 89 -36
Este tipo de arreglos se conoce como vector o arreglo unidimensional. La dimensión
también se denomina el orden de un arreglo.
En el arreglo del ejemplo el orden es 10, es decir, que contiene 10 elementos.
Cualquiera de estos elementos puede ser referenciado dándole el nombre del arreglo seguido
por el número de posición de dicho elemento en particular en paréntesis cuadrados o
corchetes ([ ]), el cual es llamado subíndice, el cual debe ser un entero o una expresión entera.
Programación Orientada a Objetos
58
El primer elemento de cualquier arreglo es el elemento cero. Entonces, el primer
elemento de un arreglo, por ejemplo el arreglo C, se conoce como C[0], el segundo como
C[1], el séptimo como C[6], y en general, el elemento de orden i del arreglo C se conoce
como C[i–1].
Si un programa utiliza una expresión como subíndice, entonces la expresión se evalúa
para determinar el subíndice. Por ejemplo, si a = 5 y b = 3, entonces el enunciado C[a + b]
+= 2; añade 2 al elemento del arreglo C[8].
Para imprimir la suma de los valores contenidos en los primeros tres elementos del
arreglo C, se escribe
Console.WriteLine(C[1]+C[2]+C[3]);
Es importante notar la diferencia entre el “séptimo elemento del arreglo” y el “elemento
7 del arreglo”. Dado que los subíndices de los arreglos empiezan en 0, “el séptimo elemento
del arreglo” tiene un subíndice de 6, en tanto que “el elemento 7 del arreglo” tiene un
subíndice de siete y, de hecho, es el octavo elemento del arreglo. Este es una fuente de error
por “diferencia de uno”.
Cómo declarar los arreglos
Los arreglos ocupan espacio en memoria. El programador especifica el tipo de cada elemento
y el número de elementos requerido por cada arreglo, de tal forma que la computadora pueda
reservar la cantidad apropiada de memoria. La sintaxis es como sigue:
tipo_datos[] nombre_arreglo = new tipo_datos[tamaño];
Por ejemplo, para indicarle a la computadora que reserve 12 elementos para el arreglo
entero C, se utiliza la siguiente declaración:
int[] C = new int[12];
Programación Orientada a Objetos
59
La memoria puede ser reservada para varios arreglos dentro de una sola declaración.
Por ejemplo, para reservar 100 elementos para el arreglo entero b y 27 elementos para el
arreglo entero x, se utiliza la siguiente declaración:
int[] b = new int[100], x = new int[27];
También se pueden declarar de forma separada, así:
int[] b, x;
b = new int[100];
x = new int[27];
Estos arreglos se pueden inicializar a través de asignación, tal como se hizo con las
variables:
int[] C = new int[12];
for (int i = 0; i < 12; i++)
C[i] = 0;
Ejemplo 5.1: Desarrolle un programa que reciba como entradas 10 números enteros y los
almacene en un arreglo, luego imprima sólo los números impares.
static void Main(string[] args)
{
int i;
int[] array = new int[10];
for (i = 0; i < 10; i++)
{
Console.Write("Ingrese el valor del elemento " + i + ": ");
array[i] = int.Parse(Console.ReadLine());
}
Console.WriteLine("La lista de los elementos impares es:");
for (i = 0; i < 10; i++)
if (array[i] % 2 != 0)
Console.Write(array[i] + " ");
Console.ReadLine();
}
Otra manera de declarar/inicializar los arreglos es incluyendo los elementos dentro de
la misma declaración, quedando la sintaxis como sigue:
tipo_datos[] nombre_arreglo = new tipo_datos[] {<valores>};
Programación Orientada a Objetos
60
Por ejemplo, si se desea crear un arreglo A con los valores enteros 1, 5, 6, 8, la
declaración queda así:
int[] A = new int[] {1, 5, 6, 8};
Incluso, se puede compactar declarando así:
int[] A = {1, 5, 6, 8};
Cómo ordenar arreglos
La ordenación de datos (es decir, colocar los datos en un orden particular, como orden
ascendente o descendente) es una de las aplicaciones más importantes de la computación. Un
banco clasifica todos los cheques por número de cuenta, de tal forma que al final de cada mes
pueda preparar estados bancarios individuales. Para facilitar la búsqueda de números
telefónicos, las empresas telefónicas clasifican sus listas de cuentas por apellido y dentro de
ello, por nombre. Virtualmente todas las organizaciones deben clasificar algún dato y en
muchos casos, cantidades masivas de información. La ordenación de datos es un problema
intrigante que ha atraído alguno de los esfuerzos más intensos de investigación en el campo
de la ciencia de la computación.
Una de las técnicas más comunes para la reorganización de arreglos es el ordenamiento
de burbuja o por hundimiento, debido a que los valores más pequeños “flotan” hacia la parte
superior del arreglo, como suben las burbujas de aire en el agua, en tanto que los valores más
grandes se hunden hacia el fondo del arreglo. La técnica consiste en llevar a cabo varias
pasadas a través del arreglo, en las cuales se comparan pares sucesivos de elementos y, al
cumplirse cierta condición, se intercambian de lugares, repitiéndose tal acción hasta tener
todo el arreglo ordenado.
Ejemplo 5.2: Llenar un arreglo de 5 elementos, imprimirlos en el orden original, y luego
imprimirlos ordenados en forma ascendente (usar el método de la burbuja).
static void Main(string[] args) {
int[] C = new int[5];
int i, vuelta, temp;
Programación Orientada a Objetos
61
for (i = 0; i < 5; i++) {
Console.Write("Ingrese el elemento {0}: ",i);
C[i] = int.Parse(Console.ReadLine());
}
// Se imprime en el orden original
for (i = 0; i < 5; i++)
Console.Write(" {0} ",C[i]);
Console.WriteLine();
// MÉTODO DE LA BURBUJA
// Se realiza el recorrido del arreglo un total de N-1 veces
for(vuelta = 0; vuelta < 4; vuelta++)
/* Se evalúan los elementos de 2 en dos, es decir,
se recorre el arreglo N-1 veces */
for(i = 0; i < 4; i++)
/* Al ser un ordenamiento de forma ascendente, se
deben colocar de menor a mayor. Si un elemento
i es uno mayor que el siguiente, se cambian */
if (C[i] > C[i + 1]) {
temp = C[i];
C[i] = C[i + 1];
C[i + 1] = temp;
}
// Se imprime el arreglo ordenado en forma ascendente
for (i = 0; i < 5; i++)
Console.Write(" {0} ",C[i]);
Console.ReadLine();
}
El programa mostrado ordena en orden ascendente los valores de los elementos de un
arreglo a de diez elementos. En cada pasada, se comparan pares sucesivos de elementos. Si
un par está en orden creciente (o son idénticos sus valores), dejamos los valores tal y como
están. Si un par aparece en orden decreciente, sus valores en el arreglo se intercambian de
lugar.
Primero el programa compara C[0] con C[1], después C[1] con C[2], a continuación
C[2] con C[3], y luego la última pasada, comparando C[n-2] con C[n-1]. Note que, aunque
existen N elementos, sólo se ejecutan N-1 comparaciones. En razón de la forma en que se
llevan a cabo las sucesivas comparaciones, en una pasada un valor grande puede pasar hacia
abajo en el arreglo muchas posiciones, pero un valor pequeño pudiera moverse hacia arriba
en una posición. En la primera pasada, el valor más grande está garantizado que se hundirá
Programación Orientada a Objetos
62
hasta el elemento más inferior del arreglo, C[N-1]. En la segunda pasada el segundo valor
más grande está garantizado que se hundirá a C[N-2]. En la N-1 pasada, el valor N-1 en
tamaño se hundirá a C[1]. Esto dejará el tamaño más pequeño en C[0], por lo que sólo se
necesitan de N-1 pasadas del arreglo para ordenarlo, aun cuando existan N elementos.
Gráficamente, este tipo de ordenamiento se observa así:
La ordenación se ejecuta mediante el ciclo for anidado. Si un intercambio es necesario,
es ejecutado mediante las tres asignaciones
temp = C[i];
C[i] = C[i + 1];
C[i + 1] = temp;
donde la variable adicional temp almacena en forma temporal uno de los dos valores en
intercambio. Éste no se puede ejecutar con sólo dos asignaciones:
C[i] = C[i + 1];
C[i + 1] = C[i];
Si, por ejemplo, C[i] es 7 y C[i + l] es 5, después de la primera asignación ambos
valores serán 5 y se perderá el valor 7. De ahí la necesidad de la variable adicional temp. La
virtud más importante de la ordenación tipo burbuja, es que es fácil de programar. Sin
embargo, la ordenación tipo burbuja es de lenta ejecución, lo que se hace aparente al ordenar
Programación Orientada a Objetos
63
arreglos extensos. Una manera de solucionar este problema es optimizar el código de forma
tal que evite hacer pasadas innecesarias.
Errores más comunes en el uso de arreglos
• Diferencia de 1: Cuando se hace referencia a un elemento N, en un arreglo de tamaño N.
• Uso de subíndices inadecuados: Negativos, fraccionarios o caracteres.
• Overflow y Underflow: Cuando se hace referencia a elementos que están fuera de los
límites del arreglo.
• Cuando se usan contadores, realizar la cuenta de 1 a N, en vez de 0 a N-1.
• No emplear los subíndices cuando se desea hacer referencia a un elemento único del
arreglo. Por ejemplo, arreglo = x en vez de arreglo[y] = x.
• Utilizar un arreglo antes de haberlo construido.
5.1.2 Arreglos anidados
Se trata de un arreglo cuyos elementos son a su vez arreglos, pudiéndose así anidar cualquier
número de arreglos. Su declaración requiere de una sintaxis muy similar a la de los arreglos
unidimensionales, sólo que en esta oportunidad se colocan tantos corchetes como nivel de
anidación se requiera. Por ejemplo, para un arreglo con 2 niveles de anidamiento, se tiene la
siguiente sintaxis:
tipo_datos[][] nombre = {new tipo_datos[tam1], new tipo_datos[tam2]};
También se puede declarar un arreglo dentado sin especificar la cantidad de elementos
del arreglo interno (en tal caso, tendría un valor inicial de null), quedando su declaración de
la siguiente forma:
tipo_datos[][] nombre = new tipo_datos[cantidad][];
O, por el contrario, se pueden asignar valores iniciales a los arreglos. Por ejemplo, para
crear un arreglo de enteros formado por 2 arreglos internos, donde el primero tenga los
valores 1 y 2, y el otro los valores 3, 4 y 5, se puede emplear la siguiente declaración:
int[][] dentado = new int[2][] {new int[]{1,2}, new int[]{3,4,5}};
Programación Orientada a Objetos
64
Como en este caso se especifican los elementos, no hace falta indicar el tamaño del
mismo, por lo que una declaración equivalente queda así:
int[][] dentado = new int[][] {new int[]{1,2}, new int[]{3,4,5}};
Incluso, al igual que un arreglo unidimensional, se puede declarar así:
int[][] dentado = {new int[]{1,2}, new int[]{3,4,5}};
Para accede a los elementos de un arreglo hay que indicar entre los corchetes cuál es el
elemento exacto de los arreglos componentes al que se desea acceder, indicándose un
elemento de cada nivel de anidación entre unos corchetes diferentes pero colocándose todas
las parejas de corchetes juntas y ordenadas del arreglo más externo al más interno. Por
ejemplo, para asignar el valor 10 al elemento cuarto del arreglo que es el elemento primero
del arreglo dentado declarado anteriormente sería:
dentado[0][3] = 10:
5.1.3 Arreglos multidimensionales
Asuma que se quiere almacenar la fecha de nacimiento de estudiantes de un salón de clases
en el formato día, mes y año. Una forma de lograrlo es teniendo tres arreglos
unidimensionales llamados dia, mes y anio. Se pueden declarar como sigue:
int[] dia = new int[100], mes = new int[100], anio = new int[100;
Sin embargo, este tipo de declaraciones podrían ser confusas y generar consumo de
recursos innecesarios. Para estos casos, C# proporciona la capacidad para crear arreglos con
múltiples subíndices, llamados arreglos multidimensionales, los cuales se usan en situaciones
donde varios arreglos unidimensionales, cada uno de ellos del mismo tipo, se usan para
denotar una entidad lógica y de significado completo. Por ejemplo, para el caso anterior, se
puede declarar un arreglo llamado ddn, con múltiples dimensiones como sigue:
int[,] ddn = new int[100,3];
Esto significa que la fecha de nacimiento de 100 estudiantes está registrada en filas, y
cada fila tiene el día, mes y año de nacimiento de un estudiante en particular en cada una de
Programación Orientada a Objetos
65
las tres columnas respectivamente. Tablas o arreglos que requieren dos subíndices para
identificar un elemento en particular se conocen como arreglos de doble subíndice,
bidimensionales o matrices.
0 1 2 3
0 X[0,0] X[0,1] X[0,2] X[0,3]
1 X[1,0] X[1,1] X[1,2] X[1,3]
2 X[2,0] X[2,1] X[2,2] X[2,3]
En la figura anterior se ilustra un arreglo de doble subíndice, X, el cual se declara de la
siguiente manera: int[,] X = new int[3,4];
El arreglo contiene tres filas (renglones) y cuatro columnas, por lo que se dice que se
trata de un arreglo de 3 por 4. En general, un arreglo con m renglones y n columnas se llama
un arreglo de m × n o matriz. En C# se pueden declarar matrices de hasta 12 dimensiones.
Al igual que los arreglos unidimensionales o dentados, existen declaraciones
alternativas para las matrices. Para el ejemplo anterior, quedan definidas así:
int[,] X = new int[3,4] {{<val_pos0>}, {<val_pos1>}, {<val_pos2>}};
int[,] X = new int[,] {{<val_pos0>}, {<val_pos1>}, {<val_pos2>}};
int[,] X = {{<val_pos0>}, {<val_pos1>}, {<val_pos2>}};
Cada uno de los elementos en el arreglo a está identificado en la figura por un nombre
de elemento de la forma X[i][j]; X es el nombre del arreglo, e i y j son los subíndices que
identifican en forma única a cada elemento dentro de X. Note que los nombres de los
elementos en el primer renglón, todos tienen un primer subíndice de 0; los nombres de los
elementos en la cuarta columna, todos tienen un segundo subíndice de 3.
Se puede inicializar una matriz de manera similar a un arreglo unidimensional:
for (int i = 0; i < 3; i++)
for (int j = 0; i < 4; j++)
X[i,j] = 0;
Nótese el uso de 2 sentencias for anidadas. La más externa recorre los renglones de la
matriz, mientras que la más interna hace un recorrido por columnas. Igualmente, nótese que
Programación Orientada a Objetos
66
para tener acceso a un elemento de la matriz se deben especificar cada una de las posiciones
(o coordenadas) dentro de la misma.
Ejemplo 5.3: Una matriz de orden n × n es simétrica si A[i,j] = A[j,i] para todos los valores
de i y j. Por ejemplo, la siguiente matriz es simétrica:
0 1 2
0 1 2 3
1 2 4 5
2 3 5 6
Desarrolle un programa que cree una matriz de orden N (entre 2 y 20), reciba por teclado
cada uno de los elementos y verifique si la misma es simétrica.
static void Main(string[] args)
{
int i, j, size, valido = 1;
int[,] a = new int[20, 20];
// Validación de la entrada
do
{
Console.Write("Ingrese el orden de la matriz (2 - 20): ");
size = int.Parse(Console.ReadLine());
if(size < 2 || size > 20)
Console.WriteLine("Número incorrecto");
} while (size < 2 || size > 20);
// Se reciben los valores de la matriz
for (i = 0; i < size; i++)
for (j = 0; j < size; j++)
{
Console.Write("Ingrese el elemento [{0},{1}]: ", i, j);
a[i, j] = int.Parse(Console.ReadLine());
}
// Se verifican los elementos de la matriz
for (i = 0; i < size; i++)
for (j = 0; j < size; j++)
if(i != j) // Si no es un elemento de la diagonal
if (a[i, j] != a[j, i])
valido = 0;
if (valido == 1)
Console.WriteLine("La matriz es simétrica");
else
Console.WriteLine("La matriz no es simétrica");
Console.ReadLine();
}
Programación Orientada a Objetos
67
5.1.4 La clase System.array
En esta clase se definen los métodos esenciales para el manejo de arreglos y matrices. Entre
los más usados están los siguientes:
Lenght
Campo de sólo lectura que informa el número total de elementos que contiene un arreglo.
Para ello, se coloca el nombre del arreglo o cadena, luego un punto (que es un operador de
acceso a los miembros de una clase) y luego el método Length. El mismo retorna un valor
entero positivo. Por ejemplo, si se desea obtener la dimensión de un arreglo X con los
elementos 1, 2, 3, 4, se realiza de la siguiente manera:
int tam = X.Lenght; // Aquí tam = 4
En el caso de arreglos multidimensionales, indica el total de elementos de todas sus
dimensiones y niveles.
Ejemplo 5.4: Desarrolle un programa que cree un arreglo dentado y una matriz, y devuelva
el tamaño de ambas estructuras.
static void Main(string[] args)
{
int[][] dentado = {new int[] {1,2}, new int[] {3,4,5}};
int[,] matriz = {{1,2}, {3,4,5,6}};
Console.WriteLine(dentado.Lenght); // Imprime 5
Console.WriteLine(matriz.Lenght); // Imprime 6
Console.ReadLine();
}
int GetLength(int dimensión)
Método que devuelve el número de elementos de la dimensión especificada. Las dimensiones
se indican empezando a contar desde cero, por lo que si se quiere obtener el número de
elementos de la primera dimensión habrá que usar GetLength(0), de la segunda
GetLength(1), etc.
Programación Orientada a Objetos
68
Ejemplo 5.5: Modifique el programa del ejemplo 5.4 para mostrar las dimensiones de los
elementos de la matriz.
static void Main(string[] args)
{
int[,] matriz = {{1,2}, {3,4,5,6}};
Console.WriteLine(matriz.GetLenght(0)); // Imprime 2
Console.WriteLine(matriz.GetLenght(1)); // Imprime 4
Console.ReadLine();
}
void CopyTo(array destino, int posición)
Copia todos los elementos del arreglo sobre la que aplica en el arreglo destino a partir de la
posición que está indicada.
Ejemplo 5.6: Elabore un programa que permita copiar un arreglo dentro de otro.
static void Main(string[] args)
{
int[] a = {1, 2, 3, 4};
int[] b = {5, 6, 7, 8, 9};
a.CopyTo(b,1);
/* A partir de ahora, el arreglo b tendrá los elementos
{1, 5, 6, 7, 8, 9} */
Console.ReadLine();
}
En este caso, ambos arreglos deben ser unidimensionales, el arreglo del destino ha de
ser de un tipo que pueda almacenar los objetos del arreglo origen, el índice especificado ha
de ser válido y no ha de haber un elemento null en ninguno de los arreglos.
5.2 Cadenas
Una cadena de texto no es más que una secuencia de caracteres. C# las representa
externamente como un tipo de dato string, es decir, un alias de la clase System.String.
Las cadenas de texto suelen crearse a partir de literales de cadenas o de otras cadenas
previamente creadas. Ejemplos de ambos casos se muestran a continuación:
Programación Orientada a Objetos
69
string cad1 = "Programación";
string cad2 = cad1;
En el primer caso se ha creado un objeto string que representa la cadena formada por
la secuencia de caracteres “Programación” indicada literalmente por las comillas dobles. En
el segundo caso, la variable cad2 se genera a partir de cad1 ya existente, por lo que ambas
variables apuntarán al mismo objeto en memoria.
En C#, las cadenas se pueden tratar como arreglos unidimensionales, donde cada
carácter ocupa una posición de dicho arreglo.
0 1 2 3 4
T e x t o
Sin embargo, las cadenas son inmutables, lo que indica que no es posible modificar los
caracteres que la forman. Esto se debe a que el compilador comparte en memoria las
referencias a literales de cadenas lexicográficamente equivalentes para ahorrar memoria, y si
se permitiese modificarlos los cambios que se hiciesen a través de una variable a una cadena
compartida afectarían al resto de las variables, causando errores que serían difíciles de
detectar. Por tanto, hacer esto es incorrecto:
string cad = "Hola";
cad[0] = "A"; // Error
Pero esto sí es válido: char letra = cad[0];
5.2.1 Operadores de igualdad y concatenación
En el lenguaje C#, se ha modificado el operador de igualdad = = para que, cuando se aplique
entre cadenas, se considere que sus operandos son iguales sólo si son lexicográficamente
equivalentes y no se referencian al mismo objeto de memoria. Además, esta comparación se
hace teniendo en cuenta la capitalización usada. Por ejemplo:
string cad1 = "Hola";
string cad2 = "hola";
string cad3 = cad2;
Console.WriteLine(cad1 == cad2);
Console.WriteLine(cad2 == cad3);
Programación Orientada a Objetos
70
En este caso, la primera instrucción (cad1 = = cad2) devolverá un valor false, debido a
la diferente capitalización de la letra H. La segunda instrucción devolverá true.
Al igual que el significado de = = ha sido especialmente modificado para trabajar con
cadenas, lo mismo ocurre con el operador binario +. En este caso, cuando se aplica entre 2
cadenas, o una cadena y un carácter, lo que hace es devolver una nueva cadena con el
resultado de concatenar sus operandos. Por ejemplo:
string cad1 = "Hola ";
string cad2 = "mundo";
Console.WriteLine(cad1 + cad2);
// La salida será Hola mundo
5.2.2 Métodos de manejo de cadenas
Al ser virtualmente tratada como arreglo, el método Length se puede utilizar para determinar
el tamaño de una cadena (en este caso, cuenta la cantidad de caracteres que la componen).
string cad = "Hola";
int letras = cad.Length; // letras = 4
Copy y Substring
El método Copy devuelve una copia del objeto que se le pasa como parámetro. Por su parte,
Substring(posición, cantidad) devuelve la subcadena de la cadena sobre la que se aplica,
desde la posición indicada y el número de caracteres especificados.
string cad1 = "Programación";
string cad2 = string.Copy(cad1);
string cad3 = cad1.Substring(3,5);
string cad4 = cad1.Substring(3);
En este caso, cad2 contiene la palabra “Programación”, y cad3 la palabra “grama”. Para
cad4, al no especificarse la cantidad de caracteres, se extrae la subcadena desde la posición
indicada hasta el final de la cadena original, por lo que en este caso cad4 contiene la palabra
“gramación”.
Programación Orientada a Objetos
71
Insert, Remove y Replace
Insert(posición, subcadena) devuelve la cadena resultante de insertar la subcadena en la
posición indicada de la cadena sobre la que se aplica. Ejemplo:
string cad1 = "Torta";
string cad2 = cad1.insert(4, "ug");
Console.WriteLine(cad2); // cad2 = Tortuga
Entretanto, Remove(posición, cantidad) elimina el número de caracteres indicado a
partir de la posición especificada. De no definir cantidad, se elimina hasta el final de la
cadena. Ejemplo:
string cad1 = "Programación";
string cad2 = cad1.Remove(8,3);
string cad3 = cad1.Remove(8);
Console.WriteLine(cad2); // cad2 = Programan
Console.WriteLine(cad3); // cad3 = Programa
Replace(original, sustituta) sustituye en la cadena sobre la que se aplica el método
todas las apariciones de la cadena “original” por la sustituta.
string cad1 = "Torta para el tarro";
string cad2 = cad1.Replace("ta", "pe");
// cad2 = Torta para el perro
Se debe recordar que existe una diferencia entre una letra mayúscula y minúscula, por
lo tanto si se tiene la palabra “Casa casa” y se reemplaza “ca” por “pa” el resultado sería
“Casa pasa” en vez de “Pasa pasa”.
IndexOf y LastIndexOf
int IndexOf(posición, cadena) indica cuál es el índice de la primera ocurrencia de la cadena
especificada en el método. De no encontrar ninguna ocurrencia, el resultado es -1. La
búsqueda inicia desde la posición 0, pero con parámetros adicionales se puede especificar
desde y hasta dónde se realiza la búsqueda. Por su parte, int LastIndexOf(posición, cadena)
realiza la misma función que IndexOf, pero devuelve la posición de la última ocurrencia de
la cadena especificada.
Programación Orientada a Objetos
72
string cad = "Cucurrucucu";
int i1 = cad.IndexOf("u");
int i2 = cad.IndexOf("u",4);
int i3 = cad.IndexOf("u",4,2);
int i4 = cad.LastIndexOf("u");
int i5 = cad.LastIndexOf("u",9);
/* Aquí los resultados serían:
i1 = 1
i2 = 6
i3 = -1
i4 = 10
i5 = 8*/
ToUpper y ToLower
ToUpper y ToLower convierten respectivamente la cadena que los invoca en mayúsculas o
minúsculas. Este método es muy útil para la simplificación de programas, ya que permite
convertir a cualquier frase colocada por el usuario a una versión mucho más genérica de la
misma.
string cad1 = "Hola";
string cad2 = cad1.ToUpper();
string cad3 = cad1.ToLower();
Console.WriteLine(cad2); // cad2 = HOLA
Console.WriteLine(cad3); // cad3 = hola
5.3 Estructuras y Enumeraciones
5.3.1 Estructuras
Una estructura (struct) es una colección de datos con un comportamiento bastante similar a
una clase, de hecho ésta puede tener constructores, métodos, propiedades, entre otros (en este
sentido, si se desea un tipo de datos de tal complejidad es mejor definirlo como clase). Se
emplea más que todo para coleccionar un grupo de datos sencillos que usualmente están
relacionados a determinado objeto.
Este tipo de datos se puede crear con o sin la palabra reservada new. Si ésta es usada,
al crear el objeto se llama a un constructor por defecto. Caso contrario, los datos permanecen
Programación Orientada a Objetos
73
sin asignar, por lo tanto, éstos deben ser asignados en cualquier punto del programa, antes de
su uso. El siguiente ejemplo ilustra esto:
struct Estructura
{
public int v1;
public int v2;
}
class Program
{
static void Main(string[] args)
{
Estructura e1 = new Estructura();
Estructura e2;
Console.WriteLine(e1.v1 + "-" + e1.v2);
e2.v1 = 5;
Console.WriteLine(e2.v1);
Console.ReadKey();
/* Aquí la salida sería:
0 - 0
5
*/
}
}
Como se puede observare, el acceso a los miembros se realiza de la misma manera que
en una clase. También es de destacar que, al crearse un constructor interno, éste no puede ser
sin parámetro de entrada, ya que, a diferencia de una clase, aún se conserva ese constructor
por defecto. Por ejemplo:
struct Estructura
{
public int v1;
public int v2;
public Estructura(int a, int b)
{
v1 = a;
v2 = b;
}
}
class Program
{
static void Main(string[] args)
Programación Orientada a Objetos
74
{
Estructura e1 = new Estructura();
Estructura e2 = new Estructura(1, 2);
Console.WriteLine(e1.v1 + "-" + e1.v2);
Console.WriteLine(e2.v1 + "-" + e2.v2);
Console.ReadKey();
/* Aquí la salida sería:
0 - 0
1 - 2
*/
}
}
5.3.2 Enumeraciones
La palabra clave enum se emplea para declarar una colección de constantes identificadas por
un valor (por defecto un entero, pero puede ser cualquier tipo de datos). Es recomendable
que ésta sea declarada en el espacio de nombres (namespace) para poder ser accedido por
cualquier clase o estructura dentro del programa, sin embargo, una enumeración puede
igualmente estar anidada en los mencionados tipos.
Obsérvese el siguiente ejemplo:
enum Enumeracion { A, B, C, D, E }
class Program
{
static void Main(string[] args)
{
int x = (int)Enumeracion.A;
Console.WriteLine(x + " " + Enumeracion.A);
Console.ReadKey();
// Salida: 0 - A
}
}
Nótese la presencia de (int). En este caso se está realizando una conversión de tipo para
obtener su valor equivalente. También es importante aclarar que, de no especificarse, la
enumeración empieza desde 0. Es decir, el valor B sería 1, C sería 2, y así sucesivamente.
Programación Orientada a Objetos
75
Al realizar esta acción:
enum Enumeracion { A=1, B, C, D, E }
se estaría forzando al sistema a enumerar desde 1, en vez de hacerlo desde 0 (en este sentido,
B = 2, C = 3, etc.). Incluso, se puede forzar la numeración así:
enum Enumeracion { A, B, C=10, D, E=20 }
por lo que A = 0, B = 1, C = 10, D = 11 y E = 20. Ahora, si lo que se desea es usar otro tipo
de datos para una enumeración, se puede especificar de la siguiente manera:
enum Enumeracion : long { A, B, C=10, D, E=20 }
Nota: Solamente se aceptan los siguientes tipos de datos para las enumeraciones: byte, sbyte,
short, ushort, int, uint, long o ulong .
Nota2: En ocasiones, al declarar Estructuras o Enumeraciones se coloca un punto y coma
luego de la llave final. Esto es completamente válido y no afecta la ejecución del programa.
5.4 Ejercicios propuestos
1) Escriba enunciados individuales que ejecuten cada una de las operaciones siguientes, de
arreglos de 1 subíndice:
a. Inicialice a 0 los 10 elementos del arreglo entero cero.
b. Añada 1 a cada uno de los 15 elementos del arreglo entero uno.
c. Lea desde el teclado 12 valores de punto flotante y almacénalos en el arreglo
flotante.
d. Imprima los 5 valores del arreglo entero imprimir en formato de columna.
2) Desarrolle un programa que reciba por teclado N elementos de un arreglo (N entre 5 y
20), muestre el arreglo original y ordenado, luego determine y muestre la mediana (la
mediana es el valor del elemento medio de un arreglo, por ejemplo, en el arreglo {1, 5,
7, 8, 10} la mediana es 7).
Programación Orientada a Objetos
76
3) Considere una matriz cuadrada de orden n × n. Escriba un programa que lea los datos de
dicha matriz (enteros) y encuentre la suma de todos los elementos del borde.
4) Realice un programa que reciba una oración e indique cuántas palabras existen. Además,
deberá indicar cuántas veces aparece la letra “a” (incluya las mayúsculas, considere que
no hay acentos)
5) Desarrolle un programa que reciba una frase y 3 palabras, y deberá realizar lo siguiente:
• Encontrar la primera ocurrencia de la primera palabra.
• Si lo anterior es cierto, deberá reemplazarla por la segunda palabra.
• De aparecer más de una vez, deberá reemplazar las demás ocurrencias por la tercera
palabra
6) Incluya en cualquiera de los ejercicios propuestos la declaración y uso de enumeraciones
y estructuras. Su contenido queda a elección.
7) Si desea hacer una enumeración con valores de tipo bool, ¿cómo lo haría?
Programación Orientada a Objetos
77
UNIDAD VI: FUNDAMENTOS AVANZADOS DE PROGRAMACIÓN
6.1 Propiedades de uso, modificadores de parámetros y regiones
En C# existen ciertas instrucciones o palabras reservadas que se emplean para fines
específicos, ya sea para la reorganización del código para mejor visibilidad, o el tratamiento
de determinados datos. Entre ellos se encuentran las sentencias get, set, ref, out y region.
6.1.1 Propiedades de uso: get y set
Las propiedades combinan aspectos tanto de los atributos como los métodos. Para el usuario
de un objeto, una propiedad aparece bajo la forma de un campo, cuyo acceso requiere de la
misma sintaxis. Para el implementador de la clase, una propiedad es uno o dos bloques de
código, representados por los modificadores de acceso get y/o set. El primero se ejecuta
durante un proceso de lectura de la propiedad, mientras el segundo lo hace durante la escritura
de la misma.
Una propiedad sin el modificador set se considera de sólo lectura, pero si el
modificador faltante es get se considera de sólo escritura. De estar presentes ambas, se trata
de una propiedad de lectura/escritura. Cabe destacar que una propiedad no se clasifica como
una variable. Las propiedades se declaran en el bloque de la clase especificando el nivel de
acceso del campo, seguido del tipo y el nombre de la propiedad, para luego añadir uno o dos
bloques de código que declara el modificador get y/o set. Por ejemplo:
public class EjemploGetSet
{
private int variable;
public int Variable
{
get { return this.variable; }
set { this.variable = value; }
}
}
Programación Orientada a Objetos
78
En este ejemplo, la propiedad Variable usa un campo privado para hacer seguimiento
del valor actual. Es recomendable hacerlo de esa forma ya que, aunque esta forma de usar a
Variable no daría error de sintaxis:
public class EjemploGetSet
{
public int Variable
{
get { return this.Variable; }
set { this.Variable = value; }
}
}
puede presentarse un error en tiempo de ejecución debido a que la llamada a set puede generar
un ciclo infinito de asignación de variable. Otra forma de solventar el problema es declarando
una “autopropiedad” (aplicable a partir de C# 3.0), de esta forma:
public class EjemploGetSet {
public int Variable { get; set; }
}
ya que aquí se estaría invocando al campo privado oculto.
Nota: El operador get, de ser definido, debe incluir una sentencia return o throw, mientras
el operador set tiene un retorno vacío, y usa la palabra reservada value cuyo tipo de dato es
el definido en la declaración de la propiedad.
Ejemplo 6.1: Elabore un programa que permita leer un valor e imprimirlo. Utilice los
operadores get y set para tal fin.
namespace GetSet
{
public class EjemploGetSet
{
private int variable = 0; // Como buena práctica de prog.,
public int Variable // se recomienda un valor inicial
{ // para la variable privada.
get { return this.variable; }
set { this.variable = value; }
}
}
Programación Orientada a Objetos
79
static void Main(string[] args)
{
EjemploGetSet ej = new EjemploGetSet();
ej.Variable = 10;
Console.WriteLine("Número: " + ej.Variable;);
Console.ReadKey();
}
}
6.1.2 Modificadores de parámetros: ref, in y out
Los modificadores de parámetros son aquellas palabras reservadas que permiten cambiar la
manera en que son tratados los parámetros dentro de un método.
El operador ref indica que una variable es pasada por referencia. Éste se usa en los
siguientes contextos:
• En la firma o llamada a un método, para pasar un argumento al método por referencia.
• En la firma de un método para retornar un valor en la llamada por referencia.
• En un miembro del cuerpo, para indicar que una variable es almacenada localmente como
referencia que el invocante pretende modificar o, en general, una variable local.
• En la declaración de una estructura para declarar una estructura de referencia, o una
estructura aleatoria.
Para usar un parámetro ref, tanto en la definición del método como el método de
llamada se debe emplear explícitamente esta palabra reservada. Además, éste debe ser
previamente inicializado. Por ejemplo:
void MetodoRef(ref int param)
{ param = param + 44; }
static void Main(string[] args)
{
int num = 1;
MetodoRef(ref num);
Console.WriteLine("Número: " + num);
Console.ReadKey();
}
Programación Orientada a Objetos
80
Los miembros de una clase no pueden tener firmas sobrecaregadas que difieran
solamente en el uso de la palabra reservada (ref, in u out). El siguiente código es un ejemplo
de ello, el cual no compliará:
class EjemploError
{
public void Metodo(out int i) { }
public void Metodo(ref int i) { }
}
Sin embargo, sí pueden existir sobrecargas si uno de los métodos tiene alguna de las
palabras reservadas, y el otro no. Por ejemplo:
class EjemploValido
{
public void Metodo(int i) { }
public void Metodo(ref int i) { }
}
Con respecto a la palabra reservada in, ésta también indica el paso de parámetros por
referencia, y al igual que ref, debe ser previamente inicializada antes de su uso. Su principal
diferencia es que en esta oportunidad el método invocante no puede modificar el valor del
atributo referenciado, cosa que es posible con ref. El parámetro in está disponible desde C#
7.2, por lo tanto, las versiones anteriores a éste generan el error de compilación CS8107.
void MetodoIn(in int param)
{ /* Instrucciones */ }
static void Main(string[] args)
{
int num = 44;
MetodoIn(num);
Console.WriteLine("Número: " + num);
Console.ReadKey();
}
Como se puede ver en el ejemplo anterior, el uso de in no es necesario en la llamada
del método. Sólo es necesario en su declaración.
Programación Orientada a Objetos
81
Este operador también puede ser utilizado dentro de una instrucción, al hacer referencia
a algún elemento de una variable inicialmente declarada como var o una estructura
compuesta.
string archivos = getArchivos();
foreach (string arch in archivos)
{ /* Instrucciones */ }
Nota: foreach es una palabra reservada que permite recorrer una estructura o lista de datos,
siendo equivalente al uso de la sentencia for.
Finalmente se tiene a out, el cual permite el paso de un parámetro por referencia sin
necesidad de éste ser inicializado. De hecho, su inicialización se realiza dentro del método
que recibe a dicho parámetro. Al igual que ref, esta palabra reservada debe estar presente
tanto en la llamada como en el método. Por ejemplo:
void MetodoOut(out int param)
{ param = 44; }
static void Main(string[] args)
{
int num;
MetodoOut(out num);
Console.WriteLine("Número: " + num);
Console.ReadKey();
}
La ventaja principal de out, es que se pueden crear métodos que, de alguna manera,
permiten obtener el valor de varias variables internas, en vez de crear un método para cada
retorno requerido (recuérdese que return sólo permite retornar un parámetro). El siguiente
ejemplo ilustra mejor este caso:
namespace NormalVsOut
{
public class Metodos
{
private int v1 = 0, v2 = 1, v3 = 10:
public int getV1() // Retorno individual de v1
{ return v1; }
public int getV2() // Retorno individual de v2
{ return v2; }
Programación Orientada a Objetos
82
public int getV3() // Retorno individual de v3
{ return v3; }
public int getAll(out int a, out int b) // Retorno de todas
{
a = v1;
b = v2;
return v3;
}
}
static void Main(string[] args)
{
Metodos m = new Metodos();
int a, b, c;
// Aquí se obtienen los valores por separado
a = m.getV1();
b = m.getV2();
c = m.getV3();
// Aquí se obtienen todos los valores juntos
c = m.getAll(out a, out b);
Console.ReadLine();
}
}
Como se pudo observar, es necesaria la declaración de la variable antes de ser empleada
con el operador out. Sin embargo, a partir de C# 7.0 dicha declaración se puede hacer en la
misma línea de su uso. Por ejemplo:
string param = "1640"; }
if(Int32.TryParse(num, out int n)
Console.WriteLine("Es un número");
else
Console.WriteLine("Error");
Incluso, se puede hacer la llamada en forma más genérica, reemplazando el tipo de dato
(en el ejemplo, int), por var:
if(Int32.TryParse(num, out var n)
{ /* Instrucciones */ }
Programación Orientada a Objetos
83
6.1.3 Regiones
Una región permite al programador especificar bloques de código delimitados por las
directivas #region y #endregion, que pueden ser fácilmente expandidos o colapsados dentro
del editor. No afecta la funcionalidad del programa, pero permite al desarrollador una mejor
organización del código, sobretodo si éste tiene demasiadas instrucciones, ya que esta
característica permite ocultar los bloques que en ese momento no están siendo utilizados,
para enfocarse en un bloque en particular.
El siguiente ejemplo muestra cómo se define una región, y cómo éstas se ven cuando
están colapsadas.
namespace ProbandoRegiones
{
class Program
{
#region Bloque de métodos principales
static void Main(string[] args)
{ /* Contenido */ }
static void MetodoP1()
{ /* Contenido */ }
#endregion
#region Bloque de métodos secundarios
static void MetodoS1()
{ /* Contenido */ }
static void MetodoS1(int x)
{ /* Contenido */ }
static void MetodoS2()
{
#region Bloque1 de MetodoS2
/* instrucciones */
#endregion
#region Bloque2 de MetodoS2
/* instrucciones */
#endregion
}
#endregion
}
}
Programación Orientada a Objetos
84
El texto escrito justo en la línea de #region sirve como identificador de la misma,
comportándose en este caso de la misma forma que un comentario (código no ejecutable).
Por eso esta línea, a pesar del evidente “error de sintaxis”, no genera error ya que ésta no
forma parte del código a compilar.
6.2 Recursividad
La recursividad es un concepto muy abstracto y complejo que tiene que ver tanto con la
lógica como también con la matemática y otras ciencias. Se trata de una forma de definir un
proceso a través del uso de premisas que dan más información que el método en sí mismo, o
que utilizan los mismos términos que ya aparecen en su nombre, por ejemplo, cuando se dice
que la definición de algo es ese algo mismo.
La recursividad tiene como característica principal la sensación de infinito, de algo que
es continuo y que por tanto no puede ser delimitado en el espacio o el tiempo porque se sigue
replicando y multiplicando de manera lógica y matemática. Así, es común encontrar casos
de recursividad por ejemplo en imágenes de espejos que hacen que la imagen sea replicada
al infinito, una dentro de otra hasta que deja de verse pero no por eso deja de existir. Otro
caso típico de recursividad en las imágenes es cuando hay una publicidad en la que el objeto
Programación Orientada a Objetos
85
tiene la propaganda de sí mismo en su etiqueta y así al infinito, o cuando una persona está
sosteniendo una caja de un producto en cuya etiqueta aparece esa misma persona sosteniendo
el mismo producto y así hasta el infinito. En estos casos, la recursividad pasa por el hecho de
que se busca definir algo con lo misma información que ya se tiene. Un ejemplo básico de
recursividad es el cálculo del factorial de un número:
n! = n × n-1 × n-2 × … × 2 × 1
Por ejemplo, 5! = 5 × 4 × 3 × 2 × 1. Nótese entonces que es igual decir que 5 multiplica
a 4!, ya que éste es 4 × 3 × 2 × 1; a su vez, se puede decir que el factorial de 4 es ese número
por el factorial de 3, y así sucesivamente. Esto permite obtener la siguiente representación
recursiva:
n! = n × (n – 1)!
cuya sucesión se detendrá cuando se alcance a 0 (ya que 0! = 1).
En programación, es una técnica que permite definir funciones que se llaman a sí
mismas en forma consecutiva, permitiendo resolver problemas a partir de casos más simples
de los mismos. La mayoría de los lenguajes de programación dan soporte a la recursión,
permitiendo a una función llamarse a sí misma desde el texto del programa.
Los lenguajes imperativos definen las estructuras de ciclos (como while y for) que son
usadas para realizar tareas repetitivas. Algunos lenguajes de programación funcionales no
definen estructuras de ciclos, sino que posibilitan la recursión llamando código de forma
repetitiva. La teoría de la computabilidad ha demostrado que estos dos tipos de lenguajes son
matemáticamente equivalentes, es decir que pueden resolver los mismos tipos de problemas,
aunque los lenguajes funcionales carezcan de las típicas estructuras while y for.
En C# sucede igual, existiendo la posibilidad de resolver operaciones tanto en forma
iterativa como recursiva. El siguiente ejemplo muestra el cálculo del factorial de un número
en ambas variantes:
Programación Orientada a Objetos
86
Ejemplo 6.2: Elabore un programa que calcule el factorial de un número. Incluya un método
iterativo y uno recursivo.
class Program
{
static void Main(string[] args)
{
Console.Write("Ingrese un valor: ");
int x = int.Parse(Console.ReadLine());
Factorial F = new Factorial(x);
Console.WriteLine("Iterativo: " + F.Iterativo());
Console.WriteLine("Recursivo: " + F.Recursivo());
Console.ReadKey();
}
}
class Factorial
{
public int n { get; private set; }
public Factorial(int n)
{ this.n = n; }
public int Iterativo()
{
if (n < 0)
return 0;
else
{
int f = 1;
if (n > 1)
for (int i = 2; i <= n; i++)
f *= i;
return f;
}
}
public int Recursivo()
{
if (n < 0) return 0;
else return Recursivo(n);
}
private int Recursivo(int m)
{
if (m == 0)
return 1;
else return m * Recursivo(m - 1);
}
}
Para entender el ejercicio, supóngase que n = 3. Entonces, el proceso de llamada sería
como sigue:
Programación Orientada a Objetos
87
Primera Llamada
• Recursivo(3)
• ¿ m == 0 ? NO
• Retorno = 3 × Recursivo(3-1)
Segunda llamada
o Recursivo(2)
o ¿ m == 0 ? NO
o Retorno = 2 × Recursivo(2-1)
Tercera llamada
▪ Recursivo(1)
▪ ¿ m == 0 ? NO
▪ Retorno = 1 × Recursivo(1-1)
Cuarta llamada
✓ Recursivo(0)
✓ ¿ m == 0 ? SI
✓ Retorno = 1
Regreso a la tercera llamada
▪ Retorno 1 × 1 = 1
Regreso a la segunda llamada
o Retorno 2 × 1 = 2
Regreso a la primera llamada
• Retorno 3 × 2 = 6
Como el programa no ha de continuar ejecutándose indefinidamente, un procedimiento
recursivo está bien definido si tiene las siguientes propiedades:
• Debe existir un cierto criterio, llamado criterio base, por el que el procedimiento no se
llama así mismo. Para el ejemplo 6.2, el caso base es m = 0.
• Cada vez que el procedimiento se llame a sí mismo (directa o indirectamente), debe estar
más cerca del criterio base. En el ejemplo, cada llamada corresponde a un valor de m
cada vez menor, acercándose a 0 (caso base).
Programación Orientada a Objetos
88
Existen dos tipos de recursividad:
• Directa: Cuando un subprograma se llama a sí mismo una o más veces directamente.
• Indirecta: Cuando se definen una serie de subprogramas usándose unos a otros. A este
tipo de recursividad también se le conoce como recursividad cruzada.
Un algoritmo recursivo consta de una parte recursiva, otra iterativa o no recursiva y
una condición de terminación. La parte recursiva y la condición de terminación siempre
existen. En cambio la parte no recursiva puede coincidir con la condición de terminación.
Algo muy importante a tener en cuenta cuando se use la recursividad es que es necesario
asegurarse que llega un momento en el que no deben hacerse más llamadas recursivas. Si no
se cumple esta condición el programa no parará nunca.
La principal ventaja es la simplicidad de comprensión y su gran potencia, favoreciendo
la resolución de problemas de manera natural, sencilla y elegante; y facilidad para comprobar
y convencerse de que la solución del problema es correcta. Su desventaja es la ineficiencia
tanto en tiempo como en memoria, dado que para permitir su uso es necesario transformar el
programa recursivo en otro iterativo, que utiliza bucles y pilas para almacenar las variables.
En el ejemplo 6.2, la implementación iterativa es probablemente más rápida en la
práctica que la recursiva. Este resultado es lógico, pues las funciones iterativas no tienen que
pagar el exceso de llamadas de funciones como en el caso de las funciones recursivas, y ese
exceso es relativamente alto en muchos lenguajes de programación. Hay otros tipos de
problemas cuyas soluciones son inherentemente recursivas, porque están al tanto del estado
anterior. Un ejemplo es el árbol transversal; otros incluyen la función de Ackermann y el
algoritmo divide y vencerás tales como Quicksort. Todos estos algoritmos pueden
implementarse iterativamente con la ayuda de una pila, pero la necesidad del mismo, puede
que anule las ventajas de la solución iterativa.
Es importante destacar que la recursividad permite resolver problemas de gran
complejidad, sin embargo, en ciertos casos puede añadir complicaciones innecesarias a los
programas. Además, los métodos recursivos deben ser diseñados con mucha cautela y
Programación Orientada a Objetos
89
precisión para no desembocar en un ciclo infinito, que conllevará a una
StackOverflowException, que no es más que una medida que el sistema adopta para advertir
al usuario de la recursividad infinita y para detener un programa que, de no aplicarse esta
medida, continuaría ejecutándose, malgastando tiempo y recursos del sistema.
6.3 Referencias internas
Para entender este punto, es necesario conocer los siguientes conceptos:
• Pila: Es una zona de memoria reservada para almacenar información de uso inmediato
por parte del hilo de ejecución actual del programa. Por ejemplo, cuando se llama a una
función se reserva un bloque en la parte superior de esta zona de memoria (de la pila)
para almacenar los parámetros y demás variables de ámbito local. Cuando se llama a la
siguiente función este espacio se “libera” (en el sentido de que ya no queda reservado) y
puede ser utilizado por la nueva función. Es por esto que si se realizan demasiadas
llamadas anidadas a funciones (en recursión, por ejemplo) en algún momento se agota el
espacio en la pila, obteniendo un “stack overflow”.
• Montón: Es una zona de memoria reservada para poder asignarla de manera dinámica.
A diferencia de la pila, aquí no existen “normas” para poder asignar o desasignar
información en el montón, pudiendo almacenar y eliminar datos en cualquier momento,
lo cual hace más complicada la gestión de la memoria en esta ubicación.
• Variables por valor: Son tipos sencillos que almacenan un dato concreto y que se
almacenan en la pila. Por ejemplo, los tipos primitivos de .NET como int o bool, las
estructuras o las enumeraciones. Se almacenan en la pila y se copian por completo cuando
se asignan a una función. Por eso cuando se pasa un tipo primitivo a una función, aunque
se cambie dentro de ésta, éste no se ve reflejado fuera de la misma.
Programación Orientada a Objetos
90
• Variable por referencia: Es aquella que contiene la dirección de la memoria del lugar
donde se encuentran los datos. En concreto, todas las clases de objetos en .NET, así como
algunos tipos primitivos que no tienen un tamaño determinado (como las cadenas). Estos
tipos de datos se alojan siempre en el montón, por lo que la gestión de la memoria que
ocupan es más compleja, y el uso de los datos es menos eficiente (y de menor
rendimiento) que con los tipos por valor.
Hasta este punto se han trabajado con ambos tipos de variables; sin embargo, es
importante establecer la diferencia entre ambas, y esto se logra con los siguientes ejemplos:
Ejemplo 6.3: Elabore un programa que use llamada por valor para la suma de números.
class Program
{
static void Main(string[] args)
{
int i = 5;
Console.WriteLine(Suma2(i)); // Salida: 7
Console.WriteLine(i); // Salida: 5
Console.ReadKey();
}
public int Suma2(int n)
{
n = n+2;
return n;
}
}
Al definir la variable i, ésta se almacena en la pila directamente ya que es un tipo por
valor. Al llamar a la función Suma2 pasándole i como argumento, lo que ocurre es que el
valor de la variable i se copia a la pila y se asigna al parámetro n de la función. De esta forma
se tienen dos copias del dato en la pila, por eso al modificar n no se afecta para nada al valor
original de i. En el montón, por cierto, no hay nada almacenado.
Programación Orientada a Objetos
91
Ejemplo 6.4: Elabore un programa que use llamada por referencia para cambiar un dato.
class Program
{
static void Main(string[] args)
{
Persona p = new Persona();
p.Nombre = "Pedro";
p.Apellidos = "Pérez";
p.Edad = 25;
CambiaNombre(p);
Console.WriteLine(p.Nombre); // Salida: Pablo
Console.ReadKey();
}
public static void CambiaNombre(Persona per) {
per.Nombre = "Pablo";
}
}
public class Persona
{
public string Nombre;
public string Apellidos;
public int Edad;
}
Al declarar una variable de tipo Persona se genera una instancia de la misma en el
montón, ya que es un tipo por referencia. Al asignar los valores de las diferentes propiedades,
esos datos se almacenan en el montón asociadas a la instancia de la clase Persona que se
acaba de crear. Lo que realmente almacena la variable p es una referencia a los datos de esa
persona que están en el montón.
Programación Orientada a Objetos
92
Es decir, ahora en la pila no se almacenan los datos en sí, sino tan solo un “puntero” a
los mismos. Es una forma indirecta de referirse a ellos, no como antes en los tipos por valor
que el dato se almacenaba directamente en la pila.
Al llamar a la función CambiaNombre, en la variable local per que constituye el
parámetro de la función, lo que se duplica ahora en la pila es la referencia al objeto, no el
objeto en sí. Es decir, al hacer la llamada se disponen de dos variables que apuntan al mismo
tiempo a los datos almacenados en el montón. Por eso, cuando se cambia una propiedad del
objeto, al estar ambas referencias apuntando a los mismos datos, los cambios se ven desde
los dos lados.
En realidad, el comportamiento de la pila es idéntico en ambos casos, lo que cambia es
la información que se almacena, que es el propio dato en el caso de los tipos por valor, y la
referencia al dato en el caso de los tipos por referencia. Este modo de funcionamiento es la
diferencia fundamental que existe entre los tipos por valor y los tipos por referencia, y es
muy importante tenerla clara pues tiene muchas implicaciones a la hora de escribir código.
6.3.1 Clases autoreferenciadas
Una clase autoreferenciada es aquella que, entre sus miembros, incluye una variable que
apunta a un objeto de su mismo tipo. Ejemplo:
class AutoRefer
{
public string nombre;
public AutoRefer siguiente;
}
Programación Orientada a Objetos
93
El miembro siguiente se conoce como un enlace o vínculo ya que sirve para vincular
una clase con otra del mismo tipo. Éstas pueden ser enlazadas juntas para formar útiles
estructuras de datos como son las listas, las colas de espera, las pilas y los árboles. La gráfica
es un ejemplo de ello:
6.3.2 Código inseguro y apuntadores
En C# existen las construcciones programáticas conocidas como punteros (o pointers), el
cual es una variable especial que almacena una dirección de memoria, pero, a diferencia de
las variables por referencia, este tipo de construcción opera en un contexto inseguro, lo que
quiere decir que la gestión automática de memoria de la CLR no está presente y es el
programador mismo quien debe encargarse de localizar (allocate) y deslocalizar (deallocate)
memoria para las estructuras de datos. Su sintaxis es como sigue:
type* identificador;
void* identificador; // Permitido, pero no recomendado
Los tipos permitidos para un puntero son:
• sbyte, byte, short, ushort, int, uint, long, ulong, char, float, float, double, decimal, o bool.
• Tipos enum (Enumeraciones en C#).
• Tipos struct definidos por el usuario (Structs en C#).
int* p; // puntero a un entero
int** p; // puntero a un puntero de un entero
int*[] p; // arreglo unidimensional de punteros de enteros
char* p; // puntero a un caracter
void* p; // puntero a un tipo no definido o desconocido
Programación Orientada a Objetos
94
Entre los operadores principales de punteros están:
• Dirección &: Apunta a la dirección de una memoria.
• Desreferencia *: Retorna la variable en la dirección apuntada.
• Apuntador a miembro –>: Versión comprimida de x–>, equivale a (*x).y
• Indexación [ ]: Indexa un apuntador.
Para poder usar los apuntadores, se debe delimitar el código con la palabra reservada
unsafe. Esto se puede observar en el siguiente ejemplo:
// Se declara un arreglo de ejemplo
int[] a = new int[5] { 10, 20, 30, 40, 50 };
// Se declara un bloque de código inseguro
unsafe
{
// Se debe fijar el objeto al montón para que no se mueva
fixed (int* p = &a[0])
{
// Como p está fijado, se crea otro apuntador
int* p2 = p;
Console.WriteLine(*p2);
// Al incrementar a p2 se desplaza por bytes (en este caso 4)
p2 += 1;
Console.WriteLine(*p2);
p2 += 1;
Console.WriteLine(*p2);
Console.WriteLine("--------");
Console.WriteLine(*p);
// Con la desreferencia, se cambia el valor de a[0]
*p += 1;
Console.WriteLine(*p);
*p += 1;
Console.WriteLine(*p);
}
}
Console.WriteLine("--------");
Console.WriteLine(a[0]);
/* Salida:
10
20
30
--------
10
11
12
--------
12 */
Programación Orientada a Objetos
95
6.4 Herencia
La herencia es una forma de reutilización del software, en la cual se crean clases nuevas a
partir de clases existentes mediante la absorción de sus atributos y métodos, y embelleciendo
éstos con capacidades que las clases nuevas requieren. Al crear una clase nueva, en vez de
escribir en su totalidad miembros de datos y métodos, el programador puede determinar que
la clase nueva puede heredar los miembros provenientes de una clase base (padre) ya
definida. La clase nueva se conoce como clase derivada (hija).
Cada objeto que instancia una clase derivada también hace referencia a la clase padre,
pero lo contrario no es cierto: Ningún objeto de la clase padre es objeto de clase derivada
alguna. Para declarar una clase derivada se utiliza la siguiente sintaxis:
class clase_derivada:clase_base
{
miembros
}
Hay que recordar que en este tipo de clases puede aplicarse el modificador de acceso
protected.
Ejemplo 6.5: Desarrolle un programa que calcule el área de las figuras cuadrangulares.
Las figuras cuadrangulares involucran los cuadrados, rectángulos, paralelogramos,
entre otras. En este ejemplo sólo se usarán las dos primeras. Para empezar, se declara la clase
padre de la siguiente manera:
class Cuadrangulares
{
private int S;
protected string tipo;
protected void area(int x, int y)
{ S = x * y; }
public void imprimir()
{
Programación Orientada a Objetos
96
Console.WriteLine("El área del {0} es {1}", tipo, S);
Console.ReadLine();
}
}
En esta clase se colocaron 2 métodos que son comunes a las figuras cuadrangulares.
Luego se tienen dos clases derivadas: Una para el cuadrado, y otra para el rectángulo.
class cuadrado : Cuadrangulares
{
public cuadrado()
{ tipo = "Cuadrado"; }
public string ejecutar(int L)
{
area(L, L);
return "Cálculo ejecutado con éxito";
}
}
class rectángulo : Cuadrangulares
{
public rectángulo()
{ tipo = "Rectángulo"; }
public string ejecutar(int B, int H)
{
area(B, H);
return "Cálculo ejecutado con éxito";
}
}
En ambas clases se puede observar que se usa el atributo tipo a pesar de no estar
definido en ellas. Esto obedece a que tipo es un atributo común a ambas, puesto que está
definido en la clase padre. Lo mismo ocurre con el método área. Ambos miembros están
definidos como protegidos, por lo tanto, si se intenta acceder a ellos desde cualquier método
externo, dará error de acceso.
Por último, se codifica el programa principal:
static void Main(string[] args)
{
cuadrado c = new cuadrado();
rectángulo r = new rectángulo();
Console.WriteLine("Ingrese el lado: ");
int L = int.Parse(Console.ReadLine());
Programación Orientada a Objetos
97
Console.WriteLine(c.ejecutar(L));
c.imprimir();
Console.WriteLine("Ingrese la base y altura: ");
int B = int.Parse(Console.ReadLine());
int H = int.Parse(Console.ReadLine());
Console.WriteLine(r.ejecutar(B,H));
r.imprimir();
}
Aquí se invoca directamente al método imprimir( ), el cual pertenece a la clase base
Cuadrangulares.
Como se pudo notar, la herencia representa uno de los pilares fundamentales de la
programación orientada a objetos, puesto que permite realizar una jerarquía de clases,
agrupando elementos comunes a varias clases en una clase principal (por ejemplo, personas),
para ser utilizadas con los atributos y métodos de las clases derivadas (por ejemplo,
estudiantes, obreros, profesores).
6.5 Interfaces
Una interfaz contiene definiciones para un grupo de funcionalidades relacionadas que pueden
ser implementadas por una clase o estructura. Con su uso el programador puede, por ejemplo,
implementar el comportamiento de múltiples fuentes dentro de una clase, ya que C# no
permite la herencia múltiple de las clases. Se puede igualmente usar la interfaz si se desea
simular la herencia de estructuras, ya que estas últimas, de por sí, no soportan la herencia.
Una interfaz se define empleando la palabra reservada Interface y, por convención /y
como recomendación), el nombre de la interfaz suele estar precedido por una I. Ejemplo:
public interface IAve {
void Volar(); void Comer();
}
Como se puede observar, en la interfaz se crean las funciones comunes para las clases
o estructuras, sin embargo no se implementan, pues las mismas deben ser implementadas
Programación Orientada a Objetos
98
dentro de las estructuras antes mencionadas. En otras palabras, una interfaz tiene un
comportamiento similar a una clase abstracta, salvo que la interfaz puede ser implementada
múltiples veces dentro de una clase o estructura, mientras una clase abstracta sólo una vez.
Las interfaces pueden contener métodos, propiedades, eventos, índices o una
combinación de ellos, mas no constantes, operadores, constructores, finalizadores o tipos.
Los miembros de una interfaz son automáticamente públicos, de hecho, éstos no pueden
incluir ningún modificador de acceso, ni tampoco pueden ser estáticos.
Para implementar un miembro de interfaz, el miembro correspondiente de la clase o
estructura que lo implementa debe ser público, no estático, y tener el mismo nombre y firma
del miembro de la interfaz. Además, la clase o estructura debe proveer la implementación a
todos los miembros definidos dentro de la interfaz. Dicha implementación se realiza una sola
vez y solamente si la clase declara a la interfaz como parte de la definición de la clase,
empleando el operador : (dos puntos).
public class AvePropiedades : IAve {
public int Patas=2; public int Alas=2; public int Cola=1; public string Nombre{ get; private set; }
public AvePropiedades(string nombre) { this.Nombre = nombre; } public void Volar() { Console.WriteLine ("Volar"); }
public void Comer() { Console.WriteLine ("Comer"); }
}
Hay que destacar que, si la interfaz es implementada por una clase base, todas las clases
hijas heredan esa implementación:
public class Ave : AvePropiedades { public Ave(string N) : base(N) { } }
Programación Orientada a Objetos
99
Ejemplo 6.6: Elabore un programa que implemente la interfaz IAve.
class MainClass
{
public static void Main(string[] args)
{
Ave canario = new Ave ("Canario");
Ave cuervo = new Ave ("Cuervo");
MainClass main = new MainClass();
main.imprimir(canario);
main.imprimir(cuervo);
Console.ReadKey();
}
public void imprimir(Ave ave)
{
Console.WriteLine("Soy un {0} tengo {1} Pata(s), {2} Ala(s) y {3}
Cola(s)", ave.Nombre, ave.Patas, ave.Alas, ave.Cola);
Console.WriteLine("Puedo " + ave.Comer());
Console.WriteLine("y tambien puedo " + ave.Volar());
}
}
¿Cuándo usar interfaces?
En general, siempre que se tengas o se prevea que se puede tener más de una clase para hacer
lo mismo: usar interfaces. Es mejor pecar de exceso que de defecto en este caso. No hay que
preocuparse por penalizaciones de rendimiento en la aplicación porque no las hay.
No significa que toda clase deba implementar una interfaz obligatoriamente, muchas
clases internas no lo implementarán, pero en el caso de las clases públicas (visibles desde el
exterior) es algo que debería tomarse en consideración. Además, pensar en la interfaz antes
que en la clase en sí, es pensar en lo que debe hacerse en lugar de pensar en cómo debe
hacerse. Usar interfaces permite a posteriori cambiar una clase por otra que implemente la
misma interfaz y poder integrar la nueva clase de forma mucho más fácil (sólo se debe
modificar donde instanciar los objetos, el resto de código queda igual).
6.6 Ejercicios propuestos
1) Elija algún ejercicio de las unidades VI o V y realice los siguientes cambios:
Programación Orientada a Objetos
100
a. Defina los atributos internos utilizando los operadores get y set.
b. Utilice los modificadores de parámetros en las llamadas a funciones.
c. Si un bloque de código supera las 10líneas, utilice la directiva #region.
2) Tome algún ejercicio iterativo de la unidad VI o V y conviértalo a su equivalente
iterativo.
3) Evalúe el método recursivo para resolver el juego Torres de Hanoi y escriba sus
conclusiones al respecto.
4) Elabore un programa a elección, donde utilice los conceptos de herencia e interfaz (puede
realizar 2 programas si lo desea).
5) Preparación para OAD: Lea sobre las Listas Enlazadas Simples (puede conseguir
material online) y evalúe la definición de su nodo y métodos de inserción/borrado.
Coloque sus conclusiones al respecto. Igualmente responda: ¿Ha sido ventajoso el uso de
una clase autoreferenciada? ¿Hay una forma más sencilla de implementar una lista
enlazada? ¿Por qué?. NOTA: No tomar en cuenta el método List ni sus variantes.
Top Related