PROGRAMACION EN C.pdf

INTRODUCCIÓN A LA PROGRAMACIÓN EN C 1. INTRODUCCIÓN 1.1. Proceso de creación de un programa ejecutable 1.2. Ingeniería

Views 82 Downloads 112 File size 1MB

Report DMCA / Copyright

DOWNLOAD FILE

Recommend stories

Citation preview

INTRODUCCIÓN A LA PROGRAMACIÓN EN C

1. INTRODUCCIÓN 1.1. Proceso de creación de un programa ejecutable 1.2. Ingeniería del software 2. INTRODUCCIÓN AL LENGUAJE C 2.1. Elementos del lenguaje C 2.1.1. Comentarios y separadores 2.1.2. Palabras clave 2.1.3. Identificadores y constantes 2.2. Variables y constantes 2.2.1. Tipos de variables, declaración e inicialización 2.2.2. Modo de almacenamiento y ámbito de una variable 2.3. Operadores 2.3.1. Aritméticos 2.3.2. Relacionales e Incrementales 2.3.3. Lógicos y de bits 2.3.4. De asignación 2.3.5. Conversiones implícitas de tipo 2.3.6. Otros operadores 2.3.7. Precedencia y asociatividad 2.3.8. Funciones matemáticas 2.4. E/S por consola 2.5. Estilo 2.6. Ejemplos 3. SENTENCIAS DE CONTROL 3.1. Condicionales 3.1.1. If-else 3.1.2. Switch-case 3.2. Bucles 3.2.1. For 3.2.2. While y Do-while 3.3. Alteraciones del flujo 3.3.1. Break y Continue 3.3.2. Goto 3.4. Ejemplos y ejercicios 4. TIPOS AVANZADOS DE DATOS 4.1. Vectores. Inicialización y acceso a datos 4.1.1. Operaciones con vectores

Programación en C

1

4.1.2. Ordenación de un vector 4.2. Matrices. Inicialización y acceso a datos 4.3. Cadenas de caracteres. Inicialización, E/S por consola 4.3.1. Operaciones con cadenas 4.3.2. Biblioteca string.h 4.4. Estructuras. Inicialización y acceso a datos 4.4.1. Copia de estructuras 4.4.2. Vector de estructuras 4.4.3. Estructuras de estructuras y vectores 4.5. Uniones 4.6. Enumeraciones 4.7. Tipos definidos por el usuario 4.8. Ejemplos 5. PUNTEROS 5.1. Declaración e inicialización 5.2. Operaciones con punteros 5.2.1. Asignación 5.2.2. Aritmética y comparación 5.3. Punteros a vectores 5.4. Punteros a cadenas de caracteres 5.5. Punteros a estructuras 5.6. Punteros a punteros 5.7. Asignación dinámica de memoria 6. ESTRUCTURA DE UN PROGRAMA 6.1. Directivas del preprocesador 6.2. Funciones 6.2.1. Declaración y definición 6.2.2. Llamada 6.3. Ámbito de una variable 6.4. Parámetros de una función. Paso por valor y referencia 6.4.1. Retorno 6.4.1.1. Retorno vacío (void) y de resultados 6.4.1.2. Retorno booleano 6.4.2. Funciones que llaman a otras funciones 6.4.3. Funciones, vectores y matrices 6.4.4. Funciones y cadenas 6.4.5. Funciones y estructuras 6.4.5.1. Estructuras como parámetros de una función 6.4.5.2. Estructuras como retorno de una función 6.5. Estructuración de funciones en ficheros 6.5. Recursividad

Programación en C

2

6.6. Parámetros de la función main 6.7. Ejemplos 7. ENTRADA / SALIDA 7.1. Salida estándar 7.1.1. printf 7.1.2. putchar y puts 7.2. Entrada estándar 7.2.1. scanf 7.2.2. getchar, gets y fflush 7.3. Gestión de ficheros 7.3.1. Abrir y cerrar ficheros. fopen y fclose 7.3.2. Gestión de errores 7.3.3. Operaciones de lectura/escritura 7.3.3.1. fprint y fscanf 7.3.3.2. fputc, fputs, fgetc y fgets 7.3.3.3. fwrite y fread 7.4. Ejemplos 8. PROBLEMAS RESUELTOS 8.1. Máximo común divisor 8.2. Adivinar un número 8.3. Lotería 8.4. Integral 8.5. Números primos 8.6. Fibonacci 8.7. Media y rango de un vector 9. APLICACIÓN PRÁCTICA: AGENDA 9.1. Funciones 9.2. Funciones 9.2.1. Gestión de 1 contacto 9.2.2. Gestión de 2 contactos 9.2.3. Gestión de la agenda 9.2.4. Gestión de E/S 9.3. Resultado BIBLIOGRAFIA

Programación en C

3

