Autor Tema: Proyecto de Curso (Dictado - Notas): Programación en C (2013, por Argentinator)

0 Usuarios y 1 Visitante están viendo este tema.

27 Septiembre, 2013, 07:28 pm
Respuesta #40

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,274
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
40. Instalación de Pelles C

   Vamos a probar aquí otro compilador de C: el Pelles C.
   Es un compilador que funciona en Windows, y está basado en un proyecto antiguo que se llamaba LCC.
   La virtud principal de Pelles C es que, según el autor mismo dice, es un compilador completamente conforme con el estándar C99.
   En particular, es posible utilizar identificadores con caracteres internacionales y con UCNs.
   Además tiene soporte parcial para C11.

   Sin embargo, en este curso seguiremos usando asiduamente el GCC. La razón de esto es que Pelles C depende esencialmente de un solo autor, y es más probable su discontinuación en el futuro. En cambio, aunque GCC no soporta completamente C99, es un proyecto colectivo con chances de seguir existiendo en el futuro, y con errores corregidos periódicamente.

   Así, vamos a usar Pelles C para hacer algunas pruebas, o para comparar compiladores.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Para instalar Pelles C sigamos los siguientes pasos:
   
\( \bullet \)   Ir a la página Pelles C

\( \bullet \)   En el costado izquierdo, clic en Download.

\( \bullet \)   Aparecen principalmente dos opciones: para Windows de 64 bits y para Windows de 32 bits. Descargar la que corresponda a nuestro sistema. En caso de dudas, descargar la vesión de 32 bits.

\( \bullet \)   Una vez que descarga, ejecutamos el archivo Setup.exe recién descargado.

\( \bullet \)   Clic en la casilla que dice "I accept the terms in the license agreement".

\( \bullet \)   Clic en Next o Siguiente, varias veces, hasta que comience la instalación de los archivos en la carpeta asignada automáticamente para Pelles C.

\( \bullet \)   Cuando termine la descarga de archivos, clic en Close. Ya está instalado Pelles C en nuestro sistema.

\( \bullet \)   Buscamos Pelles C (IDE) en el menú Inicio/Programas de Windows, y ejecutamos.

\( \bullet \)   Puede aparecer algún aviso, que lo cerramos.

\( \bullet \)   Aparece en la ventana del programa una lista de opciones. Clic en Start New Project.

\( \bullet \)   Se abre una ventana con muchos íconos. Elegimos, en la sección de Empty Projects, la opción Win32 Console Programa (es el ícono negro). O sea, un programa de consola típico.

\( \bullet \)   Pongamos debajo, como nombre de nuestro proyecto, HolaPelles.c. Luego clic en OK.

\( \bullet \)   Aparece un árbol de proyecto al costado de la ventana del programa, y podremos ver que dice HolaPelles, que es el nombre que pusimos a nuestro proyecto.

\( \bullet \)   Hagamos clic con el botón secundario del ratón en el nombre del proyecto, y luego clic en Add files to project.... Con esto intentamos agregar al menos un archivo fuente a nuestro proyecto, para poder trabajar.

\( \bullet \)   Se abre un cuadro. Escribamos HolaPelles.c y luego clic en OK.

\( \bullet \)   Ahora en el árbol del proyecto aparece agregado el nombre de archivo HolaPelles.c. Hagamos doble clic en HolaPelles.c.

\( \bullet \)   Se nos aparece en el editor de texto una ventana en blanco, que corresponde al archivo HolaPelles.c, y que espera a que escribamos algo ahí. Escribamos el siguiente programa:

#include <stdio.h>
int main(void) {
    printf("Hola mundo! (desde Pelles C)\n");
}

\( \bullet \)   Guardamos el programa, y lo compilamos así: Clic en el menú Project, y luego clic en Build HolaPelles.exe. Es decir, esto compila y crea el ejecutable para nuestro programa.

\( \bullet \)   Acá me saltó el antivirus, diciendo que el archivo ejecutable recién generado es sospechoso... En tal caso, ir al programa antivirus y desactivarlo por 1 hora, o lo que prefieran. Luego reintentar la compilación.

\( \bullet \)   Ahora ejecutemos el programa recién compilado haciendo clic en el menú Project y luego clic en Execute HolaPelles.exe.

\( \bullet \)   Listo. Observamos que el programa ejecuta bien, mostrando el mensaje de saludo, y que además el mismo programa Pelles C se encarga de frenar la consola poniendo un mensaje de Press any key to continue..., o sea, se queda ahí hasta que presionemos alguna tecla.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Cuando queramos hacer cualquier otro programa en Pelles C podemos seguir esta misma mecánica, desde la creación del proyecto hasta la compilación y ejecución.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Probando caracteres Unicode en identificadores, en Pelles C

   Una de las razones por las que les invito a instalar este compilador, es para ver que realmente es posible definir identificadores con caracteres internacionales, y con UCNs, como el estándar C99 dice.
   Probemos con el siguiente sencillo programa, que define un entero cuyo nombre de identificador tiene UCNs y caracteres internacionales.
   En Pelles C ahora podemos definir identificadores así (mientras que en GCC obtenemos errores por todos lados).
   Probemos con un ejemplo. Creemos el proyecto de consola testids, luego agreguemos el archivo TestIds.c al árbol de proyectos, y pongámoles el siguiente contenido:

int main(void) {
    int \u00c0__éñç__ = 1234;
   return \u00c0__éñç__;
}

   Si intentamos compilar, vemos que el identificador \u00c0__éñç__ se considera válido, y el compilador pasa sin problemas.

   Sin embargo, cuando intentemos colocar caracteres de otros idiomas, que no estén en la página de códigos 1252, sí tendremos mensajes de error del compilador.

   Esto podemos solucionarlo si ponemos en el menú Tools, en Options..., pestaña General, opción Source Files, y elegimos el ítem UTF-8.
   Ahora puede que nos aparezcan mensajes de Warning, avisando que no puede convertir los caracteres Unicode que hemos indicado a la página de códigos 1252 (de Windows).
   De todos modos el programa compila bien igual. Probemos con este ejemplo:

int main(void) {
    int ret\u0b82x_23_\u0aef\u03c5\u00c0__éñç__ = 1234;
   return ret\u0b82x_23_\u0aef\u03c5\u00c0__éñç__;
}

   No sólo compila bien, sino que al ejecutarlo, retorna al sistema el valor 1234 que hemos puesto ahí. Así que todo funciona de diez.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

28 Septiembre, 2013, 12:27 am
Respuesta #41

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,274
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
41. Instalación de PCC

   En este post instalaremos y probaremos el compilador PCC.

   El único problema es que tengo problemas para recordar cómo diablos hice para instalar este compilador, y que funcione.
   Así que la "versión oficial" de instalación la voy a dejar trunca en un spoiler, y en cambio les voy a pasar directamente los archivos para que los copien.

Instalación en forma oficial

   Vayamos a la siguiente dirección web:
   
Página de descarga de PCC

Aparecen varios archivos ahí:

Parent Directory
mingw-runtime-3.14pcc-src.tar.gz
pcc-20090408-win32.exe
pcc-20090818-win32.exe
pcc-20110517-win32.exe
pcc-20111206-win32.exe
w32api-3.11pcc-src.tar.gz

   Hacemos clic en aquel que tenga extensión .exe con la fecha más actualizada.
   Si ustedes hallaran la misma lista que les acabo de poner, este archivo sería el penúltimo, que se llama:

pcc-20111206-win32.exe

   Tras hacer clic en él, se realizará la descarga del programa instalador.
   Cuando termine la descarga, hacemos clic en él para que se ejecute. Procederá a iniciar la instalación de PCC para Windows.
   Seguimos todos los pasos de instalación que nos aparecen (en general, basta hacer clic en cada botón Next varias veces).
   En la 1er ventana aparece un mensaje indicando que se instalará PCC 1.1.x en su sistema. Debajo, hacemos clic en Next (o Siguiente).
   En la 2da ventana aparece la opción de elegir la carpeta donde se instalará PCC. Yo elegí poner PCC en la carpeta c:\pcc\. Les recomiendo que hagan lo mismo, a fin de que trabajemos todos de forma homogénea. Pero pueden ustedes elegir la ubicación que prefieran, o dejar la que el instalador pone por defecto.
   En la 3er ventana aparece la carpeta del Menú Inicio de Windows en la cual colocarán íconos de acceso directo a PCC. Elijan lo que quieran, no es muy importante.
   En la 4ta ventana aparece una casilla para marcar la opción de Add application directory to the system path. Esa casilla debe estar marcada. Si no lo está, marquémoslal con un clic.
   En la 5ta ventana aparece información de lo que hemos elegido, y cómo será la instalación. Hacemos clic debajo en el botón que dice Install.
   La instalación dará comienzo, y todo quedará listo para que podamos compilar programas con PCC desde cualquier punto de nuestro sistema.

   Con esto tendremos rápidamente instalado el compilador PCC.
   Sin embargo no se ha instalado ningún editor de código.
   Para escribir nuestros programas en C para el compilador PCC, tendremos que elegir qué editor de textos usar. Puede ser cualquier editor diseñado para facilitar la vida a los programadores de lenguajes cualesquiera. Un tal editor es el PsPad. Pero hay muchos otros parecidos igual de útiles.
   La realidad es que el notepad de Windows es suficiente para programar, porque después de todo un programa en C es tan sólo un archivo de texto con extensión .c.
   Lo importante luego es poder compilarlo con el compilador de nuestra preferencia, que a los fines de este post es PCC.

   Importante: Una distribución de un compilador dado contiene básicamente dos componentes distintos, que se entrelazan: el compilador propiamente dicho, y las librerías.
   Con la instalación de PCC para Windows lo que hemos instalado es el compilador de PCC con las librerías de MinGW.
   A las librerías de MinGW ya las conocíamos, porque son las mismas que acompañan a la distribución de GCC para Windows.

   Para comprobar la instalación, hagamos el típico programa "Hola mundo":
   
   Vayamos a nuestra línea de comandos: menú Inicio, Ejecutar..., y luego escribimos CMD.
   Una vez en línea de comandos vamos a NUESTRA carpeta de proyectos C, que estoy llamando aquí cproj, mediante:

CD C:\CPROJ

   Una vez allí, creamos un archivo fuente en C, el más pequeño que podamos, a fin de testear si todo funciona bien. Por ejemplo. Podríamos hacer un archivo de 4 líneas que haga un "Hola mundo".
   ¿Cómo se crea un archivo fuente, o en general, cualquier archivo de texto pequeño, desde la línea de comandos? Con COPY CON, así:

COPY CON holapcc.c
#include <stdio.h>
int main(void) {
    printf("Hola mundo! (desde PCC)\n");
}

   Una vez que terminamos la edición, tenemos que presionar las teclas CTRL Z (que significa fin de archivo de texto), y luego presionar ENTER.

   Listo, hemos creado un programa en C desde línea de comandos.

   Para ver si compila y ejecuta, invoquemos el compilador PCC así:

PCC -o holapcc.exe holapcc.c


   Ese comando invoca el compilador PCC con la intención de generar el archivo ejecutable holapcc.exe compilando el archivo fuente holapcc.c.
   La opción -o es necesaria para poder elegir apropiadamente el nombre del ejecutable. De lo contrario, siempre generará un ejecutable con el triste nombre de a.exe.

   Si todo salió bien, ya tenemos un programa ejecutable, que podemos verificar si funciona o no, así:

HOLAPCC

   Esto mostrará en pantalla el mensaje:

Hola mundo! (desde PCC)


:)

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

(Instalación manual)

Instalación manual

   Si por alguna razón no pueden descargar el archivo anterior, les dejo aquí una descarga directa para que puedan instalar PCC.

   En este mismo post está colgado un archivo denominado pcc20130927.rar.
   Descarguemos directamente de aquí el archivo pcc20130927.rar.
   Está comprimido en formato RAR, así que lo descomprimimos con WinRar o cualquier otro descompresor que funcione.
   El contenido del archivo comprimido volquémoslo en una carpeta llamada c:\pcc.
   ¡Listo!

