Recurso de Programación C

July 4, 2017 | Autor: Gera Torres | Categoría: Programacion, Lenguajes De Programacion, Programacion En C
Share Embed


Descripción

Tabla de Contenido Prólogo....................................................................................................................4 Prólogo del 2003.................................................................................................4 Prólogo del 2014.................................................................................................4 PARTE I: LO MÁS BÁSICO....................................................................................5 Introducción........................................................................................................5 Conceptos generales..........................................................................................5 Variables y constantes........................................................................................5 Instrucciones de entrada / salida........................................................................6 Secuencias de escape.....................................................................................11 Aritmética en C.................................................................................................11 Archivos de inclusión........................................................................................17 Variables globales y locales y reglas de alcance.............................................18 PARTE II: PROGRAMACIÓN ESTRUCTURADA................................................20 Condición if-else...............................................................................................20 Condición switch-case......................................................................................23 Ciclo for.............................................................................................................25 Ciclo while.........................................................................................................29 Ciclo do-while...................................................................................................30 PARTE III: FUNCIONES.......................................................................................33 PARTE IV:ARREGLOS Y ESTRUCTURAS.........................................................44 Arreglos unidimensionales...............................................................................44 Arreglos de tamaño no predefinido..............................................................57 Arreglos de dos o más dimensiones................................................................59 Arreglos con tamaño no predefinido............................................................65 Arreglos de más dimensiones..........................................................................67 Estructuras........................................................................................................69 Cadenas de caracteres....................................................................................72 PARTE V: APUNTADORES..................................................................................76 Apuntadores a variables...................................................................................77 Apuntadores y funciones..................................................................................79 Apuntadores y arreglos....................................................................................82 Arreglos de apuntadores..................................................................................87 Argumentos de la línea de comandos..............................................................92 PARTE VI: ARCHIVOS.........................................................................................93 PARTE VII: MEMORIA DINÁMICA.....................................................................108 Listas enlazadas simples................................................................................108 Árboles binarios..............................................................................................116 APÉNDICE A: LA BIBLIOTECA ESTANDAR.....................................................136 stdio.h.............................................................................................................136 Archivos......................................................................................................136 Salida con formato.....................................................................................137

Entrada con formato...................................................................................138 Entrada y salida de caracteres...................................................................139 Funciones de entrada y salida directa.......................................................140 Funciones de colocación en archivos........................................................140 Funciones de error.....................................................................................141 string.h............................................................................................................141 math.h.............................................................................................................142 stdlib.h.............................................................................................................144 assert.h...........................................................................................................145 time.h..............................................................................................................145 APÉNDICE B: ALGUNOS PROBLEMAS PROPUESTOS.................................146 APÉNDICE C: RECOMENDACIONES Y TEMAS NO VISTOS.........................149

3

Prólogo

Prólogo Prólogo del 2003 Antes que nada quiero agradecer a las personas que me apoyaron para publicar este libro, la Lic. Ana Gabriela Hernandez, la Ing. Karina Requena, el Ing Juan Carlos Saenz, el Ing. Claudio Arzola, el Ing. Jesús Valles y a Luz Cristina Hernandez, que hizo la portada. El libro lo escribí pensando en algo que fuera lo más claro posible y que fuera directo al punto, en lo personal no me gustan los libros donde lo que abunda es la paja, y según yo, creo y deseo que este prólogo sea la única paja que tenga este libro. Hablando de la claridad y de ir directo al grano, este libro no abunda en ejemplos, presenta un ejemplo que pretenda ilustrar lo más posible el tema y lo explica a profundidad. Por eso a lo largo del libro recomiendo crear situaciones distintas y practicar lo más que se pueda, porque el libro te dice cómo hacer las cosas, pero no tiene un ejemplo para cada cosa distinta. El libro no es para nada largo, al contrario, por lo mismo recomiendo que se lea completo no es la gran biblia de C, pero si lo lees completo tendrás una plataforma bastante sólida en los aspectos fundamentales (o sea los más importantes) del lenguaje. Es preferible que tengas algo de experiencia con algún otro lenguaje de programación o con el diseño de algoritmos, pero no es del todo necesario, solo necesitarías poner más atención. Es tiempo de terminar con este prologo, si bajaste el libro desde Internet o lo copiaste en un disco lo puedes regalar a quien tú quieras, pero no lo modifiques ni lo plagies. Aunque no es un súper libro ni te costó mucho adquirirlo implicó un trabajo para mí, un trabajo que espero que valores y que te sea muy útil. Disfruta entonces el libro y úsalo bien, mucha suerte :) ...

Prólogo del 2014 Hace muchos años que escribí este pequeño libro, era estudiante de quinto semestre de ingeniería y no tenía la formación que tengo ahora, aunque quizá tenía ideas más frescas. Este libro, a pesar de sus errores, pareció gustarle a la gente. Hace tiempo que doy clases de programación y pensé que podría utilizarlo como material de texto, igual el trabajo ya estaba hecho. Se le hicieron un algunas correcciones, sobre todo en ortografía, además se incluyeron algunas notas y actualizaciones a la versión C11, lo demás quedó prácticamente igual, este librito ya tenía su esencia. Espero que te sirva, ¡es gratis!

4

PARTE I: LO MÁS BÁSICO

PARTE I: LO MÁS BÁSICO Introducción. El lenguaje C lo escribieron Dennis Ritchie y Brian Kernigham cuando trabajaban con Ken Tompsom en el desarrollo de una nueva versión del sistema UNIX, en los laboratorios Bell, o sea que C se escribió para escribir UNIX. C es un lenguaje muy compacto porque el ejecutable que resulta es muy pequeño, es muy potente y flexible, aparte de que tiene gran portabilidad porque un código que se escribe para un sistema en particular puede correr sin modificaciones, o con muy pocas, sobre otro sistema diferente, aparte de que es independiente del hardware. C es un lenguaje muy pequeño en el sentido de que no tiene muchas instrucciones, lo que lo hace amplio es su biblioteca de funciones, que es algo que lo hace muy flexible porque uno puede escribir su propia librería de funciones y usarla en los programas. Algo que quiero aclarar es que mucha gente trata a C y a C++ como la misma cosa, esa gente se equivoca. El lenguaje C es un lenguaje imperativo, se basa en una secuencia de órdenes para simular o para resolver problemas, pero el lenguaje C++ es un lenguaje orientado a objetos, nada que ver. La forma de manejar varios aspectos en C es diferente a la de C++. C++ ofrece varias cualidades que C no tiene y viceversa, así que hazte a la idea de que este libro es de C y no de C++.

Conceptos generales. Las fases para hacer un programa son teclear un código fuente, compilarlo a código objeto, y finalmente se enlaza para obtener un ejecutable. 1 El código fuente es un archivo de texto que contiene todas las instrucciones que han de ejecutarse para alcanzar la meta que el programador desea. El compilar un programa es transformar todas las instrucciones del programa a un lenguaje que la computadora pueda entender. Este código objeto tiene referencias a otras funciones y datos externos, o sea que no están incluidos en el mismo código fuente, que al momento de enlazarlo se junta con los otros programas objetos de los archivos que se necesiten para que el programa funcione. Y por último tenemos el programa ejecutable que es un código que la máquina puede entender y ejecutar.

Variables y constantes. En un programa, y supongo que ya sabes más o menos qué es un programa y cómo se compone, hay valores que pueden cambiar o que deben admitir datos 1

Hablando en términos muy generales para un lenguaje de tipo compilado.

5

PARTE I: LO MÁS BÁSICO que el usuario necesite, como la temperatura, el promedio de una persona o lo que sea, estos datos son variables y pues siempre están sujetos a sufrir cambios, por otro lado tenemos valores que no deben cambiar como la constante π o algún parámetro importante para que el programa funcione bien. Tanto las variables como las constantes tienen tipos de datos, C tiene varios tipos de datos, pero los más básicos son: • • •

int: valor entero, pondría el rango admitido, pero depende en mucho del sistema. float: flotantes, números como 3.14159 o 1.23 E16. char: caracter, han de saber que en C no hay tipo de datos "cadena", ¿cómo guardar una palabra o algo así? Eso lo vemos después.

Al declarar una variable así se le permite cambiar en cualquier momento, en el caso de las que queremos que sean constantes se pueden declarar de la siguiente manera. const char letra = 'a'; Así el valor de letra no puede ser modificado por accidente. Las variables pueden tomar cualquier nombre, excepto los que sean palabras reservadas como switch o for, además de que no deben contener símbolos como comillas o paréntesis. La longitud no es limitante, pero por lo regular no deben de tener mas de treinta y dos letras, más o menos, porque hay algunos compiladores que no soportan nombres más largos. Otra condición es que no empiecen con un número, aparte C es caso sensitivo, o sea que las minúsculas no son iguales a las mayúsculas, por lo que Var es diferente que var, lo mismo aplica para todas las instrucciones, que deben estar en minúsculas, y para los datos de entrada, así que no es lo mismo teclear e y E, son datos diferentes. Vamos a trabajar con esos datos por lo pronto para pasar a otros temas más avanzados con más facilidad.

Instrucciones de entrada / salida. Hasta ahora podemos escribir, bueno no, no podemos, sabemos que hay tipos de datos y todo eso, pero ¿cómo podemos pedir un valor o desplegarlo en la pantalla? Personalmente no pondría ese tema en este punto de mi "curso", pero voy a tratar de apegarme al máximo a lo que piden en la escuela. Se va a trabajar con dos funciones de entrada y salida, la función de salida va a 6

PARTE I: LO MÁS BÁSICO ser printf y la de entrada va a ser scanf, por ejemplo una línea como la que sigue. printf("Que demonios hace esto?"); Imprime "Que demonios hace esto?" pero sin las comillas. Esas se ponen en la función para delimitar la cadena que se va a imprimir. Pero bueno vamos a juntar lo que hemos visto hasta ahora para escribir el primer programa en C. Por cierto, no pienso poner detalles acerca de cómo crear el código fuente y de cómo compilarlo, eso tiene que ver de qué clase de compilador estés usando y pues no puedo adivinar. /*Esto es un comentario*/ #include /*este es un archivo de cabecera, luego te explico, por ahora solo ponlo*/ /*si este archivo el programa no hace nada*/ int main(int argc, char **args)/*inicia la función principal, me choca decirlo, pero no preguntes solo ponlo así*/ { printf("Hola mundo!!!"); return 0; } ¡Que bonito! ¿Pero y eso de qué sirve? Por lo pronto teclea el programa y guárdalo con la extensión .c, ponle el nombre que quieras y consérvalo. /*esto es un comentario*/ Es importante poner comentarios en los programas, para acordarnos que hicimos y que otras gentes puedan entender qué pasaba por nuestra mente cuando escribimos esas líneas de código. Hay otra forma de poner comentarios, que es más propia de C++, que no es lo mismo que C, pero a veces funciona. Esta forma es poner dos diagonales (//) al principio de cada línea de comentario.

7

