Está en la página 1de 180

Conceptos básicos de C

Presentación basada en el libro:


Deitel, P. and Deitel, H. (2021). C How to program. Ninth Edition, Pearson.
Temario de la unidad
1. Conceptos básicos
1.1. Proceso de desarrollo de un programa

1.2. Tipos de datos abstractos

1.3. Organización de código

1.4. Funciones

1.5. Punteros y manejo de memoria

1.6. Fugas de memoria


Proceso de desarrollo de un programa en C
• C evolucionó a partir de dos lenguajes: BCPL y B.

• El lenguaje C fue evolucionado a partir de B por Dennis Ritchie en los


Laboratorios Bell y fue implementado originalmente en 1972.

• C se hizo inicialmente muy conocido como el lenguaje de desarrollo del


sistema operativo UNIX.

• C es en su mayor parte independiente del hardware; con un diseño


cuidadoso, es posible escribir programas en C que sean portables a la
mayoría de las computadoras.
Proceso de desarrollo de un programa en C
• Construido para el rendimiento:
• Sistemas operativos, sistemas embebidos, sistemas en tiempo real y sistemas de
comunicaciones.

• Estandarización
• El estándar C se aprobó en 1989 por el Instituto Nacional de Normalización
Americano (ANSI) y por la organización Internacional de Normalización (ISO).

• Estándares C11 y C18 (liberado en 2018)

• Librerías C Standard y Open-Source


Proceso de desarrollo de un programa en C
• Un programa en C lleva a cabo seis fases para se ejecutado:

• Editar, preprocesar, compilar, enlazar, cargar y ejecutar

• Fase 1: Crear un programa


• Editar un archivo en un programa editor
Proceso de desarrollo de un programa en C
• Fase 2 y 3: Preprocesar y compilar un programa en C

• En esta fase se ejecuta un comando para compilar el programa

• El compilador traduce el programa en C a un lenguaje máquina (código objeto)

• En un sistema C, el comando de compilación invoca un programa preprocesador antes


de que comience la fase de traducción del compilador.
Proceso de desarrollo de un programa en C

• En la fase 3, el compilador traduce el programa C en código objeto.

• Un error de sintaxis se produce cuando el compilador no puede reconocer una


sentencia porque viola las reglas del lenguaje.

• Los errores de sintaxis también se denominan errores de compilación o errores en


tiempo de compilación.
Proceso de desarrollo de un programa en C

• En la fase 4: Enlazar

• Los programas en C suelen utilizar funciones definidas en otros lugares, como las
librerías estándar, las librerías de código abierto o las privadas de un proyecto
concreto.

• El código objeto producido por el compilador de C suele contener "agujeros" debido a


estas partes que faltan.
Proceso de desarrollo de un programa en C

• Un enlazador enlaza el código objeto de un programa con el código de las funciones


que faltan para producir una imagen ejecutable (sin las piezas que faltan).

• En un sistema Linux típico, el comando para compilar y enlazar un programa es gcc (el
compilador de C de GNU). Para compilar y enlazar un programa llamado welcome.c
utilizando el último estándar C (C18).

• gcc -std=c18 welcome.c

• Si el programa compila y enlaza correctamente, el compilador produce un archivo


llamado a.out (por defecto), que es la imagen ejecutable de welcome.c.
Proceso de desarrollo de un programa en C
• Fase 5: Cargar

• El cargador toma la imagen ejecutable del disco y la transfiere a la memoria.


También se cargan los componentes adicionales de las bibliotecas compartidas
que soportan el programa.
Proceso de desarrollo de un programa en C
• Fase 6: Ejecutar

• Cada una de las fases anteriores puede fallar debido a varios errores que
discutiremos. Por ejemplo, un programa en ejecución puede llegar a
dividir por cero.
Mi primer programa
Otro programa
Conceptos de memoria
• Cada variable tiene un nombre, tipo, valor y una localización en la
memoria de la computadora:

• scanf("%d", &integer1); // read an integer

• scanf("%d", &integer2); // read an integer

• sum = integer1 + integer2; // assign total to sum


Haciendo decisiones
• Condiciones utilizan operadores de igual y relacionales
Sentencia if
Sentencia if …else, expresión condicional, e If
anidados
Sentencia de iteración while
• Una sentencia de iteración (sentencia de repetición o bucle) repite una
acción mientras la condición sigue siendo verdadera.
Sentencia de iteración while
Sentencia de iteración while
Operadores de asignación
Operadores de incremento y decremento
Operadores de incremento y decremento
Sentencia de iteración for
Sentencia de iteración for: Calculando un
ínteres compuesto
• Problema: Una persona invierte 1.000 dólares en una cuenta de ahorros que rinde
un 5% de interés. Suponiendo que todos los intereses se dejan en depósito en la
cuenta, calcula e imprime la cantidad de dinero en la cuenta al final de cada año
durante 10 años. Utiliza la siguiente fórmula para determinar estas cantidades:

a = p(1 + r)n
• Donde:
• p es la cantidad original invertida (es decir, el capital, aquí 1.000 dólares),
• r es el tipo de interés anual (por ejemplo, 0,05 para el 5%),
• n es el número de años, que aquí es 10, y
• a es la cantidad depositada al final del n año.
Sentencia de iteración for: Calculando
un ínteres compuesto
Sentencia de selección multiple switch
Sentencia de selección multiple switch
Sentencias break and continue
Sentencias break and continue
Operadores lógicos
• && (logical AND)

• || (logical OR)

• ! (logical NOT)
Operadores lógicos

• && (logical AND)

• Tablas de verdad del operador lógico &&:


Operadores lógicos

• || (logical OR)

• Tablas de verdad del operador lógico ||:


Operadores lógicos

• ! (logical Negation)

• Tablas de verdad del operador lógico ! :


Ejercicios
1. Desarrolla un programa que lea dos enteros dados por el usuario y
luego muestre su suma, producto, diferencia, cociente y resto.
2. Escribe un programa que introduzca tres números enteros diferentes
desde el teclado, y luego muestre la suma, la media, el producto, el
menor y el mayor de estos números. Utiliza sólo la forma de selección
simple de la sentencia if. El diálogo en pantalla debe aparecer como
sigue:
Ejercicios
3. La función factorial se utiliza con frecuencia en los problemas de
probabilidad. El factorial de un entero positivo n (escrito n! y
pronunciado "n factorial") es igual al producto de los enteros positivos
de 1 a n. Escribe un programa que evalúe los factoriales de los enteros
del 1 al 5. Imprime los resultados en formato de tabla. ¿Qué dificultad
podría impedirte calcular el factorial de 20?

4. Modifique el programa de interés compuesto visto en clase para repetir


sus pasos para tipos de interés del 5%, 6%, 7%, 8%, 9% y 10%. Utiliza un
ciclo for para variar el tipo de interés.
Ejercicios
5. (Cálculo de los límites de crédito) El cobro de dinero se hace cada vez más difícil
durante los periodos de recesión, por lo que las empresas pueden reducir sus
límites de crédito para evitar que sus cuentas por cobrar (el dinero que se les debe)
sean demasiado grandes. En respuesta a una recesión prolongada, una empresa ha
reducido los límites de crédito de sus clientes a la mitad. Así, si un cliente concreto
tenía un límite de crédito de 2.000 dólares, ahora es de 1.000 dólares. Si un cliente
tenía un límite de crédito de 5.000 $, ahora es de 2.500 $. Escriba un programa que
analice el estado del crédito de tres clientes de esta empresa. Para cada cliente se
le da:

a) El número de cuenta del cliente.


b) El límite de crédito del cliente antes de la recesión.
c) El saldo actual del cliente (es decir, la cantidad que debe el cliente).

Su programa debe calcular e imprimir el nuevo límite de crédito de cada cliente y


determinar (e imprimir) qué clientes tienen saldos que superan sus nuevos límites
de crédito.
Ejercicios
6. (Calcular las ventas) Un minorista en línea vende cinco productos diferentes cuyos
precios de venta al público se muestran en la siguiente tabla:

Escribe un programa que lea una serie de pares de números como los siguientes:
a) Número de producto.
b) Cantidad vendida en un día.

Su programa debe utilizar una sentencia switch para ayudar a determinar el precio
de venta al público de cada producto. Su programa debe calcular y mostrar el valor
total de venta al público de todos los productos vendidos la semana pasada.
Ejercicios
7. (Calcular el valor de π) Calcular el valor de π a partir de la serie infinita:

Imprime una tabla que muestre el valor de π aproximado por un término de esta
serie, por dos términos, por tres términos, y así sucesivamente. ¿Cuántos términos
de esta serie tienes que utilizar antes de obtener 3,14? 3.141? 3.1415? 3.14159?
Ejercicios
8. (Cálculo del salario semanal) Una empresa paga a sus empleados como directivos
(que reciben un salario semanal fijo), trabajadores por hora (que reciben un salario
fijo por hora hasta las primeras 40 horas que trabajan y "tiempo y medio" por las
horas extras trabajadas), trabajadores a comisión (que reciben 250 dólares más el
5,7% de sus ventas brutas semanales), o trabajadores a destajo (que reciben una
cantidad fija de dinero por cada uno de los artículos que producen-cada trabajador
a destajo en esta empresa trabaja en un solo tipo de artículo). Escriba un programa
para calcular la paga semanal de cada empleado. No conoce de antemano el
número de empleados. Cada tipo de empleado tiene un código de pago: Los
gerentes tienen el código de pago 1, los trabajadores por hora tienen el código 2,
los trabajadores a comisión tienen el código 3 y los trabajadores a destajo tienen el
código 4. Utilice un switch para calcular la paga de cada empleado basándose en el
código de pago. Dentro del switch, pida al usuario que introduzca los datos
apropiados que su programa necesita para calcular la paga de cada empleado
basándose en el código de pago de ese empleado. [Nota: Puede introducir valores
de tipo double utilizando la especificación de conversión %lf con scanf].
Ejercicios
9. (Programa de impresión de diamantes) Escriba un programa que
imprima la siguiente forma de diamante. Sus sentencias printf pueden
imprimir un asterisco (*) o un espacio en blanco. Utilice sentencias for
anidadas y minimice el número de sentencias printf.
Ejercicios
10. (Crecimiento de la población mundial) La población mundial ha crecido
considerablemente a lo largo de los siglos. El crecimiento continuo podría llegar a
desafiar los límites del aire respirable, el agua potable, las tierras de cultivo y otros
recursos limitados. Hay pruebas de que el crecimiento se ha ralentizado en los
últimos años y de que la población mundial podría alcanzar su punto máximo en
algún momento de este siglo y, a continuación, empezar a disminuir. Para este
ejercicio, investiga en Internet sobre el crecimiento de la población mundial.
Asegúrate de investigar varios puntos de vista. Consigue estimaciones de la
población mundial actual y de su tasa de crecimiento (el porcentaje en el que es
probable que aumente este año). Escribe un programa que calcule el crecimiento
de la población mundial cada año durante los próximos 75 años, utilizando la
hipótesis simplificadora de que la tasa de crecimiento actual se mantendrá
constante. Imprime los resultados en una tabla. La primera columna debe mostrar
el año desde el año 1 hasta el año 75. La segunda columna debe mostrar la
población mundial prevista al final de ese año. La tercera columna debe mostrar el
aumento numérico de la población mundial que se produciría ese año. Utilizando
tus resultados, determina el año en el que la población sería el doble de la actual si
la tasa de crecimiento de ese año se mantuviera.
Funciones
• Las funciones se utilizan para modularizar los programas combinando las
nuevas funciones que se escriben con las funciones preempaquetadas de
la biblioteca estándar de C.

• La biblioteca estándar de C proporciona una amplia colección de


funciones para realizar cálculos matemáticos comunes, manipulación de
cadenas, manipulación de caracteres, entrada/salida y muchas otras
operaciones útiles.

• El estándar C incluye el lenguaje C y su biblioteca estándar; los


compiladores C estándar implementan ambos.
Funciones
• Se recomienda utilizar las funciones estándar en lugar de escribir otras
nuevas:

• El uso de las funciones de la biblioteca estándar de C disminuye el tiempo de


desarrollo del programa.

• Las funciones de la biblioteca estándar de C están escritas por expertos, están bien
probadas y son eficientes.

• Ayuda a que los programas sean más portátiles.


Funciones: llamada y retorno
• Las funciones se invocan mediante una llamada a la función:
• Nombre de la función e
• Información (como argumentos) que la función necesita para realizar su tarea
designada.

• Analogía de las funciones:


Definiciones de funciones
Función del cuadrado
Definiciones de funciones
• El compilador compara la llamada de Square (línea 10) con su prototipo
para asegurarse de que:

• El número de argumentos es correcto

• Los argumentos son de los tipos correctos

• Los tipos de argumentos están en el orden correcto

• El tipo de dato retornado es consistente con el contexto en el cual la función es


llamada.
Definiciones de funciones
• Formato de la definición de una función

• El return-value-type, function-name y parameter-list son algunas veces referidos como la


cabecera de la function (function header).

• La lista de parámetros es una lista separada por comas que especifica los parámetros que
recibe la función cuando es llamada.

• Si una función no recibe ningún parámetro debe contener la palabra clave void.

• Cada parámetro debe incluir su tipo; de lo contrario, se produce un error de compilación.


Definiciones de funciones
• Las sentencias entre llaves forman el cuerpo de la función, que también es un bloque.

• Las variables locales pueden declararse en cualquier bloque, y los bloques pueden anidarse.

• Las funciones no se pueden anidar: definir una función dentro de otra función es un error de
sintaxis.

• Hay tres maneras de devolver el control de una función llamada al punto en el que se invocó la
función:

• Si la función no devuelve un resultado, el control se devuelve simplemente cuando se alcanza la llave


derecha de finalización de la función, o
• Ejecutando la sentencia return;
• Si la función devuelve un resultado: return expression;
Definiciones de funciones
Función maxinum
Definiciones de funciones
• El compilador utiliza prototipos de funciones para validar las llamadas a funciones.

• El pre-estándar de C no realizaba este tipo de comprobación, por lo que era posible


llamar a funciones de forma incorrecta sin que el compilador detectara los errores:
• Errores fatales en tiempo de ejecución o
• Errores no fatales que causaban problemas sutiles y difíciles de detectar.

• Recomendación: Se debe incluir prototipos de funciones para todas las funciones con
la finalidad de aprovechar las capacidades de comprobación de tipos de C.

• Los nombres de los parámetros en los prototipos de las funciones se incluyen por
motivos de documentación. El compilador ignora estos nombres, por lo que el
siguiente prototipo también es válido:
Coerción de argumentos y "reglas de conversión aritmética habituales“
• Otra característica importante de los prototipos de funciones es la conversión
implícita de los argumentos al tipo apropiado (coerción de los argumentos).

• Por ejemplo:

• En general, los valores de los argumentos que no se corresponden exactamente con


los tipos de parámetros del prototipo de la función se convierten al tipo adecuado
antes de llamar a la función.

• Tales conversiones pueden llevar a resultados incorrectos si no se siguen las “reglas


de conversión aritmética habituales” de C. Estas reglas especifican cómo se pueden
convertir los valores a otros tipos sin perder los datos.
Expresiones de tipo mixto
• Las reglas habituales de conversión aritmética son manejadas por el compilador.

• Se aplican a las expresiones de tipo mixto, es decir, expresiones que contienen valores
de varios tipos de datos:
• El compilador realiza copias temporales de los valores que deben convertirse y, a continuación,
convierte las copias al tipo "más alto" de la expresión, lo que se conoce como promoción.

• Para expresiones de tipo mixto que contengan al menos un valor de punto flotante:
• Si un valor es un long double, los otros valores se convierten a long double.
• Si un valor es un double, los otros valores se convierten en double.
• Si un valor es un float, los otros valores se convierten en float.

• Si la expresión de tipo mixto contiene sólo tipos enteros, las conversiones aritméticas
habituales especifican un conjunto de reglas de promoción de enteros.
Expresiones de tipo mixto
• Un valor puede ser convertido a un tipo inferior sólo asignando explícitamente el valor
a una variable de tipo inferior o utilizando un operador cast.

• Los argumentos se convierten a los tipos de parámetros especificados en un prototipo


de función como si los argumentos se asignaran a variables de esos tipos.

• Por ejemplo, si pasamos un double a nuestra función square, el double se convierte a


int (un tipo inferior), y square suele devolver un valor incorrecto:
• square(4.5) devuelve 16, no 20.25

• La conversión de un tipo de datos superior en la jerarquía de promoción a un tipo


inferior puede cambiar el valor de los datos (los compiladores emiten advertencias en
estos casos).
Notas de las funciones prototipo
• Si no hay un prototipo de función para una función, el compilador forma uno a partir
de la primera aparición de la función, ya sea la definición de la función o una llamada a
la función.

• Esto suele provocar advertencias o errores, dependiendo del compilador. Incluya


siempre prototipos de función para las funciones que defina o utilice en su programa
para ayudar a evitar errores de compilación y advertencias.

• Un prototipo de función colocado fuera de cualquier definición de función aplica a


todas las llamadas a la función que aparecen después del prototipo de función.

• Un prototipo de función colocado en el cuerpo de una función aplica sólo a las


llamadas a esa función realizadas después de ese prototipo.
Pila de llamadas a funciones y marcos de pila
• Para entender cómo C realiza las llamadas a funciones, primero tenemos que
considerar una estructura de datos (es decir, una colección de elementos de datos
relacionados) conocida como pila.

• Considere una pila como algo análogo a una pila de platos:

• Para agregar, se coloca un plato en la parte superior, lo que se conoce como poner (pushing) el plato
en la pila.
• Para quitar, se suele retirar un plato de la parte superior, lo que se denomina sacar (popping) el
plato de la pila.

• Las pilas se conocen como estructuras de datos de último en entrar, primero en salir
(last-in, first-out -LIFO): el último elemento introducido en la pila es el primero que se
saca de ella.
Pila de llamadas a funciones y marcos de pila
• Un mecanismo importante que se debe comprender es la pila de llamadas a funciones
(pila de ejecución del programa).

• Esta estructura de datos, que trabaja “detrás de escena", soporta el mecanismo de


llamada/retorno de funciones. También, la pila de llamada a función soporta la
creación, mantenimiento y destrucción de las variables locales de cada función
llamada.

• A medida que se llama a cada función, ésta puede llamar a otras funciones, que a su
vez pueden llamar a otras funciones, todo ello antes de que cualquier función regrese.

• Cada función debe eventualmente devolver el control a la función que la llamo. Por lo
tanto, se debe mantener un registro de las direcciones de retorno que cada función
necesita para devolver el control a la función que la llamó.
Pila de llamadas a funciones y marcos de pila
• La pila de llamadas a funciones es la estructura de datos perfecta para manejar esta
información.

• Cada vez que una función llama a otra función, se introduce una entrada en la pila. Esta
entrada, llamada marco de pila, contiene la dirección de retorno que la función llamada
necesita para volver a la función que la llama.

• Cuando una función llamada regresa, el marco de la pila para la llamada a la función se saca, y
el control se transfiere a la dirección de retorno especificada en el marco de la pila sacado.

• Cada función llamada siempre encuentra en la parte superior de la pila de llamadas la


información que necesita para regresar a su llamador. Si una función llamada llama a otra
función, se introduce en la pila de llamadas un marco de pila para la nueva llamada a la
función.
Pila de llamadas a funciones y marcos de pila
• Los marcos de la pila tienen otra responsabilidad importante. La mayoría de las
funciones tienen variables locales, que deben existir mientras se ejecuta una función.
Necesitan permanecer activas si la función hace llamadas a otras funciones.

• Pero cuando una función llamada regresa a su llamador, las variables locales de la
función llamada necesitan "desaparecer".

• El marco de la pila de la función llamada es un lugar perfecto para reservar la memoria


