Sie sind auf Seite 1von 49

ESCUELA POLITECNICA NACIONAL

FACULTAD DE INGENIERIA
DE SISTEMAS

MANUAL DE LENGUAJE C
2

⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗
⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗
Manual del Lenguaje C Laboratorio de Sistemas - EPN
TABLA DE CONTENIDOS

CAPITULO I
TIPOS DE DATOS
Representación de los tipos de datos
Caracteres
Enteros
Los flotantes
Tipo de dato void
Caracteres con barra invertida

CAPITULO II
OPERADORES
Operadores aritméticos
Operadores de comparación
Operadores lógicos
Operadores a nivel de bits
El operador ?
Operadores de asignación
La coma como operador
Operadores de incremento y decremento
Operadores de puntero * y &
Operador en tiempo de compilación sizeof
Los operadores de punto y flecha
Los paréntesis y los corchetes como operadores
Resumen de precedencias

CAPITULO III
TIPOS DE DATOS DERIVADOS
Arreglos
Variables de tipos puntero

CAPITULO IV
EVALUACION DE EXPRESIONES y ASIGNACION DINAMICA DE MEMORIA
Evaluación de expresiones
Asignación dinámica de memoria
CAPITULO V
VARIABLES
Variables locales
Parámetros formales
Variables globales
Clases de almacenamiento
Inicialización de variables

CAPITULO VI
SENTENCIAS DE CONTROL
Valores true y false
La sentencia while
La sentencia do-while
La sentencia for
La sentencia if-else
La sentencia switch
La sentencia break
La sentencia continue
La sentencia exit
La sentencia goto

CAPITULO VII
INTRODUCCION A LA PROGRAMACION ORIENTADA A OBJETOS
Generalización vs especialización
Funcionalidad propuesta
GENERALIDADES

Una de las características fundamentales que se busca actualmente


en un lenguaje de programación es que sea portable, esto es, que
un programa elaborado en una máquina con características propias
pueda ser corrido, realizando mínimas correcciones en otra máquina
diferente.

La estrategia de portabilidad es bastante importante ya que nos


ayuda a mantener nuestros programas, a través de múltiples
plataformas hardware. Esta estrategia influye directamente en
decisiones económicas y sociales, puesto que nos ahorra un cambio
brusco en la concepción de un lenguaje por parte de los programa-
dores que pasan a través de diferentes generaciones de lenguajes.

Pero C no se queda ahí, actualmente es el lenguaje universal, y su


universalidad se debe bastante a la gran acojida mundial que a
tenido el sistema operativo UNIX, el cual incluye en su conjunto
de herramientas básicas al compilador C.

En un principio, al igual que otros lenguajes, no existía un


conjunto de estándares definidos para C. Pero en 1983, el insti-
tuto de estándares americano ANSI definió los estándares de C. De
esta manera, todos los compiladores principales de C ya han
implementado el estándar ANSI.

Ahora, las grandes casas de hardware ofrecen al cliente como una


buena estrategia de venta, la capacidad de poder correr un
compilador C que cumpla con los estándares dados por el instituto
ANSI.

Hace pocos años C pasó a ser el núcleo de lo que hoy es el


lenguaje C++, por lo que C++ es ahora un superconjunto de C. Esta
inclusión es una gran ventaja para los programadores que trabajan
en C y que están introduciéndose a la programación orientada a
objetos ofrecida por C++.
INTRODUCCION

Cero es un punto de partida en C, pues C usa cero para indicar


falso, sus arrays comienzan con cero, y cero es el valor de
retorno de una operación exitosa en una función.

C es poderoso debido a la cuidadosa inclusión de las estructuras


correctas de control y los tipos de datos. Es un lenguaje que
tiene un conjunto compacto de palabras reservadas.

C no es un lenguaje fuertemente estructurado, en el sentido de


Pascal y Algol 68, y esto se puede observar en la imposibilidad de
inclusión de funciones anidadas. Es relativamente flexible en la
conversión de datos, aunque no realice conversión automática de
tipos de datos con la facilidad de PL/I.

C es un lenguaje de propósito general, pero que no ofrece multi-


programación, paralelismo, sincronización, o programación para
sistemas especiales; para estos propósitos existen compiladores C
con estas características particulares.

C es un lenguaje de programación de empleo general, siendo


considerado como un lenguaje de "bajo nivel" relativo, esto
significa que C trabaja con la misma clase de objetos que la
mayoría de las computadoras: caracteres, números y direcciones,
que pueden ser combinados con los operadores aritméticos y
lógicos, utilizados normalmente en las máquinas.

C no contiene funciones para trabajar directamente con elementos


como por ejemplo funciones de entrada-salida: No existen proposi-
ciones implícitas "READ" o "WRITE" como las que existen en el
lenguaje Pascal, ni métodos propios para el acceso a archivos.
Todos estos mecanismos de alto nivel deben ser aportados por
funciones llamadas explícitamente.

Un programa realizado en C, cualquiera que sea su tamaño, consta


de una o más "funciones" que especifican las operaciones de
cálculo que han de realizarse. Las funciones de C son similares a
las funciones y subrutinas de otros lenguajes de programación.

La palabra "main" identifica el nombre de la función principal que


siempre debe existir si se desea ejecutar un programa y las
instrucciones contenidas entre llaves ('{' y '}') especifican el
cuerpo de la función; estas instrucciones se cumplen en forma
secuencial, el momento en que se invoca el nombre de la función
que las contiene. La función "main" es inmediatamente ejecutada al
correr el programa ya compilado:
CAPITULO I
TIPOS DE DATOS

1.1 REPRESENTACION DE LOS TIPOS DE DATOS.

El lenguaje de programación C tiene una serie de datos básicos los


mismos que siempre estarán guardados en una serie de bits. Por
ejemplo un caracter (char en C) se guarda en un byte (8 bits). El
almacenamiento depende del tipo de máquina con el que trabajamos,
generalmente son máquinas de 16 bits o de 32 bits. En la
siguiente tabla se tiene estos tipos de datos:

⊃⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆≈
≡ µαθυινασ δε ≡
≡τιπο ρεπρεσεντα α 16 βιτσ 32 βιτσ≡
∩⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗∂
≡χηαρ χαραχτερεσ 8 8 ≡
≡ιντ εντεροσ 16 32 ≡
≡σηορτ εντεροσ χορτοσ 16 16 ≡
≡λονγ εντεροσ λαργοσ 32 32 ≡
≡υνσιγνεδ εντεροσ σιν σιγνο 16 32 ≡
≡φλοατ φλοταντεσ 32 32 ≡
≡δουβλε φλοταντε δοβλε 64 64 ≡
≡ πρεχισι⌠ν ≡
≡ϖοιδ γενεριχο 0 0 ≡
∪⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆⊆…

1.2 LOS CARACTERES

Los caracteres se guardan en ocho bits, o un byte, enumerados de


la manera inversa como se presenta a continuación:

7 6 5 4 3 2 1 0
+-+-+-+-+-+-+-+-+

la representación de "a" (0x61) en binario es:

∨⊗ℜ⊗ℜ⊗ℜ⊗ℜ⊗ℜ⊗ℜ⊗ℜ⊗↵
≥0≥1≥1≥0≥0≥0≥0≥1≥
ℵ⊗ℑ⊗ℑ⊗ℑ⊗ℑ⊗ℑ⊗ℑ⊗ℑ⊗∧

Los caracteres representan todos los dados en el código ASCII, hoy


en día se hace uso del ASCII EXTENDIDO, que se guarda en 8 bits,
del bit 0 al bit 7. Con el código ASCII extendido se puede
representar un total de 256 caracteres, que van desde el 0 al 255.
Este código representa a caracteres de control, del 0 al 31, a
caracteres tipográficos como las letras mayúsculas, las minús-
culas, los dígitos, símbolos, etc.

La representación del código ASCII generalmente hace referencia a


sus caracteres, por ejemplo, el alfabeto, tiene los caracteres de
la "a" a la "z", la letra "a" tiene el código decimal 97, que en
hexadecimal es 0x61. Esto permite operaciones como suma, resta,
etc., por ejemplo "a" + 1 equivale a 97 + 1 que da como resultado
"b". "5" - "0" equivale a restar 53 - 48 lo que nos da el código 5
(un caracter de control).