[cerrar]

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Compilar y trabajar con PCC

   Para poder usar este compilador, tenemos que trabajar directamente en línea de comandos.
   No estamos muy acostumbrados a esto, pero será una buena práctica.
   Más aún, cuando necesitamos programar algo rápido, y sólo tengamos a mano el PCC en un pendrive, y estemos fuera de casa, o lejos de nuestra confortable computadora, tenemos que saber cómo compilar y correr programas en C desde la línea de comandos.

   Necesitamos acceder a la ruta del programa ejecutable pcc.exe. Pero también requerimos la ruta de las librerías y otros programas...

   Si la instalación fue correcta, esto habrá ocurrido automáticamente y no tenemos que hacer nada.
   Pero si no, tendremos que solucionarlo de algún modo.
   Lo que les aconsejo es crear un proceso por lotes llamado XPCC.BAT, que nos ayudará con la compilación con PCC.
   Como esto es opcional, y sólo para aquellos que lo necesiten, lo pongo en spoiler:
   
Spoiler

   Por ahora nos contentaremos con una solución bastante sencilla, que funcionará para la mayoría de nuestros propósitos.
   Crearemos un archivo por lotes, que llamaremos xpcc.bat, que nos arreglará todo el asunto. El contenido de dicho archivo tenemos que editarlo nosotros. Así que con un editor de texto, pongámosle este contenido:

   (En lo que sigue yo uso la carpeta C:\PCC, que es donde instalé PCC. Ustedes deberán cambiarla por la suya).

xpcc.bat


if "%1"=="/h" goto :help
if "%PCCDIR%"=="" goto :errorstart
if "%1"=="" goto :errorpar1
goto :main

:help
echo xpcc [/x] NAME
echo        It calls:
echo        pcc.exe -o NAME.EXE NAME.C
echo       /x In additon, execute NAME.EXE with up to 7 parameters.
echo.
goto :end

:errorstart
echo Error 1. Starting PCC env. Try again.
set PCCDIR=C:\PCC\BIN\
set PATH=%PATH%;%PCCDIR%
goto :end

:errorpar1
echo Error 2. Use: pcc /h for help.
goto :end

:main
if not "%1"=="/x" goto :pcc
call c:\cproj\xpcc.bat %2
if "%errorxpcc%"=="1" goto :end
%2 %3 %4 %5 %6 %7 %8 %9
echo Program: %2   Params: %3 %4 %5 %6 %7 %8 %9   Return: %errorlevel%
goto :end

:pcc
if "%1"=="" goto :errpar1
set errorxpcc=0
pcc.exe -o %1.exe %1.c
set errorxpcc=%errorlevel%
goto :end

:end



   Este archivo por lotes asume que tenemos una carpeta llamada cproj en donde alojamos nuestros proyectos C, y en particular es en donde alojaremos al archivo mismo xpcc.bat.
   Si queremos otro nombre para esa carpeta, tengamos cuidado de reemplazar por el nombre correcto en los dos lugares en que aparece "cproj".

   El modo de uso de este archivo por lotes es el siguiente:
   
   Repitamos los pasos, si es necesario, de crear el programa "Hola mundo" desde linea de comandos como explicamos más arriba.
   Como ahora PCC no nos compila directamente, necesitamos la ayuda de nuestro proceso por lotes xpcc.bat.
   Para testear este archivo por lotes, y de paso la instalación de PCC, lo hacemos así:

XPCC holapcc

   Esto buscará el archivo holapcc.c, y lo compilará, creando el archivo ejecutable holapcc.exe.
   Por lo tanto, ya tenemos un programa ejecutable, que podemos verificar si funciona o no, así:

HOLAPCC

   Esto mostrará en pantalla el mensaje:

Hola mundo! (desde PCC)

   Finalmente, podemos hacer juntas las operaciones de compilar y ejecutar, agregando al archivo por lotes el parámetro /x, así:

XPCC /x HOLAPCC

   Nuestro archivo por lotes también tiene una pequeña "ayuda", si escribimos esto:

XPCC /h

[cerrar]

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Bueno. Espero que se haya entendido algo.
   La instalación manual de PCC fue muy directa, así que la mayor parte del trabajo estuvo en testear la instalación, y tener una forma práctica de compilar y ejecutar nuestros programas.
   De paso, se los puse a propósito así para ejercitar un poco el modo de trabajo en línea de comandos.

   Avisen si estos pasos de instalación no les funcionan bien

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

28 Septiembre, 2013, 09:56 pm
Respuesta #42

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,274
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
42. Objetos, tipos y valores en C. Discusión general

   Este post contiene una discusión que terminó resultándome demasiado filosófica.
   Yo no perdería tiempo en tal cosa si no lo considerara absolutamente necesario.
   El dilucidar la correcta interpretación de los términos que aquí estudiaremos, me ha permitido a mí sentirme sólido en la comprensión del manejo de los datos que se producen en un programa en C.

   Definición informal de: objetos, valores y tipos


   Un objeto es una porción de memoria, que contiene unos bits determinados allí.
   Típicamente, un objeto ocupa una cantidad entera de bytes, y no de bits individuales o sueltos.
   En la práctica, los objetos se consideran, pues, "una porción específica de la memoria RAM", en la que los bytes están contiguos, en bloque.
   En ese caso, el objeto consta de una posición de memoria, un tamaño medido en bytes contiguos, y un contenido formado por los bits presentes en esa región de memoria.

   Nota técnica: No debemos confundir esta noción de objeto con los tipos de datos de la Programación Orientada a Objetos que puede haber en lenguajes como C++.
   En nuestro contexto, objeto es un término que sólo se refiere a un dato guardado en una posición específica de memoria, y "ocupando" una cantidad determinada de bytes.

   Para vislumbrar mejor esto, conviene tener en cuenta la representación típica de los datos en un chip de memoria. Se considera a la memoria RAM como un conjunto de bytes alineados en secuencia y en forma contigua.
   Los elementos de la memoria se pueden indexar desde el 1ero, que lleva número 0, hasta el último, que típicamente es alguna potencia de 2 (menos uno).
   En cada byte de la memoria hay unos bits, que alojan 0's y 1's.

0   \( \longrightarrow \)   01101101
1   \( \longrightarrow \)   10101100
2   \( \longrightarrow \)   00011001
3   \( \longrightarrow \)   11100010
4   \( \longrightarrow \)   11111110
5   \( \longrightarrow \)   01000001
... ... ... ... ... ... ... ... ... ... ...

   Un objeto podría ser el ocupado por las posiciones contiguas de memoria de la 2 a la 5, otro podría ser el ocupado por las posiciones de la 11 a la 33, y así por el estilo.
   Puede haber objetos que se superpongan, ocupando algunos bytes en común.

   Para la definición formal de objeto, el estándar C no asume ningún modelo específico de memoria RAM, aunque asume que tiene una cierta "posición", la cual es compatible con operaciones aritméticas de los tipos enteros, un "tamaño" que es un entero positivo de bytes, y un "contenido".

   Para C, el contenido exacto (bit a bit) del objeto no es algo "importante" per se, en el sentido de que la codificación que se usa para representar una determinada información puede variar de un compilador a otro, o de un entorno a otro, y esto no es algo de lo que C se preocupe mayormente (en realidad sí, pero sólo en relación a ciertos detalles, y por razones lejanas a la presente discusión).
   Lo importante es que ese contenido codifique, de alguna manera, los "valores" de algún "tipo" de datos dado.
   Un valor de un cierto tipo es algo que tiene "significado" para nosotros como seres humanos. Este "valor" se exhibirá al usuario del programa de alguna manera. Pero no se trata de un concepto del todo nítido.
   Lo que el estándar C intentará es que las operaciones y comportamiento de los "valores" sea consecuente con lo que esperamos de ellos, sin importar el modo en que estén representados como bits en memoria.
   Así, cuando calculemos 3 + 5, nos tiene que dar 8, sin importar qué codificación usemos en memoria para los "valores" 3, 5 y 8.

   En resumen, lo que quiero decir aquí es que no tenemos que preocuparnos de la representación interna en bits de los valores, sino que, independientemente de dicha representación, el estándar C asegura un comportamiento bien definido.
   Es más: distintos compiladores y entornos de ejecución pueden dar lugar a representaciones internas diferentes de los mismos valores y tipos.
   Bueno, pero, si se almacena "contenido crudo en bits" y no "valores", ¿cómo es que se logra un comportamiento bien definido al operar sobre los "valores"?
   Esto es responsabilidad del diseñador del compilador, quien debe elegir con cuidado una codificación coherente de los datos.

   La "interpretación" del contenido de un objeto ocurre cuando se le mira como de cierto tipo de datos (ya veremos cómo hacer esto).
   Si "enmascaramos" al objeto con cierto tipo, resulta tener un determinado valor.
   El tipo lo podemos elegir nosotros (no quiere decir que siempre dé algo con sentido), y el valor resultante queda "ahí en el limbo" esperando que alguien lo vea.
   Posteriormente hay que buscar la manera de que ese valor sea realmente visualizado por el usuario.

   Sin embargo, cuando se realizan operaciones aritméticas entre objetos, lo que ocurre es más o menos lo siguiente:
   
