Vous êtes sur la page 1sur 122

Primera edición

Programación
en C
Fundamentos para
ingeniería

Gildardo Sánchez Ante


Tecnológico de Monterrey
Prólogo

Este libro pretende ser un apoyo en el aprendizaje del lenguaje


C para alguien que no ha tenido experiencia previa en
programación, o que la que ha tenido ha sido muy limitada.
Además busca dos objetivos adicionales. Uno de ellos es que
la introducción a los conceptos de programación estructurada
se de mediante el uso de ejemplos de diversas áreas de la
ingeniería. El otro es que el ambiente de trabajo sea LINUX.

Es así que por tanto comenzamos por una pequeña dosis de


historia de los sistemas operativos, hasta llegar al nacimiento
de UNIX/LINUX. Es importante remarcar algunas de sus
características, pues eso será de utilidad para quien programe
sobre este sistema operativo. Luego, cubrimos un tutorial mas
o menos detallado de cómo podemos instalar LINUX (Ubuntu,
en este caso) en nuestra computadora, y veremos de qué
herramientas disponemos para poder programar en dicho
entorno.

Una vez cubiertas estas cuestiones básicas, comienzan


propiamente los temas de programación. El capítulo 2 nos
habla de tipos de datos, que son los elementos básicos que
estaremos manipulando con el lenguaje de programación. En
el capítulo 3 estudiaremos algunas de las estructuras de
control más importantes. Los arreglos merecen un tratamiento
especial, así que esos los cubriremos en el capítulo 4.

i
Luego, en el capítulo 5 veremos las funciones y su importancia
en el lenguaje C. Mientras tanto, en el capítulo 6 estaremos
aprendiendo algunas de las estructuras de datos más
importantes. El capítulo 7 se enfoca a uno de los temas que
son casi exclusivos del lenguage C: apuntadores, con todo el
poder que implican. Y finalmente, en el capítulo 8 veremos la
manera de leer datos y escribirlos desde y hacia
almacenamiento secundario.

El texto está lleno de ejemplos y problemas de aplicación. De


hecho, usted podrá notar que las explicaciones tratan de ser
muy concretas y breves, y que pasamos a la aplicación tan
pronto tengamos los elementos absolutamente indispensables
para hacerlo. Es un enfoque de aprender haciendo.

Espero que en el transcurso del mismo usted descubra, al igual


que un servidor, no solo el poder sino el placer de resolver
problemas de ingeniería auxiliándose de una computadora y
del Lenguaje C como herramientas.

Junio 2012

Gildardo Sánchez

Guadalajara, Jal., México.

ii
Introducción

1
En este capítulo se
describe brevemente
algunos conceptos del
sistema operativo Linux y
se explica cómo instalarlo
en una máquina virtual.
Finalmente se instala un
ambiente de desarrollo
(IDE) para compilar y
ejecutar un primer
programa en C.
Sección 1

Preparando Algunos conceptos básicos


Este libro pretende ser un apoyo y guía para aprender a resolver

nuestro ambiente problemas de ingeniería usando el Lenguaje de Programación C como


herramienta. Es por tanto que nuestra primera pregunta es ¿Qué
necesito para poder programar?

de programación La respuesta es, en términos generales, la siguiente:

• Una computadora con su sistema operativo.


Contenido
• Un compilador.

1. Algunos conceptos básicos. • Un editor o posiblemente un ambiente de desarrollo.

1. Sistema operativo. Veamos con un poco de más detalle qué significan cada uno de ellos.

2. Compiladores. Sistema Operativo:


Podría decirse que el sistema operativo es un programa o conjunto de
3. Ambientes integrados de desarrollo (IDE).
programas que se encarga de la administración de los recursos de
2. Instalando una máquina virtual con Linux. hardware y que provee servicios a los demás programas (aplicaciones).
Es por tanto quien existe entre, digamos Word y la tarjeta madre, discos
3. Preparando nuestro Editor/IDE y compilador. duros, memoria y procesador.

4. ¡Hola mundo! La parte medular del sistema operativo es la que llamamos comunmente
núcleo o kernel del mismo. Sobre de éste se pueden construir los
sistemas que se encargan del manejo de archivos, los navegadores y
por supuesto cualquier otra aplicación que pueda ser ejecutada en
nuestro sistema de cómputo.

4
Hay una gran variedad de sistemas operativos, y cada uno de ellos tiene
sus características. Los hay mono usuario (sólo dan servicio a un
Compiladores
usuario a la vez), los hay multi usuario (varios usuarios compartiendo el El compilador es un programa que se encarga de traducir los programas
uso de los mismos recursos de hardware), de costo o gratuitos, escritos por el programador en lenguaje de alto nivel (entendible por el
compactos o muy extensos, etc. ser humano) a un lenguaje de bajo nivel que es el que la máquina
entiende y que, de esta manera, permite que pueda ser ejecutado por la
En este libro haremos énfasis en la utilización de un sistema operativo computadora.
gratuito, multiusuario y de libre acceso a su código fuente. Se trata de
LINUX, quien a su vez está basado en otro sistema operativo llamado
Ambientes De Desarrollo
UNIX.
Cuando se programa, es necesario disponer de una aplicación dónde
Hay varias razones para recomendar el uso de este sistema operativo, y escribir y probar nuestro código. Dicha aplicación puede ser una muy
la realidad es que la combinación de ellas nos pone en una simple llamada “Editor”, o puede ser algo mucho más elaborado que
circunstancia muy apropiada. Mencionaré aquí algunas de ellas: Es suele ser llamado de manera genérica “Ambiente Integrado de
gratuito, lo que significa que todos podemos usarlo legalmente sin tener Desarrollo”, o casi siempre por sus siglas en inglés: IDE (Integrated
que hacer grandes desembolsos de dinero. Es mantenido de manera Development Environment). Veremos que éstos últimos integran no solo
colaborativa por desarrolladores de todo el mundo, todo el tiempo. Esto al editor, sino componentes para ayudarnos posteriormente a probar
hace que sea un sistema muy actualizado, con tiempos de respuesta nuestro código y muchas veces contemplan funcionalidades que nos
que suelen ser muy rápidos cuando hay que modificar o añadir alguna ayudan a completar porciones del código, etc.
característica. Es extremadamente robusto y es una plataforma muy
En realidad cuál de estos usemos depende mucho de con cuál nos
utilizada en varios sectores de la industria, por lo que estaremos
sintamos más cómodos. Para propósitos de este libro, haré mención de
preparados para enfrentar problemas reales sin tantos tropiezos.
algunos de los que existen a la fecha, preferentemente gratuitos. Usted
Por supuesto, si existen otros sistemas operativos es por algo. Así que tendrá por tanto la libertad de elegir cuál desea emplear.
no pretendo decir que LINUX sea ni el único ni el mejor. No se puede
Enseguida te menciono algunos de los ambientes de desarrollo mas
hacer una aseveración como ésta última sin dar contextos. Por tanto,
comunes a la fecha.
diremos que para el tipo de aplicaciones a las que estamos orientando
este libro, LINUX resulta simplemente muy apropiado.

5
El Entorno está desarrollado en el lenguaje Delphi de Borland. Tiene
una página de paquetes opcionales para instalar, con diferentes
bibliotecas de código abierto.

Dev-C puede ser descargado desde:


http://www.bloodshed.net/dev/devcpp.html
Code::Blocks

Code::Blocks es un IDE libre y multiplataforma para el desarrollo de


XCode
programas en lenguaje C y C++. Está basado en la plataforma de
interfaces gráficas WxWidgets, lo cual quiere decir que puede usarse
libremente en diversos sistemas operativos, y está licenciado bajo la Si tu tienes una computadora Mac,
Licencia pública general de GNU. probablemente quieras usar
XCode. Esta herramienta permite
Debido a que Dev-C++ es un IDE para los lenguajes C y C++ y está el desarrollo de aplicaciones para
creado en Delphi, surgió la idea y necesidad de crear un IDE hecho en la computadora y para los
los lenguajes adecuados: C y C++. Con esta motivación se creó dispositivos móviles como el iPad
Code::Blocks. [http://es.wikipedia.org/wiki/Code::Blocks]. y el iPhone. La puedes encontrar
en el disco de utilerías de tu Mac o
Lo puedes bajar desde: http://www.codeblocks.org/
desde la AppStore.

DevC/C++ Otros IDE

Dev-C++ es un IDE para programar en


Hay muchos otros entornos de desarrollo:
lenguaje C/C++. Usa MinGW que es una
versión de GCC (GNU Compiler • NetBeans (http://netbeans.org/),
C o l l e c t i o n ) c o m o s u c o m p i l a d o r.
Dev-C++ puede además ser usado en combinación con Cygwin y • KDevelop (http://kdevelop.org/),
cualquier compilador basado en GCC.
• Eclipse (http://www.eclipse.org/)

6
Entre otros. En este sitio puedes encontrar una buena descripción de 10 Hay varias aplicaciones que permiten crear
diferentes IDEs. máquinas virtuales, quizá las dos más conocidas
son: VMWare y VirtualBox. La primera es de costo, y
Instalando Linux la puedes encontrar aquí. La segunda es gratuita, y
la puedes descargar de este sitio.
En esta sección veremos cómo podemos instalar una versión de Linux
en nuestras computadoras, para poder trabajar con C. En realidad La imagen mostrada más abajo fue tomada en una
podríamos usar cualquier sistema operativo que tenga tu computadora computadora Apple Macintosh corriendo Snow
ya instalado, aunque por las razones mencionadas con anterioridad Leopard como sistema operativo nativo y con una máquina virtual de
nosotros partiremos de la idea de que Linux será nuestra base. VirtualBox corriendo Ubuntu 12.04.

Lo primero que quisiera mencionar es que hay dos maneras (tres en Para propósitos de explicar la instalación de la máquina virtual usaré
realidad, pero no usaremos la tercera) de instalar Linux en tu VirtualBox y supondré que partimos de una computadora con Windows
computadora. En la primera o borramos por completo el sistema como sistema operativo nativo.
operativo que tu computadora tenga ahora, o creamos una partición
nueva en el disco duro e instalamos en ella nuestra versión de Linux. En
la segunda, creamos una máquina virtual y trabajamos sobre ella.
Desde el punto de vista de lo que hay que hacer no hay mucha
diferencia entre una y otra. Aquí me limitaré a explicar cómo hacer una
máquina virtual y luego a instalarle Ubuntu Linux.

Quizá deba entonces comenzar por describir un poco acerca de


máquinas virtuales. Esta es una tendencia muy importante en
computación, que lo que hace es justamente lo que su nombre indica.
Permite crear “computadoras” virtuales dentro de una computadora real.
Con ello es posible que, por ejemplo, corras Windows 7 u 8 en una
máquina virtual que corre a su vez sobre Mac OSX. O viceversa, o Linux
dentro de cualquiera de ellas. Es en realidad una maravilla porque se
puede probar sistemas operativos diferentes sin tener que cambiar de
computadora real.

7
Instalando La Máquina Virtual Si todo salió bien, tenemos instalada una máquina virtual con Ubuntu
Linux ya funcionando.

El siguiente video, producido por Gilberto Fuentes Díaz, y que puede Instalando “Guest Additions”
encontrarse en este enlace de YouTube, describe de manera muy
puntual cómo hacer la instalación.
Ahora bien, para que tu máquina virtual funcione mejor (en cuanto a su
rendimiento), es conveniente instalar los “Guest Additions” de
P ELÍCULA 1.1 Instalación de Virtual Box VirtualBox. Se trata de un conjunto de programas y drivers que harán
más rápida tu máquina virtual.

Para instalarlos, tienes que encender tu máquina y entrar a Ubuntu. En


el menú superior podrás ver la opción Dispositivos (Devices), si
seleccionas ese, en el menú que se despliega podrás ver “Install Guest
Additions”. Le das click y sigues las indicaciones. Al terminar, tendrás
instalados esos programas y estarás listo para continuar usando tu
máquina virtual.

Producido por Gilberto Fuentes Díaz.

8
Preparando Un Editor/IDE Y El Compilador. escribimos gcc. Si el compilador está instalado obtendremos algo como
lo siguiente:

Ubuntu instalará varias herramientas para que podamos programar,


entre ellas el compilador de C y C++.

Una de las primeras cosas que haremos será añadir el ícono de la


terminal de comandos a nuestro escritorio. Para ello, damos clic en el
ícono de Ubuntu que se encuentra en la esquina superior izquierda. Nos
abrirá una ventana que tiene un campo de texto donde escribiremos
“term”. Eso hará que nos muestre una pantalla similar a la siguiente:

Eso significa que el compilador está instalado. Marca el error porque no


le dimos ningún programa para compilar.

También haremos lo mismo con el comando g++.

Si nos marca un error diciendo que no encuentra el programa,


deberemos pedirle a Ubuntu que lo instale, usando la siguiente línea en
la terminal:

sudo apt-get install g++

Una vez que eso esté concluido, instalaremos Code::Blocks como


ambiente de desarrollo (IDE). Para ello escribiremos en la terminal sudo
Arrastramos el ícono que dice Terminal a la barra de la izquierda y listo. apt-get install codeblocks. Veremos una pantalla como la
Ya lo tendremos a nuestro alcance. siguiente, a la que responderemos con un ENTER.
Ahora nos aseguraremos que el compilador está instalado. Damos
doble clic en el ícono de terminal que acabamos de añadir a la barra, y
9
Una vez realizadas estas dos tareas, estamos listos para escribir
nuestro primer programa en C y probar su ejecución, lo que haremos
en la siguiente sección.

Damos clic en OK. Y veremos la interfaz del IDE ya lista para que
programemos.
Probemos ahora si nuestro IDE quedó correctamente instalado. Para
ello, escribimos en nuestra terminal: codeblocks y damos ENTER.
Veremos algo como lo que muestra la siguiente imagen.
¡Hola Mundo!

Probemos entonces si podemos usar nuestro IDE para escribir, guardar


y compilar nuestro primer programa.

10
Grabamos el archivo, con el nombre de holamundo.c

Al reconocer el IDE que se trata de un código de C, hará algunas cosas


por nosotros, como poner colores y alinear el código de una cierta
forma:

Damos clic en donde dice Workspace, a la extrema izquierda. Luego,


damos clic en el ícono que aparece en la esquina superior izquierda
(una hoja con un signo de mas) y seleccionamos Empty File. Debemos
ponerle un nombre, usaremos holamundo.c. No olvide el punto. Le
damos OK. Aparecerá una pestaña que dice *Untitled. Dentro de ella
escribimos:

#include<stdio.h>

int main(void)

printf("Hola Mundo!");

return 0; Si le presionamos en el botón que parece un “play con un engrane”,


veremos algo como lo siguiente, lo que significa que hemos ya podido
} ejecutar nuestro primer programa en C usando Code::Blocks. ¡Muy bien!

11
#include<stdio.h>

int main(void)

printf("Hola Mundo!");

return 0;

Lo grabamos con el nombre de holamundo.c y luego en otra terminal


escribimos:

gcc holamundo.c -o holamundo

Luego ponemos:

./holamundo
Otra manera de hacer esto mismo es a través de un editor de texto y
compilando en línea de comandos. Si todo funciona bien, veremos una pantalla como la siguiente. Note
cómo en la terminal aparece en la última línea el texto “Hola Mundo!”.
En la terminal, escribimos gedit. Nos aparecerá una nueva ventana Es decir, hemos escrito y compilado nuestro primer código en C usando
con un editor de texto. el editor de texto y la línea de comandos en la terminal. ¡Felicidades!

Escribimos en él:

12
13
Sección 2

Lo básico de Linux Características de diseño de


Linux/Unix
Ahora que ya tenemos una máquina virtual con alguna versión de Linux,
estudiaremos un poco más de este sistema operativo. Las versiones
más recientes del mismo incluyen entornos gráficos que lo hacen muy
intuitivo y similar a aquello que tu estás acostumbrado con otros
sistemas operativos.

Contenido Sin embargo, considerando que nuestro objetivo es aprender a


programar y sacar con ello el máximo provecho del sistema
computacional, la descripción que aquí leerás tiene más que ver con el
1. Características de diseño de Unix.
uso de ese sistema operativo pensando en programadores, por lo que
2. Comandos básicos de Unix. por ejemplo, haremos mucho énfasis en cómo hacer algunas
operaciones usando la terminal y la línea de comandos, más que
3. En entorno gráfico de Unix. aplicaciones con una interfaz gráfica.

4. Instalación de aplicaciones en Unix. Hablemos un poco de porqué Unix/Linux son importantes en nuestro
contexto y cómo lo podremos usar.
5. Ejercicios
LInux está basado en UNIX, y como tal, se caracteriza por ser un
ambiente propio para el desarrollo de programas. En palabras del
mismo Thompson, UNIX es un sistema operativo diseñado por
programadores para programadores. Las principales características de
diseño de UNIX fueron:

• Permitir la portabilidad entre diferentes plataformas de Hardware.

• Generar un sistemas de archivos que fuera independiente de los


dispositivos.

14
• Combinar programas ya existentes para realizar trabajos complejos. Entrada/Salida Del Sistema (login/logout)
• Permitir a múltiples usuarios realizar múltiples tareas al mismo
tiempo. login permite el acceso al sistema. Normalmente, login es un
proceso que corre en cualquier terminal en cuanto se enciende. Por
Por tal motivo, la estructura básica de UNIX está conformada por varios tanto, hay que proporcionar el nombre de la cuenta a la que se desea
niveles, que van desde el control de acceso al hardware, dado por el ingresar (al nombre se le llama logon). El sistema entonces solicitará la
kernel del sistema operativo, hasta las aplicaciones de usuario, pasando clave de acceso (keyword o password). Una vez proporcionada ésta, se
por el shell del sistema operativo. Esta idea se ilustra en la siguiente estará dentro del sistema, posicionado en el directorio de trabajo del
gráfica. usuario.

logout se usa para salir


de la sesión. Es
equivalente a [CTRL]D o
exit. En los sistemas
modernos, este proceso se
activa automáticamente al
“encender” la máquina
virtual, dejándonos en una
pantalla como la que se
puede observar a la
izquierda.

Cambio De Palabra Clave (passwd)


Comandos básicos de Unix
Se usa passwd cuando se desea cambiar el password. Para ello, se
A continuación se mencionan algunos de los comandos más comunes
tiene que estar dentro de la cuenta. El comando solicita el password
en UNIX. Con ellos es posible entrar y salir del sistema, así como el
anterior y luego solicita dos veces el nuevo. Una vez hecho esto, se
manejo fundamental de los archivos y directorios.
habrá cambiado de palabra clave.

15
Listado De Archivos (ls)

! El comando ls lista el contenido de un directorio dado. Es


equivalente al dir del DOS. Este comando tiene varias opciones (en
UNIX las opciones se declaran precediéndolas con un signo menos "-").
Entre las opciones más importantes de ls están:

La siguiente imagen muestra el listado simple de un directorio.

En ese último listado tenemos varios componentes:

En esta línea primero tenemos un conjunto de 10 caracteres. El primer


caracter básicamente indica si se trata de un archivo o un directorio. En
este caso como hay una “d”, sabemos que es un directorio.

En UNIX se manejan permisos de acceso a los archivos y directorios,


clasificando a los usuarios como:

! u: Propietario
Esta otra imagen muestra un listado “largo”, donde puede verse más
! g: Grupo.
información de cada archivo.
! o: Otros.

16
Los permisos pueden ser:

! r: Lectura.

! w: Escritura.

! x: Ejecución.

! Los directorios requieren permiso de ejecución para poder entrar a


ellos. Para que un permiso de escritura sea efectivo, el directorio en
cuestión debe tener el permiso de ejecución.

Volviendo a nuestro ejemplo, vienen tres conjuntos de tres caracteres.


Esos caracteres pueden ser r, w y x, que describen los permisos que
cada clase de usuario tiene sobre el archivo o directorio. Por ejemplo,
en este caso, el usuario tiene permisos de lectura, escritura y ejecución
del directorio, los usuarios de su grupo podrán leer y ejecutar, pero no
escribir, y los demás pueden también leer y escribir. Dejemos el 8 por el
momento y veamos los siguientes dos campos. Describen el dueño del
archivo (gildardo) y el grupo al que ese usuario pertenece (staff), luego
viene el tamaño del archivo en KB y luego la fecha y hora de la última
modificación del mismo. Finalmente, está el nombre del archivo o
directorio.
Visualización De Archivos (cat)
Un ls -a muestra los archivos ocultos (aquellos cuyo nombre está
precedido por un punto).
El comando cat permite mostrar archivos (similar al type del DOS).

* Despliege de usuarios (who)

El comando who muestra quiénes están trabajando sobre el sistema en


ese momento.

17
Otra salida de ese comando, cuando hay más de un usuario conectado
a un sistema:

Despliegue Del Directorio Actual (pwd)

El comando pwd (print working directory) muestra el directorio actual en


que se encuentra posicionado el usuario.
* Ayuda del manual en línea (man)

El comando man seguido del nombre del comando muestra la ayuda en


línea para el comando solicitado.

* Cambio de directorio (cd)

Por ejemplo un man ls produciría algo como esto: ! Para cambiarse de un directorio a otro, se usa el comando cd. Es
similar al homónimo de DOS, con la diferencia de que en UNIX se
emplea la diagonal normal (/). En Linux dos puntos seguidos son una
abreviatura del directorio padre de aquél en que nos encontremos en un
momento dado. Así,

18
$cd ..
 Por ejemplo:
Nos mandará al directorio inmediato anterior al actual. Otra abreviatura
es la tilde, que representa el directorio base (también llamado home) de chmod go +w ejemplo1.txt
cada usuario.
habilita el archivo ejemplo1.txt para escritura para el grupo y todos
La imagen siguiente muestra una serie de aplicaciones de pwd, y cd. los demás usuarios (g y o).

El archivo genetic1.cpp tiene permisos de lectura y escritura para el


propietario, y de lectura para el grupo.

Se le cambian los permisos, otorgando permisos de lectura y escritura a


otros, y luego se vuelven a cambiar, quitando el permiso de lectura para
el grupo.

Cambio De Permisos (chmod)


Creación De Directorios (mkdir)
Para modificar los permisos de acceso se usa:
! Para crear un directorio se emplea el comando mkdir.
chmod [tipo_usuario] +/- permiso archivo.

19
Eliminación De Directorios (rmdir) Para Saber Más

! El comando rmdir elimina al directorio especificado. Dicho


directorio debe estar vacio. ¿De dónde viene Linux?

En 1965 se unieron Bell Laboratories, General Electric y el proyecto


Despliegue De Archivos (pg Y More)
MAC del MIT para desarrollar un nuevo sistema operativo llamado
MULTICS. Lo que se buscaba era crear un sistemas operativo que
! Para desplegar archivos se pueden usar pg (page) y more. Son permitiera el acceso simultáneo de varios usuarios a los servicios de un
similares al comando cat, con la diferencia que éstos dos últimos sistema computacional, es decir, un sistema operativo multiusuario. El
despliegan texto hasta llenar la pantalla y luego se detienen. La
diferencia entre ellos es que pg espera un [ENTER] para desplegar la
siguiente pantalla. En cambio, more avanza solamente una línea con el
[ENTER], para pasar por pantallas se debe oprimir la barra espaciadora.

Cambio De Nombre A Los Archivos (mv)

! Cuando se desea cambiar el nombre a un archivo o moverlo de


lugar, se usa mv desde hacia. Si hacia es un directorio, el archivo
se moverá, si es un nombre el archivo cambiará de nombre.

Copia De Archivos (cp)


Ken Thompson y Dennis Ritchie.
! Para copiar archivos se usa cp desde hacia.
resultado fue una versión preliminar de MULTICS.
Eliminación De Archivos (rm)
En 1969, Bell Laboratories decidió separarse del grupo, y fue entonces
cuando Ken Thompson desarrolló la versión preliminar de un kernel para
! El comando rm permite borrar un archivo. una computadora GE-645. Posteriormente, Thompson creó un juego
para la Honeywell 635, que luego trasladó a una PDP-7. Poco después
20
de esto, se unieron el propio Ken Thompson, Dennis Ritchie y Brian
Kernighan y escribieron un sistema de archivos, un manejador de P ELÍCULA 1.2 Historia de Unix
procesos y algunas otras utilerías, a las que llamaron UNICS, un
nombre relacionado con su antecesor MULTICS. Sería en 1970 cuando
UNICS se transformaría en UNIX.

Ese mismo grupo migró el sistema operativo de la PDP-7 a una PDP-11,


al mismo tiempo que desarrollaban un sistema para procesamiento de
textos que sería utilizado por el departamento de patentes de la Bell. La
primera versión del kernel de UNIX ocupó solamente 16K.

El mismo Ken Thompson, al intentar desarrollar un compilador para


FORTRAN, terminó desarrollando un lenguaje denominado B, basado
en otro lenguaje llamado BCPL. El programa del lenguaje B era muy
lento, por lo que Ritchie se dedicó a mejorarlo, surgiendo así el lenguaje
C.

En 1973 el sistema operativo UNIX fue reescrito en C. Sin embargo, por


cuestiones legales Bell no pudo comercializar UNIX, por lo que lo
distribuyó libremente a universidades y centros interesados. Por ese
mismo año, se desarrolló otra versión de UNIX, la PWB. Antes ya
habían aparecido otras dos versiones llamadas MERT y LSX.
Tomada de YouTube: http://www.youtube.com/watch?v=7FjX7r5icV8
De esta manera surgieron muchas variantes de UNIX y fue hasta 1982
cuando Bell conjuntó dichas versiones en una sola llamada UNIX
System III. AT&T anunció tiempo después el lanzamiento de UNIX
System V. En la universidad de California en Berkeley se hicieron Microsoft pidió la licencia de AT&T y produjo XENIX, una versión para
modificaciones y surgió la versión BSD (Berkeley Software Distribution). microcomputadoras. En la actualidad hay muchas versiones de UNIX,
que se pueden clasificar de acuerdo a su origen en tres grandes ramas:

21
a) Las derivadas de la original AT&T, desde las versiones1-7 a los
sistemas III-V. Ejercicios
b) M i c r o s o f t / S C O X E N I X .
 1. Estudiar el formato largo de salida del comando ls. Interpretar las
Versión desarrollada entre 1978 y 1981 para la familia Intel 8088/6. siguientes líneas de salida:
Aunque hubo versiones para el Motorola 68000 y el Zilog Z8000, luego
 
del auge de las PC's fueron prácticamente olvidadas. Entre las
adiciones que hizo XENIX al UNIX están el cerrar archivos a nivel de
registros y la utilización de semáforos para la sincronización de
procesos. Ultimamente se ha movido el Sistema V de AT&T a
plataformas PC.

c) Berkeley (UCB) de IBSD a 4.4BSD. El UNIX BSD empezó como un