1.3 LOS ENTEROS

De acuerdo a su alcance tienen diferentes formas de representación


en C:

1.3.1 Enteros (int). Estos se guardan en 1 palabra (dos bytes),


el bit más significativo (el 15) es de signo (0 positivo, 1
negativo), los otros 15 bits (del 0 al 14) son bits de datos:

∨⊗⊗ℜ⊗⊗ℜ⊗⊗ℜ⊗⊗ℜ⊗⊗ℜ⊗⊗ℜ⊗⊗ℜ⊗⊗↵ ∨⊗⊗ℜ⊗⊗ℜ⊗⊗ℜ⊗⊗ℜ⊗⊗ℜ⊗⊗ℜ⊗⊗ℜ⊗
⊗↵
≥15≥14≥13≥12≥11≥10≥ 9≥ 8≥ ≥ 7≥ 6≥ 5≥ 4≥ 3≥ 2≥ 1≥ 0≥
ℵ⊗⊗ℑ⊗⊗ℑ⊗⊗ℑ⊗⊗ℑ⊗⊗ℑ⊗⊗ℑ⊗⊗ℑ⊗⊗∧ ℵ⊗⊗ℑ⊗⊗ℑ⊗⊗ℑ⊗⊗ℑ⊗⊗ℑ⊗⊗ℑ⊗⊗ℑ⊗⊗∧

el rango de valores que se puede contener un entero es:

-215 <= entero <= 215 - 1

que es lo mismo que: -32768 <= entero <= 32767

Esta representación es para compiladores de 16 bits. Para compi-


ladores de 32 bits como Watcom C, se tiene un bit de signo, el 31,
y 31 bits de datos, del 0 al 30, lo que da un rango de valores:

-231 <= entero <= 231 - 1

1.3.2 Enteros cortos (short). Para compiladores de 16 bits y 32


bits, un entero corto coincide con la definición y rango dado para
enteros (int).
1.3.3 Enteros Largos (long).- Para máquinas de 16 y 32 bits los
enteros largos se almacenan en 4 bytes, igualmente el bit más
significativo (el 31) será el bit de signo, mientras que los 31
restantes serán de datos (del 0 al 30), con lo que tenemos un
rango de valores entre -231 y 231-1.

1.3.4 Enteros sin signo (unsigned).- Para máquinas de 16 bits los


enteros sin signo se almacenan en 2 bytes, y para máquinas de 32
bits se almacenan en 4 bytes. Como su nombre lo dice, no se toma
en cuenta el bit de signo, es decir que todos los bits son de
datos, con esto tenemos un rango de valores entre 0 y 216 para
máquinas de 16 bits, y entre 0 y 232 para máquinas de 32 bits.

1.4 LOS FLOTANTES

Los valores de punto flotante se refieren a aquellos en que deben


estar presentes el punto decimal, y expontes con base 10, que son
los números reales como por ejemplo:

1.3456 3.14657 0.8754 1.2e-34

1.2e-20 representa a 0.12*10-19

El rango de valores que puede tomar el tipo flotante, está entre


10-37 y 10+37. Los números de punto flotante (float y double)
representan internamente a la mantiza, que es la parte fracciona-
ria, y al exponente. Para el último ejemplo, en la mantiza
encontramos el valor 12 (la parte fraccionaria) y el exponente es
-19.

Tanto float como double tienen el mismo rango de valores, sin


embargo double da una mayor precisión en la mantiza, teniendo
hasta diez dígitos de precisión frente a seis dados por float

A continuación se indica una tabla con los tipos de datos básicos:

Tipo Tamaño(bits) Rango


⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗
⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗
char 8 bits -128 a 127
int 32 -32768 a 32767
unsigned int 16 0 a 65535
long int 32 -2,147,483,648 a -2,147,483,647
float 32 seis dígitos de precisión
double 64 diez dígitos de precisión
long double 128 diez dígitos de precisión
⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗
⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗

El diagrama que se muestra a continuación representa las formas en


que podemos combinar los tipos de datos elementales con las
respectivas extensiones para obtener nuevos tipos de datos; por
ejemplo el grupo de enteros está conformado por el tipo de dato
elemental int y también por unsigned int, short int y long int.

tipo elemental >⊗↵


℘⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗ℜ⊗⊗⊗⊗⊗⊗char
℘⊗⊗⊗unsigned⊗⊗⊗⊗⊗⊗>⊗⊗⊗⊗⊗∧
℘⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗ℜ⊗⊗⊗⊗⊗⊗⊗int
≥ ≥
℘⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗short⊗⊗⊗⊗⊗⊗>⊗×
≥ ≥
℘⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗unsigned⊗⊗⊗>⊗×
≥ ≥
℘⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗long⊗⊗⊗⊗⊗⊗⊗>⊗∧

℘⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗float
℘⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗ℜ⊗⊗⊗⊗⊗⊗double
ℵ⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗long⊗⊗⊗⊗⊗⊗⊗>⊗⊗∧

1.5 TIPO DE DATO VOID

Es tipo de dato void no especifica un tipo de dato fijo, sino que


puede adaptarse a cualquier tipo dada una circunstancia específi-
ca. Veamos algunas declaraciones de variables y funciones tipo
void:

void *p; Indica que p es un puntero genérico, nos es útil para


guardar en memoria objetos cuyos tipos de datos nos sean descono-
cidos.

void fun1(); es una función que no retornará ningún valor, por


ejemplo, en "dos.h" existe la función clrscr(), que se limita a
borrar todo lo que esté en pantalla, esta función realizará esta
tarea sin necesidad de retornar valor alguno.
int fun2(void); indica que no se requiere colocar argumentos de
entrada, como por ejemplo una función para obtener la fecha del
sistema, date(), no se requiere de argumento alguno.

void *fun3(); esta función devolverá un puntero genérico, se lo


estudiará detenidamente cuando se revise las funciones malloc y
calloc.

1.6 CARACTERES CON BARRA INVERTIDA

El incluír entre comillas los carateres es suficiente para


imprimir en pantalla una salida, por ejemplo la siguiente cadena
de caracteres: "hola, mundo", sin embargo es necesario que se
pueda incluír dentro de esta cadena de caracteres, algunos
controles, es por ello que C dentro de cadenas nos permite
utilizar códigos de control por medio de la barra invertida \. Por
ejemplo, al imprimir el mensaje enterior, si queremos que haya un
salto de línea (nueva línea) luego de la impresión, el formato de
la cadena sería: "hola, mundo \n", donde \n es el orden de salto
de línea. Veamos una tabla de códigos de barra invertida:

codigo significado
⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗
\b espacio atrás
\f salto de página
\n salto de línea
\r salto de carro
\t tabulación horizontal
\" imprime comillas
\' imprime comilla simple
\0 caracter nulo
\\ imprime barra invertida
\v tabulación vertical
\a alerta
\o constante octal
\x constante hexadecimal
⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗

La manera de representar el caracter nulo es \0, en realidad


podemos extendernos para que \### sea la impresión del caracter
cuyo código ASCII esté dado por ###, por ejemplo para obtener un
sonido de la computadora \007.
CAPITULO II
OPERADORES

Una de las mejores características de C es la cantidad de opera-


dores que posee y los diversos niveles en los que actúa:

2.1 OPERADORES ARITMETICOS

Se tienen los siguientes operadores aritméticos ya conocidos en


otros lenguajes:

+ operador binario de suma


- operador binario de resta
* operador binario de multiplicación
/ operador binario de división
% operador binario de resto de división

El operador - se lo puede utilizar también como operador unario


que hace negativo a un número. Generalmente casi todos estos
operadores nos permiten realizar las operaciones aritméticas
básicas, adicionalmente se tiene el operador % que es el resto de
una división, por ejemplo, la respuesta a la operación 7%4 es 3
(entero), el resultado de 9 % 3 es 0, el de 5 % 2 es 1, 1 % 8 dará
1, etc.

1.2 OPERADORES DE COMPARACION

Los operadores binarios de comparación son:

> mayor que


< menor que
== igual que
>= mayor o igual que
<= menor o igual que
!= no es igual que

Estos operadores compararán dos expresiones, el resultado será un


valor entero, 0 si no cumple con la relación de comparación, o 1
si la cumple, por ejemplo si la relación x < y es verdadera,
tendremos que se devuelve un valor entero 1, caso contrario, el
valor será 0. Veamos algunos ejemplos:

si a = 10 y b = 15 entonces ( a < b ), ( a <= b ),


( a != b ) darán todos 1, en cambio que ( a > b )
( a >= b ) y ( a == b ) darán todos 0.
1.3 OPERADORES LOGICOS

Los operadores lógicos son:

! es la negación (operador unario)


&& es el operador binario lógico de intersección (and)
|| es la operador binario lógico de unión (or)

El operador ! invierte el resultado de una expresión cuando ha


sido verdadera o falsa. Para los ejemplos anteriores tendremos que
!( a > b ) dará un 1 y !( a < b ) dará un 0.

Veamos las tablas de funcionamiento (o tablas de verdad) de la


intersección (&&) y unión (||):

&& | 0 1 || | 0 1
0 | 0 0 0 | 0 1
1 | 0 1 1 | 1 1

se puede realizar operaciones mas complejas entre relaciones como:

(( a > b ) && !(1 == 2)) || (b == a)


((0) && !(0)) || (0)
(0 && 1 ) || 0
0 || 0
0

1.4 OPERADORES A NIVEL DE BITS

C soporta un completo juego de operadores a nivel de bits. Dado


que el lenguaje se diseño para sustituir al ensamblador en muchas
tareas de programación, era importante permitir todas las opera-
ciones que se pueden hacer en ensamblador.

Estas operaciones se refieren a la comprobación, asignación o


desplazamiento de los bits reales que componen un byte o una
palabra, que corresponden a los tipos estándar de C char e int.
Las operaciones a nivel de bits no se pueden usar sobre otros
tipos. La tabla de la siguiente página lista los operadores que
se aplican a las operaciones a nivel de bits.

Los operadores &, | y ~ a nivel de bits están basados en la misma


tabla de verdad que sus equivalentes lógicos, excepto que trabajan
bit a bit.
Para el caso de XOR, el resultado es verdad si uno y sólo uno de
los operandos es verdad

Operador Acción
& (Y)
| (O)
^ (XOR)
~ Complemento a uno (NOT)
>> Desplazamiento a la derecha
<< Desplazamiento a la izquierda

Como ejemplo, se detalla una función que leerá un caracter del


puerto del modem, utilizando la función leer_modem() y pondrá el
bit de paridad a cero:

char car_del_modem(void)
{
char c;
c = leer_modem(); /*obtener un caracter
del puerto del modem */
return( c & 127)
}

El bit de paridad, el cual se utiliza para indicar que el resto de


los bits del byte no han cambiado, utiliza el bit más signifi-
cativo de cada byte, que es el octavo bit.

Al realizar un AND (Y) entre un byte que tenga los bits de 1 a 7


en uno y el bit ocho en cero y el byte recibido por el modem, la
paridad se pone a cero. La expresión c & 127 da como resultado
esta operación. En el ejemplo se supone que en c se ha recibido
el carácter "A" con el bit de paridad a uno:

Bit de paridad

V
1 1 0 0 0 0 0 1 c conteniendo "A" con paridad a 1
0 1 1 1 1 1 1 1 127 en binario
&⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗ hacer Y a nivel de bits
0 1 0 0 0 0 0 1 "A" sin paridad

La operación OR (O) bit a bit siendo la inversa de (Y), pone los


bits a uno. Cualquier bit del operando que esté puesto a 1 hace
que el correspondiente bit de la variable se ponga a uno.
Recuerde que los operadores relacionales y lógicos siempre
producen un resultado que es 0 ó 1.

Los operadores de desplazamiento >> y <<, mueven todos los bits de


una variable a la derecha o a la izquierda según se especifique.
La forma general de una sentencia de desplazamiento a la derecha
es:

variable >> número de posiciones en bits

La sentencia de desplazamiento a la izquierda es:

variable << número de posiciones en bits

A medida que se desplazan los bits hacia un extremo se va relle-


nando con ceros por el extremo opuesto, recuerde que un desplaza-
miento no es una rotación, es decir que los bits que salen por el
un extremo no ingresan al otro.

Las operaciones de desplazamiento de bits pueden ser muy útiles


cuando se decodifica la entrada a través de dispositivos externos,
como los convertidores D/A, y en la lectura de información de
estado. Se pueden utilizar también para operaciones rápidas de
multiplicación y división entre enteros. Un desplazamiento a la
izquierda equivale a una multiplicación por 2 y uno a la derecha
una división por 2

Ejemplo de desplazamiento.

#include "stdio.h"
{
unsigned int i;
int j;

i = 1;

/* desplazamientos a la izquierda */
for (j=0; j<4; j++)
{
i = i << 1; /* desplazar i a la izquierda en
1, que es lo mismo que
multiplicarlo por 2 */
printf("desplazamiento a la izquuierda %d: %d\n",j,i);
}
}
El operador de complemento a uno (~) cambia el estado de cada bit
en la variable especificada, es decir, los 1 se ponen a 0 y los 0
a 1.

Los operadores a nivel de bits se usan a menudo en rutinas de


cifrado. Si se quiere que un archivo parezca ilegible, basta con
llevar a cabo en él algunas operaciones a nivel de bits.

1.5 EL OPERADOR ?

C contiene un operador muy potente que se usa para sustituir


ciertas sentencias en la forma if-then-else. El operador
ternario ? toma la forma general:

Exp1 ? Exp2 : Exp3

donde Exp1, Exp2 y Exp3 son expresiones. y ? actúa de la siguiente


forma: Evalúa Exp1. Si es cierta, evalúa Exp2 y toma ese valor
para la expresión, Si Exp1 es falsa, evalúa Exp3 tomando su valor
para la expresión. Por ejemplo:

x = 10;

y = x>9 ? 100 : 200;

a y se le asigna el valor de 100. Si x hubiera sido menor que 9, y


se habría recibido el valor de 200. Este mismo código escrito con
la sentencia if-then-else es:

x = 10;

if (x>9) y = 100;
else y = 200;

1.6 EL OPERADOR DE ASIGNACION

Al igual que otros lenguajes, en C las operaciones más básicas se


complementan con la sentencia más simple, la de asignación (por
ejemplo a = b + c). Sin embargo en C, = es un operador (así como
los operadores aritméticos), es decir que a=b+c es una expresión
antes que solo una sentencia de asignación. El tener a = como un
operador, nos permite tener operaciones complejas tales como:
a = b + c = d + 2; equivale a c = d + 2;
a = b + c;

Igualmente esta característica del igual (=) nos permite que


podamos construír operadores compuestos:

a = a + b equivale a a += b
a = a - b a -= b
a = a & 0x0F a &= 0x0F

Esta composición de operadores se permite entre los operadores


binarios aritméticos y de manipulación de bits ( + - * / & | ^ <<
>> ).

1.7 LA COMA COMO OPERADOR

Como operador, la coma encadena varias expresiones. La parte


izquierda del operador coma siempre se evalúa como void. Esto
significa que la expresión de la parte derecha se convierte en el
valor de la expresión total separada por coma. Por ejemplo:

x = (y=3, y+1);

primero asigna el valor de 3 a y y luego asigna el valor de 4 a x


Los paréntesis son necesarios debido a que el operador coma tiene
menor precendencia que el operador de asignación.

1.8 OPERADORES DE INCREMENTO Y DECREMENTO

Los operadores de incremento (++) y decremento (--) nos permiten


optimizar operaciones de suma o resta en 1

x = x + 1 es igual a x += 1 e igual que ++x o x++


x = x - 1 es igual a x -= 1 e igual que --x o x--

La diferencia entre ++x o x++ es que en el primer caso la variable


se incrementa antes de su utilización, en cambio que en el segundo
caso, la variable se incrementa luego de su utilización, igual
cosa ocurrirá con el decremento. Ejemplo:

p[++s] = 2 equivale a s += 1
p[s] = 2

p[++s] = 2 equivale a p[s] = 2


s += 1
1.9 OPERADORES DE PUNTERO & y *

Un puntero es la dirección de memoria de una variable. Una


variable puntero es una variable específicamente declarada para
contener un puntero a su tipo específico. El primer operador de
punteros es &, un operador monario que devuelve la dirección de
memoria del operando, por ejemplo:

m = &cont;

