Guia 1 - Programación II
Guia 1 - Programación II
Elabore un
cuadro comparativo de las funciones, ventajas y desventajas de cada una. ¿Qué eventos
puede analizar al lograr el foco, perder el foco, cargar, visualizar o cerrar una ventana de
cada tipo?
Presentan al usuario una ventana con un conjunto de controles y, según las acciones que el
usuario realiza sobre las mismas se producen eventos, a los cuales la aplicación reacciona
ejecutando algún algoritmo.
Los tipos de recursividad descriptos por Chaves, en “Aprenda a diseñar algoritmos” [3] son:
• Recursividad simple: se presenta cuando una función incluye un llamado a sí misma
con un argumento diferente. Ejemplo de este tipo de recursividad es la función
factorial (). Este tipo de recursividad se caracteriza porque puede pasarse fácilmente
a una solución iterativa.
• Recursividad múltiple: el cuerpo de una función incluye más de una llamado a la
misma función, por ejemplo, la función para calcular un valor de la serie Fibonacci.
• Recursividad anidada: se dice que una función recursiva es anidada cuando entre los
parámetros que se pasan a la función se incluye una invocación a la misma. Un
ejemplo de recursividad anidada es la solución al problema de Ackerman§
• Recursividad cruzada o indirecta: en este tipo de recursividad, el cuerpo de la
función no contiene un llamado a sí misma, sino a otra función; pero, la segunda
incluye un llamado a la primera. Puede ser que participen más de dos funciones.
Este tipo de implementaciones también se conoce como cadenas recursivas. Como
ejemplo de este tipo de recursividad se tiene la función para validar una expresión
matemática.
3) Describa la pila de programa del sistema operativo, la forma en que se activan distintos
puntos de ejecución. Implemente un ejemplo de función recursiva y plantee un
seguimiento de la pila de llamadas de este. ¿Que implica el problema de desbordamiento
de la pila de programa o stackoverflow? De qué manera el IDE de Visual Studio le permite
conocer la pila de llamadas durante la ejecución y debug de una aplicación.
La pila es una estructura de datos donde se almacenan las llamadas a métodos en una
aplicación. Funciona siguiendo el principio "último en entrar, primero en salir" (LIFO), similar
a una pila de platos. Cuando un método se llama, su dirección de retorno se agrega a la pila,
y cuando retorna, se saca.
La pila también guarda la memoria para las variables locales de los métodos, conocido como
registro de activación o marco de pila. Cuando un método se llama, su registro de activación
se agrega a la pila; cuando retorna, se retira y las variables locales se eliminan. Si una
variable local es la única referencia a un objeto, cuando su registro se retira, el objeto será
eliminado por la "recolección de basura".
La memoria de la pila es limitada, y si hay más llamadas de métodos de las que pueden
almacenarse en la pila, ocurre un desbordamiento de pila, un error. Este problema surge
cuando la pila se llena de registros de activación y no puede manejar más llamadas.
Ejemplo de función recursiva
4) ¿En qué casos se puede utilizar la repetitividad de los valores aleatorios generados por la
clase Random? Como es posible generarlos, como se puede evitar la generación de las
mismas secuencias. ¿Son valores realmente aleatorios?
La clase Random nos permite generar valores pseudoaleatorios variando los tipos de datos,
según el método que utilicemos. La repetitividad de los valores aleatorios generados, nos
pueden ser de utilidad a la hora de realizar pruebas y tests sobre cierto fragmento de código
o programa. Esto se debe a que para evaluar distintos algoritmos o funciones y así poder
analizar el tiempo de ejecución, cantidad de recursos empleados, entre otros, debemos
siempre correr un mismo set de elementos para mantener la consistencia de los test en
todas sus pruebas y casos.
Para generar la misma serie de valores aleatorios, debemos instanciar la clase Random
acompañada de una semilla en su constructor, esta ha de ser de tipo Int32. En caso de no
querer trabajar con las mismas secuencias de valores, debemos utilizar una sola instancia del
objeto Random. Los valores generados no son aleatorios en su sentido mas puro, sino que
son números generados a partir de un algoritmo, es por esto que pasándole la misma
semilla podemos deducir que la secuencia de valores obtenidos se repetiría.
Para contar objetos creados y destruidos, utilizamos el modificador Static. Ya que, como crea
una variable de clase, esta no se instancia cada vez que se crea un objeto, y nos permite
mantener el valor. Lo que podemos hacer para tener un contador de objetos es incrementar
la variable estática desde el constructor del nuevo objeto.
6) Investigue la clase TimeSpan y sus usos para calcular tiempos de ejecución combinado con
DateTime.Now y StopWatch.
La clase TimeSpan nos permite calcular el intervalo de diferencia entre dos fechas o periodos
de tiempo. Este objeto a su vez cuenta con propiedades que nos permite acceder a sus
distintos atributos.
Resulta importante saber que esta estructura representa un intervalo general sin referencia
a un punto inicial o final determinado, es decir, no es posible expresarlo en términos de años
y meses. Por esto utiliza días, para así mantener la coherencia, ya que el número de días en
unidades de tiempo mayores no varía, a diferencia de como en meses y años que si lo hace.
Métodos/Propiedades:
• Add: Agrega un objeto object al objeto ArrayList y devuelve un int que especifica el
índice en el que se agregó el objeto object.
• Capacity: Propiedad que obtiene y establece el número de elementos para los que
se reserva espacio en un momento dado, dentro del objeto ArrayList.
• Clear Elimina todos los elementos del objeto ArrayList.
• Contains: Devuelve true si el objeto object especificado está en el objeto ArrayList;
en caso contrario, devuelve false.
• Count: Propiedad de sólo lectura que obtiene el número de elementos almacenados
en el objeto ArrayList.
• IndexOf: Devuelve el índice de la primera ocurrencia del objeto object especificado
en el objeto ArrayList.
• Insert: Inserta un objeto object en el índice especificado. Remove Elimina la primera
ocurrencia del objeto object especificado.
• RemoveAt: Elimina un objeto en el índice especificado.
• RemoveRange: Elimina un número especificado de elementos, empezando en un
índice especificado en el objeto ArrayList.
• Sort: Ordena el objeto ArrayList.
• TrimToSize: Establece la capacidad del objeto ArrayList al número de elementos que
contiene el objeto ArrayList en un momento dado (Count).
• CreateInstance: para crear un nuevo arreglo de un tipo especificado.
• LastIndexOf: para localizar la última ocurrencia de un objeto en un arreglo, o en una
porción de este.
• Reverse: para invertir el contenido de un arreglo, o de una porción de este.
Dado un objeto de tipo ArrayList, con tres valores cargados, a la hora de llamar al método
Count, este nos arrojará “3”, y el método Capacity nos devolverá “4”. Si intentamos
establecer la capacidad del objeto a un valor menor que la cantidad de valores que este
contiene el programa nos lanzara un error, con la siguiente excepción:
Esto se debe a que no es posible establecer una capacidad menor a la cantidad de valores
que ya posee este objeto, debido a que esto nos generaría una pérdida de información.
Veamos un ejemplo
Dadas dos clases, A y B, se establece una relación cuando la clase A, llamada "Todo", tiene
una o más partes de la clase B (La cantidad de partes se determina mediante la
cardinalidad). Dado que la relación se define entre clases, pero ocurre entre objetos,
depende del ciclo de vida de estos.
En una relación de Agregación, los ciclos de vida no están fuertemente interconectados; las
partes y el todo se crean y destruyen de manera independiente. Un objeto puede contener
otros objetos, pero las partes pueden existir independientemente del objeto compuesto. La
relación entre el objeto compuesto y sus partes es más débil que en la composición. Las
partes pueden pertenecer a múltiples objetos al mismo tiempo y no se destruyen
necesariamente cuando el objeto compuesto se destruye.
11) ¿Qué se entiende por ciclo de vida de los objetos? ¿Como el ciclo de vida de los objetos
determina la diferenciación de las relaciones entre clases de agregación y composición?
El ciclo de vida de los objetos se refiere a las diferentes etapas por las que pasa un objeto
desde su creación hasta su eliminación. Desde su creación, donde se inicializan sus atributos
y se asigna un espacio de memoria para esos objetos. Pasando por el uso que se les da,
donde cambia el estado de estos a medida que se realicen las operaciones, en función de las
acciones realizadas sobre él. Cuando el objeto ha cumplido su propósito y ya no es necesario
para el programa puede ser liberado de la memoria o se pueden realizar tareas de limpieza y
liberación de recursos. Se procederá luego a la etapa de eliminación, cuando se requiera,
para liberar los recursos que el objeto haya estado utilizando.
En cambio, si hablamos de composición, tenemos una clase “todo” que contiene objetos de
otra clase “parte” de manera que los objetos componentes son esenciales para la existencia
de la clase compuesta (todo). En este caso la vida útil de los objetos componentes está
completamente controlada por la clase compuesta. Cuando esta se crea o se destruye, los
objetos “parte” también son creados o destruidos en consecuencia. Los ciclos de vida en
este caso están fuertemente ligados entre sí.
12) ¿Qué tipo de jerarquía define la relación “es un” entre objetos de diferentes clases? ¿Qué
características tiene la invocación de constructores en dichas relaciones? ¿Cuál es el
primero en ejecutarse? ¿Cuál es el primero en terminar su ejecución?
La jerarquía que define la relación “es un” en el modelado entre objetos de diferentes clases
es de Generalización. En Programación Orientada a Objetos, esta relación se llama Herencia.
Al crear una instancia de un objeto de clase derivada se empieza una cadena de llamadas a
los constructores, en los que el constructor de la clase derivada, antes de realizar sus propias
tareas, invoca al constructor de su clase base directa, ya sea en forma explícita (por medio
de un inicializador de constructor con la referencia base) o implícita (llamando al constructor
predeterminado o sin parámetros de la clase base). Si la clase base se deriva de otra clase
(como sucede con cualquier clase, excepto object), el constructor de la clase base invoca al
constructor de la siguiente clase que se encuentre a un nivel más arriba en la jerarquía, y así
en lo sucesivo.
El último constructor que se llama en la cadena es siempre el de la clase object. El cuerpo del
constructor original de la clase derivada termina de ejecutarse al último.
13) ¿Qué tipo de asignación de objetos se pueden realizar en variables declaradas en dos
diferentes clases relacionadas por herencia? ¿Una variable declarada del tipo de la clase
base admite un objeto de la clase derivada? ¿Y viceversa? ¿Por qué?
Una variable declarada de tipo base admite un objeto de la clase derivada, debido a que la
clase derivada cuenta con todos los miembros de la clase base, más los suyos, pero debemos
tener en cuenta que, si intentásemos utilizar un método propio de la clase derivada, en un
objeto declarado de la clase base, obtendríamos un error. Esta manera de declarar objetos
de clase base y asignarle un objeto de clase derivada se conoce como “upcasting”.
Cuando asignamos un objeto de clase base a una variable de clase derivada, lo reconocemos
como “downcasting”. En el es necesario que realicemos un casteo. Debemos tener en cuenta
que, si el objeto originalmente no es una instancia de la clase derivada, esto generará la
siguiente excepción:
System.InvalidCastException: 'No se puede convertir un objeto de tipo
'ConsoleApp1.ClaseBase' al tipo 'ConsoleApp1.ClaseDerivada'.'
14) ¿Qué característica brinda la sobreescritura de métodos? ¿Por qué objetos de la misma
jerarquía necesitan comportamiento diferente? ¿Qué relaciones tienen los modificadores
virtual y override?
Muchas veces los objetos de misma jerarquía requieren comportamiento diferente por que
actúan de forma diferente, por ende, la implementación no es la misma en todos. Tomemos
como ejemplo una clase Vehículo, de ella heredaran las clases Automóvil y Moto. Sabemos
que la clase vehículo debería contar con un método “Acelerar()”, sin embargo, la manera de
acelerar en Automóvil y en Moto difiere, esta es la razón por la cual debemos sobrescribir
estos métodos implementando en cada uno de ellos la forma que le corresponda.
15) ¿Qué representatividad tiene una clase abstracta? ¿Porque se definen? ¿Qué finalidad
tienen?
Las clases abstractas se definen para establecer una especie de contrato o plantilla que
define métodos (funciones) y propiedades que deben ser implementados por las clases hijas
que heredan de la clase abstracta.
16) ¿Qué caracteriza la creación de métodos abstractos? ¿Qué reglas se aplican para su
declaración e implementación? ¿Qué consecuencias tienen?
Los métodos abstractos son aquellos que se declaran en una clase abstracta pero no se les
proporciona una implementación concreta en dicha clase. En cambio, las clases derivadas
que heredan de la clase abstracta están obligadas a implementar estos métodos según sus
necesidades y contexto específico.
Las clases derivadas concretas deben proporcionar implementaciones para cada descriptor
de acceso declarado en la propiedad abstracta.
Los constructores y los métodos static no pueden declararse abstract. Los constructores no
se heredan, por lo que un constructor abstract nunca podría implementarse. De manera
similar, las clases derivadas no pueden redefinir métodos static, por lo que un método
abstract static nunca podría implementarse.
17) ¿Qué efecto tienen el modificador de acceso protected? ¿Dónde se aplica? ¿Por qué?
El uso del acceso protected ofrece un nivel intermedio de acceso entre public y private. Se
puede acceder a los miembros protected de una clase base a través de los miembros de esa
clase base y de los miembros de sus clases derivadas.
Este modificador de acceso se aplica en la clase base donde aparece por primera vez el
método o atributo. Esto es porque todos los miembros no private de la clase base retienen
su modificador de acceso original cuando se convierten en miembros de la clase derivada,
por lo que solo es necesario indicarlo la primera vez.
Un método que se declara como sealed en una clase base no puede redefinirse en una clase
derivada.
Una clase que se declara como sealed no puede ser una clase base (es decir, una clase no
puede extender a una clase sealed). Todos los métodos en una clase sealed son sealed de
manera implícita.
La declaración de un método sealed no puede cambiar nunca, por lo que todas las clases
derivadas utilizan la misma implementación del método, y las llamadas a los métodos sealed
se resuelven en tiempo de compilación; a esto se le conoce como vinculación estática.