Administración de sistemas informáticos
Lenguaje C
TEMA 3- LENGUAJE C
1. INTRODUCCIÓN
Historia de C
En 1969, Dennis Ritchie creó el lenguaje C a partir de las ideas diseñadas por otro lenguaje llamado B inventado por Ken Thompson.
Ritchie lo inventó para programar la computadora PDP-11 que utilizaba el sistema operativo UNIX.
De hecho la historia de C está muy ligada a la de UNIX, este sistema siempre ha incorporado compiladores para trabajar en C. El lenguaje C se diseñó como lenguaje pensado para programar sistemas operativos, debido a sus claras posibilidades para ello. Pero su éxito inmediato hizo que miles de programadores en todo el mundo lo utilizaran para crear todo tipo de aplicaciones (hojas de cálculo, bases de datos…).Debido a la proliferación de diferentes versiones de C, en 1983 el organismo ANSI empezó a producir un C estándar para normalizar su situación.
En 1989 aparece el considerado como C estándar que fue aceptado por ISO, organismo internacional de estándares. Actualmente éste C es el universalmente aceptado (aunque han aparecido nuevos estándares de ISO en estos últimos años). Actualmente se sigue utilizando para la creación de aplicaciones y en educación se considera el lenguaje fundamental para aprender a programar.
Características de C
Se dice que el lenguaje C es un lenguaje de nivel medio.
Esto es porque en C se pueden crear programas que manipulan la máquina casi como lo hace el lenguaje Ensamblador, pero utilizando una sintaxis que se asemeja más a los lenguajes de alto nivel.
De los lenguajes de alto nivel toma las estructuras de control que permiten programar de forma estructurada.
Al tener características de los lenguajes de bajo nivel se puede tomar el control absoluto del ordenador.
Sus características básicas son:
-
Puede manejar estructuras de bajo nivel (punteros, bits)
-
Es un lenguaje estructurado y modular. Lo que facilita su compresión y escritura.
-
Permite utilizar todo tipo de estructuras de datos complejas (arrays, pilas, colas, textos,...)
-
Es un lenguaje compilado.
-
Permite crear todo tipo de aplicaciones
Proceso de compilación de un programa.
Un programa escrito en un lenguaje de programación, no puede ser ejecutado directamente por un ordenador, sino que debe ser traducido a lenguaje máquina.
Las etapas por las que debe pasar un programa escrito en un lenguaje de programación, hasta poder ser ejecutable son:
Programa fuente Texto que contiene las sentencias del programa en el lenguaje de programación.
Compilador Programa encargado de traducir los programas fuentes a lenguaje máquina y de comprobar que las llamadas a funciones de librería se realizan correctamente.
Programa objeto Es el programa fuente traducido por el compilador a código máquina. Aún no es directamente ejecutable.
Programa ejecutable Traducción completa a código máquina, y que ya es directamente ejecutable.
Linker Es el programa encargado de insertar al programa objeto el código máquina de las funciones de librerías usadas en el programa y realizar el proceso de montaje, que producirá un programa ejecutable (.exe)
Desde el punto de vista del usuario de estos entornos, sólo hay dos pasos: compilar (conseguir el código ejecutable) y ejecutar.
2. ESTRUCTURA DE UN PROGRAMA EN C
Un programa en C consta de una o más funciones, las cuales están compuestas de diversas sentencias o instrucciones.
Una sentencia indica una acción a realizar por parte del programa.
Una función no es más que (por ahora) un nombre con el que englobamos a las sentencias que tiene dentro para poder invocarlas mediante dicho nombre.
tipodevuelto nombreFunción (parámetros)
{
sentencias
}
Los símbolos { y } indican el inicio y el final de la función.
Esos símbolos permiten delimitar bloques en el código.
La única función que debe estar OBLIGATORIAMENTE presente es la función main, siendo la primera función que es llamada cuando empieza la ejecución del programa.
Como a veces las funciones se almacenan en archivos externos, necesitamos incluir esos archivos en nuestro código mediante una sentencia especial include, que en realidad es una directiva de preprocesador o sea es una instrucción para el compilador.
El uso es:
#include <cabeceraDeArchivoExterno>
La directiva include permite indicar un archivo de cabecera en el que estará incluida la función que utilizamos.
En el lenguaje C estándar los archivos de cabecera tienen extensión h. Por tanto, los archivos de cabecera son los que permiten utilizar funciones externas (o librerías) en nuestro programa.
Una de las librerías más utilizadas en los programas, es la que permite leer y escribir en la consola del Sistema Operativo. En el caso de C esta librería está disponible al incluir la cabecera stdio.h (standard input output)
La estructura general de un programa en C será:
instrucciones del preprocesador
declaraciones globales
tipo_devuelto main (lista de parámetros)
{
secuencia de sentencias
}
tipo_devuelto funcion_1 (lista de parámetros)
{
secuencia de sentencias
}
tipo_devuelto función_2 (lista de parámetros)
{
secuencia de sentencias
}
…
…
tipo_devuelto función_n(lista de parámetros)
{
secuencia de sentencias
}
Ejemplo 0: Programa HOLA MUNDO
El primer ejemplo que veremos será sencillo, sólo tenemos que escribir la frase HOLA MUNDO en la pantalla.
/* Ejemplo 0*/
#include <stdio.h>
main ()
{
printf ("HOLA MUNDO”);
}
Todo lo que está entre /* y */ será ignorado por el compilador, puesto que el compilador lo toma como un comentario.
3. ELEMENTOS DE UN PROGRAMA EN C
Comentarios
Es texto que es ignorado por el compilador al traducir el código.
Se utilizan para documentar el programa.
La labor de documentación es fundamental.
Si no comentamos los programas el código se convierte en ilegible incluso para nosotros mismos al cabo del tiempo.
Tan importante como saber escribir sentencias es utilizar los comentarios.
Todavía es más importante cuando el código va a ser tratado por otras personas, de otro modo una persona que modifique el código de otra lo tendrá muy complicado para entender el programa.
En C los comentarios se delimitan o bien entre los símbolos /* y */
/* Este es un comentario que
ocupa más de una línea */
o bien mediante //.
// Este comentario ocupa una sola línea
En el caso de comentarios de una sola línea no hay indicador de fin de comentario.
Sentencias
Los programas en C se basan en sentencias las cuales siempre se incluyen dentro de una función.
De momento todas las sentencias van a estar dentro de la función main.
A está función le puede preceder la palabra int.
Si le precede la palabra int, al final del programa tendremos que poner return 0 y si no, no es necesario que “retorne” nada.
Al escribir sentencias hay que tener en cuenta las siguientes normas:
-
Toda sentencia en C termina con el símbolo "punto y coma" (;)
-
Los bloques de sentencias empiezan y terminan delimitados con el símbolo de llave.
{ significa inicio y
} significa fin
-
En C hay distinción entre mayúsculas y minúsculas. No es lo mismo main que MAIN. Todas las palabras claves de C están en minúsculas. Los nombres que pongamos nosotros también conviene ponerles en minúsculas ya que el código es mucho más legible así (y se es más coherente con el lenguaje)
Palabras reservadas
Se llaman así a palabras que en C tienen un significado concreto para los compiladores. No se pueden usar esas palabras para poner nombre a variables o a funciones.
La lista de palabras reservadas del lenguaje C es ésta:
auto | double | int | struct |
break | else | long | switch |
case | enum | register | typedef |
char | extern | return | union |
const | float | short | unsigned |
continue | for | signed | void |
default | goto | sizeof | volatile |
do | if | static | while |
Identificadores
Son los nombres que damos a las variables y a las funciones de C.
Ya hemos visto que no pueden coincidir con las palabras reservadas. Además puesto que C distingue entre las mayúsculas y las minúsculas, hay que tener cuidado de usar siempre las minúsculas y mayúsculas de la misma forma (es decir, nombre, Nombre y NOMBRE son tres identificadores distintos).
El límite de tamaño de un identificador es de 32 caracteres (aunque algunos compiladores permiten más tamaño).
Los identificadores deben de cumplir estas reglas:
-
Deben comenzar por una letra o por el signo de subrayado (aunque comenzar por subrayado se suele reservar para identificadores de funciones especiales del sistema).
-
Sólo se admiten letras del abecedario inglés (no se admite ni la ñ ni la tilde ni la diéresis por ejemplo), números y el carácter de subrayado (el guión bajo _)
Directivas de compilación
Las sentencias que comienzan con el símbolo “#” y que no finalizan con el punto y coma son directivas de compilación o líneas de preprocesador.
Ya hemos visto que son indicaciones para el preprocesador, pero que en el lenguaje C son muy importantes.
La más utilizada es #include que permite utilizar funciones externas en nuestro código, para lo que se indica el nombre del archivo de cabecera que incluye a la función (o funciones) que queremos utilizar.
Variables
Las variables sirven para identificar un determinado valor que se utiliza en el programa. A una variable se le pueden asignar diferentes valores en distintas partes de un programa. Sin embargo, el tipo de datos asociado a la variable NO PUEDE CAMBIAR.
En C es importante el hecho de tener en cuenta que una variable se almacena en la memoria del ordenador y por lo tanto ocupará una determinada posición en esa memoria.
Ejemplo: edad es un identificador que se refiere a una variable entera que en este instante vale 18. Esto, en realidad significa que hemos reservado un “trocito” de memoria en la que ahora se encuentra el número 18.
Declaración de variables
En C hay que declarar las variables antes de poder usarlas.
Al declarar lo que ocurre es que se reserva en memoria el espacio necesario para almacenar el contenido de la variable.
No se puede utilizar una variable sin declarar.
Para declarar una variable se usa la siguiente sintaxis:
tipo identificador;
Por ejemplo: int x;
Se declarara x como variable que podrá almacenar valores enteros.
En C se puede declarar una variable en cualquier parte del código, basta con declararla antes de utilizarla por primera vez. Pero es muy buena práctica hacer la declaración al principio del bloque o función en la que se utilizará la variable. Esto facilita la comprensión del código.
Nosotros haremos la declaración de las variables al principio del bloque o función donde se va a usar.
También es buena práctica poner un pequeño comentario a cada variable para indicar para qué sirve. A veces basta con poner un identificador claro a la variable.
Identificadores como a, b o c por ejemplo, no aclaran la utilidad de la variable; no indican nada.
Identificadores como edad, horas, nota,... son mucho más significativos.
Tipos de datos
Al declarar variables se necesita indicar cuál es el tipo de datos que va a almacenar.
En el lenguaje C existen 5 tipos básicos: carácter, entero, coma flotante, coma flotante de doble precisión y vacío.
El tipo void servirá para declarar una función que no devuelve nada o para crear punteros genéricos (esto lo veremos más adelante)
El tamaño y rango de estos tipos de datos pueden variar dependiendo del compilador.
TIPO DE DATOS | RANGO DE VALORES POSIBLES | TAMAÑO EN BITS | |
char | 0 a 255 (o caracteres simples del código ASCII) | 8 | |
int | -32768 a 32767 | 16 | |
float | 3.4E-38 a 3.3E+38 | 32 | |
double | 1.7E-308 a 1.7E+308 | 64 | |
void | sin valor | 0 |
Estos rangos son los clásicos, pero en la práctica los rangos (sobre todo el de los enteros) depende del ordenador, procesador o compilador empleado.
Tipos enteros
Los tipos char e int sirven para almacenar enteros y también valen para almacenar caracteres.
Normalmente los números se almacenan en el tipo int y los caracteres en el tipo char; pero la realidad es que cualquier carácter puede ser representado como número (ese número indica el código en la tabla ASCII). Así el carácter A y el número 65 significan exactamente lo mismo en lenguaje C.
Tipos decimales
En C los números decimales se representan con los tipos float y double.
La diferencia no solo está en que en el double quepan números más altos, sino en la precisión. Ambos tipos son de coma flotante.
Se conoce como coma flotante a una técnica de almacenamiento de números decimales en notación binaria, muy utilizada en todo tipo de máquinas electrónicas.
En este estilo de almacenar números decimales, los números decimales no se almacenan de forma exacta. La razón de esta inexactitud reside en la incompatibilidad del formato binario con el decimal.
Por ello cuantos más bits se destinen para almacenar los números, más precisión tendrá el número.
El tipo float tiene 6 dígitos de precisión y el double 10, por eso si queremos más precisión, es más conveniente usar el tipo double aunque ocupe más memoria.
Tipos lógicos
En C estándar el uso de valores lógicos se hace mediante los tipos enteros.
Normalmente el uno significa verdadero y el cero significa falso.
Ej: int x;
x=(18>6);
printf("%d",x); /*Escribe 1, es decir verdadero */
En C++ se añadió el tipo bool (booleano o lógico) para representar valores lógicos, las variables bool pueden asociarse a los números cero y uno, o mejor a las palabras true (verdadero) y false (falso).
En C para conseguir el mismo efecto se utilizan trucos mediante la directiva #define cuyo uso se explica más adelante.
Modificadores de tipos
A los tipos anteriores se les puede añadir una serie de modificadores para que esos tipos varíen su funcionamiento. Esos modificadores se colocan por delante del tipo en cuestión. Son:
-
signed. Permite que el tipo modificado admita números negativos. En la práctica se utiliza sólo para el tipo char, que de esta forma puede tomar un rango de valores de -128 a 127. En los demás no se usan ya que todos admiten números negativos.
-
unsigned. Contrario al anterior, hace que el tipo al que se refiere use el rango de negativos para incrementar los positivos.
Por ejemplo el unsigned int tomaría el rango 0 a 65535 en lugar de -32768 a 32767
-
long. Aumenta el rango del tipo al que se refiere.
Por ejemplo un long int, ocuparía 32 bits en lugar de 16 que ocupa el int y su rango sería de -2147483647 a 2147483647 en lugar de -32767 a 32767 del int.
-
short. El contrario del anterior. La realidad es que no se utiliza mucho.
Las combinaciones que más se suelen utilizar son:
TIPO DE DATOS | RANGO DE VALORES POSIBLES | TAMAÑO EN BITS |
signed char | -128 a 127 | 8 |
unsigned int | 0 a 65335 | 16 |
long int (o long a secas) | -2147483648 a 2147483647 | 32 |
long double | 3.37E-4932 a 3,37E+4932 | 80 |
Asignación de valores
A las variables se las pueden asignar valores. El operador de asignación en C es el signo “=”.
Ejemplo: x=3;
Si se utiliza una variable antes de haberla asignado un valor, ocurre un error. Pero es un error que no es detectado por el compilador.
Por ejemplo:
#include <stdio.h>
int main()
{
int a;
printf("%d",a);
return 0;
}
Este código no produce error, pero como a la variable a no se le ha asignado ningún valor, el resultado del printf es un número sin sentido. Ese número representa el contenido de la zona de la memoria asociada a la variable a, pero el contenido de esa zona no le hemos preparado, por ello el resultado será absolutamente aleatorio.
Se puede declarar e inicializar valores a la vez:
int a=5;
E incluso inicializar y declarar varias variables a la vez:
int a=8, b=7, c;
Literales
Cuando una variable se asigna a valores literales (17, 2.3,etc.) hay que tener en cuenta lo siguiente:
-
Los números se escriben tal cual (17, 34, 39)
-
El separador de decimales es el punto (123.6 se interpreta como 123 coma 6)
-
Si un número entero se escribe anteponiendo un cero, se entiende que está en notación octal. Si el número es 010, C entiende que es el 8 decimal
-
Si un número entero se escribe anteponiendo el texto 0x (cero equis), se entiende que es un número hexadecimal. El número 0x10 significa 16.
-
En los números decimales se admite usar notación científica. Así 1.23e+23 significa 1,23 * 1023
-
Los caracteres simples van encerrados entre comillas simples, por ejemplo `a'
-
Los textos (strings) van encerrados entre comillas dobles. Por ejemplo “Hola”
Secuencias de escape
Hay que tener en cuenta que hay una serie de caracteres que son especiales. Por ejemplo si queremos almacenar en una variable char el símbolo de la comilla simple, no podríamos ya que ese símbolo está reservado para delimitar caracteres.
Es decir: char a=''';/*Esto nos dará un error*/
Para resolver este problema (y otros) se usan los caracteres de escape, que representan caracteres especiales. Todos comienzan con el signo “\” (backslash) seguido de una letra minúscula, son:
CÓDIGO | SIGNIFICADO |
\a | Alarma (beep del ordenador) |
\b | Retroceso |
\n | Nueva línea |
\r | Retorno de carro |
\t | Tabulador |
\' | Comilla simple |
\“ | Comilla doble |
\\ | Barra inclinada invertida o backslash |
Ejemplo, para escribir una comilla tendré que poner:
printf(“\' He escrito una comilla”);
Ámbito de las variables
Las variables tienen un ciclo de vida:
1) En la declaración se reserva el espacio necesario para que se puedan comenzar a utilizar (digamos que se avisa de su futura existencia)
2) Se la asigna su primer valor (la variable nace)
3) Se la utiliza en diversas sentencias (no se puede utilizar su contenido sin haberla asignado ese primer valor.
4) Cuando finaliza el bloque en el que fue declarada, la variable muere. Es decir, se libera el espacio que ocupa esa variable en memoria. No se la podrá volver a utilizar.
Toda variable tiene un ámbito. Esto es la parte del código en la que una variable se puede utilizar.
Variables locales
Son variables que se crean dentro de un bloque (se entiende por bloque, el código que está entre { y }). Cuando acaba el bloque la variable es eliminada.
La mayoría de variables que usemos serán locales a una determinada función, es decir sólo se pueden utilizar dentro de esa función.
Ejemplo:
void func1()
{
int x;
x=5;
}
void func2()
{
int x;
x=300;
}
En ambas funciones se usa x como nombre de una variable local pero son dos variables distintas, aunque con el mismo nombre.
No podríamos usar ni saber el valor que tiene x (de func1) dentro de la función func2 porque estamos fuera de su ámbito.
Otro ejemplo:
void func()
{
int a;
a=13;
{
int b;
b=8;
}/*Aquí muere b*/
a=b; /*¡Error! b está muerta*/
}/*Aquí muere a*/
En la línea a=b ocurre un error de tipo “Variable no declarada”, el compilador ya no reconoce a la variable b porque estamos fuera de su ámbito.
Variables globales
Son variables que se pueden utilizar en cualquier parte del código. Para que una variable sea global basta con declararla fuera de cualquier bloque. Normalmente se declaran antes de que aparezca la primera función:
#include <stdio.h>
int a=3; //La variable “a” es global
int main()
{
printf("%d",a);
return 0;
}
En C no se permite declarar en el mismo bloque dos variables con el mismo nombre. Pero sí es posible tener dos variables con el mismo nombre si están en bloques distintos.
Problema: Cuando se utiliza la variable surge una duda: ¿qué variable utilizará el programa, la más local o la más global? La respuesta es que siempre se toma la variable declarada más localmente.
Ejemplo:
#include <stdio.h>
int a=3;
int main()
{
int a=5;
{
int a=8;
printf("%d",a); //escribe 8. No hay error
}
printf("%d",a); //escribe 5. No hay error
return 0;
}
En el código anterior se han declarado tres variables con el mismo nombre (a).
Cuando se utiliza la instrucción printf para escribir el valor de a, la primera vez escribe 8, la segunda vez escribe 5 (ya que ese printf está fuera del bloque más interior). Es imposible acceder a las variables globales si disponemos de variables locales con el mismo nombre. Por eso no es buena práctica repetir el nombre de las variables.
Conversión de tipos
En muchos lenguajes no se pueden asignar valores entre variables que sean de distinto tipo. Por ejemplo, no se podría asignar a una variable char valores de tipo int.
En C no existe esta restricción, ya que es un lenguaje más libre. Pero esto provoca problemas porque por ejemplo el tipo de datos los entero (int) normalmente utilizan 2 o 4 bytes para almacenar valores mientras que el tipo de datos carácter (char) utiliza 1 o 2.
Ejemplo:
#include <stdio.h>
int main()
{
char a;
int b=300;
a=b;
printf("%d %d",a,b);
} /* Escribe el contenido de a y de b. Escribe 44 y 300 */
nº 300 en binario es 0000000100101100
nº 44 en binario es 00101100
En ese programa el contenido de a debería ser 300, pero como 300 sobrepasa el rango de las variables char, el resultado es 44. Es decir, no tiene sentido, esa salida está provocada por el hecho de perder ciertos bits en esa asignación.
En la conversión de double a float lo que ocurre normalmente es un redondeo de los valores para ajustarles a la precisión de los float.
Modificadores de acceso
Los modificadores son palabras que se colocan delante del tipo de datos en la declaración de las variables para variar su funcionamiento (al estilo de unsigned, short o long)
Modificador extern
Se utiliza al declarar variables globales e indica que la variable global declarada, en realidad se inicializa y declara en otro archivo. Ejemplo
Archivo 1 | Archivo 2 |
int x,y; int main() { x=12; y=6; } void funcion1(void) { x=7; } | extern int x,y; void func2(void) { x=2*y; } |
El segundo archivo utiliza las variables declaradas en el primero
Modificador auto
En realidad las variables toman por defecto este valor (luego no hace falta utilizarle). Significa que las variables se eliminan al final del bloque en el que fueron creadas.
Modificador static
Se trata de variables que no se eliminan cuando el bloque en el que fueron creadas finaliza. Así que si ese bloque (normalmente una función), vuelve a invocarse desde el código, la variable mantendrá el último valor anterior. Si se utiliza este modificador con variables globales, indica que esas variables sólo pueden utilizarse desde el archivo en el que fueron creadas.
Modificador register
Todos los ordenadores posee una serie de memorias de pequeño tamaño en el propio procesador llamadas registros. Estas memorias son mucho más rápidas pero con capacidad para almacenar muy pocos datos. Este modificador solicita que una variable sea almacenada en esos registros para acelerar el acceso a la misma. Se utiliza en variables char o int a las que se va a acceder muy frecuentemente en el programa (por ejemplo las variables contadoras en los bucles). Sólo vale para variables locales.
register int cont;
for (cont=1;cont<=300000;cont++)
{ ...
}
Modificador const. Constantes
Las variables declaradas con la palabra const delante del tipo de datos, indican que son constantes.
Las constantes no pueden cambiar de valor, el valor que se asigne en la declaración será el que permanezca (es obligatorio asignar un valor en la declaración).
Ejemplo: const float PI=3.141592;
Inicialmente no existía en C este modificador, fue añadido por ANSI en versiones más modernas de C.
En su lugar para las constantes, los programadores en C utilizaban la directiva #define. De hecho aún sigue siendo una directiva muy empleada. Su funcionamiento es el siguiente:
#define identificador valor
Este directiva (que se suele poner después de las directivas #include), hace que en la fase de preprocesado, se sustituya el identificador indicado por el valor correspondiente. Normalmente el identificador se pone en mayúsculas (es el uso más recomendado) para indicar que es una constante.
Ejemplo: #define PI 3.141592
Desde ese momento podremos utilizar el nombre PI, refiriéndonos al valor 3.141592 (¡OJO! que no se utiliza ningún signo de igualdad, =) En la fase de preprocesado (antes de compilar) todas las constantes definidas con la directiva #define, serán traducidas por el valor correspondiente (de ahí que no se consideren verdaderas constantes).
Modificador volatile
Se usa para variables que son modificadas externamente al programa (por ejemplo una variable que almacene el reloj del sistema).
Entrada y salida por consola
Función printf
La función printf permite escribir datos en la consola de salida (en la pantalla). Si lo que se desea sacar es un texto literal se utiliza de esta manera: printf(“Hola”);
Lógicamente a veces se necesitan sacar el contenido de las variables en la salida.
Para ello dentro del texto que se desea mostrar se utilizan unas indicaciones de formato, las cuales se indican con el signo ”%” seguido de una letra minúscula.
Por ejemplo:
int edad=22;
...
printf (“Tengo %d años”,edad); /*escribe tengo 22 años*/
El código %d sirve para recoger el valor de la variable que se indique entendiendo que esa variable tiene valores enteros.
Esa variable va separada del texto por una coma. Si se usan más variables o valores, éstos se separan con comas entre sí.
Ejemplo:
int edad=22;
edadsgte=23;
printf (“Tengo %d años, el año que viene %d”,edad,edadsgte);
/*Escribe: Tengo 22 años, el año que viene 23 */
Además del código %d, hay otros que se pueden utilizar:
código | significado |
%d | Entero |
%i | Entero |
%c | Un carácter |
%f | Punto flotante (para double o float) |
%e | Escribe en notación científica |
%g | Usa %e o %f, el más corto de los dos |
%o | Escribe números enteros en notación octal |
%x | Escribe números enteros en notación hexadecimal |
%s | Cadena de caracteres |
%u | Enteros sin signo |
%li o %ld | Enteros largos (long) |
%p | Escribe un puntero |
%% | Escribe el signo % |
Mediante printf, también se pueden especificar anchuras para el texto. Es una de sus características más potentes.
Para ello se coloca un número entre el signo % y el código numérico (i, d, f,...). Ese número indica anchura mínima.
Por ejemplo:
int a=127, b=8, c=76;
printf(“%4d\n”,a);
printf(“%4d\n”,b);
printf(“%4d\n”,c);
Escribiría:
127
8
76
En el ejemplo se usan cuatro caracteres para mostrar los números. Los números quedan alineados a la derecha en ese espacio, si se desean alinear a la izquierda, entonces el número se pone en negativo (%-4d).
Si se coloca un cero delante del número, el espacio sobrante se rellena con ceros en lugar de con espacios.
Ejemplo:
int a=127, b=8, c=76;
printf(“%04d\n”,a);
printf(“%04d\n”,b);
printf(“%04d\n”,c);
Escribiría:
0127
0008
0076
También se pueden especificar los decimales requeridos (para los códigos decimales, como f por ejemplo).
De esta forma el código %10.4f indica que se utiliza un tamaño mínimo de 10 caracteres de los que 4 se dejan para los decimales.
Función scanf
Esta función es similar a la anterior, pero ésta sirve para leer datos. Posee al menos dos parámetros, el primero es una cadena que indica el formato de la lectura (que se usa del mismo modo que en la función printf), el segundo es la zona de memoria en la que se almacenará el resultado.
Por ejemplo, para leer un número entero por el teclado y guardarlo en una variable de tipo int llamada a, se haría:
scanf(“%d”,&a);
El símbolo &, sirve para tomar la dirección de memoria de la variable a. Esto significa que el espacio de memoria reservado para la variable a en su declaración, ahora tiene el valor leído por teclado. O lo que es lo mismo, a contendrá el valor leído por el teclado
Ya veremos el significado del símbolo &, por ahora debemos saber que es absolutamente necesario su uso con la función scanf.
Veamos más ejemplos:
scanf (“%d %f”,&a,&b); /* Esta sentencia lee 2 números por teclado. */
El usuario tendrá que colocar un espacio entre cada número.
El primero se almacenará en a y el segundo (decimal) en b.
No obstante a la hora de leer por teclado es muy aconsejable leer sólo un valor cada vez.
scanf (“%d”,&edad);
El usuario introducirá un número que se almacenará en una variable llamada edad, que es de tipo entero.
Veamos un ejemplo:
/* Programa PIES.C */
#include <stdio.h>
main ()
{
int pies;
float metros;
printf ("\n¿Pies?: ");
scanf ("%d", &pies);
metros = pies * 0.3084;
printf ("\n%d pies equivalen a %f metros\n", pies, metros);
}
Veamos instrucción por instrucción lo que hace este programa:
float metros;
Es una sentencia declarativa que indica que se va a utilizar una variable llamada metros, que es del tipo float. Este tipo de dato se utiliza para declarar variables numéricas que pueden tener decimales.
printf ("\n¿Pies?: ");
Es la función printf() . Esta sentencia simplemente sitúa el cursor al principio de la siguiente línea (\n) y visualiza la cadena tal como aparece en el argumento.
scanf ("%d", &pies);
scanf() es una función de la biblioteca estándar de C (como printf()), que permite leer datos del teclado y almacenarlos en una variable. En el ejemplo, el primer argumento, %d, le dice a scanf() que tome del teclado un número entero. El segundo argumento, &pies, indica en qué variable se almacenará el dato leído. El símbolo & antes del nombre de la variable es necesario para que scanf() funcione correctamente.
metros = pies * 0.3084;
Se almacena en la variable metros el resultado de multiplicar la variable pies por 0.3084. El símbolo * es el operador que usa C para la multiplicación.
printf ("\n%d pies equivalen a %f metros\n", pies, metros);
Aquí printf() tiene 3 argumentos. El primero es la cadena de control, con dos códigos de formato: %d y %f. Esto implica que printf() necesita dos argumentos adicionales. Estos argumentos encajan en orden, de izquierda a derecha, con los códigos de formato. Se usa %d para la variable pies y %f para la variable metros.
printf ("\n%d pies equivalen a %f metros\n", pies, metros);
El código %f se usa para representar variables del tipo float.
Operadores
Como ya sabemos, hay varios tipos de operadores, y con ellos, en C podemos crear expresiones bastante complejas.
Operadores aritméticos
operador | significado |
+ | Suma |
- | Resta |
* | Producto |
/ | División |
% | resto (módulo) |
++ | Incremento |
-- | Decremento |
Hay que tener cuidado en que el resultado de aplicar las operaciones suma, resta, multiplicación y división puede ser un número que tenga un tipo diferente de la variable en la que se pretende almacenar el resultado.
No hay operador de potenciación en C. Pero hay una función de biblioteca (pow) que es la que realiza la potenciación. Para poder usarla hay que incluir la directiva math.h
El operador resto (%) requiere que los 2 operandos sean enteros y el segundo operando no nulo, por tanto, NO puede ser aplicado a variables del tipo float ó double.
La división de una cantidad entera por otra cantidad entera es denominada división entera. Esta operación siempre tiene como resultado el cociente entero truncado (es decir, se desprecia la parte decimal del cociente)
Si la división se hace entre dos números en coma flotante, o con un número en coma flotante y un entero, el resultado es un cociente en coma flotante, es decir, es la división real.
El signo “-” también sirve para cambiar de signo (-a es el resultado de multiplicar a la variable a por -1).
El incremento (++), sirve para añadir uno a la variable a la que se aplica. Es decir x++ es lo mismo que x=x+1.
El decremento funciona igual pero restando uno.
Se puede utilizar por delante (preincremento) o por detrás (postincremento) de la variable a la que se aplica (x++ ó ++x). Esto último tiene connotaciones.
Veamos un ejemplo:
int x1=9, x2=9;
int y1, y2;
y1=x1++;
y2=++x2;
printf(“%i\n”,x1); /*Escribe 10*/
printf(“%i\n”,x2); /*Escribe 10*/
printf(“%i\n”,y1); /*¡¡¡Escribe 9!!! */
printf(“%i\n”,y2); /*Escribe 10*/
La razón de que y1 valga 9, está en que la expresión y1=x1++, funciona de esta forma:
y1=x1;
x1=x1+1;
Mientras que en y2=++x2, el funcionamiento es:
x2=x2+1;
y2=x2;
Es decir en x2++ primero se asigno y luego se incremento. Si el incremento va antes se realiza al contrario.
Operadores relacionales
Son operadores que sirven para realizar comparaciones. El resultado de estos operadores es verdadero o falso (uno o cero). Los operadores son:
operador | significado |
> | Mayor que |
>= | Mayor o igual que |
< | Menor que |
<= | Menor o igual que |
== | Igual que |
!= | Distinto de |
Operadores lógicos
Permiten agrupar expresiones lógicas. Las expresiones lógicas son todas aquellas expresiones que obtienen como resultado verdadero o falso. Estos operadores unen estas expresiones devolviendo también verdadero o falso. Son:
operador | significado |
&& | Y (AND) |
|| | O (OR) |
¡ | NO (NOT) |
Ejemplo:
(18>6) && (20<30) devuelve verdadero (1) ya que la primera expresión (18>6) es verdadera y la segunda (20<30) también.
El operador Y (&&) devuelve verdadero cuando las dos expresiones son verdaderas.
El operador O (||) devuelve verdadero cuando cualquiera de las dos es verdadera.
Finalmente el operador NO (!) invierte la lógica de la expresión que le sigue; si la expresión siguiente es verdadera devuelve falso y viceversa.
Ejemplo !(18>15) devuelve falso (0).
Operadores de bits
Permiten realizar operaciones sobre los bits del número, o números, sobre los que operan. Es decir si el número es un char y vale 17, 17 en binario es 00010001. Estos operadores operan sobre ese código binario. En estos apuntes simplemente se indican estos operadores:
operador | significado |
& | AND de bits |
| | OR de bits |
~ | NOT de bits |
^ | XOR de bits |
>> | Desplazamiento derecho de los bits |
<< | Desplazamiento izquierdo de bits |
Operador de asignación
Ya hemos visto que el signo “=” sirve para asignar valores. Se entiende que es un operador debido a la complejidad de expresiones de C.
Por ejemplo:
int x=5,y=6,z=7;
x=(z=y++)*8;
printf("%d",x); //Escribe 48
En C existen estas formas abreviadas de asignación. Esto sirve como abreviaturas para escribir código.
Así la expresión:
x=x+10;
Se puede escribir como: x+=10;
Se permiten estas abreviaturas:
operador | significado |
+= | Suma y asigna |
-= | Resta y asigna |
*= | Multiplica y asigna |
/= | Divide y asigna |
%= | Calcula el resto y asigna |
Además también se permiten abreviar las expresiones de bit:
&=, |=, ^=, >>=, <<=
operador ?
Permite escribir expresiones condicionales.
Se usa de la siguiente forma:
Expresión_a_valorar?Si_verdadera:Si_falsa
Ejemplo:
x=(y>5?'A':'B');
Significa que si la variable y es mayor de 5, entonces a x se le asigna el carácter „A‟, sino se le asignará el carácter „B‟.
Otro ejemplo:
printf(“%s”,nota>=5?”Aprobado”:”Suspenso”);
En cualquier caso hay que utilizar este operador con mucho cuidado. Su dominio exige mucha práctica. Nosotros de momento para hacer este tipo de operaciones utilizaremos la instrucción condicional if.
Operadores de puntero & y *
Aunque ya se le explicará más adelante con detalle, conviene conocerle un poco. El operador & sirve para obtener la dirección de memoria de una determinada variable. No tiene sentido querer obtener esa dirección salvo para utilizar punteros o para utilizar esa dirección para almacenar valores (como en el caso de la función scanf).
El operador * también se utiliza con punteros. Sobre una variable de puntero, permite obtener el contenido de la dirección a la que apunta dicho puntero.
Operador sizeof
Este operador sirve para devolver el tamaño en bytes que ocupa en memoria una determinada variable.
Ejemplo:
int x=18;
printf(“%i”,sizeof x); /*Escribe 2 (o 4 en algunos compiladores)*/
Devuelve 2 o 4, dependiendo del gasto en bytes que hacen los enteros en la máquina y compilador en que nos encontremos.
Orden de los operadores
En expresiones como: x=6+9/3;
Podría haber una duda: ¿cuánto vale x?
Valdría 5 si primero se ejecuta la suma y 9 si primero se ejecuta la división.
La realidad es que valdría 9 porque la división tiene preferencia sobre la suma.
Es decir hay operadores con mayor y menor preferencia.
El orden de ejecución de los operadores se puede modificar con paréntesis.
Por ejemplo: x=(6+9/3; y=(x*3)/((z*2)/8);
Como se ve en el ejemplo los paréntesis se pueden anidar. Hay unos niveles de precedencia de ordenadores. Los operadores que estén en el mismo nivel significa que tienen la misma precedencia. En ese caso se ejecutan primero los operadores que estén más a la izquierda.
(1) ( )
(2) Lo forman los siguientes:
-
NOT de expresiones lógicas: !
-
NOT de bits: ~
-
Operadores de punteros: * &
-
Cambio de signo: -
-
sizeof
-
(cast)
-
Decremento e incremento: ++ --
(3) Aritméticos prioritarios: / * %
(4) Aritméticos no prioritarios (suma y resta): + -
(5) Desplazamientos: >> <<
(6) Relacionales sin igualdad: > < >= <=
(7) Relacionales de igualdad: == !=
(8) &
(9) ^
(10) |
(11) &&
(12) ||
(13) ?:
(14) = *= /* += -= %= >>= <<= |= &= ^=
Expresiones y conversión de tipos
Operadores, variables, constantes y funciones son los elementos que permiten construir expresiones. Una expresión es pues un código en C que obtiene un determinado valor (del tipo que sea).
Cuando una expresión utiliza valores de diferentes tipos, C convierte la expresión al mismo tipo. La cuestión es, qué criterio sigue para esa conversión.
El criterio, en general, es que C toma siempre el tipo con rango más grande. En ese sentido si hay un dato long double, toda la expresión se convierte a long double, ya que ese es el tipo más grande. Si no aparece un long double entonces el tipo más grande en el que quepan los datos. El orden de tamaños es:
(1) long double
(2) double
(3) float
(4) unsigned long
(5) long
(6) int
Es decir si se suma un int y un float el resultado será float.
En una expresión como: int x=9.5*2;
El valor 9.5 es double mientras que el valor 2 es int por lo que el resultado (19) será double. Pero como la variable x es entera, el valor deberá ser convertido a entero finalmente.
Operador de molde o cast
A veces se necesita hacer conversiones explícitas de tipos. Para eso está el operador cast. Este operador sirve para convertir datos.
Su uso es el siguiente, se pone el tipo deseado entre paréntesis y a la derecha el valor a convertir.
Ej: x=(int) 8.3;
x valdrá 8 independientemente del tipo que tenga, ya que al convertir datos se pierden decimales.
Así en el ejemplo anterior:
int x=(int)9.5*2;
Hace que x valga 18, ya que al convertir a entero el 9.5 se pierden los decimales.
4.- CONTROL DE FLUJO
Las sentencias que permiten tomar decisiones en un programa son las llamadas sentencias de control de flujo.
Todas ellas se basan en evaluar una expresión lógica.
Una expresión lógica es cualquier expresión que al ser evaluada por el compilador devuelve verdadero o falso. En C se considera verdadera cualquier expresión distinta de 0 (en especial el uno, valor verdadero) y falsa el cero (falso).
Sentencia if
Condicional simple
Se trata de una sentencia que, tras evaluar una expresión lógica, ejecuta una serie de sentencias en caso de que la expresión lógica sea verdadera.
Su sintaxis es:
if ( expresión lógica )
{
sentencias …
}
Si sólo se va a ejecutar una sentencia, no hace falta usar las llaves:
if (expresión lógica) sentencia;
Ejemplo: if (nota>=5)
{
printf(“Aprobado”);
aprobados++;
}
Condicional compuesta
Es igual que la anterior, sólo que se añade un apartado else que contiene instrucciones que se ejecutarán si la expresión evaluada por el if es falsa.
Sintaxis:
if (expresión lógica)
{
sentencias ….
}
else
{
sentencias …
}
Las llaves son necesarias sólo si se ejecuta más de una sentencia.
Ejemplo:
if(nota>=5)
{
printf(“Aprobado”);
aprobados++;
}
else
{
printf(“Suspensos”);
suspensos++;
}
Anidación
Dentro de una sentencia if se puede colocar otra sentencia if. A esto se le llama anidación y permite crear programas donde se valoren expresiones complejas.
Por ejemplo en un programa donde se realice una determinada operación dependiendo de los valores de una variable.
El código podría ser:
if (x==1)
{
sentencias …
}
else
{
if(x==2)
{
sentencias …
}
else
{
if (x==3)
{
sentencias …
}
}
}
Pero si cada else tiene dentro sólo una instrucción if entonces se podría escribir de esta forma (que es más legible), llamada if-else-if:
if (x==1) {
instrucciones …
}
else if (x==2) {
instrucciones …
} else if (x==3) {
instrucciones …
}
Sentencia switch
Se trata de una sentencia que permite construir alternativas múltiples. Pero que en el lenguaje C está muy limitada.
Sólo sirve para evaluar el valor de una variable entera (o de carácter, char).
Tras indicar la expresión entera que se evalúa, a continuación se compara con cada valor agrupado por una sentencia case.
Cuando el programa encuentra un case que encaja con el valor de la expresión se ejecuta lo que indique en ese case. Cuando queremos acabar un case, debemos utilizar la sentencias break para hacer que el programa abandone el bloque switch.
Sintaxis:
switch (expresión entera)
{
case valor1: sentencias
break; /* break, sirve para que programa salte fuera del switch de otro modo pasaría por todos los demás case */
case valor2: sentencias
break;
default: sentencias
}
Ejemplo:
switch (diasemana)
{
case 1: printf(”Lunes”);
break;
case 2: printf(”Martes”);
break;
case 3: printf(”Miércoles”);
break;
case 4: printf(”Jueves”);
break;
case 5: printf(”Viernes”);
break;
case 6: printf(”Sábado”);
break;
case 7: printf(”Domingo”);
break;
default: printf(“Error”);
}
Sólo se pueden evaluar expresiones con valores concretos (no hay una case >3 por ejemplo)
Aunque sí se pueden agrupar varias expresiones aprovechando el hecho de que al entrar en un case se ejecutan las expresiones de los siguientes.
Ejemplo:
switch ( diasemana )
{
case 1:
case 2:
case 3:
case 4:
case 5: printf (“Laborable”);
break;
case 6:
case 7: printf (“Fin de semana”);
break;
default: printf(“Error”);
}
5.- BUCLES
Los bucles permiten repetir una serie de instrucciones hasta que se cumpla una determinada condición. Dicha condición debe variar en el bucle, de otro modo el bucle sería infinito.
Sentencia while
Es una de las sentencias fundamentales para poder programar. Se trata de una serie de instrucciones que se ejecutan continuamente mientras una expresión lógica sea cierta.
Sintaxis:
while (expresión lógica)
{
sentencias
}
El programa se ejecuta siguiendo estos pasos:
(1) Se evalúa la expresión lógica .
(2) Si la expresión es verdadera ejecuta las sentencias, sino el programa abandona la sentencia while
(3) Tras ejecutar las sentencias, volvemos al paso 1
Ejemplo (escribir números del 1 al 100):
int i=1;
while (i<=100)
{
printf(“%d ”,i);
i++;
}
Bucles con contador
Se llaman así a los bucles que se repiten una serie determinada de veces.
Dichos bucles están controlados por un (puede que incluso más de un) contador.
El contador es una variable que va contando (de uno en uno, de dos en dos,… o como queramos) en cada vuelta del bucle.
Cuando el contador alcanza un límite, entonces el bucle termina. En todos los bucles de contador necesitamos saber:
(1) Lo que vale la variable contadora al principio o sea, antes de entrar en el bucle.
(2) Lo que varía (lo que se incrementa o decrementa) el contador en cada vuelta del bucle.
(3) El valor final del contador. En cuanto se rebase el bucle termina. Dicho valor se pone como condición del bucle, pero a la inversa. Es decir, la condición mide el valor que tiene que tener el contador para que el bucle se repita.
Ejemplo:
i=10; /*Valor inicial del contador, empieza valiendo 10 (por supuesto i debería estar declara como entera, int) */
while (i<=200) /* condición del bucle, mientras i sea menor de 200, el bucle se repetirá, cuando i rebase este valor, el bucle termina */
{
printf(“%d\n”,i);
/*acciones que ocurren en cada vuelta del bucle en este caso simplemente escribe el valor del contador */
i+=10 /* Variación del contador, en este caso cuenta de 10 en 10*/ }
/* Al final el bucle escribe: 10 20 30 … y así hasta 200 */
Bucles de centinela
Es el segundo tipo de bucle básico. Una condición lógica llamada centinela, que puede ser desde una simple variable booleana hasta una expresión lógica más compleja, sirve para decidir si el bucle se repite o no. De modo que cuando la condición lógica se incumpla, el bucle termina.
Ejemplo:
int salir=0;
/* En este caso el centinela es una variable booleana que inicialmente vale falso,0 */
while (salir==0)
{ /* Condición de repetición: que salir siga siendo falso. Ese es el centinela. También se podía haber escrito simplemente: while(!salir) */
scanf (“%c”, &caracter);
/* Lo que se repite en el bucle: leer carácter*/
salir=(caracter=='S');
/* El centinela vale verdadero si el carácter leído es la S mayúscula, de no ser así, el centinela seguirá siendo falso y el bucle se repite */
}
Comparando los bucles de centinela con los de contador, podemos señalar estos puntos:
-
Los bucles de contador se repiten un número concreto de veces, los bucles de centinela no
-
Un bucle de contador podemos considerar que es seguro que finalice, el de centinela puede no finalizar si el centinela jamás varía su valor (aunque seguro que alguna vez lo alcanza)
-
Un bucle de contador está relacionado con la programación de algoritmos basados en series.
Un bucle podría ser incluso mixto, de centinela y de contador.
Por ejemplo imaginar un programa que se repita indefinidamente hasta que llegue una determinada contraseña (que podría ser el número 12345), pero que como mucho se repite tres veces (al tercer fallo de contraseña, el programa termina).
Sería:
int i=1; /*Contador*/
int salir=0; /*Centinela*/
char caracter;
while (salir==0 && i<=3)
{
scanf (“%c”,&caracter);
salir=(caracter='S');
i++;
}
sentencia do..while
La única diferencia respecto a la anterior está en que la expresión lógica se evalúa después de haber ejecutado las sentencias. Es decir el bucle al menos se ejecuta una vez. Los pasos que sigue el bucle son:
(1) Ejecutar sentencias
(2) Evaluar expresión lógica
(3) Si la expresión es verdadera volver al paso 1, sino continuar fuera del while
Sintaxis:
do
{
sentencias …
} while (expresión lógica)
Ejemplo (contar del 1 al 1000):
int i=0;
do
{
i++;
printf (“%d”,i);
} while (i<=1000);
Se utiliza cuando sabemos al menos que las sentencias del bucle se van a repetir una vez (en un bucle while puede que incluso no se ejecuten las sentencias que hay dentro del bucle si la condición fuera falsa, ya desde un inicio).
De hecho cualquier sentencia do..while se puede convertir en while.
El ejemplo anterior se puede escribir usando la instrucción while, así:
int i=0;
i++;
printf(“%d”,i);
while (i<=1000)
{
i++;
printf(“%d”,i);
}
Sentencia for
Es una sentencia pensada para realizar bucles contadores. Su formato es:
for ( inicialización;condición;incremento )
{
sentencias
}
Las sentencias se ejecutan mientras la condición sea verdadera. Además antes de entrar en el bucle se ejecuta la instrucción de inicialización y en cada vuelta se ejecuta el incremento. Es decir el funcionamiento es:
(1) Se ejecuta la instrucción de inicialización
(2) Se comprueba la condición
(3) Si la condición es cierta, entonces se ejecutan las sentencias. Si la condición es falsa, abandonamos el bloque for
(4) Tras ejecutar las sentencias, se ejecuta la instrucción de incremento y se vuelve al paso 2
Ejemplo (contar números del 1 al 1000):
For (int i=1;i<=1000;i++)
{
printf(“%d”,i);
}
La ventaja que tiene es que el código se reduce.
La desventaja es que el código es menos comprensible.
El bucle anterior es equivalente al siguiente bucle while:
i=1; /*sentencia de inicialización*/
while (i<=1000) { /*condición*/
printf(“%d”,i);
i++; /*incremento*/
}
Sentencias de ruptura de flujo
No es aconsejable su uso ya que son instrucciones que rompen el paradigma de la programación estructurada. Cualquier programa que las use ya no es estructurado.
Las comentaremos porque puede ser útil conocerlas, especialmente para interpretar código de terceros.
Sentencia break
Es una sentencia que hace que el flujo del programa abandone el bloque en el que se encuentra.
for (int i=1;i<=1000;i++){
printf(“%d”,i);
if(i==300) break;
}
En el listado anterior el contador no llega a 1000, en cuanto llega a 300 sale del for
Sentencia continue
Es parecida a la anterior, sólo que en este caso en lugar de abandonar el bucle, lo que ocurre es que no se ejecutan el resto de sentencias del bucle y se vuelve a la condición del mismo:
for (int i=1;i<=1000;i++){
if(i%3==0) continue;
printf(“%d”,i);
}
En ese listado aparecen los números del 1 al 1000, menos los múltiplos de 3 (en los múltiplos de 3 se ejecuta la instrucción continue que salta el resto de instrucciones del bucle y vuelve a la siguiente iteración.
El uso de esta sentencia genera malos hábitos, siempre es mejor resolver los problemas sin usar continue.
El ejemplo anterior sin usar esta instrucción quedaría:
for (int i=1;i<=1000;i++){
if(i%3!=0)
printf(“%d”,i);
}
La programación estructurada prohíbe utilizar las sentencias break y continue
4
Descargar
Enviado por: | Valentin |
Idioma: | castellano |
País: | España |