Programacion C
Programacion C
Programacion C
en C
Septiembre de 2012
EDITORIAL
`
`
UNIVERSITAT POLITECNICA
DE VALENCIA
Coleccin Acadmica
Indice general
Indice general
1 Introduccion a la computacion
1.6 El lenguaje C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10
11
2.3.1 Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
12
13
2.4 Comentarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16
17
19
2.7 Expresiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21
21
23
Indice
general
23
24
24
26
2.9 Punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
28
2.9.1 Declaracion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
28
28
29
30
31
31
32
33
34
3 Estructuras de control
3.1 Sentencias de seleccion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
35
35
39
42
42
44
47
50
51
3.3.1 Contadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
51
3.3.2 Acumuladores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
52
3.3.3 Banderas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
53
55
57
57
4 Funciones
II
35
61
4.1 Introduccion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
61
61
Indice
general
63
65
67
69
70
72
73
76
76
77
79
79
80
81
82
84
5 Vectores
87
5.1 Introduccion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
87
88
89
90
92
98
98
99
III
Indice
general
6 Estructuras
117
7 Gestion de cheros
133
Bibliografa
IV
151
Captulo 1
Introduccion a la computacion
La informatica es la ciencia que aborda el tratamiento automatico de la informacion por
medio de ordenadores. Es importante conocer el modo en que un ordenador codica,
almacena y procesa la informacion, para poder abordar con soltura la tarea de programacion. El objetivo de este captulo es dar una pequena introduccion a la computacion y al
proceso de programacion.
1.1
Cualquier sistema de tratamiento de la informacion, sea o no automatico, puede descomponerse, tal y como muestra la gura 1.1, en tres etapas basicas:
Entrada: Recogida de datos.
Proceso: Tratamiento de los datos.
Salida: Presentacion de los resultados obtenidos.
Cuando el volumen de datos a tratar es muy elevado o se requiere gran velocidad en el
proceso de los mismos o simplemente la tarea a realizar es lo sucientemente aburrida
Entrada
Proceso
Salida
S
o tediosa para un humano, sera deseable realizar este proceso de forma automatica. 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, debera disponer de los elementos hardware necesarios para la entrada de datos (teclado, raton, ...), 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, ...). Ademas de todos estos elementos, sera 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 versatil
y pueda utilizarse para realizar tareas muy diversas. En este libro aprenderemos a crear
dichos programas, pero antes sera necesario tener unos conocimientos mnimos sobre el
modo en que un ordenador codica la informacion.
1.2
Un ordenador funciona por impulsos electricos, de modo que u nicamente puede diferenciar entre presencia y ausencia de corriente o carga electrica, esto es, solo puede diferenciar dos estados. Comunmente identicamos a estos dos estados mediante los dgitos
0 y 1. Es por ello que cualquier informacion que deba ser procesada por un ordenador,
necesariamente tiene que estar representada mediante una secuencia de unos y ceros, esto
es, debe estar codicada en el sistema binario.
En el sistema decimal que empleamos los humanos, cada dgito tiene un peso distinto
segun la posicion que ocupa. De derecha a izquierda tenemos las unidades, decenas, centenas, etc, donde el dgito 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 sistema binario, empleado por los ordenadores y otros dispositivos electronicos, cada dgito,
de derecha a izquierda, tiene un peso de 20 , 21 , 22 , etc.
Por ejemplo, el numero binario 1101 equivale al decimal 13, ya que 1 23 + 1 22 + 0
21 + 1 20 = 13.
A los sistemas decimal y binario (por poner algunos ejemplos de sistemas de numeracion)
se los denomina tambien sistema en base 10 y en base 2 respectivamente. En general,
para conocer el valor decimal de cualquier numero N expresado en base B aplicaremos la
n
formula:
xi B i
N = x0 B 0 + x1 B 1 + ... + xn B n =
i=0
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 codicar los elementos de un conjunto dado (numeros enteros,
letras, colores, etc.) mediante distintas secuencias de bits. Por ejemplo, con un byte (8
bits) pueden codicarse 28 = 256 elementos. Con 4 bytes (32 bits) pueden codicarse
mas 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, mas de mil
millones de bytes).
Ya hemos visto como un ordenador puede representar numeros enteros mediante el sistema binario, pero tambien debe ser capaz de representar numeros reales, caracteres, imagenes, sonido y por supuesto instrucciones, por poner solo algunos ejemplos. Es necesario
disponer de distintos sistemas de codicacion para poder representar informacion de
cualquier tipo.
La codicacion es el proceso por el cual representamos smbolos o secuencias de un
alfabeto mediante los smbolos o secuencias de otro alfabeto, es decir, establecemos una
correspondencia biunvoca entre los smbolos de dos alfabetos distintos. En los ejemplos
anteriores hemos visto como codicar numeros enteros mediante secuencias de 1s y 0s
en lo que conocemos como sistema de codicacion BCD. Pero, tal y como acabamos de
mencionar, necesitamos otros sistemas para codicar otros objetos de otros conjuntos.
Para codicar los numeros reales en el sistema binario se utiliza lo que denominamos
representacion en coma otante o formato cientco. Por ejemplo, el numero 12.5 (el
cual acabamos de escribir en su notacion habitual o coma ja) se expresara en notacion
cientca como 0.125E2. La representacion binaria de dicho numero consiste en la utilizacion 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. Valiendonos del sistema BCD
utilizado para numeros enteros, codicaramos por un lado el numero 125 (mantisa en
formato normalizado) y por otro lado el exponente (2 en este ejemplo).
Otro sistema de codicacion es el ASCII (American Standard Code for Information Interchange) utilizado para la representacion de caracteres alfanumericos. Para ello se dene
una tabla de correspondencia, en la que a cada caracter se le asocia un codigo binario.
Dicha tabla de correspondencia es un estandar que utilizan todos los sistemas informaticos, lo que permite el intercambio de informacion entre distintos sistemas. Imaginemos
algo tan habitual como enviar un correo electronico. El texto en e l contenido, se representara mediante en una secuencia de 1s y 0s, resultado de codicar cada uno de los
caracteres del mensaje mediante la tabla ASCII. Dicha secuencia sera sencillamente descifrable por cualquier equipo informatico, sin mas que aplicar de nuevo la tabla de conversion ASCII en sentido inverso. Resulta evidente que sin la existencia de estandares de
codicacion, no sera posible compartir informacion entre los distintos ordenadores.
3
1.3
Concepto de algoritmo
Denibilidad: Toda regla debe denir perfectamente la accion a desarrollar sin que
pueda haber ambiguedad alguna de interpretacion.
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
nales pertenecen respectivamente a conjuntos especcos.
Ecacia: Se debe pretender que un algoritmo sea ecaz, esto es, que su ejecucion
resuelva el problema en el mnimo numero de operaciones posible.
Se dice que un determinado problema es decidible o computable cuando existe un algoritmo que lo resuelve. Existen, sin embargo, muchas clases de problemas no decidibles.
Estos problemas, por mucho que aumente la capacidad y velocidad de los computadores,
nunca podran resolverse.
Un ejemplo tpico de problema no decidible es el problema de la teselacion. Una tesela
(baldosa) es un cuadrado de 1x1, dividido en cuatro partes iguales por sus dos diagonales.
Cada una de estas partes tiene un color. El problema es: dada cualquier supercie nita, de
cualquier tamano (con dimensiones enteras), puede recubrirse usando teselas de forma
que cada dos teselas adyacentes tengan el mismo color en los lados que se tocan? Este es
un problema indecidible: no se puede demostrar que la supercie pueda recubrirse segun
el planteamiento del problema, pero tampoco puede demostrarse lo contrario. No existe
ningun algoritmo que resuelva este problema.
1.4
El proceso de la programacion
Un programa es la implementacion o escritura de un algoritmo en un lenguaje de programacion especco. Las fases para la correcta elaboracion de un programa son las siguientes:
1. Analisis del problema: denir el problema de la manera mas precisa posible.
del algoritmo: disenar un metodo (algoritmo) que resuelva el problema
2. Diseno
planteado.
3. Codicacion: escribir (implementar) el algoritmo en un lenguaje de programacion.
4. Vericacion: comprobacion del correcto funcionamiento del programa. Si no funciona correctamente, dependiendo del error habra que volver al paso 3, al 2 o incluso al 1.
5. Mantenimiento: desarrollo de mejoras y nuevas funcionalidades.
Estas cinco fases es lo que se denomina el ciclo de vida de un proyecto informatico. En
ocasiones el termino programacion se asocia, de forma incorrecta, u nicamente al paso
3, pero para desarrollar correctamente un programa es necesario abordar todas las fases
descritas.
5
1.5
Lenguajes de programacion
#include <stdio.h>
2
3
4
int main() {
int a, b, c;
printf("Introduce 2 n
umeros enteros");
scanf(" %d %d", &a, &b);
c = a + b;
printf("La suma es %d\n", c);
return 0;
6
7
8
9
10
11
program suma;
2
3
4
5
var a,b,c:integer;
begin
writeln(Introduce 2 n
umeros enteros);
1.6 El lenguaje C
readln(a,b);
c:= a + b;
writeln(La suma es
6
7
8
,c);
end
20
model small
.stack
.data
var1 db ?
.code
.startup
mov ah,01h
int 21h
sub al,30h
mov var1,al
mov ah,01h
int 21h
sub al,30h
add al,var1
mov dl,al
add dl,30h
mov ah,02h
int 21h
;.exit
end
1.6
El lenguaje C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
C es un lenguaje estructurado de alto nivel de proposito general. Esto quiere decir que sirve para distintos propositos: programas cientcos, programas de gestion, comunicacion,
manejadores de dispositivos (drivers), sistemas operativos, compiladores, etc.
C dispone de las estructuras tpicas de los lenguajes de alto nivel pero, a su vez, permite
trabajar a bajo nivel, por lo que algunos autores lo clasican como lenguaje de medio
nivel.
El lenguaje C fue desarrollado en la decada de los 70 por Kenneth Thompson y Dennis
Ritchie en los laboratorios Bell y alcanzo gran popularidad durante la decada de los 80
tras la publicacion de su denicion por parte de Brian Kernighan y el propio Ritchie2 . La
2 Brian
creacion del lenguaje C va ligada a otro fenomeno de la informatica: la creacion del sistema operativo UNIX, desarrollado tambien en los laboratorios Bell. El nucleo (o kernel)
del sistema operativo Unix esta escrito casi en su totalidad en lenguaje C, con excepcion
de alguna parte escrita en ensamblador. Tambien el sistema operativo Linux y muchas de
sus aplicaciones estan escritas en este mismo lenguaje.
Hoy en da el lenguaje C se sigue utilizando en multitud de programas. Ademas, muchos
otros lenguajes de programacion basan su sintaxis en C, como C++, C#, Java, PHP, Perl,
etc.
1.7
Compiladores e interpretes
Los ordenadores u nicamente entienden el lenguaje maquina. Por tanto, un programa escrito en cualquier otro lenguaje debe ser traducido a lenguaje maquina para que pueda
ser ejecutado. Este proceso de traduccion se conoce como compilacion o interpretacion,
dependiendo de como se realice.
El proceso de compilacion consiste en traducir el programa completamente a lenguaje
maquina antes de ser ejecutado. La interpretacion, por contra, consiste en ir traduciendo
las instrucciones una a una o en pequenos grupos, e ir ejecutandolas a medida que se
traducen.
Como puede deducirse, un programa compilado se ejecutara a mayor velocidad que uno
interpretado, ya que en el primer caso la traduccion se hace una u nica vez, de modo que
se almacena en un archivo aparte el programa traducido (denominado codigo maquina
o ejecutable) y a continuacion se ejecuta tantas veces como se desee el codigo maquina
generado, mientras que en el segundo caso es necesario ir traduciendo las instrucciones
durante la propia ejecucion.
El proceso de compilacion se realiza mediante un programa especial denominado compilador, mientras que el programa encargado de la interpretacion se denomina interprete.
Un programa dado, dependiendo de el lenguaje en que este escrito, debera ser compilado
o interpretado. El lenguaje C requiere del proceso de compilacion.
Captulo 2
main() {
sentencia_1;
sentencia_2;
. . .
sentencia_N;
Todo programa en C esta compuesto por una serie de instrucciones o sentencias1 , separadas por punto y coma. Estas instrucciones se encuentran dentro de la funcion main.
En el Captulo 4 se estudiaran en detalle las funciones, de momento es suciente con
entender que una funcion es un modulo o bloque de nuestro programa que contiene una
serie de instrucciones y que la funcion main (funcion principal) es el punto de inicio de
todo programa en C. Para delimitar el conjunto de instrucciones que contiene una funcion
(main en nuestro caso) se utilizan los smbolos { y }.
1A
lo largo de este libro vamos a utilizar indistintamente los terminos instruccion y sentencia.
int main() {
return 0;
}
aunque este programa no hara absolutamente nada ya que la u nica instruccion que contiene (return 0) indica la nalizacion del mismo.
2.2
El programa mas sencillo, y que ademas haga algo tangible, es aquel que muestra un
mensaje por pantalla. Por ejemplo:
1
#include <stdio.h>
2
3
4
5
6
int main() {
printf("Hola");
return 0;
}
printf("Texto a mostrar");
#include <stdio.h>
Sin entrar en detalle, diremos que es necesario incluir esta instruccion para poder utilizar
printf en nuestros programas. En el Apartado 2.10 se explican este tipo de instrucciones
10
con mayor profundidad. De momento u nicamente es necesario saber que cualquier programa que utilice la instruccion printf debera contener al inicio del mismo la sentencia
#include <stdio.h>.
La instruccion return 0 la pondremos habitualmente como u ltima instruccion de la funcion main. En el Captulo 4 se estudiaran con detalle las funciones y se explicara el uso
de la instruccion return.
2.3
Un programa esta compuesto por instrucciones y datos. Los datos, al igual que las instrucciones, se almacenan en la memoria del ordenador durante la ejecucion del programa.
Cada dato ocupa una posicion de memoria, de modo que es posible consultar o modicar
el valor de un dato mediante el acceso a la zona de memoria en la que esta almacenado.
2.3.1
Variables
Una variable puede verse como un contenedor donde almacenar un dato determinado. En
realidad una variable representa una posicion de memoria en la que se encuentra almacenado un dato. Las variables son un mecanismo que ofrecen los lenguajes de programacion
para facilitar el acceso a los datos sin necesidad de conocer las posiciones o direcciones
de memoria en que se encuentran almacenados.
Cuando se crea una variable se le asigna un nombre que la identica. Un nombre de
variable puede constar de uno o mas caracteres y debe cumplir las siguientes restricciones:
El primer caracter debe ser una letra o el caracter subrayado ( ), mientras que el
resto pueden ser letras, dgitos o el caracter subrayado ( ). Las letras pueden ser
minusculas o mayusculas del alfabeto ingles. Por lo tanto, no esta permitido el uso
vocales acentuadas, smbolos especiales, etc.
de las letras n, N,
No pueden existir dos variables con el mismo nombre dentro de una misma funcion.
El nombre de una variable no puede ser una palabra reservada. Las palabras reservadas son identicadores propios del lenguaje. Por ejemplo, int y main son
palabras reservadas del lenguaje C.
Por ejemplo, lo siguiente seran nombres validos de variables: x, y, v1, v2, temperatura,
velocidad_maxima, Vmax.
11
2.3.2
Tipos de datos
Las variables se clasican en distintos tipos, segun la informacion que almacenan. Cuando se crea una variable, es necesario especicar a que tipo de dato pertenece, de este
modo se le asigna memoria suciente para almacenar valores del tipo especicado. En
C existen cinco tipos de datos basicos: numeros enteros, numeros reales con precision
simple, numeros reales de doble precision, caracteres y punteros.
El tipo de dato de una variable determina:
(cantidad de bytes) que ocupara la variable en la memoria del ordenador.
El tamano
El rango de valores que la variable podra almacenar.
El conjunto de operaciones que se puede realizar sobre dicha variable.
En la Tabla 2.1 se muestran los tipos basicos en C, el tamano que ocupa cada uno de ellos
en memoria y el rango de valores que pueden almacenar.2
Tabla 2.1: Tipos de datos simples en C
Tipo de dato
Caracter
Entero
Real
Real (doble precision)
Puntero
Bytes
1
4
4
8
4
Rango
-127 a 128
-2147483648 a 2147483647
3.4E-38 a 3.4E38
1.7E-308 a 1.7E308
-
Como puede observarse en la Tabla 2.1, el tipo char se puede utilizar tanto para representar caracteres como numeros enteros pequenos. En la Seccion 2.8.1 se explica este tipo de
dato con mas detalle. El tipo puntero se emplea para almacenar direcciones de memoria.
En la Seccion 2.9 se explican los punteros.
Los tipos char, int, float y double tambien existen en su version unsigned (sin signo):
unsigned char, unsigned int, unsigned float y unsigned double. Las variables de estos tipos u nicamente pueden almacenar numeros positivos, pero con la ventaja
de duplicar el rango de la parte positiva. Por ejemplo, una variable de tipo unsigned char
puede almacenar numeros enteros entre 0 y 255.
Algunos tipos de datos tambien admiten los modicadores 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
no y rango de valores pueden variar en funcion de la implementacion. Los valores dados
son los habituales en un ordenador con arquitectura de 32 bits.
12
unsigned short int ocupa igualmente 2 bytes, pero en este caso puede almacenar
2.3.3
Antes de poder utilizar una variable en un programa hay que declararla. Para ello se
emplea la siguiente sintaxis:
tipo_de_dato nombre_variable;
Por ejemplo, para declarar una variable de tipo int y nombre x utilizaramos la siguiente
sentencia:
1
int x;
Si se desea declarar mas de una variable del mismo tipo, puede hacerse separandolas por
comas del siguiente modo:
1
2
int x, y, z;
float a, b;
int main() {
int x;
. . .
4
5
13
int main() {
float pi;
pi = 3.14;
. . .
5
6
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 sera adecuado declarar una variable de tipo int y tratar de
almacenar en ella un valor real, tal y como muestra el siguiente ejemplo:
1
2
3
int main() {
int x;
x = 2.75;
. . .
5
6
int main() {
float area;
area = 3.14 * 4;
. . .
5
6
14
En las operaciones tambien pueden aparecer variables. En este caso la variable que forma parte de la operacion se sustituye por su valor. Por ejemplo, el siguiente programa
almacena en la variable perim el resultado de la operacion 2 3,14 3.
1
2
3
4
5
int main() {
float pi, radio, perim;
pi = 3.14;
radio = 3;
perim = 2 * pi * radio;
. . .
nombre_variable = expresion;
donde expresion puede ser un valor constante (por ejemplo x=2), una variable (por
ejemplo x=y), una operacion (por ejemplo x=2*y), etc. En la Seccion 2.7 se explica con
mas detalle el concepto de expresion. El efecto de la operacion de asignacion sera almacenar en nombre_variable el resultado de expresion.
Es muy importante tener la precaucion de que cuando se utilice una variable como parte
de una expresion, dicha variable tenga un valor asignado previamente, de lo contrario el
resultado de la expresion sera impredecible. Por ejemplo el siguiente programa, aunque
es sintacticamente correcto, su resultado es impredecible:
1
2
3
4
5
int main() {
float pi, radio, perim;
pi = 3.14;
perim = 2 * pi * radio;
radio = 3;
. . .
7
8
Debe entenderse que las instrucciones de un programa se ejecutan secuencialmente, comenzando con la primera. El error del ejemplo anterior radica en que cuando se ejecuta la
instruccion perim = 2 * pi * radio el valor de la variable radio es desconocido,
ya que todava no se le ha asignado nada. En consecuencia, el resultado almacenado en
perim sera impredecible. El hecho de ejecutar posteriormente la instruccion radio=3 ya
no cambia para nada el valor almacenado en perim.
15
El que a una variable no le asignemos nada de forma explcita no quiere decir que no
contenga ningun valor, sino que e ste es desconocido. En el momento de declarar una
variable, e 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 operacion de asignacion es destructiva. Esto quiere decir que cuando a una variable
se le asigna un valor, pierde el valor que tuviera anteriormente.
2.4
Comentarios
Para anadir un comentario de lnea se utiliza //. El texto que aparece desde //
hasta el nal de la lnea es ignorado por el compilador. Por ejemplo:
1
2
3
4
int main() {
// Declaraci
on de variables
float r; // Radio del c
rculo
float a; // Area del c
rculo
a = 3.14 * r * r;
. . .
6
7
8
16
2.5
En la Seccion 2.2 se ha explicado como mostrar un texto simple con printf. En ocasiones, junto con el texto, necesitaremos mostrar el valor de alguna variable. Tomemos como
ejemplo el siguiente programa que calcula la suma de dos variables y muestra de forma
incorrecta el resultado:
1
#include <stdio.h>
2
3
4
int main() {
int a, b, c;
a = 2;
b = 3;
c = a + b;
printf("La suma de a y b es c");
return 0;
6
7
8
9
10
11
El error del programa anterior radica en que la instruccion printf, tal y como se ha
escrito, mostrara literalmente el texto especicado. Esto es, la ejecucion de este programa
mostrara por pantalla el mensaje:
La suma de a y b es c
Podemos observar que en la instruccion printf aparece en primer lugar un texto encerrado entre dobles comillas, seguido de una serie de variables separadas por comas. El
texto encerrado entre comillas contiene una serie de codigos especiales %d. Los codigos %
(existen mas aparte de %d) indican que esta parte del texto debe ser sustituida por el valor
de alguna expresion (una variable en el caso mas sencillo). Concretamente, el codigo %d
indica que, en la posicion en la que aparece, debe mostrarse el valor de una expresion que
de como resultado un valor entero (por ejemplo una variable de tipo int). La expresion
o variable a mostrar sera alguna de las que aparezcan a continuacion del texto encerra17
do entre comillas. La asociacion entre los codigos % y las expresiones que aparecen a
continuacion se hace por orden de aparicion. En consecuencia, en el ejemplo anterior, el
primer codigo %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 instruccion printf contenga tantas expresiones
(variables en el ejemplo anterior) como codigos %.
En la Tabla 2.2 se muestra los codigos % (especicadores) que pueden aparecer en una
instruccion printf. En la primera parte de la tabla se muestran los que se usan de forma
mas habitual y en los que, de momento, nos centraremos.
Tabla 2.2: Especicadores mas habituales.
Especicador
%d o %i
%f
%c
%u
%x o %X
%o
%e o %E
%g o %G
%s
#include <stdio.h>
2
3
4
5
6
int main() {
int entero = 47;
float real = 128.75;
char car = A;
7
8
9
10
18
printf("Mostramos el car
acter \\.\n");
printf("Variable entera: %d\n", entero);
printf("Variable real: %f\n", real);
printf("Variable de tipo car
acter: %c\n", car);
printf("Variable entera en hexadecimal: %X\n", entero);
printf("Variable entera en octal: %o\n", entero);
printf("Variable real en notaci
on exponencial: %E\n",real);
printf("Variable real en notaci
on cient
fica: %G\n", real);
11
12
13
14
15
16
17
18
19
return 0;
20
21
2.6
Habitualmente es necesario que sea el usuario del programa quien introduzca los datos.
La instruccion scanf permite almacenar en variables los datos que el usuario introduce a
traves del teclado. La sintaxis de esta instruccion es la siguiente:
scanf("especificadores", lista_de_variables);
#include <stdio.h>
2
3
4
int main() {
float a, b, c;
printf("Introduzca un n
umero: ");
scanf(" %f", &a);
printf("Introduzca otro n
umero: ");
scanf(" %f", &b);
c = a + b;
printf("La suma es %f\n", c);
return 0;
6
7
8
9
10
11
12
13
Como se puede observar en el programa anterior, es habitual que cada instruccion scanf
vaya precedida de un printf para indicar al usuario de lo que debe hacer.
Tambien es posible leer mas de un dato con una u nica instruccion scanf. Por ejemplo:
1
2
printf("Introduzca dos n
umeros: ");
scanf(" %f %f", &a, &b);
En este caso el programa se detendra en la instruccion scanf hasta que el usuario haya
introducido los dos valores.
20
2.7 Expresiones
2.7
Expresiones
Una expresion es una combinacion de constantes, variables, smbolos de operacion, parentesis y funciones. Por ejemplo, lo siguiente son expresiones validas en C:
1
2
3
a-(b+3)*c
2*3.1416*r
sin(x)/2
En la u ltima expresion del ejemplo aparece una llamada a la funcion seno (sin). Las funciones se estudiaran en el Captulo 4.
a un valor determinado, esto es, el resultado de una expreLas expresiones se evaluan
sion siempre es un valor (entero o real). Las expresiones en C pueden clasicarse en tres
categoras:
Aritmeticas
Relacionales
Logicas
En los siguientes apartados se explica cada una de ellas.
2.7.1
Expresiones aritmeticas
se desean las dos u ltimas cifras habra que hacer n % 100 (127 % 100 = 27). Para saber
si un numero n es par o impar habra que calcular n % 2. Si el resultado de esta operacion
es 0 quiere decir que n es par, mientras que si el resultado es 1 signicara que n es impar.
Para evaluar las expresiones aritmeticas existen unas reglas de prioridad y asociatividad:
Las operaciones entre parentesis se evaluan primero.
Los operadores *, / y % se evaluan antes (tienen mayor prioridad) que + y -.
Los operadores de igual prioridad se evaluan de izquierda a derecha.
Por ejemplo, el resultado de 3 + 2 * 4 es 11 (se realiza en primer lugar la multiplicacion) mientras que (3 + 2) * 4 da 20 (se realiza en primer lugar la suma).
Operadores de incremento y decremento
Los operadores ++ y -- sirven, respectivamente, para incrementar y decrementar en una
unidad el valor de una variable. Por ejemplo, n++ incrementa en uno el valor de la variable n, mientras que n-- lo decrementa. Como puede observarse, se trata de operadores
unarios (unicamente tienen un operando).
La operacion n++ es equivalente a n = n + 1 (se calcula n + 1 y el resultado se
almacena en la propia variable n, lo que provoca que el valor de n acabe incrementandose
en 1). De forma analoga, n-- es equivalente a n = n - 1.
Tambien es posible escribir el operador antes del operando (++n y --n). Esto no introduce
ninguna diferencia cuando la operacion se hace de forma aislada, aunque es importante
tenerlo en cuenta cuando esta operacion se combina con otras dentro de la misma expresion, ya que modica el orden en el que se realiza el incremento o decremento. Por
ejemplo x / y++ calcula primero la division x / y y a continuacion incrementa la variable y, mientras que x / ++y incrementa en primer lugar el valor de y y a continuacion
realiza la division (con el valor de y ya incrementado).
Operadores reducidos
Los operadores reducidos mas habituales son +=, -=, *= y /=. Estos operadores permiten
expresar de forma mas compacta algunas operaciones. Por ejemplo, x += 2 equivale a
la operacion x = x + 2 (esto es, incrementa el valor de la variable x en dos unidades).
x *= 5 equivale a la operacion x = x * 5.
A continuacion se muestra un ejemplo con el uso de las operaciones aritmeticas vistas:
22
2.7 Expresiones
#include <stdio.h>
2
3
4
int main() {
int x, y, z;
x = 6;
x--;
y = x / 2;
z = y++;
6
7
8
9
10
x += 3;
x *= 2;
z = x % 7;
11
12
13
//
//
//
//
//
//
//
x vale 5
y vale 2 (se realiza la divisi
on entera)
z vale 2 e y vale 3 (primero se realiza
la asignaci
on y luego el incremento)
x vale 8
x vale 16
z vale 2 (el resto de dividir 16 entre 7)
14
return 0;
15
16
2.7.2
Expresiones relacionales
Las expresiones relacionales son aquellas que comparan valores. Para ello se utilizan los
operadores <, <=, >, >=, == y != (que se corresponden, respectivamente, con las operaciones menor, menor o igual, mayor, mayor o igual, igual y distinto). El resultado de una
expresion relacional es 1 cuando el resultado de la comparacion es cierto y 0 cuando el
resultado es falso. Por ejemplo, dado x = 5, el resultado de la operacion x >= 5 es 1
(cierto) y el de x != 5 es 0 (falso).
No debe confundirse la operacion de comparacion == con la operacion de asignacion =
(explicada en la Seccion 2.3.3). Por ejemplo, la expresion x==5 comprueba si la variable
x vale 5. En caso armativo el resultado de esta expresion sera 1 y de lo contrario dara 0.
Por contra, la operacion x=5 almacena el valor 5 en la variable x.
2.7.3
Expresiones logicas
Las expresiones logicas son aquellas que utilizan los operadores logicos &&, ||, y !, los
cuales se corresponden, respectivamente, con las operaciones Y, O y NO (AND, OR
y NOT). Las expresiones logicas permiten combinar otras expresiones cuyo resultado es
verdadero/falso (1/0). A su vez, el resultado de una expresion logica es 1 (verdadero) o 0
(falso).
Por ejemplo, dadas las variables a=5 y b=2:
a != 0 && b >= a (a distinto de cero y b mayor o igual que a) se evalua a 0
(falso).
23
a == 4 || a+b < 8 (a igual a cuatro o a+b menor que ocho) se evalua a 1 (cierto).
!(b > a) (no b mayor que a) se evalua a 1 (cierto).
exp1
0
0
1
1
2.8
2.8.1
exp2
0
1
0
1
exp1 || exp2
0
1
1
1
!exp1
1
1
0
0
Como se ha visto en la Seccion 2.3.2, el lenguaje C utiliza el tipo char para almacenar
caracteres. En realidad el ordenador trabaja u nicamente con numeros, por lo que cada
caracter se representa mediante un numero. Esto es, el tipo char realmente almacena
numeros, los cuales pueden ser interpretados como caracteres.
La interpretacion de que caracter hay almacenado en una variable de tipo char se realiza
mediante una tabla de conversion. La tabla mas conocida (por ser la primera que aparecio)
es el estandar ASCII (American Standard Code for Information Interchange). En la Tabla 2.4 se muestra la tabla ASCII. En esta tabla, los codigos del 0 al 31 en realidad no son
caracteres imprimibles sino que representan codigos de control (por ejemplo, el codigo
13 representa el salto de lnea o Carriage Return, mientras que el codigo 27 representa
la tecla Escape). Entre el 65 y el 90 se codican las letras mayusculas, mientras que las
minusculas ocupan los codigos del 97 al 122. Los caracteres numericos (dgitos del 0 al
9) aparecen entre los codigos 48 al 57.
24
NUL
SOH
STX
ETX
EOT
ENQ
ACK
BEL
BS
HT
LF
VT
FF
CR
SO
SI
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
DLE
DC1
DC2
DC3
DC4
NAK
SYN
ETB
CAN
EM
SUB
ESC
FS
GS
RS
US
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
SP
!
#
$
%
&
(
)
*
+
,
.
/
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
0
1
2
3
4
5
6
7
8
9
:
;
<
=
>
?
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
@
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
P
Q
R
S
T
U
V
W
X
Y
Z
[
\
]
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
p
q
r
s
t
u
v
w
x
y
z
{
|
}
DEL
Debe aclararse que la tabla ASCII se denio para 7 bits, lo que permite representar 128
caracteres. Esto es insuciente para muchas lenguas que utilizan caracteres que no estan
C, etc.). Por ello se denieron otros
representados en esta tabla (vocales acentuadas, N,
estandares de 8 bits que permitan extender el numero de caracteres a 256 (por ejemplo el
ISO-8859-1).
En C los caracteres deben encerrarse entre comillas simples. Por ejemplo, para almacenar
el caracter A en una variable lo haremos del siguiente modo:
1
2
char c;
c = A;
Dado que las variables de tipo char realmente almacenan numeros, es posible realizar
operaciones aritmeticas con este tipo de variables, tal y como se muestra en el siguiente
programa:
1
#include <stdio.h>
2
3
4
int main() {
char c;
5
6
c = A;
7
8
9
10
11
12
printf(" %d\n",
printf(" %c\n",
c += 5;
printf(" %d\n",
printf(" %c\n",
c);
c);
c);
c);
//
//
//
//
//
//
//
Almacena en c el n
umero 65 (c
odigo
ascii de la letra A)
Imprime el n
umero 65
Imprime la letra A
Almacena en c el n
umero 70
Imprime el n
umero 70
Imprime una F (cuyo c
odigo ascii es 70)
25
c = 68;
c++;
printf(" %d\n", c);
printf(" %c\n", c);
c = 2;
13
14
15
16
17
18
19
20
//
//
//
//
//
//
//
//
Almacena en c el n
umero 68
c vale 69
Imprime el n
umero 69
Imprime la letra E
Almacena en c el n
umero 50 (c
odigo
ascii del car
acter 2)
Imprime el n
umero 50
Imprime el car
acter 2
21
return 0;
22
23
2.8.2
En ocasiones se utilizan expresiones en las que no todos los datos son del mismo tipo.
Imaginemos el siguiente codigo:
1
2
float f;
f = 5;
En este caso el resultado de la operacion 3.1416 * r * r se convierte a entero para poder almacenarlo en la variable area, de tipo int, con la consiguiente perdida de
precision.
Si ordenamos los tipos de datos de menor a mayor capacidad (char, int, float, double),
cualquier conversion de un tipo de menor capacidad a otro de mayor puede realizarse sin
perdida de precision, mientras que una conversion en el otro sentido (de mayor a menor
capacidad) puede conllevar perdida de informacion. El compilador realiza de forma automatica estos cambios de tipo, si bien en los casos que impliquen perdida de informacion
puede generar un warning para advertir al programador del posible error.
En ocasiones es el propio programador el que desea hacer esta conversion de forma
explcita, bien para dejar claro que desea hacer la conversion y evitar los posibles warnings dados por el compilador, bien para forzar un cambio de tipo en aquellos casos en los
26
Mediante esta conversion explcita el programador deja claro que es consciente del cambio de tipo que se va a producir, con la consecuente perdida de precision.
Tambien hay ocasiones en las que se desea forzar un cambio de tipo ya que el compilador
no lo hace de manera automatica. Tomemos el siguiente ejemplo:
1
2
3
En este caso el valor almacenado en media sera 2.0 y no 2.5. Esto es debido (tal y como
se explico en la Seccion 2.7.1) a que la operacion suma/n realiza la division 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 situacion ya que en
este caso la perdida de precision se produce en la propia operacion de division y no en la
posterior asignacion. Para solucionar este problema debera forzarse el cambio de tipo de
al menos uno de los operandos para que se realice la division real, tal y como se muestra
a continuacion:
1
2
3
En este caso la variable suma se cambia temporalmente a tipo float, con lo que se
realiza la division real. Debe quedar claro que este cambio de tipo afecta u nicamente
a la operacion en la que aparece el casting, en el resto del programa la variable suma
seguira siendo de tipo entero.
27
2.9
Punteros
La memoria del ordenador puede verse como una serie de celdas en las que se almacena la
informacion. Cada una de estas celdas tiene asociada una direccion de memoria. Cuando
declaramos una variable (por ejemplo int x), a e sta se le asigna una celda de memoria
con una direccion conocida. Hasta ahora no nos habamos preocupado por la direccion
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 que direccion se encuentran las variables de nuestros programas.
Una variable de tipo puntero es una variable que almacena direcciones de memoria. Normalmente se utilizan para almacenar la direccion de memoria en la que se encuentra
alguna otra variable. Cuando una variable de tipo puntero (por ejemplo p) contiene la
direccion de memoria de alguna otra variable (por ejemplo x) decimos que el puntero p
apunta a la variable x. Como veremos mas adelante, una variable de tipo puntero debe conocer el tipo de dato de la variable a la cual apunta (o, en general, el tipo de dato
almacenado en la direccion de memoria que contiene).
2.9.1
Declaracion
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.
2.9.2
El operador de direccion & (tambien llamado de referencia) obtiene la direccion de memoria de una variable. Por ejemplo, dada una variable x, la operacion &x obtiene la direccion de memoria donde se encuentra x, tal y como se muestra en el siguiente ejemplo:
1
#include <stdio.h>
2
3
4
5
28
int main() {
int x = 3;
printf("La variable x contiene el valor %f y se encuentra en
la direcci
on %d\n", x, &x);
2.9 Punteros
return 0;
6
7
#include <stdio.h>
2
3
4
5
8
9
int main() {
int x = 3;
int * p;
// Declaramos una variable de tipo puntero a
entero
p = &x;
// Guardamos en p la direcci
on de memoria de la
variable x
printf("La variable x contiene el valor %f y se encuentra en
la direcci
on %d\n", x, p);
return 0;
}
2.9.3
El operador de indireccion *
#include <stdio.h>
2
3
4
5
6
7
8
9
10
11
12
13
int main() {
int x = 3, y;
int * p;
//
entero
p = &x;
//
y = *p;
//
//
//
*p = 2;
p = &y;
//
*p = 5 * 3; //
return 0;
}
2.9.4
La constante NULL
Una variable de tipo puntero, ademas de direcciones de memoria, tambien puede almacenar la constante NULL. Este valor sirve para indicar que el puntero no contiene ninguna
direccion de memoria valida. Por ejemplo:
1
2
int * p;
p = NULL;
Para poder utilizar la constante NULL debe incluirse el chero stdlib.h mediante la
instruccion
1
30
#include <stdlib.h>
2.10
2.10.1
o
#include "fichero"
#include <stdio.h>
El chero stdio.h (standard input output header) contiene el codigo necesario para que
se compilen correctamente todas las funciones relacionadas con la entrada/salida (input/output) de nuestro programa, como por ejemplo printf y scanf. En la Seccion 4.3.5
se explica con mas detalle los cheros de cabecera.
La diferencia entre utilizar los corchetes angulados (<...>) y las dobles comillas ("...")
radica en que, en el primer caso, el chero incluido sera buscado en los directorios que
el compilador tenga congurados a tal efecto, mientras que en el segundo caso el chero
incluido sera buscado en el mismo directorio en el que se encuentra el chero fuente.
31
2.10.2
La directiva #define se emplea para denir constantes (en realidad tambien para denir
macros, aunque nosotros no la emplearemos con esta nalidad). La sintaxis empleada es:
#define nombre_constante valor
#define PI 3.1416
dene la constante PI con el valor especicado. Una vez denida una constante, e sta
puede utilizarse a lo largo de todo el programa:
1
2
#include <stdio.h>
#define PI 3.1416
3
4
5
int main() {
float radio, perim;
7
8
9
10
11
12
perim = 2 * PI * radio;
32
2.11
Ejercicios resueltos
SOLUCION:
1
#include <stdio.h>
2
3
4
int main() {
float base, alt, area, perim;
6
7
8
9
10
11
// Calcular resultados
area = base * alt;
perim = 2*base + 2*alt;
12
13
14
15
// Mostrar resultados
printf("Area = %f\n", area);
printf("Per
metro = %f\n", perim);
16
17
18
19
return 0;
20
21
#include <stdio.h>
2
3
4
5
int main() {
int a, b=5, res;
char c=A;
6
7
8
9
10
11
12
13
14
15
a = 2;
printf(" %d\n",
a *= 2;
b--;
printf(" %d\n",
c += a;
printf(" %c\n",
res = ( c==A
printf(" %d\n",
b/a);
a==b);
c);
|| ( a > 0 &&
res);
b < 5 ) );
16
33
return 0;
17
18
SOLUCION:
2
1
E
1
2.12
Ejercicios propuestos
#include <stdio.h>
2
3
4
5
int main() {
int a, b, c;
int * p1, * p2;
a = 2;
p1 = &b;
p2 = &c;
*p1 = a * 2;
*p2 = *p1 + 3;
printf("a= %d b= %d c= %d\n", a, b, c);
p2 = &a;
*p1 += *p2;
printf("a= %d b= %d c= %d\n", a, b, c);
c = *p1 + *p2;
printf("a= %d b= %d c= %d\n", a, b, c);
return 0;
7
8
9
10
11
12
13
14
15
16
17
18
19
34
Captulo 3
Estructuras de control
En los programas que hemos realizado hasta ahora, cada una de las instrucciones se ejecuta en modo secuencial, una tras otra y una u nica vez. Sin embargo, es habitual que
los programas necesiten ejecutar, en funcion de cierta condicion, un grupo de instrucciones u otro (ejecucion condicional), o que, por ejemplo, requieran ejecutar un bloque de
instrucciones mas de una vez (bucle). Estas situaciones se resuelven mediante lo que se
denomina sentencias de seleccion y sentencias de repeticion.
3.1
Sentencias de seleccion
3.1.1
35
instruccion_2_2;
. . .
instruccion_2_N;
}
#include <stdio.h>
2
3
4
int main() {
float nota;
6
7
8
9
10
11
12
13
14
15
Cuando alguno de los bloques if o else consta de una u nica instruccion, entonces las
llaves son opcionales.
if( expresion )
instruccion_1;
else
instruccion_2;
Por lo tanto la instruccion if-else del ejemplo anterior tambien podra haberse escrito
como:
1
2
3
4
Por otro lado, debe resaltarse que el bloque else es opcional. En el siguiente ejemplo se
utiliza una instruccion if sin el bloque else:
36
#include <stdio.h>
2
3
4
5
int main() {
float precio;
char aplicar_descuento;
7
8
9
10
11
if( aplicar_descuento == s )
precio = precio * 0.9;
// Aplico un descuento del 10 %
12
13
14
15
16
return 0;
17
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 numeros y muestra el mayor de ellos mediante el uso de
if-else anidados:
1
#include <stdio.h>
2
3
4
int main() {
float a, b, c, max;
5
6
7
printf("Introduce tres n
umeros: ");
scanf(" %f %f %f", &a, &b, &c);
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if ( a > b ) { // El m
aximo ser
a a o c
if( a > c )
max = a;
else
max = c;
}
else {
// El m
aximo ser
a b o c
if( b > c )
max = b;
else
max = c;
}
printf("El m
aximo es %f\n", max);
37
22
return 0;
23
24
. . .
2
3
4
5
6
7
8
9
10
11
Debe observarse que el codigo que acabamos de escribir coincide con el que se muestra a
continuacion, aunque el primero resulta mas legible:
if( expresion_1 ) {
instrucciones;
}
else {
if (expresion_2) {
instrucciones;
}
else {
38
if (expresion_3) {
instrucciones;
}
. . .
}
}
El siguiente ejemplo muestra la calicacion obtenida (suspenso, aprobado, notable o sobresaliente) en funcion de la nota numerica, mediante la concatenacion de varias instrucciones if-else:
1
#include <stdio.h>
2
3
4
int main() {
float nota;
6
7
8
9
10
11
12
13
14
15
16
17
return 0;
18
19
3.1.2
#include <stdio.h>
2
3
4
int main() {
int dia;
printf("Introduce el d
a de la semana (1-7): ");
scanf(" %d", &dia);
6
7
8
switch( dia ) {
case 1: printf("LUNES\n"); break;
case 2: printf("MARTES\n"); break;
case 3: printf("MIERCOLES\n"); break;
case 4: printf("JUEVES\n"); break;
case 5: printf("VIERNES\n"); break;
case 6: printf("SABADO\n"); break;
case 7: printf("DOMINGO\n"); break;
default: printf("D
a incorrecto\n");
}
return 0;
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
2
3
4
int main() {
int curso;
6
7
8
9
10
switch( curso ) {
case 1: printf("PROGRAMACI
ON\n");
case 2: printf("ALGOR
ITMICA\n");
case 3: printf("PROGRAMACI
ON AVANZADA\n");
case 4: printf("PROGRAMACI
ON DE REDES\n");
case 5: printf("INGENIERIA DEL SOFTWARE\n"); break;
default: printf("Curso incorrecto\n");
}
return 0;
11
12
13
14
15
16
17
18
19
20
Observese que, una vez se entra en un case, se ejecuta el resto de instrucciones hasta
alcanzar una instruccion break.
41
3.2
Sentencias de repeticion
3.2.1
La sentencia while
#include <stdio.h>
2
3
4
5
6
7
8
9
10
11
int main() {
int i = 0;
while( i < 10 ) {
printf("Hola mundo\n");
i++;
}
printf("Fin del programa\n");
return 0;
}
incrementa en uno. Cuando e sta variable valga 10, la condicion i < 10 sera falsa, con lo
que ya no se entrara de nuevo en el bucle.
Para evitar bucles innitos es imprescindible que alguna de las instrucciones del bucle
modique de algun modo la expresion 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 innito:
1
#include <stdio.h>
2
3
4
5
6
7
8
9
int main() {
int i = 0;
while( i < 10 ) {
// Esto siempre va a ser cierto
printf("Hola mundo\n");
}
return 0;
}
En el siguiente ejemplo se muestra un programa que solicita dos numeros y una operacion
(suma, resta, multiplicacion o division) y realiza la operacion especicada. A continuacion pregunta si se desea hacer otra operacion. En caso armativo se repite de nuevo todo
el proceso.
1
#include <stdio.h>
2
3
4
5
int main() {
float a, b, c;
int operacion, repetir;
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
43
22
23
24
25
Puede ocurrir que el contenido de un bucle while no se ejecute nunca. El siguiente programa solicita numeros enteros hasta que se introduzca un cero y al nal muestra cuantos
de ellos eran positivos.
1
#include <stdio.h>
2
3
4
int main() {
int num, positivos;
7
8
9
10
11
12
13
14
15
16
17
3.2.2
La sentencia do-while
44
printf("Introduce un n
umero positivo:");
do {
scanf(" %d", &num);
} while( num <= 0 );
printf("Introduce un n
umero positivo:");
scanf(" %d", &num);
while( num <= 0 ) {
scanf(" %d", &num);
}
#include <stdio.h>
2
3
4
int main() {
float a, b;
do {
printf("Introduce dos n
umeros (el segundo de ellos distinto
de cero) ");
scanf(" %f %f", &a, &b);
} while( b == 0 );
printf(" %f / %f = %f\n", a, b, a/b);
return 0;
6
7
8
9
10
11
12
45
Otro uso habitual del bucle do-while es para la implementacion de un menu de opciones.
A continuacion se muestra un ejemplo que presenta un menu, de modo que el programa
se ejecuta repetidas veces hasta que se escoge la opcion Salir:
1
#include <stdio.h>
2
3
4
5
int main() {
float a, b;
int opc;
do {
printf("1.- Sumar\n");
printf("2.- Restar\n");
printf("3.- Salir\n");
printf("Elige una opci
on: ");
scanf(" %d", &opc);
printf("Introduce dos n
umeros: ");
scanf(" %f %f", &a, &b);
if( opc == 1 ) printf(" %f + %f = %f\n", a, b, a+b);
else if( opc == 2 ) printf(" %f - %f = %f\n", a, b, a-b);
else if( opc == 3 ) printf("Adi
os\n");
else printf("Opci
on incorrecta\n");
} while( opc != 3 );
return 0;
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
En este caso el programa presenta un menu con dos opciones, mas una tercera para nalizar. El bucle do-while provoca que el programa se ejecute repetidas veces hasta que se
escoja la opcion 3 (salir). Cualquier otro valor distinto de 3 provoca que el bucle se repita
de nuevo. El grupo de instrucciones if-else se podra haber resuelto tambien mediante
una sentencia switch, tal y como se ha mostrado en ejemplos anteriores.
46
3.2.3
La sentencia for
Las llaves, como siempre, son opcionales si el bucle contiene una u nica instruccion. Por
otro lado, exp1, exp2 y exp3 pueden ser cualquier expresion valida en C. De forma mas
detallada diremos que:
exp1: se ejecuta una u nica vez, antes de que comiencen a ejecutarse las instruccio-
#include <stdio.h>
2
3
4
5
6
7
8
9
int main() {
int i;
for( i=1; i<=10; i++ ) {
printf("Hola Mundo\n");
}
return 0;
}
En este ejemplo, i=1 se ejecuta una u nica vez antes de que comience el bucle. La expresion i<=10 es la condicion de entrada al bucle, mientras sea cierta se ejecutaran las
instrucciones de dentro del bucle. Por u ltimo la expresion i++ se ejecuta al nalizar cada
una de las iteraciones, despues de la instruccion printf. En denitiva, la variable i se ha
utilizado a modo de contador para controlar el numero de iteraciones. Cuando el bucle se
haya repetido 10 veces, la variable i valdra 11 y por tanto nalizara.
47
i = 1;
while( i <= 10 ) {
printf("Hola Mundo\n");
i++;
}
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. Observese 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
4
5
6
7
8
9
10
11
int main() {
int n;
printf("Introduce un valor entero: ");
scanf(" %d", &n);
for( ; n>0; n-- ) {
printf("Hola Mundo\n");
}
return 0;
}
Tambien es posible que alguna de las expresiones contenga mas de una instruccion. En
este caso, dichas instrucciones deben separarse por coma, tal y como se muestra en el
siguiente ejemplo:
1
#include <stdio.h>
2
3
4
5
6
7
8
9
int main() {
int i, j;
for( i=0, j=10; i<j; i++, j-- ) {
printf(" %d + %d = %d\n", i, j, i+j);
}
return 0;
}
+
+
+
+
+
10 = 11
9 = 11
8 = 11
7 = 11
6 = 11
#include <stdio.h>
2
3
4
5
6
7
8
9
10
11
12
13
int main() {
int i, j;
i = 0;
j = 10;
while( i<j ) {
printf(" %d + %d = %d\n", i, j, i+j);
i++;
j--;
}
return 0;
}
49
3.2.4
Bucles anidados
Es posible que alguna de las instrucciones de un bucle sea, a su vez, otro bucle. Esto es lo
que se conoce como un bucle anidado. Por ejemplo, el siguiente programa:
1
#include <stdio.h>
2
3
4
5
6
7
8
9
10
11
12
13
int main() {
int i, j;
for( i=1; i<=3; i++ ) {
printf("Bucle externo, iteraci
on %d\n", i);
for( j=1; j<=2; j++ ) {
printf("\tBucle interno, iteraci
on %d- %d\n", i, j);
}
printf("-------------------------------------\n");
}
return 0;
}
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
3.3.1
Contadores
Utilizamos el termino contador para referirnos a una variable cuyo valor se va incrementando de uno en uno bajo ciertas condiciones. Este tipo de variables deben inicializarse
con algun valor (normalmente cero).
Por ejemplo, el siguiente programa solicita la introduccion de 10 numeros enteros y calcula cuantos de ellos son pares:
1
#include <stdio.h>
2
3
4
5
6
7
8
9
10
11
12
13
14
int main() {
int i, cont, num;
cont = 0;
// Inicializamos el contador
for( i=1; i<=10; i++ ) {
printf("Introduce un n
umero entero: ");
scanf(" %d", &num);
if( num %2 == 0 )
cont++;
// Incrementamos el contador
}
printf("Has introducido %d n
umeros pares\n", cont);
return 0;
}
En este ejemplo la variable cont funciona a modo de contador, de modo que se inicializa a cero y, cada vez que se detecta la entrada de un numero par, se incrementa en uno.
La variable i del bucle for tambien es un contador, en este caso empleado para controlar el numero de iteraciones que realiza el bucle. Este contador se inicializa a uno y se
incrementa tras cada iteracion del bucle.
51
3.3.2
Acumuladores
Otro uso habitual de las variables consiste en anadirles cierto valor al que ya tenan de
alguna operacion anterior, mediante operaciones del tipo
variable += valor;
i += 5;
incrementa en 5 el valor de i.
Al igual que los contadores, los acumuladores deben ser inicializados. De hecho un contador es un tipo particular de acumulador, al que se le van sumando unidades.
El siguiente ejemplo calcula la media aritmetica de 10 valores. Para ello se emplea un
acumulador para obtener, en primer lugar, la suma de todos ellos.
1
#include <stdio.h>
2
3
4
5
6
7
8
9
10
11
12
13
14
int main() {
int i;
float num, suma;
suma = 0;
// Inicializamos el acumulador
for( i=1; i<=10; i++ ) {
printf("Introduce un n
umero: ");
scanf(" %f", &num);
suma += num;
// Incrementamos el acumulador en
num unidades
}
printf("Media = %f\n", suma/10);
return 0;
}
En este ejemplo se puede observar que en la variable suma se van acumulando todos los
valores introducidos en la variable num.
Tambien es posible utilizar un acumulador en modo producto con una instruccion del tipo
variable *= valor;
52
i *= 5;
#include <stdio.h>
2
3
4
5
6
7
8
9
int main() {
int i, n, fact;
fact = 1;
// Inicializamos el acumulador
printf("Introduce un n
umero: ");
scanf(" %d", &n);
for( i=2; i<=n; i++ )
fact *= i;
// Multiplicamos i a lo que hay en el
acumulador
10
11
12
13
3.3.3
Banderas
Una bandera o ag es una variable que toma u nicamente dos valores (1/0, verdadero/falso)
y que se emplea para comprobar si se ha producido una determinada condicion durante
la ejecucion 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, ademas, 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 algun precio negativo, se mostrara al nal del programa un mensaje a modo de
advertencia. Esto puede resolverse con el uso de una bandera tal y como se muestra a
continuacion:
53
#include <stdio.h>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main() {
int i, flag;
float pvp, suma;
suma = 0;
// Inicializamos el acumulador
flag = 0;
// Inicializamos la bandera
for( i=1; i<=10; i++ ) {
printf("Introduce precio: ");
scanf(" %f", &pvp);
if( pvp < 0 )
// Si el n
umero es negativo...
flag = 1;
// nos lo anotamos en el flag
suma += pvp;
// En cualquier caso lo sumamos
}
printf("Suma = %f\n", suma);
if( flag == 1 )
// Comprobamos si el flag ha
cambiado
printf("ATENCI
ON, se han introducido precios negativos!\n")
;
return 0;
}
flag = 0
for( i=1; i<=10; i++ ) {
printf("Introduce precio: ");
scanf(" %f", &pvp);
if( pvp < 0 )
flag = 1;
else
flag = 0;
}
Observese que la sentencia else no es correcta. Este codigo provoca que la variable flag
3.4
Ejercicios resueltos
SOLUCION:
1
#include <stdio.h>
2
3
4
int main() {
float a, b;
printf("Introduce
scanf(" %f", &a);
printf("Introduce
scanf(" %f", &b);
if( b == 0 )
printf("Error:
else
printf(" %f\n",
return 0;
6
7
8
9
10
11
12
13
14
15
dividendo: ");
divisor: ");
divisi
on por cero\n");
a/b);
SOLUCION:
1
#include <stdio.h>
2
3
4
int main() {
int i, n;
printf("Introduce un n
umero: ");
scanf(" %d", &n);
for( i=1; i<=10; i++ )
printf(" %d x %d = %d\n", n, i, n*i);
6
7
8
9
10
return 0;
11
12
SOLUCION:
1
#include <stdio.h>
55
2
3
4
int main() {
int i, j;
6
7
8
9
10
11
12
13
4. Escribir un programa que calcule la media de n valores. El numero de datos a promediar (n) debera ser introducido por el usuario. Ademas, debera comprobarse que
el valor de n introducido sea mayor que cero, de lo contrario volvera a solicitarse
tantas veces como sea necesario.
SOLUCION:
1
#include <stdio.h>
2
3
4
5
int main() {
int i, n;
float num, suma;
7
8
9
10
11
12
13
14
// Pedimos n n
umeros y los sumamos
suma = 0;
for( i=1; i<=n; i++ ) {
printf("Introduce valor %d: ", i);
scanf(" %f", &num);
suma += num;
}
printf("Media = %f\n", suma/n);
return 0;
15
16
17
18
19
20
21
22
23
24
56
3.5
3.6
6. Repetir el ejercicio anterior, numerando las lneas en orden decreciente, esto es:
10. Hola mundo
9. Hola mundo
. . .
1. Hola mundo
57
7. Mostrar el valor del seno(x) para valores de x comprendidos entre 0 y 2, con incrementos de 0,1. El seno se puede calcular mediante la funcion sin(x), lo que
requiere incluir la librera math.h. La salida por pantalla debera tener el siguiente
aspecto:
X
------0.000000
0.100000
0.200000
. . .
2.000000
sen(x)
-------0.000000
0.099833
0.198669
. . .
0.909297
10
i.
i=1
12. Escribir un programa que calcule el producto de dos numeros enteros positivos sin
utilizar la operacion * (el producto debera calcularse a base de sumas).
13. Solicitar dos numeros enteros positivos a y b y calcular a/b y a %b, sin utilizar las
operaciones / y %.
Pista: Contar el numero de veces que se puede restar b de a.
14. Solicitar un numero N y calcular el factorial de N. Comprobar que el numero introducido 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 codigo, explicar que operacion realiza. Hacer una
traza suponiendo inicialmente a = 5.
1
2
3
4
5
58
valor = 0;
while( a > 0 ) {
valor += a--;
}
printf("El resultado es %d\n", valor);
Juegos
19. Escribir un programa que juegue a adivinar un numero del siguiente modo: El
usuario piensa un numero entre 1 y 100 y el ordenador debe averiguar dicho numero. Para ello, cada vez que el ordenador sugiere un numero, el usuario debe contestar
con el caracter g, p o c en funcion de que el numero sea demasiado grande, demasiado pequeno o correcto. Se debe implementar un algoritmo eciente que
adivine el numero en el menor numero de intentos posible.
20. Se desea implementar un juego con las siguientes reglas:
Se parte de una cantidad inicial de 17 chas. Dos jugadores, de forma alterna, van
retirando chas del monton, permitiendose en cada jugada retirar 1, 2 o 3 chas.
Pierde el jugador que retira la u ltima cha del monton.
Implementar un programa que simule el juego descrito, de modo que se juegue
contra el ordenador. Empezara retirando chas el jugador humano, de modo que el
ordenador seguira en todo momento la estrategia de retirar 4 x chas, siendo x
las chas que retiro el otro jugador en el turno anterior. Jugando de este modo, el
ordenador siempre ganara la partida. Cada vez que sea el turno del ordenador, e ste
informara de cuantas chas retira y cuantas quedan en el monton. A continuacion
se le pedira al usuario que introduzca el numero de chas que desea retirar, y se
mostrara por pantalla igualmente cuantas chas quedan en el monton. Este proceso
se repetira hasta que nalice la partida.
59
Captulo 4
Funciones
4.1
Introduccion
Los programas que hemos creado hasta ahora no son modulares, todo el codigo del programa aparece dentro de main. Esto puede ser adecuado para programas pequenos y
sencillos, pero cuando se abordan programas de mayor envergadura y complejidad es necesario modularizarlos, esto es, dividir el programa en bloques menores. En el lenguaje
C, esta division se hace mediante el uso de funciones.
Estudiaremos en primer lugar el uso de las funciones existentes en la librera de C, para
aprender mas adelante como crear nuestras propias funciones.
4.2
Funciones de la librera de C
#include <stdio.h>
#include <math.h>
61
Captulo 4. Funciones
4
5
int main() {
float x, y;
7
8
9
10
11
return 0;
12
13
En este ejemplo printf, scanf y pow son funciones de la librera de C. Lo primero que
debemos saber es que las funciones de C se agrupan en categoras. Por ejemplo printf
y scanf son funciones de entrada/salida, mientras que pow es una funcion matematica.
Para poder utilizar las funciones de C es necesario incluir cierta informacion en nuestro
programa. Esta informacion se encuentra almacenada en lo que se conoce como cheros
de cabecera o header les y se incluye mediante la instruccion:
#include <nombre_del_fichero_de_cabecera>
En la seccion 4.3.5 se explicara con mayor detalle los cheros de cabecera. De momento
nos basta con saber que es necesario incluirlos. En el ejemplo anterior stdio.h hace
referencia al chero de cabecera standar input/output (entrada/salida estandar). Hemos
incluido este chero ya que hacemos uso de las funciones printf y scanf, las cuales
se agrupan bajo esta categora. De forma similar, dado que nuestro programa utiliza la
funcion matematica pow, es necesario incluir el chero de cabecera math.h.
Si nos centramos en la sintaxis, se puede observar que para llamar o invocar a una funcion debemos poner el nombre de la funcion seguido de una lista de argumentos. Estos
argumentos representan la informacion que le pasamos a la funcion y deben ir encerrados
entre parentesis y separados por coma:
nombre_funcion(argumento1, argumento2, ... , argumentoN)
En el ejemplo anterior podemos observar que en las llamadas a las funciones scanf
y pow contienen dos argumentos. Por otro lado, la funcion printf, se puede invocar
con un numero variable de argumentos1 . En el ejemplo, el primer printf tiene un solo
argumento, mientras que el segundo tiene tres.
Tambien hay funciones que no tienen argumentos, como getchar(). Aun en este caso,
los parentesis deben estar presentes.
1 En realidad scanf tambi
en tiene un numero variable de argumentos. Por ejemplo scanf( %f %f, &a, &b)
tiene tres argumentos.
62
Cuando se invoca a una funcion, el programa realiza las operaciones necesarias para ejecutar la funcion en cuestion, teniendo en cuenta los argumentos especicados. Algunas
funciones (en realidad la mayora) ademas de requerir argumentos con cierta informacion,
tambien devuelven un valor, resultante de la operacion realizada. En el ejemplo anterior
la funcion pow requiere dos argumentos y devuelve un valor (en este caso el resultado de
elevar el segundo argumento al primero). Cuando una funcion devuelve un valor, e sta puede ser utilizada como parte de una expresion. Por ejemplo, lo siguiente seran expresiones
validas en C:
y = pow(x, 2);
if( pow(x, 2) > 1 ) { ...
printf("%.2f elevado a 2 = %f\n", x, pow(x, 2));
Por otro lado, los argumentos de una funcion pueden ser tambien expresiones mas complejas, tal y como se muestra en los siguientes ejemplos:
z = pow(3*x, 2*y);
z = pow(sqrt(x)*2, 1.0/y);
4.3
Hasta ahora los programas que hemos creado contienen todas las instrucciones en main.
Esto es adecuado para programas muy pequenos, pero para programas de mayor complejidad este modo de trabajar presenta grandes inconvenientes: implementacion complicada,
imposibilidad de reutilizar el codigo creado y codigo difcil de entender.
Imaginemos que queremos realizar un programa que calcule la operacion m
n , esto es, el
numero de combinaciones de m elementos tomados de n en n. Sabiendo que
m
m!
=
n
n!(m n)!
y teniendo en cuenta que el lenguaje C no dispone de ninguna funcion propia para el
calculo del factorial, podramos desarrollar un programa que calcule m
n del siguiente
modo:
1
2
3
#include <stdio.h>
int main() {
int m, n, i, fact_m, fact_n, fact_m_n, res;
4
5
6
// Pedir datos
do {
63
Captulo 4. Funciones
7
8
9
10
// Calcular m!
fact_m = 1;
for(i=1; i<=m; i++)
fact_m = fact_m * i;
11
12
13
14
15
// Calcular n!
fact_n = 1;
for(i=1; i<=n; i++)
fact_n = fact_n * i;
16
17
18
19
20
// Calcular (m-n)!
fact_m_n = 1;
for(i=1; i<=(m-n); i++)
fact_m_n = fact_m_n * i;
21
22
23
24
25
// Calcular n
umero de combinaciones y mostrar resultado
res = fact_m / (fact_n*fact_m_n);
printf("N
umero combinaciones = %d\n", res);
26
27
28
29
return 0;
30
31
#include <stdio.h>
int main() {
int m, n, res;
4
5
6
7
8
9
// Pedir datos
do {
printf("Introduce dos valores m, n, positivos, con m>=n ");
scanf(" %d %d", &m, &n);
} while (m<0 || n<0 || m<n);
10
11
12
13
14
64
// Calcular n
umero de combinaciones y mostrar resultado
res = fact(m) / (fact(n)*fact(m-n));
printf("N
umero combinaciones = %d\n", res);
return 0;
15
16
4.3.1
Cuestiones sintacticas
donde:
tipo_dato indica el tipo de dato del valor que devuelve la funcion (int, foat,
char, etc.). En caso de que la funcion no devuelva ningun valor, el tipo de dato
debera ser void.
nombre_funcion es el nombre que queremos darle a nuestra funcion, y por tanto
el que debera utilizarse cuando queramos invocarla.
lista_parametros es un conjunto de pares tipo_dato nombre_variable se-
Captulo 4. Funciones
1
2
3
Esta funcion, de nombre fact, recibe un u nico parametro 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 correspondera a una funcion que calcula la media aritmetica de tres
numeros enteros:
1
2
3
En este caso la funcion tiene tres parametros enteros que representan los numeros de los
que se desea obtener la media y devuelve un numero real (float) que representa la media
calculada.
En general, un programa en C estara compuesto por varias funciones, donde necesariamente una de ellas sera la funcion main, tal y como se muestra en el siguiente ejemplo:
1
#include <stdio.h>
2
3
4
5
6
7
8
9
10
11
12
13
66
int main() {
. . .
}
4.3.2
Cuando invocamos a una funcion, el control del ujo de ejecucion se transere a dicha funcion, esto es, se ejecutan las instrucciones contenidas en el cuerpo de la funcion.
Cuando la funcion que ha sido invocada naliza su ejecucion, se devuelve el control del
ujo de ejecucion al punto siguiente de la llamada.
La funcion invocada puede nalizar por dos motivos:
Porque se ha llegado a la u ltima instruccion de la funcion.
Porque se ha ejecutado una instruccion return. La instruccion return sirve tanto
para nalizar la funcion en curso como para devolver un valor, tal y como veremos
mas adelante.
Por otro lado, cuando invocamos a una funcion, puede existir (y normalmente existe)
una transferencia de informacion entre la funcion que realiza la llamada y la funcion
llamada. Esta transferencia de informacion se produce en dos instantes:
Al inicio de la llamada se copian los valores de los argumentos especicados en
la llamada (en caso de que existan) en los parametros denidos en la funcion. Por
ejemplo, dado el siguiente codigo:
1
2
3
4
5
6
7
8
9
10
Captulo 4. Funciones
Al nalizar la llamada, en caso de que exista una sentencia return, el valor especicado a continuacion de return es devuelto a la funcion que realizo la llamada.
Por ejemplo, dado el siguiente codigo:
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
2
3
4
5
6
7
8
9
10
11
12
13
14
68
m
n
#include <stdio.h>
2
3
4
5
6
7
8
9
10
11
int main() {
int m, n, res;
12
// Pedir datos
do {
printf("Introduce dos valores m, n, positivos, con m>=n ");
scanf(" %d %d", &m, &n);
} while (m<0 || n<0 || m<n);
13
14
15
16
17
18
19
20
21
22
return 0;
23
24
4.3.3
Parametros y argumentos
Captulo 4. Funciones
En ocasiones, en la literatura se utiliza tambien el termino parametro formal para referirse a los parametros y parametro actual para referirse a los argumentos. Tambien es
frecuente que se utilice el termino parametro (sin mas calicativos) para referirse, de
forma indistinta, tanto a los parametros como a los argumentos.
4.3.4
Normalmente conviene que las operaciones de entrada/salida (printf/scanf) se encuentren en la funcion principal. Por ejemplo, no sera adecuado resolver el programa
anterior del siguiente modo:
1
#include <stdio.h>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main() {
float num1, num2, num3;
printf("Introduce tres valores: ");
scanf(" %d %d %d", &num1, &num2, &num3);
media(num1, num2, num3);
return 0;
}
int main() {
int num1, num2, num3;
4
5
6
7
8
9
10
11
70
Ahora la funcion media no devuelve ningun 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 funcion media que devolviese un
valor numerico, tal y como hicimos en el primer ejemplo.
De forma similar, tampoco resulta interesante hacer la lectura de datos dentro de la propia
funcion media:
1
2
3
4
5
6
7
float media() {
int a, b, c, res;
printf("Introduce tres valores: ");
scanf(" %d %d %d", &a, &b, &c);
res = (a+b+c)/3.0;
return res;
}
int main() {
int num1, num2, num3, num4;
float m;
5
6
7
8
9
En denitiva, salvo en aquellos casos en los que el acometido de la funcion sea explcitamente solicitar o mostrar datos, sera conveniente que las operaciones de entrada/salida se
realicen fuera de la funcion que realiza los calculos. De este modo las funciones implementadas podran utilizarse en diferentes contextos, esto es, estaremos creando funciones
mas generales.
71
Captulo 4. Funciones
4.3.5
Por ejemplo, el prototipo de la funcion fact del ejemplo dado en la seccion anterior sera:
1
En consecuencia,
haciendo uso de la declaracion de funciones, en el ejemplo del calculo
ser
a
posible
implementar las funciones en el siguiente orden:
de m
n
1
#include <stdio.h>
2
3
// Declaraci
on de la
4
5
6
7
8
9
int main() {
. . .
res = fact(m) / (fact(n)*fact(m-n)); // Llamadas a la funci
on
fact
. . .
}
10
11
12
13
// Implementaci
on de la
int fact(int);
Cuando utilizamos funciones de la librera de C es necesario declararlas antes de su utilizacion, ya que en este caso la implementacion de las mismas no se encuentra en nuestro
chero fuente (en realidad ni siquiera se dispone de la implementacion sino del codigo
ya compilado o codigo objeto). Por ejemplo, cualquier programa que utilice la funcion
printf debera declararla previamente. Para facilitar esta labor existen unos cheros especiales que contienen las declaraciones (cabeceras) de las funciones. Estos cheros se
denominan cheros de cabecera o header les. Por poner solo algunos ejemplos, el chero stdio.h contiene las cabeceras o declaraciones de todas las funciones relacionadas
con la entrada/salida estandar (printf, scanf, etc.) y el chero math.h contiene las
cabeceras de las funciones matematicas(pow, sqrt, sin, cos, ...). En consecuencia, para
declarar estas funciones basta con incluir el chero de cabecera correspondiente mediante
una instruccion #include.
1
2
3
#include <stdio.h>
#include <math.h>
. . .
La extension .h viene precisamente de header le, si bien estos cheros ademas de cabeceras de funciones tambien pueden contener otra informacion como declaracion de
constantes (mediante instrucciones #define) o declaracion de tipos estructurados, algo
que se vera en el Captulo 6.
4.4
Se dene el a mbito de una variable como la porcion de programa donde esa variable es
visible y, por tanto, puede accederse a su contenido. En este sentido las variables pueden
clasicarse en variables locales (cuando el a mbito se restringe a una funcion) y variables
globales (cuando el a mbito se extiende a todo el programa).
Variables locales: Una variable es local cuando se declara dentro de una funcion, bien en
el cuerpo de la misma, bien como parametro. Por ejemplo, dado el siguiente programa:
73
Captulo 4. Funciones
1
2
3
4
5
6
7
8
9
10
11
12
las variables n, i y f son locales a la funcion fact y por tanto no es posible usarlas fuera
de esta funcion. De modo similar las variables num y res son locales a la funcion 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 codigo sera perfectamente valido:
1
2
3
4
5
6
7
8
9
10
11
12
En este caso tanto la funcion fact como main contienen una variable de nombre i. Esto
no supone ningun problema, de hecho es algo bastante habitual, pero debe quedar claro
que la variable i de la funcion fact es distinta de la variable i de main. Cada una ocupa
su propio espacio de memoria y no hay confusion posible entre ellas, ya que dependiendo
de la funcion donde se esten utilizando quedara claro a que variable nos referimos.
En el ejemplo mostrado la variable i de main representa el valor del cual queremos calcular 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 iteracion del
bucle no afecta para nada a la variable i de main, la cual mantiene su valor.
74
Variables globales: Una variable es global cuando se declara fuera de cualquier funcion
(normalmente a continuacion de las instrucciones #include y #define). El a mbito de
una variable global es la totalidad del programa y, por tanto, puede ser utilizada en cualquier parte del mismo.
El uso de variables globales evita tener que pasar informacion a las funciones mediante
el uso de parametros, o devolverla mediante el uso de return, sin embargo, tal y como
veremos mas adelante, su uso no se recomienda.
En el siguiente programa las variable num y f se declaran a nivel global:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
int num, f;
// Variables globales
void fact() {
int i;
// Variable local
f = 1;
for( i=2; i<=num; i++ )
f = f * i;
}
int main() {
printf("Introduce un n
umero entero positivo: ";
scanf(" %d", &num);
fact();
printf("FACTORIAL( %d)= %d\n", num, f);
}
En el ejemplo mostrado las variables num y f declaradas en la lnea 2 son globales y, por
tanto, conocidas tanto dentro de main como de fact. Observese que ahora la funcion
fact no necesita recibir ni devolver ningun valor. La funcion main puede dar valor a la
variable num (lnea 11) y, posteriormente, la funcion fact consultar el valor de esa misma
variable (lnea 6).
Esta forma de trabajar esta totalmente desaconsejada, ya que rompe en gran medida con
el principio de modularidad. Es interesante que las funciones tengan una alta cohesion
(la funcion realiza una u nica tarea bien denida) y un bajo acoplamiento (la funcion
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:
Codigo mas difcil de comprender.
Errores imprevistos debidos a efectos colaterales: esto sucede cuando se altera de
forma no deseada el contenido de una variable en algun punto del programa. Si
dicha variable es global, puede resultar muy complicado localizar el error, mientras
75
Captulo 4. Funciones
4.5
4.5.1
void f(int v) {
. . .
}
4
5
6
7
8
9
10
int main() {
int num;
. . .
f(num);
. . .
}
#include <stdio.h>
2
3
4
5
6
7
8
76
9
10
11
12
13
14
15
int main() {
int a = 3;
printf("Valor de a al inicio de main: %d\n", a);
modificar(a);
printf("Valor de a al final de main: %d\n", a);
return 0;
}
de
de
de
de
a
a
a
a
al inicio de main: 3
antes de modificar: 3
despu
es de modificar: 5
al final de main: 3
4.5.2
4
5
6
7
8
9
10
11
int main() {
int a = 3;
printf("Valor de a al inicio de main: %d\n", a);
modificar(&a);
printf("Valor de a al final de main: %d\n", a);
return 0;
}
77
Captulo 4. Funciones
En este ejemplo, la llamada modificar(&a) pasa la variable a por referencia, esto es,
pasamos la direccion de a en lugar de su valor. Esta llamada produce que en el parametro p
de la funcion modificar se copie la direccion de a. A partir de este momento, la funcion
modificar podra acceder a la variable a ya que conoce su direccion de memoria. En
concreto, la operacion *p = *p + 2 incrementa en 2 el valor de a.
Imaginemos una situacion en la que una funcion necesita devolver dos valores. Ya se ha
visto que mediante la sentencia return u nicamente es posible devolver un valor2 , por lo
que deberamos hacer uso del pase de parametros por referencia para transferir los valores
deseados. En el siguiente ejemplo se muestra el uso de una funcion que, dado el radio de
una circunferencia, devuelve (mediante argumentos pasados por referencia), tanto el a rea
como el permetro.
1
2
3
#include <stdio.h>
#define PI 3.141592
void area_perim(float radio, float * p_area, float * p_perim);
4
5
6
int main() {
float r, area, perim;
8
9
10
11
12
13
14
15
16
17
18
2 En realidad es posible devolver un grupo de variables mediante el uso de estructuras, tal y como se ver
a en
el Captulo 6, pero si nos restringimos a los tipos simples u nicamente podremos devolver un valor.
78
4.6
4.6.1
Ejercicios resueltos
valor
Funciones que no devuelven ningun
Escribir un programa que dibuje por pantalla la siguiente gura:
*
**
***
*****
******
*******
********
*********
**********
Para ello implementar una funcion que reciba un numero entero n y muestre por
pantalla una lnea con n asteriscos.
SOLUCION:
1
#include <stdio.h>
2
3
void dibuja_asteriscos(int);
4
5
6
7
8
9
10
11
int main() {
int i;
for(i=1; i<=10; i++) {
dibuja_asteriscos(i);
}
return 0;
}
12
13
14
15
16
17
18
19
Captulo 4. Funciones
x = dibuja_asteriscos(i);
4.6.2
SOLUCION:
1
2
#include <stdio.h>
#include <math.h>
3
4
5
6
7
8
9
10
11
int main() {
float r1, r2, area;
do {
printf("Introduce los radios del anillo: ");
scanf(" %f %f", &r1, &r2);
} while( r1<=0 || r2<=0 || r2<=r1 );
12
13
14
15
16
80
17
18
19
20
21
22
4.6.3
SOLUCION:
#include <stdio.h>
2
3
int primo(int);
4
5
6
int main() {
int i;
7
8
9
10
81
Captulo 4. Funciones
}
return 0;
11
12
13
14
15
16
17
es_primo = 1;
for(i=2; i<n; i++) {
if( n %i == 0 )
es_primo = 0;
}
return es_primo;
18
19
20
21
22
23
24
En la lnea 3 se declara la funcion. Las funciones que devuelven un valor del tipo
VERDADERO/FALSO se declaran de tipo int, de modo que si la funcion da como
resultado VERDADERO se devuelve 1 y si da FALSO se devuelve 0. Esta funcion
ademas de devolver un entero, recibe como parametro otro entero (el numero que
deseamos evaluar si es o no primo).
En la lnea 9 se realiza la llamada a la funcion. El valor de i se copia en la variable n. Cuando naliza la funcion, esta llamada se sustituye por el valor devuelto
en la instruccion de la lnea 23. Si se devuelve 1, la instruccion de la lnea 9 realizara la comparacion if(1==1), mientras que si devuelve 0 realizara la comparacion if(0==1).
Entre las lneas 15 y 24 se implementa la funcion. La variable es_primo se usa
a modo de ag o bandera. Se inicializa con 1 y, si durante la ejecucion del bucle
for se encuentra algun valor i que sea un divisor exacto de n (n%i == 0) entonces se cambia el ag a 0 ya que, con toda seguridad, el numero n no sera primo.
Finalmente en la lnea 23 se devuelve el valor del ag.
Una posible mejora a este algoritmo consistira en iterar u nicamente hasta n/2en el
bucle for de la lnea 19, ya que no vamos a encontrar divisores mas grandes que
este valor. Por otro lado, tambien podra forzarse la salida del bucle en cuanto se
encuentre un divisor, en cuyo caso queda claro que el numero no es primo y por
tanto no sirve de nada seguir iterando en el bucle.
4.6.4
82
SOLUCION:
1
#include <stdio.h>
2
3
4
5
6
int main() {
int seg_total, hor, min, seg;
8
9
10
11
12
return 0;
13
14
15
16
17
18
19
20
void
*h
*m
*s
}
Una funcion no puede devolver mas de un valor a traves de una sentencia return
(a no ser que lo haga mediante una variable de tipo struct, las cuales todava no
se han estudiado). Un modo habitual de devolver mas de un valor es mediante el
pase de parametros por referencia.
En la lnea 10 se encuentra la llamada a la funcion convertir. Esta funcion tiene
cuatro parametros: los segundos totales (pasado por valor) y las direcciones de memoria de las variables hor, min y seg (pase por referencia) declaradas en main.
Es necesario pasar estas tres u ltimas variables por referencia, ya que su valor debe
ser modicado dentro de la funcion. En el momento de la llamada, en el parametro
st (lnea 16) se copia el valor de seg_total, y en los parametros h, m y s se
copian las direcciones de memoria de las variables hor, min y seg respectivamente. Esto es, en el momento de la llamada se produce la siguiente transferencia
de informacion: st=seg_total, h=&hor, m=&min y s=&seg.
Entre las lneas 16 y 20 se encuentra la implementacion de la funcion. Esta funcion
asigna valores a las variables hor y min y seg declaradas en main a traves de los
punteros h, m y s.
En la lnea 3 se encuentra la declaracion de la funcion. Puede observarse que los
tres u ltimos parametros son de tipo puntero, ya que corresponden, no al valor de
una variable, sino a su direccion de memoria. La funcion es de tipo void ya que no
devuelve nada a traves de la sentencia return.
83
Captulo 4. Funciones
4.7
Ejercicios propuestos
1. Implementar una funcion que, dado un numero entero positivo, muestre por pantalla
todos sus divisores. Escribir un programa que solicite un numero y, mediante una
llamada a la funcion anterior, muestre sus divisores.
2. Implementar una funcion que, dado un valor de temperatura expresado en grados
centgrados, devuelva el equivalente expresado en grados farenheit (F=C*9/5+32).
Escribir un programa que, mediante llamadas a la funcion anterior, muestre la
equivalencia entre grados centgrados y farenheit para todos los valores de grados
centgrados comprendidos entre 0 y 30 con incrementos de dos en dos.
3. Implementar una funcion que calcule el modulo de un numero complejo (dado
mediante su parte real e imaginaria). Escribir un programa que solicite un numero
complejo y muestre su modulo.
4. Implementar una funcion que reciba tres valores (los dos primeros reales y el tercero
entero) y devuelva:
La suma de los dos primeros si el tercero es igual a 1.
La resta de los dos primeros si el tercero es igual a 2.
El producto de los dos primeros si el tercero es igual a 3.
La division de los dos primeros si el tercero es igual a 4.
Cero en cualquier otro caso
Escribir un programa que pida dos numeros y una operacion (1-4) y muestre el
resultado en funcion de la operacion solicitada.
5. Implementar una funcion que, dados tres numeros reales, devuelva el mayor de
ellos. Escribir un programa que haga uso de esta funcion para mostrar el mayor de
tres valores introducidos por teclado.
6. Implementar una funcion que, dados tres numeros reales, devuelva si estan ordenados crecientemente. Escribir un programa que solicite tres numeros y muestre si
estan o no ordenados.
7. Implementar una funcion que devuelva si dos numeros dados son amigos. Dos
numeros 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 division por si mismo. Por ejemplo 220 y
284 son numeros amigos, ya que:
Los divisores de 220 (sin contar e 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
Otros pares de numeros amigos son: (1184, 1210), (6232, 6368), (17296, 18416) y
(9363584, 9437056).
Escribir un programa que solicite dos numeros enteros positivos y muestre si son o
no amigos.
8. Implementar una funcion que, dados dos numeros reales que representen el a ngulo (a) y modulo (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 funcion que, dados dos numeros 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 funcion anterior, los muestre ordenados.
85
Captulo 5
Vectores
5.1
Introduccion
Es posible distinguir entre tipos de datos basicos y tipos compuestos. Las variables de
tipos basicos (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. Gracamente, podemos representar un vector tal y como se
muestra en la Figura 5.1.
Denominamos:
Elemento: cada una de las variables individuales que componen el vector.
Talla o rango: numero de elementos que contiene el vector.
13
21
ndices
Elementos
del vector
E
Nombre del vector
87
Captulo 5. Vectores
Indice:
la posicion que ocupa cada elemento dentro del vector. Al primer elemento
se le asocia el ndice 0 y al u ltimo el ndice talla-1.
La Figura 5.1 representa un vector de 8 elementos. El primer elemento, en la posicion 0,
almacena el valor 1, mientras que el u ltimo elemento, en la posicion 7, almacena el valor
21.
5.2
Declaracion de vectores
donde tipo_dato especica el tipo de dato de cada uno de los elementos que forman el
vector y talla el numero de elementos. Por ejemplo, la sentencia
1
int v[10];
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 posicion 0, el 28 en la posicion 1, etc.
Cuando se inicializa el vector en el momento de la declaracion, es posible omitir el tamano. En este caso se crea un vector del tamano necesario para almacenar todos los
valores especicados. Por ejemplo, la siguiente sentencia crea un vector de tamano 4 e
inicializa cada uno de sus elementos con los valores especicados entre llaves.
1
88
5.3
donde indice hace referencia al elemento al que deseamos acceder. Debe recordarse que
el primer elemento tiene ndice 0 y el u ltimo ndice talla-1.
Por ejemplo, dado el vector
1
int v[10];
v[0] = 5;
v[9] = 20;
la expresion nombre_vector[indice] se comporta, a todos los efectos, como una variable de tipo tipo_dato, por lo que puede ser empleada bajo las mismas condiciones
que se empleara cualquier otra variable de ese tipo. Por ejemplo, dado el siguiente programa:
1
#include <stdio.h>
2
3
4
5
6
7
8
9
10
int main() {
int a, b, c;
printf("Introduce dos n
umeros enteros: ");
scanf(" %d %d", &a, &b);
c = a + b;
printf("La suma de %d y %d es %d\n", a, b, c);
return 0;
}
89
Captulo 5. Vectores
#include <stdio.h>
2
3
4
5
6
7
8
9
10
int main() {
int v[3];
printf("Introduce dos n
umeros enteros: ");
scanf(" %d %d", &v[0], &v[1]);
v[2] = v[0] + v[1];
printf("La suma de %d y %d es %d\n", v[0], v[1], v[2]);
return 0;
}
Observese que v[0], v[1] y v[2] se usan exactamente igual a como se usara cualquier
otra variable de tipo int (en este caso, igual a como se usan las variables a, b y c del
primer ejemplo).
5.4
Cualquier operacion que se desee realizar con un vector, debera hacerse elemento a elemento. El lenguaje C no permite operar con el vector en su totalidad. Por ejemplo, si
queremos sumar dos vectores v1, v2 y almacenar el resultado en v3, debemos sumar
uno a uno cada uno de los elementos de los vectores. La sentencia v3 = v1 + v2 es
incorrecta (mas adelante veremos como hacer e sta operacion).
Imaginemos que disponemos de un vector de 10 elementos enteros y deseamos dar valores
a cada uno de ellos. El siguiente programa permite hacer esta operacion, aunque de un
modo poco recomendable.
1
#include <stdio.h>
2
3
4
int main() {
int v[10];
5
6
7
8
9
10
11
12
13
. . .
14
15
16
90
17
. . .
return 0;
18
19
20
Este modo de trabajar, ademas de poco operativo, es impracticable cuando el tamano 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 e sta vaya variando en cada iteracion del bucle, tal y como
se muestra en el siguiente ejemplo:
1
#include <stdio.h>
2
3
4
int main() {
int i, v[10];
6
7
8
9
10
11
12
Observese que en el bucle del programa anterior, la variable i comienza con valor 0 y
naliza con valor 9, por lo que en cada iteracion 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 tamano 10, sus elementos van desde v[0] hasta v[9].
Habitualmente el tamano de un vector se dene con una constante simbolica mediante la
directriz #define. Ello permite modicar facilmente el tamano del vector sin necesidad
de tener que cambiar la condicion en todos los bucles de acceso al vector. A continuacion
se muestra un programa de ejemplo que permite sumar dos vectores, dejando el resultado
en un tercero:
1
2
#include <stdio.h>
#define N 10
3
4
5
int main() {
int i, v1[N], v2[N], v3[N];
// Vectores de tama
no 10
6
7
8
9
91
Captulo 5. Vectores
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
return 0;
30
31
Observese que los bucles for de las lneas 9, 16, 22 y 27 iteran con el ndice i desde 0 a
N-1, siendo N el tamano de los vectores. De este modo, si se quisiera trabajar con vectores
de otro tamano, bastara con cambiar el valor de N denido en la lnea 2. Observese tambien que para sumar dos vectores, debemos hacerlo elemento a elemento (lneas 22-23).
5.5
Al igual que ocurre con otro tipo de variables, los vectores tambien pueden ser pasados
a funciones, aunque, tal y como se explicara mas adelante, el mecanismo de pase de
parametros en este caso es sensiblemente distinto.
A continuacion se muestra la sintaxis a utilizar cuando se pasan vectores como parametro
a una funcion. Para ello distinguimos entre la llamada a la funcion, la implementacion y
la declaracion.
92
Llamada a la funcion:
nombre_funcion(nombre_vector)
Por ejemplo
1
inicializar(v);
Implementacion de la funcion:
tipo nombre_funcion( tipo nombre_vector[talla] ) {
// Cuerpo de la funci
on
}
donde talla se puede omitir, en cuyo caso la funcion admite vectores de cualquier tamano.
Por ejemplo
1
2
3
4
5
Declaracion de la funcion:
tipo nombre_funcion( tipo nombre_vector[talla] );
Visto en conjunto dentro de un programa, las sentencias anteriores quedaran como sigue:
93
Captulo 5. Vectores
1
2
#include <stdio.h>
#define N 10
3
4
5
// Declaraci
on de la funci
on
void inicializar( int [] );
6
7
8
9
10
11
12
13
// Programa principal
int main() {
int vect[N];
inicializar(vec); // Llamada a la funci
on
. . .
return 0;
}
14
15
16
17
18
19
20
// Implementaci
on de la funci
on
void inicializar( int v[] ) {
int i;
for( i=0; i<N; i++ ) // Suponiendo un vector de tama
no N
v[i] = 0;
}
#include <stdio.h>
#define N 10
3
4
5
6
// Declaraci
on de funciones
void pedir_valores( int [] );
void mostrar_valores( int [] );
7
8
9
10
// Programa principal
int main() {
int vec[N];
11
pedir_valores(vec);
// Llamada a funci
on
mostrar_valores(vec);
12
13
14
return 0;
15
16
17
18
19
20
21
94
// Implementaci
on de las funciones
void pedir_valores( int v[] ) {
int i;
for( i=0; i<N; i++) {
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#define N 10
3
4
5
6
7
// Declaraci
on de funciones
void pedir_valores( int [] );
void mostrar_valores( int [] );
void sumar( int [], int [], int [] );
8
9
10
11
// Programa principal
int main() {
int v1[N], v2[N], v3[N];
12
13
pedir_valores(v1);
95
Captulo 5. Vectores
pedir_valores(v2);
sumar(v1, v2, v3);
printf("Vector 1:\n");
mostrar_valores(v1);
printf("Vector 2:\n");
mostrar_valores(v2);
printf("Suma:\n");
mostrar_valores(v3);
14
15
16
17
18
19
20
21
22
return 0;
23
24
25
26
27
28
29
30
31
32
33
// Implementaci
on de las funciones
void pedir_valores( int v[] ) {
int i;
for( i=0; i<N; i++) {
printf("Introduce el valor del elemento %d: ", i);
scanf(" %d", &v[i]);
}
}
34
35
36
37
38
39
40
41
42
43
44
45
46
mente para vectores de tamano N). Si se desea operar con vectores de distintos tamanos,
es necesario pasar como parametro tanto el vector como su tamano, tal y como se muestra
en el siguiente ejemplo, que calcula la media de distintos vectores:
1
2
3
#include <stdio.h>
#define N 10
#define M 15
4
5
6
7
// Declaraci
on de funciones
void pedir_valores( float [], int );
float media( float [], int );
8
9
10
11
// Programa principal
int main() {
float v1[N], v2[M], m1, m2;
12
pedir_valores(v1, N);
pedir_valores(v2, M);
m1 = media(v1, N);
m2 = media(v2, M);
printf("Media de v1: %f\n", m1);
printf("Media de v2: %f\n", m2);
13
14
15
16
17
18
19
return 0;
20
21
22
23
24
25
26
27
28
29
30
// Implementaci
on de las funciones
void pedir_valores( float v[], int dim ) {
int i;
for( i=0; i<dim; i++) {
printf("Introduce el valor del elemento %d: ", i);
scanf(" %d", &v[i]);
}
}
31
32
33
34
35
36
37
38
39
40
41
97
Captulo 5. Vectores
Observese que, en este caso, los bucles for implementados en las funciones (lneas 26
y 35) tienen como lmite la variable dim recibida como parametro, la cual podra tener
valores distintos dependiendo de la llamada (lneas 13-16).
Ademas de los ejemplos mostrados a lo largo de esta seccion, existen operaciones tpicas
con vectores que, debido a su uso habitual en programas de ndole muy diversa, conviene
conocer en detalle, como por ejemplo la obtencion del maximo, del mnimo, busqueda de
un elemento, etc. En el apartado de ejercicios resueltos (Seccion 5.12) se puede encontrar
la solucion de e stos y otros algoritmos.
5.6
5.7
Vectores multidimensionales
El lenguaje C tambien permite trabajar con vectores de mas de una dimension (matrices).
En este captulo abordaremos u nicamente las matrices de dos dimensiones. Una matriz
bidimensional es un conjunto de variables de un mismo tipo organizado en las y columnas.
Para declarar una matiz bidimensional se emplea la siguiente sintaxis:
tipo_dato nombre_matriz[talla_filas][talla_columnas];
donde tipo_dato especica el tipo de dato de cada uno de los elementos de la matriz
y talla_fila y talla_columnas indican el numero de las y columnas de la matriz.
Por ejemplo, la sentencia
1
int m[3][4];
98
0,0
0,1
0,2
0,3
1,0
1,1
1,2
1,3
2,0
2,1
2,2
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 (la, columna) de cada uno de los elementos de la
matriz anterior.
Para acceder a los elementos de una matriz se emplea la siguiente sintaxis:
nombre_matriz[indice_fila][indice_columna];
int m[3][4];
m[0][0] = 5;
m[2][3] = 20;
5.8
Al igual que ocurre con los vectores, las operaciones con matrices deben realizarse elemento a elemento. Para ello es conveniente (y con matrices mnimamente grandes, absolutamente necesario) automatizar el acceso a los elementos de la matriz mediante el uso
de bucles.
Para recorrer una matriz bidimensional emplearemos dos variables enteras que vayan tomando los valores de sus ndices (la y columna). Esto se consigue mediante el uso de
dos bucles anidados (ver la Seccion 3.2.4 para entender el funcionamiento de los bucles
anidados).
99
Captulo 5. Vectores
#include <stdio.h>
#define FIL 3
#define COL 4
4
5
6
int main() {
int i, j, m[FIL][COL];
8
9
10
11
12
13
14
15
16
Es importante entender como van variando los ndices i, j tras cada iteracion de los
bucles. En la primera iteracion del bucle mas externo (lnea 8) se inicializa la variable i a
0 y, seguidamente, se entra en el bucle. A continuacion se ejecutan todas las iteraciones del
bucle interno (lneas 9-12) en las que j vara de 0 a COL-1, esto es, j toma sucesivamente
los valores 0, 1, 2 y 3 con i valiendo 0. Tras la nalizacion de todas las iteraciones
del bucle interno, se vuelve a la siguiente iteracion del bucle externo, con la variable i
incrementada en 1. A continuacion 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 tamano FIL x COL utilizaramos el
siguiente codigo:
1
2
3
4
5
6
Observese que la sentencia printf de la lnea 5 provoca que, tras mostrarse todas las
columnas de una la dada (esto es, tras haberse completado el bucle interno), se inserte
un salto de lnea, con lo que la siguiente la se mostrara en una lnea distinta.
100
5.9
Implementacion de la funcion:
tipo nombre_funcion( tipo nombre_matriz[talla_filas][talla_columnas] ) {
// Cuerpo de la funci
on
}
donde talla_filas se puede omitir, en cuyo caso la funcion admite matrices con cualquier numero de las, pero talla_columnas debe especicarse obligatoriamente.
Declaracion de la funcion:
tipo nombre_funcion( tipo nombre_matriz[talla_filas][talla_columnas]);
#include <stdio.h>
#define N 3
#define M 5
4
5
6
7
8
// Declaraci
on de funciones
void pedir_valores( int [][M] );
void mostrar_valores( int [][M] );
void sumar( int [][M], int [][M], int [][M] );
9
10
11
// Programa principal
int main() {
101
Captulo 5. Vectores
12
13
pedir_valores(m1);
pedir_valores(m2);
sumar(m1, m2, m3);
printf("Matriz 1:\n");
mostrar_valores(m1);
printf("Matriz 2:\n");
mostrar_valores(m2);
printf("Suma:\n");
mostrar_valores(m3);
14
15
16
17
18
19
20
21
22
23
return 0;
24
25
26
27
28
29
30
31
32
33
34
35
// Implementaci
on de las funciones
void pedir_valores( int m[][M] ) {
int i, j;
for( i=0; i<N; i++) {
for( j=0; j<M; j++) {
printf("Introduce valor del elemento ( %d, %d): ", i, j);
scanf(" %d", &m[i][j]);
}
}
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
102
5.10
Ya se explico en la Seccion 2.9 que todas las variables tienen asociada una direccion de
memoria, que indica la posicion que ocupa dicha variable en la memoria del ordenador.
En el caso de los vectores habra que precisar que, dado que e stos son, en realidad, un
conjunto de variables (a los que denominamos elementos del vector), cada una de estos
elementos tiene asociada una direccion.
Hay que entender que los elementos de un vector se almacenan de forma consecutiva
en memoria, por lo tanto, basta con conocer la direccion del primer elemento para saber
tambien la del resto. Esto es, dado el siguiente fragmento de codigo
1
2
3
int v[10];
int * p;
p = &v[0]
en el que se observa que el puntero p almacena la direccion de memoria del primer elemento del vector, entonces sabremos que el segundo elemento esta en la direccion p+1,
el tercero en p+2 y as sucesivamente. Vemos por tanto que, a partir del puntero p, es posible acceder a todos los elementos del vector. Para ello debemos recordar que mediante
el operador de indireccion * se puede acceder a la variable apuntada por un puntero. En
consecuencia, el fragmento de codigo
1
2
3
4
int v[10];
v[0] = 0;
v[1] = 1;
v[2] = 2;
es equivalente a1
1
2
3
4
5
6
int v[10];
int * p;
p = &v[0];
*p = 0;
*(p+1) = 1;
*(p+2) = 2;
1 Conviene aclarar que cada uno de los elementos de un vector puede ocupar m
as 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 aritmetica de punteros, dado un
puntero p, la operacion p+n en realidad hace p+(n*tamano-del-dato). Es por ello que en la declaracion de un
puntero, resulta necesario indicar el tipo de dato al cual apunta.
103
Captulo 5. Vectores
Por otro lado hay que saber que el nombre de un vector, utilizado sin corchetes, representa
en realidad la direccion de memoria de su primer elemento. Esto es, dado
1
int v[10];
En consecuencia, el fragmento de codigo anterior tambien podra haberse escrito del siguiente modo:
1
2
3
4
int v[10];
*v = 0;
*(v+1) = 1;
*(v+2) = 2;
int v[10];
y entendiendo que v es, en realidad, la direccion del primer elemento del vector, entonces
las expresiones v[i] y *(v+i) son equivalentes. Esto es, podemos acceder a los elementos 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:
1
2
Por u ltimo hay que entender que, cuando en los ejemplos de la Seccion 5.5 empleabamos
la sentencia
1
inicializar(v);
104
inicializar(&v[0]);
lo que explica que los vectores siempre se pasan por referencia, aunque para ello no
utilicemos explcitamente la sintaxis clasica de punteros.
La implementacion de la funcion inicializar con la sintaxis empleada hasta el momento
1
2
3
4
5
5.11
Cadenas de caracteres
Captulo 5. Vectores
0
\0
queramos permitir, mas un elemento extra para el caracter \0. Por ejemplo, si estuviesemos pensando en una aplicacion que almacene mensajes de twitter, deberamos utilizar
vectores de tamano 141, ya que un tweet tiene una longitud maxima de 140 caracteres.
El motivo de usar el caracter especial \0 es el de permitir almacenar cadenas de menor
longitud al tamano del vector. Si no tuviesemos este caracter especial, sera imposible
determinar que caracteres del vector forman parte de la cadena y cuales no. Como hemos
dicho anteriormente, \0 indica n de cadena. Debe entenderse que una variable de tipo
char siempre contiene un valor (un caracter) aunque no haya sido inicializada (al igual
que una variable int siempre va a contener un entero). No es posible almacenar algo
que indique no_valor, de ah la necesidad de indicar de algun modo donde termina la
cadena. En el ejemplo de la gura anterior, los elementos del 5 al 9, en caso de no haber
sido inicializados, contendran cualquier caracter arbitrario.
Las cadenas de caracteres, por sus peculiaridades, tienen un tratamiento especial en C. La
principal diferencia con otro tipo de vectores es que, en este caso, no es necesario operar
elemento a elemento, sino que la librera de C dispone de funciones que permiten realizar
ciertas operaciones con la cadena completa. En los siguientes apartados de explican las
principales operaciones con cadenas de caracteres.
5.11.1
En este apartado estudiaremos las funciones printf y puts empleadas para mostrar
cadenas de caracteres por al salida estandar (esto es, por pantalla) y las funciones scanf y
gets empleadas para almacenar cadenas introducidas por la entrada estandar (el teclado)
en un vector de caracteres.
Funcion printf:
Dado el siguiente vector de caracteres
1
char cad[50];
106
i = 0;
while( cad[i] != \0 ) {
printf(" %c", cad[i]);
i++;
}
En este caso, en lugar de mostrar toda la cadena de golpe con el especicador %s, lo
vamos haciendo caracter a caracter con el especicador %c. Observese que, en el primer
caso, a la funcion printf se le pasa como argumento el vector completo (cad), mientras
que en este segundo ejemplo se le pasa, en cada iteracion, un u nico elemento del vector
(cad[i]), que corresponde a una variable de tipo char. Obviamente, el primer metodo
resulta mucho mas comodo.
Por supuesto, al igual que ocurre con otras variables, la funcion printf puede mostrar,
junto con la cadena, otra informacion. Por ejemplo:
1
Funcion puts:
La funcion puts (acronimo de put string) se emplea tambien 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 automaticamente un salto de lnea (\n). La instruccion
1
puts(cad);
es equivalente a
1
107
Captulo 5. Vectores
Funcion scanf:
Para almacenar en una cadena de caracteres un texto introducido por teclado se puede
utilizar la sentencia scanf con el especicador %s, tal y como muestra el siguiente
fragmento de codigo:
1
2
3
char cad[50];
printf("Introduce un texto: ");
scanf(" %s", cad);
El codigo anterior provoca que el texto introducido por teclado se almacene en el vector
cad. Ademas, el caracter especial \0 es introducido automaticamente al nal del texto.
Debe prestarse especial atencion a que, en este caso, en la sentencia scanf no se utiliza
el operador & precediendo al nombre de la variable, tal y como venamos haciendo hasta
ahora. La explicacion de ello esta relacionada con lo explicado en la Seccion 5.10, en la
que se vio que el nombre de un vector (cad en este caso) es, en realidad, la direccion de
memoria de su primer elemento (&cad[0]). Es decir, sin necesidad de poner el operador
& delante del nombre de la cadena, e sta ya se esta pasando por referencia.
La funcion scanf lee caracteres hasta que se encuentre un salto de lnea (provocado mediante la pulsacion 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 ejemplo, si tras la ejecucion de la sentencia scanf("%s", cad) el usuario introduce por
teclado el texto "Esto es una cadena" seguido de la pulsacion de la tecla enter, en
la cadena cad u nicamente se almacenara el texto "Esto". Para la lectura de cadenas que
contengan espacios en blanco debera emplearse la funcion gets.
Funcion gets:
Esta funcion lee caracteres de la entrada estandar (el teclado) hasta que encuentra un salto
de lnea y los almacena en una cadena. El salto de lnea (\n) no se almacena en la cadena.
A continuacion se muestra un ejemplo de uso:
1
2
3
108
char cad[50];
printf("Introduce un texto: ");
gets(cad);
5.11.2
#include <string.h>
A continuacion se muestran algunas de las funciones mas usadas. En este caso usaremos
la notacion de punteros (ver Seccion 5.10), ya que es de este modo como se encuentra habitualmente en la bibliografa. Debe entenderse que char * str es equivalente a
char str[].
Funcion
int strlen(char *str)
Descripcion
Devuelve la longitud de la cadena str,
sin incluir el caracter de terminacion
\0.
Copia la cadena almacenada en c2 a la
cadena c1, incluyendo el caracter de terminacion \0. Devuelve un puntero a la
cadena destino (la misma que se pasa como parametro, por lo que este valor devuelto es redundante).
Concatena la cadena almacenada en c2
a continuacion la cadena almacenada en
c1. El caracter de terminacion \0 de
la cadena c1 se sobreescribe con el primer caracter de la cadena c2 y se anade
un nuevo \0 en c1, al nal de la concatenacion de las dos cadenas. Devuelve
un puntero a la cadena destino (la misma
que se pasa como parametro, por lo que
este valor devuelto es redundante).
Compara las cadenas c1 y c2. Devuelve cero si ambas cadenas son iguales, un
valor positivo si c1 es, en orden alfabetico, posterior a c2, o un valor negativo si
c1 es, en orden alfabetico, anterior a c2.
109
Captulo 5. Vectores
#include <stdio.h>
#include <string.h>
#define N 50
4
5
6
7
int main() {
char cad1[N], cad2[N], cad3[N];
int x;
9
10
11
12
13
14
15
16
17
18
19
20
21
22
return 0;
23
24
5.12
Ejercicios resueltos
110
SOLUCION:
1
2
#include <stdio.h>
#define N 50
3
4
5
6
7
8
int main() {
float v[N], m;
pedir_valores(v);
m = maximo(v);
printf("M
aximo: %f\n", m);
10
11
12
13
return 0;
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Captulo 5. Vectores
SOLUCION:
1
2
#include <stdio.h>
#define N 50
3
4
5
6
7
8
int main() {
int v[N], num;
pedir_valores(v);
printf("Introduce el valor a buscar: ");
scanf(" %d", &num);
if( pertenece(v, num) )
printf(" %d se encuentra en el vector\n", num);
else
printf(" %d no se encuentra en el vector\n", num);
10
11
12
13
14
15
16
17
return 0;
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
SOLUCION:
1
2
3
4
5
6
4. Implementar una funcion que realice la misma operacion que la funcion strlen
de la librera de C (calculo de la longitud de una cadena).
SOLUCION:
1
2
3
4
5
6
5. Implementar una funcion que, dada una cadena de caracteres que contiene palabras
separadas por un espacio en blanco, muestre la u ltima palabra de la cadena.
SOLUCION:
1
2
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
113
Captulo 5. Vectores
5.13
Ejercicios propuestos
1
0
1
4
Rojo, 2 Verdes
Rojos, 2 Verdes
Rojo, 3 Verdes
Rojos
almacena, por tanto, los valores de M senales electricas muestreadas cada una de
ellas en N instantes de tiempo, representando el elemento S[i][j] el valor en voltios
de la senal 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 senal electrica muestreada
en N instantes de tiempo. Se desea comparar la senal almacenada en el vector V
con cada una de las M senales almacenadas en la matriz S y obtener aquella senal
de S que mas se parece a la almacenada en V . Para calcular como de parecidas
son dos senales X e Y se utiliza la formula:
similitud =
1
N
(Xi Yi )2
i=0
Cuanto menor sea este valor, mas parecidas seran las senales X e Y . Se pide implementar las funciones necesarias que nos permitan obtener, a partir de la matriz
S y el vector V , el ndice de la la en la matriz S donde se encuentra la senal mas
parecida a V .
115
Captulo 6
Estructuras
6.1
Introduccion
Cuando ciertas variables estan fuertemente relacionadas entre s, resulta interesante agruparlas bajo una misma entidad. Una estructura es un agrupamiento de variables en el que
e stas pueden ser de distinto tipo (a diferencia de los vectores, en los que todos sus elementos son del mismo tipo). A los elementos de una estructura se los denomina miembros.
6.2
Hasta ahora se han visto los tipos de datos simples int, float, char, double y el tipo
puntero. El lenguaje C permite crear nuevos tipos de datos para denir agrupaciones de
variables de distinta naturaleza. Este es el tipo struct. A diferencia de los tipos basicos,
que ya estan denidos en el lenguaje y pueden ser utilizados para declarar variables, el
tipo struct debe crearse a medida, con los miembros que nos interese en cada caso. Una
vez creado el tipo, sera posible declarar variables del mismo, de manera similar a como
se declaran variables de cualquier otro tipo.
En resumen, para declarar variables de un tipo estructurado se requieren dos pasos:
Denir un tipo de dato estructurado (se denen los miembros que va a tener
nuestro agrupamiento).
Declarar variables de dicho tipo.
117
Captulo 6. Estructuras
6.2.1
Para denir un tipo estructurado se debe dar un nombre al nuevo tipo e indicar el conjunto
de elementos (miembros) que contendra.
struct nombre_tipo_datos {
tipo_dato miembro1;
tipo_dato miembro2;
. . .
};
Por ejemplo:
1
2
3
4
5
6
struct persona {
char nombre[50];
float altura;
float peso;
int
anyo_nacimiento;
};
En este ejemplo se dene un nuevo tipo de datos de nombre struct persona que establece 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 variable. Simplemente hemos denido 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 podra almacenar un conjunto de valores) mientras que int, por poner
un ejemplo, es untipo basico (una variable de tipo int solo podra almacenar un valor).
Una vez denido el tipo, sera posible declarar variables del mismo.
6.2.2
Declaracion de variables
Una vez denido el tipo de datos, las variables de un tipo estructurado se declaran como
cualquier otra variable:
tipo nombre_variable;
118
struct persona p;
crea una variable p de tipo struct persona. Dicha variable p es una variable compuesta, 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 oats
(peso y altura) y 1 entero (ano de nacimiento).
6.3
6.3.1
Al igual que ocurre con otros tipos de variables, las variables de tipo estructurado se
pueden inicializar en el momento de la declaracion. Para ello se encierran entre llaves y se
separan por comas los valores con los que se desea inicializar cada uno de los miembros.
1
6.3.2
El par nombre_variable.miembro se utiliza de modo similar a como se usara cualquier otra variable. Por ejemplo, dada la variable p de tipo struct persona lo siguiente
seran instrucciones validas:
1
2
3
4
p.peso=60;
p.altura=1.75;
printf(" %s", p.nombre);
scanf(" %d", &p.anyo_nacimiento);
#include <stdio.h>
2
3
4
5
6
7
119
Captulo 6. Estructuras
int
8
9
anyo_nacimiento;
};
10
11
12
13
int main() {
struct persona p;
persona
float imc;
14
15
16
17
18
19
20
21
22
23
imc = p.peso/(p.altura*p.altura);
printf("Hola %s ", p.nombre);
printf("Tu Indice de Masa Corporal es %f\n", imc);
return 0;
24
25
26
27
28
Observese que los tipos estructurados suelen denirse a nivel global, fuera de la funcion
main. De este modo sera posible declarar variables del tipo estructurado no solo en main
sino en cualquier otra funcion de nuestro programa.
6.3.3
Asignacion
A diferencia de lo que ocurre con los vectores, entre los que no se permite la asignacion,
entre variables de tipo estructurado s que es posible realizar esta operacion.
Por ejemplo, la operacion
1
2
3
120
5
6
p1.peso = p2.peso;
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 operacion de asignacion (debe hacerse elemento a elemento mediante un bucle o, en el caso de las cadenas, mediante la funcion strcpy), si dicho vector se
encuentra dentro de una estructura, entonces la asignacion s es posible. En el ejemplo anterior, cuando se hace la asignacion p1 = p2 se esta copiando (entre otras cosas) el vector
p2.nombre en p1.nombre. Sin embargo, hacer directamente p1.nombre = p2.nombre
no sera valido ya que, como se ha mencionado, la asignacion entre vectores no esta permitida.
6.3.4
Otras operaciones
121
Captulo 6. Estructuras
6.4
Estructuras anidadas
Los miembros de una estructura pueden ser de cualquier tipo de datos, incluido otras
estructuras. Por ejemplo:
1
2
3
4
5
6
7
8
9
10
11
struct fecha {
int dia;
int mes;
int anyo;
};
struct persona {
char
float
float
struct fecha
};
nombre[50];
altura;
peso;
fecha_nacimiento;
struct persona p;
2
3
4
5
6
6.5
Vectores de estructuras
Es muy habitual utilizar las variables de tipo estructurado como elementos de vectores.
Un vector de variables estructuradas se declara de forma similar a como se declara un
vector de cualquier otro tipo de datos:
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, debera tratarse como tal.
Por ejemplo, para acceder al nombre de la persona almacenada en la primera posicion del
vector emplearamos v[0].nombre y para acceder al peso de la u ltima v[99].peso.
En el siguiente ejemplo se dene una estructura de tipo alumno con dos miembros (nombre y nota), a continuacion se declara un vector de N alumnos, se introducen los datos de
cada uno, se calcula la nota media y nalmente se muestra un listado de los aprobados:
1
2
#include <stdio.h>
#define N 100
3
4
5
6
7
struct alumno {
char nombre[50];
float nota;
};
8
9
10
11
12
13
14
15
16
17
18
19
int main() {
struct alumno v[N]; // Declaramos un vector de 100 alumnos
int i;
float suma;
// Introducir datos
for(i=0; i<N; i++) {
printf("Nombre: ");
scanf(" %s", v[i].nombre);
printf("Nota: ");
scanf(" %f", &v[i].nota);
}
20
21
22
23
24
25
26
// Mostrar aprobados
printf("Listado de aprobados\n");
for(i=0; i<N; i++) {
if( v[i].nota >= 5 )
printf(" %s\n", v[i].nombre);
}
return 0;
27
28
29
30
31
32
33
34
123
Captulo 6. Estructuras
6.6
Punteros a estructuras
Los punteros a variables de tipo estructurado se utilizan de forma similar a como se emplean los punteros a otro tipo de variables.
Dada la variable
1
3
4
puntero_alumno = &un_alumno;
// Guardamos en puntero_alumno la
// direcci
on de la variable
un_alumno
un_alumno.nota = 10;
es equivalente a
1
(*puntero_alumno).nota = 10;
124
6.7
Las estructuras, como cualquier otro tipo de variables, pueden pasarse a una funcion como parametro. A continuacion se muestran distintos ejemplos de paso de estructuras como parametros. Tal y como podra observarse, no existen diferencias respecto al paso de
parametros de otro tipo de variables.
6.7.1
Una posibilidad consiste en pasar los distintos miembros de la estructura de forma independiente. Esto es equivalente a pasar variables de tipos basicos.
1
#include <stdio.h>
2
3
4
5
6
struct alumno {
char nombre[50];
float nota;
};
7
8
9
10
11
int main() {
struct alumno un_alumno;
12
13
14
15
16
17
18
19
20
21
22
23
6.7.2
Tambien es posible pasar toda la estructura completa, como una sola variable. En este caso, y siguiendo con el ejemplo anterior, la funcion mostrar_alumno recibira una
variable de tipo struct alumno.
Nota.- Para simplicar el codigo, en sucesivos ejemplos supondremos ya denido el tipo
struct alumno , y todas las funciones declaradas.
125
Captulo 6. Estructuras
1
2
int main() {
struct alumno un_alumno;
4
5
6
7
8
9
10
11
12
13
14
Es importante entender que las estructuras se pasan por valor, tal y como ocurre con las
variables de tipos basicos (o primitivos). Esto quiere decir que cualquier modicacion
que hagamos sobre los miembros de la estructura no tendra efecto fuera de la funcion
donde se realiza (la funcion que recibe la estructura opera con una copia de la misma). A
continuacion se muestra un ejemplo:
1
2
3
4
5
6
int main() {
struct alumno un_alumno;
. . .
subir_nota(un_alumno);
. . .
}
7
8
9
10
En este ejemplo la funcion subir_nota no tiene ningun efecto sobre la variable un_alumno.
Esta variable seguira teniendo el mismo valor que tena antes de la llamada a la funcion
subir_nota. Obviamente, aunque a la variable a de la funcion subir_nota le hubieramos dado el nombre un_alumno la situacion sera exactamente la misma.
126
6.7.3
Si queremos que una funcion modique el valor de algun miembro de una variable estructurada, es necesario pasar dicha variable por referencia, esto es, pasar la direccion de
memoria de la variable en lugar del valor de la misma.
1
2
3
4
5
6
int main() {
struct alumno un_alumno;
. . .
subir_nota( &un_alumno );
. . .
}
7
8
9
10
11
12
13
6.7.4
El paso de un vector como parametro a una funcion se realiza siempre del mismo modo, independientemente de que el vector contenga variables simples o estructuradas. En
general hay que recordar que los vectores se pasan implcitamente por referencia. Esto quiere decir que cualquier modicacion que hagamos sobre los elementos del vector
(sean estructuras o no) se mantendra cuando termine la funcion donde se realiza.
1
2
3
4
5
6
7
8
int main() {
struct alumno v[N]; // Declaramos un vector de N alumnos
. . .
subir_notas( v );
// Subir la nota a todos los alumnos
. . .
}
void subir_notas( struct alumno v[] ) {
int i;
10
11
12
13
127
Captulo 6. Estructuras
6.7.5
Las funciones tambien pueden devolver estructuras. Esta circunstancia, como veremos,
esta relacionada con el hecho de que la asignacion entre estructuras esta permitida.
1
2
3
4
5
6
int main() {
struct alumno un_alumno;
. . .
un_alumno = crear_alumno();
. . .
}
7
8
9
10
printf("Nombre: ");
scanf(" %s", a.nombre);
printf("Nota: ");
scanf(" %f", &a.nota);
11
12
13
14
15
return a;
16
17
La funcion crear_alumno declara una variable a de tipo struct alumno. Esta variable
es devuelta al nal de la funcion. Por tanto a la variable un_alumno declarada en la
funcion 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 operacion de asignacion entre estructuras.
6.8
Ejercicios resueltos
implementar un programa que solicite la parte real e imaginaria de un numero complejo, y calcule su modulo mediante el uso de una funcion.
SOLUCION:
1
2
3
128
#include <stdio.h>
#include <math.h>
4
5
6
7
struct complejo {
float real;
float imag;
};
8
9
10
11
12
13
int main() {
float m;
struct complejo c;
// Declaraci
on de variable
14
15
16
17
18
19
20
21
22
23
24
2. Implementar un programa que solicite el peso, altura y sexo de 10 personas y lo almacene en un vector de estructuras. A continuacion calcular, mediante una funcion,
el peso medio de las personas con una altura comprendida entre 1.70 y 1.80.
SOLUCION:
1
2
#include <stdio.h>
#define N 5
3
4
5
6
7
8
struct persona {
float peso;
float altura;
int sexo; // 1=Mujer
};
2=Hombre
9
10
11
12
13
14
15
int main() {
struct persona v[N];
float m;
16
17
pedir_valores(v);
129
Captulo 6. Estructuras
18
19
20
return 0;
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
SOLUCION:
// Definimos el tipo RGB
struct RGB {
int r;
int g;
int b;
};
1
2
3
4
5
6
7
int main() {
// Declaramos una matriz de tipo RGB
struct RGB imagen[FIL][COL];
. . .
// Llamada a la funci
on
modificar_azul(imagen, 10);
. . .
}
8
9
10
11
12
13
14
15
16
// Implementaci
on de la funci
on
void modificar_azul( struct RGB img[FIL][COL], int x ) {
int i, j;
for(i=0; i<FIL; i++)
for(j=0; j<COL; j++)
img[i][j].b += x;
}
17
18
19
20
21
22
23
6.9
Ejercicios propuestos
1. Denir una estructura que pueda almacenar los datos de un alumno: dni, nombre,
grupo y nota (la nota de una u nica asignatura). A continuacion, declarar:
Una variable del tipo recien creado.
100 variables del tipo recien creado.
2. Modicar la estructura del ejercicio anterior para poder almacenar, ademas, la direccion del alumno (calle, numero, CP y poblacion). Utilizar una estructura de datos
anidada.
3. Escribir un programa que lea los datos de 100 personas (nombre, dni, fecha nacimiento y sexo) y muestre el numero de hombres y mujeres nacidos a partir de
1980.
4. Se dispone de una biblioteca de hasta 500 libros con codigos numericos correlativos desde el 1. Se quiere informatizar el proceso de mantenimiento. Escribir la
estructura de datos necesaria y las funciones para:
131
Captulo 6. Estructuras
132
Captulo 7
Gestion de cheros
7.1
Introduccion
La memoria principal de los ordenadores es volatil. Esto quiere decir que en ausencia de
corriente electrica la memoria pierde toda la informacion que tena almacenada. Por otro
lado, no tendra sentido introducir en un programa informatico los datos de matrcula de
20.000 alumnos y luego no poder almacenarlos de forma permanente para su posterior
recuperacion (no podemos conar en que el ordenador vaya a estar encendido indenidamente). Es necesario un mecanismo que nos permita almacenar los datos de nuestros
programas para futuros usos.
Los ordenadores pueden almacenar informacion de forma permanente1 en lo que se denomina sistemas de almacenamiento secundarios: discos, CDs, DVDs, memorias USB,
etc.
Un chero (o archivo) es una secuencia de bytes que representan cierta informacion, almacenada permanentemente en algun soporte digital. La informacion se almacena en los
cheros en forma de una secuencia de bytes terminada con el caracter especial EOF (End
Of File). El modo en que debe interpretarse la secuencia de bytes contenida en el chero dependera del sistema de codicacion empleado. Un mismo chero podra contener
caracteres, numeros, imagenes, etc. codicados cada uno en un sistema diferente. Es responsabilidad del programador conocer que criterios de codicacion tienen las secuencias
de bytes contenidas en el chero, para poder interpretar correctamente la informacion que
contiene.
1 El t
ermino permanente debera ser interpretado con cautela, ya que ningun sistema de almacenamiento
esta exento del riesgo de perdida de informacion. En este sentido, las copias de seguridad en mas de un medio
debera ser una practica habitual.
133
7.2
Tipos de cheros
Cuando se trabaja con cheros se distinguen basicamente dos formas en las que puede
almacenarse la informacion (lo que da lugar a dos tipos de cheros):
Como caracteres. En este caso se entiende que la secuencia de bytes almacenada
en el chero contiene una secuencia de caracteres representados en algun sistema
de codicacion de caracteres (ASCII, Unicode, etc.). Logicamente, este tipo de
cheros serviran para almacenar texto (mas concretamente texto plano, sin ningun
tipo de formato, ya que estos sistemas de codicacion no tienen capacidad para
representar conceptos como negrita, cursiva, tamano de la letra, etc.). Los cheros
que contienen u nicamente caracteres se denominan cheros de texto.
Como informacion binaria. En este caso se entiende que la secuencia de bytes representa informacion codicada en cualquier sistema (no necesariamente un sistema
de codicacion de caracteres). Por tanto, cualquier chero que no contenga texto
plano diremos que es un chero binario.
En ambos casos el chero contiene una secuencia de bytes terminada con el caracter
especial EOF. La u nica diferencia radica en la forma en la que el programa que trata el
chero debe interpretar la informacion en e l contenida.
7.3
Para trabajar con cheros hay que realizar siempre las siguientes acciones:
1. Abrir el chero mediante la instruccion fopen.
2. Realizar operaciones de lectura o escritura sobre el chero.
3. Cerrar el chero mediante la instruccion fclose.
Las operaciones de apertura y cierre de chero se realizan de modo similar, independientemente de que el chero sea de texto o binario, mientras que las operaciones de
lectura/escritura seran distintas en funcion del tipo de chero. En los siguientes apartados
se explican con detalle estas operaciones.
134
7.3.1
Todos los cheros tienen un nombre que los identica (o, con mayor precision, un nombre
y un directorio). El lenguaje C, sin embargo, para operar con cheros no utiliza su nombre
sino lo que se conoce como descriptor de chero. Estos descriptores se almacenan en
variables de un nuevo tipo de datos hasta ahora no visto denominado FILE *.
Antes de realizar cualquier operacion con un chero es necesario abrirlo mediante la
instruccion fopen. Esta instruccion asocia un nombre de chero a un descriptor. Para
ello, recibe el nombre del chero como parametro y devuelve su descriptor. A partir de
este momento, y para el resto de operaciones, el nombre ya no volvera a usarse y, en su
lugar, se identicara el chero mediante su descriptor. La instruccion fopen se usa del
siguiente modo:
descriptor = fopen("nombre_del_fichero", modo);
donde:
descriptor debera ser una variable de tipo FILE *.
nombre_del_fichero es, obviamente, el nombre del chero. Si este nombre no
contiene la ruta de ningun directorio, entonces se entendera que el chero se encuentra en el directorio actual de trabajo.
modo es una cadena de caracteres que indica el modo en que se desea abrir el chero
(lectura, escritura o anadir) y el tipo de chero (binario o texto), tal y como se indica
en la siguiente tabla:
Texto
rt
wt
Binario
rb
wb
at
ab
rt+
rb+
wt+
wb+
at+
ab+
Respecto a la tabla anterior debe puntualizarse que, en modo texto, puede omitirse la letra
t, esto es, tambien son validos 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 captulo abordaremos u nicamente los modos de solo lectura y solo escritura, esto es,
r, w, rb y wb.
Si, por ejemplo, pretendemos abrir un chero de texto de nombre datos.txt en modo
lectura, deberamos hacer:
1
2
FILE * f;
// Mi descriptor de fichero
f = fopen("datos.txt", "r"); // Abre el fichero en modo lectura
#include <stdio.h>
2
3
4
5
6
7
8
9
int main() {
FILE * f;
f = fopen("datos.txt", "r");
if( f == NULL ) {
printf("Error abriendo el fichero datos.txt \n");
return 0;
}
10
11
12
13
En ocasiones resulta interesante que sea el propio usuario quien indique el nombre del
chero, en lugar de establecerlo directamente en el codigo del programa.
1
2
3
int main() {
FILE * f;
char nombre_fichero[512];
5
6
7
8
9
136
Cuando un chero termina de usarse debe cerrarse siempre mediante la instruccion fclose.
fclose(descriptor_de_fichero);
7.3.2
donde descriptor hace referencia al descriptor del chero obtenido mediante la funcion
fopen. En el parametro formato podran utilizarse los codigos %d, %f, %c y %s, de modo
similar a como se emplean en la funcion printf.
El siguiente programa de ejemplo escribe el texto Hola Mundo en un chero de nombre
hola.txt.
137
1
2
3
#include <stdio.h>
int main() {
FILE * f;
6
7
8
9
f = fopen("hola.txt", "a");
Por supuesto, se puede hacer mas de un fprintf sobre el mismo chero. Cada instruccion fprintf ira anadiendo el nuevo texto a continuacion del que ya tenamos, tal y
como se muestra en el siguiente fragmento de codigo:
1
2
3
4
5
6
f = fopen("quijote.txt", "w");
fprintf(f, "En un lugar de la Mancha ");
fprintf(f, "de cuyo nombre no quiero acordarme,\n");
fprintf(f, "no ha mucho tiempo que viv
a un hidalgo ");
fprintf(f, "de los de lanza en astillero\n");
fclose(f);
#include <stdio.h>
int main() {
FILE * f;
int i, j;
5
6
7
138
f = fopen("tablas_de_multiplicar.txt", "w");
for( i = 1; i <= 10; i++ ) {
8
9
10
}
fclose(f);
return 0;
11
12
13
14
Operaciones de lectura
La sintaxis de la instruccion scanf es la siguiente:
int fscanf(descriptor, formato [, lista_de_punteros_a_variables]);
La lectura, como se ha comentado anteriormente, se realiza de forma secuencial, comenzando desde el inicio del chero. Esto quiere decir que cada operacion de lectura con
fscanf ira leyendo partes o trozos consecutivos del chero. Cuanta informacion se lee
en cada sentencia fscanf depende de el/los codigo/s % que se indiquen en el parametro
formato.
Por ejemplo
1
leera una palabra (se entiende por palabra una secuencia de caracteres delimitada por un
espacio en blanco, un tabulador, un salto de lnea o el caracter especial EOF). La proxima
instruccion fscanf leera la informacion que aparezca a continuacion de dicha palabra.
Por ejemplo, dado un chero de texto que almacena cien numeros enteros, el siguiente
programa lee todos los valores del chero y calcula su media:
1
2
3
4
#include <stdio.h>
int main() {
FILE * f;
int i, num, suma = 0;
5
6
7
8
9
10
11
12
f = fopen("numeros.txt", "r");
if( f == NULL ) {
printf("Error abriendo fichero\n");
return 0;
}
for( i = 1; i <= 100; i++ ) {
fscanf( f, " %d", &num );
139
suma += num;
}
printf("Media = %f\n", suma/100.0);
fclose(f);
return 0;
13
14
15
16
17
18
En el ejemplo anterior hemos ledo un chero de tamano conocido, sin embargo, Es muy
habitual que no sepamos de antemano la cantidad de datos almacenados en el chero. En
este caso habra que ir leyendo hasta que se encuentre el caracter EOF. Para ello hay que
saber que la instruccion fscanf devuelve el numero de elementos ledos o EOF en caso
de que se haya alcanzado el nal del chero, con lo que sera facil averiguar cuando hemos
terminado de leer toda la informacion.
El siguiente ejemplo lee, caracter a caracter, el contenido completo de un chero y lo
muestra por pantalla.
1
2
3
4
#include <stdio.h>
int main() {
FILE * f;
char caracter;
// Abrir el fichero
f = fopen("quijote.txt", "r");
if( f == NULL ) {
printf("Error abriendo el fichero quijote.txt\n");
return 0;
}
6
7
8
9
10
11
12
13
14
15
16
// Cerrar el fichero
fclose(f);
return 0;
17
18
19
20
140
Con este esquema, en la propia condicion del bucle while se realiza una lectura y se comprueba si dicha lectura ha tenido e xito, en cuyo caso fscanf devuelve un valor distinto
de EOF. En este caso, se procesan los datos recien ledos y se vuelve al inicio del bucle
para realizar la siguiente lectura. En el momento en que fscanf devuelva EOF, sera senal
de que hemos alcanzado el nal del chero.
En los ejemplos mostrados hasta ahora el nombre del chero viene jado en el programa,
pero en ocasiones puede resultar necesario que sea el usuario quien introduzca dicho
nombre. El siguiente ejemplo copia el contenido del chero que elija el usuario sobre otro
chero.
1
2
3
4
#include <stdio.h>
int main() {
FILE * f_in, * f_out;
char nom_fich_in[512], nom_fich_out[512], caracter;
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
141
return 0;
32
33
Debe observarse que en este ejemplo, los nombres de los cheros son introducidos por el
usuario y almacenados en cadenas de caracteres (lneas 8 y 17). A continuacion, en las
instrucciones fopen (lneas 9 y 18) utilizamos como argumento estas variables en lugar
de una cadena constante.
Tal y como se ha visto ya en algun ejemplo, es perfectamente posible que un chero de
texto contenga numeros. Simplemente, estaran almacenados mediante sus correspondientes caracteres. En este caso es posible, a la hora de leerlos mediante fscanf, guardarlos
en nuestro programa en variables de tipos numericos (int, float o double). El propio
fscanf hara la conversion al tipo especicado. En el siguiente ejemplo se parte de un
chero que contiene, por cada lnea, el nombre de un alumno, el grupo en el que esta matriculado y su nota. El programa lee toda esta informacion y la almacena en un vector de
estructuras.
1
2
#include <stdio.h>
#define MAX_ALUMNOS 500
3
4
5
6
7
8
struct alumno {
char nombre[50];
char grupo;
float nota;
};
9
10
11
12
13
14
15
16
17
int main() {
struct alumno v[MAX_ALUMNOS];
int num_alumnos;
num_alumnos = cargar_datos(v);
. . .
}
18
19
20
21
22
23
24
25
26
27
28
29
142
// Abrir fichero
printf("Introduce nombre de fichero de alumnos: ");
scanf(" %s", nom_fich);
f = fopen(nom_fich, "r");
if( f == NULL )
return 0; // No se han podido leer los datos
30
31
32
33
34
35
36
37
38
39
40
// Cerrar fichero
fclose(f);
41
42
43
return i;
44
45
// Devolvemos el n
umero de alumnos le
dos
7.3.3
Operaciones de escritura
La funcion fwrite se usa del siguiente modo:
fwrite(direccion_memoria, tama
no_variable, numero_variables, descriptor);
donde:
direccion_memoria es la direccion de la variable que queremos escribir.
tama
no_variable es el numero de bytes que ocupa la variable a escribir. Se puede conocer el tamano de una variable con la instruccion sizeof(variable) (o
tambien sizeof(tipo)).
numero_variables es la cantidad de variables que vamos a escribir. En caso de
que direccion_memoria sea la direccion de un vector, este valor podra ser mayor
de uno.
143
Por ejemplo, si quisieramos guardar en un chero binario el valor de cierta variable entera
n, haramos:
1
2
3
4
FILE * f;
int n;
. . .
fwrite(&n, sizeof(int), 1, f);
Para guardar una variable de un tipo estructurado (por ejemplo una variable alum de tipo
struct alumno) ejecutaramos:
1
2
3
4
FILE * f;
struct alumno alum;
. . .
fwrite(&alum, sizeof(struct alumno), 1, f);
Si en lugar de tener una u nica variable tenemos un vector, podemos guardar el vector
entero con una u nica instruccion del siguiente modo:
1
2
3
4
FILE * f;
struct alumno v[N];
. . .
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.
Basicamente, la instruccion fwrite del ejemplo anterior podra interpretarse como: .a
partir de la direccion de memoria v (esto es, &v[0]), selecciona X bytes y guardalos en el
chero f, donde X=N*sizeof(struct alumno).
Operaciones de lectura
La funcion fread se usa del siguiente modo:
fread(puntero_variable, tama
no_variable, numero_variables, descriptor);
FILE * f;
struct alumno v[N]; // Suponiendo N = 500
f=fopen("alumnos.bin", "rb");
fread(v, sizeof(struct alumno), N, f);
fclose(f);
FILE * f;
struct alumno v[MAX_ALUMNOS];
int i = 0;
4
5
6
7
8
9
f=fopen("alumnos.bin", "rb");
while( i < MAX_ALUMNOS && fread(&v[i], sizeof(struct alumno), 1,
f) > 0 )
i++;
fclose(f);
printf("Hemos le
do %d alumnos\n", i);
Como puede observarse en el ejemplo anterior, en cada iteracion del bucle while se leen
los datos de un alumno. En el momento en que la instruccion fread devuelva cero se
saldra del bucle, ya que esto indica que se han ledo todos los datos del chero. En caso
de que el chero contenga mas de MAX_ALUMNOS elementos, el bucle nalizara cuando se
alcance este numero de alumnos, evitando de este modo sobrepasar el rango del vector v.
145
7.4
Acceso aleatorio
Hasta ahora se han utilizado los cheros en modo secuencial, esto es, las distintas operaciones de escritura/lectura iban guardando/consumiendo los datos en el chero de forma
secuencial. Para ello el descriptor del chero recuerda la posicion donde se realizo la
u ltima operacion de lectura/escritura, de modo que la proxima operacion se realiza siempre en la siguiente posicion.
En algunas ocasiones puede resultar interesante romper esta secuencialidad y leer o escribir en una posicion determinada del chero. En este caso hablamos de acceso aleatorio.
La funcion fseek permite avanzar o retroceder en el chero para realizar la operacion
de lectura o escritura en la posicion deseada (en el caso de escritura sobreescribiendo lo
que haba previamente en dicha posicion, no insertando). El uso de la funcion fseek es
el siguiente:
fseek(descriptor, desplazamiento, modo);
donde descriptor es el descriptor del chero, desplazamiento es un entero que indica la cantidad de bytes a desplazarse (puede ser positivo o negativo) y modo es otro entero
que indica a partir de donde se realiza el desplazamiento. Para este u ltimo parametro
pueden utilizarse las siguientes constantes denidas en stdio.h:
SEEK_SET: Desde el principio del chero.
SEEK_CUR: Desde la posicion actual.
SEEK_END: Desde el nal del chero.
7.5
Ejercicios resueltos
1. Dado un chero de texto de nombre polares.txtcon una cantidad de lneas desconocida, en el que cada lnea contiene las coordenadas polares (modulo y a ngulo)
de un punto, escribir un programa que lea la informacion contenida en dicho chero y almacene en un segundo chero de nombre cartesianas.txttodos los puntos
expresados en coordenadas cartesianas.
146
SOLUCION:
1
2
#include <stdio.h>
#include <math.h>
3
4
5
6
int main() {
FILE * f_in, *f_out;
float mod, ang, x, y;
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Cerrar ficheros
fclose( f_in );
fclose( f_out );
23
24
25
26
return 0;
27
28
2. Un chero binario contiene los datos censales de cierto numero de personas (como maximo 5.000). Concretamente almacena, para cada persona, el nombre (50
caracteres), el municipio de residencia (50 caracteres) y el da, mes y ano de nacimiento (enteros). Implementar una funcion que lea la informacion de dicho chero,
la almacene en un vector de estructuras y devuelva el numero de personas ledas.
Supondremos que esta denido el siguiente tipo estructurado:
struct persona {
char nombre[50];
char municipio[50];
int dia, mes, anyo;
};
SOLUCION:
147
2
3
4
f = fopen(nom_fich, "rb");
if( f == NULL )
return 0;
while( fread( &v[i], sizeof(struct persona), 1, f ) > 0 )
i++;
fclose(f);
return i;
5
6
7
8
9
10
11
12
13
int main() {
struct persona v[5000];
char nom_fich[50];
int num_personas;
14
15
16
17
18
19
20
21
22
// Llamada a la funci
on
num_personas = cargar_datos_censales(nom_fich, v);
23
24
25
26
27
28
return 0;
29
30
7.6
Ejercicios propuestos
148
Implementar una funcion que reciba un vector de tipo struct alumno y almacene
en el chero aprobados.txt el nombre y la nota de los alumnos aprobados, y en
suspensos.txt el nombre y la nota de los suspendidos.
3. Se dispone de un chero binario de nombre alumnos que contiene, para cada
alumno, su nombre (cadena de 50 caracteres), su grupo y su nota. Implementar un
programa que, a partir de dicho chero, almacene en otro chero binario de nombre
aprobados el nombre, grupo y nota de los aprobados.
149
Bibliografa
[1] B. S. Gottfried, Programacion en C. Mc. Graw-Hill, 1997.
[2] H. Schildt, C. Manual de referencia. Mc. Graw-Hill, 2000.
[3] M. W. y Stephen Prata, Programacion en C. Anaya Multimedia, 1990.
[4] Programacion en c - wikiversidad. http://es.wikiversity.org/wiki/
Programaci%C3%B3n_en_C.
[5] Programacion en c - wikilibros.
Programaci%C3%B3n_en_C.
http://es.wikibooks.org/wiki/
151