1. INTRODUCCIÓN Un lenguaje de programación es un lenguaje artificial que permite escribir un programa (conjunto de instrucciones que interpreta y ejecuta un ordenador). Para que un ordenador interprete un programa, sus instrucciones tienen que estar codificadas en lenguaje máquina (binario). El humano está acostumbrado al lenguaje natural. Escribir un programa en binario es engorroso. Esta es la esencia de los lenguajes de programación. Por eso se les denomina lenguajes simbólicos o de alto nivel, en oposición a los lenguajes máquina o de bajo nivel. Queda pendiente entonces, la tarea de traducir un programa de lenguaje simbólico a lenguaje o código máquina sigue pendiente. Para ello se usan los programas intérpretes y compiladores. El lenguaje de más bajo nivel es el ensamblador, parecido al lenguaje máquina con modificaciones nemotécnicas para facilitar su uso. La tabla muestra un ejemplo de código en ensamblador válido para procesadores INTEL 80XX/80X86e, que imprime en pantalla el mensaje Hola Mundo. Los lenguajes de alto nivel son más cercanos al lenguaje natural, más fáciles de comprender. Entre los lenguajes de alto nivel y el ensamblador existen lenguajes, de nivel medio, que combinan las características de los primeros con operaciones propias del lenguaje ensamblador, como la manipulación de bits y direcciones de memoria, entre los que se encuentran C, C++ o Java. El programa siguiente, escrito en C también muestra en pantalla el mensaje Hola Mundo: #include void main() {

printf (“Hola Mundo\n"); }

DATA SEGMENT SALUDO DB “Hola Mundo",13,10,"$" DATA ENDS CODE SEGMENT ASSUME CS:CODE, DS:DATA, SS:STACK INICIO: MOV AX,DATA MOV DS,AX MOV DX,OFFSET SALUDO MOV AH,09H INT 21H MOV AH,4CH INT 21H CODE ENDS END INICIO

Comparando ambos programas, se aprecia que el escrito en C es más corto e inteligible. La programación en ensamblador consigue más eficiencia al ejecutar más rápido y aprovechar mejor el hw de la máquina. Sin embargo, son más largos y hay que aprender un lenguaje específico para cada procesador. Los lenguajes de alto nivel penniten escribir programas abstrayendose del procesador concreto de una máquina, lo que permite que se ejecute en distintas máquinas (portabilidad). El lenguaje C fue inventado por Dennis Ritchie en Laboratorios Bell en 1972 para ser usado con UNIX. Deriva del lenguaje B, escrito en 1970 por Ken Thompson para el primer UNIX, que a su vez deriva del lenguaje BCPL (Martin Richards, años 60). El nombre C se dio por ser la siguiente letra a B. El siguiente lenguaje, que a lo mejor se debiera llamar D, se da en llamar C++ (C orientado a objetos, groseramente). Hasta los 80, aunque existían compiladores para diversas arquitecturas, C siguió ligado a UNIX. En 1989, se convirtió en el estándar ANSI C. La estandarización siguió hasta 1999, cuando se publicó un nuevo estándar, C99, manteniéndose en la vanguardia de los lenguajes de programación no orientados a objetos. En la actualidad, C sigue siendo muy popular. Es un lenguaje de propósito general, compacto (32 palabras reservadas), fácil de aprender, que combina la programación estructurada con la eficiencia y velocidad del ensamblador. Su ventaja es permitir programar a diferentes niveles, desde drivers de un dispositivo hw hasta un SO. UNIX y DOS están escritos en C. Además, es buena base para abordar la POO con C++. 1.1. Proceso de creación de un programa ejecutable Resolver un problema con un ordenador, mecanizar una solución o automatizar un proceso, son conceptos semejantes que suponen codificar una serie de instrucciones que la máquina debe interpretar correctamente. Es lo que ocurre con el lenguaje humano, incluyendo los gestos. La máquina sólo entiende código maquina, binario. La transformación del código fuente de un lenguaje de alto nivel a código maquina se llama construcción (build) y distingue las siguientes etapas: Preprocesado. El código es un fichero de texto. La máquina no lo entiende. Así, lo primero es preprocesar el código (fuente), realizado tareas como sustituir partes del código por otras equivalentes o analizar dependencias entre ficheros generando una versión del código también en texto.

Programación en C

4

Compilado. Consiste en transformar el archivo preprocesado en uno binario (código máquina). En general visible en disco (con extensión .obj en WS y .o en Linux). Estos archivos ya son parte del programa binario que ejecutaría el equipo. Son una parte, no el programa completo. Enlazado estático (linkado). Es normal necesitar código adicional, ya compilado en, por ejemplo, bibliotecas estáticas (.liben en WS y .a en Linux). El enlazador o linker realiza la composición del archivo ejecutable (.exe en WS, sin extensión en Linux). El código de biblioteca necesario, queda embebido en el ejecutable, por lo que no es necesario adjuntar la biblioteca para su ejecución. Enlazado dinámico. Al ejecutar un programa puede necesitarse más código no enlazado en la etapa anterior y que se encuentre en bibliotecas dinámicas (o compartidas, extensión .dll en WS y .so en Linux). Cuando estas bibliotecas se encuentran en el SO (como ocurre a menudo) no es necesario distribuirlas con el ejecutable. Al enlazar dinámicamente el SO, el programa se convierte ya en un proceso completo en ejecución. Entre los compiladores de C están el de MS (Visual C), el de Borland (Builder) y el GCC, de entornos Unix. Para escribir un programa en C puede usarse cualquier editor, como Notepad, el bloc de notas u otros específicamente creados para escribir y manejar programas fuentes en C. Los errores de un programa se pueden dividir básicamente en 2 tipos: errores en tiempo de desarrollo y errores en tiempo de ejecución. Los errores en tiempo de desarrollo se dan cuando el archivo ejecutable no puede llegar a serlo. A su vez distinguen 2 subtipos: errores de sintaxis y de linkado. Los primeros se refieren a la escritura del programa, como una falta de ortografía. El compilador lo detecta e informa al usuario. Son fáciles de detectar y corregir. Los errores de linkado se refieren a que siendo el código sintácticamente correcto, falla la creación del ejecutable por faltar código (por ejemplo una biblioteca o se ha omitido un archivo). También son relativamente sencillos de detectar. El compilador puede emitir avisos (warnings) informando de anomalías, no impiden la creación del ejecutable, pero pueden suponer errores en ejecución. Los errores en tiempo de ejecución se dan cuando el programa está en funcionamiento. Su detección es más compleja. Para solucionarlo se usan herramientas de depuración (debugger) que permiten ejecutar un programa paso a paso, visualizando su estado en cada momento. Un ejemplo paradigmático es la operación de división entre 0. 1.2. Ingeniería del software La programación es una parte de lo que se conoce como Ingeniería del Software. La creación de sw no es un proceso artístico, no se improvisan soluciones a un problema. La ingeniería del sw establece metodologías para el desarrollo de código, abarcando áreas como el análisis de requisitos, del dominio del problema, diseño, codificación, pruebas, validación, documentación, control de versiones, etc. Al igual que un edificio, un programa necesita un proyecto, el diseño de un arquitecto. El albañil no improvisa, se ciñe al proyecto y lo ejecuta con un resultado que será revisado. Un programa, puede ser manipulado por distintas personas, por tanto, el código tiene que ser legible. Para ello, son buenas prácticas de programación, entre otras, incluir comentarios, explicando los detalles, no obviedades; seguir un estilo aceptado en la organización (tabulaciones, saltos de línea) o usar nombres significativos en variables y funciones (variable "radio", en vez de "r" o "x"). También es importante buscar programas genéricos que particulares, cuando sea posible. Por ejemplo, podría ser mejor opción codificar un programa con un vector que maneje un número de elementos indeterminado, en principio, que uno que maneje sólo 10 datos. Por fin, que un programa funcione, o lo parezca, no garantiza que el programa este bien realizado y la solución sea correcta.

Programación en C

5

2. INTRODUCCIÓN AL LENGUAJE C El código del siguiente programa, es el típico con el que comenzar el estudio de un lenguaje de programación. Lo único que hace es mostrar en pantalla el mensaje “Hola mundo”.

#include void main () {

Para su estudio, se analizará su código línea a línea.

printf("Hola mundo\n");

#include Es la línea necesaria para usar funciones de entrada / salida estándar.

}