PARTE I: LO MÁS BÁSICO #include Esta parte es muy importante, aquí incluimos un archivo, que es el que tiene las funciones de entrada y salida, su nombre significa STandardD Input Output, la extensión .h se refiere a head o cabecera, se les llama así porque siempre se ponen a la cabeza del código fuente. Hay muchos archivos de cabecera y ojalá que los veamos después. int main(int argc, char **args) { ... ... return 0; } En C todo se divide en funciones o segmentos de código que hacen alguna tarea en especial, regresan algún tipo de valor o no regresan nada y también reciben datos o no, la función main es la directora de todas las demás, nada funciona sin ella, en este caso main regresa un valor entero (int) mediante la línea return 0; esta vez solo regresa un 0, los valores que recibe se os pasa el sistema operativo. De vuelta con las funciones de entrada / salida, sabemos que un programa no sirve de nada si no regresa un resultado útil, la cosa es cómo poner los números y datos importantes en la salida o la entrada de nuestros programas, veamos el siguiente código. #include int main(int argc, char **args) { int numero = 10; /*el número vale 10*/ printf("el numero vale %d", numero); return 0; }

8

PARTE I: LO MÁS BÁSICO Podrás ver la notación "%d" al final de la cadena, esto le dice a la función que en ese punto inserte el valor entero decimal que viene a continuación, luego después de la coma viene la variable que tiene que imprimir, no es tan complicado después de todo. Ese mismo programa pudo haberse escrito así. ... int main(int argc, char **args) { printf("el numero vale %d", 10); return 0; } El resultado es el mismo, lo que a la función printf le interesa es recibir un valor entero, no le importa de donde venga. Hay más formatos para imprimir diversos tipos de valores, son los que siguen. •

%d Imprimen un valor entero en notación decimal.



%x Valor entero de forma exadecimal.



%o Pone un entero en notación octal.



%f Valor flotante.



%c Imprime un caracter.

No son todos los formatos de entrada / salida, pero sí nos sacan de casi cualquier apuro. Vamos a ver como poner varias cosas dentro del mismo printf, mira el siguiente programa. #include int main(int argc, char **args) { int val = 5; char letra = 'o'; /*nota que el caracter se delimita por ' '*/

9

PARTE I: LO MÁS BÁSICO printf("El numer%c es %d", letra, val); return 0; } Como ves, se pueden poner varios argumentos en la función printf y se van recogiendo en orden, o sea que si pongo printf("%d %f", 10.56, 4) es un error, se debe poner printf("%d %f", 4, 10.56) en este programa vimos cómo colocar caracteres y cómo se inicializan, no pierdas el detalle de poner las comillas simples para encerrar el caracter que se va a poner. Los formatos de salida son los mismos que los formatos de entrada, con unas pocas diferencias, revisa el siguiente código para aceptar valores desde el teclado. #include int main(int argc, char **args) { int num1, num2, num3; /*si, se pueden poner varias variables dentro de la misma declaración*/ printf(" Introduce un numero: "); scanf("%d", &num1); printf(" Introduce otros dos numeros: ") scanf("%d %d", &num2, &num3); printf("numero 1 = %d, numero 2 = %d, numero 3 = %d", num1, num2, num3); return 0; } Como podrás adivinar el primer scanf lee un numero entero y lo pone en "num1", el detalle de poner un & delante del nombre de la variable es muy importante, si no lo pones pues el número que metiste no se va a guardar en la variable, luego vemos qué significa ese símbolo. La parte más complicada viene en el segundo scanf, vienen dos variables, aquí lo que pasa es que hay que poner el numero 2, un espacio y luego el numero 3, en lo personal no me gusta usar esa clase de 10

PARTE I: LO MÁS BÁSICO formato, pero cada quien...

Secuencias de escape. Si estuviste compilando y corriendo los programas, cosa que espero que hagas y que experimentes un poco, te aseguro que es la mejor forma de aprender. Si lo hiciste te habrás fijado en lo feo que imprime las cosas, para eso tenemos unas secuencias de escape que nos permiten dar cierto formato a la impresión (tanto como una terminal lo permite), hay secuencias de escape para poner una nueva línea, para retroceder sobre la línea, etc., son los siguientes. •

\n Nueva línea



\t Tabulador horizontal



\\ Diagonal inversa



\v Tabulador vertical



\' Apostrofe



\r Regreso de carro



\" Comillas



\b Regresar un espacio



\o Número octal, p Ej. \o25



\f Avance de forma



\x Número exadecimal, p Ej. \x16.

Por ejemplo para escribir "hola mundo!!!" sin que el prompt empiece en la misma línea y el programa quede medio difícil de entender se pone así. printf("\n\t\"Hola mundo!!!\"\n\n"); Escribe algunos programas o lo que quieras donde puedas revisar las secuencias de escape para que veas cómo funcionan, ya que domines bien las funciones de entrada y salida vamos a ver cómo hacer operaciones con números.

Aritmética en C. Se supone que ya sabes (muy poco) de cómo introducir variables y como 11

PARTE I: LO MÁS BÁSICO imprimirlas, resulta más o menos claro que la mayoría de los programas deben realizar cálculos numéricos para que sirvan de algo, las operaciones que podemos hacer son las siguientes. •

Suma: su símbolo es "+", si queremos sumar dos números la sintaxis es a +b



Resta: "-", a - b



Multiplicación: "*", igual que las demás, a * b



División: su símbolo es "/"



Módulo: Supongo que es algo más o menos nuevo, su símbolo es "%", si queremos saber cual es el residuo de 5/2 pues escribimos 5 % 2, en este caso 5/2 = 2 y el residuo es 1



Asignación: El famosísimo signo "igual" o "=", la asignación se hace de derecha a izquierda. c = a + b está bien escrito, pero a + b = c no va a hacer exactamente lo que queremos



Incrementos: Son operadores unitarios, o sea que solo se usan con una variable, no como la suma que requiere dos variables, escribir ++var es como decir "incrementa la variable var en uno antes de usarla", en cambio escribir var++ sería decir "usa la variable var y luego la incrementas en uno". También se puede usar la resta poniendo --var o var--, pero así no se puede mostrar bien su funcionamiento, será más claro cuando veamos ciclos.

También hay operadores para comparar, como el > (mayor que), o el

Descripción Post- incremento y decremento Llamada a función Elemento de vector Selección de elemento por referencia Selección de elemento con puntero

Asociatividad

Izquierda a derecha

++ -+!~ (type) * & sizeof

Pre- incremento y decremento Suma y resta unitaria NOT lógico y NOT binario Conversión de tipo Indirección Dirección de Tamaño de

*/% +>

Multiplicación, división y módulo Suma y resta Operaciones binarias de desplazamiento Operadores relaciones "menor que", "menor o igual que", "mayor que" y "mayor o igual que" Izquierda a Operadores relaciones "igual a" y "distinto derecha de" AND binario XOR binario OR binario AND lógico OR lógico Operador ternario Derecha a

< >= == != & ^ | && || c?t:f

16

Derecha a izquierda

PARTE I: LO MÁS BÁSICO = += -= *= /= %= = &= ^= |=

Asignaciones

izquierda

Este es el operador de menor prioridad en C. Sirve para separar una colección de , Izquierda a expresiones, que se irán evaluando de izquierda a derecha Fuente: http://es.wikipedia.org/wiki/Anexo:Operadores_de_C_y_C%2B %2B#Precedencia_de_operadores

Archivos de inclusión. Como viste, en todos los programas que hemos hecho viene la inclusión del archivo del archivo stdio.h, este es parte de la biblioteca estándar. Y ya te había explicado que ahí vienen todas las instrucciones de entrada y salida. Trata de quitar esa línea y verás que el compilador de manda errores en las funciones de entrada y salida. Esos archivos son para guardar funciones que se usan mucho y pues es pesado estarlas escribiendo, a veces yo los uso para dividir mis programas en archivos más chicos y fáciles de leer. Al poner el nombre del archivo entre "" le decimos al compilador que lo busque en su propio directorio include, en cambio si le ponemos comillas le damos la ruta completa, por ejemplo #include "c:\\mio.h" busca precisamente ese archivo y no lo busca en otro lado. Si ponemos #include "lynx.h" lo busca en el mismo directorio donde está el código fuente. Vamos a hacer un experimento, teclea el siguiente código. /*contenido de cabeza.h*/ #include int a; int b; int c; /*fin de cabeza.h*/ Guarda este archivo como cabeza.h, dentro del mismo directorio escribe el siguiente código.

17

PARTE I: LO MÁS BÁSICO /*contenido de cabeza.c*/ #include"cabeza.h" int main(int argc, char **args) { a = 5; b = 6; c = a+b; printf("La suma es %d\n", c); return 0; } Ahora guarda este código como cabeza.c en el mismo directorio donde guardaste cabeza.h y compila el programa, básicamente el resultado es el mismo que si hubieras escrito todo en el mismo archivo.

Variables globales y locales y reglas de alcance. Como habrás notado en el ejemplo anterior, las variables a, b y c quedaron afuera de la función main, eso se conoce como variable global, ya que en C todo se divide en funciones cada función tiene sus propias variables, cuando las variables se declaran fuera de todas las funciones, como en el ejemplo que acabamos de ver, estas variables son accesibles desde todas partes del programa, por eso se les llama variables globales. Cuando las variables se declaran dentro de una función, como main por ejemplo, solo son visibles para esa función y no para las demás, por eso se llaman variables locales. En C también existen bloques que pueden tener sus propias variables, veamos el siguiente código y checa el resultado que da. #include int main(int argc, char **args) {/*inicio de un bloque, bloque 1*/

18

PARTE I: LO MÁS BÁSICO int a = 5;/*variable local*/ {/*inicio de un bloque, bloque 2*/ int a = 13;/*otra variable, pero propia del bloque dos, la variable a del bloque 1 es invisible aquí*/ printf("Dentro del bloque 2 a vale %d\n", a); } /*a la salida del bloque 2 a vale 5, nunca cambió su valor*/ printf("Dentro del bloque 1 a vale %d\n", a); return 0; } En este caso hay dos variables a en el programa, pero la a del bloque 2 "oculta" a la a del bloque 1. #include int main(int argc, char **args) {/*bloque 1*/ int a = 5; {/*bloque 2*/ int b = 10; /*dentro del bloque 2 a (del bloque 1) es visible*/ printf("Dentro del bloque 2 a vale %d y b vale %d\n", a, b); } /*fuera del bloque 2 b es invisible, no existe*/ return 0; }

19

PARTE I: LO MÁS BÁSICO Aquí vemos que la variable a es visible en el bloque 2, pero b no es visible en el bloque 1, lo que pasa es que una variable es visible "hacia adentro" de los bloques, pero no hacia afuera, por eso de las variables globales, ya que se declaran muy afuera son visibles en todos los bloques. El truco es que si dentro de un bloque o función declaramos una variable con el mismo nombre que una de "más afuera", la de más afuera no será visible, solo se podrá trabajar con la de más adentro. Tienes que trabajar un poco poniendo a prueba lo que vimos aquí, ya que es la manera en que puedas aprender, este libro es solo la línea de salida para que sepas qué es C y cómo usarlo. Ahora que ya vimos las primicias de C, más o menos como son los errores, variables, etc., podemos pasar a la siguiente parte, que es la programación estructurada.