perfeccionamiento a las versiones 6 y 7 del UNIX AT&T, a fin de explotar
el VAX (sistemas de DEC utilizados ampliamente en Berkeley). Algunas
de las características que AT&T ha incorporado las extrajo de las
versiones BSD, como por ejemplo, el C shell, el vi y el termcap. Sin
embargo, BSD no tiene fines de lucro, por lo que su distribución es libre.   2. Estudiar el comando ls.

a) Listar recursivamente todos los archivos bajo el directorio /etc


deteniendo el despliegue en cada pantalla.

b) Probar otras opciones del comando ls usando el directorio /etc o /bin.

c) Listar archivos visibles e invisibles bajo el directorio actual, en formato


largo.

3. Sistema de archivos.

a) Trasladarse al directorio bin, bajo del directorio usr, debajo del


directorio raíz. Mostrar el directorio actual.

22
b) Trasladarse al directorio local, debajo del directorio usr, debajo del
directorio raíz. Mostrar el directorio actual; mostrar los archivos
contenidos en él.

c) Trasladarse al directorio propio del usuario desde cualquier otro


directorio.

4. Usuarios.

a) Mostrar información relativa al usuario en la sesión actual.

b) Mostrar qué usuarios hay en el sistema en el momento actual.

c) Mostrar el nombre de la máquina.

5. Manual.

a) Desplegar en pantalla la página del manual para el comando que


muestra en pantalla las páginas del manual.

b) Guardar en el archivo pwd.man del directorio actual, la página del


manual pwd.

6. Investigue ¿Qué es el UID? ¿Qué es el GID?

7. Analizar la salida de los siguientes comandos: who, who am i,


whoami e indicar las direrencias. Analizar la salida de los comandos
hostname, id. 

23
Sección 3

Introducción a la Algoritmos
Al final de cuentas una computadora no es más que una máquina

programación en C sofisticada, a la que podemos controlar si aprendemos las instrucciones


que ésta “entiende”. Así pues, podríamos considerar que un programa
computacional es una secuencia de comandos que hace que una
computadora desarrolle una tarea tal que produzca un cierto resultado.
Las instrucciones de ese programa están contenidas en un determinado
lenguaje de programación. Hay muchos lenguajes, quizá hayas
Contenido escuchado de algunos de ellos: Java, FORTRAN, BASIC, PERL,
PYTHON, etc. Los lenguajes podrían agruparse en clases, dependiendo
de si comparten o no ciertas características. En el caso que nos ocupa
1. Algoritmos. ahora, aprenderemos el lenguaje de programación C, que es un
lenguaje de la clase de los lenguajes estructurados. Un poco más
2. Estructura de un programa en C. adelante hablaremos de ello con mayor detalle. Por ahora,
introduzcamos un concepto por demás importante para nosotros: el de
3. El proceso de compilación.
algoritmo.
4. Compilación en un ambiente Unix/Linux. Un algoritmo es una definición paso a paso y libre de ambigüedad de
las instrucciones que describen cómo es que una cierta tarea o cálculo
tiene que desarrollarse.

Para un ingeniero es importantísimo desarrollar un pensamiento


algorítmico, ya que el que normalmente tenemos presente es el
pensamiento intuitivo, que no siempre es tan fácil de programar. Si en
este momento tuvieras hambre, es muy probable que puedas resolver el
problema sin tener que hacer una descripción algorítmica del cómo
lograrlo, aunque si desearas que un robot lo hiciera por tí, tendrías que
ponerlo paso a paso, tal cual la definición de algoritmo lo señala.

24
Para ilustrar un poco esto, hablemos de una anécdota sobre el gran Suma= (50*100) + 50 = 5050
matemático Gauss, de quien estaremos hablando con frecuencia en
este texto. Hay al menos otras dos maneras de resolver este problema. La que
quizá la mayoría de nosotros pensaríamos es justamente el sumar todos
Se dice que siendo muy joven (quizá unos 10 años), su profesor de esos enteros, uno tras otro. Una más sería haciendo uso de la siguiente
aritmética pidió al grupo en que se encontraba Gauss que calcularan la fórmula que nos da el resultado:
suma de los 100 primeros enteros, quizá pensando que tendría un poco
de tiempo para comerse su manzana. Sin embargo, no había Donde n es el número entero hasta el cual deseamos hacer la suma, en
transcurrido mucho desde que el profesor solicitara el cálculo cuando este caso n = 100.
Gauss le dió la respuesta: 5050. La manera en que Gauss pudo hacerlo
También hablaremos un poco más de fórmulas como esta. Algunas de
fue la siguiente.
ellas son muy importantes por una sencilla razón: nos harán desarrollar
Si colocáramos los números en secuencia: códigos más eficientes. Para ejemplificarlo pensemos en el caso del
método dos, en el que iríamos sumando los enteros. Si lo analizamos,
0 resulta que nuestro programa tendría que realizar tantas sumas como
enteros haya que sumar, 100 en este caso. Cualquiera que sea el
1 tiempo que la computadora tarde en sumar un par de números,
deberemos multiplicarlo por n. Pensemos ahora en la fórmula. Ahí tengo
2
una suma, una multiplicación y una división, es decir 3 operaciones, no
... 100. ¡Eso seguro nos ahorrará tiempo de cómputo!

98
Algoritmos y pseudocódigo
99 Una vez que hemos planteado la solución del problema con un
algoritmo, resulta conveniente escribirlo usando una notación a la que
100 llamaremos pseudocódigo, porque no es código de algún lenguaje
particular, pero sí debe ser suficientemente claro como para que pueda
Podremos ver que si vamos tomando los extremos y los sumamos,
aplicarse la definición de algoritmo. El pseudocódigo puede incluso
obtenemos siempre como resultado 100. Es decir, (0 + 100) = 100, (1 +
hacer uso de un lenguaje natural, como el español o el inglés.
99) = 100 y así sucesivamente. Habrá 50 de estos pares de sumas y
nos sobrarán un número 50. Por tanto el total se puede calcular
multiplicando 100 por 50 y sumándole 50.
25
Por ejemplo, para el caso de la fórmula que acabamos de describir, un
pseudocódigo que la calcule pudiera ser como el siguiente:
Ejercicios
A fin de practicar lo que acabamos de mencionar sobre algoritmos y
1.Pide el valor de n. pseudocódigos, considere los siguientes ejercicios:

2. Calcula suma = n*(n+1)/2 1. Describa el procedimiento paso a paso para:

3. Imprime el resultado almacenado en “suma”. a. Preparar un hot dog.

Estas tres líneas podrán traducirse con facilidad a cualquier lenguaje de b. Cambiar una llanta ponchada.
programación, así que el trabajo que hemos desarrollado hasta ahora es
igualmente valioso independientemente del lenguaje de aplicación. c. Comprar un café.

Por cierto, es importante mencionar que en este texto siempre 2. Determine y describa en pseudocódigo un procedimiento para
seguiremos los pasos que se enumeran enseguida para resolver calcular cuánto dinero se tiene en una caja registradora que contiene
problemas: n pesos, m monedas de 5 pesos, l monedas de 10 pesos y k
monedas de 50 centavos.
1. Identificar cuáles son los datos que se requieren para calcular.
3. Si tuviéramos una lista desordenada con apellidos de personas,
2. Diseñar los pasos que permitan realizar los cálculos necesarios para diseñe un algoritmo y descríbalo en pseudocódigo para encontrar el
llegar al resultado primer sitio de la lista en que aparezca el apellido “LOPEZ”.

3. Imprimir o almacenar los resultados para el usuario. 4. Determine y describa en pseudocódigo un procedimiento para que
dados cuatro números enteros, estos sean impresos de menor a
El paso dos es escencialmente el diseño del algoritmo y su
mayor.
pseudocódigo. En el caso que hemos usado ahora para ilustrar, el
pseudocódigo es muy sencillo, pero en problemas más complejos será
más elaborado y nos ayudará mucho a evitar cometer errores de
programación que luego son más difíciles de encontrar y corregir, asi
que trabajaremos para hacer el hábito de diseñar siempre el
pseudocódigo.

26
2. Solo se permiten otras letras, números o guiones bajos posteriores a
Estructura de un Programa en C la primera letra. No se permiten espacios en blanco.
Funciones 3. No se pueden usar las palabras clave (keywords) de C. Se muestran
El lenguaje C se basa en el concepto de construcción con módulos. en la siguiente tabla.
Cada uno de esos módulos está encargado de realizar una tarea
específica. En realidad, los módulos son subprogramas y como tal, debe
hacer lo que hacen todos los programas: recibir datos, procesarlos y
generar un resultado. Estos módulos se llaman funciones. Por tanto, un
programa en C puede ser visto como una colección de una o más
funciones. Cada función tiene un nombre y una lista de argumentos que
recibirá. En general, se puede usar cualquier nombre, excepto main que
está reservado para la función que inicia la ejecución. De hecho,
main() es la única función que siempre debe existir en un programa en
C. Esta función debe contener la esencia de lo que hace el programa y
puede hacer llamadas a otras funciones que realicen ciertas tareas
específicas. Dichas funciones pueden estar en el mismo archivo que
main() o bien, pueden estar en archivos diferentes. 4. El número máximo de caracteres que la computadora alcanza a
reconocer en el nombre de la función depende de la computadora. El
estándar ANSI de C exige que sean al menos 31 caracteres.
DATOS FUNCION RESULTADOS
5. Todos los nombres de funciones son seguidos por paréntesis.

Algunos nombres válidos de funciones serían:

Nomenclatura Para Funciones. funcion_foo(), mi_funcion_007(), calcula_suma()


Hay algunas cosas que debemos recordar cuando trabajemos con
Y nombres no válidos serían:
funciones. Comencemos por sus nombres:
98_funcion(), auto(), la funcion buena()
1. El nombre de la función debe comenzar con una letra o con el guión
bajo “_”.

27
Hay una convención que nos invita a usar minúsculas en los nombres En ese caso, tenemos que main() llama a tres funciones, primero a la
de las funciones, aunque el lenguaje no lo exige así. Luego veremos función A, cuando esta termina regresa su resultado a la función que la
que respetar ciertas convenciones hará que nuestros códigos sean más llamó (main() ) y luego sigue la función B que hace lo mismo y
fáciles de leer por otras personas. finalmente la función C. Por supuesto, puede darse el caso en que una
de esas funciones a su vez llame a otra:
Estructura De Un Programa En C main()
Decíamos que todo programa en C al menos tendrá un módulo, que es
el módulo principal (main). En el caso de un problema simple, quizá con
Función A
eso baste para resolver el problema, pero en la mayoría de los casos el
problema será resuelto usando la función principal main mas otras
Función C
funciones, sea que estas sean provistas por un tercero o sea que las
tengamos que desarrollar nosotros mismos.

Cualquiera que sea el caso, el main funciona como una especie de Función B
director de orquesta, que es el que le pide a cada uno de los
instrumentos que intervengan en un momento en particular.
En este otro caso, main llama primero a la función A. Esta a su vez
La idea se ilustra de la siguiente forma: require a la función C, cuando C termina, le regresa el resultado a A.
main() Cuando A termina, lo regresa a main, quien a su vez tiene entonces la
posibilidad de llamar a la función B. Veremos que en la práctica puede
Función A
haber muchas llamadas a funciones en un programa complejo.

Generalizando, un programa en C tendrá una estructura como la que se


Función B ilustra en el siguiente diagrama:

Función C

28
declaración de variables globales En el diagrama aparecen varias cosas que aún no estudiamos, como las
declaraciones de variables. Otra cosa que quizá hayas notado es la
main() siguiente línea:
{! /* indica el inicio de la función */ /* indica el inicio de la funcion */

! declaración de variables locales Esa es en realidad la sintaxis para escribir comentarios en nuestros
códigos. Un comentario es algo que ayuda a los humanos a entender
! instrucciones ...
con mayor facilidad de qué se trata el código. Es una buena práctica
} “comentariar” nuestros códigos.

