Unidad I Conceptos Básicos PDF
Unidad I Conceptos Básicos PDF
Unidad I Conceptos Básicos PDF
1.4. Funciones
• 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).
• 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.
• 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).
• 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:
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 OR)
• ! (logical Negation)
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.
• Las funciones de la biblioteca estándar de C están escritas por expertos, están bien
probadas y son eficientes.
• 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.
• 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:
• 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:
• 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.
• 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).
• 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.
• Pero cuando una función llamada regresa a su llamador, las variables locales de la
función llamada necesitan "desaparecer".
• 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.
• 6655651153
• 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
• 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.
• 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.
• 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.
• 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.
• 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.
• 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:
• 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, …
• 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.
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:
• (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.
• 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.
• 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;
• 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).
• 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);
• 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
• 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.
• 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
• 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.
• 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:
• 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
• 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
• 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
• 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
• restar un puntero de otro, lo que sólo tiene sentido cuando ambos punteros apuntan a los elementos
de un mismo arreglo.
• 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
• 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--;
• 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í
• 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 *).
• 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.
• 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]
• bPtr + 3
Punteros
• El nombre de un arreglo también puede ser tratado como un puntero y utilizado en la aritmética de
punteros.
• *(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.
• 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.
• 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.
• 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.
• 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.
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.