PARTE II: PROGRAMACIÓN ESTRUCTURADA En un programa que se puede considerar más o menos útil deben existir estructuras de control que permitan tomar decisiones o hacer una tarea repetidas veces. C tiene varias estructuras de control para trabajar con ellas, vamos a empezar por la más sencilla: la decisión.

Condición if-else. A veces es necesario decidir entre dos caminos o dos acciones distintas si una condición, o varias se cumplen, la primer estructura para tomar decisiones que vamos a ver es el if, que a veces es acompañado por else. Para tomar decisiones hay que evaluar condiciones, en C tenemos operadores lógicos y relacionales, son los siguientes. Relacionales: •

Mayor que

>



Menor que

<



Mayor o igual que

>=



Menor o igual que

10) equivale a decir "si num es mayor que 10"*/ if(num > 10) { /*en caso de que num sea mayor que diez imprime el mensaje*/ printf("

Tu numero es mayor que diez\n");

} /*se sale del if y se continua con el programa*/ return 0; } El programa es muy sencillo, simplemente compara el número con diez e imprime un mensaje si es mayor, el funcionamiento de la instrucción if es muy simple, ahora vamos a cambiar un poco el programa, si el número es mayor que diez va a imprimir el mensaje, pero de lo contrario va a imprimir otro mensaje. Cambia el siguiente código... 21

PARTE II: PROGRAMACIÓN ESTRUCTURADA

