Sie sind auf Seite 1von 17

GUÍA RÁPIDA DE C

Índice:
1. CONCEPTOS BÁSICOS............................................................................................................................... 2
1.1. Estructura de un programa ................................................................................................................. 2
1.2. Tipos de datos ...................................................................................................................................... 2
1.3. Operadores ........................................................................................................................................... 4
1.4. Asignación, lectura y escritura ........................................................................................................... 4
1.5. Lectura y escritura de secuencias ..................................................................................................... 5
2. INSTRUCCIONES DE CONTROL ................................................................................................................ 6
2.1. Estructura secuencial .......................................................................................................................... 6
2.2. Estructura alternativa .......................................................................................................................... 6
2.3. Estructura repetitiva ............................................................................................................................ 6
3. ARRAYS ........................................................................................................................................................ 7
4. PUNTEROS ................................................................................................................................................... 8
4.1. Arrays dinámicos ................................................................................................................................. 9
4.2. Arrays y punteros ................................................................................................................................. 9
5. FUNCIONES ................................................................................................................................................ 11
5.1. Paso de parámetros ........................................................................................................................... 11
5.2. Parámetros de variables indexadas ................................................................................................. 12
5.3. Agrupar procedimientos.................................................................................................................... 12
6. GESTIÓN DE FICHEROS ........................................................................................................................... 14
7. TIPOS DE DATOS COMPUESTOS ............................................................................................................ 16
7.1. Enum.................................................................................................................................................... 16
7.2. Array .................................................................................................................................................... 16
7.3. Registros o tuplas (struct)................................................................................................................. 16
8. PROCEDIMIENTOS DE USO GENERAL .................................................................................................. 17

1
1. CONCEPTOS BÁSICOS
1.1. Estructura de un programa
La estructura de un programa sencillo en C es la siguiente
declaración de importaciones /* Archivos de cabecera de funciones. */
definición de constantes #include <stdio.h>
definición de tipos #include <stdlib.h>
prototipos de función #include <conio.h>
declaración de variables globales void main() { /* programa principal*/
void main (void) { clrscr(); // Borra la pantalla
declaración variables locales printf("Hola Mundo"); // Imprime
instrucciones ejecutables getch(); // espera pulsación teclado
} }
implementación de funciones
Declaración de importaciones. Indica al compilador qué acciones y funciones de las mencionadas en el
programa no se encuentran implementadas en este. Estos procedimientos externos al programa se hallan
disponibles en estructuras llamadas módulos que tienen asociado un fichero con extensión.h que contiene
la lista de procedimientos importables. Para incluirlos se escribe una instrucción del tipo
#include <nombre_módulo.h>
Módulos empleados con frecuencia son stdio (procedimientos de E/S) y math (funciones matemáticas).
Definición de constantes. Se asigna a un identificador un valor. Un proceso previo a la compilación
substituirá el identificador por la cadena de caracteres cada vez que lo encuentre. Su formato es
#define identificador cadena
y al igual que con la instrucción #include, no hace falta poner el terminador; al final de la instrucción.
Definición de tipos. Permite definir tipos de datos que serán utilizados en todo el programa.
C no posee ninguna implementación primitiva del tipo booleano. Para declarar variables de este tipo, hace
falta definirlo. La manera es añadir entre las declaraciones de tipos la siguiente:
typedef enum {FALSE=0, TRUE=1} booleano;
Que define el tipo booleano y asigna a sus elementos FALSE y TRUE los valores 0 y 1.
Prototipos de función. Indican las funciones a las que hacer referencia más adelante en el código.
Declaración de variables globales. Se declaran las variables que podrán usarse por cualquier procedimiento
del programa, sin necesidad de pasarlas como parámetro.
La función principal o main(). Es el único requisito El código C de un algoritmo cualquiera podría ser:
indispensable que ha de aparecer en un programa. Es
typedef enum {FALSE=0, TRUE=1} booleano;
el cuerpo del programa.
void main (void)
Declaración de variables locales. Se declaran
variables que se usarán dentro del programa. {
Instrucciones ejecutables. Comandos para el proceso char c;
de datos.
int larg, num_palabras;
Implementación de funciones. Se define el cometido
de cada una de las funciones que utilizará el booleano error;
programa, y ha de coincidir con el prototipo de ...
función.
}
1.2. Tipos de datos
Los identificadores en C como nombres de variables son cadenas de hasta 32 caracteres alfanuméricos.
Pueden contener el guion '_' para unir palabras. La cadena ha de comenzar por una letra. Tradicionalmente
se usan letras minúsculas y para nombres de constantes, mayúsculas. Las variables se representan en la

