- Aprender
- Informática
- Programación
- Programación en C
- Primeros programas en C
Primeros programas en C: tipos, operadores y bucles
printf y scanf. Construye expresiones con los operadores aritméticos, de comparación, lógicos y a nivel de bits de C, prediciendo las conversiones y la precedencia, y después dirige un programa con if, switch, while, do-while y for. Para terminar, aprende por qué se desbordan los enteros, qué ocurre cuando sucede y cómo escribir y probar código que lo evite.PrincipianteUnidad 1 de 8 del curso Curso de Programación en C
Lo que aprenderás
- Convierte código fuente en C en un programa ejecutable, comprende qué hace el compilador en cada etapa y utiliza sus mensajes para detectar y corregir errores.
- Escribir, compilar y ejecutar un primer programa en CIdentifica las partes de un programa mínimo en C, compílalo con gcc o clang y ejecuta el archivo ejecutable desde una terminal.
- Leer los mensajes del compilador y corregir erroresDistingue los errores de compilación, las advertencias, los errores de enlace, de ejecución y lógicos, y utiliza los mensajes del compilador y las ejecuciones de prueba para corregirlos.
- Escribir, compilar y ejecutar un primer programa en C
- Declara e inicializa variables, elige entre los tipos enteros y de coma flotante de C y predice el rango y la precisión que ofrece cada tipo.
- Declarar, inicializar y dar nombre a variablesCrea variables con un tipo y un nombre significativo, dales valores iniciales y usa constantes para los valores fijos.
- Elegir un tipo entero y conocer su rangoElige entre los tipos enteros con y sin signo de C y los valores de carácter y booleanos, a partir de sus tamaños, rangos y formas literales.
- Trabajar con números de coma flotante y sus limitacionesAlmacena números reales en float y double, predice su precisión y sus errores de redondeo, y convierte de forma segura entre valores de coma flotante y enteros.
- Declarar, inicializar y dar nombre a variables
- Da formato preciso a números y texto con printf, y lee y comprueba los valores introducidos por el usuario con scanf.
- Dar formato a la salida con printfUsa especificaciones de conversión, anchos de campo, precisión, indicadores y secuencias de escape para producir exactamente la salida que deseas.
- Leer y comprobar la entrada con scanfLee números y caracteres introducidos por el usuario, comprueba que la lectura se haya realizado correctamente y recupérate de entradas no válidas.
- Dar formato a la salida con printf
- Usa los operadores aritméticos, de asignación, de comparación, lógicos y a nivel de bits de C, y predice el valor y el tipo de cualquier expresión a partir de las conversiones, la precedencia y la asociatividad.
- Calcular con operadores aritméticos y de asignaciónAplica +, -, *, / y %, la división entera, la asignación compuesta y el incremento y decremento, y evita sus errores habituales.
- Predecir las conversiones de tipos en expresiones mixtasExplica cómo convierte C los operandos de distintos tipos, usa conversiones explícitas de tipo de forma deliberada y evita el problema de mezclar valores con y sin signo.
- Combinar condiciones con operadores relacionales, lógicos y a nivel de bitsEscribe comparaciones y condiciones compuestas correctas, usa la evaluación en cortocircuito y manipula bits individuales.
- Aplicar la precedencia, la asociatividad y el orden de evaluaciónPredice cómo agrupa C los operadores de una expresión, evalúa expresiones paso a paso y usa paréntesis cuando la agrupación no esté clara.
- Calcular con operadores aritméticos y de asignación
- Elige entre alternativas con if, else y switch, y repite tareas con bucles while, do-while y for que empiecen, se ejecuten y terminen exactamente como deseas.
- Tomar decisiones con if y elseEscribe sentencias if, else if y else con condiciones y llaves correctas, y evita los errores habituales de la sintaxis de C.
- Seleccionar entre casos con switchUsa switch con etiquetas case, break y default para elegir entre valores enteros fijos, y utiliza la continuación al siguiente caso solo de forma deliberada.
- Repetir mientras se cumpla una condición con while y do-whileEscribe bucles controlados por una condición que empiecen correctamente, progresen y terminen, incluidos bucles de entrada y bucles que deban ejecutarse al menos una vez.
- Contar con bucles for, anidar bucles y terminarlos anticipadamenteEscribe bucles for que se ejecuten exactamente el número de veces deseado, combina bucles dentro de otros bucles y usa break y continue de forma deliberada.
- Tomar decisiones con if y else
- Explica qué ocurre cuando un resultado no cabe en su tipo entero y escribe y prueba código que evite o detecte desbordamientos.
- Explicar qué ocurre cuando un valor no cabe en su tipoDescribe la reducción modular de enteros sin signo, el desbordamiento indefinido de enteros con signo y las conversiones fuera de rango, y relaciónalos con fallos reales.
- Escribir código que evite o detecte desbordamientosElige tipos con suficiente rango, comprueba las operaciones antes de que se desborden y usa herramientas del compilador y pruebas de valores límite para detectar desbordamientos a tiempo.
- Explicar qué ocurre cuando un valor no cabe en su tipo
Guía de estudio de Primeros programas en C
1Construir y ejecutar programas en C
El código C se convierte en un programa ejecutable mediante una construcción. Aprende las partes de un primer programa completo, compílalo con un estándar del lenguaje elegido y ejecuta el archivo resultante. Cuando falle una construcción o una prueba, usa la etapa en que se produjo el fallo y el primer mensaje del compilador para localizar la causa. Seguir cuidadosamente el ciclo de edición, construcción y prueba evita que un ejecutable antiguo oculte un cambio fallido y facilita la comprobación de cada corrección.
1.1 Del código fuente a un programa en ejecución
Un programa en C empieza como texto plano y se ejecuta como código máquina. El compilador conecta esas dos formas. Guardar un cambio modifica el código fuente; no cambia el ejecutable que construiste antes.
Lee un programa completo
Guarda esto como hello.c:
#include <stdio.h>
int main(void)
{
printf("Hello, world!\n");
return 0;
}
#include <stdio.h> pone a disposición las declaraciones para la entrada y la salida estándar, incluida printf. La ejecución empieza en main. En int main(void), int indica el tipo de retorno y void significa que no hay parámetros. Las llaves delimitan el cuerpo. La llamada a printf escribe el texto entre comillas; \n pasa a una nueva línea. return 0; termina el programa e informa al sistema operativo de que la ejecución fue correcta. Un estado distinto de cero puede indicar un fallo. Desde C99, llegar al final de main también devuelve cero.
Las sentencias simples terminan con punto y coma. La línea de inclusión no lo lleva. C distingue entre mayúsculas y minúsculas: Printf y printf son nombres distintos. Los espacios entre tokens normalmente no afectan al programa, pero una sangría uniforme permite ver la estructura de los bloques.
Compila y después ejecuta
En la terminal, abre la carpeta que contiene el código fuente e introduce:
gcc -std=c17 -Wall -Wextra hello.c -o hello
./hello
El primer comando construye un ejecutable llamado hello; el segundo lo ejecuta desde la carpeta actual. Su salida es Hello, world! seguido de un salto de línea. Puedes usar clang en lugar de gcc. Sin -o hello, el nombre habitual de salida en Linux o macOS es a.out. Los ejecutables de Windows usan .exe. make hello de CS50 es un atajo que pide a la herramienta de construcción que invoque al compilador.
Después de cambiar el saludo, guarda, vuelve a compilar y ejecuta de nuevo. Si falla la compilación, puede que aún exista un ejecutable anterior. Al ejecutarlo se muestra el comportamiento antiguo, por lo que debes comprobar que la construcción haya tenido éxito antes de evaluar tu cambio.
Qué hace la construcción
El preprocesador procesa directivas como #include, insertando el texto de las cabeceras. El compilador propiamente dicho traduce C a lenguaje ensamblador. El ensamblador produce código objeto, que aún puede contener nombres sin resolver. El enlazador combina el código objeto y resuelve referencias como printf. El código de biblioteca puede copiarse en un ejecutable mediante enlace estático o proporcionarse mediante una biblioteca compartida que se carga al ejecutar el programa.
Puedes detener la construcción en etapas intermedias:
| Opción | Resultado |
|---|---|
-E | Texto preprocesado |
-S | Lenguaje ensamblador |
-c | Un archivo objeto, como hello.o |
Un archivo objeto aún no es el ejecutable terminado. Un ejecutable puede funcionar sin el compilador, pero está construido para un sistema operativo y un procesador concretos. Para trasladar el programa a otra plataforma, compila allí su código fuente.
Comentarios y versiones del lenguaje
// inicia un comentario que termina en el salto de línea. /* ... */ puede abarcar varias líneas, pero estos comentarios no pueden anidarse: el primer */ termina el comentario. Usa comentarios para explicar una decisión que el código por sí solo no deja clara.
Selecciona explícitamente la versión del lenguaje. C17 admite los ejemplos anteriores, incluidos los comentarios //, long long y las declaraciones en cabeceras de for. C23 añade características como bool como palabra clave; se publicó como ISO/IEC 9899:2024. -Wall -Wextra activa advertencias útiles. Los valores predeterminados de los compiladores varían, por lo que un -std=c17 explícito deja claras las reglas del lenguaje que se pretenden usar.
Puntos clave
- El código fuente de C debe volver a compilarse después de cada cambio.
- La ejecución empieza en
main; devolver cero indica que la ejecución fue correcta. - Las etapas de construcción son preprocesamiento, compilación, ensamblado y enlace.
- Usa
-std=c17 -Wall -Wextrapara seleccionar el estándar y activar las advertencias.
1.2 Encontrar la primera causa de un error
El momento en que aparece un problema ayuda a encontrar su causa. Un programa puede no llegar a construirse, detenerse durante la ejecución o terminar con una respuesta incorrecta. Esas situaciones requieren distintas comprobaciones.
Distingue los tipos de problemas
| Tipo | Qué observas | Dónde buscar |
|---|---|---|
| Error de compilación | No hay un nuevo ejecutable | Sintaxis, declaraciones y tipos |
| Advertencia | Un posible problema; la construcción suele continuar | La expresión indicada en el mensaje |
| Error de enlace | Existe código objeto, pero no se puede completar el ejecutable | Nombres de funciones sin resolver o bibliotecas necesarias |
| Error de ejecución | Aparece un problema durante la ejecución | Operaciones y valores alcanzados con esa entrada |
| Error lógico | El programa se ejecuta, pero da una respuesta no deseada | Condiciones, fórmulas y actualizaciones |
Una advertencia puede señalar código no válido o comportamiento indefinido, además de un error habitual. Una compilación correcta no garantiza que el programa sea correcto.
Lee un mensaje de diagnóstico
Un mensaje del compilador suele indicar una ubicación en el código fuente, un nivel de gravedad y una descripción. Un mensaje como hello.c:5:16: error: expected ';' after expression señala que falta un punto y coma cerca de la línea 5. A veces el compilador detecta el problema en la línea siguiente. Revisa la línea indicada y la inmediatamente anterior.
Corrige primero el primer error y después vuelve a compilar. Una comilla sin cerrar puede hacer que las líneas posteriores parezcan parte de una cadena; una llave sin pareja puede hacer que las declaraciones posteriores parezcan estar en el bloque equivocado. Varios mensajes pueden desaparecer después de una sola corrección. No empieces cambiando todas las líneas mencionadas en la salida.
Para este fragmento:
int total = 6
printf("%d\n", total);
La declaración necesita un punto y coma después de 6. La línea de printf puede ser donde el compilador detecta por primera vez que la declaración anterior no terminó. Comprueba también la escritura exacta, que los paréntesis y las llaves estén equilibrados y la cabecera que declara una función de biblioteca. Una llamada a printf necesita <stdio.h>; las llamadas a funciones no declaradas no están permitidas en C99 y posteriores.
Toma las advertencias en serio
Construye con -Wall -Wextra. Estas opciones ayudan a detectar una asignación en una condición, variables no utilizadas, comparaciones entre valores con y sin signo y formatos de salida que no coinciden. Por ejemplo, printf("%d\n", 3.5); pide un entero pero pasa un double; el formato no convierte el argumento por ti. Corrige el formato a %f o convierte el valor deliberadamente. -Werror hace que las advertencias detengan la construcción.
Comprende el comportamiento indefinido
Comportamiento indefinido significa que C no impone requisitos sobre el comportamiento del programa. Pueden causarlo el desbordamiento de enteros con signo, la división entera por cero y algunas lecturas de variables sin inicializar. Un programa puede detenerse, mostrar un valor inesperado o parecer que funciona. Las opciones del compilador pueden cambiar lo que ocurre.
Un mensaje como «Floating point exception» puede incluso ser resultado de una división entera por cero en algunos sistemas. Diagnostica la operación y la entrada, no solo la redacción de un mensaje del sistema operativo. Comprueba el divisor antes de ejecutar la división.
Sigue la ejecución de un resultado incorrecto
Para un error lógico, predice a mano un caso pequeño y compáralo con la salida real. Añade llamadas temporales a printf para mostrar un total acumulado o el valor comprobado por una condición. Cambia una causa a la vez y vuelve a construir después de cada cambio. Prueba valores habituales y límites como cero, uno, una entrada negativa y la mayor entrada esperada. Una traza muestra dónde empieza el programa a apartarse de tu predicción.
Puntos clave
- Corrige el primer mensaje de diagnóstico y vuelve a construir antes de investigar los mensajes posteriores.
- Una advertencia puede señalar comportamiento indefinido; la compilación por sí sola no demuestra que el programa sea correcto.
- Ajusta las conversiones de formato a los tipos de los argumentos.
- Para un resultado incorrecto, sigue la ejecución con una entrada pequeña y compara los valores intermedios.
- Prueba tanto los límites como las entradas habituales.
2Almacenar datos en variables de los tipos básicos
Un tipo determina cómo se interpreta un valor almacenado y qué valores puede contener. Empieza por las declaraciones, los valores iniciales y los nombres disponibles dentro de un bloque. Después elige los tipos enteros según su rango y los tipos de coma flotante según su precisión y su rango. Los códigos de caracteres y los valores booleanos también forman parte de este panorama. Leer literales y comprobar límites te prepara para predecir qué puede representar un cálculo antes de escribirlo.
2.1 Variables, nombres y valores fijos
Una variable da nombre a un valor almacenado. Su tipo indica a C cómo interpretar ese almacenamiento. Declaras el tipo antes de usar la variable, y esta conserva ese tipo durante todo su tiempo de vida.
Da un valor inicial a cada variable
int count = 0;
double length_m = 1.8;
count = count + 1;
Las dos primeras líneas declaran e inicializan variables. La tercera asigna un nuevo valor a una variable existente: lee el valor actual de count, suma uno y vuelve a almacenar el resultado. La asignación es una acción, por lo que count = count + 1 tiene sentido aunque no sería una ecuación matemática.
La inicialización ocurre en una declaración; la asignación puede ocurrir después. En int a, b = 5;, solo se inicializa b. Una a local tiene un valor indeterminado hasta que almacenas uno. Leerla puede producir un comportamiento impredecible, incluido comportamiento indefinido. Inicializa los contadores y los totales al declararlos, como en int total = 0;.
La asignación copia el valor en ese momento:
int width = 4;
int area = width * 3;
width = 7;
printf("%d %d\n", width, area);
Esto muestra 7 12. La asignación a area calculó 12 una sola vez. No creó una fórmula que vuelva a calcularse cada vez que cambia width. Para actualizar area, asígnale un nuevo cálculo.
Elige nombres legibles
Un identificador puede contener letras, dígitos y guiones bajos, pero no puede empezar por un dígito ni ser una palabra clave. height_cm y second_place son válidos; 2nd_place no lo es, y my-score se interpreta como una resta. Los nombres distinguen entre mayúsculas y minúsculas, por lo que total y Total son distintos. Los nombres que indican un significado o una unidad facilitan la revisión de las fórmulas.
Sigue el ámbito de bloque
Un nombre local está disponible desde su declaración hasta el final de su bloque. Desde C99, las declaraciones pueden aparecer entre otras sentencias.
int count = 2;
{
int count = 9;
printf("%d\n", count);
}
printf("%d\n", count);
La declaración interior oculta el nombre exterior mientras se ejecuta ese bloque. La salida es 9 y después 2, en líneas separadas. Son dos variables, no dos asignaciones a una misma variable. Declarar dos veces el mismo nombre en un bloque es un error. Reutilizar nombres en bloques anidados está permitido, pero dificulta seguir el código, por lo que es preferible usar nombres que aclaren cada función.
Da nombre a los valores fijos
const int SECONDS_PER_MINUTE = 60;
#define MAX_HEIGHT 8
const crea una variable con tipo a la que no se puede asignar un nuevo valor mediante ese nombre. Una sentencia posterior SECONDS_PER_MINUTE = 90; requiere un mensaje de diagnóstico. El inicializador proporciona el valor fijo en la declaración.
#define crea una sustitución del preprocesador: las apariciones posteriores de MAX_HEIGHT se sustituyen por el token 8 antes de la compilación. Esta directiva no lleva = ni punto y coma. Un punto y coma pasaría a formar parte de la sustitución y podría romper una expresión como height > MAX_HEIGHT.
Un nombre como SECONDS_PER_MINUTE indica a quien lee por qué aparece 60 en un cálculo y te da un solo lugar donde mantenerlo. Las mayúsculas son una convención de nombres, no una regla que haga constante un valor. La declaración o la directiva proporciona ese comportamiento.
Puntos clave
- Inicializa las variables locales antes de leerlas.
- La asignación copia un valor una vez; los valores dependientes no se actualizan por sí solos.
- Un bloque limita dónde puede usarse un nombre local.
- Los nombres distinguen entre mayúsculas y minúsculas y no pueden empezar por un dígito.
constproporciona una variable con tipo de solo lectura;#defineproporciona una sustitución de tokens.
2.2 Elegir tipos enteros, de carácter y booleanos
Los tipos enteros almacenan números enteros de forma exacta dentro de un rango fijo. Elige un tipo según los valores que debe manejar el programa, incluidos los resultados intermedios, y según si los valores negativos tienen sentido.
Tamaños y rangos
El estándar garantiza anchos mínimos: al menos 8 bits para char, 16 para short e int, 32 para long y 64 para long long. Cada tipo de esta secuencia tiene al menos el mismo ancho que el anterior. Los tamaños reales dependen de la plataforma.
| Tipo | Bytes habituales en Linux/macOS de 64 bits | Diferencia en Windows de 64 bits |
|---|---|---|
char | 1 | Ninguna |
short | 2 | Ninguna |
int | 4 | Ninguna |
long | 8 | 4 bytes |
long long | 8 | Ninguna |
sizeof(int) indica el tamaño en bytes como un size_t; muéstralo con %zu. La cabecera <limits.h> proporciona nombres como INT_MIN, INT_MAX, UINT_MAX, LLONG_MAX y CHAR_BIT, el número de bits por byte.
Con N bits de valor, un entero sin signo tiene valores de 0 a 2ᴺ − 1. Un entero con signo en complemento a dos con N bits abarca de −2ᴺ⁻¹ a 2ᴺ⁻¹ − 1. Así, un int de 32 bits abarca de −2 147 483 648 a 2 147 483 647, mientras que un unsigned int de 32 bits llega a 4 294 967 295. C23 exige el complemento a dos; C17 también permitía otras representaciones.
En un patrón de complemento a dos de 8 bits, el bit de mayor peso tiene valor −128. 11111111 representa −1 y 10000000 representa −128. Para formar −5, empieza con 00000101, invierte los bits para obtener 11111010 y suma uno para obtener 11111011.
Los caracteres también son enteros
Un char almacena un código de carácter en un byte. Usa comillas simples para una constante de carácter, como en 'A'. En ASCII, 'A' tiene el código 65 y '0' tiene el código 48. printf("%c %d\n", 'A', 'A'); muestra A 65 en ASCII. Una constante de carácter en C tiene tipo int, por lo que sizeof('A') es sizeof(int).
char sin modificador puede tener signo o no, según la implementación. Usa signed char o unsigned char cuando importe el rango numérico. <stdint.h> ofrece tipos enteros de ancho exacto como int32_t y uint8_t en las plataformas que admiten esos anchos exactos; las plataformas habituales los admiten.
Lee literales enteros
Una constante decimal como 42 tiene tipo int cuando cabe en ese tipo. Los sufijos solicitan otros tipos: 42u es sin signo, 42L es long, 42LL es long long y 42ULL es unsigned long long. El hexadecimal usa 0x, como en 0x2A. Un cero inicial indica octal: 052 también significa 42 y 010 significa 8. 09 no es un octal válido. C23 añade notación binaria como 0b101010.
Valores de verdad y elección del tipo
En C17, incluye <stdbool.h> para usar bool, true y false; C23 los convierte en palabras clave. Una conversión a bool da falso para cero y verdadero para cualquier valor distinto de cero, incluido 0.5. Muestra un valor booleano con %d para ver 0 o 1.
Usa int para recuentos y contadores de bucle habituales. Usa long long o un tipo de ancho exacto adecuado cuando el rango pueda superar aproximadamente los 2,1 mil millones. Los tipos sin signo son adecuados para patrones de bits, pero una resta por debajo de cero se reduce modularmente a un valor grande, por lo que también necesitan comprobaciones de rango.
Puntos clave
- Usa los límites del tipo, no un tamaño supuesto para
long. - Un
intcon signo de 32 bits llega a 2 147 483 647. - Los valores sin signo no tienen rango negativo y se reducen modularmente al bajar de cero.
- Un cero inicial indica un literal entero octal.
- Que
charsin modificador tenga signo depende de la implementación. - En C17, los nombres booleanos proceden de
<stdbool.h>.
2.3 Valores decimales y precisión limitada
Un tipo de coma flotante almacena una aproximación a un número real. Tiene un rango amplio, pero solo un número fijo de cifras significativas. El rango y la precisión responden a preguntas distintas: qué magnitud puede tener un valor y con qué detalle pueden distinguirse valores cercanos.
Elige un tipo de coma flotante
En los sistemas IEEE 754 habituales, float ocupa 4 bytes y contiene aproximadamente 6–7 cifras decimales significativas. double ocupa 8 bytes y contiene aproximadamente 15–16. Sus mayores valores finitos son aproximadamente 3,4 × 10³⁸ y 1,8 × 10³⁰⁸. long double tiene al menos la misma precisión que double, con un formato que depende de la plataforma. double es una opción predeterminada útil para magnitudes medidas.
Las constantes como 2.0, 3.14 y 1e-3 son double; 1e-3 significa 0,001. Añade f para float, como en 2.5f, o L para long double. La constante 2 es un entero, por lo que puede dar lugar a una división entera donde 2.0 produciría una división de coma flotante.
Observa la aproximación almacenada
Muchas fracciones decimales no tienen una forma binaria finita exacta. En un sistema habitual de precisión doble:
printf("%.20f\n", 0.1);
printf("%.17f\n", 0.1 + 0.2);
Las salidas son 0.10000000000000000555 y 0.30000000000000004. Las cifras adicionales muestran la aproximación almacenada. Explican por qué 0.1 + 0.2 == 0.3 puede ser falso.
Usa una tolerancia al comparar medidas calculadas:
#include <math.h>
/* inside main */
if (fabs(a - b) < 1e-9) {
printf("Close enough\n");
}
fabs devuelve una diferencia absoluta. Elige la tolerancia según la escala y la precisión de los valores; 1e-9 es un ejemplo, no un ajuste universal. Algunas construcciones en Linux necesitan -lm al final del comando del compilador para enlazar las funciones matemáticas.
La precisión afecta a los enteros grandes
Un double binario representa exactamente todos los enteros hasta 2⁵³. Un float binario deja de representar todos los enteros por encima de 2²⁴ = 16 777 216: asignar 16 777 217 a un float de ese tipo da 16 777 216. Un rango mayor no significa que sigan disponibles todos los pasos entre enteros consecutivos.
La impresión redondea el valor binario almacenado. printf("%.2f\n", 2.675); suele mostrar 2.67, porque el valor almacenado es ligeramente menor que 2,675. Para importes exactos en centésimas enteras, usa un entero como long long cents = 1999; y después da formato a la presentación al final.
Convierte y redondea de forma deliberada
La conversión a un entero descarta la fracción hacia cero: (int)3.99 es 3 y (int)-3.99 es −3. round de <math.h> devuelve un valor de coma flotante redondeado al entero más cercano, con los valores a mitad de camino alejados de cero: round(-2.5) da −3,0. Convierte ese resultado a un entero solo cuando quepa en el tipo. Una conversión de coma flotante a entero cuya parte entera está fuera de rango tiene comportamiento indefinido.
En la aritmética IEEE, los resultados fuera del rango finito pueden convertirse en infinito. Una división de coma flotante de un valor distinto de cero por cero puede producir infinito; cero dividido por cero produce NaN, que significa «no es un número». Un NaN se compara como distinto incluso de sí mismo. Estos son comportamientos de coma flotante IEEE; la división entera por cero tiene comportamiento indefinido.
Puntos clave
- Los tipos de coma flotante tienen precisión y rango limitados.
- Compara las medidas calculadas con una tolerancia adecuada.
- La impresión redondea la aproximación almacenada.
- La conversión de coma flotante a entero trunca hacia cero y requiere un resultado representable.
3Mostrar la salida y leer la entrada con printf y scanf
Un programa necesita un intercambio claro de texto y valores. Da formato a la salida para que quien la lea pueda distinguir etiquetas, campos y medidas redondeadas, y después ajusta cada conversión de entrada a la variable que la recibe. La lectura de texto tiene varios resultados posibles: todos los valores solicitados, solo algunos o ninguna conversión. Comprobar esos resultados y el texto sin leer hace que los cálculos posteriores dependan de valores que el programa realmente ha obtenido.
3.1 Mostrar valores con el formato deseado
printf convierte valores en texto mediante una cadena de formato. Los caracteres ordinarios se muestran tal como están escritos. Una conversión que empieza por % indica cómo mostrar el siguiente argumento.
Ajusta cada conversión a su argumento
printf("%d samples, %.2f kg\n", 4, 1.5);
Esto muestra 4 samples, 1.50 kg y un salto de línea. Las conversiones corresponden a los argumentos en orden. Un formato no convierte el tipo del argumento proporcionado al tipo que pretendías usar.
| Tipo de valor o representación | Conversión |
|---|---|
int | %d o %i |
unsigned int | %u |
long, long long | %ld, %lld |
size_t | %zu |
double, o un float promocionado | %f, %e, %g |
long double | %Lf |
| Carácter, literal de cadena | %c, %s |
| Hexadecimal sin signo, octal | %x o %X, %o |
Un argumento float se promociona a double en printf, por lo que ambos usan %f; aquí %lf es equivalente. Pasar 3 a %f es una discrepancia de tipos, no una petición para mostrar 3.0. Usa 3.0 o (double)3. Un tipo que no coincide o un argumento ausente puede causar comportamiento indefinido. Construye el programa con las advertencias activadas para que se comprueben las cadenas de formato literales.
Establece el ancho y la precisión
El ancho es un número mínimo de caracteres, incluidos el signo y el punto decimal. La precisión de coma flotante después del punto controla los decimales de %f. En %8.2f, ocho es el ancho mínimo y dos es el número de cifras después del punto decimal. Un número más ancho se muestra completo; el ancho no lo recorta.
Con el entero 42, %5d da 42, %-5d da 42 , %05d da 00042 y %+d da +42. Sin una precisión, %f muestra seis decimales.
printf("%-6s%8s\n", "Item", "Price");
printf("%-6s%8.2f\n", "Tea", 2.5);
printf("%-6s%8.2f\n", "Cake", 12.75);
Cada etiqueta ocupa un campo de seis caracteres alineado a la izquierda, seguido de un campo de precio de ocho alineado a la derecha. Ambos precios tienen dos decimales, por lo que sus puntos decimales quedan alineados. Los espacios proceden de los anchos de campo, no de una separación automática entre argumentos.
Representaciones científica y general
%e usa notación científica: 12 345,678 se muestra como 1.234568e+04 con la precisión predeterminada. Para %g, la precisión cuenta cifras significativas, seis de forma predeterminada. Usa notación científica cuando el exponente decimal es menor que −4 o al menos igual a la precisión, y elimina los ceros finales. Así, %g muestra 0,0001 como 0.0001, 0,00001 como 1e-05 y 1 000 000,0 como 1e+06. No se limita a elegir la representación más corta posible.
Muestra caracteres especiales
Dentro de una cadena de C, \n significa nueva línea, \t significa tabulación, \\ significa barra invertida y \" significa comilla. Un signo de porcentaje en un formato se escribe %%: printf("100%%\n"); muestra 100% y un salto de línea.
printf no añade espacios ni saltos de línea por sí solo. printf("Count:"); printf("%d", 4); muestra Count:4. Una terminal puede retrasar el texto sin salto de línea; fflush(stdout); envía un mensaje de solicitud pendiente antes de pedir la entrada.
Puntos clave
- Ajusta las conversiones a los tipos reales de los argumentos.
- El ancho de campo es un mínimo; la precisión de
%fcuenta decimales. - La precisión de
%gcuenta cifras significativas. - Escribe
%%para un signo de porcentaje literal en un formato. printfno añade espacios ni saltos de línea salvo que el formato los incluya.
3.2 Leer la entrada y comprobar qué se ha leído
scanf lee texto, lo convierte y almacena valores en variables. Comprueba su resultado antes de usar esas variables. Un texto que empieza por dígitos puede aceptarse solo en parte, por lo que una conversión correcta y una línea de entrada completamente válida son cosas distintas.
Proporciona un destino del tipo adecuado
int count;
double mass_kg;
if (scanf("%d %lf", &count, &mass_kg) != 2) {
return 1;
}
El & pasa la dirección de cada variable para que scanf pueda almacenar datos en ella. %d necesita un int, %u un entero sin signo, %ld un long y %lld un long long. Para la entrada de coma flotante, %f necesita un float y %lf un double. A diferencia de printf, estas dos conversiones no son intercambiables. La falta de & o un tipo de destino incorrecto pueden causar comportamiento indefinido.
%d lee decimal. %i también reconoce entradas hexadecimales y octales con cero inicial; con %i, el texto «010» significa 8. Usa %d para recuentos decimales habituales.
Comprueba el valor de retorno
scanf devuelve el número de asignaciones completadas. Para dos valores solicitados, una lectura correcta significa 2. Una conversión fallida deja su destino sin cambios. Puede que siga sin inicializar, por lo que no debes usarlo salvo que el número devuelto confirme que la lectura fue correcta. EOF significa que la entrada terminó o que ocurrió un error de entrada antes de que se completara la primera conversión.
Para scanf("%d:%d", &h, &m), los dos puntos deben aparecer literalmente. La entrada «14:30» rellena ambas variables. La entrada «14 30» rellena h, después no encuentra los dos puntos y deja m sin cambios. El valor de retorno es 1.
Comprende los caracteres en blanco y la entrada parcial
Las conversiones numéricas omiten espacios, tabulaciones y saltos de línea iniciales. %c lee el carácter inmediatamente siguiente, incluido un salto de línea que haya quedado pendiente. Usa scanf(" %c", &choice) para omitir los caracteres en blanco antes de leer un carácter visible. Evita un salto de línea final en el formato: puede hacer que la llamada espere un carácter posterior que no sea blanco.
Con %d, «12abc» almacena 12 y deja «abc» sin leer. «3.7» almacena 3 y deja «.7». «abc» no almacena nada y devuelve 0. Volver a llamar a la misma conversión sin consumir la entrada incorrecta puede fallar indefinidamente con el mismo carácter.
Recupérate de una conversión fallida
int n, result;
while ((result = scanf("%d", &n)) != 1) {
if (result == EOF) {
return 1;
}
int ch;
while ((ch = getchar()) != '\n' && ch != EOF) {
}
printf("Try again: ");
fflush(stdout);
}
getchar devuelve un int, lo que le permite representar tanto un carácter como EOF. El bucle interior descarta la línea que falló. El bucle exterior vuelve a intentarlo, pero sigue aceptando un prefijo numérico como 12 en «12abc». Exigir que el siguiente carácter sea '\n' comprueba la regla más estricta de un entero seguido inmediatamente de un salto de línea; esa regla también rechaza los espacios finales. Una política que permita espacios o una última línea terminada en EOF debe manejar esos casos explícitamente.
Después de una conversión correcta, comprueba el rango permitido, como de 1 a 8 para un ajuste de altura. scanf no rechaza de forma segura un valor numérico fuera de rango: un valor demasiado grande para su destino tiene comportamiento indefinido. Usa entradas de prueba que quepan en el tipo elegido. Para calcular un resultado como la masa dividida por la altura al cuadrado, valida que las medidas sean positivas y mantén el cuadrado agrupado como height_m * height_m.
Puntos clave
- Pasa las direcciones de las variables y usa
%lfpara leer undouble. - Exige que el número devuelto coincida con el número de valores necesarios.
- Una conversión correcta puede dejar entrada adicional sin leer.
- Consume la entrada incorrecta antes de reintentar una conversión numérica fallida.
- Comprueba los valores permitidos después de leer; la conversión de entrada tiene sus propios límites de rango del tipo.
- Usa
" %c"cuando una lectura de carácter deba omitir los caracteres en blanco.
4Realizar cálculos con operadores y expresiones
Una expresión combina valores, tipos y operadores. Sigue primero la división entera y las actualizaciones, y después las conversiones que ocurren en cada operación. Las comparaciones y las condiciones lógicas de protección controlan si se llega a un cálculo, mientras que las operaciones a nivel de bits actúan sobre bits individuales. Por último, distingue la agrupación establecida por la precedencia de la secuenciación necesaria cuando una expresión modifica una variable. Tanto el valor como el tipo de un resultado intermedio importan.
4.1 Aritmética, asignación y actualizaciones de una unidad
Toda expresión en C tiene un valor y un tipo. El tipo determina cómo funciona una operación. La división entera, por ejemplo, no puede conservar un resultado fraccionario aunque después lo almacenes en una variable de coma flotante.
Cocientes y restos
+, - y * suman, restan y multiplican. / divide y % da un resto entero. La multiplicación, la división y el resto tienen mayor precedencia que la suma y la resta.
Cuando ambos operandos de una división son enteros, C trunca el cociente hacia cero. 17 / 5 es 3, -7 / 2 es −3 y 7 / -3 es −2. El resto cumple la identidad de la división: multiplicar el cociente por el divisor y sumar el resto da el dividendo. Así, -7 % 2 es −1 y 7 % -3 es 1. Un resto distinto de cero tiene el signo del dividendo.
Para comprobar si un número es par, usa n % 2 == 0. Para comprobar si es impar, tanto para enteros positivos como negativos, usa n % 2 != 0; comprobar si es exactamente 1 deja fuera los valores impares negativos. % necesita operandos enteros; <math.h> proporciona fmod para restos de coma flotante. Un divisor entero igual a cero hace que tanto / como % sean indefinidos, por lo que debes comprobarlo antes de la operación.
Para 135 minutos, 135 / 60 da dos horas completas y 135 % 60 da 15 minutos restantes. Para un entero decimal positivo, % 10 extrae su última cifra y / 10 elimina esa cifra: 4096 da 6 y 409.
Conserva las fracciones cuando las necesites
int correct = 45, total = 60;
double percent = 100.0 * correct / total;
La constante de coma flotante hace que la multiplicación sea de coma flotante, y la división da 75,0. En correct / total * 100, la primera operación es una división entera y da cero. Multiplicar ese cero no puede recuperar la fracción perdida. Multiplicar primero con enteros, correct * 100 / total, da aquí 75, siempre que el producto quepa en el tipo.
Almacena un nuevo valor
Una asignación evalúa su lado derecho y almacena el valor convertido en el lado izquierdo. La expresión de asignación tiene ese valor almacenado, por lo que a = b = 0; asigna cero a ambas variables, agrupando desde la derecha.
Las asignaciones compuestas combinan un cálculo y su almacenamiento: x += 5 suma cinco a x, y x /= 2 divide x entre dos usando su tipo actual. Todo el operando derecho forma parte del cálculo. Si x es 4, x *= 2 + 1 lo convierte en 12, porque lo multiplica por tres.
Actualizaciones prefijas y posfijas
++ suma uno y -- resta uno. En una línea por separado, i++; y ++i; tienen el mismo efecto final. Dentro de una expresión mayor, la forma posfija produce el valor anterior; la prefija produce el nuevo.
| Inicio | Sentencia | Valores finales |
|---|---|---|
a = 5 | y = a++; | y = 5, a = 6 |
a = 5 | y = ++a; | y = 6, a = 6 |
Mantén las modificaciones en un orden fácil de seguir. i = i++ + 1; y printf("%d %d\n", n, n++); tienen usos conflictivos no secuenciados de la misma variable y comportamiento indefinido. Separa la lectura, la actualización y la salida en sentencias cuando importe su orden.
Puntos clave
- La división entera trunca hacia cero.
- Un resto entero distinto de cero tiene el signo del dividendo.
- Usa un operando de coma flotante antes de la división si necesitas una fracción.
- La forma posfija produce el valor anterior; la prefija produce el valor actualizado.
- Mantén las lecturas y actualizaciones conflictivas en sentencias separadas.
4.2 Los tipos se determinan antes de almacenar un resultado
C convierte los valores a medida que se aplica cada operador. La variable de destino recibe el resultado final; no cambia las operaciones anteriores dentro de la expresión.
Convierte antes de dividir
int sum = 7, count = 2;
double a = sum / count;
double b = (double)sum / count;
double c = (double)(sum / count);
a pasa a valer 3,0: los dos operandos enteros producen el entero 3, que después se convierte. b pasa a valer 3,5: la conversión explícita transforma sum antes de la división, por lo que count también se convierte a double. c pasa a valer 3,0: su conversión explícita está fuera de la división y se realiza después de descartar la fracción.
La misma regla se aplica dentro de una expresión más larga. 7 / 2 * 2.0 da 6,0, porque el primer resultado es el entero 3. 7 / 2.0 * 2 da 7,0. Recorre los operadores en su orden de agrupación y marca el tipo de cada valor intermedio.
Encuentra un tipo aritmético común
Si alguno de los operandos es de coma flotante, la aritmética suele convertir al tipo de coma flotante más ancho que intervenga: long double, después double y después float. Para los enteros, los tipos pequeños pasan primero por una promoción de enteros. En plataformas con char de 8 bits, short de 16 bits e int de 32 bits, todos los valores de los tipos más pequeños caben en int, por lo que esos tipos se promocionan a int.
Con unsigned char a = 200, b = 100;, a + b es el entero 300 en un sistema con bytes de 8 bits e int de 32 bits. No se reduce modularmente en 255 durante la suma. Almacenar 300 en un carácter sin signo de 8 bits lo reduce a 44. La promoción también puede producir desbordamiento con signo: dos valores unsigned short de 50 000 se multiplican como int, pero 2 500 000 000 supera un INT_MAX de 32 bits.
Para los tipos con y sin signo, el rango del tipo y el rango de valores representables determinan el tipo común. int con unsigned int se convierte a un tipo sin signo. Un tipo con signo de rango superior puede mantener la operación con signo cuando representa todos los valores del tipo sin signo; en caso contrario, aún puede resultar una operación aritmética sin signo.
El problema de mezclar tipos con y sin signo
Con un entero sin signo de 32 bits, -1 < 0u es falso. El −1 con signo se convierte al valor sin signo 4 294 967 295 antes de la comparación. De igual modo, 2u - 10 da 4 294 967 288. Un resultado de sizeof también es sin signo, lo que importa al compararlo con un recuento que podría ser negativo. Activa las advertencias y mantén las cantidades relacionadas en tipos compatibles.
Las conversiones al almacenar pueden perder información
Una conversión de coma flotante a entero trunca hacia cero. Su parte entera debe caber en el tipo o el comportamiento es indefinido. Un entero convertido a un tipo sin signo de N bits se reduce módulo 2ᴺ; −1 se convierte en el valor máximo sin signo. En C17, una conversión entera fuera de rango a un tipo con signo tiene un resultado definido por la implementación o emite una señal definida por la implementación. Esa conversión es distinta del desbordamiento indefinido de la aritmética con signo.
Las constantes de carácter también participan en la aritmética. '7' - '0' da 7 porque C garantiza códigos consecutivos para los dígitos decimales. En ASCII, las letras también son consecutivas, pero no supongas esa propiedad para todos los juegos de caracteres. En las fórmulas con enteros, multiplicar antes de dividir solo puede conservar información útil si ese producto cabe en el tipo del cálculo.
Puntos clave
- El tipo de destino no cambia la aritmética anterior.
- Convierte explícitamente un operando antes de la operación que necesita un tipo de mayor rango o de coma flotante.
- Los enteros pequeños se promocionan antes de la aritmética.
- Mezclar operandos con y sin signo puede cambiar las comparaciones y las restas.
- La conversión fuera de rango y el desbordamiento aritmético con signo siguen reglas distintas.
- Sigue los tipos intermedios además de los valores finales.
4.3 Condiciones y patrones de bits
Una condición pregunta si algo es verdadero. Una operación a nivel de bits actúa sobre los bits individuales de un entero. C asigna operadores distintos a estas tareas, aunque sus símbolos se parezcan.
Compara valores y combina pruebas
Las comparaciones <, >, <=, >=, == y != producen un int: 1 para verdadero y 0 para falso. En una condición, cero es falso y cualquier valor distinto de cero es verdadero. Así, if (count) comprueba si count es distinto de cero. == compara dos valores; = asigna uno nuevo.
&& significa que ambas condiciones deben ser verdaderas, || significa que al menos una debe ser verdadera y ! invierte el valor de verdad. Devuelven 0 o 1. Un rango de 1 a 8 necesita dos comparaciones:
height >= 1 && height <= 8
1 <= height <= 8 no expresa ese rango en C. Se agrupa como (1 <= height) <= 8, por lo que la segunda comparación recibe 0 o 1 y siempre es verdadera. Para comprobar si está fuera del rango, escribe height < 1 || height > 8.
Negar una condición compuesta cambia ambas partes: !(a && b) equivale a !a || !b, y !(a || b) a !a && !b. Estas son las leyes de De Morgan. Explican por qué estar fuera de un intervalo necesita un «o», aunque estar dentro necesite un «y».
Coloca una protección antes de la operación de riesgo
if (d != 0 && n / d > 2) {
printf("Above two\n");
}
&& evalúa primero su lado izquierdo. Si ese lado es falso, omite el derecho. || omite su lado derecho cuando el izquierdo es verdadero. Esta evaluación en cortocircuito permite que una condición de protección proteja una operación posterior. Invertir las dos partes anteriores haría la división antes de comprobar el divisor. Para enteros con signo, existe un problema de rango aparte cuando INT_MIN se divide por −1 en un sistema de complemento a dos; comprueba también esa pareja cuando sean posibles valores arbitrarios.
Una prueba de año bisiesto combina divisibilidad y excepciones: (y % 4 == 0 && y % 100 != 0) || y % 400 == 0. El año 2000 supera la prueba de 400; 1900 falla porque se aplica la excepción de los años de siglo.
Trabaja bit a bit
El operador a nivel de bits & conserva los bits activados en ambos operandos; | conserva los activados en cualquiera de ellos; ^ conserva los bits que difieren; ~ invierte los bits. Con 12 (1100) y 10 (1010), los resultados son 8, 14 y 6 para &, | y ^.
Los desplazamientos mueven bits. Para valores no negativos cuyo resultado cabe en el tipo, x << k multiplica por 2ᵏ y x >> k descarta k bits de menor peso, dividiendo por 2ᵏ y descartando el resto. Usa tipos sin signo para trabajar con bits. En C17, desplazar una cantidad negativa o al menos igual al ancho del tipo promocionado es indefinido; desplazar a la izquierda un valor negativo con signo es indefinido y desplazarlo a la derecha está definido por la implementación.
Las posiciones de los bits empiezan en cero. flags | (1u << 3) activa el bit 3; flags & ~(1u << 3) lo desactiva; (flags >> 3) & 1u lo lee. 1 & 2 es 0 porque no hay ningún bit activado en común, mientras que 1 && 2 es 1 porque ambos valores son distintos de cero. Los operadores a nivel de bits evalúan ambos operandos y no pueden sustituir a las condiciones lógicas de protección.
Puntos clave
- Las comparaciones y los operadores lógicos producen 0 o 1.
- Una prueba de rango une dos comparaciones con
&&. - Las condiciones de protección en cortocircuito deben ir antes de la operación protegida.
- Usa operadores lógicos para valores de verdad y operadores a nivel de bits sin signo para máscaras.
- Las posiciones de los bits empiezan en cero; las cantidades de desplazamiento deben estar dentro del ancho del tipo promocionado.
4.4 Agrupar expresiones y ordenar efectos
Dos preguntas ayudan a leer una expresión: ¿qué operandos pertenecen a cada operador y cuándo ocurren las operaciones con efectos secundarios? La precedencia y la asociatividad responden a la pregunta sobre la agrupación. En general, no fijan el orden de evaluación de los operandos.
Sigue la precedencia
Los operadores situados más arriba en esta tabla tienen mayor precedencia. Cada fila está por debajo de la anterior; los operadores separados por «después» tienen niveles distintos.
| De mayor a menor | Operadores |
|---|---|
| Posfijos | x++, x--, llamadas |
| Unarios | ++x, --x, +, -, !, ~, conversiones explícitas, sizeof, obtención de dirección & |
| Multiplicativos | *, /, % |
| Aditivos | +, - |
| Desplazamiento | <<, >> |
| Relacionales | <, <=, >, >= |
| Igualdad | ==, != |
| A nivel de bits | &, después ^, después ` |
| Lógicos | &&, después ` |
| Condicional | ?: |
| Asignación | =, += y otras asignaciones compuestas |
| Coma | , |
Los paréntesis modifican la agrupación. 2 + 3 * 4 es 14; (2 + 3) * 4 es 20.
Usa la asociatividad en niveles iguales
La mayoría de los operadores aritméticos binarios se agrupan de izquierda a derecha. 10 - 4 - 3 significa (10 - 4) - 3, que es 3. 100 / 10 / 2 da 5. La asignación se agrupa de derecha a izquierda, por lo que a = b = 0 almacena cero en ambas variables. La agrupación describe la estructura de la expresión; no establece una regla general de ejecución de izquierda a derecha.
Para int a = 3, b = 4, c = 5;, sigue la ejecución de a + b * c > 20 && c % 2 == 1. La multiplicación da 20, la suma da 23 y 23 > 20 da 1. El resto da 1 y su prueba de igualdad da 1. El 1 && 1 final es 1.
Protege con paréntesis las pruebas de bits y las asignaciones
x & 1 == 0 significa x & (1 == 0), por lo que siempre produce cero. Escribe (x & 1) == 0 para comprobar el bit de menor peso. La suma también tiene mayor precedencia que el desplazamiento: 1 << 2 + 1 es 1 << 3, u 8.
La asignación tiene menor precedencia que la comparación. a = b == c almacena el 0 o 1 de la comparación en a. En while ((ch = getchar()) != '\n'), los paréntesis interiores hacen que la asignación reciba primero el carácter. Sin ellos, ch recibiría un resultado de comparación.
Elige un valor o secuencia dos acciones
condition ? first : second evalúa la condición y después solo la expresión del valor seleccionado. max = (a > b) ? a : b; elige un valor; un if puede elegir un bloque de acciones. El operador coma evalúa primero su expresión izquierda y produce el valor de su expresión derecha. Puede coordinar dos actualizaciones en una cabecera de for. Las comas entre argumentos de funciones son separadores y no establecen ese orden.
Mantén separados los efectos secundarios
C deja sin especificar muchos órdenes de operandos y argumentos. n++ * n++ tiene cambios no secuenciados en la misma variable, por lo que tiene comportamiento indefinido. Los paréntesis adicionales no corrigen la falta de secuenciación. Usa sentencias separadas cuando una acción deba preceder a otra. &&, ||, ?: y el operador coma proporcionan reglas específicas de secuenciación; la precedencia aritmética ordinaria no ofrece esa garantía.
Puntos clave
- La precedencia determina la agrupación; en general no determina el orden de evaluación.
- En un mismo nivel aritmético, la agrupación suele ser de izquierda a derecha.
- Pon entre paréntesis un resultado de una operación a nivel de bits antes de compararlo.
- El operador condicional evalúa solo la expresión del valor seleccionado.
- El operador coma secuencia operandos; las comas que separan argumentos no lo hacen.
- Separa las actualizaciones conflictivas para evitar comportamiento indefinido.
5Controlar el flujo del programa con condiciones y bucles
El control de flujo determina qué sentencias se ejecutan y con qué frecuencia. Usa condiciones para seleccionar una rama, casos enteros exactos para seleccionar una entrada de switch y bucles para repetir una tarea hasta que se cumpla su regla de parada. Las trazas hacen visible el orden: valores iniciales, pruebas, sentencias del cuerpo y actualizaciones. Presta especial atención a las entradas límite y al destino de break o continue dentro de construcciones anidadas, donde un pequeño cambio de sintaxis puede alterar todo el recorrido.
5.1 Elegir la rama adecuada
Una sentencia if elige si se ejecuta una acción. Su condición debe estar entre paréntesis. Cero significa falso; cualquier valor distinto de cero significa verdadero.
Usa llaves para mostrar la rama
if (temperature < 0) {
printf("Freezing\n");
} else {
printf("At or above freezing\n");
}
El primer bloque se ejecuta para una temperatura negativa. Exactamente en cero, la comparación es falsa y se ejecuta el bloque else. Lee el límite directamente a partir de <, <=, > o >=; el mensaje debe coincidir con ese límite.
Sin llaves, solo se controla la siguiente sentencia:
if (temperature > 30)
printf("Hot\n");
printf("Drink water\n");
El segundo mensaje se ejecuta a cualquier temperatura. La sangría ayuda a las personas, pero no cambia la gramática. Las llaves permiten añadir una segunda sentencia a una rama sin dejarla fuera por accidente.
Ordena una cadena else-if
Una cadena if–else if–else ejecuta la primera rama cuya condición es verdadera y después omite el resto. Para intervalos de puntuación, empieza por el límite inferior más alto:
if (score >= 90) {
grade = 'A';
} else if (score >= 80) {
grade = 'B';
} else if (score >= 70) {
grade = 'C';
} else {
grade = 'F';
}
Una puntuación de 85 no supera la primera prueba y supera la segunda. Una puntuación de 90 supera la primera. Si comprobaras primero score >= 70, 85 entraría en esa rama y nunca llegaría a la rama B deseada. Las sentencias if separadas se comportan de otra manera: cada condición se comprueba de forma independiente, por lo que pueden ejecutarse varios bloques.
Vigila dos pequeños errores de sintaxis
if (x = 5) asigna 5 y después trata ese valor distinto de cero como verdadero. Usa if (x == 5) para comparar. Con if (x = 0), la asignación sigue ocurriendo, pero la condición es falsa. Las advertencias ayudan a revelar asignaciones accidentales en las condiciones.
En if (x > 0);, el punto y coma es una sentencia controlada vacía. Un bloque colocado después se ejecuta de forma independiente, incluso cuando x es negativo. No añadas un punto y coma entre una condición if y el bloque que debe controlar.
Asocia cada else explícitamente
Un else se asocia al if anterior más cercano que aún no tenga un else. En decisiones anidadas, usa llaves para dejar clara la asociación deseada. La anidación es útil cuando las pruebas fallidas necesitan mensajes distintos: comprueba primero si una medida es válida y después si su valor está en el intervalo deseado. Combinar ambas con && funciona cuando solo se necesita una acción compartida.
Coloca las excepciones antes de las reglas generales
Los años bisiestos proporcionan una decisión ordenada útil. Comprueba primero la divisibilidad por 400, que significa año bisiesto. Después comprueba la divisibilidad por 100, que significa que no es bisiesto. Luego comprueba la divisibilidad por 4; en caso contrario, el año no es bisiesto. Para 2000 se aplica la primera rama. Para 1900 se aplica la segunda. Comprobar solo la divisibilidad por cuatro dejaría fuera la excepción de los años de siglo. El orden importa porque las reglas generales pueden incluir casos con una excepción más específica.
Puntos clave
- Una cadena else-if ejecuta solo su primera rama verdadera.
- Las llaves controlan qué sentencias pertenecen a una rama.
==compara;=asigna.- Un punto y coma después de una condición
ifcrea una rama vacía.
5.2 Seleccionar casos fijos con switch
Una sentencia switch elige un punto de entrada mediante un valor entero. Es útil para un carácter de comando o un número de menú con varias alternativas fijas. La ejecución puede continuar después hacia los casos siguientes, salvo que la detengas.
Selecciona un caso y sal de él
switch (choice) {
case 1:
printf("Start\n");
break;
case 2:
printf("Pause\n");
break;
default:
printf("Unknown choice\n");
break;
}
Con choice igual a 2, el programa empieza en case 2, muestra Pause y sale del switch al llegar a break. Si ningún caso coincide, la ejecución empieza en default. Sin un default, un valor sin coincidencia omite el switch.
La expresión de control debe tener un tipo entero, incluido char o una enumeración. Cada caso debe ser una expresión constante entera distinta, como 3, 'q' o un nombre adecuado definido con #define. En C17, una variable const int no es una expresión constante entera válida para una etiqueta case. Los valores de coma flotante y las cadenas no pueden controlar un switch.
Comprende la continuación al siguiente caso
Las etiquetas case marcan puntos de entrada, no bloques separados que terminan automáticamente. Si eliminas el primer break anterior, una opción de 1 muestra tanto Start como Pause. El programa no comprueba si choice es 2 antes de continuar por ese caso. La ejecución se detiene solo ante una transferencia de control o al final del switch.
Varias etiquetas pueden compartir una acción:
case 'y':
case 'Y':
printf("Yes\n");
break;
Ambos caracteres conducen a la misma acción. Cuando un caso realiza una tarea y continúa deliberadamente en otro, explica la intención con un comentario. C23 también ofrece [[fallthrough]];. Las advertencias pueden ayudar a detectar un break ausente, pero agrupar etiquetas deliberadamente no requiere repetir la acción.
Da un bloque a las variables locales de un caso
En C17, una declaración no puede seguir directamente a una etiqueta case. Usa llaves alrededor de las sentencias de ese caso:
case 3: {
int delay = 5;
printf("%d\n", delay);
break;
}
El bloque también mantiene delay como variable local de ese caso. C23 flexibiliza la restricción sobre las etiquetas anteriores a declaraciones, pero las llaves siguen dejando claro el ámbito.
Elige entre switch e if
Usa switch para alternativas enteras exactas. Usa if–else if para desigualdades, condiciones que impliquen varias variables o medidas de coma flotante. Los intervalos de puntuación de igual ancho pueden convertirse a veces en casos exactos mediante división entera. Con puntuaciones de 0 a 100, score / 10 da 8 para 85; los casos 9 y 10 pueden compartir el intervalo superior. Valida ese dominio para que las puntuaciones negativas o excesivas no reciban silenciosamente un intervalo.
Un switch dentro de un bucle
Un break sale del switch o bucle que lo contiene más cercano. Dentro de un switch que a su vez está dentro de un bucle, break termina solo el switch. Para terminar el bucle de menú, establece un indicador que compruebe su condición o vuelve desde main cuando el programa deba terminar. Un continue en ese switch se aplica al bucle que lo contiene y lo lleva a su siguiente iteración. Sigue el destino de cada transferencia antes de decidir si el menú se detiene.
Puntos clave
- Un switch selecciona una etiqueta de entrada mediante un valor entero.
- La ejecución continúa por los casos hasta
break, otra transferencia de control o el final. - Los valores de los casos deben ser expresiones constantes enteras distintas.
- Usa un bloque para una declaración local de un caso en C17.
- El
breakde un switch no termina también el bucle que lo contiene.
5.3 Repetir hasta que los datos indiquen que hay que parar
Un bucle while comprueba una condición antes de cada pasada. Un bucle do–while la comprueba después del cuerpo, por lo que se ejecuta al menos una vez. Elige la forma que coloque la prueba donde el programa la necesita.
Sigue un cálculo controlado por una condición
int n = 4096, sum = 0;
while (n > 0) {
sum += n % 10;
n /= 10;
}
n % 10 extrae una cifra y n /= 10 la elimina. Los valores después de las pasadas son:
| Pasada | sum | n |
|---|---|---|
| 1 | 6 | 409 |
| 2 | 15 | 40 |
| 3 | 15 | 4 |
| 4 | 19 | 0 |
La siguiente condición es falsa, por lo que el bucle termina con una suma de cifras de 19. Si n empieza en cero, el cuerpo se ejecuta cero veces y la suma sigue siendo cero. Las entradas negativas también omiten este bucle, por lo que un programa que las acepte necesita una regla definida.
Un bucle de conteo establece un contador, lo comprueba y lo actualiza. Otros bucles progresan leyendo datos o llegando a break. Al dividir repetidamente 100 por la mitad mediante división entera, los valores son 50, 25, 12, 6, 3 y 1: se necesitan seis pasadas para llegar a uno.
Deja que la entrada controle la repetición
int x, sum = 0, count = 0;
while (scanf("%d", &x) == 1 && x != 0) {
sum += x;
count++;
}
if (count != 0) {
printf("%.2f\n", (double)sum / count);
}
Para la entrada «5 8 3 0», el valor centinela cero termina el bucle y no se suma. La suma es 16, el recuento es 3 y la media mostrada es 5.33. La comprobación del número devuelto protege el uso de x; la prueba del recuento protege la división si el primer valor es cero. Este cálculo también requiere que las entradas y los totales quepan en int.
Sin la comprobación del centinela, while (scanf("%d", &x) == 1) se detiene ante una conversión numérica fallida o el fin de la entrada. En una terminal, Ctrl+D suele indicar el fin de la entrada en Linux y macOS; en Windows usa Ctrl+Z seguido de Intro.
Ejecuta una vez antes de comprobar
int height;
do {
printf("Height (1-8): ");
fflush(stdout);
if (scanf("%d", &height) != 1) {
return 1;
}
} while (height < 1 || height > 8);
El mensaje de solicitud y la lectura ocurren antes de la prueba de rango. Los valores cero y nueve hacen que se repita; los valores uno y ocho se aceptan. El punto y coma después de while (...) es obligatorio en esta forma. La comprobación de la conversión termina este ejemplo ante una entrada no numérica; la prueba de rango por sí sola no puede manejar una conversión fallida.
Progresa en cada recorrido que continúe
break sale inmediatamente del bucle más interno. continue omite el resto del cuerpo y vuelve a la condición en un bucle while. Si hay una actualización después de un continue, también se omite. El bucle puede entonces seguir comprobando el mismo valor indefinidamente.
Antes de ejecutar un bucle, identifica qué cambia y por qué se alcanzará finalmente la condición de parada. Un while (1) deliberado puede detenerse mediante break; olvidar actualizar un contador es una situación distinta. Ctrl+C detiene un programa bloqueado en una terminal. Prueba la entrada más pequeña y el valor del límite de parada para detectar errores de cero pasadas o de una pasada de más.
Puntos clave
whilecomprueba primero;do–whilecomprueba después de al menos una pasada.- Un valor centinela detiene la entrada sin formar parte de los datos.
- Inicializa los acumuladores fuera del bucle.
- Un recorrido que continúa debe progresar hacia la condición de parada.
- Un continue de
whilepuede omitir una actualización posterior. - Comprueba si hay cero datos antes de calcular una media.
5.4 Contar pasadas y combinar bucles
Un bucle for mantiene un patrón de conteo en una sola cabecera. Los bucles anidados repiten una tarea interior por cada pasada exterior. Ambos son más fáciles de comprobar cuando enumeras los valores reales del contador e identificas cada salida.
Sigue la cabecera en orden
for (int i = 0; i < 3; i++) {
printf("%d ", i);
}
La inicialización se ejecuta una vez. Después se comprueba la condición, el cuerpo se ejecuta si es verdadera y la actualización se ejecuta antes de la siguiente comprobación. La salida es 0 1 2 . La variable declarada en la cabecera es local al bucle; declárala antes del bucle si necesitas su valor final después.
Para n no negativo, i = 0; i < n; i++ se ejecuta n veces y visita desde cero hasta n − 1. Con i <= n, se ejecuta una vez más. Para un paso positivo s desde a hasta un b incluido, el número de pasadas es (b - a) / s + 1 mediante división entera, siempre que la aritmética y las actualizaciones del contador quepan en el tipo. De 2 a 20 en pasos de 3, los valores son 2, 5, 8, 11, 14, 17, 20: siete pasadas.
Mantén el acumulador fuera
int total = 0;
for (int i = 1; i <= 100; i++) {
total += i;
}
El total final es 5050. La inicialización debe ocurrir una vez antes del bucle. Mover int total = 0; al cuerpo crea un total nuevo en cada pasada y pierde el trabajo anterior.
Elige contadores que puedan llegar al final
Un punto y coma inmediatamente después de la cabecera de for deja el cuerpo vacío. Un bloque posterior se ejecuta entonces una vez después del bucle. Usa llaves directamente junto a la cabecera para que el cuerpo sea visible.
Una cuenta atrás sin signo con i >= 0 no puede detenerse de esa manera: los valores sin signo siempre son no negativos y al decrementar cero se reducen modularmente al máximo. Un contador de coma flotante que suma 0.1 repetidamente puede saltarse el valor exacto 1.0, por lo que x != 1.0 no es fiable como regla de parada. Cuenta con un entero y calcula una medida como x = i / 10.0 dentro del cuerpo.
Construye filas con bucles anidados
En cada pasada exterior, el bucle interior empieza y ejecuta su propia secuencia. Tres pasadas exteriores y cuatro pasadas interiores fijas ejecutan el cuerpo interior doce veces. Cuando el límite interior depende de la fila, cuenta cada fila por separado.
int height = 3;
for (int row = 1; row <= height; row++) {
for (int s = 0; s < height - row; s++) printf(" ");
for (int h = 0; h < row; h++) printf("#");
printf("\n");
}
Las filas contienen dos espacios y una almohadilla, un espacio y dos almohadillas, y después tres almohadillas. El salto de línea va después de los bucles interiores, por lo que cada pasada exterior crea una fila.
Omite trabajo o termina antes
continue en un bucle for va a la actualización y después a la siguiente comprobación. Un bucle que suma solo los valores impares de 1 a 9 puede omitir los valores pares y terminar con 25. break sale inmediatamente del bucle más interno. En bucles anidados, un break interior no detiene el bucle exterior.
Un bucle for suele ser la elección más clara para un rango conocido de un contador; while es adecuado para una repetición controlada por datos y do–while para tareas que deben realizarse antes de la comprobación. Reescribir un bucle for como un bucle while requiere especial cuidado con continue, porque la actualización debe seguir ocurriendo en ese recorrido.
Puntos clave
- Un bucle
forejecuta la inicialización una vez y la actualización después de cada pasada que continúa. - Comprueba los valores visitados para detectar errores de una unidad.
- Usa contadores enteros con suficiente rango.
- Los bucles anidados reinician la secuencia interior en cada pasada exterior.
- Un
breakinterior sale solo del bucle interior. - Un continue de
forsigue ejecutando la actualización de la cabecera. - Mantén la inicialización de un total acumulado fuera del cuerpo que se repite.
6Reconocer y prevenir el desbordamiento de enteros
Los rangos finitos de los enteros afectan a los cálculos incluso cuando la respuesta final parece lo bastante pequeña. Distingue la reducción modular de enteros sin signo, el desbordamiento con signo y los valores perdidos durante una conversión, y después examina los tipos de las operaciones intermedias. La prevención comienza antes de un cálculo de riesgo: convierte un operando a un tipo de mayor rango, demuestra los límites o usa una operación con comprobación de desbordamiento. Los rangos de los contadores y las pruebas de valores límite ayudan a encontrar fallos que las entradas pequeñas habituales y las pruebas breves pueden pasar por alto.
6.1 Qué ocurre cuando un entero no cabe en su tipo
El rango de un entero es finito. Una respuesta matemática puede ser válida y, aun así, demasiado grande para el tipo de C utilizado para calcularla. Las consecuencias dependen de si se trata de una operación sin signo, con signo o de una conversión.
La aritmética sin signo se reduce modularmente
Un resultado sin signo de N bits se reduce módulo 2ᴺ. Con un unsigned int de 32 bits, UINT_MAX + 1u da cero, 0u - 1u da 4 294 967 295 y 3u - 5u da 4 294 967 294. El resultado está definido, pero puede ser incorrecto para la tarea: restar cinco artículos de una existencia de tres no debería producir miles de millones de artículos.
Para uint8_t b = 255;, b = b + 1; almacena cero. En un sistema habitual con int de 32 bits, la suma ocurre primero como int y produce 256; convertir ese valor de nuevo a un tipo sin signo de 8 bits produce cero. Un almacenamiento pequeño sin signo no significa que cada operación intermedia tenga ese tipo pequeño.
El desbordamiento de la aritmética con signo es indefinido
Con un int de 32 bits, sumar uno a INT_MAX requiere 2 147 483 648. C no define el resultado aritmético con signo. El hardware puede parecer que se reduce modularmente a INT_MIN, pero el compilador puede optimizar suponiendo que el desbordamiento con signo nunca ocurre. Una comprobación como if (x + 1 < x) puede eliminarse para enteros con signo, porque toda suma definida de uno a un entero con signo da un valor mayor.
No calcules primero para después examinar el daño. Una operación indefinida ya ha ocurrido antes de que se ejecute una prueba posterior.
Un destino de mayor rango puede llegar demasiado tarde
int a = 50000, b = 50000;
long long product = a * b;
La multiplicación se realiza como int, por lo que se desborda en un sistema con int de 32 bits. El ancho del destino no puede repararlo. (long long)a * b cambia el tipo de la operación antes de la multiplicación y produce 2 500 000 000.
Los factoriales crecen rápidamente: 12! es 479 001 600 y cabe en un entero con signo de 32 bits; 13! es 6 227 020 800 y no cabe. Con aritmética sin signo de 32 bits, 13! se reduce modularmente a 1 932 053 504. Un long long con signo de 64 bits contiene 20! = 2 432 902 008 176 640 000, pero no 21!.
La conversión tiene reglas distintas
Convertir un entero a un tipo sin signo utiliza reducción modular. En C17, convertir un entero fuera de rango a un tipo con signo da un resultado o una señal definidos por la implementación. Convertir un valor de coma flotante cuya parte entera truncada no cabe en el destino entero tiene comportamiento indefinido. Leer un número fuera de rango mediante una conversión numérica de scanf también es indefinido.
Los fallos de rango pueden tener grandes efectos
El primer vuelo del Ariane 5, el 4 de junio de 1996, se perdió después de que un valor de coma flotante no pudiera caber en un entero con signo de 16 bits en su software de referencia inercial. Ese programa estaba escrito en Ada; la lección sobre comprobar los rangos de conversión también se aplica a C.
Un contador de segundos con signo de 32 bits que empieza en la época Unix alcanza 2 147 483 647 a las 03:14:07 UTC del 19 de enero de 2038. Los sistemas que usan esa representación del tiempo necesitan una de mayor rango. En 2015, la FAA también abordó un problema en el software de control de generadores del Boeing 787 que podía desbordarse después de 248 días de alimentación continua. Estos fallos muestran por qué tanto un tiempo de ejecución prolongado como los valores intermedios deben incluirse en una estimación de rango.
Los límites de la coma flotante son distintos: los sistemas IEEE habituales pueden producir infinito y un float puede perder precisión para números enteros antes de alcanzar su máximo. La reducción modular de enteros sin signo produce un valor numérico normal; el desbordamiento con signo no ofrece ningún resultado fiable.
Puntos clave
- La aritmética sin signo usa reducción modular.
- El desbordamiento de la aritmética con signo tiene comportamiento indefinido.
- Un destino de mayor rango no puede corregir una operación intermedia de menor rango.
- La conversión de enteros, la conversión de coma flotante y el desbordamiento aritmético tienen reglas distintas.
- Estima los valores durante todo el tiempo de funcionamiento.
6.2 Prevenir el desbordamiento antes de que ocurra
Empieza por el mayor valor que puede producir cada cálculo. Incluye los productos intermedios y las actualizaciones de los contadores, no solo la respuesta final. Después elige un tipo o una comprobación previa que mantenga cada operación dentro de rango.
Amplía el rango del cálculo desde el principio
Un entero con signo de 32 bits contiene aproximadamente 2,1 mil millones, mientras que un long long con signo de 64 bits contiene hasta 9 223 372 036 854 775 807. Un contador de milisegundos alcanza el límite de un entero con signo de 32 bits en aproximadamente 24,9 días. Usa un tipo de 64 bits cuando una duración pueda ser mayor.
Escribe (long long)a * b para ampliar el rango de un producto entero antes de evaluarlo. Las constantes también importan: 86400LL * days inicia la multiplicación en long long. (long long)(a * b) convierte solo después del producto de menor rango, por lo que no puede evitar su desbordamiento. Los tipos de mayor rango siguen teniendo límites; compáralos con los mayores valores posibles.
Comprueba la suma mediante una resta
Para int a e int b, incluye <limits.h> y comprueba:
if ((b > 0 && a > INT_MAX - b) ||
(b < 0 && a < INT_MIN - b)) {
printf("Sum out of range\n");
} else {
int sum = a + b;
printf("%d\n", sum);
}
Cada resta está protegida por el signo de b, lo que mantiene la propia comprobación dentro del rango representable. Para 2 000 000 000 más 200 000 000, INT_MAX - b es 1 947 483 647, menor que a, por lo que se rechaza la suma. El caso de igualdad cabe: cuando a es igual a INT_MAX - b, la suma es exactamente INT_MAX.
Comprueba una multiplicación positiva mediante división
Para operandos int positivos, a > INT_MAX / b significa que el producto superaría el máximo. La condición evita calcular el producto y evita un divisor cero porque b es positivo. En un cálculo de factoriales, la prueba antes de multiplicar 12! por 13 detecta que 479 001 600 es mayor que 165 191 049. Detente antes de esa multiplicación.
Para existencias sin signo, comprueba stock >= requested antes de restar. Cambiar solo a un tipo sin signo no evita un resultado sin sentido por reducción modular.
Reescribe expresiones intermedias de riesgo
El punto medio (low + high) / 2 puede desbordarse incluso cuando su respuesta cabe en el tipo. Cuando 0 <= low <= high y ambos son int, usa low + (high - low) / 2. La diferencia es no negativa y está dentro de rango, y el resultado nunca supera high. Estas condiciones importan; la fórmula no es una demostración general para extremos con signo arbitrarios.
Los contadores de bucle también necesitan rango. Un short de 16 bits no puede llegar a 40 000, por lo que un bucle que lo use para contar hasta ahí no puede terminar como se pretende. Usa un tipo de contador que pueda representar el límite y cualquier actualización final.
Usa aritmética con comprobación de desbordamiento y pruebas
C23 proporciona <stdckdint.h> con ckd_add, ckd_sub y ckd_mul. Por ejemplo, ckd_add(&r, a, b) almacena un resultado y devuelve verdadero cuando la respuesta matemática no cabe en r. GCC y clang también proporcionan __builtin_add_overflow, __builtin_sub_overflow y __builtin_mul_overflow como extensiones del compilador.
Las advertencias detectan algunos desbordamientos de constantes y errores al mezclar valores con y sin signo. En GCC o clang, -fsanitize=undefined -g puede informar del desbordamiento con signo alcanzado durante una prueba; -ftrapv solicita interrupciones ante desbordamientos de sumas, restas y multiplicaciones con signo. Prueba cero, uno, menos uno, la mayor entrada esperada y valores en los límites y justo por debajo de ellos. Una herramienta de prueba comprueba los recorridos que ejecutas, así que mantén el razonamiento sobre rangos en el código.
Puntos clave
- Convierte un operando a un tipo de mayor rango antes de una operación de riesgo.
- Usa comprobaciones previas que tengan en cuenta el signo para la suma con signo.
- La comprobación previa de multiplicación mediante división requiere operandos positivos.
- La resta sin signo sigue necesitando una comprobación de rango.
- La fórmula más segura del punto medio requiere extremos no negativos y ordenados.
- La aritmética con comprobación de desbordamiento indica si el resultado cabe en su destino.
- Las pruebas de valores límite y los sanitizadores complementan el razonamiento sobre rangos.
Ejemplos de Primeros programas en C
1Construir un saludo y diagnosticar una edición fallida
9 pasos
- 1IntroducciónSigue la construcción
Un programa en C guardado es texto. Para ejecutarlo se necesita un ejecutable producido a partir de ese texto. Ten claras las funciones de los dos archivos: una edición cambia el código fuente, mientras que una construcción correcta sustituye el ejecutable.
Construirás un saludo breve, examinarás qué hace cada etapa y diagnosticarás una edición que falla. El hábito central es comprobar el resultado de la construcción antes de usar una ejecución como prueba del nuevo código fuente.
- 2ExplicaciónLee la estructura del programa
El programa empieza en
main:#include <stdio.h> int main(void) { printf("Ready\n"); return 0; }La cabecera proporciona la declaración de
printf. El cuerpo muestra una línea y después devuelve un valor que indica que la ejecución fue correcta.voidsignifica aquí que main no tiene parámetros;intindica el tipo de su valor de retorno. Las llaves delimitan el cuerpo y las sentencias simples terminan con punto y coma. - 3EjemploConstrúyelo y ejecútalo
Guarda el código fuente como
ready.c. En su carpeta, introduce:gcc -std=c17 -Wall -Wextra ready.c -o ready ./readyLa construcción selecciona C17 y advertencias útiles, y después da al ejecutable el nombre
ready. La ejecución muestraReadyseguido de un salto de línea. En Linux o macOS,./selecciona la carpeta actual. Puedes usar clang en la misma posición del comando; los ejecutables de Windows usan el sufijo.exe. - 4ConexiónObserva dónde encaja el código objeto
El preprocesamiento procesa la directiva de inclusión. Después, la compilación produce lenguaje ensamblador y el ensamblado produce código objeto. El enlace resuelve referencias y completa el ejecutable; una biblioteca compartida puede proporcionar código de biblioteca en tiempo de ejecución.
Las opciones
-E,-Sy-cdetienen el proceso en etapas sucesivamente posteriores. En particular,-cse detiene en un archivo objeto: aún no ha completado la etapa de enlace necesaria para el ejecutable. - 5DesafíoPredice la siguiente ejecución
Construyes el saludo y después guardas una edición que cambia «Ready» por «Begin». Ejecutas el archivo ejecutable existente sin compilar. ¿Qué texto aparece y cómo lo cambias?
---reveal---
Sigue mostrando
Ready. El ejecutable refleja la construcción correcta anterior. Compila la edición guardada y ejecuta el nuevo ejecutable para mostrarBegin. Guardar solo el código fuente no actualiza el código máquina. - 6EjemploCorrige el primer error
Una edición deja esta línea sin punto y coma:
printf("Begin\n") return 0;El compilador puede señalar la primera línea o
return, donde detecta que falta el final de la sentencia de expresión. Añade el punto y coma después de la llamada y vuelve a construir. Empieza por esta primera causa aunque los mensajes posteriores mencionen otras líneas; un solo error de sintaxis puede alterar la forma en que el compilador lee el código siguiente. - 7ConsejoTrata las advertencias como indicios útiles
Una advertencia suele permitir que continúe la construcción, pero puede señalar un error grave.
printf("%d\n", 2.5);pasa un double donde el formato requiere int, lo que tiene comportamiento indefinido.Corrige la discrepancia de tipos en lugar de aceptar lo que muestre una ejecución de prueba. Las advertencias de
-Wall -Wextraayudan a revelar esos errores;-Werrorhace que detengan la construcción. Para un cálculo incorrecto, muestra los valores intermedios y compáralos con una pequeña traza hecha a mano. - 8DesafíoDistingue una construcción fallida de una ejecución incorrecta
Después de construir correctamente el saludo, una nueva edición no compila. Al ejecutar
./readysigue apareciendo un saludo. ¿Demuestra eso que la edición es válida y qué deberías revisar a continuación?---reveal---
No. El ejecutable anterior puede permanecer después de una construcción fallida. Revisa el primer error del compilador, incluida la línea anterior del código fuente, corrígelo y vuelve a construir correctamente. Después ejecuta ese nuevo archivo y compara su salida con tu predicción. El saludo existente no dice nada sobre la edición fallida.
- 9RepasoUsa un ciclo de corrección claro
Mantén cada prueba vinculada al código fuente que realmente se construyó. Guarda la edición, compila, lee el primer mensaje de diagnóstico, corrige su causa y vuelve a construir antes de ejecutar.
Si el programa se construye pero calcula un valor incorrecto, usa una pequeña traza y entradas límite. Si una operación tiene comportamiento indefinido, cambia el código para que no pueda ocurrir; una salida de apariencia normal no es un resultado fiable para esa operación.
2Elegir almacenamiento para recuentos y medidas
13 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónPregúntate qué necesita un valor
- 2EjemploUn valor copiado sigue siendo una copia
- 3ExplicaciónMantén los nombres dentro de sus bloques
- 4DesafíoSigue un nombre oculto
- 5ExplicaciónCompara los rangos antes de elegir
- 6EjemploLee un patrón de bits y un literal
- 7DesafíoElige un rango portable
- 8ExplicaciónObserva por qué se desvían las fracciones decimales
- 9EjemploCompara con un error permitido
- 10DesafíoDistingue la conversión del redondeo
- 11Idea claveLa precisión puede fallar antes que el rango
- 12DesafíoAjusta el tipo a la tarea
- 13RepasoComprueba el almacenamiento de dos maneras
3Leer medidas y mostrar resultados claros
11 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónConecta valores con tipo y texto
- 2ExplicaciónLee el formato de salida
- 3EjemploConstruye una presentación alineada
- 4DesafíoPredice un entero con relleno
- 5ExplicaciónLee en los destinos adecuados
- 6EjemploDetecta una conversión parcial
- 7ConsejoNo leas por accidente un salto de línea pendiente
- 8DesafíoSigue lo que conserva una lectura fallida
- 9EjemploComprueba el dominio del cálculo
- 10DesafíoCombina la lectura y la presentación
- 11RepasoUsa dos comprobaciones en la entrada
4Leer una expresión desde los tipos hasta los efectos
17 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónMarca tanto los valores como los tipos
- 2EjemploDivide una cantidad en partes enteras
- 3ExplicaciónDistingue los dos incrementos
- 4DesafíoSigue una actualización compuesta
- 5EjemploColoca el operando de coma flotante a tiempo
- 6Idea claveLos valores pequeños sin signo pueden calcularse como int con signo
- 7DesafíoSigue un valor con signo en una operación sin signo
- 8ExplicaciónUne comparaciones completas
- 9EjemploHaz que la protección se ejecute primero
- 10EjemploCambia un indicador sin alterar los demás
- 11DesafíoCompara los valores de verdad con los bits compartidos
- 12ExplicaciónAgrupa antes de seguir la ejecución
- 13ConsejoPon la máscara dentro de la comparación
- 14ConexiónElige un valor sin evaluar ambos
- 15Desafío¿Pueden los paréntesis corregir dos actualizaciones?
- 16DesafíoCombina la agrupación con una prueba de rango
- 17RepasoExamina una expresión en tres pasadas
5Seguir decisiones y construir un total acumulado
18 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónSigue el recorrido que realmente se toma
- 2ExplicaciónUsa la primera rama verdadera
- 3ConsejoTanto las llaves como la igualdad importan
- 4DesafíoEncuentra el primer intervalo coincidente
- 5ExplicaciónSelecciona una entrada de comando
- 6EjemploComparte una acción de forma deliberada
- 7DesafíoEncuentra el destino de break
- 8ExplicaciónLee antes de usar la entrada
- 9EjemploSigue el cálculo con valor centinela
- 10DesafíoManeja una parada inmediata
- 11ConexiónPregunta una vez con do-while
- 12ExplicaciónCuenta exactamente las visitas previstas
- 13EjemploConserva un total entre pasadas
- 14ConsejoEvita contadores que no alcancen la salida
- 15EjemploCuenta las filas por separado
- 16DesafíoSigue una salida anticipada en un bucle anidado
- 17DesafíoCombina un indicador y un bucle de menú
- 18RepasoComprueba cada recorrido hacia la siguiente pasada
6Asegurar un producto antes de calcularlo
12 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónExamina la aritmética antes del almacenamiento
- 2EjemploEncuentra la operación de menor rango
- 3ExplicaciónUn tipo sin signo tiene un resultado definido, pero quizá incorrecto
- 4Desafío¿Corrige el producto una conversión explícita tardía?
- 5EjemploAmplía el rango de la propia operación
- 6ExplicaciónComprueba una suma sin sumar
- 7EjemploComprueba un producto positivo mediante división
- 8DesafíoComprueba el límite correctamente
- 9Idea claveUna respuesta segura puede ocultar una suma insegura
- 10ConsejoUsa herramientas y prueba los límites
- 11DesafíoComprueba un diseño completo de rangos
- 12RepasoHaz la comprobación antes de la operación
Ejercicios de Primeros programas en C
Set 1: Construir programas y seguir los valores almacenados10 Preguntas de Práctica
Pregunta 1 de 10
Mostrar respuestaOcultar respuesta
Pregunta 2 de 10
Mostrar respuestaOcultar respuesta
Pregunta 3 de 10
¿Qué valores muestra este código?
int units = 6;
int total = units * 4;
units = 9;
printf("%d %d\n", units, total);
Mostrar respuestaOcultar respuesta
Pregunta 4 de 10
Mostrar respuestaOcultar respuesta
Pregunta 5 de 10
014 de C17?Mostrar respuestaOcultar respuesta
Pregunta 6 de 10
Mostrar respuestaOcultar respuesta
Pregunta 7 de 10
printf("|%04d|", 23); entre las barras verticales?Mostrar respuestaOcultar respuesta
Pregunta 8 de 10
int a = 2, b = 9;, una llamada a scanf("%d:%d", &a, &b) recibe «17 42». ¿Qué valor devuelve y qué valores quedan almacenados?Mostrar respuestaOcultar respuesta
Pregunta 9 de 10
En un sistema con unsigned char de 8 bits e int de 32 bits, ¿qué muestra esto?
unsigned char a = 230, b = 40;
int sum = a + b;
unsigned char small = sum;
printf("%d %u\n", sum, (unsigned)small);
Mostrar respuestaOcultar respuesta
Pregunta 10 de 10
gcc -std=c17 -c sensor.c se ejecuta correctamente. ¿Qué produjo y qué etapa de construcción sigue siendo necesaria antes de una ejecución normal?Mostrar respuestaOcultar respuesta
Set 2: Leer entradas y calcular con tipos de C10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
double distance_m;. ¿Qué conversión y argumento de scanf coinciden con su tipo?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
-23 / 6 y -23 % 6 en C17?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
int distance = 19, time = 4;, ¿qué expresión calcula una velocidad double que conserva la parte fraccionaria?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
%d acepta la entrada «46kg». ¿Qué valor se almacena y qué entrada queda sin leer inmediatamente después de la conversión?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
total y count, ¿qué condición comprueba que la media entera sea mayor que 5 sin dividir por cero?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
2 + 5 * 3 % 7?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
printf("|%6.1f|", 8.375); con el redondeo habitual a la décima más cercana?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
¿Qué valores se muestran?
int n = 8;
int a = n++;
int b = ++n;
a *= b % 4;
printf("%d %d %d\n", a, b, n);
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
Con int y unsigned int de 32 bits, ¿qué muestra esto?
int delta = -3;
unsigned int limit = 2;
printf("%d %u\n", delta < limit, limit + delta);
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
flags = 10u. ¿Qué valor resulta al activar el bit 2, contando el bit de menor peso como bit 0?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Set 3: Seguir ramas, sentencias switch y bucles10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
Una puntuación es 76. ¿Qué carácter se asigna?
if (score >= 70) grade = 'C';
else if (score >= 85) grade = 'B';
else grade = 'F';
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
¿Qué se muestra cuando mode es 2?
switch (mode) {
case 1: printf("A"); break;
case 2: printf("B");
case 3: printf("C"); break;
default: printf("D");
}
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
¿Qué valores finales tienen n y passes?
int n = 45, passes = 0;
while (n > 2) {
n /= 2;
passes++;
}
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
for (int i = 4; i < 19; i += 4)?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
¿Qué se muestra?
int level = 2;
if (level = 0) {
printf("Low");
} else {
printf("Other");
}
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
¿Qué total se muestra?
int total = 0;
for (int r = 1; r <= 4; r++) {
for (int c = 1; c <= r; c++) {
if (c == 3) break;
total += c;
}
}
printf("%d\n", total);
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
¿Qué suma se obtiene?
int sum = 0;
for (int i = 2; i <= 8; i++) {
if (i % 3 == 0) continue;
sum += i;
}
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
¿Cuántas veces se muestra «tick»?
int running = 1, passes = 0;
while (running && passes < 3) {
switch (passes) {
case 1: break;
default: printf("tick\n");
}
passes++;
}
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
La entrada es «9 4 0 7». ¿Qué recuento y media se muestran?
int x, sum = 0, count = 0;
while (scanf("%d", &x) == 1 && x != 0) {
sum += x;
count++;
}
if (count) printf("%d %.1f\n", count, (double)sum / count);
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Set 4: Diagnosticar conversiones y fallos de rango10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
0u - 1u cuando unsigned int tiene 32 bits de valor?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
a = 60000 y b = 40000, ¿qué expresión calcula de forma segura su producto exacto en un long long típico de 64 bits?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
flags = 6u sin signo, ¿qué resultado produce flags & 2u == 0u?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
a + b?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
f * i superaría INT_MAX antes de la multiplicación?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
-fsanitize=undefined. ¿Qué conclusión debería orientar la corrección?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Set 5: Combinar entrada, control de flujo y aritmética segura10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
¿Qué valor se muestra?
int total = 0;
for (int r = 2; r <= 5; r++) {
for (int c = 1; c < r; c++) {
if (c % 2 == 0) continue;
total += r;
}
}
printf("%d\n", total);
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
days vale 30 000. ¿Qué expresión calcula correctamente su duración en milisegundos en un long long de 64 bits?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
¿Qué garantiza este fragmento de C17 sobre su salida?
int n = 4;
printf("%d %d\n", n++, n++);
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
¿Qué se muestra cuando a = -2 y b = 7?
if (a > 0)
if (b > 0) printf("Both");
else printf("A only");
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
¿Por qué este bucle deja de progresar después de mostrar cero una vez?
int i = 0;
while (i < 4) {
if (i == 1) continue;
printf("%d\n", i);
i++;
}
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
Con unsigned int de 32 bits, ¿qué valor deja este código en flags?
unsigned int flags = 13u;
flags &= ~(1u << 2);
flags |= 1u << 1;
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
Un menú empieza con running = 1 y actions = 0. La entrada es «s q s». ¿Cuánto vale actions después de este bucle?
char cmd;
while (running && scanf(" %c", &cmd) == 1) {
switch (cmd) {
case 'q': running = 0; break;
case 's': actions++; break;
}
}
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
¿Qué valor se asigna a rate?
int completed = 14, planned = 20;
double rate = completed / planned * 100.0;
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
¿Te atascas con una pregunta tuya? Ayuda con Tareas
Términos Clave
A
Acumulador
Advertencia
%d se combinó con un argumento double, una combinación no válida.Almacenamiento de salida en búfer
fflush(stdout) lo envíe antes de una llamada de entrada.Ancho de campo
%6d añade cuatro espacios antes de 42, mientras que un valor de siete cifras sigue mostrándose completo.Aritmética con comprobación de desbordamiento
ckd_mul de C23 puede informar de un producto que no cabe en su destino.Asignación
count = count + 1; lee el valor anterior y lo sustituye por un valor una unidad mayor.Asignación compuesta
total += item; actualiza el total acumulado sin cambiar item.Asociatividad
24 / 4 / 2 como (24 / 4) / 2, lo que da tres.Á
Ámbito de bloque
B
Bloque de sentencias
Bucle anidado
Bucle do-while
Bucle for
i < n e i++ hace n pasadas para n no negativo cuando las actualizaciones caben en el tipo.Bucle infinito
Bucle while (ciclo while)
C
Cadena de formato
printf("Mass: %.1f kg\n", mass) proporciona una etiqueta, una precisión decimal y una unidad.Caracteres en blanco
%c lee un salto de línea pendiente salvo que se le indique que lo omita.Código fuente
Compilador
Complemento a dos
10000000 representa menos 128.Comportamiento definido por la implementación
Comportamiento indefinido
Comprobación de rango
Condición
if (items) es falsa para cero, mientras que un recuento negativo distinto de cero hace que se tome la rama.Constante de carácter
'A' puede mostrarse como la letra A con %c o como el número 65 con %d.Continuación al siguiente caso (fallthrough)
Conversión a un tipo de mayor rango
a * b cambia el tipo del producto; una conversión explícita después del producto llega demasiado tarde.Conversión explícita de tipo (cast)
(double)sum / count se aplica antes de la división y conserva un cociente fraccionario.Conversión fuera de rango
Conversión implícita
Conversiones aritméticas usuales
D
Declaración
int count = 0; da al nombre un tipo entero y también proporciona su valor inicial.Desbordamiento de enteros
Desplazamiento de bits
División entera
22 / 5 da cuatro incluso cuando el resultado final se asigna a un double.E
Efecto secundario
Ejecutable
./hello inicia el ejecutable de la carpeta actual sin volver a invocar al compilador de C.Else ambiguo (dangling else)
Enlazador (linker)
printf; su código de biblioteca puede proporcionarse mediante una biblioteca compartida.Entero con signo
Entero sin signo
Entrada estándar
Error de compilación
Error de redondeo
Error de una unidad (off-by-one)
i < n por i <= n en un bucle de conteo que empieza en cero causa un error de una unidad.Error lógico
Especificación de conversión
%lld corresponde a un argumento long long; %d no puede convertirlo automáticamente.Estado de salida
Etiqueta case
case 4: es válida en C17; una variable const int ordinaria no puede proporcionar su valor.Etiqueta default
Evaluación en cortocircuito
count != 0 && sum / count > 4 omite la división cuando count es cero.F
Fallo de coincidencia
%d causan un fallo de coincidencia y permanecen sin leer para un nuevo intento.Fin de la entrada
I
Identificador
distance_m es válido; 2distance no puede ser un nombre porque empieza por un dígito.Incremento posfijo
n = 3, el incremento posfijo de old = n++; da a old el valor tres y deja n en cuatro.Inicialización
Iteración
L
Literal entero
0x30 usa hexadecimal, mientras que 48u escribe ese valor en decimal sin signo.M
Macro
#define LIMIT 8, el token LIMIT se sustituye por 8 allí donde se aplica esa macro.Máscara de bits
1u << 5 selecciona el bit cinco, contando el bit menos significativo como posición cero.N
NaN (no es un número)
Número de asignaciones
O
Operador a nivel de bits
& conserva solo los bits activados en común de doce y diez, dando ocho.Operador coma
i++, j--; las comas entre argumentos de printf no lo hacen.Operador condicional
condition ? a : b para elegir una expresión de valor después de comprobar la condición.condition ? a : b evalúa b y omite a.Operador lógico
&&, || o !.ready && valid comprueba si ambos valores almacenados son distintos de cero.Operador relacional
<, <=, > o >= y produce un int cero o uno.height >= 1 comprueba un límite; otra comparación comprueba el otro.Operando
sum / count, los tipos de los operandos determinan si la división conserva un resultado fraccionario.Orden de evaluación
P
Precedencia
4 * 2 antes de la suma en 3 + 4 * 2.Precisión
Preprocesador
#include y la sustitución de macros antes de las etapas principales de traducción de C.Promoción de enteros
Prueba de valores límite
R
Rama
Reducción modular de enteros sin signo
Resto (residuo)
% de C; si es distinto de cero, tiene el signo del dividendo.-22 % 5 es −2 porque el cociente truncado es −4 y debe cumplirse la identidad de la división.Resultado intermedio
(low + high) / 2 es un resultado intermedio que puede desbordarse incluso cuando el punto medio cabe en el tipo.S
Sanitizador (sanitizer)
Secuencia de escape
\n añade un salto de línea dentro de una cadena de C; printf usa %% para un signo de porcentaje.Sentencia break
Sentencia continue
Sentencia switch
T
Tipo entero de ancho exacto
uint8_t, cuando está disponible, contiene valores de cero a 255.Tolerancia
fabs(measured - target) con la tolerancia en lugar de exigir igualdad exacta.Traza de ejecución
Truncamiento hacia cero
V
Valor booleano
<stdbool.h> en C17, bool ready = 2; almacena el valor booleano verdadero.Valor centinela
Valor de coma flotante (punto flotante)
Variable
mass_kg almacena una medida mientras count registra cuántas muestras se introdujeron.Sobre esta unidad
Crea tus propios Materiales de Estudio
Convierte tus notas, diapositivas y documentos en Guías de Estudio, Preguntas de Práctica, Flashcards y 6 herramientas más.