funcion_1(lista de argumentos) De hecho, siempre usaremos un encabezado tal como:

declaración de lista de argumentos /* Nombre del programa

{ Nombre del programador

! variables locales Descripción general del programa */

! instrucciones ... Note que /* */ nos permiten escribir comentarios de múltiples líneas.
También es posible usar // para comentarios de una sola línea:
}!
// Este es un comentario simple
función_n(lista de argumentos)
La comunicación entre funciones se realiza mediante el paso de
declaración de lista de argumentos parámetros, por lo que todas las funciones en C llevan cero o más
argumentos (un argumento es un valor que se pasa a la función)
{
separados por comas.
! variables locales
Cuando se define una función con uno o más parámetros, estos deben
! instrucciones ser declarados para especificar qué tipo de variables recibirán cada uno
de ellos. A estos argumentos de les denomina parámetros formales de
}! la función.
29
Las funciones en C se denominan así debido a que regresan un solo archivo stdio.h mediante la directiva de precompilación #include
valor a la función que las llamó, por medio de la instrucción return. <stdio.h>. La sintaxis de la función printf es la siguiente:

printf("cadena de control", lista de argumentos);


Forma De Las Funciones En C
La forma general de las funciones en C es: La cadena de control contiene los especificadores de formato de los
argumentos. Éstos le indican a printf cómo han de visualizarse los
tipo_dato nombre_funcion(lista de argumentos) argumentos por pantalla y su número. Después de la cadena de control,
aparecen los argumentos de la función separados por comas.
declaracion de lista de argumentos

{
Tipo de dato Especificador Comentario
! Cuerpo de la función
Entero %d o %i Cualquier entero
} Flotante %f Número real

En este caso, la función se llama nombre_funcion, recibirá como datos Núm. Real de precisión
Doble %lf
lo que quede en “lista de argumentos” y regresará un resultado que será doble
de un cierto tipo. Cadena %s String

Caracter %c
Entradas Y Salidas En C
Notación
Hay dos elementos mas que es importante mencionar en este momento, %e o %E
Científica
antes de que comencemos a trabajar más en detalle en el lenguaje C.
Esos dos elementos son los que nos permitirán recibir datos y los que
nos permitirán imprimir los resultados.

Para imprimir resultados usaremos comúnmente la función printf(). Proceso de Compilación en C.


La función printf básicamente imprime una cadena de caracteres sobre Los compiladores de C usualmente consisten de uno o más programas
la pantalla de la computadora. Tanto esta función como otras funciones que trabajan juntos. El proceso de compilación puede ser dividido en
de entrada/salida están definidas en la biblioteca stdio. Por esta razón, varias fases. Una fase es una tarea en particular que tiene que ser
cada vez que deseamos hacer uso de la misma tenemos que incluir el ejecutada antes de que el proceso de compilación pueda continuar.

30
Frecuentemente se combinan varias fases de compilación dentro de un ! b) Procesar las directivas de preprocesamiento. Estas directivas
paso de compilación. Regularmente, un paso involucra la lectura de un siempre empiezan con un carácter #. Entre las más comunes están:
archivo de entrada y la generación de un archivo de salida.
! #define: Crea macros.

! #undef: Elimina macros.


Los pasos de compilación para un programa en C son generalmente los
siguientes: ! #include: Reemplaza la línea por el archivo que se indique a
continuación.

! #ifdef: Compila el código si un macro ha sido definido con un


#define previo.

! #if: Compila el código si la expresión es verdadera.

El preprocesador no es parte del compilador.

Compilador:

! El paso de compilación involucra por lo menos cuatro fases:

! a) Análisis Léxico.

! b) Parser.

Descripción de los pasos de compilación: ! c) Optimización.

Preprocesador: ! d) Generación de código.

Toma como entrada el código fuente de un programa en C (Archivo Después de las dos primeras fases, se genera un código intermedio. El
texto ASCII). En este paso se realizan dos funciones: optimizador toma ese código e intenta que éste utilice menos espacio y
sea más rápido en la ejecución. Generalmente los compiladores no
! a) Eliminar los comentarios del código fuente. pueden tener ambas cosas, por lo que se debe escoger entre menor

31
espacio y mayor rapidez o viceversa. La fase de generación de código
traduce de código intermedio a lenguaje ensamblador.
Compilación de Programas en UNIX.
! La compilación depende del sistema al que se tenga acceso,
Ensamblador: aunque no hay diferencias muy grandes entre uno y otro. En este caso
se explica la instrucción para compilar en AIX.
Toma como entrada el programa en lenguaje ensamblador y lo traduce a
un lenguaje de máquina muy parecido al código ejecutable. Algunas ! Para compilar se usa:
veces se incorpora el ensamblador a la fase de generación de código,
de tal forma que se crea un archivo en lenguaje ensamblador. ! ! gcc archivo.c

La salida del ensamblador es un módulo objeto relocalizable. Los ! Este comando genera un archivo ejecutable llamado a.out. Si se
programas en C usualmente se escriben como varios archivos fuente deseara cambiar el nombre, habrá que usar:
que son compilados por separado y unidos posteriormente en una
gcc archivo.c -o ejecutable.exe
última etapa del proceso de compilación, mediante el ligador (Linker).
! En caso de requerir ligar con alguna biblioteca u otro programa, se
Una función puede llamar a otra que se encuentre localizada en un
emplea:
archivo diferente. Lo mismo sucede con las variables globales (variables
que deben ser conocidas por todas las funciones del programa). Este ! gcc archivo.c -o ejecutable.exe -l biblioteca
conjunto de objetos son llamados objetos externos. El ensamblador
traduce lo que puede a lenguaje de máquina, pero como aún no sabe Características del Lenguaje C.
dónde se ubicarán los objetos externos en la memoria, entonces
Muchas veces se ha clasificado a C como un lenguaje de nivel medio, lo
reemplaza todas sus referencias por alguna marca y crea una tabla con
cual se debe a que reúne características de un lenguaje de bajo nivel
esas direcciones y se las pasa al ligador.
(como ensamblador) con otras propias de lenguajes de alto nivel. C fué
Ligador (linker): desarrollado como un lenguaje para la programación de sistemas, por lo
cual posee características que lo han hecho muy útil en esas tareas. Por
El ligador toma la tabla de los objetos relocalizables y los ubica para ejemplo:
formar un sólo código ejecutable reemplazando todas las direcciones
marcadas con direcciones reales al conjuntar todos los objetos que
forman el sistema.

32
• En C realmente hay más operadores y combinaciones de operadores cercano al hardware hace que la velocidad de ejecución se aproxime
que palabras clave. C tiene menos reglas de sintaxis que muchos a la de sus equivalentes en lenguaje ensamblador.
otros lenguajes.
• C es un lenguaje con una alta portabilidad. Esto quiere decir que es
• C es un lenguaje extremadamente reducido. El lenguaje original fácil trasladar un programa en C desde una plataforma a otra. Esta
solamente contempla 27 palabras clave. El estándar ANSI de C tiene característica está muy relacionada con el manejo de bibliotecas. De
unas 32 palabras clave. A pesar de que C no incluye muchas de las hecho, la responsabilidad de que las funciones definidas en cada
funciones comúnmente definidas para otros lenguajes, se ofrecen biblioteca estén disponibles para una plataforma dada, dependen del
bibliotecas de uso tan común que prácticamente forman parte del fabricante.
lenguaje. Sin embargo, una de las ventajas de C es que es fácil
modificar esas funciones.

• C maneja con mayor facilidad los tipos, lo que le da una gran


flexibilidad (se dice que es un lenguaje weakly typed).

• Es un lenguaje estructurado, por tanto incluye todas las estructuras


de control como el for, el if-then-else, el case, y el do-while.

• Dado que C soporta el concepto de compilación y enlace separados,


es posible compilar las partes que cambien de un programa, sin tener
que compilarlo todo de nuevo. Esto es especialmente útil cuando se
trata de programas grandes.

• Por sus origenes íntimamente relacionados con UNIX, C permite un


muy buen manejo de los objetos a nivel de bits. Además, permite el
direccionamiento en memoria. Esto aumenta la velocidad de
ejecución.

• El código en C tiende a ser muy eficiente. La combinación de un


lenguaje pequeño, un sistema de ejecución pequeño y un lenguaje

33
Tipos de Datos

2
Cualquier lenguaje de
programación tiene que
leer datos, calcular con
ellos, tomar decisiones y
ofrecer respuestas.
Esa información se guarda
en diversos tipos de datos,
algunos variables, otros
constantes. Este capítulo
trata de ello.
Sección 1

Variables y Identificadores
Los identificadores son los nombres con los cuales se hace referencia a
Constantes una variable, constante o función. Los identificadores en C tienen las
siguientes características:

• Están formados por una secuencia de letras y/o dígitos que siempre
deben comenzar con una letra. Los siguientes caracteres pueden ser
letras, números o el carácter de subrayado.
Contenido
• Las letras mayúsculas y minúsculas son distintas cuando se utilizan
en una variable. Por ejemplo, la siguiente declaración define tres
1. Identificadores. variables distintas: int var, Var, VAR;

2. Tipos de datos en C. • En la mayoría de las computadoras sólo son significativos los


primeros ocho caracteres del identificador.
1. Tipo entero.
• Las palabras reservadas en C no deben usarse como nombres de
2. Tipo real. variables.

3. Tipo Caracter. • Se deben usar identificadores distinto para las variables y las
funciones.
3. Variables.

4. Constantes. Tipos de Datos en C


5. Ejercicios. Un tipo de dato define los valores que puede tomar una variable, así
como el conjunto de operaciones que se pueden realizar con la misma.
El lenguaje C tiene solamente cinco tipos de datos predefinidos, y cada
uno tiene asociada una palabra clave. El tamaño y rango de estos tipo

35
depende del tipo de computadora. En la siguiente tabla se muestran los
valores típicos para el caso de una computadora PC de 8 bits.
Tipo entero
Este tipo de datos se usa para representar números enteros con o sin
signo, que estarán compuestos por los dígitos del 0 al 9, pudiendo ser
precedidos por los signos + o -.

Algunos ejemplo de datos enteros son: 0, 23, -176, -1, etc.

El tipo de datos entero se define en el lenguaje de programación C por


la palabra reservada int.

Así que para declarar una variable llamada dato1 como un entero, se
escribe lo siguiente:

int dato1;
Debe aclararse que en diversas versiones de C y C++ se definen otros
tipos (por ejemplo enum y void). Cuando sea conveniente se Sin embargo, en ocasiones además de declarar la variable vamos a
mencionarán. querer darle un valor inicial. A esto se se llama inicialización. La
inicialización es opcional. Si tenemos varias variables de un mismo tipo,
Para poder usar variables en C hay que declararlas. Eso es, avisarle al es posible definirlas todas en una misma línea de código escribiendo un
compilador qué variables vamos a usar en nuestro programa y de qué único int, separando el nombre de las variables por comas (“,”). Una
tipo de dato son. vez que se haya acabado de definir variables, se cierra la línea de
código con punto y coma (“;”),
Aunque C en realidad nos exige que lo hagamos antes de usar la
variable, por limpieza de nuestro código, trataremos siempre de agrupar Por ejemplo:
todas las declaraciones de variables al inicio del programa.
int edad = 24; /*declara e inicializa edad */
La declaración de variables se hace anteponiendo una palabra especial
que define al tipo, antes del identificador de la variable. int edad; /* declarara edad como entero */

int edad, num1, num2; /*declara las 3 como enteros*/

36
Tipo real
Usado para variables que representan números reales (con decimales).

Para definir datos reales se antepone la palabra reservada float al


identificador de la variable.

float identificador = valor;

Por ejemplo:

float numero1, numero2;

float numero3 = 123.43;

float numero3;

Además del float, también es posible usar el tipo de dato double, el


cual también permite representar números reales, pero con mayor
cantidad de dígitos (mayor precisión). Tabla de códigos ASCII (http://batman.usal.es/codigos/ascii.htm)

double numero1, numero2;


Para representar este tipo de dato se antepone la palabra reservada
double numero3 = 123.43; char al identificador de la variable.

double numero3; Una constante tipo char se representa como un solo carácter
encerrado entre comillas simples.
Tipo carácter
Por ejemplo:
Este tipo de datos se emplea para representar un carácter perteneciente
a un determinado código utilizado por la computadora (normalmente el char letra, letra2; /* dos variables de tipo char */
código ASCII o el código UNICODE). Ambas tablas se muestran
enseguida. char letra=’a’; /* declara y asigna el caracter a */

37
char identificador[cantidad] = “ mensaje ”;

En la siguiente línea de código decimos que vamos a tener una cadena


que puede contener hasta 20 caracteres y le llamamos cadena1 a esa
variable:

char cadena[20];

Por otro lado, la siguiente línea además de apartar el espacio para los
20 caracteres, almacena ya una cadena con los caracteres de la frase
“Hola mundo”.

char cadena[20] = “Hola mundo”;

Y, finalmente, en la siguiente línea vemos como se ha omitido el número


entre corchetes cuadrados. En este caso lo que hará C es contar
cuántos caracteres hay en la cadena que se está asignando y apartará
eso como espacio máximo, es decir 4 en este caso:

Tabla de códigos UNICODE [http://blog.mamalibre.com.ar/category/etiquetas/ascii] char cadena[] = “HOLA”;

Por ello, la línea inmediata anterior es exactamente equivalente a que


Tipo cadena de caracteres: una cadena de caracteres es un número
hubiéramos usado:
de caracteres consecutivos (incluso ninguno) encerrado entre unos
delimitadores determinados, que en el lenguaje C son las comillas char cadena[4] = “HOLA”;
dobles. El lenguaje C no tiene realmente un tipo de dato para las
cadenas, pero es posible manejarlas de cualquier manera. Para manejar Omitir el número sólo se puede cuando además de la declaración se
variables de tipo cadena, estas se definen como vectores de caracteres, hace la inicialización. Si se intenta sin ella, el compilador marcará un
anteponiendo la palabra reservada char al identificador de la variable, y error.
después entre corchetes la longitud máxima de cadena. Si inicializamos
En la siguiente tabla se hace un resumen de los distintos tipos de datos:
la variable, debemos hacerlo poniendo la cadena entre comillas
dobles.

38
Palabra
Tipo de Dato Ejemplo
reservada

Entero int int numero=0;

Real float float num = 21.4;

Carácter char char letra = ‘a’;

char palabra[5]= b) Conversiones por mezclas de tipos de datos en expresiones: En


String char
“hola”; este caso, se trata de que en ocasiones se requiere evaluar expresiones
que utilizan variables de diferentes tipos, por ejemplo:

Conversión entre tipos: ! int x, z;


La conversión de tipos se refiere a la situación en la que las variables de
! double y;
un tipo son mezcladas con variables de otro tipo. Cuando hacemos esto,
C hará conversiones automáticas por nosotros, por lo que es importante ! z = x + y;
que sepamos qué hará.
La variable x es de tipo int, y la variable y es de tipo double. Para
Las conversiones pueden ser de tres tipos: poder hacer la suma, ambas deben tener el mismo tipo. Lo que ocurrirá
es que de manera automática C convertirá x a un doble y hará la suma,
a) Conversiones automáticas: ocurren cuando se tiene que convertir
cuyo resultado será un doble.
objetos de tipo int, short, long, char y float dentro de una
asignación. Una regla fundamental es que se eliminarán los bits de La asignación es otra operación, por lo que al almacenar la suma en z
mayor peso que sean necesarios. (que es de tipo entero) truncará el valor real para convertirlo en un
entero. Las conversiones de este tipo se hacen jerárquicamente, es
La siguiente tabla muestra las reglas de conversión de tipos durante la
decir, se convierten los objetos de un tipo bajo a objetos de tipo alto. El
asignación.
orden, iniciando con el más bajo es el siguiente: char, int,
unsigned, long y double.

39
! c) Las conversiones solicitadas por el usuario, también
llamadas cast, obligan a que una expresión sea de un tipo específico. El
Variables locales:
formato general es: (tipo de dato) expresión, donde tipo de dato Las variables locales se declaran dentro de una función. Sólo pueden
indica el tipo específico deseado. ser usadas por las expresiones que hay dentro de esa función. En C las
variables locales son creadas cuando la función es llamada, y
! Por ejemplo: destruidas cuando se sale de la función. Igualmente, la memoria
utilizada para almacenarlas es creada y destruida dinámicamente. Por lo
! (float)x / 2; el resultado de esta división será de tipo anteriormente expuesto, hay que considerar que las variables locales
float, independientemente del tipo de x. pierden su valor al salir de la función.

! x= sqrt((double)23); algunas funciones requieren que su


argumento sea de algún tipo en especial, en el caso de sqrt, requiere Parámetros formales:
un tipo double. Si una función tiene argumentos, estos tienen que haber sido
declarados. Estos argumentos son conocidos como parámetros

Variables formales de la función. La mayoría de los compiladores actuales


permiten hacer la declaración dentro de los mismos paréntesis de la
función. Es decir,
Declaración de variables
En C todas las variables deben haber sido declaradas antes de usarlas. funcion_1(int x, int y, float z)
Básicamente las variables se pueden declarar en tres partes de un
{
programa en C: dentro de las funciones, en la definición de los
parámetros de la función o fuera de todas las funciones. Estas variables ... Cuerpo de la función
se llaman variables locales, parámetros formales y variables globales,
respectivamente. }

Debe tenerse cuidado en que los tipos declarados para los parámetros
formales coincidan con los argumentos, pues de lo contrario puede
pasar cualquier cosa, ya que C no verifica esta correspondencia.

40
una variable diferente (aunque tenga el mismo nombre) y actuará sobe
Variables globales: la variable local siempre.
Las variables globales mantienen su valor a lo largo de todo el
programa mientras dura su ejecución. Este tipo de variables se crean Por ejemplo:
declarándolas fuera de toda función. Por ejemplo, en el siguiente
int main(void)
programa la variable resultado1 podrá ser utilizada por cualquiera de
las funciones o incluso por la función main(). {
int resultado1; /* variable global */ ! int dato1 = 100; /* variable global */
main() ! ....
{ ! funcion2(...)
! funcion_1(...) ! {
! {! ... ! ! int dato1=80; /*variable local */
! } ! ! ...
! funcion_2(...) ! ! printf(“El valor es: %d”, dato1);
! { float x=10.0; /* variable local */ ! }
! resultado1 = x / 3.0; ! printf(“El valor es: %d”, dato1);
! } }
! printf(“El resultado fue: %f”, resultado1); Si ejecutáramos ese programa, obtendríamos dos resultados, uno con
80 y otro con 100. La variable que dejamos en azul es diferente de la
}
que está con color rojo, aunque se llamen igual. La que está en color
Si por alguna razón se declara dentro de una función una variable que rojo sólo “existe” dentro de la función en la que fue declarada, es decir
ya había sido declarada como global, C interpretará a ésta última como funcion2.

41
Archivo 1! Archivo 2
Es importante recordar que si una variable local y una global tienen el
mismo nombre, todas las referencias a ese nombre dentro de la función, int x,y;
 extern int x,y;