If(num > 10) { printf(... } /*justo aquí se agrega un else, que quiere decir "de lo contrario"*/ else { printf("

Tu numero es menor o igual a diez\n");

} Al poner esas últimas líneas es como decir "si es mayor que diez haz esto, de lo contrario haz esto otro". Se pueden poner sentencias más complicadas para tomar decisiones, por ejemplo para evaluar rangos de valores. El siguiente pedazo de programa ve que el número esté entre 100 y 200, que serían los límites permitidos, en cazo de que el número este fuera de ese rango será rechazado. If( (num >= 100) && (num = 60) && (cal >>");

gets(NULL); printf("\n

%s\a\n", respuesta());

printf("\n

Pulsa enter para seguir");

getchar(); }while(1); return 0; } char *respuesta(void){ char *resps[] = { "Definitivamente sí :D", "Sí :)", "Busca la respuesta en tu corazón :o", "Es muy probable :)", "Eso me parece bien :D", "Es mejor cambiar de tema :\\", "Concéntrate y vuelve a hacer la pregunta :|", "Cualquier cosa puede suceder :\\", "Las cosas son inciertas :$", "No te puedo contestar ahora :|", "No sé y no me importa :@", "Los ángenles de Charlie fué un churro cinematográfico :o", "Hay cosas que es mejor ignorarlas :o", "Sigue así y no habrá problemas :)", 88

PARTE V: APUNTADORES "Eso me parece mal :|", "Las posibilidades son pocas :(", "Prefiero no contestar :@", "Tal vez no :(", "No :\\", "Definitivamente no :b" }; srand(time(NULL)); return (resps[rand() % N]); } Prueba el programa, a mí me gustó, para ser de menos de cincuenta líneas está bien. Vámonos parte por parte para que cheques el código. char *respuesta(void); Te has de preguntar por qué puse un asterisco en el nombre de la función, lo que pasa es que esta función regresa un apuntador, créeme, no es ningún error. system("cls"); Ya la habíamos visto antes, solo para recordar, la función system llama a los comandos del sistema, aquí lo que se hace es limpiar la pantalla. gets(NULL); Recuerda que gets recibe como parámetro un arreglo de caracteres (char []), aunque en realidad su declaración es gets(char *), para el caso es lo mismo, recibe un apuntador para poner las letras ahí, pero en este caso le mandamos un NULL, así agarra los datos del teclado, pero no los pone en ningún lado. printf("\n

%s\a\n", respuesta());

La función respuesta regresa un apuntador a una cadena, pero se supone que %s recibe un arreglo de caracteres (char []), el caso es lo mismo (te lo repito una ves más, un arreglo es mas o menos un apuntador), respuesta le da a %s su cadena de caracteres para que los imprima.

89

PARTE V: APUNTADORES

char *resps[] = { "Definitivamente sí :D", "Sí :)", ... ... ... "Definitivamente no :b" }; En estas líneas estoy declarando un arreglo, un arreglo de apuntadores a char (char * []). Recuerda que declarar char a[] = "hola" es lo mismo que char *b = "hola", aquí lo que hice fue hacer un "arreglo de apuntadores a char" o un "arreglo de arreglos char" o un "arreglo de cadenas", como lo digas a mi parecer está bien dicho. srand(time(NULL)); return (resps[rand() % N]); Acuérdate de la función para generar números aleatorios, lo que se hace es seleccionar un puntero de entre el arreglo podría ser resps[1] o resps[6], pero es al azar, luego lo mandamos con el return. Para que te quede más claro el asunto de los apuntadores y arreglos mira el siguiente código. #include void pon_cadena(char *a){ while(*a != '\0'){ putchar(*a++); } }

90

PARTE V: APUNTADORES int main(int argc, char **args) { int i = 0; char *rollo[] = { "Hola ", "saltamontes, ", "espero ", "que ", "todo ", "este ", "rollo ", "te ", "sirva ", "de ", "algo ", '\0' }; while(rollo[i] != '\0'){ pon_cadena(rollo[i++]); } pon_cadena("\n"); return 0; } Para que te sea más fácil entenderle este código es más sencillo. La diferencia es que el último elemento del arreglo de apuntadores char es que el último elemento es '\0', se lo puse para que el ciclo donde imprime todo el rollo no se pase, porque de lo contrario imprime el rollo este y luego se pone a imprimir la marca del compilador. Si sé de donde saca esos datos, pero no tiene caso explicártelo todo, solo que son cadenas que el programa tiene perdidas por ahí. 91

PARTE V: APUNTADORES

Argumentos de la línea de comandos. Hay programas que reciben argumentos para realizar su trabajo, por ejemplo el programa del, que tiene que recibir el nombre del archivo que va a eliminar. O el programa mplayer que puede recibir como parámetros qué archivo va a reproducir y si la reproducción va a ser automática. Vamos a escribir un programa que reciba nuestro nombre como parámetro y lo imprima junto con un saludo. #include int main(int argc, char **args){ int i; printf("Hola "); for(i=1; i 1){ leer(argumentos[1]); }else{ char cual[N]; printf("Escribe un nombre de archivo, pero no te pases de %d caracteres\n", N); printf(" >>>"); gets(cual); leer(cual); } return 0; } void leer(char *nombre){ FILE *archivo; int espacios = 0; char caracter = ' '; if((archivo = fopen(nombre, "r")) != NULL){ system("cls"); do{ 97

PARTE VI: ARCHIVOS caracter = getc(archivo); switch(caracter){ case '\n': espacios = espacios/80 + 1; espacios *= 80; break; case '\t': espacios += 8; break; case '\v': espacios += 80; break; case '\b': espacios -= 1; break; case '\r': espacios = espacios/80; espacios *= 80; break; default: espacios++; } if(espacios >= 1760){ putchar('\n'); system("pause"); espacios = 0; system("cls"); } putchar(caracter); }while(caracter != EOF); 98

PARTE VI: ARCHIVOS putchar('\n'); }else{ printf("El archivo no se abrio\n"); } } Creo que no hay mucho que explicar, es un programa de 62 líneas y no tiene nada nuevo para ti. El programa lee los caracteres del archivo y va contando los caracteres que se imprimen en la pantalla, recuerda que estamos trabajando en una consola de 80 x 25, por eso usé tanto el número 80. En el switch lo que hago es analizar los caracteres de entrada, por ejemplo si se recibe un enter ('\n') primero calculo las líneas que se han impreso con espacios = espacios / 80 + 1, al sumarle un uno al último se supone que agrego una línea a las que ya se imprimieron, luego multiplico esto por 80 y ya tengo los espacios de la pantalla que se han utilizado, por ejemplo digamos que llevamos 135 caracteres, si dividimos esto entre 80 nos daría 1 (porque estamos poniéndolo en un int), le sumamos 1 y tenemos dos líneas completas, lo multiplicamos por 80 y obtenemos el espacio que se ha utilizado en la pantalla. Hasta ahora hemos tenido que abrir y cerrar los archivos, para volverlos a abrir, si primero queremos escribir algo en ellos y luego queremos leerlo. Hay otros modos de trabajar con los archivos, los modos son los siguientes. • •

"w" Abre un archivo para escritura, si no existe lo crea y si existe lo borra. "r"

Abre un archivo para lectura, si no existe regresa error.



"a" Crea un archivo si no existe o lo abre para escritura, este modo forza todas las escrituras al final del archivo.



"w+" Abre un archivo para escritura, si no existe lo crea, pero lo trunca al igual que "w", pero aparte se puede leer su contenido cuando se introducen datos.



"r+" Abre un archivo para lectura y escritura, si el archivo no existe la llamada falla.



"a+" Abre el archivo para lectura y escritura, pero todas las escrituras del archivo se añaden al final.

Este programa explica un poco cómo funcionan los modos de cómo abrir un archivo y algo acerca de ciertos archivos especiales.

99

PARTE VI: ARCHIVOS

#include int main(int argc, char **args){ FILE *archivo; char c; archivo = fopen("texto.txt", "w+"); fputs("Este archivo se supone que se abrió como lectura escritura\n", archivo); rewind(archivo); while((c = fgetc(archivo)) != EOF){ putchar(c); } puts("Escribe lo que quieras y termina con un EOF (control + z en dos y control + d en linux)"); while((c = getchar()) != EOF){ putc(c, archivo); } rewind(archivo); fputs("\nEste es el contenido del archivo\n", stdout); while((c = fgetc(archivo)) != EOF){ putchar(c); } 100

PARTE VI: ARCHIVOS

fclose(archivo); archivo = fopen("texto.txt", "a+"); fputs("\n final del archivo\n", archivo); fclose(archivo); return 0; } archivo = fopen("texto.txt", "w+"); Abre el archivo para escribir sobre él (hasta el final del archivo) y para leerlo. rewind(archivo); La función rewind se regresa al principio del archivo para volverlo a leer o para reescribirlo desde el principio. puts("Escribe lo que quieras y termina con un EOF (control + z en dos y control + d en linux)"); while((c = getchar()) != EOF){ putc(c, archivo); } En dos el EOF es control + z, en este ciclo se pueden introducir caracteres directamente sobre el archivo hasta que se detecte un EOF. Te recomiendo que estudies la función int fflush(FILE *nombre_de_archivo), te aseguro que te será de utilidad algún día. fputs("\nEste es el contenido del archivo\n", stdout); Existen tres archivos que siempre usamos y nunca nos damos cuenta, son stdout, stderr y stdin, y se refieren a la salida estándar, el error estándar y la entrada estándar, generalmente son el monitor (stdout y stderr) y el teclado (stdin), pero se pueden re-direccionar a algún otro dispositivo. En esta línea la función fputs imprime la cadena en la pantalla.

101

PARTE VI: ARCHIVOS while((c = fgetc(archivo)) != EOF){ putchar(c); } La función fgetc es similar a getc, solo que ésta sí es una función y getc es una macro. Aunque el resultado es el mismo. fclose(archivo); archivo = fopen("texto.txt", "a+"); fputs("\n final del archivo\n", archivo); fclose(archivo); En estas líneas cerramos el archivo y lo volvemos a abrir en modo append, agregamos la línea "\n final de archivo\n", que se coloca al final por la opción append y por último cerramos el archivo. El siguiente código es un pequeño directorio de búsqueda secuencial, es muy sencillo y es lo último que vamos a ver en esta parte, para ya pasar a la siguiente parte que son las listas enlazadas y otros métodos para la memoria dinámica, en la siguiente parte vamos a trabajar con éste mismo programa, pero va a ser más poderoso. Vamos a ver el código y luego lo vamos a ver parte por parte como siempre. #include #include #include #define D 80 /*largo de los dtos*/ #define NOM 12 /*largo del nombre*/ #define DAT "libro.dat" /*nombre del archivo*/ struct batos{ char nombre[NOM]; char datos[D]; }; 102

PARTE VI: ARCHIVOS

int buscar(char *, FILE *); void f_gets(char *, FILE *); void agregar(FILE *, struct batos); void consultar(FILE *, struct batos); void minuscula(char *a){ if(*a >= 65 && *a n = i; n->sig = NULL; } return n; } /*pone un nuevo elemento en la lista*/ void poner(int *j){ /*el apuntador a la estructura original*/ struct lista *n; if((n = nuevo(*j))!= NULL){ printf(" Se ha creado el elemento %d\n", (*j)++); n->sig = inicio; inicio = n; }else{ printf("No se ha podido agregar otro elemento\n"); } } /*quita un elemento de la lista*/ void quitar(void){ struct lista atras, *s, *q, *p; int cual; printf("Cual es el elemento que quieres quitar? "); scanf("%d", &cual); getchar(); 109

PARTE VII: MEMORIA DINÁMICA atras.sig = inicio; p = &atras; while(p->sig != NULL){ /*si el siguiente es el que se va a eliminar*/ if(p->sig->n == cual){ /*s apuntara al que sigue del que se vava eliminar en caso de que no exista tal s sera NULL*/ s = p->sig->sig; /*q apunta al que se vava eliminar*/ q = p->sig; /*p->sig apunta al que este adelante del que se vava eliminar*/ p->sig = s; /*si el que se va a quitar es el inicio movemos el inicio hacia adelante*/ if(inicio == q) inicio = s; /*liberamos el espacio asignado*/ free(q); /*queda un elemento menos*/ printf("Elemento %d eliminado con exito\n", cual); return; } p = p->sig; } printf("El elemento %d no existe\n", cual); } void recorre(void){ struct lista *p = inicio; while(p != NULL){ printf(" %d->", p->n); 110

PARTE VII: MEMORIA DINÁMICA p = p->sig; } printf(" NULL\n"); } int main(int argc, char **args){ int j = 0; /*el numero del elemento que se va a crear*/ char op; do{ printf(" printf("

LISTA ENLAZADA SIMPLE\n\n"); Menu\n");

printf(" a) Agregar elemento\n"); printf(" b) Eliminar elemento\n"); printf(" c) Recorre la lista\n"); printf(" d) Salir\n"); printf("

Tu opcion: ");

op = getchar(); if(op != '\n'){ getchar(); } switch(op){ case 'a': poner(&j); break; case 'b': quitar(); break; case 'c': recorre(); break; 111

PARTE VII: MEMORIA DINÁMICA } printf("Pulsa enter para seguir"); getchar(); }while(op != 'd'); return 0; } Espero que hayas escrito este programa y que hayas visto como funciona. Todo este libro no te servirá de nada si no has practicado porque no es muy explícito ni muy claro, más bien es práctico y si no haces experimentos no vas a aprender nada. struct lista{ int n; struct lista *sig; }; Esta estructura es auto referenciada, quiere decir que tiene un apuntador que apunta hacia sí misma, aunque más bien este apuntador apunta hacia otra estructura igual. struct lista *nuevo(int i){ /*creamos un nuevo elemento usando malloc*/ struct lista *n = (struct lista *) malloc(sizeof(struct lista)); if(n != NULL){ n->n = i; n->sig = NULL; } return n; } Recuerda que una función puede regresar datos de cualquier tipo, en este caso regresa un apuntador a una estructura. Esta función crea un nuevo elemento de la lista y regresa la dirección de dicho elemento, esto lo hace usando la función 112

PARTE VII: MEMORIA DINÁMICA malloc (void *malloc(size_t)) la función malloc busca un espacio que sea del tamaño adecuado y regresa la dirección del mismo, pero la dirección la regresa por medio de un apuntador genérico, por eso usamos la notación "struct lista *n = (struct lista *) malloc...) struct lista *n es un apuntador a una estructura struct lista, pero lo que malloc nos regresa es un puntero void así que debemos transformarlo a un apuntador estructura lista 5. Nota la manera en que llamo al tipo de dato, no le digo estructura, le digo estructura lista, un apuntador que apunte a una estructura a no puede apuntar a una estructura b porque son tipos de datos distintos, ten presente eso. Por otro lado la función malloc necesita recibir como parámetro el tamaño de memoria que debe apartar, pero no sabemos cuanto mide la estructura, por eso usamos la función sizeof(), esta función forma parte de C y regresa el tamaño de lo que le metamos adentro, por ejemplo sizeof(int) nos regresa un 16, o sea 16 bits de espacio en memoria. Por eso escribimos ...malloc(sizeof(struct lista)) así calculamos el espacio necesario y se lo mandamos a malloc para que el se encargue de todo lo demás. Cuando malloc puede apartar un espacio en la memoria nos regresa la dirección, pero si no puede nos regresa un valor NULL, por eso hice la prueba en el if, si n != NULL quiere decir que malloc lo logró y que ya tenemos una estructura nueva para trabajar. Dentro de ese if ponemos los valores adecuados en la estructura nueva, pero fíjate en la forma en que trabajo con los datos de la estructura, antes lo hacíamos con un punto, pero eso es cuando no estamos con un apuntador, cuando usamos la estructura con un apuntador debemos usar una flecha -> (guión y mayor qué). Al final de todo regresamos la dirección de la nueva estructura con el return. Acuérdate que *n es el dato en sí, pero n es la pura dirección. void poner(int *j){ /*el apuntador a la estructura original*/ struct lista *n; if((n = nuevo(*j))!= NULL){ printf(" Se ha creado el elemento %d\n", (*j)++); n->sig = inicio; inicio = n; }else{ printf("No se ha podido agregar otro elemento\n"); } 5

Ahora con el casting implícito no es necesario, pero puede hacer el código más legible.

113

PARTE VII: MEMORIA DINÁMICA } En la función que pone un elemento nuevo en la lista si es que se pudo crear if((n = nuevo(*j))!= NULL). Básicamente lo que hace es colocar la nueva estructura al principio, de tal suerte que lo que estaba al principio ahora está en segundo lugar n->sig = inicio y luego se hace que el inicio de la lista sea el elemento nuevo inicio = n. void quitar(void){ struct lista atras, *s, *q, *p; int cual; printf("Cual es el elemento que quieres quitar? "); scanf("%d", &cual); getchar(); atras.sig = inicio; p = &atras; while(p->sig != NULL){ /*si el siguiente es el que se va a eliminar*/ if(p->sig->n == cual){ /*s apuntara al que sigue del que se vava eliminar en caso de que no exista tal s sera NULL*/ s = p->sig->sig; /*q apunta al que se vava eliminar*/ q = p->sig; /*p->sig apunta al que este adelante del que se vava eliminar*/ p->sig = s; /*si el que se va a quitar es el inicio movemos el inicio hacia adelante*/ if(inicio == q) inicio = s; /*liberamos el espacio asignado*/ free(q); 114

PARTE VII: MEMORIA DINÁMICA /*queda un elemento menos*/ printf("Elemento %d eliminado con exito\n", cual); return; } p = p->sig; } printf("El elemento %d no existe\n", cual); } Esta función para quitar elementos es más complicada, cuando apenas estaba aprendiendo no podía encontrar códigos que explicaran como quitar elementos. El chiste es que el apuntador que está atrás del elemento que se va a eliminar (que apunta a ese elemento) deje de apuntar al elemento que se va a eliminar y apunte al que está después del que se va a eliminar. Checa el código donde se busca al elemento que se va a quitar, checa el if donde está la condición p->sig>n == cual, esto es válido, así se puede ver si la estructura de adelante es la que se busca sin estar ahí, es el trabajo sucio de los apuntadores, el fisgonear, como en los archivos. En el caso de que sí sea la estructura que queremos quitar se hace toda la faramalla de cambiar el apuntador anterior para que apunte a la siguiente estructura (la que está adelante de la que vamos a quitar), después de esto se hace que q apunte a la estructura que se va a eliminar y liberamos ese espacio con free(void *) que libera el espacio que se apartó con malloc, después de esto nos salimos de la función con un return. Si terminamos el ciclo y nunca se eliminó la estructura deseada pues quiere decir que no existe. void recorre(void){ struct lista *p = inicio; while(p != NULL){ printf(" %d->", p->n); p = p->sig; } printf(" NULL\n"); } La función más fácil de todas, esta función recorre toda la lista mientras y va imprimiendo los valores que están en ella. 115

PARTE VII: MEMORIA DINÁMICA Todo este trabajo no se puede hacer sin apuntadores, te recomiendo que revisas el código varias veces para que le termines de entender, porque con esta explicación dudo que te haya quedado del todo claro. Se supone que estos son temas muy difíciles. Hay un detalle que quiero que veas, podemos movernos hacia adelante con ptr = ptr->siguiente, pero si quisiéramos movernos hacia atrás no podríamos, no hay modo.

Árboles binarios. Ya llegamos a la recta final, el último programa que te voy a enseñar es uno de los que más me han gustado, es la versión de nuestro directorio que tiene memoria dinámica, este programa va a ordenar todos los elementos en orden alfabético y va a poder eliminar elementos o actualizarlos. Antes que nada es necesario explicarte los árboles binarios, un árbol binario es el árbol donde cada nodo puede tener dos o menos hijos y solo un padre, aparte los elementos están ordenados de modo que los menores a la raíz van en el subárbol izquierdo y los mayores en el subárbol derecho. El recorrido en orden es en el cual se recorre el subárbol izquierdo, luego la raíz y al final el subárbol derecho. Te recomiendo que antes de ver este programa repases lo que es la recursividad y el uso de cadenas. #include #include #include #include #define NOM 12 #define D 65 #define DAT "libro.dat" struct binario{ char nombre[NOM]; char datos[D]; int valido; struct binario *i; struct binario *d; 116

PARTE VII: MEMORIA DINÁMICA }; struct binario *raiz = NULL; /*crea una nueva estructura y regresa su direccion*/ struct binario *nuevo(char *nombre, char *datos){ struct binario *n = malloc(sizeof(struct binario)); if(n){ strcpy(n->nombre, nombre); strcpy(n->datos, datos); n->i = NULL; n->d = NULL; n->valido = 1; } return n; } /*coloca el nuevo elemento en el lugar indicado*/ void colocar(struct binario *hoja, char *nombre, char *datos){ char op; int i; /*primero ver si el arbol no está vacío*/ if(raiz){ /*comparar los dos nombres*/ i = strcmp(hoja->nombre, nombre); if(i){ /*si el nombre va despues de raiz->nombre*/ if(i < 0){ /*si hay hijo derecho*/ if(hoja->d != NULL){ 117

PARTE VII: MEMORIA DINÁMICA /*ponemos el nuevo nombre e el lado derecho*/ colocar(hoja->d, nombre, datos); }else{ hoja->d = nuevo(nombre, datos); } }else{ if(hoja->i != NULL){ colocar(hoja->i, nombre, datos); }else{ hoja->i = nuevo(nombre, datos); } } }else{ printf(" Ese nombre ya existe, pulsa s + enter para actualizar sus datos "); op = getchar(); if(op != '\n'){ getchar(); } if(op == 's'){ strcpy(hoja->datos, datos); } } }else{ raiz = nuevo(nombre, datos); } } /*cambia el contenido de una cadena a mayusculas*/ void mayusculas(char *s){ while(*s != '\0'){ 118

PARTE VII: MEMORIA DINÁMICA *s = toupper(*s); s++; } } /*se encarga de agregar nuevos elementos*/ void agregar(void){ char nombre[NOM]; char datos[D]; printf(" Nombre: "); fgets(nombre, NOM, stdin); mayusculas(nombre); printf(" Datos: "); fgets(datos, D, stdin); colocar(raiz, nombre, datos); } /*carga el archivo DAT en la memoria*/ int cargar(void){ FILE *a = fopen(DAT, "r"); if(a != NULL){ char nombre[NOM]; char datos[D]; do{ fgets(nombre, NOM, a); fgets(datos, D, a); if(!feof(a)){ colocar(raiz, nombre, datos); } }while(!feof(a)); 119

PARTE VII: MEMORIA DINÁMICA fclose(a); return 0; }else{ return 1; } } /*recorre el arbol y guarda cada elemento en el archivo DAT*/ void archivar(struct binario *hoja, FILE *a){ if(hoja && a){ if(hoja->i != NULL){ archivar(hoja->i, a); } if(hoja->valido){ fprintf(a, "%s", hoja->nombre); fprintf(a, "%s", hoja->datos); } if(hoja->d != NULL){ archivar(hoja->d, a); } } } /*guarda el contenido de la memoria en el archivo DAT usando la funcion archivar*/ void guardar(void){ FILE *a = fopen(DAT, "w"); if(a != NULL){ if(raiz){ archivar(raiz, a); 120

PARTE VII: MEMORIA DINÁMICA } fclose(a); }else{ printf(" No es posible guardar la informacion\n"); } } /*busca un nombre y regresa su direcci¢n*/ struct binario *buscar(struct binario *hoja, char *s){ if(hoja){ int i = strcmp(hoja->nombre, s); if(i != 0){ /*si s esta a la derecha*/ if(i < 0){ if(hoja->d != NULL){ return(buscar(hoja->d, s)); }else{ return NULL; } }else{ if(hoja->i != NULL){ return(buscar(hoja->i, s)); }else{ return NULL; } } }else{ return (hoja->valido?hoja:NULL); } }else{ 121

PARTE VII: MEMORIA DINÁMICA return NULL; } } /*imprime los datos de una persona en la pantalla*/ void ver(void){ char nombre[NOM]; struct binario *p; printf(" Cual es el nombre que buscas? "); fgets(nombre, NOM, stdin); mayusculas(nombre); p = buscar(raiz, nombre); if(p){ printf("\n\t%s\n", p->nombre); printf("

Datos: %s\n", p->datos);

}else{ printf(" El nombre %s no existe\n", nombre); } } /*utiliza la funcion buscar para modificar los datos*/ void modificar(void){ struct binario *p; char nombre[NOM]; printf(" Cual es el nombre que quieres actualizar? "); fgets(nombre, NOM, stdin); mayusculas(nombre); if((p = buscar(raiz, nombre))){ printf(" Introduce los nuevos datos: "); fgets(p->datos, D, stdin); 122

PARTE VII: MEMORIA DINÁMICA }else{ printf(" El nombre %s no existe\n", nombre); } } /*elimina un elemento*/ void eliminar(void){ char nombre[NOM]; struct binario *p; printf(" Que nombre quieres eliminar? "); fgets(nombre, NOM, stdin); mayusculas(nombre); if((p = buscar(raiz, nombre))){ p->valido = 0; printf(" %s será eliminado al salir del programa\n", nombre); }else{ printf(" El nombre %s no existe\n", nombre); } } /*recorre el arbol n orden alfabetico*/ void recorre(struct binario *hoja, int *i, int *j){ if(hoja){ if(hoja->i != NULL){ recorre(hoja->i, i, j); } if(*i >= 20){ printf("

Pulsa enter para continuar");

getchar(); *i = 0; 123

PARTE VII: MEMORIA DINÁMICA } if(hoja->valido){ (*j)++; printf("

%s\n", hoja->nombre);

printf(" Datos: %s\n\n", hoja->datos); *i += 3; } if(hoja->d != NULL){ recorre(hoja->d, i, j); } }else{ printf(" El arbol está vacío\n\n"); } } int main(int argc, char **args){ FILE *aux; char op; int i, j; raiz = NULL; if(cargar() == 0){ do{ i = j = 0; printf(" printf("

DIRECTORIO\n"); Menu\n");

printf(" a) Agregar\n"); printf(" b) Buscar\n"); printf(" c) Modificar\n"); printf(" d) Eliminar\n"); printf(" e) Recorrer el directorio\n"); 124

PARTE VII: MEMORIA DINÁMICA printf(" f) Salir\n"); printf("

Tu opcion: ");

op = getchar(); if(op != '\n'){ getchar(); } switch(op){ case 'a': agregar(); break; case 'b': ver(); break; case 'c': modificar(); break; case 'd': eliminar(); break; case 'e': recorre(raiz, &i, &j); printf(" Hay %d personas en el directirio\n", j); break; case 'f': continue; default: printf(" Tecla incorrecta\n"); } printf("Pulsa enter para seguir"); getchar(); 125

PARTE VII: MEMORIA DINÁMICA }while(op != 'f'); guardar(); printf("

Hasta luego :)\n");

}else{ if((aux = fopen(DAT, "w")) != NULL){ printf(" El archivo %s no se puede abrir, se ha creado el archivo ", DAT); printf("pero necesitas reiniciar el programa\n"); }else{ printf(" No se encuentra el archivo %s\n", DAT); } } return 0; } Modestia aparte este programita me quedó muy bien, lo he estado revisando y no tiene bugs, tenía uno, pero lo encontré. Pruébalo y ve qué tal, es como una base de datos muy (muchísimo) primitiva, pero los árboles se usan como no tengas una idea y son lo que se usa en las bases de datos. Esto es lo último que vamos a ver, fue el programa de despedida de este pequeño curso. Te lo voy a explicar con todo detalle para que sea tu graduación, hay mucho más del lenguaje, pero solo son librerías que no vimos aquí, lo demás ya lo sabes y no te costará mucho trabajo aprender lo que sigue. Te voy a explicar pues este programa. #include #include #include #include Las librerías que se van a usar, como vamos a trabajar mucho con cadenas de caracteres necesitamos la librería string.h, aunque solo se van a usar dos funciones de aquí. Además de esto incluimos la librería ctype.h, en la cual se incluyen algunas funciones de manejo de caracteres.

126

PARTE VII: MEMORIA DINÁMICA #define NOM 12 #define D 65 #define DAT "libro.dat" Definimos el largo del nombre, de los datos y el nombre del programa. Si, también se pueden definir cadenas de datos. struct binario{ char nombre[NOM]; char datos[D]; int valido; struct binario *i; struct binario *d; }; Esta estructura tiene los datos que se van a usar en nuestra base de datos. La variable int valido va a valer 1 cuando el elemento esté normal, pero valdrá 0 cuando el elemento se vaya a eliminar. Nota que ahora tiene apuntadores para el hijo izquierdo y el hijo derecho. void colocar(struct binario *hoja, char *nombre, char *datos){ char op; int i; /*primero ver si el arbol no está vacío*/ if(raiz){ /*comparar los dos nombres*/ i = strcmp(hoja->nombre, nombre); if(i){ /*si el nombre va despues de raiz->nombre*/ if(i < 0){ /*si hay hijo derecho*/ if(hoja->d != NULL){ /*ponemos el nuevo nombre e el lado derecho*/ 127

PARTE VII: MEMORIA DINÁMICA colocar(hoja->d, nombre, datos); }else{ hoja->d = nuevo(nombre, datos); } }else{ if(hoja->i != NULL){ colocar(hoja->i, nombre, datos); }else{ hoja->i = nuevo(nombre, datos); } } }else{ printf(" Ese nombre ya existe, pulsa s + enter para actualizar sus datos "); op = getchar(); if(op != '\n'){ getchar(); } if(op == 's'){ strcpy(hoja->datos, datos); } } }else{ raiz = nuevo(nombre, datos); } } En el árbol binario el hijo de la izquierda debe ser menor a la raíz y el hijo de la derecha debe ser mayor que la raíz, y así es para cada nodo del árbol, esta función es recursiva y se basa en esa regla la función strcmp compara las dos cadenas, si la primera es menor regresa un número negativo, si es mayor regresa un número positivo y si son iguales regresa un 0. Revisa bien el código, si son diferentes la función ve de que lado va, revisa si el nodo tiene un hijo 128