La E/S estándar es la consola, en la que mostrar mensajes y leer lo que teclea el usuario. El significado intuitivo de esta línea de código es que "incluya" (include) el fichero stdio.h (std=estandar, io=input, output). void main () { Esta línea define el punto de entrada al programa, la función main () o principal. Un programa en C necesita la existencia de una función main (). Sólo una función puede ser denominada así. La llave de apertura define el comienzo de un bloque de sentencias. En este caso el comienzo de la función main (). printf("Hola mundo\n"); } Realiza la llamada a la función printf (), que muestra mensajes de texto en la consola. Se encuentra declarada en el fichero stdio.h, por lo que se codificó la primera linea del programa con #include . El final de la sentencia se hace con punto y coma. La llave de cierre indica el fial del bloque de sentencias, en este caso, main (). Cuando el programa alcanza el final de la función main (), termina su ejecución. Los programas se ejecutan en secuencia, una tras otra, de arriba abajo. El programador debe tenerlo en cuenta. Si se omite la palabra void antes de main (), el compilador lo acepta. Cada sentencia se delimita con un punto y coma y generalmente se escribe una por línea, aunque no es imprescindible, es cuestión de estilo. Un conjunto de sentencias se pueden agrupar en un bloque delimitado por llaves. El lenguaje C siempre distingue entre mayúsculas y minúsculas. Es case-sensitive. 2.1. Elementos del lenguaje C 2.1.1. Comentarios y separadores Todo programa debe ser sencillo y claro. Para ello se emplean comentarios descriptivos. Texto que no es tenido en cuenta por el compilador, no es código ejecutable. Es útil para las personas que leen el programa. En C los comentarios de una línea van precedidos de una doble barra (//). Los comentarios de más de una línea se encierran entre una barra y asterisco (/* --- */.

//comentario de una linea #include /*Este es un comentario de varias lineas, explicando lo que sea necesario*/ void main() //aqui puede ir un comentario { printf("Hola mundo\n"); //aqui puede ir otro comentario

Un separador es un caracter o conjunto de ellos que separan elementos del lenguaje. Los separadores puede ser espacios, retornos de carro o tabulaciones. Los comentarios también podrían considerarse separadores. Un separador delimita dos elementos diferentes, sin efecto sobre la sintaxis. Los separadores se usan típicamente para hacer más legible el código. Por ejemplo, el programa anterior podía haber sido escrito de las siguientes formas, añadiendo o quitando espacios y retornos de carro, siendo el ejecutable binario generado al compilar exactamente el mismo.