serán para la variable local y no tendrán efecto alguno sobre la variable char ca;"" " extern char ca;

global. int main(void)
 int main(void)

{
 {

Modificadores de tipos de variables: funcion1()
 funcion1()

Debido a que C permite compilar por separado los módulos de un {! ! ! ! {! ! ! 

programa, para enlazarlos sin que haya problemas por las variables, se ! ...!
 ! ...!

han definido tres modificadores que actúan sobre las variables. Dichos }
 }

modificadores son: extern, static y register. } }

Variables Externas (extern):


Las variables globales son muy útiles en ocasiones, pero debe evitarse
Cuando se intenta declarar dos veces una variable global se obtendrá
su uso si no es necesario, ya que ocupan memoria durante todo el
un error. Igualmente se tendrán problemas si se intenta declarar las
tiempo de ejecución del programa, el usar variables globales en lugar de
mismas variables locales en todos los archivos. La solución es declarar
locales hace que las funciones sean menos generales, ya que se
todas las variables globales en un archivo y utilizar declaraciones
refieren a algo que se define fuera de ellas y, finalmente, el usar muchas
extern en los otros.
variables globales puede ocasionar efectos laterales desconocidos.
Por ejemplo: En el archivo 1 declaramos como enteros x y y. Luego, en
el archivo2, quiero usar esas mismas variables (como si fueran Variables Estáticas (static)
globales), para ello, debo declararlas pero usando extern, con lo que C Las variables estáticas son variables permanentes en su propia función
entenderá que las variables ya fueron usadas en otro módulo del o archivo. Se diferencían de las variables globales en que aunque no
programa. son conocidas fuera de su función o archivo, mantienen sus valores
entre llamadas sucesivas. Un ejemplo de función en donde esta
declaración puede ser útil es cuando se define una serie recursiva. Por
ejemplo:

42
serie()

{
Constantes
Las constantes en C pueden ser de cualquier tipo, en ocasiones se
! static float result; separan en dos grandes grupos:

! result = result + result/3.0; ! a) Constantes numéricas. Las constantes numéricas tienen


implícitamente un tipo de dato que está determinaso por la forma en la
! return(result);
cual se representa a la constante. En el caso de las constantes enteras,
} es posible manejar tres tipos: Decimal, Octal y Hexadecimal.

En este ejemplo, la variable result permanece entre llamadas sucesivas. ! b) Constantes de carácter y cadena (string). Las constantes de
Esto permite producir un número nuevo cada que se llama a serie(), que carácter son indicadas mediante comillas sencillas, por ejemplo: 'a'. En
se basa en el último número generado. C existen constantes para manejar los caracteres no imprimibles. Esas
constantes se indican con el slash (barra invertida). Estas se usan para
utilizar varios códigos especiales. En la siguiente tabla se muestran
Variables Registro (register):
algunos de los más importantes.
C tiene otro modificador de tipo de variable, que casi siempre se aplica a
los tipos int y char. El modificador register obliga al compilador de
C a mantener el valor de las variables declaradas con este modificador
en un registro del CPU en vez de hacerlo en memoria, que es donde
normalmente se almacenan las variables. Esto hace que las
operaciones con este tipo de variables sean muy rápidas, por lo que son
ideales para el control de ciclos. El modificador register sólo es
aplicable a las variables globales y a los parámetros formales. No se
puede usar con variables globales. Desafortunadamente, el número de
variables register dentro de una misma función depende del
Las constantes de cadena se indican mediante comillas dobles. Debe
procesador empleado. Por lo regular los de 8 bits permiten una variable
tenerse en cuenta que C añade a las constantes de cadena el carácter
de este tipo, mientras que los de 16 bits permiten dos o más.
nulo ('\0') al final de la misma. Por ejemplo, la representación de la
cadena "abc" es en realidad: a b c d \0.

43
2.- Determine los tipos de datos apropiados para los siguientes datos:
Ejercicios a. El promedio de tres datos.
1.- Sin correr este código escriba qué debería obtener. Luego, copie y
ejecute el siguiente código. Compare. b. El número de días en la semana.

#include <stdio.h> c. La longitud del Puente Matute Remus.

int main(void) d. La distancia de la tierra a la Luna.


{
e. El nombre de un estudiante.
! int numero1=1;
f. El código postal de una casa.
! int numero2=2.1;
! float numero3=12.23; 3.- Cree un programa que tenga:

! char letra=’a’; a. Una declaración de una variable global.


! char palabra[5]=”HOLA”;
b. Una declaración de una variable local.
! printf("\n El valor de numero1 es: %d", numero1);
c. La impresión de ambas.
! printf("\n El valor de numero2 es: %d", numero2);
! printf("\n El valor de numero3 es: %f", numero3);
! printf("\n El valor de letra es: %c", &letra);
! printf("\n El valor de numero1 es:%s",& palabra);

! return 0;
}

44
Sección 2

Operadores y Operadores
Un operador es un símbolo que indica al compilador que se lleven a

expresiones cabo ciertas manipulaciones. C maneja cinco tipos de operadores:


aritméticos, lógicos, relacionales, de bits y especiales.

Operadores Aritméticos:

Contenido

1. Operadores aritméticos.

2. Operadores relacionales.

3. Operadores lógicos.

4. Operadores de bits.
Los operadores de incremento y decremento permiten añadir y restar la
5. Operadores especiales. unidad a sus operandos, respectivamente. Por ejemplo, x= x+1 es lo
mismo que x++ ó que ++x. Sin embargo, existe una diferencia entre si
se usa el operador a la izquierda o a la derecha. Cuando el operador
precede al operando, el C utiliza el valor del operando antes de
incrementarlo o decrementarlo.

45
Operadores Relacionales: Operadores De Bits:

Los operadores relacionales se refieren a la relación que existe entre El lenguaje C soporta un completo juego de operadores de bits. Las
dos valores. La clave de este tipo de operadores son los valores de operaciones con bits se refieren a la comprobación, colocación o
verdadero (true) y falso (false). En C, cualquier valor distinto de 0 es desplazamiento de los bits actuales de una variable entera o carácter.
equivalente a verdadero. Las operaciones que los usan devolverán un Este tipo de operaciones son muy utilizados en aplicaciones con
1 cuando sea verdadero y un 0 cuando sea falso. Estos operadores controladores de dispositivos ya que permiten enmascarar ciertos bits,
tienen un nivel de precedencia menor al de los operadores aritméticos. como el de paridad.

La operación AND es una forma de poner a cero los bits. Con AND,
cualquier bit que esté a 0 en el operando hará que el correspondiente bit
de la variable se ponga a 0.

El OR bit a bit pude usarse para poner los bits a 1. Cualquier bit del
operando que esté a 1 hará que su correspondiente bit en la variable se
ponga a 1 también.

El XOR pondrá a uno sólo los bits que al compararlos sean distintos.
Operadores Lógicos:
Los operadores de desplazamiento mueven todos los bits de una
variable a la derecha o a la izquierda, según se especifique. A medida
Los operadores lógicos se refieren a las formas en que pueden darse que se desplazan los bits, se introducen ceros.
las relaciones entre dos objetos. La forma de evaluación es similar a la
de los operadores relacionales, sin embargo en los operadores lógicos e
usan los valores de verdad.

46
Los operadores & y *

El operador & es de tipo unario y devuelve la dirección de memoria del


operando. Por ejemplo:

! m= &contador;

Guarda en la variable m la dirección de memoria donde reside la


variable contador. m debe ser una variable de tipo apuntador.

El operador * es un operador unario que devuelve el valor de la variable


Operadores Especiales: ubicada en la dirección de memoria de su operando. Por ejemplo:

! q = *m;
C tiene definidos algunos operadores especiales, que son el operador
condicional (?) y los operadores para el manejo de apuntadores. Si m contiene la dirección de memoria dela variable contador, entonces
el valor de contador se copiará hacia q.
El operador ?

El operador condicional es el único operador ternario que existe en C.


Su sintaxis es la siguiente:

! expresion_1 ? expresion_2 : expresion_3

Si expresion_1 es verdadera, evalúa expresion_2 y este será el


valor del resultado. Si expresion_1 resultó falsa, evalúa
expresion_3 y ese será el resultado. Es equivalente a una expresión
if-then-else, es decir:

! if expresion_1 then expresion_2;

! else expresion_3;

47
Tabla de precedencias.

! La siguiente tabla muestra todas las precedencias entre


Ejercicios
operadores. 1.- Diseña e implementa un programa en C que dada una longitud en
pulgadas y un peso en libras convierta la longitud a metros y el peso a
kilogramos.

2.- Diseña y construye un programa en C que dados el tipo de cambio al


día de hoy y una cantidad de dólares, diga a cuántos pesos equivale.

3.- Diseña y construye un programa que reciba un número entero de


cinco dígitos y que lo imprima dígito por dígito. Por ejemplo, dado N=
23461, que imprima:

! 2
! 3
! 4
! 6
! 1
4.- Se sabe que sobre un terreno rectangular se ha construido una casa
de ciertas dimensiones. Suponiendo que la casa es también rectangular
y que todo lo que no es casa es jardín, diseñe e implemente un
programa en C que diga cuánto tiempo se tardará un jardinero en podar
el pasto si lo hace a razón de 2 m2 por 15 minutos. Se conocen las
medidas del terreno y las medidas de la casa. Elabore primero el
algoritmo en pseudocódigo.

48
5.- La expansión lineal de una barra de acero como función de la
temperatura se calcula mediante la siguiente ecuación:

l = l0 [1 + ↵(Tf T0 )]
Donde l0 es la longitud a una temperatura T0, α es el coeficiente de
expansión lineal y Tf es la temperatura final de la barra. Diseñe e
implemente un programa en C que dados la longitud inicial, las
temperaturas inicial y final y el coeficiente de expansión, calcule la
longitud final.

6.- La resistencia combinada de tres resistencias conectadas en


paralelo, está dada por la siguiente ecuación.

Escriba un programa en C que calcule y muestre el valor de la


resistencia.

a) Pruebe con valores de R1= 1000, R2=1000 y R3=1000. Cuál es el


resultado? Cómo sabe usted si es el resultado correcto?

b) Si las resistencias tienen valores de R1=1500, R2=1200 y R3=2000


ohms, diga cuál es el resultado.

7.- Para convertir una temperatura dada en grados Celsius a grados


Fahrenheit se usa la ecuación:

Diseñe e implemente en C un programa que haga ese cálculo.


49
Estructuras de
Control

3
Programar significa en
realidad hacer que la
computadora procese
datos de una cierta
manera. Para ello, debe
controlarse el flujo de los
datos. Este capítulo habla
de ciclos, de tomas de
decisión y de secuencias
de pasos.
Sección 1

Condicionales La estructura if
En C existen dos tipos de estructuras de control condicional. Se trata del
if-else y el switch.

La estructura if:

! La forma general de la estructura if es la siguiente:

! if(expresión_1)
Contenido
! expresión_2

! else expresión_3
1. La estructura if.
La parte correspondiente al else es opcional. Cuando se desea ejecutar
2. La expresión switch.
un conjunto de expresiones, estas se encierran entre llaves y se
3. Ejercicios separan por puntos y comas:

! if(expresión_1)

! {

! ! conjunto1 de expresiones

! }

! else { conjunto2 de expresiones}

51
En ambos casos, si el resultado de evaluar expresión_1 es verdadero, else if(condición)
se ejecuta expresión_2 o el bloque correspondiente. En caso de
cualquier otro resultado se ejecuta expresión_3. ! expresión