\( \bullet \) Se toman los objetos sobre los que se quiere operar.
\( \bullet \) Se considera a esos objetos como de cierto tipo, según reglas que luego veremos.
\( \bullet \) Se efectúan operaciones entre los valores de los objetos, respecto el tipo de datos elegido para cada uno.
\( \bullet \) Se obtiene un resultado que es otro valor, que tiene también su propio tipo.
\( \bullet \) Se codifica este resultado en algún objeto en memoria.

   Así, en memoria no se guarda ni el valor ni el tipo, pero para acceder a un objeto, debemos hacerlo mediante un tipo e interpretando su contenido como un valor de dicho tipo.

   Ejemplus:
   
   El programador escribe la constante 33, cuyo tipo es int según las normas de C. Esto quiere decir que, en un compilador que interpreta enteros de 16 bits y bytes de 8 bits, el "valor" 33 se codificará en memoria como un objeto que ocupa 2 bytes, así: 00000000  00100001.
   El objeto resultante es un bloque contiguo de 2 bytes de memoria RAM, con los bits antes indicados como contenido.
   Cuando queremos leer el dato, asumiendo que es un int, esos bits se "interpretan" como el número entero matemático y abstracto 33.
   Esa "abstracción" no queda almacenada en ninguna parte, ni se "ve" en ninguna parte de la computadora.
   Todo lo que podemos asegurar es que, operando con ese objeto, se comportará como si fuera el número 33.
   Por ejemplo, si le sumamos 1, el resultado será otro objeto en memoria, cuya codificación coincidirá con la elegida para el valor 34 de tipo int.

   Si tenemos ahora el valor negativo -33, ya ni siquiera voy a poner su formato binario como ejemplo.
   Se elegirá alguna codificación en bits, como objeto en la memoria, que "provino" del valor -33, de tipo int.
   Ese objeto, interpretado ahora como de tipo unsigned int, puede darnos un valor sin sentido para nosotros, tal como 65503.
   Es que ahora el tipo unsigned int impide que un objeto en memoria sea interpretado como un número negativo.

   ¿Qué ocurre entonces cuando printf() nos muestra un "valor"?
   Ahí está el meollo del problema. Una función que nos muestra información como printf(), no nos muestra un dato que está "almacenado por ahí en memoria". En realidad lo que hace es: "tomar un objeto alojado en memoria, e interpretar su contenido según un tipo que se habrá seleccionado, y al valor resultante lo exhibe en pantalla con un formato específico".
   De nuevo, el "valor" sigue dando vueltas en abstractilandia, y es algo de lo que la computadora no se entera ni le concierne.
   Así, hay una sutil relación entre "contenido de un objeto" y "valor interpretado" de ese contenido, que depende de las subjetividades del intelecto humano hasta cierto punto.
   El que estas subjetividades humanas sean consecuentes con operaciones de objetos en una computadora, se debe a la combinación de varios elementos de diseño: una elección de codificación para los valores de un tipo dado, unas reglas de operación con dichos valores que son firmes y predecibles, y una elección del formato de visualización de los datos.
   La visualización de los datos no es algo trivial, sino que involucra una compleja cadena de operaciones internas.
   Por ejemplo, para que printf() nos muestre el valor 33, antes tiene que hacerse una conversión de binario a decimal, tras varias operaciones de división. Luego los dígitos resultantes se transforman en caracteres que representan dígitos, obligando a hacer nuevas operaciones.

   Los objetos son modificables.

   Si bien las posiciones de memoria están fijas en una computadora, así como el tamaño de cada byte, en cambio el "contenido" de 1 byte de memoria puede alterarse, cambiando sus bits por otros.
   Es decir, el contenido de un objeto puede variar.
   Como resultado, un mismo objeto puede representar diferentes valores a lo largo de un programa.

   Hay ocasiones en que se habla de posiciones de memoria de "sólo lectura", o de "objetos no modificables" (o "constantes").
   Esto quiere decir que hay objetos que, una vez que tienen determinado contenido, éste no varía a lo largo del programa.
   Sin embargo, estas son convenciones dentro del mismo programa, o del sistema operativo en donde se ejecuta, y no un impedimento de la computadora, cuya memoria RAM siempre es físicamente alterable.

   Así, para impedir que un objeto varíe su contenido a lo largo de un programa, es necesario que el programa mismo efectúe algunas comprobaciones extra todo el tiempo.
   Esta tarea no la debe realizar el programador, sino que es el compilador el que ha de producir un programa ejecutable diesñado de esa manera. Por lo tanto, no debemos preocuparnos por tales detalles.

   Es interesante darse cuenta de cuánto hay de "engaño" cuando usamos una computadora. Creemos que vemos números y datos, y en realidad sólo obtenemos el cínico reflejo de nuestras convenciones sociales sobre números e información.
   Es decir, mucho del trabajo computacional lo termina haciendo nuestro cerebro, sin que nos demos siquiera cuenta de ello.
   La máquina sólo transforma unas cosas en otras, unos bits de un lugar en otros bits en otro lugar.
   En el ejemplo del número 33, cuando un programa finalmente nos muestra ese "valor", no nos muestra ni el "contenido" en bytes del objeto en memoria que lo tiene almacenado, ni tampoco el número 33 propiamente dicho, que es una abstracción matemática que la computadora ni sabe que existe. Lo que nos muestra printf() es un par de caracteres ASCII 3 (cuyo código ASCII es 51...), que es lo mismo que exhibir la cadena de caracteres "33".
   El "valor" 33, ¿dónde está? En el imaginario social.

   ¿Y qué es un tipo? Es también una entidad abstracta: es el nombre de un cierto conjunto de cosas.
   Típicamente los conjuntos a que nos referiremos son "finitos".
   Si bien un conjunto está unívocamente determinado por sus "elementos", resulta que podemos darle más de un "nombre" distinto.
   Esos "nombres" son lo que, a grandes rasgos, consideramos un tipo.
   Sin embargo, un tipo especifica también propiedades globales de los elementos del conjunto a que se refiere: qué operaciones aritméticas son válidas entre elementos del conjunto, qué relaciones hay entre ellos, y cómo se vinculan con elementos de otros tipos.

   Los elementos de un conjunto, nombrado por un tipo, son lo que se entiende por valores posibles para ese tipo.
   Así, un valor está determinado por dos propiedades: el elemento que representa, y el tipo al que pertenece.
   Un mismo elemento, considerado de tipos distintos, representa valores distintos.
   Un 33 de tipo int es un valor distinto que un 33 de tipo long long int.

   A pesar de eso, las cosas están diseñadas en C para que todas las versiones "distintas" de 33 funcionen como si fueran equivalentes aritméticamente, y esto hace transparentes los cálculos para un programador.
   En todo caso, a fin de no pifiarle, tendremos que estudiar luego las reglas que permiten pasar de un tipo a otro, y qué consecuencias hay para los valores considerados.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Intento formal de definición de: objetos, valores y tipos

   En C se le dice objeto a una región de almacenamiento de datos en el entorno de ejecución cuyos contenidos pueden representar valores.
   Un valor es el significado preciso de los contenidos de un objeto cuando se interpretan teniendo un tipo específico.

   Se supone que esas frases definen el significado de los términos objeto, valor y tipo.
   A mí me parece todo circular y mal definido.
   A fin de definir las cosas con más certeza, podríamos intentar subsumir estos términos en una fraseología matemática.
   Lo que sigue son definiciones mías, y no del estándar C99:
   
   Digo que un tipo es un nombre dado a un conjunto finito de elementos, sujetos a ciertas relaciones y operaciones y reglas.
   Esto del "nombre" es importante, porque un mismo conjunto puede tener diferentes nombres, o sea, ser designado mediante diferentes tipos.
   Digo que un valor es uno de los elementos del conjunto a que se refiere un tipo dado. Pero no es un elemento "suelto", sino que un valor dado lleva atado siempre un tipo al cual pertenece.
   Un objeto es una entidad formada por dos cosas: una región de almacenamiento de datos (típica, aunque no necesariamente, un bloque contiguo de 1 o más bytes de memoria RAM), y unos contenidos alojados en dicho región de almacenamiento, susceptibles de modificación a lo largo del tiempo.

   En la práctica, los contenidos de un objeto son sólo unos bits puestos en fila, sin significado alguno.
   Sólo tienen un significado concreto cuando se les interpreta con un tipo de datos específico.
   Ese significado es uno de los valores aceptados para el tipo en cuestión.

   El "significado" es una noción que atañe al intelecto humano. Se refiere al sentido subjetivo último que una mente humana le da a los entes manejados y exhibidos por computadoras.

   Si el tipo es algo "abstracto", ¿cómo se entera la máquina de que le estamos indicando un tipo y no otro, en determinadas operaciones aritméticas, o cuando le pedimos que nos muestre un resultado?
   Ahí es la máquina la que debe interpretar correctamente el tipo, y no nuestra psique.
   Hay algunos detalles de los tipos que se almacenan como información en el programa.
   Por ejemplo, a un tipo se le asocia comunmente una determinada cantidad fija de bytes, que será su tamaño.
   Hay relaciones concretas entre los tipos y la codificación de sus valores como objetos en memoria, que permiten un trabajo coherente.
   Estas relaciones se almacenan como parte del programa ejecutable, y entonces todo comienza a funcionar, como un engranaje.
   No voy a discutir aquí cómo se hace esto, porque en realidad lo que acabo de decir es incluso más alegórico que cierto.

   Como sea, parece que estoy ampliando aquí la definición de tipo. (En matemática no me dejan hacer eso de "ampliar luego un poquito más la definición"). ;)

   En cierto modo, un tipo tiene dos caras: una serie de operaciones y relaciones entre objetos de los que se almacenan en memoria, y una serie de operaciones y relaciones entre los elementos de un conjunto abstracto.
   Se estipula una correspondencia uno a uno entre dichos objetos de memoria con los elementos abstractos, de modo que se conserven operaciones y relaciones en forma coherente.
   En particular, esto obliga a que los conjuntos resultantes se comporten de un modo algo "trabado", siguiendo las limitaciones que una computadora impone.
   Por ejemplo, el tipo int no puede representar todos los números enteros, sino sólo una porción de ellos. Las operaciones con números grandes darán resultados no claramente definidos, o con significado inútil.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Una nota sobre el documento estándar

   Para mi gusto, las nociones presentadas en el estándar son poco claras, algo circulares, y sólo con experiencia y reflexión uno termina logrando una comprensión exacta de los términos allí "definidos".
   Salvando este defecto, parece ser que el estándar ha intentado definir los términos objeto, tipo y valor de forma "operacional", es decir, indicando más bien "cómo interactúan entre ellos", o qué efectos producen.
   En tal caso, se deja la puerta abierta a más sutiles abstracciones.
   Esto puede ser preferible, dado que el concepto de máquina en el estándar C es la más indefinida de las nociones que he visto allí nombrar.
   Dejando algo de flexibilidad para el significado de los valores y objetos asociados, puede resultar fecundo en algún caso.

   Los ejemplos que yo dí, con bytes de 8 bits, y con modelos de memoria RAM concretos, aún cuando lo hice de forma simplificada, no dejan de ser una elección concreta, un modelo de almacenamiento de datos que no necesariamente hay que asumir.
   Cuando estudiemos los punteros, veremos qué es lo máximo que el estándar C asume como propiedades ciertas de un dispositivo de almacenamiento de datos.
   Ejemplificar con la RAM sirve más para "fijar ideas" y concretar en un ejemplo, cosa útil pedagógicamente, pero que quita generalidad. Ha de tenerse en cuenta que el estándar intenta ser lo más general que sea posible, es decir, va en la dirección contraria. Esto es así debido a que, a fin de cuentas, C es un lenguaje de alto nivel, y tanto mejor es si puede ser ejecutado en la más variada gama de sistemas y máquinas (incluyendo dispositivos móviles, máquinas virtuales, microcontroladores, placas de robots, y un largo etcétera).

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

29 Septiembre, 2013, 06:14 am
Respuesta #43

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,274
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
43. Tipos de datos básicos en C

   Los tipos de datos en C se designan con ciertos identificadores (ver sección 39).
   Algunos de estos identificadores vienen ya predefinidos en C, y otros pueden ser definidos por el usuario.
   El programador puede definir sus propios tipos de datos mediante la palabra clave typedef, cuya sintaxis debemos posponer.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Tipos de datos básicos de C

   Ya hemos estudiado muchos tipos de datos en C, pero los hemos analizado desde la perspectiva de sus rangos de valores, y las reglas de asignación de tipos para constantes literales.
   Ahora nos interesan desde el punto de vista de la teoria de tipos, que inicialmos en la sección 42.
   Los tipos predefinidos en C tienen palabras clave asociadas.

\( \bullet \)   En C existe, como ya vimos, un tipo booleano llamado: _Bool, cuyo rango de valores contiene al 0 y al 1.
      ¿Qué significa esto? ¿Puede contener más valores que esos? Esteee... Sí (en algún maligno compilador).

\( \bullet \)   Existen los tipos char, signed char y unsigned char, que se consideran todos distintos.
      Sin embargo el rango de valores de char coincide exactamente con el de alguno de los otros dos.
      Estos 3 tipos se llaman tipos de caracter, y corresponden a objetos que ocupan exactamente 1 byte en memoria.

\( \bullet \)   Los tipos de enteros estándar con signo (o signados) son: signed char, short int, int, long int, long long int.
      Sinónimos son:
                   short para short int.
                   signed int y signed para int.
                   signed long int y signed long para signed long int.
                   signed long long int, signed long long y long long para long long int.
\( \bullet \)   Los tipos enteros estándar sin signo son: _Bool, unsigned char, unsigned int, unsigned long int, unsigned long long int.
      Sinónimos son:
                   unsigned para unsigned int.
                   unsigned long para unsigned long int.
                   unsigned long long para unsigned long long int.
\( \bullet \)   En conjunto, se llama tipos de enteros estándar al conjunto de todos los tipos enteros estándar con signo y sin signo.
\( \bullet \)   Por cada tipo entero estándar con signo, existe un correspondiente tipo sin signo, que se indica anteponiendo unsigned.
\( \bullet \)   Los objetos definidos con las versiones sin signo y con signo de un tipo estándar entero, ocupan la misma cantidad de bytes de almacenamiento.
\( \bullet \)   La implementación local también puede definir sus propios tipos enteros, que se denominan extendidos.
      Se tienen así: tipos extendidos con signo, tipos extendidos sin signo.
      También, por cada tipo entero extendido con signo hay un correspondiente tipo entero extendido sin signo, ocupando la misma cantidad de bytes en memoria.
   Nosotros no tendremos en cuenta tipos extendidos, porque dependen del compilador, y nos conviene ceñirnos estrictamente a lo definido explícitamente en el estándar.