#include

#include

void main ()

void main () {

{ //aqui comentario

//aqui comentario

printf("Hola mundo\n"); }

printf("Hola mundo\n");} }

2.1.2. Palabras clave C dispone 32 palabras clave (reservadas, defmidas en el estándar ANSI C. En la siguiente tabla se muestran clasificadas por función. Las 3 primeras filas se relacionan con los tipos de datos. La ultima tiene que ver con el control del flujo del programa. Al programar en C hay que indicar al compilador los tipos de datos que se usarán. Al usar una variable para la edad de una persona, podrá ser de tipo entero y se hará con la palabra clave 'int' abreviatura de "integer" (entero).

Programación en C

6

Para usar una variable que represente la altura de una persona en metros, habrá que usar decimales y será conveniente definirla con la palabra reservada 'float' o 'double' en función de la precisión decimal. Las sentencias de control indican cómo tratar la ejecución. Si se precisa tomar una decisión condicional se usará 'if', por ejemplo. Para repetir un proceso se podrán usar 'for', 'while' o lo que corresponda. Modificadores de tipo

auto

extern

register

static

const

volatile

signed

Tipos primitivos

char

double

float

int

long

short

void

Tipos datos avanzados

enum

struct

typedef

union

sizeof

Sentencias de control

break

case

continue

do

else

if

for

return

goto

default

switch

unsigned

while

2.1.3. Identificadores y constantes Un identificador es un nombre simbólico referido a un dato o a una parte de un programa. Son nombres asignados a datos (variables o constantes) y funciones. En C siguen unas reglas: 1. Sólo pueden contener caracteres alfanuméricos, mayúsculas o minúsculas y el caracter "underscore" (A-Z, a-z, 0-9, '_') . El alfabeto es inglés (la letra 'ñ' está excluida). 2. No pueden contener el caracter espacio ni caracteres especiales o de puntuación 3. El primer caracter no puede ser un dígito, esto es, debe comenzar por una letra o el caracter ' '. 4. Se distinguen mayúsculas de minúsculas 5. El estándar ANSI C admite un máximo de 31 caracteres de longitud, aunque en la practica a veces pueden ser más caracteres. 6. No se pueden utilizar palabras clave reservadas como 'long', 'auto', 'const', etc. Algunas buenas prácticas que suelen aplicar los programadores son:: 1. Usar identificadores o nombres significativos. Por ejemplo para referirse a una altura, usar “altura”, mejor que “al”, ya que el programa será más legible. 2. Al usar variables auxiliares o de sentido matemático, se recomienda usar nombres de identificadores similares a los matemáticos. Por ejemplo, un vector “v” con subíndice “i”, se puede poner “vi”. Usar el identificador “i” para contadores, repeticiones e indexar vectores. Asimismo, usar los identificadores 'i', 'j', 'k' para contadores, bucles anidados o índices. Usar los identificadores 'x' e 'y' para coordenadas espaciales o variables independientes e independiente de una función. Las constantes son datos que no cambian de valor y no se modifican. Pueden ser numéricas, caracteres y cadenas de caracteres. En constantes enteras si el número es suficientemente pequeño se usa el tipo int y long automáticamente si el valor es grande. Para indicar formato long se añade una L al final del número. 34 constante tipo int

34L constante tipo long

También se pueden usar constantes en formato octal, precediendo el número por 0. Para constantes hexadecimales, precediendo con 0x o 0X. 07 constante octal

0xff constante hexadecimal

0XA1 constante hexadecimal

Las constantes reales, son por defecto de tipo double, a no ser que se diga lo contrario con la letra f ó F. También se permite la notación científica con la letra e o E, seguida del exponente (base 10). 2.28 constante double

1.23f constante float

0.234 constante double

.345 constante doublé

.67F constante float

123e3 constante double (123000.0)

.23E-2f constante float (0.0023f) Las constantes de caracteres se representan entre comillas simples: ‘a´ constante caracter (letra a)

'+' constante caracter (símbolo +)

Las constantes de cadenas de caracteres se limitan por comillas dobles: "Hola mundo"

Programación en C

7

2.2. Variables y constantes Las variables son los datos básicos de datos en un programa. Son un campo de memoria con un nombre identificativo, que puede almacenar valores diferentes en la ejecución de un programa. Al nombre dado a una variable se le llama identificador y tiene que cumplir las normas explicadas para los identificadores. Las constantes son valores fijos con los que se puede asignar un valor a variables, operar con variables y constantes, etc. y en general usarse a lo largo de la ejecución de un programa. En C existen además las constantes simbólicas, establecidas mediante la palabra clave const y cuyo valor no puede ser modificado en el código.

void main () { const int c=4; /* constante simbolica de tipo entero denominada 'c', que vale 4 */ int a;

// variable tipo entero denominada 'a'

a=3;

// 'a' toma el valor 3

a=8;

// 'a' toma ahora el valor 8

a=c;

/* 'a' toma el valor de c, 4. La constante 'c' no es modificada */

c=5;

// ¡Error! se intenta modificar la constante 'c'

}

2.2.1. Tipos de variables, declaración e inicialización Toda variable que vaya a usarse en un programa debe ser declarada antes. Esto es definir su tipo y asignarle nombre. Opcionalmente se puede inicializar. El tipo indica su dominio (entero, carácter…) e imnplícitamente sus operaciones. Los tipos de dato existen para ahorrar espacio en memoria. Los tipos de datos enteros son: char, short, int, long. Los reales float y double. Se entiende por enteras las cantidades sin decimales y reales (decimal o de coma flotante) las que tienen parte entera y decimal. Tienen punto decimal, aunque esa parte sea 0. Un caracter es un entero que se corresponde a un símbolo ASCII. Caracteres y cadenas se estudian más adelante. La sintaxis de declaración de una variable es la mostrada.

[=valor inicial];

Ejemplos de declaración: Al declarar una variable, realmente se indica a la máquina que reserve memoria, a la que se hará referencia con el nombre de la variable, de forma al leer o escribir en la variable, se lea o se escriba en su posición de memoria. Entre la memoria disponible, es el SO el que asigna una posición libre. El programador no decide en ese proceso. En C, cuando las declaraciones de variables se realizan en un bloque de sentencias entre llaves, las deben ubicarse siempre al principio, siendo un error de sintaxis intentar declararlas en otro lugar.

int a; // declara una variable de tipo entero denominada 'a' double b=3.1; // declara una variable real de precision doble y valor inicial 3.1 a=4;

// correcto

b=14.567;

// correcto

c=23; declarada

// error de sintaxis, variable 'c' no

int d;

// error de sintaxis,

En cada bloque de sentencias la zona de declaraciones termina con la primera línea que no es declaración. La diferencia entre los tipos de datos enteros y reales es el tamaño que ocupan en memoria, el número de bytes que usan para codificar su valor. En función de su tamaño, el rango de valores varia. Se puede afirmar que el tamaño charfrase2 ordenadas\n");

if (orden==-1)

printf("Frase1mm=7;

p->aaaa=2007;

longitud=strlen(p); for(i=longitud-l;i>=0;i--) printf("%c", *(p+i));

printf("%d-%d-%d\n", p->dd, p->mm, p->aaaa);

printf("\n");

free(p);

free(p);

}

}