PARTE VII: MEMORIA DINÁMICA izquierdo (en caso de que el nombre que queremos meter vaya de ese lado), si no lo tiene pone el nuevo elemento como hijo izquierdo, eso no requiere mucha explicación, pero si lo tiene, tiene que recurrir a sí misma y le dice a la función que busque a partir de ese hijo, lo hace así "colocar(raíz->i, nombre, datos)", la función hace lo mismo para el caso de que el nombre vaya del lado derecho. Si el nombre es encontrado la función pregunta si los datos deben ser actualizados. struct binario *nuevo(char *nombre, char *datos){ struct binario *n = malloc(sizeof(struct binario)); if(n){ strcpy(n->nombre, nombre); strcpy(n->datos, datos); n->i = NULL; n->d = NULL; n->valido = 1; } return n; } Esta función ya la conocemos bien, pero observa la línea que dice n->valido = 1, eso quiere decir que el elemento está activo, o sea que no ha sido borrado. int cargar(void){ FILE *a = fopen(DAT, "r"); if(a != NULL){ char nombre[NOM]; char datos[D]; do{ fgets(nombre, NOM, a); fgets(datos, D, a); if(!feof(a)){ colocar(raiz, nombre, datos); } 129

PARTE VII: MEMORIA DINÁMICA }while(!feof(a)); fclose(a); return 1; }else{ return 0; } } Esta función se encarga de volcar el contenido del archivo en la memoria, lee las cadenas del archivo y luego las coloca usando la función colocar. void agregar(void){ char nombre[NOM]; char datos[D]; printf(" Nombre: "); fgets(nombre, NOM, stdin); mayusculas(nombre); printf(" Datos: "); fgets(datos, D, stdin); colocar(raiz, nombre, datos); } Funciona igual que la función cargar, solo que los datos los lee desde el teclado y no del archivo. void mayusculas(char *s){ while(*s != '\0'){ *s = toupper(*s); s++; } } Convierte el contenido de una cadena a mayúsculas, es sencillo el cómo funciona, simplemente convierte hasta que se topa con un fin de cadena. Utilia la 130