\( \bullet \)   Existen tres tipos reales de punto flotante: float, double y long double.
\( \bullet \)   Existen tres tipos complejos: float _Complex, double _Complex, long double _Complex.
\( \bullet \)   Por cada tipo real de punto flotante existe un correspondiente tipo complejo de punto flotante, el cual se obtiene agregándole la palabra clave _Complex.
\( \bullet \)   Todos ellos se denominan en conjunto tipos de punto flotante.

\( \bullet \)   El tipo char, los tipos enteros con signo, los tipos enteros sin signo y los tipos de punto flotante se llaman colectivamente tipos básicos.
\( \bullet \)   Un compilador puede definir nuevas palabras clave para indicar los mismos tipos básicos (sinónimos). En tal caso, se siguen considerando parte del conjunto de tipos básicos.


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Declaración de objetos de tipos básicos

   Aquí daremos una rápida manera de declarar objetos de un tipo dado.
   En otro post daremos más detalles, pues la sintaxis es más amplia que lo que mostraremos aquí.

   Un objeto de tipo T quedará caracterizado por su identificador y por su tipo.
   El identificador lo usamos en nuestro programa, para no escribir explícitamente la posición de memoria (fija) en que se aloja nuestro objeto.
   Para "crear" nuevos objetos en el programa, y poder utilizarlos, es decir, asignarles valores, o incluso modificar luego estos valores en el curso del programa, hacemos una declaración como esta:
   
tipo identificador ;

   Es muy importante terminar la declaración con un punto y coma: ;

   Por ejemplo:

int x;
float numb;
char letra;
unsigned long int big_integer;
unsigned char ASCII_element;
_Bool flag;
   

   Ahí hemos declarado un objeto que persistentemente será considerado de tipo int, y por lo tanto decimos que tiene tipo int. Su nombre es x.
   Además declaramos un objeto llamado numb de tipo float, uno de tipo unsigned long int llamado big_integer, uno de tipo unsigned char llamado ASCII_element, y uno de tipo _Bool llamado flag.


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

05 Octubre, 2013, 05:33 am
Respuesta #44

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,274
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
44. Operaciones aritméticas en C. Introducción

Presentación de los operadores aritméticos

   Desde el punto de vista teórico no estamos en condiciones de dar aquí una teoría completa de expresiones en C. Así que el concepto de expresión lo daremos de un modo algo informal.
   El estándar C99 tampoco es muy preciso al hablar de operandos y operadores, ambos ingredientes esenciales en la definición de expresión.
   Iremos de a poco, a riesgo de ser algo repetitivos.

   Un operador en C actúa sobre uno o más valores de un cierto tipo y da como resultado otro valor de un cierto tipo. Con esto queremos remarcar que los valores siempre van atados a sus tipos.
   Lo anterior no alcanza para definir completamente el término operador. Los operadores pueden actuar también sobre objetos, produciendo modificaciones a dichos objetos. Es decir, al mismo tiempo que el operador hace un cálculo con los valores alojados en un objeto, según el tipo de dicho valor, también es posible que realice acciones que modifiquen las características, atributos o valores de los objetos involucrados.
   Por ejemplo, el operador unario izquierdo ++ actuando sobre una variable entera j: 5 / ++j hace dos cosas: cambia el valor de la variable j aumentándolo en 1, y ese valor es también el resultado de la operación ++j, el cual será usado como dividendo en la expresión de división que hemos puesto de ejemplo.

   En C un puntuador (punctuator) es uno de los siguientes:

[   ]   (   )   {   }   .   ->
++  --  &   *   +   -   ~   !
/   %   <<  >>  <   >   <=  >=   ==   !=   ^   |   &&   ||
?:  ;   ...
=   *=  /=  %=  +=  -=  <<= >>=  &=   ^=   |=
,   #   #
<:  :>  <%  %>  %:  %:%:


   Los puntuadores del último renglón son en realidad digrafos, vale decir, secuencias de 2 caracteres que reemplazan a algún otro caracter.
   El significado de estos digrafos es como sigue:

<:     [
:>     ]
<%     {
%>     }
%:     #
%:%:   #


   A diferencia de los trigrafos, los digrafos funcionan como símbolos "de verdad" en el lenguaje, y no son reemplazados en contextos en que no se espera un puntuador. Por ejemplo, los digrafos no son reemplazados si se encuentran dentro de una constante literal de cadena de caracteres.

   La mayoría de los puntuadores funcionan, según el contexto, como operadores.
   Un operando es una entidad sobre la cual un operador actúa.
   Esto define, en el estándar C, los términos operador y operando. No me parece a mí muy satisfactorio, pero así es como está. Notemos que se usa la palabra entidad para referirse a los operandos, en vez de la palabra objeto. Esto es así porque los objetos son un caso particular de "entidades": aquellas que tienen asociada una ubicación concreta en el espacio de almacenamiento (memoria RAM).

   Un operador es unario si actúa sobre un solo operando. En este caso se dice que es prefijo si el operador se escribe a la izquierda del operando, y en cambio es posfijo si el operador se escribe a la derecha del operando.
   Un operador es binario si actúa sobre dos operandos.
   Un operador es ternario cuando actúa sobre tres operandos.

   De todo un posible universo de operadores, existen unos pocos que pueden utilizarse explícitamente en C.
   Los operadores a nivel de bits en C son los siguientes:

Unarios:     ~
Binarios:    <<   >>    &    |    ^

   Restricción: Los operadores de bits requieren operandos de tipo entero.
   Estos operadores permiten jugar con los bits de la representación interna de los valores de los operandos. Veremos luego que los valores de tipos enteros sin signo tienen una representación binaria muy concreta, pero que no es así para los tipos enteros con signo. Esto nos obliga a tomar precauciones cuando se nos ocurra trabajar a nivel de bits con datos enteros signados. El resultado depende de la implementación local, entre otros factores no especificados por el estándar.
   Si queremos jugar con los bits de otros objetos en C, primero tendremos que enmascararlos de algún modo en un tipo entero.


   Los operadores posfijos (unarios) de incremento y decremento son los siguientes:

++    --


   Los operadores prefijos (unarios) de incremento y decremento son los siguientes:

++    --


   Los operadores prefijos unarios siguientes, se aplican sólo a tipos aritméticos:

+    -


   El operador prefijo unario de tamaño es el siguiente:

sizeof


   Los operadores cast (unarios prefijos) son aquellos que permiten convertir el tipo del valor del operando. Existen conversiones implícitas y conversiones explícitas. Explicaremos este tema en detalle más adelante. Por ahora nos conformamos con saber que, si T denota un tipo de datos, y X denota un objeto o constante de un tipo dado, entonces se puede hacer un cast explícito hacia el tipo T, de la siguiente manera:

(T) X

   El cast se considera un operador unario más, y está sujeto a ciertas restricciones, que no explicaremos en este lugar.
   Sin embargo, los casts de tipos básicos como los que hemos estudiado en la Sección 33, tienen un comportamiento bastante intuitivo. De todos modos, creo conveniente postergar estos detalles también.

   Los operadores aritméticos (binarios) son los siguientes:

*    /    %
+    -

   Estos operadores significan, grosso modo, las operaciones aritméticas de: producto, cociente, resto, suma, resta.

   Los operadores relacionales (binarios) son los siguientes:

<    >    <=    >=
==   !=

   Sirven para comparar, y grosso modo significan: menor que, mayor que, menor o igual que, mayor o igual que, igual que, distinto que.

   Los operadores lógics (binarios) son los siguientes:

&&   ||

   Representan la conjunción y disyunción lógicas.

   El operador condicional (ternario) es el siguiente:

?:

   Se usa para evaluar expresiones alternativas, según que una cierta condición sea verdadera o falsa.

   Los operadores de asignación (binarios) son los siguientes:

=    *=    /=    %=    +=    -=    <<=   >>=   &=    ^=    |=


   El operador coma (binario) es el siguiente:

,



CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Precedencia, asociatividad, corto circuito

   En general vamos a tener expresiones complicadas, mezclando muchos operadores y operandos. En ese caso, es importante tener claro el orden en que se efectúan las operaciones, cómo se asocian, y qué acciones se realizan antes que otras, o se dejan de hacer.
   Un operador tiene mayor precedencia que otro, si se asocia en forma prioritaria que éste último.
   Por ejemplo, la división tiene mayor precedencia que la resta, y entonces una expresión como

x - y / z

se asociará de modo que antes se efectúe la división, y luego la resta, así:

x - (y / z)

(Eso es muy distinto que si fuésemos en el orden en que las operaciones se han escrito, de izquierda a derecha: (x - y) / z)

   Aunque el orden de asociación esté establecido, eso es algo que afecta al resultado matemático de las operaciones, pero que no nos dice nada acerca de qué operando se evalúa primero en una expresión complicada.
   El orden de evaluación muchas veces no está especificada. Sólo en algunos casos está claramente indicado.

   En la siguiente lista ponemos los operadores mencionados hasta ahora, según su precedencia, de la más alta primero, hacia la más baja después. (Operadores más arriba en la tabla, se asocian primero).

Operadores           Asociación    Evaluación                            Nomenclatura
( )                     izq. a der.   (unaria)                              Paréntesis
++    --                izq. a der.   antes de modificar objeto             Incremento y decremento posfijos
++    --                der. a izq.   después de modificar objeto           Incremento y decremento prefijos
+     -                 der. a izq.   (unaria)                              "más" unario y "menos" unario
!     ~                 der. a izq.   (unaria)                              Negación lógica, negación bit a bit
(T)                     der. a izq.   (unaria)                              Cast explícito hacia el tipo "T"
sizeof                  der. a izq.   (unaria)                              Tamaño del objeto en bytes
*     /     %           izq. a der.   (no definida)                         Operadores multiplicativos (producto, cociente, resto)
+     -                 izq. a der.   (no definida)                         Operadores aditivos (suma, resta)
<<    >>                izq. a der.   (no definida)                         Operadores de corrimiento de bits
<     <=    >     >=    izq. a der.   (no definida)                         Operadores relacionales
==    !=                izq. a der.   (no definida)                         Operadores relacionales
&                       izq. a der.   (no definida)                         AND bit a bit
^                       izq. a der.   (no definida)                         OR bit a bit
|                       izq. a der.   (no definida)                         XOR bit a bit
&&                      izq. a der.   (no definida)                         AND lógico
?:                      der. a izq.   condición, luego 2do o 3er operando   Operador condicional
=
+=   -=   *=   /=   %=
<<=  >>=  &=   ^=   |=  der. a izq.   1ro. el operando de la derecha        Operadores de asignación
,                       izq. a der.   1ro. el operando de la izquierda      Operador coma


   Notas sobre el operador condicional:
   
\( \bullet \)  La asociatividad de derehca a izquierda se comprende bien cuando hay varios operadores condicionales juntos. Por ejemplo, a?b:c?d:e se asocia así: a?b:(c?d:e). Programar este tipo de cosas con cuidado.
\( \bullet \)  El signo separador : tiene, en realidad, menor precedencia que el operador coma. Por ejemplo, una expresión como la siguiente:

    p? x, y: a, b;