Programación en C

38

6. ESTRUCTURA DE UN PROGRAMA Estructurar un programa es importante para que sea fácilmente interpretado por otra persona. Para ello se usan funciones, subprogramas que divididen el problema en partes independientes que evitan errores y la reutilización de código. El lenguaje C permite la definición de funciones por parte del programador. 6.1. Directivas del preprocesador Las directivas del preprocesador no forman parte del lenguaje C, ni son entendidas por el compilador, sino por un programa previo denominado preprocesador. Las directivas se distinguen de las líneas de código por comenzar con el símbolo #. Las directivas del preprocesador más usadas son: #include. Permite incluir en el código generado otros archivos de cabecera. Esta directiva puede usarse dos formas distintas, como se muestra en el cuadro.

#include #include "miFichero.h"

La diferencia entre usar < > o " " está en la ubicación del fichero de cabecera. Al usar < > se indica al preprocesador que busque en los directorios por defecto del entorno de desarrollo y en los que suelen estar ficheros de librerías estándar como . Cuando se usa “ ”, se indica que primero busque en la carpeta de trabajo, en la que se suelen estar los archivos fuente. Si no lo encuentra, buscará en las demás carpetas. Las comillas se usan por tanto para ficheros de cabecera del usuario. La directiva #include es lo primero que se escribe. El lenguaje C contiene una amplia variedad de archivos cabecera (.h) de utilidad. #define y #undef. La directiva #define permite definir símbolos, nombres alternativos a una constante y #undef elimina símbolos previamente definidos. La declaración de las directivas es la mostrada.

#define nombre valor #undef nombre_símbolo

Las directivas no llevan punto y coma (;) al final. Aunque no obligatorio, los símbolos definidos empleando la directiva suelen escribirse en mayúsculas para facilitar la lectura. El preprocesador recorre el archivo sustituyendo las ocurrencias de la constante por su valor numérico. Macros. La directiva #define también permite definir macros, operaciones sobre datos o variables. La sintaxis de una macro es la mostrada.

#define nombre_macro (param1, ... , paramN) código

En la sintaxis, “código” son sentencias en C y los parámetros (param1,…,paramN) son símbolos en código. El preprocesador sustituye cada llamada a la macro por su código y los parámetros por los valores que tengan cuando se llama a la macro. Como en el ejemplo. En el código se define la macro CUBO, que tiene un único parámetro llamado "x". Cuando el preprocesador encuentra la macro, la sustituye directamente por su código sustituyendo la "x" por el parámetro entre paréntesis, en el código, la variable “a”. Es el código de la derecha. El uso de macros se desaconseja a menos que sea necesario. En caso de usarse se recomienda que el código sea lo más sencillo posible.

#include

#include

#define CUBO(x) x*x*x

void main ()

void main () {

{

float a=3,b;

float a=3,b;

b=CUBO (a);

b=a*a*a;

printf("b= %f\n",b);

printf("b= %f\n",b);

}

}