PARTE VII: MEMORIA DINÁMICA función toupper para convertir un caracer a mayúsculas void guardar(void){ FILE *a = fopen(DAT, "w"); if(a != NULL){ if(raiz){ archivar(raiz, a); } fclose(a); }else{ printf(" No es posible guardar la informacion\n"); } } guardar vuelca el contenido de la memoria al archivo, así guarda todos los datos actualizados y en orden alfabético. Utiliza a la función archivar que viene siendo el motor, como la función colocar. void archivar(struct binario *hoja, FILE *a){ if(hoja && a){ if(hoja->i != NULL){ archivar(hoja->i, a); } if(hoja->valido){ fprintf(a, "%s", hoja->nombre); fprintf(a, "%s", hoja->datos); } if(hoja->d != NULL){ archivar(hoja->d, a); } }

131

PARTE VII: MEMORIA DINÁMICA } La función archivar hace un recorrido en orden, o sea que recorre el subárbol izquierdo, luego la raíz y al final el subárbol derecho. Es muy simple y también es recursiva. Primero revisa si hay hijo izquierdo, si lo hay se va para allá con archivar(hoja->i, a), también le pasa el archivo donde debe escribir los datos. Luego que termina con el subárbol izquierdo se va a la raíz y ve si el elemento no se va a borrar con if(raiz->valido), si no se va a borrar lo guarda en el archivo con las funciones fprintf, si el elemento sí se va a borrar simplemente no lo guarda y se quita de problemas. Ya que trabajó en la raíz se va a ver que onda con el subárbol derecho. void ver(void){ char nombre[NOM]; struct binario *p; printf(" Cual es el nombre que buscas? "); fgets(nombre, NOM, stdin); mayusculas(nombre); p = buscar(raiz, nombre); if(p){ printf("\n\t%s\n", p->nombre); printf("

Datos: %s\n", p->datos);

}else{ printf(" El nombre %s no existe\n", nombre); } } Esta función utiliza a la función buscar para que le diga la posición del nombre que busca, si la encuentra imprime la información, si no avisa que el nombre no existe. struct binario *buscar(struct binario *hoja, char *s){ if(hoja){ int i = strcmp(hoja->nombre, s); if(i != 0){ 132

PARTE VII: MEMORIA DINÁMICA /*si s esta a la derecha*/ if(i < 0){ if(hoja->d != NULL){ return(buscar(hoja->d, s)); }else{ return NULL; } }else{ if(hoja->i != NULL){ return(buscar(hoja->i, s)); }else{ return NULL; } } }else{ return (hoja->valido?hoja:NULL); } }else{ return NULL; } } Buscar toma la cadena que va a buscar y hace lo mismo que la función para colocar los datos, compara los nombres, si el que busca está a la izquierda se va a ese lado, si está a la derecha se va a ese otro, por ejemplo si el nombre debiera estar a la izquierda y no hay hijo izquierdo regresa un NULL, pero si sí hay un hijo izquierdo trata de buscarlo ahí. En el caso de que lo encuentre regresa la dirección de donde está. void modificar(void){ struct binario *p; char nombre[NOM]; 133

PARTE VII: MEMORIA DINÁMICA printf(" Cual es el nombre que quieres actualizar? "); fgets(nombre, NOM, stdin); mayusculas(nombre); if((p = buscar(raiz, nombre))){ printf(" Introduce los nuevos datos: "); fgets(p->datos, D, stdin); }else{ printf(" El nombre %s no existe\n", nombre); } } Para modificar los datos de una persona usa la función buscar para que le diga donde está, si la encuentra le cambia los datos directamente. void eliminar(void){ char nombre[NOM]; struct binario *p; printf(" Que nombre quieres eliminar? "); fgets(nombre, NOM, stdin); mayusculas(nombre); if((p = buscar(raiz, nombre))){ p->valido = 0; printf(" %s será eliminado al salir del programa\n", nombre); }else{ printf(" El nombre %s no existe\n", nombre); } } Cuando se elimina a una persona del directorio simplemente se busca con buscar y luego se cambia el valor de valido a 0 para indicar que ese dato se debe eliminar. void recorre(struct binario *hoja, int *i, int *j){ 134

PARTE VII: MEMORIA DINÁMICA if(hoja){ if(hoja->i != NULL){ recorre(hoja->i, i, j); } if(*i >= 20){ printf("

Pulsa enter para continuar");

getchar(); *i = 0; } if(hoja->valido){ (*j)++; printf("

%s\n", hoja->nombre);

printf(" Datos: %s\n\n", hoja->datos); *i += 3; } if(hoja->d != NULL){ recorre(hoja->d, i, j); } }else{ printf(" El arbol está vacío\n\n"); } } Recorre hace un recorrido en orden, o sea que ve que onda con el subárbol izquierdo, visita la raíz y luego se va al lado derecho. Pues eso fue todo, hemos terminado, espero que esto te haya servido, fue algo intensivo, pero me pareció bastante conciso y traté de hacerlo lo más entendible posible, aunque creo que no me salió bien en algunas partes del libro, pero te da las bases para que practiques, así fue como yo aprendí también, practicando y cometiendo errores, durante este libro aprendí mucho y descubrí que sabía cosas que ignoraba que sabía. Espero que te pase lo mismo a ti también, sigue practicando, este libro no inculca el amor al lenguaje, pero muestra lo flexible y 135

PARTE VII: MEMORIA DINÁMICA poderoso que puede ser. Solo fue una embarrada del pastel, pero lo demás te toca a ti. La siguiente parte es un apéndice con algunas funciones que te pueden resultar útiles y otras cosas que no se vieron en el trayecto.

APÉNDICE A: LA BIBLIOTECA ESTANDAR Lo siguiente es un resumen del apéndice B del libro "El lenguaje de programación C" de Brian Kernighan y Dennis Ritchie, solo puse algunas funciones de las que vienen en el libro, cuando hice el resumen encontré un par de errores, los corregí, pero lo demás puede tener falla, las funciones no están explicadas al cien por ciento, en parte porque esto ya no es de mi autoría y porque considero que es mejor que te apegues a la librería de tu compilador, porque aunque esta librería es estándar hay funciones que no son del todo iguales, aparte algunas no están en todos los compiladores. Comprobé algunas funciones usando mi compilador, algunas explicaciones no están basadas en el libro de Ritchie sino que las basé en la ayuda de mi compilador, que es el Pacific C de Hi-Tech software. Aclarando, las librerías de la biblioteca estándar no son parte del lenguaje en sí, pero al ser la librería estándar vienen en todos los compiladores como parte integral del lenguaje C. Al haber una biblioteca estándar también puede haber una biblioteca propia con funciones que sirvan para nuestros fines particulares, por eso se dice que el lenguaje C es extensible, ya que se pueden extender sus alcances. stdio.h

Archivos. FILE *fopen(char *nombre, char *modo) Abre un archivo con el nombre y el modo especificado, si el archivo se puede abrir regresa el apuntador y si no se puede regresa un NULL, los modos son los siguientes. •

"r" Abre el archivo como solo lectura, si el archivo no existe regresa un NULL.



"r+" Abre el archivo como lectura y escritura, igual si el archivo no existe la llamada falla.



"w" Crea un archivo nuevo, en caso de que el archivo exista lo sobre escribe borrando su contenido, el archivo se abre para escritura.



"w+" Igual que el modo w, pero aparte permite escribir en el archivo.



"a" Abre el archivo para escritura o lo crea si no existe, todas las 136

APÉNDICE A: LA BIBLIOTECA ESTANDAR escrituras en el archivo se forzan a ir hasta el final. •

"a+" Aparte de modo escritura abre el archivo para lectura, funciona igual que a pero con la opción de leer.

Aparte de estas llamadas existe la opción b, esta opción se usa para archivos binarios, una llamada "w+b" o "wb+" crearía un archivo para escribir y leer de el en modo binario. int fflush(FILE *nombre) Escribe en el archivo de salida todos los datos que estén almacenados en el buffer asociado, esto para dejar el buffer despejado. Regresa un EOF si existe algún error o un 0 si todo salió bien. int fclose(FILE *nombre) Cierra el archivo especificado, si hay algún error regresa un EOF o 0 si todo está bien. int remove(char *nombre) Elimina el archivo nombre del directorio. Regresa 0 cuando no hay error. int rename(char *nombre, char nuevo_nombre) Renombra un archivo según las cadenas introducidas, regresa 0 si todo sale bien. FILE *freopen(const char *archivo, const char *modo, FILE *archivo2) Reabre archivo con el modo estipulado y asocia al archivo2 con el, o sea que freopen("archivo.txt", "w", stdout) re-direcciona la salida stdout a archivo.txt, así que al poner printf("Hola a todos :)\n") la salida no se imprime en la pantalla sino en archivo.txt. freopen regresa un apuntador a archivo.

Salida con formato. int fprintf(FILE *archivo, const char *formato, ...) Regresa el número de caracteres escritos o un valor negativo si ocurre algún error. Los formatos empiezan con % y terminan con un caracter de conversión. Tiene banderas que son las siguientes. •

+ Indica que el numero siempre sea impreso con signo, un printf("%+d", 6) imprimiría +6.



Espacio, si el primer caracter no es un signo imprime un espacio.



# Es una forma alternativa para la impresión, %#x antepone un 0x a los números hexadecimales, el %#o antepone un 0 al principio de los 137

APÉNDICE A: LA BIBLIOTECA ESTANDAR octales, para g, G, E y f siempre e pondrá un punto decimal. Hay conversiones para printf como d, c o f, aparte están las siguientes. •

d,i

int, notación decimal con signo.



o

int, notación octal sin signo.



x,X

int, Notación hexadecimal sin signo.



u

int, notación decimal sin signo.



c

int, caracter sin signo después de ser convertido a unsigned char.



s char *, se imprime la cadena hasta encontrar un '\0' o hasta la cantidad de caracteres indicados por la precisión.



f double, notación decimal, la precisión se puede estipular como %.2f para limitar la salida a dos decimales, la precisión por defecto son seis decimales.



e,E double, notación científica. Imprime un número y luego los decimales, la precisión se define igual que en %f.



g,G double, imprime como %f si la precisión es menor que -4 o mayor o igual que la precisión estipulada, la precisión se escribe igual que con %f.



%

Se imprime como %.

int printf(const char *formato, ...) Es lo equivalente a fprintf(stdout, ...) *NOTA: En el caso de mi compilador para escribir un entero largo (long) la conversión es %ld, para imprimir un entero largo en notación decimal. Por ejemplo para escribirlo en notación octal sería %lo.

Entrada con formato. int fscanf(FILE *archivo, const char *formato, ...) Lee del archivo especificado y convierte la entrada a lo que se pide en el formato, regresa EOF si ocurre algún error, de lo contrario regresa el número de elementos de entrada convertidos y asignados. Básicamente funcione igual que fprintf, solo que con datos de entrada y no de salida. int scanf(const char *formato, ...) Es como fscanf(stdin, const char *formato, ...)

138

APÉNDICE A: LA BIBLIOTECA ESTANDAR

Entrada y salida de caracteres. int fgetc(FILE *archivo) Regresa el siguiente caracter del archivo o un EOF si hay fin de archivo o algún error. char *fgets(char *cadena, int n, FILE *archivo) Lee los caracteres del archivo hasta que sean n-1 caracteres leídos o hasta encontrar un '\n' y los almacena en char *cadena. fgets regresa EOF en el fin de archivo o en caso de error. int fputc(int c, FILE *archivo) Escribe el caracter en el archivo, regresa un EOF si hay error o el caracter escrito. int fputs(char *s, FILE *archivo) Escribe una cadena en el archivo, regresa un valor no negativo o un EOF si hay un error. int getc(FILE *archivo) Lee un caracter del archivo y lo regresa. int getchar(void) Es como getc(stdin). char *gets(char *cadena) Lee caracteres de la entrada estándar y los pone en la cadena, regresa cuando hay un '\n' y lo sustituye por un '\0'. El valor de retorno es el apuntador a la cadena o un NULL si ocurre un EOF o un error. int putc(int c, FILE *archivo) Es lo equivalente a putc, pero es una macro. int putchar(int c) Es lo equivalente a putc(c, stdout). int puts(const char *s) Escribe la cadena mas un '\n' en la salida estándar, regresa EOF si ocurre algún error o un número positivo si todo sale bien. int ungetc(int c, FILE *archivo) Regresa el último caracter leído de nuevo al archivo, de donde será regresado en la próxima lectura. Solo se puede regresar un caracter por archivo, EOF no se puede devolver. La función regresa un EOF si hay un error o el caracter 139

APÉNDICE A: LA BIBLIOTECA ESTANDAR regresado.

Funciones de entrada y salida directa. size_t fread(void *apuntador, size_t tamaño, size_t n_objetos, FILE *archivo) Lee directamente desde el archivo y regresa la cantidad de objetos que se pudieron leer. Donde void *apuntador es donde va a colocar la información leída, size_t tamaño es el tamaño de lo que va a leer, size_t n_objetos es cuantas veces va a leer antes de poner la información en el apuntador y FILE *archivo es donde la va a poner. Escribir fread(cadena, sizeof(cadena), 2, archivo) hace que fread lea el tamaño de cadena del archivo dos veces y almacene el último valor leído en cadena. Por ejemplo para leer una estructura se puede hacer así: struct estructura{ ... ... }s; fread(&s, sizeof(struct estructura), 1, archivo) Así fread lee el contenido del archivo hasta que cumple con el tamaño especificado y coloca la información leída en la estructura, esto lo haría una sola vez. size_t fwrite(void *apuntador, size_t tamaño, size_t n_objetos, FILE *archivo) Funciona igual que fread, pero para escribir sobre un archivo.

Funciones de colocación en archivos. int fseek(FILE *archivo, long offset, int origen) Fija la posición en el archivo especificado, donde offset es el desplazamiento dentro del archivo, o sea cuanto nos tenemos que desplazar dentro del archivo para llegar a la posición deseada. Origen es de donde empezamos a buscar, 0 es empezar desde el principio del archivo, 1 es empezar desde la posición actual y 2 es empezar desde al final del archivo. Así que escribir fseek(archivo, 2, 1) es decirle a fseek que avance dos bytes a partir de la posición actual. fseek regresa un valor diferente de 0 en caso de error. 140

APÉNDICE A: LA BIBLIOTECA ESTANDAR long ftell(FILE *archivo) Regresa la posición actual del apuntador del archivo o -1 en caso de error. void rewind(FILE *archivo) Regresa el apuntador del archivo hasta el principio, equivale a { fseek(archivo, 0, 0); clearerr(archivo) }

Funciones de error. void clearerr(FILE *archivo) Limpia los indicadores de error y de EOF para el archivo. int feof(FILE *archivo) Regresa un valor diferente de cero si encuentra un fin de archivo. int ferror(FILE *archivo) Igual que feof, pero para el error. * NOTA: En el caso de mi compilador clearerr es clrerr y clreof, donde clrerr limpia el error y clreof limpia el estado de EOF.

string.h char *strcpy(char *destino, char *fuente) Copia la cadena fuente en la cadena destino incluyendo el '\0', regresa destino. char *strncpy(char *destino, char *fuente, size_t n) Copia la cadena fuente en destino hasta n caracteres. char *strcat(char *destino, char *fuente) Concatena las cadenas destino y fuente en destino. char *strncat(char *destino, char *fuente, size_t n) Igual, pero concatena hasta n caracteres. int strcmp(char *c1, char *c2) Compara c1 con c2, si c1 es menor regresa un número negativo, si c1 es mayor regresa un número positivo, si son iguales regresa cero.

141

APÉNDICE A: LA BIBLIOTECA ESTANDAR int strncmp(char *c1, char *c2, size_t n) Igual que strcmp, pero compara hasta n caracteres. char *strchr(char *c1, char c) Regresa un apuntador a la primer ocurrencia de c en ct o NULL si no hay. char strrchr(char *c1, char c) Regresa un apuntador a la última ocurrencia de c en ct o NULL si no hay. size_t strlen(char c1) Regresa la longitud de c1. Las funciones mem... manipulan objetos como cadenas de caracteres. void *memcpy(void *arreglo1, void *arreglo2, size_t n) Copia n caracteres de arreglo2 a arreglo1 y regresa arreglo1. int memcmp(void *arreglo1, void *arreglo2, size_t n) Regresa lo mismo que strcmp o strncmp, comparando hasta n objetos, pero no se detiene al encontrar un '\0'. void *memset(void *arreglo, char c, size_t n) Coloca el caracter c en las primeras n posiciones del arreglo.

math.h Todas las funciones trigonométricas están en radianes. double sin(double x) Seno de x. double cos(double x) Coseno de x. double tan(double x) Tangente de x. double asin(double x) Inverso del seno en el rango de -pi/2 a pi/2. double acos(double x) Inverso del coseno de x en el rango de 0 a pi. 142

APÉNDICE A: LA BIBLIOTECA ESTANDAR double atan(double x) Inverso tangente de x en el rango de -pi/2 a pi/2. double atan2(doublex, double y) Inverso tangente de x/y en al rango de -pi a pi. double sinh(double x) Seno hiperbólico de x. double cosh(double x) Coseno hiperbólico. double tanh(double x) Tangente hiperbólica. double exp(double x) e a la x. double log(double x) Logaritmo natural de x. double log10(double x) Logaritmo base 10. double pow(double x, double y) Potencia de x a la y. double sqrt(double x) Raíz cuadrada de x. double ceil(double x) Regresa el entero no mayor que x. double floor(double x) El entero no menor que x. double fabs(double x) Valor absoluto de x double ldexp(double x, int n) Regresa x·2^n. double frexp(double x, int *exp) Divide a x en una fracción normalizada dentro del rango 1/2 a 1, este valor se 143

APÉNDICE A: LA BIBLIOTECA ESTANDAR regresa, y una potencia de 2 se almacena en exp. double modf(double x, double *ip) divide a x en una parte entera y otra fraccionaria, cada una con el mismo signo que x. Almacena la parte entera en *ip y regresa la parte fraccionaria. double fmod(double x, double y) Regresa el modulo flotante de x/y.

stdlib.h int rand(void) Devuelve un número pseudoaleatorio entre 0 y RAND_MAX, que suele ser 32767. void srand(unsigned int seed) Siembra una semilla para crear una nueva secuencia de números pseudoaleatorios usando a seed. void *calloc(size_t nobj, size_t size) Devuelve un apuntador al espacio para un arreglo de nobj objetos, cada uno de tamaño size. Regresa NULL si no puede encontrar el espacio, mismo que se inicia con ceros. void *malloc(size_t size) Regresa un apuntador a un espacio de tamaño size o NULL si no puede hacerlo. void realloc(void *p, size_t size) Cambia el tamaño del objeto al que apunta p, si la llamada es efectiva realloc regresa el apuntador al espacio nuevo, si no regresa NULL, en este caso p no cambia. void free(void *p) Libera el espacio a donde apunta p, debe ser un espacio asignado por malloc, calloc o realloc. void exit(int estado) Termina el programa y regresa al invocador mandándole el estado de salida, el estado de salida depende del sistema. int system(const char *s) Pasa la cadena al entorno para que la ejecute, el valor de regreso depende de el sistema.

144

APÉNDICE A: LA BIBLIOTECA ESTANDAR double atof(char *s) Convierte s a un numero doble. int atoi(char *s) Convierte s a un numero entero. long atol(char *s) Convierte s a un numero entero largo.

assert.h void assert(int exp) Se usa para buscar errores en los programas, cuando la expresión exp vale 0 assert interrumpe el programa y luego imprime la expresión que fallo, el nombre del archivo donde se encuentra dicha expresión y el número de linea donde se interrumpió el programa.

time.h La librería time.h tiene funciones para manipular la hora del sistema. La estructura struct tm tiene los componentes del calendario. int tm_sec; segundos después del minuto (0-59) int tm_min; minutos después de la hora (0-59) int tm_hour; horas después del a medianoche (0-23) int tm_day; dia del mes (1-31) int tm_mon; meses desde enero (0-11) int tm_year; años desde 1900 int tm_wday; dias desde el domingo (0-6) int tm_yday; dias desde enero 1 (0-365) int tm_isdst; bandera de "daylight saving time" time_t time(time_t *tp) Regresa la hora y la fecha actual, regresa -1 si el tiempo no está disponible. Si tp no es NULL el valor de retorno se le asigna a tp. char *asctime(const struct tm *tp)

145

APÉNDICE A: LA BIBLIOTECA ESTANDAR Convierte la hora almacenada en tp a una cadena de la forma "sat feb 21 14:29:10 2004\n\0". char *ctime(const time_t *tp) Convierte la hora almacenada en tp a una cadena de la forma "sat feb 21 14:29:10 2004\n\0". Del mismo modo que lo haría asctime. struct tm *gmtime(const time_t *tp) Convierte la hora almacenada en tp a la hora coordinada universal, regresa NULL si no está disponible. struct tm *localtime(const time_t *tp) Convierte la hora almacenada en tp a la hora local.

APÉNDICE B: ALGUNOS PROBLEMAS PROPUESTOS 1. En la aritmética de C hay varios tipos de datos. Haz un programa que calcule la división 5/2 eliminando los decimales. 2. Escribe un programa que calcule el residuo de una división. 3. Escribe un archivo de cabecera (.h) con dos números enteros, luego, en la función main imprime el resultado de la división de estos dos en notación decimal con punto flotante (6.45673 por ejemplo). 4. Inventa un programa que capture una calificación y que diga si la calificación es aprobatoria o no. Usa if's 5. Escribe un programa que acepte una temperatura (float) y que la clasifique por rangos, si la temperatura va de -40 a 0 escribe un mensaje que diga "congelamiento", de 0.0001 a 10 "mucho frío", de 10.0001 a 20 "Es mejor usar un abrigo" de 20.0001 a 30 "buen tiempo" y de 30.0001 en adelante que imprima "mucho calor", además si la temperatura es menor a -40.0001 o mayor que 50 debe de imprimir un mensaje que diga "Eso es inhumano". Usa if's anidados. 6. Captura una calificación entera del 5 al 10, para cada calificación escribe un mensaje, como "excelente" para el 10 o "regular" para el 7, pero si la calificación no está comprendida entre el 10 o el 5 debe de imprimir un mensaje que diga "Calificación incorrecta". Usa la estructura switch-case. 7. Escribe un programa que imprima una secuencia de números del 1 al 20, pero omitiendo los números pares, puedes usar el ciclo for combinado con un if o solo el ciclo for. 8. Escribe un programa que muestre un menú, la opción 'a' debe de imprimir las tablas de multiplicar del uno al diez (ciclos for anidados), la opción 'b' 146

APÉNDICE B: ALGUNOS PROBLEMAS PROPUESTOS debe de mostrar la serie del dos a la potencia n mientras n sea menor o igual a 10, usando el ciclo while. El menú lo puedes hacer usando if's anidados o un switch-case. 9. Al programa anterior mejórale el menú haciendo que se imprima indefinidamente hasta que se oprima la 'c' para salir, usa la función system para borrar la pantalla y el ciclo do-while para imprimir el menú correctamente. 10. Usa el mismo programa anterior, solo que ahora separa la opción 'a' y la opción 'b' en dos funciones aparte. 11. Escribe una función que reciba una letra como parámetro, la función debe de regresar un 1 si la letra es mayúscula o un 0 si es minúscula. 12. Usando algo parecido a la función del programa anterior construye un programa que acepte una cadena de 10 caracteres, y mande el arreglo a una función que regrese la primer posición donde encuentre una mayúscula, por ejemplo si se escribe Perro debe de regresar un 0, o si se escribe un peRRo debe regresar un 2, en caso de que no encuentre ninguna mayúscula debe regresar un -1. 13. Escribe una función que acepte un arreglo bidimensional como parámetro y que regrese el número de la columna con la suma mayor de elementos. 14. Escribe un programa que capture 5 estructuras con los siguientes datos cada una: Nombre Teléfono Dirección 15. Debes de usar un arreglo de estructuras, La función que captura los datos no debe de ser main, el arreglo de estructuras debe de ser declarado en main y la función que imprime los datos debe de ser una función aparte también. 16. Escribe un programa que acepte una variable cualquiera y que imprima su dirección en memoria junto con su valor. Usa lo visto en apuntadores. 17. Escribe una función que sume un valor a una variable, dicha función debe ser void suma(tipo *v, tipo s), donde *v es el número que va a cambiar su valor y s es la cantidad que se va a agregar. 18. Revisa el siguiente código, hazlo funcionar y explica cómo es que funciona en base a lo que se vio en el tema de apuntadores. #include 147

APÉNDICE B: ALGUNOS PROBLEMAS PROPUESTOS int main(int argc, char **args){ long direccion; int *apuntador; int variable = 5; printf("El valor de la variable es %d\n", variable); printf("La direccion de la variable es %ld\n", (long)&variable); direccion = (long)&variable; printf("El valor de direccion es %ld\n", direccion); apuntador = (int *)direccion; printf("El valor almacenado en %ld es %d\n", (long)apuntador, *apuntador); return 0; } 19. Escribe una función que acepte como parámetro un apuntador a una cadena de caracteres, que convierta todos las letras a mayúsculas y que regrese el número de caracteres que hay en la cadena. La función sería de la forma int convertir(char *cadena). 20. Escribe con base al programa del problema 14 otro programa que guarde las estructuras en un archivo y que luego las recupere usando fwrite y fread respectivamente, las estructuras ya no deben de estar declaradas como arreglo. 21. Por último escribe un programa que permita almacenar un número indefinido de estructuras, usando una lista enlazada doble, el programa debe de permitirnos recorrer la lista en ambas direcciones (atrás y adelante), insertar un elemento en su posición correcta y eliminar elementos. Al final la lista debe de grabarse en un archivo y quedar con un formato agradable a la vista, esto no se puede hacer con fread y fwrite, así que debes de usar fprintf y fscanf. Al decir que los elementos deben insertarse en orden correcto me refiero a que el elemento tiene un número de índice, así que si hay un 1 y un 4 al añadir un 2 este debe de quedar entre el 1 y el 4. La estructura a usar es la siguiente: struct p20{

148

APÉNDICE B: ALGUNOS PROBLEMAS PROPUESTOS int lugar; char nombre[20]; struct p20 *atras; struct p20 *adelante; }

APÉNDICE C: RECOMENDACIONES Y TEMAS NO VISTOS En sí, como se vio en este libro, C es un lenguaje muy pequeño, flexible y extensible. Las estructuras que usa no son tan complicadas y nos permiten hacer casi cualquier cosa. Mi recomendación es que después de haber leído este libro y haber comprendido bien cómo funciona C, busques más bibliografía más avanzada, en cuanto apuntadores, que fue el tema fuerte del libro y es la mejor arma de C a mi gusto, el libro me pareció bastante completo, pero hay mucho más acerca de la programación en C que no puede ser vista en un solo libro ni tampoco escrita por un solo autor. El libro te va a servir muy bien para conocer el lenguaje, pero necesitas usar un buen compilador y tratarlo, algunos detalles de C pueden variar un poco. La librería del compilador que uses puede tener funciones diferentes, de hecho las funciones de la librería estándar que se vieron en este libro son muy pocas, hay más, que no las incluí porque varían de un compilador a otro y porque no es algo que vas a usar comúnmente. El compilador que elijas puede tener sus propias librerías con funciones para un modo gráfico en una interfaz windows o kde, funciones para comunicación en red, otras funciones matemáticas, funciones específicas para el sistema, ya sea DOS, Windows, Unix, Linux, etc. La programación aplicada específicamente a un sistema operativo en particular requiere de bibliografías más avanzadas. Hay dos temas que no se ven en este libro, el preprocesador de C y el enlazado de varios códigos fuentes dentro del mismo proyecto. El preprocesador de C, que se usa para incluir archivos por medio de #include o para definir constantes con el uso de #define tiene otras aplicaciones, por ejemplo las funciones macro, o la compilación condicional. En cuanto a el enlazado de varios archivos fuente, no se vio porque depende en gran parte de el compilador que uses. De todas formas creo necesario darte una breve explicación de cómo se hace. Digamos que tienes un archivo fuente que se llama 2.c, en este archivo debes de poner lo siguiente. /* 2.c */ 149

APÉNDICE C: RECOMENDACIONES Y TEMAS NO VISTOS #include int parte2(char *nombre){ int n; printf("Hola %s, introduce un numero: ", nombre); scanf("%d", &n); return n; } Ahora el archivo 1.c debe de tener lo siguiente. /* 1.c */ #include /* se declara una funcion externa */ extern int parte2(char *); int main(int argc, char **args){ char nombre[10]; int numero; printf("Como te llamas? "); gets(nombre); numero = parte2(nombre); printf("%d es un buen numero\n", numero); return 0; } Solo puede haber una función main y todas las funciones y variables que se encuentren en otro archivo fuente deben de ser declaradas como externas donde se vayan a usar, Las funciones de cabecera se deben incluir en todos los archivos fuentes, pero hay que ser cuidadoso con las inclusiones múltiples, las 150

APÉNDICE C: RECOMENDACIONES Y TEMAS NO VISTOS funciones de la librería estándar no tiene ese problema. Un ejemplo de pre-procesamiento sería el siguiente. En el archivo config.h se selecciona la opción 0 o 1 para signar a N, en base a esta desición el preprocesador elije qué archivos incluir y qué valores definir. /* archivo config.h */ #if !defined YA Si YA no está definido lo define #define YA esto asegura que no se defina a N varias veces. #define N 1 /* 1 = dos 0 = unix */ asigna el valor adecuado a N.

Se le

#endif Fin del if. /* fin de config.h */ /* archivo main.h */ #include "config.h" Incluye el archivo de configuración #if N Si N vale 1 incluye el archivo d.h y asigna #include "d.h" 0 a SIS. #define SIS 0 #else De lo contrario incluye el archivo u.h y asigna #include "u.h" 1 a SIS. #define SIS 1 #endif Fin del if. 151

APÉNDICE C: RECOMENDACIONES Y TEMAS NO VISTOS

extern void funcion(void); Declara la función externa. /* fin de main.h */ /* archivo d.h */ #define OP 2 El archivo d.h asigna 2 a OP. /* fin de d.h */ /* archivo u.h */ #define OP 3 El archivo u.h asigna 3 a OP. /* fin de u.h */ /* archivo main.c */ #include #include"main.h" main(){ char *sistema[] = {"dos", "Unix", "es bueno", "es exelente"}; printf("%s, %s\n", sistema[SIS], sistema[OP]); funcion(); } /* fin de main.c */ /* archivo dor.c */ #include #include"main.h"

152

APÉNDICE C: RECOMENDACIONES Y TEMAS NO VISTOS void funcion(void){ printf("jejeje %d je\n", N); } /* fin de dos.h */ Esto se pudo hacer mucho más sencillo, pero lo escribí así para que veas cómo el pre-procesamiento puede decidir qué archivos incluir o qué valores asignar. Aun hay más acerca del lenguaje C, pero por mi parte ha sido todo por el momento. Espero sinceramente que todo esto te sirva bastante, que seas un buen programador gracias a tu práctica y a este libro ;) y que la pases de maravilla. Hasta pronto...

153

Lihat lebih banyak...

Comentarios

Copyright © 2017 DATOSPDF Inc.