es equivalente a haber asociado de la siguiente manera:

    p? (x, y): a, b;

   En realidad conviene pensar que los signos ? y : actúan como un paréntesis para la expresión que se encierra entre ellos. Es decir, ?x: debe entenderse como ?(x):.

   La utilidad del operador coma es que permite escribir varias expresiones en una misma línea de programa, en forma cómoda. Dado que las asignaciones son también expresiones, pueden agruparse varias asignaciones de esta manera, separadas por comas. El último operando de una cadena de operaciones con operador coma es el resultado de la expresión total. Pero cuando programamos así, normalmente no nos interesa este último valor, y lo descartamos.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   En el siguiente post vamos a estudiar los operadores actuando sobre valores y objetos de los tipos básicos. Muchos de estos operadores actúan sobre otro tipo de objetos, pero los detalles los veremos en profundidad en otro momento.

   Cuando el resultado de una operación no cabe en un determinado tipo, a veces ocurre una promoción a un tipo de mayor nivel.
   Los detalles exactos de cómo se producen estas promociones serán explicados en otra sección. Conformémnos con algunas reglas más o menos intuitivas y fáciles de entender:
   
   Si en una operación, los operandos tienen el mismo tipo, el resultado será de la operación será del mismo tipo.
   Si en una operación, uno de los operandos tiene un tipo de menor nivel que el otro operando, el tipo del resultado coincidirá con el del operando con nivel mayor.
   Si el resultado no cabe en el tipo esperado, a veces se buscará un tipo mayor en donde quepa el valor del cálculo obtenido, y otras veces no (remarcablemente, con tipos unsigned). Los detalles, más adelante en el curso.
   Cuando el resultado se espera que sea de tipo _Bool, todo valor distinto de 0 (o 0.0) se reduce a 1.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

05 Octubre, 2013, 06:02 am
Respuesta #45

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,274
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
45. Operaciones aritméticas en C. Significado de los operadores (parte I)

   A continuación explicaremos cómo actúan los operadores, y pondremos programas de ejemplo. Para visualizar el resultado que cada programa devuelve al sistema, hay que: guardarlo con un nombre (y con extensión .c), compilarlo, ejecutarlo desde la línea de comandos, y luego ejecutar el comando de sistema:

ECHO %ERRORLEVEL%


\( \bullet \)  x++   Operador incremento posfijo: El resultado de esta operación es el valor del objeto de la izquierda, sin cambios. Luego, al objeto x se lo modifica, sumándole 1 al valor que tenía antes.
\( \bullet \)  x--   Operador decremento posfijo: El resultado de esta operación es el valor del objeto de la izquierda, sin cambios. Luego, al objeto x se lo modifica, restándole 1 al valor que tenía antes.
Ejemplo:

int main(void) {
    int x;
    x = 13;
    int resultado;
    resultado = x++;
   
    return resultado;
}

Aunque la variable x quedará con un valor igual a 14 en el programa anterior, el valor que queda en la variable resultado es 13 (éste es el valor retornado al sistema).:

\( \bullet \)  ++x   Operador incremento prefijo: Al objeto x se lo modifica, sumándole 1 al valor que tenía antes. El resultado de la operación es este valor modificado.
\( \bullet \)  --x   Operador decremento prefijo: Al objeto x se lo modifica, restándole 1 al valor que tenía antes. El resultado de la operación es este valor modificado.
Ejemplo:

int main(void) {
    int x;
    x = 13;
    int resultado;
    resultado = ++x;
   
    return resultado;
}

Aquí la variable x quedará con un valor igual a 14, y también el valor retornado al sistema es 14.

\( \bullet \)  +x   Operador "más" unario:   El resultado de esta operación es el valor del objeto de la izquierda, sin cambios. (Esto imita el efecto matemática de anteponer un signo positivo a una expresión aritmética).
\( \bullet \)  -x   Operador "menos" unario: El resultado de esta operación es el negativo del valor del objeto de la izquierda. Cuando este resultado se sale de rango, tendremos que estudiar en detalle los efectos más adelante.
Ejemplo:

int main(void) {
    int x;
    x = 13;
   
    return -x;
}

El resultado devuelto al sistema es:

-13
   

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

\( \bullet \)  sizeof x   Operador de tamaño: El resultado de la operación es un número entero que indica el tamaño en bytes que requiere el objeto x.
Ejemplo:

int main(void) {
    signed char x;
    x = 13;

    return sizeof x;
}

Esta programa retorna el tamaño de un char medido en bytes, que siempre es 1.
Para otros tipos de datos, este tamaño está definido por la implementación local.

\( \bullet \)  (T) x   Cast explícito de tipo T: El resultado de la operación es el valor x convertido al tipo T, siempre que esto tenga sentido. Si no, el resultado no está siempre bien definido.
Ejemplo:

int main(void) {
    float x;
    x = 9.8076;

    return (int) x;
}

Este programa retorna el valor entero 9, es decir, el truncamiento del valor de tipo flotante 9.8076.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

\( \bullet \)  a+b   Suma: El resultado de la operación es el valor de la suma de los valores de a y b.
\( \bullet \)  a-b   Resta: El resultado de la operación es el valor de la resta de los valores de a y b.
Ejemplo:

int main(void) {
    int a;
    int b;
    a = 15;
    b = 11;

    return a + b - 4;
}

Este programa retorna 22.

\( \bullet \)  a*b   Producto: El resultado de la operación es el valor del producto de los valores de a y b.
\( \bullet \)  a/b   División: El resultado de la operación es el valor del cociente de los valores de a y b.
\( \bullet \)  a%b   Resto: El resultado de la operación es el valor del resto de la división de los valores de a y b.
   Estos 3 operadores se llaman multiplicativos, y sólo se aplican a tipos aritméticos.
   Los operandos del operador % tienen que ser enteros.
   Cuando los dos operandos de una división son de tipos enteros, la división es entera, es decir, se trunca la parte fraccionaria. Y más aún, el tipo del resultado es también entero.
   Para la división / y para el resto %, si el 2do operando es 00.0), el resultado de la operación es indefinido. Puede producir errores durante la ejecución del programa.
   Cuando los dos operandos son de tipos enteros, se satisface siempre la siguiente condición:
   
\( \bullet \)        El valor de a es igual al de:            (a/b)*b + a%b

   En C99 son muy claras las reglas de los signos. En matemática abstracta, el resto de la división siempre es no-negativo. Pero en C99 el signo del resto de la división siempre coincide con el signo del dividendo.

int main(void) {
    int a;
    int b;
    a = -35;
    b = 4;

    return a/3 % b;
}

El resultado de a/3 es -11, porque la división es entera. Luego, a este valor se lo divide por 4 y se retorna el resto de la división, que da -3.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

\( \bullet \)  a<b   Menor: El resultado de la operación es 1 si el valor de a es estrictamente menor que el de b, y 0 en caso contrario.
\( \bullet \)  a>b   Mayor: El resultado de la operación es 1 si el valor de  a es estrictamente menor que el de b, y 0 en caso contrario.
\( \bullet \)  a<=b  Menor o igual: El resultado de la operación es 1 si el valor de  a es menor o igual que el de b, y 0 en caso contrario.
\( \bullet \)  a>=b  Mayor o igual: El resultado de la operación es 1 si el valor de  a es mayor o igual que el de b, y 0 en caso contrario.
   En el caso de operandos aritméticos, ambos operandos han de tener siempre un tipo real. (No se pueden comparar números complejos).
   Los valores retornados 0 ó 1 se consideran de tipo int.
Ejemplo:

int main(void) {
    float a;
    signed long long int b;
    a = 12.1476;
    b = 9876543210;

    return a <= b;
}

Este programa retorna 1.

\( \bullet \)  a==b   Igual: El resultado de la operación es 1 si el valor de  a es exactamente igual que el de b, y 0 en caso contrario.
\( \bullet \)  a!=b   Distinto: El resultado de la operación es 1 si el valor de  a es diferente que el de b, y 0 en caso contrario.
   Los valores retornados 0 ó 1 se consideran de tipo int.
Ejemplo:

int main(void) {
    double a;
    double b;
    a = 12.1476;
    b = 23.99;

    return a != b;
}

Este programa retorna 1.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

05 Octubre, 2013, 06:05 am
Respuesta #46

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,274
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
46. Operaciones aritméticas en C. Significado de los operadores (parte II)

\( \bullet \)  ~x    Negación bit a bit: Se toman los bits que representan el valor del entero x, y se los cambia a todos por el bit contrario (0's por 1's y 1's por 0's).
      La operación NOT (o negación) bit a bit, se rige por la siguiente tabla:

                x   ~x
                0   1
                1   0

Ejemplo:

int main(void) {
    unsigned char x;
    x = 121;          /* En binario es: 01111001 */
    unsigned char resultado;
    resultado = ~x;   /* En binario es: 10000110 */
   
    return resultado; 
}

El programa anterior retorna al sistema el valor resultado, que es igual a:

134


\( \bullet \)  a&b   Operador AND bit a bit: Se toman los bits que representan los valores de los enteros a y b, y se realiza una "conjunción lógica" bit a bit, con los bits que se corresponden de cada operando.
      La operación AND bit a bit se rige por la siguiente tabla:

                a   b   a&b
                0   0    0
                0   1    0
                1   0    0
                1   1    1

Ejemplo:

int main(void) {
    unsigned char a;
    unsigned char b;
    a = 47;            /* En binario es: 00101111 */
    b = 137;           /* En binario es: 10001001 */
    unsigned char resultado;
    resultado = a&b;   /* En binario es: 00001001 */
   
    return resultado; 
}

El programa anterior retorna al sistema el valor resultado, que es igual a:

9


\( \bullet \)  a^b   Operador XOR bit a bit: Se toman los bits que representan los valores de los enteros a y b, y se realiza una "disyunción exclusiva lógica" bit a bit, con los bits que se corresponden de cada operando.
      La operación XOR bit a bit se rige por la siguiente tabla:

                a   b   a^b
                0   0    0
                0   1    1
                1   0    1
                1   1    0

Ejemplo:

int main(void) {
    unsigned char a;
    unsigned char b;
    a = 47;            /* En binario es: 00101111 */
    b = 137;           /* En binario es: 10001001 */
    unsigned char resultado;
    resultado = a^b;   /* En binario es: 10100110 */
   
    return resultado; 
}

El programa anterior retorna al sistema el valor resultado, que es igual a:

166


\( \bullet \)  a|b   Operador OR bit a bit: Se toman los bits que representan los valores de los enteros a y b, y se realiza una "disyunción lógica" bit a bit, con los bits que se corresponden de cada operando.
      La operación OR bit a bit se rige por la siguiente tabla:

                a   b   a|b
                0   0    0
                0   1    1
                1   0    1
                1   1    1

Ejemplo:

int main(void) {
    unsigned char a;
    unsigned char b;
    a = 47;            /* En binario es: 00101111 */
    b = 137;           /* En binario es: 10001001 */
    unsigned char resultado;
    resultado = a|b;   /* En binario es: 10101111 */
   
    return resultado; 
}

El programa anterior retorna al sistema el valor resultado, que es igual a:

207


\( \bullet \)  a<<b   Operador corrimiento de bits hacia la izquierda: Se toman los bits del operando izquierdo, y se hace un "corrimiento" hacia la izquierda, tantas veces como indica el operando de la derecha.
   Las posiciones que quedan "vacantes" se rellenan con 's.
   Si el operando b es negativo o demasiado grande, el resultado es impredecible.
   Si el operando izquierdo a es de un tipo unsigned dado, tal que M es el máximo valor representable en dicho tipo, el resultado de la operación es:
\( a \cdot 2^{b} \% (M+1) \)
en donde \( \% \) significa "resto de la división".
   Si se trata de un valor de tipo signed, pero es no-negativo, y en el rango del correspondiente tipo unsigned, el resultado es el mismo que antes.
   En otro caso, el resultado depende de la implementación.
Ejemplo:

int main(void) {
    unsigned char a;
    int b;
    a = 141;            /* En binario es: 10001101 */
    b = 5;
    unsigned char resultado;
    resultado = a<<b;   /* En binario es: 10100000 */
   
    return resultado;
}

El programa anterior retorna al sistema el valor resultado, que es igual a:

160


\( \bullet \)  a>>b   Operador corrimiento de bits hacia la derecha: Se toman los bits del operando izquierdo, y se hace un "corrimiento" hacia la derecha, tantas veces como indica el operando de la derecha.
   Las posiciones que quedan "vacantes" se rellenan con 's.
   Si el operando b es negativo o demasiado grande, el resultado es impredecible.
   Si el operando izquierdo a es de un tipo unsigned dado, tal que M es el máximo valor representable en dicho tipo, el resultado de la operación es:
\( a / 2^{b} \)
en donde \( / \) significa "división entera".
   Si se trata de un valor de tipo signed, pero es no-negativo, y en el rango del correspondiente tipo unsigned, el resultado es el mismo que antes.
   En otro caso, el resultado depende de la implementación.
Ejemplo:

int main(void) {
    unsigned char a;
    int b;
    a = 173;            /* En binario es: 10101101 */
    b = 4;
    unsigned char resultado;
    resultado = a>>b;   /* En binario es: 00001010 */
   
    return resultado;
}

El programa anterior retorna al sistema el valor resultado, que es igual a:

10




\( \bullet \)  !p   Negación lógica Si p es nulo, el resultado de la operación es 1. Si es no-nulo, el resultado es 0.
   El tipo del resultado es int.
Ejemplo:

int main(void) {
    _Bool p;
    p = 9 == 5;

    return !p;
}

El valor de p es 0. Luego el programa retorna el valor 0.

\( \bullet \)  p&&q   AND Es la conjunción lógica. El resultado de la operación es 1 si ambos operandos son no nulos. En otro caso, el resultado es 0.
\( \bullet \)  p||q   OR  Es la disyunción lógica. El resultado de la operación es 1 si alguno de los operandos es no nulo. En otro caso, el resultado es 0.
   Los valores retornados 0 ó 1 se consideran de tipo int.
   Que un operando sea "nulo" quiere decir que al compararlo en una igualdad contra el valor 0, el resultado es "verdadero" (o sea, 1).
   Se garantiza evaluación de izquierda a derecha en corto-circuito. Esto quiere decir que los operandos se evalúan en orden de izquierda a derecha, hasta que aseguren el resultado 0 ó 1 de la operación. Una vez que esto puede asegurarse, el resto de la expresión no se evalúa. Analizaremos esto más adelante.
Ejemplo:

int main(void) {
    _Bool p;
    _Bool q;
    p = 5 < 9.17;  /* Acá p vale 1 */
    q = -11 != 29; /* Acá q vale 1 */

    return p && q;
}

Este programa retorna 1.



\( \bullet \)  p?a:b   Condicional Se evalúa el 1er operando p. Si el resultado es "no nulo", se evalúa a, y este es el resultado de la operación. Si no, se evalúa b, y este es el resultado de la operación.
Ejemplo:

int main(void) {
    _Bool p;
    int a;
    int b;
    a = 19;
    b = -1234;
    p = a < b;

    return p?a:b;
}

El valor de p es 0. El programa retorna el valor de b, que es -1234.

\( \bullet \)  x=v    Asignación simple Se modifica el valor del objeto x, poniéndole el valor que tiene el objeto v. Esto es una operación que arroja un resultado. El resultado es el valor del objeto x, luego de la modificación de su valor.
\( \bullet \)  x+=v   Asignación con suma Se modifica el valor del objeto x, sumándole al valor que ya tenía, el valor de v. El resultado de esta operación es el valor del objeto x, luego de la modificación de su valor.
\( \bullet \)  x-=v   Asignación con resta Se modifica el valor del objeto x, restándole al valor que ya tenía, el valor de v. Esto es una operación que arroja un resultado. El resultado de esta operación es el valor del objeto x, luego de la modificación de su valor.
\( \bullet \)  x*=v   Asignación con producto Se modifica el valor del objeto x, multiplicándole el valor que ya tenía por el valor de v. Esto es una operación que arroja un resultado. El resultado de esta operación es el valor del objeto x, luego de la modificación de su valor.
\( \bullet \)  x/=v   Asignación con cociente Se modifica el valor del objeto x, dividiéndole el valor que ya tenía por el valor de v. Esto es una operación que arroja un resultado. El resultado de esta operación es el valor del objeto x, luego de la modificación de su valor.
\( \bullet \)  x%=v   Asignación con resto Se modifica el valor del objeto x, tomándole el resto al valor que ya tenía según el divisor valor v. Esto es una operación que arroja un resultado. El resultado de esta operación  es el valor del objeto x, luego de la modificación de su valor.
\( \bullet \)  x&=v   Asignación con AND bit a bit Se modifica el valor del objeto x, haciéndole un AND bit a bit al valor que ya tenía junto con el valor de v. Esto es una operación que arroja un resultado. El resultado de esta operación  es el valor del objeto x, luego de la modificación de su valor.
\( \bullet \)  x|=v   Asignación con OR inclusivo bit a bit Se modifica el valor del objeto x, haciéndole un OR (inclusivo) al valor que ya tenía junto con el valor de v. Esto es una operación que arroja un resultado. El resultado de esta operación es el valor del objeto x, luego de la modificación de su valor.
\( \bullet \)  x^=v   Asignación con XOR (OR exclusivo) bit a bit Se modifica el valor del objeto x, haciédole un XOR (OR exclusivo) al valor que ya tenía junto con el valor de v. Esto es una operación que arroja un resultado. El resultado de esta operación es el valor del objeto x, luego de la modificación de su valor.
\( \bullet \)  x<<=v  Asignación con corrimiento de bits a izquierda Se modifica el valor del objeto x, haciéndole un corrimiento de bits a izquierda al valor que ya tenía, según indica el valor de v. Esto es una operación que arroja un resultado. El resultado de esta operación es el valor del objeto x, luego de la modificación de su valor.
\( \bullet \)  x>>=v  Asignación con corrimiento de bits a derecha Se modifica el valor del objeto x, haciéndole un corrimiento de bits a derecha al valor que ya tenía, según indica el valor de v. Esto es una operación que arroja un resultado. El resultado de esta operación es el valor del objeto x, luego de la modificación de su valor.
Ejemplo:

int main(void) {
    unsigned int x;
    int v;
    x = 2048;
    v = 7;
   
    return x >>= v;
}

   Primero, las expresiones de igualdad simple x = 2048 y v = 7, son operaciones de igualdad, que arrojan un valor. En el ejemplo esto no se ve muy claro.
   Luego viene la expresión x >>= v. Allí el valor de x, que es 2048, y en binario 100000000000, se le hace un corrimiento de de 7 bits hacia la derecha, dando el valor binario 10000, es decir, 16 (en decimal). Este resultado se asigna ahora como valor a la variable x, que queda así, modificada. Tras todas estas acciones, la expresión misma tiene como resultado este valor modificado de x, a saber, 16. Por lo tanto, el valor de p es 0. El programa retorna al sistema el valor 16.

\( \bullet \)  x,y   Coma Se evalúa el 1er operando x, y luego se evalúa el 2do operando y. El resultado de la operación es el valor de este 2do operando, es decir y.
Ejemplo:

int main(void) {
    float x;
    short int y;
    x = 3.1416;
    y = 77;
    return x,y;
}

El programa retorna el valor de y, que es 77.

\( \bullet \)  (x)   Paréntesis Se evalúa el operando x. El resultado de la operación es este valor sin cambios.
   Esto parece una trivialidad, pero en realidad los paréntesis se usan para agrupar expresiones, cambiando el orden o prioridad de evaluación en una expresión dada.
Ejemplo:

int main(void) {
    short int n;
    n = 26;

    return (n+9);
}

El programa retorna el valor 35.



Organización

Comentarios y Consultas