6.2. Funciones En general, un problema se divide en partes más sencillas que permitan un desarrollo más ágil, mantenible y reutilizable. Estas partes, subprogramas son las funciones. Ya se han usado funciones como printf () o scanf (). El interés, ahora está en la definición de funciones por parte del programador. El cuadro muestra un ejemplo. El cuerpo de la función Saluda () está fuera de main (), que contiene una llamada o invocación a la función. El flujo de ejecución del programa comienza en la primera linea del main, que llama a la función Saluda. En ese momento se salta al código de la función, que comienza a ejecutarse. Cuando se completa la ejecución de la función se continúa en la siguiente línea de main, que como en este caso no hay, termina. La función main () siempre debe existir en un programa. Sólo un bloque de código puede llamarse main.

Programación en C

#include // prototipos (interfaz) de funciones void Saluda(); void main () { Saluda(); // llamada a la funcion } // cuerpo de funciones void Saluda() { printf("Hola mundo\n"); }

39

6.2.1. Declaración y definición Al igual que para usar una variable hay que declararla previamente, para programar una función, es necesario establecer su prototipo o declaración. El prototipo de una función consta de 3 partes principales: 1. Nombre de la función. Es su identificador que debe seguir las normas sintácticas de identificadores (sin espacios, no comenzar por número, etc.) y de estilo (nombre significativo…). 2. Parámetros de función. Debe especificarse su tipo (tipos básicos, vectores, estructuras) y puede darse el caso de no tenerlos. 3. Tipo de la función o tipo de su valor de retomo. Indica el valor que se devuelve, si es el caso. La sintaxis del prototipo de una función es la mostrada. Se puede analizar alguna función de la biblioteca como ejemplo. Así, el prototipo de la función sqrt () para calcular la raíz cuadrada es: double sqrt(double);.

tipo nombre_función (tipo1 param1, ... , tipoN paramN) { // cuerpo de la función; }

El prototipo especifica que la función tiene un parámetro de entrada, especificado entre paréntesis de tipo double. Este parámetro es el número del que hallar la raíz cuadrada. El tipo de la función es double, es decir, el valor que devuelva será la raíz cuadrada del parámetro indicado representado como un real de precisión doble. En el caso anterior no se da nombre al parámetro, por ser obvio. Los nombres de los parámetros no son necesarios en los prototipos pero si convenientes. Si el usuario quisiera programar una función que calcule el área de un circulo de radio conocido, el prototipo de la función podría ser: float AreaCirculo(float radio), equivalente a float AreaCirculo(float). El usuario especifica el radio, pasado como parámetro a AreaCirculo, que devuelve un dato float, el área. Aunque el nombre "radio" es opcional, en los prototipos, es muy informativo, ya que, por ejemplo, elimina la duda de si se está pasando un radio o un diámetro. Si no se especifica valor de retomo de la función, se asume que se devuelve un valor de tipo int. Si no se desea que una función devuelva valor, se puede indicar con el tipo void (vacío). Esto es valido para los parámetros, como en la función Saluda(), que podría haberse definido como void Saluda(void). En la sintaxis del prototipo, se especifica “cuerpo de la función”. Lógicamente es el conjunto de sentencias que implementan la tarea propia de la función. Se recomienda usar idéntica nomenclatura para declaración y defiición. Por fin, el cuerpo de la función no está en la función main (), sino a nivel global. Una función se define típicamente al formalizar la llave de cierre de la función main () y la última línea de su código suele ser la que permite devolver el resultado: la palabra clave return. Cuando el compilador se encuentra una llamada a una función, debe conocer su prototipo. La declaración puede hacerse con el interfaz o con el código, justo después de las directivas del preprocesador. Se recomienda declarar sólo el interfaz, es decir, el prototipo de las funciones y después del cuerpo de código principal definir el código de cada función. 6.2.2. Llamada La llamada a una función se realiza con el nombre de la misma y entre paréntesis los parámetros que requiere su interfaz para operar. El número y tipo de parámetros empleados en la llamada a la función debe coincidir con los definidos en el prototipo. La llamada a la función se puede realizar en cualquier línea de código en que se precise. Si la función devuelve algún valor (no es tipo void) la llamada a la función debe estar incluida en una expresión que recoja el valor devuelto.

void main () { float r,a; printf("Radio: "); scanf("%f",&r); a=AreaCirculo(r); /* llama a la funcion, pasando "r" como radio y asignando el area a "a" */ printf("Area: %f\n",a); }

Los datos concretos empleados en la llamada a una función se denominan parámetros reales (en el ejemplo el radio "r"), ya que se refieren a la información que se transfiere a la función para su proceso. Los nombres de los parámetros formales no tienen por qué coincidir con los nombres de las variables usadas como parámetros reales en el momento de la llamada. 6.3. Ámbito de una variable Las variables de un programa pueden clasificarse en función del ámbito en el cual se conocen y por tanto son accesibles. Se distingue entre variables locales y globales. Programación en C

40

La idea es ésta. Sea un país donde su presidente se llama Alfredo. Si en una reunión de amigos alguno se llama Alfredo, en el ámbito de la reunión Alfredo es una variable local. Si alguien invoca el nombre de Alfredo, todos entenderán que se está refiriendo a ese amigo. Si en la reunión no hubiera ningún Alfredo, el ámbito siguiente, por ejemplo el país, sería ahora el de la variable Alfredo, es decir, se referiría al presidente, con la consiguiente carga de improperios que se dirían sobre ese tal Alfredo. De esta forma, una variable global se halla declarada fuera de toda función, al inicio del programa principal. Su ámbito se extiende para todas las funciones del fichero de código fuente. El ''tiempo de vida" es el que dura la ejecución del programa, por lo que se crean al comienzo de la ejecución del programa y se destruyen al finalizar la ejecución. En las funciones no hay que declarar las variables globales. Se pueden usar sin más, como insultar al presidente. Eso si, los cambios efectuados sobre una variable global afectan a los que la usen posteriormente. En el siguiente ejemplo existe una variable global "a", accesible a todas las funciones. Al principio, vale 3. Al comenzar la ejecución, el programa mostrará ese valor. Luego, se cambia su valor a 7 y se llama a función(). Cuando se ejecuta, lo primero es mostrar a, luego se mostrará en pantalla el valor 7. Al seguir la ejecución de función(), se asigna el valor 10 a “a” y termina. Se sigue ejecutando en main de forma que se vuelve a mostrar el contenido de a. Ahora será 10.

#include int a=3;

//variable global

void funcion(); void main() {

//main tiene acceso a "a"

printf("A vale: %d\n",a); a=7; funcion();

// /tambien tiene acceso a "a"

printf("A vale: %d\n",a); } void funcion () { printf("A vale en la funcion: %d\n",a); a= 10; }

El uso de variables globales debe evitarse a ser posible. Cuando el código fuente queda repartido en ficheros diferentes y se quiere usar una variable global para todos, se usa la palabra clave extern. En cuanto a variables locales, son las que se declaran en el ámbito particular de una función. Sólo se conocen en ese ámbito. Su tiempo de vida es el de ejecución de la función. Se crean al invocar la función y se destruyen al finalizar su ejecución. Como en el ejemplo de la derecha. No se puede acceder a las variables fuera de su ámbito. Resulta evidente ya que además los nombres son distintos. Podría plantearse el caso en que ambas variables, aunque locales, se llamasen igual. Es el ejemplo del presidente Alfredo. Cada función tiene por su espacio de memoria, luego varias funciones pueden definir variables locales con el mismo nombre sin que su trato interfiera con otras funciones.

#include void funcion(); void main () { int a=3;

// ‘a’ es local a main

b=28;

// Error de sintaxis. ‘b’ no se conoce

} void funcion () { int b=1;

// ‘b’ es local a la función

a=18;

// Error de sintaxis. ‘a’ no se conoce

}