coloca en m la dirección de memoria de la variable cont. No tiene


nada que ver con el valor de cont.

El segundo operador de punteros es * que es complementario de &.


Es un operador monario que devuelve el valor de la variable
ubicada en la dirección que se especifica. Por ejemplo, si m
contiene la dirección de memoria de la variable cont, entonces:

q = *m;

colocará el valor de cont en q.

Las variables que vayan a contener punteros se declararán como


tales. Las variables que vayan a mantener direcciones de memoria
o punteros, deberán declararse colocando un * delante del nombre
de la variable. Esto indica al computador que va a contener un
puntero a ese tipo de variable. Por ejemplo, para declarar c como
puntero a caracter podemos escribir

char *c;

Aquí, c no es un caracter, sino un puntero a un caracter. El tipo


de dato al que apunta un puntero, en este caso char, se denomina
tipo base del puntero, así, un puntero a caracter (o cualquier
puntero en general) tiene un tamaño sufuciente para guardar una
dirección tal como esté definida por la arquitectura de la
computadora que se utilice.

1.10 EL OPERADOR DE TIEMPO DE COMPILACION sizeof

El operador sizeof es un operador monario de tiempo de compilación


que devuelve la longitud, en bytes, de la variable o del
especificador de tipo entre paréntesis al que precede. Por
ejemplo, suponiendo que los enteros son de 2 bytes y los float de
8 bytes, entonces

float f;

printf("%f", sizeof f);


printf("%d", sizeof int);

mostrará 8 2

Recuerde que para calcular el tamaño de un tipo, el nombre del


tipo debe ir entre paréntesis

1.11 LOS OPERADORES PUNTO (.) y FLECHA (->)

Estos operadores referencian elementos individuales de las


estructuras y de las uniones. Las estructuras y las uniones son
tipos de datos compuestos que se pueden referenciar bajo un solo
nombre.

El operador punto se usa cuando se trabaja realmente con la


estructura o la unión, el operador flecha se usa cuando se usa un
puntero a una estructura o una unión. Por ejemplo, dada la
estructura global:

struct empleado
{
char nombre [80];
int edad;
float sueldo;
} emp;

struct empleado *p = &emp; /* en p, la dirección de emp */

Se escribirá el siguiente código para asignar el valor 123,34 al


elemento sueldo de la estructura emp:

emp.sueldo = 123.23;

Sin embargo, la misma asignación usando un puntero a la estructura


emp sería:

p->sueldo = 123.23;
1.12 LOS PARENTESIS Y LOS CORCHETES COMO OPERADORES

Los paréntesis son operadores que aumentan la precedencia de las


operaciones que contienen.

Los corchetes llevan a cabo el indexamiento de arrays. Dado un


array, la expresión entre corchetes proporciona un índice para el
array, como en el ejemplo de la siguiente página, se asigna
primero el valor 'X' al cuarto elemento (recuerde que los arrays
en C comienzan en el elemento cero) del array c y luego se imprime
ese elemento.

#include "stdio.h"

char c[80];

void main (void)


{
c[3] = 'X';
printf("%c", c[3]);

1.13 RESUMEN DE PRECEDENCIAS

La siguiente tabla lista la precedencia de todos los operadores


del lenguaje C, Observe que todos los operadores menos los
monarios y ?, asocian de izquierda a derecha. Los operadores
monarios (*,&,) y el ? asocian de derecha a izquierda.

⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗
⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗
Mayor precedencia () [] → .
! ~ ++ -- - (tipo) * & sizeof
* / %
= -
<< >>
< <= > >=
== !=
&
^
|
&&
||
?
= += -= *= /=
Menor precedencia ,
⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗
⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗
CAPITULO III
TIPOS DE DATOS DERIVADOS

C nos permite construír a partir de datos elementales otros más


complejos. Algunos ya los tenemos definidos por el lenguaje,
mientras que otros se derivan de los tipos fundamentales.

3.1 ARRAY (ARREGLOS)

Son tablas de datos del mismo tipo.

Declaración.

Se necesitarán de cuatro elementos:

* nombre del array


* tipo
* dimensión o cantidad de elementos que lo componen
* clase de almacenamiento en memoria

Ejemplos:

int tab[100];

En este ejemplo se ha declarado un array de tipo entero de nombre


tab que tendrá 100 elementos enteros (tab[0]..tab[99]); el
almacenamiento en memoria queda implícito, pues será de tipo
externo o automático).

static char texto[32];

En este ejemplo, el tipo ya no es implícito, la clases de ubica-


ción posibles de una array en memoria son la externa, estática y
automática.

Un array puede ser considerado como una variable o como un todo.


Considerada como un todo, es una colección de variables de un tipo
dado, en que cada uno de los elementos de la colección está
destinado a contener un valor concreto. Si t es el nombre del
array de la tabla entonces:

• t[0] es el primer elemento de esta colección.


• t[i] es el elemento (i-1)-ésimo de la colección y además es
una variable de determinado tipo.

Si en cambio t se considera como un identificador de un todo, el


identificador t nos permite señalar la dirección donde comienza el
almacenamiento de la tabla. La dirección igual puede obtenerse
utilizando el operador & ; es decir &t[0] es lo mismo que t. Si
queremos buscar la dirección i entonces podemos obtenerla sea como
&t[i] o como t+i.

Valores iniciales de los elementos de un array.

Si el almacenamiento de memoria para el array es externo o


estático, entonces sus valores iniciales también podrán ser
declarados en el momento de la compilación. Se deberá especificar
los elementos de la lista separados por comas y encerrando la
lista entre llaves, quedando por lo tanto opcional la declaración
del total de elementos del array que, como en los siguientes
ejemplos, se calculará automaticamente:

static char letra[] = {'t','e','x','t','o'};


static int digito[] = { 0,1,2,3,4,5,6,7,8,9 };

En caso de haberse especificado el número total de elementos del


array, los valores iniciales que se especifican deben estar en un
número igual o menor al número total explícito:

short pares[5] = { 2,4,6 }; donde:

pares[0] = 2 pares[1] = 4 pares[2] = 6


pares[3] = 0 pares[4] = 0

Tablas multidimensionales

Para la definición de tablas multidimensionales solo necesitamos


juntar los operadores [] que sean necesarios. En el siguiente
ejemplo tenemos una tabla de 10 tablas de 20 enteros cada una:

int t2d[10][20] ;

Además se comporta como una tabla bidimensional donde 10 es el


número de líneas y 20 el número de columnas. El acceso a un
elemento se obtiene con los índices internos, los mismos que deben
variar entre 0 y la dimensión menos 1.

Si queremos inicializar una tabla multidimensional procedemos de


la misma manera como se explicitó anteriormente:

long t2d [5][4] = {


{ 0,1,2,3 } /* t2d[0][] */
{ 4,5,6,7 } /* t2d[1][] */
{ 8,9,10,11 } /* t2d[2][] */
} ;

int t3d [2][2][2] = {{


{ 1,2 } /* t3d[0][0][] */
{ 3,4 } /* t3d[0][1][] */
}
{
{ 5,6 } /* t3d[1][0][] */
{ 7,8 } /* t3d[1][1][] */
}} ;

El siguiente ejemplo, en cambio inicializa la primera columna a 1:

int tab[3][2] = {{1},{1},{1}} ; /* fig.1 */

y tiene un comportamiento diferente a:

int tab[3][2] = { 1,1,1 } ; /* fig.2 */

fig.1 fig.2

1 | 0 1 | 1
1 | 0 1 | 0
1 | 0 0 | 0

3.2 VARIABLES DE TIPOS PUNTERO (POINTER)

Un puntero (pointer) es una dirección de memoria en cuya localidad


está almacenado un valor correspondiente a un dato. Para el manejo
de punteros en C se tienen dos operadores:

& se refiere a la dirección de un dato.


* se refiere al contenido de un dato.

Declaración

Es necesario que una variable de tipo puntero señale a algún tipo


definido:

int *ptri ; /* ptri es un puntero a un número entero */

El operador de tipo puntero * puede solo aplicarse a expresiones


de tipo puntero. El momento de declarar, no se tiene todavía en
claro a qué objeto apuntar, pues se conoce únicamente su tipo, es
por ello que también se permite dar un valor inicial. En el
siguiente ejemplo:

int i, *p, t[100] ;

la variable i está declarada como entero, p como un puntero a un


entero, y t un array de 100 enteros. Serán correctas las siguien-
tes instrucciones de asignación:

p = &i ; /* p apunta al entero i */


*p = 0 ; /* el valor 0 es asignado a i */

Para la puesta a 0 de todos los elementos de la tabla t:

for (i=0 ; i<99 ; i++ ) t[i]=0;

también puede hacerse de las siguiente manera utilizando el


puntero:

for ( p=&t[0] ; p < &t[100] ; p++ ) *p=0 ;

o también:

for ( p=t ; p < t+100 ; p++ ) *p=0 ;

Operaciones con punteros

Existe una forma equivalente de lograr las direcciones de un


array, tomando en cuenta la última declaración del array t[100] de
100 enteros:

t es equivalente a &t[0]
t+i es equivalente a &t[i]
t[i] es equivalente a *(t+i)

Es por ello que se permiten operaciones como p++ y t+100.

Una constante no puede ser asignada como valor a una variable,


salvo en el caso de cero:

p = 0 ; /* puntero vacío */

En tal caso el puntero no apunta a ningún lado.

En cambio la adición o substracción (como ya se vio) del valor de


una variable o constante entera son operaciones válidas: p++ (que
es lo mismo que p=p+1), y añade 2 al valor actual de p, ya que p
señala a un entero en memoria (un entero se almacena en 2 bytes),
la operación p++ debe señalar al entero siguiente. Si se ubiera
declarado como un puntero a double, entonces se añadiría 8 al
valor actual de p. Otras operaciones como p-=2 y p+=(i+5) también
son correctas.

Esto indica que el valor añadido es convertido en múltiplo del


tamaño del objeto al cual señala el puntero afectado por opera-
ciones de incremento o decremento. Las otras operaciones aritmé-
ticas y lógicas no están permitidas. ( * / % & | ^ ~ ). Operacio-
nes como:

int *p, *q ;
p = 1;
q = 2;

if (p < q);
....

son inválidas ya que en este caso se realiza una operación de


comparación con punteros.
CAPITULO IV
EVALUACION DE EXPRESIONES y
ASIGNACION DINAMICA DE MEMORIA

4.1 EVALUACION DE EXPRESIONES

Para el estudio de evaluación de expresiones en C, primero debemos


observar las prioridades de los operadores, las mismas que se dan
en el siguiente cuadro, donde los operadores () [] -> . son de
máxima prioridad mientras que el de menor prioridad será el
operador coma.

∨⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗ℜ
⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗↵
≥ () [] −> . ≥ ιζθ. α δερ≥
≥ ! ∼ ++ −− − ∗ & (ΤΙΠΟ) σιζεοφ ≥ δερ. α ιζθ≥
≥ ∗ / % ≥ ιζθ. α δερ≥
≥ + − ≥ ιζθ. α δερ≥
≥ << >> ≥ ιζθ. α δερ≥
≥ < <= > >= ≥ ιζθ. α δερ≥
≥ == != ≥ ιζθ. α δερ≥
≥ & ≥ ιζθ. α δερ≥
≥ ⊥ ≥ ιζθ. α δερ≥
≥ | ≥ ιζθ. α δερ≥
≥ && ≥ ιζθ. α δερ≥
≥ || ≥ ιζθ. α δερ≥
≥ ?| ≥ ιζθ. α δερ≥
≥ = += −= ∗= /= %= >>= <<= &= |= ⊥= ≥ δερ. α ιζθ≥
≥ , ≥ ιζθ. α δερ≥
ℵ⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗ℑ
⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗∧

La primera consideración a tomar en cuenta para la evaluación de


expresiones es que las expresiones (que no sean de asignación) se
evalúan de izquierda a derecha, tomando en cuenta la tabla de
prioridad de operadores, por ejemplo:

a + b * c es equivalenta a a + (b * c)
b / c + b * c es equivalente a (b / c) + (b * c)

La segunda consideración, para los operadores unarios y de


asignación, la evaluación va de derecha a izquierda. Por ejemplo
en la siguiente asignación:

a = a / d + b * c;

primero se evalua la a /d luego b * c, seguimos con la suma de


estas dos expresiones evaluadas, y por último, luego de haber
evaluado la expresión de la derecha procedemos a la asignación.

Para el caso de asignaciones múltiples, la evaluación será de


derecha a izquierda, en el siguiente ejemplo, primero se evaluará
a + b cuyo resultado se asigna a k, el valor asignado a k se
asigna luego a j, y este valor se asigna por último a i.

i = j = k = a + b;

Veamos otros ejemplos:

x = x + b * c; es equivalenta a x += b * c;
x = x * (b + c); es equivalente a x *= b + c;
x = x * b + c; es equivalente a x = (x * b) + c;

4.2 ASIGNACIÓN DINAMICA DE MEMORIA

Un aspecto importante de C es la signación dinámica de memoria que


se necesitan para algunos casos. Por ejemplo, cuando declaramos
una variable tipo entero, y un punetero a entero:

int a; /* a variable entera */


int *pa; /* pa es un puntero a un entero */

En el primer caso, para la variable entera a, no es necesario que


el programador cree el espacio donde se guardará el valor asignado
a tal variable, el espacio está creado. Para el segundo caso, el
del puntero a entero *pa, en cambio tenemos dos opciones:

a) Como el puntero es una dirección a un objeto específico,


entonces tenemos la opción de que el puntero apunte a una
dirección donde esté un entero, en este caso es a:

pa = &a ; /* &a quiere decir la dirección de a */

b) La segunda opción es crear el espacio donde se guarde el


valor apuntado por pa. Para esto C nos proporciona tres
funciones: calloc, malloc y realloc. Si con estas tres
funciones se nos permite la reserva de espacios de memoria,
también debemos tener capacidad de liberar espacios de
memoria, esto lo haremos con la funsión free.

Estas cuatro funciones (malloc, calloc, realloc y free) se


encuentran en la librería stdlib.h.

4.2.1 La función malloc()

#include "stdlib.h"
void *malloc(tam);
unsigned int tam;

Supongamos que se requiere reservar memoria para guardar un número


entero, el mismo que será señalado por pa, el formato para
realizar dicha operación de reserva de memoria es el siguiente:

pa = (int *)malloc(sizeof(int)) ;

entonces se puede realizar operaciones de asignación como la


siguiente: *pa = 5;. Esta operación no es posible realizar sin la
reserva de memoria previa. La operación de reserva de memoria
puede ser generalizada como sigue:

ptr = (tipo *)malloc(sizeof(tipo)) ;

En realidad, esta operación puede resultar confusa, sería natural


que se reserve memoria de longitud dada solamente de la siguiente
manera:

ptr = malloc(sizeof(tipo)) ;

Lo que pasa es que malloc() reserva memoria contínua de longitud


dada por sizeof(tipo), lo que devuelve es un puntero a void,
(puntero genérico). Esto implica que (tipo *) hace un casting
(conversión de tipo) para adaptar la reserva de memoria al tipo de
puntero correcto.

La expresión (tipo *)malloc(sizeof(tipo)) puede ser llamada en


forma más simple si declaramos una macro como la que sigue:

#define MALLOC(x) ((x*)malloc(sizeof(x))) ;

De esta manera la operación de reserva para la variable ap sería:


ap = MALLOC(int) ;

4.2.2 La función calloc()

#include "stdlib.h"
void *calloc(num,tam);
unsigned int num;
unsigned int tam;

calloc(), se caracteriza porque guarda una serie de espacios, cada


uno de los cuales tiene una longitud dada, además que estos
valores reservados son inicializados con ceros.

Para la utilización de calloc debemos conocer dos informaciones,


la primera sobre el número de elementos (n), y la segunda sobre la
longitud del tipo de elemento (sizeof(tipo)). Como la función
sigue entregando un puntero a void es necesario seguir utilizando
el casting. Por tanto el formato es el siguiente:

ptr = (tipo *)calloc(n,sizeof(tipo)) ;

Así mismo es posible, que por medio de una macro, simplificar la


llamada a esta función, la macro podría ser:

#define CALLOC(n,x) ( (x *)calloc(n,sizeof(x)) ) ;

Para nuestro ejemplo podríamos concluír una equivalencia:

ap = MALLOC(int) ; es equivalente a ap = CALLOC(1,int) ;

4.2.3 Punetros Nulos (NULL)

A los punteros no les podemos asignar valores como:

ap = 21 ; /* asignación incorrecta */

En cambio es posible asignarle un valor cero, esto significa que


el puntero no señala a ninguna parte:

ap = 0 ; es equivalente a ap = NULL ;

NULL es una constante predefinida para punteros, que indica


dirección nula.
Este valor resulta cuando fracasa el llamado a las funciones
malloc y calloc, generalmente cuando se solicita más memoria de la
disponible, por ejemplo, en la siguiente expresión:

ap = MALLOC(int) ;

Si la memoria disponible es menor a la solicitada entonces ap


señalará a NULL.

4.2.4 Liberación de Bloques de Memoria (free)

#include "stdlib.h"
void *free(ptr);
void ptr;

De la misma manera como reservamos memoria es también necesario


liberarla, pues si hemos asignado memoria en forma dinámica,
cuando retiremos la referencia a ese espacio, se mantendrá y no
estará disponible, es decir se ha convertido en basura.

Si no vamos a seguir utilizando ese espacio de memoria entonces


debemos liberarlo, esto lo conseguiremos con la función free, cuyo
formato es:

free(ptr);

4.2.5 realloc()

#include "stdlib.h"
void *realloc(ptr,tam);
void *ptr;
unsigned int tam;

Esta función cambia el tamaño de la memoria apuntada por ptr. La


longitud del nuevo tamaño está especificada por tam, pudiendo
tomar valores mayores o menores al anteriormente asignado.
CAPITULO V
VARIABLES

Las variables son posiciones en memoria representados por identi-


ficadores que almacenan valores de un tipo determinado, y cuyo
tiempo de vida y modo de almacenamiento está determinado por dónde
y cómo se declaren.

Un programa en C, el momento de correrse tiene el siguiente mapa


de memoria:

∨⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗↵
≥ ≥ διρεχχιονεσ δε ρετορνο δε φυνχιονεσ, εσταδο δελ
≥ πιλα ≥ ΧΠΥ, ϖαριαβλεσ λοχαλεσ (αυτοµ〈τιχασ).
℘⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗×
≥ µοντον ≥ σε υτιλιζα παρα λα ασιγναχι⌠ν διν〈µιχα δε
≥ (ηεαπ) ≥ µεµορια (µανεϕο δε πυντεροσ).
℘⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗×
≥ ϖαριαβλεσ ≥
≥ γλοβαλεσ ≥
℘⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗×
≥ χ⌠διγο δελ ≥
≥ προγραµα ≥
ℵ⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗∧

Los identificadores se componen de letras (A, B, .. , Z, a, b, ..


, z) y dígitos (0,1,..9), pero siempre deben comenzar con una
letra o subrayado.

C diferencia las letras mayúsculas de las minúsculas, es decir que


el identificador de función malloc() es diferente a MALLOC().

La declaración de una variable tiene la siguiente forma:

<clase> <tipo> <lista de identificadores> ;

Por ejemplo:

auto int i,j,k;


float fi,fj,fk;

Primeramente hemos definido las variables i,j,k, todas ellas de


tipo entero y cuya clase es automática. En el segundo caso se ha
declarado la lista de variables fi,fj,fk de tipo float, quedando
explícita la clase.

Según donde se declare una variables, éstas puenen ser:

1) Variables locales (dentro de funciones).


2) Parámetros formales (argumentos de funciones).
3) Variables Globales (fuera de funciones).

5.1 VARIABLES LOCALES

Son todas las variables que se declaran dentro de una función. Su


tiempo de vida es el tiempo que dure la ejecución de dicha
función, luego de lo cual se destruye. Esto, en cuanto a funcio-
nes, pero también son locales las variables que se crean dentro de
bloques (un bloque queda delimitado por { }). Veamos dos ejemplos:

∨⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗↵ ∨⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗↵
≥ εν φυνχι⌠ν: ≥ ≥εν βλοθυε: ≥
≥ ≥ ≥ ≥
≥ φυνχ1() ≥ ≥... ≥
≥ { ≥ ≥ιφ (χονδιχιον) ≥
≥ ιντ ξ ; ≥ ≥{ ≥
≥ ... ≥ ≥ ιντ αρραψ[20] ; ≥
≥ } ≥ ≥ ... ≥
≥ ≥ ≥} ≥
≥ φυνχ2() ≥ ℵ⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗∧
≥ { ≥ φιγ. 2
≥ ιντ ξ ; ≥
≥ ... ≥
≥ } ≥ φιγ. 1
ℵ⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗⊗∧

En el caso de la figura 1, tenemos dos funciones, en ambas se


declara una variable x, la variable x de func1 nada tiene que ver
con la variable x de func2, son totalmente independientes. Para
el caso de la figura 2 tenemos una declaración dentro de un
bloque, int array[20]; esta variable local se creará en forma
automática si y solo si se ejecuta el respectivo bloque, si
hacemos referencia a esta variable fuera del bloque, entonces
obtendremos un error.

Las variables locales también pueden declararse como:

auto int x;

pero esto es innecesario, pues toda variable que no es global es


local o automática, es por ello que la palabra reservada auto, que
significa automática, local, casi no es utilizada.

Hemos dicho que una variable local está creada mientras dura la
ejecución de la función donde se las declara, esto impide que se
las pueda inicializar en tiempo de compilación, es por ello que si
queremos inicializar o mantener su valor sin que se destruya,
podemos declararlas con la palabra reservada static:

static int x = 5 ;
static float fx ;

Esto implica que durante la ejecución del programa se va a


mentener el espacio en memoria para las variables estáticas en
lugar de crearse o destruírse automáticamente, pero el acceso a
ellas es solo por medio de las funciones donde se las declare.

5.2 PARAMETROS FORMALES

Los parámetros formales son los argumentos de una función, si es


que una función tiene argumentos como en el siguiente caso:

int suma(a,b);
int a,b;
{
return (a+b);
}

En este ejemplo los parámetros formales son a y b, sus tipos son


declarados luego de la declaración de la función.

5.3 VARIABLES GLOBALES


Las variables globales se mantienen durante todo el programa y
pueden ser llamadas y cambiadas en cualquier parte de él (como las
variables en BASIC), la declaración de una variable global debe
hacerse desde fuera de las funciones, por ejemplo:

int cuenta; /* cuenta es variable global */


main()
{
...
cuenta = 100 ;
func() ;
/* el valor de cuenta ahora es de 101 */
...
}

func()
{
...
++cuenta; /* cuenta adquire un nuevo valor (101)*/
...
}

5.4 CLASES DE ALMACENAMIENTO

Existen 4 clases de almacenamiento en memoria para las variables:


extern, static, register, auto, las que a continuación son
descritas.

5.4.1 extern

Cuando tenemos varios módulos de programación, y necesitamos en


determinado módulo utilizar variables globales que fueron decla-
radas en otro, debemos volver a declarar las mismas variables pero
de clase extern. Supongamos que en el módulo princip.c tenemos
las siguientes declaraciones:

princip.c int cuenta; /* cuenta es variable global */


main()
{
...
}

y en el módulo auxil.c necesitamos utilizar la variable global


cuenta declarada en princip.c, entonces tenemos la siguiente
declaración:

auxil.c extern int cuenta;


func()
{
...
}

5.4.2 static

Las variables estáticas, son variables permanentes, como las


globales, pero su contenido es conocido sólo si se llama a la
función donde fueron declaradas. Las variables estáticas pueden
ser locales o globales. Ya se explicó las variables estáticas
locales. Sobre las variables estáticas globales, podemos decir
que solo pueden ser reconocidas y utilizadas únicamente en el
archivo o módulo donde fueron declaradas.

5.4.3 register

Las variables register, residen en uno o varios de los registros


del CPU, no están en la memoria principal de la máquina. En una
máquina con procesador de 16 bits (una PC,XT,AT, etc) una variable
de clase register puede tener hasta 16 bits de longitud, es decir
puede ser un char (8 bits), un int, unsigned int, etc.

Las variables register pueden ser locales o parámetros formales,


no pueden ser globales. Se caracterizan por ser sumamente
veloces, auque son limitadas en su número y longitud. Un ejemplo
de declaración es el siguiente:

potencia(m,e);
int m; register int e;

5.4.3 auto

Las variables automáticas (auto) son las variables locales, y no


es necesario la utilización de esta palabra clave por razones
anteriormente expuestas en el estudio de variables locales. Se
llaman automáticas porque se crean y se destruyen automáticamente
al correr el bloque donde fueron declaradas.
5.5 INICIALIZACION DE VARIABLES

La inicialización de variables permite que éstas tengan un valor


al momento de compilación del programa. La inicialización de
variables solo es posible para variables globales y estáticas.
Observemos los siguiente ejemplos:

char ch = 'a' ;
int primero = '0' ;
static float balance = 123.23 ;

Mas adelante, en el estudio de tipos de datos derivados nos


extenderemos en formas más complejas de inicialización.

CAPITULO VI
SENTENCIAS DE CONTROL

C tiene una serie de sentencias de control. Para control de bucles


o lazos: while, for, do-while. Sentencias condicionales: if y
switch. Funciones que afectan al control de flujo: break,
continue y goto.

6.1 VALORES true y false

Las pruebas condicionales se basan en expresiones que sean


verdaderas o falsas, en C un valor falso es un valor 0, y un
verdadero es un valor diferente de 0. Por ejemplo si a==b, es
falso, es decir, a es diferente de b, entonces el resultado de
esta expresión será 0 (flase), caso contrario será un valor
diferente de 0, para este caso es 1 (true).

6.2 LA SENTENCIA while

La sentencia while ejecuta las sentencias dentro de un bloque


mientras sea verdad una condición y tiene el siguiente formato:

while (condición)
{
sentencias
} ;

Como vemos, antes de que se ejecuten las sentencias dentro del


bloque, debe confirmarse la condición. Esta condición es una
expresión booleana cuyo resultado será true o false. Veamos el
siguiente ejemplo donde se imprimen los cuadrados del 1 al 10:

int a;
a = 1;
while (a<=10)
{
printf(" %2d %3d \n" , a , a*a );
a++;
}

En caso de que al inicio del bucle while, el valor de a no fuera 1


sino 11, el bloque correspondiente al bucle no se hubiera
ejecutado ni una sola vez.

6.3 LA SENTENCIA do-while

En el bucle while primero se confirma un condición antes de


proceder con la ejecución de sentencias, en do-while ocurre que
primero se ejecuta sentencias y luego se verifica condición, esto
nos dice que sentencias al menos se ejecutará una vez, el formato
es el siguiente:

do
{
sentencias
}
while(condición) ;

La salida del siguiente ejemplo:

int a;
a = 11;
do
{
printf(" %2d %3d \n" , a , a*a );
a++;
} while (a<=10) ;

será: 11 121

por cuanto aunque no cumpla con la condición, al menos va a


ejecutarse un vez. Si en lugar de la asignación a = 11; tendría-
mos la asignación a=1; el ejemplo planteado es equivalente al
ejemplo dado en while.

6.4 LA SENTENCIA for

El formato de la sentencia for es el siguiente:

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


{
sentencias
} ;

Donde sentencias se ejecuta un número de veces determinado por los


argumentos de for. La inicialización es por lo general una
sentencia de asignación para la variable de control, condición es
la expresión booleana de terminación, incremento es la forma como
se incrementa la variable inicializada. Para nuestro ejemplo:

int a;
for (a=1 ; a<=10 ; a++)
{
printf(" %2d %3d \n",a,a*a) ;
}

La variable de control es a. Un bucle infinito (sin salida),


utilizando for sería:

for (;;) printf("bucle infinito\b");

En la sentencia for, podemos utilizar el operador coma para más de


una variables de control. Por ejemplo en el siguiente ejemplo se
copia la cadena s en r, pero en reversa:

reverse(s,r)
char *s, *r ;
{
int i,j ;
for ( i=strlen(s)-1,j=0 ; i>0 ; j++,i-- )
{
r[i] = s[j];
}
r[j] = '\0';
}

Aquí podemos ver que existen dos variables de control, i y j, i va


disminuyendo de valor, y j va aumentando. Se ejecutará la única
sentencia, r[i] = s[j], mientras i sea mayor que 0.

Un bucle (while, do-while, for) no necesariamente tiene cuerpo,


por ejemplo, un bucle de retardo:

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

no tiene ninguna sentencia de ejecución, lo único que hace es


aumentar la variable de control, lo que produce un retardo.

6.5 LA SENTENCIA if-else

La sentencia if ejecuta las sentencias1 en caso de que la condi-


ción de un valor true o verdadero, caso contrario (else) ejecutará
sentencias2. El formato es:

if (condición)
{
sentencias1;
}
else
{
sentencias2;
}

Para el caso de sentencias de condición anidadas, else siempre


corresponde al último if:

if (condición1)
if (condición2)
{
sentencias1 ;
}
else
{
sentencias2 ;
}

el último else corresponde al if de condición2.

6.6 LA SENTENCIA switch


En if, condición podía tener solo dos valores true o flase, sin
embargo, son muchos los casos en que una condición adquiere n
valores (valor1, valor2, etc), debiendo tener sentencias diferen-
tes para cada caso, veamos el formato:

switch (condición);
{
case valor1:
sentencias1;
break;
case valor2:
sentencias2;
break;
...
case valorn
sentenciasn;
break;
default:
sentenciasd
}

Default ejecutará un conjunto de sentencias cuando han fallado


todos los valores posibles. Si la condición es uno de los valores
propuestos (valor1, valor2,... ,valorn), se ejecutarán las
sentencias respectivas hasta encontrar un break.

La sentencia switch sólo puede comprobar la igualdad, en cambio


que if puede evaluar expresiones relacionales y lógicas.

6.7 LA SENTENCIA break

La sentencia break tiene dos usos:

1) finaliza un case dentro de un switch (como ya lo vimos en el


estudio de la sentencia switch).

2) forzamos la salida de cualquiera de los bucles estudiados


(while, do while, for), dejando de lado las condiciones de
repetición y el resto de sentencias que se encuentren luego
del break en el bloque de repetición.

6.8 LA SENTENCIA continue


Si break forza la terminación de un bucle, continue obliga a que
se vuelva a evaluar la condición del bucle para que pueda volverse
a ejecutar el bloque de repetición, esto, dejando de lado las
sentencias que sigan a continuación de continue; en el siguiente
ejemplo, se obliga a leer un dígito o un caracter:

char ch;
for (;;);
{
ch = getchar();
if (ch>32 && ch<127) break
else
{
if ( ch<=32 ) continue
else printf("ascii extendido");
}
}

6.9 LA SENTENCIA exit()

La función exit() obliga a la terminación de un programa. Puede


tener como argumento a 0, exit(0), que significa terminación
normal, en caso de que tenga como argumento a un número diferente
de 0, se supone que puede acceder a analizar el error con ese
argumento.

6.10 LA SENTENCIA goto

El formato de goto es: goto <etiqueta> ;

y el de etiqueta es: <nombre-etiqueta>:

La sentencia goto nos permite dar un salto a la parte del programa


donde se encuentre la etiqueta respectiva. Un programa debidamente
estructurado debe evitar la utilización del goto, de hecho es la
"sentencia prohibida" en la programación estructurada.
CAPITULO VII
INTRODUCCION A LA PROGRAMACION
ORIENTADA A OBJETOS

"Si no se conoce con exactitud qué es lo que un programa


contemplará, es mejor es no comenzar a escribirlo"

Tenemos una empresa que es a la vez un conglomerado de varias


empresas. El software debe reconocer las demandas existentes de
dicha empresa así como la evolución a nuevas necesidades, razón
suficiente como para utilizar las técnicas orientadas a objeto en
la programación.

En un primer acercamiento, se han establecido los siguientes


objetivos:

* Un sistema de control de inventario.


* Un sistema de ventas.
* Una base de datos del personal.
* Una consolidación de los datos relevantes de las tres
partes anteriores.

Un primer problema que se encuentra, la incertidumbre al no quedar


claramente expuestas las necesidades de la empresa. Esta situación
cambiará con el tiempo. Un proceso continuo de especificaciones,
implementación y refinamiento de las especificaciones es el método
que siguen las técnicas orientadas a objeto. Una de las ventajas
de estas técnicas, es que el tiempo que el programador utiliza
para reescribir código, ahora se lo utilizará en mejorar las
aplicaciones.