para las variables locales. Ese marco de pila existe sólo mientras la función llamada está
activa.

• Cuando esa función regresa -y ya no necesita sus variables locales- su marco de pila es
retirado de la pila. Estas variables locales ya no son conocidas por el programa.
Pila de llamadas a funciones en acción
• Ejemplo:
Pila de llamadas a funciones en acción
• Paso 1: El sistema operativo invoca a main para ejecutar la aplicación
Pila de llamadas a funciones en acción
• Paso 2: La función main invoca a la función square para realizar el cálculo
Pila de llamadas a funciones en acción
• Paso 3: La función square regresa su resultado a la función main
Pasar argumentos por valor y por referencia
• En muchos lenguajes de programación, hay dos formas de pasar
argumentos: por valor y por referencia.

• Cuando un argumento se pasa por valor, se hace una copia del valor del
argumento y se pasa a la función. Los cambios en la copia no afectan al
valor de la variable original en el llamador.

• Cuando un argumento se pasa por referencia, el llamador permite que la


función llamada modifique el valor de la variable original.
Pasar argumentos por valor y por referencia
• El paso por valor debe utilizarse siempre que la función llamada no
necesite modificar el valor de la variable original del llamante. Esto evita
efectos secundarios accidentales (modificaciones de variables) que
pueden dificultar el desarrollo de sistemas de software correctos y
fiables.

• El paso por referencia debe utilizarse sólo con funciones llamadas de


confianza que necesiten modificar la variable original.

• En C, todos los argumentos se pasan por valor.


Ejercicio de funciones
1. Elabore un programa que realice las funciones que a continuación se
indican. Todas las opciones deben ser presentadas al usuario a través
de un menú de opciones.

• Escriba una función en la que se introduzcan 10 enteros y determine cuales de


estos enteros son pares y cuales son impares.
• Un número entero es un “número perfecto” si sus divisores, incluyendo al 1
(pero excluyendo al número mismo), suman igual que el numero. Ejemplo: 6 es
un numero perfecto porque 6= 1+2+3. Escriba una función que regrese los
primeros 100 números perfectos. Esta función debe tener una función anidada
que determine al número perfecto.
• Escriba una función que tome un valor entero de cuatro dígitos y regrese el
número con los dígitos invertidos. Por ejemplo, dado el número 8967, la función
deberá regresar 7698.
Ejercicio de funciones
2. (Cargos por estacionamiento) Un estacionamiento cobra una tarifa mínima de $2.00 por
estacionar hasta tres horas y un adicional de 0,50 dólares por hora por cada hora o parte de ella
que supere las tres horas. La tarifa máxima por un periodo de 24 horas es de 10 dólares. Suponga
que ningún coche aparca más de 24 horas seguidas. Escriba un programa que calcule e imprima
las tarifas de aparcamiento de cada uno de los tres clientes que aparcaron ayer sus coches en este
garaje. Debe introducir las horas de estacionamiento de cada cliente. Su programa debe imprimir
los resultados en un formato tabular, y debe calcular e imprimir el total de los recibos de ayer. El
programa debe utilizar la función calcularCargos para determinar el cargo de cada cliente. Sus
resultados deben aparecer en el siguiente formato:
Generación de números aleatorios
• Lanzar un dado de seis caras
Generación de números aleatorios
• Lanzar un dado de seis caras 60,000,000 veces
Aleatorizar el generador de números aleatorios
• Al ejecutar de nuevo el programa de la Fig. 5.4 se obtiene

• 6655651153

• Esta es la secuencia exacta de valores que se mostró anteriormente.

• La función rand genera en realidad números pseudoaleatorios.

• Llamar a rand repetidamente produce una secuencia de números que


parece ser aleatoria. Sin embargo, la secuencia se repite cada vez que se
ejecuta el programa.
Aleatorizar el generador de números aleatorios

• Un programa se puede condicionar para que produzca una secuencia


diferente de números aleatorios en cada ejecución.

• Esto se llama aleatorización y se logra con la función de la biblioteca


estándar srand.

• La función srand toma un argumento int y siembra la función rand para


producir una secuencia diferente de números aleatorios para cada
ejecución del programa.
Aleatorizar el generador de números aleatorios
Escalado y desplazamiento generalizado de números aleatorios
• Los valores producidos directamente por rand están siempre en el rango:
• 0 ≤ rand() ≤ RAND_MAX

• La siguiente sentencia simula el lanzamiento de un dado de seis caras:


• int face = 1 + rand() % 6;

• La anchura de este rango (es decir, el número de enteros consecutivos en


el rango) es 6, y el número inicial del rango es 1.

• Por lo tanto, la anchura del rango viene determinada por el número


utilizado para escalar rand con el operador resto (es decir, 6), y el número
inicial del rango es igual al número (es decir, 1) que se suma a rand % 6.
Escalado y desplazamiento generalizado de números aleatorios
• Podemos generalizar este resultado de la siguiente manera:

• int n = a + rand() % b;

• Donde
• a es el valor de desplazamiento (que es igual al primer número del
rango deseado de enteros consecutivos), y

• b es el factor de escala (que es igual a la anchura del rango deseado de


enteros consecutivos).
Caso de estudio de simulación de números aleatorios: Creación
de un juego de casino
• En este caso de estudio, se simula el juego de dados conocido
como "craps". Las reglas del juego son las siguientes:

• Un jugador tira dos dados. Cada dado tiene seis caras. Estas caras
contienen 1, 2, 3, 4, 5 y 6 puntos. Una vez que los dados se han posado,
se calcula la suma de los puntos de las dos caras superiores. Si la suma
es 7 u 11 en el primer lanzamiento, el jugador gana. Si la suma es 2, 3 o
12 en la primera tirada (lo que se llama "craps"), el jugador pierde (es
decir, la "casa" gana). Si la suma es 4, 5, 6, 8, 9 o 10 en la primera
tirada, esa suma se convierte en el "punto" del jugador. Para ganar,
debe seguir tirando los dados hasta "hacer su punto". El jugador pierde
si saca un 7 antes de hacer el punto.
Caso de estudio de simulación de números aleatorios: Creación
de un juego de casino
Clases de almacenamiento
• En varios programas hemos utilizado identificadores para los nombres de las variables. Los atributos de
las variables incluyen nombre, tipo, tamaño y valor. También utilizamos identificadores como nombres
para las funciones definidas por el usuario.

• Cada identificador en un programa en C tiene otros atributos: clase de almacenamiento, duración del
almacenamiento, alcance o ámbito y vinculación. En C se proporciona los especificadores de clase de
almacenamiento auto, extern y static.

• Una clase de almacenamiento determina la duración de almacenamiento, el ámbito y la vinculación de


un identificador:
• La duración del almacenamiento es el período durante el cual un identificador existe en la memoria. Algunos
existen brevemente, otros se crean y destruyen repetidamente, y otros existen durante toda la ejecución del
programa.
• El ámbito determina dónde puede referenciar un programa un identificador. Algunos pueden ser referenciados
a lo largo de un programa, otros sólo desde partes de un programa.
• Para un programa de múltiples archivos fuente, la vinculación de un identificador determina si el identificador
es conocido sólo en el archivo fuente actual o en cualquier archivo fuente con las declaraciones adecuadas.
Variables locales y duración del almacenamiento automático
• Los especificadores de clase de almacenamiento se dividen en duración de almacenamiento automática
y duración de almacenamiento estática.

• La palabra clave auto declara que una variable tiene una duración de almacenamiento automática. Estas
variables se crean cuando el control del programa entra en el bloque en el que están definidas.

• Existen mientras el bloque está activo y se destruyen cuando el control del programa sale del bloque.
Sólo las variables pueden tener una duración de almacenamiento automática.

• Las variables locales de una función -aquellas declaradas en la lista de parámetros o en el cuerpo de la
función- tienen una duración de almacenamiento automática por defecto, por lo que la palabra clave
auto rara vez se utiliza.

• La duración de almacenamiento automática es un medio para conservar la memoria porque las variables
locales existen sólo cuando se necesitan.
Clase de almacenamiento estático
• Las palabras clave extern y static declaran identificadores para variables y funciones con duración de
almacenamiento estática.

• Los identificadores de duración de almacenamiento estático existen desde el momento en que el


programa empieza a ejecutarse hasta que termina.

• Para las variables estáticas, el almacenamiento se asigna e inicializa sólo una vez, antes de que el
programa comience a ejecutarse.

• Para las funciones, el nombre de la función existe cuando el programa comienza su ejecución. Sin
embargo, aunque estos nombres existen desde el inicio de la ejecución del programa, no siempre son
accesibles.

• La duración del almacenamiento y el ámbito (donde se puede utilizar un nombre) son cuestiones
distintas.
Clase de almacenamiento estático
• Hay varios tipos de identificadores con duración de almacenamiento estática: los identificadores
externos (como las variables globales y los nombres de funciones) y las variables locales declaradas con
el especificador de clase de almacenamiento static.

• Las variables globales y los nombres de función tienen por defecto la clase de almacenamiento extern.
Las variables globales se crean colocando declaraciones de variables fuera de cualquier definición de
función. Conservan sus valores durante la ejecución del programa.

• Las variables globales y las funciones pueden ser referenciadas por cualquier función que siga a sus
declaraciones o definiciones en el archivo.

• Esta es una de las razones por las que se utilizan prototipos de funciones: cuando incluimos stdio.h en un
programa que llama a printf, el prototipo de la función se coloca al principio de nuestro archivo para que
el nombre printf sea conocido por el resto del archivo.
Clase de almacenamiento estático
• En general, debe evitar las variables globales, excepto en situaciones con requisitos de rendimiento
únicos.

• Las variables usadas sólo en una función particular deben ser definidas como variables locales en esa
función.

• Las variables locales estáticas sólo se conocen en la función en la que están definidas y conservan su
valor cuando la función regresa. La próxima vez que se llama a la función, la variable local estática
contiene el valor que tenía cuando la función salió por última vez.