Cada función tiene sus variables propio dato, y las modificaciones que haga sobre el no tienen ningún efecto sobre los otros, tal y como se muestra en el ejemplo siguiente: 6.4. Parámetros de una función. Paso por valor y referencia Cuando se realiza la llamada a una función con parámetros, su valor concreto en una invocación se llama argumentos. Si el tipo de dato del argumento no coincide con el del parámetro, se produce una conversión implícita de tipo en caso de ser posible. Por ejemplo, si se pasa un argumento entero, estando definido un parámetro tipo float, el entero se convierte implícitamente a float. El compilador mostrará un warning avisando de ello. El caso contrario, parámetro entero y argumento float, lo convertiría implícitamente a entero, truncando decimales. El compilador también avisaría de esta acción. Al proceder de esta forma se le denomina paso por valor (o copia) porque a la función Imprime no se le pasa el argumento, la variable, sino una copia de su valor, que se obtiene del parámetro. Si se usa el argumento como tal, se dice que se hace un “paso por referencia”. La idea implícita es similar a la de variable global y local. La idea de variable global se asociaría al paso por referencia y la de variable local al paso por valor o copia. Así, el paso por valor copia el valor del argumento en el parámetro correspondiente. La función no modifica el valor de los parámetros. Programación en C

#include void ImprimeDoble(float x);

41

En el ejemplo propuesto, el código de la función modifica el valor de su parámetro x. El programa principal, el main() lo utiliza con la variable ‘a’. Por tanto, funcionalmente, main usa x para imprimir ‘a’, pero ImprimeDoble no tiene acceso a ‘a’, sólo usa su valor. De ahí el nombre de “paso por valor”. Cuando ImprimeDoble multiplica por 2 ‘x’, modifica la copia local del dato, es decir ‘x’, pero la variable ‘a’ sigue sin modificarse, por lo que después de la llamada a ImprimeDoble, al imprimir ‘a’ se mostrará su valor en main, esto es a=1.500000.

void main () { float a= 1.5f; ImprimeDoble(a); printf("a= %f\n",a); } void ImprimeDoble(float x) { // copia el valor de a en x x*=2;

// modifica x, no a

printf("Doble = %f\n",x); }

