You are on page 1of 83

Asignatura de

Sistemas Operativos

INTRODUCCIÓN A LA
PROGRAMACIÓN EN
LENGUAJE C
GUÍA BÁSICA PARA PRINCIPIANTES V. 1.0

Universidad de Valladolid

Segundo de Ingeniería Técnica Informática de Sistemas

1 de noviembre de 2010
Introducción a la programación en C by CTG © Página 1
ÍNDICE DE CONTENIDOS
0. NOTA DEL AUTOR ................................................ 4
1. INTRODUCCIÓN .................................................... 6
2. VARIABLES .......................................................... 8
3. OPERADORES ..................................................... 12
4. OPERACIONES E/S.............................................. 18
5. CONTROL DE FLUJO ........................................... 22
6. FUNCIONES ........................................................ 28
7. VECTORES ......................................................... 34
8. PUNTEROS .......................................................... 42
9. ESTRUCTURA DE DATOS .................................... 46
10. PREPROCESADOR................................................ 52
11. FICHEROS ........................................................... 54
12. HERRAMIENTAS ................................................. 58

APÉNDICE A ............................................................. 60
APÉNDICE B.............................................................. 62
APÉNDICE C ............................................................. 64
APÉNDICE D ............................................................. 66
APÉNDICE E.............................................................. 68
APÉNDICE F .............................................................. 70
APÉNDICE G ............................................................. 72
APÉNDICE H ............................................................. 76

BIBLIOGRAFÍA .......................................................... 82

Introducción a la programación en C by CTG © Página 2


Introducción a la programación en C by CTG © Página 3
Capítulo 0 – Nota Del Autor

En algunos casos el código que se muestra como ejemplo no es directamente


codificable y ejecutable.
Por razones de ahorro de espacio y legibilidad se han eliminado ciertas porciones de
código.
En caso de dudas sobre las estructuras y funciones que aquí se referencian, consulte la
bibliografía o el manual en línea, por ejemplo:

# man fprintf

Cada apartado de este curso está dividido en dos zonas:


Apuntes de teoría con ejemplos y cuestiones teóricas.
Ejercicios propuestos. Aquí hay dos tipos de enlaces:
Ficheros descargables, con código para compilar y observar su
funcionamiento.
Guiones de laboratorio aparecidos en cursos anteriores, en los que se
ponen en práctica los conceptos desarrollados en el apartado teórico.

En cada ejercicio propuesto se indica su grado de dificultad


(* = fácil, ** = media, *** = difícil).

Realice los guiones propuestos, y no pase al siguiente apartado sin haber respondido
las preguntas y realizado los ejercicios.

Introducción a la programación en C by CTG © Página 4


Introducción a la programación en C by CTG © Página 5
Capítulo 1 – Introducción
Historia

1967, BCPL (Basic Combined Programming Lenguage)- Martin Richards busca un


lenguaje para el diseño de Sistemas Operativos.
1970, Ken Thompson – primera versión de UNIX. Usa lenguaje B.
1972, Denis Ritchie – Lenguaje C.
o Lenguaje de alto nivel – independiente de máquina.
o Con instrucciones sencillas, próximas al código máquina.

Con la popularidad de las microcomputadoras muchas compañías comenzaron a


implementar su propio C por lo cual surgieron discrepancias entre sí.

1983, ANSI (American National Standars Institute) estableció un comité para crear
una definición no ambigua del lenguaje C e independiente de la máquina que pudiera
utilizarse en todos los tipos de C.

El primer programa

Un programa siempre ha de estar documentado. Use /* Comentarios */


