Documente Academic
Documente Profesional
Documente Cultură
El lenguaje no ha cambiado, por lo que los apuntes siguen siendo vigentes, con la excepción
de las partes que hacen referencia a los modelos de memoria y a los punteros de 16 bits
(especialmente el tema 32, así como algunos comentarios relacionados con el tamaño y
alcance de los punteros).
página 1
Programacion en C Eduard Martín 1995
1. Introducción
El lenguaje C se deriva de los lenguajes BCPL y del B. Su "nacimiento" lo podemos
fijar a principios de los años 70 de manos de D. Ritchie. El primer compilador en C se
implementó en una máquina unix, lo que hace pensar que el primer unix no había sido escrito
en C. Sin embargo sí que se pensó en el lenguaje C como en una herramienta para el
desarrollo de ese sistema operativo, cosa que ocurrió en la realidad dando muy buenos frutos,
hasta tal punto que en sus primeros tiempos la comunión unix-C fue total.
En 1978 aparece la primera edición del libro "Programación en C" de Ritchie, y por lo
tanto será a partir de esta fecha cuando se empiece a universalizar el uso de este lenguaje
carente hasta este momento de manual alguno.
En 1987 aparece el ANSI C, considerado como el standar de este lenguaje. Entre sus
mejoras destacan el hecho de que convierte al compilador de C en un compilador riguroso y
muy estricto con los errores, cosa que hasta el momento no era así.
2. Características principales
a) Es un lenguaje compilado.
c) Es un lenguaje de nivel medio en el sentido de que posee todas las ventajas de un lenguaje
de alto nivel pero también alguna de las características más importantes de los lenguajes de
bajo nivel
Por lo que respecta a los inconvenientes de C diremos que los inconvenientes mayores
que presenta este lenguaje vienen dados preferentemente por el hecho de la necesidad de
velocidad de ejecución que se pretendía conseguir con él. Esta necesidad produjo que se
diseñará un lenguaje que no verifica buena parte de los errores en tiempo de ejecución ( por
ejemplo el pasarse de los límites de un array en una búsqueda). En cuanto al control de errores
en la compilación diremos que ha mejorado mucho desde la aparición del ansi C.
página 2
Programacion en C Eduard Martín 1995
Por lo que respecta a la aplicación que se hace de este lenguaje diremos que
fundamentalmente se aplica a la elaboración de sistemas operativos, compiladores, desarrollo
de otros lenguajes (como clipper), e incluso elaboración de aplicaciones científicas, donde ha
comenzado a desplazar a fortran. También, en su evolución -C++- es el lenguaje preferido para
el desarrollo de aplicaciones de tratamiento de gráficos e imágenes.
#include <stdio.h>
void main()
{
printf("Hola")
}
Como vemos en este programa solo tenemos una función main(). Esto significa que es
la función principal, es decir, aquella que iniciará la ejecución del programa.
El hecho de que distingamos una función es porque detrás lleva paréntesis. Como
podemos ver esta función no lleva parámetros y no retorna nada void. En este sentido diremos
que en C, una función sin salida equivale a una procedure en pascal.
En cuanto a las llaves {} delimitan el ámbito de función; todas las instrucciones, por
otro lado, terminan con punto y coma.
La función printf es una llamada a una función que se encuentra en una librería, y que
nos imprimirá el string que situemos entre comillas dobles.
Cada vez que construimos una función nueva se deberá declarar el prototipo, que se
declarará al principio del programa e indicará qué es lo que le entrará y qué es lo que devolverá.
#include...
void hola(void);
void main()
{
hola();
}
#include <stdio.h>
void main()
{
int pies;
fload metros;
printf("Introduce un número de pies: ");
scanf("%d",&pies);
metros=pies * 0.3048;
printf("%d pies son %f metros",pies,metros);
página 3
Programacion en C Eduard Martín 1995
}
Como vemos en el ejemplo anterior, y para ir avanzando tenemos ya dos tipos de
variables:
En C cuando una variable no se inicializa no quiere decir que este a 0. En este caso el
compilador nos avisaría de este hecho. También podremos declarar en una misma línea varias
variables.
En el ejemplo anterior tenemos por otra parte la función scanf. Este función permite
captar un valor por teclado guardándolo en la variable que le indiquemos. Esta variable deberá
llevar, en principio, aunque luego veremos que no siempre es así, un amperseand delante. Esta
función no es la que utilizaremos en la confección de programas complejos, puesto que suele
fallar, además que no nos depura las entradas.
a) Hallamos en el literal el símbolo % que hace referencia al tipo de variable del final: %f: hace
referencia a un float; %d hace referencia a números en base decimal
Por otro lado diremos que para realizar comentarios dentro de un código escrito en C lo
haremos acotando estos mediante los símbolos */ y /*.
Hemos visto anteriormente la función scanf. Hemos comentado que no es muy fiable,
pese a ello la utilizaremos por el momento. Con esta función no podemos especificar texto:
scanf("%d %d %d",&pies,&x,&y)
Los datos del tipo char sirven para almacenar tanto caracteres ASCII como números y
también se utilizan para cálculos pequeños.
En el caso de los int, su tamaño dependerá del procesador, aunque es preferible que
se especifique el tipo long o short para así facilitar la transportabilidad de los programas.
Por otro lado tenemos las cláusulas unsigned y signed. Con estas clausulas
podemos determinar que una variable no tenga signo o lo tenga. Por defecto todas lo tienen por
lo que deberemos especificar (unsigned int x;).
página 4
Programacion en C Eduard Martín 1995
En el caso de que declaremos una variable como sin signo provocaremos que su
capacidad de admisión de valores se duplique. Así los chars podrán contener valores de 0 a
255 y lo short de 0 a 65535.
5. Códigos de Formato
Son los siguientes:
6. Constantes
Constantes enteras
Una constante numérica tiene por defecto el tipo integer (un short o un long). En
concreto una constante si pasa de 32 bits será un long.
Cuando queremos pasar un valor short a uno long utlitzaremos una L: 100L.
Constantes char
Cuando tenemos una constantes del tipo char la spondremos entre comillas
simples.Si ponemos comillas dobles estaremos ante un string:
a) '\n': salta una línea y equivale a '\10' y a 10 (printf("Hola\n") es lo mismo que printf("Hola\10"))
página 5
Programacion en C Eduard Martín 1995
c) '\t': es un tabulador
d) '\b': es un retroceso
e) '\\': visualiza un \
Constantes hexadecimales
Constantes octales
Constantes reales
Una constante numérica con un punto se considera double (por ejemplo 101.23 y
101). Si quisieramos que una constante real fuese de 32 bits y no de 64 deberiamos añadir una
f (de float) al final. Por ejemplo 101.23f
Por otro lado diremos que las constantes de carácter pueden entrar en cálculos. Por
ejemplo:
int x;
x 0 10 + 'A' daría 75
7. Variables
La sintaxis general para declarar variables sería tipo nomvariable. Podemos declarar
variables seguidas e inicializarlas, así: int x=1, y=55. Por otro lado será imprescindible
inicializar las variables dado que no se pueden asumir ningún valor.
Variables locales
Las variables locales son las que se declaran al principio de un bloque. Estas variables
pueden corresponder a una función, o a un bloque más extenso de la misma función. De esta
manera una variable dentro de un bloque es reconocida en todos los subbloques que hayan. En
ANSI C las variables tienen que estar definidas al principio de bloque, en C++ pueden
declararse donde se quiera
Las variables declaradas en unbloque no se ven en los que cuelgan de éste. De todas
maneras estas variables son accesibles durante todo el programa si son del procedimiento
principal. De todas maneras en aras a la portabilidad de los programas, es mejor no utilizar
variables globales.
Si las variables locales se declaran dentro de los bloques, las globales se declaran
arriba de todo o entre funciones:
#include....
página 6
Programacion en C Eduard Martín 1995
void main()
{
...
}
(VARIABLES GLOBALES RECONOCIDAS DESDE ESTE PUNTO EN ADELANTE)
void f()
{
...
}
En caso de declarar una variable local con el mismo nombre que una global mandará la
más interna. Por tanto en ste procedimiento no se conocería la global dentro del procedimiento
sino que se reconocería sólo la local.
8. Especificadores de almacenamiento
Existen cuatro especificadores de almacenamiento que harán que todo lo comentado
acerca de las variables quede matizado. Estos especificadores se situarían delante de la
declaración de cada una de las variables (por ejemplo static int x=0), y son los siguientes:
b) REGISTER: sólo se aplica a variables locales y en este sentido sólo a variables de tipo
CHAR o INT. No podemos declarar variables de tipo local con el modificador register. Este
modificador crea la variable sobre el registro en vez de en la memoria o en la pila. En general
esto servirá para ganar en rapidez.
De todas maneras el empleo de este especificador tiene limitaciones puesto que sólo
tendrá efecto si el compilador considera oportuno, según sea posible, que esta variable se cree
en registro. En caso de que no pueda ello no producirá un error de compilación, sino que
simplemente el compilador las situará sobre la pila o la memoria.
c) EXTERN: se utiliza únicamente con variables globales y solo tiene sentido cuando tenemos
varios módulos de compilación separados
página 7
Programacion en C Eduard Martín 1995
{....
int x;
....
f(x);
}
void f(int a);
{...
}
Esta función realizará algo con el valor que le pasamos como parámetro. Decimos
valor, porque de momento no pasaremos parámetros por referencia.
En el caso de tener que pasar varios parámetros cuando declaremos la función ésta
deberá de recoger los parámetros en el mismo orden y con el mismo tipo que son invocados:
Las funciones que devuelven algun valor no son declaradas con void, sino con el tipo
del valor que retornan y finalizan con la clausula return. Las funciones pueden devolver,
variables, constantes, expresiones e incluso otras funciones.
Una función de este tipo siempre devolverá el valor que sigue a return:
El valor que retorna una función puede ser asignado a una variable del mismo tipo de
retorno de la función.
Todos los return que situemos en una función deben retornar el mismo tipo que el de la
función. Si la función es void no podrá tener ningún return.
10 Estructura de un programa en C
La estructura de un programa en C sería la que sigue:
página 8
Programacion en C Eduard Martín 1995
void x()
{
[instrucciones];
}
Como vemos después de las instrucciones a ejecutar se pone un punto y coma a
excepción de en los includes, en las definiciones de las funciones y después de una llave.
En este sentido cabe reseñar a los prototipos. Un prototipo viene a ser un resumen de
la función a la que se refiere. Se indican en primer lugar y van a permitir verificar la existencia
de la función en el código y que los parámetros que se le pasen sean del tipo especificado.
En cuanto a las funciones diremos que su resultado puede ser asignado a una variable
y asimismo pueden anidarse: f(f(y),f(c))
a) Aritméticos
b) Relacionales
c) Lógicos
d) De bits
1) Binarios
2) Unarios
En cuanto al operador / referido a la división diremos que realiza una división de enteros
si los operandos de la misma son dos enteros. En el momento en que uno de ellos no lo sea el
resultado no lo será. Así si :
En cuanto al operador % diremos que es el MOD del Pascal y sólo funciona con
operandos enteros. En caso de operar con operandos no enteros produce un error.
El operador unario - cambia el signo cuando se aplica a una variable con signo.
página 9
Programacion en C Eduard Martín 1995
int x=5,y;
y=++x; -----> y valdrá 6 pues el incremento se hace previamente a la asignación.
int x=5,y;
y=x++; ------> y valdrá 5 puesto que el incremento se hace posteriormente a la
asignación del valor de x a y.
int x=5, y;
y=x++;
printf("%d %d",++x,y++) ---> mostrará por pantalla 7 y 5
int x=5, y;
y=x++;
printf("%d %d",x,y) -----------> mostrará por pantalla 6 y 6
++x;
f(x);
En el caso de que ++x o x++ se encuentren aisladas en el código sin estar asignado
su valor a nada es indiferente en el orden en que se incremente la variable.
Cuando el valor de una variable depende de su antiguo valor utilizaremos este tipo de
expresiones, por ejemplo si tenemos una función que nos convierte metros a pies podriamos
hacer:
float centimetros=125
centimetros+=(conversion(pies)*100)
Como vemos para inicializar una variable no es necesario que lo hagamos con valores
constantes, sino que lo podemos hacer con expresiones. En realidad en C todo tiene algún
valor, a excepción de las funciones void.
Por otro lado también hemos visto como en C es fácil combinar operaciones con
variables de distinto tipo. Ahora bien, es imprescindible tener en cuenta las siguientes reglas de
conversión de tipos:
página 10
Programacion en C Eduard Martín 1995
a) En una expresión donde aparecen varias variables, constantes, etc, todo char promueve
temporalmente a un int. Es decir, se convierte temporalmente a un int
b) Todos los float promueven a double. Esto quiere decir que siempre se trabaja en C con
doble precisión.
Otra consecuencia de ello es que trabajar con floats hará que se produzcan los
cálculos de forma más lenta puesto que siempre se convertirán a double para operar y se
recoonvertirán después a float. De todas maneras ganaremos espacio en memoria puesto que
un float ocupa la mitad de espacio que un double.
d) Sólo es aplicable cuando no es aplicable la tercera regla: cuando exista una variable de tipo
long la otra también lo será. De esta manera si :
(tipo) expresión
Lo que hace este operador es convertir temporalmente al tipo indicado la expresión que
viene a continuación. La prioridad de cast es mayor que la de cualquier operador. Así por
ejemplo:
l= (long) x*y. Es correcto puesto que primero se evalúa el cast y se aplica sobre los operadores
que siguen convirtiéndo la x a long y esto se propaga a la y. No sería correcto hacer
l=(long)(x*y) puesto que primero se evaluaria la expresión x*y y se aplicaría el cast al resultado
de ésta cuando el resultado ya se haya truncado.
De la misma manera:
b) Operadores relacionales
página 11
Programacion en C Eduard Martín 1995
== : igual que
!= : diferente que
c) Operadores lógicos
5y0o1
0o1=1
El operador condicional
página 12
Programacion en C Eduard Martín 1995
12 Sentencias de Control
Existen tres tipos de sentencias de control:
a) Estructuras condicionales
1- IF ... ELSE
La estructura if es la siguiente:
if (condicion)
{
sentencia;
------------;
------------;
}
En cuanto a la condición puede ser cualquier expresión que tenga un valor, incluso una
variable y una constante... etc.
if (c=f(x))
{
....
}
if (c==f(x))
{
....
}
2- SWITCH
switch (expresion)
{
case const2 : sentencias;
--------------;
......;
case const3 : sentencias;
--------------;
......;
case const4 : sentencias;
--------------;
......;
case const5 : sentencias;
página 13
Programacion en C Eduard Martín 1995
--------------;
......;
case constn : sentencias;
--------------;
......;
[default: sentencias;]
}
La expresión como siempre podrá ser de cualquier tipo de los que hemos hablado.
Además las constantes podrán ser numéricas o de caracter.
Switch evalúa la expresión y compara en cada caso hasta que encuentra el valor
idéntico. Si no lo encuentra entra en el caso default, caso que exista. Ahora bien, hay que
tener en cuenta que en esta construcción se ejecutarán las sentencias que correspondan
según el valor igualado a la constante, pero también todas las sentencias a partir de ese
estadio. Para evitar que ocurra esto y que sólo se ejecuten las correspondientes al caso que
nos interesa deberemos utilizar la instrucción break. Esta instrucción provocará la salida de la
estructura switch.
Aparte de break podemos utilizar también return(), teniendo en cuenta que return provoca la
salida de la función donde se inserte esta estructura:
switch(operador)
{
case '+': return(x+y);
case ‘a’:
case ‘A’:
--------
--------
--------
Este fragmento de código se ejecutará si la opción fuese tanto a, como A, puesto que
después del primer case no existe ninguna instrucción y directamente nos vamos a otro case.
De todas maneras esta no es la mejor solución. La mejor solución en este caso sería
la siguiente:
switch(pasamayusc(opcion())
{
case ‘A’...
Es decir seria mejor pasar primero la opción que se pasa como parámetro a switch, a
mayúsculas, para de este modo tener depurado, de entrada, la entrada en el case.
En C existe una función que hace esto y que es toupper(char), que nos pasa lo que le
pasemos como parámetro a mayúsculas.
Como hemos visto con anterioridad para salir de una estructura case debemos hacerlo
con un break o con un return.
página 14
Programacion en C Eduard Martín 1995
b) Estructuras iterativas.
1.- WHILE
while (condicion)
sentencia;
En este caso la condición puede ser cualquier expresión que tenga un valor: 1 o 0. Si
es uno las acciones del bucle se repetiran -verdadero-, si es 0 no se cumplirá la condición.
Existe una diferencia con if en el while. Esta diferencia es que podemos tener un while
en una sola línea (while (condicion); esto que en un if no tendría sentido, hará que se repita la
instrucción que se autocontenga en la condición cuando esta se cumpla. Por ejemplo
tendríamos que cuando una función retorne cierto se repetirá su ejecución hasta que devuelve
falso: while (seguir()). Como resulta obvio es preciso que la función devuelva algún valor puesto
que si no no tendría sentido esta sentencia.
En las estructuras iterativas del tipo while también podemos utilizar break para salir de
la misma en un momento determinado:
while (condicion)
{
....
if (condición2) break;
....
}
while (condicion)
{
....
if (condición2) continue;
....
}
En relaciónal ejercicio propuesto hace unos días (calcular una potencia de un número),
diremos que una manera de resolver éste mediante una estructura while sería la siguiente:
página 15
Programacion en C Eduard Martín 1995
resul*=base;
-- expo;
}
return resul;
}
while (expo--);
resul*=base;
2. DO...WHILE
La estructura do..while sería una estructura “repeat until” que mantiene, sin embargo,
su condición de manera afirmativa. En realidad una estructura repeat until, sería una estructura
“do... while (!condicion).
do sentencia;
while (condicion);
3. FOR
Como siempre, la sentencia (3) puede ser una sola sentencia, un bloque de
instrucciones o nada. En este caso hemos de tener en cuenta que será posible un for de una
sola línea. En cuanto a los elementos que componen esta estructura tenemos:
a) inicialización (1): sólo se realiza una vez. Puede ser el típico “y=1” de los for de otros
lenguajes o bien otras cosas.
b) condicion (2): como de costumbre la condición puede ser tan complicada como deseemos.
La única premisa que debe cumplirse es que valga algo. Si la condición se cumple se ejecutará
la sentencia, sino saldrá fuera de la estructura. Cuando entramos en el for ya se evalúa la
condición.
c) sentencia(4): se ejecuta cuando acaba la sentencia (3). En un lenguaje más simple sería l
atípica modificación del contador (y=y+1). En C en este apartado puede ir cualquier sentencia.
Una vez ejecutada volveremos a la condición.
En este ejemplo lo que se hace es entrar en un bucle que hasta que y sea mayor que
10 imprimirá su valor. El aumento del contador lo podriamos hacer en muchos otros lugares (en
el printf, en la condición...)
página 16
Programacion en C Eduard Martín 1995
En este sentido diremos que en C no es tan extraño provocar un bucle infinito. Es más
es habitual en programas complejos que lo requieran. La manera de generar un bucle infinito
puede tener varias modalidades. Son especialmente usadas:
while(1) for(;;)
{ {
..... .....
} }
Para poder salir de un bucle infinito deberemos de utilizar la orden break. Esta orden
situada convenientemente dentro de una estructura alternativa y dentro de una iteración nos
permitirá salir del bucle infinito cuando se de determinada circunstancia
c) void getche(void),void getch(void): estas dos funciones son más utilizadas que la
anterior. Ambas se utilizan en mayor medida que la anterior. La diferencia entre ambas es que
la primera produce un eco en pantalla de lo introducido por teclado. La segunda no produce
este eco.
d) char putchar(char): devuelve el mismo caracter que entramos por teclado. Este valor de
retorno no suele hacerse servir.
Ejemplos:
putchar (getche()): visualizaría el caracter que entramos por teclado dos veces.
14. Preprocesador
página 17
Programacion en C Eduard Martín 1995
a) INCLUDE: viene seguido siempre del nombre de un fichero que puede estar entre comillas o
entre mayor y menor. La diferencia entre ambos símbolos que encierran el nombre del fichero
es simplemente relativa al hecho de la ubicación en el arbol de directorios del mismo:
Por otro lado estos ficheros suelen tener extensión h , ahora bien, esto no es
obligatorio.
Normalmente estos ficheros poseen extensión h, como hemos señalado, aunque esto
no es preceptivo. El nombre de los mismos es el de ficheros de cabecera. Estos ficheros no
tienen los fuentes de las funciones que incluyen si no que, en todo caso, harán referencia a los
prototipos de funciones que se hallen en diferentes librerias.
b) DEFINE: un define tiene la siguiente sintaxis: #define HOLA 30. Lo que hace un define es
sistuituir en el fuente el segundo valor por lo expresado en el primero. En general se pone en
mayúsculas el primer valor a fin de evitar las sustituciones no deseadas (cambios parciales... )
Podemos utilizar defines para determinar errores en nuestro programa, así si hacemos
#define ERR1 “Error apertura ficheros” y en el código del programa ponemos print(ERR1),
se nos imprimirá el error señalado.
c) MACROS: una macro no es más que un define con argumentos. Así si ponemos:
int=-5;
ABS(x);
página 18
Programacion en C Eduard Martín 1995
La expresión x+y sería sustituida por x+y>0?x+y:-x+y. Como vemos esto no da un resultado
correcto. Para que hubiese sido correcto el mismo deberiamos haber definido en la macro esta
expresión entre paréntesis:
(x)>0?(x):(-x)
Es más para evitar sorpresas deberiamos haber escrito la expresión en la macro de la siguiente
manera:
( (x)>0?(x):(-x) )
Por tanto en una macro tanto los argumentos como la expresión deberán ir entre
paréntesis para evitar efectos extraños. Además no pondremos punto y coma al final para evitar
que el preprocesador sustituya todas las incidencias poniendo un punto y coma al final.
tipo nombre[tamaño]
Otra cosa que se puede hacer es inicializar un array dando valores a todos sus
elementos:
Hay que tener en cuenta además que en un array el primer elemento es el elemento 0.
Por tanto, un array de cinco elementos va enumerado, siempre del 0 al 4. Por tanto, el tamaño
de un array siempre será igual a su tamaño más uno. De esta manera una forma típica de
recorrer un array sería:
Los indices de un array pueden ser de ocho o dieciseis bits, chars o shorts. Además,
es conveniente inicializarlos como sin signo (unsigned).
Cadenas
página 19
Programacion en C Eduard Martín 1995
En cuanto a las cadenas diremos que en C no existe el tipo de datos string, como un
tipo especial. Las cadenas no son más que arrays de caracteres, lo que no quiere decir que
todos los arrays de caracteres sean cadenas. Así la expresión:
char dias[]={31,23,.... 28} no es una cadena puesto que su propio aspecto, además de otras
circunstancias así nos lo determinan. Lo anterior es simplemente un array de chars
representados por su valor decimal.
El array char hola []={‘h’,’o’,’l’,’a’} tampoco es una cadena puesto que para que
podamos utilizar las funciones de cadena que nos proporciona C éstas deben acabar en 0 - el
valor binario 0-. Por tanto sí sería una cadena:
Ahora bien, tampoco es exacta esta declaración puesto que tenemos una forma más
sencilla de declararlas: utilizando las comillas dobles. Cuando utilizamos las comillas dobles
no hace falta que nos preocupemos del cero final porque se encarga el propio C de hacerlo.
Por ejemplo podemos tener la tabla: int tabla [3] [5]. Esta tabla nos indica que posee tres filas
de cinco columnas cada una de ellas. Por supuesto podriamos inicializar la misma de la
siguientes maneras:
int tabla[3][5]={1,2,3,4,5,1,2,3,4,5,1,2,3,4,5}
int tabla[3][5]={{1,2,3,4,5},{1,2,3,4,5},{1,2,3,4,5}}
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
Como hemos visto en C los arrays se organizan por filas, siendo estas contiguas en
memoria, esto no ocurre siempre así puesto que existen lenguajes, como el Fortran, en los que
la ordenación de las tablas se realiza en relación a las columnas.
página 20
Programacion en C Eduard Martín 1995
(fila * numero_columnas)+columna
(2*5)+0 = 10 que corresponde al número 1 de la tercera fila (es la 2 puesto que se comienza
por 0)
Si tenemos la tabla:
El elemento 1,2 de la tabla sería la letra ‘b’ de Febrero puesto que el número de
columnas harían referencia al número de letras que componen los meses, por ello ponemos 11
como dimensión , es decir, el número máximo de letras 10 más una más, para señalar el final
del string con un 0
0 1 2 3 4 5 6 7 8 9 10
E n e r o 0
F e b r e r o 0
M a r z o 0
16. Punteros en C
Los punteros son variables que contienen direcciones de memoria. Los punteros son
diferentes en C que en cualquier otro lenguaje. En ensamblador no existe propiamente el
concepto de puntero, en Pascal son más limitados en todos los sentido que en C. En C es casi
imposible no utilizar punteros. Deberemos de tener en cuenta que:
página 21
Programacion en C Eduard Martín 1995
x y
10
1000 1002
p1 1000 p2
1004 1006
Los punteros, aunque especiales, son variables y por tanto también tienen una
dirección.En este sentido aclararemos que:
Si nos fijamos en x:
a) La dirección de x es 1000
b) El valor de x es 10
c) X , como no es un puntero, no tiene contenido
Una vez se ha comprendido este extremo podemos continuar diciendo que existen dos
operadores para la gestión de punteros:
&. Al igual que el anterior es un operador unario y nos devuelve una dirección de memoria, por
tanto lo podemos aplicar a cualquier variable a excepción, en principio de las variables de tipo
register, pues estas no se hallan en memoria. Si aplicamos &x nos devolverá la dirección de x,
es decir, 1000
b) &x=p: esta es una operación sin sentido, puesto que significaría el cambio de localización
en memoria de la variable x, la cual cosa es absurda, al igual que lo sería el intentar asignar el
valor de una variable a una constante (3=x).
d) x=*p1: a través de esta operación asignamos como valor de x el contenido de p1. Es decir
asignamos a x 10.
e) &(*p1): con esta operación se produce la devolución de la dirección del contenido de p1, es
decir devolverá la dirección de la variable donde apunta p1 (1000).
página 22
Programacion en C Eduard Martín 1995
No podemos realizar la operación *1002 puesto que cuando damos contenido de una
dirección va a depender el mismo del tipo asociado al puntero. Esto es lo que conocemos con
el nombre de TIPO BASE DEL PUNTERO. Así el contenido de un puntero a un char es un
byte, el contenido de un puntero a un integer es de dos bytes. Por tanto no podremos mezclar
punteros cuyo tipo base sea distinto.
int *p;
int x;
int *p=&x; En este sentido diremos que aunque pueda parecer que estamos asignando al
contenido del puntero la dirección de x, no es así, el contenido del puntero será el valor de x.
En realidad esta expresión es equivalente a p=&x.
int x,*p1,*p2,desp;
p1=&x;
p2=p1+desp;
Como vemos podemos añadir a un puntero un int. No podemos añadir chars ni reales a
un puntero. En cualquier caso esto tiene utilidad para desplazarse por la memoria. Estamos
creando un puntero que apunta a otro lugar. No varía el tipo base del puntero que se crea en
función del primero.
En cuanto a las operaciones que podemos hacer con punteros diremos que podremos
sumar, restar, decrementar e incrementar.
Por otro lado la resta de punteros de un mismo tipo devuelve un integer que es igual al
número de elementos que existan entre dos punteros. Por tanto si le restamos 1000, siguiendo
el ejemplo anterior, a 1020, el resultado no será 20, sino 10, puesto que automáticamente C
página 23
Programacion en C Eduard Martín 1995
detectará el tipo de datos de que se trata y nos devolverá el número de elementos de ese tipo
que se ha avanzado.
Cuando llamamos a una función se crea una variable temporal que es copia de la que le
pasamos. Etas es la razón por la que cuando la función finaliza no se ha alterado el valor de la
variable pasada como parámetro. En caso de que pasemos como parámetro a la dirección
donde se halla esta variable, lo que hacemos es modificar directamente el contenido de aquélla,
con lo que estaremos modificando el valor mismo de la variable:
int x=5;
f(&x);
Para que esto se pueda hacer el argument de la función debe de ser del tipo puntero
del mismo tipo base de la variable pasada por referencia. Así la anterior función , el prototipo
sería void f(int *p).
Siempre que pasamos una dirección a una función ésta la debe de recoger un
puntero.Veamos dos ejemplos clarificatorios:
#include <stdio.h>
#include <conio.h>
void main()
{
int x=7,y=8;
clrscr();
printf("%d %d",x,y);
swap(&x,&y);
printf("%d %d",x,y);
}
#include <stdio.h>
#include <conio.h>
void main()
{
int r,s,p;
página 24
Programacion en C Eduard Martín 1995
int x=5,y=6;
float d;
clrscr();
vario(x,y,&s,&r,&p,&d);
printf("%d %d %d %f",s,r,p,d);
}
De esta manera tenemos que &cad[0] será la dirección inicialde la cadena, que
también podemos referenciar de la siguiente manera pc=cad.
Cuando pasamos una cadena siempre la pasamos por referencia y aque no podemos
pasar una cadena por valor. El nombre del array es la dirección inicial del mismo que es una
constante y no se puede modificar:
pide cad(cad,&0);
correspondería a :
Como ejemplo de ellos veamos la función que nos mostraría una cadena por pantalla
página 25
Programacion en C Eduard Martín 1995
int i=0;
while(cad[i] !=0)
putchar(cad [i++]);
}
Como vemos el nombre del array es la dirección del primer elemento. Podemos recoger
el nombre de una cadena en un puntero porque el nombre de una cadena no es más que una
dirección, por tanto siendo p un puntero (*p) podemos hacer, siendo cadena un array :
p=cadena. Por tanto, cuando pasamos una cadena por referencia no pasamos en sí un array,
sino que pasamos un puntero. Por ello podemos operar con ella como con un puntero.
cad[i] = *(cad+i)
Por tanto, es mejor declarar las funciones que reciben un array así: visucad (char
*cad); no siendo necesario pasar la longitud del array, puesto que esto no nos sirve de nada
ya que si el array no es numérico se supone que acabará en 0.
Por ejemplo una función que buscase un carácter en una cadena utilizando índices
sería, retornando un puntero que señala a la posición donde se ha encontrado el elemento
buscado:
En el ejemplo que sigue observamos como podemos hacer una función que devuelva la
longitud de una cadena:
página 26
Programacion en C Eduard Martín 1995
b) Puts(). Puts nos permite visualizar un string. Es por tanto en todo equivalente a hacer un
printf(“%s”,cad), pero con la ventaja de una mayor eficiencia. Nos devuelve un int. Su
prototipo es:
printf(“%s”,cad)
printf(“%s”,”Hola”)
printf(“%s”,p);
Por tanto, en realidad, un literal es equivalente a un array, es más, podriamos decir que
un literal no es más que un array sin nombre.
Por todo ello para modificar el contenido de un array no podremos utilizar una
asignación vulgar y corriente. Para ello deberemos utilizar una función que vaya susituyendo los
elementos del array uno a uno, mediante punteros.
Por ejemplo, veamos la función char *copiar(char *destino, char *origen) que copia
una cadena a otra:
Dos punteros pueden tener el mismo valor y apuntar a una misma dirección de
memoria. Cuando comparemos dos cadenas debemos por tanto utilizar dos punteros que irán
avanzando por la memoria comparando carácter a carácter. En realidad el resultado que se
podría inferir de la función que tiene como prototipo int comparar(char *s1,char *s2) es un
página 27
Programacion en C Eduard Martín 1995
integer que será 0 si las cadenas son iguales, un número positivo si s1 es mayor que s2 y un
número negativo si s2 es mayor que s1.
Ahora bien, sería conveniente siempre pasar como parámetro también un integer que
nos sirva de tope a la hora de recorrer la memoria:
#include <stdio.h>
#include <conio.h>
void main()
{
char cadena1[20]="Hola amigos mios que";
char cadena2[20];
char *punt1=cadena1;
char *punt2=cadena2;
clrscr();
funcion_sol(punt1,punt2);
puts(punt2);
}
página 28
Programacion en C Eduard Martín 1995
while(*aux++);
while(aux--!=uno) {printf("%c",*aux);}
}
página 29
Programacion en C Eduard Martín 1995
/************************************************************************************
** 2 EJERCICIO C **
** Escribir una función que reciba dos cadenas y busque en la primera **
** el primer caracter de la segunda **
*************************************************************************************
#include <stdio.h>
#include <conio.h>
void main()
{
char cadena1[20]="Hola amigos que tal ";
char cadena2[20];
char *uno=cadena1;
char *dos=cadena2;
clrscr();
gets(cadena2);
busca_char(uno,dos);
getchar();
}
/***************************************************************************************
** EJERCICIO 3 DE C **
** Confeccionar una función que recibe dos cadenas y devuelve la direcci**
** ón de la primera donde comienza la segunda subcadena o nulo si no lo *
** encuentra **
***************************************************************************************
#include <stdio.h>
#include <conio.h>
void main()
{
char cadena1[40];
char cadena2[40];
char *uno=cadena1;
char *dos=cadena2;
int direccion=0;
gets(cadena1);
gets(cadena2);
printf("La direcci¢n es %d",direccion=busca_cad(uno,dos));
}
página 30
Programacion en C Eduard Martín 1995
int auxiliar=0;
while(*continente)
{
if(*continente==*contenido)
{
auxiliar=&continente;
while(*contenido && *continente && *contenido==*continente)
{
contenido++;
continente++;
}
if(*contenido==0) {return auxiliar;}
}
continente++;
}
return 0;
}
/******************************************************************************************
** 4 EJERCICIO DE C **
** Para almacenar las notas de unos alumnos tenemos tres tablas: alumnos **
** asignaturas y notas por asignatura. Confeccionar una función en la que
** pasaremos el nombre de un alumno, el total de los alumnos y el total
** de las asignaturas y nos devolver la media de las notas de ese alumno.
*******************************************************************************************
#include <stdio.h>
#include <conio.h>
void main()
{
float media=0.0;
clrscr();
printf("La nota media es %f\n",media=med_al("Manolo",5,5));
}
página 31
Programacion en C Eduard Martín 1995
Pensemos en como hariamos para presentar por pantalla todos los meses del año,
siguiendo la utilización del ejemplo anterior, sin hacer utilización de punteros:
El problema se plantea cuando hablamos del nombre del array, en nuestro caso de
mes.
Intentaremos explicar la problemática con más detenimiento: está claro que el nombre
de un array es la dirección del primer elemento del mismo en un array de una sola dimensión.
Así mes es equivalente a &mes[0]. En un array de dos o más dimensiones el nombre del
primer elemento del array es la dirección del primer elemento del mismo. Así mes[0] es
equivalente a &mes[i][0] El problema se plantea con el objeto mes. Este nombre no es una
variable, en definitiva es una etiqueta con un valor, una dirección de memoria. Es en definitiva
una constante y como tal su valor es permanente e inmutable, ya que posee la dirección inicial
del array. La prueba de ello es que no podemos incrementar su valor pues se nos presentará un
error de compilación.
página 32
Programacion en C Eduard Martín 1995
Tampoco podemos hacer algo como char *p=mes[0] puesto que en este caso lo que
estariamos haciendo es destrozar la estructura de array de dos dimensiones que se halla en
memoria y pasariamos a tratar el array de una forma lineal.
Teniendo claro que mes es una etiqueta, una constante cuando nos refiramos a un
array de dos dimensiones y queramos utilizar un puntero deberemos referenciar al mismo como
un puntero que apunta a un conjunto de elementos del tipo que sea. Por ejemplo para
referenciar el array mes hariamos:
char (*p)[11]=mes; esto significa que tenemos un puntero que apunta a once chars, la
segunda dimensión del array. Con ello obtenemos un comportamiento curioso. Cada vez que
incrementemos el puntero saltaremos una fila. Esta es la manera de pasar un array como
parámetro de una función. De todas maneras esto tiene el inconveniente de que esta función
sólo podrá ser utilizada para trabajar con un array cuya segunda dimensión sea como máximo
de 11 chars.
También podemos pasar un array de dos dimensiones como parámetro de una función
simplemente referenciándolo así mes[][11], la cual cosa es la más práctica si utilizamos
índices para trabajar con ellos.
/**************************************************************************************************************
*******
** EJERCICIO 5
** Manejo de punteros con arrays de dos dimensiones
** En este ejemplo se demuestra como: Referenciar un array de dos dimensiones con punteros
** Pasar un array de este estilo a una función **
** Comparar diversos modos de visuali zar un array de este
** estilo
**************************************************************************************************************
*******/
#include <stdio.h>
#include <conio.h>
void main()
{
char cadena[][6]={"Uno","Dos","Tres"}; /*Array de dos dimensiones*/
char (*p)[6]=cadena, (*m)[6]=cadena; /*Punteros a este array*/
int i=0;
clrscr();
ver(cadena); /*Paso a función todo el array*/
for(i=0;i<3;i++) {puts(cadena[i]);} /*Visualizo filas con índice*/
for(i=0;i<3;i++) {printf("%s \n",p++);} /*Visualizo filas con puntero*/
for(i=0;i<3;i++) {printf("%d\n",*m); m++; } /*Visualizo dirección filas*/
página 33
Programacion en C Eduard Martín 1995
for(i=0;i<3;i++) {(printf("%s \n",cad++));} /*Visualizo array con puntero pasado como parámetro*/
}
página 34
Programacion en C Eduard Martín 1995
Uno
Dos
Tres
Uno
Dos
Tres
Uno
Dos
Tres
-30
-24
-18
Como vemos en las tres últimas líneas se visualiza la dirección del primer char de cada
fila. Como vemos la diferencia es de 6, que coincide con el número máximo de columnas por
fila (5 + 1 para el 0 final).
Imaginemos el array anterior, de los meses como una estructura en la que tenemos un
puntero para referenciar cada uno de los elementos que la componen (strings):
char *mes[]={“Enero”,....”Diciembre”}
...
114 Marzo
106 Febrero
100 Enero
mes
for(i=0;i<12;++i)
puts(mes[i]);
- mes[0]= 100
-*mes[0] = “E”
-*mes = 100 ...... > *(mes+i)
-*mes+1 = 101 ..... >(*mes)+1
página 35
Programacion en C Eduard Martín 1995
-mes[0] = 100
-*(mes[0]+1) = “n”
-*(mes+1) = 106
-++ mes = INCORRECTO, pues mes es el nombre de un array y por tanto una constante.
-*(mes[i]+j) = mes[i][j]
En este sentido diremos que la expresión mes[i] es evaluada por el compilador como
*(mes+i). Por tanto mes[i][j] es evaluado como *(*(mes+i)+j).
Por otro lado si tenemos (mes+1) valdrá la expresión 202 si la dirección de mes es
200, puesto que tenemos un array de punteros y los punteros son de 16 bits el incremento va
de 2 bytes en 2 bytes.
-**(mes+1)= vale F
Una variable puntero a puntero se podria referenciar también así: *pp[]. Los punteros a
punteros son muy utilizados para pasar arrays de punteros a funciones y modificar los
contenidos de los mismos
El primer parámetro que se le pasa a la función main, por defecto, es el nombre del
programa incluido su path.
página 36
Programacion en C Eduard Martín 1995
La solución a todo ello es la memoria dinámica. Existen tres funciones importantes que
son de Ansi C, aunque dependiendo del compilador, podemos tener otras muchas.
a) malloc: esta función nos sirve para reservar memoria. Nos devuelve un puntero al inicio de
la zona de memoria reservada. El tipo de retorno es un void , es decir un puntero a cualquier
cosa. El prototipo de esta función es:
Así si tenemos la variable int *pi, y queremos reservar espacios para 50 ints lo
podremos hacer de esta manera:
pi=(int*)malloc(100)
En cuanto sizeof diremos que es un operador unitario que puede ser un tipo o una
variable y retorna el tamaño del tipo o la variable. Además este operador se calcula en
compilación y no frena los programas.
Por otro lado es habitual ver en las ayudas de los compiladores la expresión size_t en
lugar de unsigned int en el prototipo de malloc y de otras funciones. Estas dos expresiones
son idénticas.
if(!(pi=(int*)malloc(100)))
puts(“No existe suficiente memoria”)
página 37
Programacion en C Eduard Martín 1995
{
int *pi;
if(!(pi=(int*)malloc(100)))
puts(“No existe suficiente memoria”);
return pi;
}
reservar(**pi,int n)
{
if(!(*pi=(int*)malloc(100)))
puts(“No existe suficiente memoria”);
}
b) free: esta función es necesaria para liberar la memoria reservada con malloc. Free libera el
espacio medainte determinar como parámetro a l dirección inicial del espacio a liberar. Su
prototipo es :
void free(void *)
Por tanto podriamos mover el puntero inicial y luego eliminar el que hemos igualado a
aquél:
int *p1,*p2;
p1=(int*)malloc(100);
p2=p1;
p1++;
....
free(p2);
c) Realloc: esta funcón nos permite cambiar el tamaño de una zona reservada: supongamos
que hemos reservado 100 pero queremos más espacio pero que sea contiguo en memoria. El
prototipo de la función es:
En el caso que pidamos más memoria y no exista memoria suficiente contigua a la que
pedimos, realloc actuará así:
En cualquier caso realloc nos devolverá el puntero al inicio de la zona que le hemos
solicitado. También es mejor recibir este puntero en otra variable diferente al puntero inicial para
página 38
Programacion en C Eduard Martín 1995
así no perder el contenido de aquella zona en caso que realloc devuelva nullo por no existir
memoria disponible.
a) AND: representado en C por el operador &. Este operador nos sirve para determinar si un bit
está activado o no y también para desactivar ciertos bits. La forma de operar de and es que los
bits que pongamos a 1 en la máscara no afectarán a la variable a la que la apliquemos, los que
pongamos a 0 determinarán que el resultado sea 0.
También podemos operar con la máscara, así podemos hacer cosas como x&=masc.
Con esta expresión lo que haremos es desactivar bits
11111111
11110111 resultará después de aplicar el and segundo -> 11110111
b) OR: representado por | en C el efecto que produce es: un uno producirá en el resultado un
uno y un cero no alterará el resultado.
Por tanto:
1010
1100 dará como resultado 1110
d) NOT: en C se manifiesta con el símbolo C ~ . Este operador cambia todos los bits de una
variable, es decir, produce la generación del complemento a uno. Se utiliza mucho en
combinación con el AND.
Si los punteros son de 16 bits indican un desplazamiento dentro del segmento, pero un
puntero a una función indica un desplazamiento dentro del segmento de código no dentro del
segmento de datos, o de pila como hasta ahora.
página 39
Programacion en C Eduard Martín 1995
También se hace servir para el caso de que tengamos una función que aueramos que
dependiendo de algo nos haga diferentes cosas. De esa manera, podriamos psasar como
parámetro de esta función una función a la que ha de llamar. Esto flexibiliza los códigos.
Para pasar una función com parámetro no podemos hacerlo así: f1(f2()). Lo haremos
así:
f1(int(*pf)(char *))
{
char *p;
int a;
....
a=(*pf)(p);
...
}
int (*p) ( );
p=f;
Asimismo también podremos poner varios parámetros a la función e incluso que sean
de diferente tipo. Incluso podriamos tener funciones con parámetros variables:
Los tres puntos indican que se pueden recibir más parámetros: los que queramos.
Dentro de la función deberemos utilizar variables internas del sistema y lo podemos consultar
con la variable va_arg. Este sistema es el aplicado en funciones como scanf y printf.
Siendo:
página 40
Programacion en C Eduard Martín 1995
prin
n_elem
tam_elem
Si hemos de ordenar algo raro deberemos construir una función y la salida deberá
retornar 0 mayor que 0 o menor que 0. Esta función existe en toda libreria de ansi C y se llama
qsort, que utiliza el algoritmo quick sort. Lo unico que deberemos escribir par aque funcione es
la función que compara. Podrmeos comparar cualquier cosa que comporta posiciones
adyacentes de memoria. Cada tipo de objeto se comparará de una manera por ello crearemos
una función para cada tipo.
De todas maneras la función antes descrita no está completa puesto que deberia
introducir en su prototipo la cláusula const:
Lo que indicamos con esta cláusula es que el valor que sigue es constante y por tanto
no podrá ser modificado en el código. Si lo hacemos se producirá un error en tiempo de
compilación.
Otra gran aplicación de los punteros a funciones es la de crear menús. Así si tenemos:
int f1(void);
int f2(void);
int f3(void);
....
inf (*pf[3])( ) esto es un array de punteros a tres funciones
29 Creación de proyectos
En C por un proyecto entendemos el grupo de fuentes y librerias necesarias para
generar un ejecutable. No todos los lenguajes incorporan la idea de proyecto. Con la opción
make podemos generar un proyecto. Cuando la ejecutemos nos compilará todos los fuentes
que lo compongan y además linkará estos con las librerias que tengamos configuradas en el
entorno de borland creando un solo ejecutable.
Las ventajas de utilizar proyectos son evidentes pues nos permitirán fragmentar los
trabajos.
a) crear librerias
b) añadir módulos a las librerias
c) borrar objetos de una libreria
página 41
Programacion en C Eduard Martín 1995
Los mandatos o opciones para conseguir esto los podemos conseguir utilizando el
parámetro /? al llamar a tlib. En general son:
Con los punteros a 16 bitsf sólo nos podemos mover por un solo segmento. Con los
punteros de 32 bits nos podremos mover por más de uno. Es decir, con los punteros de 16 bits
únicamente teniamos la posibilidad de un desplazamiento respecto a un segmento por defecto.
Con un puntero de 32 bits se guarda tanto la referencia al segmento como el desplazamiento
sobre el mismo. En este último caso podemos determinar el segmento y por tanto tener
programas más grandes.
- Si deseamos 1 bloque de memoria de 180 kb no podemos hacer servir 3 punteros far, sino
una huge
seg des
p1 p1 seg des
p2 p2
Si hacemos p1<p2 debería de dar como resultado cierto pero da falso. P1 está en
posición más pequeña que p2 pero en cambio su desplazamiento es más grando y es lo que
página 42
Programacion en C Eduard Martín 1995
está evaluando. La comparación de punteros far tiene sentido cuando estos punteros se
desplazan en un mismo segmento.
b) HUGE: estos punteros están normalizados, es decir se pueden incrementar sin prblemas y
se pueden comparar sin problemas:
Este último caso es incorrecto puesto que perderiamos el segmento , ya que sólo se
copia el desplazamiento
struct nombre
{
tipo campo1;
tipo campo2;
.....
.....
};
Como vemos la llave del final lleva punto y coma. Después deberiamos definir variables
de este tipo:
Las estructuras pueden declararse dentro o fuera de las funciones. Dentro sólo serán
reconocibles por esa función. Fuera serán reconocibles dentro del fuente. Siutilizamos varios
fuentes deberemos declararla en cada uno de ellos o bien en un fichero de cabecera para que
sean accesibles desde todos los módulos.
typedef struct
{
.......
} ; nom_estructura
página 43
Programacion en C Eduard Martín 1995
nom_estructura var1,var2...;
Por otro lado también es posible delcarar un tipo e inicializar variables del mismo a la
vez:
struct pila
{
....
}p1,p2;
Todo puede ser utilziado como campo de una estructura, incluso otras estructuras
previamente declaradas
Para acceder a los datos de una estructura poseemos el operador punto (.). Por
ejemplo si tenemos:
struct DATOS
{
int edad;
};
struct NODO
{
struct DATOS info;
struct NODO *pnodo;
};
como vemos aquí hacemos referencia a una estructura -datos- que previamente habrá sido
definida y además se hace referencia a un campo que es la misma estructura que estamos
definiendo. Esto último no es posible en otros lenguajes como pascal.
primero.info.edad
struct CLIENT
{
char nom[10 ];
int edad;
};
Además las estructuras se pueden asignar entre si. Así con el ejemplo anterior
podriamos hacer:
struct client2=client1;
Esto aunque sea incoherente pues en C por ejemplo no podemos asignar arrays a
arrays, se puede hacer puesto que C gestiona las estructuras como paquetes de bytes.
página 44
Programacion en C Eduard Martín 1995
client1.nom=“Pepe”;
deberemos hacer:
strcpy(client1.nom,”Pepe”);
Si a una función le pasamos esto f1(client1.nom) lo recibirá como f1(char *p) puesto
que nom es del tipo char *. Esto es absolutamente normal.
Si pasamos toda la estructura f1(client) lo deberemos recoger con el tipo que le hemos
dado a struct: f1(struct client cli). Ahora bien esto lo estamos pasando por valor lo que en el
caso de estructuras grandes hará desperdiciar mucho espacio. Para evitar esto es mejor
pasarlas por referencia, es mejor, incluso, que nunca las pasemos por valor.
Para pasar una estructura por referencia o bien lo haremos a través de un puntero, o
bien pasando su dirección de memoria:
f1(&clien1)
(*pcli).nom o pcli->nom
pcli->nombre.apellido
Por otro lado lla expresión *pcli->nom o *(pcli->nom) nos mostraría el primer caracter,
en este caso, de nom, puesto que nom es la dirección de un campo de chars.
Asimismo podemos declarar unpuntero a estructura struct client *pli. Este puntero
para inicializarlo lo podremos hacer así:
página 45
Programacion en C Eduard Martín 1995
struct CLIENT
{
int edad;
};
int i=0;
struct CLIENT clis[2 ];
for(i=0;i<2;++i)
clis[i ].edad=i*10;
for(i=0;i<2;++i)
printf(“%d \n”,pclis->edad); ... dara 0 0 porque no incrementamos el puntero
-tiny
-small
-compact
-medium
-large
-huge
Tanto los modelos tiny como huge no se pueden utilizar en windows. Nosotros siempre
utilizamos el modelo small que utiliza por defecto punteros de 16 bits pero los podemos
declarar de 32. En los otros hasta en el huge, por defecto, los punteros son far.
Modelo tiny
En este modelo todos los punteros son near . Es imposible declarar puneteros a 32
bits. Normalmente se utilizan para hacer programas .com.
página 46
Programacion en C Eduard Martín 1995
Modelo small
Todos los punteros a funciones (que son
punteros a código) serán de 16 bits en este
cs modelo. Además los datos no ocupan más de
Codigo 64 K un segmento. En C los datos los constituyen las
ds variables globales y las estáticas. Tanto los
ss datos como la pila se incluyen en el segmento
Datos
de datos. La pila incluye las variables locales. La
Heap > memoria dinámica se obtiene del heap que
Pila ^ crece en sentido inverso a la pila.
Modelo compact
El código debe caber en 64 Kb pero en cambio utiliza punteros de 32 bits para acceder
a los datos.
Aquí no se distingue entre heap y farheap.
Esto es porque todas las funciones que trabajan con
cs punteros lo hacen con punteros de 32 bits y por
Codigo 64 K tanto malloc y farmalloc son lo mismo. En cuanto al
ds código todo es igual: punteros de 16 bits. Como
Datos 64 K vemos con el modelo compact y el small se puede
ss conseguir mucha memoria pero es preferible utilizar el
Pila^ 64 K modelo compact cuando manejemos grandes
cantidades de datos.
Heap >
Modelo medium
Es lo inverso: los datos se comportan como en el modelo small (16 bits... ) pero
podemos tener varios segmentos de código. Esto último quiere decir que el código puede ser
más grande pero debemos tener en cuenta que cada módulo debe caber en 64 kb. Esto lo
debemos hacer nosotros utilizando compilación separada.
Todas las llamadas a las funciones se harán far. Para ir más rápido podemos forzar a
que una función sea near (esto podemos decidirlo si estamos seguros que una función sólo
página 47
Programacion en C Eduard Martín 1995
será llamada por código de su propio segmento). La palabra near se pone entre el retorno y el
nombre de la función : tipo near func().
Modelo large
página 48
Programacion en C Eduard Martín 1995
33 Las listas en C
Supongamos una estructura en la que uno de los campos es un puntero a una
estructura igual:
struct REGISTRE
{
......
......
struct REGISTRE *pregis;
}
Si tenemos una estructura así podriamos tener una lista o un encadenamiento simple
de estructuras. La representación gráfica de ello sería:
registre
pregis
registre
pregis
struct NODO
{
struct REGISTRE reg;
struct NODO *pregis;
};
siendo registre:
struct REGISTRE
{
....
....
}
página 49
Programacion en C Eduard Martín 1995
if(!reservar(&pNodo))
puts(“No memoria”);
union NOM
{
.....
.....
};
union NOMBRE
{
int i;
char c;
};
Todas las variables comienzan por la misma dirección (la más baja) Si accedemos a c
es como acceder al byte menos significativo de i:
página 50
Programacion en C Eduard Martín 1995
100 101
Por ejemplo:
union NOM x;
x.i = 0 x 1234 //Tendremos en la parte baja 12 y en la alta 34
printf(“%x”,x.c) //Muestra 34
x.o=0x56 //Tendremos en la parte baja 56 y en la alta12
printf(“%x”,x.i) //Mostrará 1256.
union NOM
{
int i;
char[2 ]; // Con c[.. ] puedo acceder a la parte alta y baja de i
};
int i;
char *pc;
pc=(char *) &i;
pc[0 ] //byte menos significativo
pc[1 ] //byte más significativo.
struct REG
{
char tiporeg;
union REG3 uni;
};
union REG3
{
struct REGA datosA;
struct REGB datosB;
char +[- ];
};
struct REGA
{
char nom[10 ];
.....
};
struct REGB
{
.....
página 51
Programacion en C Eduard Martín 1995
Podemos tener una estructura en la que el primer campo determine el formato de como
acceder. Dependiendo de tiporeg accederemos con REGA, REGB o de forma lineal.
35 Campos de bits
Son tiposs particulares de estructuras en las que el tamaño de sus campos se dan en
bits no en bytes, esto hace posible que el tamaño de cada campo pueda ser cualquier número,
y no necesariamente múltiplo de 8.
En un campo de bit que tiene de tamaño 1 byte por tanto, sólo puede tener como
máximo 8 campos.
En el caso de tene run campo de bits de 7 dependiendo del procesador nos sobrará el
bit más alto (caso de los Intel y compatibles) o los más bajos (caso de los Motorola).
struct nombre
{
unsigned nombre_campo : longitud; // la longitud puede ser cualquier valor
positivo
}
struct nom
{
unsigned campo1: 1;
unsigned campo2: 3;
unsigned campo3; 2;
unsigned campo4; 2;
};
Para acceder a los campos se hará de la forma habitual. Los campos se utilizarán
como numéricos normales y podrán tener el valor que les permite su longitud. No podemos
tener punteros a campos de bits. No podemos aplicar el operador &. Lo que si que podemos
hacer es lo siguiente:
union byte
{
unsigned char c;
struct bits8 bits;
};
página 52
Programacion en C Eduard Martín 1995
Prácticamente se podía hace rlo mismo con ambo grupos. Al crear un C estándar se
escogió crear un tercer grupo, que será el grupo de funciones de ficheros de ANSI C.
Más tarde al entrar en juego el C++, se ofreció una cuarta alternativa al tratamiento de
ficheros, que serán las funciones de ficheros de C++.
En C todos los dispositivos de entrada / salida se tratan igual que un fichero (por
influencia de unix) esto quiere decir que sería posible utilizar las funciones de ficheros
señalando que el dispositivo de entrada es la cónsola y no utilizar las funciones que hasta
ahora hemos visto.
Este puntero suele ser llamado en los libros como corriente, canal, secuencia... en
ingles es llamado stream¸que no es lo mismo que un handler.
página 53
Programacion en C Eduard Martín 1995
Por otro lado existen funciones recogidas en ficheros de cabecera, como dir.h, que
realizan funciones a nivel de sistema operativo como eliminar ficheros (remove en stdio.h) y
otras.
a) Byte a byte.
b) Línea a línea: secuencia de caracteres con un indicador de final de línea (no tienen un
tamaño fijo).
c) Bloque a bloque: n bytes. El tamaño máximo de un bloque es de 64 Kb en el modelo small
d) Con formato: equivalente a printf y scanf. Se hacen conversiones.
Un fichero texto es una secuencia de líneas que estan compuestas por caracteres
imprimibles separadas por un indicador de final de línea. Lo malo es que en diferentes sistemas
operativos los indicadores de final de línea son diferentes. Esto llevará a la existencia de
diferencias en la apertura en modo texto o binario. Por ejemplo:
Es curioso que en C sólo sea preciso \n para saltar de línea cuando en ensamblador es
necesario especificar ambos caracteres. Esto es porque cuando escribimos en un fichero
abierto en modo texto al escribir un 10 se añade automáticamente un 13. Por ello cuando
abrimos un fichero en modo texto desaparecen los 13, esto significa que un fichero abierto en
modo texto en memoria tendrá un byte menos por línea.
Funciones de ficheros
a) fopen: se utiliza para abrir ficheros. Para ello precisaremos: el nombre del fichero, el modo
de apertura que deseamos para éste.
El nombre del fichero puede expresarse con la ruta completa de directorio de situación,
pero con la precaución de especificar las barras invertidas con otra delante, para evitar
confusiones. Un ejemplo:
FILE *pf;
pf=fopen(“mi_fich”,-.....);
En caso de que el fichero no se pueda abrir por algún motivo esta función retorna
NULL. Por tanto siempre deberesmo mirar que el retorno de fopen no sea null
página 54
Programacion en C Eduard Martín 1995
En cuanto a los modos de apertura (por defecto son en modo texto si no se especifica t
o b):
Las diferencias entre una r y una w es que los ficheros siempre han de existir. Por
tanto si abrimos un fichero no existente con r se devolverá un null. Si lo abrimos con w se
creará. En caso de existir el fichero se borraría en este último caso.
FILE *pf;
if(!(pf=fopen(“fich”,”r+”))
if(!(pf=fopen(“fich”,”w+”))
puts(“Error en apertura”);
perror: admite como argumento una cadena, la que queramos y su retorno es un mensaje con
la cadena que hayamos pasado como argumento más dos puntos y un mensaje del sistema
que indica el error que se ha producido. Este mensaje está en ingles. Si quisieramos
modificarlo para que apareciese en castellano deberiamos primero escribir una función que
hiciese algo asi:
Por otro lado hay que decir que cuando hay un error no se modifica el valor de errno.
Para ello hay que hacer clearerr()
b) fclose: permite cerrar ficheros: int fclose(FILE *). Cuando un programa se ejecuta de forma
normal al finalizar correctamente cierra por sí sólo los ficheros por lo que no precisaremos
cerrar los ficheros con esta instrucción. También una salida con exit cerrará los ficheros.
página 55
Programacion en C Eduard Martín 1995
Son válidos con ficheros abiertos en modo binario o en modo texto. Se puede leer y
escribir cualquier cosa. Al in8iciarse un programa en C se abren tres punteros de tipo FILE. No
son modificables:
Por otro lado también tenemos stdprt y stdaux, que nos redirigen la salida hacia el lpt1
y com1 respectivamente.
Podriamos hacer una función que funcionase igualmente ante un programa que recibe
un fichero como parámetro o bien que se le pasa como argumento de entrada el mismo fichero:
FILE *pf;
if(argc==1)
pf=stdin;
else
....
char *fgets(char *,int,FILE *). El int determina el núm. máximo de caracteres a leer.
int fputs(char *,FILE *)
fgets lee del fichero indicado en FILE *, lo guarda en char* y lo hace hasta no encontrar
una /n o se llegue al valor señalado en int. Ofrece esta última ventaja frente a gets( ).
fgets retorna la misma dirección inicial o un NULL en caso de final de fichero. Por tanto
una buena forma de controlar el final de fichero en lecturas por líneas es la siguiente:
while(fgets( ))
{
....
}
Los finales de las cadenas en gets se producían cambiando el enter final por un 0. puts
escribía hasta encontrar un 0 y saltaba líneas.
fputs lo que hace es escribir hasta que encuentra un 0 pero después no añade nada.
Por tanto se producirá el sato también porque habrá encontrado la /n antes del 0.
página 56
Programacion en C Eduard Martín 1995
while( )
{
gets(s);
strcat(s,”\n”); añadimos nosotros la /n
fputs(s,pfich);
}
while( )
{
fgets(s,...,stdin);
fputs(s,pfile);
}
página 57
Programacion en C Eduard Martín 1995
int x;
scanf(“%d”,&x);
fscanf devuelve el número de copias leídos que coincidirá con el número de formatos
leídos.
Esta forma no tiene equivalente por cónsola. Sólo se trabaja con ficheros binarios y la
cónsola simempre se abre en modo texto.
a) fread
Siempre lee un número entero de bytes. Por ejemplo teniendo un fichero con 55 bytes
si queremos leer 100 int leerá 27 (no puede leer 27’5 X 2). Si decimos que lea dos elementos
de 100 bytes no leera nada.
int t[100 ];
fread(t,sizeof(int),100,pfin);
int x;
fread(&x,sizeof(int),1,pfin);
fread(&c,sizeof(char),1,pfin);
página 58
Programacion en C Eduard Martín 1995
while(fread(&c,sizeof(char),1,pfin)
{
...
}
fread pretende leer unidades de información enteras. Si no hay suficientes para leer no
acabará nunca de leerlo. Si hay 55 bytes en un fichero y le decimos que lea de 9 en 9 sólo
leerá hasta 45.
Por tanto, para evitar esto, le daremos dos valores: el tamaño de la unidad a leer y el
número de unidades a leer.
fread nos devolverá el número de unidades que ha leido : esto significará por lo
general,que si el númerode unidades devueltas es inferior a las solicitadas, es que estamos en
el final de fichero.
b) fwrite.
Hace exactamente lo mismo que el anterior, pero en lugar de leer ficheros escribe.
Con fread si no tienes reservado suficiente memoria el programa se colgaraá en el ecaso de
fwrite si hacemos por ejemplo:
char *s=“hola”;
fwrite(s,1,1000,pfont);
nos llenará 1000 bytes en pfont a partir del inicio del string s. Lo cierto es que con
fwrite podemos simular un puts:
fwrite(s,1,strlen(s),pfont)
La existencia de estas dos funciones nos obliga a conocer otras dos: la que nos
permite posicionarnos en un fichero y la que nos informa sobre la posición actual en un fichero:
fseek
ftell
fseek se posiciona en un fichero: sus parámetros son: el fichero, cuántos bytes nos
queremos desplazar y a partir de donde:
página 59
Programacion en C Eduard Martín 1995
fseek(pfin,52*sizeof(struct reg),seek_set)
Si fseek nos devuelve un número indicador del éxito del posicionamiento este número
será un 0.
i=fseek(pfin,0L*sizeof(struct reg),seek_set);
j=ftell(pfin);
De la manera anterior copiariamos un fichero. EOF no nos indica que ha leido el final de
fichero sino que ya no puede leer más.
Con un fichero de texto funciona pero con uno binario tendriamos problemas ya que
podriamos encontrar un byte que fuese 11111111 y en cambio no sería el final de fichero.
while(!feof(pfin))
putc(getc(pfin),pfout);
Este bucle copia un byte de más ya que la última iteración, cuando se encuentra el
final de fichero, primero lo capta y después pregunta por él. Por tanto en el fichero de destino
se copiaría un byte de más. Una mejora sería la que sigue:
c=getc(pfin)
while(!feof(pfin))
{
putc(c,pfont);
c=getc(pfin);
}
página 60
Programacion en C Eduard Martín 1995
Aquí primero se lee y después de copia. Por ello la mejor solución sería:
página 61
Programacion en C Eduard Martín 1995
b) Que la variable sea un integer. Cuando trabajamos con integers con las funciones de
ficheros, trabajamos con la parte baja del byte.
while((c=getc(pfin) != EOF)
putc(c,pfout);
página 62