Un caso para pensar, es que ocurriría si ‘a’, en vez de llamarse ‘a’, se llamase ‘x’. Es lo explicado antes. El resultado no cambia, ya que la x del ámbito de ImprimeDoble es una copia de la x del ámbito main. Es decir una persona se puede llamar Antonio en dos ámbitos, Segovia y Madrid, y aún llamándose igual son dos personas diferentes. En el paso por referencia, se copia la dirección de memoria del argumento en el parámetro formal. Para recibir una dirección de memoria, el parámetro formal de la función debe ser un puntero. Como se está dando la referencia, la dirección de memoria del argumento, la función podrá modificar su valor. En el ejemplo, la función Duplica, recibe como argumento un puntero. Cuando se pasa como parámetro &a, se da en el puntero "p" la dirección de memoria de "a", por ejemplo, 1234. Cuando se hace float d=*p; es equivalente a hacer d=*(1234)=a=1.5. Con lo cual, la siguiente instrucción, *p = 2*d; dejará el valor a= 3.0. Lo interesante es apreciar que la función Duplica modifica el valor de "a" sin conocer variable con ese nombre. Lo que conoce es su dirección. Entra en casa y roba. Cambia su valor. Esto es lo peligroso. Y se debe a que cualquier operación sobre *p es como hacerla con "a".

#include void Duplica(float* p); void rnain () { float a=1.5f; Duplica(&a); printf("a = %f\n",a); } void Duplica(float* p) { float d=*p; *p = 2*d; }

Para intercambiar 2 valores mediante una función NO es posible hacerlo sin el uso del paso por referencia. Una función puede devolver sólo un valor, el que se especifica con return. Si se desea que una función devuelva varios valores, puede hacerse con variables pasadas por referencia, como en el ejemplo propuesto.

tinclude

La función “operaciones” no devuelve valor, aunque podía haberse aprovechado para alguna operación. De los 5 parámetros de la función, los 2 primeros se pasan por valor y los 3 últimos por referencia (las operaciones a devolver).

void main () {

El paso por referencia lo usa, por ejemplo, scanf. Si se hace scanf("%f",&x); para modificar la variable ''x" y escribir en ella el valor tecleado, se pasa "x" por referencia. Sin embargo printf () recibía los parámetros por valor ya que no necesita cambiar su contenido, sólo una copia.

void operaciones (float x, float y, float *s, float *p, float *d);

float a=1.0f, b=2.0f, suma, producto, division; operaciones(a, b, &suma, &producto, &division); printf("La suma vale %f\n", suma); printf("El producto vale %f\n", producto); printf("La division vale %f\n", division); } void operaciones (float x, float y, float *s, float *p, float *d) { *s=x+y;

*p=x*y;

*d=x/y;

}

6.4.1. Retorno 6.4.1.1. Retorno vacío (void) y de resultados Al finalizar la ejecución de una función, se devuelve el control a la parte del código desde donde se realizó la llamada. Cuando una función es de tipo void, no devuelve valor. Su ejecución termina en la última sentencia, pero se puede

Programación en C

#include void ContarHasta(int n); void main () {

42

terminar antes, usando la sentencia return.

ContarHasta(-3); ContarHasta(5);

Return finaliza la función en el momento en que se ejecuta. Se devuelve el control a la línea siguiente de donde se realizó la llamada. En el ejemplo, se implementa una función que cuenta de 0 hasta el número entero que se pasa como parámetro. Si se introduce como parámetro un número negativo se informa al usuario y se termina la función, ya que no se quiere seguir ejecutando nada. Si el parámetro es positivo, la función termina cuando se llega a la llave de cierre. Se podría haber puesto return antes de esta llave, pero si el tipo de la función es void, es opcional.

} void ContarHasta(int n) { int i; if (nb) return a; else return b; }

else printf("El maximo es %d",b); }

Programación en C

43

6.4.1.2. Retorno booleano Es común el uso de funciones que devuelven un número entero, de sólo dos valores, 0 (falso) o 1 (verdadero). En este caso se pueden usar estas funciones directamente como proposiciones lógicas.

#include

En el ejemplo la función EsImpar admite un parámetro de tipo entero y devuelve 0 si el número es par y 1 si es impar. Se usa la función de forma compacta. Se lee intuitivamente como "Si el número num es impar".

int num;

Otra tarea típica de los programas es contar con una función tipo menú que pregunte al usuario si desea continuar la ejecución del programa, mostrar un menú, repetir la pregunta si la respuesta del usuario no es adecuada, etc. e integrarlo en el main con alguna sentencia del estilo while(menu()).

int EsImpar(int n); void main () { printf("Teclee un nº: ”); scanf("%d",&num); if (Esimpar (num)) printf("Su numero es impar\n"); else printf("Su numero es par\n"); } int EsImpar(int n) { if (n%2==1) return 1; else return 0; }

6.4.2. Funciones que llaman a otras funciones Sea el siguiente ejemplo en que se realiza un programa para calcular el número combinatorio "n sobre k":

𝑛 𝑛! ( )= (𝑛 𝑘 − 𝑘)! 𝑘!

Para calcular ese valor se puede usar el programa de la izquierd. Se observa que utiliza 3 bucles for para calcular el factorial de 3 números: repite el código 3 veces, lo que indica que se puede usar una función para aligerar el código, como en el ejemplo de la derecha. #include

#include

int factorial(int a);

int factorial(int a);

void main() {

void main () {

int n, k, i;

int n, k, NsobreK;

int factN=1,factNK=1,factK=1;

printf("Introduzca n y k: "); scanf("%d %d", &n,&k);

int n_sobre_k;

NsobreK = factorial(n)/(factorial(n-k)*factorial(k));

printf("Introduzca n y k: "); scanf("%d %d", &n,&k);

printf("%d sobre %d = %d\n", n, k, NsobreK);

for(i=1; i