Cada sentencia termina en ;
En lenguaje C un programa es una serie de llamadas a funciones (devuelve un nuevo dato).
Todo programa C debe contener la función especial int main(). Ésta es la función
principal del programa, o lo que es lo mismo, la función por la que empezará a
ejecutarse el programa.
A la hora de identificar variables y funciones es importante tener en cuenta que el
lenguaje C distingue Mayúsculas y minúsculas
Los ficheros de cabecera (ficheros acabados en .h y que se incluyen en un programa C
mediante la palabra #include), contienen, como se verá más adelante, prototipos, es
decir, declaraciones y/o definiciones de variables, constantes, funciones, etc.
El cuerpo de una función va entre llaves {}.
La palabra clave return marca el final de una función. Cuando se ejecuta esta
sentencia, el control pasa a la función que llamó a la que ahora finaliza. Si se trata de
la función principal (función main), el control pasa al intérprete de comandos del
Sistema Operativo.

Introducción a la programación en C by CTG © Página 6


Compilación

El lenguaje C habitualmente es compilado, con lo que el código fuente se debe traducir a


código máquina a través del compilador. Dependiendo del Sistema Operativo y la plataforma
de desarrollo, se tendrán distintos compiladores. En máquinas tipo UNIX / LINUX los
compiladores más habitualmente usados son el cc o el gcc. La sintaxis de estos compiladores
es la siguiente:

cc nombre_fuente.c –o nombre_ejecutable (-Wall indica si hay algún warning)


Opciones: -ansi, -l ... (ver man cc ó man gcc).

Una opción de uso frecuente es la –l que seguida por una letra indica al compilador aquellas
bibliotecas de funciones que deben ser incluidas, cuando estás no son la estándar. Es habitual
usar en programas funciones matemáticas como seno, coseno, potencias, etc. Estas funciones
están definidas en la biblioteca matemática, que para que sea incluida en la compilación debe
añadirse a la línea de compilación la opción –lm.

Ejercicio/Ejemplo. Mi primer programa C. Mediante un editor escribe el siguiente código,


compílalo y ejecútalo.

Responde a las siguientes preguntas:

1. ¿Qué hace la función printf?. Cambia el valor del argumento a esa función (valor
dentro del paréntesis) y examina qué efectos tiene al compilar y volver a ejecutar el
programa.
2. Cambia el valor del argumento del comando return por otro entero. Vuelve a
compilar el programa y ejecutarlo. Tras esto, Y SIN EJECUTAR NINGÚN OTRO
COMANDO ENTRE MEDIAS, ejecuta el siguiente comando UNIX: “echo $?”.
¿Cuál es el significado del argumento de la función return.
3. Cambia el nombre a la función main e intenta compilar el programa. ¿Por qué da
error?

Ejercicio/Ejemplo. Al compilar el siguiente programa verás que da error. Contiene un error


de sintaxis muy común al escribir programas en C. ¿Cuál es el error?

/* Programa con error */


#include <stdio.h>
int main( void )
{
printf( "Hola a todos!\n" )
return 0;
}

Introducción a la programación en C by CTG © Página 7


Capítulo 2 – Variables
Introducción

El lenguaje C maneja datos tanto numéricos (enteros y reales) como de tipo carácter. En este
último caso hay que diferenciar entre los caracteres tratados de manera individual, y las
cadenas de estos. En la siguiente tabla podemos ver un ejemplo de lo expuesto (las comillas
que aparecen en los tipo carácter y cadenas son necesarias al usar este tipo de datos en C).

Enteros 19 -152

Reales 1.4 -156.2 19.13e+12

Carácter `a` `3` `\n`

Cadenas "Hola Mundo" "Adiós\n"

Tipos de datos

Una variable de tipo entero puede ser:

Con signo Char Short Int long


(complemento a 2)
Sin signo unsigned char unsigned short Unsigned int unsigned
(binario natural) long
Tamaño (bytes) 1 2 4 4
(orientativo. Es
dependiente de la
máquina)

Ejercicio. Calcular con lápiz y papel el rango de cada tipo de dato entero.

En la siguiente tabla aparecen los distintos formatos de variables de tipo real. Estos datos se
almacenan en punto flotante, siguiendo, generalmente, la norma IEEE 754

Tipo Float double long double


Tamaño (bytes) 4 8 16 (ansi)

Introducción a la programación en C by CTG © Página 8


Ejercicio/Ejemplo. Uso de variables. Mediante un editor escribe el siguiente código,
compílale y ejecútale

/* Primera aproximación al uso de variables */


#include <stdio.h>
int main( void )
{
/* Ejemplo de declaración de variable sin asignación */
int a, b, UnEntero1;
char c;
unsigned long x;
float f;

/* Declaración y asignación */
int d=-52;
unsigned char UnaLetra=„a‟;
double Grande=-141.255e+7;

/* Ejemplo de asignación de valor a variables */


a=23;
c=‟s‟;
f=3.78;
printf (“valor asignado a la variable a: %d a c: %c a f: %f y a Grande: %lf\n”, a, c,
f, Grande);
printf (“Valor en decimal de la variable UnaLetra: %d. Valor interpretada como
carácter: %c\n”, UnaLetra, UnaLetra);

/* Ejemplo de asignación en otros sistemas de numeración */


a=0x050; /* Constante en hexadecimal */
b=0311; /* Constante en octal */
printf (“Valor de a en hexadecimal: %x y en decimal: %d. Valor de b en octal: %o
y en decimal: %d\n”, a, a, b, b);

return 0;
}

Responde a las siguientes preguntas:

1. ¿Cuál es la función de la letra que está a continuación del símbolo “%” en printf?
2. Viendo lo mostrado por el segundo printf (el que muestra la variable “UnaLetra”,
¿Cuál es el valor ASCII en decimal del carácter „a‟?
3. Añade una línea que, usando la función printf, muestra por pantalla el carácter
correspondiente al valor ASCII en hexadecimal 50 (último valor de la variable a).

Introducción a la programación en C by CTG © Página 9


Ejercicio/Ejemplo. Al compilar el siguiente programa dará error. Contiene otro error típico
en programas C. Buscar y corregir el error.

/* Programa con error */


#include <stdio.h>
int main( void ) {

/* Ejemplo de declaración de variable sin asignación */


int a, b, UnEntero1;
Char c;
unsigned long x;
float f;
a=23;
c=‟s‟;
f=3.78;

printf (“valor asignado a la variable a: %d a c: %c a f: %f y a Grande: %lf\n”, a, c,


f, Grande);
return 0;
}

Declaración de variables

C es (en general) un lenguaje compilado, y todas las variables han de ser declaradas antes de
ser usadas. A la hora de identificar a una variable (darle nombre) hay que tener en cuenta las
siguientes reglas muy simples:

Se puede usar cualquier combinación de letras (A-Z, a-z) y números (0-9). También se
pueden usar los siguientes caracteres “-“ y “_”
Los dígitos numéricos nunca debe estar al principio del nombre.
No se deben usar acentos ni ñ.
Se distingue entre mayúsculas y minúsculas. Por ejemplo dos variable llamadas Pepe
y pepe, serán diferentes.

Introducción a la programación en C by CTG © Página 10


Introducción a la programación en C by CTG © Página 11
Capítulo 3 – Operadores
Tipos

En el lenguaje C podemos distinguir los siguientes tipos de operadores:

De asignación: Asigna un valor a una variable. Forma del operador: “=”. Ejemplos de
uso:

Int a=45;

b = „a‟;

Aritméticos: Sirven para crear expresiones aritméticas. operadores: suma (+), resta (-
), producto (*), cociente (/) y módulo (%). Ejemplos de uso:

var1 = 3.5 * a;

Hola = b * 7.0 + 5.7;

Autoincremento/autodecremento: Dada la frecuencia de uso de variables que se


incrementan o decrementan en una unidad, estos operadores surgen para simplificar la
escritura de ese tipo de operaciones. Operadores: autoincremento (++), incrementa en
una unidad el valor de la variable y autodecremento (--), decrementa en una unidad el
valor de la variable. Pueden ir a la derecha o a la izquierda de la variable. Ejemplo y
explicación de su funcionamiento:

a++; ó ++a; es lo mismo que escribir: a = a +1;

a--; ó --a; es lo mismo que escribir: a = a – 1;

Ejercicio 6. Buscar cuál es la diferencia de poner los operadores a la derecha o a la


izquierda de la variable

Operadores relacionales: Permiten comparar variables y/o constantes. Devuelven un


valor lógico: verdadero o falso. Operadores: igual (==), distinto (!=), mayor (>),
menor (<), mayor o igual (>=) y menor o igual (<=)

Ejercicio/Ejemplo 7. En realidad los operadores relacionales devuelven un entero, cuyo


valor se interpreta como falso si el valor devuelto es 0 y verdadero si el valor devuelto es
distinto de 0. Con el siguiente programa se puede comprobar esto:

/* Ejemplo de valor entero devuelto por los operadores relacionales */

#include <stdio.h>

Introducción a la programación en C by CTG © Página 12


int main( void ) {

printf (“Valor devuelto si falso: %d, valor devuelto si verdadero: %d\n”, 6<5,
6!=5);

return 0;

Operadores lógicos: Permiten crear expresiones lógicas. El valor devuelto es


verdadero o falso. A este valor devuelto se le puedo aplicar lo indicado en el punto
anterior. Operadores: and (&&), or (||) y not (!). Normalmente se usan para concatenar
expresiones creadas con los operadores relacionales. Ejemplo:

a <= 5 || a > 3 Esta expresión será verdad si la variable a (suponiendo que sea
entera) vale 4 ó 5.

Operadores binarios: Sirven para operaciones de “bajo nivel”, es decir, permiten


operar con los datos en binario. Operadores:

o Desplazamiento aritmético en complemento a 2 a la derecha: C >> n. Donde C en


la variable/constante a desplazar y n es el número de desplazamientos. Recordar
que un desplazamiento a la derecha de una cantidad entera supone su división por
2.

o Desplazamiento aritmético en complemento a 2 a la izquierda: C << n. Donde C


en la variable/constante a desplazar y n es el número de desplazamientos.
Recordar que un desplazamiento a la izquierda de una cantidad entera supone su
multiplicación por 2.

o Operación lógico AND: &. Recordar que la operación AND sobre dos
combinaciones binarias implica la realización de la operación AND sobre los
valores de cada posición.

o Operación lógico OR: |. Recordar que la operación OR sobre dos combinaciones


binarias implica la realización de la operación AND sobre los valores de cada
posición.

o Operación lógico NOT: ~. En este caso es un operador unario, y su resultado es el


complemento del operando. Ejemplo: ~variable.

Introducción a la programación en C by CTG © Página 13


Ejercicio/Ejemplo 8. El siguiente programa sirve para recordar de Fundamentos de
Informática II algunas de las utilidades de estos operadores:

/* Ejemplo de uso de operadores binarios*/

#include <stdio.h>

int main( void ) {

int a=-50, b;

/* Emplo de multiplicación/división rápida por potencias de 2 */

printf (“Resultado de desplazar a dos posiciones a la derecha: %d\n”, a>>2);

printf (“Resultado de desplazar a cuatro posiciones a la izquierda: %d\n”, a<<4);

/* Comprobación de si el dígito cuarto (empezando por la derecha) de la variable a


vale 0 ó 1 */

b=8;

if ( (b & a) == 0) {

printf(“el dígito vale 0\n”);

else { printf(“el dígito vale 1\n”);

return 0;

Otros operadores

o Operador coma (“,”): Sirve para agrupar expresiones: expr1,expr2,expr3. El


resultado es que se evalúan todas las expresiones y se devuelve como valor el
resultado de la expresión más a la derecha. Su uso más típico es en sentencias for
(sentencia que se verá más adelante), por ejemplo:

for (a=0,b=3; a<=50; a++,b++)

o Operador “?” o condicional: Su sintaxis es: expresión ? expresión1 :


expresión2. Si expresión es verdad se evalúa y devuelve expresión1, en caso
contrario se evalúa y devuelve expresión2. Ejemplo:

max = a>b ? a : b ;

Introducción a la programación en C by CTG © Página 14


Conversión de tipos (casting).

Cuando en una expresión aritmética se mezclan variables de distintos tipos, el compilador de


C realiza una conversión implícita al tipo de mayor longitud/rango, siguiendo la siguiente
jerarquía:

int < unsigned < long < unsigned long < float < double.

Por ejemplo si multiplicamos un entero por un real, el compilador realizará una conversión de
tipo entero a real, y la multiplicación se realizará entre dos cantidades reales. Lo mismo
ocurre si el resultado de una expresión es de un tipo, y la asignación ser realiza a una variable
de tipo distinto. Sin embargo, el resultado de estas conversiones implícitas no siempre es el
deseado, por eso es aconsejable que sea el propio programador el que realice, en casos como
los comentados, la conversión de manera explícita. Para esto existe el operador de cambio de
tipo. La sintaxis es la siguiente:

(nuevo_tipo) expresión/variable.

Ejercicio/Ejemplo 9. El siguiente programa sirve para ver el problema que puede surgir si se
deja la conversión de tipo al compilador:

/* Ejemplo de conversión implícita/explícita de tipo*/

#include <stdio.h>

int main( void ) {

int a=5, b=3;

float f1, f2;

f1 = a / b; /* el resultado del cociente es un entero, que es convertido a float en la


asignación a f1 */

f2 = (float) a / (float) b; /* el cociente se realiza entre números reales */

printf(“Resultado conversion implicita: %f Resultado conversion explicita:


%f\n”,f1, f2);

return 0;

Introducción a la programación en C by CTG © Página 15


Precedencia.

La precedencia o prioridad aplicada a los operadores a la hora de calcular una expresión es la


siguiente, ordenada la lista de mayor a menor prioridad (algunos operadores serán vistos más
adelante):

() [] -> .
! ~ ++ -- - (type) * & sizeof (todos son unarios)
*/%
+-
<< >>
< <= > >=
== !=
&
^
|
&&
||
?:
= += -= *= /= %= ^= &= |=
,

Introducción a la programación en C by CTG © Página 16


Introducción a la programación en C by CTG © Página 17
Capítulo 4 - Operaciones Entrada-Salida
En esta sección vamos a ver las funciones más importantes en el lenguaje C para la entrada y
salida de datos de y hacia la entrada y la salida estándar, respectivamente, que, recordemos,
que son el teclado y el monitor.

Operaciones sencillas de E/S.

Las funciones que veremos en esta sección permiten la entrada/salida de caracteres. Ambas
están definidas en el fichero de cabecera stdio.h, por lo que este fichero deberá ser incluido en
el programa.

· Función de entrada de carácter: getchar (). Obtiene un carácter de la entrada estándar,


siendo este carácter el valor devuelto. El carácter tecleado no es “capturado” por esta función
hasta que no se pulsa retorno de carro. Es importante tener en cuenta si se utilizan varias
funciones getchar() seguidas que el retorno de carro es tratado como un carácter más (lo
veremos en el ejemplo).

· Función de salida de carácter: putchar (char c). Muestra el carácter c por la salida
estándar.

Ejercicio/Ejemplo. El siguiente programa sirve para ver el problema que puede surgir si se
deja la conversión de tipo al compilador:

/* Ejemplo de E/S de caracteres*/


#include <stdio.h>
int main( void )
{
char a, b;

printf(“Teclea un caracter y después pulsa retorno de carro (return)\n”);


a = getchar();
getchar(); /* para “capturar el retorno de carro */
printf(“Teclea otro caracter y después pulsa retorno de carro (return)\n”);
b = getchar();

printf (“los caracteres introducidos son: %c y %c\n”, a, b);


return 0;
}

Responde a las siguientes preguntas:

1. Elimina del programa anterior la línea “getchar(); /* para “capturar el retorno de


carro */” (puedes hacerlo comentando la línea sin borrarla) y vuelve a compilarlo y
ejecutarlo. ¿Por qué no nos deja introducir el segundo carácter? ¿Cuál es el valor de
la variable b?

Introducción a la programación en C by CTG © Página 18


Ejemplo. El siguiente programa muestra un ejemplo de captura de varios caracteres
tecleados de manera seguida:

/* Ejemplo 2 de E/S de caracteres*/


#include <stdio.h>
int main( void )
{
char char1, char2, char3, char4;

printf(“Teclea una palabra de cuatro caracteres y después pulsa retorno de carro


(return)\n”);
char1 = getchar();
char2 = getchar();
char3 = getchar();
char4 = getchar();

printf (“La palabra introducida es: %c%c%c%c\n”, char1, char2, char3, char4);
return 0;
}

Operaciones de E/S con formato

Las funciones que veremos en esta sección permiten la entrada/salida de cualquier tipo de
variable. El tipo es especificado como argumento de la función. Ambas están definidas en el
fichero de cabecera stdio.h, por lo que este fichero deberá ser incluido en el programa.

Función int printf(const char *format). Imprime por la salida estándar una secuencia
de caracteres cuya estructura está definida en format. Se permite dar salida a cualquier
tipo de dato predefinido. format tiene la estructura de una cadena de caracteres normal
pero admite además caracteres de conversión (%letra) para indicar qué tipo de dato se
ha de imprimir. La estructura de una cadena de formato es:

%[flags][.width][.prec][F|N|h|l]type

Donde type es un carácter de conversión. El campo flag afecta al tipo de justificación; width
es la anchura utilizada para imprimir, .prec es la precisión (número de decimales) si el dato
a imprimir es un número en coma flotante. El último campo opcional afecta a la
interpretación del argumento (se deja al estudiante buscar el significado de este campo). Los
tipos de datos a imprimir (campo type) son los siguientes:

%c Caracter

%d Entero decimal

%ld Entero largo (long int)

%e Flotante se representa con exponente

%f Flotante se representa sin exponente

Introducción a la programación en C by CTG © Página 19


%lf double

%i entero decimal, hexadecimal u octal

%g Menor entre %e y %f

%o Entero octal, sin el cero inicial

%u Entero decimal sin signo

%x Entero representado en hexadecimal sin 0x

%s Strings (cadenas de caracteres).

int scanf(const char *format, ...). Permite la entrada de datos de forma estructurada
por teclado. En la cadena de formato se pueden poner todos los caracteres de
conversión (%) de la tabla anterior. En el ejemplo se explicará más detenidamente la
sintaxis de esta función.

Ejemplo. El siguiente programa muestra un ejemplo de uso de scanf y printf:

#include <stdio.h>
int main( void )
{
int i;
double f;

scanf (“%d %lf”, &i, &f);


printf (“i=%d f=%lf\n”,i, f);
return 0;
}

Explicación de la sintaxis de scanf


Los argumentos a esta función se pueden dividir en dos partes:
A la izquierda de la coma y entre comillas la cadena de caracteres donde se
indica el tipo de datos a leer de teclado, especificados de la misma manera que
la usada en printf. El número y tipos a especificar puede ser cualquiera.
A la derecha de la coma van las variables donde guardar los valores leídos,
más concretamente, hay que especificar la dirección de memoria central
donde se almacenan estas variables. Esta es una consideración muy importante
a tener en cuenta a la hora de usar scanf. Para obtener la dirección de memoria
donde se almacena una variable se usa el operador unario &, operador que se
estudiara en profundidad más adelante cuando se hable de punteros. Este
operador devuelve la dirección de memoria donde se almacena la variable
especificada a su derecha. En el caso del ejemplo, &i devuelve la dirección de
memoria donde se almacena la variable i. Igual para &f.
¡Un espacio en blanco significa terminar la lectura de caracteres!

Introducción a la programación en C by CTG © Página 20


Introducción a la programación en C by CTG © Página 21
Capítulo 5 - Control de Flujo
Sentencia if

Permite la ejecución condicional de un conjunto de sentencias. Su sintaxis es:

if (condición)

Sentencia simple/bloque_1

else

Sentencia simple/bloque_2

Si condición es verdad (recordemos: valor devuelto por la expresión lógica distinto de 0), se
ejecuta Sentencia simple/ bloque_1, si es falso (recordemos: valor devuelto por la expresión
lógica 0), se ejecuta Sentencia simple/ bloque_2. La sentencia else es opcional.

Ejemplo. Ejemplo de sentencia if

#include <stdio.h>

int main( void ) {

int valor1, valor2;

printf (“Introduzca dos valores enteros distintos entre sí\n”);

scanf (“%d %d”, &valor1, &valor2);

if (valor 1 > valor 2)

printf (“El primer dato es mayor que el segundo\n”);

else

printf (“El primer dato es menor que el segundo\n”);

return 0;

Introducción a la programación en C by CTG © Página 22


Bucle while (o ó mas veces)

Permite la ejecución iterativa de un conjunto de sentencias mientras se cumpla una


determinada condición. La condición es cualquier expresión lógica. Su sintaxis es:

while (condición)

Sentencia simple/bloque

Bucle do – while (1 ó mas veces)

Similar al bucle while pero el conjunto de sentencias se ejecuta al menos una vez. Su sintaxis
es:

do

Sentencia simple/bloque

while (condición)

Bucle for (x veces prefijadas)

Permite la ejecución iterativa de un conjunto de sentencias. Su sintaxis es:

for (inic; perman; actualiz)

Sentencia simple/bloque

Donde:

· inic es una expresión que sólo se ejecuta una vez al principio del bucle. El
bucle for suele utilizarse en combinación con un contador. Un contador es una

Introducción a la programación en C by CTG © Página 23


variable que lleva la cuenta de las veces que se han ejecutado las instrucciones sobre las
que actúa el comando for. Por tanto inic suele contener una expresión que nos permite
inicializar ese contador.

· perman es la expresión que nos indica cuando debe finalizar el


bucle, por tanto se tratará de una expresión condicional. Su
interpretación sería algo como: repite la sentencia (o sentencias) mientras se
cumpla perman. Esta expresión se evaluará en cada ciclo del bucle para determinar si
se debe realizar una nueva iteración. Es MUY IMPORTANTE tener en cuenta que
perman se evalúa al principio del bucle, y no al final. Por tanto es posible no ejecutar
el bucle ninguna vez.

· actualiz exp3 es una expresión que se ejecuta al final de cada iteración. Puesto que
como ya se ha indicado el bucle for se utiliza junto a un contador, actualiz,
en general, contiene una instrucción que actualiza la variable contador.

Ejemplo. El siguiente programa cuenta hasta 10, empezando por 0:

#include <stdio.h>

int main( void ) {

int cont;

for (cont=0; cont <=10; cont++)

printf(“Valor del contador: %d\n”, cont);

Ejemplo. Ejemplo anterior realizado con un bucle while:

#include <stdio.h>

int main( void ) {

int cont;

cont = 0;

while (cont <=10) {

printf(“Valor del contador: %d\n”, cont);

cont ++; }

return 0; }

Introducción a la programación en C by CTG © Página 24


Saltos incondicionales

Las siguientes sentencias permiten la realización de saltos incondicionales en un


programa C: break y continue.
La ejecución del primero de ellos, continue, implica que se reevalúe la
condición de salida del bucle, es decir, después de ejecutar continue la
siguiente instrucción que se ejecutará será el for o el while.

Ejemplo. Ejemplo de uso de la función continue. Este programa lee desde teclado 10
números cuyo valor tiene que estar entre 1 y 100, si no es así se le vuelve a pedir al usuario
que introduzca un valor, descartando el introducido no válido.

#include <stdio.h>
int main( void )
{
int n;
int cont=0;

do
{
printf ("\nIntroduce un número entre 1 y 100:");
scanf ("%d",&n);
if ((n<1)||(numero>100))
continue;
cont++;
} while (cont<50);

return 0;
}

La función de break hace que la ejecución del programa continúa en la línea


siguiente al bucle, es decir, tras su ejecución se abandona el bucle y se continúa la
ejecución en la línea inmediatamente siguiente al éste.

Ejemplo. Ejemplo de uso de la función break. Este programa lee desde teclado valores
numéricos y los muestra por pantalla, salvo que el número introducido sea el 2.

#include <stdio.h>
int main( void )
{
int n;

do
{
printf ("\nIntroduce un número entero:");
scanf ("%d",&n);

Introducción a la programación en C by CTG © Página 25


if (n == 2)
break;
printf (“El número introducido es: %d\n”, n);
} while (1);

return 0;
}

Condicional múltiple switch case

Es una sentencia de selección múltiple, que compara sucesivamente el valor de una expresión
con una lista de constantes enteras o de caracteres. Cuando se encuentra una correspondencia,
se ejecutan las sentencias asociadas con la constante. La sintaxis de esta sentencia es la
siguiente:

switch (expresión)
{
case constante1:
Sentencia/s
break;
···
case costanten:
Sentencia/s
break;
···
[default:
Sentencia/s]
}

Si el valor de expresión coincide con cualquiera de las constantes a la derecha de una


sentencia case, entonces se ejecutan las sentencias que estén a continuación de ese “case”
hasta llegar a la sentencia break. Un caso especial es la sentencia default. Ésta es opcional, si
aparece la sentencias que están a continuación de ella se ejecutarán si el resultado de
expresión no coincide con ninguna de las constantes anteriores. La comprobación de valores
se realiza de arriba abajo, es decir, el resultado de expresión primero se comprueba con
constante1, después con constante2 y así sucesivamente hasta llegar al final.

No puede haber dos constantes en el mismo switch que tengan los mismos valores. Por
supuesto que una sentencia switch contenida en otra sentencia switch pude tener constantes
que sean iguales.

Si se utilizan constantes de tipo carácter en la sentencia switch, se convierten


automáticamente a sus valores enteros siguiendo el código ASCII.

Introducción a la programación en C by CTG © Página 26


Ejemplo. Ejemplo de uso de la función switch para crear un menú.
#include <stdio.h>
int main( void )
{
int n;

do {
printf (“introduzca un valor entero entre 1 y 3:”);
scanf ("%d",&n);
switch (n)
{
case 1:
printf(“Ha escogido la opción 1\n”);
break;
case 2:
printf(“Ha escogido la opción 2\n”);
break;
case 3:
printf(“Ha escogido la opción 3\n”);
exit (3);
break;
default:
printf(“Opción no válida\n”);
}
} while (1)

return 0;
}

Responde a las siguientes preguntas:

1. ¿Qué hace la función exit? ¿Qué significa su argumento (valor a su derecha entre
paréntesis? ¿Se podría eliminar la sentencia break que sigue a “exit (3)”?
2. ¿Qué pasa si se quita la sentencia break asociada a “case 1:”?

Introducción a la programación en C by CTG © Página 27


Capítulo 6 – Funciones
Definición de Función

En el lenguaje C una función se define de la siguiente manera:

tipo nombre (tipo param1, tipo param2, ...)


{
/*cuerpo de la función*/
...
[return expresión;]
}

Donde:

tipo es tipo del dato que devuelve. Si no devuelve nada se declarará de tipo “void”.
Sólo se pueden devolver tipos asignables: enteros, reales, punteros, estructuras y void.
Si no se especifica el tipo, se supone que es entero.
nombre es el nombre con que la función es identificada (mayúscula primera letra).
Parámetros de la función. Es una lista de nombres de variables separados por comas
con sus tipos asociados, que recibirán valor en el momento de llamar a la función. Los
paréntesis siempre deben aparecer, aunque la función no tenga argumentos. Estas
variables sólo estarán definidas dentro de la función, es decir, son locales a la función,
salvo que hayan sido definidas como globales (ver al final de este apartado), creándose
al comienzo de la ejecución de la función y destruyéndose al finalizar ésta. Esto es
aplicable a cualquier otra variable declarada dentro de la función, excepto si se
declaran como static (esta sentencia debe aparecer antes del tipo de la variable). En
este caso el compilador no las destruye y guarda su valor para la próxima llamada,
aunque la variable sigue teniendo limitado el ámbito al interior de la función.
La sentencia return es opcional, sólo debe aparecer si la función retorna un valor.
Puede aparecer en cualquier punto del cuerpo de una función.

En C no se permite la definición anidada de funciones, es decir, definir una función dentro de


otra. Si se permite la recursividad.

Una variable es global cuando se declara fuera de una función, y se puede utilizar en todo el
programa desde el punto de la declaración. Todas las variables usadas en los ejemplos
mostrados en estos apuntes son locales, es decir, declaradas dentro de un bloque de
sentencias, y su ámbito de aplicación es dentro del bloque en que son declaradas. No es
aconsejable el uso de variables globales.

Introducción a la programación en C by CTG © Página 28


Declaración de una Función

Toda función debe ser declarada antes de ser definida. La forma de declarar una función es la
siguiente:

tipo nombre (tipo param1, tipo param2, ...);

La declaración de una función de esta forma se llama prototipo.

Las funciones son siempre globales, de manera que su declaración debe ser realizada al
principio del programa; si la primera función definida en éste es la main(), antes de esta
definición. La declaración puede evitarse si la función es definida antes de ser invocada, sin
embargo, es aconsejable declarar al comienzo del programa los prototipos de las funciones
que vamos a definir, incluyendo comentarios sobre su finalidad.

Llamadas a funciones

Las funciones pueden ser llamadas desde cualquier punto de un programa.

La llamada de una función se produce mediante el uso de su nombre en una sentencia,


pasando una lista de argumentos que deben coincidir en número y tipo con los especificados
en la declaración, en caso contrario se produciría una conversión de tipos cuyos resultados
pueden no ser los esperados. En general, los parámetros se pueden pasar por valor y por
referencia, usando en este caso punteros (dirección de memoria donde se almacena la
variable), que serán vistos más adelante. En este último caso será posible modificar el valor
de la variable usada en la llamada.

Introducción a la programación en C by CTG © Página 29


Ejemplo. Ejemplo de uso de funciones donde es obligatorio la declaración

#include <stdio.h>

/* Prototipo de la función */

int Maximo(int var1, int var2); /*Devuelve el mayor de var1 y var2 */

/* Función principal */

int main (void) {

int i = 3, j = 5;

printf (“Maximo (%d, %d) = %d\n”, i, j, Maximo (i,j));

return (Maximo (i, j));

/* Definición de la función */

int Maximo (int a, int b)

/* Esto también se podría hacer:

int Maximo (a,b)

int a,b; */

if (a < b)

return (b);

else

return (a);

Introducción a la programación en C by CTG © Página 30


Ejemplo. Ejemplo anterior pero definiendo la función de manera que no sea necesaria su
declaración.

#include <stdio.h>

/* Definición de la función */

int Maximo (int a, int b)

/* Esto también se podría hacer:

int Maximo (a,b)

int a,b; */ {

if (a < b)

return (b);

else

return (a);

/* Función principal */

int main (void) {

int i = 3, j = 5;

printf (“Maximo (%d, %d) = %d\n”, i, j, Maximo (i,j));

return (Maximo (i, j));

Introducción a la programación en C by CTG © Página 31


Ejemplo. Ejemplo de función tipo void y sin parámetros

#include <stdio.h>

#include <stdlib.h>

/* Declaración de la función */

void suerte ();

/* Función principal */

int main (void) {

int i;

for (i=0; i<10; i++)

suerte();

return 0;

/* Definición de la función */

void suerte () {

int i;

i= 1 + (int) (10.0 * rand() / (RAND_MAX + 1.0));

/* La constante RAND_MAX esta definida en el fichero stdlib.h */

printf (“Tu número de la suerte es %d\n", i);

Introducción a la programación en C by CTG © Página 32


Ejercicio/Ejemplo. Ejemplo error típico en el uso de funciones

#include <stdio.h>

/* Prototipo */

void Cambia (int var1, int var2);

int main () {

int i=1, j=2;

Cambia (i, j);

printf (“i=%d, j=%d”, i, j);

return 0;

/* Definición */

void cambia (int i, int j) {

int tmp;

tmp=i;

i = j;

j = tmp;

Responde a la siguiente pregunta:

1. ¿Por qué no se han intercambiado los valores de las variables i y j tras la ejecución
de la función Cambia() en main()?

Introducción a la programación en C by CTG © Página 33


Capítulo 7 – Vectores
Conceptos generales

Un vector es una asignación contigua de memoria donde se almacena una colección de datos
todos del mismo tipo.

La forma de declarar vectores en C es la siguiente:


cualificador tipo nombre[expresión][expresión]…;
Donde expresión es un valor constante.
Se puede realizar la asignación en el momento de la declaración de la siguiente
manera:
cualificador tipo nombre[expresión][expresión]... ={valor, ...};
Donde valor también tiene que ser una constante. Si en la declaración se realiza la
asignación se puede eliminar la expresión.
No se comprueba la longitud del vector a la hora de acceder a él. Es
responsabilidad del programador controlar los accesos fuera de rango, ya que no
siempre genera un error en la ejecución. Este es un motivo de error muy típico al
realizar programas C.
La forma de hacer referencia o acceder a un elemento del vector es:
nombre[expresión][expresión]...
Donde expresión es una constante que se denomina índice, e indica el elemento del
vector al que queremos acceder. Este índice es un entero positivo que comienza por 0,
y su valor máximo será n-1 con n el número de elementos de cada “dimensión” del
vector.

Ejemplo. De uso de vectores.

#include <stdio.h>

int main () {

int x[5], i;

x[0] = 1;

for (i=1; i<5; i++)

printf (“Dame el valor %d del vector:”, i);

Introducción a la programación en C by CTG © Página 34


scanf (“%d”, &x[i]);

printf(“Valores del vector: %d %d %d %d %d %d\n”, x[0],x[1],x[2],x[3],x[4]);

return 0;

En la siguiente tabla se muestra la relación entre elemento del vector y posición de memoria
que ocupa.

Un vector de datos enteros: int x[5]; (Cada int ocupa 4 bytes)

Dirección de memoria Contenido

m x[0]

m+4 x[1]

m+8 x[2]

m+12 x[3]

m+16 x[4]

Ejemplo. De error en el uso de vectores, al acceder fuera de rango.

#include <stdio.h>

main () {

int a[3];

a[10000] = 41;

printf (“%d\n”, a[10000]);

Introducción a la programación en C by CTG © Página 35


Cadenas de caracteres

Las cadenas de caracteres son vectores de datos tipo char, con la característica adicional de
que acaban en el carácter nulo „\0‟. Si se da valor al número de elementos de la cadena en su
declaración habrá que tener en cuenta este carácter adicional. Por ejemplo para guardar una
cadena de 10 caracteres, se debe reservar espacio para 11.

Ejemplo. De uso de cadenas de caracteres.

#include <stdio.h>

int main () {
char cad1[]=”hola”, cad2[20];

printf (“Introduce una cadena de menos de 19 caracteres:”);


scanf (“%s”, &cad2[0]);
//scanf con %s introduce automáticamente el carácter „\0‟ tras el último carácter de
la cadena tecleada

printf(“cad1: %s cad2: %s\n”, cad1, cad2);


// printf con %s solo muestra los caracteres del vector desde el índice 0, hasta que
encuentre el carácter „\0‟

return 0;
}

No se pueden hacer asignaciones del tipo:

cad2="hola mundo";

Hay muchas funciones de biblioteca estándar para tratarlas. Por ejemplo, para asignar valor a
una cadena tras su declaración se puede usar la función strcpy (para su uso hay que incluir en
el programa el fichero de cabecera string.h):

strcpy (cad2, "hola mundo");

También la función strcmp que compara elementos sucesivos de dos cadenas, hasta que
encuentra dos elementos distintos, o hasta que se acaban las cadenas. Devuelve: valor
negativo si primera menor que la segunda (valor lexicográfico), un cero si son iguales o
un valor positivo si la primera es mayor que la segunda.

strcmp (info.cadena,”hola”);

Ya hemos visto funciones que permiten la captura desde entrada estándar (sscanf) o muestran
en la salida estándar (printf) cadenas de caracteres. Aquí vamos a añadir dos que pueden
resultar útiles:

Introducción a la programación en C by CTG © Página 36


char *gets(char *s). Lee una línea de la entrada estándar (stdin) y la guarda en la
dirección indicada en el puntero a cadena s, hasta que se encuentre bien un caracter
terminador nueva-línea, bien EOF (End Of File, constante que indica el final de
fichero definida en stdio.h). Añade automáticamente el carácter „\0‟ al final de la
cadena leída. Devuelve s en caso de éxito o NULL en caso de error o cuando se llegue
al final del fichero mientras que no se haya leído ningún caracter.
int puts(const char *s) Escribe en la salida estándar la cadena apuntada por s y un
retorno de carro. Devuelve un número no negativo si acaban bien, o EOF en caso de
error.

Vectores y punteros

El nombre del vector es un puntero al primer elemento del vector. Esta propiedad ya
ha sido usada en varios de los ejemplos anteriores. Ya que determinadas
funciones necesitan que se especifique el vector mediante el puntero a su primer
elemento. Un ejemplo de esto es printf cuando el dato a imprimir es una cadena (%s).
Ejemplo. Vamos a modificar el ejemplo del primer apartado de esta sección
(características generales) usando en scanf la relación entre punteros y vectores
indicada.

#include <stdio.h>

int main () {

int x[5], i;

x[0] = 1;

for (i=1; i<5; i++) {

printf (“Dame el valor %d del vector:”, i);

scanf (“%d”, x+i); //Recordar la aritmética de punteros

printf(“Valores del vector: %d %d %d %d %d %d\n”, x[0],x[1],x[2],x[3],x[4]);

return 0;

Introducción a la programación en C by CTG © Página 37


Cuando se trabaja con cadenas de caracteres la expresión “cadena” (aquí las comillas forman
parte de la expresión) devuelve un puntero a la cadena. Así, por ejemplo, printf(“%s”, hola)
dará error, ya que printf espera un puntero cuando se usa %s; la versión correcta será:
printf(“%s”, “hola”)

Vectores como argumentos a funciones

Si se quiere pasar todo un vector a una función habrá que hacerlo por referencia, es
decir, se pasa el puntero al inicio del vector, es decir, el nombre del vector, como acabamos de
ver en el aparatdo anterior. En este caso hay que tener en cuenta que la función desconoce el
tamaño del vector, por lo que será el programador el responsable de que no se acceda más allá
del límite, por ejemplo, pasando también como argumento a la función el tamaño del vector.

Argumentos a un programa c

Recordemos de UNIX, que llamamos argumentos de un comando o programa


ejecutable a cualquier cadena de caracteres que se pone a la derecha del nombre del comando.
Por ejemplo, en el comando cp fich1 fich2 los argumentos serán fich1 y fich2. El lenguaje C
permite que se puedan introducir argumentos en línea de comandos, es decir, en el momento
de la ejecución del programa. La declaración de estos argumentos se hace en la función
main(), de la siguiente manera:

main( int argc, char *argv[])

El primer argumento se suele llamar argc, pero se le puede asignar cualquier otro
nombre, y es un entero cuyo valor es el número de argumentos pasados al programa
incluyendo como argumento el nombre del propio programa. Al segundo argumento se suele
llamar argv, pero, como antes, se le puede asignar cualquier otro nombre. Este segundo
argumento es un vector de punteros a carácter. Dada la dificultad conceptual de este tipo de
vectores vamos a abandonar momentáneamente la explicación de los argumentos al programa
C, para centrarnos en los vectores de puntero.

Un puntero es un dato, y como para cualquier otro tipo de dato se permite declarar un
vector de punteros. La forma de hacerlo será:

cualificador tipo *nombre[expresión][expresión]…;

Cada elemento del vector será, en este caso, un puntero a un dato de tipo tipo, es decir,
cada elemento del vector contiene la dirección de memoria de un dato de tipo tipo.

Introducción a la programación en C by CTG © Página 38


Ejemplo. Cálculo de la distancia, norma 1, entre dos vectores de números reales de tamaño
cualquiera.

#include <stdio.h>
#include <math.h>

int main ()
{
double *x[2], dist;
int tam_vec, i;

printf (“Dame el tamaño del vector:”);


scanf (“%d”, &tam_vec);

// Reservamos memoria
if ( (x[0]=(double *)malloc(tam_vec * sizeof(double))) == NULL)
{
printf(“ERROR al reservar memoria para el primer vector\n”);
return 1;
}
if ( (x[1]=(double *)malloc(tam_vec * sizeof(double))) == NULL)
{
printf(“ERROR al reservar memoria para el segundo vector\n”);
return 1;
}

// Pedimos datos
for (i=0; i<tam_vec; i++)
{
printf (“Dame el valor %i de primer vector:”,i);
scanf (“%lf”, x[0]+i); //x[0] es un puntero al primer elemento del vector 1
}
for (i=0; i<tam_vec; i++)
{
printf (“Dame el valor %i de segundo vector:”,i);
scanf (“%lf”, x[1]+i); //x[1] es un puntero al primer elemento del vector 2
}

// Calculamos distancia
dist = 0.0;
for (i=0; i< tam_vec; i++)
dist += fabs((x[0][i] – x[1][i]));

printf(“Distancia: %f \n”, dist);

return 0;

Introducción a la programación en C by CTG © Página 39


Volvamos a los argumentos a un programa C en línea de comandos, centrando nuestra
atención en el segundo parámetro de main, el que hemos llamado argv (recordemos que le
podemos dar cualquier otro nombre. Este parámetro es un vector de punteros a char, más
concretamente, es un vector de punteros a cada una de los argumentos introducidos en la
ejecución del programa, considerando cada uno de estos (incluido el propio nombre del
programa) una cadena de caracteres.

Vemos esto mediante un ejemplo. Supongamos que ejecutamos el programa a.out con
los siguientes argumentos: a.out uno 2 3.89 “cua tro”. Pues bien, argv[0] será un puntero
(dirección de memoria del primer carácter) al nombre del programa (a.out), argv[1] será un
puntero a la cadena de caracteres introducida como primer argumento (uno), argv[2] será un
puntero a la cadena de caracteres introducida como segundo argumento (2), argv[3] será un
puntero a la cadena de caracteres introducida como tercer argumento (3.89) y argv[4] será un
puntero a la cadena de caracteres introducida como cuarto argumento (cua tro).

Ejemplo. Programa que muestra los argumentos pasados en línea de comandos

#include<stdio.h>

int main (int argc, char *argv[])


{
int i;

printf (“Número de argumentos pasados: %d\n”,argc);


for (i= 0; i < argc; i++)
{
printf (“El argumento %d vale: %s\n”,i, argv[i]);
}

return 0;
}

Es importante tener en cuenta que todos los argumentos son pasados a main() como cadenas
de caracteres. Es el programador el que tendrá que, usando las funciones adecuadas, pasar los
datos de tipo cadena al tipo que desee. Una de estas funciones es, por ejemplo, sscanf().

Ejercicio/Ejemplo. De cambio de tipo de un argumento a programa usando sscanf().

#include <stdio.h>

//Cambiamos de nombre a los parámetros a main


int main (int num_arg, char *arg[]) {
int arg2;

if ( sscanf(arg[2], "%d", &arg2) != 1) {

Introducción a la programación en C by CTG © Página 40


fprintf(stderr,"*** ERROR en la lectura del segundo argumento. Valor leido:
%s\n", arg[2]);
exit(1);
}
...
}

Responde a las siguientes preguntas:

1. ¿Qué valor devuelve la función sscanf()?


2. ¿Cómo funciona fprintf? ¿A qué salida se está mandando el mensaje de error, en caso
de que se genere?

Para acabar, en la figura 1 se puede ver un ejemplo gráfico simplificado de la ubicación en


memoria de los argumentos pasados a un programa: argv será la dirección de memoria del
primer elemento del vector de punteros argv[], es decir, la dirección de memoria del elemento
argv[0]; a su vez argv[0] apunta al primer elemento (carácter) de la cadena compuesta por el
nombre del programa, es decir, argv[0] es a su vez un puntero a una cadena de caracteres. De
igual manera se razona para argv[1] y argv[2] con respecto a las cadenas introducidas como
primer y segundo argumento. Una conclusión de lo expuesto es que argv es un puntero a un
puntero, y como tal puede ser tratado.

Fig 1.- Esquema simplificado del almacenamiento en memoria de los argumentos


pasados a un programa en línea de comandos. Se supone que el programa ejecutado es:
a.out uno 1. Esta figura muestra también la relación entre punteros y vectores con
respecto a los argumentos a un programa.

Introducción a la programación en C by CTG © Página 41


Capítulo 8 - Punteros
Conceptos generales

Los punteros son una de las herramientas más útiles, pero a la vez, complejas y
peligrosas de C. Un puntero es una variable entera sin signo que almacena la
dirección de memoria de otra variable o dato. Para ser más exactos, almacena la
dirección del primer byte que la variable ocupa en memoria. Esto es importante
tenerlo en cuenta, a la hora de entender la aritmética de punteros.
La declaración de un puntero se realiza de la siguiente manera:
[cualificador] tipo *nombre;
Donde cualificador recordemos que puede ser extern o static y es opcional, sólo será
necesario si la variable queremos que se comporte de la forma indicada para esos
cualificadores. Tipo es cualquiera de los vistos ahora y alguno que añadiremos más
adelante, e indica el tipo de la variable almacenada en la dirección de memoria
apuntada por el puntero. El carácter “*” es el que indica que la variable que estamos
definiendo es un puntero. Y, por último, nombre es el identificador de la variable
puntero.
Es importante tener en cuenta que el hecho de declarar un puntero implica que se
reservará espacio en memoria para guardar un puntero, no para el tipo de datos al que
apunta.

Ejemplo.
La declaración:
int *i;
implica que se está declarando un puntero que vamos a llamar ”i” que apunta a un
entero, es decir, la variable i contendrá una dirección de memoria central donde se
almacenará un entero;, más exactamente, contiene la dirección de memoria del primer
byte del entero almacenado. Con esta declaración se reserva espacio para almacenar “i”,
es decir, el puntero, no para almacenar el entero. Esto último se tendrá que hacer con la
declaración correspondiente, como veremos en siguientes ejemplos.
De igual manera se declararían punteros a tipos float, long, char, etc.

Operaciones con punteros

Existen sólo dos operaciones que se puedan usar con punteros: la suma y la resta. Más
concretamente las operaciones permitidas son el incremento/decremento, la suma/resta
de valores enteros y la suma/resta de punteros.
Las operaciones más útiles son el incremento/decremento, como veremos al hablar de
vectores. Cada vez que se incrementa un puntero, apunta a la posición de memoria del
siguiente elemento de su tipo base. Cada vez que se decrementa, apunta a la posición
del elemento anterior. Los punteros aumentan o decrecen la longitud del tipo de datos
a los que apuntan.

Introducción a la programación en C by CTG © Página 42


Ejemplo. El puntero punt apunta a un float (4 bytes). Supongamos que su valor es dir.
Entonces, después de la expresión punt++; punt contiene el valor dir+4, no el dir+1.
El siguiente programa permite ver esto:
float *punt, var;
punt = &var;
printf (“Valor de punt antes de incrementar su valor: %x. Valor después: %x\n”,
punt, punt++);
Comentario: dado que un puntero es una dirección de memoria, su valor se visualiza
de manera más compacta es hexadecimal.

El valor NULL

Un valor especial que se le puede asignar a un puntero es el valor NULL (constante


definida con valor 0 en el fichero de cabecera stddef.h, fichero incluido en el stdio.h).
Un puntero al que se le asigna este valor significa que no apunte a ninguna posición de
memoria.

Operadores

Existen dos operadores especiales relacionados con punteros:

El operador “&” (dirección de). Devuelve la dirección de memoria donde está


almacenada una determinada variable. La sintaxis es: &nombre_variable. Una de las
formas de asignar valor a un puntero (iremos viendo más) es mediante el uso de este
operador, como se verá en el ejemplo.
El operador“*”. Asociado a un puntero devuelve el valor de la dirección de memoria a
la que apunta el puntero. A este proceso se le llama indirección.

Ejemplo. De uso de los operadores “&” y “*” (a partir de aquí en numerosos ejemplos sólo
mostraremos la parte de código que nos interesa, se deja para el alumno añadir las líneas
necesarias para crear un programa que pueda ser compilado).
int *i, j;
j=5;
i=&j; // Asignamos a j la dirección de i
printf (“dirección de j (en hexadecimal)=%x\n”,i);
// Acceso a valor mediante dirección
printf (“valor de j=%d\n”,*i);
// Acceso a valor mediante variable, como lo hemos venido haciendo hasta ahora
printf (“valor de j=%d\n”, j);

Punteros como argumentos a funciones

Vimos al hablar de funciones que los parámetros se podías pasar por valor o por
referencia usando punteros. En este segundo caso, pasamos a la función la dirección
de la variable, por lo que si modificamos el contenido de esa dirección (no el
contenido del puntero, si no el contenido de la dirección apuntada por el puntero), esa

Introducción a la programación en C by CTG © Página 43


modificación será vista al acabar la ejecución de la función. Ésta es una manera de que
una función pueda modificar variables de la función que la llamó.
Ejemplo. Repetimos el último Ejemplo del apartado de funciones, pero ahora usando
punteros que nos van a permitir intercambiar el valor de dos variables.

#include <stdio.h>

/* Prototipo */

void cambia (int var1, int var2);

int main () {

int i=1, j=2;

printf (“Valores antes de funcion Cambia: i=%d, j=%d”, i, j);

Cambia (&i, &j);

printf (“Valores despues de funcion Cambia: i=%d, j=%d”, i, j);

return 0;

/* Definición */

void cambia (int *a, int *b) {

int tmp;

tmp = *a;

*a = *b;

*b = tmp;

Memoria Dinámica

Otra forma de dar valor a un puntero es asignarle memoria dinámica mediante una
función de asignación de memoria. Recordar que al declarar un puntero simplemente
se está reservando memoria para almacenar una dirección de memoria, no se le está
asignando una dirección correcta, esto habrá que hacerlo posteriormente, como ya
hemos visto en ejemplos anteriores, y veremos también en esta sección.

Introducción a la programación en C by CTG © Página 44


Las funciones de asignación de memoria reservan memoria de manera dinámica, es
decir, durante la ejecución del programa, devolviendo la dirección del primer byte de
la zona reservada. Las funciones más habituales son calloc y malloc, definidas en el
fichero alloc.h o bien en stdlib.h. Su sintaxis es la siguiente:
· malloc(size_t size). Donde el argumento es el número de bytes que se quieren
reservar.
· calloc(size_t n_items, size). Esta función reserva n_items bloques de size bytes cada
uno, es decir, n_bytes*size bytes de memoria.
Ejemplo. Ejemplo de uso de malloc que reservar memoria para almacenar n enteros,
donde n es un valor introducido por teclado. En el programa sólo mostramos la parte
de reserva de memoria. La asignación tendrá más sentido cuando usemos estructuras
de datos como vectores, arrays o estructuras, tipos de datos que veremos más
adelante.

#include <stdio.h>

int main () {

int *x, num_dat;

printf (“Cuantos valores quieres almacenar:”);

scanf (“%d”, &num_dat);

// Si malloc falla devuelve el valor NULL,

// antes de continuar comprobamos este posible error

if ( (x=(int*)malloc(num_dat * sizeof(int))) == NULL) {

printf(“ERROR al reservar memoria\n”);

return 1;

return 0;

La memoria no usada se puede liberar con la función free(puntero_a_memoria).


Para más información sobre estas funciones, se puede consultar el manual de UNIX
o bibliografía sobre el tema.

Introducción a la programación en C by CTG © Página 45


Capítulo 9 - Estructura de Datos
Conceptos generales

Es una colección de variables del mismo o distinto tipo que se referencia bajo un
único nombre, proporcionando un medio eficaz de mantener junta la información
relacionada.
La definición de una estructura se realiza de la siguiente manera:
struct nombre {
lista de variables
};
Donde struct es la palabra clave que indica al compilador que se va a definir una
estructura, nombre es el identificador de la estructura, y en lista de variables es donde
se declaran las variables que van a componer esa estructura.
Ejemplo. De definición de una estructura.

struct complejo {
double real;
double imag;
};

Definida la estructura se pueden declarar variables de este tipo. Hay varias maneras
de realizar esto.
1. Directamente en la definición:
struct nombre {
lista de variables
} nombre_variable1, nombre_variable2, … ;

2. Tras la definición, de la siguiente manera:


struct nombre {
lista de variables
};
struct nombre nombre_variable1, nombre_variable2, … ;

3. Usando la sentencia typedef, sentencia que permite definir un tipo de dato en


función de los existentes. Su sintaxis es la siguiente: typedef tipo nombre;. A partir de
esta sentencia podremos usar nombre para declarar variables de tipo tipo. Uso:
typedef struct {
lista de variables
} nombre;
nombre nombre_variable1, nombre_variable2, … ;

Introducción a la programación en C by CTG © Página 46


Tanto la asignación de valor, como el acceso al asignado a cada variable de la
estructura se hace de la siguiente manera: variable.miembro. Donde variable es el nombre
de la variable declarada como de algún tipo de estructura y miembro es cualquiera de las
variables declaradas en el cuerpo de la definición de la estructura (lista de variables).

Ejemplo. De definición de una estructura, declaración de variables de ese tipo y


acceso a sus miembros.

// Usando la forma 1 de declarar una variable de tipo estructura


struct complejo {
double real;
double imag;
} z;
z.real = 1.2;
z.imag = 3.4;
printf(“Parte real: %f. Parte imaginaria: %f\n”, z.real, z.imag);

// Usando la forma 2 de declarar una variable de tipo estructura


struct complejo {
double real;
double imag;
};
struct complejo z;
z.real = 1.2;
z.imag = 3.4;
printf(“Parte real: %f. Parte imaginaria: %f\n”, z.real, z.imag);

// Usando la forma 3 de declarar una variable de tipo estructura


Typedef struct {
double real;
double imag;
} complejo;
complejo z;
z.real = 1.2;
z.imag = 3.4;
printf(“Parte real: %f. Parte imaginaria: %f\n”, z.real, z.imag);

Introducción a la programación en C by CTG © Página 47


Punteros a estructuras

Como con cualquiera de los tipos de variables vistas se pueden declarar


punteros a estructuras. En este caso el acceso a los miembros de la estructura apuntada
se realiza de la siguiente manera: puntero_estructura->miembro.
Ejemplo. De uso de punteros a estructura

typedef struct complejo {

double real;

double imag;

} complejo;

complejo z, *t;

t=&z;

t->real = 1.0;

t->imag = 2.1;

Uniones

Las uniones son similares a las estructuras, con la diferencia de que en las
uniones se almacenan en los campos solapándose unos con otros en la misma
disposición; al contrario que en las estructuras, donde los campos se almacenan unos a
continuación de otros. En esencia, las uniones sirven para ahorrar espacio en memoria.
Para almacenar los miembros de una unión, se requiere una zona de memoria
igual a la que ocupa el miembro más largo de la unión. Todos los miembros son
almacenados en el mismo espacio de memoria y comienzan en la misma dirección. El
valor almacenado es sobrescrito cada vez que se asigna un valor al mismo miembro o
a un miembro diferente, aquí radica la diferencia con las estructuras.

La declaración de uniones es similar a la de las estructuras:

Después de definir un tipo union, se puede declarar una o más variables de ese tipo de
la siguiente forma: union tipo_union [variables];

Introducción a la programación en C by CTG © Página 48


Una declaración union define un tipo. La llave derecha que termina la lista de
miembros puede ser seguida por una lista de variables de la siguiente manera:

Dicho de una forma simple, una unión le permite manejar los mismos datos
con diferentes tipos, o utilizar el mismo dato con diferente nombre, a continuación le
presento un ejemplo:

En éste ejemplo tenemos dos elementos en la unión: la primera parte es el


entero llamado valor el cual es almacenado en algún lugar de la memoria de la
computadora como una variable de dos bytes. El segundo elemento está compuesto de
dos variables de tipo char llamadas primero y segundo. Estas dos variables son
almacenadas en la misma ubicación de almacenamiento que valor porque ésto es
precisamente lo que una unión hace, le permite almacenar diferentes tipos de datos en
la misma ubicación física. En éste caso Usted puede poner un valor de tipo entero en
valor y después recobrarlo en dos partes utilizando primero y segundo, ésta técnica es
utilizada a menudo para empaquetar bytes cuando, por ejemplo, combine bytes para
utilizarlos en los registros del microprocesador.

Introducción a la programación en C by CTG © Página 49


La unión no es utilizada frecuentemente y casi nunca por programadores
principiantes, en este momento no necesita profundizar en el empleo de la unión así
que no dedique mucho tiempo a su estudio, sin embargo no tome a la ligera el
concepto de la unión, podría utilizarlo a menudo.

Tipos enumerados

Una enumeración es una lista de constantes enteras con nombre. Su función es


similar a la sentencia #define (se verá más adelante) en el sentido que permite crear
constantes que serán accedidas mediante un nombre, pero para secuencias de valores.
Se comportará como un nuevo tipo de dato que solo podrá contener los valores
especificados en la enumeración.

Ejemplo: uso de enum

enum dias_semana {LUNES=1, MARTES=2, MIERCOLES=3, JUEVES=4,


VIERNES=5, SÁBADO=6, DOMINGO=7 };

dias_semana dia;
dia = LUNES;
dia = 1; /* Ambas asignaciones son equivalentes */

Introducción a la programación en C by CTG © Página 50


Introducción a la programación en C by CTG © Página 51
Capítulo 10 - El Preprocesador
El preprocesador de C

Se denomina preprocesador a un programa que actúa antes que el compilador. Es una


etapa previa a la compilación entre cuyas tareas están la inclusión de ficheros, expansión de
macros y proceso de directivas.

Las sentencias del preprocesador pueden aparecer en cualquier punto del programa
(aunque la mayoría suelen estar al principio) y se caracterizan porque empiezan por el
caracter “#”. Las más importantes son:

· #define. Define una macro o una constante. Su sintaxis es #define x y, y lo que hace el
preprocesador es sustituir en el resto del programa donde encuentre el texto x por y.

Ejemplo.

#define PI 3.1416 // Cada aparición de PI será sustituida por 3.1416

#define MES_ERR “ERROR en la ejecución del programa\n” // Donde pongamos


MES_ERR será sustituido por la cadena de la derecha

¡No hay que poner ; al final de #define!

Podemos usar parámetros en la sustitución de macros.

Ejemplo.

#define max(A,B) ( (A)>(B) ? (A) : (B) )

y usarla como

x=max(p+q,r+s);

que sería sustituida por

x=((p+q)>(r+s) ? (p+q) : (r+s));

· #undef. Borra una macro de la tabla de definiciones.

Introducción a la programación en C by CTG © Página 52


· #if, #ifdef, #ifndef, #else y #endif. Se puede preguntar por el valor de una constante o la
existencia o no de una macro. En caso de ser cierta la condición se compila el código entre #if
y #else, en caso de ser falsa se compila el código entre #else y #endif

Ejemplo.

#ifndef PI

#define PI 3.1416

#endif

· #include. Permite incluir un fichero dentro del programa C. Este fichero puede ser de
cabecera (.h) u otro programa C. Los ficheros de cabecera contienen:

Los prototipos de funciones.


Definiciones de constantes y macros
No deben contener código (declaraciones de funciones...)

Ejemplo. De uso de instrucciones del preprocesador

#include <stdio.h>
#include <math.h>
/* #include “mifichero.h” */
#define TAMMAX 10
#define MES_DESP “finalizada la ejecucion del programa\n”

main ()
{
double a[TAMMAX];
int i;
for (i=0; i<TAMMAX; i++)
{
scanf (“%lf”, &(a[i]));
printf (“%lf”, sin(a[i]));
}
printf(MES_DESP);
return 0;
}

Introducción a la programación en C by CTG © Página 53


Capítulo 11 – Ficheros
Conceptos generales

El manejo de ficheros en C se realiza mediante una estructura denominada FILE. Esta


estructura está definida en el fichero stdio.h. Más concretamente, los ficheros se manejan con
punteros a ese tipo de estructuras:

FILE *nombre;

Donde nombre es el identificador con el que haremos referencia al fichero en las distintas
funciones que usemos para operar con él. Cada vez que operamos sobre un fichero se
modifica su estado, con lo que las funciones que lo manipulan siempre modifican la variable
(por eso es un puntero).

Lo primero que hay que hacer para operar con un fichero es “abrirle” mediante la función
fopen(), cuya sintaxis es:

fopen (nombre_fichero, modo)

Donde nombre_fichero es un puntero a una cadena de caracteres cuyo contenido es el


nombre del fichero (debe incluir la ruta). modo es un puntero a una cadena de caracteres que
especifica el tipo de operación que queremos realizar sobre el fichero. La forma de especificar
ésta es:

“r”, “w”, “a”. Se va a realizar una operación de lectura, escritura posicionando el flujo
al principio del fichero o escritura posicionando el flujo al final del fichero, en modo
ASCII.
“r+”, “w+”, “a+”. Se va a realizar una operación de lectura y escritura posicionando el
flujo al principio del fichero, escritura posicionando el flujo al principio del fichero y
lectura o escritura posicionando el flujo al final del fichero y lectura, en modo ASCII.
Si se añade al final de cualquiera de las opciones anteriores el caracter b la operación
del lectura y escritura se realizarán en modo binario.

La función fopen() devuelve un puntero a una estructura FILE si el fichero se ha


abierto correctamente o el valor NULL si ha habido error. Es importante comprobar esta
posibilidad en el programa antes de continuar.

Para cerrar un fichero que ya no se vaya a usar se utiliza la función flose(nombre). Con
nombre el nombre del puntero a FILE con que hayamos identificado al fichero que queremos
cerrar.

Introducción a la programación en C by CTG © Página 54


Entrada/salida

Existen numerosas funciones para realizar operaciones de entrada/salida sobre


ficheros. Las más usadas son:

fprintf(FILE *nombre, const char *format ). Donde FILE *nombre es la variable


puntero a FILE que usemos para identificar el fichero y const char *format tiene el
mismo significado y formato que en printf(). Esta función se utiliza para escribir datos
en ASCII en un fichero. En caso de éxito, esta funciones devuelven el numero de
caracteres impresos (no incluyendo el caracter `\0' usado para terminar la
salida de cadenas). Si se encuentra un error de salida, se devuelve un valor negativo.
Tanto en ésta, como en el resto de funciones que veremos, el valor devuelto sirve para
comprobar que la operación se ha realizado con éxito.
fscanf(FILE *nombre, const char *format). Los argumentos tienen el mismo
significado que en fprintf(). Se usa para leer datos de un fichero en ASCII. El valor
devuelto es el número de datos, no de caracteres, leídos.
fwrite(const void *ptr, size_t tam, size_t nmiemb, FILE *nombre). Función para
escritura de datos en binario en un fichero. Escribe nmiemb elementos de datos, cada
uno de tam bytes de largo, al fichero apuntado por nombre, obteniéndolos del
sitio apuntado por ptr, esto es, ptr es un puntero a una variable, vector, estructura, etc,
tal que su tamaño sea de tam*nmiemb bytes. tam y nmiemb son datos enteros.
Devuelve el número de elementos (no de bytes) escritos correctamente. Si ocurre un
error o se llega al fin-de-fichero, el valor devuelto es un número menor del esperado (o
cero).
fread(void *ptr, size_t tam, size_t nmiemb, FILE *nombre). Función para lectura
de datos en binario en un fichero. El significado de los parámetros de la función es el
mismo que para fwrite salvo ptr que es un puntero a la zona de memoria donde se
almacenará la información leida. El valor devuelto es el mismo que en fwrite pero en
este caso haciendo referencia al número de datos leídos.
Otras funciones para entrada/salida son: fgets, fgetchar, fputs, fputchar. Su
funcionamiento es similar a las de nombre parecido (sin la f inicial) que sirven para
entrada/salida desde la estándar correspondiente. La búsqueda de más información se
deja para el estudiante.
fseek( FILE *nombre, long desplto, int origen). Sirve para el control de flujo de
datos sobre un fichero, más concretamente mueve el indicador que marca la posición
de escritura/lectura en el fichero correspondiente. La nueva posición del indicador,
medida en bytes, se obtiene añadiendo desplto bytes a la posición especificada por
origen. Para especificar el punto de origen del desplazamiento se pueden usar la
siguientes constantes:
SEEK_SET. Comienzo del fichero.
SEEK_CUR. Posición actual.
SEEK_END. Fin de fichero.

En caso de que todo haya ido correcto en la operación esta función devuelve un 0, y si ha
habido error un -1.

rewind( FILE *nombre). Es la versión rápida de fseek(nombre, 0, SEEK_SET), es


decir, mover el apuntador al comienzo del fichero. Esta función no devuelve nada.
feof(FILE *nombre). Devuelve un valor distinto de 0 si el indicador de fichero no ha
llegado al final, y 0 en caso contrario.

Introducción a la programación en C by CTG © Página 55


Ejemplo. De manejo de ficheros. Este programa debe recibir 2 argumentos. El primero es un
fichero de texto con cantidades reales. Lee esas cantidades y las almacena en formato float
(binario) en el fichero incluido como segundo argumento. Cada valor leído es mostrado por
pantalla usando fprintf para mostrar el uso del puntero a la salida estándar “stdout”.

#include<stdio.h>

main (int argc, char **argv)


{
FILE *fpin, *fpout_fich, *fpout_std;
float dat;
int cont=0;

if (argc < 3)
{
fprintf (stderr,”ERROR numero de argumentos insuficiente\n”);
exit (1);
}

if ( (fpin = fopen(argv[1], "r")) == NULL)


{
fprintf (stderr,"ERROR No se pudo abrir %s\n", argv[1]);
exit (2);
}
if ( (fpout_fich = fopen(argv[2], "wb")) == NULL)
{
fprintf (stderr,"ERROR No se pudo abrir %s\n", argv[2]);
exit (2);
}
fpout_std = stdout;

fscanf (fpin,”%f”,&dat);
while (!feof(fpin)) {
if ( fwrite(&dat, sizeof(float), 1, fpout_fich) != 1)
{
fprintf(stderr, “ERROR al escribir dato %d en %s\n”, cont, arg[2]);
exit(3);
}
fprintf (fpout_std,"Dato escrito %.2f\n",dat); // se muestra con 2 decimales
fscanf (fpin,”%f”,&dat);
cont++;
}

fclose (fpin);
fclose (fpout_fich);
return 0;
}

Ejercicio. Modificar la condición de finalización de lectura de fichero en el


ejemplo anterior, usando el valor devuelto por fscanf().

Introducción a la programación en C by CTG © Página 56


Introducción a la programación en C by CTG © Página 57
Capítulo 12 – Herramientas
Introducción

En esta sección indicaremos una serie de trucos y herramientas que facilitan la


realización y depuración de programas en C. Queda fuera del alcance de esta introducción al
lenguaje C la explicación en profundidad de las herramientas que a continuación se indicarán,
por lo que serán los alumnos interesados los que tengan que profundizar en su
funcionamiento.

Depuración de un programa

Es bastante frecuente al crear un programa C que dé errores tanto en compilación


como en ejecución no siempre fáciles de detectar.

Una opción a seguir para detectar la zona que genera el error al compilar es la de ir
comentando partes del programa, hasta detectar la zona con error.

En cuanto a errores en ejecución, los consejos a seguir para detectarlos son:

Añadir mensajes para seguir el hilo de la ejecución y vigilar los valores de variables.
Acotar la sección de código que contiene el error.
Evaluar los parámetros que provocan el error.

Una herramienta que permita la depuración de un programa es la gdb. Esta


herramienta permite observar lo que ocurre dentro del programa en el momento de su
ejecución. Realiza cuatro operaciones básicas:

Ejecución del programa.


Detener la ejecución bajo ciertas condiciones.
Examinar que ha sucedido cuando el programa se ha detenido
Cambiar el estado de la ejecución del programa.

Si se quiere trabajar con el depurador mediante un interfaz gráfico se puede usar la


herramienta ddd.

Make

La herramienta make es una utilidad que permite mantener, actualizar y regenerar


eficientemente un programa que depende de múltiples archivos, determinando
automáticamente qué partes de este gran programa necesitan ser recompilado ejecutando los
comandos para esto.

Esta utilidad es tanto más conveniente cuanto más complejo sea el programa
ejecutable que se está construyendo. La situación ideal es cuando el fichero ejecutable consta
de muchos módulos que pueden ser compilados separadamente.

Introducción a la programación en C by CTG © Página 58


Embellecedor de C

Una estrategia muy útil y aconsejable para la creación de código C es indentar


adecuadamente el código de las distintas partes del fichero fuente. De este modo, por ejemplo,
con un simple vistazo podremos determinar si hemos cerrado o no una sentencia bloque-

Dependiendo del sistema operativo en que estemos trabajando, el comando puede


variar. Así, para las versiones LINUX instaladas en el laboratorio tenemos el comando indent.
Por defecto lee datos de la entrada estándar y la respuesta la proporciona por la salida
estándar. También puede admitir argumentos, pero preste atención, pues si no se indica lo
contrario, el fichero modificado reemplaza el original.

El comando indent para hacer su trabajo hace un pequeño análisis sintáctico, y por lo
tanto puede indicar ciertos errores, como son un número no balanceado de apertura de
paréntesis y cierre de paréntesis.

Otros

Sytem (clear) = limpia pantalla

Introducción a la programación en C by CTG © Página 59


Apéndice A

Introducción a la programación en C by CTG © Página 60


Introducción a la programación en C by CTG © Página 61
Apéndice B

Introducción a la programación en C by CTG © Página 62


Introducción a la programación en C by CTG © Página 63
Apéndice C

Introducción a la programación en C by CTG © Página 64


Introducción a la programación en C by CTG © Página 65
Apéndice D

Introducción a la programación en C by CTG © Página 66


Introducción a la programación en C by CTG © Página 67
Apéndice E

Introducción a la programación en C by CTG © Página 68


Introducción a la programación en C by CTG © Página 69
Apéndice F

Introducción a la programación en C by CTG © Página 70


Introducción a la programación en C by CTG © Página 71
Apéndice G

Introducción a la programación en C by CTG © Página 72


Introducción a la programación en C by CTG © Página 73
Introducción a la programación en C by CTG © Página 74
Introducción a la programación en C by CTG © Página 75
Apéndice H

Introducción a la programación en C by CTG © Página 76


Introducción a la programación en C by CTG © Página 77
Introducción a la programación en C by CTG © Página 78
Introducción a la programación en C by CTG © Página 79
Introducción a la programación en C by CTG © Página 80
Introducción a la programación en C by CTG © Página 81
Bibliografía
Diego R. Llanos Ferraris, “Fundamentos de Informática y Programación en C”. Paraninfo,
2010. ISBN 13: 978-84-9732-792-3.

Brian Kernighan & Denis Ritchie. “El lenguaje de programación C”. Prentice Hall.

Byron S. Gottfried, “Programación en C”, McGraw-Hill. 2ª Edición.

Introducción a la programación en C by CTG © Página 82

You might also like