Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
en C
Septiembre de 2012
EDITORIAL
UNIVERSITAT POLITÈCNICA DE VALÈNCIA
Colección Académica
© de la presente edición:
Editorial Universitat Politècnica de València
www.editorial.upv.es
Distribución: pedidos@editorial.upv.es
Tel. 96 387 70 12
ISBN: 978-84-8363-945-0
Impreso bajo demanda
Ref. editorial: 298
Impreso en España
Índice general
Índice general I
1 Introducción a la computación 1
1.1 Tratamiento automático de la información . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Codificación de la información: el sistema binario . . . . . . . . . . . . . . . . . . . . . 2
1.3 Concepto de algoritmo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.4 El proceso de la programación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.5 Lenguajes de programación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.6 El lenguaje C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.7 Compiladores e intérpretes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.4 Comentarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.5 Uso avanzado de printf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.6 Leyendo datos con scanf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.7 Expresiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.7.1 Expresiones aritméticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.7.2 Expresiones relacionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
I
Índice general
2.9 Punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.9.1 Declaración. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.9.2 El operador de dirección & . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.9.3 El operador de indirección * . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.9.4 La constante NULL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3 Estructuras de control 35
3.1 Sentencias de selección . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.1.1 Selección con if-else. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.1.2 Selección con switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4 Funciones 61
4.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
4.2 Funciones de la librerı́a de C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
II
Índice general
5 Vectores 87
5.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
5.2 Declaración de vectores. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
5.3 Acceso a los elementos de un vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
5.4 Operaciones con vectores: automatización mediante bucles. . . . . . . . . . . . . . . . 90
5.5 Paso de vectores a una función. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
5.6 Devolución de vectores en una función. . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
5.7 Vectores multidimensionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
5.8 Operaciones con matrices: automatización mediante bucles . . . . . . . . . . . . . . . 99
5.9 Paso de matrices a una función . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
5.10 Relación entre vectores y punteros. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
5.11 Cadenas de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
5.11.1 Entrada y salida con cadenas de caracteres . . . . . . . . . . . . . . . . . . . . . . . . 106
5.11.2 Funciones de manipulación de cadenas de caracteres . . . . . . . . . . . . . . . . . . 109
III
Índice general
6 Estructuras 117
6.1 Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
6.2 Declaración de variables de tipos estructurados . . . . . . . . . . . . . . . . . . . . . . . 117
6.2.1 Definición de tipos estructurados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
6.2.2 Declaración de variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
Bibliografı́a 151
IV
Capı́tulo 1
Introducción a la computación
o tediosa para un humano, serı́a deseable realizar este proceso de forma automática. En
este sentido, los ordenadores tienen la capacidad de procesar gran cantidad de datos a una
velocidad de miles de millones de operaciones por segundo.
Para que un ordenador pueda llevar a cabo cualquier tarea, deberá disponer de los elemen-
tos hardware necesarios para la entrada de datos (teclado, ratón, ...), para el procesamiento
de los mismos (procesador o CPU), para la salida de resultados (monitor, impresora, ...) y
para el almacenamiento temporal o permanente de instrucciones y datos (memoria, disco
duro, ...). Además de todos estos elementos, será necesario introducir en el ordenador las
instrucciones necesarias para realizar las operaciones deseadas sobre los datos, esto es,
los programas (software). Los programas son los que hacen que un ordenador sea versátil
y pueda utilizarse para realizar tareas muy diversas. En este libro aprenderemos a crear
dichos programas, pero antes será necesario tener unos conocimientos mı́nimos sobre el
modo en que un ordenador codifica la información.
En el sistema decimal que empleamos los humanos, cada dı́gito tiene un peso distinto
según la posición que ocupa. De derecha a izquierda tenemos las unidades, decenas, cen-
tenas, etc, donde el dı́gito que representa las unidades tiene un peso de 100 , el de las
decenas de 101 , el de las centenas de 102 , y ası́ sucesivamente. De modo similar, en el sis-
tema binario, empleado por los ordenadores y otros dispositivos electrónicos, cada dı́gito,
de derecha a izquierda, tiene un peso de 20 , 21 , 22 , etc.
A los sistemas decimal y binario (por poner algunos ejemplos de sistemas de numeración)
se los denomina también sistema en base 10 y en base 2 respectivamente. En general,
para conocer el valor decimal de cualquier número N expresado en base B aplicaremos la
fórmula: n
N = x0 B 0 + x1 B 1 + ... + xn B n = xi B i
i=0
Denominamos bit a un dı́gito binario 0/1 y byte a una agrupación de 8 bits. Un bit úni-
camente nos permite diferenciar entre dos estados posibles. Con una secuencia de 2 bits
2
1.2 Codificación de la información: el sistema binario
pueden formarse 4 combinaciones distintas (00, 01, 10 y 11), con 3 bits 8 combinaciones
(000, 001, 010, 011, 100, 101, 110 y 111). En general, con n bits pueden formarse 2n
combinaciones. Podemos codificar los elementos de un conjunto dado (números enteros,
letras, colores, etc.) mediante distintas secuencias de bits. Por ejemplo, con un byte (8
bits) pueden codificarse 28 = 256 elementos. Con 4 bytes (32 bits) pueden codificarse
más de cuatro mil millones de elementos.
Siguiendo con las unidades de medida, diremos que un kilobyte (1 KB) son 1024 bytes,
un gigabyte (1 GB) son 1024 KB y un terabyte (1 TB) son 1024 GB (esto es, más de mil
millones de bytes).
Ya hemos visto cómo un ordenador puede representar números enteros mediante el siste-
ma binario, pero también debe ser capaz de representar números reales, caracteres, imáge-
nes, sonido y por supuesto instrucciones, por poner sólo algunos ejemplos. Es necesario
disponer de distintos sistemas de codificación para poder representar información de
cualquier tipo.
Para codificar los números reales en el sistema binario se utiliza lo que denominamos
representación en coma flotante o formato cientı́fico. Por ejemplo, el número 12.5 (el
cual acabamos de escribir en su notación habitual o coma fija) se expresarı́a en notación
cientı́fica como 0.125E2. La representación binaria de dicho número consiste en la utili-
zación de una cierta cantidad de bits para representar lo que se denomina mantisa (0.125)
y otra cierta cantidad de bits para representar el exponente. Valiéndonos del sistema BCD
utilizado para números enteros, codificarı́amos por un lado el número 125 (mantisa en
formato normalizado) y por otro lado el exponente (2 en este ejemplo).
Otro sistema de codificación es el ASCII (American Standard Code for Information Inter-
change) utilizado para la representación de caracteres alfanuméricos. Para ello se define
una tabla de correspondencia, en la que a cada carácter se le asocia un código binario.
Dicha tabla de correspondencia es un estándar que utilizan todos los sistemas informáti-
cos, lo que permite el intercambio de información entre distintos sistemas. Imaginemos
algo tan habitual como enviar un correo electrónico. El texto en él contenido, se repre-
sentará mediante en una secuencia de 1’s y 0’s, resultado de codificar cada uno de los
caracteres del mensaje mediante la tabla ASCII. Dicha secuencia será sencillamente des-
cifrable por cualquier equipo informático, sin más que aplicar de nuevo la tabla de con-
versión ASCII en sentido inverso. Resulta evidente que sin la existencia de estándares de
codificación, no serı́a posible compartir información entre los distintos ordenadores.
3
Capı́tulo 1. Introducción a la computación
Aunque los algoritmos datan de tiempos babilónicos y los griegos ya diseñaron algorit-
mos, aún famosos hoy en dı́a, como por ejemplo el de Euclides (300 A.C.) para calcular
el máximo común divisor de dos números, fue el matemático persa Al-Khowarizmi (si-
glo IX) el primero que diseñó algoritmos pensando en su eficiencia, en particular, para el
cálculo de raı́ces de ecuaciones. La palabra algoritmo procede de las sucesivas deforma-
ciones sufridas hasta hoy en dı́a del nombre Al-Khowarizmi1 .
a b
------------
5 15
15 5
5 10
10 5
5 5
Finitud: Un algoritmo tiene que acabar siempre tras un número finito de pasos.
1 Hay muchas variantes para el nombre de Al Khowarizmi al usar el alfabeto latino, tales como Al-Khorezmi,
4
1.4 El proceso de la programación
Definibilidad: Toda regla debe definir perfectamente la acción a desarrollar sin que
pueda haber ambigüedad alguna de interpretación.
General: Un algoritmo no debe solucionar un problema aislado particular, sino
toda una clase de problemas para los que los datos de entrada y los resultados
finales pertenecen respectivamente a conjuntos especı́ficos.
Eficacia: Se debe pretender que un algoritmo sea eficaz, esto es, que su ejecución
resuelva el problema en el mı́nimo número de operaciones posible.
Lenguajes de bajo nivel: Son lenguajes cuya sintaxis está más próxima a la máqui-
na (al ordenador) que al humano. El lenguaje de más bajo nivel es el código máqui-
na (cuyas instrucciones son secuencias de unos y ceros), seguido por el lenguaje
ensamblador. Los lenguajes de bajo nivel resultan poco legibles para el humano.
Lenguajes de alto nivel: Son lenguajes cuya sintaxis está más próxima al lenguaje
natural de los humanos y, por tanto, resultan más comprensibles. Existen multitud
de lenguajes de alto nivel como C, C++, Java, Pascal, Basic, PHP, etc. (por citar
sólo unos pocos ejemplos).
1 #include <stdio.h>
2
3 int main() {
4 int a, b, c;
5
1 program suma;
2
3 var a,b,c:integer;
4 begin
5 writeln(’Introduce 2 números enteros’);
6
1.6 El lenguaje C
6 readln(a,b);
7 c:= a + b;
8 writeln(’La suma es ’,c);
9 end
1 model small
2 .stack
3 .data
4 var1 db ?
5 .code
6 .startup
7 mov ah,01h
8 int 21h
9 sub al,30h
10 mov var1,al
11 mov ah,01h
12 int 21h
13 sub al,30h
14 add al,var1
15 mov dl,al
16 add dl,30h
17 mov ah,02h
18 int 21h
19 ;.exit
20 end
1.6 El lenguaje C
C es un lenguaje estructurado de alto nivel de propósito general. Esto quiere decir que sir-
ve para distintos propósitos: programas cientı́ficos, programas de gestión, comunicación,
manejadores de dispositivos (drivers), sistemas operativos, compiladores, etc.
C dispone de las estructuras tı́picas de los lenguajes de alto nivel pero, a su vez, permite
trabajar a bajo nivel, por lo que algunos autores lo clasifican como lenguaje de medio
nivel.
7
Capı́tulo 1. Introducción a la computación
creación del lenguaje C va ligada a otro fenómeno de la informática: la creación del sis-
tema operativo UNIX, desarrollado también en los laboratorios Bell. El núcleo (o kernel)
del sistema operativo Unix está escrito casi en su totalidad en lenguaje C, con excepción
de alguna parte escrita en ensamblador. También el sistema operativo Linux y muchas de
sus aplicaciones están escritas en este mismo lenguaje.
Como puede deducirse, un programa compilado se ejecutará a mayor velocidad que uno
interpretado, ya que en el primer caso la traducción se hace una única vez, de modo que
se almacena en un archivo aparte el programa traducido (denominado código máquina
o ejecutable) y a continuación se ejecuta tantas veces como se desee el código máquina
generado, mientras que en el segundo caso es necesario ir traduciendo las instrucciones
durante la propia ejecución.
8
Capı́tulo 2
int main() {
sentencia_1;
sentencia_2;
. . .
sentencia_N;
}
Todo programa en C está compuesto por una serie de instrucciones o sentencias1 , se-
paradas por punto y coma. Estas instrucciones se encuentran dentro de la función main.
En el Capı́tulo 4 se estudiarán en detalle las funciones, de momento es suficiente con
entender que una función es un módulo o bloque de nuestro programa que contiene una
serie de instrucciones y que la función main (función principal) es el punto de inicio de
todo programa en C. Para delimitar el conjunto de instrucciones que contiene una función
(main en nuestro caso) se utilizan los sı́mbolos { y }.
1A lo largo de este libro vamos a utilizar indistintamente los términos instrucción y sentencia.
9
Capı́tulo 2. Elementos básicos de un programa en C
1 int main() {
2 return 0;
3 }
aunque este programa no harı́a absolutamente nada ya que la única instrucción que con-
tiene (return 0) indica la finalización del mismo.
1 #include <stdio.h>
2
3 int main() {
4 printf("Hola");
5 return 0;
6 }
Hola
1 printf("Texto a mostrar");
1 #include <stdio.h>
Sin entrar en detalle, diremos que es necesario incluir esta instrucción para poder utilizar
printf en nuestros programas. En el Apartado 2.10 se explican este tipo de instrucciones
10
2.3 Variables y tipos de datos
con mayor profundidad. De momento únicamente es necesario saber que cualquier pro-
grama que utilice la instrucción printf deberá contener al inicio del mismo la sentencia
#include <stdio.h>.
2.3.1 Variables
Una variable puede verse como un contenedor donde almacenar un dato determinado. En
realidad una variable representa una posición de memoria en la que se encuentra almace-
nado un dato. Las variables son un mecanismo que ofrecen los lenguajes de programación
para facilitar el acceso a los datos sin necesidad de conocer las posiciones o direcciones
de memoria en que se encuentran almacenados.
El primer carácter debe ser una letra o el carácter subrayado ( ), mientras que el
resto pueden ser letras, dı́gitos o el carácter subrayado ( ). Las letras pueden ser
minúsculas o mayúsculas del alfabeto inglés. Por lo tanto, no está permitido el uso
de las letras ’ñ’, ’Ñ’, vocales acentuadas, sı́mbolos especiales, etc.
No pueden existir dos variables con el mismo nombre dentro de una misma función.
El nombre de una variable no puede ser una palabra reservada. Las palabras re-
servadas son identificadores propios del lenguaje. Por ejemplo, int y main son
palabras reservadas del lenguaje C.
Por ejemplo, lo siguiente serı́an nombres válidos de variables: x, y, v1, v2, temperatura,
velocidad_maxima, Vmax.
11
Capı́tulo 2. Elementos básicos de un programa en C
En la Tabla 2.1 se muestran los tipos básicos en C, el tamaño que ocupa cada uno de ellos
en memoria y el rango de valores que pueden almacenar.2
Como puede observarse en la Tabla 2.1, el tipo char se puede utilizar tanto para represen-
tar caracteres como números enteros pequeños. En la Sección 2.8.1 se explica este tipo de
dato con más detalle. El tipo puntero se emplea para almacenar direcciones de memoria.
En la Sección 2.9 se explican los punteros.
Los tipos char, int, float y double también existen en su versión unsigned (sin signo):
unsigned char, unsigned int, unsigned float y unsigned double. Las varia-
bles de estos tipos únicamente pueden almacenar números positivos, pero con la ventaja
de duplicar el rango de la parte positiva. Por ejemplo, una variable de tipo unsigned char
puede almacenar números enteros entre 0 y 255.
Algunos tipos de datos también admiten los modificadores short y long para disminuir y
aumentar respectivamente el rango de valores (y consecuentemente el espacio de memoria
empleado). Por ejemplo, una variable de tipo short int ocupa 2 bytes (en lugar de los 4
que ocupa el int) y permite almacenar valores entre -32768 y 32768. Una variable de tipo
2 En realidad el tamaño y rango de valores pueden variar en función de la implementación. Los valores dados
unsigned short int ocupa igualmente 2 bytes, pero en este caso puede almacenar
valores entre 0 y 65535.
tipo_de_dato nombre_variable;
Por ejemplo, para declarar una variable de tipo int y nombre x utilizarı́amos la siguiente
sentencia:
1 int x;
Si se desea declarar más de una variable del mismo tipo, puede hacerse separándolas por
comas del siguiente modo:
1 int x, y, z;
2 float a, b;
Al inicio de cada función (por ejemplo de la función principal main). A estas va-
riables se las denomina variables locales.
1 int main() {
2 int x;
3
4 . . .
5 }
13
Capı́tulo 2. Elementos básicos de un programa en C
1 int main() {
2 float pi;
3 pi = 3.14;
4
5 . . .
6 }
1 3.14 = pi;
Obviamente el valor que recibe la variable debe de ser compatible con el tipo de dato de
la misma. Por ejemplo, no serı́a adecuado declarar una variable de tipo int y tratar de
almacenar en ella un valor real, tal y como muestra el siguiente ejemplo:
1 int main() {
2 int x;
3 x = 2.75;
4
5 . . .
6 }
1 int main() {
2 float area;
3 area = 3.14 * 4;
4
5 . . .
6 }
14
2.3 Variables y tipos de datos
En las operaciones también pueden aparecer variables. En este caso la variable que for-
ma parte de la operación se sustituye por su valor. Por ejemplo, el siguiente programa
almacena en la variable perim el resultado de la operación 2 × 3,14 × 3.
1 int main() {
2 float pi, radio, perim;
3 pi = 3.14;
4 radio = 3;
5 perim = 2 * pi * radio;
6
7 . . .
8 }
1 nombre_variable = expresion;
donde expresion puede ser un valor constante (por ejemplo x=2), una variable (por
ejemplo x=y), una operación (por ejemplo x=2*y), etc. En la Sección 2.7 se explica con
más detalle el concepto de expresión. El efecto de la operación de asignación será alma-
cenar en nombre_variable el resultado de expresion.
Es muy importante tener la precaución de que cuando se utilice una variable como parte
de una expresión, dicha variable tenga un valor asignado previamente, de lo contrario el
resultado de la expresión será impredecible. Por ejemplo el siguiente programa, aunque
es sintácticamente correcto, su resultado es impredecible:
1 int main() {
2 float pi, radio, perim;
3 pi = 3.14;
4 perim = 2 * pi * radio;
5 radio = 3;
6
7 . . .
8 }
15
Capı́tulo 2. Elementos básicos de un programa en C
El que a una variable no le asignemos nada de forma explı́cita no quiere decir que no
contenga ningún valor, sino que éste es desconocido. En el momento de declarar una
variable, ésta ya contiene un valor, resultado de lo que hubiese previamente en la zona de
memoria que se le asigna, sin embargo, como se ha dicho, este valor es impredecible.
La operación de asignación es destructiva. Esto quiere decir que cuando a una variable
se le asigna un valor, pierde el valor que tuviera anteriormente.
2.4 Comentarios
En un programa es posible (y recomendable) introducir comentarios. Un comentario es
un texto que incluimos en nuestros programas pero que el compilador ignora por com-
pleto. El objetivo de los comentarios es hacer los programas más legibles, ayudando a la
comprensión de los mismos.
Para añadir un comentario de lı́nea se utiliza //. El texto que aparece desde //
hasta el final de la lı́nea es ignorado por el compilador. Por ejemplo:
1 int main() {
2 // Declaración de variables
3 float r; // Radio del cı́rculo
4 float a; // Area del cı́rculo
5
6 a = 3.14 * r * r;
7 . . .
8 }
16
2.5 Uso avanzado de printf
1 #include <stdio.h>
2
3 int main() {
4 int a, b, c;
5
6 a = 2;
7 b = 3;
8 c = a + b;
9 printf("La suma de a y b es c");
10 return 0;
11 }
El error del programa anterior radica en que la instrucción printf, tal y como se ha
escrito, mostrará literalmente el texto especificado. Esto es, la ejecución de este programa
mostrará por pantalla el mensaje:
La suma de a y b es c
La suma de 2 y 3 es 5
Podemos observar que en la instrucción printf aparece en primer lugar un texto ence-
rrado entre dobles comillas, seguido de una serie de variables separadas por comas. El
texto encerrado entre comillas contiene una serie de códigos especiales %d. Los códigos %
(existen más aparte de %d) indican que esta parte del texto debe ser sustituida por el valor
de alguna expresión (una variable en el caso más sencillo). Concretamente, el código %d
indica que, en la posición en la que aparece, debe mostrarse el valor de una expresión que
de como resultado un valor entero (por ejemplo una variable de tipo int). La expresión
o variable a mostrar será alguna de las que aparezcan a continuación del texto encerra-
17
Capı́tulo 2. Elementos básicos de un programa en C
do entre comillas. La asociación entre los códigos % y las expresiones que aparecen a
continuación se hace por orden de aparición. En consecuencia, en el ejemplo anterior, el
primer código %d se asocia a la variable a, el segundo a la variable b y el tercero a c.
Debe observarse que es necesario que la instrucción printf contenga tantas expresiones
(variables en el ejemplo anterior) como códigos %.
En la Tabla 2.2 se muestra los códigos % (especificadores) que pueden aparecer en una
instrucción printf. En la primera parte de la tabla se muestran los que se usan de forma
más habitual y en los que, de momento, nos centraremos.
1 #include <stdio.h>
2
3 int main() {
4 int entero = 47;
5 float real = 128.75;
6 char car = ’A’;
7
20 return 0;
21 }
El porqué las variables deben ir precedidas por el sı́mbolo & es algo que se verá en la
Sección 2.9. De momento basta con saber que es necesario hacerlo de este modo.
1 #include <stdio.h>
2
3 int main() {
4 float a, b, c;
5
Como se puede observar en el programa anterior, es habitual que cada instrucción scanf
vaya precedida de un printf para indicar al usuario de lo que debe hacer.
También es posible leer más de un dato con una única instrucción scanf. Por ejemplo:
En este caso el programa se detendrá en la instrucción scanf hasta que el usuario haya
introducido los dos valores.
20
2.7 Expresiones
2.7 Expresiones
Una expresión es una combinación de constantes, variables, sı́mbolos de operación, parénte-
sis y funciones. Por ejemplo, lo siguiente son expresiones válidas en C:
1 a-(b+3)*c
2 2*3.1416*r
3 sin(x)/2
En la última expresión del ejemplo aparece una llamada a la función seno (sin). Las fun-
ciones se estudiarán en el Capı́tulo 4.
Las expresiones se evalúan a un valor determinado, esto es, el resultado de una expre-
sión siempre es un valor (entero o real). Las expresiones en C pueden clasificarse en tres
categorı́as:
Aritméticas
Relacionales
Lógicas
se desean las dos últimas cifras habrá que hacer n % 100 (127 % 100 = 27). Para saber
si un número n es par o impar habrá que calcular n % 2. Si el resultado de esta operación
es 0 quiere decir que n es par, mientras que si el resultado es 1 significará que n es impar.
Para evaluar las expresiones aritméticas existen unas reglas de prioridad y asociatividad:
También es posible escribir el operador antes del operando (++n y --n). Esto no introduce
ninguna diferencia cuando la operación se hace de forma aislada, aunque es importante
tenerlo en cuenta cuando esta operación se combina con otras dentro de la misma ex-
presión, ya que modifica el orden en el que se realiza el incremento o decremento. Por
ejemplo x / y++ calcula primero la división x / y y a continuación incrementa la va-
riable y, mientras que x / ++y incrementa en primer lugar el valor de y y a continuación
realiza la división (con el valor de y ya incrementado).
Operadores reducidos
Los operadores reducidos más habituales son +=, -=, *= y /=. Estos operadores permiten
expresar de forma más compacta algunas operaciones. Por ejemplo, x += 2 equivale a
la operación x = x + 2 (esto es, incrementa el valor de la variable x en dos unidades).
x *= 5 equivale a la operación x = x * 5.
22
2.7 Expresiones
1 #include <stdio.h>
2
3 int main() {
4 int x, y, z;
5
6 x = 6;
7 x--; // x vale 5
8 y = x / 2; // y vale 2 (se realiza la división entera)
9 z = y++; // z vale 2 e y vale 3 (primero se realiza
10 // la asignación y luego el incremento)
11 x += 3; // x vale 8
12 x *= 2; // x vale 16
13 z = x % 7; // z vale 2 (el resto de dividir 16 entre 7)
14
15 return 0;
16 }
a == 4 || a+b < 8 (a igual a cuatro o a+b menor que ocho) se evalúa a 1 (cierto).
exp1 && exp2 se evalúa a cierto cuando tanto exp1 como exp2 son ciertas. En
cualquier otro caso se evalúa a falso.
exp1 || exp2 se evalúa a cierto cuando alguna de las expresiones exp1 o exp2
son ciertas. Se evalúa a falso cuando tanto exp1 como exp2 son falsas.
!exp1 se evalúa a cierto cuando exp1 es falso y viceversa.
La interpretación de qué carácter hay almacenado en una variable de tipo char se realiza
mediante una tabla de conversión. La tabla más conocida (por ser la primera que apareció)
es el estándar ASCII (American Standard Code for Information Interchange). En la Ta-
bla 2.4 se muestra la tabla ASCII. En esta tabla, los códigos del 0 al 31 en realidad no son
caracteres imprimibles sino que representan códigos de control (por ejemplo, el código
13 representa el salto de lı́nea o Carriage Return, mientras que el código 27 representa
la tecla Escape). Entre el 65 y el 90 se codifican las letras mayúsculas, mientras que las
minúsculas ocupan los códigos del 97 al 122. Los caracteres numéricos (dı́gitos del 0 al
9) aparecen entre los códigos 48 al 57.
24
2.8 Otros conceptos sobre tipos de datos
Debe aclararse que la tabla ASCII se definió para 7 bits, lo que permite representar 128
caracteres. Esto es insuficiente para muchas lenguas que utilizan caracteres que no están
representados en esta tabla (vocales acentuadas, Ñ, Ç, etc.). Por ello se definieron otros
estándares de 8 bits que permitı́an extender el número de caracteres a 256 (por ejemplo el
ISO-8859-1).
En C los caracteres deben encerrarse entre comillas simples. Por ejemplo, para almacenar
el carácter A en una variable lo haremos del siguiente modo:
1 char c;
2 c = ’A’;
Dado que las variables de tipo char realmente almacenan números, es posible realizar
operaciones aritméticas con este tipo de variables, tal y como se muestra en el siguiente
programa:
1 #include <stdio.h>
2
3 int main() {
4 char c;
5
22 return 0;
23 }
1 float f;
2 f = 5;
Si ordenamos los tipos de datos de menor a mayor capacidad (char, int, float, double),
cualquier conversión de un tipo de menor capacidad a otro de mayor puede realizarse sin
pérdida de precisión, mientras que una conversión en el otro sentido (de mayor a menor
capacidad) puede conllevar pérdida de información. El compilador realiza de forma au-
tomática estos cambios de tipo, si bien en los casos que impliquen pérdida de información
puede generar un warning para advertir al programador del posible error.
Mediante esta conversión explı́cita el programador deja claro que es consciente del cam-
bio de tipo que se va a producir, con la consecuente pérdida de precisión.
También hay ocasiones en las que se desea forzar un cambio de tipo ya que el compilador
no lo hace de manera automática. Tomemos el siguiente ejemplo:
En este caso el valor almacenado en media será 2.0 y no 2.5. Esto es debido (tal y como
se explicó en la Sección 2.7.1) a que la operación suma/n realiza la división entera, ya
que ambos operandos (suma y n) son enteros. El hecho de que el resultado se almacene
posteriormente en una variable de tipo float no cambia para nada la situación ya que en
este caso la pérdida de precisión se produce en la propia operación de división y no en la
posterior asignación. Para solucionar este problema deberı́a forzarse el cambio de tipo de
al menos uno de los operandos para que se realice la división real, tal y como se muestra
a continuación:
En este caso la variable suma se cambia temporalmente a tipo float, con lo que se
realiza la división real. Debe quedar claro que este cambio de tipo afecta únicamente
a la operación en la que aparece el casting, en el resto del programa la variable suma
seguirá siendo de tipo entero.
27
Capı́tulo 2. Elementos básicos de un programa en C
2.9 Punteros
La memoria del ordenador puede verse como una serie de celdas en las que se almacena la
información. Cada una de estas celdas tiene asociada una dirección de memoria. Cuando
declaramos una variable (por ejemplo int x), a ésta se le asigna una celda de memoria
con una dirección conocida. Hasta ahora no nos habı́amos preocupado por la dirección
de memoria en la que se ubican nuestras variables, nos bastaba con conocer su nombre
para poder acceder al dato almacenado en las mismas. Este es el momento de empezar a
preguntarnos en qué dirección se encuentran las variables de nuestros programas.
Una variable de tipo puntero es una variable que almacena direcciones de memoria. Nor-
malmente se utilizan para almacenar la dirección de memoria en la que se encuentra
alguna otra variable. Cuando una variable de tipo puntero (por ejemplo p) contiene la
dirección de memoria de alguna otra variable (por ejemplo x) decimos que el puntero p
apunta a la variable x. Como veremos más adelante, una variable de tipo puntero de-
be conocer el tipo de dato de la variable a la cual apunta (o, en general, el tipo de dato
almacenado en la dirección de memoria que contiene).
2.9.1 Declaración
Para declarar una variable de tipo puntero se emplea la siguiente sintaxis:
tipo_del_dato_apuntado * nombre_del_puntero;
Por ejemplo
1 int * p;
declara una variable puntero de nombre p que debe utilizarse para almacenar direcciones
de memoria en las que se encuentren datos de tipo int.
1 #include <stdio.h>
2
3 int main() {
4 int x = 3;
5 printf("La variable x contiene el valor %f y se encuentra en
la dirección %d\n", x, &x);
28
2.9 Punteros
6 return 0;
7 }
1 #include <stdio.h>
2
3 int main() {
4 int x = 3;
5 int * p; // Declaramos una variable de tipo puntero a
entero
6 p = &x; // Guardamos en p la dirección de memoria de la
variable x
7 printf("La variable x contiene el valor %f y se encuentra en
la dirección %d\n", x, p);
8 return 0;
9 }
1 #include <stdio.h>
2
3 int main() {
4 int x = 3, y;
5 int * p; // Declaramos una variable de tipo puntero a
entero
6 p = &x; // Guardamos en p la dirección de x
7 y = *p; // Almacenamos en y el valor de la variable
8 // apuntada por p, esto es, el valor de x
9 *p = 2; // Ahora x vale 2
10 p = &y; // p contiene la dirección de y (p apunta a y)
11 *p = 5 * 3; // Ahora y vale 15
12 return 0;
13 }
29
Capı́tulo 2. Elementos básicos de un programa en C
El porqué nos interesa almacenar la dirección de una variable en un puntero y luego ac-
ceder a dicha variable a través de su puntero en lugar de utilizar su propio nombre es algo
que a estas alturas es difı́cil de explicar, aunque debe decirse que es de gran importan-
cia entender el manejo de punteros para poder entender en profundidad muchos aspectos
de la programación. De forma muy simplificada diremos que en ocasiones puede haber
alguna parte de un programa en la que lo único que se conozca de algunas variables sea
las direcciones de memoria que ocupan pero no sus nombres (los cuales pueden inclu-
so ni existir). En estos casos el único modo de acceder a dichas variables es mediante
el manejo de punteros. De momento basta con entender la sintaxis y manejo de punte-
ros, en capı́tulos posteriores, en la Sección 4.5.2, se verá una situación en la que resultan
imprescindibles.
1 int * p;
2 p = NULL; // p no contiene ninguna dirección de memoria
Para poder utilizar la constante NULL debe incluirse el fichero stdlib.h mediante la
instrucción
1 #include <stdlib.h>
30
2.10 Directivas del precompilador
#include <fichero>
#include "fichero"
1 #include <stdio.h>
El fichero stdio.h (standard input output header) contiene el código necesario para que
se compilen correctamente todas las funciones relacionadas con la entrada/salida (inpu-
t/output) de nuestro programa, como por ejemplo printf y scanf. En la Sección 4.3.5
se explica con más detalle los ficheros de cabecera.
La diferencia entre utilizar los corchetes angulados (<...>) y las dobles comillas ("...")
radica en que, en el primer caso, el fichero incluido será buscado en los directorios que
el compilador tenga configurados a tal efecto, mientras que en el segundo caso el fichero
incluido será buscado en el mismo directorio en el que se encuentra el fichero fuente.
31
Capı́tulo 2. Elementos básicos de un programa en C
1 #define PI 3.1416
define la constante PI con el valor especificado. Una vez definida una constante, ésta
puede utilizarse a lo largo de todo el programa:
1 #include <stdio.h>
2 #define PI 3.1416
3
4 int main() {
5 float radio, perim;
6
1 perim = 2 * PI * radio;
32
2.11 Ejercicios resueltos
1 #include <stdio.h>
2
3 int main() {
4 float base, alt, area, perim;
5
12 // Calcular resultados
13 area = base * alt;
14 perim = 2*base + 2*alt;
15
16 // Mostrar resultados
17 printf("Area = %f\n", area);
18 printf("Perı́metro = %f\n", perim);
19
20 return 0;
21 }
1 #include <stdio.h>
2
3 int main() {
4 int a, b=5, res;
5 char c=’A’;
6
7 a = 2;
8 printf(" %d\n", b/a);
9 a *= 2;
10 b--;
11 printf(" %d\n", a==b);
12 c += a;
13 printf(" %c\n", c);
14 res = ( c==’A’ || ( a > 0 && b < 5 ) );
15 printf(" %d\n", res);
16
33
Capı́tulo 2. Elementos básicos de un programa en C
17 return 0;
18 }
SOLUCIÓN:
2
1
E
1
1 #include <stdio.h>
2
3 int main() {
4 int a, b, c;
5 int * p1, * p2;
6
7 a = 2;
8 p1 = &b;
9 p2 = &c;
10 *p1 = a * 2;
11 *p2 = *p1 + 3;
12 printf("a= %d b= %d c= %d\n", a, b, c);
13 p2 = &a;
14 *p1 += *p2;
15 printf("a= %d b= %d c= %d\n", a, b, c);
16 c = *p1 + *p2;
17 printf("a= %d b= %d c= %d\n", a, b, c);
18 return 0;
19 }
34
Capı́tulo 3
Estructuras de control
En los programas que hemos realizado hasta ahora, cada una de las instrucciones se eje-
cuta en modo secuencial, una tras otra y una única vez. Sin embargo, es habitual que
los programas necesiten ejecutar, en función de cierta condición, un grupo de instruccio-
nes u otro (ejecución condicional), o que, por ejemplo, requieran ejecutar un bloque de
instrucciones más de una vez (bucle). Estas situaciones se resuelven mediante lo que se
denomina sentencias de selección y sentencias de repetición.
if( expresion ) {
instruccion_1_1;
instruccion_1_2;
. . .
instruccion_1_N;
}
else {
instruccion_2_1;
35
Capı́tulo 3. Estructuras de control
instruccion_2_2;
. . .
instruccion_2_N;
}
Por ejemplo, el siguiente programa indica si cierta nota introducida por teclado corres-
ponde a un aprobado o a un suspenso:
1 #include <stdio.h>
2
3 int main() {
4 float nota;
5
Cuando alguno de los bloques if o else consta de una única instrucción, entonces las
llaves son opcionales.
if( expresion )
instruccion_1;
else
instruccion_2;
Por lo tanto la instrucción if-else del ejemplo anterior también podrı́a haberse escrito
como:
Por otro lado, debe resaltarse que el bloque else es opcional. En el siguiente ejemplo se
utiliza una instrucción if sin el bloque else:
36
3.1 Sentencias de selección
1 #include <stdio.h>
2
3 int main() {
4 float precio;
5 char aplicar_descuento;
6
17 return 0;
18 }
Las instrucciones if-else pueden anidarse, esto es, tanto dentro del bloque if como del
else pueden aparecer otras instrucciones if-else. En el siguiente ejemplo se muestra
un programa que solicita tres números y muestra el mayor de ellos mediante el uso de
if-else anidados:
1 #include <stdio.h>
2
3 int main() {
4 float a, b, c, max;
5
37
Capı́tulo 3. Estructuras de control
22
23 return 0;
24 }
1 . . .
2
if( expresion_1 ) {
instrucciones;
}
else if (expresion_2) {
instrucciones;
}
else if (expresion_3) {
instrucciones;
}
. . .
Debe observarse que el código que acabamos de escribir coincide con el que se muestra a
continuación, aunque el primero resulta más legible:
if( expresion_1 ) {
instrucciones;
}
else {
if (expresion_2) {
instrucciones;
}
else {
38
3.1 Sentencias de selección
if (expresion_3) {
instrucciones;
}
. . .
}
}
1 #include <stdio.h>
2
3 int main() {
4 float nota;
5
18 return 0;
19 }
39
Capı́tulo 3. Estructuras de control
switch( expresion ) {
case valor_1: instrucciones;
case valor_2: instrucciones;
. . .
case valor_N: instrucciones;
default: instrucciones;
}
1 #include <stdio.h>
2
3 int main() {
4 int dia;
5
9 switch( dia ) {
10 case 1: printf("LUNES\n"); break;
11 case 2: printf("MARTES\n"); break;
12 case 3: printf("MIERCOLES\n"); break;
13 case 4: printf("JUEVES\n"); break;
14 case 5: printf("VIERNES\n"); break;
15 case 6: printf("SABADO\n"); break;
16 case 7: printf("DOMINGO\n"); break;
17 default: printf("Dı́a incorrecto\n");
18 }
19 return 0;
20 }
Debe tenerse en cuenta que si se encuentra un case que coincida con el valor de la expre-
sión (en este ejemplo dia), se ejecutan todas las instrucciones que aparezcan a continua-
ción, hasta que se alcance una instrucción break. Esto quiere decir que si en el ejemplo
anterior no se hubieran incluido las instrucciones break y el usuario introduce, por ejem-
plo, un 6, se hubiera ejecutado no sólo la instrucción printf("SABADO\n") sino tam-
bién las instrucciones printf("DOMINGO\n") y printf("Dı́a incorrecto\n"). Lo
habitual será, por tanto, que cada grupo de instrucciones especificados en un case termi-
ne con la instrucción break. Sin embargo, tal y como se muestra en el ejemplo siguiente,
habrá ocasiones en las que interese no incluir la instrucción break.
1 #include <stdio.h>
2
3 int main() {
4 int curso;
5
11 switch( curso ) {
12 case 1: printf("PROGRAMACIÓN\n");
13 case 2: printf("ALGORÍTMICA\n");
14 case 3: printf("PROGRAMACIÓN AVANZADA\n");
15 case 4: printf("PROGRAMACIÓN DE REDES\n");
16 case 5: printf("INGENIERIA DEL SOFTWARE\n"); break;
17 default: printf("Curso incorrecto\n");
18 }
19 return 0;
20 }
Obsérvese que, una vez se entra en un case, se ejecuta el resto de instrucciones hasta
alcanzar una instrucción break.
41
Capı́tulo 3. Estructuras de control
while( expresion ) {
instruccion_1;
instruccion_2;
. . .
instruccion_N;
}
Al igual que ocurre con la instrucción if-else, si el bucle contiene una única instrucción,
las llaves se pueden omitir.
El siguiente ejemplo muestra 10 veces el texto “Hola mundo” y a continuación una vez el
texto “Fin del programa”.
1 #include <stdio.h>
2
3 int main() {
4 int i = 0;
5 while( i < 10 ) {
6 printf("Hola mundo\n");
7 i++;
8 }
9 printf("Fin del programa\n");
10 return 0;
11 }
42
3.2 Sentencias de repetición
incrementa en uno. Cuando ésta variable valga 10, la condición i < 10 será falsa, con lo
que ya no se entrará de nuevo en el bucle.
Para evitar bucles infinitos es imprescindible que alguna de las instrucciones del bucle
modifique de algún modo la expresión de la sentencia while, de lo contrario, una vez
se entra en el bucle ya no se puede salir del mismo. El siguiente programa muestra un
ejemplo de un bucle infinito:
1 #include <stdio.h>
2
3 int main() {
4 int i = 0;
5 while( i < 10 ) { // Esto siempre va a ser cierto
6 printf("Hola mundo\n");
7 }
8 return 0;
9 }
En el siguiente ejemplo se muestra un programa que solicita dos números y una operación
(suma, resta, multiplicación o división) y realiza la operación especificada. A continua-
ción pregunta si se desea hacer otra operación. En caso afirmativo se repite de nuevo todo
el proceso.
1 #include <stdio.h>
2
3 int main() {
4 float a, b, c;
5 int operacion, repetir;
6
Puede ocurrir que el contenido de un bucle while no se ejecute nunca. El siguiente pro-
grama solicita números enteros hasta que se introduzca un cero y al final muestra cuántos
de ellos eran positivos.
1 #include <stdio.h>
2
3 int main() {
4 int num, positivos;
5
do {
instruccion_1;
instruccion_2;
. . .
instruccion_N;
} while( expresion );
44
3.2 Sentencias de repetición
Cualquier código escrito con un bucle do-while puede reescribirse mediante un bucle
while y viceversa. Por ejemplo, el siguiente bucle do-while
1 #include <stdio.h>
2
3 int main() {
4 float a, b;
5
6 do {
7 printf("Introduce dos números (el segundo de ellos distinto
de cero) ");
8 scanf(" %f %f", &a, &b);
9 } while( b == 0 );
10 printf(" %f / %f = %f\n", a, b, a/b);
11 return 0;
12 }
45
Capı́tulo 3. Estructuras de control
do {
solicitar datos
} while( datos incorrectos );
Otro uso habitual del bucle do-while es para la implementación de un menú de opciones.
A continuación se muestra un ejemplo que presenta un menú, de modo que el programa
se ejecuta repetidas veces hasta que se escoge la opción “Salir”:
1 #include <stdio.h>
2
3 int main() {
4 float a, b;
5 int opc;
6
7 do {
8 printf("1.- Sumar\n");
9 printf("2.- Restar\n");
10 printf("3.- Salir\n");
11 printf("Elige una opción: ");
12 scanf(" %d", &opc);
13 printf("Introduce dos números: ");
14 scanf(" %f %f", &a, &b);
15 if( opc == 1 ) printf(" %f + %f = %f\n", a, b, a+b);
16 else if( opc == 2 ) printf(" %f - %f = %f\n", a, b, a-b);
17 else if( opc == 3 ) printf("Adiós\n");
18 else printf("Opción incorrecta\n");
19 } while( opc != 3 );
20 return 0;
21 }
En este caso el programa presenta un menú con dos opciones, más una tercera para finali-
zar. El bucle do-while provoca que el programa se ejecute repetidas veces hasta que se
escoja la opción 3 (salir). Cualquier otro valor distinto de 3 provoca que el bucle se repita
de nuevo. El grupo de instrucciones if-else se podrı́a haber resuelto también mediante
una sentencia switch, tal y como se ha mostrado en ejemplos anteriores.
46
3.2 Sentencias de repetición
Las llaves, como siempre, son opcionales si el bucle contiene una única instrucción. Por
otro lado, exp1, exp2 y exp3 pueden ser cualquier expresión válida en C. De forma más
detallada diremos que:
exp1: se ejecuta una única vez, antes de que comiencen a ejecutarse las instruccio-
nes del bucle. Suele utilizarse para inicializar alguna variable.
1 #include <stdio.h>
2
3 int main() {
4 int i;
5 for( i=1; i<=10; i++ ) {
6 printf("Hola Mundo\n");
7 }
8 return 0;
9 }
En este ejemplo, i=1 se ejecuta una única vez antes de que comience el bucle. La ex-
presión i<=10 es la condición de entrada al bucle, mientras sea cierta se ejecutarán las
instrucciones de dentro del bucle. Por último la expresión i++ se ejecuta al finalizar cada
una de las iteraciones, después de la instrucción printf. En definitiva, la variable i se ha
utilizado a modo de contador para controlar el número de iteraciones. Cuando el bucle se
haya repetido 10 veces, la variable i valdrá 11 y por tanto finalizará.
47
Capı́tulo 3. Estructuras de control
1 i = 1;
2 while( i <= 10 ) {
3 printf("Hola Mundo\n");
4 i++;
5 }
exp1;
while(exp2) {
instruccion_1;
. . .
instruccion_N;
exp3;
}
En un bucle for puede omitirse cualquiera de las expresiones. Por ejemplo, el siguiente
programa muestra n veces el texto ”Hola Mundo”, donde n es un valor introducido por el
usuario. Obsérvese que en el bucle for se ha omitido exp1 ya que en este caso la variable
n ya tiene un valor inicial:
1 #include <stdio.h>
2
3 int main() {
4 int n;
5 printf("Introduce un valor entero: ");
6 scanf(" %d", &n);
7 for( ; n>0; n-- ) {
8 printf("Hola Mundo\n");
9 }
10 return 0;
11 }
También es posible que alguna de las expresiones contenga más de una instrucción. En
este caso, dichas instrucciones deben separarse por coma, tal y como se muestra en el
siguiente ejemplo:
1 #include <stdio.h>
2
3 int main() {
4 int i, j;
5 for( i=0, j=10; i<j; i++, j-- ) {
6 printf(" %d + %d = %d\n", i, j, i+j);
7 }
8 return 0;
9 }
1 + 10 = 11
2 + 9 = 11
3 + 8 = 11
4 + 7 = 11
5 + 6 = 11
1 #include <stdio.h>
2
3 int main() {
4 int i, j;
5 i = 0;
6 j = 10;
7 while( i<j ) {
8 printf(" %d + %d = %d\n", i, j, i+j);
9 i++;
10 j--;
11 }
12 return 0;
13 }
49
Capı́tulo 3. Estructuras de control
1 #include <stdio.h>
2
3 int main() {
4 int i, j;
5 for( i=1; i<=3; i++ ) {
6 printf("Bucle externo, iteración %d\n", i);
7 for( j=1; j<=2; j++ ) {
8 printf("\tBucle interno, iteración %d- %d\n", i, j);
9 }
10 printf("-------------------------------------\n");
11 }
12 return 0;
13 }
Como puede observarse, el bucle externo se repite tres veces y, en cada una de estas
iteraciones, el bucle interno se repite dos veces.
50
3.3 Algunas técnicas útiles: contadores, acumuladores y banderas
3.3.1 Contadores
Utilizamos el término contador para referirnos a una variable cuyo valor se va incremen-
tando de uno en uno bajo ciertas condiciones. Este tipo de variables deben inicializarse
con algún valor (normalmente cero).
1 #include <stdio.h>
2
3 int main() {
4 int i, cont, num;
5 cont = 0; // Inicializamos el contador
6 for( i=1; i<=10; i++ ) {
7 printf("Introduce un número entero: ");
8 scanf(" %d", &num);
9 if( num %2 == 0 )
10 cont++; // Incrementamos el contador
11 }
12 printf("Has introducido %d números pares\n", cont);
13 return 0;
14 }
En este ejemplo la variable cont funciona a modo de contador, de modo que se iniciali-
za a cero y, cada vez que se detecta la entrada de un número par, se incrementa en uno.
La variable i del bucle for también es un contador, en este caso empleado para contro-
lar el número de iteraciones que realiza el bucle. Este contador se inicializa a uno y se
incrementa tras cada iteración del bucle.
51
Capı́tulo 3. Estructuras de control
3.3.2 Acumuladores
Otro uso habitual de las variables consiste en añadirles cierto valor al que ya tenı́an de
alguna operación anterior, mediante operaciones del tipo
variable += valor;
1 i += 5;
incrementa en 5 el valor de i.
Al igual que los contadores, los acumuladores deben ser inicializados. De hecho un con-
tador es un tipo particular de acumulador, al que se le van sumando unidades.
1 #include <stdio.h>
2
3 int main() {
4 int i;
5 float num, suma;
6 suma = 0; // Inicializamos el acumulador
7 for( i=1; i<=10; i++ ) {
8 printf("Introduce un número: ");
9 scanf(" %f", &num);
10 suma += num; // Incrementamos el acumulador en ’
num’ unidades
11 }
12 printf("Media = %f\n", suma/10);
13 return 0;
14 }
En este ejemplo se puede observar que en la variable suma se van acumulando todos los
valores introducidos en la variable num.
También es posible utilizar un acumulador en modo producto con una instrucción del tipo
variable *= valor;
1 i *= 5;
52
3.3 Algunas técnicas útiles: contadores, acumuladores y banderas
1 #include <stdio.h>
2
3 int main() {
4 int i, n, fact;
5 fact = 1; // Inicializamos el acumulador
6 printf("Introduce un número: ");
7 scanf(" %d", &n);
8 for( i=2; i<=n; i++ )
9 fact *= i; // Multiplicamos ’i’ a lo que hay en el
acumulador
10
3.3.3 Banderas
Una bandera o flag es una variable que toma únicamente dos valores (1/0, verdadero/falso)
y que se emplea para comprobar si se ha producido una determinada condición durante
la ejecución del programa. La variable se inicializa a 0 (falso) o a 1 (verdadero) y, si se
cumplen ciertas condiciones en un instante determinado, se cambia su valor. Finalmente
se chequea la variable para ver si su valor ha cambiado.
Por ejemplo, imaginemos que se desea hacer un programa que sume el precio de 10
productos y, además, queremos sacar un mensaje de advertencia si alguno de los precios
introducidos es negativo. La suma se desea calcular igualmente, pero en caso de que se
detecte algún precio negativo, se mostrará al final del programa un mensaje a modo de
advertencia. Esto puede resolverse con el uso de una bandera tal y como se muestra a
continuación:
53
Capı́tulo 3. Estructuras de control
1 #include <stdio.h>
2
3 int main() {
4 int i, flag;
5 float pvp, suma;
6 suma = 0; // Inicializamos el acumulador
7 flag = 0; // Inicializamos la bandera
8 for( i=1; i<=10; i++ ) {
9 printf("Introduce precio: ");
10 scanf(" %f", &pvp);
11 if( pvp < 0 ) // Si el número es negativo...
12 flag = 1; // nos lo anotamos en el flag
13 suma += pvp; // En cualquier caso lo sumamos
14 }
15 printf("Suma = %f\n", suma);
16 if( flag == 1 ) // Comprobamos si el flag ha
cambiado
17 printf("ATENCIÓN, se han introducido precios negativos!\n")
;
18 return 0;
19 }
Continuando con el ejemplo anterior, un error habitual con el uso de banderas es el si-
guiente:
1 flag = 0
2 for( i=1; i<=10; i++ ) {
3 printf("Introduce precio: ");
4 scanf(" %f", &pvp);
5 if( pvp < 0 )
6 flag = 1;
7 else
8 flag = 0;
9 }
Obsérvese que la sentencia else no es correcta. Este código provoca que la variable flag
se quede con valor 0 o 1, dependiendo del signo del último valor introducido. Esto es,
con el código anterior, si el último número introducido es positivo, el flag quedará con
valor 0, independientemente de que los números anteriores fueran positivos o negativos.
54
3.4 Ejercicios resueltos
1 #include <stdio.h>
2
3 int main() {
4 float a, b;
5
1 #include <stdio.h>
2
3 int main() {
4 int i, n;
5
11 return 0;
12 }
1 #include <stdio.h>
55
Capı́tulo 3. Estructuras de control
3 int main() {
4 int i, j;
5
1 #include <stdio.h>
2
3 int main() {
4 int i, n;
5 float num, suma;
6
56
3.5 Ejercicios propuestos: condicionales
2. Una empresa de transportes cobra 30 euros por cada bulto que transporta. Además,
si el peso total de todos los bultos supera los 300 kilos, cobra 0.9 euros por cada
kilo extra. Por último, si el transporte debe realizarse en sábado, cobra un plus de
60 euros. La empresa no realiza el pedido si hay que transportar más de 30 bultos,
si el peso total supera los 1.000 kilos o si se solicita hacerlo en domingo. Se pide
realizar un programa que solicite el número de bultos, el peso total y el dı́a de la
semana (número entre 1 y 7) y muestre el precio en caso de que pueda realizarse el
transporte, o un mensaje de advertencia en caso contrario.
4. Mostrar N veces por pantalla el texto “Hola mundo”. Pedir el valor de N al inicio
del programa.
5. Repetir el ejercicio anterior, numerando cada una de las lı́neas. Por ejemplo, si N
vale 10, el programa deberá mostrar:
1. Hola mundo
2. Hola mundo
. . .
10. Hola mundo
6. Repetir el ejercicio anterior, numerando las lı́neas en orden decreciente, esto es:
57
Capı́tulo 3. Estructuras de control
7. Mostrar el valor del seno(x) para valores de x comprendidos entre 0 y 2, con in-
crementos de 0,1. El seno se puede calcular mediante la función sin(x), lo que
requiere incluir la librerı́a math.h. La salida por pantalla deberá tener el siguiente
aspecto:
X sen(x)
------- --------
0.000000 0.000000
0.100000 0.099833
0.200000 0.198669
. . . . . .
2.000000 0.909297
10. Solicitar 10 valores y mostrar la suma total de los pares por un lado y la de los
impares por otro.
10
11. Calcular 1 ∗ 2 ∗ · · · ∗ 10, esto es, i.
i=1
12. Escribir un programa que calcule el producto de dos números enteros positivos sin
utilizar la operación ’*’ (el producto deberá calcularse a base de sumas).
13. Solicitar dos números enteros positivos a y b y calcular a/b y a %b, sin utilizar las
operaciones “/” y “ %”.
Pista: Contar el número de veces que se puede restar b de a.
14. Solicitar un número N y calcular el factorial de N. Comprobar que el número intro-
ducido es positivo y menor o igual que 20. En caso contrario solicitarlo de nuevo,
hasta que el valor de N sea correcto.
15. Dado el siguiente fragmento de código, explicar qué operación realiza. Hacer una
traza suponiendo inicialmente a = 5.
1 valor = 0;
2 while( a > 0 ) {
3 valor += a--;
4 }
5 printf("El resultado es %d\n", valor);
58
3.6 Ejercicios propuestos: bucles
18. Solicitar 10 números y decir si están ordenados crecientemente, esto es, si cada
nuevo valor introducido es mayor que el anterior.
Pista: Utiliza una variable para almacenar el último valor introducido y otra para
almacenar el penúltimo y, si ultimo < penultimo, activa un flag.
Juegos
19. Escribir un programa que juegue a “adivinar un número” del siguiente modo: El
usuario piensa un número entre 1 y 100 y el ordenador debe averiguar dicho núme-
ro. Para ello, cada vez que el ordenador sugiere un número, el usuario debe contestar
con el carácter g, p o c en función de que el número sea “demasiado grande”, “de-
masiado pequeño” o “correcto”. Se debe implementar un algoritmo eficiente que
adivine el número en el menor número de intentos posible.
59
Capı́tulo 4
Funciones
4.1 Introducción
Los programas que hemos creado hasta ahora no son modulares, todo el código del pro-
grama aparece dentro de main. Esto puede ser adecuado para programas pequeños y
sencillos, pero cuando se abordan programas de mayor envergadura y complejidad es ne-
cesario modularizarlos, esto es, dividir el programa en bloques menores. En el lenguaje
C, esta división se hace mediante el uso de funciones.
Aunque ya hemos hecho uso de las funciones en capı́tulos anteriores, conviene observar
con mayor detalle el modo de utilizarlas. A continuación se muestra un ejemplo sencillo:
1 #include <stdio.h>
2 #include <math.h>
3
61
Capı́tulo 4. Funciones
4 int main() {
5 float x, y;
6
12 return 0;
13 }
En este ejemplo printf, scanf y pow son funciones de la librerı́a de C. Lo primero que
debemos saber es que las funciones de C se agrupan en categorı́as. Por ejemplo printf
y scanf son funciones de entrada/salida, mientras que pow es una función matemática.
Para poder utilizar las funciones de C es necesario incluir cierta información en nuestro
programa. Esta información se encuentra almacenada en lo que se conoce como ficheros
de cabecera o header files y se incluye mediante la instrucción:
#include <nombre_del_fichero_de_cabecera>
En la sección 4.3.5 se explicará con mayor detalle los ficheros de cabecera. De momento
nos basta con saber que es necesario incluirlos. En el ejemplo anterior stdio.h hace
referencia al fichero de cabecera standar input/output (entrada/salida estándar). Hemos
incluido este fichero ya que hacemos uso de las funciones printf y scanf, las cuales
se agrupan bajo esta categorı́a. De forma similar, dado que nuestro programa utiliza la
función matemática pow, es necesario incluir el fichero de cabecera math.h.
Si nos centramos en la sintaxis, se puede observar que para llamar o invocar a una fun-
ción debemos poner el nombre de la función seguido de una lista de argumentos. Estos
argumentos representan la información que le pasamos a la función y deben ir encerrados
entre paréntesis y separados por coma:
En el ejemplo anterior podemos observar que en las llamadas a las funciones scanf
y pow contienen dos argumentos. Por otro lado, la función printf, se puede invocar
con un número variable de argumentos1 . En el ejemplo, el primer printf tiene un solo
argumento, mientras que el segundo tiene tres.
También hay funciones que no tienen argumentos, como getchar(). Aun en este caso,
los paréntesis deben estar presentes.
1 En realidad scanf también tiene un número variable de argumentos. Por ejemplo scanf(” %f %f”, &a, &b)
62
4.3 Creando nuestras propias funciones
Cuando se invoca a una función, el programa realiza las operaciones necesarias para eje-
cutar la función en cuestión, teniendo en cuenta los argumentos especificados. Algunas
funciones (en realidad la mayorı́a) además de requerir argumentos con cierta información,
también devuelven un valor, resultante de la operación realizada. En el ejemplo anterior
la función pow requiere dos argumentos y devuelve un valor (en este caso el resultado de
elevar el segundo argumento al primero). Cuando una función devuelve un valor, ésta pue-
de ser utilizada como parte de una expresión. Por ejemplo, lo siguiente serı́an expresiones
válidas en C:
y = pow(x, 2);
Por otro lado, los argumentos de una función pueden ser también expresiones más com-
plejas, tal y como se muestra en los siguientes ejemplos:
z = pow(3*x, 2*y);
z = pow(sqrt(x)*2, 1.0/y);
1 #include <stdio.h>
2 int main() {
3 int m, n, i, fact_m, fact_n, fact_m_n, res;
4
5 // Pedir datos
6 do {
63
Capı́tulo 4. Funciones
11 // Calcular m!
12 fact_m = 1;
13 for(i=1; i<=m; i++)
14 fact_m = fact_m * i;
15
16 // Calcular n!
17 fact_n = 1;
18 for(i=1; i<=n; i++)
19 fact_n = fact_n * i;
20
21 // Calcular (m-n)!
22 fact_m_n = 1;
23 for(i=1; i<=(m-n); i++)
24 fact_m_n = fact_m_n * i;
25
30 return 0;
31 }
1 #include <stdio.h>
2 int main() {
3 int m, n, res;
4
5 // Pedir datos
6 do {
7 printf("Introduce dos valores m, n, positivos, con m>=n ");
8 scanf(" %d %d", &m, &n);
9 } while (m<0 || n<0 || m<n);
10
64
4.3 Creando nuestras propias funciones
15 return 0;
16 }
tipo_dato nombre_funcion(lista_parametros) {
/* Cuerpo de la función */
}
donde:
tipo_dato indica el tipo de dato del valor que devuelve la función (int, foat,
char, etc.). En caso de que la función no devuelva ningún valor, el tipo de dato
deberá ser void.
nombre_funcion es el nombre que queremos darle a nuestra función, y por tanto
el que deberá utilizarse cuando queramos invocarla.
lista_parametros es un conjunto de pares tipo_dato nombre_variable se-
parados por comas. La lista de parámetros utilizada a la hora de implementar la
función debe coincidir en número y tipo con la lista de argumentos empleada en la
llamada a dicha función.
El tipo de dato devuelto por la función, junto con el nombre y la lista de parámetros es lo
que se conoce como cabecera de la función, mientras que el código que queda encerrado
entre la apertura y cierre de llave se denomina cuerpo de la función.
Por ejemplo, si quisiéramos crear una función que calcule el factorial de cierto número
entero, escribirı́amos:
65
Capı́tulo 4. Funciones
Esta función, de nombre fact, recibe un único parámetro de tipo int y de nombre n que
representa el valor del que queremos obtener el factorial y devuelve un valor de tipo int
(el resultado de calcular el factorial de n).
El siguiente ejemplo corresponderı́a a una función que calcula la media aritmética de tres
números enteros:
En este caso la función tiene tres parámetros enteros que representan los números de los
que se desea obtener la media y devuelve un número real (float) que representa la media
calculada.
1 #include <stdio.h>
2
11 int main() {
12 . . .
13 }
66
4.3 Creando nuestras propias funciones
Por otro lado, cuando invocamos a una función, puede existir (y normalmente existe)
una transferencia de información entre la función que realiza la llamada y la función
llamada. Esta transferencia de información se produce en dos instantes:
67
Capı́tulo 4. Funciones
Al finalizar la llamada, en caso de que exista una sentencia return, el valor espe-
cificado a continuación de return es devuelto a la función que realizó la llamada.
Por ejemplo, dado el siguiente código:
1 #include <stdio.h>
2
68
4.3 Creando nuestras propias funciones
m
Si retomamos el ejemplo del cálculo de n , el programa completo quedarı́a como sigue:
1 #include <stdio.h>
2
10 int main() {
11 int m, n, res;
12
13 // Pedir datos
14 do {
15 printf("Introduce dos valores m, n, positivos, con m>=n ");
16 scanf(" %d %d", &m, &n);
17 } while (m<0 || n<0 || m<n);
18
23 return 0;
24 }
69
Capı́tulo 4. Funciones
1 #include <stdio.h>
2
9 int main() {
10 float num1, num2, num3;
11 printf("Introduce tres valores: ");
12 scanf(" %d %d %d", &num1, &num2, &num3);
13 media(num1, num2, num3);
14 return 0;
15 }
1 int main() {
2 int num1, num2, num3;
3
70
4.3 Creando nuestras propias funciones
Ahora la función media no devuelve ningún resultado (es de tipo void), por lo que no
es posible comparar si el resultado de la misma es mayor o menor que cinco. En este
ejemplo nuestro objetivo no era mostrar por pantalla la media obtenida, sino operar con
el resultado obtenido, por lo tanto nos interesaba una función media que devolviese un
valor numérico, tal y como hicimos en el primer ejemplo.
De forma similar, tampoco resulta interesante hacer la lectura de datos dentro de la propia
función media:
1 float media() {
2 int a, b, c, res;
3 printf("Introduce tres valores: ");
4 scanf(" %d %d %d", &a, &b, &c);
5 res = (a+b+c)/3.0;
6 return res;
7 }
1 int main() {
2 int num1, num2, num3, num4;
3 float m;
4
En definitiva, salvo en aquellos casos en los que el acometido de la función sea explı́cita-
mente solicitar o mostrar datos, será conveniente que las operaciones de entrada/salida se
realicen fuera de la función que realiza los cálculos. De este modo las funciones imple-
mentadas podrán utilizarse en diferentes contextos, esto es, estaremos creando funciones
más generales.
71
Capı́tulo 4. Funciones
Sin embargo, existe una alternativa que permite implementar las funciones en cualquier
orden, pudiendo incluso estar la implementación después de la llamada. Para ello basta
con declarar la función antes de la llamada (habitualmente al inicio del fichero). La
declaración de una función consiste en escribir el prototipo, esto es, la cabecera, seguido
de un punto y coma. Recordemos que la cabecera de una función está formada por el tipo
de dato devuelto, el nombre y la lista de parámetros:
tipo_dato nombre_funcion(lista_parametros);
Por ejemplo, el prototipo de la función fact del ejemplo dado en la sección anterior serı́a:
En consecuencia,
haciendo uso de la declaración de funciones, en el ejemplo del cálculo
de m n serı́a posible implementar las funciones en el siguiente orden:
1 #include <stdio.h>
2
5 int main() {
6 . . .
7 res = fact(m) / (fact(n)*fact(m-n)); // Llamadas a la función
fact
8 . . .
9 }
10
1 int fact(int);
1 #include <stdio.h>
2 #include <math.h>
3 . . .
La extensión .h viene precisamente de header file, si bien estos ficheros además de ca-
beceras de funciones también pueden contener otra información como declaración de
constantes (mediante instrucciones #define) o declaración de tipos estructurados, algo
que se verá en el Capı́tulo 6.
Variables locales: Una variable es local cuando se declara dentro de una función, bien en
el cuerpo de la misma, bien como parámetro. Por ejemplo, dado el siguiente programa:
73
Capı́tulo 4. Funciones
las variables n, i y f son locales a la función fact y por tanto no es posible usarlas fuera
de esta función. De modo similar las variables num y res son locales a la función main.
En realidad, todas las variables que hemos venido usando hasta ahora en los ejemplos
mostrados son variables locales.
Es posible que dos variables que se encuentren en funciones distintas tengan el mismo
nombre. Por ejemplo, el siguiente código serı́a perfectamente válido:
En este caso tanto la función fact como main contienen una variable de nombre i. Esto
no supone ningún problema, de hecho es algo bastante habitual, pero debe quedar claro
que la variable i de la función fact es distinta de la variable i de main. Cada una ocupa
su propio espacio de memoria y no hay confusión posible entre ellas, ya que dependiendo
de la función donde se estén utilizando quedará claro a qué variable nos referimos.
En el ejemplo mostrado la variable i de main representa el valor del cual queremos calcu-
lar el factorial, mientras que la variable i de fact se usa a modo de contador en el bucle
for. El hecho de que la variable i de fact vaya cambiando su valor en cada iteración del
bucle no afecta para nada a la variable i de main, la cual mantiene su valor.
74
4.4 Variables locales y globales
Variables globales: Una variable es global cuando se declara fuera de cualquier función
(normalmente a continuación de las instrucciones #include y #define). El ámbito de
una variable global es la totalidad del programa y, por tanto, puede ser utilizada en cual-
quier parte del mismo.
El uso de variables globales evita tener que pasar información a las funciones mediante
el uso de parámetros, o devolverla mediante el uso de return, sin embargo, tal y como
veremos más adelante, su uso no se recomienda.
1 #include <stdio.h>
2 int num, f; // Variables globales
3 void fact() {
4 int i; // Variable local
5 f = 1;
6 for( i=2; i<=num; i++ )
7 f = f * i;
8 }
9 int main() {
10 printf("Introduce un número entero positivo: ";
11 scanf(" %d", &num);
12 fact();
13 printf("FACTORIAL( %d)= %d\n", num, f);
14 }
En el ejemplo mostrado las variables num y f declaradas en la lı́nea 2 son globales y, por
tanto, conocidas tanto dentro de main como de fact. Obsérvese que ahora la función
fact no necesita recibir ni devolver ningún valor. La función main puede dar valor a la
variable num (lı́nea 11) y, posteriormente, la función fact consultar el valor de esa misma
variable (lı́nea 6).
Esta forma de trabajar está totalmente desaconsejada, ya que rompe en gran medida con
el principio de modularidad. Es interesante que las funciones tengan una alta cohesión
(la función realiza una única tarea bien definida) y un bajo acoplamiento (la función
tiene una baja dependencia de otras funciones). El uso de variables globales aumenta la
dependencia entre funciones, esto es, aumenta el acoplamiento. Otros motivos por los que
debe evitarse el uso de variables globales son:
75
Capı́tulo 4. Funciones
1 void f(int v) {
2 . . .
3 }
4
5 int main() {
6 int num;
7 . . .
8 f(num);
9 . . .
10 }
Esto es lo que se conoce como pase de parámetros por valor. Hay que tener en cuenta
que las variables num y v ocupan espacios de memoria distintos (incluso aunque ambas
tuviesen el mismo nombre) por lo que un cambio en el valor de v no afecta para nada a la
variable num. Veamos el siguiente ejemplo:
1 #include <stdio.h>
2
76
4.5 Paso de argumentos por valor y por referencia
9 int main() {
10 int a = 3;
11 printf("Valor de ’a’ al inicio de main: %d\n", a);
12 modificar(a);
13 printf("Valor de ’a’ al final de main: %d\n", a);
14 return 0;
15 }
En definitiva, mediante el pase de argumentos por valor, no es posible que una función
modifique el valor de una variable perteneciente a otra función. Esta forma de trabajar
es la más adecuada en muchas situaciones, pero en ocasiones interesa que una función
modifique alguna de las variables de la función que la invocó. Esto se consigue mediante
el paso de argumentos por referencia, el cual se basa en el uso de punteros. Para poder
entender el siguiente apartado es necesario haber comprendido previamente el manejo de
punteros explicado en el Capı́tulo 2.
5 int main() {
6 int a = 3;
7 printf("Valor de ’a’ al inicio de main: %d\n", a);
8 modificar(&a);
9 printf("Valor de ’a’ al final de main: %d\n", a);
10 return 0;
11 }
77
Capı́tulo 4. Funciones
En este ejemplo, la llamada modificar(&a) pasa la variable a por referencia, esto es,
pasamos la dirección de a en lugar de su valor. Esta llamada produce que en el parámetro p
de la función modificar se copie la dirección de a. A partir de este momento, la función
modificar podrá acceder a la variable a ya que conoce su dirección de memoria. En
concreto, la operación *p = *p + 2 incrementa en 2 el valor de a.
Imaginemos una situación en la que una función necesita devolver dos valores. Ya se ha
visto que mediante la sentencia return únicamente es posible devolver un valor2 , por lo
que deberı́amos hacer uso del pase de parámetros por referencia para transferir los valores
deseados. En el siguiente ejemplo se muestra el uso de una función que, dado el radio de
una circunferencia, devuelve (mediante argumentos pasados por referencia), tanto el área
como el perı́metro.
1 #include <stdio.h>
2 #define PI 3.141592
3 void area_perim(float radio, float * p_area, float * p_perim);
4
5 int main() {
6 float r, area, perim;
7
2 En realidad es posible devolver un grupo de variables mediante el uso de estructuras, tal y como se verá en
el Capı́tulo 6, pero si nos restringimos a los tipos simples únicamente podremos devolver un valor.
78
4.6 Ejercicios resueltos
*
**
***
*****
******
*******
********
*********
**********
Para ello implementar una función que reciba un número entero n y muestre por
pantalla una lı́nea con n asteriscos.
SOLUCIÓN:
1 #include <stdio.h>
2
3 void dibuja_asteriscos(int);
4
5 int main() {
6 int i;
7 for(i=1; i<=10; i++) {
8 dibuja_asteriscos(i);
9 }
10 return 0;
11 }
12
79
Capı́tulo 4. Funciones
1 x = dibuja_asteriscos(i);
1 #include <stdio.h>
2 #include <math.h>
3
6 int main() {
7 float r1, r2, area;
8 do {
9 printf("Introduce los radios del anillo: ");
10 scanf(" %f %f", &r1, &r2);
11 } while( r1<=0 || r2<=0 || r2<=r1 );
12
17
1 #include <stdio.h>
2
3 int primo(int);
4
5 int main() {
6 int i;
7
11 }
12 return 0;
13 }
14
18 es_primo = 1;
19 for(i=2; i<n; i++) {
20 if( n %i == 0 )
21 es_primo = 0;
22 }
23 return es_primo;
24 }
En la lı́nea 3 se declara la función. Las funciones que devuelven un valor del tipo
VERDADERO/FALSO se declaran de tipo int, de modo que si la función da como
resultado VERDADERO se devuelve 1 y si da FALSO se devuelve 0. Esta función
además de devolver un entero, recibe como parámetro otro entero (el número que
deseamos evaluar si es o no primo).
En la lı́nea 9 se realiza la llamada a la función. El valor de i se copia en la varia-
ble n. Cuando finaliza la función, esta llamada se sustituye por el valor devuelto
en la instrucción de la lı́nea 23. Si se devuelve 1, la instrucción de la lı́nea 9 rea-
lizará la comparación if(1==1), mientras que si devuelve 0 realizará la compara-
ción if(0==1).
Entre las lı́neas 15 y 24 se implementa la función. La variable es_primo se usa
a modo de flag o bandera. Se inicializa con 1 y, si durante la ejecución del bucle
for se encuentra algún valor i que sea un divisor exacto de n (n%i == 0) enton-
ces se cambia el flag a 0 ya que, con toda seguridad, el número n no será primo.
Finalmente en la lı́nea 23 se devuelve el valor del flag.
Una posible mejora a este algoritmo consistirı́a en iterar únicamente hasta n/2en el
bucle for de la lı́nea 19, ya que no vamos a encontrar divisores más grandes que
este valor. Por otro lado, también podrı́a forzarse la salida del bucle en cuanto se
encuentre un divisor, en cuyo caso queda claro que el número no es primo y por
tanto no sirve de nada seguir iterando en el bucle.
82
4.6 Ejercicios resueltos
SOLUCIÓN:
1 #include <stdio.h>
2
5 int main() {
6 int seg_total, hor, min, seg;
7
13 return 0;
14 }
15
Una función no puede devolver más de un valor a través de una sentencia return
(a no ser que lo haga mediante una variable de tipo struct, las cuales todavı́a no
se han estudiado). Un modo habitual de devolver más de un valor es mediante el
pase de parámetros por referencia.
En la lı́nea 10 se encuentra la llamada a la función convertir. Esta función tiene
cuatro parámetros: los segundos totales (pasado por valor) y las direcciones de me-
moria de las variables hor, min y seg (pase por referencia) declaradas en main.
Es necesario pasar estas tres últimas variables por referencia, ya que su valor debe
ser modificado dentro de la función. En el momento de la llamada, en el parámetro
st (lı́nea 16) se copia el valor de seg_total, y en los parámetros h, m y s se
copian las direcciones de memoria de las variables hor, min y seg respectiva-
mente. Esto es, en el momento de la llamada se produce la siguiente transferencia
de información: st=seg_total, h=&hor, m=&min y s=&seg.
Entre las lı́neas 16 y 20 se encuentra la implementación de la función. Esta función
asigna valores a las variables hor y min y seg declaradas en main a través de los
punteros h, m y s.
En la lı́nea 3 se encuentra la declaración de la función. Puede observarse que los
tres últimos parámetros son de tipo puntero, ya que corresponden, no al valor de
una variable, sino a su dirección de memoria. La función es de tipo void ya que no
devuelve nada a través de la sentencia return.
83
Capı́tulo 4. Funciones
Escribir un programa que pida dos números y una operación (1-4) y muestre el
resultado en función de la operación solicitada.
5. Implementar una función que, dados tres números reales, devuelva el mayor de
ellos. Escribir un programa que haga uso de esta función para mostrar el mayor de
tres valores introducidos por teclado.
6. Implementar una función que, dados tres números reales, devuelva si están orde-
nados crecientemente. Escribir un programa que solicite tres números y muestre si
están o no ordenados.
7. Implementar una función que devuelva si dos números dados son amigos. Dos
números a y b son amigos si a es la suma de los divisores de b y b es la suma
de los divisores de a, sin considerar la división por si mismo. Por ejemplo 220 y
284 son números amigos, ya que:
Los divisores de 220 (sin contar él mismo) son 1, 2, 4, 5, 10, 11, 20, 22, 44,
55 y 110, que suman 284.
Los divisores de 284 son 1, 2, 4, 71 y 142, que suman 220.
84
4.7 Ejercicios propuestos
Otros pares de números amigos son: (1184, 1210), (6232, 6368), (17296, 18416) y
(9363584, 9437056).
Escribir un programa que solicite dos números enteros positivos y muestre si son o
no amigos.
8. Implementar una función que, dados dos números reales que representen el ángu-
lo (a) y módulo (m) de un vector, devuelva las coordenadas cartesianas de dicho
vector (x = m cos(a), y = m sin(a)). Escribir un programa que solicite un vector
expresado en coordenadas polares y lo muestre en coordenadas cartesianas.
9. Implementar una función que, dados dos números reales, los intercambie en el caso
de que el primero sea mayor que el segundo. Escribir un programa que solicite dos
valores y, tras invocar a la función anterior, los muestre ordenados.
85
Capı́tulo 5
Vectores
5.1 Introducción
Es posible distinguir entre tipos de datos básicos y tipos compuestos. Las variables de
tipos básicos (char, int, float, double y tipo puntero) pueden almacenar un solo
dato en un instante determinado. Por contra, las variables de tipos compuestos permiten
almacenar un conjunto de valores.
Un vector en C es una variable compuesta que permite almacenar una serie de valores,
todos ellos del mismo tipo. Gráficamente, podemos representar un vector tal y como se
muestra en la Figura 5.1.
Denominamos:
0 1 2 3 4 5 6 7 Í
Índices
V 1 1 2 3 5 8 13 21
Elementos
E del vector
Nombre del vector
87
Capı́tulo 5. Vectores
Índice: la posición que ocupa cada elemento dentro del vector. Al primer elemento
se le asocia el ı́ndice 0 y al último el ı́ndice talla-1.
tipo_dato nombre_vector[talla];
donde tipo_dato especifica el tipo de dato de cada uno de los elementos que forman el
vector y talla el número de elementos. Por ejemplo, la sentencia
1 int v[10];
Al igual que ocurre con otros tipos de variables, es posible inicializar los valores de un
vector en el momento de la declaración. Por ejemplo, la siguiente sentencia crea un vector
de enteros de nombre dias_mes e inicializa cada uno de los elementos con los dı́as que
tiene cada uno de los meses del año:
Vemos que para inicializar un vector deben escribirse los valores de cada uno de sus
elementos encerrados entre llaves y separados por coma. La sentencia anterior crea el
vector dias_mes y almacena el valor 31 en la posición 0, el 28 en la posición 1, etc.
88
5.3 Acceso a los elementos de un vector
nombre_vector[indice]
donde indice hace referencia al elemento al que deseamos acceder. Debe recordarse que
el primer elemento tiene ı́ndice 0 y el último ı́ndice talla-1.
1 int v[10];
1 v[0] = 5;
2 v[9] = 20;
tipo_dato nombre_vector[talla];
1 #include <stdio.h>
2
3 int main() {
4 int a, b, c;
5 printf("Introduce dos números enteros: ");
6 scanf(" %d %d", &a, &b);
7 c = a + b;
8 printf("La suma de %d y %d es %d\n", a, b, c);
9 return 0;
10 }
89
Capı́tulo 5. Vectores
1 #include <stdio.h>
2
3 int main() {
4 int v[3];
5 printf("Introduce dos números enteros: ");
6 scanf(" %d %d", &v[0], &v[1]);
7 v[2] = v[0] + v[1];
8 printf("La suma de %d y %d es %d\n", v[0], v[1], v[2]);
9 return 0;
10 }
Obsérvese que v[0], v[1] y v[2] se usan exactamente igual a como se usarı́a cualquier
otra variable de tipo int (en este caso, igual a como se usan las variables a, b y c del
primer ejemplo).
1 #include <stdio.h>
2
3 int main() {
4 int v[10];
5
13 . . .
14
90
5.4 Operaciones con vectores: automatización mediante bucles
17
18 . . .
19 return 0;
20 }
Este modo de trabajar, además de poco operativo, es impracticable cuando el tamaño del
vector es grande. Sin embargo, resulta sencillo acceder secuencialmente a los elementos
de un vector mediante el uso de bucles. Para ello debe usarse una variable entera en el
lugar del ı́ndice, de modo que ésta vaya variando en cada iteración del bucle, tal y como
se muestra en el siguiente ejemplo:
1 #include <stdio.h>
2
3 int main() {
4 int i, v[10];
5
Obsérvese que en el bucle del programa anterior, la variable i comienza con valor 0 y
finaliza con valor 9, por lo que en cada iteración la sentencia scanf va almacenando
valores en los distintos elementos del vector (v[0], v[1], v[2], ...). Debe recordarse
que en un vector v de tamaño 10, sus elementos van desde v[0] hasta v[9].
1 #include <stdio.h>
2 #define N 10
3
4 int main() {
5 int i, v1[N], v2[N], v3[N]; // Vectores de tamaño 10
6
91
Capı́tulo 5. Vectores
30 return 0;
31 }
Obsérvese que los bucles for de las lı́neas 9, 16, 22 y 27 iteran con el ı́ndice i desde 0 a
N-1, siendo N el tamaño de los vectores. De este modo, si se quisiera trabajar con vectores
de otro tamaño, bastarı́a con cambiar el valor de N definido en la lı́nea 2. Obsérvese tam-
bién que para sumar dos vectores, debemos hacerlo elemento a elemento (lı́neas 22-23).
92
5.5 Paso de vectores a una función
Llamada a la función:
nombre_funcion(nombre_vector)
Por ejemplo
1 inicializar(v);
Implementación de la función:
donde talla se puede omitir, en cuyo caso la función admite vectores de cualquier ta-
maño.
Por ejemplo
Declaración de la función:
Por ejemplo
Visto en conjunto dentro de un programa, las sentencias anteriores quedarı́an como sigue:
93
Capı́tulo 5. Vectores
1 #include <stdio.h>
2 #define N 10
3
4 // Declaración de la función
5 void inicializar( int [] );
6
7 // Programa principal
8 int main() {
9 int vect[N];
10 inicializar(vec); // Llamada a la función
11 . . .
12 return 0;
13 }
14
15 // Implementación de la función
16 void inicializar( int v[] ) {
17 int i;
18 for( i=0; i<N; i++ ) // Suponiendo un vector de tamaño N
19 v[i] = 0;
20 }
1 #include <stdio.h>
2 #define N 10
3
4 // Declaración de funciones
5 void pedir_valores( int [] );
6 void mostrar_valores( int [] );
7
8 // Programa principal
9 int main() {
10 int vec[N];
11
15 return 0;
16 }
17
94
5.5 Paso de vectores a una función
Obviamente, una misma función puede ser invocada con distintos vectores en distintos
puntos del programa, lo que permite la reutilización de la función. En el siguiente ejemplo
se muestra un programa que suma dos vectores mediante el uso de funciones. Obsérvese
cómo se invoca más de una vez a una misma función, con distintos vectores en cada caso.
1 #include <stdio.h>
2 #define N 10
3
4 // Declaración de funciones
5 void pedir_valores( int [] );
6 void mostrar_valores( int [] );
7 void sumar( int [], int [], int [] );
8
9 // Programa principal
10 int main() {
11 int v1[N], v2[N], v3[N];
12
13 pedir_valores(v1);
95
Capı́tulo 5. Vectores
14 pedir_valores(v2);
15 sumar(v1, v2, v3);
16 printf("Vector 1:\n");
17 mostrar_valores(v1);
18 printf("Vector 2:\n");
19 mostrar_valores(v2);
20 printf("Suma:\n");
21 mostrar_valores(v3);
22
23 return 0;
24 }
25
Por último, cabe señalar que en ocasiones resulta interesante diseñar funciones que sean
capaces de operar con vectores de distintos tamaños (las funciones de los ejemplos an-
teriores, por el modo en que están implementados los bucles for, están pensadas única-
96
5.5 Paso de vectores a una función
mente para vectores de tamaño N). Si se desea operar con vectores de distintos tamaños,
es necesario pasar como parámetro tanto el vector como su tamaño, tal y como se muestra
en el siguiente ejemplo, que calcula la media de distintos vectores:
1 #include <stdio.h>
2 #define N 10
3 #define M 15
4
5 // Declaración de funciones
6 void pedir_valores( float [], int );
7 float media( float [], int );
8
9 // Programa principal
10 int main() {
11 float v1[N], v2[M], m1, m2;
12
13 pedir_valores(v1, N);
14 pedir_valores(v2, M);
15 m1 = media(v1, N);
16 m2 = media(v2, M);
17 printf("Media de v1: %f\n", m1);
18 printf("Media de v2: %f\n", m2);
19
20 return 0;
21 }
22
97
Capı́tulo 5. Vectores
Obsérvese que, en este caso, los bucles for implementados en las funciones (lı́neas 26
y 35) tienen como lı́mite la variable dim recibida como parámetro, la cual podrá tener
valores distintos dependiendo de la llamada (lı́neas 13-16).
Además de los ejemplos mostrados a lo largo de esta sección, existen operaciones tı́picas
con vectores que, debido a su uso habitual en programas de ı́ndole muy diversa, conviene
conocer en detalle, como por ejemplo la obtención del máximo, del mı́nimo, búsqueda de
un elemento, etc. En el apartado de ejercicios resueltos (Sección 5.12) se puede encontrar
la solución de éstos y otros algoritmos.
tipo_dato nombre_matriz[talla_filas][talla_columnas];
donde tipo_dato especifica el tipo de dato de cada uno de los elementos de la matriz
y talla_fila y talla_columnas indican el número de filas y columnas de la matriz.
Por ejemplo, la sentencia
1 int m[3][4];
98
5.8 Operaciones con matrices: automatización mediante bucles
0 1 2 3
Al igual que ocurre con los vectores, los ı́ndices comienzan en 0 y terminan en talla - 1.
En la Figura 5.2 se muestra los ı́ndices (fila, columna) de cada uno de los elementos de la
matriz anterior.
nombre_matriz[indice_fila][indice_columna];
1 int m[3][4];
1 m[0][0] = 5;
2 m[2][3] = 20;
Para recorrer una matriz bidimensional emplearemos dos variables enteras que vayan to-
mando los valores de sus ı́ndices (fila y columna). Esto se consigue mediante el uso de
dos bucles anidados (ver la Sección 3.2.4 para entender el funcionamiento de los bucles
anidados).
99
Capı́tulo 5. Vectores
1 #include <stdio.h>
2 #define FIL 3
3 #define COL 4
4
5 int main() {
6 int i, j, m[FIL][COL];
7
Es importante entender cómo van variando los ı́ndices i, j tras cada iteración de los
bucles. En la primera iteración del bucle más externo (lı́nea 8) se inicializa la variable i a
0 y, seguidamente, se entra en el bucle. A continuación se ejecutan todas las iteraciones del
bucle interno (lı́neas 9-12) en las que j varı́a de 0 a COL-1, esto es, j toma sucesivamente
los valores 0, 1, 2 y 3 con i valiendo 0. Tras la finalización de todas las iteraciones
del bucle interno, se vuelve a la siguiente iteración del bucle externo, con la variable i
incrementada en 1. A continuación se ejecutan de nuevo todas las iteraciones del bucle
interno, donde j toma nuevamente los valores 0, 1, 2 y 3, pero esta vez con i valiendo 1.
De este modo se procede hasta haber recorrido todos los elementos de la matriz.
Para mostrar por pantalla una matriz m de enteros de tamaño FIL x COL utilizarı́amos el
siguiente código:
Obsérvese que la sentencia printf de la lı́nea 5 provoca que, tras mostrarse todas las
columnas de una fila dada (esto es, tras haberse completado el bucle interno), se inserte
un salto de lı́nea, con lo que la siguiente fila se mostrará en una lı́nea distinta.
100
5.9 Paso de matrices a una función
Llamada a la función:
nombre_funcion(nombre_matriz)
Implementación de la función:
donde talla_filas se puede omitir, en cuyo caso la función admite matrices con cual-
quier número de filas, pero talla_columnas debe especificarse obligatoriamente.
Declaración de la función:
Como se observa, la principal diferencia respecto de los vectores radica en que en este
caso hay que utilizar una doble pareja de corchetes ( [ ][ ] ) en lugar de una simple ( [ ] ).
Hay que recordar que, como en el caso de los vectores, con las matrices también se emplea
el método de paso de argumentos por referencia.
1 #include <stdio.h>
2 #define N 3
3 #define M 5
4
5 // Declaración de funciones
6 void pedir_valores( int [][M] );
7 void mostrar_valores( int [][M] );
8 void sumar( int [][M], int [][M], int [][M] );
9
10 // Programa principal
11 int main() {
101
Capı́tulo 5. Vectores
14 pedir_valores(m1);
15 pedir_valores(m2);
16 sumar(m1, m2, m3);
17 printf("Matriz 1:\n");
18 mostrar_valores(m1);
19 printf("Matriz 2:\n");
20 mostrar_valores(m2);
21 printf("Suma:\n");
22 mostrar_valores(m3);
23
24 return 0;
25 }
26
102
5.10 Relación entre vectores y punteros
Hay que entender que los elementos de un vector se almacenan de forma consecutiva
en memoria, por lo tanto, basta con conocer la dirección del primer elemento para saber
también la del resto. Esto es, dado el siguiente fragmento de código
1 int v[10];
2 int * p;
3 p = &v[0]
en el que se observa que el puntero p almacena la dirección de memoria del primer ele-
mento del vector, entonces sabremos que el segundo elemento está en la dirección p+1,
el tercero en p+2 y ası́ sucesivamente. Vemos por tanto que, a partir del puntero p, es po-
sible acceder a todos los elementos del vector. Para ello debemos recordar que mediante
el operador de indirección * se puede acceder a la variable apuntada por un puntero. En
consecuencia, el fragmento de código
1 int v[10];
2 v[0] = 0;
3 v[1] = 1;
4 v[2] = 2;
es equivalente a1
1 int v[10];
2 int * p;
3 p = &v[0];
4 *p = 0;
5 *(p+1) = 1;
6 *(p+2) = 2;
1 Conviene aclarar que cada uno de los elementos de un vector puede ocupar más de un byte, dependiendo del
tipo. Por ejemplo, los enteros ocupan 4 bytes. Esto quiere decir que cada elemento del vector ocupa en realidad
4 posiciones (direcciones) de memoria. Debe entenderse que cuando se emplea aritmética de punteros, dado un
puntero p, la operación p+n en realidad hace p+(n*tamaño-del-dato). Es por ello que en la declaración de un
puntero, resulta necesario indicar el tipo de dato al cual apunta.
103
Capı́tulo 5. Vectores
Por otro lado hay que saber que el nombre de un vector, utilizado sin corchetes, representa
en realidad la dirección de memoria de su primer elemento. Esto es, dado
1 int v[10];
En consecuencia, el fragmento de código anterior también podrı́a haberse escrito del si-
guiente modo:
1 int v[10];
2 *v = 0;
3 *(v+1) = 1;
4 *(v+2) = 2;
1 int v[10];
y entendiendo que v es, en realidad, la dirección del primer elemento del vector, entonces
las expresiones v[i] y *(v+i) son equivalentes. Esto es, podemos acceder a los ele-
mentos de un vector mediante la sintaxis de corchetes que hemos venido utilizando hasta
ahora, o mediante la sintaxis de punteros que acabamos de presentar.
Por ejemplo, dado el vector int v[10], el siguiente bucle inicializa todos los elementos
del vector a cero:
Por último hay que entender que, cuando en los ejemplos de la Sección 5.5 empleábamos
la sentencia
1 inicializar(v);
1 inicializar(&v[0]);
104
5.11 Cadenas de caracteres
lo que explica que los vectores siempre se pasan por referencia, aunque para ello no
utilicemos explı́citamente la sintaxis clásica de punteros.
0 1 2 3 4 5 6 7 8 9
H O L A \0 - - - - -
queramos permitir, más un elemento extra para el carácter ’\0’. Por ejemplo, si estuviése-
mos pensando en una aplicación que almacene mensajes de twitter, deberı́amos utilizar
vectores de tamaño 141, ya que un tweet tiene una longitud máxima de 140 caracteres.
Función printf:
1 char cad[50];
Como vemos, el especificador “%s” indica que la variable asociada es una cadena de
caracteres (string). En este caso, la función printf mostrará por pantalla los caracteres
almacenados en cad, desde la posición 0 del vector hasta la aparición de ’\0’ (sin incluir
este último).
1 i = 0;
2 while( cad[i] != ’\0’ ) {
3 printf(" %c", cad[i]);
4 i++;
5 }
En este caso, en lugar de mostrar toda la cadena de golpe con el especificador ”%s”, lo
vamos haciendo caracter a caracter con el especificador ”%c”. Obsérvese que, en el primer
caso, a la función printf se le pasa como argumento el vector completo (cad), mientras
que en este segundo ejemplo se le pasa, en cada iteración, un único elemento del vector
(cad[i]), que corresponde a una variable de tipo char. Obviamente, el primer método
resulta mucho más cómodo.
Por supuesto, al igual que ocurre con otras variables, la función printf puede mostrar,
junto con la cadena, otra información. Por ejemplo:
Función puts:
La función puts (acrónimo de put string) se emplea también para mostrar cadenas de
caracteres por pantalla. Su sintaxis es:
puts(nombre_cadena);
La principal diferencia con printf radica en que puts no admite otros argumentos extra
y en que inserta automáticamente un salto de lı́nea (\n). La instrucción
1 puts(cad);
es equivalente a
107
Capı́tulo 5. Vectores
Función scanf:
Para almacenar en una cadena de caracteres un texto introducido por teclado se puede
utilizar la sentencia scanf con el especificador ”%s”, tal y como muestra el siguiente
fragmento de código:
1 char cad[50];
2 printf("Introduce un texto: ");
3 scanf(" %s", cad);
El código anterior provoca que el texto introducido por teclado se almacene en el vector
cad. Además, el carácter especial ’\0’ es introducido automáticamente al final del texto.
Debe prestarse especial atención a que, en este caso, en la sentencia scanf no se utiliza
el operador & precediendo al nombre de la variable, tal y como venı́amos haciendo hasta
ahora. La explicación de ello está relacionada con lo explicado en la Sección 5.10, en la
que se vio que el nombre de un vector (cad en este caso) es, en realidad, la dirección de
memoria de su primer elemento (&cad[0]). Es decir, sin necesidad de poner el operador
& delante del nombre de la cadena, ésta ya se está pasando por referencia.
La función scanf lee caracteres hasta que se encuentre un salto de lı́nea (provocado me-
diante la pulsación de la tecla enter), un espacio en blanco o un tabulador. Esto implica
que scanf no permite la lectura de cadenas que contengan espacios en blanco. Por ejem-
plo, si tras la ejecución de la sentencia scanf("%s", cad) el usuario introduce por
teclado el texto "Esto es una cadena" seguido de la pulsación de la tecla enter, en
la cadena cad únicamente se almacenará el texto "Esto". Para la lectura de cadenas que
contengan espacios en blanco deberá emplearse la función gets.
Función gets:
Esta función lee caracteres de la entrada estándar (el teclado) hasta que encuentra un salto
de lı́nea y los almacena en una cadena. El salto de lı́nea (\n) no se almacena en la cadena.
A continuación se muestra un ejemplo de uso:
1 char cad[50];
2 printf("Introduce un texto: ");
3 gets(cad);
108
5.11 Cadenas de caracteres
1 #include <string.h>
A continuación se muestran algunas de las funciones más usadas. En este caso usaremos
la notación de punteros (ver Sección 5.10), ya que es de este modo como se encuen-
tra habitualmente en la bibliografı́a. Debe entenderse que char * str es equivalente a
char str[].
Función Descripción
int strlen(char *str) Devuelve la longitud de la cadena str,
sin incluir el carácter de terminación
’\0’.
char * strcpy(char *c1, char *c2) Copia la cadena almacenada en c2 a la
cadena c1, incluyendo el carácter de ter-
minación ’\0’. Devuelve un puntero a la
cadena destino (la misma que se pasa co-
mo parámetro, por lo que este valor de-
vuelto es redundante).
char * strcat(char *c1, char *c2) Concatena la cadena almacenada en c2
a continuación la cadena almacenada en
c1. El carácter de terminación ’\0’ de
la cadena c1 se sobreescribe con el pri-
mer carácter de la cadena c2 y se añade
un nuevo ’\0’ en c1, al final de la con-
catenación de las dos cadenas. Devuelve
un puntero a la cadena destino (la misma
que se pasa como parámetro, por lo que
este valor devuelto es redundante).
int strcmp(char *c1, char *c2) Compara las cadenas c1 y c2. Devuel-
ve cero si ambas cadenas son iguales, un
valor positivo si c1 es, en orden alfabéti-
co, posterior a c2, o un valor negativo si
c1 es, en orden alfabético, anterior a c2.
109
Capı́tulo 5. Vectores
1 #include <stdio.h>
2 #include <string.h>
3 #define N 50
4
5 int main() {
6 char cad1[N], cad2[N], cad3[N];
7 int x;
8
23 return 0;
24 }
110
5.12 Ejercicios resueltos
SOLUCIÓN:
1 #include <stdio.h>
2 #define N 50
3
7 int main() {
8 float v[N], m;
9
10 pedir_valores(v);
11 m = maximo(v);
12 printf("Máximo: %f\n", m);
13
14 return 0;
15 }
16
111
Capı́tulo 5. Vectores
SOLUCIÓN:
1 #include <stdio.h>
2 #define N 50
3
7 int main() {
8 int v[N], num;
9
10 pedir_valores(v);
11 printf("Introduce el valor a buscar: ");
12 scanf(" %d", &num);
13 if( pertenece(v, num) )
14 printf(" %d se encuentra en el vector\n", num);
15 else
16 printf(" %d no se encuentra en el vector\n", num);
17
18 return 0;
19 }
20
3. Implementar una función que, dada una matriz de enteros de tamaño NxM, obtenga
su traspuesta.
112
5.12 Ejercicios resueltos
SOLUCIÓN:
4. Implementar una función que realice la misma operación que la función strlen
de la librerı́a de C (cálculo de la longitud de una cadena).
SOLUCIÓN:
5. Implementar una función que, dada una cadena de caracteres que contiene palabras
separadas por un espacio en blanco, muestre la última palabra de la cadena.
SOLUCIÓN:
113
Capı́tulo 5. Vectores
2. Implementar una función que, dados un vector y un valor, devuelva la posición que
ocupa dicho valor en el vector (o -1 si el valor no se encuentra).
3. Implementar una función que, dado un vector de reales, muestre por pantalla todos
los máximos locales. Un elemento de un vector es un máximo local cuando su valor
es mayor que el anterior y el posterior. Consideraremos que el primer y último
elemento del vector no son, en ningún caso, máximos locales.
4. Escribir un programa que solicite una cadena de caracteres y nos diga si es palı́ndro-
ma (se lee igual del derecho que del revés).
7. Implementar una función que, dada una matriz cuadrada, calcule su traspuesta
(cambiar filas por columnas). La función recibirá una única matriz, y el resulta-
do se almacenará sobre la matriz original.
8. Se dispone de una matriz S de M filas por N columnas, en la que cada fila almacena
los valores en voltios de cierta señal eléctrica a lo largo del tiempo. La matriz S
114
5.13 Ejercicios propuestos
almacena, por tanto, los valores de M señales eléctricas muestreadas cada una de
ellas en N instantes de tiempo, representando el elemento S[i][j] el valor en voltios
de la señal i en el instante j. Por otro lado se dispone de un vector V de N elementos
que almacena igualmente los valores en voltios de una señal eléctrica muestreada
en N instantes de tiempo. Se desea comparar la señal almacenada en el vector V
con cada una de las M señales almacenadas en la matriz S y obtener aquella señal
de S que más se parece a la almacenada en V . Para calcular cómo de “parecidas”
son dos señales X e Y se utiliza la fórmula:
N
−1
similitud = (Xi − Yi )2
i=0
Cuanto menor sea este valor, más parecidas serán las señales X e Y . Se pide im-
plementar las funciones necesarias que nos permitan obtener, a partir de la matriz
S y el vector V , el ı́ndice de la fila en la matriz S donde se encuentra la señal más
parecida a V .
115
Capı́tulo 6
Estructuras
6.1 Introducción
Cuando ciertas variables están fuertemente relacionadas entre sı́, resulta interesante agru-
parlas bajo una misma entidad. Una estructura es un agrupamiento de variables en el que
éstas pueden ser de distinto tipo (a diferencia de los vectores, en los que todos sus elemen-
tos son del mismo tipo). A los elementos de una estructura se los denomina miembros.
Definir un tipo de dato estructurado (se definen los miembros que va a tener
nuestro agrupamiento).
117
Capı́tulo 6. Estructuras
struct nombre_tipo_datos {
tipo_dato miembro1;
tipo_dato miembro2;
. . .
};
Por ejemplo:
1 struct persona {
2 char nombre[50];
3 float altura;
4 float peso;
5 int anyo_nacimiento;
6 };
En este ejemplo se define un nuevo tipo de datos de nombre struct persona que esta-
blece un agrupamiento de cuatro miembros (nombre, altura, peso y anyo_nacimiento).
Debe quedar claro que el nombre del nuevo tipo es la palabra compuesta struct persona
y no simplemente persona.
Por otro lado es importante entender que de momento no hemos declarado ninguna varia-
ble. Simplemente hemos definido un nuevo tipo de datos. En este ejemplo struct persona
es equivalente a int o float en el sentido de que es un tipo de dato. La diferencia
principal es que struct persona es un tipo compuesto (esto es, una variable de tipo
struct persona podrá almacenar un conjunto de valores) mientras que int, por poner
un ejemplo, es untipo básico (una variable de tipo int sólo podrá almacenar un valor).
Una vez definido el tipo, será posible declarar variables del mismo.
tipo nombre_variable;
1 struct persona p;
118
6.3 Operaciones con estructuras
crea una variable p de tipo struct persona. Dicha variable p es una variable com-
puesta, ya que internamente almacena varios datos. Concretamente la variable p contiene
cuatro miembros: nombre, altura, peso y anyo_nacimiento. De hecho, cuando se
declara la variable p, se reserva memoria para almacenar 50 caracteres (nombre), 2 floats
(peso y altura) y 1 entero (año de nacimiento).
6.3.1 Inicialización
Al igual que ocurre con otros tipos de variables, las variables de tipo estructurado se
pueden inicializar en el momento de la declaración. Para ello se encierran entre llaves y se
separan por comas los valores con los que se desea inicializar cada uno de los miembros.
nombre_variable.miembro
1 p.peso=60;
2 p.altura=1.75;
3 printf(" %s", p.nombre);
4 scanf(" %d", &p.anyo_nacimiento);
1 #include <stdio.h>
2
8 int anyo_nacimiento;
9 };
10
11 int main() {
12 struct persona p; // Declaramos una variable de tipo ’struct
persona’
13 float imc;
14
24 imc = p.peso/(p.altura*p.altura);
25 printf("Hola %s ", p.nombre);
26 printf("Tu Indice de Masa Corporal es %f\n", imc);
27 return 0;
28 }
Obsérvese que los tipos estructurados suelen definirse a nivel global, fuera de la función
main. De este modo será posible declarar variables del tipo estructurado no solo en main
sino en cualquier otra función de nuestro programa.
6.3.3 Asignación
A diferencia de lo que ocurre con los vectores, entre los que no se permite la asignación,
entre variables de tipo estructurado sı́ que es posible realizar esta operación.
5 p1.peso = p2.peso;
6 p1.anyo_nacimiento = p2.anyo_nacimiento;
Es curioso observar que aunque no es posible copiar todos los elementos de un vector
en otro mediante la operación de asignación (debe hacerse elemento a elemento median-
te un bucle o, en el caso de las cadenas, mediante la función strcpy), si dicho vector se
encuentra dentro de una estructura, entonces la asignación sı́ es posible. En el ejemplo an-
terior, cuando se hace la asignación p1 = p2 se está copiando (entre otras cosas) el vector
p2.nombre en p1.nombre. Sin embargo, hacer directamente p1.nombre = p2.nombre
no serı́a válido ya que, como se ha mencionado, la asignación entre vectores no está per-
mitida.
121
Capı́tulo 6. Estructuras
1 struct fecha {
2 int dia;
3 int mes;
4 int anyo;
5 };
6 struct persona {
7 char nombre[50];
8 float altura;
9 float peso;
10 struct fecha fecha_nacimiento;
11 };
nombre_variable.miembro.submiembro;
1 struct persona p;
2
tipo nombre_vector[dimension];
declara 100 variables de tipo struct persona. En este caso, cada elemento del vector
v contiene una variable de tipo struct persona y, por tanto, deberá tratarse como tal.
Por ejemplo, para acceder al nombre de la persona almacenada en la primera posición del
vector emplearı́amos v[0].nombre y para acceder al peso de la última v[99].peso.
En el siguiente ejemplo se define una estructura de tipo alumno con dos miembros (nom-
bre y nota), a continuación se declara un vector de N alumnos, se introducen los datos de
cada uno, se calcula la nota media y finalmente se muestra un listado de los aprobados:
1 #include <stdio.h>
2 #define N 100
3
4 struct alumno {
5 char nombre[50];
6 float nota;
7 };
8
9 int main() {
10 struct alumno v[N]; // Declaramos un vector de 100 alumnos
11 int i;
12 float suma;
13 // Introducir datos
14 for(i=0; i<N; i++) {
15 printf("Nombre: ");
16 scanf(" %s", v[i].nombre);
17 printf("Nota: ");
18 scanf(" %f", &v[i].nota);
19 }
20
27 // Mostrar aprobados
28 printf("Listado de aprobados\n");
29 for(i=0; i<N; i++) {
30 if( v[i].nota >= 5 )
31 printf(" %s\n", v[i].nombre);
32 }
33 return 0;
34 }
123
Capı́tulo 6. Estructuras
Dada la variable
1 un_alumno.nota = 10;
es equivalente a
1 (*puntero_alumno).nota = 10;
El lenguaje C permite una sintaxis alternativa al * para el caso particular de punteros a es-
tructuras. Dado un puntero a una variable estructurada, es posible acceder a los miembros
de esa variable según la sintaxis habitual
(*puntero).miembro
puntero->miembro
124
6.7 Paso de estructuras como parámetros
1 #include <stdio.h>
2
3 struct alumno {
4 char nombre[50];
5 float nota;
6 };
7
10 int main() {
11 struct alumno un_alumno;
12
1 int main() {
2 struct alumno un_alumno;
3
Es importante entender que las estructuras se pasan por valor, tal y como ocurre con las
variables de tipos básicos (o primitivos). Esto quiere decir que cualquier modificación
que hagamos sobre los miembros de la estructura no tendrá efecto fuera de la función
donde se realiza (la función que recibe la estructura opera con una copia de la misma). A
continuación se muestra un ejemplo:
1 int main() {
2 struct alumno un_alumno;
3 . . .
4 subir_nota(un_alumno);
5 . . .
6 }
7
En este ejemplo la función subir_nota no tiene ningún efecto sobre la variable un_alumno.
Esta variable seguirá teniendo el mismo valor que tenı́a antes de la llamada a la función
subir_nota. Obviamente, aunque a la variable a de la función subir_nota le hubiéra-
mos dado el nombre un_alumno la situación serı́a exactamente la misma.
126
6.7 Paso de estructuras como parámetros
1 int main() {
2 struct alumno un_alumno;
3 . . .
4 subir_nota( &un_alumno );
5 . . .
6 }
7
1 int main() {
2 struct alumno v[N]; // Declaramos un vector de N alumnos
3 . . .
4 subir_notas( v ); // Subir la nota a todos los alumnos
5 . . .
6 }
7 void subir_notas( struct alumno v[] ) {
8 int i;
9
127
Capı́tulo 6. Estructuras
1 int main() {
2 struct alumno un_alumno;
3 . . .
4 un_alumno = crear_alumno();
5 . . .
6 }
7
11 printf("Nombre: ");
12 scanf(" %s", a.nombre);
13 printf("Nota: ");
14 scanf(" %f", &a.nota);
15
16 return a;
17 }
La función crear_alumno declara una variable a de tipo struct alumno. Esta variable
es devuelta al final de la función. Por tanto a la variable un_alumno declarada en la
función main se le asigna el valor de a. Esto es posible ya que, tal y como se ha comentado
en el apartado 6.3.3, el lenguaje C permite la operación de asignación entre estructuras.
1 #include <stdio.h>
2 #include <math.h>
3
128
6.8 Ejercicios resueltos
4 struct complejo {
5 float real;
6 float imag;
7 };
8
11 int main() {
12 float m;
13 struct complejo c; // Declaración de variable
14
1 #include <stdio.h>
2 #define N 5
3
4 struct persona {
5 float peso;
6 float altura;
7 int sexo; // 1=Mujer 2=Hombre
8 };
9
13 int main() {
14 struct persona v[N];
15 float m;
16
17 pedir_valores(v);
129
Capı́tulo 6. Estructuras
21 return 0;
22 }
23
130
6.9 Ejercicios propuestos
SOLUCIÓN:
8 int main() {
9 // Declaramos una matriz de tipo RGB
10 struct RGB imagen[FIL][COL];
11 . . .
12 // Llamada a la función
13 modificar_azul(imagen, 10);
14 . . .
15 }
16
17 // Implementación de la función
18 void modificar_azul( struct RGB img[FIL][COL], int x ) {
19 int i, j;
20 for(i=0; i<FIL; i++)
21 for(j=0; j<COL; j++)
22 img[i][j].b += x;
23 }
Nombre de la estación.
Distancia en kilómetros desde esta estación a la siguiente.
Precio desde esta estación a la siguiente.
Se pide:
Definir la estructura de datos necesaria para representar la información anterior.
Según esa representación, y suponiendo almacenada en esa estructura la informa-
ción ordenada según el recorrido de la primera estación a la estación terminal, escri-
bir una función que, recibiendo como parámetros dos estaciones, escriba en pantalla
las estaciones por las que va a pasar el tren y devuelva la cantidad de kilómetros y
precio necesario para hacer el recorrido.
132
Capı́tulo 7
Gestión de ficheros
7.1 Introducción
La memoria principal de los ordenadores es volátil. Esto quiere decir que en ausencia de
corriente eléctrica la memoria pierde toda la información que tenı́a almacenada. Por otro
lado, no tendrı́a sentido introducir en un programa informático los datos de matrı́cula de
20.000 alumnos y luego no poder almacenarlos de forma permanente para su posterior
recuperación (no podemos confiar en que el ordenador vaya a estar encendido indefini-
damente). Es necesario un mecanismo que nos permita almacenar los datos de nuestros
programas para futuros usos.
Un fichero (o archivo) es una secuencia de bytes que representan cierta información, al-
macenada permanentemente en algún soporte digital. La información se almacena en los
ficheros en forma de una secuencia de bytes terminada con el carácter especial EOF (End
Of File). El modo en que debe interpretarse la secuencia de bytes contenida en el fiche-
ro dependerá del sistema de codificación empleado. Un mismo fichero podrá contener
caracteres, números, imágenes, etc. codificados cada uno en un sistema diferente. Es res-
ponsabilidad del programador conocer qué criterios de codificación tienen las secuencias
de bytes contenidas en el fichero, para poder interpretar correctamente la información que
contiene.
1 El término “permanente” deberı́a ser interpretado con cautela, ya que ningún sistema de almacenamiento
está exento del riesgo de pérdida de información. En este sentido, las copias de seguridad en más de un medio
deberı́a ser una práctica habitual.
133
Capı́tulo 7. Gestión de ficheros
En ambos casos el fichero contiene una secuencia de bytes terminada con el carácter
especial EOF. La única diferencia radica en la forma en la que el programa que trata el
fichero debe interpretar la información en él contenida.
134
7.3 Operaciones con ficheros
donde:
Texto Binario
“rt” “rb” Abre el fichero para lectura (read). El fichero debe existir.
“wt” “wb” Abre el fichero para escritura (write). Si el fichero no existe,
se crea uno nuevo, en caso contrario se borra el contenido del
fichero existente.
“at” “ab” Abre el fichero en modo añadir (append). Similar al modo
de escritura, pero no borra el contenido de los ficheros exis-
tentes, sino que añade los nuevos datos al final.
“rt+” “rb+” Abre el fichero tanto para lectura como para escritura. El
fichero debe existir.
“wt+” “wb+” Abre el fichero tanto para lectura como para escritura. Si
el fichero no existe, se crea uno nuevo, en caso contrario se
borra el contenido del fichero existente.
“at+” “ab+” Abre el fichero tanto para lectura como para añadir. Si el
fichero no existe, se crea uno nuevo.
135
Capı́tulo 7. Gestión de ficheros
Respecto a la tabla anterior debe puntualizarse que, en modo texto, puede omitirse la letra
“t”, esto es, también son válidos los modos “r”, “w”, “a”, etc. En este caso se entiende
que se desea abrir en modo texto. Por otro lado, en los ejemplos presentados a lo largo de
este capı́tulo abordaremos únicamente los modos de sólo lectura y sólo escritura, esto es,
“r”, “w”, “rb” y “wb”.
Si, por ejemplo, pretendemos abrir un fichero de texto de nombre “datos.txt” en modo
lectura, deberı́amos hacer:
1 #include <stdio.h>
2
3 int main() {
4 FILE * f;
5 f = fopen("datos.txt", "r");
6 if( f == NULL ) {
7 printf("Error abriendo el fichero datos.txt \n");
8 return 0;
9 }
10
En ocasiones resulta interesante que sea el propio usuario quien indique el nombre del
fichero, en lugar de establecerlo directamente en el código del programa.
1 int main() {
2 FILE * f;
3 char nombre_fichero[512];
4
136
7.3 Operaciones con ficheros
Cuando un fichero termina de usarse debe cerrarse siempre mediante la instrucción fclose.
fclose(descriptor_de_fichero);
Tanto la lectura como la escritura se realizan de modo secuencial. Esto quiere decir
que operaciones sucesivas de escritura sobre un mismo fichero irán añadiendo el
nuevo texto a continuación del que ya habı́a, tal y como ocurre cuando escribimos
sobre la consola. Del mismo modo, en cada operación de lectura se irá leyendo de
forma consecutiva la información contenida en el fichero.
Operaciones de escritura
donde descriptor hace referencia al descriptor del fichero obtenido mediante la función
fopen. En el parámetro formato podrán utilizarse los códigos %d, %f, %c y %s, de modo
similar a como se emplean en la función printf.
137
Capı́tulo 7. Gestión de ficheros
1 #include <stdio.h>
2 int main() {
3 FILE * f;
4
1 f = fopen("hola.txt", "a");
Por supuesto, se puede hacer más de un fprintf sobre el mismo fichero. Cada instruc-
ción fprintf irá añadiendo el nuevo texto a continuación del que ya tenı́amos, tal y
como se muestra en el siguiente fragmento de código:
1 f = fopen("quijote.txt", "w");
2 fprintf(f, "En un lugar de la Mancha ");
3 fprintf(f, "de cuyo nombre no quiero acordarme,\n");
4 fprintf(f, "no ha mucho tiempo que vivı́a un hidalgo ");
5 fprintf(f, "de los de lanza en astillero\n");
6 fclose(f);
Por otro lado, que el fichero sea de texto no quiere decir que no podamos escribir números.
Simplemente, éstos se escribirán como caracteres, utilizando la codificación ASCII, tal y
como se muestra en el siguiente ejemplo:
1 #include <stdio.h>
2 int main() {
3 FILE * f;
4 int i, j;
5
6 f = fopen("tablas_de_multiplicar.txt", "w");
7 for( i = 1; i <= 10; i++ ) {
138
7.3 Operaciones con ficheros
Operaciones de lectura
Por ejemplo
leerá una palabra (se entiende por palabra una secuencia de caracteres delimitada por un
espacio en blanco, un tabulador, un salto de lı́nea o el carácter especial EOF). La próxima
instrucción fscanf leerá la información que aparezca a continuación de dicha palabra.
Por ejemplo, dado un fichero de texto que almacena cien números enteros, el siguiente
programa lee todos los valores del fichero y calcula su media:
1 #include <stdio.h>
2 int main() {
3 FILE * f;
4 int i, num, suma = 0;
5
6 f = fopen("numeros.txt", "r");
7 if( f == NULL ) {
8 printf("Error abriendo fichero\n");
9 return 0;
10 }
11 for( i = 1; i <= 100; i++ ) {
12 fscanf( f, " %d", &num );
139
Capı́tulo 7. Gestión de ficheros
13 suma += num;
14 }
15 printf("Media = %f\n", suma/100.0);
16 fclose(f);
17 return 0;
18 }
En el ejemplo anterior hemos leı́do un fichero de tamaño conocido, sin embargo, Es muy
habitual que no sepamos de antemano la cantidad de datos almacenados en el fichero. En
este caso habrá que ir leyendo hasta que se encuentre el carácter EOF. Para ello hay que
saber que la instrucción fscanf devuelve el número de elementos leı́dos o EOF en caso
de que se haya alcanzado el final del fichero, con lo que será fácil averiguar cuándo hemos
terminado de leer toda la información.
1 #include <stdio.h>
2 int main() {
3 FILE * f;
4 char caracter;
5
6 // Abrir el fichero
7 f = fopen("quijote.txt", "r");
8 if( f == NULL ) {
9 printf("Error abriendo el fichero quijote.txt\n");
10 return 0;
11 }
12
17 // Cerrar el fichero
18 fclose(f);
19 return 0;
20 }
Con este esquema, en la propia condición del bucle while se realiza una lectura y se com-
prueba si dicha lectura ha tenido éxito, en cuyo caso fscanf devuelve un valor distinto
de EOF. En este caso, se procesan los datos recién leı́dos y se vuelve al inicio del bucle
para realizar la siguiente lectura. En el momento en que fscanf devuelva EOF, será señal
de que hemos alcanzado el final del fichero.
En los ejemplos mostrados hasta ahora el nombre del fichero viene fijado en el programa,
pero en ocasiones puede resultar necesario que sea el usuario quien introduzca dicho
nombre. El siguiente ejemplo copia el contenido del fichero que elija el usuario sobre otro
fichero.
1 #include <stdio.h>
2 int main() {
3 FILE * f_in, * f_out;
4 char nom_fich_in[512], nom_fich_out[512], caracter;
5
141
Capı́tulo 7. Gestión de ficheros
32 return 0;
33 }
Debe observarse que en este ejemplo, los nombres de los ficheros son introducidos por el
usuario y almacenados en cadenas de caracteres (lı́neas 8 y 17). A continuación, en las
instrucciones fopen (lı́neas 9 y 18) utilizamos como argumento estas variables en lugar
de una cadena constante.
1 #include <stdio.h>
2 #define MAX_ALUMNOS 500
3
4 struct alumno {
5 char nombre[50];
6 char grupo;
7 float nota;
8 };
9
12 int main() {
13 struct alumno v[MAX_ALUMNOS];
14 int num_alumnos;
15 num_alumnos = cargar_datos(v);
16 . . .
17 }
18
24 // Abrir fichero
25 printf("Introduce nombre de fichero de alumnos: ");
26 scanf(" %s", nom_fich);
27 f = fopen(nom_fich, "r");
28 if( f == NULL )
29 return 0; // No se han podido leer los datos
142
7.3 Operaciones con ficheros
30
41 // Cerrar fichero
42 fclose(f);
43
Operaciones de escritura
donde:
Por ejemplo, si quisiéramos guardar en un fichero binario el valor de cierta variable entera
n, harı́amos:
1 FILE * f;
2 int n;
3 . . .
4 fwrite(&n, sizeof(int), 1, f);
Para guardar una variable de un tipo estructurado (por ejemplo una variable alum de tipo
struct alumno) ejecutarı́amos:
1 FILE * f;
2 struct alumno alum;
3 . . .
4 fwrite(&alum, sizeof(struct alumno), 1, f);
Si en lugar de tener una única variable tenemos un vector, podemos guardar el vector
entero con una única instrucción del siguiente modo:
1 FILE * f;
2 struct alumno v[N];
3 . . .
4 fwrite(v, sizeof(struct alumno), N, f);
Debe recordarse que el nombre de un vector es, en realidad, un puntero al primer elemento
del vector. Por este motivo la variable v no debe llevar, en este caso, el & delante.
Operaciones de lectura
La función fread devuelve el número de elementos leı́dos. Si el valor devuelto por fread
es cero, querrá decir que no ha sido posible llevar a cabo la lectura (normalmente porque
se ha alcanzado el final de fichero). Por ejemplo, si disponemos de un fichero de nombre
“alumnos.bin” que contiene 500 variables de tipo struct alumno, podrı́amos leerlo con
el siguiente fragmento de código:
1 FILE * f;
2 struct alumno v[N]; // Suponiendo N = 500
3 f=fopen("alumnos.bin", "rb");
4 fread(v, sizeof(struct alumno), N, f);
5 fclose(f);
1 FILE * f;
2 struct alumno v[MAX_ALUMNOS];
3 int i = 0;
4
5 f=fopen("alumnos.bin", "rb");
6 while( i < MAX_ALUMNOS && fread(&v[i], sizeof(struct alumno), 1,
f) > 0 )
7 i++;
8 fclose(f);
9 printf("Hemos leı́do %d alumnos\n", i);
Como puede observarse en el ejemplo anterior, en cada iteración del bucle while se leen
los datos de un alumno. En el momento en que la instrucción fread devuelva cero se
saldrá del bucle, ya que esto indica que se han leı́do todos los datos del fichero. En caso
de que el fichero contenga más de MAX_ALUMNOS elementos, el bucle finalizará cuando se
alcance este número de alumnos, evitando de este modo sobrepasar el rango del vector v.
145
Capı́tulo 7. Gestión de ficheros
En algunas ocasiones puede resultar interesante romper esta secuencialidad y leer o escri-
bir en una posición determinada del fichero. En este caso hablamos de acceso aleatorio.
SOLUCIÓN:
1 #include <stdio.h>
2 #include <math.h>
3
4 int main() {
5 FILE * f_in, *f_out;
6 float mod, ang, x, y;
7
23 // Cerrar ficheros
24 fclose( f_in );
25 fclose( f_out );
26
27 return 0;
28 }
2. Un fichero binario contiene los datos censales de cierto número de personas (co-
mo máximo 5.000). Concretamente almacena, para cada persona, el nombre (50
caracteres), el municipio de residencia (50 caracteres) y el dı́a, mes y año de naci-
miento (enteros). Implementar una función que lea la información de dicho fichero,
la almacene en un vector de estructuras y devuelva el número de personas leı́das.
Supondremos que está definido el siguiente tipo estructurado:
struct persona {
char nombre[50];
char municipio[50];
int dia, mes, anyo;
};
5 f = fopen(nom_fich, "rb");
6 if( f == NULL )
7 return 0;
8 while( fread( &v[i], sizeof(struct persona), 1, f ) > 0 )
9 i++;
10 fclose(f);
11 return i;
12 }
13
14 int main() {
15 struct persona v[5000];
16 char nom_fich[50];
17 int num_personas;
18
23 // Llamada a la función
24 num_personas = cargar_datos_censales(nom_fich, v);
25
29 return 0;
30 }
148
7.6 Ejercicios propuestos
Implementar una función que reciba un vector de tipo struct alumno y almacene
en el fichero “aprobados.txt” el nombre y la nota de los alumnos aprobados, y en
“suspensos.txt” el nombre y la nota de los suspendidos.
149
Bibliografı́a
151