Programación en C/Texto completo
Prólogo
Prólogo
El avance de la tecnología y la concurrente entrega de información, nos permite generar una edición, variable y alternativa en la enseñanza del lenguaje de programación que más impacto social ha tenido en la historia de la informática. Este libro ha sido forjado con la incansable ayuda de informáticos de habla hispana, quienes byte a byte han colaborado por hacer de la información una fuente de conocimiento global. De la misma forma, continúa hoy siendo modificado en una constante búsqueda de superación de la calidad.
Esta obra está llamada a ser la piedra angular en la enseñanza de la programación, logrando abarcar todos los aspectos del lenguaje en diversos niveles y de esta forma ser tanto una referencia técnica para quienes ya dominan el lenguaje como una introducción sencilla para quienes están empezando a conocerlo.
Viajaremos por la historia del lenguaje, veremos su propósito e indagaremos en la ciencia de la programación. El fin es otorgar al lector una doctrina clara de la programación y el lenguaje C; lo induciremos a conseguir un manejo importante del lenguaje.
¿Para quién es este libro?
Este libro está dirigido a todos los que deseen obtener conocimientos de programación, pues el objetivo explícito que nos ha motivado a crearlo es difundir la importancia del lenguaje C en el mundo de la informática. Si nos enfocamos a un grupo social específico, podremos indicar que este libro contribuirá con los estudiantes de carreras del área informática, debido a que los temas convenidos, son parte de su plan de estudios.
¿Por qué otro manual de C?
Porque el lenguaje C es la base fundamental de la programación. Para quienes están en el ambiente de la informática es crucial tener por lo menos nociones de este lenguaje. Varios sistemas operativos, cientos de bibliotecas, y miles de programas están construidos utilizando C, al conocerlo es posible entender, colaborar y desarrollar en este lenguaje.
Los sistemas, programas, juegos y herramientas que nosotros disfrutamos hoy fueron construidos por personas como nosotros, que empezaron con nuestro mismo potencial y fueron aprendiendo a hacer cosas fantásticas con las herramientas que tenían a mano.
Una razón importante para otro libro de lenguaje C es también que los libros existentes muestran muy poca documentación de calidad. En Internet existe una cantidad inmensa de información publicada pero está dispersa, y mal manejada en algunos sitios. Es la intención de este libro crear un buen compendio de información, que permita a los interesados aprender a programar en C.
Enlaces
Nociones básicas de programación (generales, independientes del lenguaje):
Wikilibro similar a éste en inglés
esquema de operadores y expresiones
Licencia y autores
- Copyright © 2004 Envite
- Copyright © 2005 Alejandro Moreno Calvo
- Copyright © 2006 Andreu Correa Casablanca
- Copyright © 2009 zerohours
Ver el historial de cada página para el resto de autores.
¿Cómo contribuir a este WikiLibro?
Contribuir con este libro es muy simple primero deberías registrarte un usuario y/o entrar (esto no es necesario pero si muy conveniente) a Wikilibros, para que podamos identificar tus ediciones. Luego, si nunca has contribuido en un proyecto de wikipedia o en otro wiki deberías leer el manual de uso de wikilibros.
Una vez hecho esto todo lo que tienes que hacer es agregar el contenido que consideres necesario para el libro. Para editar cualquier sección basta con hacer click al link que dice editar en la pestaña en la parte superior de la pagina, seria bueno revisar (antes de editar cualquier cosa) la pestaña de discusión que está ahí para ser usada. Ten en cuenta que el material que ya está fue escrito por personas que deseaban contribuir igual que tú, así que trata de respetarlo. Aunque con esto no me refiero a que si se necesita alguna corrección, reorganización, quitar partes que sean ambiguas, no dejes de hacerlo. Lo mejor sería que todos contribuyéramos de cualquier manera al libro.
Además, es recomendable consultar la página de discusión del libro y la del articulo en particular que quieras modificar, ya que de esta manera se pueden coordinar esfuerzos.
Recuerda que todo el contenido que añadas al libro es publicado bajo la licencia GFDL, por lo que no uses material que no haya sido escrito por ti o que no esté ya publicado bajo GFDL. Recientemente wikimedia decidió adoptar la Licencia Creative Commons Compartir-Igual 3.0 para todos los aportes, por lo que actualmente el libro tiene una licencia dual.
Introducción
Objetivos
El objetivo principal de este Wikilibro es que cualquier persona sin conocimientos previos de programación pueda ser capaz de programar en el lenguaje C.
Una vez logrado el dominio del lenguaje, es probable que los lectores se interesen por otros temas más complejos que superen a los temas básicos. También les será más o menos sencillo aprender cualquier otro lenguaje de programación estructurada.
Sin embargo, este no es un libro que apunte únicamente a programadores principiantes. También puede resultar de interés para quienes ya tengan experiencia en el área de programación. En esta introducción hay dos secciones en las que se explica para los dos grupos principales de lectores qué camino seguir para comenzar a programar en el lenguaje C o bien perfeccionar conocimientos.
El lenguaje C es tan usado porque es un lenguaje de programación que emplea pocas instrucciones en lenguaje máquina para traducir elementos del código. Esto reduce los tiempos de ejecución de los programas.
Nota sobre la exactitud
Muchas de las cosas expresadas en este wikilibro, especialmente en los primeros capítulos, no son completamente exactas, aunque son buenas aproximaciones. Los detalles más exactos irán apareciendo posteriormente, una vez que los materiales anteriores hayan sido correctamente asimilados por el lector. En general, dadas dos definiciones o datos contradictorios en este wikilibro, debe considerarse siempre como más exacto al segundo, habiendo aparecido el primero como una introducción más general al tema.
Estándar utilizado
El lenguaje C fue creado en los años setenta, y a lo largo de su historia ha pasado por muchas modificaciones, tanto con respecto a la sintaxis como con respecto al código incluido dentro de la biblioteca estándar. Es por ello que se fueron desarrollando estándares, para que todos sepan con qué versión del lenguaje se está trabajando.
Los distintos estándares del lenguaje C han sido: el C de Kernighan y Ritchie, un estándar no-oficial que surgió luego de la publicación de su libro en 1978; el C89 o C90, el primer estándar oficial, posterior a la publicación de los estándares ANSI en 1989 e ISO en 1990; y el C99, publicado en 1999.
En este libro se utilizará el estándar C99, si bien por cuestiones de estilo y compatibilidad muchas veces se utilizará código compatible con el estándar C89.
Para los principiantes
Para quien no haya programado antes, es recomendable seguir el orden del libro. Los temas están especialmente organizados de manera incremental o acumulativa. Tal vez, lo que se te va a hacer más útil en el camino del aprendizaje es la constancia; sé terco, no trastabilles, no te rindas, tal vez tu pregunta sea ¿cuántas veces tengo que intentar?, las veces necesarias para lograr tu objetivo, sería la respuesta.
Claro que el principal enemigo de nosotros los humanos es el tiempo y por eso en caso de que de verdad estés trancado en algo busca ayuda de alguien que sepa "más que tú". ¿Que no tienes a nadie a tu alrededor con esa característica? Tal vez no buscaste bien y tal vez quieras usar la red de redes. Utiliza los buscadores, pregunta en IRC, en foros de programación, en listas de correo.
Para los más avanzados
El lanzamiento queda libre por supuesto, solo tú sabes lo que necesitas. Las reglas del juego son las mismas de siempre: primero saber lo que se quiere o necesita y atacar por ahí.
En este caso, te será útil acceder a los contenidos a partir del índice, eligiendo sólo aquellos que te sean necesarios.
Requisitos
Se presupone que los lectores tienen conocimientos elementales de informática a nivel de usuario, y son capaces de instalar un compilador del lenguaje C en sus sistema. Los detalles sobre la instalación se verán en la sección Herramientas.
Con respecto al Hardware, sólo será necesario contar con una PC con sistema operativo, donde sea posible instalar un compilador, y en lo posible un entorno de desarrollo. Cuanto mejor sea la computadora, más rápido será el proceso de compilación y ejecución de los programas. Sin embargo, cualquier PC sirve para aprender con los ejemplos de este libro.
Para quienes no tengan conocimientos básicos de programación, puede ser una buena idea comenzar leyendo los primeros capítulos del Wikilibro Fundamentos de programación, ya que algunos temas explicados en ese libro se asumen ya conocidos.
Finalmente, un requisito imprescindible en todo programador es tener sentido común. Muchas veces se pueden adoptar mejores o peores soluciones ante los diversos problemas, y la decisión de cuál elegir pasa por la aplicación del sentido común.
Herramientas
Para programar tanto en C, como en C++, Java o cualquier otro lenguaje de programación, necesitamos contar con aplicaciones o herramientas que nos permitan poner en funcionamiento nuestro programa.
El lenguaje de programación C es compilado, así que en este caso necesitaremos un compilador, que será el encargado de transformar nuestro código fuente en código que la computadora pueda ejecutar.
Además, para facilitar la tarea de los programadores existen los denominados Entorno de desarrollo integrados (IDE). En muchos casos, estos entornos incluyen un compilador, un depurador, y otras herramientas.
Las herramientas a instalar dependerán del sistema operativo utilizado. A continuación se listan algunas posibilidades para el sistema operativo Windows o GNU/Linux, no es imprescindible utilizar estas herramientas en particular, cualquier compilador puede servir.
Windows
Uno de los entornos de desarrollo más conocidos entre los programadores de C sobre Windows, tanto novatos como expertos, es el Bloodshed Dev-C++, que es un entorno libre multiplataforma. Tal entorno de desarrollo fue abandonado y retomado mejorándolo pasando a llamarse WxDev-C++. Otro entorno libre y gratuito es el Code::Blocks. Ambos entornos pueden utilizarse tanto para C como para C++.
También hay otras alternativas privativas como los compiladores de Borland o de Microsoft (Microsoft Visual C++).
GNU/Linux
En los sistemas GNU/Linux, será necesario tener instaladas las herramientas gcc
y make
y la versión 6 de la glibc con su documentación, que son las que permitirán compilar los programas.
Para escribir y modificar el código, es posible utilizar cualquier editor de texto plano (en lo posible que cuente con resaltado de sintaxis), como son emacs, vim, kate, gedit o geany.
Sin embargo, para quienes son novatos en la programación, es recomendable utilizar un entorno de desarrollo como son el Anjuta DevStudio (para el entorno GNOME) o KDevelop (para el entorno KDE), ya que incluyen facilidades adicionales para la ejecución y solución de problemas.
Los programas mencionados se incluyen dentro de la instalación estándar de la mayoría de las distribuciones actuales de GNU/Linux, de modo que para instalarlos sólo será necesario seguir el procedimiento usual de instalación de aplicaciones para la distribución deseada.
Historia de C
El lenguaje de programación C fue creado por Dennis Ritchie entre 1969 y 1973 cuando trabajaba en Bell Laboratories de AT&T junto con Ken Thompson en el diseño del sistema operativo UNIX. C fue creado para poder escribir dicho sistema operativo en un lenguaje de alto nivel, independiente del hardware donde se ejecutara.
Contar con un lenguaje de alto nivel permitió el avance de los sistemas operativos, ya que el mismo código podía ser utilizado en las distintas plataformas, propiciando la reutilización de código y reduciendo los tiempos de desarrollo. Así es que los sistemas operativos basados en UNIX, el sistema BSD, el sistema GNU/Linux y muchos otros fueron desarrollados en C.
Además, con el paso del tiempo se han desarrollado cientos de bibliotecas que permiten a los programadores de C utilizar el código desarrollado por otros para la realización de tareas comunes. Esto, a su vez, ha propiciado el desarrollo de aplicaciones en lenguaje C.
Actualmente es imposible contar la cantidad de aplicaciones y herramientas desarrolladas en C.
Evolución
A mediados de los años 60s, Martin Richards diseñó el lenguaje BCPL con la finalidad de usarlo para escribir software de sistemas operativos y compiladores.
En 1969, Ken Thompson escribió el Lenguaje B, en Bell Laboratories, con el objetivo de recodificar UNIX (escrito hasta ese momento en lenguaje ensamblador) usando un lenguaje de alto nivel más portable y flexible.
Durante los siguientes años, Dennis Ritchie modificó el lenguaje B, llegando a crear el lenguaje C y reescribiendo el sistema UNIX en dicho lenguaje; añadió características nuevas, como son el diseño de tipos y las estructuras de datos.
En 1978, Dennis Ritchie y Brian Kernighan publicaron la primera edición del libro El lenguaje de programación C. Este libro fue durante años la especificación informal del lenguaje. El lenguaje descrito en la primera edición de este libro, fue conocido como "el C de Kernighan y Ritchie" o simplemente "K&R C". En este libro se introdujeron nuevas características al lenguaje: los tipo de datos struct, long int y unsigned int; los operadores =+ y =- fueron sustituidos por += y -=.
A mediados de los años 80, Bjarne Stroustrup (también de los laboratorios Bell), crea el lenguaje C++, un lenguaje basado en C, con numerosas características adicionales, siendo la principal que está orientado a objetos. Si bien se han creado muchos lenguajes basados en C, C++ es el que ha permanecido más asociado a C.
En los años siguientes a la publicación del C de Kernighan y Ritchie, se añadieron al lenguaje muchas características no oficiales, que estaban presentes en algunos compiladores y no en otros. Fue por ello que en 1989 ANSI (American National Standards Institute) publicó el primer estándar oficial de C, que es conocido como ANSI C.
En este estándar se tomaron muchas de las funcionalidades no oficiales y se agregaron funcionalidades nuevas como los prototipos de función, y un preprocesador mejorado. También se cambió la sintaxis de la declaración de parámetros de funciones, para que incluyeran el tipo junto con el nombre.
Al año siguiente, en 1990 se publicó la estandarización ISO del lenguaje. Este estándar es básicamente el estándar ANSI, con unas pocas modificaciones de formato. A este estándar se lo conoce, entonces, como C89, o C90, y se trata del mismo lenguaje.
Basándose en el estándar ANSI que estaba en preparación, en 1988 Kernighan y Ritchie publicaron la segunda edición de su libro, que es aún hoy utilizada como una de las referencias principales del lenguaje.
Durante los siguientes años, el lenguaje C permaneció sin demasiados cambios. Sin embargo, como había sucedido antes, los distintos compiladores fueron incorporando características adicionales, que otros compiladores no tenían, siendo C++ la principal influencia.
Fue por ello que a finales de los noventa se decidió revisar el estándar de C, lo que llevó a la publicación del estándar C99. Este estándar incluye varias nuevas características como son: las funciones inline; la posibilidad de declarar variables en cualquier parte del código; los comentarios de una sola línea utilizando //; los tipos de datos long long int, bool y complex, entre otras.
Aún hoy el proceso de evolución del lenguaje sigue avanzando, y desde 2007 se está trabajando en el armado de un nuevo estándar.
Más información
Fundamentos de programación
En este capítulo veremos un resumido listado de conceptos básicos, esta información puede encontrarse en forma más elaborada en el WikiLibro Fundamentos de programación.
Definiciones
- Se denomina algoritmo a una secuencia de instrucciones que permiten obtener un resultado en particular. No necesariamente son programas de computadora, una receta de cocina, o las instrucciones para cambiar un neumático son ejemplos de algoritmos de la vida real.
- Las computadoras, son maquinas sin inteligencia propia, cuya única finalidad es interpretar el código que se les provee.
- El lenguaje de máquina es el único lenguaje que la computadora "entiende" y es capaz de ejecutar.
- Los lenguajes de programación son el medio de comunicación entre el programador y una computadora. El programador escribe en algún lenguaje de programación y utiliza las herramientas provistas por ese lenguaje para transformarlo en lenguaje de máquina.
- Finalmente, denominamos programa a una secuencia de órdenes a ser ejecutadas por una computadora. Un programa debe estar escrito en algún lenguaje de programación, y puede incluir uno o más algoritmos.
Tipos de lenguajes
Existe una gran cantidad de lenguajes de programación, que están pensados para distintas finalidades, siguen distintos paradigmas, y de una u otra forma se diferencian de los demás.
Esquemas de programación
El esquema de programación llamado Programación Imperativa, consiste en escribir una secuencia de instrucciones una detrás de la otra, que se ejecutarán en orden. Algunas de esas instrucciones pueden hacer que la máquina pase a una instrucción que no sea la siguiente, tal vez porque se cumpla una condición que hayamos establecido.
En los últimos años ha tomado fuerza otro paradigma de computación, llamado Programación Orientada a Objetos , en el cual se intentan modelar los sistemas creados como extensiones de la realidad mediante la definición de "objetos" que modelan entidades de la vida real y que interactúan entre sí mediante "mensajes" llamadas métodos.
El lenguaje C es un lenguaje imperativo, no orientado a objetos.
Alto o bajo nivel
Por otro lado, los lenguajes de programación se clasifican en niveles. Un lenguaje es de más bajo nivel cuanto más cercano esté al código de máquina, y un lenguaje que es de más alto nivel cuanto más lejano esté de la máquina y más cercano al lenguaje humano.
C es un lenguaje de alto nivel aunque tiene muchas características de lenguaje de bajo nivel (como el uso que permite hacer de la memoria). Estas características hacen que C sea un lenguaje muy potente, ya que permite optimizar al máximo los recursos de la máquina. Por ende, esto también hace que la dificultad y que los errores que se puedan cometer programando aumenten. Así que a C se le considera de nivel medio.
Lenguajes de más alto nivel que C son aquellos en los que el programador no necesita encargarse de manipular la memoria, como Java, C#, Python, Ruby, entre otros.
Compilados o interpretados
Otra forma de clasificar a los lenguajes de programación que es según la forma en que se ejecutan sus órdenes. Existen los lenguajes que son interpretados, cuyas órdenes pasan a través de un intérprete que se encarga de ejecutarlas (a partir del código fuente) en el mismo momento en que están siendo leídas. Algunos de los lenguajes interpretados son Python, Perl o Tcl, entre muchos otros.
La contraparte de los lenguajes interpretados son los lenguajes compilados (como el mismo C) que se diferencian en que las órdenes son transformadas a lenguaje de máquina que se almacena en un archivo ejecutable. Ese archivo puede ejecutarse luego, sin recurrir al compilador.
Los lenguajes compilados tienen la ventaja de la velocidad y la eficiencia, pero los interpretados tienen la ventaja de que, generalmente, son muy portables y de más alto nivel.
Estructura de la memoria
Parte de esta potencia de C viene de que permite acceder con mucha libertad a la memoria de la máquina. Para entender un poco cómo es posible, debemos entender cómo se guardan los datos en la memoria.
Imaginemos que la memoria tiene un montón de casillas, una enorme fila de casillas, cada una de las cuales contiene un dígito binario (bit):
0101001010100001010101001010000100111010110010010101001011010110001101010110101010110111...
Es exactamente así, pero es más cómodo recordar que esos bits se encuentran agrupados de ocho en ocho, formando octetos (bytes):
01010010 | 10100001 | 01010100 | 10100001 | 00111010 | 11001001 | 01010010 | 11010110 | 00110101 | 01101010 | 10110111 | ... |
Cada octeto puede contener combinaciones distintas de ceros y unos, es decir, cualquier número entre 0 y 255:
82 | 161 | 84 | 161 | 58 | 201 | 82 | 214 | 181 | 106 | 183 | ... |
También podemos representar estos números en base hexadecimal:
0x52 | 0xA1 | 0x54 | 0xA1 | 0x3A | 0xC9 | 0x52 | 0xD6 | 0x35 | 0x6A | 0xB7 | ... |
O considerarlos caracteres, mediante alguna codificación:
R | ¡ | T | ¡ | : | É | R | Ö | 5 | j | · | ... |
Este es el tipo de dato más elemental que nos podemos encontrar en C: el caracter. Un caracter ocupa exactamente un byte (8 bits) de memoria, y puede contener un número entre 0 y 255, o entre -128 y 127, dependiendo si queremos considerarlo como sin signo o con él.
Primer programa en C
En el libro "El Lenguaje de Programación C", Kernighan y Ritchie introdujeron al lenguaje C utilizando un sencillo programa que mostraba un saludo por la pantalla. Desde entonces se hizo tradición empezar con cualquier lenguaje de programación con el ejemplo del Hola mundo.
En particular en C se involucran muchas partes y sintaxis del lenguaje, por lo cual es especialmente útil verlo como el primer ejemplo de programación en C.
Ejemplo: Hola mundo
/* Inclusión de archivos */
#include <stdio.h>
/* Función principal */
int main (int argc,char **argv)
{
/* Impresión por pantalla y salida del programa*/
printf("Hola mundo\n");
return 0;
}
Para poder editar y ejecutar este programa será necesario utilizar algún editor y luego un compilador, como se explicó en la sección Herramientas necesarias.
Si se tiene el compilador gcc
en un entorno UNIX o GNU/Linux, la forma sencilla de compilar y ejecutar será:
$ gcc holamundo.c $ ./a.out Hola Mundo $
Es decir que el compilador genera un archivo, en este caso llamado a.out, y la salida generada por ese archivo es "Hola mundo". A continuación una explicación detallada sobre el proceso de compilación del programa, y luego un análisis línea por línea del contenido de este ejemplo.
Pre-requisitos para la compilación de programas
Como ya se mencionó, será necesario tener instalado el compilador y un editor o entorno de desarrollo que permitan escribir el código a compilar. Para más información ver la sección Herramientas necesarias.
El código a compilar debe guardarse con un nombre que represente al programa en cuestión y la extensión .c
. En el caso del ejemplo del Hola mundo, el archivo puede llamarse hola.c
.
En las explicaciones a continuación, se asume que se cuenta con un compilador instalado y se ha editado un archivo hola.c
que se quiere compilar. Si tu sistema operativo no aparece en esta lista busca en internet, ya que seguro que existe algún compilador para ese sistema.
Compilación de programas según la plataforma
GNU/Linux
Si bien existen otros compiladores, lo más usual y más sencillo para compilar un programa en GNU/Linux es el compilador gcc
, ya que es el que se incluye en todas las distribuciones.
De cualquier forma, es posible realizar la compilación desde línea de comandos o desde el entorno gráfico.
Para realizarla desde línea de comandos, será necesario contar con una terminal (xterm, konsole, gnome-terminal, etc). No es necesario contar con permisos de root para crear o compilar programas. En esa terminal será necesario escribir
gcc hola.c
Si no existen errores en el código, este comando nos creará un archivo ejecutable, que por omisión se llama "a.out", y que podemos ejecutar desde la línea de comandos de la siguiente forma:
./a.out Hola mundo
Es una buena idea especificar el nombre que el archivo ejecutable tendrá, pasando como parámetro al compilador la opción -o
, de la siguiente forma:
gcc hola.c -o hola
Con lo cual, el nombre del archivo creado será hola
. Este archivo no tiene extensión ya que es la forma usual de llamar a los archivos ejecutables en los entornos UNIX y GNU/Linux, sin embargo funcionaría de la misma forma si se llamara hola.exe
.
Para ejecutarlo, haremos los mismo que en el caso anterior:
./hola Hola mundo
Existen otros parámetros que podemos especificar al compilador en la línea de comandos, dependiendo del tipo de programa, y en función de la complejidad del mismo. Por ejemplo, podemos agregar las siguientes opciones:
gcc hola.c -o hola -Wall -pedantic
La opción -Wall
nos mostrará todos los avisos que produzca el compilador, no solamente los errores. Los avisos nos indican dónde y/o porqué podría surgir algún error en nuestro programa.
La opción -pedantic
nos aporta más información sobre los errores y los avisos mostrados por GCC.
int main() 9 { 10 float litros, gal, ganancia, precio; 11 cout "introduzca el numero de litros de leche vendidos:" endl; 12 cin litros 13 cout "precio por galon:" endl 14 cin precio; 15 gal= (litros/3.785); 16 ganancia=(precio*gal); 17 count "ganancia total en el dia= " ganancia endl; 18 return 0; }
Mac OS
Programación en C/Compilar un programa/Mac OS
-->
Diseccionando el "Hola Mundo"
A continuación veremos cuál es la estructura básica de un programa en C, para poder entender qué hace cada una de las líneas de nuestro sencillo programa.
Es probable que lo primero que salte a la vista sea la línea:
printf("Hola mundo\n");
Esta es la línea que hace aparecer la cadena Hola Mundo
en nuestra pantalla. Notamos que en C la sentencia para imprimir algo por pantalla es printf()
y, además, hay que colocar paréntesis alrededor de lo que queremos imprimir para utilizarla.
Esto se debe a que en C, printf
es una función, que imprime su argumento (la cadena Hola Mundo\n
) en la pantalla. Se denomina invocar una función a la acción de utilizarla para que realice una acción.
Podemos observar también que la cadena a imprimir termina con una extraña combinación: \n
. La combinación \n
no representa a dos caracteres independientes, sino que representa un único carácter no imprimible: el salto de línea. Sin el salto de línea, el resultado al ejecutar el programa sería:
$ ./a.out Hola Mundo$
Es decir que no hay salto de línea entre la cadena impresa, y la siguiente entrada de la línea de órdenes, que no es lo que esperábamos.
Lo último a notar en la línea es que termina con un punto y coma. En C, todas las sentencias terminan con un punto y coma. Al principio puede parecer obvio dónde termina una sentencia, pero ya veremos más adelante que no lo es tanto.
Observemos ahora la siguiente sentencia del programa:
return 0;
Luego de esta sentencia, termina el programa. En el caso de la instrucción return
dentro de la función main
, el resultado es que se finaliza el programa, comunicándole al sistema operativo que el valor de retorno (un código numérico que el sistema utiliza para saber si el programa ha funcionado bien o ha dado fallos) es 0, es decir, correcto.
Las dos últimas sentencias se encuentran encerradas entre llaves. De esta manera, forman un bloque, es decir, un grupo de sentencias que se ejecutarán siempre de forma correlativa.
¿Y qué es esa línea que precede (en realidad, que da nombre) al bloque?
int main (int argc, char **argv)
Pues es la definición de una función, en este caso llamada main
. En C (y en general en todos los lenguajes de programación estructurada) todo se hace a base de funciones, como main
y printf
.
La función main
es especial, porque es el la que se invoca cuando se ejecuta el programa. Todos los programas en C comienzan su ejecución al principio de la función main
, y cuando ésta acaba, el programa también.
Veamos con más detalle la definición de la función:
int main (int argc, char **argv)
{
...
}
- El nombre de la función que viene a continuación, entre llaves, es
main
. - Recibe dos argumentos:
int argc
ychar **argv
(que representan a la cantidad de argumentos ingresados al ejecutar el programa y a los valores de estos argumentos respectivamente).[1] - La función devuelve como resultado un número entero,
int
(que es el0
de la instrucciónreturn
).[2]
Finalmente, y un tanto aparte (está separada del resto por una línea en blanco), tenemos la línea:
#include <stdio.h>
Que parece bastante distinta al resto del programa, y que, además, parece no tener sentido, puesto que ya hemos definido la función main
que hace todo el trabajo.
Efectivamente, esa línea no es parte del programa, aunque sea imprescindible. La línea es una instrucción del preprocesador de C, como nos lo indica el símbolo #
, y lo que hace es incluir en ese punto el contenido de otro fichero, antes (de ahí el nombre de preprocesador) de que comience la compilación. El fichero stdio.h
es el que contiene la definición de la función printf()
, que antes utilizamos pero que no escribimos, ya que forma parte de la biblioteca estándar de C.
Comentarios
Una vez escrito un código, tratar de entenderlo un año más tarde solo con leerlo puede ser frustrante: no hay manera de saber (si el programa es medianamente complicado) qué es cada variable, o qué hace cada bloque de código. Por esto, en cualquier lenguaje de programación son importantes los comentarios.
Un comentario en C es todo lo que se encuentre entre los símbolos /*
y */
. Hay que tener en cuenta que los comentarios no se pueden anidar: si dentro de un comentario hay un /*
, seguirá siendo el primer */
el que finalice el comentario, no se esperará al segundo.
Hay otro tipo de comentarios en C, procedentes del lenguaje C++, e incorporadas al estándar de C a partir de C99: //
. Todo lo que esté después de estos signos, hasta el final de la línea, se considerará un comentario y el compilador no lo tomará en cuenta.
En el ejemplo presentado pueden verse tres líneas con comentarios, que documentan someramente las distintas funcionalidades del código. En los próximos capítulos podrán verse mejores usos de los comentarios dentro del código.
También podría decirse que es una herramienta básica basada en compilador
- ↑ En un capítulo posterior podrá ver un ejemplo del uso de los parámetros que recibe
main
. - ↑ Es importante señalar que el estándar dice que
main
deberá definirse como función que retorna un entero, o de lo contrario el resultado queda indefinido.
Tipos de datos
Historia
En el lenguaje C estandarizado como C89, existían cuatro tipos de datos básicos que son: los números enteros, los números reales, los caracteres, y los punteros. A partir del estándar C99 se agregan: los valores lógicos (verdadero o falso) y los números complejos.
Estos tipos de datos son parte del lenguaje, y por ello se los considera primitivos. Más adelante veremos que con el uso de estructuras y uniones es posible crear tipos compuestos de datos a partir de estos tipos primitivos.
En este capítulo veremos los enteros, los reales y los caracteres. Más adelante se verán otros tipos de datos más complejos, como son los vectores, las cadenas de caracteres, y los punteros en general.
Enteros
Los enteros son el tipo de dato más primitivo en C. Se usan para representar números enteros. Pero siempre se pueden encontrar otras aplicaciones para los números enteros. En general se pueden usar para representar cualquier variable discreta.
Los tipos de datos enteros son: short
, int
, long
y long long
, cada uno representando un número entero de un tamaño o capacidad determinado. Según el compilador y la plataforma de hardware, cada uno de estos tipos de dato puede ocupar desde 1 byte hasta 8 bytes en memoria (para más detalles busca en la referencia).
Además, el lenguaje C hace la distinción de si el entero es con signo (signed
) o sin signo (unsigned
). En caso de que no se declare si es con signo o sin signo, se toma con signo.
Algunos ejemplos de declaraciones de enteros:
int a;
unsigned int a;
signed long a;
signed long long a = 10000000;
Todos los números son representados en memoria mediante una cadena de bits. En el caso de los números con signo, el bit más significativo es el que se usa para representar el signo. La representación de los números negativos se realiza mediante el complemento a dos, que es una técnica que permite operar con los números negativos de forma lógica.
A modo de ejemplo, la representación en memoria del número -8 en una variable de 2 bytes, entera, con signo, sería la siguiente:
1111111111111000
Reales
Los tipos de datos que representan a los números reales, ya que utilizan un sistema de representación basado en la técnica de coma flotante, que permite operar con números reales de diversas magnitudes, mediante un número decimal llamado mantisa y un exponente que indica el orden de magnitud.
El tipo de dato flotante en lenguaje C sólo tiene dos tamaños: el float
y el double
, que son 4 bytes y 8 bytes respectivamente. Se los puede utilizar tanto para representar números decimales, como para representar números enteros con un orden de magnitud muy grande.
La forma de declarar una variable flotante es escribiendo en una línea uno de los tipos de datos flotantes y a continuación el nombre de la variable y tal vez algún valor que se les quiera dar.
Algunos ejemplos:
float a;
double a = 1e23;
double a = 3.1416;
float a = 4e-9;
double a = -78;
Hay que tener en cuenta que aunque los valores flotantes son más convenientes para algunas aplicaciones, hay casos en los que se prefieren los enteros. Esto se debe a que los números flotantes no necesariamente tienen soporte de hardware, en particular en las plataformas integradas. Una alternativa que se utiliza en estas situaciones es interpretar los enteros como decimales de forma que 150 se interprete como 1.5 y 2345 como 23.45.
Para el caso de los flotantes de 4 bytes, se utiliza 1 bit para el signo, 7 bits para el exponente y 24 bits para el valor del número. El procedimiento para almacenar un número en una variable flotante es el siguiente:
- Se convierte a binario la parte entera.
- Se coloca el signo en el bit más significativo de la misma manera que en los enteros (1 para el - y 0 para el +).
- Se mueve la coma (en la representación binaria de la parte entera) hasta que esté a la derecha del primer uno y éste se descarta (el uno más significativo). El valor del exponente será el número de posiciones que se movió la coma. El exponente usa la representación de un entero con complemento a dos.
- Se convierte en binario la parte decimal del número. Esto usando el peso de los bits. el bit decimal más significativo vale 1/2, el siguiente vale 1/4, el otro 1/8, el otro 1/16 y así hasta completar lo que falta para los 23bits del valor.
- Se concatena todo y ese es el valor flotante representado en memoria.
Caracteres
Los caracteres se representan utilizando el tipo char
, que tiene sólo 1 byte de tamaño. Este tipo se utiliza para representar los 256 caracteres de la tabla de caracteres del sistema. El tipo char
es también un tipo entero, ya que puede tomar valores de 0 a 255. Por lo tanto también puede ser signed
o unsigned
.
En cuanto a la forma de declarar variables de tipo char
es la misma forma que con los otros tipos.
char a;
char a = 's';
unsigned char a = 48;
Como puedes ver, se le puede asignar un número a una variable char
, ya que se trata de un tipo entero. En muchas situaciones se utiliza el tipo char
para almacenar números pequeños, ya que ocupa en memoria sólamente un byte.
Es importante notar que con la llegada de la codificación UTF-8, los caracteres de los diversos idiomas pueden ocupar 1, 2, 3 o 4 bytes, de modo que el tipo char
ya no alcanza para la representación de todos los caracteres posibles. Por ello, el estándar C99 introduce el tipo wchar
que puede ocupar más de 1 byte, según sea necesario para la codificación utilizada por el sistema.
Interacción con el usuario
En este capítulo veremos un poco más sobre como interactuar con el usuario de nuestros programas desde la consola, utilizando printf()
como vimos en el primer ejemplo "Hola mundo", así como scanf()
para la lectura del teclado.
Imprimir por pantalla
Como hemos visto hasta ahora en los ejemplos, hay una función que utilizamos para sacar por pantalla textos arbitrarios o el resultado de alguna operación: la función printf()
.
Si miramos (en la documentación) su definición, no nos aclarará demasiado:
int
printf (
const char
*TEMPLATE, ...)
...claro que por algo tiene una sección completa de la documentación para ella sola.
Veámosla poco a poco. Se trata de una función de la biblioteca estándar, lo que quiere decir que para utilizarla tenemos que incluir previamente su definición. La encontraremos en <stdio.h>
.
Lo primero que vemos en la definición es que es una función de tipo int
, lo que quiere decir que devuelve un entero. Ese entero es el número de caracteres impresos en la pantalla, o un número negativo en caso de que se produzca algún error.
Lo siguiente a notar es su primer argumento: const char
*TEMPLATE
. Se trata de una cadena de caracteres (char
*
) que no será modificada por la función (const
), con lo que puede ser una constante de cadena o una variable que contenga una cadena, pero siempre debe acabar con el carácter nulo \0
.
Y luego vienen esos extraños puntos suspensivos. Esa elipsis nos indica que como argumentos adicionales de printf()
podemos poner una serie ilimitada de otros argumentos, que se supone que la función sabrá qué hacer con ellos. Y eso es justamente lo que hace tan fabulosa y útil a printf()
.
Como hemos visto, el uso más simple de printf()
es imprimir una cadena de texto simple y corriente. Como ya vimos:
printf(
"Hola Mundo
\n
"
);
/*imprime la cadena*/
Y también hemos visto printf()
también puede, con un argumento extra y una sintaxis especial, imprimir un número entero que hayamos almacenado en una variable:
char
resultado;
resultado=
5
+
2
;
printf(
"Resultado de la suma:
%i
\n
"
,resultado);
Aquí el punto de inserción es la secuencia %i
. printf()
siempre trata las secuencias que comiencen por %
como secuencias de control que le dicen que debe imprimir algo que le proporcionamos en los otros argumentos. Así, podemos imprimir varios enteros distintos en los sitios que queramos de la cadena, insertando varias de estas secuencias %i
:
int
numero;
numero=
3
;
printf(
"El doble de
%i
es
%i
y su cuadrado es
%i\n
"
,numero,numero*2,numero*numero);
Lectura de datos del teclado
La entrada de datos se puede hacer de muchas maneras y entre ellas están desde el uso de dispositivos especiales hasta nuestro simple teclado. La entrada de datos se refiere a cualquier forma de influencia del usuario sobre los datos que posee el sistema.
Con el fin de mostrar una forma de entrada simple para el aprendizaje vamos a hablar de la función scanf()
que se encuentra definida en <stdio.h>
y que se usa para capturar diferentes tipos de datos.
La función scanf()
scanf()
es una de las funciones más usadas por los principiantes para hacer entrada de datos en el lenguaje C. Tiene una sintaxis muy parecida a printf
: recibe una cadena con el formato de los datos y luego se ponen las variables en orden que correspondan a ese tipo de datos. Es decir, así como en printf
se pueden mostrar por pantalla los datos de varias variables en una misma sentencia, en scanf
se pueden capturar varios datos en una sola sentencia.
#include <stdio.h>
int main() {
int a;
printf ("diga un valor para a:");
scanf("%i",&a);
printf ("el valor es: %i\n",a);
return 0;
}
Por ahora no nos interesan las demás sentencias, sólo la que contiene scanf
. En el código se ve lo siguiente:
scanf("%i",&a);
Se observa que la funcion printf dejó en pantalla una petición para que el usuario introdujera un valor. Entonces, scanf
recibe como argumento una cadena del formato en que se van a capturar los datos y la lista de variables que van a recibir valores y que deben coincidir con los del formato.
En este caso la cadena de formato, "%i"
, especifica que el usuario ingresará un número entero. Luego se designa a la variable a
para contener a ese número. El símbolo (&) que precede a a
es para especificar que lo que se está enviando como argumento no es el valor que posee la variable a
sino la dirección de memoria en que se encuentra. En este momento eso no tiene mucha relevancia, sólo hay que recordar que se debe usar el símbolo & dentro del scanf
. En el momento en que hablemos de punteros veremos más detalles de esto.
Otro ejemplo del uso de scanf
:
#include <stdio.h>
int main() {
int a,b;
printf ("introduzca dos valores con el formato \"a,b\" :");
scanf("%i,%i",&a,&b);
printf ("el primer valor : %i\n",a);
printf ("el segundo valor : %i\n",b);
return 0;
}
Aquí hemos introducido una nueva variable en el código. La cadena de formato, "%i,%i"
especifica que el usuario ingresará un número, seguido de una coma, y luego otro número. El primer %i
será capturado por la variable a
y el segundo por b
.
Expresiones
Vamos a tratar ahora de que el ordenador haga un poco de matemáticas para nosotros. Por ejemplo, que realice unas pocas sumas, restas multiplicaciones y divisiones.
#include <stdio.h>
#include <math.h>
int main()
{
int resultado;
resultado=5+2;
printf("Resultado de la suma: %i\n",resultado);
resultado=5-2;
printf("Resultado de la resta: %i\n",resultado);
resultado=5*2;
printf("Resultado de la multiplicación: %i\n",resultado);
resultado=5/2;
printf("Resultado de la división: %i\n",resultado);
return(0);
}
Después de grabarlo (por ejemplo, con el nombre ejemplo.c
), lo compilamos y ejecutamos, con (respectivamente):
$ gcc ejemplo.c $ ./a.out Resultado de la suma: 7 Resultado de la resta: 3 Resultado de la multiplicación: 10 Resultado de la división: 2 $
Fijémonos en la línea del principio de la función main
:
int resultado;
Esta línea lo que hace es reservar un trozo de memoria, del tamaño de un int
(normalmente 4 bytes), y asignarle el nombre resultado
, para poder después referirnos a él. A partir de este momento, podemos considerar que en nuestro programa existe una variable, que no tiene valor definido, pero a la que le podremos dar valor posteriormente.
Las líneas con printf()
ya las conocemos, pero hay algo en ellas que no habíamos visto antes. Esos %i
y la parte de resultado
son nuevas para nosotros.
La función printf()
no sólo sabe imprimir cadenas simples, como "Hola Mundo\n"
, sino también imprimir variables. Para ello, en el lugar de la cadena donde queremos que aparezca el valor de la variable, introducimos lo que se llama una cadena de conversión de printf()
. Estas cadenas siempre empiezan por %
, siendo %i
la cadena para imprimir un entero, como es en nuestro caso int resultado
. Finalmente, printf()
debe saber qué valor escribir, por eso le damos otro argumento (u otros), usando ,
como separador, que contienen las variables cuyos valores queremos mostrar.
En el resto del programa hemos visto cómo decirle al ordenador que ejecute una suma, una resta, una multiplicación y una división entera, con los operadores +
, -
, *
y /
. Es de notar que el resultado de una operación como estas entre números enteros será siempre otro entero, como se puede observar en la división, en la que no obtenemos un bonito decimal, sino un resultado entero. Además, hemos visto que el resultado de esas operaciones, que llamamos expresiones, puede ser asignado a una variable:
resultado = 7;
Esa asignación se hace mediante el operador de asignación: =
. Con él, ya conocemos cinco operadores.
Pero, como =
también es un operador, ¿cómo sabe el ordenador qué operador debe ejecutar primero? Y si es un operador, ¿por qué no da un resultado? ¿No crea una expresión?
Operadores | Precedencia |
---|---|
* /
|
Izq. a Der. |
+ -
|
Izq. a Der. |
=
|
Der. a Izq. |
Empezando por las últimas preguntas, el operador de asignación sí crea una expresión, como los operadores de suma, resta, multiplicación y división, y esa expresión tiene un resultado, que es el valor que obtiene el lado izquierdo al realizar la operación. En cuanto a saber qué se debe ejecutar primero, el ordenador tiene una lista de precedencia, según la cual siempre ejecuta primero las multiplicaciones y divisiones, de izquierda a derecha, a continuación las sumas y restas, de izquierda a derecha, y a continuación las asignaciones, de derecha a izquierda. Para más detalles acerca de la precedencia de los operadores ver el anexo de los operadores.
En cuanto a los caracteres de punto y coma, notamos aquí que una expresión también puede ser una sentencia por sí misma, sin necesidad de que haya ninguna función. De hecho, una sentencia puede no tener siquiera una expresión. La línea:
;
es una sentencia perfectamente válida, la sentencia vacía, que sera útil en puntos donde el lenguaje requiera una sentencia pero no sea necesaria para nuestro programa.
Instrucciones de control
Como ya se ha mencionado, C es un ejemplo de programación estructurada. En este tipo de programación, es necesario contar con ciertas estructuras que permitan controlar el flujo del programa, es decir, tomar decisiones y repetir acciones.
La estructura condicional if ... else
En la gran mayoría de los programas será necesario tomar decisiones sobre qué acciones realizar. Esas decisiones pueden depender de los datos que introduzca el usuario, de si se ha producido algún error o de cualquier otra cosa.
La estructura condicional if ... else
es
la que nos permite tomar ese tipo de decisiones. Traducida literalmente del
inglés, se la podría llamar la estructura "si...si no", es decir, "si se cumple
la condición, haz esto, y sino, haz esto otro".
Un ejemplo sencillo sería el siguiente (no se trata de un programa completo, sino tan sólo una porción de código):
if (edad < 18)
printf("No puedes acceder.\n");
else
printf("Bienvenido.\n");
Este código de ejemplo dice que si el valor de la variable edad
es
menor que 18
se imprimirá "No puedes acceder.\n"
, mientras que en caso
contrario se imprimirá "Bienvenido.\n"
.
Como se ve en el ejemplo, la estructura de un condicional es bastante simple:
if (condición) {
sentencias_si_verdadero;
} else {
sentencias_si_falso;
}
La condición, encerrada entre paréntesis, es una expresión que puede dar como resultado 0 (interpretado como falso) o cualquier valor distinto de 0 (interpretado como verdadero). Cuando la condición sea verdadera, se ejecutarán las sentencias dentro del primer bloque de código, cuando la condición sea falsa, se ejecutarán las sentencias del segundo bloque de código. Las expresiones y valores de tipo verdadero/falso son también llamados valores lógicos o booleanos.
La indentación o sangría (los espacios al comienzo de las líneas) no es
necesaria, pero ayuda a la claridad del código. La utilización de las llaves
{
...}
es obligatoria cuando se quiere utilizar más de una instrucción por bloque, y
optativa cuando sólo se quiere escribir una instrucción. Por claridad, sin
embargo, es recomendable utilizarlas aún cuando sólo vaya a haber una instrucción.
El bloque del else
es opcional.
Si no se lo encuentra, sólo se realizará la acción correspondiente al bloque
if
.
A continuación, un ejemplo con una función, que devuelve el mayor de dos números:
int mayor(int a, int b)
{
if (b > a) {
return b;
}// No posee especificación de la parte "else", ya que no es necesaria.
return a; // Finaliza la función retornando el valor de "a".
}
Operadores de comparación
El símbolo >
visto en el último ejemplo es un operador, que en este caso compara dos números enteros y devuelve verdadero si el primero es mayor, falso en caso contrario.
A continuación un listado de los posibles operadores de comparación en C y su significado.
Operador | Significado |
---|---|
<
|
estrictamente menor que |
>
|
estrictamente mayor que |
<=
|
menor o igual que |
>=
|
mayor o igual que |
==
|
igual a |
!=
|
distinto de |
Teniendo en cuenta que en C se toma como falso el valor 0, y como verdadero
cualquier otro valor, una práctica común es expresar condiciones sin utilizar ningún operador:
float division(int dividendo, int divisor)
{
if (divisor) {
return dividendo / divisor;
} else {
printf ("No se puede dividir por cero\n");
return 0;
}
}
En este caso, la expresión (divisor)
es equivalente a (divisor != 0)
.
Operadores lógicos
Los operadores &&
("y"), ||
("o") y !
("no") son operadores lógicos. Permiten operar con expresiones lógicas para generar expresiones más complejas.
Por ejemplo: determinar si un año es bisiesto o no. Los años son bisiestos si son divisibles por 4, pero no si son divisibles por 100, a menos que también sean divisibles por 400.
if ( (!(a % 4) && (a % 100)) || !(a % 400) ) {
printf("es un año bisiesto.\n");
} else {
printf("no es un año bisiesto.\n");
}
En realidad, teniendo en cuenta la prioridad de los operadores utilizados, podemos simplificar la expresión anterior del siguiente modo:
if ( !(a % 4) && (a % 100) || !(a % 400) ) {
printf("es un año bisiesto.\n");
} else {
printf("no es un año bisiesto.\n");
}
Además, como a cada rama del if le sigue una única instrucción, podemos expresar la expresión anterior del siguiente modo:
if ( !(a % 4) && (a % 100) || !(a % 400) )
printf("es un año bisiesto.\n");
else
printf("no es un año bisiesto.\n");
En este caso, se utiliza el operador módulo (%), que obtiene el resto de la división
entera de un número por otro. Cuando un número es divisible por otro, el resto de su división
entera será cero. Siendo que cero es equivalente a falso, y cualquier valor distinto de cero es
equivalente a verdadero, podemos usar el operador %
para
verificar si el número es múltiplo de 4, de 100 o de 400.
Evaluación de cortocircuito
La evaluación en corto circuito es una característica del lenguaje C que se utiliza para optimizar la ejecución de programas. Consiste en que el programa puede verificar si una expresión es verdadera o falsa antes de haber evaluado toda condición.
Por ejemplo, si se tiene una condición como la siguiente:
if ((a > 2) || (b < 4)) {
...
}
Al ejecutarse el programa, se evaluará primero si
a > 2
. En el caso en que sea verdadero,
no continuará con la siguiente condición, ya que el resultado será de cualquier
modo verdadero.
De la misma forma, si la condición fuera:
if ((a > 2) && (b < 4)) {
...
}
En este caso, si no se cumple que a > 2
,
no se evaluará la siguiente condición, ya que el resultado será falso de todos
modos.
Esta característica no tiene demasiada importancia al comenzar a programar, pero facilitará ciertas operaciones y optimizaciones en programas avanzados.
La estructura condicional abierta y cerrada switch ... case
La estructura condicional switch ... case
se utiliza cuando queremos evitarnos las llamadas escaleras de decisiones. La
estructura if
nos puede proporcionar,
únicamente, dos resultados, uno para verdadero y otro para falso. Una
estructura switch ... case
, por su parte,
nos permite elegir entre muchas opciones. Ejemplo:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int dia;
printf("¿Qué número de día de la semana es?");
scanf("%i",&dia);
switch(dia) {
case 1 :
printf("Lun, Lunes");
break;
case 2 :
printf("Mar, Martes");
break;
case 3 :
printf("Mier, Miercoles");
break;
case 4 :
printf("Jue, Jueves");
break;
case 5 :
printf("Vie, Viernes");
break;
case 6 :
printf("Sab, Sabado");
break;
case 7 :
printf("Dom, Domingo");
break;
default :
printf("No existe");
}
return 0;
}
La estructura anterior, de realizarse con sentencias if
, necesitaría cuatro de ellas, resultando un enorme bloque
muy difícil de leer. En la mayoría de los casos, además, la sentencia switch
proporciona una ganancia en velocidad del
código, pues permite al compilador trabajar en base a que se trata de una
decisión múltiple para una única variable, cosa que con sentencias if
el compilador no tiene por qué detectar.
Como vemos, para cada valor de la variable se ejecuta un bloque de sentencias
distinto, en el que no necesitamos llaves. Hay un caso especial, default
, que se ejecuta si ningún otro
corresponde, y que no es necesario poner. Es, en todo, equivalente al bloque
else
de una sentencia if
.
Las sentencias break
son muy importantes,
ya que el comportamiento normal de un bloque switch
es ejecutarlo todo desde la etiqueta case
que corresponda hasta el final. Por ello, si
no queremos que se nos ejecute más de un bloque, pondremos sentencias break
al final de cada bloque excepto el último.
Es decir, las etiquetas case
son puntos de
entrada de la ejecución, y no implican que al acabarse el bloque case
la ejecución salte al final del bloque
switch
. Las etiquetas case
siguientes a la que hemos utilizado para entrar son,
sencillamente, ignoradas.
A la ausencia de sentencias break
se le
llama, en ocasiones, "dejar caer la cascada switch
".
El bucle while
El bucle while
sirve para ejecutar código
reiteradas veces.
while (/*condicion*/) {
/* Código */
}
La condición debe de ser una expresión lógica, similar a la de la sentencia if
. Primero se evalúa la condición. Si el resultado es verdadero, se ejecuta el bloque de código. Luego se vuelve a evaluar la condición, y en caso de dar verdadero se vuelve a ejecutar el bloque. El bucle se corta cuando la condición da falso.
Ejemplo: imprimir los números de 0 a 99:
int i = 0;
while (i < 100) {
printf("%d\n", i);
i = i + 1;
}
Inicialmente se declara que la variable i tiene un valor de 0. Al iniciar el bucle, se cumple la condición i < 100, por lo que se procede a la instrucción de imprimir dicho número (cero, en el caso inicial). Posteriormente i cambiará su valor de uno en uno por la instrucción i = i + 1 y seguidamente dicho valor nuevo, será evaluado en la condicion while
hasta que i llegue al valor 100, donde debido a la condicional, éste será un valor falso, dando fin al código.
El bucle for
El bucle for
es un bucle muy flexible y a la
vez muy potente ya que tiene varias formas interesantes de implementarlo, su
forma más tradicional es la siguiente:
for (/* inicialización */; /* condición */; /* incremento */) {
/* código a ejecutar */
}
Inicialización: en esta parte se inicia la variable que controla el bucle y es la primera sentencia que ejecuta el bucle. Sólo se ejecuta una vez ya que solo se necesita al principio del bucle.
Expresión condicional: al igual que en el bucle while
, esta expresión determina si el bucle continuará ejecutándose o no.
Incremento: es una sentencia que ejecuta al final de cada iteración del bucle. Por lo general, se utiliza para incrementar la variable con que se inicio el ciclo. Luego de ejecutar el incremento, el bucle revisa nuevamente la condición, si es verdadera tiene lugar una ejecución más del cuerpo del ciclo, si es falsa se termina el ciclo y así.
Aquí se muestra el mismo ejemplo visto para el bucle while
, pero implementado con un bucle for
:
int i;
for (i=0; i < 100; i = i + 1) {
printf("%d\n", i);
}
Nota: En C, la sentencia i = i + 1
puede escribirse en forma más reducida como i++
. Esta forma se utiliza más comúnmente en el bucle for
:
int i;
for (i=0; i < 100; i++) {
printf("%d\n", i);
}
El bucle do
...
while
El bucle do...while
es un bucle que, por
lo menos, se ejecuta una vez. Do significa literalmente "hacer", y while significa "mientras".
Su forma es esta:
do {
/* CODIGO */
} while (/* Condición de ejecución del bucle */)
Os muestro un ejemplo sencillo de uso:
int aleatorio;
do {
aleatorio = rand();
} while (aleatorio != 25);
La verdad es que este ejemplo puede resultar un poco absurdo, pero es bastante intuitivo. El código del bucle asigna un valor aleatorio a la variable definida anteriormente, y mientras esa variable no tenga el valor 25, el bucle sigue ejecutándose.
La sentencia goto
La sentencia goto
sirve para indicar al
programa que continue ejecutándose desde la línea de código indicada. Su sintaxis es más o menos así:
/* Código */
ETIQUETA:
/* Código */
goto ETIQUETA;
/* Código */
Así, cuando se ejecute la sentencia goto
,
el programa "saltará" y continuará su ejecución a partir de la etiqueta marcada.
Como se
puede observar se puede usar para crear un bucle, o para ir a una parte del
código u otra si se combina con una sentencia if...else
. Pero por lo general puede obtenerse el mismo efecto utilizando los bucles anteriormente vistos.
Por eso, la sentencia goto
es poco aceptada por la comunidad de programadores, pues
puede provocar que se hagan programas un poco "sucios" y confusos. Sólo en ocasiones muy excepcionales será recomendado el uso del goto al crear
iteraciones muy complejas. Sin embargo, con el pasar de los años este comando ya ha quedado prácticamente descartado del lenguaje de los programadores.
Uso de Funciones
clase de Funciones
Como vimos
anteriormente
C tiene como bloque básico la función main()
, también hemos visto la sentencia printf()
que es otra función, y de igual forma hay muchas más funciones predefinidas, pero
nosotros mismos también podemos definir nuestras propias funciones. De hecho, es
fundamental hacerlo.
Podemos definir una función cualquiera de la misma manera en que definimos la
función main()
. Basta con poner su tipo,
su nombre, sus argumentos entre paréntesis y luego, entre llaves, su código:
/* Inclusión de archivos */
#include <stdio.h>
void holamundo(void) /* Función donde se ejecuta la lógica del programa */
{
printf("Hola Mundo\n"); /* imprime la cadena */
return; /* sale de la función */
}
int main(void) /* Función principal del programa */
{
holamundo(); /* llamada a la función holamundo */
return 0; /* sale del programa con código 0 (correcto) */
}
Este código es en todo equivalente al "Hola Mundo" original, sólo que nos
muestra cómo escribir y cómo utilizar una función. Y además nos muestra un
principio de buena programación: meter las sentencias que "hacen el trabajo" en
otras funciones específicas para sacarlas de main()
, dejando en ésta tan sólo un guión general de lo que
hace el programa, no las órdenes específicas. De esta manera se facilita la
comprensión del programa, y por tanto el futuro trabajo de modificarlo.
La sentencia return
La sentencia return
puede utilizarse dentro de una función para terminar su ejecución.
En el ejemplo anterior, la función holamundo
fue declarada con valor de retorno de tipo void
(es decir, valor de retorno nulo). En ese caso, la sentencia return
no lleva ningún parámetro adicional, ya que la función no debe devolver ningún valor a la función que la llama.
En cambio, la función main
tiene un valor de retorno de tipo int
, por lo que return
debe ir seguido de un valor entero (0 en el ejemplo). El valor 0 se utiliza para indicar que el programa ha llegado a un punto en el que todo se ha desarrollado correctamente y se utiliza cualquier otro valor para indicar que ha habido algún tipo de error.
La instrucción return
no es una función, se trata de una sentencia que lo que hace es retornar como valor de la función el valor que se le proporciona como argumento.
Argumentos
Las funciones también pueden recibir argumentos o parámetros, para modificar su comportamiento. Por ejemplo, la definición de una función para sumar dos números sería de la siguiente manera:
#include <stdio.h>
int sumar(int numero1, int numero2)
{
return numero1 + numero2;
}
int main(void)
{
int suma = sumar(5, 3); /* Se ejecuta correctamente*/
printf("La suma es: %d ", suma);
return 0;
}
Declaración y definición
En el ejemplo anterior podemos notar que la función sumar
figura en el código antes que main
. ¿Qué pasaría si las escribiéramos en distinto orden?
#include <stdio.h>
int main(void)
{
int suma = sumar(5, 3); /* ERROR, sumar no ha sido declarada aún */
printf("La suma es: %d ", suma);
return 0;
}
int sumar(int numero1, int numero2)
{
return numero1 + numero2;
}
En este caso el programa es erróneo y no compila, ya que en la línea donde se llama a la función sumar
, el compilador aún no conoce ninguna función con ese nombre, y cuáles son sus argumentos y valor de retorno.
Una posible solución es declarar el prototipo de la función al principio, para informar al compilador que existe, y luego definir el cuerpo de la misma en cualquier lugar del programa:
#include <stdio.h>
/* Declaración */
int sumar(int numero1, int numero2);
int main(void)
{
int suma = sumar(5, 3);
printf("La suma es: %d ", suma);
return 0;
}
/* Definición */
int sumar(int numero1, int numero2)
{
return numero1 + numero2;
}
Paso de Parámetros
Las funciones pueden recibir datos como lo hemos observado, pero existen dos formas de enviar los datos hacia una función por valor y por referencia, las cuales modifican en diferente forma el comportamiento del programa.
Por Valor
El paso por valor envía una copia de los parámetros a la función por lo tanto
los cambios que se hagan en ella no son tomados en cuenta dentro de la función
main()
. Ejemplo:
/*
* por_valor.c
*
* Julio César Brizuela <brizuelaalvarado@gmail.com> 2009
*
* para el wikilibro "Programación en C"
* bajo licencia FDL, adaptado del Dominio Público
*/
#include <stdio.h>
void sumar_valor(int numero); /* prototipo de la función */
int main(void)
{
int numero = 57; /* definimos numero con valor de 57*/
sumar_valor(numero); /* enviamos numero a la función */
printf("Valor de numero dentro de main() es: %d\n", numero);
/* podemos notar que el valor de numero se modifica
* sólo dentro de la función sumar_valor pero en la principal
* número sigue valiendo 57
*/
return 0;
}
void sumar_valor(int numero)
{
numero++; /* le sumamos 1 al numero */
/* el valor de número recibido se aumenta en 1
* y se modifica dentro de la función sumar_valor()
*/
printf("Valor de numero dentro sumar_valor() es: %d\n", numero);
return;
}
Por Referencia
El paso por referencia se hace utilizando apuntadores. Se envía la dirección de memoria de la variable, por lo tanto los cambios que haga la función si afectan el valor de la variable. Ejemplo:
/*
* por_referencia.c
*
* Julio César Brizuela <brizuelaalvarado@gmail.com> 2009
*
* para el wikilibro "Programación en C"
* bajo licencia FDL, adaptado del Dominio Público
*/
#include <stdio.h>
void sumar_referencia(int *numero); /* prototipo de la función */
int main(void)
{
int numero = 57; /* definimos numero con valor de 57*/
sumar_referencia(&numero); /* enviamos numero a la función */
printf("\nValor de numero dentro de main() es: %d ", numero);
/* podemos notar que el valor de numero se modifica
* y que ahora dentro de main() también se ha modificado
* aunque la función no haya retornado ningún valor.
*/
return 0;
}
void sumar_referencia(int *numero)
{
*numero += 1; /* le sumamos 1 al numero */
/* el valor de numero recibido se aumenta en 1
* y se modifica dentro de la función
*/
printf("\nValor de numero dentro sumar_referencia() es: %d", *numero);
return;
}
Variables Locales y Globales
Además de pasar valores a una función, también se pueden declarar tipos de datos dentro de las funciones, estos tipos de datos declarados dentro de una función solo son accesibles dentro de esta misma función y se les conocen como variables locales, así pues podemos definir los mismos nombres de variables en diferentes funciones, ya que estas variables solo son accesibles dentro de esas funciones. Ejemplo:
/*
* locales.c
*
* Julio César Brizuela <brizuelaalvarado@gmail.com> 2009
*
* para el wikilibro "Programación en C"
* bajo licencia FDL, adaptado del Dominio Público
*/
#include <stdio.h>
void funcion1()
{
int dato = 53; /* definimos dato en 53*/
char num1 = 'a'; /* num1 vale a */
/* imprimimos */
printf("Funcion1, dato=%d, num1=%c\n", dato, num1);
return;
}
void funcion2()
{
int dato = 25; /* definimos dato en 25*/
char num2 = 'z'; /* num2 vale z*/
/* imprimimos */
printf("Funcion2, dato=%d, num2=%c\n", dato, num2);
return;
}
int main(void)
{
funcion1(); /* llamamos a funcion1() */
funcion2(); /* llamamos a funcion2() */
return 0;
}
En este caso la variable dato, esta definida dentro de cada una de las funciones y son totalmente distinta una de otra y no se puede utilizar fuera de esta, así pues num2 no puede ser utilizada por la funcion1() y num1 tampoco puede ser utilizada por funcion2().
Existen pues variables que se definen fuera de la función principal main() y fuera de cualquier otra función creada por nosotros, estas variables se les conoce con el nombre de Variables Globales ya que se pueden utilizar dentro de main() y dentro de cualquier función creada por nosotros. Ejemplo:
/*
* global.c
*
* Julio César Brizuela <brizuelaalvarado@gmail.com> 2009
*
* para el wikilibro "Programación en C"
* bajo licencia FDL, adaptado del Dominio Público
*/
#include <stdio.h>
int variable_global = 99; /* inicializamos la variable global */
void funcion();
int main(void)
{
/* imprimimos el valor*/
printf("main(), acceso a variable_global %d\n", variable_global);
/* llamamos a la función */
funcion();
return 0;
}
void funcion()
{
/* imprimimos el valor*/
printf("funcion(), acceso a variable_global %d\n", variable_global);
return;
}
Funciones Recursivas
La recursividad (recursión) es la propiedad por la cual una función se llama a sí misma directa o indirectamente. La recursión indirecta implica utilizar más de una función.
Se puede considerar la recursividad como una alternativa a la iteración. La recursión permite especificar soluciones naturales, sencillas, que serían, en caso contrario, difíciles de resolver. Toda función recursiva debe contemplar un caso base o condición de salida, para terminar, o la recursividad no podrá terminar nunca.
Una función recursiva podría definirse así:
funcion_recursiva( /* parámetros recibidos por la función */ )
{
/* Código */
funcion_recursiva( ); /* llamada a la función misma */
/* Código */
}
Uno de los ejemplos más representativos en la recursividad es el factorial de un numero ( n! ):
la definición de recursividad del factorial es:
En esta definición, n = 0, es nuestro caso base, que le da fin a la recursividad.
Entonces nuestro programa que calcula el factorial es:
/*
*factorial.c
*
* Julio César Brizuela <brizuelaalvarado@gmail.com> 2009
*
* para el wikilibro "Programación en C"
* bajo licencia FDL, adaptado del Dominio Público
*/
#include <stdio.h>
long factorial(int n)
{
if (n == 0) /* caso base */
return 1; /* como 0! = 1, se retorna 1*/
else
return n * factorial (n - 1); /* llamada a esta misma función */
}
int main(void)
{
/* en este caso se llama a la función y se imprime directamente*/
printf("%ld ", factorial(5));
return 0;
}
También existen otros tipos de funciones recursivas como lo es el producto de dos números. El producto de a b, donde a y b son números enteros positivos seria:
Solución iterativa:
Solución recursiva:
Así pues es:
Podemos ver que la multiplicación de dos números a, b se puede transformar en otro problema más pequeño multiplicar a por (b-1), el caso base se produce cuando b = 0 y el producto es 0. Ejemplo:
/*
* producto.c
*
* Julio César Brizuela <brizuelaalvarado@gmail.com> 2009
*
* para el wikilibro "Programación en C"
* bajo licencia FDL, adaptado del Dominio Público
*/
#include <stdio.h>
int producto(int a, int b)
{
if (b == 0) /* caso base */
return 0; /* como b = 0, se retorna 0*/
else
return a + producto (a, b - 1); /* llamada a esta misma función */
}
int main(void)
{
/* en este caso se llama a la función y se imprime directamente*/
printf("%i ", producto( 7, 3));
return 0;
}
Recursividad indirecta o recursión mutua
Esta se produce cuando una función llama a otra, que esta a su vez terminará llamando de nuevo a la primera función. El siguiente programa visualiza el alfabeto utilizando recursión indirecta o mutua:
/*
* elalfabeto.c
*
* Julio César Brizuela <brizuelaalvarado@gmail.com> 2009
*
* para el wikilibro "Programación en C"
* bajo licencia FDL, adaptado del Dominio Público
*/
#include <stdio.h>
void funcionA(char c); /* se declara el prototipo de la función para que el llamado */
void funcionB(char c); /* a la misma en la función no sea implícita */
int main(void)
{
funcionA('z'); /* llamado a funcionA */
return 0;
}
void funcionA(char c)
{
if (c > 'a') /* caso base mientras c no sea menor que A */
funcionB(c); /* llamado a la funcionB */
printf("%c ", c); /* imprimimos el valor de c */
*la variable es un parametro no utilizado para este proceso
}
void funcionB(char c)
{
funcionA(--c); /* llamado a la funcionA decrementando el valor de 'z' */
}
Recursión versus Iteración
Tanto la iteración como la recursión se basan en estructura de control: la iteración utiliza una estructura repetitiva y la recursión una estructura de selección. La iteración utiliza explícitamente una estructura repetitiva mientras que la recursión consigue la repetición mediante llamadas repetitivas a funciones.
La iteración termina si la condición del bucle no se cumple, mientras que la recursión termina cuando se reconoce un caso base.
La recursión puede presentar desventajas ante la iteración ya que se invoca repetidas veces al mecanismo de llamada de funciones y se necesita un tiempo mayor para realizar cada llamada.
La razón por la cual se puede elegir u optar por usar recursividad es que existen muchos problemas complejos que poseen naturaleza recursiva y, en consecuencia, son mas fáciles de implementar.
Ejemplo Iterativo
/*
* iterativo.c
*
* Julio César Brizuela <brizuelaalvarado@gmail.com> 2009
*
* para el wikilibro "Programación en C"
* bajo licencia FDL, adaptado del Dominio Público
*/
#include <stdio.h>
long factorial(int numero);
int main(int argc, char** argv)
{
int contador = 0;
/* calcula el factorial de 0 a 10 */
for ( contador = 0; contador <= 10; contador++ )
printf("%d! = %ld\n", contador, factorial( contador ));
return 0;
}
/* funcion factorial iterativa */
long factorial( int numero )
{
long resultado = 1;
int i = 0;
/* declaracion de la función factorial iterativa */
for ( i = numero; i >= 1; i-- )
resultado *= i;
return resultado;
}
Ejemplo Recursivo
/*
* recursivo.c
*
* Julio César Brizuela <brizuelaalvarado@gmail.com> 2009
*
* para el wikilibro "Programación en C"
* bajo licencia FDL, adaptado del Dominio Público
*/
#include <stdio.h>
long factorial(int numero);
int main(int argc, char** argv)
{
int contador = 0;
/* calcula el factorial de 0 a 10 */
for ( contador = 0; contador <= 10; contador++ )
printf("%d! = %ld\n", contador, factorial( contador ));
return 0;
}
/* función factorial recursiva */
long factorial( int numero )
{
if ( numero <= 0 ) /* caso base */
return 1; /* casos bases: 0! = 1 y 1! = 1 */
else /* llamada recursiva */
return numero * factorial( numero - 1 ); /* llamada a la función factorial */
}
Vectores
Los arreglos son una forma de almacenar datos que permiten contener una serie de valores del mismo tipo, cada uno de los valores contenidos tiene una posición asociada que se usará para accederlos. Está posición o índice será siempre un número entero positivo.
En C la cantidad de elementos que podrá contener un arreglo es fijo, y en principio se define cuando se declara el arreglo. Los arreglos se pueden declarar de la siguiente forma:
tipo_elemento nombre[largo];
Esto declara la variable nombre como un arreglo de tipo_elementos que podrá contener largo cantidad de elementos, y cada uno de estos elemento podrá contener un valor de tipo tipo_elemento.
Por ejemplo:
double valores[128];
En este ejemplo declaramos un arreglo de 128 elementos del tipo double, los índices de los elementos irían entre 0 (para el primer elemento y 127 para el último).
De la misma forma que con las otras declaraciones de variables que hemos visto se le puede asignar un valor iniciar a los elementos.
O también se pueden declarar:
tipo_elemento nombre[largo]={valor_0, valor_1, valor_2};
En caso estamos asignadole valores a los primeros 3 elementos del arreglo nombre. Notar que largo debe ser mayor o igual a la cantidad de valores que le estamos asignando al arreglo, en el caso de ser la misma cantidad no aporta información, por lo que el lenguaje nos permite escribir:
tipo_elemento nombre[]={valor_0, valor_1, valor_2};
Que declarará nombre como el arreglo de largo 3.
Para acceder a un elemento accederemos a través de su posición. Es decir:
tipo_elemento elemento;
...
elemento = nombre[2];
Asumiendo que tenemos el arreglo anterior definido estaríamos guardando valor_2 en elemento.
Veamos algunos ejemplos:
/*
* Ejemplo : El producto escalar de dos vectores
*/
#include <stdio.h>
double producto_escalar(double v1[], double v2[], int d);
int main()
{
const int largo = 3;
double vector_1[] = {5,1,0};
double vector_2[] = {-1,5,3};
double resultado = producto_escalar(vector_1, vector_2, largo);
// imprime el resultado
printf("(%f, %f, %f) . (%f, %f, %f) = %f\n",
vector_1[0], vector_1[1], vector_1[2],
vector_2[0], vector_2[1], vector_2[2],
resultado);
return 0;
}
/* producto escalar entre dos arreglos */
double producto_escalar(double v1[], double v2[], int d)
{
double resultado = 0;
int i;
for (i=0; i < d; i++) {
resultado += v1[i] * v2[i];
}
return resultado;
}
En el ejemplo anterior usamos los arreglos de C para representar vectores matemáticos y calcular el producto escalar entre ellos. Una peculiaridad que se puede notar es que al recibir un arreglo en una función no se especifica el largo, volveremos a esto en un capítulo posterior.
Otra función clásica es la búsqueda de un máximo o mínimo, que podemos escribirla de la siguiente manera:
int buscar_maximo(double valores[], int num_valores)
{
int maximo_pos = 0;
for (int i = 1; i < num_valores; i++) {
if (valores[i] > valores[maximo_pos]) {
maximo_pos = i;
}
}
return maximo_pos;
}
Otro ejemplo sencillo, calcular el promedio de los valores.
double promedio(double valores[], int largo)
{
double suma=0;
for (int i=0;i<largo;i++) {
suma+=valores[i];
}
return suma/largo;
}
Cuando una función recibe un arreglo por parámetro y cambia su contenido y el cambio es permanente (se ve aún fuera de la función). Esto puede parecer extraño después del énfasis que pusimos en resaltar que todos los parámetros de una función se reciben por valor, pero se aclarará en el siguiente capitulo.
Mientras tanto usemos esto para definir una función que le aplique otra función que recibe por parámetro a cada elemento del arreglo, guardando el resultado en el mismo arreglo y una llamada de ejemplo a esta.
void cuadrados(double arreglo[], int largo)
{
for (int i=0;i<largo;i++) {
arreglo[i]=cuadrado(arreglo[i]);
}
}
...
double cuadrado(double valor) {
return valor*valor;
}
...
cuadrados(elementos,num_elem);
...
De la misma forma que venimos usando arreglos de tipos básicos, podemos tener arreglos de arreglos, estos se declaran de la siguiente forma:
int matriz[3][7];
int tabla[3][4]={ { 1, 2, 3, 4},
{ 5, 6, 7, 8}, /* los espacios y saltos de líneas no son tomados en cuenta */
{ 9,10,11,12} };
double v[2][2][2];
...
printf("tabla[0][3]: %i\n", tabla[0][3]); // Imprime 4
printf("tabla[2][0]: %i\n", tabla[2][0]); // Imprime 9
...
En este ejemplo tabla es un arreglo de longitud 3, cuyos elementos son arreglos de longitud 4 de elementos de tipo int.
En resumen, suponiendo que v[n] es un arreglo de cualquier tipo de dato con n cantidad de posiciones, al arreglo v se le aplican las siguientes reglas:
- La primera posición siempre será v[0]
- La última posición es v[n-1]
- En versiones previas a C99 n es una constante definida antes de la declaración de v[n]