Beruflich Dokumente
Kultur Dokumente
POLITCNICA
DE CARTAGENA
rea de Lenguajes y Sistemas Informticos
FUNDAMENTOS DE INFORMTICA.
PROGRAMACIN EN C.
Pedro Mara Alcover Garau
Ingeniero Industrial
Ingeniero Tcnico Industrial
FUNDAMENTOS DE INFORMTICA.
PROGRAMACIN EN C.
Pedro Mara Alcover Garau
PRESENTACIN
Despus de varios aos de dar clases de fundamentos de informtica y
de programacin en lenguaje C en algunas titulaciones de la Escuela de
Ingenieros Industriales de la UPCT, me he decidido a poner por escrito,
para gua del alumno, un manual que recoja toda la materia que se
imparte en esas asignaturas.
La informacin que aqu se recoge supera las posibilidades de docencia
que pueden alcanzarse en una asignatura cuatrimestral. He querido
extenderme ms all de lo que es posible ver en las aulas, porque creo
que vale la pena ofrecer la posibilidad de que alguien que adquiera este
manual
para
apoyo
de
una
asignatura,
pueda
luego
continuar
NDICE
PARTE I. Primeros pasos en C
CAPTULO 1
LENGUAJE C.
Introduccin
Entorno de programacin.
Estructura bsica de un programa en C.
Elementos lxicos
Sentencias simples y sentencias compuestas
Errores y depuracin
Recapitulacin
El entorno de Borland C++
5
7
9
12
13
14
15
15
CAPTULO 2
TIPOS DE DATOS Y VARIABLES EN C
19
Declaracin de variables.
Tipos de datos primitivos en C: sus dominios.
Tipos de datos primitivos en C: sus operadores.
Operador asignacin.
Operadores aritmticos.
Operadores relacionales y lgicos.
Operadores a nivel de bit.
20
22
25
26
27
30
34
Operadores compuestos.
Operador sizeof
Expresiones en las que intervienen variables de diferente
tipo.
Operador para forzar cambio de tipo.
Propiedades de los operadores.
Valores fuera de rango en una variable.
Constantes. Directiva #define.
Intercambio de valores de dos variables.
Ayudas On line.
Recapitulacin
Ejemplos y Ejercicios propuestos (del 1 al 15)
39
40
41
43
45
48
50
51
52
52
53
CAPTULO 3
FUNCIONES DE ENTRADA Y SALIDA POR
CONSOLA
65
66
74
76
77
83
CAPTULO 4
ESTRUCTURAS DE CONTROL
Introduccin
Conceptos previos
Estructuras de control condicionales
Estructura de seleccin mltiple: Sentencia switch
Un ejercicio planteado.
Estructuras de repeticin. Iteracin.
Sentencias de salto: break y continue
Palabra reservada goto
Variables de control de las iteraciones
Recapitulacin.
Ejercicios. (del 20 al 39)
ii
87
87
89
90
97
101
104
116
120
120
122
122
CAPTULO 5
MBITO Y VIDA DE LAS VARIABLES
147
mbito y Vida.
El almacenamiento de las variables en la memoria.
Variables Locales y Variables Globales
Variables estticas y dinmicas.
Variables en registro.
Variables extern
En resumen
Ejercicios (40)
147
148
150
154
156
157
158
160
CAPTULO 6
ARRAYS NUMRICOS: VECTORES Y MATRICES
163
164
166
169
CAPTULO 7
CARACTERES Y CADENAS DE CARACTERES
181
182
185
186
188
191
196
199
CAPTULO 8
PUNTEROS
203
Definicin y declaracin
Dominio y operadores para los punteros
Punteros y vectores
ndices y operatoria de punteros
204
205
209
212
iii
215
219
221
Puntero a puntero
Advertencia final
Ejercicios. (del 52 al 53)
CAPTULO 9
FUNCIONES
223
Definiciones
Funciones en C
Declaracin de la funcin.
Definicin de la funcin.
Llamada a la funcin
La sentencia return
mbito y vida de las variables
Recurrencia
Llamadas por valor y llamadas por referencia
Vectores y matrices como argumentos
Funciones de escape
Ejercicios. (del 54 al 63)
224
227
228
230
232
233
236
239
243
246
249
250
273
CAPTULO 10
ASIGNACIN DINMICA DE MEMORIA
277
Funcin malloc
Funcin free
Ejemplo: la Criba de Erastthenes. (Ejercicio 64)
Matrices en memoria dinmica
Ejercicios. (65)
279
283
283
288
292
CAPTULO 11
ALGUNOS USOS CON FUNCIONES
299
Punteros a funciones
Vectores de punteros a funciones
300
303
iv
305
308
312
315
316
320
325
328
CAPTULO 12
ESTRUCTURAS ESTTICAS DE DATOS Y
DEFINICIN DE TIPOS
329
330
331
333
334
339
341
344
348
CAPTULO 13
GESTIN DE ARCHIVOS
353
354
356
359
359
361
373
375
vi
PARTE I:
Primeros pasos
en lenguaje C.
algoritmos
construir
programas.
Los
lenguajes
de
la
informacin
de
nuestros
programas
diferentes
8,
un
aspecto
de
la
programacin
en
C,
ciertamente
CAPTULO 1
LENGUAJE C.
Presentamos en este captulo una primera vista de la programacin en
lenguaje C. El objetivo ahora es mostrar los conceptos bsicos de un
entorno de programacin, y redactar, con el entorno que cada uno
quiera (a lo largo del curso emplearemos fundamentalmente el Turbo
C++, de la casa Borland), un primer programa en C, que nos servir
para conocer las partes principales de un programa.
Introduccin.
Los lenguajes de programacin estn especialmente diseados para
programar computadoras. Sus caractersticas fundamentales son:
1. Son independientes de la arquitectura fsica del ordenador.
Los lenguajes estn, adems, normalizados, de forma que queda
garantizada la portabilidad de los programas escritos en esos
lenguajes.
Captulo 1. Lenguaje C.
Entorno de programacin.
Para realizar la tarea de escribir el cdigo de una aplicacin en un
determinado lenguaje, y poder luego compilar y obtener un programa
que realiza la tarea planteada, se dispone de lo que se denomina un
entorno de programacin.
Un entorno de programacin es un conjunto de programas necesarios
para construir, a su vez, otros programas. Un entorno de programacin
incluye editores, compiladores, archivos para incluir, archivos de
biblioteca, enlazadores y depuradores (ya veremos todos estos
conceptos en el primer Captulo de este manual). Gracias a Dios existen
entornos de programacin integrados, de forma que en una sola
aplicacin quedan reunidos todos estos programas. Ejemplos de
entornos integrados de programacin en C son el programa Microsoft
Visual C++, o el Turbo C++ de Borland.
Un editor es un programa que permite construir ficheros de caracteres,
que el programador introduce a travs del teclado. Un programa no es
ms que archivo de texto. El programa editado en el lenguaje de
programacin se llama fichero fuente. Algunos de los editores facilitan
el correcto empleo de un determinado lenguaje de programacin, y
advierten de inmediato la insercin de una palabra clave, o de la
presencia de un error sintctico, marcando el texto de distintas formas.
Un compilador es un programa que compila, es decir, genera ficheros
objeto que entiende el ordenador. Un archivo objeto todava no es
una archivo ejecutable.
El entorno ofrece tambin al programador un conjunto de archivos para
incluir
archivos
de
cabecera.
Esos
archivos
suelen
incluir
llamadas depuradores,
Captulo 1. Lenguaje C.
Compilacin
No
Errores de
compilacin
Obtencin del
programa objeto (.obj)
Obtencin del
programa
ejecutable (.exe)
S
Errores de
ejecucin
Enlace
No
Programas
objeto del
usuario
Archivos de
biblioteca (.lib)
#include <stdio.h>
/* Este es un programa en C. */
// Imprime un mensaje en la pantalla del ordenador
void main(void)
{
printf(mi primer programa en C);
}
Todos los programas en C deben tener ciertos componentes fijos. Vamos
a ver los que se han empleado en este primer programa:
1. #include <stdio.h>: Los archivos .h son los archivos de cabecera
en C. Con esta lnea de cdigo se indica al compilador que se desea
emplear, en el programa redactado, alguna funcin que est
declarada en el archivo de biblioteca stdio.h. Esta archivo contiene
las declaraciones de una coleccin de programas de entrada y salida
por consola (pantalla y teclado).
Esta instruccin nos permite utilizar cualquiera de las funciones
declaradas en el archivo. Esta lnea de cdigo recoge el nombre del
archivo stdio.h, donde estn recogidos todos los prototipos de las
funciones de entrada y salida estndar. Todo archivo de cabecera
contiene identificadores, constantes, variables globales, macros,
prototipos de funciones, etc.
Toda
lnea
que
comience
por
se
llama
directiva
de
10
Captulo 1. Lenguaje C.
11
Elementos lxicos.
Entendemos por elemento lxico cualquier palabra vlida en el
lenguaje C. Sern elementos lxicos, o palabras vlidas, todas aquellas
palabras que formen parte de las palabras reservadas del lenguaje, y
todas aquellas palabras que necesitemos generar para la redaccin del
programa, de acuerdo con una normativa sencilla.
Para crear un identificador (un identificador es un smbolo empleado
para representar un objeto dentro de un programa) en el lenguaje C se
usa cualquier secuencia de una o ms letras (de la A a la Z, y de la
a a la z, excluida las letras y ), dgitos (del 0 al 9) o smbolo
subrayado (_). Un identificador es cualquier palabra vlida en C. Con
ellos podemos dar nombre a variables, constantes, tipos de dato,
nombres de funciones o procedimientos, etc. Tambin las palabras
propias del lenguaje C son identificadores; estas palabras se llaman
palabras clave o palabras reservadas.
Adems de la restriccin en el uso de caracteres vlidos para crear
identificadores, existen otras reglas bsicas para su creacin en el
lenguaje C:
1. Debe comenzar por una letra del alfabeto o por el carcter
subrayado. Un identificador no puede comenzar por un dgito.
2. El compilador slo reconoce los primeros 32 caracteres de un
identificador, pero ste puede tener cualquier otro tamao mayor.
Aunque no es nada habitual generar identificadores tan largos, si
alguna vez as se hace hay que evitar que dos de ellos tengan
iguales los 32 primeros caracteres, porque entonces para el
compilador ambos identificadores sern el mismo.
12
Captulo 1. Lenguaje C.
3. Las
letras
de
los
identificadores
pueden
ser
maysculas
palabras
reservadas,
palabras
clave,
son
identificadores
auto
break
case
char
const
continue
default
do
double
else
enum
extern
float
for
(goto)
if
int
long
register
return
short
signed
sizeof
static
struct
switch
typedef
union
unsigned
void
volatile
while
13
Errores y depuracin.
No es extrao que, al terminar de redactar el cdigo de un programa, al
iniciar la compilacin, el compilador deba abortar su proceso y avisar de
que existen errores. El compilador ofrece algunos mensajes que
clarifican frecuentemente el motivo del error, y la correccin de esos
errores no comporta habitualmente demasiada dificultad. A esos errores
sintcticos los llamamos errores de compilacin. Ejemplo de estos
errores pueden ser que se haya olvidado terminar una sentencia con el
punto y coma, o que falte una llave de cierre de bloque de sentencias
compuestas, o sobre un parntesis, o se emplee un identificador mal
construido
Otras veces, el compilador no haya error sintctico alguno, y compila
correctamente el programa, pero luego, en la ejecucin, se producen
errores que acaban por abortar el proceso. A esos errores los llamamos
errores de ejecucin. Un clsico ejemplo de este tipo de errores es
forzar al ordenador a realizar una divisin por cero, o acceder a un
espacio de memoria para el que no estamos autorizados. Esos errores
tambin suelen ser sencillos de encontrar, aunque a veces, como no son
debidos a fallos sintcticos ni de codificacin del programa sino que
pueden estar ocasionados por el valor que en un momento concreto
adquiera una variable, no siempre son fcilmente identificables, y en
esos casos puede ser necesario utilizar los depuradores que muchos
entornos de programacin ofrecen.
Y puede ocurrir tambin que el cdigo no tenga errores sintcticos, y por
14
Captulo 1. Lenguaje C.
Recapitulacin.
En este captulo hemos introducido los conceptos bsicos iniciales para
poder comenzar a trabajar en la programacin con el lenguaje C. Hemos
presentado el entorno habitual de programacin y hemos visto un
primer programa en C (sencillo, desde luego) que nos ha permitido
mostrar las partes bsicas del cdigo de un programa: las directivas de
preprocesador, los comentarios, la funcin principal, las sentencias
(simples o compuestas) y las llaves que agrupan sentencias. Y hemos
aprendido las reglas bsicas de creacin de identificadores.
15
16
Captulo 1. Lenguaje C.
Para compilar y ejecutar basta con elegir la opcin Men Debug Run
o, como indica esa misma opcin, pulsar simultneamente las teclas
Control y F9. Una tercera opcin es pulsar el botn en forma de rayo
amarillo situado en la sexta posicin de la barra de botones.
17
18
CAPTULO 2
TIPOS DE DATOS Y VARIABLES EN C
Un tipo de dato define de forma explcita un conjunto de valores,
denominado dominio, sobre el cual se pueden realizar una serie de
operaciones. Un valor es un elemento del conjunto que hemos llamado
dominio. Una variable es un espacio de la memoria destinada al
almacenamiento de un valor de un tipo de dato concreto, referenciada
por un nombre. Son conceptos sencillos, pero muy necesarios para
saber exactamente qu se hace cuando se crea una variable en un
programa.
Un tipo de dato puede ser tan complejo como se quiera. Puede necesitar
un byte para almacenar cualquier valor de su dominio, o requerir de
muchos bytes.
Cada lenguaje ofrece una coleccin de tipos de datos, que hemos
llamado primitivos. Tambin ofrece herramientas para crear tipos de
dato distintos, ms complejos que los primitivos y ms acordes con el
tipo de problema que se aborde en cada momento.
Declaracin de variables.
Antes de ver los tipos de dato primitivos, conviene saber cmo se crea
una variable en C.
Toda variable debe ser declarada previa a su uso. Declarar una
variable es indicar al programa un identificador o nombre para esa
variable, y el tipo de dato para la que se crea esa variable.
La declaracin de variable tiene la siguiente sintaxis:
tipo var_1 [=valor1, var_2 = valor_2, , var_N = valor_N];
Donde tipo es el nombre del tipo de variable que se desea crear, y
var_1, es el nombre o identificador de esa variable.
Aclaracin a la notacin: en las reglas sintcticas de un lenguaje de
programacin, es habitual colocar entre corchetes ([]) aquellas partes
de la sintaxis que son optativas.
En este caso tenemos que en una declaracin de variables se pueden
declarar una o ms variables del mismo tipo, todas ellas separadas por
el operador coma. Al final de la sentencia de declaracin de variables
20
21
hasta
2 1.
7
TIPOS
RANGO DE VALORES
MENOR
MAYOR
SIGNO
-128
0
+127
+255
-32.768
0
-2.147.483.648
0
+32.767
+65.535
+2.147.483.647
4.294.967.295
char
short
long
-3.402923E+38
-1.7976931E+308
-1.2E+4932
+3.402923E+38
+1.7976931E+308
+1.2E+4932
22
23
24
25
los
Operador asignacin.
El operador asignacin permite al programador modificar los valores de
las variables y alterar, por tanto, el estado de la memoria del ordenador.
El carcter que representa al operador asignacin es el carcter =. La
forma general de este operador es
nombre_variable = expresin;
Donde expresin puede ser un literal, otra variable, o una combinacin
de variables, literales y operadores y funciones. Podemos definirlo como
una secuencia de operandos y operadores que unidos segn ciertas
reglas producen un resultado.
Este signo en C no significa igualdad en el sentido matemtico al que
estamos acostumbrados, sino asignacin. No puede llevar a equvocos
expresiones como la siguiente:
a = a + 1;
Ante esta instruccin, el procesador toma el valor de la variable a, lo
copia en un registro de la ALU donde se incrementa en una unidad, y
almacena (asigna) el valor resultante en la variable a, modificando por
ello el valor anterior de esa posicin de memoria. La expresin
comentada no es una igualdad de las matemticas, sino una orden para
incrementar en uno el valor almacenado en la posicin de memoria
reservada por la variable a.
26
Operadores aritmticos.
Los operadores aritmticos son:
1. Suma. El identificador de este operador es el carcter +. Este
operador es aplicable sobre cualquier variable primitiva de C. Si el
operador + se emplea como operador unario, entonces es el
operador de signo positivo.
27
28
los
29
30
x + y == z + t;
Con frecuencia interesar evaluar una expresin en la que se obtenga
verdadero o falso no solo en funcin de una relacin, sino de varias. Por
ejemplo, se podra necesitar saber (obtener verdadero o falso) si el valor
de una variable concreta est entre dos lmites superior e inferior. Para
eso necesitamos concatenar dos relacionales. Y eso se logra mediante
los operadores lgicos.
Un error frecuente (y de graves consecuencias en la ejecucin del
programa) al programar en C C++ es escribir el operador asignacin
(=), cuando lo que se pretenda escribir era el operador relacional
igual que (==). El C C++ la expresin variable = valor; ser
siempre verdadera si valor es distinto de cero. Si colocamos una
asignacin donde desebamos poner el operador relacional igual que,
tendremos dos consecuencias graves: se cambiar el valor de la variable
colocada a la izquierda del operador asignacin (cosa que no queramos)
31
a && b
a || b
!a
F
F
V
V
F
V
F
V
F
F
F
V
F
V
V
V
V
V
F
F
32
float pi = 3.141596;
long x = 0, y = 100, z =1234;
3 * pi < y && (x + y) * 10 <= z / 2;
3 * pi < y || (x + y) * 10 <= z / 2;
3 * pi < y && !((x + y) * 10 <= z / 2);
// FALSO
// VERDADERO
// VERDADERO
33
34
binario
a
b
a_and_b
a_or_b
a_xor_b
1010
0110
0010
1110
1100
1011
0111
0011
1111
1100
1100
1000
1000
1100
0100
1101
1001
1001
1101
0100
hex.
dec.
ABCD
6789
2389
EFCD
CC44
43981
26505
9097
61389
52292
short
short
short
short
int
int
int
int
a = 0xABCD, b = 0x6789;
a_and_b = a & b;
a_or_b = a | b;
a_xor_b = a ^ b;
35
0
0
1
1
and
or
xor
0
0
0
1
0
1
1
1
0
1
1
0
0
1
0
1
tantos
bits
puestos
cero
desplazamiento.
Por ejemplo, si tenemos el siguiente cdigo:
short int var1 = 0x7654;
short int var2 = var1 << 3;
36
como
indique
el
la
izquierda,
entonces
ya habremos
perdido
esa
37
var2 = var1 << 1;: estado de memoria para la variable var2 ser
FFF2, que es la codificacin del entero -14.
6. Operador desplazamiento a derecha. Su identificador es la cadena
>>. Es un operador binario, que realiza un desplazamiento de
todos los bits de la variable o valor literal sobre la que se aplica un
nmero dado de posiciones hacia la derecha. Los bits ms a la
derecha (los menos significativos) se pierden; a la izquierda se van
introduciendo tantos bits como indique el desplazamiento. En esta
ocasin, el valor de los bits introducidos por la izquierda depender
del
signo
del
entero
sobre
el
que
se
aplica
el
operador
derecha
est
declarado
como
unsigned,
38
Operadores compuestos.
Ya se ha visto que una expresin de asignacin en C trae, a su izquierda
(Lvalue), el nombre de una variable y, a su derecha (Rvalue) una
expresin a evaluar, o un literal, o el nombre de otra variable. Y ocurre
frecuentemente que la variable situada a la izquierda forma parte de la
expresin de la derecha. En estos casos, y si la expresin es sencilla,
todos los operadores aritmticos y los operadores a nivel de bit binarios
(exceptuando,
por
tanto,
los
operadores
de
signo,
incremento,
x
x
x
x
x
x
x
x
x
x
+= y;
-= y;
*= y;
/= y;
%= y;
>>= y;
<<= y;
&= y;
|= y;
^= y;
es
es
es
es
es
es
es
es
es
es
lo
lo
lo
lo
lo
lo
lo
lo
lo
lo
mismo
mismo
mismo
mismo
mismo
mismo
mismo
mismo
mismo
mismo
que
que
que
que
que
que
que
que
que
que
decir
decir
decir
decir
decir
decir
decir
decir
decir
decir
x
x
x
x
x
x
x
x
x
x
=
=
=
=
=
=
=
=
=
=
x
x
x
x
x
x
x
x
x
x
+ y;
y;
* y;
/ y;
% y;
>> y;
<< y;
& y;
| y;
^ y;
39
Operador sizeof.
Ya sabemos el nmero de bytes que ocupan en memoria todas las
variables de tipo de dato primitivo en C: 1 byte las variables tipo char;
2 las de tipo short; 4 las de tipo long y float; 8 las de tipo double, y
10 las variables long double.
Pero ya se ha dicho que adems de estos tipos primitivos, C permite la
definicin de otros tipos diferentes, combinacin de esos primitivos. Y
los tamaos de esos tipos definidos pueden ser tan diversos como
diversas pueden ser las definiciones de esos nuevos tipos. no es extrao
trabajar con tipos cuyas variables ocupan 13 bytes, 1045, cualquier
otro tamao.
C ofrece un operador que devuelve la cantidad de bytes que ocupa una
variable o un tipo de dato concreto. El valor devuelto es tomado como
un entero, y puede estar presente en cualquier expresin de C. Es el
operador sizeof. Su sintaxis es:
sizeof(nombre_variable); sizeof(nombre_tipo_de_dato);
ya que se puede utilizar tanto con una variable concreta como
indicndole al operador el nombre del tipo de dato sobre el que
queramos conocer su tamao. No es vlido utilizar este operador
indicando entre parntesis el tipo de dato void: esa instruccin dara
error en tiempo de compilacin.
Con este operador aseguramos la portabilidad, al no depender la
aplicacin del tamao del tipo de datos de la mquina que se vaya a
usar. Aunque ahora mismo no se ha visto en este texto qu utilidad
puede tener en un programa conocer, como dato de clculo, el nmero
40
de bytes que ocupa una variable, la verdad es que con frecuencia ese
dato es muy necesario.
Ejemplo: Podemos ver el tamao de los diferentes tipos de datos
primitivos de C. Basta teclear este cdigo en nuestro editor:
#include <stdio.h>
main()
{
printf("int
printf("char
printf("short
printf("long
printf("float
printf("double
}
=>
=>
=>
=>
=>
=>
%d\n",sizeof(int));
%d\n",sizeof(char));
%d\n",sizeof(short));
%d\n",sizeof(long));
%d\n",sizeof(float));
%d\n",sizeof(double));
41
en
una
expresin
donde
intervienen
elementos
de
42
43
al tipo
Posibles prdidas
char
signed char
short
char
long int
char
long int
short
float
int
double
float
long double
double
44
de dar,
compiladores
este
no
tipo
de
interrumpen
conversiones
el
trabajo
compilacin
Los
cuando
expresin
puede
tener
diferentes
interpretaciones.
el
45
46
()
!
.*
[]
~
>
==
++
ID
.
--
DI
&
ID
->*
<<
->
ID
ID
ID
>>
>=
<
ID
<=
ID
!=
&
ID
ID
ID
&&
ID
||
DI
?:
ID
+=
-=
*=
/=
%=
&=
|=
<<=
>>=
DI
ID
47
48
situaciones
problemas
donde
jugar
con
las
reglas
de
49
modo
de
definir
constantes
es
mediante
la
directiva
de
50
un
ejemplo
para
comprobar
intercambio:
short int variable1 = 3579;
short int variable2 = 2468;
51
que
realmente
realiza
el
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1
1
0
1
1
1
0
1
1
0
0
0
0
0
0
1
1
0
1
1
1
1
0
1
1
1
0
1
1
0
1
1
0
1
1
1
0
1
1
0
1
0
1
1
0
0
1
1
0
1
1
0
1
1
0
1
0
1
1
0
Ayudas On line.
Muchos editores y compiladores de C cuentan con ayudas en lnea
abundante. Todo lo referido en este captulo puede encontrarse en ellas.
Es buena prctica de programacin saber manejarse por esas ayudas,
que llegan a ser muy voluminosas y que gozan de buenos ndices para
lograr encontrar el auxilio necesario en cada momento.
Recapitulacin.
Despus de estudiar este captulo, ya sabemos crear y operar con
nuestras variables. Tambin conocemos muchos de los operadores
definidos en C. Con todo esto podemos realizar ya muchos programas
sencillos.
Si conocemos el rango o dominio de cada tipo de dato, sabemos
tambin de qu tipo conviene que sea cada variable que necesitemos. Y
estaremos vigilantes en las operaciones que se realizan con esas
variables, para no sobrepasar ese dominio e incurrir en un overflow.
Tambin hemos visto las reglas para combinar, en una expresin,
variables y valores de diferente tipo de dato. Es importante conocer bien
52
1.
#include <stdio.h>
void main(void)
{
signed long a, b;
signed long sum, res, pro, coc, mod;
printf("Introduzca el valor del 1er. operando ... ");
scanf("%ld",&a);
printf("Introduzca el valor del 2do. operando ... ");
scanf("%ld",&b);
// Clculos
sum =
res =
pro =
coc =
mod =
a
a
a
a
a
+
*
/
%
b;
b;
b;
b;
b;
53
2.
#include <stdio.h>
void main(void)
{
float a, b;
float sum, res, pro, coc;
printf("Introduzca el valor del 1er. operando ... ");
scanf("%f",&a);
printf("Introduzca el valor del 2do. operando ... ");
scanf("%f",&b);
// Clculos
sum = a + b;
res = a - b;
pro = a * b;
coc = a / b;
// mod = a % b; : esta operacin no est permitida
// Mostrar resultados por pantalla.
printf("La suma es igual a %f\n", sum);
printf("La resta es igual a %f\n", res);
printf("El producto es igual a %f\n", pro);
printf("El cociente es igual a %f\n", coc);
}
En este caso se ha tenido que omitir la operacin mdulo, que no est
definida para valores del dominio de los nmeros de coma flotante. Al
igual que en el ejemplo anterior, se debera verificar (an no se han
presentado las herramientas que lo permiten), antes de realizar el
cociente, que el divisor era diferente de cero.
54
3.
#include <stdio.h>
void main(void)
{
float a, b;
printf("Introduzca el valor del parmetro a ... ");
scanf("%f",&a);
printf("Introduzca el valor del parmetro b ... ");
scanf("%f",&b);
printf("x = %f",-b / a);
}
De nuevo sera ms correcto el programa si, antes de realizar el
cociente, se verificase que la variable a es distinta de cero.
4.
#include <stdio.h>
void main(void)
{
short x;
long cuadrado, cubo;
printf("Introduzca un valor ... ");
scanf("%hi",&x);
cuadrado = (long)x * x;
cubo = cuadrado * x;
printf("El cuadrado de %hd es %li\n",x, cuadrado);
printf("El cubo de %hd es %li\n",x, cubo);
}
Es importante crear una presentacin cmoda para el usuario. No
tendra sentido comenzar el programa por la funcin scanf, porque en
ese caso el programa comenzara esperando un dato del usuario, sin
aviso previo que le indicase qu es lo que debe hacer. En el siguiente
55
tema se presenta con detalle las dos funciones de entrada y salida por
consola.
La variable x es short. Al calcular el valor de la variable cuadrado
forzamos el tipo de dato para que el valor calculado sea long y no se
pierda informacin en la operacin. En el clculo del valor de la variable
cubo no es preciso hacer esa conversin, porque ya la variable cuadrado
es de tipo long. En esta ltima operacin no queda garantizado que no
se llegue a un desbordamiento: cualquier valor de x mayor de 1290
tiene un cubo no codificable con 32 bits. Se puede probar qu ocurre
introduciendo valores mayores que ste indicado.
5.
#include <stdio.h>
void main(void)
{
double b, h, S;
printf("Introduzca la base ... ");
scanf("%lf",&b);
printf("Introduzca la altura ... ");
scanf("%lf",&h);
S = b * h / 2;
printf("La superficie del triangulo de ");
printf("base %.2lf y altura %.2lf ",b,h);
printf("es %.2lf",S);
}
Las variables se han tomado double. As no se pierde informacin en la
operacin cociente. Puede probar a declarar las variables como de tipo
short, modificando tambin algunos parmetros de las funciones de
entrada y salida por consola:
void main(void)
{
short b, h, S;
56
6.
#include <stdio.h>
#define PI 3.14159
void main(void)
{
signed short int r;
double l, S;
const double pi = 3.14159;
printf("Indique el valor del radio ... ");
scanf("%hd",&r);
printf("La longitud de la circunferencia");
printf(" cuyo radio es %hd",r);
l = 2 * pi * r;
printf(" es %lf. \n",l);
printf("La superficie de la circunferencia");
printf(" cuyo radio es %hd",r);
S = PI * r * r;
printf(" es %lf. \n",S);
}
En este ejemplo hemos mezclado tipos de dato. El radio lo tomamos
como entero. Luego, en el clculo de la longitud l, como la expresin
tiene el valor de la constante pi, que es double, se produce una
conversin implcita de tipo de dato, y el resultado final es double.
57
7.
#include <stdio.h>
void main(void)
{
double fahr, cels;
printf("Temperatura en grados Fahrenheit ... ");
scanf("%lf",&fahr);
cels = (5 / 9) * (fahr - 32);
printf("La temperatura en grados Celsius ");
printf("resulta ... %lf.",cels);
}
Tal y como est escrito el cdigo parece que todo ha de ir bien. Si
ensayamos el programa con la entrada en grado Fahrenheit igual a 32,
entonces la temperatura en Celsius resulta 0, que es correcto puesto
que esas son las temperaturas, en las dos tablas, en las que se derrite
el hielo.
Pero, y si probamos con otra entrada?... Tambin da 0! Por qu?
Pues porque (5 / 9) es una operacin cociente entre dos enteros, cuyo
resultado es un entero: el truncado, es decir, el mayor entero menor
que el resultado de la operacin; es decir, 0.
Cmo se debera escribir la operacin?
58
8.
Una
operacin
que
tiene
uso
en
algunas
aplicaciones
de
tipo
esas
posiciones,
pero
sin
perder
los
bits
menos
59
1010 1011
5
0111 1001
8 * 2 5
0000 0000
0111 1001
1100 1101
1010 0000
= 11
0001 0101
1011 0101
9.
int a = 10, b = 5, c, d;
float x = 10.0 ,y = 5.0, z, t, v;
c = a/b;
d = x/y;
z = a/b;
t = (1 / 2) * x;
v = (1.0 / 2) * x;
60
10.
#include <stdio.h>
void main(void)
{
char a = 127;
a++;
printf("%hd", a);
}
La salida que se obtiene con este cdigo es -128. Intente justificar por
qu ocurre. No se preocupe si an no conoce el funcionamiento de la
funcin printf(). Verdaderamente la variable a ahora vale -128. Por
qu?
Si el cdigo que se ejecuta es el siguiente, explique la salida obtenida.
Sabra adivinar qu va a salir por pantalla antes de ejecutar el
programa?
#include <stdio.h>
void main(void)
{
short sh = 0x7FFF;
long ln = 0x7FFFFFFF;
printf("\nEl valor inicial de sh es ... %hd", sh);
printf("\nEl valor inicial de ln es ... %ld", ln);
sh++;
ln++;
printf("\nEl valor final de sh es ... %hd", sh);
printf("\nEl valor final de ln es ... %ld", ln);
}
11.
#include <stdio.h>
void main(void)
{
61
12.
#include <stdio.h>
void main(void)
{
char a = 'X', b = 'Y';
printf("\nvalor (caracter) de a: %c", a);
printf("\nvalor (caracter) de b: %c", b);
printf("\nvalor de a (en base 10): %hd", a);
printf("\nvalor de b (en base 10): %hd", b);
printf("\nvalor (caracter) de a + b: %c", a + b);
printf("\nvalor (en base 10) de a + b: %hd", a + b);
13.
(caracter) de a: X
(caracter) de b: Y
de a (en base 10): 88
de b (en base 10): 89
(caracter) de a + b:
(en base 10) de a + b: 177
(caracter) de a + b + 5:
(en base 10) de a + b + 5: 182
V = 4 3 r3
62
del
cociente,
sino
un
nuevo
valor
entero,
que
es
14.
#include <stdio.h>
#include <math.h>
void main(void)
{
double aureo;
printf("Nmero AUREO: tal que x + 1 = x * x.\n");
// Clculo del nmero Aureo
// x^2 = x + 1 ==> x^2 - x - 1 = 0 ==> x = (1 + sqrt(5)) / 2.
aureo = (1 + sqrt(5)) / 2;
printf("El nmero AUREO es .. %lf\n",aureo);
printf("aureo + 1 ........... %lf\n",aureo + 1);
printf("aureo * aureo ....... %lf\n", aureo * aureo);
}
La funcin sqrt() est definida en la biblioteca math.h. Calcula el valor
de la raz cuadrada de un nmero. Espera como parmetro una variable
de tipo double, y devuelve el valor en este formato o tipo de dato.
El ejercicio es muy sencillo. La nica complicacin (si se le puede llamar
complicacin a esta trivialidad) es saber cmo se calcula el nmero
aureo a partir de la definicin aportada. Muchas veces el problema de la
programacin no est en el lenguaje, sino en saber expresar una
solucin viable de nuestro problema.
63
15.
Vy = V0 sen( ) g t
x = V0 t cos( )
y = V0 t sen( )
64
1
g t2
2
CAPTULO 3
FUNCIONES DE ENTRADA Y SALIDA
POR CONSOLA
Hasta el momento, hemos presentado las sentencias de creacin y
declaracin de variables. Tambin hemos visto multitud de operaciones
que se pueden realizar con las diferentes variables y literales. Pero an
no sabemos cmo mostrar un resultado por pantalla. Y tampoco hemos
aprendido todava a introducir informacin, para un programa en
ejecucin, desde el teclado.
El objetivo de este breve captulo es iniciar en la comunicacin entre el
programa y el usuario.
Lograr que el valor de una variable almacenada de un programa sea
mostrado por pantalla sera una tarea compleja si no fuese porque ya
ANSI C ofrece funciones que realizan esta tarea. Y lo mismo ocurre
cuando el programador quiere que sea el usuario quien teclee una
entrada durante la ejecucin del programa.
66
introducimos
ahora
otra
instruccin
con
la
funcin
printf
67
68
de
presentacin
determinado.
Ese
carcter
esos
cadena
de
texto
de
la
funcin
printf
puede
tener
tantos
69
70
71
long int a
printf(La
printf(La
printf(La
= 123, b
variable
variable
variable
=
a
b
c
4567, c = 135790;
vale ... %6li.\n,a);
vale ... %6li.\n,b);
vale ... %6li.\n,c);
72
sqrt(2);
de dos vale
de dos vale
de dos vale
de dos vale
de dos vale
de dos vale
de dos vale
de dos vale
de dos vale
%lf\n",raiz_2);
%12.1lf\n",raiz_2);
%12.3lf\n",raiz_2);
%12.5lf\n",raiz_2);
%12.7lf\n",raiz_2);
%12.9lf\n",raiz_2);
%12.11lf\n",raiz_2);
%5.7lf\n",raiz_2);
%012.4lf\n",raiz_2);
Raiz
Raiz
Raiz
Raiz
Raiz
Raiz
Raiz
Raiz
Raiz
de
de
de
de
de
de
de
de
de
dos
dos
dos
dos
dos
dos
dos
dos
dos
vale
vale
vale
vale
vale
vale
vale
vale
vale
1.414214
1.4
1.414
1.41421
1.4142136
1.414213562
1.41421356237
1.4142136
0000001.4142
73
74
75
scanf
es
frecuente
en
programadores
noveles.
de
Recapitulacin.
Hemos presentado el uso de las funciones printf() y scanf(), ambas
declaradas en el archivo de cabecera stdio.h. Cuando queramos hacer
uno de una de las dos funciones, o de ambas, deberemos indicarle al
programa con la directiva de preprocesador #include <stdio.h>.
El uso de ambas funciones se aprende en su uso habitual. Los ejercicios
del
captulo
anterior
pueden
ayudar,
76
ahora
que
ya
las
hemos
Ejercicios.
16.
#include <stdio.h>
void main(void)
{
unsigned char ch;
printf("Introduzca un carcter por teclado ... ");
scanf("%c",&ch);
printf("El carcter introducido ha sido %c\n",ch);
printf("Su cdigo ASCII es el %hd", ch);
}
Primero mostramos el carcter introducido con el especificador de tipo
%c: as muestra el carcter por pantalla. Y luego mostramos el mismo
valor de la variable ch con el especificador %hd, es decir, como entero
corto, y entonces nos muestra el valor numrico de ese carcter.
17.
#include <stdio.h>
void main(void)
{
signed long int sli;
signed short int ssi;
printf("Introduzca un valor negativo para sli ... ");
scanf("%ld",&sli);
printf("Introduzca un valor negativo para ssi ... ");
scanf("%hd",&ssi);
77
78
sino
32
bits
de
valor
positivo
codificado
en
binario:
79
18.
#include <stdio.h>
#include <math.h>
void main(void)
{
double a = M_PI;
printf(" 1.
printf(" 2.
printf(" 3.
printf(" 4.
printf(" 5.
printf(" 6.
printf(" 7.
printf(" 8.
printf(" 9.
printf("10.
printf("11.
printf("12.
printf("13.
printf("14.
printf("15.
El
El
El
El
El
El
El
El
El
El
El
El
El
El
El
valor
valor
valor
valor
valor
valor
valor
valor
valor
valor
valor
valor
valor
valor
valor
de
de
de
de
de
de
de
de
de
de
de
de
de
de
de
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
es
es
es
es
es
es
es
es
es
es
es
es
es
es
es
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
%20.1lf\n",a);
%20.2lf\n",a);
%20.3lf\n",a);
%20.4lf\n",a);
%20.5lf\n",a);
%20.6lf\n",a);
%20.7lf\n",a);
%20.8lf\n",a);
%20.9lf\n",a);
%20.10lf\n",a);
%20.11lf\n",a);
%20.12lf\n",a);
%20.13lf\n",a);
%20.14lf\n",a);
%20.15lf\n",a);
}
La salida que ofrece por pantalla es la siguiente:
1. El valor de Pi es ... 3.1
2. El valor de Pi es ... 3.14
80
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
El
El
El
El
El
El
El
El
El
El
El
El
El
valor
valor
valor
valor
valor
valor
valor
valor
valor
valor
valor
valor
valor
de
de
de
de
de
de
de
de
de
de
de
de
de
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
Pi
es
es
es
es
es
es
es
es
es
es
es
es
es
...
...
...
...
...
...
...
...
...
...
...
...
...
3.142
3.1416
3.14159
3.141593
3.1415927
3.14159265
3.141592654
3.1415926536
3.14159265359
3.141592653590
3.1415926535898
3.14159265358979
3.141592653589793
19.
#include <stdio.h>
#include <stdlib.h>
void main(void)
{
randomize();
for(int i = 1 ; i <= 20 ; i++)
printf("%2d. %10.5lf\n",i,
(double)random(1000000) / random (10000));
}
81
57.21568
62.41973
147.16501
120.04998
215.02813
52.82802
260.75406
721.83456
9.85598
150.42073
7.11266
78.85494
73.41685
196.21048
88.43795
192.40674
315.92087
50.11689
7.16849
187.26519
82
<stdio.h>
Salida
Ejemplo
Carcter
doi
392
7235
610
7fa
7FA
392.65
3.9265e2
3.9265E2
392.65
392.65
Cadena de caracteres
sample
B800:0000
83
modificador
significado
ancho
significado
num
.precisin significado
.num
flags
significado
blanco
84
argumento(s)
Parmetro(s) opcional(es) que contiene(n) los datos que se insertarn
en el lugar de los % etiquetas especificados en los parmetros del
formato. Debe haber el mismo nmero de parmetros que de etiquetas
de formato.
Valor de retorno de printf.
Si tiene xito representa el nmero total de caracteres impresos. Si hay
un error, se devuelve un nmero negativo.
Ejemplo.
/* printf: algunos ejemplos de formato*/
#include <stdio.h>
void main(void)
{
printf("Caracteres: %c %c \n", 'a', 65);
printf("Decimales: %d %ld\n", 1977, 650000);
printf("Precedidos de blancos: %10d \n", 1977);
printf("Precedidos de ceros: %010d \n", 1977);
printf("Formato: %d %x %o %#x %#o\n",100,100,100,100,100);
printf("float: %4.2f %+.0e %E\n", 3.1416, 3.1416, 3.1416);
printf("Ancho: %*d \n", 5, 10);
printf("%s \n", "Mi mam me mima");
}
Y la salida:
Caracteres: a A
Decimales: 1977 650000
Precedidos con blancos:
1977
Precedidos con ceros: 0000001977
Formato: 100 64 144 0x64 0144
float: 3.14 +3e+000 3.141600E+000
Ancho:
10
Mi mam me mima
85
86
CAPTULO 4
ESTRUCTURAS DE CONTROL
El lenguaje C pertenece a la familia de lenguajes del paradigma de la
programacin estructurada. En este captulo quedan recogidas las reglas
de la programacin estructurada y, en concreto, las reglas sintcticas
que se exige en el uso del lenguaje C para el diseo de esas estructuras.
El
objetivo
del
captulo
es
aprender
crear
estructuras
Introduccin.
Las reglas de la programacin estructurada son:
1. Todo programa consiste en una serie de acciones o sentencias que
Instruccin 1
Instruccin 2
Instruccin N
88
Conceptos previos.
La regla 3 de la programacin estructurada habla de tres estructuras de
control: la secuencia, la seleccin y la repeticin. Nada nuevo hay ahora
que decir sobre la secuencia, que vendra esquematizada en la figura
4.1. En la figura 4.2. se esquematizan diferentes posibles estructuras de
seleccin; y en la figura 4.3. las dos estructuras bsicas de repeticin.
Las dos formas que rompen el orden secuencial de ejecucin de
sentencias son:
1. Instruccin condicional: Se evala una condicin y si se cumple
se transfiere el control a una nueva direccin indicada por la
instruccin.
2. Instruccin incondicional. Se realiza la transferencia a una nueva
direccin sin evaluar ninguna condicin (por ejemplo, llamada a una
funcin).
En ambos casos la transferencia del control se puede realizar con o sin
retorno: en el caso de que exista retorno, despus de ejecutar el bloque
de instrucciones de la nueva direccin se retorna a la direccin que
sucede a la que ha realizado el cambio de flujo.
89
No
Condicin
No
Instruccin
Instruccin
Instruccin
Condicin
Instruccin
Instruccin
Bifurcacin abierta
Bifurcacin cerrada
No
Condicin
Instruccin
Instruccin
S
No
Instruccin
Condicin
Instruccin
Estructura while
Estructura do - while
sentencia
que
est
precedida
por
la
estructura
de
control
90
91
92
primer if */
segundo if */
tercer if */
alternativa al tercer if */
alternativa al 2 if */
alternativa al primer if */
C
S
S
S
S1
C3
C2
C1
No
No
No
S2
S3
S4
93
#include <stdio.h>
void main(void)
{
short D, d;
char opcion;
printf("Programa para dividir dos enteros...\n");
printf("Introduzca el dividendo ... ");
scanf("%hd",&D);
printf("Introduzca el divisor ... ");
scanf("%hd",&d);
if(d != 0)
printf("%hu / %hu = %hu", D, d, D / d);
else
{
printf("No se puede dividir por cero.\n");
printf("Introducir otro denominador (s/n)?");
opcion = getchar();
if(opcion == 's')
{
printf("\nNuevo denominador ... ");
scanf("%hd",&d);
if(d != 0)
printf("%hu / %hu = %hu", D, d, D/d);
else
printf("De nuevo ha introducido 0.");
}
}
}
La funcin getchar() est definida en la biblioteca stdio.h. Esta funcin
espera a que el usuario pulse una tecla del teclado y, una vez pulsada,
devuelve el cdigo ASCII de la tecla pulsada.
En este ejemplo hemos llegado hasta un tercer nivel de anidacin.
Escala if - else if
Cuando se debe elegir entre una lista de opciones, y nicamente una de
ellas ha de ser vlida, se llega a producir una concatenacin de
condiciones de la siguiente forma:
if(condicin1) setencia1;
else
{
if(condicin2) sentencia2;
else
{
if(condicin3) sentencia3;
94
else sentencia4;
}
El flujograma recogido en la Figura 4.4. representara esta situacin sin
ms que intercambiar los caminos de verificacin de las condiciones C1,
C2 y C3 recogidas en l (es decir, intercambiando los rtulos de S y
de No).
Este tipo de anidamiento se resuelve en C con la estructura else if, que
permite una concatenacin de las condicionales. Un cdigo como el
antes escrito quedara:
if(condicin1) sentencia1;
else if (condicin2) sentencia2;
else if(condicin3) sentencia3;
else sentencia4;
Como se ve, una estructura as anidada se escribe con mayor facilidad y
expresa tambin ms claramente las distintas alternativas. No es
necesario
que,
en
un
anidamiento
de
sentencias
condicionales,
95
96
97
switch(a)
{
case 1:
case 2:
case 3:
default:
}
printf(UNO\t);
printf(DOS\t);
printf(TRES\t);
printf(NINGUNO\n);
TRES
NINGUNO.
C
No
No
No
a=3
a=2
a=1
UNO
DOS
TRES
NINGUNO
98
C
No
No
No
a=3
a=2
a=1
UNO
break;
DOS
break;
TRES
break;
NINGUNO
F
99
switch(nota)
{
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
case 10:
default:
printf(SUSPENSO);
break;
printf(APROBADO);
break;
printf(NOTABLE);
break;
printf(SOBRESALIENTE);
break;
printf(MATRCULA DE HONOR);
break;
printf(Nota introducida errnea.);
}
No se puede poner una etiqueta case fuera de un bloque switch. Y
tampoco tiene sentido colocar instrucciones dentro del bloque switch
antes de aparecer el primer case: eso supondra un cdigo que jams
podra llegar a ejecutarse. Por eso, la primera sentencia de un bloque
switch debe estar ya etiquetada.
Se pueden anidar distintas estructuras switch.
El ejemplo de las notas, que ya se mostr al ejemplificar una anidacin
de sentencias ifelseif puede servir para comentar una caracterstica
importante de la estructura switch. Esta estructura no admite, en sus
distintas entradas case, ni expresiones lgicas o relacionales, ni
expresiones aritmticas, sino literales. La nica relacin aceptada es,
pues, la de igualdad. Y adems, el trmino de la igualdad es siempre
entre una variable o una expresin entera (la del switch) y valores
literales: no se puede indicar el nombre de una variable. El programa de
las notas, si la variable nota hubiese sido de tipo float, como de hecho
quedo definida cuando se resolvi el problema con los condicionales if
elseif no tiene solucin posible mediante la estructura switch.
100
Un ejercicio planteado.
Planteamos ahora un ejercicio a resolver: solicite del usuario que
introduzca por teclado un da, mes y ao, y muestre entonces por
pantalla el da de la semana que le corresponde.
La resolucin de este problema es sencilla si se sabe el cmo. Sin un
correcto algoritmo que nos permita saber cmo procesar la entrada no
podemos hacer nada.
Por lo tanto, antes de intentar implementar un programa que resuelva
este problema, ser necesario preguntarse si somos capaces de
resolverlo sin programa. Porque si no sabemos hacerlo nosotros, menos
sabremos explicrselo a la mquina.
Buscando en Internet he encontrado lo siguiente: Para saber a qu da
de la semana corresponde una determinada fecha, basta aplicar la
siguiente expresin:
d = (26 M 2 ) 10 + D + A + A 4 + C 4 2 C mod7
101
D = 15
C = 19
es decir martes!
Queda ahora hacer el programa que nos d la respuesta al da de la
semana en el que estamos. Har falta emplear dos veces la estructura
de control condicional if y una vez el switch. El programa queda como
sigue:
#include <stdio.h>
void main(void)
{
unsigned short D, mm, aaaa;
unsigned short M, A, C;
printf("Introduzca la fecha ... \n");
printf("Da ... ");
scanf("%hu", &D);
printf("Mes ... ");
scanf("%hu", &mm);
102
mm + 10;
(aaaa - 1) % 100;
(aaaa - 1) / 100;
mm - 2;
aaaa % 100;
aaaa / 100;
103
Estructura while.
104
105
encuentra
extensamente
documentado
este
otros
muchos
106
5
5
4
20
3
60
2
120
1
120
0
120
donde todo es igual excepto que ahora se ha hecho uso del operador
compuesto en la primera sentencia del bloque. Pero an se puede
compactar ms:
1. La condicin de permanencia ser verdad siempre que n no sea cero.
Y por definicin de verdad en C (algo es verdadero cuando es
107
dos
sentencias
simples
iteradas
en
el
bloque
pueden
Esta lnea de programa espera una entrada por teclado. Cuando sta se
produzca comprobar que hemos tecleado el carcter a minscula; de
no ser as, volver a esperar otro carcter.
Una forma ms sencilla fcil de ver el significado de esta ltima lnea
de cdigo vista sera expresarlo de la siguiente manera:
while(ch != a)
ch = getchar();
teclado.
No es necesario explicar el concepto de mximo comn divisor. S es
necesario en cambio explicar un mtodo razonable de plantear al
ordenador cmo se calcula ese valor: porque este ejemplo deja claro la
importancia
de
tener
no
slo
conocimientos
108
de
lenguaje
de
109
Estructura dowhile.
toda
solucin
un
problema
resuelto
con
una
110
PROCESO
S
repetir
No
111
opcion
S
S
C2
C1
No
No
112
Estructura for.
s1
e1
No
s3
F
s2
113
Por ejemplo, veamos un programa que muestra por pantalla los enteros
pares del 1 al 100:
#include <stdio.h>
void main(void)
{
short i;
for(i = 2 ; i <= 100 ; i += 2)
printf("%5hd",i);
}
114
{
short i;
for(i = 2 ; i <= 100 ; i += 2)
{
printf("%5hd",i);
if(i % 10 == 0) printf("\n");
}
}
115
116
}
printf("Ha introducido el entero par %hd",num);
printf(" despus de %hd impares. ",i - 1);
}
i 1
num
C1 num mod2 = 0
i i +1
C1
i 1
i i +1
num
C1
Con break
Sin break
117
C1 nummod2 0
suma 0
suma
num
suma + num
suma
C1
118
que repetir la ejecucin del cdigo hasta que se pulse la tecla a, y que
ocasionar la ejecucin del break.
Una sentencia continue dentro de un bloque de instrucciones de una
estructura de iteracin interrumpe la ejecucin de las restantes
sentencias iteradas y vuelve al inicio de las sentencias de la estructura
de control, si es que la condicin de permanencia as lo permite.
Veamos, por ejemplo, el programa antes presentado que muestra por
pantalla los 100 primeros enteros pares positivos. Otro modo de
resolver ese programa podra ser el siguiente:
#include <stdio.h>
void main(void)
{
short i;
for(i = 1 ;i <= 100 ; i++)
{
if(i % 2) continue;
printf("%4hd\t",i);
}
}
i i +1
C2
C1 i 100
C 2 i mod2 0
119
iteracin.
Si alguna vez ha programado con esta palabra, la recomendacin es que
se olvide de ella. Si nunca lo ha hecho, la recomendacin es que la
ignore.
Y, eso s: hay que acordarse de que esa palabra es clave en C: no se
puede generar un identificador con esa cadena de letras.
120
para
introducir
datos.
Cuando
el
usuario
121
Recapitulacin.
Hemos presentado las estructuras de control posibles en el lenguaje C.
Las
estructuras
condicionales
de
bifurcacin
abierta
cerrada,
Ejercicios.
En todos los ejercicios que planteamos a continuacin quiz ser
conveniente que antes de abordar la implementacin se intente disear
un algoritmo en pseudocdigo o mediante un diagrama de flujo. Si en
algn caso el
ste
en
las
pginas
122
del
manual
Fundamentos
de
20.
#include <stdio.h>
void main(void)
{
long suma = 0;
short i;
for(i = 2 ; i <= 200 ; i += 2)
suma += i;
printf("esta suma es ... %ld.\n",suma);
}
21.
#include <stdio.h>
void main(void)
{
long suma;
short i, num;
for(i = 0, suma = 0 ; ; i++)
{
printf("Introduzca nmero ... ");
scanf("%hd",&num);
suma += num;
if(num == 0) break;
}
if(i == 0) printf("No se han introducido enteros.");
else printf("La media es ... %.2f.",(float)suma / i);
}
123
22.
#include <stdio.h>
void main(void)
{
short suma;
short i, num;
for(num = 2 ; num < 10000 ; num++)
{
for(i = 1, suma = 0 ; i <= num / 2 ; i++)
if(num % i == 0) suma += i;
if(num == suma)
printf("En entero %hd es perfecto.\n",num);
}
}
124
23.
#include <stdio.h>
void main(void)
{
unsigned short int a0,a1,a2,a3;
printf("Introduzca cuatro enteros ... \n\n");
printf("Primer entero ... ");
scanf("%hu",&a0);
printf("Segundo entero ... ");
scanf("%hu",&a1);
printf("Tercer entero ... ");
scanf("%hu",&a2);
printf("Cuarto entero ... ");
scanf("%hu",&a3);
if(a0 > a1)
{
a0 ^= a1;
a1 ^= a0;
a0 ^= a1;
}
if(a0 > a2)
{
a0 ^= a2;
a2 ^= a0;
a0 ^= a2;
}
if(a0 > a3)
{
a0 ^= a3;
a3 ^= a0;
a0 ^= a3;
}
if(a1 > a2)
{
a1 ^= a2;
a2 ^= a1;
a1 ^= a2;
}
if(a1 > a3)
{
a1 ^= a3;
a3 ^= a1;
a1 ^= a3;
125
}
if(a2 > a3)
{
a2 ^= a3;
a3 ^= a2;
a2 ^= a3;
}
printf("\nOrdenados... \n");
printf("%hu <= %hu <= %hu <= %hu.", a0, a1, a2, a3);
}
24.
126
25.
#include <stdio.h>
void main(void)
{
127
signed long a;
unsigned long Test;
char opcion;
do
{
Test = 0x80000000;
printf("\n\nIndique el entero ... ");
scanf("%ld", &a);
while(Test)
{
Test & a ? printf("1") : printf("0");
Test >>= 1;
}
printf("\nDesea introducir otro entero? ... ");
do
opcion = getchar();
while (opcion != 's' && opcion != 'n');
}while(opcion == 's');
}
128
26.
#include <stdio.h>
#include <math.h>
void main(void)
{
unsigned long int numero, raiz;
unsigned long int div;
char chivato;
printf("Dame el numero que vamos a testear ... ");
scanf("%lu", &numero);
chivato = 0;
raiz = sqrt(numero);
for(div = 2 ; div <= raiz ; div++)
{
if(numero % div == 0)
{
chivato = 1;
break;
}
}
if(chivato == 1)
printf("El numero %lu es compuesto",numero);
else printf("El numero %lu es primo",numero);
}
129
n .
Entonces
tendremos:
n = a b <
n n =nn<n
130
27.
131
28.
#include <stdio.h>
#include <math.h>
void main(void)
{
float a, b, c;
double r;
// introduccin de parmetros...
printf("Introduzca los coeficientes...\n\n");
printf("a --> "); scanf("%f",&a);
printf("b --> "); scanf("%f",&b);
printf("c --> "); scanf("%f",&c);
// Ecuacin de primer grado...
if(a == 0)
{
// No hay ecuacin ...
if(b == 0) printf("No hay ecuacin.\n");
else // S hay ecuacin de primer grado
{
printf("Ecuacin de primer grado.\n");
printf("Tiene una nica solucin.\n");
printf("x1 --> %lf\n", -c / b);
}
}
// Ecuacin de segundo grado. Soluciones imaginarias.
else if ((r = b * b - 4 * a * c) < 0)
{
printf("Ecuacin sin soluciones reales.\n");
r = sqrt(-r);
132
29.
#include <stdio.h>
void main(void)
{
long int numero;
long int div;
printf("Nmero --> ");
scanf("%ld",&numero);
printf("\n\nLos divisores de %ld son:\n\t", numero);
printf("1, ");
for(div = 2 ; div <= numero / 2 ; div++)
if(numero % div == 0)
printf("%ld, ",div);
printf("%ld.",numero);
}
133
30.
2
6
2.
k =1 k
#include <stdio.h>
#include <math.h>
#define LIMITE 10000
void main(void)
{
double PI = 0;
for(int i = 1 ; i < LIMITE ; i++)
PI += 1.0 / (i * i);
PI *= 6;
PI = sqrt(PI);
printf("El valor de PI es ... %lf.",PI);
}
debe poner como 1.0 para que el resultado del cociente no sea un
entero igual a cero sino un valor double.
31.
( 1)
2k +1
k =0
134
#include <stdio.h>
#define LIMITE 100000
void main(void)
{
double PI = 0;
for(int i = 1 , e = 4 ; i < LIMITE ; i += 2 , e = -e)
PI += e / (double)i;
printf("El valor de PI es ... %lf.",PI);
}
32.
2 2 4 4 6 6 8 8
... .
1 3 3 5 5 7 7 9
De nuevo el clculo del valor del nmero pi. Estos ejercicios son muy
sencillos de buscar (Internet est llena de definiciones de propiedades
del nmero pi) y siempre es fcil comprobar si hemos realizado un buen
cdigo: basta ejecutarlo y comprobar si sale el famoso 3.14.
En esta ocasin, el cdigo podra tomar la siguiente forma:
135
#include <stdio.h>
#define LIMITE 100000
void main(void)
{
double PI = 2;
for(int num = 2 , den = 1, i = 1 ;
i < LIMITE ; i++ , i % 2 ? num+=2 : den+=2)
PI *= num / (double)den;
printf("El valor de PI es ... %lf.",PI);
}
33.
b = 1, c = 2;
trabajo
que
acaban
agotados,
por
lo
que
deciden
136
tres marineros.
Al levantarse por la maana procedieron a repartirse los cocos
que quedaban entre ellos cinco, no sobrando ahora ninguno.
Cuntos cocos haban recogido inicialmente? Mostrar todas las
soluciones posibles menores de 1 milln de cocos.
137
34.
pero,
con
el
transcurrir
del
tiempo,
el
error
un
algoritmo
que
solicite
al
usuario
una
fecha
138
35.
e=
1
1
1
1
+
+
+
+ ...
0! 1! 2! 3!
#include <stdio.h>
void main(void)
{
double p = 1, e = 1;
for(short n = 1 ; n < 100 ; n++)
{
p *= 1.0 / n;
e += p;
}
printf("El numero e es ... %20.17lf.",e);
}
36.
Juego
de
las
15
cerillas:
Participan
dos
jugadores.
139
#include <stdio.h>
void main(void)
{
// Con cuntas cerillas se va a jugar.
unsigned short cerillas;
// Cuntas cerillas se pueden quitar cada vez.
unsigned short quitar;
// Cerillas que quita el ganador y el perdedor.
unsigned short qp, qg;
char ganador;
do
{
printf("\n\n\nCon cuntas cerillas
se va a jugar ... ");
scanf("%hu",&cerillas);
if(cerillas == 0) break;
do
{
printf("\Cuntas cerillas pueden
quitarse de una vez... ");
scanf("%hu",&quitar);
if(quitar >= cerillas)
printf("No pueden quitarse tantas
cerillas.\n");
}while(quitar >= cerillas);
qg = (cerillas - 1) % (quitar + 1);
// MOSTRAR CERILLAS ...
printf("\n");
for(short i = 1 ; i <= cerillas ; i++)
{
printf(" |");
if(!(i % 30))printf("\n\n");
}
printf("\n");
// Fin de MOSTRAR CERILLAS
if(qg)
{
printf("\nComienza la mquina...");
printf("\nMquina Retira %hu cerillas...
\n", qg);
140
cerillas -= qg;
// MOSTRAR CERILLAS ...
printf("\n");
for(short i = 1 ; i <= cerillas ; i++)
{
printf(" |");
if(!(i % 30))printf("\n\n");
}
printf("\n");
}
// FIN DE MOSTRAR CERILLAS
else printf("\nComienza el jugador...");
while(cerillas != 1)
{
do
{
printf("\nCerillas que retira el
jugador ... ");
scanf("%hu",&qp);
if(qp > quitar)
printf("\nNo puede quitar mas
de %hu.\n",quitar);
}while(qp > quitar);
cerillas -= qp;
// MOSTRAR CERILLAS ...
printf("\n");
for(short i = 1 ; i <= cerillas ; i++)
{
printf(" |");
if(!(i % 30)) printf("\n\n");
}
printf("\n");
// Fin de MOSTRAR CERILLAS
if(cerillas == 1)
{
ganador = 'j';
break;
}
qg = quitar - qp + 1;
printf("\nLa mquina retira %hu
cerillas.\n",qg);
cerillas -= qg;
// MOSTRAR CERILLAS ...
printf("\n");
for(short i = 1 ; i <= cerillas ; i++)
{
printf(" |");
if(!(i % 30))printf("\n\n");
}
printf("\n");
141
37.
1 = 1
3 = ( + 1) ( 1)
=1+1
= 1+
y otras
1
=1+
1
1
1+
=1+
1+
...
1
1+
142
1+
1+
1+
1
1 + ...
1
1+
1
1
procedimiento
haciendo,
por
ejemplo,
la
sustitucin
#include <stdio.h>
#define LIMITE 1000
void main(void)
{
double au = 1;
for(int i = 0 ; i < LIMITE ; i++)
au = 1 + 1 / au;
printf("El nmero ureo es ..... %lf.\n",au);
printf("ureo al cuadrado es ... %lf.\n",au * au);
}
38.
143
tenemos:
= 1 + = 1 + 1 + = 1 + 1 + 1 + = ... = 1 + 1 + 1 + 1 + 1 + ... 1 +
#include <stdio.h>
#include <math.h>
#define LIMITE 100000
void main(void)
{
double au = 1;
for(int i = 0 ; i < LIMITE ; i++)
au = sqrt(1 + au);
printf("El nmero ureo es ..... %lf.\n",au);
printf("ureo al cuadrado es ... %lf.\n",au * au);
}
39.
#include <stdio.h>
#include <math.h>
void main(void)
{
long a, b, div;
printf("Lmite inferior ... ");
scanf("%ld",&a);
printf("Lmite superior ... ");
scanf("%ld",&b);
printf("Los primos entre %ld y %ld son ...\n\t", a, b);
for(long num = a, primos = 0 ; num <= b ; num++)
{
144
El primer for recorre todos los enteros comprendidos entre los dos
lmites introducidos por teclado. El segundo for averigua si la variable
num codifica en cada iteracin del primer for un entero primo o
compuesto: si al salir del segundo for se tiene que num % div es igual a
cero, entonces num es compuesto y se ejecuta las sentencia continue
que vuelve a la siguiente iteracin del primer for. En caso contrario, el
valor de num es primo, y entonces sigue adelante con las sentencias del
primer for, que estn destinadas nicamente a mostrar por pantalla, de
forma ordenada, ese entero primo, al igual que habr mostrado
previamente todos los otros valores primos y mostrar los que siga
encontrando posteriormente.
La salida por pantalla del programa podra ser la siguiente:
Lmite inferior ... 123
Lmite superior ... 264
Los primos entre 123 y 264 son ...
127,
179,
233,
131,
181,
239,
137,
191,
241,
139,
193,
251,
149,
197,
257,
151,
199,
263,
157,
211,
163,
223,
167,
227,
173,
229,
145
146
CAPTULO 5
MBITO Y VIDA DE LAS VARIABLES
Este breve captulo pretende completar algunos conceptos presentados
en el captulo 3 y que, una vez hemos visto algo de cdigo en el captulo
4, sern ahora ms sencillos de presentar y de comprender. Tambin
presentamos
una
breve
descripcin
de
cmo
se
gestiona
el
mbito y Vida.
Entendemos por mbito de una variable el lugar, dentro de un
programa, en el que esta variable tiene significado. Hasta el momento
todas nuestras variables han tenido como mbito todo el programa, y
quiz ahora no es sencillo hacerse una idea intuitiva de este concepto;
pero realmente, no todas las variables estn en activo a lo largo de
todo el programa.
variables
coloca
en
estas
posiciones
privilegiadas.
El
mediante
algunas
palabras
clave,
la
conveniencia
148
149
salvo
para
la
declaracin
de
variables
globales
(y
150
151
152
#include <stdio.h>
long b = 0, c = 0;
void main(void)
{
for(long b = 0 ; b < 10 ; b++) c++;
printf("El valor de b es %ld y el de c es %ld", b, c);
}
Las variables b y c han sido declaradas globales. Y ambas han sido
inicializadas a cero. Luego, dentro de la funcin principal, se ha
declarado, local dentro del for, la variable b. Y dentro del for se han
variado los valores de las variables b y c.
Cul es la salida que ofrecer por pantalla este cdigo? Por lo que
respecta a la variable c no hay ninguna duda: se ha incrementado diez
veces, y su valor, despus de ejecutar la estructura for, ser 10. Pero,
y b? Esta variable ha sufrido tambin una variacin y ha llegado al
valor 10. Pero cul de los dos variables b ha cambiado?: la de mbito
ms local. Y como la sentencia que ejecuta la funcin printf ya est
fuera de la estructura for, y para entonces la variable local b ya ha
muerto, la variable b que muestra la funcin printf no es otra que la
global: la nica viva en este momento. La salida que mostrar el
programa es la siguiente: El valor de b es 0 y el de c es 10.
Una advertencia importante: ya se ha visto que se pueden declarar, en
mbitos ms reducidos, variables con el mismo nombre que otras que
ya existen en mbitos ms globales. Lo que no se puede hacer es
declarar, en un mismo mbito, dos variables con el mismo nombre. Ante
esa circunstancia, el compilador dar error y no compilar.
Una ltima observacin sobre las variables locales: El lenguaje C
requiere que todas las variables se definan al principio del
bloque donde tienen su mbito: esas declaraciones de variables
deben ser las primeras sentencias en cada bloque que tenga variables
locales en l. As, cuando el compilador crea el bloque, puede asignar el
espacio exacto requerido para esas variables en la pila de la memoria.
En C++ es posible diseminar las declaraciones de las distintas variables
153
154
una
variable
static
dentro
del
bloque
funcin.
El
< 3 ; i++)
0 ; j < 4 ; j++)
long a = 0;
= 0;
155
printf("a = %3ld.
b = %3ld.\n", a, b);
}
}
El programa ofrece la siguiente salida por pantalla:
a
a
a
a
a
a
a
a
a
a
a
a
=
=
=
=
=
=
=
=
=
=
=
=
5.
10.
15.
20.
25.
30.
35.
40.
45.
50.
55.
60.
b=
b=
b=
b=
b=
b=
b=
b=
b=
b=
b=
b=
5.
5.
5.
5.
5.
5.
5.
5.
5.
5.
5.
5.
Variables en registro.
Cuando se declara una variable, se reserva un espacio de memoria para
almacenar sus sucesivos valores. Cul sea ese espacio de memoria es
cuestin que no podemos gobernar del todo. Especialmente, como ya se
ha dicho, no podemos decidir cules son las variables que deben
ubicarse en los espacios de registro.
Pero el compilador, al traducir el cdigo, puede detectar algunas
variables empleadas de forma recurrente, y decidir darle esa ubicacin
preferente. En ese caso, no es necesario traerla y llevarla de la ALU a la
memoria y de la memoria a la ALU cada vez que hay que operar con
ella.
El programador puede tomar parte en esa decisin, e indicar al
compilador que alguna o algunas variables conviene que se ubiquen en
los registros de la ALU. Eso se indica mediante la palabra clave
register.
Si al declarar una variable, se precede a toda la declaracin la palabra
register, entonces esa variable queda creada en un registro de la ALU.
156
Una variable candidata a ser declarada register es, por ejemplo, las
que actan de contadoras en estructuras for.
Tambin puede ocurrir que no se desee que una variable sea
almacenada en un registro de la ALU. Y quiz se desea indicar al
compilador que, sea cual sea su opinin, una determinada variable no
debe ser almacenada all sino en la memoria, como una variable
cualquiera normal. Para evitar que el compilador decida otra cosa se le
indica con la palabra volatile.
El compilador tomas las indicaciones de register a ttulo orientativo. Si,
por ejemplo, se ha asignado el carcter de register a ms variables que
permite la capacidad de la ALU, entonces el compilador resuelve el
conflicto segn su criterio, sin abortar el proceso de compilacin.
Variables extern
Aunque estamos todava lejos de necesitar este tipo de declaracin,
presentamos ahora esta palabra clave de C, que hace referencia al
mbito de las variables.
El lenguaje C permite trocear un problema en diferentes mdulos que,
unidos, forman una aplicacin. Estos mdulos muchas veces sern
programas independientes que despus se compilan por separado y
finalmente se linkan o se juntan. Debe existir la forma de indicar, a
cada uno de esos programas desarrollados por separado, la existencia
de variables globales comunes para todos ellos. Variables cuyo mbito
trasciende el mbito del programa donde se declaran, porque abarcan
todos los programas que luego, linkados, darn lugar a la aplicacin
final.
Se podran declarar todas las variables en todos los archivos. C en la
compilacin de cada programa por separado no dara error, y asignara
tanta memoria como veces estuvieran declaradas. Pero en el enlazado
dara error de duplicidad.
157
Para evitar ese problema, las variable globales que deben permanecer
en todos o varios de los mdulos de un programa se declaran como
extern en todos esos mdulos excepto en uno, donde se declara como
variable global sin la palabra extern. Al compilar entonces esos
mdulos, no se crear la variable donde est puesta la palabra extern,
y permitir la compilacin al considerar que, en alguno de los mdulos
de linkado, esa variable s se crea. Evidentemente, si la palabra extern
se coloca en todos los mdulos, entonces en ninguno se crea la variable
y se producir error en el linkado.
El identificador de una variable declarada como extern es conveniente
que no tenga ms de seis caracteres, pues en los procesos de linkado de
mdulos slo los seis primeros caracteres sern significativos.
En resumen
mbito:
El mbito es el lugar del cdigo donde las sentencias pueden hacer uso
de una variable.
Una variable local queda declarada en el interior de un bloque. Puede
indicarse ese carcter de local al compilador mediante la palabra auto.
De todas formas, la ubicacin de la declaracin ofrece suficientes pistas
al compilador para saber de la localidad de cada variable. Su mbito
queda localizado nicamente a las instrucciones que quedan dentro del
bloque donde ha sido creada la variable.
Una variable es global cuando queda declarada fuera de cualquier
bloque del programa. Su mbito es todo el programa: cualquier
sentencia de cualquier funcin del programa puede hacer uso de esa
variable global.
Extensin:
158
159
Ejercicios
40.
#include <stdio.h>
void main(void)
{
unsigned short a, b, mcd;
do
{
printf("Valor de a ... ");
scanf("%hu",&a);
printf("Valor de b ... ");
scanf("%hu",&b);
if(a == 0 && b == 0) break;
while(b)
{
static unsigned short cont = 0;
mcd = b;
b = a % b;
a = mcd;
cont++;
printf("\ncont = %hu", cont);
printf("\n\nEl mcd es %hu.", mcd);
}while(1);
160
}
Cada vez que se ejecuta el bloque de la estructura dowhile se
incrementa en uno la variable cont. Esta variable se inicializa a cero
nicamente la primera vez que se ejecuta la sentencia while de clculo
del mximo comn divisor.
Observacin: quiz podra ser interesante, que al terminar de ejecutar
todos los clculos que desee el usuario, entonces se mostrara por
pantalla el nmero de veces que se ha entrado en el bucle. Pero eso no
es posible tal y como est el cdigo, puesto que fuera del mbito de la
estructura while que controla el clculo del mximo comn divisor, la
variable cont, sigue viva, pero estamos fuera de mbito y el compilador
no reconoce ese identificador como variable existente.
161
162
CAPTULO 6
ARRAYS NUMRICOS: VECTORES Y
MATRICES
Hasta el momento hemos trabajado con variables, declaradas una a una
en la medida en que nos han sido necesarias. Pero pudiera ocurrir que
necesitsemos un bloque de variables grande, por ejemplo para definir
los valores de una matriz numrica, o para almacenar los distintos
valores obtenidos en un proceso de clculo del que se obtienen
numerosos resultados del mismo tipo. Supongamos, por ejemplo, que
deseamos hacer un programa que ordene mil valores enteros: habr
que declarar entonces mil variables, todas ellas del mismo tipo, y todas
ellas con un nombre diferente.
Es posible hacer una declaracin conjunta de un grupo de variables. Eso
se realiza cuando todas esas variables son del mismo tipo. Esa es,
intuitivamente, la nocin del array. Y ese es el concepto que
introducimos en el presente captulo
no
comprueba
los
lmites
del
vector.
Es
responsabilidad
del
164
165
166
=
=
=
=
=
=
=
0012FF80
0012FF81
0012FF82
0012FF83
0012FF84
0012FF85
0012FF86
167
&m[1][2] = 0012FF87
&m[1][3] = 0012FF88
&m[1][4] = 0012FF89
Donde vemos que los elementos van ordenados desde el m[0][0] hasta
el m[0][4], y a continuacin el m[1][0]: cuando termina la primera fila
comienza la segunda fila.
Si ahora, por equivocacin, escribiramos el siguiente cdigo
#include <stdio.h>
void main(void)
{
char m[2][5];
short i, j;
for(i = 0 ; i < 2 ; i++)
for(j = 0 ; j < 6 ; j++)
printf("&m[%hd][%hd] = %p\n",i,j,&m[i][j]);
}
(donde, como se ve, el ndice j recorre hasta el valor 5, y no slo hasta
el valor 4) tendramos la siguiente salida por pantalla:
&m[0][0]
&m[0][1]
&m[0][2]
&m[0][3]
&m[0][4]
&m[0][5]
&m[1][0]
&m[1][1]
&m[1][2]
&m[1][3]
&m[1][4]
&m[1][5]
=
=
=
=
=
=
=
=
=
=
=
=
0012FF80
0012FF81
0012FF82
0012FF83
0012FF84
0012FF85
0012FF85
0012FF86
0012FF87
0012FF88
0012FF89
0012FF8A
168
m[0][5] = 0;
lo que estaremos poniendo a cero, quiz sin saberlo, es la variable
m[1][0].
Conclusin: mucho cuidado con los ndices al recorrer matrices y
vectores.
Ejercicios
41.
169
42.
#include <stdio.h>
void main(void)
{
short matriz[3][3];
short i, j;
for(i = 0 ; i < 3 ; i++)
for(j = 0 ; j < 3 ; j++)
{
printf("matriz[%hd][%hd] = ", i, j);
scanf("%hd",&matriz[i][j]);
}
for(i = 0 ; i < 3 ; i++)
{
printf("\n\n");
for(j = 0 ; j < 3 ; j++)
printf("%5hd",matriz[i][j]);
}
printf("\n\n\n");
for(i = 0 ; i < 3 ; i++)
{
printf("\n\n");
for(j = 0 ; j < 3 ; j++)
printf("%5hd",matriz[j][i]);
}
}
Primero muestra la matriz tal y como la ha introducido el usuario, y ms
abajo muestra su traspuesta.
43.
170
#define TAM 3
#include <stdio.h>
void main(void)
{
short a[TAM][TAM];
short b[TAM][TAM];
short s[TAM][TAM];
short p[TAM][TAM];
short i, j, k;
// Entrada matriz a.
for(i = 0 ; i < TAM ; i++)
for(j = 0 ; j < TAM ; j++)
{
printf("a[%hd][%hd] = ", i, j);
scanf("%hd",&a[i][j]);
}
// Entrada matriz b.
for(i = 0 ; i < TAM ; i++)
for(j = 0 ; j < TAM ; j++)
{
printf("b[%hd][%hd] = ", i, j);
scanf("%hd",&b[i][j]);
}
// Clculo Suma.
for(i = 0 ; i < TAM ; i++)
for(j = 0 ; j < TAM ; j++)
s[i][j] = a[i][j] + b[i][j];
// Clculo Producto.
// p[i][j]=a[i][0]*b[0][j]+a[i][1]*b[1][j]+a[i][2]*b[2][j]
for(i = 0 ; i < TAM ; i++)
for(j = 0 ; j < TAM ; j++)
{
p[i][j] = 0;
for(k = 0 ; k < TAM ; k++)
p[i][j] += a[i][k] * b[k][j];
}
// Mostrar resultados.
// SUMA
for(i = 0 ; i < TAM ; i++)
{
printf("\n\n");
for(j = 0 ; j < TAM ; j++)
printf("%4hd",a[i][j]);
printf("\t");
for(j = 0 ; j < TAM ; j++)
printf("%4hd",b[i][j]);
printf("\t");
for(j = 0 ; j < TAM ; j++)
printf("%4hd",s[i][j]);
printf("\t");
}
171
// PRODUCTO
printf("\n\n\n");
for(i = 0 ; i < TAM ; i++)
{
printf("\n\n");
for(j = 0 ; j < TAM ; j++)
printf("%4hd",a[i][j]);
printf("\t");
for(j = 0 ; j < TAM ; j++)
printf("%4hd",b[i][j]);
printf("\t");
for(j = 0 ; j < TAM ; j++)
printf("%4hd",p[i][j]);
printf("\t");
}
}
En el manejo de matrices y vectores es frecuente utilizar siempre, como
estructura de control de iteracin, la opcin for. Y es que tiene una
sintaxis que permite manipular muy bien las iteraciones que tienen un
nmero prefijado de repeticiones. Ms, en el caso de las matrices, que
las mismas variables de control de la estructura son las que sirven para
los ndices que recorre la matriz.
44.
#include <stdio.h>
void main(void)
{
short datos[1000];
short i, j, nn;
// Introduccin de datos.
i = 0;
do
{
printf("Entada de nuevo dato ... ");
scanf("%hi",&datos[i]);
i++;
172
173
45.
#include <stdio.h>
#define TAM 1000
void main(void)
{
unsigned long int numero, mitad;
unsigned long int i;
unsigned long int div;
unsigned long int D[TAM];
for(i = 0 ; i < TAM ; i++) D[i] = 0;
D[0] = 1;
printf("Numero que vamos a testear ... ");
scanf("%lu", &numero);
mitad = numero / 2;
for(i = 1 , div = 2 ; div <= mitad ; div++)
{
if(numero % div == 0)
{
D[i] = div;
i++;
if(i == TAM)
{
printf("Vector mal dimensionado.");
break;
}
}
}
if(i < TAM) D[i] = numero;
if(i == 1) printf("\n%lu es PRIMO.\n",numero);
else
{
printf("\n%lu es COMPUESTO. ", numero);
printf("Sus divisores son:\n\n");
for(i = 0 ; i < TAM && D[i] != 0; i++)
printf("\n%lu", D[i]);
}
}
Este programa es semejante a uno presentado en el captulo de las
estructuras de control. All se comenta el diseo de este cdigo. Ahora
aadimos que, cada vez que se encuentra un divisor, se almacena en
174
46.
175
scanf("%li",&N);
if(N == 0) break;
for(i=0,Test = 0x80000000 ; Test ; Test >>=1,i++)
bits[i] = Test & N ? 1 : 0;
printf("\nCodificacin binaria interna ... ");
for(i = 0 ; i < sizeof(long) * 8 ; i++)
printf("%hu", bits[i]);
}while(1);
}
Este cdigo permite introducir tantos enteros como quiera el usuario.
Cuando el usuario introduzca el valor cero entonces se termina la
ejecucin del programa. Ya qued explicado el funcionamiento de este
algoritmo en un tema anterior. Ahora simplemente hemos introducido la
posibilidad de que se almacenen los dgitos binarios en un array.
Una posible salida por pantalla de este programa sera la siguiente:
Introduce un entero ... 12
Codificacin binaria interna ... 00000000000000000000000000001100
Introduce un entero ... -12
Codificacin binaria interna ... 11111111111111111111111111110100
Introduce un entero ... 0
47.
176
una
columna
la
izquierda.
Estos
177
Aparte del cdigo que cada uno pueda escribir de la mano del
flujograma, ofrecemos ahora otro que agiliza de forma notable la
bsqueda de la siguiente posicin del cuadro donde se ha de colocar el
siguiente valor de numero. En el cdigo se ha definido una macro
mediante la directiva #define. No es trivial verlo a la primera, pero
ayuda el ejemplo a comprender que a veces un cdigo bien pensado
facilita su comprensin.
El cdigo es el siguiente:
#include <stdio.h>
#define lr(x, N) ((x)< 0 ? N+(x)%N : ((x)>=N ? (x)%N : (x) ))
void main(void)
{
unsigned short magico[17][17];
unsigned short fil, col, dim, num;
do
{
printf( "\nDimensin ( impar entre 3 y 17 ): ");
scanf("%hu", &dim);
}while(dim % 2 == 0);
printf( "\nCuadro Mgico de dimensin %hu:\n\n", dim);
//Inicializamos la matriz a cero
for(fil = 0 ; fil < dim ; fil++)
for(col = 0 ; col < dim ; col++)
magico[fil][col] = 0;
// Algoritmo de asignacin de valores...
for(fil = dim/2 , col = 0 , num = 1 ; num < dim*dim;)
{
if(magico[fil][col] == 0)
{
magico[fil][col] = num++;
fil = lr(fil + 1, dim);
col = lr(col - 1, dim);
}
else
{
fil = lr(fil - 1, dim);
col = lr(col + 2, dim);
}
}
178
179
dim
fil 0
C
fil 0
Salto de lnea
Inicializar matriz
M
C1
No
col 0
Rellenar matriz
M
No
C1
col 0
M[fil ][col ] 0
M[fil ][col ]
Mostrar matriz
M
No
C2
No
C2
Proceso general
Mostrar Matriz M
Iniciar Matriz M
fil dim 2
M[fil ][col ] 1
col 0
numero 2
numero numero + 1
S
Buscar siguiente
posicin (fil , col )
Rellenar Matriz M
C
posf fil
No
C1 numero < dim2
posc col
S
Buscar siguiente
posicin (fil , col )
C1
fil dim 1
C1
C1 fil = 0
No
fil fil 1
C2
C 2 col = 0
No
col dim 1
No
col col 1
C3
Si
C 3 M[fil ][col ] 0
M[fil ][col ] numero
col posc + 1
fil posf + 1
180
CAPTULO 7
CARACTERES Y CADENAS DE
CARACTERES
Ya hemos visto que un carcter se codifica en C mediante el tipo de dato
char. Es una posicin de memoria (la ms pequea que se puede
reservar en C: un byte) que codifica cada carcter segn un cdigo
arbitrario. El ms extendido en el mundo del PC y en programacin con
lenguaje C es el cdigo ASCII.
Hasta el momento, cuando hemos hablado de los operadores de una
variable char, hemos hecho referencia al uso de esa variable (que no se
ha recomendado) como entero de pequeo rango o dominio. Pero donde
realmente tiene uso este tipo de dato es en el manejo de caracteres y
en el de vectores o arrays declarados de este tipo.
A un array de tipo char se le suele llamar cadena de caracteres.
En este captulo vamos a ver las operaciones bsicas que se pueden
realizar con caracteres y con cadenas. No hablamos de operadores,
biblioteca
ctype.h
contiene
abundantes
funciones
para
la
182
183
e = 0, i = 0, o = 0, u = 0;
a++;
'e') e++;
'i') i++;
'o') o++;
184
Entrada de caracteres.
Hemos visto dos funciones que sirven bien para la introduccin de
caracteres.
La funcin scanf espera la entrada de un carcter ms el carcter intro.
No es muy cmoda cuando se espera nicamente un carcter.
La funcin getchar tambin est definida en la biblioteca stdio.h. De
todas formas, su uso no es siempre el esperado, por problemas del
buffer de teclado. Un buffer es como una cola o almacn que crea y
gestiona
el
sistema
operativo.
Nuestro
programas
nunca
actan
185
Cadena de caracteres.
Una cadena de caracteres es una formacin de caracteres. Es un vector
tipo char, cuyo ltimo elemento es el carcter nulo ( NULL, \0 se
escribe en C). Toda cadena de caracteres termina con el carcter
llamado carcter NULO de C. Este carcter indica donde termia la
cadena.
La declaracin de una cadena de caracteres se realiza de forma similar a
la de un vector de cualquier otro tipo:
char mi_cadena[dimensin];
donde dimensin es un literal que indica el nmero de bytes que se
deben reservar para la cadena (recuerde que una variable tipo char
ocupa un byte).
186
187
188
189
la
va
almacenando
en
la
cadena.
Veamos
una
posible
implementacin.
#define TAM 30
#include <stdio.h>
#include <ctype.h>
void main(void)
{
char nombre[TAM];
short int i;
printf("Cmo te llamas? ");
for(i = 0 ; i < TAM ; i++)
nombre[i] = NULL;
i = 0;
while(i < TAM)
{
nombre[i] = getchar();
if (nombre[i] == 10)
{
nombre[i] = NULL;
break;
}
else if(nombre[i] == 8 && i > 0)
{
nombre[i] = NULL;
printf("\b \b");
i--;
}
else if(isgraph(nombre[i]) || nombre[i] == 32)
printf("%c",nombre[i++]);
}
printf("\n\nHola, %s.", nombre);
}
Donde el valor ASCII 8 es el carcter borrar uno hacia detrs: eso
significa que hay que retroceder, escribir un blanco donde ya habamos
escrito otro carcter, y volver a retroceder; adems hay que reducir en
uno el valor del ndice i, puesto que hemos eliminado un carcter. Si
estbamos en el carcter 0, la operacin de borrado no surte efecto,
porque as lo hemos condicionado en el if correspondiente. El valor
ASCII 10 es el carcter intro, que vamos a entender como final de
entrada de la cadena: por eso se interrumpe el proceso de entrada de
caracteres y se cambia el valor de ese ltimo carcter que deja de ser
190
191
192
193
194
break;
}
i++;
}
if(chivato == 1)
printf("cadena01 > cadena02");
else if(chivato == 2)
printf("cadena02 > cadena01");
else if(cad01[i] == NULL && cad02[i] != NULL)
printf("cadena02 > cadena01");
else if(cad01[i] != NULL && cad02[i] == NULL)
printf("cadena01 > cadena02");
else
printf("cadenas iguales");
}
Y una vez ms, disponemos de una funcin en la biblioteca string que
realiza la comparacin de cadenas:
int strcmp(const char *s1, const char *s2);
Que recibe como parmetros las cadenas a comparar y devuelve un
valor negativo si s1 < s2; un valor positivo si s1 > s2; y un cero si
ambas cadenas son iguales
#include <string.h>
#include <stdio.h>
void main(void)
{
char c1[100] = "Texto de la cadena primera";
char c2[100] = "Texto de la cadena segunda";
short int comp;
printf(Primera Cadena ... );
gets(c1);
printf(Segunda Cadena ... );
gets(c2);
comp = strcmp(c1,c2);
if(comp < 0) printf("cadena02 > cadena01");
else if(comp > 0) printf("cadena01 > cadena02");
else printf("Cadenas iguales");
}
Tambin existe una funcin que compara hasta una cantidad de
caracteres sealado, es decir, una porcin de la cadena:
int strncmp(const char *s1, const char *s2, size_t maxlen);
195
196
197
198
j = 0;
i++;
}
else if((e[i] == '+' || e[i] == '-') && est == 2)
{
op = e[i];
i++;
est = 3;
}
else if(e[i] == '=' && est == 5)
{
printf("%lu %c %lu = ",a1,op,a2);
printf("%lu", op=='+' ? a1 + a2 : a1 - a2);
break;
}
else
{
err = 1;
break;
}
}
if(err == 1) printf("Error de entrada de datos");
}
Ejercicios.
48.
#include <stdio.h>
void main(void)
{
char cadena[100];
short int a, e, i, o, u, cont;
printf("Introduzca una cadena de texto ... \n");
gets(cadena);
a = e = i = o = u = 0;
for(cont = 0 ; cadena[cont] != NULL ; cont++)
{
if(cadena[cont] == 'a') a++;
else if(cadena[cont] == 'e') e++;
else if(cadena[cont] == 'i') i++;
199
49.
muestre
por
pantalla
esa
misma
cadena
en
maysculas.
#include <stdio.h>
#include <ctype.h>
void main(void)
{
char cadena[100];
short int cont;
printf("Introduzca una cadena de texto ... \n");
gets(cadena);
for(cont = 0 ; cadena[cont] != NULL ; cont++)
cadena[cont] = toupper(cadena[cont]);
printf("La cadena introducida ha sido ...\n");
printf("%s\n",cadena);
}
50.
200
#include <stdio.h>
#include <string.h>
void main(void)
{
char cadena[100];
short int i, j;
printf("Introduzca una cadena de texto ... \n");
gets(cadena);
for(i = 0 ; cadena[i] != NULL ; )
if(cadena[i] == 32)
for(j = i ; cadena[j] != NULL ; j++)
cadena[j] = cadena[j + 1];
else i++;
printf("La cadena introducida ha sido ...\n");
printf("%s\n",cadena);
}
Si el carcter i es el carcter blanco (ASCII 32) entonces no se
incrementa el contador sino que se adelantan una posicin todos los
caracteres hasta el final. Si el carcter no es el blanco, entonces
simplemente se incrementa el contador y se sigue rastreando la cadena
de texto.
Una observacin: la estructura for situada inmediatamente despus de
la funcin gets, controla una nica sentencia simple, que est controlado
por una estructura ifelse, que a su vez controla otra sentencia for,
tambin con una sola sentencia simple. No es necesario utilizar ninguna
llave en todo ese cdigo porque no hay ni una sola sentencia
compuesta.
51.
la
cadena
con
todos
los
caracteres
alfabticos
201
#include <ctype.h>
#include <stdio.h>
void main(void)
{
char c[100];
short int i;
short int d;
printf("Introduzca una cadena de texto ... \n");
gets(c);
for(i = 0 ; c[i] != NULL ; i++)
c[i] = toupper(c[i]);
printf("Introduzca el desplazamiento ... \n");
scanf("%hd",&d);
d %= 26; /* Si d = 26 * k + d', donde d' < 26
entonces desplazar d en el
abecedario es lo mismo que desplazar d'. */
for(i = 0 ; c[i] != NULL ; i++)
{
if(isalpha(c[i]))
{
c[i] += d;
if(c[i] > 'Z') c[i] = 'A' + c[i] - 'Z' - 1;
}
}
printf("La cadena transformada queda ... \n");
printf("%s",c);
}
Una nica observacin a este cdigo y, en general, a todos los que se
presentan como ejemplo en este manual. Si bien vienen resueltos,
cuando de verdad se aprende a programar es en el momento en que
uno se pone delante de la pantalla y comienza a echar lneas de cdigo
en busca de SU solucin. Este programa, como la mayora, tiene
infinidad de formas diferentes de solventarlo. La que aqu se muestra no
es la mejor, simplemente es la que se ha ocurrido a quien esto escribe
en el tiempo que ha tardado en redactar esa solucin. Si hubiera
resuelto el programa maana, el libro tendra una solucin distinta.
Y es que copiar el cdigo en un editor de C y compilarlo no sirve para
aprender. Que este manual es de C, y no de mecanografa.
202
CAPTULO 8
PUNTEROS
La memoria puede considerarse como una enorme cantidad de
posiciones de almacenamiento de informacin perfectamente ordenadas.
Cada posicin es un octeto o byte de informacin. Cada posicin viene
identificada de forma inequvoca por un nmero que suele llamarse
direccin de memoria. Cada posicin de memoria tiene una direccin
nica. Los datos de nuestros programas se guardan en esa memoria.
La forma en que se guardan los datos en la memoria es mediante el uso
de variables. Una variable es un espacio de memoria reservado para
almacenar un valor: valor que pertenece a un rango de valores posibles.
Esos valores posibles los determina el tipo de dato de esa variable.
Dependiendo del tipo de dato, una variable ocupar ms o menos bytes
de la memoria, y codificar la informacin de una u otra manera.
Si, por ejemplo, creamos una variable de tipo float, estaremos
reservando cuatro bytes de memoria para almacenar sus posibles
valores. Si, por ejemplo, el primero de esos bytes es el de posicin
Definicin y declaracin.
Una variable tipo puntero es una variable que contiene la
direccin de otra variable.
Para cada tipo de dato, primitivo o creado por el programador, permite
la creacin de variables puntero hacia variables de ese tipo de dato.
Existen punteros a char, a long, a double, etc. Son nuevos tipos de
dato: puntero a char, puntero a long, puntero a double,
Y como tipos de dato que son, habr que definir para ellos un dominio y
unos operadores.
Para declarar una variable de tipo puntero, la sintaxis es similar a la
empleada para la creacin de las otras variables, pero precediendo al
nombre de la variable del carcter asterisco (*).
tipo *nombre_puntero;
Por ejemplo:
204
Captulo 8. Punteros.
205
ser
necesario
creemos
que
tambin
ha
de
ser
fcilmente
206
Captulo 8. Punteros.
207
208
Captulo 8. Punteros.
Punteros y vectores
Los punteros sobre variables simples tienen una utilidad clara en las
funciones. All los veremos con detenimiento. Lo que queremos ver
ahora es el uso de punteros sobre arrays.
Un array, o vector, es una coleccin de variables, todas del mismo tipo,
y colocadas en memoria de forma consecutiva. Si creo una array de
cinco variables float, y el primero de los elementos queda reservado en
la posicin de memoria FF54:AB10, entonces no cabe duda que el
segundo estar en FF54:AB14, y el tercero en FF54:AB18, y el cuarto en
FF54:AB1C y el ltimo en FF54:AB20.
Supongamos la siguiente situacin:
long a[10];
long *pa;
pa = &a[0];
Y con esto a la vista, pasamos ahora a presentar otros dos operadores,
muy usados con los punteros.
Operador incremento (++) y decremento (): Estos operadores
no son nuevos, y ya los conocemos. Todas sus propiedades que se
vieron con los enteros siguen vigentes ahora con las direcciones de
memoria.
Desde luego, si el incremento es sobre el contenido de la variable
apuntada sobre el puntero (*pa)++;, no hay nada que aadir: se
incrementar el contenido de esa variable apuntada, de la misma forma
que si el operador estuviera aplicado sobre la variable apuntada misma:
es lo mismo que escribir a[0]++;.
Nos referimos a incrementar el valor del puntero. Qu sentido tiene
incrementar en 1 una direccin de memoria? El sentido ser el de acudir
209
a[0]
a[1]
a[2]
a
F6C3:9870
F6C3:9871
F6C3:9872
F6C3:9873
F6C3:9874
F6C3:9875
F6C3:9876
F6C3:9877
F6C3:9878
F6C3:9879
F6C3:987A
F6C3:987B
F6C3:987C
F6C3:987D
pa
pa + 1
pa + 2
pa + 3
pb
pb + 1
pb + 2
pb + 3
pb + 4
pb + 5
pb + 6
b
F6C3:9870
F6C3:9871
F6C3:9872
F6C3:9873
F6C3:9874
F6C3:9875
F6C3:9876
F6C3:9877
F6C3:9878
F6C3:9879
F6C3:987A
F6C3:987B
F6C3:987C
F6C3:987D
b[0]
b[1]
b[2]
b[3]
b[4]
b[5]
210
Captulo 8. Punteros.
pc2 =
&c[9];
ph2 =
&h[9];
pf2 =
&f[9];
pd2 =
&d[9];
pld2 = &ld[9];
pc1(0012FF80) = 9
ph1(0012FF50) = 9
211
pf2(0012FF4C) - pf1(0012FF28) = 9
pd2(0012FF20) - pd1(0012FED8) = 9
pld2(0012FECE) - pld1(0012FE74) = 9
(long)pc2
(long)ph2
(long)pf2
(long)pd2
(long)pld2
- (long)pc1
- (long)ph1
- (long)pf1
- (long)pd1
- (long)pld1
=
=
=
=
=
9
18
36
72
90
212
Captulo 8. Punteros.
213
214
Captulo 8. Punteros.
Puntero a puntero
Un puntero es una variable que contiene la direccin de otra variable.
Segn sea el tipo de variable que va a ser apuntada, as, de ese tipo,
debe ser declarado el puntero. Ya lo hemos dicho.
Pero un puntero es tambin una variable. Y como variable que es, ocupa
una porcin de memoria: tiene una direccin.
Se puede, por tanto, crear una variable que almacene la direccin de
esa variable puntero. Sera un puntero que almacenara direcciones de
tipo de dato puntero. Un puntero a puntero.
Por ejemplo:
float F, *pF, **ppF;
Acabamos de crear tres variables: una, de tipo float, llamada F. Una
segunda variable, de tipo puntero a float, llamada pF. Y una tercera
variable, de tipo puntero a puntero float, llamada ppF.
Y eso no es un rizo absurdo. Tiene mucha aplicacin en C. Igual que se
puede hablar de un puntero a puntero a puntero a puntero a float.
Y as como antes hemos visto que hay una relacin directa entre
punteros a un tipo de dato y vectores de este tipo de dato, tambin
veremos ahora que hay una relacin directa entre punteros a punteros y
matrices de dimensin 2. Y entre punteros a punteros a punteros y
215
216
Captulo 8. Punteros.
m =
*(m
*(m
*(m
*(m
0012FECC
+ 0) = 0012FECC
+ 1) = 0012FEFC
+ 2) = 0012FF2C
+ 3) = 0012FF5C
&m[0][0]
&m[1][0]
&m[2][0]
&m[3][0]
=
=
=
=
0012FECC
0012FEFC
0012FF2C
0012FF5C
m[0][5]
m[1][5]
m[2][5]
m[3][5]
Tenemos que m vale lo mismo que *(m + 0); su valor es la direccin del
primer elemento de la matriz: m[0][0]. Despus de l, vienen todos los
dems, uno detrs de otro: despus de m[0][5] vendr el m[1][0], y
esa direccin la podemos obtener con *(m + 1); despus de m[1][5]
vendr el m[2][0], y esa direccin la podemos obtener con *(m + 2);
despus de m[2][5] vendr el m[3][0], y esa direccin la podemos
obtener con *(m + 3); y despus de m[3][5] se termina la cadena de
elementos reservados.
Es decir, en la memoria del ordenador, no se distingue entre un vector
de 24 variables tipo double y una matriz 4 * 6 de variables tipo
double. Es el lenguaje el que sabe interpretar, mediante una operatoria
de punteros, una estructura matricial donde slo se dispone de una
secuencia lineal de elementos. Esos punteros no existen en realidad, y si
se imprime la posicin que ocupa m, *m la pantalla nos mostrar el
mismo valor que al solicitarle que nos muestre la direccin de m[0][0].
No existen, pero el lenguaje C admite esa sintaxis, y podemos trabajar
como si de punteros constantes se trataran.
217
218
Captulo 8. Punteros.
}
det = 0;
det += *(*(m + 0) + 0) * *(*(m + 1)
det += *(*(m + 0) + 1) * *(*(m + 1)
det += *(*(m + 0) + 2) * *(*(m + 1)
det -= *(*(m + 0) + 2) * *(*(m + 1)
det -= *(*(m + 0) + 1) * *(*(m + 1)
det -= *(*(m + 0) + 0) * *(*(m + 1)
printf("El determinante ... \n");
for(i = 0 ; i < 3 ; i++)
{
printf("\n | ");
for(j = 0 ; j < 3 ; j++)
printf("%8.2lf",*(*(m +
printf(" | ");
}
printf("\n\n es ... %lf",det);
+
+
+
+
+
+
1)
2)
0)
1)
0)
2)
*
*
*
*
*
*
*(*(m
*(*(m
*(*(m
*(*(m
*(*(m
*(*(m
+
+
+
+
+
+
2)
2)
2)
2)
2)
2)
+
+
+
+
+
+
2);
0);
1);
0);
2);
1);
i) + j));
Advertencia final
El uso de puntero condiciona el modo de programar. El puntero es una
herramienta muy poderosa y muy arriesgada tambin. Es necesario
saber bien qu se hace, porque jugando con punteros se pueden
burlar muchas seguridades de C.
Veamos un ejemplo. Primero presentamos el siguiente cdigo, donde se
emplea una variable static: una variable que se extiende a la duracin
de todo el programa, pero cuyo mbito queda reducido al del bloque en
el que se ha definido. Esa variable, que en el ejemplo llamamos local, se
inicializa a cero la primera vez que se entra en el bloque donde est
definida, y posteriormente se va aumentando de uno en uno cada vez
que se ejecuta su bloque.
#include <stdio.h>
void main(void)
{
short a, b = 0;
219
do
{
a = 0;
do
{
static short local = 0;
local++;
printf("local = %2hd\n",local);
a++;
}while (a < 5);
printf("\n");
b++;
}while(b < 5);
}
La salida de este programa es la que sigue:
local
local
local
local
local
= 1
= 6
= 11
= 16
= 21
local
local
local
local
local
= 2
= 7
= 12
= 17
= 22
local
local
local
local
local
= 3
= 8
= 13
= 18
= 23
local
local
local
local
local
= 4
= 9
= 14
= 19
= 24
local
local
local
local
local
=
=
=
=
=
5
10
15
20
25
220
Captulo 8. Punteros.
local
local
local
local
local
=
=
=
=
=
1
1
1
1
1
local
local
local
local
local
=
=
=
=
=
2
2
2
2
2
local
local
local
local
local
=
=
=
=
=
3
3
3
3
3
local
local
local
local
local
=
=
=
=
=
4
4
4
4
4
local
local
local
local
local
=
=
=
=
=
5
5
5
5
5
Ejercicios
52.
#include <stdio.h>
void main(void)
{
char c[3];
short i[3], cont;
float f[3];
printf("Las direcciones
for(cont = 0 ; cont < 3
{
printf("&c[%2d] =
printf("&i[%2d] =
printf("&f[%2d] =
}
}
de memoria son:\n");
; cont++)
%10p\t",cont,c + cont);
%10p\t",cont,i + cont);
%10p\n\n",cont,f + cont);
&i[ 0] = 0064FDF8
&f[ 0] = 0064FDEC
&c[ 1] =
&i[ 1] =
&f[ 1] =
&c[ 2] =
&i[ 2] =
&f[ 2] =
221
53.
#include <stdio.h>
main()
{
char c[20],*pc1,*pc2;
short int i[20],*pi1,*pi2;
float f[20],*pf1,*pf2;
pc1
pc2
pi1
pi2
pf1
pf2
=
=
=
=
=
=
&c[0];
&c[19];
&i[0];
&i[19];
&f[0];
&f[19];
printf("(int)pc2-(int)pc1 es %d\n",(int)pc2-(int)pc1);
printf("pc2 - pc1 es %d\n\n",pc2 - pc1);
printf("(int)pi2-(int)pi1 es %d\n",(int)pi2-(int)pi1);
printf("pi2 - pi1 es %d\n\n",pi2 - pi1);
printf("(int)pf2-(int)pf1 es %d\n",(int)pf2-(int)pf1);
printf("pf2 - pf1 es %d\n\n",pf2 - pf1);
(int)pc2 - (int)pc1 es 19
pc2 - pc1 es 19
(int)pi2 - (int)pi1 es 38
pi2 - pi1 es 19
(int)pf2 - (int)pf1 es 76
pf2 - pf1 es 19
222
CAPTULO 9
FUNCIONES
Hemos llegado a las funciones.
Al principio del tema de estructuras de control sealbamos que haba
dos maneras de romper el flujo secuencial de sentencias. Y hablbamos
de dos tipos de instrucciones que rompen el flujo: las instrucciones
condicionales y de las incondicionales. Las primeras nos dieron pie a
hablar largamente de las estructuras de control: condicionales y de
iteracin. Ahora toca hablar de las instrucciones incondicionales que
realizan la transferencia a una nueva direccin del programa sin evaluar
condicin alguna.
De hecho, ya hemos visto muchas funciones. Y las hemos utilizado.
Cuando hemos querido mostrar por pantalla un mensaje hemos acudido
a la funcin printf de la biblioteca stdio.h. Cuando hemos querido saber
la longitud de una cadena hemos utilizado la funcin strlen, de la
biblioteca string.h. Y cuando hemos querido hallar la funcin seno de
un valor concreto, hemos acudido a la funcin sin, de math.h.
Y ya hemos visto que, sin saber cmo, hemos echado mano de una
funcin estndar programada por ANSI C que nos ha resuelto nuestro
problema. Cmo se ha logrado que se vea en pantalla un texto, o el
valor de una variable? Qu desarrollo de Taylor se ha aplicado para
llegar a calcular el seno de un ngulo dado? No lo sabemos. Dnde est
el cdigo que resuelve nuestro problema? Tampoco lo sabemos. Pero
cada vez que hemos invocado a una de esas funciones, lo que ha
ocurrido es que el contador de programa ha abandonado nuestra
secuencia de sentencias y se ha puesto con otras sentencias, que son
las que codifican las funciones que hemos invocado.
De forma incondicional, cada vez que se invoca una funcin, se
transfiere el control de ejecucin a otra direccin de memoria, donde se
encuentran codificadas otras sentencias, que resuelven el problema para
el que se ha definido, editado y compilado esa funcin.
Son transferencias de control con retorno. Porque cuando termina la
ejecucin de la ltima de las sentencias de la funcin, entonces regresa
el control a la sentencia inmediatamente posterior a aquella que invoc
esa funcin.
Quiz ahora, cuando vamos a abordar la teora de creacin, diseo e
implementacin de funciones, ser buen momento para releer lo que
decamos en el tema 4 al tratar de la modularidad. Y recordar tambin
las
tres
propiedades
que
deban
tener
los
distintos
mdulos:
Definiciones
Abstraccin Modularidad Programacin estructurada.
Esas eran las tres notas bsicas que presentamos al presentar el
lenguaje de programacin C. De la programacin estructurada ya hemos
hablado, y lo seguiremos haciendo en este captulo. La abstraccin es el
224
Captulo 9. Funciones.
225
ser
exportable.
Perderamos
entonces
el
valor
de
la
226
Captulo 9. Funciones.
Tanto los procedimientos como las funciones pueden ser vistos como
cajas negras: un cdigo del que desconocemos sus sentencias, al que se
le puede suministrar unos datos de entrada y obtener modificaciones
para esos valores de entrada y/o el clculo de un nuevo valor, deducido
a partir de los valores que ha recibido como entrada.
Con eso se consigue programas ms cortos; que el cdigo pueda ser
usado ms de una vez; mayor facilidad para gestionar un correcto orden
de ejecucin de sentencias; que las variables tengan mayor carcter
local, y no puedan ser manipuladas fuera del mbito para el que han
sido creadas.
Funciones en C
Una funcin, en C, es un segmento independiente de cdigo fuente,
diseado para realizar una tarea especfica.
Las funciones son los elementos principales de un programa en C. Cada
una de las funciones de un programa constituye una unidad, capaz de
realizar una tarea determinada. Quiz se podra decir que un programa
es simplemente un conjunto de definiciones de distintas funciones,
empleadas luego de forma estructurada.
La primera funcin que aparece en todo programa C es la funcin
principal, o funcin main. Todo programa ejecutable tiene una, y slo
una, funcin main. Un programa sin funcin principal no genera un
ejecutable. Y si todas las funciones se crean para poder ser utilizadas, la
funcin principal es la nica que no puede ser usada por nadie: nadie
puede invocar a la funcin principal de un programa. Tampoco puede
llamarse
misma
(aunque
este
concepto
de
autollamada,
227
Declaracin de la funcin.
La declaracin de una funcin se realiza a travs de su prototipo. Un
prototipo tiene la forma:
tipo_funcion nombre_funcion([tipo1 [var1] [, tipoN [varN]]);
228
Captulo 9. Funciones.
229
Definicin de la funcin.
Ya tenemos la funcin declarada. Con el prototipo ha quedado definido
el modo en que podemos utilizarla: cmo nos comunicamos nosotros
con ella y qu resultado nos ofrece.
Ahora queda la tarea de definirla.
Hay que escribir el cdigo, las sentencias, que van a realizar la tarea
para la que ha sido creada la funcin.
La forma habitual que tendr la definicin de una funcin la conocemos
ya, pues hemos visto ya muchas: cada vez que hacamos un programa,
y escribamos la funcin principal, estbamos definiendo esa funcin
main. Esa forma es:
tipo_funcion nombre_funcion([tipo1 var1][, tipoN varN])
{
[declaracin de variables locales]
[cuerpo de la funcin: grupo de sentencias]
[return(parmetro);]
}
Donde el tipo_funcin debe coincidir con el de la declaracin, lo mismo
que nombre_funcin y lo mismo que la lista de parmetros. Ahora, en la
definicin, los parmetros de la funcin siguen recogiendo el tipo de
dato y el nombre de la variable: pero ahora ese nombre NO es opcional.
Debe ponerse, porque esos nombres sern los identificadores de las
variables que recojan los valores que se le pasan a la funcin cuando se
la llama o invoca. A esas variables se las llama parmetros formales:
230
Captulo 9. Funciones.
231
Llamada a la funcin
La llamada a una funcin es una sentencia habitual en C. Ya la hemos
usado
con
frecuencia,
invocando
hasta
el
momento
nicamente
de
dato
incompatibles,
pero
el
resultado
ser
inesperado
totalmente.
La lista de argumentos estar formada por nombres de variables que
recogen los valores que se desean pasar, o por literales. No es necesario
(ni es lo habitual) que los identificadores de los argumentos que se
232
Captulo 9. Funciones.
La sentencia return
Hay dos formas ordinarias de terminar la ejecucin de una funcin.
1. Llegar a la ltima sentencia del cuerpo, antes de la llave que cierra el
bloque de esa funcin.
2. Llegar a una sentencia return. La sentencia return fuerza la salida
de la funcin, y devuelve el control del programa a la funcin que la
llam, en la sentencia inmediatamente posterior a la de la llamada a
la funcin.
Si la funcin es de un tipo de dato distinto de void, entonces en el
bloque de la funcin debe recogerse, al menos, una sentencia return.
En ese caso, adems, en esa sentencia y a continuacin de la palabra
return, deber ir el valor que devuelve la funcin: o el identificador de
233
234
Captulo 9. Funciones.
235
236
Captulo 9. Funciones.
237
238
Captulo 9. Funciones.
Recurrencia
Ya lo hemos comentado antes. Una funcin decimos que es recurrente si
existe una llamada a s misma en el cuerpo de su definicin.
Conceptualmente la recurrencia no ofrece mucha complicacin. El quid
de la recurrencia est en saber utilizarla. Hay problemas donde la
recurrencia cabe perfectamente, y agiliza mucho los algoritmos de
solucin. Implementar una funcin mediante recurrencia es sencillo en
C. El problema no est en el C, sino en llegar al algoritmo que haga uso
de ella.
Lo ms adecuado para explicar la recurrencia es ver algunos ejemplos.
Veremos la solucin de los dos programas del epgrafe anterior,
solventados ahora mediante recurrencia. Comenzamos por el clculo del
factorial.
Por definicin, el factorial de un entero positivo es igual al producto de
ese entero por el factorial del entero inmediatamente inferior:
n ! = n (n 1)!
239
240
Captulo 9. Funciones.
241
242
Captulo 9. Funciones.
en
nuestro
ejemplo
intercambio),
las
variables
que
243
244
Captulo 9. Funciones.
245
nombre(tipo
246
Captulo 9. Funciones.
247
; i++)
+
+
-
i))
i))
i))
i))
*M
*M
*m
*M
=
=
=
=
*(v
*(v
*(v
*(v
+
+
-
i);
i);
i);
i);
248
Captulo 9. Funciones.
cules son los lmites del vector y no permitir que se acceda a posiciones
de memoria que estn fuera de la dimensin del vector.
Funciones de escape
Existen ocasiones en que lo mejor que se puede hacer es abortar a
ejecucin de una aplicacin antes de llegar a consecuencias ms
desastrosas si se continuara la ejecucin del programa. A veces ms
vale abortar misin intentando salvar la mayor parte de los muebles,
que dejar que una situacin irresoluble arruine la lnea de ejecucin y
entonces se produzca la interrupcin de la ejecucin de una forma no
controlada por nosotros.
Para realizar esas operaciones de salida inmediata disponemos de dos
funciones, definidas en la biblioteca stdlib.h: la funcin exit y la funcin
abort.
La funcin exit nos permite abandonar la ejecucin de un programa
antes de su finalizacin, volviendo el control al sistema operativo. Antes
de regresar ese control, realiza algunas tareas importantes: por
ejemplo, si el programa tiene abiertos algunos archivos, la funcin los
cierra antes de abandonar la ejecucin de la aplicacin, y as esos
archivos no se corrompen. Esta funcin se utiliza cuando no se cumple
una condicin, imprescindible para la correcta ejecucin del programa.
El prototipo de la funcin es
void exit(int status);
El parmetro status indica el modo en que se debe realizar la operacin
de finalizacin inmediata del programa. El valor habitual es el cero, e
indica que se debe realizar una salida inmediata llamada normal.
Ahora mismo no vamos a poner ejemplos de uso de esta funcin. Pero
ms adelante, en el prximo tema, se vern ocasiones claras en que su
uso es muy necesario.
249
Ejercicios
54.
en
cuenta
que
siempre
se
verifica
que
a b = mcd(a, b) mcm(a, b) .
#include <stdio.h>
// Declaracin de las funciones ...
short mcd(short, short);
long mcm(short, short);
// Funcin principal...
void main(void)
{
short a, b;
printf("Introduzca el valor de a ... ");
scanf("%hd",&a);
printf("Introduzca el valor de b ... ");
scanf("%hd",&b);
printf("El mximo comn divisor de %hd y %hd ", a, b);
printf("es %hd", mcd(a,b));
printf("\ny el mnimo comn mltiplo %ld.", mcm(a,b));
flushall();
getchar();
}
250
Captulo 9. Funciones.
/* ------------------------------------------------------- */
/*
Definicin de las funciones
*/
/* ------------------------------------------------------- */
/* Funcin clculo del mximo comn divisor. ------------- */
short mcd(short a, short b)
{
short m;
while(b)
{
m = a % b;
a = b;
b = m;
}
return a;
}
55.
#include <stdio.h>
#include <conio.h>
unsigned long fibonacci(short);
void main(void)
{
short N;
printf("Indique el trmino de la serie: ");
251
scanf("%hd", &N);
printf("\nEl trmino %hd es %lu.", N, fibonacci(N));
}
/* ------------------------------------------------------- */
/*
Definicin de las funciones
*/
/* ------------------------------------------------------- */
/* Funcin Fibonacci. ------------------------------------ */
unsigned long fibonacci(short x)
{
unsigned long fib1 = 1 , fib2 = 1, Fib = 1;
while(x > 2)
{
Fib = fib1 + fib2;
fib1 = fib2;
fib2 = Fib;
x--;
}
return Fib;
}
56.
#include <stdio.h>
// Declaracin de la funsin Factorial ...
unsigned long factorial(short);
// Funcin principal ...
void main(void)
{
short N;
printf("Indique un entero menor que 13: ");
scanf("%hd", &N);
printf("\nEl factorial de %hd, N);
printf( es %lu.", factorial(N));
}
/* ------------------------------------------------------- */
252
Captulo 9. Funciones.
/*
Definicin de las funciones
*/
/* ------------------------------------------------------- */
/* Funcin Factorial. ------------------------------------ */
unsigned long factorial(short x)
{
unsigned long F = 1;
while(x) F *= x--;
return F;
}
57.
/* ------------------------------------------------------- */
/*
Definicin de las funciones
*/
/* ------------------------------------------------------- */
253
254
Captulo 9. Funciones.
58.
#include <stdio.h>
#define TAM 100
// Declaracin de las funciones ...
short divisores(long, long*, short);
void mostrar(long *, short);
// Funcin principal ...
void main(void)
{
long N, vector[TAM];
short div;
printf("Introduzca un entero ... ");
scanf("%ld",&N);
if((div = divisores(N,vector, TAM)) > 0)
mostrar(vector, div);
else
printf("No se ha podido realizar la operacin");
}
/* ------------------------------------------------------- */
/*
Definicin de las funciones
*/
/* ------------------------------------------------------- */
255
59.
#include <stdio.h>
#define TAM 100
// Declaracin de funciones ...
long mcd(long, long);
long MCD(long*,short);
// Funcin principal ...
void main(void)
{
long numeros[100];
long m;
short i;
256
Captulo 9. Funciones.
/* ------------------------------------------------------- */
/*
Definicin de las funciones
*/
/* ------------------------------------------------------- */
// Funcin clculo mcd de dos enteros ...
long mcd(long a, long b) {
return b ? mcd(b,a % b) : a; }
// Funcin clculo mcd de varios enteros ...
long MCD(long*n,short d)
{
short i;
long m;
// Si algn entero es 0, el mcd lo ponemos a cero.
for(i = 0 ; i < d ; i++)
if(!*(n + i)) return 0;
// Si solo hay un entero, el mcd es ese entero.
if(d == 1) return *(n + 0);
i = 2;
m = mcd(*(n + 0),*(n + 1));
while(i < d) m = mcd(m,*(n + i++));
return m;
}
60.
mostrar
los
nmeros
perfectos
257
entre
dos
enteros
#include <stdio.h>
#include <conio.h>
// Declaracin de la funcin perfecto ...
short perfecto(long);
// Funcin principal ...
void main(void)
{
long a, b;
printf("Limite inferior ... ");
scanf("%ld",&a);
printf("Limite superior ... ");
scanf("%ld",&b);
for(long i = a ; i <= b; i++)
if(perfecto(i) == 1) printf("%6ld", i);
}
/* ------------------------------------------------------- */
/*
Definicin de las funciones
*/
/* ------------------------------------------------------- */
// Funcin perfecto ...
short perfecto(long x)
{
long suma = 1;
for(long div = 2 ; div <= x / 2 ; div++)
if(x % div == 0) suma += div;
return suma == x ? 1 : 0;
}
61.
muestre
por
pantalla
todos
los
movimientos
258
Captulo 9. Funciones.
#include <stdio.h>
// Declaracin de la funcin Hanoi ...
void Hanoi(short, short, short, short);
// Funcin principal ...
void main(void)
{
short discos;
printf("Indique con cuntos discos desea jugar: ");
scanf("%hd",&discos);
Hanoi(1, discos, 1, 3);
}
/* ------------------------------------------------------- */
/* Funcin Hanoi.
*/
/* ------------------------------------------------------- */
void Hanoi(short D1, short D2, short i, short j)
{
if(D1 == D2)
printf("Disco %hu de %hu a %hu\n",D1, i, j);
else
{
Hanoi(D1 , D2 - 1 , i , 6 - i - j);
Hanoi(D2 , D2 , i , j);
Hanoi(D1 , D2 - 1 , 6 - i - j , j);
}
}
62.
259
f
i =1
= fn+2 1
f
i =1
2i
= f2n+1 1
f
i =1
2i 1
= f2n
verifica que:
n
f
i =1
= fn fn+1
260
Captulo 9. Funciones.
/* ======================================================= */
/*
PROGRAMA DE LA SERIE DE FIBONACCI.
*/
/* ======================================================= */
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define RANGO 40
// Declaracin de las funciones...
char menu(void);
void fib01(unsigned long*);
void fib02(unsigned long*);
void fib03(unsigned long*);
void fib04(unsigned long*);
void fib05(unsigned long*);
void fib06(unsigned long*);
long mcd(long, long);
void fib07(unsigned long*);
void fib08(unsigned long*);
// Funcin principal...
void main(void)
{
unsigned long fib[RANGO + 1];
// No utilizaremos el elemento 0.
unsigned short i;
char opcion;
fib[1] = 1;
261
fib[2] = 1;
// Serie de fibonacci...
for(i = 3 ; i <= RANGO ; i++)
fib[i] = fib[i - 1] + fib[i - 2];
do
{
// Men de opciones ...
opcion = menu();
switch(opcion)
{
case '1': fib01(fib); break;
case '2': fib02(fib); break;
case '3': fib03(fib); break;
case '4': fib04(fib); break;
case '5': fib05(fib); break;
case '6': fib06(fib); break;
case '7': fib07(fib); break;
case '8': fib08(fib); break;
case '0': printf("\nFin del programa");
printf("\nPulse tecla para terminar");
flushall();
getchar();
break;
default: printf("opcin no definida ... ");
}
}while(opcion != '0');
}
/* ------------------------------------------------------- */
/*
Definicin de las funciones
*/
/* ------------------------------------------------------- */
/* Funcin que muestra el men de opciones
char menu(void)
{
char opcion;
clrscr();
printf("\n\n\tPROPIEDADES
printf("\n\n\t0. Salir de
printf("\n\t1. La suma de
printf("\n\t
es igual a
DE LA SERIE DE FIBONACCI");
programa.");
los n primeros terminos");
f[n + 2] - 1.");
262
*/
Captulo 9. Funciones.
263
264
Captulo 9. Funciones.
265
266
Captulo 9. Funciones.
";
clrscr();
printf("\n\n\n\tOPCION SELECCIONADA 7.");
printf("\n\tEl cociente de dos elementos
consecutivos de la serie");
printf("\n\tse aproxima al numero aureo.\n\n");
for(n = 1 ; n < RANGO ; n++)
{
printf("f[%2hu] = %10lu\t",n,*(f + n));
printf("f[%2hu] = %10lu\t",n + 1, *(f + n + 1));
printf("COCIENTE = %20.18lf\n"
(double)*(f + n + 1) / *(f + n));
}
printf("\n%sNumero de oro (1+sqrt(5)) / 2 -> %20.18lf",
blancos,oro);
printf("\n\nPulse intro para volver a menu principal");
getchar();
}
// Funcin fib08
void fib08(unsigned long*f)
{
unsigned short n;
267
clrscr();
printf("\n\n\n\tOPCION SELECCIONADA 8.");
printf("\n\tMostrar los 40 primeros elementos
de la serie de Fibonacci.\n\n");
for(n = 1 ; n <= RANGO ; n++)
printf("f[%2hu] = %10lu%s",
n,*(f + n),n % 2 ? "\t" : "\n");
printf("\n\nPulse intro para volver a menu principal");
getchar();
}
63.
268
Captulo 9. Funciones.
269
strcat(leido,unidades(u,d));
strcat(leidoM,leido);
printf("El numero %lu se lee ... ",n);
printf("\n%s",leidoM);
}while(n); // Termina la aplicacin cuando el usuario
// quiera leer el nmero cero.
}
/* ------------------------------------------------------- */
/*
Definicin de las funciones
*/
/* ------------------------------------------------------- */
/* Funcin para las unidades. ---------------------------- */
char* unidades(unsigned short u, unsigned short d)
{
if(d == 1 && (u == 0 || u == 1 || u == 2 ||
u == 3 || u == 4 || u == 5))
return "";
switch(u)
{
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
}
return
return
return
return
return
return
return
return
return
return
"";
"uno ";
"dos ";
"tres ";
"cuatro ";
"cinco ";
"seis ";
"siete ";
"ocho ";
"nueve ";
}
/* Funcin para las decenas. ----------------------------- */
char* decenas(unsigned short u, unsigned short d)
{
if(d == 1 && (u == 0 || u == 1 || u == 2 ||
u == 3 || u == 4 || u == 5))
switch(u)
{
case 0:
return "diez ";
case 1:
return "once ";
case 2:
return "doce ";
case 3:
return "trece ";
case 4:
return "catorce ";
case 5:
return "quince ";
}
switch(d)
{
270
Captulo 9. Funciones.
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
return "";
return "dieci";
if(u) return "veinti";
else return "veinte";
if(u) return "treinta y ";
else return "treinta ";
if(u) return "cuarenta y ";
else return "cuarenta ";
if(u) return "cincuenta y ";
else return "cincuenta";
if(u) return "sesenta y ";
else return "sesenta ";
if(u) return "setenta y ";
else return "setenta ";
if(u) return "ochenta y ";
else return "ochenta ";
if(u) return "noventa y ";
else return "noventa ";
}
}
/* Funcin para las centenas. ---------------------------- */
char* centenas(unsigned short c,unsigned short d,
unsigned short u)
{
switch(c)
{
case 0:
return "";
case 1:
if(!u && !d) return "cien ";
else return "ciento ";
case 2:
return "doscientos ";
case 3:
return "tresientos ";
case 4:
return "cuatrocientos ";
case 5:
return "qinientos ";
case 6:
return "seiscientos ";
case 7:
return "setecientos ";
case 8:
return "ochocientos ";
case 9:
return "novecientos ";
}
}
271
272
PARTE II:
Profundizando
en C.
274
275
276
CAPTULO 10
ASIGNACIN DINMICA DE
MEMORIA
Cuando hemos hablado de los vectores, hemos remarcado la idea de
que la dimensin indicada en su declaracin debe ser un literal:
long numeros[100];
Con esa sentencia creamos un vector de 100 enteros largos: acabamos
de reservar 400 bytes para codificar valores de este tipo de dato.
En muchas ocasiones nos podra interesar que la declaracin de este
vector se hiciera mediante una variable, y poder as reservar el espacio
de memoria que realmente necesitramos. Por ejemplo, al crear unas
matrices en un programa de operatoria de matrices, vendra bien poder
codificar algo del siguiente estilo:
long f, c;
printf(Nmero de filas de su matriz A ... );
scanf(%ld,&f);
printf(Nmero de columnas de su matriz A ... );
scanf(%ld,&c);
long A[f][c];
Y as, quedara creada la matriz de acuerdo con la dimensin deseada.
Pero este cdigo es errneo.
Esta forma de crear matrices no puede llevarse a cabo bajo ningn
concepto, porque la reserva de memoria de una matriz o de un vector
se realiza en tiempo de compilacin: el compilador, antes de ejecucin
alguna, debe saber cunta memoria se debe reservar y para qu
dominio de valores.
Eso es un serio problema. Si deseamos hacer un programa que necesite
manejar informacin con arrays o matrices, siempre habr que
dimensionar esos espacios de memoria de manera que consideren el
caso ms desfavorable: para la ocasin en que se necesite un mayor
tamao. Y luego recorrer ese array o esa matriz acotando el uso a la
zona que nos interesa.
Un ejemplo simple: si queremos crear una matriz cuadrada, y su
dimensin puede estar en un valor entre 2 y 100, el programador
deber hacer lo siguiente:
long matriz[100][100];
short int dimension;
short int i, j;
printf(Indique la dimensin de la matriz ... );
scanf(%hd,&dimension);
// Introduccin de valores...
for( i = 0 ; i < dimension ; i++)
for(j = 0 ; j < dimension j++)
{
printf(matriz[%hd][%hd] = , i, j);
scanf(%ld,&matriz[i][j]);
}
Y as, si el usuario introduce el valor 3, entonces emplearemos 36 bytes
para el manejo de nuestra matriz de 9 enteros largos. Pero la memoria
reservada ser de cuarenta mil bytes.
C dispone de un modo para lograr que la reserva de memoria quede
optimizada, haciendo esa reserva en tiempo de ejecucin, cuando el
278
Funcin malloc
Esta funcin malloc queda definida en la biblioteca stdlib.h. Tambin la
encontramos en la biblioteca alloc.h. Recomendamos hacer uso siempre
de la definicin recogida en stdlib.
Su prototipo es el siguiente:
void *malloc(size_t size);
Donde el tipo de dato size_t lo consideraremos ahora nosotros
simplemente como un tipo de dato long.
La funcin reserva tantos bytes consecutivos como indique el valor de la
variable size y devuelve la direccin del primero de esos bytes. Esa
direccin debe ser recogida por una variable puntero: si no se recoge,
tendremos la memoria reservada pero no podremos acceder a ellas
porque no sabremos dnde ha quedado hecha esa reserva.
Como se ve, la direccin de devuelve con el tipo de dato void*. La
funcin malloc as lo hace porque est definida para hacer reserva de
bytes, al margen de para qu se reservan esos bytes. Pero no tendra
sentido trabajar con direcciones de tipo de dato void. Por eso, en la
asignacin al puntero que debe recoger la direccin del array, se debe
indicar, mediante el operador forzar tipo, el tipo e la direccin.
Veamos un ejemplo:
long dim;
float *vector;
printf(Dimensin de su vector ...);
279
scanf(%ld,&dim);
vector = (float*)malloc(4 * dim);
En el ejemplo, hemos reservado tantos bytes como hace falta para
reservar memoria para un array de float de la dimensin indicada por el
usuario.
Como
cada
variable
float
ocupa
cuatro
bytes
hemos
280
281
282
Funcin free
Esta funcin viene tambin definida en la biblioteca stdlib.h. Su
cometido es liberar la memoria que ha sido reservada mediante la
funcin malloc, o calloc, o realloc. Su sintaxis es la siguiente:
void free(void *block);
Donde block es un puntero que tiene asignada la direccin de memoria
de cualquier bloque de memoria que haya sido reservada previamente
mediante una funcin de memoria dinmica, como por ejemplo la
funcin malloc ya presentada y vista en el epgrafe anterior.
La memoria que reserva el compilador ante una declaracin de un
vector se ubica en la zona de variables de la memoria. La memoria que
se reserva mediante la funcin malloc queda ubicada en otro espacio de
memoria distinto, que se llama habitualmente heap. En ningn caso se
puede liberar, mediante la funcin free, memoria que haya sido creada
esttica, es decir, vectores declarados como tales en el programa y que
reservan la memoria en tiempo de ejecucin. Es esa memoria del heap
la que libera la funcin free. Si se aplica esa funcin sobre memoria
esttica se produce un error en tiempo de compilacin.
283
284
Una vez tenemos claro el algoritmo que nos permitir llegar a crear la
tabla de los primos, el siguiente paso ser implementar el programa que
nos haga este proceso.
64.
285
}
TablaPrimos(num, primos, MAX + 1);
free(num);
// Mostramos los primos por pantalla ...
printf("Primos menores que %ld... \n\n",MAX);
for(i = 0 ; *(primos + i) != 0 ; i++)
printf("%10ld", *(primos + i));
free(primos);
}
long Criba(char* num, long rango)
{
long i, j;
// En principio marcamos todos los elementos como PRIMOS
for(i = 0 ; i < rango; i++)
num[i] = 'p';
for(i = 2 ; i < sqrt(rango) ; i++)
if(num[i] == 'p')
for(j = 2 * i ; j < rango ; j += i)
num[j] = 'c';
for( i = 1 , j = 0 ; i < rango ; i++)
if(num[i] == 'p') j++;
return j;
}
void TablaPrimos(char* num, long* primos, long rango)
{
long i, j;
for(i = 1 , j = 0 ; i < rango ; i++)
if(num[i] == 'p')
{
*(primos + j) = i;
j++;
}
*(primos + j) = 0;
}
Vamos viendo este programa, resuelto mediante funciones. Primero
reservamos, en la funcin main, un espacio de memoria suficiente para
albergar tantas variables char (de un bytes cada una) como indica MAX
+ 1. MAX es la macro que recoge el lmite superior sobre el que se van a
buscar todos los primos inferiores. A esa memoria, que manejaremos
desde un puntero tipo char* que hemos llamado num, le asignamos a
todas sus posiciones el valor p, que vamos a entender que significa
primo. Y es sobre ese vector sobre quien se hace la criba de
286
287
288
getchar();
exit(0);
}
// Creacin de las columnas ...
for( i = 0 ; i < f ; i++)
{
*(p + i) = (float*)malloc(c * sizeof(float));
if(*(p + i) == NULL)
{
printf("Memoria insuficiente.\n");
printf("La ejeccin se interrumpir.\n");
printf("Pulse una tecla para terminar...");
getchar();
exit(0);
}
}
// Asignacin de valores ...
for(i = 0 ; i < f ; i++)
for(j = 0 ; j < c ; j++)
{
printf("matriz[%2hd][%2hd] = ", i, j);
scanf("%f",*(p + i) + j);
}
// Mostrar la matriz por pantalla ...
for(i = 0 ; i < f ; i++)
{
printf("\n");
for(j = 0 ; j < c ; j++)
printf("%6.2f\t",*(*(p + i) + j));
}
// Liberar la memoria ...
for(i = 0 ; i < f ; i++)
free(*(p + i));
free(p);
}
Desde luego, hemos trabajado con operatoria de punteros. Ya se explic
que hablar de *(*(p + i) + j) es lo mismo que hablar de p[i][j]. Y que
hablar de (*(p + i) + j) es hablar de &p[i][j].
Y as se podra haber codificado. Veamos algunas de las lneas del
cdigo anterior, recogidas con operatoria de ndices:
// Asignacin de valores ...
for(i = 0 ; i < f ; i++)
for(j = 0 ; j < c ; j++)
{
printf("matriz[%2hd][%2hd] = ", i, j);
scanf("%f",&p[i][j]);
}
289
290
}
// Mostrar la matriz por pantalla ...
for(i = 0 ; i < f ; i++)
{
printf("\n");
for(j = 0 ; j < c ; j++)
printf("%6.2f\t",*(p + (i * c + j)));
}
// Mostrar los datos como vector lineal ...
printf("\n\n");
for(i = 0 ; i < f * c ; i++)
printf("%6.2f\t",*(p + i));
// Liberar la memoria ...
free(p);
}
291
Ejercicios.
65.
ya
est
ocupado
con
un
nmero
anterior),
debajo
de
la
ltima
casilla
rellenada.
292
el punto anterior.
Escriba un programa que genere el cuadro mgico de la
dimensin que el usuario desee, y lo muestre luego por
pantalla.
usi Dimension(void);
void CuadroACero(uli**,usi);
uli** AsignarMemoria(uli**,usi);
void CrearCuadro(uli**,usi);
void MostrarCuadro(uli**, usi);
void main(void)
{
usi dim;
uli **cuadro;
usi i;
do
{
// Valor de la dimensin ...
dim = Dimension();
if(!dim) break;
// Asignacin de memoria ...
cuadro = AsignarMemoria(cuadro,dim);
if(cuadro == NULL) break;
// Inicializamos la matriz a cero...
CuadroACero(cuadro,dim);
// Asignar valores a los elementos del cuadro mgico...
CrearCuadro(cuadro,dim);
293
294
uli** AsignarMemoria(uli**C,usi d)
{
usi i;
if((C = (uli**)malloc(d * sizeof(uli*))) == NULL)
{
printf("\nError (1) de asignacion de
memoria.");
printf("\nLa ejecucion del programa no
puede continuar.");
printf("\nPulse cualquier tecla para terminar
la aplicacion");
getchar();
return C;
}
for(i = 0 ; i < d ; i++)
if((*(C + i)=(uli*)malloc(d*sizeof(uli)))==NULL)
{
printf("\nError (2) de asignacin de
memoria.");
printf("\nLa ejecucin del programa no
puede continuar.");
printf("\nPulse cualquier tecla para
terminar la aplicacion");
getchar();
C = NULL;
}
return(C);
}
/* ------------------------------------------------------- */
/*
Funcin CrearCuadro()
*/
/* ------------------------------------------------------- */
void CrearCuadro(uli**C,usi d)
{
usi posX, posY, antX, antY, elem;
// Posicin inicial: el centro de la primera fila...
posX = d / 2;
posY = 0;
elem = 1;
while(elem <= d * d)
{
*(*(C + posX) + posY) = elem;
// Nueva posicin X ...
antX = posX;
posX = posX ? posX - 1 : d - 1;
// Nueva posicion Y ...
295
antY = posY;
posY = posY ? posY - 1 : d - 1;
// Si la casilla ya ha sido ocupada ...
if(*(*(C + posX) + posY))
{
posX = antX;
posY = antY == d - 1 ? 0 : antY + 1;
}
elem++;
/* ------------------------------------------------------- */
/*
Funcin MostrarCuadro()
*/
/* ------------------------------------------------------- */
void MostrarCuadro(uli**C, usi d)
{
uli *sumaf,*sumac,sumad[2];
usi i, j;
sumac = (uli*)malloc(d * sizeof(uli));
sumaf = (uli*)malloc(d * sizeof(uli));
if(sumaf != NULL)
{
for(i = 0 ; i < d ; i++)
{
*(sumaf + i) = 0;
for(j = 0 ; j < d ; j++)
*(sumaf + i) += *(*(C + i) + j);
}
}
if(sumac != NULL)
{
for(i = 0 ; i < d ; i++)
{
*(sumac + i) = 0;
for(j = 0 ; j < d ; j++)
*(sumac + i) += *(*(C + j) + i);
}
}
sumad[0] = sumad[1] = 0;
for(i = 0 ; i < d ; i++)
sumad[0] += *(*(C + i) + i);
for(i = 1 ; i <= d ; i++)
sumad[1] += *(*(C + d - i) + i - 1);
for(i = 0 ; i < d ; i++)
296
printf("\n");
for(j = 0 ; j < d ; j++)
printf("%5hu",*(*(C + j) + i));
printf(" -> %lu\n",*(sumaf + i));
printf("\n");
for(i = 0 ; i < d ; i++)
printf("
|");
printf("\n");
for(i = 0 ; i < d ; i++)
printf("
V");
printf("\n\n");
for(i = 0 ; i < d ; i++)
printf("%5lu",*(sumac + i));
printf("\n\nSuma Diagonal principal .... %5lu",
sumad[0]);
printf("\n\nSuma Diagonal secundaria ... %5lu",
sumad[1]);
}
297
298
CAPTULO 11
ALGUNOS USOS CON FUNCIONES
En un captulo anterior hemos visto lo bsico sobre funciones. Con todo
lo dicho en ese tema se puede trabajar perfectamente en C, e
implementar multitud de programas, con buena modularidad.
En este tema queremos presentar muy brevemente algunos usos ms
avanzados de las funciones: distintas maneras en que pueden ser
invocadas. Punteros a funciones, vectores de punteros a funciones, el
modo de pasar una funcin como argumento de otra funcin. Son
modos de hacer sencillos, que aaden, a todo lo dicho en el tema
anterior, posibilidades de diseo de programas.
Otra cuestin que abordaremos en este tema es cmo definir aquellas
funciones de las que desconozcamos a priori el nmero de parmetros
que han de recibir. De hecho, nosotros ya conocemos algunas de esas
funciones: la funcin printf puede ser invocada con un solo parmetro
(la cadena de caracteres que no imprima ningn valor) o con tantos
como se quiera: tantos como valores queramos que se impriman en
Punteros a funciones
En los primeros temas de este manual hablbamos de que toda la
informacin de un ordenador se guarda en memoria. No slo los datos.
Tambin las instrucciones tienen su espacio de memoria donde se
almacenan y pueden ser ledas. Todo programa debe ser cargado sobre
la memoria principal del ordenador antes de comenzar su ejecucin.
Y si una funcin cualquiera tiene una ubicacin en la memoria, entonces
podemos hablar de la direccin de memoria de esa funcin. Desde
luego, una funcin ocupar ms de un byte, pero se puede tomar como
direccin de memoria de una funcin aquella donde se encuentra la
entrada de esa funcin.
Y si tengo definida la direccin de una funcin No podr definir un
puntero que almacene esa direccin? La respuesta es que s, y ahora
veremos cmo poder hacerlo. Por tanto, podremos usar un puntero para
ejecutar una funcin. Ese puntero ser el que tambin nos ha de
permitir poder pasar una funcin como argumento de otra funcin.
La declaracin de un puntero a una funcin es la declaracin de una
variable. sta puede ser local, y de hecho, como siempre, ser lo
habitual. Cuando se declara un puntero a funcin para poder asignarle
posteriormente la direccin de una o u otra funcin, la declaracin de
ese puntero a funcin debe tener un prototipo coincidente con las
funciones a las que se desea apuntar.
Supongamos que tenemos las siguientes funciones declaradas al inicio
de un programa:
tipo_funcin nombre_funcin_1 (tipo1, , tipoN);
tipo_funcin nombre_funcin_2 (tipo1, , tipoN);
300
esta
declaracin
podemos
hacer
las
siguientes
importantes
observaciones:
1. tipo_funcin debe coincidir con el tipo de la funcin a la que va a
apuntar el puntero a funcin. De la misma manera la lista de
argumentos debe ser coincidente, tanto en los tipos de dato que
intervienen como en el orden. En definitiva, los prototipos de la
funcin y de puntero deber ser idnticos.
2. Si
*puntero_a_funcin
NO
viniese
recogido
entre
parntesis
301
sum(float,
res(float,
pro(float,
div(float,
float);
float);
float);
float);
void main(void)
{
float a, b;
unsigned char op;
float (*operacion)(float, float);
printf("Primer operador ... ");
scanf("%f",&a);
printf("Segundo operador ... ");
scanf("%f",&b);
printf("Operacin ( + , - , * , / ) ... ");
do
op = getchar();
while(op !='+' && op !='-' && op !='*' && op !='/');
switch(op)
302
{
case
case
case
case
}
'+':
'-':
'*':
'/':
operacion
operacion
operacion
operacion
=
=
=
=
sum;
res;
pro;
div;
break;
break;
break;
vlidas
como
programadores.
Pero
desde
luego
las
303
i
i
i
i
=
=
=
=
0;
1;
2;
3;
break;
break;
break;
304
}
Y ahora la ejecucin de la funcin ser como sigue:
printf(\n\n%f %c %f = %f, a, op, b, (*operacin[i])(a, b));
305
Entonces
cuando
se
compile
nombre_funcin
el
compilador slo sabr que esta funcin recibir como argumento, entre
otras cosas, la direccin de una funcin que se ajusta al prototipo
declarado como parmetro. Cul sea esa funcin es cuestin que no se
conocer hasta el momento de la ejecucin y de la invocacin a esa
funcin.
La forma en que se llamar a la funcin ser la lgica de acuerdo con
estos parmetros. El nombre de la funcin y seguidamente, entre
parntesis, todos sus parmetros en el orden correcto. En el momento
de recoger el argumento de la direccin de la funcin se har de la
siguiente forma:
*puntero_a_funcin(parmetros)
De nuevo ser conveniente seguir con el ejemplo anterior, utilizando
ahora una quinta funcin para realizar la operacin y mostrar por
pantalla su resultado:
#include <stdio.h>
#include <conio.h>
float sum(float, float);
float res(float, float);
float pro(float, float);
float div(float, float);
void mostrar(float, char, float, float(*f)(float, float));
void main(void)
{
float a, b;
unsigned char op;
float (*operacion[4])(float, float) ={sum,res,pro,div};
do
{
printf("\n\nPrimer operador ... ");
scanf("%f",&a);
printf("Segundo operador ... ");
scanf("%f",&b);
printf("Operacin ... \n)");
printf("\n\n1. Suma\n2. Resta);
printf(\n3. Producto\n4. Cociente");
printf("\n\n\tSu opcin (1 , 2 , 3 , 4) ... ");
306
do
op = getche();
while(op - '0' < 1 || op - '0' > 4 );
mostrar(a,op,b,operacion[(short)(op - '1')]);
printf("\n\nOtra operacin (s / n) ... ");
do
op = getche();
while(op != 's' && op != 'n');
}while(op == 's');
}
float sum(float x, float y)
{ return x + y; }
float res(float x, float y)
{ return x - y; }
float pro(float x, float y)
{ return x * y; }
float div(float x, float y)
{ return y ? x / y : 0; }
void mostrar(float x,char c,float y,float(*f)(float,float))
{
if(c == '1') c = '+';
else if(c == '2') c = '-';
else if(c == '3') c = '*';
else c = '/';
printf("\n\n%f %c %f = ", x, c, y);
printf("%f.", (*f)(x,y));
}
Vamos viendo poco a poco el cdigo. Primero aparecen las declaraciones
de cinco funciones: las encargadas de realizar suma, resta, producto y
cociente de dos valores float. Y luego, una quinta funcin, que hemos
llamado mostrar, que tiene como cuarto parmetro un puntero a
funcin. La declaracin de este parmetro es como se dijo: el tipo del
puntero de funcin, el nombre del puntero, recogido entre parntesis y
precedido de un asterisco, y luego, tambin entre parntesis, la lista de
parmetros del puntero a funcin. As ha quedado declarada.
Y luego comienza la funcin principal, main, donde viene declarado un
vector de cuatro punteros a funcin. A cada uno de ellos le hemos
asignado una de las cuatro funciones.
307
el
cuarto
parmetro
es
la
direccin
de
la
operacin
308
309
310
311
Estudio de tiempos
A veces es muy ilustrativo poder estudiar la velocidad de algunas
aplicaciones que hayamos implementado en C.
En algunos programas de ejemplo de captulos anteriores habamos
presentado un programa que ordenaba cadenas de enteros. Aquel
programa, que ahora mostraremos de nuevo, estaba basado en un
mtodo de ordenacin llamado mtodo de la burbuja: consiste en ir
pasando para arriba aquellos enteros menores, de forma que van
quedando cada vez ms abajo, o ms atrs (segn se quiera) los
enteros mayores. Por eso se llama el mtodo de la burbuja: porque lo
liviano sube.
Vamos a introducir una funcin que controla el tiempo de ejecucin. Hay
funciones bastante diversas para este estudio. Nosotros nos vamos
ahora a centrar en una funcin, disponible en la biblioteca time.h,
llamada clock, cuyo prototipo es:
clock_t clock(void);
Vamos a considerar por ahora que el tipo de dato clock_t es
equivalente a tipo de dato long (de hecho as es). Esta funcin est
recomendada para medir intervalos de tiempo. El valor que devuelve es
proporcional al tiempo trascurrido desde el inicio de ejecucin del
programa en la que se encuentra esa funcin. Ese valor devuelto ser
mayor cuanto ms tarde se ejecute esta funcin clock, que no realiza
tarea alguna ms que devolver el valor actualizado del contador de
tiempo. Cada breve intervalo de tiempo (bastantes veces por segundo:
no vamos ahora a explicar este aspecto de la funcin) ese contador que
indica el intervalo de tiempo transcurrido desde el inicio de la ejecucin
del programa, se incrementa en uno.
Un modo de estudiar el tiempo trascurrido en un proceso ser el
siguiente:
time_t t1, t2;
t1 = clock();
312
313
314
Creacin de MACROS
La directiva #define, que ya hemos visto, permite la definicin de
macros.
Una macro es un bloque de sentencias a las que se les ha asignado un
nombre o identificador.
Una macro no es una funcin. Es cdigo que se inserta all donde
aparece su identificador.
Veamos un ejemplo sencillo:
#include <stdio.h>
// Definicin de la macro ...
#define cuadrado(x) x * x
void main(void)
{
short a;
unsigned long b;
printf("Intoduzca el valor de a ... ");
scanf("%hd",&a);
printf("El cuadrado de %hd es %lu", a, cuadrado(a));
}
cuadrado NO es una funcin, aunque su invocacin tenga la misma
apariencia. En el cdigo no aparece ni un prototipo con ese nombre, ni
315
el
texto
todas
las
veces
donde
venga
escrita
la
cadena
316
\
\
\
\
\
\
\
\
\
\
\
Las barras invertidas al final de cada lnea indican que aunque hay un
salto de lnea en el editor, el texto contina en la lnea siguiente. Deben
ponerse tal cual, sin espacio en blanco alguno posteriormente a ellas.
Las diferencias principales con respecto al cdigo de la funcin se basan
en las siguientes peculiaridades de las macros:
317
318
\
\
\
\
\
\
\
\
319
// Un parmetro
// Dos parmetros
// Tres parmetros
Vamos a ver en este epgrafe cmo lograr definir una funcin en la que
el nmero de parmetros sea variable, en funcin de las necesidades
que tenga el usuario en cada momento.
Existen una serie de macros que permiten definir una funcin como si
tuviera una lista variable de parmetros. Esas macros, que ahora
veremos, estn definidas en la biblioteca stdarg.h.
320
empleadas
para
operar
sobre
objetos
de
tipo
lista
de
argumentos. Este tipo de dato tendr una forma similar a una cadena de
caracteres.
Toda funcin con un nmero de argumentos variable deber tener
declarada, en su cuerpo, una variable de tipo de dato va_list.
tipo nombre_funcion (tipo_1,[..., tipo_N], ...)
{
va_list argumentos /* lista de argumentos */
321
Lo primero que habr que hacer con esta variable de tipo va_list ser
inicializarla con la lista de argumentos variables recibida en la llamada a
la funcin.
Para inicializar esa variable se emplea una de las tres macros definidas
en la biblioteca stdarg.h: la macro va_start, que tiene la siguiente
sintaxis:
void va_start(va_list ap, lastfix);
Donde ap es la variable que hemos creado como de tipo de dato
va_list, y donde lastfix es el ltimo argumento fijo de la lista de
argumentos.
La macro va_start asigna a la variable ap la direccin del primer
argumento variable que ha recibido la funcin. Necesita, para esta
operacin, recibir el nombre del ltimo parmetro fijo que recibe la
funcin como argumento. Se guarda en la variable ap la direccin del
comienzo de la lista de argumentos variables. Esto obliga a que en
cualquier funcin con nmero de argumentos variable exista al menos
un argumento de los llamados aqu fijos, con nombre en la definicin de
la funcin. En caso contrario, sera imposible obtener la direccin del
comienzo para una lista de los restantes argumentos.
Ya tenemos localizada la cadena de argumentos variables. Ahora ser
necesario recorrerla para extraer todos los argumentos que ha recibido
la funcin en su actual llamada. Para eso est definida una segunda
macro de stdarg.h: la macro va_arg. Esta rutina o macro extrae el
siguiente argumento de la lista.
Su sintaxis es:
tipo va_arg(va_list ap, tipo);
Donde el primer argumento es, de nuevo, nuestra lista de argumentos,
llamada ap, que ya ha quedado inicializada con la macro va_start. Y
tipo es el tipo de dato del prximo parmetro que se espera encontrar.
322
primer
parmetro
(la
cadena
que
ha
de
ser
impresa)
los
especificadores de formato.
La rutina va extrayendo uno tras otro los argumentos de la lista variable
de argumentos. Para cada nuevo argumento se invoca de nuevo a la
macro. La macro extrae de la lista ap el siguiente parmetro (que ser
del tipo indicado) y avanza el puntero al siguiente parmetro de la lista.
La macro devuelve el valor extrado de la lista. Para extraer todos los
elementos de la lista habr que invocar a la macro va_arg tantas veces
como sea necesario. De alguna manera la funcin deber detectar que
ha terminado ya de leer en la lista de variables. Por ejemplo, en la
funcin printf, se invocar a la macro va_arg tantas veces como veces
haya aparecido en la primera cadena un especificador de formato: un
carcter % no precedido del carcter \.
Si se ejecuta la macro va_arg menos veces que parmetros se hayan
pasado en la actual invocacin, la ejecucin no sufre error alguno:
simplemente dejarn de leerse esos argumentos. Si se ejecuta ms
veces que parmetros variables se hayan pasado, entones el resultado
puede ser imprevisible.
Si, despus de la cadena de texto que se desea imprimir, la funcin
printf recoge ms expresiones (argumentos en la llamada) que
caracteres % ha consignado en la cadena (primer argumento de la
llamada), no pasar percance alguno: simplemente habr argumentos
que no se imprimirn y ni tan siquiera sern extrados de la lista de
parmetros. Pero si hay ms caracteres % que variables e nuestra lista
variable de argumentos, entonces la funcin printf ejecutar la macro
va_arg en busca de argumentos no existentes. En ese caso, el
resultado ser completamente imprevisible.
Y cuando ya se haya recorrido completa la lista de argumentos,
entonces deberemos ejecutar una tercera rutina que restablece la pila
de llamada a funciones. Esta macro es necesaria para permitir la
323
324
las
funciones
con
parmetros
variables
presentan
esperados.
325
326
327
Ejercicios
66.
67.
#include <stdio.h>
#include <math.h>
#define CUADRADO(x) x*x
#define SUMA(x,y) CUADRADO(x) + CUADRADO(y)
#define RECTO(x,y) sqrt(SUMA(x,y))
void main(void)
{
printf("%2.1f",RECTO(3,4));
}
328
CAPTULO 12
ESTRUCTURAS ESTTICAS DE
DATOS Y DEFINICIN DE TIPOS
En uno de los primeros temas hablamos largamente de los tipos de
dato. Decamos que un tipo de dato determina un dominio (conjunto de
valores posibles) y unos operadores definidos sobre esos valores.
Hasta el momento hemos trabajado con tipos de dato estndar en C.
Pero con frecuencia hemos hecho referencia a que se pueden crear otros
diversos tipos de dato, ms acordes con las necesidades reales de
muchos problemas concretos que se abordan con la informtica.
Hemos visto, de hecho, ya diferentes tipos de dato a los que por ahora
no hemos prestado atencin alguna, pues an no habamos llegado a
este captulo: tipo de dato size_t, time_t: cada vez que nos los
hemos encontrado hemos despejado con la sugerencia de que se
considerasen, sin ms, tipos de dato iguales a long.
En este tema vamos a ver cmo se pueden definir nuevos tipos de dato.
330
331
O tambin:
typedef char* CADENA;
Y as, a partir de ahora, en todo nuestro programa, nos bastar declarar
las variables enteras como uno de esos nuevos cuatro tipos. O declarar
una cadena de caracteres como una variable de tipo CADENA. Es
evidente que con eso no se ha creado un nuevo tipo de dato, sino
simplemente un nuevo identificador para un tipo de dato ya existente.
Tambin se puede dar nombre a los nuevos tipos de dato creados. En el
ejemplo del tipo de dato enum llamado semaforo, podramos hacer:
typedef enum {verde, amarillo, rojo} semaforo;
332
333
Estructuras de datos en C
Una estructura de datos en C es una coleccin de variables, no
necesariamente del mismo tipo, que se referencian con un nombre
comn. Lo normal ser crear estructuras formadas por variables que
tengan alguna relacin entre s, de forma que se logra compactar la
informacin, agrupndola de forma cabal. Cada variable de la estructura
se llama, en el lenguaje C, elementos de la estructura. Este concepto es
equivalente al presentado antes al hablar de campos.
La sintaxis para la creacin de estructuras presenta diversas formas.
Empecemos viendo una de ellas:
struct nombre_estructura
{
tipo_1 identificador_1;
tipo_2 identificador_2;
...
tipo_N identificador_N;
334
};
La definicin de la estructura termina, como toda sentencia de C, en un
punto y coma.
Una vez se ha creado la estructura, y al igual que hacamos con las
uniones, podemos declarar variables del nuevo tipo de dato dentro de
cualquier funcin del programa donde est definida la estructura:
struct nombre_estructura variable_estructura;
Y el modo en que accedemos a cada uno de los elementos (o campos)
de la estructura (o registro) ser mediante el operador miembro, que
se escribe con el identificador punto (.):
variable_estructura.identificador_1
Y, por ejemplo, para introducir datos en la estructura haremos:
variable_estructura.identificador_1 = valor_1;
La declaracin de una estructura se hace habitualmente fuera de
cualquier funcin, puesto que el tipo de dato trasciende el mbito de
una funcin concreta. De todas formas, tambin se puede crear el tipo
de dato dentro de la funcin, cuando ese tipo de dato no va a ser
empleado ms all de esa funcin. En ese caso, quiz no sea necesario
siquiera dar un nombre a la estructura, y se pueden crear directamente
las variables que deseemos de ese tipo, con la siguiente sintaxis:
struct
{
tipo_1 identificador_1;
tipo_2 identificador_2;
...
tipo_N identificador_N;
}nombre_variable;
Y as queda definida la variable nombre_variable, de tipo de dato
struct. Evidentemente, esta declaracin puede hacerse fuera de
cualquier funcin, de forma que la variable que generemos sea de
mbito global.
335
336
337
\n");
");
");
");
");
C = restc(A,B);
mostrar(C);
// PRODUCTO ...
printf("\n\n");
mostrar(A);
printf(" * ");
mostrar(B);
C = prodc(A,B);
mostrar(C);
}
complejo sumac(complejo c1, complejo c2)
{
c1.real += c2.real;
c1.imag += c2.imag;
return c1;
}
complejo restc(complejo c1, complejo c2)
{
c1.real -= c2.real;
c1.imag -= c2.imag;
return c1;
}
complejo prodc(complejo c1, complejo c2)
{
complejo S;
S.real = c1.real + c2.real; - c1.imag * c2.imag;
S.imag = c1.real + c2.imag + c1.imag * c2.real;
return S;
}
void mostrar(complejo X)
{
printf("(% .2lf%s%.2lf * i) ", X.real, X.imag > 0 ? "
+" : " " , X.imag);
}
As podemos ir definiendo un nuevo tipo de dato, con un dominio que es
el producto cartesiano del dominio de los double consigo mismo, y con
unos operadores definidos mediante funciones.
Las nicas operaciones que se pueden hacer sobre la estructura (aparte
de las que podamos definir mediante funciones) son las siguientes:
operador direccin (&), porque toda variable, tambin las estructuradas,
338
339
340
}
for(i = 0 ; i < n ; i++)
{
printf("\n\nAsignatura %hd ... \n",i + 1);
printf("clave ......... ");
scanf("%ld",&(curr + i)->clave);
printf("Descripcion ... ");
gets((curr + i)->descr);
printf("creditos ...... ");
scanf("%f",&(curr + i)->cred);
}
// Listado ...
for(i = 0 ; i < n ; i++)
{
printf("(%10ld)\t",(curr + i)->clave);
printf("%s\t",(curr + i)->descr);
printf("%4.1f creditos\n",(curr + i)->cred);
}
}
Anidamiento de estructuras
Podemos definir una estructura que tenga entre sus miembros una
variable que sea tambin de tipo estructura. Por ejemplo:
341
typedef struct
{
unsigned short dia;
unsigned short mes;
unsigned short anyo;
}fecha;
typedef struct
{
unsigned long clave;
char descripcion[50];
double creditos;
fecha convocatorias[3];
}asignatura;
Ahora a la estructura de datos asignatura le hemos aadido un vector de
tres elementos para que pueda consignar sus fechas de exmenes en
las tres convocatorias. EL ANSI C permite hasta 15 niveles de
anidamiento de estructuras.
El modo de llegar a cada campo de la estructura fecha es, como
siempre, mediante los operadores de miembro. Por ejemplo, si
queremos que la primera convocatoria se realice el 15 de enero del
presente ao, la segunda convocatoria el 21 de junio y la tercera el 1 de
septiembre, las rdenes debern ser:
#include <time.h>
#include <stdio.h>
#include <string.h>
typedef struct
{
unsigned short dia;
unsigned short mes;
unsigned short anyo;
}fecha;
typedef struct
{
unsigned long clave;
char descripcion[50];
double creditos;
fecha c[3];
}asignatura;
void main(void)
{
342
asignatura asig;
time_t bloquehoy;
struct tm *hoy;
bloquehoy = time(NULL);
hoy = localtime(&bloquehoy);
asig.clave = 10102301;
*asig.descripcion = '\0';
strcat(asig.descripcion,"fundamentos de informtica");
asig.creditos = 7.5;
asig.c[0].dia = 15;
asig.c[0].mes = 1;
asig.c[0].anyo = hoy->tm_year - 100;
asig.c[1].dia = 21;
asig.c[1].mes = 6;
asig.c[1].anyo = hoy->tm_year - 100;
asig.c[2].dia = 1;
asig.c[2].mes = 9;
asig.c[2].anyo = hoy->tm_year - 100;
printf("Asignatura %10ld\n",asig.clave);
printf("%s\t",asig.descripcion);
printf("%4.1lf\n", asig.creditos);
printf("\npriemra convocatoria ... ");
printf("%2hu-%2hu-%02hu", asig.c[0].dia,
asig.c[0].mes,asig.c[0].anyo);
printf("\nsegunda convocatoria ... ");
printf("%2hu-%2hu-%02hu", asig.c[1].dia,
asig.c[1].mes,asig.c[1].anyo);
printf("\ntercera convocatoria ... ");
printf("%2hu-%2hu-%02hu", asig.c[2].dia,
asig.c[2].mes,asig.c[2].anyo);
Hemos asignado a cada uno de los tres elementos del vector c los
valores de da, mes y ao correspondientes a cada una de las tres
convocatorias. Hemos utilizado ndices de vectores para referenciar cada
una de las tres fechas. Podramos haber trabajado tambin con
operatoria de punteros. Por ejemplo, la referencia al da de la segunda
convocatoria es, con operatoria de ndices
asig.c[1].dia = 21;
y mediante operatoria de punteros:
343
344
typedef union
{
tipo_1 identificador_1;
tipo_2 identificador_2;
...
tipo_N identificador_N;
} nombre_union;
O en cualquiera otra de las formas que hemos visto para la creacin de
estructuras.
Es responsabilidad del programador mantener la coherencia en el uso de
esta variable: si la ltima vez que se asign un valor a la unin fue
sobre un miembro de un determinado tipo, luego, al acceder a la
informacin de la unin, debe hacerse con referencia a un miembro de
un tipo de dato adecuado y coherente con el ltimo que se emple. No
tendra sentido almacenar un dato de tipo float de uno de los campos
de la unin y luego querer leerlo a travs de un campo de tipo char. El
resultado de una operacin de este estilo es imprevisible.
Veamos un ejemplo, y comparmoslo con una estructura de definicin
similar:
#include <stdio.h>
#include <string.h>
typedef union
{
long dni;
char ss[30];
}ident1;
typedef struct
{
long dni;
char ss[30];
}ident2;
// nmero de dni.
// nmero de la seg. social.
// nmero de dni.
// nmero de la seg. social.
void main(void)
{
ident1 id1;
ident2 id2;
printf("tamao de la union: %ld\n",sizeof(ident1));
printf("tamao de la estructura:%ld\n",sizeof(ident2));
// Datos de la estructura ...
345
id2.dni = 44561098;
*(id2.ss + 0) = NULL;
strcat(id2.ss,"12/0324/5431890");
printf("\nid2.dni = %ld\n",id2.dni);
printf("id2.ss = %s\n",id2.ss);
// Datos de la unin ...
*(id1.ss + 0) = NULL;
strcat(id1.ss,"12/0324/5431890");
printf("\nid1.dni = %ld (mal)\n",id1.dni);// Mal.
printf("id1.ss = %s\n",id1.ss);
id1.dni = 44561098;
printf("\nid1.dni = %ld\n",id1.dni);
printf("id1.ss = %s(mal)\n",id1.ss);
// Mal.
}
El programa ofrece la siguiente salida por pantalla;
tamao de la union: 30
tamao de la estructura:34
id2.dni = 44561098
id2.ss = 12/0324/5431890
id1.dni = 808399409 (mal)
id1.ss = 12/0324/5431890
id1.dni = 44561098
id1.ss = 324/5431890 (mal)
El tamao de la estructura es la suma del tamao de sus miembros. El
tamao de la unin es el tamao del mayor de sus miembros.
En la estructura se tienen espacios disjuntos para cada miembro: por un
lado se almacena el valor long de la variable dni y por otro la cadena de
caracteres ss. En la unin, si la ltima asignacin se ha realizado sobre
la cadena, no tiene sentido que se pretenda obtener el valor del
miembro long dni; Y si la ltima asignacin se ha realizado sobre el
campo dni, tampoco tiene sentido pretender leer el valor de la cadena
ss.
Una buena prueba de que la unin comparte la memoria la tenemos en
el ejemplo donde ha quedado como mal uso la impresin del dni. Si
vemos el valor impreso (808399409) y lo pasamos a hexadecimal
tenemos 302F3231. Y si separamos esa cifra en pares, tendremos
346
while(Test)
{
printf("%c",a.lvalor & Test ? '1' : '0');
Test >>= 1;
}
347
esa clase de valores, pero quien quiera conocerla y cotejarla bien puede
hacerlo: la norma est fcilmente accesible; y el programa que
acabamos de presentar permite la visualizacin de esa codificacin.
Ejercicios
68.
La estructura que define el nuevo tipo de dato podra ser como la que
sigue:
#define Byte4 32
typedef unsigned long int UINT4;
typedef struct
{
/* nmero de elementos UINT4 reservados. */
UINT4 D;
/* nmero de elementos UINT4 utilizados actualmente. */
UINT4 T;
/* nmero de bits significativos. */
UINT4 B;
/* El nmero, que tendr tantos elementos como indique D. */
UINT4 *N;
}NUMERO;
Que tiene cuatro elementos, que servirn para conocer bien las
propiedades del nmero (nuevo tipo de dato entero) y que nos
permitirn agilizar luego numerosas operaciones con ellos. El puntero N
recoger un array (en asignacin dinmica) donde se codificarn los
numeros; a este puntero se le asignan tantos elementos enteros largos
consecutivos como indique el campo D. Y una vez creado el nmero
(reservada la memoria), siempre convendr mantener actualizado el
348
#define msg01_001
\
"01_001: Error de asignacin de memoria en CrearNumero()\n"
void CrearNumero(NUMERO*n)
{
/* Con esta funcin asignamos una cantidad de memoria a n->N
(tantos elementos UINT4 como indique n->D) */
349
350
}
}
Test >>= 1;
n->B--;
351
*/
*/
a n2->T... */
< n2. */
> n2. */
352
CAPTULO 13
GESTIN DE ARCHIVOS
Hasta el momento, toda la informacin (datos) que hemos sido capaces
de gestionar, la hemos tomado de dos nicas fuentes: o eran datos del
programa, o eran datos que introduca el usuario desde el teclado. Y
hasta el momento, siempre que un programa ha obtenido un resultado,
lo nico que hemos hecho ha sido mostrarlo en pantalla.
Y, desde luego, sera muy interesante poder almacenar la informacin
generada por un programa, de forma que esa informacin pudiera luego
ser consultada por otro programa, o por el mismo u otro usuario. O
sera muy til que la informacin que un usuario va introduciendo por
consola quedase almacenada para sucesivas ejecuciones del programa o
para posibles manipulaciones de esa informacin.
En definitiva, sera muy conveniente poder almacenar en algn soporte
informtico (por ejemplo, en el disco del ordenador) esa informacin, y
poder luego acceder a ese disco para volver a tomarla, para actualizar la
354
355
356
357
358
359
interfaz
independiente
permite
del
que
el
dispositivo
trabajo
final
de
fsico
acceso
al
archivo
sea
donde
se
realizan
las
360
361
Apertura de archivo.
La funcin fopen abre un archivo y devuelve un puntero asociado al
mismo, que puede ser utilizado para que el resto de funciones de
manipulacin de archivos accedan a este archivo abierto.
Su prototipo es:
FILE *fopen(const char*nombre_archivo, const char
*modo_apertura);
Donde nombre_archivo es el nombre del archivo que se desea abrir.
Debe ir entre comillas dobles, como toda cadena de caracteres. El
nombre debe estar consignado de tal manera que el sistema operativo
sepa identificar el archivo de qu se trata.
Y donde modo_apertura es el modo de acceso para el que se abre el
archivo. Debe ir en comillas dobles. Los posibles modos de apertura de
un archivo secuencial con buffer son:
r
r+
w+
362
rb
wb
ab
r+b
w+b
363
gets(nombre_archivo);
fopen(nombre_archivo, w);
Y ya hemos dicho que si la funcin fopen no logra abrir el archivo,
entonces devuelve un puntero nulo. Es muy conveniente verificar
siempre que el fichero ha sido realmente abierto y que no ha habido
problemas:
FILE *archivo;
if(archivo = fopen(datos.dat, w) == NULL)
printf(No se puede abrir el archivo\n);
Dependiendo del compilador se podrn tener ms o menos archivos
abiertos a la vez. En todo caso, siempre se podrn tener, al menos ocho
archivos abiertos simultneamente.
364
365
366
367
368
}
for(i = 0 ; i < TAM ; i++)
fprintf(ARCH,"Valor %04hi-->%12.4f\n",i,or[i]);
fclose(ARCH);
ARCH = fopen("numeros.dat", "r");
if(ARCH == NULL)
{
printf("No se ha podido abrir el archivo.\n");
printf("Pulse una tecla para finalizar... ");
getchar();
exit(1);
}
0000
0001
0002
0003
0004
0005
0006
0007
0008
0009
-->
-->
-->
-->
-->
-->
-->
-->
-->
-->
9.4667
30.4444
12.5821
0.2063
16.4545
28.7308
9.9574
0.1039
18.0000
4.7018
369
370
371
fclose(ARCH);
372
373
374
while(!feof(ARCH))
{
fseek(ARCH,16,SEEK_CUR);
fscanf(ARCH,"%f",cp + i++);
}
Donde hemos indicado 16 en el desplazamiento en bytes, porque 16 son
los caracteres que no deseamos que se lean en cada lnea.
Los desplazamientos en la funcin fseek pueden ser positivos o
negativos. Desde luego, si los hacemos desde el principio lo razonable
es hacerlos positivos, y si los hacemos desde el final hacerlos negativos.
La funcin acepta cualquier desplazamiento y no produce nunca un
error. Luego, si el desplazamiento ha sido errneo, y nos hemos
posicionado en medio de ninguna parte o en un byte a mitad de dato,
entonces la lectura que pueda hacer la funcin que utilicemos ser
imprevisible.
Una ltima funcin que presentamos en este captulo es la llamada
rewind, cuyo prototipo es:
void rewind(FILE *nombre_archivo);
Que rebobina el archivo, devolviendo el puntero a su posicin inicial, al
principio del archivo.
Ejercicios
69.
375
376
el
carcter
en
mayscula,
nos
ahorramos
muchas
377
fclose(archivo);
for(i = 0 ; i < 27 ; i++) suma += letra[i];
for(i = 0 ; i < 26 ; i++)
{
printf("[ %c ]= %10ld\t",(char)(i+A),letra[i]);
printf("%7.2lf\n",((float)letra[i]/suma)*100);
}
printf("[ %c ] = %10ld\t", 165,letra[26]);
printf("%7.2lf\n",((float)letra[26] / suma) * 100);
}
70.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct
{
unsigned long clave;
378
char descr[50];
double cred;
}asignatura;
short mostrar_opciones(void);
void error(void);
short anyadir(char*);
short pantalla(char*);
short impresora(char*);
void main(void)
{
char nombre_archivo[80];
short opcion;
short oK;
printf("Nombre del archivo de asignaturas ... ");
gets(nombre_archivo);
do
{
opcion = mostrar_opciones();
switch(opcion)
{
case '1':
oK = anyadir(nombre_archivo);
if(oK) error();
break;
case '2':
oK = pantalla(nombre_archivo);
if(oK) error();
break;
case '3':
oK = impresora(nombre_archivo);
if(oK) error();
break;
case '4':
exit(1);
}
}while(1);
}
short mostrar_opciones(void)
{
char opcion;
clrscr();
printf("\n\n\t\tOpciones y Tareas");
printf("\n\n\t1. Aadir nueva asignatura.");
printf("\n\t2. Mostrar listado por pantalla.");
printf("\n\t3. Mostrar listado en archivo.");
printf("\n\t4. Salir del programa.");
printf("\n\n\t\t\tElegir opcion ... ");
do opcion = getchar(); while(opcion <'0'&&opcion >'4');
return opcion;
}
void error(void)
379
380
asignatura asig;
ARCH1 = fopen(archivo,"rb");
ARCH2 = fopen("impresora","w");
while(fread(&asig,sizeof(asig),1,ARCH1) == 1)
{
fprintf(ARCH2,"\n\nClave\t%lu", asig.clave);
fprintf(ARCH2,"\nDescripcion \t%s", asig.descr);
fprintf(ARCH2,"\nCreditos\t%6.1lf", asig.cred);
}
printf("\n\n\tPulsar una tecla para continuar ... ");
getchar();
if(ferror(ARCH1)) return 1;
fclose(ARCH1);
fclose(ARCH2);
return 0;
}
La funcin principal presenta nicamente una estructura switch que
gestiona cuatro posibles valores para la variable opcion. Esos valores se
muestran en la primera de las funciones, la funcin mostrar_opciones,
que imprime en pantalla las cuatro posibles opciones del programa,
(aadir
registros,
mostrarlos
por
pantalla,
crear
un
archivo
de
381
382