Uno de los aspectos más confusos de las expresiones if es tenerlas ! else .....
anidadas. Por ejemplo:
! ! else
if(x)
! ! expresión.
{ ....}
Las condiciones se evalúan de arriba para abajo. Tan pronto como se
else encuentre una condición cierta, se ejecuta la expresión asociada, y se
salta el resto de la escalera. Si ninguna de las condiciones resulta cierta,
{ se ejecutará la expresión asociada al último else. Esta expresión se usa
muchas veces como condición por defecto (default), es decir, si todas
if(y) {....} las demás fallan, ésta se ejecutará.

else {.....}
La expresión switch:
} Aunque, tal y como se vió, la escalera permite ejecutar una serie de
pruebas, el código es difícil de seguir e incluso puede confundir. Por
Hay que recordar que en C, el else se asocia al if más inmediato que no
esto, C incluye una expresión condicional con alternativas múltiples,
tenga una expresión else.
llamada switch. Su formato general es:
También es posible construir escaleras if-else-if. Su esquema es como
switch(variable) {
el siguiente:
! case constante1: expresión;
if(condición)
! ! ! ! break;
expresión;
! case constante 2: expresión;
else if(condición)
! ! ! ! break;
expresión

52
! ....

! default : expresión;

La diferencia entre switch y el if es que en el primero sólo es posible


verificar igualdades. Por ejemplo, sea resp una variable que almacenará
caracteres. Entonces, un código para el switch podría ser:

switch(resp)

! case '1': expresión;

break;

! case '2': expresión;

! break;

! default: expresión;

La expresión break se usa junto con switch. Esta origina que el control
del programa salga del switch y continué en la expresión que sigue. Si
no se incluye ningún break, todas las expresiones antes y después de la
igualdad se ejecutarán.

53
Sección 2

Ciclos La estructura for


El ciclo for se usa cuando se quiere ejecutar alguna expresión más de
una vez. El formato general del for en C es el siguiente:

for(inicialización; condición; incremento)

! expresión;

Cuando se trata de bloques de expresiones, el formato es:


Contenido
for(inicialización; condición; incremento)

! {
1. La estructura for.
! ! expresión_1;
2. La estructura while.
! ! ....
3. La estructura do-while.
! ! expresión_n;
4. Expresiones break, exit y continue.
! }
5. Ejercicios.
La inicialización normalmente es una expresión de asignación que se
usa para fijar la variable de control del ciclo. La condición es una
expresión relacional que determina cuándo se saldrá del ciclo. El
incremento define cómo cambiará la variable de control del ciclo cada
vez que se repita éste. El ciclo se continuará mientras que el resultado
de la condición sea verdadero. Por ejemplo, el siguiente código realiza
la suma de los n primeros enteros:

54
for(i=1; i<n;i++) Esto significa que un ciclo do-while siempre se ejecutará al menos una
vez. El formato general del ciclo do-while es:
suma=suma+i;
do{
La prueba de la condición siempre se lleva a cabo primero. Esto es, el
código dentro del ciclo podría no ejecutarse ninguna vez si la condición ! ! expresiones;
resulta falsa la primera vez que se evalúe.
} while(condición);
En la inicialización es posible incluir más de una expresión,
separándolas por comas. La condición puede estar formada por varios Quizás la utilización más común de do-while sea en una rutina de
elementos relacionados entre sí mediante operadores lógicos y selección por menú.
relacionales. El incremento también puede tener varias expresiones,
solamente hay que separarlas con comas. Las expresiones break, exit y continue
Salida mediante break y exit():
El ciclo while:
La expresión break y la función de biblioteca exit() permiten forzar una
Otra manera de formar un ciclo es mediante while. El formato general
salida de un ciclo, saltándose la condición.
es:
La expresión break:
! while(condición) expresión;
Cuando se encuentra una instrucción break dentro de un ciclo, éste se
en donde expresión puede ser un bloque de instrucciones. La condición
finaliza inmediatamente, y el control del programa pasa a la siguiente
puede ser cualquier expresión, siendo cierto cualquier valor distinto de
expresión del ciclo.
cero. La expresión se ejecuta mientras que la condición sea verdadera.
Al igual que en el for, while comprueba la condición en la parte superior La función exit():
del ciclo, lo que significa que es posible que el código no se ejecute.
Una segunda forma de terminar un ciclo desde dentro es utilizando la
función exit() que se encuentra en la biblioteca estándar. Debido a que
El ciclo do-while: la función originará una terminación inmediata del programa y que el
A diferencia de los ciclos for y while, que prueban la condición del ciclo
control pase al sistema operativo, su utilización es algo limitada.
al principio, el ciclo do-while comprueba su condición al final del ciclo.
Normalmente se llama a la función con un argumento 0 para indicar que

55
la terminación es normal. Se hace uso común de exit() cuando no se
satisface una condición obligatoria para la ejecución del programa.

La instrucción continue:

La expresión continue actúa al revés que la expresión break; obliga a


que tenga lugar la siguiente iteración, saltándose cualquier código
intermedio. En los ciclos while y do-while, una expresión continue será
la causa de que el control vaya directamente a la condición y de que
luego continúe el proceso del ciclo. En el caso del for, se ejecuta la parte
de incremento del ciclo, se ejecuta la condición y el ciclo continúa.
Como se puede ver, continue se puede utilizar para facilitar la salida de
un ciclo, obligando a que la condición se compruebe antes.

Ejercicios

56
Arreglos

4
Lorem ipsum dolor rutur
amet. Integer id dui sed
odio imperd feugiat et nec
ipsum. Ut rutrum massa
non ligula facilisis in
ullamcorper purus dapibus.
Sección 1

Arreglos Arreglos unidimensionales


Una de las estructuras de datos más importantes son los arreglos

unidimensionales (vectores y matrices). Un arreglo puede considerarse como un conjunto


de elementos del mismo tipo que se encuentran situados en posiciones
contiguas de memoria y que son referenciados por un solo nombre. En
C es posible construir arreglos de cualquier tipo de datos. Los arreglos
están íntimamente relacionados con los apuntadores, de hecho, el
nombre de un arreglo es un apuntador constante al elemento cero del
Contenido arreglo.

El que en el lenguaje C se vea a los arreglos de forma muy cercana a la


1. Declaración de arreglos. implementación en la máquina, (por ejemplo, considerando al arreglo
como un apuntador al lugar de la memoria donde inicia éste) puede
2. Inicialización. explicar porqué los arreglos inician con el índice cero.

3. Uso de arreglos.

4. Strings o cadenas.

5. Ejercicios.

La declaración general para un arreglo unidimensional es:

tipo_de_dato nombre_arreglo[tamaño];

Por ejemplo:

! int a[12]; Arreglo de 12 enteros.

58
! char b[4]; Arreglo de 4 caracteres. El compilador de C no hace ninguna comprobación de los límites de los
arreglos. Esto quiere decir que si de declara un arreglo de 10 elementos
! float c[15]; Arreglo de 15 flotantes (reales). y se intenta usar el elemento 14, el compilador no marca ningún error,
simple y sencillamente leerá lo que se encuentre 14 posiciones después
del inicio. Es importante tomar esto en cuenta, pues se podría
Tal y como se mencionó antes, los arreglos empiezan con el elemento sobreescribir fuera de un arreglo y escribir algún dato de otra variable o
cero. Por ejemplo, cuando se escribe int x[10]; se está declarando un incluso en una parte del programa. En los programas escritos
arreglo de enteros que tiene 10 elementos, de x[0] a x[9]. correctamente, esto generalmente no es un problema, pero puede serlo
cuando se está aprendiendo a programar en C.
Al mismo tiempo que se declara un arreglo se puede inicializar, para lo
cual se incluyen los elementos del mismo entre llaves y separados por Cuando se declara un arreglo para almacenar una cadena de
comas, por ejemplo: caracteres, debe preverse un byte extra para el carácter nulo (\0), que
tienen todas las cadenas. Por ejemplo si se quisiera declarar un arreglo
int A[10] = {1,1,1,1,1,1,1,1,1,1}; mis_datos para contener una cadena de 10 caracteres, se tendría que
escribir:
Desafortunadamente no hay forma de abreviar esta situación. En caso
de tener un arreglo más grande, se podría requerir escribir un ciclo para ! char mis_datos[11];
inicializarlo.
La siguiente figura ilustra las dos formas en que puede ser visto un
Cuando la lista de inicialización es más pequeña que el número real de arreglo en C:
datos que contendrá el arreglo, C inicializa el resto en cero. Para
asegurarnos de ello se suele emplear:

int A[10]={0};

Es posible inicializar arreglos en los que no se especifique el número de


elementos, por ejemplo:
Dado que no se puede usar a los arreglos como argumentos de
int inicio[]={1,2,3,4,5,6}; funciones o para regresar valores, en esos casos se emplea el punto de
vista de apuntadores, de tal suerte que lo que debe pasarse como
C reservará tanto espacio de memoria como sea necesario para que argumento es en realidad la dirección del primer elemento del arreglo.
quepan todos los elementos en el arreglo. De esta forma, C no hace una copia entera del arreglo, sino que
59
solamente se referencía a la dirección de memoria donde inicia el char string[];
arreglo. Esto significa que cualquier operación que se realice en el
arreglo mientras se está dentro de la función tendrá efecto fuera de la {
misma.
! ....
Cuando se pasan arreglos unidimensionales a las funciones, se llama a
}
la función con el nombre del arreglo. Por ejemplo, para pasar el arreglo
y a la función gets(), se escribiría: gets(y); Ambos métodos de declaración son idénticos porque le indican al
compilador que se va a recibir un apuntador a carácter. Realmente, por
lo que respecta al compilador,
Esto haría que la dirección del primer elemento de y pase a gets().
foo(string)
Realmente se está pasando un apuntador a un carácter.
char string[11]:
Si una función va a recibir un arreglo unidimensional, hay que declararlo
como parámetro formal en una de las siguientes dos formas: como un {
apuntador o como un arreglo. Por ejemplo, para recibir s dentro de una
función denominada foo(), ésta se declararía como: ! ....

foo(string) }

char *string también valdría porque el C generará un código que indica a func() que
va a recibir un apuntador a carácter. Como no hay comprobación de
{ límites, al llamar a la rutina se puede pasar un arreglo de cualquier
tamaño, incluso aunque sólo se hubiese declarado un tamaño de 11.
! .....
Por ejemplo, para declarar un arreglo de enteros llamado listanum con
}
diez elementos se hace de la siguiente forma:
o como

foo(string)
int listanum[10];

60
En C, todos los arreglos usan cero como índice para el primer elemento. Observar que para declarar cada dimensión lleva sus propios paréntesis
Por tanto, el ejemplo anterior declara un arreglo de enteros con diez cuadrados.
elementos desde listanum[0] hasta listanum[9].
Para acceder los elementos se procede de forma similar al ejemplo del
La forma como pueden ser accesados los elementos de un arreglo, es arreglo unidimensional, esto es,
de la siguiente forma:

tabladenums[2][3] = 15; /* Asigna 15 al elemento de la 3ª fila y la 4ª


listanum[2] = 15; /* Asigna 15 al 3er elemento del arreglo listanum*/ columna*/

num = listanum[2]; /* Asigna el contenido del 3er elemento a la variable num = tabladenums[25][16];
num */
A continuación se muestra un ejemplo que asigna al primer elemento de
El lenguaje C no realiza comprobación de contornos en los arreglos. En un arreglo bidimensional cero, al siguiente 1, y así sucesivamente.
el caso de que sobrepase el final durante una operación de asignación,
entonces se asignarán valores a otra variable o a un trozo del código, main()
esto es, si se dimensiona un arreglo de tamaño N, se puede referenciar
{
el arreglo por encima de N sin provocar ningún mensaje de error en
tiempo de compilación o ejecución, incluso aunque probablemente se int t,i,num[3][4];
provoque el fallo del programa. Como programador se es responsable
de asegurar que todos los arreglos sean lo suficientemente grandes
para guardar lo que pondrá en ellos el programa.
for(t=0; t<3; ++t)
C permite arreglos con más de una dimensión , el formato general es:
for(i=0; i<4; ++i)

num[t][i]=(t*4)+i*1;
tipo nombre_arr [ tam1 ][ tam2 ] ... [ tamN];

Por ejemplo un arreglo de enteros bidimensionales se escribirá como:


for(t=0; t<3; ++t)
int tabladenums[50][50];
61
{ caracteres se debe considerar un carácter adicional a la cadena más
larga que se vaya a guardar. Por ejemplo, si se quiere declarar un
for(i=0; i<4; ++i) arreglo cadena que guarde una cadena de diez caracteres, se hará
como:
printf("num[%d][%d]=%d ", t,i,num[t][i]);
char cadena[11];
printf("\n");
Se pueden hacer también inicializaciones de arreglos de caracteres en
}
donde automáticamente C asigna el caracter nulo al final de la cadena,
de la siguiente forma:

} char nombre_arr[ tam ]="cadena";

En C se permite la inicialización de arreglos, debiendo seguir el Por ejemplo, el siguiente fragmento inicializa cadena con ``hola'':
siguiente formato:
char cadena[5]="hola";

El código anterior es equivalente a:


tipo nombre_arr[ tam1 ][ tam2 ] ... [ tamN] = {lista-valores};
char cadena[5]={'h','o','l','a','\0'};
Por ejemplo:
Para asignar la entrada estándar a una cadena se puede usar la función
int i[10] = {1,2,3,4,5,6,7,8,9,10}; scanf con la opción %s (observar que no se requiere usar el operador
&), de igual forma para mostrarlo en la salida estándar.
int num[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
Por ejemplo:
5.2 Cadenas
main()
A diferencia de otros lenguajes de programación que emplean un tipo
denominado cadena string para manipular un conjunto de simbolos, en {
C, se debe simular mediante un arreglo de caracteres, en donde la
char nombre[15], apellidos[30];
terminación de la cadena se debe indicar con nulo. Un nulo se
especifica como '\0'. Por lo anterior, cuando se declare un arreglo de

62
printf("Introduce tu nombre: "); ! 1.! Escribir un programa que lea un arreglo de cualquier tipo
(entero, flotante, char), se podría pedir al usuario que indique el tipo de
scanf("%s",nombre); arreglo, y también escribir un programa que revise el arreglo para
encontrar un valor en particular.
printf("Introduce tus apellidos: ");
! 2.! Leer un texto, un caracter a la vez desde la entrada estándar
scanf("%s",apellidos);
(que es el teclado), e imprimir cada línea en forma invertida. Leer hasta
printf("Usted es %s %s\n",nombre,apellidos); que se encuentre un final-de-datos (teclar CONTROL-D para generarlo).
El programa podría probarse tecleando progrev | progrev para ver si una
} c o p i a e x a c t a d e l a e n t r a d a o r i g i n a l e s r e c r e a d a .

Para leer caracteres hasta el final de datos, se puede usar un ciclo
El lenguaje C no maneja cadenas de caracteres, como se hace con
c o m o e l s i g u i e n t e

enteros o flotantes, por lo que lo siguiente no es válido:

main() char ch;

{ ! 3.! while( ch=getchar(), ch>=0 ) /* ch < 0 indica fin-de-datos */

char nombre[40], apellidos[40], completo[80]; ! 4 .!



ochar ch;

! 5.! while( scanf( "%c", &ch ) == 1 ) /* se lee un caracter */


nombre="José María"; /* Ilegal */
! 6 .!

! apellidos="Morelos y Pavón"; /* Ilegal */

! completo="Gral."+nombre+appellidos; /* Ilegal */ ! 7.! Escribir un programa para leer un texto hasta el fin-de-datos,
y mostrar una estadística de las longitudes de las palabras, esto es, el
}
número total de palabras de longitud 1 que hayan ocurrido, el total de
5.3 Ejercicios longitud 2 y así sucesivamente. Define una palabra como una secuencia
de caracteres alfabéticos. Se deberán permitir palabras hasta de una
l o n g i t u d d e 2 5 l e t r a s .


63
U n a s a l i d a t í p i c a p o d r í a s e r c o m o e s t a :


longitud 1 : 16 ocurrencias

! 8.! longitud 2 : 20 ocurrencias

! 9.! longitud 3 : 5 ocurrencias

! 10.! longitud 4 : 2 ocurrencias

! 11.! longitud 5 : 0 ocurrencias

! 12.! ........

! 13.!

64
Sección 2

Arreglos Arreglos multidimensionales


El lenguaje C permite arreglos multidimensionales. La forma más simple

multidimensionales del arreglo multidimensional es el arreglo bidimensional. En esencia un


arreglo bidimensional es un arreglo de arreglos unidimensionales. Para
declarar un arreglo entero bidimensional de tamaño 10,20 se escribirá

int d[10][20];

Hay que poner


Contenido especial cuidado
en la
declaración: a
1. Declaración.
diferencia de la
2. Inicialización. mayoría de los
d e m á s
3. Uso de arreglos. lenguajes que
utilizan comas
4. Ejercicios. para separar las
dimensiones del arreglo, el C coloca cada dimensión entre paréntesis
cuadrados.

Los arreglos bidimensionales se almacenan en una matriz de filas y


columnas, en donde el primer índice indica la fila (renglón) y el segundo
índice indica la columna. Esto significa que el índice más a la derecha
cambia más rápidamente que el que está más a la izquierda cuando se
recorre le arreglo en secuencia.

Hay que recordar que el almacenamiento para todos los elementos del
arreglo está asignado permanentemente. En el caso de un arreglo

65
bidimensional, la siguiente fórmula determinará el número de bytes en la Los arreglos de tres o más dimensiones casi no se utilizan debido a que
memoria: ocupan mucha memoria. Tal y como se indicó anteriormente, el espacio
para todos los elementos del arreglo está asignado permanentemente
! bytes = fila * columna* número de bytes según el tipo de dato.! durante la ejecución del programa. Ese espacio aumenta
Por lo tanto, un arreglo entero con dimensiones 10,5 tendrá 10*5*2=100 exponencialmente con el número de dimensiones. La computadora
bytes asignados. tarda tiempo en generar cada índice y esto puede hacer que los arreglos
multidimensionales accedan mas lentamente que los arreglos
unidimensionales con el mismo número de elementos. Por esta y otras
Cuando se pasan arreglos bidimensionales a las funciones, sólo se razones, cuando son necesarios arreglos multidimensionales muy
pasa un apuntador al primer elemento. Esto se puede hacer utilizando el grandes, se deben asignar dinámicamente los bits y las partes del
nombre del arreglo sin índices. Sin embargo, una función que reciba un arreglo que sean necesarios, y utilizar apuntadores.
arreglo bidimensional como parámetro tiene que definir la longitud de la
Cuando se pasan arreglos multidimensionales como argumentos de
segunda dimensión. Por ejemplo, una función que recibiera un arreglo
funciones, hay que declarar todas las dimensiones excepto la primera.
entero bidimensional con las dimensiones 10,10 se declararía como:
Por ejemplo, si se declara el arreglo m como int m[4][3][6][5];
foo(x) entonces una función foo() que recibiera m, empezaría:

int x[][10]; foo(d)

{ int d[][3][6][5];

.... {

} ....

También sería posible dar la primera dimensión, pero no es necesario. }

El lenguaje C permite arreglos de más de dos dimensiones. El límite


exacto, si lo hay, vendrá determinado por el compilador. El formato
general de una declaración de un arreglo multidimensional es:

! tipo_de_dato nombre_arreglo [a][b][c]...[n];

66
Funciones

5
Lorem ipsum dolor rutur
amet. Integer id dui sed
odio imperd feugiat et nec
ipsum. Ut rutrum massa
non ligula facilisis in
ullamcorper purus dapibus.
Sección 1

Sin título Lorem Ipsum


5. FUNCIONES

5.1 Formato de una Función

Lorem Ipsum ! La unidad fundamental para la elaboración de programas


en C es la función. Las funciones son conjuntos de expresiones
1. Lorem ipsum dolor sit amet, consectetur. con los que se construyen los programas. Un programa en C
está formado por una o más funciones que pueden estar en
2. Nulla et urna convallis nec quis blandit odio uno o más archivos físicos. Esto permite un diseño altamente
mollis. modular, con lu cual es posible reutilizar funciones, elaborar
herramientas de desarrollo, diseñar bibliotecas de funciones,
3. Sed metus libero cing elit, lorem ipsum. Adip etc.
inscing nulla mollis urna libero blandit dolor.

4. Lorem ipsum dolor sit amet, consectetur.


! Las características de las funciones en C permiten
5. Sed metus libero cing elit, lorem ipsum. Quis que considerarlas como cajas negras a las cuales se les
euismod bibendum sag ittis. proporciona un conjunto de información de entrada (datos) y
devuelven información de salida. El siguiente diagrama
6. Sed metus libero cing elit, lorem ipsum.
muestra gráficamente ésta idea.
7. Quis que euismod bibendum sag ittis.

68
! }

! Todas las funciones devuelven un valor (de ahí su ! Una función se puede utilizar en expresiones debido a
nombre). Este valor se especifica dentro de la función mediante que cada función tiene un valor que, o bien es un valor
la instrucción return: devuelto o por defecto es cero. Por lo tanto, cada una de las
siguientes expresiones es válida en C:

! return(expresión)
x=potencia(y);
donde expresión es cualquier expresión válida (incluyendo una
constante), también puede ser 0 si no se especifica ningún otro if(max(x,y)>100) printf("Mayor que");
valor. Por defecto, las funciones devolverán valores enteros. Si
for(ca=getchar();esdigito(ca);)...;
se especifica, pueden devolver otros tipos de valores.

! Sin embargo, una función no puede ser objeto de una


! El formato general de una función en C es:
asignación. Una expresión como: intercambio(x,y)=100 no es
válida.

! nombre_funcion(lista de parámetros)

! declaraciones de los parametros

! {

! cuerpo de la función;

69
! Aunque todas las funciones devuelven valores, es posible 5.2 Argumentos de las Funciones
clasificarlas en tres tipos, dependiendo de lo que devuelven:

! Generalmente hay dos formas en las que pueden pasarse


! Las que llevan a cabo operaciones específicas sobre sus los argumentos a las funciones. La primera se denomina
argumentos y devuelven un valor como resultado de esa llamada por valor. Este método copia el valor de cada uno de
operación. Por ejemplo, sqrt(). los argumentos en los parámetros formales de la función. Con
este método, los cambios que se hacen en los parámetros de
la función no tienen efecto en las varibales utilizadas para
! Las que manejan información y devuelven un valor que llamar a la función.
simplemente indica el éxito o fracaso de esa manipulación. Por
ejemplo write().
! La segunda forma es la llamada por referencia, en la que
los argumentos se pueden pasar a las funciones. Con este
! Las que no devuelven un valor explícito. Básicamente, la método, se copia la dirección de cada argumento en los
función es simplemente un procedimiento que no devuelve parámetros de la función. Esto significa que los cambios
ningún valor. Un ejemplo es clasifica(), una función que hechos en los parámetros afectarán a las variables utilizadas
clasificará datos. para llamar a la función.

! No es necesario asignar el valor que devuelven las ! Las funciones en C utilizan la llamada por valor. Esto
funciones a alguna variable, en el caso de que no se haga, C significa, por lo general, que no se pueden alterar las variables
simplemente descarta dicho valor. utilizadas para llamar a la función. Por ejemplo, en la siguiente
función:

70
que cualquier otro valor. Por ejemplo, la función intercambia(),
que intercambie los valores de sus dos argumentos enteros se
cuadrado();
escibirá como:
int x;

x=x*x;

return(x);

si el argumento es una constante, el valor de la misma se copia


intercambia(x,y)
en el parámetro x. Cuando la asignación x=x*x tenga lugar, lo
único que se modifica es la variable local x. La constante int *x,*y;
original no queda afectada. El mismo proceso tiene lugar
{
cuando se llama a la función con una variable.
! int temp;! !

! temp=*x; /* Almacena el contenido de la dirección


! Debido a que todos los argumentos en C se pasan por
indicada en x */
valor, no es posible cambiar las variables utilizadas en la
llamada. Sin embargo, C permite simular una llamada por ! *x=*y; /* Almacena el contenido de la dirección indicada
referencia utilizando apuntadores para pasar la dirección de
una variable a la función y para cambiar la variable utilizada en en y en la dirección indicada en x */
la llamada. Se puede pasar una dirección a una función igual
! *y=temp;
71
} de memoria donde se encuentran ubicadas

las variables */

! Recordemos que el operador unario " * " se usa para }


indicar el contenido de la localidad de memoria señalada por la
variable que se encuentre a continuación del operador.
! Antes de que una función reciba un apuntador, hay que
definir el tipo de datos al que van a apuntar, pues de no hacerlo
! Pero esto es solamente la mitad de la historia, cuando se se pueden obtener algunos resultados inesperados.
llame a la función, el valor que se debe pasar como argumento
es la dirección de las variables que se deseen intercambiar. El
operador unario " & " permite hacer referencia a la dirección de Llamadas a funciones con Arreglos:
las variables. Por lo tanto, en el ejemplo anterior, habría que
hacer algo como lo siguiente:

! Cuando se llama a una función con el nombre de un


arreglo, lo que se pasa a la función es la dirección del primer
main() elemento en el arreglo. Esto significa que la declaración de
parámetros tiene que ser un apuntador. Por ejemplo, si se
{
quisiera escribir un programa que introdujese 10 números
! int x,y; desde el teclado y los imprimiera, habría que escribir un
programa como el siguiente:
! x=10;

! y=20;
main()
! intercambia(&x,&y); /* Se pasan como argumentos las
direcciones {
72
! int t[10],i; operando y alterando potencialmente el contenido real de los
elementos del arreglo utilizados para llamar a la función.
! for(i=0;i<10;++i)

! t[i]=getnum();
Funciones void:
! visualiza(t);

}
! Existen muchas funciones que no regresan ningún valor,
por ejemplo, una rutina de ordenamiento, una que invirtiera una
vizualiza(num) cadena alfanumérica o una que imprima errores.

int *num;

{ ! Generalmente a este tipo de funciones se les asigna el


tipo void. Cuando se declara una función con este tipo, se le
! int i; esta diciendo al compilador que la función no regresa ningún
valor. En algunos compiladores de C no es necesario
! for(i=0;i<10;i++)
especificar el tipo void de las funciones, ya que cuando
! printf("%d",num[i]); alcanzan el fin de la misma, el control regresa
automáticamente hacia el lugar donde se hizo la llamada. Sin
} embargo es recomendable que se especifique cúando la
! Un elemento de un arreglo utilizado como un argumento función es void para asegurar la portabilidad de los programas.
es tratado igual que cualquier otra variable sencilla. Sin
embargo, cuando se utiliza el nombre de un arreglo como un
argumento de función, se pasa por referencia. Se estará 5.3 Recursión

73
! En el lenguaje C es posible definir funciones recursivas, }
es decir, aquellas funciones que se llaman a sí mismas. Un
ejemplo clásico es la función para calcular el factorial de un
número, que se puede estructurar como sigue: fact(n)

int n

{
! Hay que notar que todas las funciones recursivas deben int resp;
tener una condición de terminación o criterio de paro, pues de
no ser así, se volverían infinitas. if (n==1)

return(1);

#include <stdio.h> resp = fact(n-1) * n;

#define NUM 5 return (resp);

main()

{ ! Cada vez que la función se llama a sí misma, se crean


nuevas variables locales y parámetros formales en el área de
int x; stack y la función se vuelve a efectuar con estos nuevos datos.
Uno de los problemas que se pueden presentar en las
x = NUM;
funciones recursivas es que el número se llamadas a sí misma
printf ("El factorial de %d es: %d", x, fact(x)); sea tan grande que se agote el área del stack.

74
75
Estructuras de
Datos

6
Lorem ipsum dolor rutur
amet. Integer id dui sed
odio imperd feugiat et nec
ipsum. Ut rutrum massa
non ligula facilisis in
ullamcorper purus dapibus.
Sección 1

Estructuras de Introducción
6. ESTRUCTURAS, UNIONES Y TIPOS DEFINIDOS POR EL

datos USUARIO

6.1 Estructuras

Contenido
! Una estructura es una colección de variables que están
referenciadas bajo un solo nombre. Cada estructura está formada por
1. Introducción. una ó mas variables que están relacionadas lógicamente. Estas
variables se denominan elementos de la estructura. Las estructuras,
2. Estructuras (struct). como grupos de variables conectados lógicamente, se pueden pasar
fácilmente a funciones.
3. Uniones (union).

4. Tipos de datos definidos por el usuario.


! Por ejemplo, un nombre y una dirección agrupadas en una lista de
correo es un conjunto común de inforrmación relacionada. El siguiente
fragmento de código declara una estructura para mantener los campos
del nombre y la dirección. La palabra clave struct le indica al compilador
que se va a definir una plantilla de una estructura:

! struct directorio {

! ! ! char nombre [30];

77
! ! ! char calle [40]; ! ! ! char nombre[30];

! ! ! char ciudad [20]; ! ! ! :

! ! ! char edo [20]; ! ! ! :

! ! ! unsigned long int cp; ! ! } info;

! ! ! };

! El formato general de una definición de estructura es :

! La definición se termina con un punto y coma porque es una


sentencia. Ahora bien, hasta este momento todavía no se ha declarado
una variable, sino que solamente se ha definido el formato del dato. ! ! ! struct nombre_estructura {
Para declarar una variable con esta estructura se debería escribir:
! ! ! ! ! tipo nombre_var;

! ! ! ! ! tipo nombre_var;
struct directorio info;
! ! ! ! ! ! :

! ! ! ! ! ! :
! Esto declarará una variable de tipo directorio denominada info.
! ! ! ! ! } variables_estructura;
Cuando se define una estructura, en esencia se está definiendo una
variable de tipo complejo formado por elementos de la estructura.

! Para hacer referencia a los elementos de la estructura se usa el


operador punto, por ejemplo, si se quisiera asignar el valor 62490 a la
! Si sólo se necesita una variable estructura, no se necesita definir
variable de estructura info declarada anteriormente:
el nombre de la estructura, por ejemplo:
info.cp = 62490;
! ! struct1

78
putchar(info.nombre[t]);

! Entonces, el nombre de la estructura seguido por un punto y el


nombre del elemento referenciará a ese elemento individual de la
estructura. El formato general de la definición es : ! Quizás la utilización mas común de las estructuras sea la de
arreglos de estructuras. Para declarar un arreglo de estructuras, primero
hay que definir una estructura y luego declarar un arreglo de ese tipo.
Por ejemplo, para declarar un arreglo de 100 elementos de estructuras
! ! ! nombre_estructura.nombre_elemento. ! del tipo directorio se escribiría :

! Por tanto para imprimir en pantalla el código postal, se escribirá : ! ! ! struct directorio info[100];
printf("%d",info.cp);

! Para imprimir el C. P. de la estructura 3 del arreglo se usa :

! Si deseáramos capturar un nombre e introducirlo en la variable se


usaría :
! ! ! printf("%d",info[2].cp);
gets(info.nombre);

6.2 Paso de Estructuras a Funciones


! Si se quisiera acceder a los elementos individuales de
info.nombre, se podría indexar nombre. Por ejemplo :

! Cuando una estructura que pasa a una función, ocurren varios


cambios en la forma en que se referencia un elemento de la estructura.
! register int t;

! for (t=0;info.nombre[t];++t)

79
! Cuando se pasa un elemento de una variable de tipo estructura,
realmente se está pasando el valor de ese elemento a la función, a
menos que ese elemento sea complejo, como un arreglo de caracteres.
Por ejemplo :

! ! struct xyz{
! Cuando una estructura se pasa a una función sólo se pasa la
! ! char x; dirección del primer byte de estructura. Esto es parecido a la forma en
que se pasan los arreglos a las funciones. Debido a que la función
! ! char y; referenciará a la estructura real y no a una copia, será posible modificar
los contenidos de los elementos reales de la estructura. El concepto
! ! int z; general es que se está pasando la dirección, lo cual significa que se
estará trabajando con un apuntador a una estructura.
! ! }wkl;

! El operador flecha -> se usa en vez del operador punto cuando se


! ! ! ! funcion_1(wkl.x)! /*Pasa el valor carácter de x*/
accede a un elemento de una estructura dentro de una función. Por
! ! ! ! funcion_2(wkl.y)! /*Pasa el valor carácter de y*/ ejemplo, la siguiente función actualiza los valores de unos contadores
de horas, minutos y segundos en una variable de tipo estructura:
! ! ! ! funcion_3(wkl.z)! /*Pasa el valor entero de z*/

actualizar(t)
! Sin embargo, si se quisiera pasar la dirección de elementos
individuales de la estructura, se colocaría el operador & antes del struct tiempo *t;
nombre de la estructura, por ejemplo :
{

(*t).segundos++;
! ! funcion_1(&wkl.x)! /*Pasa la dirección del carácter x */

80
if((*t).segundos==60){ }

(*t).segundos=0; ! Mediante el operador flecha, (*t).horas es lo mismo que t -> horas.


(Se usan los paréntesis porque el operador punto tiene una prioridad
(*t).minutos++; mayor que el operador *). Por lo tanto, la función quedaría como:

if((*t).minutos==60){ actualizar(t)

(*t).minutos=0; struct tiempo *t;

(*t).horas++; {

} t->segundos++;

if((*t).horas==24) if(t->segundos==60){

(*t).horas=0; t->segundos=0;

} t->minutos++;

! }

donde la estructura se define como: if(t->minutos==60){

t->minutos=0;

struct tiempo{ t->horas++;

! int horas; }

! int minutos; if(t->horas==24)

! int segundos; t->horas=0;


81
}

para referenciar el entero 3,7 en el elemento a de la estructura y, se


escribirá
! El operador punto se utiliza para acceder a los elementos de la
estructura cuando la estructura es global o está definida dentro de la y.a[3][7]
misma función que el código que la referencia. Se utiliza el operador
flecha -> para referenciar los elementos de la estructura cuando un
apuntador de la estructura se pasa a una función.
Una estructura puede ser un elemento de otra estructura, como en

6.3 Estructuras dentro de estructuras.


struct clientes{

struct directorio nuevadir[2]; /*Arreglo de estructuras de tipo x */


! Los elementos de las estructuras puede ser simples o complejos.
int cuenta;
Un elemento simple es cualquiera de los incluidos en los tipos de datos,
tal como un entero o un carácter. Un elemento complejo es un arreglo. } juan;

! Un elemento de una estructura que es un arreglo se trata como se en donde directorio es la estructura definida previamente. Aqui la
podría esperar, por ejemplo, en el siguiente programa estructura clientes se ha definido como formada por dos elementos: un
arreglo de estructuras del tipo directorio y un número de cuenta de tipo
entero. Este fragmento de código asignará el código 61853 al segundo
struct x { elemento de nuevadir:

! int a[10][10]; /* Arreglo de enteros de 10 x 10 */ juan.nuevadir[1].cp= 61853;

! float b;

} y; 6.4 Estructuras y Campos de Bits

82
! C permite el acceso a bits individuales dentro de un tipo de dato
mayor, por ejemplo, un byte. Esta característica es muy útil cuando se
quieren alterar máscaras de datos usadas en sistemas de información y
gráficos. Esta posibilidad se basa en las estructuras.

! La siguiente estructura define tres variables de un bit cada una:

struct dispositivo{

! unsigned activo : 1;

! unsigned listo : 1;
! La siguiente figura muestra el campo de bit de la variable
! unsigned error : 1; disp_codificado:

! } disp_codificado;

! En otro ejemplo, podría interesarnos alterar el registro del estado


del teclado en la computadora. El registro contiene la siguiente
información:

83
! Estado del teclado: 7 6 5 4 3 2 1 0 (bits del registro) ! ctrl! ! : 1;

! puerto (417h) ! alt! ! : 1;

! bit 0 = May derecho oprimido (1) ! scroll ! : 1;

! bit 1 = May izq oprimido (1) ! numlock! : 1;

! bit 2 = CTRL oprimido (1) ! caplock! : 1;

! bit 3 = ALT oprimido (1) ! insert! : 1; ! /* msb */

! bit 4 = BLOQ PANT activado (1) }mi_teclado;

! bit 5 = BLOQ NUM activado (1)

! bit 6 = BLOQ MAY activado (1) ! Los bits se especifican en la estructura empezando por el menos
significativo (lsb) y continuando hasta el más significativo (msb). Se esta
! bit 7 = INS activado(1) usando un tipo de dato unsigned char por que éste emplea 8 bits para
su representación. Podemos especificar más de un bit para cada dato
del registro dando la cantidad (en lugar de 1). Como es lógico, podemos
Para acceder a esta información y manipularla habría que crear esta usar únicamente tipos de datos enteros para los campos de bits.
estructura:

! Para acceder a cada campo de bit, se usa el operador punto, sin


struct bits_teclado{ embargo, si la estructura se pasa a una función, hay que utilizar el
operador ->.
! unsigned char

! rshift! : 1;! ! /*lsb */

! lshift! : 1;
6.5 Unión
84
justamente el requerido por el tipo de mayor tamaño en memoria. En el
ejemplo citado, un entero usa 16 bits, en tanto que un carácter requiere
! Una unión es un espacio de la memoria que puede ser usado por 8, por tanto el compilador reservará los 16.
más de una variable. Las variables que hagan uso de ese espacio
mediante el union, no necesariamente deben ser del mismo tipo. Por
ejemplo, enseguida se da la unión de un carácter y un entero, llamada
junta: 6.6 El sizeof

union junta { ! Tanto las estructuras como las uniones se pueden utilizar para
crear variables largas, pero el tamaño de estas puede cambiar de
! int i; máquina a máquina. El operador unario sizeof se utiliza para determinar
tamaño (en memoria) de cualquier tipo de variable, incluyendo uniones y
! char caract; estructuras.

};

! Por ejemplo

! Tal y como las estructuras, aqui no se ha declarado ninguna ! printf("%d",sizeof(int));


variable, para hacerlo, basta con colocar el nombre de la variable al final
de la definición de la unión. Con la unión, tanto i como caract comparten devolverá 2 en la mayoría de las computadoras (al menos en las PC),
la misma dirección de memoria. Para acceder a un elemento de la unión ya que se requieren dos bytes para almacenarlo.
se usan los operadores punto y flecha.

! Con el sizeof es posible reservar memoria de acuerdo a las


! La unión sirve para hacer un código más portable, debido a que el necesidades de cada sistema. Es muy recomendable usarlo en especial
compilador sigue la pista de todos los tamaños, con lo cual no se cuando de maneja memoria dinámica, ya que hace el código muy
depende de la máquina. Sin embargo, hay que tener presente que solo portable. En el siguiente capítulo se darán algunas aplicaciones.
es posible usar una de las variables por vez, ya que se sobreescribe en
las mismas. Por otro lado, el espacio asignado por el compilador es
85
6.7 Tipos definidos por el usuario.

! En C es posible definir nuevos nombres a los tipos de datos. Al


igual que en las estructuras y las uniones, en realidad no se estan
creando tipos nuevos, sino que solamente se les esta dando otro
nombre. Por ejemplo, se puede declarar un tipo de dato Edad:

typedef int Edad;

De esta forma es posible definir variables de tipo Edad:

! Edad Ed_Juan;

! Edad Ed_Luis;

! Los tipos definidos se usan para aumentar la legibilidad de los


programas, aunque no es muy común emplearlos.

86
Apuntadores

7
Lorem ipsum dolor rutur
amet. Integer id dui sed
odio imperd feugiat et nec
ipsum. Ut rutrum massa
non ligula facilisis in
ullamcorper purus dapibus.
Sección 1

Apuntadores Lorem Ipsum


El manejo de los apuntadores tiene una importancia fundamental en el
lenguaje C, ya que permite mejorar la eficiencia de algunas rutinas,
modificar los argumentos de las funciones y utilizar la asignación de
memoria dinámica. Sin embargo, su uso debe hacerse con mucho
cuidado y en forma disciplinada, ya que si no se utilizan correctamente,
pueden ser fuente de problemas muy difíciles de detectar. Por ejemplo,
se puede apuntar por error a localidades de memoria inexistentes o a
Contenido localidades que no deseamos apuntar.

Un apuntador es una variable que contiene la dirección de otra variable,


1. Declaración de apuntadores. es decir, una variable que apunta a otra variable.

2. Operadores. Por ejemplo, sea p un apuntador y sea x una variable que reside en la
dirección 100 de memoria. Si hacemos que p apunte a x, entonces
tendremos algo como:

7.2 Declaración y Uso de los Apuntadores:

El uso de apuntadores permitirá referirnos a la variable apuntada de una


forma indirecta por medio del apuntador. Es necesario que la
declaración de los apuntadores sea en forma explícita, ya que al hacerlo
estamos indicando al compilador que se trata de una variable que
contendría direcciones en vez de datos, y además, debemos indicar el
tipo de datos al cual está apuntando, ya que existe una aritmética de
apuntadores.

La sintaxis de la declaración de un apuntador se indica a continuación:

tipo_de_dato *nombre

88
donde tipo_de_dato indica el tipo de dato al cual está apuntando, y
nombre indica el nombre del apuntador, por ejemplo:
! float *pf;
! short *ps;
! float pi= 3.14;
! int *pi;
! pf = &pi;
! char *
pc; ! printf("%f",*pf);

! double
*pd;
El siguiente ejemplo muestra tanto la declaración como el uso de los
ps apuntará a una apuntadores
variable de tipo
#include <stdio.h>
short, pi apuntará a
una variable de tipo main()
int, y pd apuntará a una variable de tipo double.
{
Operadores
int *p, *q;/* declaramos dos apuntadores a enteros */
Existen dos operadores unarios para el manejo de los apuntadores: int x,y;

! El operador *, el cual indica el contenido de la variable a la cual x=2;


está apuntando el apuntador.
p= &x; ! /* p apunta a la variable x */
! El operador &, que indica la dirección donde se encuentra el
operando que se localice a continuación de este operador. Este q= &y ! /* q apunta a la variable y */
operador permite, entre otras cosas, asociar un apuntador con una
*q= *p-7 !/* Resta 7 al contenido de la variable
variable, es decir, hacer que un apuntador apunte a una variable.
apuntada
Por ejemplo:

89
por p y lo asigna a la variable ! pi= &x; ! /* pi apunta a la dirección 1000 */
apuntada por q */
! pi = pi+2;! /* pi apunta a la dirección 1004, es decir, el
printf("x= %d, y= %d", *p, y);
incremento se hace en base al tamaño del dato que
}
está apuntando. En este caso consideramos que el

entero se almacena en dos bytes */


El resultado se ilustra a continuación:

7.3 Aritmética de Apuntadores:


Si se tratara de una variable de tipo long:

! C permite realizar las operaciones de suma y resta sobre


apuntadores. Sin embargo, esta aritmética tiene la característica de que ! long *p1;
los incrementos o decrementos se realizan en base al tipo de dato al
! long z; ! /* Supongamos que z está en la dirección 1000 */
que se esté apuntando.
! p1= &z; ! /* p1 apunta a la dirección 1000 */

! p1 = p1+2;!/* p1 apunta a la dirección 1008, es decir, el


! int *pi
incremento se hace en base al tamaño del dato que se
! int x; ! /* Supongamos que x está en la dirección 1000 */
está apuntando. En este caso consideramos que una

90
variable tipo long se almacena en cuatro bytes. */

El siguiente programa muestra cómo es posible manipular las


direcciones en los apuntadores:
Fig. 2.- Luego de ejecutar temp = apun_char1;

char codigo1='A', codigo2='B';

char *apun_char1, * apun_char2, *temp;

apun_char1= &codigo1;

apun_char2= &codigo2;
Fig. 3.- Luego de ejecutar apun_char1=apun_char2;
temp = apun_char1;

apun_char1 = apun_char2;

apun_char2= temp;
Fig. 4.- Luego de ejecutar apun_char2=temp;
printf("%c %c",*apun_char1, *apun_char2);

7.4 Asignación Dinámica de Memoria

! Cuando se compila un programa en C, la memoria de la


Fig. 1.- Estado Inicial del programa computadora se divide en cuatro zonas que contienen:

91
! Código de programa ! El argumento que espera la función malloc() es un entero sin signo
que representa el número de bytes de almacenamiento requeridos. Si el
! Información global, espacio de almacenamiento está disponible, malloc() devuelve un
apuntador de tipo void, que se puede transformar en el un apuntador de
! Pila (stack) y
cualquier tipo deseado. El concepto de apuntadores void se introdujo en
! Heap (montón). el C ANSI estándar, y representa un apuntador a un tipo desconocido, o
un apuntador genérico. Un apuntador void no pude ser utilizado para
referenciar cualquier cosa (ya que no apunta a ningún tipo específico de
información y recordemos que C necesita saber el tipo de dato para
! El heap es un área de memoria libre (a veces denominado
poder efectuar su aritmética de apuntadores), pero puede contener un
almacenamiento libre) manipulada con las funciones de asignación
apuntador de cualquier otro tipo. Por tanto, podemos transformar
dinámica malloc() y free().
cualquier apuntador en un apuntador void sin ninguna pérdida de
información.

El siguiente segmento de código asigna almacenamiento suficiente para


200 valores float.
! Al llamar a malloc(), se asigna un bloque contiguo de
almacenamiento al objeto especificado, devolviendo un apuntador al
comienzo del bloque. La función free() libera memoria previamente
! float *apun_float;
asignada dentro y fuera del heap, permitiendo que ésta vuelva a ser
reasignada cuando sea necesario. ! int num_float= 200;

! apun_float= malloc(num_float * sizeof(float));

92
! La función malloc() se ha utilizado para obtener almacenamiento ! Una aplicación particular podría tener que utilizar variables con un
suficiente para guardar los 200 datos de tipo float. Note la utilización del tamaño desconocido durante la compilación. Es esos casos no es
sizeof que se explicó en el capítulo anterior. Este código funcionará posible usar variables cuyos valores se almacenen en el stack o en el
perfectamente tanto en una PC como en una RISC, por ejemplo. Eso es espacio para las variables globales, ya que C requiere que se le diga al
portabilidad. Cada bloque de almacenamiento solicitado está totalmente compilador cúanto espacio se requerirá. En estas circunstancias, lo que
separado y es distinto de todos los demás. No debemos hacer habría que hacer es asignar la memoria por uno mismo, manipulando el
suposiciones acerca de dónde están localizados los bloques. heap. Podemos imaginar el heap ocupando la parte inferior del espacio
Generalmente los bloque están "marcados" con algún tipo de de memoria de programa y creciendo hacia arriba, mientras la pila
información que permite que el sistema operativo controle su ocupa la parte superior y crece hacia abajo.
localización y tamaño. Cuando el bloque ya no es necesario podemos
liberarlo al sistema operativo utilizando la siguiente instrucción.

! Un programa en C o en C++ puede asignar y liberar memoria del


heap en cualquier circunstancia. A diferencia de otras variables, las
free(apun_float) declaradas en el heap no están sujetas a las reglas del ámbito. Estas
variables nunca salen de ámbito, lo cual significa que una vez que
asigna memoria en el heap, el programador es el responsable de
liberarla. Si se continúa asignando espacio de heap sin liberarlo, el
! Como el C, C++ asigna la memoria disponible de dos modos.
programa podría fallar con el tiempo.
Cuando se declaran las variables, el espacio para almacenarlas se crea
en la pila empujando hacia abajo del apuntador de la misma. Cuando
estas variables salen del ámbito o alcance para el cual fueron creadas
(por ejemplo, cuando una variable local ya no es necesaria),
automáticamente se libera el espacio de esa variable llevando hacia
arriba el apuntador de la pila. El tamaño de memoria reservado para la
pila tiene que ser conocido durante la compilación. (En DOS es posible
alterar el tamaño del stack modificando una línea en el AUTOEXEC.BAT,
en el caso de RISC el funcionamiento es diferente, por lo que no hay
que preocuparse por eso).

93
! La mayoría de los compiladores de C utilizan las funciones de block_mem = (int *)malloc(MAX * sizeof(int));
biblioteca malloc() y free() para permitir la asignación de memoria
dinámica .Sin embargo, en C++ estas capacidades fueron consideradas if (block_mem==NULL)
tan importantes que se implementaron como parte del núcleo del
printf("Memoria insuficiente\n");
lenguaje. En realidad, C++ utiliza las funciones new() y delete() para
asignar y liberar la memoria del heap. El argumento de la función new() else
es una expresión que regresa el número de bytes asignados. El valor
devuelto es un apuntador al comienzo de este bloque de memoria. El ! {
argumento para delete() es la dirección inicial del bloque de memoria a
printf("Memoria asignada\n");
liberar. Los programas siguientes ilustran las similitudes y diferencias
entre aplicaciones de C++ y C que utilizan asignación de memoria free(block_mem);
dinámica.
! }

return (0);
Aquí tenemos el ejemplo de C:
}

#include <stdio.h>
! Las funciones malloc y free están definidas en el archivo stdlib.h.
#include <stdlib.h> Después de que el programa define la variable int *block _mem, se
llama a la función malloc para devolver la dirección a un bloque de
memoria de tamaño MAX * sizeof(int). Un algoritmo robusto debería
#define MAX 256 revisar siempre el éxito o fracaso de la asignación de memoria. Este
programa simple termina devolviendo de nuevo la memoria asignada al
heap con la función free y pasando la dirección inicial del bloque
asignado.
main( )

{ int *block_mem;
! El programa en C++ no parece muy distinto:
94
#include <iostream.h> ! La única diferencia significativa entre ambos programas está en la
sintaxis utilizada con las funciones new y delete. Mientras que la función
malloc requiere la función sizeof para garantizar la asignación de
memoria adecuada, la función new ha sido definida para realizar
#define MAX 256
automáticamente la función sizeof sobre el tipo de dato que se le pasa
main( ) como argumento. Ambos programas asignarán 256 bloques de 2 bytes
de memoria consecutiva (en sistema que asignan 2 bytes por entero).
{
8. Apuntadores
int *bloque_de_memoria;
Los apuntadores son una parte fundamental de C. Si usted no puede
usar los apuntadores apropiadamente entonces esta perdiendo la
potencia y la flexibilidad que C ofrece básicamente. El secreto para C
bloque_de_memoria=new(int(MAX));,
esta en el uso de apuntadores.
if (bloque_de_memoria == NULL )
C usa los apuntadores en forma extensiva. ¿Porqué?
{
! •! Es la única forma de expresar algunos cálculos.
cout << "Memoria asignada\n";
! •! Se genera código compacto y eficiente.
delete (bloque-de_memoria);
! •! Es una herramienta muy poderosa.
}
C usa apuntadores explícitamente con:
else cout << "Memoria insuficiente \n";
! •! Es la única forma de expresar algunos cálculos.
return(0);
! •! Se genera código compacto y eficiente.
}
! •! Es una herramienta muy poderosa.

95
C usa apuntadores explícitamente con: {

! •! Arreglos, int x = 1, y = 2;

! •! Estructuras y int *ap;

! •! Funciones

ap = &x;

8.1 Definición de un apuntador

Un apuntador es una variable que contiene la dirección en memoria de y = *ap;


otra variable. Se pueden tener apuntadores a cualquier tipo de variable.

El operador unario o monádico & devuelve la dirección de memoria de


una variable. x = ap;

El operador de indirección o dereferencia * devuelve el ``contenido de


un objeto apuntado por un apuntador''.
*ap = 3;
Para declarar un apuntador para una variable entera hacer:
}

Cuando se compile el código se mostrará el siguiente mensaje:



int *apuntador; warning: assignment makes integer from pointer without a cast.

Se debe asociar a cada apuntador un tipo particular. Por ejemplo, no se Con el objetivo de entender el comportamiento del código supongamos
puede asignar la dirección de un short int a un long int. que la variable x esta en la localidad de la memoria 100, y en 200 y ap
en 1000. Nota: un apuntador es una variable, por lo tanto, sus valores
Para tener una mejor idea, considerar el siguiente código: necesitan ser guardados en algún lado.

main()

96
int x = 1, y = 2;

int *ap; 100

200

ap = &x; 1000

100 1

200 y

1000 1

x ap

1 100

y Después y obtiene el contenido de ap. En el ejemplo ap apunta a la


localidad de memoria 100 -- la localidad de x. Por lo tanto, y obtiene el
2 valor de x -- el cual es 1.

ap

100 x = ap;

Las variables x e y son declaradas e inicializadas con 1 y 2


respectivamente, ap es declarado como un apuntador a entero y se le
asigna la dirección de x (&x). Por lo que ap se carga con el valor 100. 100

200

y = *ap; 1000
97
x 1

100 ap

y 100

1 Finalmente se asigna un valor al contenido de un apuntador (*ap).

ap Importante: Cuando un apuntador es declarado apunta a algún lado. Se


debe inicializar el apuntador antes de usarlo. Por lo que:
100

Como se ha visto C no es muy estricto en la asignación de valores de


diferente tipo (apuntador a entero). Así que es perfectamente legal main()
(aunque el compilador genera un aviso de cuidado) asigna el valor
actual de ap a la variable x. El valor de ap en ese momento es 100. {

int *ap;

*ap = 3; *ap = 100;

100 puede generar un error en tiempo de ejecución o presentar un


comportamiento errático.
200
El uso correcto será:
1000

x
main()
3
{
y
int *ap;
98
int x; flq = flp;

ap = &x; NOTA: Un apuntador a cualquier tipo de variables es una dirección en


memoria -- la cual es una dirección entera, pero un apuntador NO es un
*ap = 100; entero.

} La razón por la cual se asocia un apuntador a un tipo de dato, es por


que se debe conocer en cuantos bytes esta guardado el dato. De tal
Con los apuntadores se puede realizar también aritmética entera, por
forma, que cuando se incrementa un apuntador, se incrementa el
ejemplo:
apuntador por un ``bloque'' de memoria, en donde el bloque esta en
función del tamaño del dato.

main() Por lo tanto para un apuntador a un char, se agrega un byte a la


dirección y para un apuntador a entero o a flotante se agregan 4 bytes.
{ De esta forma si a un apuntador a flotante se le suman 2, el apuntador
entonces se mueve dos posiciones float que equivalen a 8 bytes.
float *flp, *flq;

8.2 Apuntadores y Funciones


*flp = *flp + 10;
Cuando C pasa argumentos a funciones, los pasa por valor, es decir, si
el parámetro es modificado dentro de la función, una vez que termina la
++*flp; función el valor pasado de la variable permanece inalterado.

Hay muchos casos que se quiere alterar el argumento pasado a la


función y recibir el nuevo valor una vez que la función ha terminado.
(*flp)++; Para hacer lo anterior se debe usar una llamada por referencia, en C se
puede simular pasando un puntero al argumento. Con esto se provoca
que la computadora pase la dirección del argumento a la función.

99
Para entender mejor lo anterior consideremos la función swap() que int temp;
intercambia el valor de dos argumentos enteros:

temp = *px; /* guarda el valor de la direccion x */


void swap(int *px, int *py);
*px = *py; /* pone y en x */

*py = temp; /* pone x en y */


main()
}
{

int x, y;
8.3 Apuntadores y arreglos

Existe una relación estrecha entre los punteros y los arreglos. En C, un


x = 10; nombre de un arreglo es un índice a la dirección de comienzo del
arreglo. En esencia, el nombre de un arreglo es un puntero al arreglo.
y = 20; Considerar lo siguiente:

printf("x=%d\ty=%d\n",x,y); int a[10], x;

swap(&x, &y); int *ap;

printf("x=%d\ty=%d\n",x,y);

} ap = &a[0]; /* ap apunta a la direccion de a[0] */

void swap(int *px, int *py) x = *ap; /* A x se le asigna el contenido de ap (a[0] en este caso) */

100
*(ap + 1) = 100; /* Se asigna al segundo elemento de 'a' el valor 100 Por lo tanto:
usando ap*/
strlen(s) es equivalente a strlen(&s[0])
Como se puede observar en el ejemplo la sentencia a[t] es idéntica a
ap+t. Se debe tener cuidado ya que C no hace una revisión de los Esta es la razón por la cual se declara la función como:
límites del arreglo, por lo que se puede ir fácilmente más alla del arreglo
int strlen(char s[]); y una declaración equivalente es int strlen(char *s);
en memoria y sobreescribir otras cosas.
ya que char s[] es igual que char *s.
C sin embargo es mucho más sútil en su relación entre arreglos y
apuntadores. Por ejemplo se puede teclear solamente: La función strlen() es una función de la biblioteca estándar que regresa
la longitud de una cadena. Se muestra enseguida la versión de esta
ap = a; en vez de ap = &a[0]; y también *(a + i) en vez de a[i], esto es,
función que podría escribirse:
&a[i] es equivalente con a+i.

Y como se ve en el ejemplo, el direccionamiento de apuntadores se


puede expresar como: int strlen(char *s)

a[i] que es equivalente a *(ap + i) {

Sin embargo los apuntadores y los arreglos son diferentes: char *p = s;

! •! Un apuntador es una variable. Se puede hacer ap = a y


ap++.
while ( *p != '\0' )
! •! Un arreglo NO ES una variable. Hacer a = ap y a++ ES
ILEGAL. p++;

Este parte es muy importante, asegúrese haberla entendido. return p - s;

Con lo comentado se puede entender como los arreglos son pasados a }


las funciones. Cuando un arreglo es pasado a una función lo que en
realidad se le esta pasando es la localidad de su elemento inicial en
memoria.
101
Se muestra enseguida una función para copiar una cadena en otra. Al ¿Cómo se puede hacer lo anterior?
igual que en el ejercicio anterior existe en la biblioteca estándar una
función que hace lo mismo.

! 1.! Guardar todas las líneas en un arreglo de tipo char grande.


Observando que \n marca el fin de cada línea. Ver figura 8.1.
void strcpy(char *s, char *t)
! 2.! Guardar los apuntadores en un arreglo diferente donde cada
{ apuntador apunta al primer caracter de cada línea.

while ( (*s++ = *t++) != '\0' ); ! 3.! Comparar dos líneas usando la función de la biblioteca
estándar strcmp().
}
! 4.! Si dos líneas están desacomodadas -- intercambiar (swap)
En los dos últimos ejemplos se emplean apuntadores y asignación por los apuntadores (no el texto).
valor. Nota: Se emplea el uso del caracter nulo con la sentencia while
para encontrar el fin de la cadena.

Figura 8.1: Arreglos de apuntadores (Ejemplo de ordenamiento de


cadenas).

8.4 Arreglos de apuntadores

En C se pueden tener arreglos de apuntadores ya que los apuntadores Con lo anterior se elimina:
son variables.
! •! el manejo complicado del almacenamiento.
A continuación se muestra un ejemplo de su uso: ordenar las líneas de
un texto de diferente longitud. ! •! alta sobrecarga por el movimiento de líneas.

Los arreglos de apuntadores son una representación de datos que


manejan de una forma eficiente y conveniente líneas de texto de
8.5 Arreglos multidimensionales y apuntadores
longitud variable.

102
Un arreglo multidimensional puede ser visto en varias formas en C, por En el último ejemplo se requieren los parénteis (*a) ya que [ ] tiene una
ejemplo: precedencia más alta que *.

Un arreglo de dos dimensiones es un arreglo de una dimensión, donde Por lo tanto:


cada uno de los elementos es en sí mismo un arreglo.

Por lo tanto, la notación


int (*a)[35]; declara un apuntador a un arreglo de 35 enteros, y por
a[n][m] ejemplo si hacemos la siguiente referencia a+2, nos estaremos
refiriendo a la dirección del primer elemento que se encuentran en el
nos indica que los elementos del arreglo están guardados renglón por tercer renglón de la matriz supuesta, mientras que
renglón.

Cuando se pasa una arreglo bidimensional a una función se debe


especificar el número de columnas -- el número de renglones es int *a[35]; declara un arreglo de 35 apuntadores a enteros.
irrelevante.
Ahora veamos la diferencia (sutil) entre apuntadores y arreglos. El
La razón de lo anterior, es nuevamente los apuntadores. C requiere manejo de cadenas es una aplicación común de esto.
conocer cuantas son las columnas para que pueda brincar de renglón
en renglón en la memoria. Considera:

Considerando que una función deba recibir int a[5][35], se puede


declarar el argumento de la función como:
char *nomb[10];

f( int a[][35] ) { ..... }


char anomb[10][20];
o aún
En donde es válido hacer nomb[3][4] y anomb[3][4] en C.

Sin embargo:
f( int (*a)[35] ) { ..... }

103
-

anomb es un arreglo verdadero de 200 elementos de dos dimensiones Figura 8.2: Arreglo de 2 dimensiones VS. arreglo de apuntadores.
tipo char.

El acceso de los elementos anomb en memoria se hace bajo la


siguiente fórmula 20*renglon + columna + dirección_base 8.6 Inicialización estática de arreglos de apuntadores

- La inicialización de arreglos de apuntadores es una aplicación ideal para


un arreglo estático interno, por ejemplo:
En cambio nomb tiene 10 apuntadores a elementos.

NOTA: si cada apuntador en nomb indica un arreglo de 20 elementos


entonces y solamente entonces 200 chars estarán disponibles (10 func_cualquiera()
elementos).
{
Con el primer tipo de declaración se tiene la ventaja de que cada
static char *nomb[] = { "No mes", "Ene", "Feb", "Mar", .... };
apuntador puede apuntar a arreglos de diferente longitud.
}
Considerar:
Recordando que con el especificador de almacenamiento de clase static
se reserva en forma permanente memoria el arreglo, mientras el código
char *nomb[] = { "No mes", "Ene", "Feb", "Mar", .... }; se esta ejecutando.

char anomb[][15] = { "No mes", "Ene", "Feb", "Mar", ... }; 8.7 Apuntadores y estructuras

Lo cual gráficamente se muestra en la figura 8.2. Se puede indicar que Los apuntadores a estructuras se definen fácilmente y en una forma
se hace un manejo más eficiente del espacio haciendo uso de un directa. Considerar lo siguiente:
arreglo de apuntadores y usando un arreglo bidimensional.
104
int valor;

main() struct ELEMENTO *sig;

{ } ELEMENTO;

struct COORD { float x,y,z; } punto;

ELEMENTO n1, n2;

struct COORD *ap_punto;

n1.sig = &n2;

punto.x = punto.y = punto.z = 1; La asignación que se hace corresponde a la figura 8.3

ap_punto = &punto; /* Se asigna punto al apuntador */ Figura 8.3: Esquema de una lista ligada con 2 elementos.

ap_punto->x++; /* Con el operador -> se accesan los miembros */ Nota: Solamente se puede declarar sig como un apuntador tipo
ELEMENTO. No se puede tener un elemento del tipo variable ya que
ap_punto->y+=2; /* de la estructura apuntados por ap_punto */ esto generaría una definición recursiva la cual no esta permitida. Se
permite poner una referencia a un apuntador ya que los los bytes se
ap_punto->z=3;
dejan de lado para cualquier apuntador.
}

Otro ejemplo son las listas ligadas:


8.8 Fallas comunes con apuntadores

A continuación se muestran dos errores comunes que se hacen con los


typedef struct { apuntadores.

105
C o n s i d e r a r :


! •! No asignar un apuntador a una dirección de memoria antes *p = (char *) malloc(100): /* pide 100 bytes de la memoria */
de usarloint *x
! •!
! •!
! •! *p = 'y';
! •! *x = 100;
! •!

! •!
 E x i s t e u n e r r o r e n e l c ó d i g o a n t e r i o r . ¿ C u á l e s ?

lo adecuado será, tener primeramente una localidad física de memoria, El * en la primera línea ya que malloc regresa un apuntador y *p no
d i g a m o s i n t y ;
 a p u n t a a n i n g u n a d i r e c c i ó n .


 E l c ó d i g o c o r r e c t o d e b e r á s e r :

int *x, y; 

p = (char *) malloc(100);
! •!
! •!

! •! x = &y;
Ahora si malloc no puede regresar un bloque de memoria, entonces p
! •! *x = 100; e s n u l o , y p o r l o t a n t o n o s e p o d r á h a c e r :


! •!
 *p = 'y';

! •!

! •! Indirección no válida Supongamos que se tiene una función Un buen programa en C debe revisar lo anterior, por lo que el código
llamada malloc() la cual trata de asignar memoria dinámicamente (en a n t e r i o r p u e d e s e r r e e s c r i t o c o m o :

tiempo de ejecución), la cual regresa un apuntador al bloque de 

memoria requerida si se pudo o un apuntador a nulo en otro caso.
 p = (char *) malloc(100): /* pide 100 bytes de la memoria */

char *malloc() -- una función de la biblioteca estándar que se verá más ! •!
a d e l a n t e .

! •! if ( p == NULL )
S u p o n g a m o s q u e s e t i e n e u n a p u n t a d o r c h a r * p


106
! •! { (una cadena larga). Leer los datos de la entrada estándar. La primera
línea es una sola palabra, en la segunda línea se tiene un texto general.
! •! printf("Error: fuera de memoria\n"); Leer ambas hasta encontrar un caracter de nueva línea. Recordar que
s e d e b e i n s e r t a r u n c a r a c t e r n u l o a n t e s d e p r o c e s a r.

! •! exit(1);
L a s a l i d a t í p i c a p o d r í a s e r :

! •! } 

La palabra es "el"
! •!
! 4.! La sentencia es "el perro, el gato y el canario"
! •! *p = 'y';
! 5.! La palabra ocurrio 3 veces.
! •!

! 6.!

8.9 Ejercicios

! 1.! Escribir el programa que ordena las líneas de un texto leído


desde la entrada estándar, donde cada línea tiene diferente longitud,
según lo descrito en la sección de arreglo de apuntadores.

! 2.! Escribir una función que convierta una cadena s a un


número de punto flotante usando apuntadores. Considerar que el
número tiene el siguiente formato 99999999.999999, es decir, no se
dará en notación científica. La función deberá suministrársele una
cadena y deberá devolver un número.

! 3.! Escribir un programa que encuentre el número de veces que


una palabra dada (esto es, una cadena corta) ocurre en una sentencia

107
Entrada/Salida

8
Lorem ipsum dolor rutur
amet. Integer id dui sed
odio imperd feugiat et nec
ipsum. Ut rutrum massa
non ligula facilisis in
ullamcorper purus dapibus.
Sección 1

Sin título Lorem Ipsum


! En el lenguaje C existen cinco funciones básicas para controlar la
entrada/salida (E/S). Dichas funciones son:

! ! fopen(). Abre un archivo para su utilización.

! ! putc(). Escribe un carácter en un archivo.


Lorem Ipsum
! ! getc(). Lee un carácter de un archivo.

! ! fclose(). Cierra un archivo.


1. Lorem ipsum dolor sit amet, consectetur.
! ! fseek(). Se utiliza para ejecutar operaciones de disco
2. Nulla et urna convallis nec quis blandit odio
aleatorias.
mollis.

3. Sed metus libero cing elit, lorem ipsum. Adip


8.1 La función fopen()
inscing nulla mollis urna libero blandit dolor.

4. Lorem ipsum dolor sit amet, consectetur.


! La función fopen() sirve para realizar dos acciones
5. Sed metus libero cing elit, lorem ipsum. Quis que
! 1.- Abrir un archivo en disco para utilizarlo y,
euismod bibendum sag ittis.
! 2.- Para devolver un apuntador al archivo. El formato general de
6. Sed metus libero cing elit, lorem ipsum.
fopen() es :
7. Quis que euismod bibendum sag ittis.

! ! FILE *fp;
109
! ! fp = fopen(nombre_archivo, modo); ! ! fp = fopen("prueba1", "w");

en donde modo es una cadena que contiene una r para leer (read), o Sin embargo, normalmente se escribirá como
una w para escribir (write), o una a (append) para añadir. Normalmente
el modo de lectura/escritura se especifica con la cadena rw. Esto varía
dependiendo del compilador, por lo que se debe de comprobar el
! ! If ((fp = fopen("test', "w"))= =NULL) {
manual de usuario. El nombre del archivo tiene que ser una cadena de
caracteres que forme un nombre válido para el sistema operativo bajo el ! ! puts ("No se puede abrir el fichero\n");
cual se este trabajando (a manera de recordatorio, en DOS los
identificadores para archivos se forman con máximo ocho caracteres, un ! ! exit();
punto y una extensión de tres caracteres. En UNIX los nombres pueden
! ! }
tener cualquier longitud y se pueden incluir puntos entre ellos -p.e. :
mi_archivo.uno.c- solo hay que mantener en mente que UNIX es
sensitivo a las mayusculas).
! Este método detecta cualquier error al abrir un archivo, tal como
una protección a escritura o un disco lleno, antes de intentar escribir en
él.
! Por otro lado, la variable fp es del tipo FILE y es un apuntador al
archivo en cuestión. FILE es un tipo de dato específico definido en
stdio.h . Todos los apuntadores a archivos deben declararse como de
tipo FILE. Algunos compiladores puede que denominen a este tipo de ! Si se utiliza fopen() para abrir un archivo para escritura, cualquier
datos por un nombre diferente, por lo que hay que comprobarlo en la archivo que hubiera existido con ese nombre se borrará y se abrirá uno
documentación. nuevo. Si se quiere añadir algo al final del archivo hay que utilizar el
modo a.

Si se quisiese abrir un archivo para escritura con el nombre prueba1, se


debería escribir

110
8.2 La función putc() ! en donde fp es un apuntador a archivo de tipo FILE devuelto por
fopen(). El apuntador indica a getc() de qué fichero debe de leer.

! La función putc() se utiliza para escribir caracteres en un archivo


en disco que haya sido abierto utilizando la función fopen() con el modo ! La función getc() devolverá una marca de final de fichero (EOF)
w. El formato general de la función es : cuando se encuentre al final del archivo. El archivo de encabezado
stdio.h utilizará un #define para crear la macro EOF que será la marca
del final del archivo. Por lo tanto, para leer hasta la marca de final del
archivo, se podría utilizar el siguiente código :
! ! putc(c, fp);

! ! ca = getc(fp);
! en donde fp es el apuntador a archivo devuelto por fopen() y c es
el carácter a enviar al archivo en disco. El apuntador a archivo le dice a ! ! while (ca != EOF) {
putc() en qué archivo en disco debe de escribir.
! ! ! .

! ! ! .
8.3 La función getc()
! ! ! .

! ! ! ca = getc(fp);
! La función getc() se utiliza para leer caracteres de un archivo
abierto en modo lectura por fopen(). El formato general de la función es : ! ! }

! ! char ca; ! Ya que en el marcador EOF no se puede imprimir como un


carácter, no trate de imprimirlo en su pantalla.
! ! ca = getc(fp);

111
8.4 La función fclose()

! Además de las funciones de E/S básicas getc() y putc(), las


librerías de la mayoría de las realizaciones del C tienen las funciones
! La función fclose() se utiliza para cerrar un archivo que se haya fprintf() y fscanf(), que se pueden utilizar para escribir varios formatos de
abierto mediante una llamada a fopen(). Hay que cerrar todos los datos en un archivo abierto por fopen(). El formato general de fprintf()
archivos antes de terminar el programa. La función fclose() hace más es :
que liberar el apuntador al archivo; escribe cualquier dato que todavía
no se haya escrito en el disco y hace un cierre formal al nivel de sistema
operativo. Un fallo al cerrar un archivo invita a todo tipo de problemas,
incluyendo la pérdida de datos, la destrucción de archivos y posibles ! ! fprintf(fp, cadena de control, lista de argumentos);
errores intermitentes en el programa.

! y el formato general de fscanf() es :


! El formato general para llamar a la función fclose() es :

! ! fscanf(fp, cadena de control, lista de argumentos);


! ! fclose(fp);

! en donde fp es un apuntador a archivo devuelto a llamar a fopen().


! en donde fp es el apuntador a archivo devuelto por la llamada a Excepto en el caso de dirigir su salida al archivo definido mediante fp,
fopen(). fprintf() y fscanf() operan exactamente igual que printf() y scanf(),
respectivamente ( lo cual quiere decir que en el caso del fscanf() hay
que preceder a los identificadores de las variables con el ampersand &).

! Aunque fprintf() y fscanf() son a menudo la forma más fácil para


escribir y leer archivos en disco, no son las más eficaces, ya que los
8.5 Las funciones fprintf() y fscanf() datos ASCII se van a escribir justo como aparecen en la pantalla en vez

112
de en binario. Si le preocupa la velocidad o el tamaño del archivo, biblioteca también proporciona extensiones definibles por el usuario
probablemente debería escribir rutinas de archivo adaptadas similares a para manipular el tipo class.
putw() y getw().

! Las operaciones de entradas básicas son soportadas por la clase


8.6 Entrada y Salida en C++ istream, la salida básica a través de la clase ostream y la E/S
bidireccional se realiza a través de la clase iostream, que se deriva tanto
de istream como de ostream. Hay cuatro objetos de flujo predefinidos
para el usuario:
! Una de las mejoras más llamativas del compilador de C++ es la
nueva biblioteca de E/S, denominada biblioteca iostream. No incluyendo
facilidades de E/S dentro del lenguaje mismo, sino más bien
implementadas en C++ y ofrecidas como un componente de la ! cin : Un objeto de la clase istream vinculado a la entrada estándar.
biblioteca estándar, la E/S puede desarrollarse de la forma en que sea
necesario. Esta nueva biblioteca reemplaza a la versión anterior de la ! cout : Un objeto de la clase ostream vinculado a la salida estándar.
biblioteca de E/S referida como la biblioteca stream.
! cerr : Un objeto de la clase ostream sin memoria intermedia
vinculado al error estándar.

! En su nivel más bajo, C++ interpreta un archivo como un flujo o ! clog : Un objeto de la clase ostream con memoria intermedia
secuencia de bytes. A este nivel, el concepto de tipo de dato vinculado al error estándar.
desaparece. Hay un componente de la biblioteca de E/S que se encarga
de la transferencia de estos bytes.

! Sin embargo, desde la perspectiva del usuario, un archivo está


compuesto por una serie de caracteres alfanuméricos, valores ! Cualquier programa que utilice la biblioteca iostream tiene que
numéricos o posiblemente, objetos de clase entremezclados. La incluir el archivo de cabecera iostream.h. Como iostream.h trata a
biblioteca iostream se predefine como un conjunto de operaciones para stream.h como un alias, los programas escritos utilizando stream.h
manejar la lectura y escritura de los tipos de datos incorporados. La

113
pueden necesitar o no alteraciones, dependiendo de la estructura E/S, dándonos mayor flexibilidad para introducir la interfaz de usuario
particular utilizada. mas eficiente .

! La nueva biblioteca de E/S también puede ser utilizada para ! En C lamentablemente hay poca consistencia entre las funciones
realizar operaciones de entrada y salida en archivos. Un archivo puede de E/S en términos de los valores devueltos y la secuencia de
asociarse a su programa definiendo la instancia de uno de los siguientes parámetros. Debido a esto, los programadores tienden a confiar la E/S
tres tipos de clase : formateada a las funciones printf, scanf y otras similares, especialmente
cuando los objetos que se manipulan son números u otros valores que
no son caracteres. Estas funciones de E/S formateadas son
convenientes y compartidas en la mayoría de los casos con una interfaz
! fstream: Derivada de iostream, vincula un archivo a nuestra
consistente. Sin embargo, su uso resulta pesado porque tienen que
aplicación tanto para entrada como para salida.
manipular muchas clases diferentes de valores.
! ifstream: Derivada de istream, vincula un archivo a nuestra
aplicación sólo para entrada.
! El operador de extracción << y el operador de inserción >> se han
! ofstream: Derivada de ostream, vincula un archivo a nuestra
modificado para aceptar argumentos de cualquier tipo de dato
aplicación sólo para salida.
incorporado, incluyendo char*. También puede extenderse para aceptar
argumentos de tipo clase.

! El flujo de E/S se describe como un conjunto de clases en


iostream.h. Estas clases se sobrecargan (posteriormente se verá con
! Probablemente, la primera mejora incompatible la
más detalle el concepto de sobrecarga) con las operaciones "put on"
experimentaremos cuando convirtamos un programa utilizando la
(poner en) y "get from" (tomar de) << y >>. Para entender mejor por qué
antigua biblioteca E/S transformándola en la extensión cout<<form. En
la biblioteca de flujo de C++ es mas conveniente que su homóloga de
la nueva versión, cada biblioteca de objetos de la clase iostream
C, primero se debe revisar la forma en que C maneja la entrada y
mantiene un estado de formato (formate state) que controla los detalles
salida. Recordemos que C no incorpora instrucciones de entrada y
de las operaciones de formato, como la base de conversión para
salida; funciones como printf son parte de la biblioteca estándar pero no
notación numérica entera o la precisión de un valor en punto flotante. Un
parte del lenguaje. Igualmente C++ no ha incorporado facilidades de

114
programador puede manipular los indicadores de estado utilizando las ! ios::hex! ! ! ! ! Base numérica hexadecimal.
funciones setf y unsetf.
! ios::oct! ! ! ! ! Base numérica octal.

! ios::fixed! ! ! ! Notación decimal


! La función miembro setf activa un indicador en el estado de
formato especificado. Hay dos instancias sobrecargadas: ! ios::scientific! ! ! ! Notación científica.

! setf(long); 8.7 cin, cout y cerr

! setf(long,long);

! En C++ los flujos homólogos a stdin, stdout y stderr son : cin, cout
y cerr. Estos tres flujos se abren automáticamente cuando comienza la
ejecución de nuestro programa, y se convierten en la interfaz entre el
programa y el usuario. El flujo cin está asociado con el teclado del
terminal.Los flujos cout y cerr se asocian con el monitor.

8.8 Los operadores >> y <<

! La entrada y salida en C++ ha sido significativamente realzada y


perfeccionada por la biblioteca de operadores de flujo >> ("get from" o
! INDICADOR! ! ! ! SIGNIFICADO
extracción) y << ("put on" o inserción). Una de las mayores mejoras que
! ios::showbase! ! ! ! Muestra la base numérica. se añadieron a C++ fue la sobrecarga de operadores. La sobrecarga de
operadores, permite al compilador determinar qué operador lógico o
! ios::showpoint! ! ! ! Muestra el punto decimal. función nombrada debe ejecutarse basándose en los tipos de datos de
la variables asociadas. Los operadores de extracción e inserción son
! ios::dec! ! ! ! ! Base numérica decimal.
115
buenos ejemplos de esta nueva capacidad. Cada operador se ! Una situación idéntica existe con el operador de extracción, que
sobrecarga de modo que pueda manipular todos los tipos de datos realiza entrada de información. Veamos el siguiente ejemplo de C y su
estándar, incluyendo clases. equivalente en C++:

! El siguiente ejemplo muestra la mayor facilidad de manejo para /* entrada en C */


operaciones básicas en E/S .Primero una rápida mirada a una
instrucción de salida de C utilizando printf: scanf("%d %f %c", &entero,&real,&caracter);

! printf("Un entero %d, un real %f",entero,real); // entrada en C++

cin>> entero>> real>> caracter;

Ahora el equivalente en C++.

! No se necesita preceder las variables de entrada con el operador


de dirección &. En C++ el operador de extracción se encarga de calcular
! cout << "Un entero "<<entero <<",un real "<<real; la dirección de almacenamiento de la variable, los requsitos de
almacenamiento y el formato.

! Un examen cuidadoso del equivalente revela cómo el operador de ! Habiendo visto ejemplos en C++ de los operadores << y >> ,
inserción está sobrecargado para manipular los tres tipos de datos: podríamos estar ligeramente confundidos en cuanto a por qué se
cadena, entero y real. Como cualquier otro programador de C , no debe nombran de este modo. La forma más simple de recordar qué operador
perderse tratando de encontrar los símblolos % necesarios para las realiza la salida y cuál realiza la entrada es imaginar cómo estos dos
especificaciones de formato de printf y scanf. Como resultado de la operadores se realcionan con el flujo de archivos de E/S .Cuando
sobrecarga de operadores, el operador de inserción examinará el tipo queremos información de entrada, extraemos (>>) ésta del flujo de
de dato que le hemos pasado y determinará un formato apropiado. entrada (cin) y ponemos la información en una variable, por ejemplo,
entero. Para sacar información tomamos una copia de la información de
la variable (real) y la insertamos (<<) en el flujo de salida (cout).

116
! Como resultado directo de la sobrecarga de operadores C++ ! Por último, pero no menos importante, los operadores de inserción
permitirá a un programa expandirse sobre los operadores de inserción y y extracción tienen una ventaja adicional: su tamaño de código final. Las
extracción. El siguiente segmento de código muestra cómo el operador funicones de E/S de propósito general printf y scanf llevan consigo
de inserción puede estar sobrecargado, imprimiendo el nuevo tipo segmentos de código en la versión ejecutable final de un programa, que
empleado. frecuentemente no son utilizados. En C, aunque sólo trate con datos
enteros, arrastrará todas las conversiones para los tipos de datos
estándar adicionales. En contrase, el compilador C++ sólo incorpora
aquellas rutinas necesarias.
ostream& operador << (ostream& out_file , empleado un_empleado)

{
Un programa de demosración
out _file << " " << un_empleado.nombre;

out_file << " " << un_empleado.dirección;


! El siguiente programa muestra cómo utilizar el operador de
out_file << " "<<< un_empleado.teléfono;
entrada >> para leer diferentes tipos de datos.
return out_file;

! Suponiendo que la variable de estructura un_empleado ha sido


inicializada, la impresion de la información se hace en una instrucción
simple de una línea:

cout << un_empleado

117
char respuesta;

int entero;

float real;

double real:doble ;

char nombre [LONG+CAR_NULO]

cout <<"¿Quiere introducir alguna información "<<"\ n";

cout<<"Por Favor pulse S para si y N para no:";

cin >>respuesta;

#include <iostream.h> if (respuesta == ´S´) {

#define LONG 30 cout <<"\n"<< "Por favor introduzca un valor entero :";

#define CAR_NULO 1

cin >>entero

main( ) cout zz "\n\n";

cout <<"Por favor introduzca un valor real :";

118
cin >>real; instrucción de entrada parece idéntica excepto por el nombre de
variable.
cout <<"\n\n";

cout <<"Por Favor introduzca un valor real de doble precisión";

cin <<real doble ;

cout<< \n\n;

cout <<"Por favor introduzca su nombre de pila : ";

cin >>nombre;

Intente ejecutar el siguiente ejecutar el siguiente ejemplo:


cout << \n\";

#include <iostream.h>
}
#define LONG 30
return (0);
#define CAR_NULO 1
}

main ( )
! En este ejemplo, el operador de salida << se utiliza en su forma
más simple para la salida inmediata de una cadena de literales. Observe
que aunque el programa utiliza cuatro tipos diferentes de datos, cada
119
{ tabulador o retorno de carro. Por tanto, cuando se imprime nombre sólo
se muestra a la salida el primer nombre leído.
char nombre [LONG+CAR_NULO];

! Podemos resolver este problema rescribiendo el programa y


cout <<"Por favor introduzca su nombre y apellidos:"; utilizando la función cin.get.

cin>>nombre ;

cout <<"\n\n Gracias," << nombre; #include <iostream.h>

#define LONG 30

return (0); #define CAR_NULO 1

main ( )

Una muestra de la ejecución del programa es la siguiente:

Por Favor introduzca su nombre y apellidos : Juan Diaz char nombre [LONG+CAR_NULO];

Gracias, Juan cout <<"Porfacor introduzca su nombre y apellidos:";

cin.get(nombre, LONG);

! El operador de entrada,>>, define la lectura de información tan cout <<"\n\n Gracias," << nombre;
pronto como se encuentra un blanco. El blanco puede ser un espacio,

120
return (0); leería LONG caracteres en nombre, o todos los caracteres hasta un
símbolo * (pero sin incluirlo) o un retorno de carro.
}

La salida del programa ahora es la siguiente:

Por Favor introduzca su nombre y apellidos :Juan Diaz

Gracias, Juan

! La función cin.get tiene dos parámetros adicionales. Solamente


uno de estos (el número de caracteres de entrada) se ha utilizado en el
ejemplo anterior. La función cin.get leerá todo, incluyendo el blanco,
hasta que haya leído el número máximo de caracteres especificados en
ella, hasta el siguiente retorno de carro. El tercer parámetro es opcional
(no mostrado) e identifica un símbolo de finalización. Por ejemplo:

cin.get(nombre,LONG,´*´);

121

Vous aimerez peut-être aussi