for, while, do-while
o de switch
(ver este último).switch
.for, while
o
do-while
. Se emplea poco. Por ejemplo, si a[]
es una lista de números, suponiendo incluido <math.h>
, el siguiente subprograma halla las raíces cuadradas
de los positivos y deja los negativos igual.for (i=0;i<=LONGITUD;++i){
if (a[i]<0)
continue;
a[i]=sqrt(a[i]);
}
if-else
.
switch
.while
en el que la condición se comprueba al final. Esto
es, do
instrucciones while(condición)
; ejecutará las instrucciones mientras se satisfaga
la condición. if
.for (expr1;expr2;expr3)
instrucciones
expr1;
while (expr2){
instrucciones
expr3;
}
main(){ |
main(){ |
main(){ } |
5
333 |
0
1 2
3 4
333 |
0
333 1 333
2 333 3
333 4 333 |
main(){
int c;
c=getchar();
while (c!=EOF){
putchar(c);
c=getchar();
}
}
main(){
int c;
while ((c=getchar())!=EOF){
putchar(c);
}
}
a.out < fichero.txt
es lo mismo que cat fichero.txt
. break
múltiple").
Su estructura es goto
etiqueta
; donde
etiqueta:
debe incluirse en el punto al que
se quiere saltar. Ejemplo:main(){
printf("Este programa comienza aquí.\n");
goto salto;
printf("No pasa por esta línea.\n");
salto:
printf("Y salta hasta aquí.\n");
}
Este programa comienza aquí.
Y salta hasta aquí.
if (condición)
instrucciones
else if (condición)
instrucciones
else
instrucciones
else if
se puede repetir
cuantas veces sea necesario. Las condiciones involucran típicamente
operadores lógicos o relaciones recogidos en la tabla siguiente:
|
|
main(){ |
|
printf(
"%2c %5c
%10f %4.1e %3.2f %12g" " %s\n",'A','B',70.1/7.2,70.1/7.2,70.1/7.2,70.1/7.2,"hola");
A B 9.736111 9.7e+00
9.74 9.73611 hola
%-7.2f
indica un número con dos decimales que ocupa siete espacios
y que se justifica a la izquierda. Las claves para los tipos de variables
que se quieren imprmir son:
|
|
se puede considerar
printf
como una función que devuelve
el número de caracteres impresos. Por ejemplo:
int a;
a=printf("%s\n","hola");
printf("%d\n",a);
getchar
. Véase el ejemplo
allí mostrado. void
. Es posible que su argumento sea una expresión más
complicada que una simple variable. Por ejemplo, el siguiente programa
imprime una tabla de valores de la función x^3-x+1 en
[0,1].float funcion(float);
main(){
float x;
for (x=0; x<1.01; x+=0.1)
printf("x= %.2f f(x)= %.2f\n",x,funcion(x));
}
float funcion(float t){
return (t*t*t-t+1);
}
printf
salvo que los datos se deben cargar en direcciones
(punteros) en vez de en las variables mismas. También
puede entenderse como una función que da el número
de datos correctamente introducidos. int a,v;
float b;
double c;
v=scanf("%d %f %lf",&a,&b,&c);
printf("\n Valores tecleados:\n%9d
%6f %9lf",a,b,c);
printf("\n %d\n",v);
12 (enter), 12.1 (enter), 12.2 (enter)
se
obtiene
Valores tecleados:
12 12.100000 12.200000
3
scanf
indica el formato de los datos. Por ejemplo, la instrucción
scanf("uno=%d, dos=%d",&a,&b);
requiere teclear
algo de la forma uno=1234, dos=5678
.int incr(int);
main(){
int a[4]={2,7,5,6};
int b=2,c=3;
printf("Los caracteres ocupan %d bytes\n",sizeof(char));
printf("Los enteros ocupan %d bytes\n",sizeof(int));
printf("Los reales simples ocupan %d bytes\n",sizeof(float));
printf("Los \"long double\" ocupan %d bytes\n",sizeof(long
double));
printf("La matriz entera a[4]={2,7,5,6} ocupa %d
bytes\n",sizeof(a));
printf("b, c y b+c ocupan %d, %d y %d bytes\n",
sizeof(b),sizeof(c),sizeof(a+b));
printf("La función incr ocupa %d bytes\n",sizeof(incr));
}
int incr(int n){
n++;
return n;
}
Los caracteres ocupan 1 bytes
Los enteros ocupan 4 bytes
Los reales simples ocupan 4 bytes
Los "long double" ocupan 12 bytes
La matriz entera a[4]={2,7,5,6} ocupa 16 bytes
b, c y b+c ocupan 4, 4 y 4 bytes
La función incr ocupa 1 bytes
if
. La estructura general esswitch (expresión){
case valor1:
instrucciones
case valor2:
instrucciones
........ ..........
default:
instrucciones
}
default
. Normalmente
cada bloque case
acaba con break
lo cual permite salir de switch
(de otro
modo se pasaría por el código del siguiente caso). El
break
en default
es opcional.main(){
|
|
while (condición){
instrucciones
}
a.out<fichero
da el número
de palabras del fichero.main(){
char flag=0; /* 1 dentro de la palabra, 0 fuera
*/
char c;
int n=0;
while((c=getchar())!=EOF){
// Comprueba si es un espacio, cambio de línea
o tabulación
if(c==' ' || c== '\n' || c=='\t')
flag=0;
else if (flag==0){
flag=1;
++n;
}
}
printf("El número de palabras es %d.\n",n);
}
main(){
char flag1,flag2; /*espacio o no espacio en carácter
previo y actual */
char c1=111,c2; /*carácter previo y actual
*/
int n=0;
while((c2=getchar())!=EOF){
//Comprueba si c1 y c2 son un espacio, cambio
de línea o tabulación
flag1=(c1==' ' || c1== '\n' || c1=='\t');
flag2=(c2==' ' || c2== '\n' || c2=='\t');
// Comprueba si comienza palabra, y en ese caso
cambia de línea
if((!flag1)&&flag2)
printf("\n");
//Imprime la letra
if (!flag2)
printf("%c",c2);
c1=c2;
}
}
while
puede ser una
expresión en lugar de una condición. Se ejecutarán
las instrucciones mientras la expresión no valga cero.tipo_de_resultado nombre_de_función(tipo_de_argumento_1,
... ,tipo_de_argumento_n);
return
. int doble(int); /*
Prototipo de función */ |
void asteris(int); /* Prototipo
de función */ |
Sus salidas son:
El doble de 1 es 2 |
* |
void no_cambio(int);
main(){
int a=1;
printf("El valor inicial es: %d\n", a);
no_cambio(a);
printf("Y el final es: %d\n", a);
}
void no_cambio(a)
{
a=2;
}
El valor inicial es: 1
Y el final es: 1
void si_cambio(int*);
|
void si_cambio(int a[]); |
int fact(int);
main(){
int i;
printf("Introduce un entero no negativo pequeño:
");
scanf("%d",&i);
printf("Su factorial es %d\n",fact(i));
}
int fact(int n){
if (n<=1)
return 1;
else
return (n*fact(n-1));
}
main
comparte las propiedades del resto de las funciones. en particular
puede tener argumentos y resultado. En K & R se suele acabar
main
con return 0
; y en la página
26 se dice: "Típicamente un valor de retorno nulo implica
una finalización normal, mientras que los no nulos señalan
una finalización inusual o errónea". En K & P
no se da ningún valor de retorno a main
,
es siempre de tipo void
.main
, llamados convencionalmente argc
y arg[]
son un entero y una matriz de punteros. El primero indica el
número de comandos que se han utilizado para lanzar el programa,
y cada entrada de arg[]
apunta a cada uno de los
comandos. Por ejemplo, si creamos el programa ejem.c
cuyo código es:main(int argc,char *arg[]){
printf("Número de comandos: %d\n",argc);
printf("Primer comando: %s\t",arg[0]);
printf("Segundo comando: %s\t",arg[1]);
printf("Tercer comando: %s\n",arg[2]);
}
a.out
que ejecutamos
con a.out tres palabras
, el resultado será:Número de comandos: 3
Primer comando: a.out Segundo comando: tres
Tercer comando: palabras
tipo nombre[tamaño];
nombre[0], nombre[1], ...
, nombre[tamaño-1]
. Una matriz se
puede iniciar escribiendo algo de la formanombre[tamaño]={valor1,valor2,...};
main(){
|
|
"hola"
"a todos"
equivale a "hola a todos"
) lo cual sirve para escribir líneas muy largas sin problemas
con el editor.int a[4]={1,2};
equivale a int a[4]={1,2,0,0};
int a[2][2][3]={{{1, 1, 0}, {2, 0, 0}}, {{3, 0,
0}, {4, 4, 0}}};
int a[2][2][3]={{{1, 1}, {2}}, {{3}, {4, 4}}};
tipo de variable *puntero;
main(){
|
|
si_cambio
).
main(){
int a[3]={2,7,5};
int *puntero;
puntero=&a[0];
printf("\n\nDirección hexadecimal %x\n",puntero);
printf("Valor de la variable %d\n",*puntero);
printf("Incrementa el puntero\n");
puntero++;
printf("\n\nDirección hexadecimal %x\n",puntero);
printf("Valor de la variable %d\n",*puntero);
printf("Incrementa el puntero\n");
puntero++;
printf("\n\nDirección hexadecimal %x\n",puntero);
printf("Valor de la variable %d\n",*puntero);
}
|
|
puntero=&matriz[0];
puntero=matriz;
*(puntero+i)
es lo mismo
que matriz[i]
puntero[i]
o *(matriz+i)
para representar este
valor. Todo funciona como si el nombre de la matriz y del puntero
fueran intercambiables. Incluso se puede definir un puntero a una
cadena de caracteres con char *puntero="cadena de caracteres";
y para imprimirla se puede escribir printf("%s",puntero);
(lo que prueba que una cadena de caracteres es también un puntero).
La prinicpal diferencia entre punteros y matrices es que éstas
se comportan como punteros constantes que no admiten asignaciones
( matriz=otra_matriz
y matriz++
son ilegales). main(){
|
|
int suma(int[]);
main(){
int matriz[3]={2,3,5};
printf("Suma de los tres elementos de la matriz: %d\n",suma(matriz));
}
int suma(int *puntero){
return *puntero+*(puntero+1)+*(puntero+2);
}
main(){ |
main(){ |
tipo de resultado de la función (*puntero)(argumentos
de la función);
puntero
apunta a una función,
entonces (*puntero)
es el valor de dicha función.
Es decir, es posible evaluar funciones a través de punteros.
El siguiente programa calcula 2*(a+b)
:int doble_de_suma(int ,int );
main(){
int a=5,b=3;
int (*puntero)(int,int);
puntero=doble_de_suma;
printf("Doble de a+b = %d\n",(*puntero)(a,b));
}
int doble_de_suma(int n,int m){
n+=m;
n*=2;
return n;
}
a
y b
con la fase f
. La función
suma_trigonométrica admite estos tres argumentos permitiendo
elegir la fase siempre que la hayamos definido antes. #include <math.h>
float suma_trigonometrica(int ,int, float (*)(int
) );
float f(int);
float g(int);
main(){
int a=1,b=13; /* valores arbitrarios. Límite
inferior y superior */
printf("Suma de Gauss de %d a %d: %f\n", a,b,suma_trigonometrica(a,b,f));
printf("Núcleo de Dirichlet en .1 de %d a %d:
%f\n", a,b,suma_trigonometrica(a,b,g));
}
float suma_trigonometrica(int lim_inf,int lim_sup,
float (*puntero)(int ) ){
float suma=0;
int i;
for (i=lim_inf; i<=lim_sup; i++)
suma+=cos(2*M_PI*(*puntero)(i)); /* M_PI está
definido como pi en math.h */
return suma;
}
float f(int n){
float x=n;
return (x*x/13);
}
float g(int n){
float x=n;
return (.1*x);
}
float suma_trigonometrica(int
,int, float (*)(int ) );
indica que los dos primeros argumentos
de la función son enteros y el tercero es un puntero que
apunta a una función que toma valores enteros y los aplica
en reales. Al llamar a suma_trigonometrica
debemos escribir como tercer argumento el nombre de la función
que da la fase deseada pues ella misma es ya un puntero. De nuevo la
sintaxis es bastante maleable y permite escribir puntero(i)
en vez de (*puntero)(i)
. Esto es análogo
al caso de las matrices.printf
tienen
un número variable de argumentos. Es posible crear funciones de
este tipo usando la librería <stdarg.h> . En
ella va_list
, va_start
, va_end
y va_arg
son una especie de análogos de
FILE
, fopen, fclose
y getc
para
los argumentos de la función.
Tipo
|
Rango
|
Bytes
|
char |
-128, 127 |
1
|
short int |
-32768, 32767 |
2
|
int |
-2147483648=-2^31, 2147483647=2^31-1 |
4
|
float |
-3.4028235E38=-2^128, 3.4028235E38=2^128 |
4
|
double |
-1.797693134E308=-2^1024, 1.797693134E308=2^1024 |
8
|
long double |
¿no funciona? Imprime poniendo
(double) antes de la variable. |
12
|
M_E, M_PI, M_SQRT2 y HUGE_VAL
son los nombres que la librería <math.h> da
a e, pi, raíz de 2 y más infinito (?!).a
sea una variable float
, escribiendo a=3/2;
printf("Valor de a=%f\n",a);
se obtendrá como resultado
1, lo correcto sería escribir a=3.0/2.0.
Además
están las operaciones a bit
&, |, ~, ^, << y >> que representan AND, OR,
negación, XOR, shift a la izquierda y shift
a la derecha.main(){ |
produce |
Valores de
a y b=14, 13 |
variable+=expresión
es un
atajo para variable=variable+expresión
. Lo mismo
se aplica cambiando + por -, *, / ó %.auto
int a
equivale a int a
. Indica
que la variable en cuestión sólo tiene sentido
dentro de la función en la que está declarada y
es invisible fuera de ella.printf(" Como carácter: %c. Como número:
%d.\n",'A'+1,'A'+1);
Como carácter:
B. Como número: 66
.
|
|
main(){
char escape[12]="\n\t\v\b\r\f\a\\\?\'\"";
int i;
for (i=0; i<=10; ++i)
printf("Carácter: %2d\t código:
%d\n",i+1,escape[i]);
}
EOF
( end of file
), está definida como constante -1. Ejecutando main(){
int c;
while ((c=getchar())!=EOF){}
printf("%d",c);
}
main(){
const float PI=3.1416;
printf("El valor de PI es: %f\n", PI);
++PI;
printf("El valor de PI es: %f\n", PI);
}
ejem.c: In function `main':
ejem.c:8: warning: increment of read-only
variable `PI'
El valor de PI es: 3.141600
El valor de PI es: 4.141600
enum nombre_grupo {constante1,constante2, ...};
constantej
tomará
el valor j-1. También se puede especificar el valor de la
primera constante escribiendo constante1=k
y los valores
del resto de las constantes serán consecutivos, o especificar todos
los valores El nombre_grupo
es opcional. Esta instrucción
es una alternativa a #define
. Por ejemplo,
el siguiente código produce el resultado de la derechamain(){ |
Números: 0 1 2 Meses: E F M Letras: a b c
|
nombre_grupo
se puede usar para hacer
declaraciones de variables. La instrucción:enum nombre_grupo variable;
nombre_grupo
. Estos dos usos de enum
se
pueden combinar en una sola línea escribiendoenum nombre_grupo
{constante1,constante2,
...}
variable;
auto
. Indica que una variable ya ha
sido declarada fuera de una función en curso y sirve
para que se pueda acceder a ella por referencia. Los códigosint a=1; |
int a=1; |
El valor inicial es: 1
|
El valor inicial es: 1 |
a
de main
sea la externa declarada al principio. Todo funciona como
si en lugar de a
escribimos otra letra. Es indiferente
el cambio de la a
externa en no_cambio
porque no es la de main
.short int i=32765;
short int j=i+3;
printf("%d %d\n",i, j);
32765 -32768
.main(){
long int i=2147483647; /* Esto
es 2^31-1 */
long int j=i+1;
printf("%d %d\n",i, j);
}
2147483647
-2147483648
.
main(){
int i=1;
for(i=1;;i*=2){
printf("%d\n",i);
}
}
for(i=1;i<número_no_muy_grande;i*=2){
todo funciona como cabe esperar dando una
lista de potencias de dos. La razón es que a partir
de cierto valor las potencias de dos son nulas con el
formato int y no da tiempo a ver los primeros valores
en pantalla.int
es
long int
.int
o double
.main(){ |
main(){ |
int
o double
.
signed char c
implica que -128 =< c < 128 y
con unsigned char c
se tendrá 0 =< c
< 256. De este modomain(){
signed char c1=129;
unsigned char c2=129;
printf("El valor de c1 es %d y el de
c2 es %d\n", c1,c2);
}
El valor de c1 es
-127 y el de c2 es 129
static
void suma_otro(void); |
Introduzca cinco números enteros |
Introduzca cinco números
enteros |
struct nombre_grupo
{declaración
de componentes}
nombre(s)_estructura(s);
nombre_grupo
es opcional y su utilidad
viene de que struct nombre_grupo
nombre_estructura
define una estructura que se ajusta
al nombre_grupo
anterior. struct complejo {float x; float y;} z;
z
es una estructura del tipo
genérico llamado complejo que está formado por dos
números float
. Las estructuras se
comportan como variables en el sentido de que pueden participar en
asignaciones y funciones y se pueden crear punteros a ellas. Para
acceder a las componentes de una estructura se escribe el nombre de
la estructura y de la componente separados por un punto. Así en
el ejemplo anterior z.x
y z.y
son
la "parte real" y "parte imaginaria" de z
.main(){ |
main(){ |
main(){ |
->
y después
el nombre la componente. Por ejemplo, si z
es una estructura
del tipo complejo, como antes, y p
es un puntero que
señala a z
(esto es, p=&z
), entonces (*p).x
equivale a p->x
.struct complejo {float x; float
y;}; |
ruct complejo {float x; float y;} paralel[2]; |
:número_de_bits
. Por ejemplo,
el siguiente prograna define una estructura que contiene el primer y
último bits de un byte.
main(){
unsigned char c;
struct {unsigned char prim:1; unsigned char ult:1;} bit;
printf("Introduce un número de un byte (0-255)\n");
scanf("%d",&c );
bit.prim=((c&128)/128);
bit.ult=c&1;
printf("Primer y último bits = %d y %d.\n", bit.prim,bit.ult);
printf("La estructura ocupa %d byte\n",sizeof(bit));
}
struct
{unsigned char prim:1; unsigned char ult:1; float x;} bit;
struct complejo {float x; float y;};
struct complejo producto(struct complejo,struct complejo);
main(){
struct complejo z,w;
printf("Parte real de z\n");
scanf("%f", &z.x);
printf("Parte imaginaria de z\n");
scanf("%f", &z.y);
printf("\nParte real de w\n");
scanf("%f", &w.x);
printf("Parte imaginaria de w\n");
scanf("%f", &w.y);
printf("\nRe(z·w)= %f, Im(z·w)=%f\n", producto(z,w).x,
producto(z,w).y);
}
struct complejo producto(struct complejo multiplicador,struct
complejo multiplicando){
struct complejo resultado;
resultado.x=multiplicador.x*multiplicando.x-multiplicador.y*multiplicando.y;
resultado.y=multiplicador.x*multiplicando.y+multiplicador.y*multiplicando.x;
return resultado;
}
struct complejo
. La forma de evitarlo es con el comando
typedef
.typedef antiguo_tipo nuevo_tipo
;typedef int entero;
entero
en lugar
de int
. Se pueden concatenar varias de estas
definiciones. Si nuevo_tipo es una matriz, antiguo_tipo se refiere
a sus elementos. Por ejemplo, typedef float vectorR3[3];
crea el nuevo tipo vectorR3
y al escribir por ejemplo
vectorR3 a;
se declara que a es una matriz 1x3. De la misma
forma typedef vectorR3 matriz[3];
declara matriz como
una matriz de matrices 1x3, esto es, una matriz 3x3.#define
pero en
el compilado.complejo
.main(){ imaginario z={1.7,2.871}; |
main(){ |
typedef
, el programa anterior para
multiplicar números complejos sería:
typedef struct {float x; float y;} comple;
comple producto(comple,comple);
main(){
comple z,w;
printf("Parte real de z\n");
scanf("%f", &z.x);
printf("Parte imaginaria de z\n");
scanf("%f", &z.y);
printf("\nParte real de w\n");
scanf("%f", &w.x);
printf("Parte imaginaria de w\n");
scanf("%f", &w.y);
printf("\nRe(z·w)= %f, Im(z·w)=%f\n", producto(z,w).x,
producto(z,w).y);
}
comple producto(comple multiplicador,comple multiplicando){
comple resultado;
resultado.x=multiplicador.x*multiplicando.x-multiplicador.y*multiplicando.y;
resultado.y=multiplicador.x*multiplicando.y+multiplicador.y*multiplicando.x;
return resultado;
}
union nombre_union
{tipos permitidos
para la variable (con nombres)}
variable(s);
union enteros
{int ent;
char car;}n
hace que n
pueda tomar valores
de tipo entero o de tipo carácter. Para distinguir ambos casos
se procede como para separar las componentes de las estructuras,
utilizando n.ent
y n.car
. Como en
el caso de las estructuras el nombre_union
puede emplearse
para definir nuevas variables con una sintaxis como la anterior sin
la parte entre llaves. Las uniones pueden aparecer en estructuras y
matrices y viceversa.main(){ |
|
signed
. malloc
./* Crea una lista de enteros de longitud variable
(esto se podría |
|
/* Insertar en el cuarto lugar un 444 */
if (longi>=4){ /* Para evitar listas cortas */
LISTA *inserto;
LISTA *transi; /* Sólo como buffer para guardar un puntero
*/
inserto=(LISTA*)malloc(sizeof(LISTA)); /* Reserva espacio para
el
nuevo elemento */
inserto->num=444; /* El nuevo elemento será 444 */
actual=comienzo; /* Inicia el puntero actual */
for(i=1;i<3;i++) /*Bucle hasta el elemento anterior */
actual=(actual->rama);
transi=(actual->rama); /* Guardamos la dirección a la
que apunta el
tercer
elemento, esto es, la del cuarto */
actual->rama=inserto; /*Hacemos que apunte a inserto */
inserto->rama=transi; /* y que inserto apunte donde antes apuntaba
el tercer
elemento */
}/*Fin del if*/
/* Imprime lista */
simplemente
por imprimlista(comienzo); /* Imprime lista recursivamente
*
imprilista
es una función
definida como:void imprimlista(LISTA *tabla){
if ((tabla->rama)==NULL){
return;
}
imprimlista((tabla->rama));
}
#include <stdio.h> |
|
sin,
cos, tan, asin, acos, atan, atan2, sinh, cosh, tanh, exp, log, log10, pow,
sqrt, ceil, floor, fabs.
Como ya se ha mencionado, las constantes
M_PI
y M_E
son pi y e. La lista de las
constantes está en http://www.cs.cf.ac.uk/Dave/C/
.atan2(a,b)
da el argumento entre -pi y pi del vector (b,a)
.gcc
andemás
del include correspondiente hay que escribir gcc fichero.c -lm
para que enlace con la librería correspondiente.
s
posible crear funciones con un número variable de argumentos usando
esta librería, en la que va_list
, va_start
, va_end
y va_arg
son una especie de análogos
de FILE
, fopen, fclose
y getc
.
iene dos argumentos, el primero es un puntero
de tipo va_list y el segundo indica el tipo de parámetro al que
apunta. Su efecto es obtener el valor de dicho parámetro.va_end(puntero)
es
la que indica el cierre de va_start. Según parece,
no hace nada en algunas implementaciones (
http://www.cs.tut.fi/~leopold/Boar/AnsiLib/stdarg.html#va_end
).va_list puntero
se declara un tipo especial
de puntero que apunta a una lista de argumentos.va_list(puntero,variable)
provoca que puntero
apunte al primer argumento trás
de variable
que debe ser el último argumento de
la función verdaderamente declarado.#include <stdio.h> |
RESULTADO:
|
. Incluye las siguientes funciones:FILE *puntero
. Por defecto hay declarados ya tres punteros de este tipo
(tres "corrientes"), que son: stdin
, stdout
y stderror
. El primero corresponde al teclado
(entrada estándar) y los otros dos a la pantalla (salida estándar
y error estándar).
fopen(puntero);
cierra
la comunicación con el fichero al que corresponde el puntero. fopen(nombre,modo)
donde nombre es el nombre
de un fichero y modo es un modo de acceso, "r"
para lectura,
"w
" para escritura y "a"
para escribir
añadiendo. El resultado de fopen
es un puntero
de los declarados con FILE
correspondiente al fichero del
nombre indicado, que queda abierto en el modo indicado. Si se elige la
opción "r+"
se podrá no sólo leer, sino
también escribir en el fichero (pero no crear uno nuevo). Cuando
se alternan lectura y escritura, hay que tener en cuenta que el acceso
es secuencial (¿por medio de un puntero que no vemos pero del que
tenemos información con ftell
?) y la escritura provoca
el cambio del carácter en curso. Por ejemplo, supongamos un fichero
llamado datos.dat
cuyo contenido es 1234567890. La
acción del siguiente programa sería la indicada en la derecha:#include <stdio.h> |
Nuevo fichero datos.dat: |
"r+"
por "r"
, no se podría efectuar la escritura y el fichero quedaría
igual. Con "a"
o "a+"
en lugar de "r+"
, se añadirían diez 'x'
al final del
fichero, mientras que con "w"
o "w+"
, el fichero
estaría formado por estas diez 'x'
.print
f salvo que tiene
un primer argumento extra que es un puntero de tipo FILE. Por ejemplo,
fprintf(stdout,"hola\n"); equivale a printf("hola\n").scanf
de la misma forma que
fprintf
corresponde a printf
.
fseek(puntero, offset, SEEK_SET)
,
fseek(puntero, offset, SEEK_CUR)
y fseek(puntero, offset,
SEEK_END)
donde puntero
es un puntero de tipo
FILE y
offset
es un entero (de tipo long
).
Su efecto es
hacer que el nuevo caracter en curso difiera offset unidades del principio
del fichero o del actual carácter en curso o del fin del fichero
(en el último caso el offset
debe ser negativo. Por
ejemplo, supongamos como antes un fichero llamado datos.dat
cuyo contenido es 1234567890, entonces el programa :#include <stdio.h>
main(){
FILE *puntero;
puntero=fopen("datos.dat","r");
fseek(puntero,2,SEEK_CUR);
printf("%c\t", getc(puntero));
fseek(puntero,2,SEEK_SET);
printf("%c\t", getc(puntero));
fseek(puntero,-2,SEEK_END);
printf("%c\t", getc(puntero));
fseek(puntero,0,SEEK_CUR);
printf("%c\t", getc(puntero));
fseek(puntero,4,SEEK_SET);
printf("%c\n", getc(puntero));
fclose(puntero);
}
tendría como salida: 3
3 9
0 5.FILE
y
devuelve el número de carácter en curso
. Por ejemplo, tras fseek(puntero, 0, SEEK_END
)
, ftell(puntero)
da la longitud del fichero en
bytes . getchar
pero con un argumento
de tipo puntero FILE
. concretamente getchar()
equivale a getc(stdin)
.putchar
con un argumento extra de
tipo puntero FILE
que va al final. Así pues
putchar(c)
equivale a putc(c,stdout)
.main(){ |
main(int argc, char *argv[]){ |
el puntero FILE
indicado por su único argumento.malloc
pero ahora con dos
argumentos enteros, el primero indica el número de datos que
queremos guardar en el espacio reservado, y el segundo indica cuántos
bytes ocupa cada uno.free(puntero)
libera el
espacio de memoria al que apunta un puntero (creado con malloc
o calloc
) para que se pueda reutilizar.main(){
int i,n;
char *p;
printf("Introduce dimensión de la matriz.\n");
scanf("%d",&n);
p=(char *)malloc(n);
printf("Introduce los datos (enteros [0,255])\n");
for(i=0;i<n;++i) scanf("%d",p++);
printf("Los datos introducidos son en orden inverso:\n");
for(i=n;i>0;--i) printf("a[%d]= %d\n",i-1,*(--p));
free(p);
}
(char *)
antes de malloc
es necesario para indicar al compilador
de cuánto en cuánto se incrementa el puntero.
Permite "quitar las comillas" en una
cadena de carcteres que representa un entero, por ejemplo, atoi("211")
es 211
.
Lo mismo que atoi
pero en coma flotante.rand()
se devuelve un número
aleatorio
que en la máquina en uso parece
estar entre 0 y 2^31-1.srand( (unsigned int)time(NULL));
NULL
final).#include <string.h>
main(){
char cadena1[6]="Hola ";
char cadena2[6]="Allo ";
char cadena3[]=" amigos";
printf("Las longitudes de las cadenas \"%s\", \"%s\" y \"%s\"
son:\t", cadena1, cadena2,cadena3);
printf("%d, %d y %d \n",strlen(cadena1),strlen(cadena2),strlen(cadena3));
printf("Se cumple \"%s\"<\"Homa\" porque strcmp(cadena1,\"Homa\")=%d\n",
cadena1,strcmp(cadena1,"Homa"));
printf("Antes de strcpy(cadena2,cadena1), se tiene cadena2=%s\n",cadena2);
strcpy(cadena2,cadena1),
printf("Después de strcpy(cadena2,cadena1), se tiene cadena2=%s\n",cadena2);
strcat(cadena1,cadena3);
printf("El efecto de strcat(cadena1,cadena3) es que cadena1=%s\n",
cadena1);
}
Las longitudes de las cadenas "Hola ", "Allo " y " amigos" son: 5,
5 y 7
Se cumple "Hola "<"Homa" porque strcmp(cadena1,"Homa")=-1
Antes de strcpy(cadena2,cadena1), se tiene cadena2=Allo
Después de strcpy(cadena2,cadena1), se tiene cadena2=Hola
El efecto de strcat(cadena1,cadena3) es que cadena1=Hola
amigos
clock()
. Es un entero ¿milésimas
de segundo? time(NULL)
da el tiempo en segundos desde el uno de enero de 1970. malloc
. El cuarto es el stack donde
se almacenan variables temporales y datos de las llamadas a funciones.
main(){}
, procesado
con gcc, cc y g++ ocupa respectivamente 10991, 10991 y 11111 bytes.
¿Qué librerías cargan o enlazan? Se puede
reducir mucho el tamaño de un programa vacío (hasta 778
bytes en la máquina en curso) usando las opciones del compilador
gcc: #include
y #define
que no necesitan punto y coma al final. Cuando encuentra
#include "fichero"
o
#include <fichero>
, el efecto es análogo
a reemplazar esa línea con el código del fichero en
cuestión. En el primer caso comienza buscando el fichero en
el directorio del programa fuente y si no lo encuentra, busca después
en ciertos sitios predeterminados. En el segundo caso, sólo
busca en los sitios predeterminados (que dependen del sistema). Normalmente
los ficheros incluidos tiempen prototipos de funciones y declaraciones de
variables y estructuras. Aparentemente si sólo tienen prototipos
de funciones basta incluirlos en el fichero principal que llama a todas
las funciones, sin embargo las definiciones de estructuras y variables pueden
ser necesarias en todas las funciones.
#define
sirve para crear abreviaturas, nombres
alternativos o macros. Su estructura es #define alias
texto.
Por ejemplo, #define pi 3.1416
provoca que el preprocesador escriba 3.1416
en todos los sitios en los que pi
funciona
como palabra clave (no dentro de una cadena de caracteres). El
preprocesador efectúa las simplificaciones que puede, así
con #define pim (3.1416/2)
no se sustituirá
pim
por (3.1416/2)
si no por
1.5708
. Por ejemplo:#define pi 3.1416
#define pim (3.1416/2)
main(){
printf("Aproximación de pi %f y pi
medios %f\n",pi,pim);
}
Aproximación de pi 3.141600
y pi medios 1.570800
__DATE__
|
Cadena de caracters con la fecha |
__FILE__
|
Cadena de caracters con el nombre del fichero |
__TIME__
|
Cadena de caracters con la hora |
__LINE__
|
Entero con el número de línea |
__STDC__
|
Entero no nulo si se cumplen las normas ANSI |
main(){
printf("La fecha de hoy es: %s\n",__DATE__);
printf("La hora es: %s\n",__DATE__);
printf("Esta línea es la número %d\n",__LINE__);
printf("El fichero es: %s\n",__FILE__);
}
#define
para crear nuevas instrucciones (macros)
a partir de otras conocidas. Simplemente se escriben letras
arbitrarias en lugar de los argumentos. Esto puede dar lugar a
resultados un poco inesperados. Por ejemplo, el primer programa
parece análogo al segundo salvo que éste emplea una
función en lugar de #define
:#define cubo(a) a*a*a |
#include <stdio.h> |
2+1*2+1*2+1=7
y el del segundo (2+1)*(2+1)*(2+1)=27
.#define
se puede
utilizar # para transformar una expresión en una cadena
de caracteres y ## para pegar dos expresiones. Por ejemplo,#define imprvar(a) printf("La variable " #a " vale %d\n",a)
#define pega(a,b) a##b
main(){
int i=1,j=2,ij=3;
imprvar(i);
imprvar(j);
imprvar(pega(i,j));
}
La variable i vale 1
La variable j vale 2
La variable pega(i,j) vale 3
defined
que indica si un nombre está definido o no. Por
ejemplo (K & R p.91), si un fichero cabecera.h es
incluido en diferentes partes de un programa y sólo queremos
que lo haga la primera (para no desperdiciar memoria), se
puede sustituir cabecera.h por #if !defined(FLAG)
#define FLAG
#endif
FLAG
quedará definido y el
ciclo if saltará el código el resto de
las veces. #if
para que ciertas instrucciones de comprobación se compilen
sólo si así lo queremos. Por ejemplo, si en algún
punto de un programa incluimos:#if defined(FASE_DE_PRUEBAS)
printf("Comprueba valor de a=%d",a);
#endif
#define
FASE_DE_PRUEBAS
entonces se compilará
la instrucción que imprime la variable, y si la omitimos
no se compilará. #define SISTEMA
(tipo de sistema)
y entonces el siguiente ciclo podría
elegir la cabecera adecuada según el sistema operativo:#if SISTEMA==LINUX
#include "cabeceralinux.h"
#elif SISTEMA==MSDOS
#include "cabeceramsdos.h"
#else
#include "cabeceraotros.h"
#endif
float suma(float, float); |
|
|
gcc fichero1.c fichero2.c fichero3.c
, un ejemplo del resultado será el mostrado en la siguiente
columna de la izquierda, pero si omitimos el primer portotipo de la función
o ambos, en la máquina en curso se obtienen los resultados
erróneos de las otras dos columnas:Primer número: |
Primer número: |
Primer número: |
cabecera.h
y sustituirlas por #include "cabecera.h".
make
. Se puede crear un fichero con el
nombre Makefile
conteniendo las instrucciones de compilado y
al ejecutar el comando make
se llevarán a cabo. La utilidad
make
tiene un lenguaje de programación propio (véase
http://www.math.utah.edu/docs/info/make_1.html
y http://www.math.utah.edu/docs/info/make-stds_1.html
). La estructura básica de las instrucciones más
simples es:fichero_objetivo: ficheros_requisitos
comandos
ejem1.c ejem2.c ejem3.c ejem4.c
y digamos que ejem1.c
es el principal y los otros contienen
las funciones que se emplean en él. La instrucción para
el compilado sería gcc -o a.out ejem1.o ejem2.o ejem3.o ejem4
.o si ya tuviéramos los códigos objetos de los ficheros. En
Makefile podemos poner como requisito ejem1.o ejem2.o ejem3.o ejem4.o
. Si por ejemplo ejem4.o
no estuviera actualizado
se buscaría una línea en que es objetivo, dicha línea
debería exigir como requisito el fichero ejem4.c
con el código fuente, y el comando correspondiente debería
ser gcc -c ejem4.c
. en definitiva, en esta situación podríamos
hacer un Makefile
del tipo:##############
# Makefile
##############
# Enlaza los códigos objetos
a.out: ejem1.o ejem2.o ejem3.o ejem4.o
gcc -o a.out ejem1.o ejem2.o ejem3.o
ejem4.o
echo Make completado
# Compila los códigos fuente
ejem1.o: ejem1.c
gcc -c ejem1.c
ejem2.o: ejem2.c
gcc -c ejem2.c
ejem3.o: ejem3.c
gcc -c ejem3.c
ejem4.o: ejem4.c
gcc -c ejem4.c
Los caracteres # indican comienzos de una línea de comentarios.
Por otra parte, echo
sirve para imprimir en pantalla. Otros
comandos Linux/UNIX que funcionan dentro de Makefile, según la documentación,
son: cat cmp cp egrep expr grep ln mkdir mv pwd rm rmdir sed test touch
Makefile
se pueden incluir variables arbitrarias.
Hay también algunas de carácter especial. algunas de ellas
sonVariable |
Significado |
$@ |
El nombre del fichero objetivo. |
$? |
Ficheros requisitos más recientes que el objetivo
. |
$< |
Igual que $? con reglas de sufijos (ver más
adelante). |
$* |
Igual que $@ con reglas de sufijos (ver más
adelante). |
$+ |
Todos los ficheros requisitos separados por espacios. |
% |
Uno o varios caracteres. |
variable = valor
y referirnos
a ellas con $(variable)
. Normalmente se llama CC
al compilador y CFLAGS a las opciones del compilador. Poir ejemplo, el
Makefile
anterior se podría reducir a:####################
# Makefile abreviado
####################
CC = gcc
OBJECTS = ejem1.o ejem2.o ejem3.o ejem4.o
# Enlaza los códigos objetos
a.out: $(OBJECTS)
$(CC) -o $@ $+
echo Make completado
# Compila los códigos fuente
%.o: %.c
$(CC) -c $+
%.ext1:
%.ext2
, simplemente sustituyéndolo por .est1.ext2:
En el ejemplo anterior, las dos últimas líneas podrían
ser:.c.o:
$(CC) -c $<
##############
# Mi makefile
##############
# Compila con gcc
CC=gcc
OBJECTS = perse.o leetec.o inipan.o creani.o colisi.o mupin.o
OBJECTS += muer.c
## Para nueva línea también se podría usar "\"
INC_DIRS = /usr/include/SDL
LIBS = -lSDL -lpthread -lSDL_image -lSDL_mixer
FLAGS = -D_REENTRANT -Wl,-rpath
##########################
## sdl-config --cflags
## produce: -I/usr/include/SDL -D_REENTRANT
## y sdl-config --libs
## -L/usr/lib -Wl,-rpath,/usr/lib -lSDL -lpthread
##########################
# Enlaza los códigos objetos
a.out: $(OBJECTS)
$(CC) -o $@ $+ $(FLAGS) -I$(INC_DIRS)
$(LIBS)
# Compila los códigos fuente
%.o: %.c cabecera.h
$(CC) -c $< -I$(INC_DIRS)