05 Octubre, 2013, 10:00 am
Respuesta #47

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,274
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
47. Introducción a las estructuras de control en C

   Supongamos que hemos escrito un programa en C, y lo hemos compilado y ejecutado.

   El programa se ejecuta a través de una línea de tiempo, que podemos imaginar discreta y secuencial (esto es, numerando los "instantes" con enteros no negativos: 0, 1, 2, 3, ...).
   En un entorno de ejecución en que hay 1 solo procesador, sólo puede ejecutarse, como máximo, una instrucción del microprocesador en cada instante de tiempo.
   Si hay N procesadores en paralelo, pueden ejecutarse a lo sumo N instrucciones por cada instante de tiempo.
   El lenguaje C no se involucra con lo que ocurre a nivel de microprocesadores. El estándar C99 ni siquiera introduce formalismos para tener en cuenta ejecuciones multitarea o multiproceso. En cambio el estándar C11 ya se inmiscuye en estos asuntos, pues hay experiencia previa suficiente para introducir una formalización.

   De todas maneras, la definición formal de C se mantiene abstraída y alejada de estos pormenores, y en cambio concibe un modelo de ejecución secuencial.
   Un programa en C se divide en varios trozos, en los cuales es posible distinguir separaciones llamadas puntos de secuencia (en inglés: sequence points.
   Entre dos puntos de secuencia consecutivos, se supone que la máquina realiza de algún modo una serie de tareas que desde el programa fuente en C se han especificado.
   No se puede iniciar un nuevo punto de secuencia sin haber concluido limpiamente todas las tareas que debían realizarse hasta el anterior punto de secuencia.

   Los puntos de secuencia respetan la línea de tiempo.
   La presencia de un ; (punto y coma) suele indicar una terminación de instrucción, lo cual, en particular, pone fin a todas las tareas previas a dicho signo, y no se continúa el flujo del programa hasta haber terminado allí todo lo necesario.
   Hay otros modos de obtener puntos de secuencia. Por ejemplo, con algunos operadores especiales (conjunción y disyunción lógica, condicional, operador coma).

   En general, una sentencia es una lista de operaciones en C, terminadas por un punto y coma.
   Las sentencias se ejecutan en orden, empezando por la primer línea en el cuerpo de la función main(), y siguiendo en orden de arriba a abajo, hasta llegar a la llave de cierre de main().
   Esto define, grosso modo, el flujo de ejecución normal del programa.
   Pero el flujo de ejecución puede alterarse, y de hecho es lo más común en programación. Es lo que le da gracia a la programación de computadoras.
   Esto se logra con las estructuras de control.

   Pasemos a estudiar cuáles son las estructuras de control en C, y qué es lo que hacen:
   
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Bifurcación condicional:

Es la estructura de control if(), la cual tiene dos formas posibles:
   
1ra. forma:

    if (condición) {
        // lista de sentencias...
    }
   
    // Continuación del flujo normal del programa

   Se evalúa una expresión que aparece donde dice: condición, verificando si se trata de un valor verdadero (esto en C equivale a decir: no nulo). En caso afirmativo, se ejecutan todas las tareas que están entre el par de llaves a continuación del if().
   En caso de que la condición sea falsa (o sea, la expresión condición tiene un valor nulo), no se hace nada, y se continúa con el flujo normal del programa, inmediatamente debajo de la llave que cierra el bloque if().

2da. forma:

    if(condición) {
        // lista de sentencias...
    } else {
        // lista de sentencias...
    }
   
    // Continuación del flujo normal del programa

   Si la condición es verdadero, se ejecutan todas las tareas que están entre el par de llaves a continuación del if().
   En caso de que la condición sea falsa, se descarta el bloque de sentencias que está a continuación de if(), y en cambio se ejecuta el bloque de sentencias entre las llaves que siguen a la palabra clave else.

   En el caso de que alguno de los bloques de sentencias anteriores contengan una sola línea terminada con punto y coma, las llaves pueden omitirse. Esto es una práctica poco recomendada. Aún así, ustedes verán que muchas veces omito las llaves, si puedo, porque me resulta más amigable escribir código sin llaves, mientras sea posible.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Bifurcación condicional múltiple:

   Es la estructura de control switch(). La sintaxis es la siguiente:

    switch(expresión entera) {
        case valor1:
               // sentencias para el caso valor1
        case valor2:
               // sentencias para el caso valor2
        // ... ... ... ...
        case valorN:
               // sentencias para el caso valorN
        default:
               // sentencias para el caso "por defecto"               
    }

   Se evalúa la expresión, que ha de tener como resultado un valor de tipo entero. No se acepta otra cosa.
   Se contrasta la expresión con las etiquetas case, a ver cuál de los valores valor1, valor2, ..., valorN coincide con el de la expresión.
   Luego se ejecutan todas las sentencias que están a continuación de dicha etiqueta. Inclusive, se ejecutan las sentencias de las etiquetas case que siguen, y también las de la etiqueta default.
   Lo típico es que queramos evitar esto, pues nos parecerá más claro realizar una sola lista de tareas concretas para cada caso.
   Para lograr esto, tras la lista de sentencias asociadas a un caso dado, podemos agregar la palabra clave break, que tiene el efecto de interrumpir la seguidilla se sentencias en ese punto, dando por terminado el bloque switch().
   Esto quedaría más o menos así:

    switch(expresión entera) {
        case valor1:
               // sentencias para el caso valor1
               break;
        case valor2:
               // sentencias para el caso valor2
               break;
        // ... ... ... ...
        case valorN:
               // sentencias para el caso valorN
               break;
        default:
               // sentencias para el caso "por defecto"               
    }

   Notar que no hace falta poner un break al final del bloque switch, porque allí ya termina dicho bloque.

   Tras finalizar el bloque switch, el programa continúa con su flujo normal de sentencias.

   Nota adicional: la etiqueta default no necesariamente tiene que ser la última, sino que puede aparecer en cualquier otro lugar.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Repetición precondicional:

   Es la estructura while().
   La sintaxis es la siguiente:

    while (condición) {
        // lista de sentencias...
    }
   
    // Continuación del flujo normal del programa

   Se evalúa la condición, y si es verdadera (en el mismo sentido que para if()), se ejecuta la lista de sentencias que está entre llaves, a continuación de while().
   Tras terminar esto, el flujo del programa vuelve otra vez "arriba", a la línea del while(), y vuelve a evaluar la condición, y de nuevo ejecuta las sentencias del bloque si la condición es verdadera.
   Este proceso se repite hasta que la condición se vuelve falsa.
   Luego continúa con el flujo normal del programa, debajo del bloque de sentencias de while().

   Si el bloque de sentencias contiene una sola línea terminada en punto y coma, entonces las llaves del bloque pueden omitirse.
   Diría que hacer esto es poco recomendable. Aún así, ustedes verán que a menudo intento omitir las llaves, porque me incomodan.

   Le he llamado precondicional porque antes de entrar al bloque de sentencias, sí o sí se evalúa una determinada condición.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Repetición poscondicional:

   Es la estructura while().
   La sintaxis es la siguiente:

    do {
        // lista de sentencias...
    } while (condición);
   
    // Continuación del flujo normal del programa

   Se ejecuta el bloque de instrucciones que está entre llaves, a continuación de la palabra clave do, y luego se evalúa la condición. Si es verdadera (en el mismo sentido que para if()), el flujo del programa vuelve "arriba", a la línea donde está la palabra do, y repite el mismo procedimiento.
   Esto se repite hasta que la condición se vuelva falsa.
   Luego continúa con el flujo normal del programa, debajo del bloque de sentencias de do while();.

   Notar que ahora la sintaxis de la construcción do while() obliga a poner un punto y coma al final de la línea que dice while().

   Si el bloque de sentencias contiene una sola línea terminada con punto y coma, está permitido omitir las llaves.
   Para la construcción do while yo nunca omito las llaves, porque considero que la lectura del programa se vuelve demasiado confusa (se puede confundir con la repetición precondicional).

   Le he llamado poscondicional porque primero se ejecuta el bloque de sentencias al menos una vez, siempre, y después se evalúa la condición.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Repetición condicional generalizada:

   Es la estructura for( ; ; ).
   La sintaxis es la siguiente:

    for (inicialización; condición; post-expresión) {
        // lista de sentencias...
    }
   
    // Continuación del flujo normal del programa

   En la estructura for( ; ; ) se ven claramente 3 secciones separadas por punto y coma: inicialización, condición y post-expresión.
   La 1er. acción que se realiza es ejecutar las expresiones que aparecen en la sección de inicialización. Esto se hace por única vez.

   La siguiente acción es evaluar la condición. Si es verdadera, se ejecuta la lista de sentencias que está entre llaves, a continuación de for( ; ; ).
   Tras terminar esto, se ejecutan las expresiones que aparecen en la sección de post-expresión.
   Luego se vuelve a repetir la secuencia de pasos: "evaluar condición", "ejecutar sentencias del bloque", "evaluar post-expresión".
   Esto continúa hasta que la condición es falsa.
   Luego continúa el flujo normal del programa, debajo del bloque de sentencias de for( ; ; ).

   Si el bloque de sentencias contiene una sola línea terminada en punto y coma, entonces las llaves del bloque pueden omitirse.
   Diría que hacer esto es poco recomendable. Aún así, ustedes verán que a menudo intento omitir las llaves, porque me incomodan.

   La estructura for( ; ; ) empaqueta todas las acciones que comunmente se llevan a cabo en un while(): utilizar una variable para controlar el bucle, inicializar la/las variables de control antes de iniciar la secuencia de repeticiones, evaluar la condición de control, ejecutar las sentencias del bloque, al final de dichas sentencias modificar el valor de las variables de control.

   En la sección de inicialización también es posible declarar una variable, que se usará para controlar el for( ; ; ), pero que sólo será "visible" en el ámbito de la estructura de ese for( ; ; ) en concreto. Ejemplo:

     for (int j = 0; j < 10; j++)
         printf("Pin %d\n", j);


   La estructura

    for (init; cond; expr) {
        // sentencias
    } 

es equivalente a la construcción:

    init;
    while (cond) {
        // sentencias
       
        expr;
    } 


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Saltos e interrupciones:

   A las estructuras repetitivas while(), do while(); y for( ; ; ) las llamaremos también bucles o ciclos.
   Cada ciclo de repeticiones se llama una iteración.

   Un bucle puede necesitar que se pase por alto una porción de la lista de sentencias del bloque que tiene asociado.
   Para ello se usa la sentencia:

continue;

   Indica obviar todo lo que resta, y continuar con la siguiente iteración.
   En un bucle while() el efecto es obviar el resto de sentencias del bloque, e ir hasta el punto en que se evalúa nuevamente la condición.
   En un bucle do while() ocurre lo mismo.
   En un bucle for( ; ; ) el efecto es obviar el resto de sentencias del bloque, e ir hasta el punto en que se ejecuta la sección post-expresión, continuando las iteraciones normalmente a partir de allí.

   Un bucle puede necesitar ser interrumpido abruptamente.
   Esto se logra con la sentencia:

break;

   En tal caso, el control del flujo del programa se pasa directamente a la siguiente sentencia debajo del bloque de sentencias del bucle (o sea, el bucle se da por terminado).

   Cuando hay varios bucles anidados, las sentencias continue y break actúan en relación al bucle más cercano al que pertenecen.
   Así, en el siguiente ejemplo, la seguidilla de asignaciones es como se muestra abajo:

    for (int x = 0; x < 4; x++)
       for (int y = 0; y < 1000; y++)
           if (x == y)
              break;

Se obtiene:

x = 0, y = 0,
x = 1, y = 0, y = 1,
x = 2, y = 0, y = 1, y = 2,
x = 3, y = 0, y = 1, y = 2, y = 3,
x = 4
(fin de ambos ciclos)


   En el caso de las funciones (tema que veremos más adelante), pueden terminarse en cualquier parte mediante una sentencia return, la cual devuelve el control al punto del programa que hizo la llamada a la función.
   El valor de retorno de la función se comunica además en la misma sentencia return.

return valor_de_retorno;

   Las funciones que no devuelven valores pueden retornar sencillamente mediante:

return;


   Nosotros conocemos el caso de la función main(), la cual estamos obligados a usarla en todos nuestros programas.
   La función main() retorna un valor de tipo int.
   Este valor ya no podemos manejarlo desde dentro de nuestro programa, sino que es capturado por el sistema operativo.
   En el caso de DOS/Windows, el valor de retorno queda alojado en la variable de sistema ERRORLEVEL. Para visualizar dicho valor, debemos ejecutar la instrucción de línea de comandos:

ECHO %ERRORLEVEL%


Así, un programita como éste:

int main(void) {
   return 1234;
}

retorna el valor 1234, que puede visualizarse mediante:

ECHO %ERRORLEVEL%

en línea de comandos. En efecto, nos mostrará esto:

1234


   Sólo la función main() tiene el efecto de "retornar" al sistema operativo.
   Si queremos retornar al sistema desde cualquier punto de un programa en C, debemos usar la función exit() de la librería <stdlib.h>.
   Entre paréntesis debemos pasarle un valor de tipo int que indicará un código de error que podrá ser capturado por el sistema operativo. (Equivale a "retornarle" un valor al sistema).
   Comunmente se usa el valor 0 para indicar ausencia de errores al terminar un programa.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Salto incondicional:

   Se puede saltar a cualquier punto (dentro del bloque delimitada por una función) mediante una sentencia goto.
   Para esto, hay que declarar etiquetas, que son identificadores seguidas del signo dos puntos ( : ).
   Luego, si escribimos por ejemplo:

jump2me:
   
    // sentencias
    // más sentencias
   
goto jump2me;

    // otras sentencias

significará que el flujo del programa salta a la sentencia que sigue inmediatamente al punto marcado con la etiqueta jump2me:

   El goto puede estar arriba o debajo del punto de salto.

   Notemos que no hay una sintaxis especializada para las etiquetas. Ellas se ponen "por ahí", y punto. El signo de dos puntos al final del identificador es suficiente indicación para saber que allí se ha declarado una etiqueta.
   Es posible que haya etiquetas a las cuales no salte ningún goto. No está prohibido.

   En general los libros recomiendan religiosamente evitar completamente el uso de goto.
   Yo diría que, como con cualquier herramienta de programación, lo que hay que evitar es el mal uso de la misma.
   O sea, basta evitar el mal uso de goto.
   En algunos casos es la mejor solución, por ejemplo, cuando queremos "escapar" desde lo profundo de unos cuantos ciclos anidados, para los cuales un solo break no hace ni mella.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

   Hemos terminado este importante tópico.

   Gracias a los temas desarrollados en esta sección, y las precedentes, podremos echar a andar una gran variedad de programas que de verdad resuelvan "algo".



CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas


05 Octubre, 2013, 10:12 am
Respuesta #48

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,274
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
48. Problemas (I) - Aritmética básica (convenciones previas)

Convenciones elementales de trabajo.

   Para los problemas que siguen nos restringiremos a unos pocos tipos aritméticos:
   
\( \bullet \)  _Bool para valores lógicos (0 ó 1).
\( \bullet \)  char para caracteres o enteros pequeños (de 1 byte). En el caso de enteros pequeños, nos restringiremos a los valores 0...127.
\( \bullet \)  int para números estrictamente enteros. Usaremos sólo los valores en el rango -32767...32767.
\( \bullet \)  double para números reales de punto flotante.
         \( \bullet \) Usaremos sólo el rango de valores cuyo valor absoluto esté entre \( 10^{-37} \) y \( 10^{+37} \).
         \( \bullet \) Confiaremos en que dos valores \( x,y \), con \( |x|>|y| \), son claramente distintos sólo cuando \( 2^{-e_x}|x-y|\geq 10^{-9} \), donde \( e_x \) es el exponente del número punto flotante \( x \).
         \( \bullet \) Los efectos del redondeo sólo pueden estimarse "por arriba", porque en realidad puede que haya más precisión que la que estamos suponiendo.
         \( \bullet \) No asumiremos nada acerca del método de redondeo (hacia par, hacia impar, hacia 0, hacia infinito, etc.). Sólo nos contentaremos con estimar el error por redondeo, el cual depende del valor absoluto de la diferencia entre los valores exactos y aproximados.
         \( \bullet \) En el resultado de toda operación, consideraremos significativos los primeros 10 dígitos de la mantisa. Más precisamente, si consideremos un número en decimal, lo pasamos a binario, lo redondeamos en binario, lo volvemos a pasar a decimal, lo redondeamos en decimal a 10 dígitos de precisión en la mantisa, obtendremos el valor original.
         \( \bullet \) En cuanto a ceros negativos, no diremos nada, si hay o no hay.
\( \bullet \)  double _Complex para trabajar con números complejos de punto flotante. En memoria se almacenan exactamente como dos objetos contiguos de tipo double.
\( \bullet \)  No invocaremos el tipo double _Imaginary (o double imaginary).

   Las palabras clave _Bool y _Complex son feas.

   Cuando necesitemos trabajar con valores booleanos, utilizaremos siempre la librería <stdbool.h>, que permite escribir bool en vez de _Bool, y los valores 0 y 1 se pueden expresar de modo más significativo mediante las palabras false y true.
   Cuando necesitemos trabajar con valores complejos, utilizaremos siempre la librería <complex.h>, que permite escribir complex en vez de _Complex.
   De paso usaremos siempre la macro I para indicar la unidad imaginaria \( \color{blue}i \) (recordemos que su tipo es float complex, que es compatible sin problema alguno con double complex).

   Para exhibir resultados, usaremos instrucciones printf() sencillas (y por lo tanto incluiremos la librería <stdio.h>), así:
   
\( \bullet \)  Para exhibir un valor n de tipo int:

                    printf("%d", n);

\( \bullet \)  Para exhibir un valor x de tipo double:

                    printf("%g", x);

\( \bullet \)  Para exhibir un valor z de tipo double complex:

                    printf("%g + %g i", creal(z), cimag(z));


   En el último caso, para obtener las partes real e imaginaria de z se han usado las funciones creal() y cimag() de la librería <complex.h>, que sirven justamente para eso. Los valores que arrojan son reales de tipo double, encajando así con nuestro esquema de trabajo.

   En ocasiones querremos pedir al usuario que introduzca valores por teclado, y hacer luego cálculos con esos valores. Esto lo haremos con la función scanf() de la librería <stdio.h>.
   Para leer desde el teclado valores numéricos, usaremos estas especificaciones:
   
\( \bullet \)  Para ingresar un valor de tipo int y guardarlo en la variable n, previamente declarada de tipo int:

                    int n;
                    scanf("%d", &n);

\( \bullet \)  Para ingresar un valor de tipo double y guardarlo en la variable x, previamente declarada de tipo double:

                    double x;
                    scanf("%lg", &x);

\( \bullet \)  Para ingresar un valor de tipo complex y guardarlo en la variable z, previamente declarada de tipo complex, vamos a usar unas variables auxiliares x,y de tipo double para alojar allí las partes real e imaginaria de z, y luego las "cargaremos" con datos mediante scanf(), así:

                    double x;
                    double y;
                    double complex z;
                    scanf("%lg + %lg i", &x, &y);
                    z = x + y * I;


   Poner siempre el signo & antes de las variables en las sentencias scanf().

   El operador & obtiene la dirección en memoria de una variable. No lo estudiaremos aquí.

   Observamos que para la carga de valores complejos de punto flotante el procedimiento se complica un poco, debido a que no hay una directiva concreta que permita ingresar complejos, sino sólo reales de punto flotante. Esto obliga a que pidamos al usuario el ingreso de dos valores reales consecutivos (de tipo double).
   Además, la cadena de formato utilizada: "%lg + i %lg" obliga al usuario a escribir los números complejos respetando la notación estándar \( a + i b \), dejando además algunos espacios de separación, para mayor claridad.

   Notemos que para especificar valores de tipo double, basta poner "%g" en printf(), pero hay que poner "%lg" en scanf(). Esto es así... porque sí.
   Si hay temor de confusión, es válido también poner "%lg" en printf(), lo cual funcionará exactamente igual que "%g" (se ignora el modificador "l").

   En resumen: Para leer o escribir datos de tipo double, ante la duda usar siempre "%lg".

   Los valores de punto flotante serán exhibidos con una precisión de 6 dígitos decimales. Por ahora dejaremos esto así.

CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas

06 Octubre, 2013, 12:31 am
Respuesta #49

argentinator

  • Consultar la FIRMAPEDIA
  • Administrador
  • Mensajes: 7,274
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
  • Vean mis posts activos en mi página personal
    • Mis posts activos (click aquí)
49. Problemas (II) - Aritmética básica

Instrucciones generales:
   
      En todos los problemas que siguen, realizar una 2da versión que cuente las operaciones y comparaciones realizadas en el programa.

\( \bullet \)  49.1. Pedir al usuario que ingrese una base \( b \). Luego, pedir al usuario que ingrese números reales (en decimal), y convertirlos a base \( b \) a medida que el usuario los va ingresando. Continuar hasta que el usuario ingrese 0 o un valor fuera del rango que hemos convenido adoptar para el tipo double.

http://rinconmatematico.com/foros/index.php?topic=70570.msg280182#msg280182


\( \bullet \)  49.2. Dados dos enteros, hallar el máximo común divisor. Repetir hasta que el usuario ingrese un par de 0's.



\( \bullet \)  49.3. Hallar el factorial de números enteros. Guardar los resultados en variables de tipo double. Permitir sólo enteros cuyo factorial es menor que \( 10^{37} \). Repetir hasta que el usuario ingresa un entero negativo.

[url=http://]]]


\( \bullet \)  49.3. Hallar el factorial de números enteros. Guardar los resultados en variables de tipo double. Permitir sólo enteros cuyo factorial es menor que \( 10^{37} \). Repetir hasta que el usuario ingresa un entero negativo.




\( \bullet \)  49.4. Determinar si un número entero es primo. Repetir hasta que el usuario ingresa 0.

[url=http://]



\( \bullet \)  49.4. Determinar si un número entero es primo. Repetir hasta que el usuario ingresa 0.




\( \bullet \)  49.5. Calcular eficientemente la parte entera del logaritmo en base 2 de un entero.

[url=http://]]]



\( \bullet \)  49.5. Calcular eficientemente la parte entera del logaritmo en base 2 de un entero.




\( \bullet \)  49.6. Dada una fracción, convertirla a formato decimal (periódico si es necesario), y viceversa. Hacer lo mismo para otras bases distintas de diez.

[url=http://]



\( \bullet \)  49.6. Dada una fracción, convertirla a formato decimal (periódico si es necesario), y viceversa. Hacer lo mismo para otras bases distintas de diez.




\( \bullet \)  49.7. Dadas las funciones \( y=e^x, y = \sen x, y = \cos x \), hallar sus aproximaciones de Taylor de grado \( n \), y estimar el error.

[url=http://]]]



\( \bullet \)  49.7. Dadas las funciones \( y=e^x, y = \sen x, y = \cos x \), hallar sus aproximaciones de Taylor de grado \( n \), y estimar el error.




\( \bullet \)  49.8. Calcular la raíz cuadrada de un número real.

[url=http://]



\( \bullet \)  49.8. Calcular la raíz cuadrada de un número real.




\( \bullet \)  49.9. Dadas 3 longitudes, determinar si existe algún triángulo cuyos lados midan eso, y calcular los ángulos de dicho triángulo.

[url=http://]]]



\( \bullet \)  49.9. Dadas 3 longitudes, determinar si existe algún triángulo cuyos lados midan eso, y calcular los ángulos de dicho triángulo.




\( \bullet \)  49.10. Calcular el tiempo que demora en caer una pelota de tenis que pesa \( 58 \) gramos, que se suelta a determina altura \( x \) sobre el suelo, asumiendo una aceleración gravitatoria de \( 9.8 m/s^2 \).

[url=http://]



\( \bullet \)  49.10. Calcular el tiempo que demora en caer una pelota de tenis que pesa \( 58 \) gramos, que se suelta a determina altura \( x \) sobre el suelo, asumiendo una aceleración gravitatoria de \( 9.8 m/s^2 \).




\( \bullet \)  49.11. Dado un año del calendario cristiano, decir si es o no bisiesto. (Investigar en Internet las reglas de los años bisiestos).

[url=http://]]]



\( \bullet \)  49.11. Dado un año del calendario cristiano, decir si es o no bisiesto. (Investigar en Internet las reglas de los años bisiestos).




\( \bullet \)  49.12. Dado un año del calendario cristiano, generar un almanaque en pantalla para ese año.

[url=http://]



\( \bullet \)  49.12. Dado un año del calendario cristiano, generar un almanaque en pantalla para ese año.




\( \bullet \)  49.13. Pedir al usuario que ingrese los coeficientes \( r, s \), de una relación de recurrencia lineal \( x_{n+1} = rx_n+s \), así como también el valor inicial \( x_0 \) de la recurrencia. Luego, pedir repetidamente al usuario que ingrese un índice \( n \) para el cual se desea calcular el valor de \( x_n \). Calcular dicho valor, y mostrarlo en pantalla. Repetir esto hasta que el usuario ingrese un número negativo.

[url=http://]]]



\( \bullet \)  49.13. Pedir al usuario que ingrese los coeficientes \( r, s \), de una relación de recurrencia lineal \( x_{n+1} = rx_n+s \), así como también el valor inicial \( x_0 \) de la recurrencia. Luego, pedir repetidamente al usuario que ingrese un índice \( n \) para el cual se desea calcular el valor de \( x_n \). Calcular dicho valor, y mostrarlo en pantalla. Repetir esto hasta que el usuario ingrese un número negativo.




\( \bullet \)  49.14. Dada una esfera \( n \)-dimensional, con radio \( r \), calcular su volumen \( n \)-dimensional y su área \( n-1 \)-dimensional. (El usuario debiera ingresar la dimensión \( n \) y el radio \( r \), y todo se debe repetir hasta que el usuario elija una dimensión igual a 0 o negativa).

[url=http://]



\( \bullet \)  49.14. Dada una esfera \( n \)-dimensional, con radio \( r \), calcular su volumen \( n \)-dimensional y su área \( n-1 \)-dimensional. (El usuario debiera ingresar la dimensión \( n \) y el radio \( r \), y todo se debe repetir hasta que el usuario elija una dimensión igual a 0 o negativa).




\( \bullet \)  49.15. Calcular \( \zeta(s) \) (la función zeta de Riemann) para valores reales \( s>1 \). Repetir hasta que el usuario ingresa s = 0.

[url=http://]]]



\( \bullet \)  49.15. Calcular \( \zeta(s) \) (la función zeta de Riemann) para valores reales \( s>1 \). Repetir hasta que el usuario ingresa s = 0.




\( \bullet \)  49.16. Calcular el \( n \)-ésimo término de la serie armónica. (Repetir hasta que el usuario pone un valor de n = 0).

[url=http://]



\( \bullet \)  49.16. Calcular el \( n \)-ésimo término de la serie armónica. (Repetir hasta que el usuario pone un valor de n = 0).




\( \bullet \)  49.17. Generar los primeros \( n \) dígitos del número \( \pi \) en base 16.

[url=http://]]]



\( \bullet \)  49.17. Generar los primeros \( n \) dígitos del número \( \pi \) en base 16.




\( \bullet \)  49.18. Convertir una expresión en grados sexagesimales (con minutos y segundos, y fracciones de segundos) a radianes, y viceversa.

[url=http://]



\( \bullet \)  49.18. Convertir una expresión en grados sexagesimales (con minutos y segundos, y fracciones de segundos) a radianes, y viceversa.




\( \bullet \)  49.19. Resolver la ecuación \( x^x=c \), donde \( c \) es un número real positivo que ingresa el usuario. Repetir hasta que el usuario ingresa el valor 0.

[url=http://]]]



\( \bullet \)  49.19. Resolver la ecuación \( x^x=c \), donde \( c \) es un número real positivo que ingresa el usuario. Repetir hasta que el usuario ingresa el valor 0.




\( \bullet \)  49.20. Resolver la ecuación \( z^z=c \), donde \( c \) es un número complejo que ingresa el usuario. Repetir hasta que el usuario ingresa \( 0 + 0i \).

[url=http://]



\( \bullet \)  49.20. Resolver la ecuación \( z^z=c \), donde \( c \) es un número complejo que ingresa el usuario. Repetir hasta que el usuario ingresa \( 0 + 0i \).




¿Otros? :)


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

[url=http://rinconmatematico.com/foros/index.php?topic=64834.msg260247#msg260247]Organización]



¿Otros? :)


CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Organización

Comentarios y Consultas