Lo primero que debemos hacer, es descomponer los estados requeri-


dos como existen en la actualidad:

El sistema de inventario requerirá de ítemes.

El sistema de ventas requerirá de vendedores y distribuidores que


harán constar las ordenes.

La base de datos del personal implicará la existencia de gente que


trabaje para la empresa.

Por hoy vamos a ignorar -no olvidar- la consolidación, que es la


interrelación de todos los aspectos anteriores.

Los sistemas orientados a objeto tratan con objetos que son el


análogo de las cosas del mundo real. Los objetos son autocon-
tenidos, resultan de datos y métodos para manipular esos datos:
objetos([datos],[métodos])

El mayor concepto que está atado a la programación orientada a


objeto es el modelamiento, es decir que el trabajo del programador
es construir modelos de software de las cosas del mundo real. Si
ese trabajo se lo realiza en forma correcta, muchos de los
problemas encontrados en la interacción de esos objetos, dejarán
de ser problema, lo que nos llevará a una situación diferente que
no se la puede encontrar en los lenguajes tradicionales.

En el diseño orientado a objeto, si el primer nivel de las


operaciones específicas del objeto están correctamente concebidas,
entonces el segundo nivel será relativamente fácil de implementar.
Esto ocurre porque las operaciones de la aplicación global son
construidas desde operaciones basadas en subcomponentes
específicos de la aplicación. Se facilitan las cosas si se
explotan las bondades de bajo nivel, y si no se logra hacerlo,
entonces es mejor reimplementarlas.

Nos resultan familiares las librerías de los compiladores C, las


mismas que nos ofrecen operaciones de bajo nivel con las que
construimos funciones primitivas. Estas funciones para ser
utilizadas, así como las operaciones de bajo nivel de las libre-
rías, deben estar bien implementadas y documentadas.

Retornando a la empresa, tomaremos las tres secciones planteadas


las que serán descompuestas. Para hacerlo debemos hacer una
cuidadosa examinación de las interrelaciones entre ellas.

Primero, el control de inventario es básicamente un contenedor de


ítemes de inventario. Cada ítem es un objeto con algún dato
asociado aún desconocido. Pero podemos asumir algunas cosas
previas:

* Existen cierto número de ítemes en stock para cada ítem.

* Cada ítem tiene un número de inventario o número parte.

* Cada ítem es comprado a uno o mas distribuidores.

* Hay una historia de compras y ventas para cada ítem.

Segundo, el control de ventas es un contenedor de ventas indivi-


duales. Para cada venta debe haber:
* Una orden del cliente.

* Información acerca de la entrega final.

* Información acerca del pago.

Y, tercero, la base de datos de los empleados, tiene una colección


de empleados individuales, que contendrá como mínimo:

* Dirección del empleado.

* Información del impuesto.

* Historial del empleo y descripción del trabajo.

Lo primero que debemos observar son las similitudes entre cada


objeto para que sean explotadas. Para este caso, por ejemplo, el
sistema de inventario conoce de distribuidores, el sistema de
ventas conoce de clientes y el sistema de empleados conoce de
empleados. Cada uno de esos objetos (clientes, distribuidor y
empleados) son personas, con nombre y dirección. Por lo tanto,
debe haber un tipo de objeto general en el sistema, un objeto que
define la característica general de cada uno de esos elementos,
las características que todos ellos tienen en común. Podemos decir
que esos elementos son clases de otro objeto más general, al que
llamaremos entidad.

En la POO cada objeto está definido como una clase que pueda ser
vista como un padrón para la creación de objetos específicos que
tengan un comportamiento similar.

Las clases son agrupadas en jerarquías, que contemplarán clases


muy generales, hasta aquellas que aplicarán las características a
clases específicas. Por ejemplo, tenemos la clase "carnívoro",
que tiene las características específicas para todos los animales
carnívoros, es decir trata por igual a perros y leones, porque la
característica común de los carnívoros es que comen carne. A un
nivel más bajo, entonces ya sabremos que los perros son muy
diferentes de los leones, entonces para describir estas clases, ya
no será necesario repetir las características de los carnívoros,
solo será necesario describir las características específicas.

En este punto ya nos vamos aproximando a una estructura de la


Compañía de Comercio, la que por lo menos contiene tres coleccio-
nes distintas de objetos, una para inventario, otra para ventas y
otra para personal. Estas colecciones actúan de la misma manera,
es decir que debemos manipular una lista de sus respectivos tipos,
y dando un significado uniforme para acceder a ellos. Cada uno de
esos subtipos tienen una organización única para un propósito
individual, sin embargo, todos utilizan la clase entidad para
mantener información acerca de las varias clases de gente con las
que se debe tratar.

Esto permite que asumamos dos cosas importantes acerca de las


operaciones del sistema. Los tres grupos de información son
colecciones de datos que operan de la misma manera. No importa
que sea un ítem de inventario, un registro de vendedor, o un
registro de empleado, serán recuperados de una colección, el
mecanismo para recuperación es el mismo. Al nivel mas alto, la
aplicación puede ser esperada para saber que esas son colecciones
y para saber como acceder a sus contenidos. Segundo, hay una
clase objeto, Entidad, que agrupa a personas tales como vendedo-
res, empleados y distribuidores. Esta clase define cosas genera-
les para todas las personas, desde compañías hasta personas
reales. Por lo tanto deberían contener cosas como:

* El nombre de la persona o compañía.


* La dirección de la persona o compañía.

Debemos tener presente que un objeto Entidad no determina si es


una real o una compañía. Ello ocurrirá cuando tengamos clases más
detalladas de entidades para hacer esta distinción. La
programación orientada a objeto nos permite el desarrollo de
software en forma de top-down sin los temores irritantes sobre la
integración final de los módulos que acompañan a este método en la
programación tradicional. Parece que vamos necesitar un objeto
general llamado Colección, que debe comportarse de una manera
uniforme. Todos los accesos a las bases de datos especializadas
se realizará a través de esa colección, y todos los accesos son
garantizados que se harán de la misma manera. Ahora es cuando
podemos ignorar al nivel superior y comenzar una descomposición
más detallada de cada elemento de la base de datos.

Previo a la resolución de los problemas de la compañía, deben ser


direccionados algunos problemas tácticos del programador. Real-
mente no hay problemas, pero hay límites dentro de los que el
programador debe operar, y en los que debe definir con exactitud
la responsabilidad de hacer en ese momento. La primera área es la
información que debe manejar y la que no debería manejar. La
segunda es que funcionalidad el programador puede asumir en este
instante y cual puede ser postergada para otro momento.

Generalización vs. especialización


Antes de empezar a diseñar los componentes de la base de datos
especializada, las responsabilidades del programador deben ser
claramente delineadas. Por ejemplo, para el ítem de inventario,
debe haber un número de parte y una descripción. No todos tienen
color y talla. La POO nos permite definir lo que es común para
todos los objetos y que es particular para unos pocos. La POO
permite al diseñador posponer la especialización hasta que la
estructura general ha sido construida. Una regla general se
vuelve vital en la POO: Ignorar casos especiales hasta que no
puedan ser ignorados mas.

Algunos programadores comienzan buscando las peculiaridades de la


información, ha tal punto que llegan a tener información que no
puede ser relacionada, en lugar de tener una colección de infor-
mación coherente sin excepciones y con rasgos comunes. La
especialización es el último paso a ser desarrollado, luego de
tener todas las similitudes del sistema a ser explotado. Después
de que el diseño del sistema general ha sido llevado a cavo y
luego de que las excepciones han sido conocidas entonces e imple-
mentadas, continúa un proceso de búsqueda de excepciones raras
hasta que el cliente encuentra sus propios errores. Debemos
generalizar bien para que podamos mejorar y aumentar la especia-
lización al sistema sin afectar cualquier trabajo desarrollado
previamente.

Funcionalidad Pospuesta.

En la programación tradicional, primero creamos las estructuras de


datos, y luego creamos las funciones para manipular dichas
estructuras de datos. En POO, esto no ocurre. Cuando se crea una
nueva estructura u objeto se marca el fin de la responsabilidad
corriente del programador

Das könnte Ihnen auch gefallen