• La siguiente sentencia declara la variable local count como estática y la inicializa a 1:

• static int count = 1;


Reglas del ámbito
• El ámbito de un identificador es la parte del programa en la que se puede hacer referencia al
identificador.

• Por ejemplo, una variable local en un bloque puede ser referenciada sólo después de su definición en
ese bloque o en bloques anidados dentro de ese bloque.

• Los cuatro ámbitos del identificador son el ámbito de la función, el ámbito del archivo, el ámbito del
bloque y el ámbito del prototipo de función.

• Ámbito de la función: Las etiquetas son identificadores seguidos de dos puntos, como por ejemplo start:. Las
etiquetas son los únicos identificadores con alcance de función. Las etiquetas pueden utilizarse en cualquier
parte de la función en la que aparecen, pero no pueden referenciarse fuera del cuerpo de la función. Las
etiquetas se utilizan en las sentencias switch (como etiquetas case) y en las sentencias goto.
Reglas del ámbito

• Ámbito de los archivos: Un identificador declarado fuera de cualquier función tiene ámbito de archivo. Dicho
identificador es "conocido" (es decir, accesible) en todas las funciones desde el punto en el que se declara el
identificador hasta el final del archivo. Las variables globales, las definiciones de función y los prototipos de
función colocados fuera de una función tienen alcance de archivo.

• Ámbito del bloque: Los identificadores definidos dentro de un bloque tienen ámbito de bloque. El ámbito del
bloque termina en la llave derecha (}) que termina el bloque:

• Las variables locales definidas al principio de una función tienen ámbito de bloque

• Parámetros de la función que son considerados como variables locales por la función.

• Cualquier bloque puede contener definiciones de variables. Cuando se anidan bloques y el identificador de un bloque
exterior tiene el mismo nombre que el identificador de un bloque interior, el identificador del bloque exterior se oculta
hasta que el bloque interior termina.

• Las variables locales declaradas como estáticas siguen teniendo ámbito de bloque, aunque existan desde antes del inicio
del programa. Por lo tanto, la duración del almacenamiento no afecta al ámbito de un identificador.
Reglas del ámbito

• Ámbito del prototipo de función: Los únicos identificadores con ámbito de prototipo de función son los
utilizados en la lista de parámetros de un prototipo de función.

• Los prototipos de función no requieren nombres en la lista de parámetros, sólo se requieren tipos. Si se utiliza un
nombre en la lista de parámetros de un prototipo de función, el compilador lo ignora.
• Los identificadores utilizados en un prototipo de función pueden ser reutilizados en otras partes del programa sin
ambigüedad.
Ejemplo del ámbito
Ejercicio de funciones
1. (Cálculos de la hipotenusa) Define una función llamada hipotenusa
que calcula la hipotenusa de un triángulo rectángulo, basándose en
los valores de los otros dos lados. La función debe tomar dos
argumentos double y devolver la hipotenusa como un double. Prueba
tu programa con los valores de los lados especificados en la siguiente
tabla:
Ejercicio de funciones
2. (Conversiones de temperatura) Implementa las siguientes funciones
enteras:
a) toCelsius devuelve el equivalente en Celsius de una temperatura en
Fahrenheit.
b) toFahrenheit devuelve el equivalente en Fahrenheit de una
temperatura en Celsius.
Utiliza estas funciones para escribir un programa que imprima tablas
que muestren los equivalentes Fahrenheit de todas las temperaturas
Celsius de 0 a 100 grados, y los equivalentes Celsius de todas las
temperaturas Fahrenheit de 32 a 212 grados. Imprima las salidas en un
formato tabular que minimice el número de líneas de salida sin dejar
de ser legible.
Ejercicio de funciones
3. (Lanzamiento de monedas) Escribe un programa que simule el
lanzamiento de una moneda. Para cada lanzamiento, muestre Cara o
Cruz. Deje que el programa lance la moneda 100 veces y cuente el
número de caras y cruces. Muestre los resultados. El programa debe
llamar a una función flip que no toma argumentos y devuelve 0 para
cruz y 1 para cara. Si el programa simula de forma realista el
lanzamiento de la moneda, entonces cada cara de la moneda debería
aparecer aproximadamente la mitad de las veces para un total de
aproximadamente 50 caras y 50 cruces.
Recursividad
• Una función recursiva es aquella que se llama a sí misma directamente o
indirectamente a través de otra función.

Casos base y llamadas recursivas

• Los enfoques de resolución de problemas recursivos tienen varios


elementos en común:
• Se llama a una función recursiva para resolver un problema. La función en
realidad sólo sabe cómo resolver el caso(s) más simple(s), o el llamado caso(s)
base. Este devuelve un resultado.
Recursividad
• Cuando se le llama con un problema más complejo, la función suele dividir el
problema en dos partes conceptuales:
• Una parte que la función sabe cómo hacer, y
• Otra parte que la función no sabe hacer.

• Para que la recursividad sea factible, esta última pieza debe parecerse al
problema original, pero ser una versión ligeramente más simple o más
pequeña.

• Como este nuevo problema se parece al problema original, la función lanza


(llama) una nueva copia de sí misma para trabajar en el problema más
pequeño, lo que se denomina llamada recursiva o paso de recursión.
Recursividad
• El paso de recursión también incluye una declaración de retorno, porque su resultado
se combinará con la parte del problema que la función sabía resolver para formar un
resultado que se devolverá al llamante original.

• El paso de recursión se ejecuta mientras la llamada original a la función está en


pausa, esperando el resultado del paso de recursión.

• Esto puede dar lugar a muchas más llamadas recursivas de este tipo, ya que la
función sigue dividiendo cada problema con el que se llama en dos piezas
conceptuales.

• Para que la recursión termine, cada vez que la función se llama a sí misma con una
versión ligeramente más simple del problema original, esta secuencia de problemas
más pequeños debe converger finalmente en el caso base.
Recursividad
• Cuando la función reconoce el caso base:

• Se devuelve un resultado a la copia anterior de la función, y se produce una


secuencia de devoluciones en toda la línea hasta que la llamada original de la
función finalmente devuelve el resultado final a su convocante.

• Como ejemplo de estos conceptos en el trabajo, vamos a escribir un


programa recursivo para realizar un cálculo matemático popular.
Recursividad
Cálculo recursivo de factoriales