2
máquina como un conjunto finito de datos. En este caso, se genera un tamaño máximo de enteros
manejables, pero en el rango definido la aritmética entera es exacta.
Las palabras reservadas de C para declarar caracteres o enteros son, respectivamente, char e int. Sus
representaciones internas permiten operaciones muy rápidas. Si se requiere mayor precisión, existe el tipo
long int. Lógicamente, sus operaciones son más lentas para su aritmética. La tabla siguiente resume las
características de los tipos elementales de datos:
Nombre Tipo Bits Tamaño
Unsigned char Carácter 8 0 a 255
Char Carácter 8 -128 a 128
Enum Enumeracion 16 -32,768 a 32,767
Unsigned int Numero 16 0 a 65,535
Sort int Numero 16 -32,768 /32,767
Int Numero 16 -32,768 /32,767
Unsigned long Numero 32 0 to 4,294,967,295
Long Numero 32 -2,147,483,648 / +
Float Numero 32 3.4 * (10**-38)
Double Numero 64 1.7 * (10**-308)
Long double Numero 80 3.4 * (10**-4932)
Los caracteres se representan con palabras de 8 bits. Esto limita su número a 256, de los cuales los
primeros 128 siguen el estándar ASCII (American Society for Computer Interchange of information). Los 128
restantes varían en función de factores culturales y geográficos. La función que asigna un entero a cada
carácter recibe el nombre de código ASCII.
Caracteres no imprimibles Caracteres imprimibles
Nombre Car. Dec Hex Car. Dec Hex Car. Dec Hex Car. Dec Hex
Nulo NUL 0 00 Esp . 32 20 @ 64 40 ` 96 60
Inicio de cabecera SOH 1 01 ! 33 21 A 65 41 a 97 61
Inicio de texto STX 2 02 " 34 22 B 66 42 b 98 62
Fin de texto ETX 3 03 # 35 23 C 67 43 c 99 63
Fin de transmisión EOT 4 04 $ 36 24 D 68 44 d 100 64
enquiry ENQ 5 05 % 37 25 E 69 45 e 101 65
acknowledge ACK 6 06 & 38 26 F 70 46 f 102 66
Campanilla (beep) BEL 7 07 ' 39 27 G 71 47 g 103 67
backspace BS 8 08 ( 40 28 H 72 48 h 104 68
Tabulador horizontal HT 9 09 ) 41 29 I 73 49 i 105 69
Salto de línea LF 10 0A * 42 2A J 74 4A j 106 6A
Tabulador vertical VT 11 0B + 43 2B K 75 4B k 107 6B
Salto de página FF 12 0C , 44 2C L 76 4C l 108 6C
Retorno de carro CR 13 0D - 45 2D M 77 4D m 109 6D
Shift fuera SO 14 0E . 46 2E N 78 4E n 110 6E
Shift dentro SI 15 0F / 47 2F O 79 4F o 111 6F
Escape línea de datos DLE 16 10 0 48 30 P 80 50 p 112 70
Control dispositivo 1 DC1 17 11 1 49 31 Q 81 51 q 113 71
Control dispositivo 2 DC2 18 12 2 50 32 R 82 52 r 114 72

3
Control dispositivo 3 DC3 19 13 3 51 33 S 83 53 s 115 73
Control dispositivo 4 DC4 20 14 4 52 34 T 84 54 t 116 74
neg acknowledge NAK 21 15 5 53 35 U 85 55 u 117 75
Sincronismo SYN 22 16 6 54 36 V 86 56 v 118 76
Fin bloque transmitido ETB 23 17 7 55 37 W 87 57 w 119 77
Cancelar CAN 24 18 8 56 38 X 88 58 x 120 78
Fin medio EM 25 19 9 57 39 Y 89 59 y 121 79
Sustituto SUB 26 1A : 58 3A Z 90 5A z 122 7A
Escape ESC 27 1B ; 59 3B [ 91 5B { 123 7B
Separador archivos FS 28 1C < 60 3C \ 92 5C | 124 7C
Separador grupos GS 29 1D = 61 3D ] 93 5D } 125 7D
Separador registros RS 30 1E > 62 3E ^ 94 5E ~ 126 7E
Separador unidades US 31 1F ? 63 3F _ 95 5F DEL 127 7F
Una característica de la parte estándar del conjunto de los 128 primeros caracteres es que contiene las
letras mayúsculas, minúsculas y dígitos ordenados en orden natural. Así, la siguiente expresión booleana
decide si el carácter contenido en la variable c es una letra mayúscula ‘A’<=c && c<=’Z’.
Para pasar de un carácter a su código ASCII o viceversa, C dispone de los operadores (int) y (char). El
primero situado delante de una variable de tipo char retorna el código ASCII del contenido de la variable. Es
recomendable, no obstante, no apoyarse en exceso en el uso de estos operadores. Posiblemente el caso
de utilidad más evidente es la transformación de una variable c de tipo char (que contiene un dígito) a una
expresión entera que se evalúa en el valor de este dígito mediante (int)c-(int)’0’
1.3. Operadores
aritméticos a nivel de bit booleanos
+ suma & and a nivel de bit && and
* producto | or a nivel de bit || or
- diferencia ! not
/ división entera relacionales a valores boléanos
% módulo (resto de división == igual != diferente > mayor que
entera)
< menor que <= menor o igual >= mayor o igual
1.4. Asignación, lectura y escritura
La asignación en C presenta el aspecto variable = expresión;
La expresión ha de tomar un valor del tipo de la variable. Es conveniente dejar un espacio entre el signo = y
la expresión. En caso de no hacerlo, pueden generarse efectos no deseados. En caso de expresiones
compuestas de una constante de tipo carácter, ha de aparecer entre comillas simples. Por ejemplo, para
asignar el carácter 4 a c_1 y el carácter v a c_2, se hace c_1 = ‘4’; c_2 = ‘v’;. Las asignaciones c_1 = 4; c_2
= v; asignarían el entero 4 a c_1 (incorrecto ya que c_1 es tipo carácter) y el valor de la variable v a c_2. C
posee formas de asignaciones abreviadas:
TIPO RESULTADO TIPO RESULTADO
Variable++ Variable = variable +1 Variable += incremento Variable = variable + incremento
Variable- - Variable = variable –1 Variable -= incremento Variable = variable – incremento
Para leer o escribir valores de variables en casos particulares, por una parte, las variables serán de los tipos
elementales (int o char) y, por otra, solo se consideran R/W estándar (teclado y pantalla). Para leer variables
desde teclado, se usa scanf. Su formato es:
scanf(“formato_1 formato_2... formato_k”, &variable_1, &variable_2,... ,&variable_k)

4
Donde formato_1... formato_k representan los tipos de los datos a leer según la correspondencia siguiente:
Mascara Resultado Mascara Resultado
%d Muestra decimal %c Carácter
%o Muestra en octal %s Modificador de cadena (string)
%x Muestra entro hexadecimal (minúsculas) \n Otro renglón (ENTER)
%X Muestra entro hexadecimal (mayúscula) \t Tabulador
%f Notación científica (10E20) \\ Barra de separación.
\a Beep por parlante
Los valores leídos han de estar separados por espacios o retornos de línea. La parte de la instrucción entre
comillas recibe el nombre de control y sirve para interpretar los valores leídos.
Para leer caracteres hay instrucciones más sintéticas y ventajosas como getch y variable = getch ();
getche. Leen un carácter desde el teclado y la almacenan en variable. La
variable = getche ();
diferencia es que getch no escribe el carácter leído en pantalla y getche sí.
Para escribir en pantalla, se usa printf. Su formato es similar al de lectura con la diferencia que se pueden
intercalar texto e instrucciones de formateado del mensaje. Para intercalar un retorno de línea en un cierto
punto del mensaje, se ha de escribir el modificador \n.
Para mostrar en pantalla un carácter, se puede usar también la instrucción putchar() que muestra su
argumento. Para hacer uso de scanf, printf y putchar hay que importarlas con stdio (#include <stdio.h>). Las
instrucciones getch y getche se deben importar del módulo conio.
1.5. Lectura y escritura de secuencias
La la lectura y escritura de secuencias se puede hacer de diversas maneras según el soporte físico de la
secuencia. De momento solo se considerará secuencias de E/S estándar.
Esto comporta la limitación de trabajar con una única secuencia y la particularidad de no declarar las
secuencias como variables locales del programa. Los programas acceden a los elementos de una
secuencia uno por uno y nunca almacenan la secuencia entera. Para leer secuencias se usa la instrucción
scanf. En todo caso, es conveniente insertar antes de la instrucción que imprima un mensaje por pantalla
pidiendo al usuario que introduzca la secuencia y la termine con la marca deseada.
En caso de secuencias de caracteres es conveniente usar en lugar de scanf la variable = getchar();
instrucción getchar con el formato
Esta instrucción lee un carácter del teclado y lo almacena en una memoria intermedia (stdin) interrumpiendo
temporalmente la ejecución del programa. La máquina lee caracteres de teclado y los almacena en la
memoria hasta leer un retorno de carro. La ejecución del programa se reanuda y las sucesivas instrucciones
de lectura se realizan desde memoria intermedia. Este protocolo se itera hasta que se finalice su ejecución,
y tiene la ventaja de permitir al usuario borrar caracteres de su secuencia si lo desea. Estos borrados se
efectúan en la memoria intermedia.
Análogamente, para escritura de secuencias, se usa printf (o putchar es una secuencia de caracteres) para
imprimir la cadena de caracteres. Algunos SO pueden redireccionar el canal estándar hacia un fichero. Esto
permite escribir una secuencia en un fichero o leer desde un fichero la entrada para un programa.
En UNIX o DOS, el formato es nombre_programa <fichero_entrada> fichero_salida, que hará que el
programa nombre_programa se ejecute leyendo la entrada del fichero fichero_entrada y escribiendo la
salida en el fichero fichero_salida. Los ficheros de entrada han de ser de texto y, si son ficheros de enteros,
los elementos han de estar separados por espacios, tabuladores o cambios de línea. Por la misma razón, si
se desea generar una secuencia de enteros y almacenarla en un fichero, hace falta escribir un espacio
después de %d en la instrucción scanf porque si no, al editar el fichero se verá una hilera ininterrumpida de
dígitos. Para añadir comentarios a un programa C se encierra el comentario entre los símbolos /* y */, o
anteponiendo // a la línea del comentario.

5
2. INSTRUCCIONES DE CONTROL
2.1. Estructura secuencial
Para secuenciar instrucciones en C se escriben consecutivamente, separadas por punto y coma y en líneas
diferentes para aumentar la legibilidad del programa.
2.2. Estructura alternativa
La estructura alternativa se realiza con la instrucción if-else-if, con el formato del cuadro izauierdo. En caso
de tener una única instrucción en lugar de una secuencia, no hace falta cerrarla entre llaves. Si condicion_k
es la negación de las condiciones precedentes, puede no escribirse la parte if (condicion_k), ya que C
interpreta else como el caso contrario. Si secuencia_de_instrucciones_k es la secuencia vacía, puede
omitirse su bloque de instrucciones.
if (condicion_1) { switch (variable) {
case valor_1: secuencia_de_instrucciones_1;
secuencia_de_instrucciones_1
break;
}
case valor_2: secuencia_de_instrucciones_2
else if (condicion_2) {
break;
secuencia_de_instrucciones_2
...
}...
case valor_k: secuencia_de_instrucciones_k
else if (condicion_k) {
break;
secuencia_de_instrucciones_k
default:
}
secuencia_de_instrucciones_por_defecto
}
}
Cuando en la estructura anterior el valor de una variable determina distintos procesos puede usarse la
instrucción switch que presenta el formato de la derecha. Si la secuencia_de_instrucciones_por_defecto es
la secuencia vacía, se pueden omitir el default y la secuencia misma.
2.3. Estructura repetitiva
En C hay tres formas de implementar iteraciones. while (condición) {
La básica es la sentencia de iteración while,
secuencia_de_instrucciones
como se muestra en el cuadro de la derecha.
Una variante es la estructura do-while. }
Se diferencian en que do-while verifica la condición de iteración tras ejecutar la secuencia_de_instrucciones
y, por lo tanto, la ejecuta como mínimo una vez. Su sintaxis es la mostrada en el cuadro de la izquierda.
Finalmente, la instrucción for presenta el esquema del cuadro derecho.
do { for (variable=valor_inicial; condición; paso) {
secuencia_de_instrucciones secuencia_de_instrucciones
} while (condición) }
Donde paso indica el incremento (o decremento) de la variable.

6
3. ARRAYS
La declaración de arrays sigue el siguiente formato: tipo nombre_vector [dim]
Donde dim ha de ser un número entero y denota la dimensión o longitud del array. Así, las instrucciones int
datos[10]; char nombre[12], apellido[12]; declaran un array de enteros de 10 elementos y dos arrays de
caracteres de 12 elementos cada uno.
Una vez declarado un array de n elementos, se puede leer y escribir en sus componentes teniendo en
cuenta que éstas se numeran de 0 a n-1. Para referenciar la primera letra de apellido se ha de escribir
apellido[0] y para referenciar el último elemento se ha de escribir datos[9].
Para declarar variables indexadas con más de un tipo nombre_variable[dim_1][dim_2]...[dim_k]
índice, el formato es el mostrado.
Usualmente se llama array una variable con un índice y matriz una variable con dos o más índices. Además,
el primer índice se llama fila, el segundo columna y el tercero página. A partir del cuarto índice no se usan
nombres específicos.
Es frecuente emplear vectores para la manipulación de cadenas de caracteres, como nombres de personas
u otros. En estos casos, se ha de declarar la variable con una longitud capaz de alojar el nombre más largo
de entre los valores posibles.
Resulta incómodo rellenar con espacios las posiciones vacías cada vez que se lee un nombre del teclado.
Para facilitar el trato de estos vectores de caracteres con longitud variable, C define un tipo conveniente: el
tipo string (cadena de caracteres). Su declaración es la normal de un vector de caracteres.
Lo que cambia es su lectura y escritura. En el momento de leer una cadena de caracteres, la máquina los
lee uno a uno hasta encontrar un salto de línea. Cuando esto suceda, añadirá al final de la secuencia leída
un carácter nulo (/0). Esta marca permitirá saber dónde se encuentra el fin de la cadena.
La lectura de una cadena de caracteres se realiza poniendo en el control el formato %s y quitando del
nombre de la variable el prefijo & en la instrucción scanf. Existe otra manera de leer una cadena desde el
teclado, usando la instrucción gets con el formato gets (nombre_cadena); que lee todos los caracteres
tecleados hasta pulsar enter.
Para la escritura, también se usa el formato %s en la parte de control de printf. C también posee una gran
variedad de funciones para manipular cadenas. Las más corrientes son:
strcpy (cad_1, cad_2);
strcat (cad_1, cad_2);
res_comparacion = strcmp (cad_1, cad_2);
longitud = strlen (cad);
La primera copia el contenido de cad_2 sobre cad_1. La segunda añade el contenido de cad_2 al final de
cad_1. La tercera compara lexicográficamente los contenidos de las cadenas que recibe como entradas y
retorna 0 si son la misma. Si cad_1 es mayor que cad_2, entonces retorna un número positivo y, si es
menor, uno negativo. Finalmente strlen(cad) retorna la longitud de cad.
Para usar estas instrucciones hace falta importarlas del módulo string. Las cadenas constantes se encierran
entre comillas dobles “. Por ejemplo, en strcpy (cad, ”hola”); se asocia la cadena hola a la variable cad.

7
4. PUNTEROS
Un puntero es una variable numérica, que ocupa 4 bytes que representa una dirección de memoria. La
diferencia entre una variable "normal" y un puntero es el uso que hace de ella el programador y el
compilador cuando traduce las expresiones en las que se usan.
Sea el fragmento de programa propuesto a la derecha. La void FuncionUna (void) {
función declara dos variables locales: i, de tipo integer, y p, de
int i;
tipo puntero a integer.
int* p;
Durante la ejecución del programa, a cada variable se le
asigna una zona de memoria, donde vive. Una zona de i = 4;
memoria tiene un inicio (número de byte de memoria donde
p = &i;
empieza) y una cantidad de bytes reservados, que varía en
función de su tipo. Sea en el ejemplo que a la variable i le *p = *p + 2;
haya correspondido la posición 1000 de memoria y a p le haya
correspondido la posición 1004. La variable i es un integer que printf ("El valor de i es: %d\n",i);
ocupa 4 bytes, y la variable p un puntero a integer, que ocupa }
también 4 bytes.
El operador unario & de C devuelve la dirección de memoria de una variable. Si se imprime el valor de i,
después de ejecutar la primera instrucción (i = 4), se obtiene el valor 4. Si se imprime el valor &i, se
obtendrá 1000.
El operador unario *, da acceso a la posición de memoria indicada por la variable a que se aplica. En el
ejemplo anterior, al hacer p = &i, se asigna a la variable p el resultado de &i, que sería 1000. Si se imprime
p, se obtendría 1000. Si se imprime *p, se obtendría 4. Si se imprime &p, se obtendría 1004, que es donde
se encuentra. Un puntero también es una variable, y como tal, tiene una dirección de memoria asignada.
Cuando se evalúa la expresión [*p = *p + 2], se busca el valor de lo que hay en la posición de memoria que
indica p (1000), con lo cual se obtiene un 4. A este 4 se le suma 2, total 6, valor que se asigna a la posición
de memoria de p, la 1000. Como en la 1000 está viviendo la variable i, al final i valdrá 6.
Los punteros son una herramienta fundamental para construir programas. void Sumar (int a, int b, int c) {
A continuación se exponen unos ejemplos. A la derecha se tienen dos
int Resultado;
trozos de código. La rutina FuncionUna imprimirá un valor indeterminado.
Resultado = a + b;
Esta idea sirve para ver cómo se crean y destruyen variables locales de
una función y cómo funciona el paso de parámetros. c = Resultado;
Las variables globales de un programa se declaran fuera del ámbito de }
las funciones. Existe un área de memoria usada por el programa donde
se encuentran almacenadas estas variables. Son conocidas y pueden ser void FuncionUna (void) {
usadas por todas las funciones del programa. int x, y, z;
Cada función de C dispone de un "área propia" donde se almacenan sus x = 4;
variables locales y sus parámetros mientras que se ejecutan. Esta área
se crea cuando la función es llamada, y se destruye cuando termina. y = 2;

Entonces, al ejecutarse la instrucción Sumar (x, y, z), primero se activa el Sumar (x,y,z);
bloque de la función, que reserva espacio para una variable local printf ("La z vale: %d\n",z);
Resultado y que también reserva espacio para los parámetros a, b y c. A
continuación, el valor de x es copiado a ‘a’, el valor de y es copiado a b, y }
el valor de z es copiado a c.
Se ejecuta la función, que en este caso solo accede a los datos alojados en esta área temporal. Resultado
valdrá 6, c valdrá 6, a valdrá 4 y b valdrá 2. Cuando termina la ejecución de la función Sumar, el bloque de
activación creado (donde residen Resultado, a, b y c) es destruido, y sus valores se pierden. Por lo tanto, el
programa imprimirá el valor de z, que no es 6, ya que en ningún momento se le ha establecido este valor. El
valor de z es indeterminado porque es una variable no inicializada, y el contenido de la memoria donde le ha
tocado vivir puede ser cualquiera.
Si se quiere que la variable z valga 6 después de retornar de la función void Sumar (int a, int b, int* c)
Sumar, debería programarse como en el código de la derecha, por {
ejemplo, con punteros.
int Resultado;
Ahora la función Sumar no recibe un integer como tercer parámetro, sino
Resultado = a + b;
un puntero a integer. Se puede pasar por valor un puntero, pero no es
más que eso, una variable de tipo puntero a algo pasada por valor, no un *c = Resultado;

8
paso por referencia.
}
En este caso, el programa crea el bloque de activación, igual que antes,
void FuncionUna (void) {
para alojar a la variable local Resultado, y a los parámetros a, b y c.
Copia el valor de x en a, el valor de y en b, y el valor de &z en c. int x, y, z;
Dependiendo del bloque de activación previo de esta función, z vivirá en x = 4;
una posición de memoria determinada, que podría ser por ejemplo la
y = 2;
5000. Se está pasando el valor 5000 a c, y por tanto, la variable c vale
5000. Al ejecutar *c = Resultado, se está escribiendo en la posición de Sumar (x,y,&z);
memoria indicada por c el valor 6. Esta posición es la 5000, que fue el
valor pasado a la función y como en la posición 5000 se encuentra la printf ("La z vale: %d\n",z);
variable z, ahora es cuando la variable z toma por valor 6. }
Así se pasan los argumentos por referencia, usando punteros. El paso por referencia es importante porque,
si no existieran los parámetros por referencia, todas las variables del programa deberían ser públicas
(globales), al menos las que se fueran a compartir entre funciones. No se podrían declarar las variables
deseadas como locales en las funciones (con la seguridad de código que esto ofrece), ya que otra función
no podría acceder a ellas, como en el caso del ejemplo anterior.
4.1. Arrays dinámicos
Sea ahora una función que recibe por parámetro el nombre del fichero a usar, ha de leerlo del disco,
modificarlo y guardarlo otra vez. El problema es no conocer de antemano lo que ocupa el fichero. Es la
esencia del array dinámico. Si el archivo es pequeño, se podría desperdiciar memoria. Si es grande, podría
no poder tratarse, aunque existan recursos. La solución es la gestión dinámica de memoria.
En el ejemplo sólo se declara una variable Foto, que es void ProcesarFoto (char Nombre[]) {
un puntero. Se abre el fichero y se consulta su longitud
char* Foto;
en bytes.
FILE* FFoto;
A continuación, se llama a la función malloc y se pide la
cantidad de memoria necesaria. El valor retornado por int TamFoto;
malloc es la dirección de memoria donde se ha asigna
FFoto = fopen (Nombre,"rb");
el espacio pedido. Este valor se guarda en el puntero,
con lo cual, Foto es exactamente igual que un array TamFoto = filelength (fileno (FFoto));
declarado de forma estática.
Foto = (char*) malloc (TamFoto);
Se lee el contenido del fichero sobre el espacio de
memoria reservado en Foto y se usa el array obtenido fread (Foto,TamFoto,1,FFoto);
dinámicamente, pudiendo tratar la foto. fclose (FFoto);
Se abre el fichero y se escribe el contenido de la foto // Tratamiento de la imagen leida en Foto
modificada. Por fin, se libera la memoria pedida al
sistema, con free. A partir de aquí, cualquier acceso a FFoto = fopen (Nombre,"wb");
los elementos apuntados por Foto es ilegal, provocará fwrite (Foto,TamFoto,1,FFoto);
un error interno del sistema.
fclose (FFoto);
El fragmento de código anterior no comprueba ni el
posible error por la ausencia del fichero, ni el posible free (Foto);
error por la falta de memoria. }
Lo que se ha conseguido es que el programa use sólo la memoria estrictamente necesaria para trabajar.
Además, solo fallará en caso que físicamente no haya memoria disponible en la máquina.
4.2. Arrays y punteros
Un array y un puntero se identifican de igual forma. Un array se puede entender como un puntero estático.
De hecho, una variable no es más que un array de dimensión 1. Sean las siguientes declaraciones:
Cliente ClientesA[1000]; Cliente* ClientesB; ClientesB = (Cliente*) malloc (sizeof(Cliente)*1000);
Sea que al array ClientesA le ha tocado la posición 10000, y que un cliente ocupa 100 bytes. Sea también
que el primer elemento de la estructura del cliente es un integer, con su código y el segundo es su nombre,
un char de 50 elementos. El código del primer cliente está en la posición 10000 de memoria, ocupando sus
4 bytes. El nombre del primer cliente está en la posición 10004 de memoria, ocupando sus 50 elementos.
El código del segundo cliente está en la posición 10100 de memoria, ocupando sus 4 bytes, y el nombre del
segundo cliente está en la posición 10104 de memoria.

9
ClientesA[5].Codigo = 28 coloca el valor 28 en la posición de memoria 10000 + 5 * 100 = 10500.
Supóngase que el puntero ClientesB recibe el valor 20000, retornado por la función malloc. El código del
primer cliente está en la posición 20000 de memoria, ocupando sus 4 bytes y el nombre del primer cliente
estará en la posición 20004 de memoria, ocupando sus 50 elementos. El código del segundo cliente estará
en la posición 20100 de memoria, ocupando sus 4 bytes, y el nombre del segundo cliente está en la
posición 20104 de memoria. ClientesB[5].Codigo = 28 coloca el valor 28 en la posición de memoria 20000 +
5 * 100 = 20500.
Supóngase que ahora se hace lo siguiente:
Cliente* ClientesC;
ClientesC = ClientesB + 2;
El valor de ClientesC parece que debería ser 20000+2=20002. No; su valor será: 20000 + 2*100 = 20200.
Cuando se suman o restan valores a un puntero se hace en relación al tamaño ocupado por los elementos
apuntados por el puntero. Esto es lo que hace que un array y un puntero se interpreten igual.
Las declaraciones (ClientesC + 2)->Codigo = 28, (*(ClientesC + 2)).Codigo = 28 y ClientesC[2].Codigo = 28
tienen el mismo efecto. Cuando se usa la sintaxis de array sobre un puntero, C lo entiende como la
expresión anterior (una suma del indice + la dirección base del puntero), y como la suma de un valor a un
puntero tiene relación con el tamaño ocupado por el elemento apuntado por el puntero, el resultado visto.
Por ello es necesario que un puntero apunte a algún tipo, en la mayor parte de los casos. C debe saber el
tipo de lo apuntado por el puntero para hacer las operaciones de acceso a los datos necesarias, y para ello
precisa saber cuánto ocupa el tipo apuntado.

10
5. FUNCIONES
En C la diferencia entre subprogramas pasa por un nuevo “tipo de dato” llamado void (vacío). Void designa
un tipo sin contenido, sin valor (por tanto no ocupa memoria). Así, el formato de un subprograma en C es:
Donde tipo puede ser void, int, char u otro y designa el tipo tipo nombre (lista_parámetros_formales); {
de valor retornado por el subprograma. En un procedimiento,
cuerpo del procedimiento
será void y en una función otro tipo, distinguiendo así su
esencia. La primera línea se llama cabecera. }
Cuando el procedimiento es una función, la instrucción para indicar al programa el valor que ha de retornar
es return expresión;. C no permite retornar valores de cualquier tipo, como vectores. Para ello se usan
punteros, que permiten retornar tipos de datos en principio no permitidos.
El cuerpo del procedimiento consta de una declaración de variables (que serán locales) y de una secuencia
de instrucciones, con la misma estructura que cualquier otro programa.
5.1. Paso de parámetros
Para la trasferencia de información entre procedimientos, C usa 2 formas. En caso de un parámetro de
entrada, se genera otra variable, de nombre el del parámetro formal de la cabecera y hace en ella una copia
del valor (en el momento de la llamada) del parámetro real. Esto es costoso en recursos, pero seguro ya
que el valor del parámetro original no se altera. Esta forma de trabajo es el “paso por valor”.
La otra forma es el “paso por referencia” o dirección. El programa transmite al procedimiento invocado la
dirección de memoria donde se almacena la variable parámetro. Así, el procedimiento invocado podrá
acceder a la variable y modificar su contenido. Como las direcciones de memoria ocupan solo una palabra
en memoria, su transmisión es muy eficiente.
El paso por referencia hace uso de punteros. En C los operadores de punteros son: & y *. El primero,
aplicado a una variable (no indexada), indica su dirección de memoria. El segundo, aplicado a una
dirección, indica su contenido. El formato de invocación es:
Donde “lista_de_parámetros” son los nombres de las variables nombre_función (lista_de_parámetros)
pasadas con & delante para las pasadas por referencia.
En la cabecera del procedimiento invocado, la lista de parámetros es una secuencia de declaraciones de la
forma tipo nombre_parámetro donde, si la variable se pasa por referencia, se ha de escribir * delante del
nombre. Se pueden agrupar muchas variables del mismo tipo bajo una única declaración de tipo. Los
nombres de las variables de la cabecera pueden no coincidir con los de las variables pasadas, pero el orden
ha de ser el mismo. Para leer o escribir en una variable recibida por referencia se ha de usar siempre *
delante del nombre del parámetro formal, ya que el nombre declarado en la cabecera del procedimiento
denota justamente la dirección de la variable recibida.
Por ejemplo, si un procedimiento usa las variables enteras num_1 y num_2 y la booleana error, e invoca la
acción modifica pasándole num_1 y error por referencia y num_2 por valor, el aspecto del programa será:
{ void modifica(int *a, int b, booleano *c) {
int num_1, num_2; int d;
booleano error; ...
... if (*c) *a=d; else *c=TRUE;
modifica(&num_1, num_2, &error); acc1(a); acc1(&b);
... acc2(b+3); acc2(*a);
} ...
}
void acc1(int *x) { ... }
void acc2(int x) { ... }
Como modifica recibe la dirección de la variable a como primer argumento, al invocar acc1 (que recibe su
argumento por referencia) con esta variable, le pasa exactamente la dirección recibida. En cambio, cuando
invoca acc2 (que recibe su argumento por valor) con a, le ha de pasar su valor y éste es obtenido mediante
el uso de *. Exactamente lo contrario pasa con el segundo argumento.

11
Para variables definidas mediante tuplas C simplifica la notación. Si x es la dirección de memoria de una
tupla y campo es un campo de x, se tiene que x->campo equivale a (*x).campo.
Así, si aniversario tiene los campos día, mes y año, al escribir, por ejemplo, a=x->dia;
x=&(cliente.aniversario); y con la notación del cuadro, se asignaría a las variables a, b y
b=x->mes;
c respectivamente, el día, mes y año del aniversario de cliente.
c=x->año;
5.2. Parámetros de variables indexadas
En C hay una relación estrecha entre vectores y direcciones de memoria: el …{
nombre de un vector denota la dirección de memoria de su primer elemento.
int mat[10][10];
Por ejemplo, el vector int a[10]; en C equivale a: &a[0]. Esto importa en el
paso de parámetros de variables indexadas. En C se pasan siempre por ...
referencia las variables indexadas, ya que al pasar su nombre, se pasa su
modifica(mat);
dirección. Así, en la instrucción que invoca, se escribe el nombre de la
variable sin más y en la cabecera del procedimiento invocado, el tipo y ...
nombre de la variable con sus dimensiones. P. ej. Si la acción modifica
recibe una matriz 10 por 10 de enteros, el programa sería el del cuadro. }

Las secuencias no se declaran como variables locales en los programas C, void modifica(int m[10][10])
cuando se manipulan mediante E/S estándar. Consecuentemente, tampoco {...
se pasarán como parámetros entre procedimientos. Como se está limitado a
trabajar con una única secuencia, las instrucciones de lectura (o escritura) m[2][7]=45;
solo pueden hacer referencia a ésta y ello no genera ambigüedades. ...}
5.3. Agrupar procedimientos
En el momento de implementar en C un programa declaracion_de_importaciones
con procedimientos y funciones, se crea un fichero
definiciones_de_constantes
fuente. El algoritmo principal tendrá la cabecera void
main(void) y, si bien no ha de ser necesariamente el typedef int matriz[10][10];
primero de los procedimientos en el fichero, se
acostumbra a situarlo primero. void modifica(int *a, matriz mat, booleano error);

C exige poner una copia (seguida de punto y coma) int calcula(int num, div);
de las cabeceras de las subrutinas antes de booleano condición(int a, int b, matriz m);
comenzar el programa principal.
void main(void)
Así, el aspecto general del código C de un algoritmo
que use el procedimiento modifica, la función entera { cuerpo_del_procedimiento }
calcula y la función booleana condición tendrá la void modifica(int *a, matriz mat, booleano error);
estructura tipo:
{ cuerpo_del_procedimiento }
int calcula(int num, div);
{ cuerpo_del_procedimiento }
booleano condición(int a, int b, matriz m);
{ cuerpo_del_procedimiento }
Los siguientes programas muestran un ejemplo de esta estructura.
Prototipos Programa principal
#include<stdio.h> void main(void) {
#define MAX 15 palabra primera, otra;
typedef enum{FALSE=0, TRUE=1} booleano; char c;
typedef char letras[MAX]; int num_rep;
typedef struct { printf(“Escribe un texto acabado en punto \n”);
letras cont; leer_primera_palabra(&c,&primera);
int long; num_rep=0;
} palabra; leer_palabra(&c,&otra);
void leer_primera_palabra(char *c, palabra *pal); while (¡ultima_palabra(otra)){

12
void leer_palabra(char *c, palabra *pal); if (palabras_iguales(primera,otra))
num_rep++;
booleano ultima_palabra(palabra pal);
leer_palabra(&c,&otra);
booleano palabras_iguales(palabra pal1, pal2);
}
void saltar_blancos(char *c);
printf(“La primera palabra del texto aparece %d
void leer_letras(char *c, palabra *pal);
veces \n”, num_rep);
booleano comparar_texto(letras t1, letras t2, int long);
}
Leer Primera Leer Palabra
void leer_primera_palabra(char *c, palabra *pal);{ void leer_palabra(char *c, palabra *pal);
*c=getchar(); {
saltar_blancos( c ); saltar_blancos( c );
leer_letras(c,pal); leer_letras(c,pal);
} }
Última palabra Saltar blancos
booleano ultima_palabra(palabra pal);{ void saltar_blancos(char *c); {
return pal.long==0; while (*c==’ ‘) *c=getchar();
} }
Palabras iguales Leer letras
booleano palabras_iguales(palabra pal1, pal2);{ void leer_letras(char *c, palabra *pal); {
booleano iguales; (*pal).long=0;
if(pal1.long==pal2.long) while(*c!=’ ‘ && *c!=’.’) {
iguales=comparar_letras(pal1.cont,pal2.cont,pa (*pal).long++;
l1.long);
(*pal).cont[(*pal).long]=*c;
else iguales==FALSE;
*c=getchar();
return iguales;
}
}
}
Comparar texto
booleano comparar_texto(letras t1, letras t2, int long);
{
int i;
i=0;
while(t1[i]==t2[i] && i<long-1)
i++;
return t1[i]==t2[i];
}

13
6. GESTIÓN DE FICHEROS
Para hacer uso de ficheros en C hay que declarar identificadores con la palabra reservada FILE. Si f1 y f2
son los identificadores lógicos usados para trabajar con dos ficheros en un programa hará falta declararlos
con el formato FILE *f1, *f2;. Con esta declaración, f1 y f2 son direcciones de memoria que indican el lugar
de la memoria central donde se almacenan las características del fichero (nombre, estado, etc.).
En los ficheros de texto, cada registro almacena sólo un carácter. Éstos pueden ser abiertos desde el SO y
ser editados. Las operaciones para gestionar ficheros de acceso secuencial son:
f=fopen("nombre", "rb"); abrir fichero binario para lectura
f=fopen("nombre", "wb"); crear fichero binario para escritura
fread(&x, sizeof(x), 1, f); leer del fichero en la posición del cursor y guardar en x
fwrite(&x,sizeof(x),1,f); escribir en la posición del cursor la variable x
feof (f); final de fichero unlink("nombre"); borrar fichero "nombre"
fclose(f); cerrar fichero
Estas instrucciones son para ficheros binarios. Para ficheros de texto son las mismas sustituyendo "rb" o
"wb" por "r" o "w", respectivamente. La función sizeof() es ejecutada por C en tiempo de compilación y sus
apariciones en código se sustituyen por su valor; el tamaño del tipo de dato que toma como argumento. De
esta manera, durante la ejecución, el programa sabe, por ejemplo, cuantos bits ha de leer del fichero en
cada operación de lectura.
La cadena de caracteres "nombre" identifica al fichero desde el SO. Así, creando un fichero de nombre
AUX.CHT, éste será almacenado en el directorio en que se ejecute el programa que crea el fichero. Si se
quiere almacenar el fichero en otro lugar, se tendrá que especificar la ruta en la misma cadena "nombre".
Por ejemplo, trabajando en un PC bajo DOS, se crea el fichero AUX.CHT en el directorio TRABAJO que
está en el disco duro identificado con D: entonces, se escribe f=fopen("D:\TRABAJO\AUX.CHT", "wb");
Como ejemplo, se muestra el código del algoritmo fusion_condicionada. Se supone un módulo del que se
importa el tipo t_elemento, la constante MAX y las funciones booleanas menor y prop. Además, se supone
que el tipo nombre usado para almacenar nombres de ficheros son cadenas de caracteres. Ello permitirá
usarlos directamente en las instrucciones de apertura de ficheros. El código sería el mostrado.
fus_cond.h fus_cond.c
#include <stdio.h> typedef t_elemento tabla_elem[N];
#include "elemento.h" typedef FILE *tabla_fich[N];
#include "nombres.h" static void abrir_tabla_ficheros(tabla_nombres arch, int
n, tabla_fich fARCH, tabla_elem v, int *num);
#define N 100
static void fusionar_ficheros(FILE *fFinal, tabla_fich
typedef nombre tabla_nombres[N];
fARCH, tabla_elem v, int *num, int n);
void fusion_condicionada(tabla_nombres arch,
static void cerrar_tabla_ficheros(tabla_fich fARCH, int n);
nombre final, int n);
static void avanzar_fich(int i, tabla_fich fARCH,
tabla_elem v, int *num);
static int minimo(tabla_elem v, int n);
Fusión condicionada Abrir tabla ficheros
void fusion_condicionada(tabla_nombres arch, static void abrir_tabla_ficheros(tabla_nombres arch, int
nombre final, int n) { n, tabla_fich fARCH, tabla_elem v, int *num) {
tabla_fich fARCH; int i;
FILE *fFinal; *num=0;
int num; for (i=0; i<n; i++) {
tabla_elem v; fARCH[i]=fopen(arch[i], "wb");
fFinal=fopen(final, "wb"); avanzar_fich(i, fARCH, v, num);
abrir_tabla_ficheros(arch, n, fARCH, v, &num); }

14
fusionar_ficheros(fFinal, fARCH, v, &num, n); }
cerrar_tabla_ficheros(fARCH, n);
fclose(fFinal);
}
Minimo Cerrar tabla ficheros
static int minimo(tabla_elem v, int n); { static void cerrar_tabla_ficheros(tabla_fich fARCH, int n);
{
int min, i;
int i;
min=0;
for (i=0; i<n; i++) fclose(fARCH[i]);
for (i=1; i<n; i++)
}
if (menor(v[i], v[min])) min=i; return min
}
Avanzar fich Fusionar ficheros
static void avanzar_fich(int i, tabla_fich fARCH, static void fusionar_ficheros(FILE *fFinal, tabla_fich
tabla_elem v, int *num); { fARCH, tabla_elem v, int *num, int n); {
fread(&v[i], sizeof(v[i]), 1, fARCH[i]); int i;
if (feof(fARCH[i])) { while (*num<n){
v[i]=MAX; i=minimo(v,n);
*num++; if (prop(v[i])) fwrite(&v[i],sizeof(v[i]),1,fFinal);
} avanzar_fich(i,fARCH, v, num);
} }
}
Nótese la declaración de tipo typedef FILE *tabla_fich[N], con la que se define un vector de N elementos, en
que cada uno es una dirección de memoria que contiene un identificador de fichero, e implica que si fARCH
es una variable de este tipo, la expresión fARCH[i] haga referencia a la i-ésima de esas direcciones. Para
gestionar ficheros de acceso directo, es similar, ya que se dispone de las instrucciones siguientes:
f=fopen("nombre", "r+b"); abrir fichero de acceso directo
fseek(f, sizeof(x)*(p-1), SEEK_SET); Leer en un fichero de acceso directo
fread(&x, sizeof(x), 1, f);
fseek(f, sizeof(x)*(p-1), SEEK_SET); Escribir en un fichero de acceso directo
fwrite(&x, sizeof(x), 1, f);
fclose(f); unlink("nombre"); cerrar fichero / borrar fichero
Estas instrucciones aplican a ficheros binarios. Para ficheros de texto, se sustituyen "r+b" y "w+b" por "r+t" y
"w+t", respectivamente. Las instrucciones de R/W se descomponen en dos partes. En la primera, el cabezal
de R/W se desplaza a lo largo del fichero buscando la posición p, y en la segunda realiza la operación de
lectura o escritura.
En la instrucción fseek, el parámetro SEEK_SET indica que el desplazamiento ha de comenzar desde el
origen del fichero y el segundo argumento que la longitud de este desplazamiento es de p-1 veces el
tamaño sizeof(x) (cosa que sitúa el cabezal en el origen del fichero del p-ésimo registro). C permite también
comenzar el desplazamiento desde la posición actual del cabezal, o desde el final del fichero, con los
parámetros SEEK_CUR y SEEK_END, respectivamente, que se han de pasar como tercer argumento.
En estos casos, el segundo argumento tendrá la forma sizeof(x)*k con un k adecuadamente calculado para
leer o escribir en el registro deseado.

15
7. TIPOS DE DATOS COMPUESTOS
7.1. Enum
La definición de tipos por extensión o typedef enum {valor_1, valor_2, ..., valor_k} nombre_tipo
enumeración define tipos de datos con valores
en un conjunto finito de sintaxis simple.
Con esta definición, los k valores quedan ordenados en el orden de declaración. El hecho de poder hacer
estas identificaciones puede resultar útil en muchos casos. Por ejemplo:
typedef enum {lunes=0, martes=1, miercoles=2, jueves=3,viernes=4} dia_laborable;
7.2. Array
Para definir nuevos tipos de datos con array se usa typedef tipo_conocido tipo_nuevo[dim_1]...[dim_k];
typedef, con el formato del cuadro donde dim_k es la
longitud del k-ésimo dominio.
7.3. Registros o tuplas (struct)
El formato para definir un registro o tupla en C es: typedef struct {
donde, los tipos de la declaración pueden ser tipos tipo_1 nombre_1;
primitivos o definidos previamente y los nombres
...
pueden ser de variables indexadas. Para acceder a
los campos de struct, también se usa el punto. tipo_k nombre_k; } tipo_nombre_nuevo;
Así, si cliente es una variable del tipo persona definido como:
typedef struct {
char nombre[15]; las cliente.nombre Denotan respectivamente, el
expresiones: nombre, la edad y la primera
char apellido[15]; cliente.edad
letra de la ciudad de cliente.
int edad; cliente.ciudad[0]
char ciudad[15]; } persona;

16
8. PROCEDIMIENTOS DE USO GENERAL
Un error habitual en la creación de módulos funcionales es el trato de dimensiones de los vectores que un
subprograma ha de recibir como parámetros. Por ejemplo, en la función que evalúa un polinomio con la
regla de horner, el polinomio se pasa como un vector float a[MAX], siendo MAX declarado como constante.
Si se quiere implementar la misma función independientemente del programa que la invoca, el valor de MAX
será desconocido y, por tanto, el parámetro formal float a[MAX] no tendrá sentido.
Para resolver la situación se hace uso del hecho de que, al recibir float horner(int n, float *a, float x) {
un parámetro por dirección, el procedimiento recibe, la dirección de
int i;
memoria del parámetro, y las consultas y modificaciones que se le
hacen se efectúan directamente sobre esa posición de memoria. float p;
Se puede entonces, pasar la dirección de la primera componente p=0;
del vector junto con un nuevo parámetro de entrada de tipo entero,
for (i=n;i>=0;i=i-1) p=p*x+a[i];
que indique la dimensión del vector en el procedimiento que invoca
al llamado. En el ejemplo, el grado del polinomio cumple esta return p;
última función, y el aspecto general de la función horner sería:
}
En el caso de matrices la situación es análoga. Solo se debe tener en cuenta que C considera una matriz
como un vector de vectores.
Por lo tanto, si se ha declarado float #include <stdio.h>
a[15][20] y se quiere acceder al
#define ind(fil, col, n_col) (fil*n_col+col)
elemento a[3][6], como su dirección
de memoria es a+3*20+6, habría typedef enum{FALSO=0, CIERTO=1} booleano;
que indicar (a+3*20+6).
booleano simetrica(int *mat, int n)
De manera más general, si se tiene
una tabla a[d1][d2]..[dn] la dirección {
de memoria del elemento int n,fil,col,dim,pos;
a[p1][p2]..[pn] será:
booleano sim;
a+p1*(d2*..*dn)+p2*(d3*..*dn)+..+pn
y una manera de evitar la escritura dim=0;
de esta expresión cada vez que sim=CIERTO;
haga falta consiste en poner un
alias con la definición: while (dim!=n-1 && sim) {

#define ind(p1,..,pn, d2,...,dn) pos=0;


(p1*(d2*...*dn)+p2*(d3*..*dn)+...+pn) while (pos!=dim+1 &&
que permite usar la primera *(mat+ind(dim+1,pos,n))==*(mat+ind(pos,dim+1,n)) {
expresión en lugar de la segunda pos++;
en cualquier lugar del programa con
esta definición. }

Como ejemplo, se presenta el sim=*(mat+ind(dim+1,pos,n))==*(mat+ind(pos,dim+1,n));


código de una función de uso dim++;
general que determina si una matriz
es simétrica. La función que se }
implementa recibe la dirección de return sim;
una matriz cuadrada nxn junto con
su dimensión n. }

17