• El factorial de un entero no negativo n, escrito n! (pronunciado "n


factorial"), es el producto:

• con 1! igual a 1, y 0! definido como 1. Por ejemplo, 5! es el producto 5 * 4


* 3 * 2 * 1, que es igual a 120.
Recursividad
• El factorial de un número entero, mayor o igual a 0, puede calcularse de
forma iterativa (no recursiva) utilizando una sentencia for como la
siguiente:

• Se llega a una definición recursiva de la función factorial observando la


siguiente relación:
Recursividad
• Por ejemplo, 5! es claramente igual a 5 * 4! como se muestra en lo
siguiente:

• Evaluar 5! Recursivamente
Recursividad
Recursividad
• La serie Fibonacci comienza con 0 y 1 y tiene la propiedad de que cada
número Fibonacci posterior es la suma de los dos números Fibonacci
anteriores.

• 0, 1, 1, 2, 3, 5, 8, 13, 21, …

• La serie de Fibonacci puede definirse de forma recursiva como sigue:


Recursividad
Recursividad
• El siguiente diagrama muestra cómo la función fibonacci evaluaría
fibonacci(3):
Recursividad vs Iteración
Características comunes de la iteración y la recursión

• Tanto la iteración como la recursión se basan en una sentencia de control: La


iteración utiliza una sentencia de iteración; la recursión utiliza una sentencia de
selección.

• Tanto la iteración como la recursión implican repetición: La iteración utiliza una


sentencia de iteración; la recursión consigue la repetición mediante llamadas a
funciones repetidas.

• Tanto la iteración como la recursión tienen una prueba de terminación: La


iteración termina cuando falla la condición de continuación del bucle; la recursión
cuando se reconoce un caso base.
Recursividad vs Iteración

• Tanto la iteración como la recursión controladas por contador se acercan


gradualmente a la terminación: La iteración sigue modificando un contador hasta
que el contador asume un valor que hace que la condición de continuación del
bucle falle; la recursión sigue produciendo versiones más simples del problema
original hasta que se alcanza el caso base.

• Tanto la iteración como la recursión pueden ocurrir infinitamente: Un bucle


infinito ocurre con la iteración si la prueba de continuación del bucle nunca se
hace falsa; la recursión infinita ocurre si el paso de recursión no reduce el
problema cada vez de manera que converja en el caso base. La iteración y la
recursión infinitas suelen producirse como resultado de errores en la lógica de un
programa.
Recursividad vs Iteración
• Aspectos negativos de la recursividad

• Invoca repetidamente el mecanismo, y en consecuencia el overhead de las


llamadas a funciones.

• Esto puede ser costoso tanto en tiempo de procesador como en espacio de


memoria.

• La iteración se produce normalmente dentro de una función, por lo que se omite


el overhead de las llamadas repetidas a la función y la asignación extra de
memoria.

• ¿Por qué elegir la recursividad?


Recursividad vs Iteración

• Cualquier problema que pueda resolverse de forma recursiva también


puede resolverse de forma iterativa (no recursiva).

• Un enfoque recursivo se elige en lugar de un enfoque iterativo cuando el


enfoque recursivo refleja más naturalmente el problema y resulta en un
programa que es más fácil de entender y depurar.

• Otra razón para elegir una solución recursiva es que una solución iterativa
puede no ser evidente.
Actividad en clase
• (Exponenciación) Escriba una función integerPower(base, exponente) que
devuelva el valor de baseexponente. Por ejemplo, integerPower(3, 4) = 3 * 3 * 3 *
3. Asuma que el exponente es un entero positivo, no nulo, y la base es un
entero. La función integerPower debe utilizar una sentencia for para controlar
el cálculo. No utilice ninguna función de la biblioteca de math.

• (Exponenciación recursiva) Escribe una función recursiva integerPower(base,


exponente) que al ser invocada devuelva baseexponente. Por ejemplo,
integerPower(3, 4) = 3 * 3 * 3 * 3. Supongamos que el exponente es un entero
mayor o igual a 1. Sugerencia: El paso de recursividad utilizaría la relación
baseexponente = base * baseexponente-1 y la condición de terminación se produce
cuando el exponente es igual a 1 porque base1 = base
Ejercicios
• (Múltiplos) Escribe una función isMultiple que determine para un par de enteros si el
segundo entero es un múltiplo del primero. La función debe tomar dos argumentos enteros
y devolver 1 (verdadero) si el segundo es un múltiplo del primero, y 0 (falso) en caso
contrario. Utilice esta función en un programa que introduzca una serie de pares de enteros.

• (Fibonacci) La serie de Fibonacci

0, 1, 1, 2, 3, 5, 8, 13, 21, ...

comienza con los términos 0 y 1 y tiene la propiedad de que cada término siguiente es la
suma de los dos términos anteriores. Primero, escriba una función no recursiva
fibonacci(n) que calcule el enésimo número de Fibonacci. Utilice int como parámetro de la
función y unsigned long int como tipo de retorno. A continuación, determine el mayor
número de Fibonacci que se puede imprimir en su sistema.
Ejercicios
• (Cuadrado de Asteriscos) Escriba una función que muestre un cuadrado sólido de
asteriscos cuyo lado se especifica en el parámetro entero lado. Por ejemplo, si el lado
es 4, la función muestra:

• (Visualización de un cuadrado de cualquier carácter) Modifique la función en el


Ejercicio anterior para formar el cuadrado de cualquier carácter contenido en el
parámetro char fillCharacter. Así, si el lado es 5 y fillCharacter es "#", entonces esta
función debería imprimir:
Ejercicios
• (Máximo común divisor) El máximo común divisor (GCD) de dos números enteros es el mayor
número entero que divide uniformemente a cada uno de los dos números. Escribe una
función gcd que devuelva el máximo común divisor de dos números enteros.

• (Puntos de calidad para las notas de los estudiantes) Escribe una función toQualityPoints que
introduzca la media de un estudiante y devuelva 4 si es 90-100, 3 si es 80-89, 2 si es 70-79,1 si
es 60-69, y 0 si la media es inferior a 60.

• (Lanzamiento de monedas) Escriba un programa que simule el lanzamiento de una moneda.


Para cada lanzamiento, muestre Cara o Cruz. Deje que el programa lance la moneda 100
veces y cuente el número de caras y cruces. Muestre los resultados. El programa debe llamar
a una función flip que no toma argumentos y devuelve 0 para cruz y 1 para cara. Si el
programa simula de forma realista el lanzamiento de la moneda, entonces cada cara de la
moneda debería aparecer aproximadamente la mitad de las veces para un total de
aproximadamente 50 caras y 50 colas.
Ejercicios
• (Adivina el número) Escribe un programa en C que juegue al juego de "adivinar el número" de la
siguiente manera: Tu programa elige el número a adivinar seleccionando un entero al azar en el rango de
1 a 1000. El programa entonces escribe:

• El jugador escribe una primera respuesta. El programa responde con uno de los siguientes:

• Si la respuesta es incorrecta, el programa debe hacer un bucle hasta que el jugador adivine el número.
Su programa debe seguir diciéndole al jugador Demasiado alto o Demasiado bajo para ayudar al jugador
a "poner a cero" la respuesta correcta.
Ejercicios
• (Modificación de Adivina el Número) Modifica tu solución del Ejercicio anterior para
contar el número de adivinanzas que hace el jugador. Si el número es 10 o menos,
imprime "¡O sabes el secreto o has tenido suerte!" Si el jugador adivina el número en
10 intentos, imprima "¡Ajá! Sabes el secreto!" Si el jugador adivina más de 10 veces,
entonces imprime "¡Deberías ser capaz de hacerlo mejor!" ¿Por qué no debería tardar
más de 10 intentos? Bueno, con cada "buena suposición" el jugador debería ser capaz
de eliminar la mitad de los números. Ahora muestre por qué cualquier número del 1 al
1000 puede ser adivinado en 10 o menos intentos.

• (Máximo comun divisor recursivo) El máximo común divisor de los enteros X y Y es el


mayor número entero que divide uniformemente a X y Y. Escribe una función recursiva
gcd que devuelva el máximo común divisor de X y Y. El máximo común divisor de X y Y
se define recursivamente como sigue: Si Y es igual a 0, entonces gcd(X, Y) es X; en caso
contrario gcd(X, Y) es gcd(Y, X % Y), donde % es el operador del resto.
Ejercicios
• (Modificación del juego de dados) Modifique el programa de dados de las diapositivas
75 y 76 para permitir las apuestas. Empaquete como una función la parte del programa
que ejecuta un juego de dados. Inicialice la variable bankBalance a 1000 dólares. Pida al
jugador que introduzca una apuesta. Utilizar un bucle while para comprobar que la
apuesta es menor o igual al bankBalance, y si no, pedir al usuario que vuelva a
introducir la apuesta hasta que se introduzca una apuesta válida. Después de introducir
una apuesta correcta, ejecute una partida de dados. Si el jugador gana, aumenta el
bankBalance por la apuesta e imprime el nuevo bankBalance. Si el jugador pierde,
disminuye el bankBalance por la apuesta, imprime el nuevo bankBalance, comprueba si
el bankBalance se ha convertido en cero, y si es así imprime el mensaje, "Lo siento. Te
has pasado". A medida que el juego avanza, imprime varios mensajes para crear un
poco de "charla" como, "Oh, vas a por todas, ¿eh?" o "¡Ah, vamos, arriésgate!" o "Has
subido mucho. Ahora es el momento de cobrar tus fichas".
Ejercicios
• En teoría de la computación, la función de Ackermann es una función
recursiva que toma dos números naturales como argumentos y devuelve
un único número natural. Escriba un programa en C, que determine la
serie de Ackerman cuando se proporcionan los parámetros m y n. Como
norma general se define como sigue:
Ejercicios arreglos
• (Eliminación de duplicados) Utilice un arreglo unidimensional para
resolver el siguiente problema. Lee 20 números, cada uno de los
cuales está entre 10 y 100, ambos inclusive. A medida que se lee cada
número, imprímalo sólo si no es un duplicado de un número ya leído.
Prevea el "peor caso" en el que los 20 números sean diferentes.
Utiliza la matriz más pequeña posible para resolver este problema.
Ejercicios arreglos
• Escriba un programa en C, que realice la ordenación de un vector de una
dimensión usando el método de “Ordenación de selección”. Una
ordenación de selección recorre un arreglo buscando el elemento más
pequeño del mismo. Cuando encuentra el más pequeño, es intercambiado
con el primer elemento del arreglo. El proceso a continuación se repite
para el subarreglo que empieza con el segundo elemento del arreglo. Cada
pasada del arreglo resulta en un elemento colocado en su posición
correcta. Esta ordenación requiere de capacidades de procesamiento
similares a la ordenación de tipo burbuja para un arreglo de n elementos,
deberán de hacerse n-1 pasada, y para cada subarreglo, se harán n-1
comparaciones para encontrar el valor más pequeño. Cuando el subarreglo
bajo procesamiento contenga un solo elemento, el arreglo habrá quedado
terminado y ordenado.
Punteros
• Definición e inicialización de variables de puntero

• Los punteros son variables cuyos valores son direcciones de memoria.

• Un puntero contiene la dirección de una variable que contiene un valor específico. El


puntero apunta a esa variable.

• Por lo tanto, un nombre de variable hace referencia directa a un valor, y un puntero


hace referencia indirecta a un valor.
Punteros
• Referenciar un valor a través de un puntero se llama indirección.
Punteros
• Declaración de los punteros

• Los punteros, como todas las variables, deben ser definidos antes de poder ser
utilizados. La siguiente declaración define la variable countPtr como un int *-un
puntero a un entero:

• int *countPtr;

• Esta definición se lee de derecha a izquierda, "countPtr es un puntero a int" o


"countPtr apunta a un objeto de tipo int". El * indica que la variable es un puntero.
Punteros
• Nombres de las variables de puntero

• Nuestra convención es terminar el nombre de cada variable puntero con Ptr para indicar que la variable es un
puntero y debe ser manejada en consecuencia.
• Otras convenciones de nomenclatura comunes incluyen comenzar el nombre de la variable con p (por ejemplo,
pCount) o p_ (por ejemplo, p_count).

• Definir variables en sentencias separadas

• El * en la siguiente definición no se distribuye a cada variable:


• int *countPtr, count;

• por lo que countPtr es un puntero a int, pero count es sólo un int.

• Por esta razón, siempre debe escribir la declaración anterior como dos sentencias para evitar la ambigüedad:
• int *countPtr;
• int count;
Punteros
Inicialización y asignación de valores a los punteros

• Los punteros deben ser inicializados cuando se definen, o se les puede asignar un valor.
Un puntero puede ser inicializado a NULL, 0 o una dirección:

• Un puntero con el valor NULL no apunta a nada. NULL es una constante simbólica con el valor 0 y está definida en
la cabecera <stddef.h> (y en varias otras cabeceras, como <stdio.h>).

• Inicializar un puntero a 0 es equivalente a inicializarlo a NULL. Se prefiere la constante NULL porque enfatiza que
se está inicializando un puntero en lugar de una variable que almacena un número. Cuando se asigna 0, primero
se convierte en un puntero del tipo apropiado. El valor 0 es el único valor entero que puede asignarse
directamente a una variable puntero.

• Asignación de la dirección de una variable a un puntero. Inicialice los punteros para evitar resultados
inesperados.
Punteros
Operadores de puntero (Operadores de dirección (&) y de indirección (*), y su relación)

• El operador unario de dirección (&) devuelve la dirección de su operando. Por ejemplo, dada la siguiente
definición de y:
int y = 5;
la sentencia
int *yPtr = &y

• Inicializa la variable puntero yPtr con la dirección de la variable y - se dice que yPtr "apunta a" y. El
siguiente diagrama muestra las variables yPtr and y en memoria:
Punteros
Representación del puntero en memoria

• El siguiente diagrama muestra la representación del puntero anterior en memoria, suponiendo que la
variable entera y se almacena en la posición 600000 y la variable puntero yPtr se almacena en la posición
500000:

• El operando de & debe ser una variable y no puede aplicarse a valores literales (como 27 o 41.5) o
expresiones.
Punteros
El operador de indirección (*)
• El operador de indirección unario (*), también llamado operador de desreferenciación, se aplica a un
operando puntero para obtener el valor del objeto al que apunta el puntero. Por ejemplo, la siguiente
sentencia imprime 5, que es el valor de la variable y:

• printf("%d", *yPtr);

• El uso de * de esta manera se llama desreferenciar un puntero.

La desreferenciación de un puntero que no ha sido inicializado o asignado a la dirección de otra variable en


memoria es un error. Esto podría
• causar un error fatal en tiempo de ejecución,
• modificar accidentalmente datos importantes y permitir que el programa se ejecute hasta el final con resultados
incorrectos, o
• conducir a un fallo de seguridad.
Punteros
Demostración de los operadores & y *
Punteros
Paso de argumentos a funciones por referencia

• Hay dos maneras de pasar argumentos a una función: por valor y por referencia.

• Por defecto, los argumentos (que no sean arrays) se pasan por valor.

• Las funciones a menudo necesitan modificar variables en el llamador o recibir un puntero a un objeto de
datos grande para evitar la sobrecarga de copiar el objeto (como en pass-by-value).

• Una sentencia return puede devolver como máximo un valor de una función llamada a su convocante. El
pase por referencia también puede permitir que una función "devuelva" múltiples valores modificando las
variables del llamante.
Punteros
Uso de & y * para lograr el paso por referencia

• Los punteros y el operador de indirección permiten el paso por referencia.

• Cuando se llama a una función con argumentos que deben ser modificados en el llamador, se utiliza &
para pasar la dirección de cada variable.

• Los arrays no se pasan usando el operador & porque el nombre de un array es equivalente a
&nombredelarray[0]-la ubicación inicial del array en memoria.

• Una función que recibe la dirección de una variable en el llamador puede usar el operador de indirección
(*) para modificar el valor en esa ubicación en la memoria del llamador, efectuando así el paso por
referencia.
Punteros
Paso por valor
Punteros
Paso por referencia
Punteros
Utilizar un parámetro puntero para recibir una dirección

• Una función que recibe una dirección como argumento debe recibirla en un parámetro puntero.
Por ejemplo, en la Fig. 7.3, la cabecera de la función cubeByReference (línea 17) es:
• void cuboPorReferencia(int *nPtr) {

• el cual especifica que cubeByReference recibe la dirección de una variable entera como
argumento, almacena la dirección localmente en el parámetro nPtr y no devuelve un valor.

Parámetros de puntero en los prototipos de función

• El prototipo de función para cubeByReference especifica un parámetro int *. Como con otros
parámetros, no es necesario incluir los nombres de los punteros en los prototipos de función -
son ignorados por el compilador - pero es una buena práctica incluirlos para propósitos de
documentación.
Punteros
Funciones que reciben arrays unidimensionales

• Para una función que espera un argumento de un arreglo unidimensional, el prototipo y


la cabecera de la función pueden utilizar la notación de puntero mostrada en la lista de
parámetros de la función cubeByReference.

• El compilador no diferencia entre una función que recibe un puntero y una que recibe
un array unidimensional. Por lo tanto, la función debe "saber" cuando está recibiendo un
array frente a una única variable pasada por referencia.

• Cuando el compilador encuentra un parámetro de función para un array unidimensional


de la forma int b[], el compilador convierte el parámetro a la notación de puntero int *b.
Las dos formas son intercambiables. Del mismo modo, para un parámetro de la forma
const int b[] el compilador convierte el parámetro a const int *b.
Punteros
Paso a paso: paso por valor contra paso por referencia
• Análisis de un típico pase por valor
Punteros
Paso a paso: paso por valor contra paso por referencia
• Análisis de un pase por referencia típico con un argumento de puntero.
Punteros
Uso del calificador const con punteros

• El calificador const le permite informar al compilador que el valor de una variable en


particular no debe ser modificado, aplicando así el principio de mínimo privilegio.

• Esto puede reducir el tiempo de depuración y evitar efectos secundarios involuntarios,


haciendo un programa más robusto y más fácil de modificar y mantener. Si se intenta
modificar un valor declarado como const, el compilador lo detecta y emite un error.

• A lo largo de los años, una gran base de código heredado se escribió en las primeras
versiones de C que no utilizaba const porque no estaba disponible. Incluso código actual
no utiliza const tan a menudo como debería.
Punteros
• Hay cuatro formas de pasar a una función un puntero a datos:

• Un puntero no constante a datos no constantes.

• Un puntero constante a datos no constantes.

• Un puntero no constante a datos constantes.

• Un puntero constante a datos constantes.

• Cada una de las cuatro combinaciones proporciona diferentes privilegios de acceso

• ¿Cómo elegir una de las posibilidades?


• Déjese guiar por el principio del mínimo privilegio.
Punteros
Conversión de una cadena a mayúsculas utilizando un puntero no constante a datos no
constantes

• El nivel más alto de acceso a los datos se concede mediante un puntero no constante a datos no
constantes.

• Los datos pueden ser modificados a través del puntero desreferenciado, y el puntero puede ser
modificado para apuntar a otros elementos de datos.

• Una función podría utilizar un puntero de este tipo para recibir como argumento una cadena, y luego
procesar (y posiblemente modificar) cada carácter de la cadena.
Punteros
Conversión de una cadena a mayúsculas utilizando un puntero no constante a datos no
constantes
Punteros
Impresión de una cadena de caracteres de uno en uno utilizando un puntero no
constante a datos constantes

• Un puntero no constante a datos constantes puede ser modificado para apuntar a


cualquier elemento de datos del tipo apropiado, pero los datos a los que apunta no
pueden ser modificados.

• Una función podría recibir un puntero de este tipo para procesar los elementos de un
argumento de un arreglo sin modificarlos.
Punteros
Impresión de una cadena de caracteres de uno en uno utilizando un puntero no
constante a datos constantes
Punteros
Intentando modificar datos constantes
Punteros
Pasar Estructuras vs. Arrays

• Los arrays son tipos agregados que almacenan elementos de datos relacionados del mismo tipo bajo un mismo nombre.

• En los siguientes temas, se abordara otra forma de tipo agregado llamada estructura—structure- (a veces llamada registro o
tupla en otros lenguajes), que puede almacenar elementos de datos relacionados del mismo o diferente tipo bajo un mismo
nombre; por ejemplo, información de un empleado, como su número de identificación, nombre, dirección y salario.

• A diferencia de los arreglos, las estructuras se pasan por valor: se pasa una copia de toda la estructura. Esto requiere la
sobrecarga en tiempo de ejecución de hacer una copia de cada elemento de datos en la estructura y almacenarla en la pila de
llamadas de función del ordenador.

• Al pasar objetos grandes, como estructuras, utilizando punteros a datos constantes, se obtiene el rendimiento de la
transferencia por referencia y la seguridad de la transferencia por valor. En este caso, el programa sólo copia la dirección en la
que está almacenada la estructura, normalmente cuatro u ocho bytes.

• Si la memoria es escasa y la eficiencia de la ejecución es una preocupación, utilice punteros. Si la memoria es abundante y la
eficiencia no es una preocupación importante, pase los datos por valor para aplicar el principio de mínimo privilegio. Algunos
sistemas no aplican bien el const, por lo que el paso por valor sigue siendo la mejor manera de evitar que los datos se
modifiquen.
Punteros
Intentar modificar un puntero constante a datos no constantes

• Un puntero constante a datos no constantes siempre apunta a la misma ubicación de memoria, pero los
datos en esa ubicación pueden ser modificados a través del puntero.

• Esto es lo que ocurre por defecto con el nombre de un array, que es un puntero constante al primer
elemento del array. Se puede acceder a todos los datos del arreglo y modificarlos utilizando el nombre del
arrreglo y la subinscripción de la misma.

• Un puntero constante a datos no constantes puede usarse para recibir un array como argumento de una
función que accede a los elementos del array usando la notación de subíndices del array.

• Los punteros declarados const deben ser inicializados cuando se definen. Si el puntero es un parámetro de
función, se inicializa con un argumento de puntero cuando se llama a la función.
Punteros
Intentar modificar un puntero constante a datos no constantes
Punteros
Intentar modificar un puntero constante a datos constantes

• El menor privilegio de acceso lo otorga un puntero constante a datos constantes.

• Un puntero de este tipo siempre apunta a la misma ubicación de memoria, y los datos en
esa ubicación de memoria no pueden ser modificados.

• Esto es como debe pasarse un array a una función que sólo mira los elementos del array
utilizando la notación de subíndices del array y no modifica los elementos.
Punteros
Intentar modificar un puntero constante a datos constantes
Punteros
Ordenamiento de Burbuja usando Paso-Por-Referencia
Punteros
El operador sizeof
• C proporciona el operador unario sizeof para determinar el tamaño de un objeto o tipo en
bytes.
Punteros
Determinación de los tamaños de los tipos estándar, un array y un puntero
Punteros
Expresiones de puntero y aritmética de puntero

• Los punteros son operandos válidos en expresiones aritméticas, expresiones de


asignación y expresiones de comparación.

• Sin embargo, no todos los operadores aritméticos son válidos con variables puntero.

• Esta sección describe los operadores que pueden tener punteros como operandos, y
cómo se utilizan estos operadores.
Punteros
Expresiones de puntero y aritmética de puntero

• Las siguientes operaciones aritméticas están permitidas para los punteros:

• incrementar (++) o disminuir (--),

• sumar un entero a un puntero (+ o +=),

• restar un entero de un puntero (- o -=), y

• restar un puntero de otro, lo que sólo tiene sentido cuando ambos punteros apuntan a los elementos
de un mismo arreglo.

• La aritmética de punteros sobre punteros que no se refieren a elementos de un arreglo es


un error lógico.
Punteros
Apuntando un puntero a un array

• Supongamos que el array int v[5] está definido, y su primer elemento está en la posición
3000 de la memoria. Además, supongamos que el puntero vPtr apunta a v[0]-así que el
valor de vPtr es 3000. El siguiente diagrama ilustra este escenario para una computadora
con enteros de cuatro bytes:

• La variable vPtr puede ser inicializada para apuntar al array v con cualquiera de las
sentencias
• vPtr = v;
• vPtr = &v[0];
Punteros
Sumando un entero a un puntero

• En la aritmética convencional, 3000 + 2 da el valor 3002. Esto no suele ocurrir con la aritmética de punteros.
Cuando se añade un número entero a un puntero o se le resta uno, el puntero aumenta o disminuye en ese
número entero por el tamaño del objeto al que se refiere el puntero. Por ejemplo, la sentencia

• vPtr += 2;

• produciría 3008 (3000 + 2 * 4), suponiendo que un entero se almacena en cuatro bytes de memoria. En el
array v, vPtr apuntaría ahora a v[2], como en el siguiente diagrama:
Punteros
Restando un entero a un puntero

• Si vPtr se hubiera incrementado hasta 3016 (v[4]), la sentencia

• vPtr -= 4;

• devolvería a vPtr a 3000 (v[0]), el principio del array. Usar la aritmética de punteros para ajustar los
punteros para que apunten fuera de los límites de un array es un error lógico que podría llevar a problemas
de seguridad.
Punteros
Incremento y decremento de un puntero

• Para incrementar o decrementar un puntero en uno, utilice los operadores de incremento (++) y
decremento (--). Cualquiera de las sentencias

• ++vPtr;
• vPtr++;

• incrementa el puntero para que apunte al siguiente elemento del array. Cualquiera de las sentencias

• --vPtr
• vPtr--;

• disminuye el puntero para que apunte al elemento anterior del array.


Punteros
Restar un puntero a otro

• Si vPtr contiene la posición 3000 y v2Ptr contiene la dirección 3008, la sentencia

• x = v2Ptr - vPtr;

• asigna a x el número de elementos del array entre vPtr y v2Ptr, en este caso, 2 (no 8). La aritmética de
punteros es indefinida a menos que se realice sobre elementos del mismo array.

• No podemos asumir que dos variables del mismo tipo estén almacenadas una al lado de la otra en la
memoria a menos que sean elementos adyacentes de un array.
Punteros
Asignación de punteros entre sí

• Los punteros del mismo tipo pueden asignarse entre sí.

• La excepción a esta regla es un puntero a void (es decir, void *), un puntero genérico que puede representar
cualquier tipo de puntero.

• Todos los tipos de punteros pueden ser asignados a un void *, y a un void * se le puede asignar un puntero
de cualquier tipo (incluyendo otro void *).

• En ambos casos, no se requiere una operación de conversión.


Punteros
Puntero a void

• Un puntero a void no puede ser desreferenciado.

• Considere lo siguiente: El compilador sabe en una computadora con enteros de cuatro bytes que un int *
apunta a cuatro bytes de memoria.

• Sin embargo, un void * contiene una ubicación de memoria para un tipo desconocido - el número exacto de
bytes a los que se refiere el puntero no es conocido por el compilador.

• El compilador debe conocer el tipo para determinar el número de bytes que representan el valor
referenciado. Desreferenciar un puntero void * es un error de sintaxis.
Punteros
Comparación de punteros

• Se pueden comparar punteros utilizando operadores de igualdad y relacionales, pero dichas comparaciones
sólo tienen sentido si los punteros apuntan a elementos del mismo array; en caso contrario, dichas
comparaciones son errores lógicos.

• Las comparaciones de punteros comparan las direcciones almacenadas en los punteros. Dicha comparación
puede mostrar, por ejemplo, que un puntero apunta a un elemento del array con un número mayor que el
otro.

• Un uso común de la comparación de punteros es determinar si un puntero es NULL.


Punteros
Relación entre punteros y arreglos

• Los arreglos y los punteros están íntimamente relacionados y a menudo pueden usarse indistintamente. Puedes
pensar en un nombre de array como un puntero constante al primer elemento del array. Los punteros pueden
utilizarse para realizar cualquier operación que implique la subscripción de un array. Supongamos las siguientes
definiciones:

• int b[5];
• int *bPtr;

• Como el nombre del array b (sin subíndice) es un puntero al primer elemento del array, podemos establecer bPtr
a la dirección del primer elemento del array b con la sentencia

• bPtr = b;

• Esto equivale a tomar la dirección del primer elemento del array b de la siguiente manera:

• bPtr = &b[0];
Punteros
Notación de puntero/desplazamiento

• El elemento del arreglo b[3] puede ser referenciado alternativamente con la expresión de puntero

• *(bPtr + 3)

• El 3 de la expresión es el desplazamiento del puntero. Cuando bPtr apunta al primer elemento del arreglo, el
desplazamiento indica a qué elemento del arreglo se debe hacer referencia; el valor del desplazamiento es idéntico al
subíndice del arreglo.

• Esta notación se conoce como notación puntero/desplazamiento. Los paréntesis son necesarios porque la precedencia
de * es mayor que la de +. Sin los paréntesis, la expresión anterior añadiría 3 al valor de la expresión *bPtr (es decir, se
añadiría 3 a b[0], suponiendo que bPtr apunta al principio del arreglo). Así como el elemento del arreglo puede ser
referenciado con una expresión de puntero, la dirección

• &b[3]

• puede escribirse con la expresión de puntero

• bPtr + 3
Punteros
• El nombre de un arreglo también puede ser tratado como un puntero y utilizado en la aritmética de
punteros.

• Por ejemplo, la expresión

• *(b + 3)

• se refiere al elemento b[3]. En general, todas las expresiones de arreglos con subíndice pueden
escribirse con un puntero y un desplazamiento. En este caso, se ha utilizado la notación
puntero/desplazamiento con el nombre del arreglo como puntero.

• La declaración anterior no modifica el nombre del arreglo de ninguna manera; b sigue apuntando al
primer elemento.
Punteros
Notación de punteros/subscriptores

• Los punteros pueden tener subíndices como los arreglos. Si bPtr tiene el valor b, la expresión

• bPtr[1]

• Se refiere al elemento del arreglo b[1]. Esto se conoce como notación de puntero/subscripción.

No se puede modificar el nombre de un arreglo con la aritmética de punteros

• Un nombre de arreglo siempre apunta al principio del arreglo, por lo que es como un puntero constante. Así, la
expresión

• b += 3

• no es válida porque intenta modificar el valor del nombre del arreglo con aritmética de punteros. Intentar
modificar el valor de un nombre de arreglo con aritmética de punteros es un error de compilación.
Punteros
Demostración de la subinscripción de punteros y desplazamientos
Punteros
Copia de cadenas con arreglos y punteros
Punteros
Arreglos de punteros

• Los arreglos pueden contener punteros. Un uso común de un arreglo de punteros es formar un arreglo de
cadenas, referido simplemente como un arreglo de cadenas. Cada elemento de una cadena en C es
esencialmente un puntero a su primer carácter.

• Así, cada entrada en un arreglo de cadenas es en realidad un puntero al primer carácter de una cadena.
Considere la definición del arreglo de cadenas suit, que podría ser útil para representar una baraja de
cartas.

• const char *suit[4] = {"Corazones", "Diamantes", "Tréboles", "Picas"};

• El arreglo tiene cuatro elementos. El char * indica que cada elemento del suit es de tipo "puntero a char". El
calificador const indica que la cadena a la que apunta cada elemento no puede ser modificada. Las cadenas
"Corazones", "Diamantes", "Tréboles" y "Picas" se colocan en el arreglo. Cada una de ellas se almacena en
memoria como una cadena de caracteres con terminación nula que es un carácter más largo que el número
de caracteres de las comillas. Así, las cadenas tienen 7, 9, 6 y 7 caracteres.
Punteros
• Aunque parece que estas cadenas se colocan en el arreglo, en realidad sólo se almacenan punteros, como
se muestra en el siguiente diagrama:

• Cada puntero apunta al primer carácter de su cadena correspondiente. Por lo tanto, aunque un arreglo char
* tiene un tamaño fijo, puede apuntar a cadenas de caracteres de cualquier longitud. Esta flexibilidad es un
ejemplo de las poderosas capacidades de estructuración de datos de C. Los juegos podrían haberse
colocado en un arreglo bidimensional, en el que cada fila representaría un juego, y cada columna
representaría una letra del nombre del juego.

• Esta estructura de datos tendría que tener un número fijo de columnas por fila, y ese número tendría que
ser tan grande como la cadena más grande. Por lo tanto, se podría desperdiciar una cantidad considerable
de memoria al almacenar muchas cadenas más cortas que la cadena más larga.
Punteros
Punteros de función

• Hemos visto que el nombre de un arreglo es en realidad la dirección en memoria del primer
elemento del arreglo.

• Del mismo modo, el nombre de una función es realmente la dirección inicial en memoria del
código que realiza la tarea de la función.

• Un puntero a una función contiene la dirección de la función en memoria.

• Los punteros a funciones pueden pasarse a funciones, devolverse desde funciones, almacenarse
en arreglos, asignarse a otros punteros a funciones del mismo tipo y compararse entre sí para
comprobar su igualdad o desigualdad.
Punteros
Ordenación ascendente o descendente
Punteros
Uso de punteros de función para crear un sistema gestionado por menús

• Un uso común de los punteros de función es en los sistemas gestionados por menús. Un
programa pide al usuario que seleccione una opción de un menú (posiblemente de 0 a 2)
escribiendo el número del elemento del menú.

• El programa da servicio a cada opción con una función diferente. Almacena los punteros a cada
función en un arreglo de punteros de función.

• La elección del usuario se utiliza como un subíndice del arreglo, y el puntero en el arreglo se utiliza
para llamar a la función.
Punteros
Uso de punteros de función para crear un sistema gestionado por menús
Ejercicios con uso obligatorio de punteros
• (Eliminación de duplicados) Utilice un arreglo unidimensional para
resolver el siguiente problema. Lee 20 números, cada uno de los
cuales está entre 10 y 100, ambos inclusive. A medida que se lee cada
número, imprímalo sólo si no es un duplicado de un número ya leído.
Prevea el "peor caso" en el que los 20 números sean diferentes.
Ejercicios con uso de punteros
• Escriba un programa en C, que realice la ordenación de un vector de una
dimensión usando el método de “Ordenación de selección”. Una
ordenación de selección recorre un arreglo buscando el elemento más
pequeño del mismo. Cuando encuentra el más pequeño, es intercambiado
con el primer elemento del arreglo. El proceso a continuación se repite
para el subarreglo que empieza con el segundo elemento del arreglo. Cada
pasada del arreglo resulta en un elemento colocado en su posición
correcta. Esta ordenación requiere de capacidades de procesamiento
similares a la ordenación de tipo burbuja para un arreglo de n elementos,
deberán de hacerse n-1 pasada, y para cada subarreglo, se harán n-1
comparaciones para encontrar el valor más pequeño. Cuando el subarreglo
bajo procesamiento contenga un solo elemento, el arreglo habrá quedado
terminado y ordenado.
Ejercicios con punteros
• Analizar y entender el caso de estudio propuesto en el punto 7.11
“Random-Number Simulation Case Study: Card Shuffling and Dealing”
del libro de Deitel, P. and Deitel, H. (2021). C How to program. Ninth
Edition, Pearson.

• Realizar un video donde se describa a detalle el caso de estudio con base en


una presentación. Este video se subirá a la actividad respectiva en Google
Classroom.

• En la clase del 27 de septiembre se seleccionará al azar un alumno para que


explique el caso de estudio con base en su presentación.
En clase: Ejercicio 1 con arreglos de punteros
a funciones
• Realizar un programa que lleve a cabo la suma, resta, multiplicación y
división de dos números de tipo float utilizando un arreglo de
punteros a funciones del mismo nombre de las operaciones. El
usuario selecciona la opción de la operación y los valores a utilizar.
En clase: ejercicio 2 con punteros a funciones
• (Adivina el número) Escribe un programa en C que juegue al juego de "adivinar el número" de la
siguiente manera: Tu programa elige el número a adivinar seleccionando un entero al azar en el rango de
1 a 1000. El programa entonces escribe:

• El jugador escribe una primera respuesta. El programa responde con uno de los siguientes:

• Si la respuesta es incorrecta, el programa debe hacer un bucle hasta que el jugador adivine el número.
Su programa debe seguir diciéndole al jugador Demasiado alto o Demasiado bajo para ayudar al jugador
a "poner a cero" la respuesta correcta.
En clase: ejercicio con punteros a función
• (Modificación de Adivina el Número) Modifica tu solución del Ejercicio anterior para
contar el número de adivinanzas que hace el jugador. Si el número es 10 o menos,
imprime "¡O sabes el secreto o has tenido suerte!" Si el jugador adivina el número en
10 intentos, imprima "¡Ajá! Sabes el secreto!" Si el jugador adivina más de 10 veces,
entonces imprime "¡Deberías ser capaz de hacerlo mejor!" ¿Por qué no debería tardar
más de 10 intentos? Bueno, con cada "buena suposición" el jugador debería ser capaz
de eliminar la mitad de los números. Ahora muestre por qué cualquier número del 1 al
1000 puede ser adivinado en 10 o menos intentos.
Ejercicios con uso obligatorio de punteros
• (Barajar y repartir cartas: repartir manos de póker) Modifique el
programa de la Fig. 7.16 para que la función de repartir cartas reparta
una mano de póker de cinco cartas. Luego escriba las siguientes
funciones adicionales:
• Determinar si la mano contiene una pareja.
• Determinar si la mano contiene dos pares.
• Determinar si la mano contiene un trío (por ejemplo, tres jotas).
• Determinar si la mano contiene cuatro iguales (por ejemplo, cuatro ases).
• Determinar si la mano contiene un color (es decir, las cinco cartas del mismo
palo).
• Determinar si la mano contiene una escalera (es decir, cinco cartas de valores
consecutivos).
Ejercicios con uso obligatorio de punteros
• (Proyecto: Barajar y repartir cartas-¿Qué mano de póker es mejor?) Utilice
las funciones desarrolladas en el Ejercicio 7.12 para escribir un programa que
reparta dos manos de póker de cinco cartas, evalúe cada una y determine
cuál es la mejor mano.

• (Proyecto: Barajar y repartir cartas-Simulando a la banca) Modifique el


programa desarrollado en el Ejercicio 7.13 para que pueda simular a la
banca. La mano de cinco cartas de la banca se reparte "boca abajo" para que
el jugador no pueda verla. El programa debe entonces evaluar la mano del
crupier, y en base a la calidad de la mano, el crupier debe robar una, dos o
tres cartas más para reemplazar el número correspondiente de cartas
innecesarias en la mano original. El programa debería entonces reevaluar la
mano del crupier. [Precaución: ¡este es un problema difícil!]
Ejercicios con uso obligatorio de punteros
• (Proyecto: Barajar y repartir cartas-Permitir a los jugadores robar
cartas) Modifique el programa desarrollado en el Ejercicio 7.14 para
que pueda manejar la mano del repartidor automáticamente, pero se
permita al jugador decidir qué cartas de la mano del jugador
reemplazar. El programa debería entonces evaluar ambas manos y
determinar quién gana. Ahora utilice este nuevo programa para jugar
20 partidas contra el ordenador. ¿Quién gana más partidas, tú o el
ordenador? Haz que uno de tus amigos juegue 20 partidas contra el
ordenador. ¿Quién gana más partidas? Basándose en los resultados de
estas partidas, perfeccione su programa de juego de póquer. Juegue 20
partidas más. ¿Su programa modificado juega mejor?
Ejercicios con uso obligatorio de arreglos de punteros
• (Arreglos de punteros de función) Reescriba el programa de la Fig. 6.17 para utilizar una
interfaz dirigida por menús. El programa debería ofrecer al usuario cuatro opciones como las
siguientes:

Una de las restricciones al uso de arreglos de punteros a funciones es que todos los punteros
deben tener el mismo tipo. Los punteros deben ser a funciones del mismo tipo de retorno que
reciben argumentos del mismo tipo. Por esta razón, las funciones de la Fig. 6.17 deben ser
modificadas para que cada una devuelva el mismo tipo y tome los mismos parámetros.
Modifique las funciones mínimo y máximo para que impriman el valor mínimo o máximo y no
devuelvan nada. Para la opción 3, modifique la función promedio de la Fig. 6.17 para que
devuelva el promedio de cada estudiante (no de un estudiante específico). La función promedio
no debería devolver nada y tomar los mismos parámetros que printArray, mínimo y máximo.
Guarde los punteros a las cuatro funciones en el arreglo processGrades y utilice la elección hecha
por el usuario como subíndice en el arreglo para llamar a cada función.
Ejercicios con uso obligatorio de arreglos de punteros
• (Calcular la Circunferencia, el Área del Círculo o el Volumen de la Esfera
utilizando punteros de función) Utilizando las técnicas de la Fig. 7.18, cree
un programa guiado por menús. Permita al usuario elegir si desea calcular
la circunferencia de un círculo, el área de un círculo o el volumen de una
esfera. El programa debería entonces introducir un radio del usuario,
realizar el cálculo apropiado y mostrar el resultado. Utilice un arreglo de
punteros de función en el que cada puntero represente una función que
devuelva void y reciba un parámetro doble. Cada una de las funciones
correspondientes debe mostrar mensajes que indiquen qué cálculo se ha
realizado, el valor del radio y el resultado del cálculo.
Ejercicios con uso obligatorio de arreglos de punteros
• (Calculadora utilizando punteros de función) Utilizando las técnicas que
aprendió en la Fig. 7.18, cree un programa dirigido por un menú que
permita al usuario elegir si desea sumar, restar, multiplicar o dividir dos
números. El programa debería entonces introducir dos valores dobles del
usuario, realizar el cálculo apropiado y mostrar el resultado. Utilice un
arreglo de punteros a funciones en el que cada puntero represente una
función que devuelva void y reciba dos parámetros dobles. Cada una de
las funciones correspondientes debe mostrar mensajes que indiquen qué
cálculo se ha realizado, los valores de los parámetros y el resultado del
cálculo.
Ejercicios con uso obligatorio de arreglos de punteros
• (Calculadora de la huella de carbono) Utilizando arreglos de punteros de funciones,
como aprendiste en este capítulo, puede especificar un conjunto de funciones que se
llaman con los mismos tipos de argumentos y devuelven el mismo tipo de datos. Los
gobiernos y las empresas de todo el mundo están cada vez más preocupados por la
huella de carbono (las emisiones anuales de dióxido de carbono a la atmósfera) de los
edificios que queman varios tipos de combustibles para la calefacción, los vehículos
que queman combustibles para la energía, y similares. Muchos científicos culpan a
estos gases de efecto invernadero del fenómeno llamado calentamiento global. Crea
tres funciones que ayuden a calcular la huella de carbono de un edificio, un coche y
una bicicleta, respectivamente. Cada función debe introducir los datos adecuados del
usuario, y luego calcular y mostrar la huella de carbono. (Consulta algunas páginas web
que explican cómo calcular la huella de carbono). Cada función no debe recibir
parámetros y devolverá un valor nulo. Escriba un programa que pida al usuario que
introduzca el tipo de huella de carbono a calcular, y luego llame a la función
correspondiente en el arreglo de punteros de función. Para cada tipo de huella de
carbono, muestre alguna información de identificación y la huella de carbono del
objeto.

También podría gustarte