Capítulo – 5
Introducción a la Programación en C
Introducción
'C' es uno de los lenguajes informáticos más populares en el mundo informático actual. El lenguaje de programación 'C' fue diseñado y desarrollado por Brian Kernighan y Dennis Ritchie en The Bell Research Labs en 1972.
'C' es un lenguaje creado específicamente para permitir que el programador acceda a casi todas las funciones internas de la máquina: registros, ranuras de E/S y direcciones absolutas. Al mismo tiempo, 'C' permite tanto el manejo de datos y la modularización de texto programado como sea necesario para permitir la construcción de proyectos multiprogramadores muy complejos de manera organizada y oportuna.
Aunque originalmente este lenguaje estaba destinado a ejecutarse bajo UNIX, ha habido un gran interés en ejecutarlo bajo el sistema operativo MS-DOS en PC IBM y compatibles. Es un lenguaje excelente para este entorno debido a la simplicidad de expresión, la compacidad del código y la amplia gama de aplicabilidad.
Además, debido a la simplicidad y facilidad de escribir un compilador de C, suele ser el primer lenguaje de alto nivel disponible en cualquier computadora nueva, incluidas las microcomputadoras, minicomputadoras y mainframes.
Por qué usar C en la programación de recuperación de datos
En el mundo actual de la programación informática, hay muchos lenguajes de alto nivel disponibles. Estos lenguajes son buenos con muchas características adecuadas para la mayoría de las tareas de programación. Sin embargo, hay varias razones por las que C es la primera opción de los programadores que están dispuestos a programar para la recuperación de datos, la programación de sistemas, la programación de dispositivos o la programación de hardware:
- C es un lenguaje popular preferido por los programadores profesionales. Como resultado, hay disponible una amplia variedad de compiladores de C y accesorios útiles.
- C es un lenguaje portátil. Un programa en C escrito para un sistema informático se puede compilar y ejecutar en otro sistema con poca o ninguna modificación. La portabilidad se mejora con el estándar ANSI para C, el conjunto de reglas para los compiladores de C.
- C permite un amplio uso de módulos en la programación. El código C se puede escribir en rutinas llamadas funciones. Estas funciones se pueden reutilizar en otras aplicaciones o programas. No es necesario que realice esfuerzos adicionales en la programación de una nueva aplicación para crear el mismo módulo que desarrolló anteriormente en otra programación de aplicaciones.
Puede usar esta función en un nuevo programa sin ningún cambio o con algunos cambios menores. En el caso de la programación de recuperación de datos, encontrará que esta cualidad es de gran ayuda cuando necesite ejecutar las mismas funciones varias veces en diferentes aplicaciones de diferentes programas.
- C es un lenguaje poderoso y flexible. Esta es la razón por la que C se usa para proyectos tan diversos como sistemas operativos, procesadores de texto, gráficos, hojas de cálculo e incluso compiladores para otros lenguajes.
- C es un lenguaje de pocas palabras, que contiene solo un puñado de términos, llamados palabras clave, que sirven como base sobre la cual se construye la funcionalidad del lenguaje. Estas palabras clave, también llamadas palabras reservadas, lo hacen más poderoso y le dan una amplia área de programación y hacen que un programador se sienta para hacer cualquier tipo de programación en C.
Déjame asumir que no sabes nada en C
Supongo que no sabes nada sobre programación en C y tampoco tienes idea de programación. Comenzaré con los conceptos más básicos de C y lo llevaré hasta el alto nivel de programación en C, incluidos los conceptos generalmente intimidantes de punteros, estructuras y asignación dinámica.
Para comprender completamente estos conceptos, le llevará bastante tiempo y trabajo de su parte porque no son particularmente fáciles de entender, pero son herramientas muy poderosas.
La programación en C es una gran ventaja en aquellas áreas en las que es posible que necesite usar el lenguaje ensamblador, pero preferiría mantenerlo como un programa simple de escribir y fácil de mantener. El tiempo ahorrado en la codificación de C puede ser tremendo en tales casos.
Aunque el lenguaje C disfruta de un buen historial cuando los programas se transportan de una implementación a otra, existen diferencias en los compiladores que encontrará cada vez que intente utilizar otro compilador.
La mayoría de las diferencias se hacen evidentes cuando usa extensiones no estándar, como llamadas al BIOS de DOS cuando usa MS-DOS, pero incluso estas diferencias pueden minimizarse eligiendo cuidadosamente las construcciones de programación.
Cuando se hizo evidente que el lenguaje de programación C se estaba convirtiendo en un lenguaje muy popular disponible en una amplia gama de computadoras, un grupo de personas preocupadas se reunió para proponer un conjunto estándar de reglas para el uso del lenguaje de programación C.
El grupo representaba a todos los sectores de la industria del software y después de muchas reuniones y muchos borradores preliminares, finalmente escribieron un estándar aceptable para el lenguaje C. Ha sido aceptado por el Instituto Nacional Estadounidense de Estándares (ANSI), y por la Organización Internacional de Normalización (ISO).
No se impone a ningún grupo o usuario, pero dado que es tan ampliamente aceptado, sería un suicidio económico para cualquier compilador negarse a cumplir con el estándar.
Los programas escritos en este libro son principalmente para uso en una PC IBM o una computadora compatible, pero se pueden usar con cualquier compilador estándar ANSI ya que se ajusta muy de cerca al estándar ANSI.
Comencemos
Antes de que pueda hacer cualquier cosa en cualquier idioma y comenzar a programar, debe saber cómo nombrar un identificador. Un identificador se utiliza para cualquier variable, función, definición de datos, etc. En el lenguaje de programación C, un identificador es una combinación de caracteres alfanuméricos, siendo el primero una letra del alfabeto o un subrayado, y el resto cualquier letra del alfabeto, cualquier dígito numérico o el subrayado.
Se deben tener en cuenta dos reglas al nombrar los identificadores.
- El caso de los caracteres alfabéticos es significativo. C es un lenguaje sensible a mayúsculas y minúsculas. Eso significa que Recovery es diferente de recovery y rEcOveRY es diferente de los dos mencionados anteriormente.
- De acuerdo con el estándar ANSI-C, se pueden usar al menos 31 caracteres significativos y un compilador conforme a ANSI-C los considerará significativos. Si se usan más de 31, cualquier compilador puede ignorar todos los caracteres más allá del 31.
Palabras clave
Hay 32 palabras definidas como palabras clave en C. Estas tienen usos predefinidos y no se pueden usar para ningún otro propósito en un programa C. Son utilizados por el compilador como una ayuda para compilar el programa. Siempre se escriben en minúsculas. Una lista completa sigue:
auto |
break |
case |
char |
const |
continue |
default |
do |
double |
else |
enum |
extern |
float |
for |
goto |
if |
int |
long |
register |
return |
short |
signed |
sizeof |
static |
struct |
switch |
typedef |
union |
unsigned |
void |
volatile |
while |
Aquí vemos la magia de C. La maravillosa colección de solo 32 palabras clave le da un amplio uso en diferentes aplicaciones. Cualquier programa de computadora tiene dos entidades a considerar, los datos y el programa. Son altamente dependientes uno del otro y la planificación cuidadosa de ambos conduce a un programa bien planificado y bien escrito.
Empecemos con un programa simple en C:
/* Primer programa para aprender C */
#include <stdio.h>
void main()
{
printf("This is a C program\n"); // printing a message
}
Aunque el programa es muy simple, algunos puntos son dignos de mención. Examinemos el programa anterior. Todo lo que está dentro de /* y */ se considera un comentario y el compilador lo ignorará. No debe incluir comentarios dentro de otros comentarios, por lo que algo como esto no está permitido:
/* esto es un /* comentario */ dentro de un comentario, lo cual es incorrecto */
También hay una forma de documentación que funciona dentro de una línea. Al usar // podemos agregar una pequeña documentación dentro de esa línea.
Cada programa C contiene una función llamada main. Este es el punto de partida del programa. Cada función debe devolver un valor. En este programa, la función main no devuelve ningún valor de retorno, por lo que hemos escrito void main. También podríamos escribir este programa como:
/* Primer programa para aprender C */
#include <stdio.h>
main()
{
printf("This is a C program\n"); // printing a message
return 0;
}
Ambos programas son iguales y realizan la misma tarea. El resultado de ambos el programa imprimirá el siguiente resultado en la pantalla:
Este es un programa en C
#include<stdio.h> permite que el programa interactúe con la pantalla, el teclado y el sistema de archivos de su computadora. Lo encontrará al comienzo de casi todos los programas en C.
main() declara el inicio de la función, mientras que los dos corchetes muestran el inicio y el final de la función. Los corchetes en C se usan para agrupar declaraciones como en una función o en el cuerpo de un ciclo. Tal agrupación se conoce como declaración compuesta o bloque.
printf("This is a C program\n"); imprime las palabras en la pantalla. El texto a imprimir se encierra entre comillas dobles. El \n al final del texto le dice al programa que imprima una nueva línea como parte de la salida. La función printf() se usa para la visualización del monitor de la salida.
La mayoría de los programas en C están en minúsculas. Por lo general, encontrará letras mayúsculas utilizadas en las definiciones del preprocesador que se discutirán más adelante, o dentro de comillas como partes de cadenas de caracteres.
Compilando el programa
Dejemos que el nombre de nuestro programa sea CPROG.C. Para ingresar y compilar el programa C, siga estos pasos:
- Cree el directorio activo de sus programas en C e inicie su editor. Para esto, se puede usar cualquier editor de texto, pero la mayoría de los compiladores de C, como Turbo C++ de Borland, tienen un entorno de desarrollo integrado (IDE) que le permite ingresar, compilar y vincular sus programas en una configuración conveniente.
- Escriba y guarde el código fuente. Debe nombrar el archivo CPROG.C.
- Compile y vincule CPROG.C. Ejecute el comando apropiado especificado por los manuales de su compilador. Debería recibir un mensaje que indique que no hubo errores ni advertencias.
- Verifique los mensajes del compilador. Si no recibe errores ni advertencias, todo debería estar bien. Si hay algún error al escribir el programa, el compilador lo detectará y mostrará un mensaje de error. Corrija el error que se muestra en el mensaje de error.
- Su primer programa en C ahora debería estar compilado y listo para ejecutarse. Si muestra una lista de directorios de todos los archivos llamados CPROG, obtendrá los cuatro archivos con diferente extensión que se describen a continuación:
- CPROG.C, el archivo de código fuente
- CPROG.BAK, el archivo de copia de seguridad del archivo fuente que creó con el editor
- CPROG.OBJ, contiene el código objeto para CPROG.C
- CPROG.EXE, el programa ejecutable creado cuando compilaste y vinculaste CPROG.C
- Para ejecutar, o ejecutar, CPROG.EXE, simplemente ingrese cprog. El mensaje This is a C program se muestra en la pantalla.
Ahora examinemos el siguiente programa:
/* Primer programa para aprender C*/ // 1
// 2
#include <stdio.h> // 3
// 4
main() // 5
{
// 6
printf("This is a C program\n"); // 7
// 8
return 0; // 9
} // 10
Cuando compila este programa, el compilador muestra un mensaje similar al siguiente:
cprog.c(8): Error: ';' esperado
dividamos este mensaje de error en partes. cprog.c es el nombre del archivo donde se encontró el error. (8) es el número de línea donde se encontró el error. Error: ';' lo esperado es una descripción del error.
Este mensaje es bastante informativo y te dice que en la línea 8 de CPROG.C el compilador esperaba encontrar un punto y coma pero no lo hizo. Sin embargo, sabe que el punto y coma en realidad se omitió en la línea 7, por lo que existe una discrepancia.
¿Por qué el compilador informa de un error en la línea 8 cuando, de hecho, se omitió un punto y coma en la línea 7? La respuesta radica en el hecho de que a C no le importan cosas como los saltos entre líneas. El punto y coma que pertenece después de la instrucción printf() podría haberse colocado en la línea siguiente, aunque hacerlo sería una mala programación en la práctica.
Solo después de encontrar el siguiente comando (retorno) en la línea 8, el compilador está seguro de que falta el punto y coma. Por lo tanto, el compilador informa que el error está en la línea 8.
Puede haber varias posibilidades de diferentes tipos de errores. Analicemos la vinculación de mensajes de error. Los errores del enlazador son relativamente raros y, por lo general, resultan de escribir mal el nombre de una función de biblioteca de C. En este caso, obtiene un error: símbolos no definidos: mensaje de error, seguido del nombre mal escrito. Una vez que corrija la ortografía, el problema debería desaparecer.
Números de impresión
Veamos el siguiente ejemplo:
// Cómo imprimir los números //
#include<stdio.h>
void main()
{
int num = 10;
printf(“ The Number Is %d”, num);
}
La salida del programa se mostrará en la pantalla de la siguiente manera:
El Número Es 10
El signo % se usa para señalar la salida de muchos tipos diferentes de variables. El carácter que sigue al signo % es una d, que le indica a la rutina de salida que obtenga un valor decimal y lo envíe.
Uso de variables
En C, una variable debe declararse antes de que pueda usarse. Las variables se pueden declarar al comienzo de cualquier bloque de código, pero la mayoría se encuentran al comienzo de cada función. La mayoría de las variables locales se crean cuando se llama a la función y se destruyen al regresar de esa función.
Para usar variables en sus programas C, debe conocer las siguientes reglas al dar el nombre a las variables en C:
- El nombre puede contener letras, dígitos y el carácter de subrayado (_).
- El primer carácter del nombre debe ser una letra. El guión bajo también es un primer carácter válido, pero no se recomienda su uso.
- C distingue entre mayúsculas y minúsculas, por lo tanto, el nombre de la variable num es diferente de Num.
- Las palabras clave de C no se pueden usar como nombres de variables. Una palabra clave es una palabra que forma parte del lenguaje C.
La siguiente lista contiene algunos ejemplos de nombres de variables C legales e ilegales:
Nombre de variable |
Legal o no |
Num |
Legal |
Ttpt2_t2p |
Legal |
Tt pt |
Illegal: Space is not allowed |
_1990_tax |
Legal but not advised |
Jack_phone# |
Illegal: Contains the illegal character # |
Case |
Illegal: Is a C keyword |
1book |
Illegal: First character is a digit |
Lo primero que destaca es la primera línea del cuerpo de main():
int número = 10;
Esta línea define una variable llamada 'num' de tipo int y la inicializa con el valor 10. Esto también podría haberse escrito como:
int num; /* define uninitialized variable 'num' */
/* and after all variable definitions: */
num = 10; /* assigns value 10 to variable 'num' */
Las variables se pueden definir al comienzo de un bloque (entre las llaves {y}), por lo general esto es al comienzo del cuerpo de una función, pero también puede estar al comienzo de otro tipo de bloque.
Las variables que se definen al principio de un bloque tienen por defecto el estado 'automático'. Esto significa que solo existen durante la ejecución del bloque. Cuando comience la ejecución de la función, se crearán las variables pero su contenido no estará definido. Cuando la función regrese, las variables serán destruidas. La definición también se podría haber escrito como:
auto int num = 10;
Dado que la definición con o sin la palabra clave auto es completamente equivalente, la palabra clave auto obviamente es bastante redundante.
Sin embargo, a veces esto no es lo que quieres. Suponga que desea que una función lleve la cuenta de cuántas veces se llama. Si la variable se destruyera cada vez que regresa la función, esto no sería posible.
Por lo tanto, es posible darle a la variable lo que se llama duración estática, lo que significa que permanecerá intacta durante toda la ejecución del programa. Por ejemplo:
número entero estático = 10;
Esto inicializa la variable num a 10 al comienzo de la ejecución del programa. A partir de ese momento, el valor permanecerá intacto; la variable no se reinicializará si la función se llama varias veces.
A veces no es suficiente que la variable sea accesible desde una sola función o puede que no sea conveniente pasar el valor a través de un parámetro a todas las demás funciones que lo necesitan.
Pero si necesita acceder a la variable desde todas las funciones en el archivo fuente completo, también puede hacerlo con la palabra clave estática, pero colocando la definición fuera de todas las funciones. Por ejemplo:
#include <stdio.h>
static int num = 10; /* will be accessible from entire source file */
int main(void)
{
printf("The Number Is: %d\n", num);
return 0;
}
Y también hay casos en los que una variable debe ser accesible desde todo el programa, que puede constar de varios archivos fuente. Esto se denomina variable global y debe evitarse cuando no se requiere.
Esto también se hace colocando la definición fuera de todas las funciones, pero sin usar la palabra clave estática:
#include <stdio.h>
int num = 10; /* will be accessible from entire program! */
int main(void)
{
printf("The Number Is: %d\n", num);
return 0;
}
También existe la palabra clave extern, que se utiliza para acceder a variables globales en otros módulos. También hay algunos calificadores que puede agregar a las definiciones de variables. El más importante de ellos es const. Una variable que se define como const no se puede modificar.
Hay dos modificadores más que se usan con menos frecuencia. El modificador volátil y de registro. El modificador volátil requiere que el compilador acceda realmente a la variable cada vez que se lee. Puede que no optimice la variable colocándola en un registro más o menos. Esto se utiliza principalmente para subprocesos múltiples y procesamiento de interrupciones, etc.
El modificador de registro solicita al compilador que optimice la variable en un registro. Esto solo es posible con variables automáticas y, en muchos casos, el compilador puede seleccionar mejor las variables para optimizar en registros, por lo que esta palabra clave está obsoleta. La única consecuencia directa de hacer un registro variable es que no se puede tomar su dirección.
La tabla de variables que se muestra en la página siguiente describe la clase de almacenamiento de cinco tipos de clases de almacenamiento.
En la tabla vemos que la palabra clave extern se coloca en dos filas. La palabra clave extern se usa en funciones para declarar una variable externa estática que se define en otro lugar.
Tipos de variables numéricas
C proporciona varios tipos diferentes de variables numéricas porque los diferentes valores numéricos tienen diferentes requisitos de almacenamiento de memoria. Estos tipos numéricos difieren en la facilidad con la que se pueden realizar ciertas operaciones matemáticas sobre ellos.
Los números enteros pequeños requieren menos memoria para almacenarse y su computadora puede realizar operaciones matemáticas con tales números muy rápidamente. Los números enteros grandes y los valores de coma flotante requieren más espacio de almacenamiento y más tiempo para las operaciones matemáticas. Al usar los tipos de variables apropiados, se asegura de que su programa se ejecute de la manera más eficiente posible.
Las variables numéricas de C se dividen en las siguientes dos categorías principales:
- Variables enteras
- Variables de punto flotante
Dentro de cada una de estas categorías hay dos o más tipos de variables específicas. La tabla que se muestra a continuación muestra la cantidad de memoria, en bytes, necesaria para contener una única variable de cada tipo.
El tipo char puede ser equivalente a char con signo o sin firmar, pero siempre es un tipo separado de cualquiera de estos.
En C no hay diferencia entre almacenar caracteres o sus valores numéricos correspondientes en una variable, por lo que tampoco es necesaria una función para convertir entre un carácter y su valor numérico o viceversa. Para los otros tipos de enteros, si omite firmado o sin firmar, el valor predeterminado será firmado, por ejemplo. int y sign int son equivalentes.
El tipo int debe ser mayor o igual que el tipo short y menor o igual que el tipo long. Si simplemente necesita almacenar algunos valores que no son demasiado grandes, a menudo es una buena idea usar el tipo int; por lo general, es el tamaño con el que el procesador puede manejar el más fácil y, por lo tanto, el más rápido.
Con varios compiladores double y long double son equivalentes. Eso, combinado con el hecho de que la mayoría de las funciones matemáticas estándar funcionan con el tipo doble, es una buena razón para usar siempre el tipo doble si tiene que trabajar con números fraccionarios.
La siguiente tabla es para describir mejor los tipos de variables:
Tipos de propósito especial comúnmente utilizados:
Tipo de variable |
Descripción |
size_t |
tipo sin firmar utilizado para almacenar los tamaños de objetos en bytes |
time_t |
se utiliza para almacenar los resultados de la función time() |
clock_t |
se utiliza para almacenar los resultados de la función clock() |
FILE |
se utiliza para acceder a una secuencia (generalmente un archivo o dispositivo) |
ptrdiff_t |
tipo firmado de la diferencia entre 2 punteros |
div_t |
utilizado para almacenar resultados de la función div() |
ldiv_t |
utilizado para almacenar los resultados de la función ldiv() |
fpos_t |
se utiliza para mantener la información de posición del archivo |
va_list |
utilizado en el manejo de argumentos variables |
wchar_t |
tipo de carácter ancho (usado para juegos de caracteres extendidos) |
sig_atomic_t |
utilizado en los manejadores de señales |
Jmp_buf |
utilizado para saltos no locales |
Para comprender mejor estas variables, tomemos un ejemplo:
/* Programa para indicar el rango y tamaño en bytes de la variable C */
#include <stdio.h>
int main()
{
int a; /* simple integer type */
long int b; /* long integer type */
short int c; /* short integer type */
unsigned int d; /* unsigned integer type */
char e; /* character type */
float f; /* floating point type */
double g; /* double precision floating point */
a = 1023;
b = 2222;
c = 123;
d = 1234;
e = 'X';
f = 3.14159;
g = 3.1415926535898;
printf( "\nA char is %d bytes", sizeof( char ));
printf( "\nAn int is %d bytes", sizeof( int ));
printf( "\nA short is %d bytes", sizeof( short ));
printf( "\nA long is %d bytes", sizeof( long ));
printf( "\nAn unsigned char is %d bytes",
sizeof( unsigned char ));
printf( "\nAn unsigned int is %d bytes",
sizeof( unsigned int ));
printf( "\nAn unsigned short is %d bytes",
sizeof( unsigned short ));
printf( "\nAn unsigned long is %d bytes",
sizeof( unsigned long ));
printf( "\nA float is %d bytes", sizeof( float ));
printf( "\nA double is %d bytes\n", sizeof( double ));
printf("a = %d\n", a); /* decimal output */
printf("a = %o\n", a); /* octal output */
printf("a = %x\n", a); /* hexadecimal output */
printf("b = %ld\n", b); /* decimal long output */
printf("c = %d\n", c); /* decimal short output */
printf("d = %u\n", d); /* unsigned output */
printf("e = %c\n", e); /* character output */
printf("f = %f\n", f); /* floating output */
printf("g = %f\n", g); /* double float output */
printf("\n");
printf("a = %d\n", a); /* simple int output */
printf("a = %7d\n", a); /* use a field width of 7 */
printf("a = %-7d\n", a); /* left justify in
field of 7 */
c = 5;
d = 8;
printf("a = %*d\n", c, a); /* use a field width of 5*/
printf("a = %*d\n", d, a); /* use a field width of 8 */
printf("\n");
printf("f = %f\n", f); /* simple float output */
printf("f = %12f\n", f); /* use field width of 12 */
printf("f = %12.3f\n", f); /* use 3 decimal places */
printf("f = %12.5f\n", f); /* use 5 decimal places */
printf("f = %-12.5f\n", f); /* left justify in field */
return 0;
}
El resultado del programa después de la ejecución se mostrará como:
A char is 1 bytes
An int is 2 bytes
A short is 2 bytes
A long is 4 bytes
An unsigned char is 1 bytes
An unsigned int is 2 bytes
An unsigned short is 2 bytes
An unsigned long is 4 bytes
A float is 4 bytes
A double is 8 bytes
a = 1023
a = 1777
a = 3ff
b = 2222
c = 123
d = 1234
e = X
f = 3.141590
g = 3.141593
a = 1023
a = 1023
a = 1023
a = 1023
a = 1023
f = 3.141590
f = 3.141590
f = 3.142
f = 3.14159
f = 3.14159 |
Antes de su uso, una variable en un programa C, debe declararse. Una declaración de variable le dice al compilador el nombre y tipo de una variable y opcionalmente inicializa la variable a un valor específico.
Si su programa intenta usar una variable que no ha sido declarada, el compilador genera un mensaje de error. Una declaración de variable tiene la siguiente forma:
nombre de tipo nombrevar;
typename especifica el tipo de variable y debe ser una de las palabras clave. varname es el nombre de la variable. Puede declarar varias variables del mismo tipo en una línea separando los nombres de las variables con comas:
int count, number, start; /* three integer variables */
float percent, total; /* two float variables */
La palabra clave typedef
La palabra clave typedef se usa para crear un nuevo nombre para un tipo de datos existente. En efecto, typedef crea un sinónimo. Por ejemplo, la declaración
typedef int entero;
aquí vemos que typedef crea un entero como sinónimo de int. Luego puede usar enteros para definir variables de tipo int, como en este ejemplo:
número de enteros;
Así que typedef no crea un nuevo tipo de datos, solo le permite usar un nombre diferente para un tipo de datos predefinido.
Inicializar variables numéricas
Cuando se declara cualquier variable, se le indica al compilador que reserve espacio de almacenamiento para la variable. Sin embargo, el valor almacenado en ese espacio, el valor de la variable, no está definido. Puede ser cero, o puede ser algo aleatorio de "basura" valor. Antes de usar una variable, siempre debe inicializarla con un valor conocido. Tomemos este ejemplo:
int count; /* Set aside storage space for count */
count = 0; /* Store 0 in count */
This statement uses the equal sign (=), which is C's assignment operator. You can also initialize a variable when it's declared. To do so, follow the variable name in the declaration statement with an equal sign and the desired initial value:
int count = 0;
double rate = 0.01, complexity = 28.5;
Be careful not to initialize a variable with a value outside the allowed range. Here are two examples of out-of-range initializations:
int amount = 100000;
unsigned int length = -2500;
The C compiler does not catch such errors. Your program may compile and link, but you may get unexpected results when the program is run.
Tomemos el siguiente ejemplo para calcular el número total de sectores en un disco:
// Programa Modelo Para Calcular Sectores En Un Disco //
#include<stdio.h>
#define SECTOR_PER_SIDE 63
#define SIDE_PER_CYLINDER 254
void main()
{
int cylinder=0;
clrscr();
printf("Enter The No. of Cylinders in the Disk \n\n\t");
scanf("%d",&cylinder); // Get the value from the user //
printf("\n\n\t Total Number of Sectors in the disk = %ld", (long)SECTOR_PER_SIDE*SIDE_PER_CYLINDER* cylinder);
getch();
}
El resultado del programa es el siguiente:
Ingrese el número de cilindros en el disco
1024
Número total de sectores en el disco = 16386048
En este ejemplo vemos tres cosas nuevas para aprender. #define se usa para usar constantes simbólicas en el programa o, en algunos casos, para ahorrar tiempo definiendo palabras largas en símbolos pequeños.
Aquí hemos definido el número de sectores por lado que es 63 como SECTOR_PER_SIDE para que el programa sea fácil de entender. El mismo caso es cierto para #define SIDE_PER_CYLINDER 254. scanf() se usa para obtener la entrada del usuario.
Aquí estamos tomando el número de cilindros como entrada del usuario. * se usa para multiplicar dos o más valores como se muestra en el ejemplo.
la función getch() básicamente obtiene una entrada de un solo carácter desde el teclado. Al escribir getch(); aquí paramos la pantalla hasta que se pulse cualquier tecla del teclado.
Operadores
Un operador es un símbolo que le indica a C que realice alguna operación o acción en uno o más operandos. Un operando es algo sobre lo que actúa un operador. En C, todos los operandos son expresiones. Los operadores C pertenecen a las siguientes cuatro categorías:
- El operador de asignación
- Operadores matemáticos
- Operadores relacionales
- Operadores lógicos
Operador de asignación
El operador de asignación es el signo igual (=). El uso del signo igual en la programación es diferente de su uso en las relaciones algebraicas matemáticas regulares. Si tú escribes
x = y;
En un programa C, no significa que "x es igual a y". En su lugar, significa "asignar el valor de y a x". En una declaración de asignación de C, el lado derecho puede ser cualquier expresión y el lado izquierdo debe ser un nombre de variable. Así, el formulario es el siguiente:
variable = expresión;
Durante la ejecución, se evalúa la expresión y el valor resultante se asigna a la variable.
Operadores matemáticos
Los operadores matemáticos de C realizan operaciones matemáticas como la suma y la resta. C tiene dos operadores matemáticos unarios y cinco operadores matemáticos binarios. Los operadores matemáticos unarios se denominan así porque toman un solo operando. C tiene dos operadores matemáticos unarios.
Los operadores de incremento y decremento solo se pueden usar con variables, no con constantes. La operación que se realiza es sumar o restar uno al operando. En otras palabras, las sentencias ++x; y --y; son los equivalentes de estas declaraciones:
x = x + 1;
y = y - 1;
Los operadores matemáticos binarios toman dos operandos. Los primeros cuatro operadores binarios, que incluyen las operaciones matemáticas comunes que se encuentran en una calculadora (+, -, *, /), le resultan familiares. El quinto operador Módulo devuelve el resto cuando el primer operando se divide por el segundo operando. Por ejemplo, 11 módulo 4 es igual a 3 (11 se divide por 4, dos veces y sobra 3).
Operadores relacionales
Los operadores relacionales de C se utilizan para comparar expresiones. Una expresión que contiene un operador relacional se evalúa como verdadera (1) o falsa (0). C tiene seis operadores relacionales.
Operadores lógicos
Los operadores lógicos de C le permiten combinar dos o más expresiones relacionales en una sola expresión que se evalúa como verdadera o falsa. Los operadores lógicos se evalúan como verdaderos o falsos, según el valor verdadero o falso de sus operandos.
Si x es una variable entera, las expresiones que usan operadores lógicos podrían escribirse de las siguientes maneras:
(x > 1) && (x < 5)
(x >= 2) && (x <= 4)
Operador |
Símbolo |
Descripción |
Ejemplo |
Operadores de asignación |
equal |
= |
assign the value of y to x |
x = y |
Mathematical operators |
Increment |
++ |
Increments the operand by one |
++x, x++ |
Decrement |
-- |
Decrements the operand by one |
--x, x-- |
Addition |
+ |
Adds two operands |
x + y |
Subtraction |
- |
Subtracts the second operand from the first |
x - y |
Multiplication |
* |
Multiplies two operands |
x * y |
Division |
/ |
Divides the first operand by the second operand |
x / y |
Modulus |
% |
Gives the remainder when the first operand is divided by the second operand |
x % y |
Relational operators |
Equal |
= = |
Equality |
x = = y |
Greater than |
> |
Greater than |
x > y |
Less than |
< |
Less than |
x < y |
Greater than or equal to |
>= |
Greater than or equal to |
x >= y |
Less than or equal to |
<= |
Less than or equal to |
x <= y |
Not equal |
!= |
Not equal to |
x != y |
Logical operators |
AND |
&& |
True (1) only if both exp1 and exp2 are true; false (0) otherwise |
exp1 && exp2 |
OR |
|| |
True (1) if either exp1 or exp2 is true; false (0) only if both are false |
exp1 || exp2 |
NOT |
! |
False (0) if exp1 is true; true (1) if exp1 is false |
!exp1 |
Things to remember about logical expressions
x * = y |
is same as |
x = x * y |
y - = z + 1 |
is same as |
y = y - z + 1 |
a / = b |
is same as |
a = a / b |
x + = y / 8 |
is same as |
x = x + y / 8 |
y % = 3 |
is same as |
y = y % 3 |
El operador coma
La coma se usa frecuentemente en C como un simple signo de puntuación, para separar declaraciones de variables, argumentos de funciones, etc. En ciertas situaciones, la coma actúa como un operador.
Puede formar una expresión separando dos subexpresiones con una coma. El resultado es el siguiente:
- Ambas expresiones se evalúan, y la expresión de la izquierda se evalúa primero.
- La expresión completa se evalúa como el valor de la expresión correcta.
Por ejemplo, la siguiente declaración asigna el valor de b a x, luego incrementa a, y luego incrementa b:
x = (a++, b++);
C operator precedence (Summary of C operators)
Rank and Associativity |
Operators |
1(left to right) |
() [] -> . |
2(right to left) |
! ~ ++ -- * (indirection) & (address-of) (type)
sizeof + (unary) - (unary) |
3(left to right) |
* (multiplication) / % |
4(left to right) |
+ - |
5(left to right) |
<< >> |
6(left to right) |
< <= > >= |
7(left to right) |
= = != |
8(left to right) |
& (bitwise AND) |
9(left to right) |
^ |
10(left to right) |
| |
11(left to right) |
&& |
12(left to right) |
|| |
13(right to left) |
?: |
14(right to left) |
= += -= *= /= %= &= ^= |= <<= >>= |
15(left to right) |
, |
() is the function operator; [] is the array operator. |
|
Tomemos un ejemplo de uso de operadores:
/* Uso de operadores */
int main()
{
int x = 0, y = 2, z = 1025;
float a = 0.0, b = 3.14159, c = -37.234;
/* incrementing */
x = x + 1; /* This increments x */
x++; /* This increments x */
++x; /* This increments x */
z = y++; /* z = 2, y = 3 */
z = ++y; /* z = 4, y = 4 */
/* decrementing */
y = y - 1; /* This decrements y */
y--; /* This decrements y */
--y; /* This decrements y */
y = 3;
z = y--; /* z = 3, y = 2 */
z = --y; /* z = 1, y = 1 */
/* arithmetic op */
a = a + 12; /* This adds 12 to a */
a += 12; /* This adds 12 more to a */
a *= 3.2; /* This multiplies a by 3.2 */
a -= b; /* This subtracts b from a */
a /= 10.0; /* This divides a by 10.0 */
/* conditional expression */
a = (b >= 3.0 ? 2.0 : 10.5 ); /* This expression */
if (b >= 3.0) /* And this expression */
a = 2.0; /* are identical, both */
else /* will cause the same */
a = 10.5; /* result. */
c = (a > b ? a : b); /* c will have the max of a or b */
c = (a > b ? b : a); /* c will have the min of a or b */
printf("x=%d, y=%d, z= %d\n", x, y, z);
printf("a=%f, b=%f, c= %f", a, b, c);
return 0;
}
y el resultado de este programa se mostrará en la pantalla como:
x=3, y=1, z=1
a=2.000000, b=3.141590, c=2.000000
Algo más sobre printf() y Scanf()
Considere las siguientes dos sentencias printf
printf(“\t %d\n”, num);
printf(“%5.2f”, fract);
en la primera instrucción printf \t solicita el desplazamiento de tabulación en la pantalla, el argumento %d le dice al compilador que el valor de num debe imprimirse como un entero decimal. \n hace que la nueva salida comience desde una nueva línea.
En la segunda instrucción printf, %5.2f le dice al compilador que la salida debe estar en punto flotante, con cinco lugares en total y dos lugares a la derecha del punto decimal. En la siguiente tabla se muestra más sobre el carácter de barra invertida:
Constant |
Meaning |
‘\a’ |
Audible alert (bell) |
‘\b’ |
Backspace |
‘\f’ |
Form feed |
‘\n’ |
New line |
‘\r’ |
Carriage return |
‘\t’ |
Horizontal tab |
‘\v’ |
Vertical tab |
‘\’’ |
Single quote |
‘\”’ |
Double quote |
‘\?’ |
Question mark |
‘\\’ |
Backslash |
‘\0’ |
Null |
Consideremos la siguiente sentencia scanf
scanf(“%d”, &num);
Los datos del teclado se reciben mediante la función scanf. En el formato anterior, el & El símbolo (ampersand) antes de cada nombre de variable es un operador que especifica la dirección del nombre de variable.
Al hacer esto, la ejecución se detiene y espera a que se escriba el valor de la variable num. Cuando se ingresa el valor entero y se presiona la tecla de retorno, la computadora continúa con la siguiente instrucción. Los códigos de formato scanf e printf se enumeran en la siguiente tabla:
Code |
Reads... |
%c |
Single character |
%d |
Decimal integer |
%e |
Floating point value |
%f |
Floating point value |
%g |
Floating point value |
%h |
Short integer |
%i |
Decimal, hexadecimal or octal integer |
%o |
Octal integer |
%s |
String |
%u |
Unsigned decimal integer |
%x |
Hexadecimal integer |
Declaraciones de control
Un programa consta de una serie de sentencias que normalmente se ejecutan en secuencia. Los programas pueden ser mucho más poderosos si podemos controlar el orden en que se ejecutan las sentencias.
Las declaraciones se dividen en tres tipos generales:
- Asignación, donde los valores, generalmente los resultados de los cálculos, se almacenan en variables.
- Entrada / Salida, los datos se leen o imprimen.
- Control, el programa toma una decisión sobre qué hacer a continuación.
Esta sección discutirá el uso de sentencias de control en C. Mostraremos cómo se pueden usar para escribir programas poderosos por;
- Repetir secciones importantes del programa.
- Seleccionar entre secciones opcionales de un programa.
La sentencia if else
Esto se usa para decidir si hacer algo en un punto especial, o para decidir entre dos cursos de acción.
La siguiente prueba decide si un estudiante ha aprobado un examen con una calificación de aprobación de 45
if (result >= 45)
printf("Pass\n");
else
printf("Fail\n");
It is possible to use the if part without the else.
if (temperature < 0)
print("Frozen\n");
Cada versión consta de una prueba, en la declaración entre corchetes que sigue al si. Si la prueba es verdadera, entonces se obedece la siguiente declaración. Si es falso, entonces se obedece la afirmación que sigue al else, si está presente. Después de esto, el resto del programa continúa con normalidad.
Si deseamos tener más de una declaración después de if o else, deben agruparse entre corchetes. Tal agrupación se llama enunciado compuesto o bloque.
if (result >= 45)
{ printf("Passed\n");
printf("Congratulations\n");
}
else
{ printf("Failed\n");
printf("Better Luck Next Time\n");
}
A veces deseamos tomar una decisión múltiple basada en varias condiciones. La forma más general de hacer esto es usando la variante else if en la instrucción if.
Esto funciona conectando en cascada varias comparaciones. Tan pronto como uno de estos da un resultado verdadero, se ejecuta la siguiente instrucción o bloque y no se realizan más comparaciones. En el siguiente ejemplo, estamos otorgando calificaciones según el resultado del examen.
if (result <=100 && result >= 75)
printf("Passed: Grade A\n");
else if (result >= 60)
printf("Passed: Grade B\n");
else if (result >= 45)
printf("Passed: Grade C\n");
else
printf("Failed\n");
En este ejemplo, todas las comparaciones prueban una sola variable llamada resultado. En otros casos, cada prueba puede involucrar una variable diferente o alguna combinación de pruebas. El mismo patrón se puede usar con más o menos if's, y se puede omitir el último else.
Depende del programador idear la estructura correcta para cada problema de programación. Para entender mejor el uso de if else veamos el ejemplo
#include <stdio.h>
int main()
{
int num;
for(num = 0 ; num < 10 ; num = num + 1)
{
if (num == 2)
printf("num is now equal to %d\n", num);
if (num < 5)
printf("num is now %d, which is less than 5\n", num);
else
printf("num is now %d, which is greater than 4\n", num);
} /* end of for loop */
return 0;
}
resultado del programa
num is now 0, which is less than 5
num is now 1, which is less than 5
num is now equal to 2
num is now 2, which is less than 5
num is now 3, which is less than 5
num is now 4, which is less than 5
num is now 5, which is greater than 4
num is now 6, which is greater than 4
num is now 7, which is greater than 4
num is now 8, which is greater than 4
num is now 9, which is greater than 4
La declaración del cambio
Esta es otra forma de decisión múltiple. Está bien estructurado, pero solo se puede usar en ciertos casos donde;
- Solo se prueba una variable, todas las ramas deben depender del valor de esa variable. La variable debe ser de tipo integral. (int, largo, corto o char).
- Cada valor posible de la variable puede controlar una sola rama. Opcionalmente, se puede usar una rama predeterminada final, atrapar todo, para interceptar todos los casos no especificados.
El ejemplo dado a continuación aclarará las cosas. Esta es una función que convierte un número entero en una descripción vaga. Es útil cuando solo nos interesa medir una cantidad cuando es bastante pequeña.
estimate(number)
int number;
/* Estimate a number as none, one, two, several, many */
{ switch(number) {
case 0 :
printf("None\n");
break;
case 1 :
printf("One\n");
break;
case 2 :
printf("Two\n");
break;
case 3 :
case 4 :
case 5 :
printf("Several\n");
break;
default :
printf("Many\n");
break;
}
}
Cada caso interesante se enumera con una acción correspondiente. La declaración de ruptura evita que se ejecuten más declaraciones al dejar el interruptor. Dado que el caso 3 y el caso 4 no tienen ruptura siguiente, continúan permitiendo la misma acción para varios valores de número.
Tanto las construcciones if como switch permiten al programador realizar una selección entre una serie de acciones posibles. Veamos un ejemplo:
#include <stdio.h>
int main()
{
int num;
for (num = 3 ; num < 13 ; num = num + 1)
{
switch (num)
{
case 3 :
printf("The value is three\n");
break;
case 4 :
printf("The value is four\n");
break;
case 5 :
case 6 :
case 7 :
case 8 :
printf("The value is between 5 and 8\n");
break;
case 11 :
printf("The value is eleven\n");
break;
default :
printf("It is one of the undefined values\n");
break;
} /* end of switch */
} /* end of for loop */
return 0;
}
La salida del programa será
The value is three
The value is four
The value is between 5 and 8
The value is between 5 and 8
The value is between 5 and 8
The value is between 5 and 8
It is one of the undefined values
It is one of the undefined values
The value is eleven
It is one of the undefined values
La declaración de ruptura
Ya nos hemos encontrado con un quiebre en la discusión de la sentencia switch. Se usa para salir de un ciclo o un interruptor, pasando el control a la primera declaración más allá del ciclo o un interruptor.
Con los bucles, se puede usar break para forzar una salida anticipada del bucle, o para implementar un bucle con una prueba para salir en medio del cuerpo del bucle. Una ruptura dentro de un bucle siempre debe protegerse dentro de una declaración if que proporcione la prueba para controlar la condición de salida.
La declaración de continuación
Esto es similar a romper pero se encuentra con menos frecuencia. Solo funciona dentro de bucles donde su efecto es forzar un salto inmediato a la instrucción de control de bucle.
- En un ciclo while, salta a la declaración de prueba.
- En un ciclo do while, salta a la declaración de prueba.
- En un bucle for, pase a la prueba y realice la iteración.
Al igual que un descanso, continuar debe estar protegido por una declaración if. Es poco probable que lo use muy a menudo. Para comprender mejor el uso de romper y continuar, examinemos el siguiente programa:
#include <stdio.h>
int main()
{
int value;
for(value = 5 ; value < 15 ; value = value + 1)
{
if (value == 8)
break;
printf("In the break loop, value is now %d\n", value);
}
for(value = 5 ; value < 15 ; value = value + 1)
{
if (value == 8)
continue;
printf("In the continue loop, value is now %d\n", value);
}
return 0;
}
La salida del programa será la siguiente:
In the break loop, value is now 5
In the break loop, value is now 6
In the break loop, value is now 7
In the continue loop, value is now 5
In the continue loop, value is now 6
In the continue loop, value is now 7
In the continue loop, value is now 9
In the continue loop, value is now 10
In the continue loop, value is now 11
In the continue loop, value is now 12
In the continue loop, value is now 13
In the continue loop, value is now 14
Bucles
El otro tipo principal de instrucción de control es el bucle. Los bucles permiten que se repita una sentencia, o un bloque de sentencias. Las computadoras son muy buenas para repetir tareas simples muchas veces. El bucle es la forma en que C logra esto.
C te permite elegir entre tres tipos de bucle, while, do-while y for.
- El bucle while sigue repitiendo una acción hasta que una prueba asociada devuelve falso. Esto es útil cuando el programador no sabe de antemano cuántas veces se recorrerá el ciclo.
- Los bucles do while son similares, pero la prueba ocurre después de que se ejecuta el cuerpo del bucle. Esto asegura que el cuerpo del bucle se ejecute al menos una vez.
- El ciclo for se usa con frecuencia, generalmente donde el ciclo se recorrerá un número fijo de veces. Es muy flexible y los programadores novatos deben tener cuidado de no abusar del poder que ofrece.
El ciclo while
El ciclo while repite una declaración hasta que la prueba en la parte superior resulta falsa. Como ejemplo, aquí hay una función para devolver la longitud de una cadena. Recuerde que la cadena se representa como una matriz de caracteres terminada en un carácter nulo '\0'.
int string_length(char string[])
{ int i = 0;
while (string[i] != '\0')
i++;
return(i);
}
La cadena se pasa a la función como argumento. No se especifica el tamaño de la matriz, la función funcionará para una cadena de cualquier tamaño.
El bucle while se utiliza para examinar los caracteres de la cadena de uno en uno hasta que se encuentra el carácter nulo. Luego se sale del bucle y se devuelve el índice del nulo.
Si bien el carácter no es nulo, el índice se incrementa y la prueba se repite. Profundizaremos en las matrices más adelante. Veamos un ejemplo de ciclo while:
#include <stdio.h>
int main()
{
int count;
count = 0;
while (count < 6)
{
printf("The value of count is %d\n", count);
count = count + 1;
}
return 0;
}
y el resultado se muestra de la siguiente manera:
The value of count is 0
The value of count is 1
The value of count is 2
The value of count is 3
The value of count is 4
The value of count is 5
El bucle do while
Esto es muy similar al ciclo while excepto que la prueba ocurre al final del cuerpo del ciclo. Esto garantiza que el bucle se ejecute al menos una vez antes de continuar.
Este tipo de configuración se utiliza con frecuencia cuando se van a leer datos. Luego, la prueba verifica los datos y vuelve a leerlos nuevamente si no son aceptables.
do
{
printf("Enter 1 for yes, 0 for no :");
scanf("%d", &input_value);
} while (input_value != 1 && input_value != 0)
To better understand the do while loop let us see the following example:
#include <stdio.h>
int main()
{
int i;
i = 0;
do
{
printf("The value of i is now %d\n", i);
i = i + 1;
} while (i < 5);
return 0;
}
El resultado del programa se muestra de la siguiente manera:
The value of i is now 0
The value of i is now 1
The value of i is now 2
The value of i is now 3
The value of i is now 4
El bucle for
El ciclo for funciona bien cuando se conoce el número de iteraciones del ciclo antes de ingresar al ciclo. La cabeza del bucle consta de tres partes separadas por punto y coma.
- El primero se ejecuta antes de ingresar al bucle. Esta suele ser la inicialización de la variable de bucle.
- El segundo es una prueba, se sale del ciclo cuando devuelve falso.
- La tercera es una instrucción que se ejecuta cada vez que se completa el cuerpo del bucle. Esto suele ser un incremento del contador de bucles.
El ejemplo es una función que calcula el promedio de los números almacenados en una matriz. La función toma la matriz y el número de elementos como argumentos.
float average(float array[], int count)
{
float total = 0.0;
int i;
for(i = 0; i < count; i++)
total += array[i];
return(total / count);
}
El bucle for garantiza que se sume el número correcto de elementos de la matriz antes de calcular el promedio.
Las tres declaraciones al principio de un ciclo for normalmente hacen solo una cosa cada una, sin embargo, cualquiera de ellas puede dejarse en blanco. Una primera o última declaración en blanco significará que no hay inicialización ni incremento en ejecución. Una declaración de comparación en blanco siempre se tratará como verdadera. Esto hará que el ciclo se ejecute indefinidamente a menos que se interrumpa por algún otro medio. Esto podría ser una declaración de retorno o ruptura.
También es posible colocar varias sentencias en la primera o tercera posición, separándolas con comas. Esto permite un bucle con más de una variable de control. El siguiente ejemplo ilustra la definición de dicho ciclo, con las variables hi y lo comenzando en 100 y 0 respectivamente y convergiendo.
El bucle for ofrece una variedad de abreviaturas para usar en él. Tenga cuidado con la siguiente expresión, en esta expresión el bucle único contiene dos bucles for. Aquí hi-- es lo mismo que hi = hi - 1 y lo++ es lo mismo que lo = lo + 1,
for(hi = 100, lo = 0; hi >= lo; hi--, lo++)
El ciclo for es extremadamente flexible y permite especificar muchos tipos de comportamiento del programa de manera simple y rápida. Veamos un ejemplo de bucle for
#include <stdio.h>
int main()
{
int index;
for(index = 0 ; index < 6 ; index = index + 1)
printf("The value of the index is %d\n", index);
return 0;
}
El resultado del programa se muestra de la siguiente manera:
The value of the index is 0
The value of the index is 1
The value of the index is 2
The value of the index is 3
The value of the index is 4
The value of the index is 5
La declaración goto
C tiene una instrucción goto que permite realizar saltos no estructurados. Para usar una instrucción goto, simplemente use la palabra reservada goto seguida del nombre simbólico al que desea saltar. Luego, el nombre se coloca en cualquier parte del programa seguido de dos puntos. Puede saltar casi a cualquier lugar dentro de una función, pero no puede saltar a un bucle, aunque sí puede saltar fuera de un bucle.
Este programa en particular es realmente un desastre, pero es un buen ejemplo de por qué los desarrolladores de software están tratando de eliminar el uso de la declaración goto tanto como sea posible. El único lugar en este programa donde es razonable usar el goto es donde el programa salta de los tres bucles anidados en un solo salto. En este caso, sería bastante complicado configurar una variable y saltar sucesivamente de cada uno de los tres bucles anidados, pero una instrucción goto lo saca de los tres de una manera muy concisa.
Algunas personas dicen que la declaración goto nunca debe usarse bajo ninguna circunstancia, pero esto es un pensamiento estrecho de miras. Si hay un lugar donde un goto claramente hará un flujo de control más limpio que cualquier otra construcción, siéntase libre de usarlo, sin embargo, como está en el resto del programa en su monitor. Veamos el ejemplo:
#include <stdio.h>
int main()
{
int dog, cat, pig;
goto real_start;
some_where:
printf("This is another line of the mess.\n");
goto stop_it;
/* the following section is the only section with a useable goto */
real_start:
for(dog = 1 ; dog < 6 ; dog = dog + 1)
{
for(cat = 1 ; cat < 6 ; cat = cat + 1)
{
for(pig = 1 ; pig < 4 ; pig = pig + 1)
{
printf("Dog = %d Cat = %d Pig = %d\n", dog, cat, pig);
if ((dog + cat + pig) > 8 ) goto enough;
}
}
}
enough: printf("Those are enough animals for now.\n");
/* this is the end of the section with a useable goto statement */
printf("\nThis is the first line of the code.\n");
goto there;
where:
printf("This is the third line of the code.\n");
goto some_where;
there:
printf("This is the second line of the code.\n");
goto where;
stop_it:
printf("This is the last line of this mess.\n");
return 0;
}
Veamos los resultados mostrados
Dog = 1 Cat = 1 Pig = 1
Dog = 1 Cat = 1 Pig = 2
Dog = 1 Cat = 1 Pig = 3
Dog = 1 Cat = 2 Pig = 1
Dog = 1 Cat = 2 Pig = 2
Dog = 1 Cat = 2 Pig = 3
Dog = 1 Cat = 3 Pig = 1
Dog = 1 Cat = 3 Pig = 2
Dog = 1 Cat = 3 Pig = 3
Dog = 1 Cat = 4 Pig = 1
Dog = 1 Cat = 4 Pig = 2
Dog = 1 Cat = 4 Pig = 3
Dog = 1 Cat = 5 Pig = 1
Dog = 1 Cat = 5 Pig = 2
Dog = 1 Cat = 5 Pig = 3
Those are enough animals for now.
This is the first line of the code.
This is the second line of the code.
This is the third line of the code.
This is another line of the mess.
This is the last line of this mess.
Punteros
A veces queremos saber dónde reside una variable en la memoria. Un puntero contiene la dirección de una variable que tiene un valor específico. Al declarar un puntero, se coloca un asterisco inmediatamente antes del nombre del puntero. .
La dirección de la ubicación de memoria donde se almacena la variable se puede encontrar colocando un ampersand delante del nombre de la variable.
int num; /* Normal integer variable */
int *numPtr; /* Pointer to an integer variable */
El siguiente ejemplo imprime el valor de la variable y la dirección en la memoria de esa variable.
printf("The value %d is stored at address %X\n", num, &num);
Para asignar la dirección de la variable num al puntero numPtr, asigne la dirección de la variable, num, como en el siguiente ejemplo:
numPtr = #
Para averiguar qué se almacena en la dirección a la que apunta numPtr, es necesario desreferenciar la variable. La desreferenciación se logra con el asterisco con el que se declaró el puntero.
printf("The value %d is stored at address %X\n", *numPtr, numPtr);
Todas las variables de un programa residen en la memoria. Las declaraciones dadas a continuación solicitan que el compilador reserve 4 bytes de memoria en una computadora de 32 bits para la variable de coma flotante x, luego coloque el valor 6.5 en ella.
float x;
x = 6.5;
Como la ubicación de la dirección en la memoria de cualquier variable se obtiene colocando el operador & antes de su nombre, por lo tanto, &x es la dirección de x. C nos permite ir un paso más allá y definir una variable, llamada puntero que contiene la dirección de otras variables. Más bien podemos decir que el puntero apunta a otra variable. Por ejemplo:
float x;
float* px;
x = 6.5;
px = &x;
define px para que sea un puntero a objetos de tipo float y lo establece igual a la dirección de x. Así, *px se refiere al valor de x:
Examinemos las siguientes afirmaciones:
int var_x;
int* ptrX;
var_x = 6;
ptrX = &var_x;
*ptrX = 12;
printf("value of x : %d", var_x);
La primera línea hace que el compilador reserve un espacio en la memoria para un número entero. La segunda línea le dice al compilador que reserve espacio para almacenar un puntero.
Un puntero es una ubicación de almacenamiento para una dirección. La tercera línea debería recordarle las declaraciones scanf. La dirección "&" El operador le dice al compilador que vaya al lugar donde almacenó var_x y luego proporcione la dirección de la ubicación de almacenamiento a ptrX.
El asterisco * delante de una variable le dice al compilador que elimine la referencia del puntero y vaya a la memoria. Luego puede realizar asignaciones a variables almacenadas en esa ubicación. Puede hacer referencia a una variable y acceder a sus datos a través de un puntero. Veamos un ejemplo de punteros:
/* ilustración del uso de punteros */
#include <stdio.h>
int main()
{
int index, *pt1, *pt2;
index = 39; /* any numerical value */
pt1 = &index; /* the address of index */
pt2 = pt1;
printf("The value is %d %d %d\n", index, *pt1, *pt2);
*pt1 = 13; /* this changes the value of index */
printf("The value is %d %d %d\n", index, *pt1, *pt2);
return 0;
}
La salida del programa se mostrará de la siguiente manera:
The value is 39 39 39
The value is 13 13 13
Veamos otro ejemplo para entender mejor el uso de punteros:
#include <stdio.h>
#include <string.h>
int main()
{
char strg[40], *there, one, two;
int *pt, list[100], index;
strcpy(strg, "This is a character string.");
/* the function strcpy() is to copy one string to another. we’ll read about strcpy() function in String Section later */
one = strg[0]; /* one and two are identical */
two = *strg;
printf("The first output is %c %c\n", one, two);
one = strg[8]; /* one and two are identical */
two = *(strg+8);
printf("The second output is %c %c\n", one, two);
there = strg+10; /* strg+10 is identical to &strg[10] */
printf("The third output is %c\n", strg[10]);
printf("The fourth output is %c\n", *there);
for (index = 0 ; index < 100 ; index++)
list[index] = index + 100;
pt = list + 27;
printf("The fifth output is %d\n", list[27]);
printf("The sixth output is %d\n", *pt);
return 0;
}
La salida del programa será así:
The first output is T T
The second output is a a
The third output is c
The fourth output is c
The fifth output is 127
The sixth output is 127
Matrices
Una matriz es una colección de variables del mismo tipo. Los elementos individuales de la matriz se identifican mediante un índice entero. En C el índice comienza en cero y siempre se escribe entre corchetes.
Ya hemos encontrado arreglos de una sola dimensión que se declaran así
int results[20];
Los arreglos pueden tener más dimensiones, en cuyo caso podrían declararse como
int results_2d[20][5];
int results_3d[20][5][3];
Cada índice tiene su propio conjunto de corchetes. Una matriz se declara en la función principal, generalmente tiene detalles de dimensiones incluidos. Es posible usar otro tipo llamado puntero en lugar de una matriz. Esto significa que las dimensiones no se fijan de inmediato, pero el espacio se puede asignar según sea necesario. Esta es una técnica avanzada que solo se requiere en ciertos programas especializados.
Como ejemplo, aquí hay una función simple para sumar todos los números enteros en una matriz de una sola dimensión.
int add_array(int array[], int size)
{
int i;
int total = 0;
for(i = 0; i < size; i++)
total += array[i];
return(total);
}
El programa dado a continuación creará una cadena, accederá a algunos datos y la imprimirá. Acceda a él nuevamente usando punteros y luego imprima la cadena. Debe imprimir "¡Hola!" y “012345678” en líneas diferentes. Veamos la codificación del programa:
#include <stdio.h>
#define STR_LENGTH 10
void main()
{
char Str[STR_LENGTH];
char* pStr;
int i;
Str[0] = 'H';
Str[1] = 'i';
Str[2] = '!';
Str[3] = '\0'; // special end string character NULL
printf("The string in Str is : %s\n", Str);
pStr = &Str[0];
for (i = 0; i < STR_LENGTH; i++)
{
*pStr = '0'+i;
pStr++;
}
Str[STR_LENGTH-1] = '\0';
printf("The string in Str is : %s\n", Str);
}
[] (corchetes) se utilizan para declarar la matriz. La línea del programa char Str[STR_LENGTH]; declara una matriz de diez caracteres. Estos son diez caracteres individuales, todos reunidos en la memoria en el mismo lugar. Se puede acceder a todos a través de nuestro nombre de variable Str junto con [n] donde n es el número de elemento.
Cuando se habla de matriz, siempre se debe tener en cuenta que cuando C declara una matriz de diez, los elementos a los que puede acceder se numeran del 0 al 9. Acceder al primer elemento corresponde a acceder al elemento 0. Entonces, en el caso de las matrices, siempre cuente desde 0 hasta el tamaño de la matriz: 1.
A continuación, observe que ponemos las letras "¡Hola!" en la matriz, pero luego ponemos un '\0', probablemente se esté preguntando qué es esto. "\0" significa NULL y representa el final de la cadena. Todas las cadenas de caracteres deben terminar con este carácter especial '\0'. Si no lo hacen, y luego alguien llama a printf en la cadena, entonces printf comenzaría en la ubicación de memoria de su cadena, y continuaría imprimiendo, le diría que encuentra '\ 0' y, por lo tanto, terminará con un montón de basura al final. de tu cuerda. Así que asegúrese de terminar sus cadenas correctamente.
Matrices de caracteres
Una constante de cadena, como
"Soy una cuerda"
es una matriz de caracteres. Está representado internamente en C por los caracteres ASCII en la cadena, es decir, "I", en blanco, "a", "m",... o la cadena anterior, y termina con el carácter nulo especial "\0" para que los programas puedan encuentra el final de la cadena.
Las constantes de cadena se usan a menudo para hacer que la salida del código sea inteligible usando printf:
printf("Hello, world\n");
printf("The value of a is: %f\n", a);
Las constantes de cadena se pueden asociar con variables. C proporciona la variable de tipo de carácter, que puede contener un carácter (1 byte) a la vez. Una cadena de caracteres se almacena en una matriz de tipo de carácter, un carácter ASCII por ubicación.
Nunca olvide que, dado que las cadenas terminan convencionalmente con el carácter nulo "\0", necesitamos una ubicación de almacenamiento adicional en la matriz.
C no proporciona ningún operador que manipule cadenas enteras a la vez. Las cadenas se manipulan mediante punteros o mediante rutinas especiales disponibles en la biblioteca de cadenas estándar string.h.
Usar punteros de caracteres es relativamente fácil ya que el nombre de una matriz es solo un puntero a su primer elemento. Considere el programa dado a continuación:
#include<stdio.h>
void main()
{
char text_1[100], text_2[100], text_3[100];
char *ta, *tb;
int i;
/* set message to be an arrray */
/* of characters; initialize it */
/* to the constant string "..." */
/* let the compiler decide on */
/* its size by using [] */
char message[] = "Hello, I am a string; what are
you?";
printf("Original message: %s\n", message);
/* copy the message to text_1 */
i=0;
while ( (text_1[i] = message[i]) != '\0' )
i++;
printf("Text_1: %s\n", text_1);
/* use explicit pointer arithmetic */
ta=message;
tb=text_2;
while ( ( *tb++ = *ta++ ) != '\0' )
;
printf("Text_2: %s\n", text_2);
}
La salida del programa será la siguiente:
Original message: Hello, I am a string; what are you?
Text_1: Hello, I am a string; what are you?
Text_2: Hello, I am a string; what are you?
La biblioteca estándar de "cadenas" contiene muchas funciones útiles para manipular cadenas, que aprenderemos en la sección de cadenas más adelante.
Acceso a los Elementos
Para acceder a un elemento individual en la matriz, el número de índice sigue al nombre de la variable entre corchetes. La variable se puede tratar como cualquier otra variable en C. El siguiente ejemplo asigna un valor al primer elemento de la matriz.
x[0] = 16;
El siguiente ejemplo imprime el valor del tercer elemento en una matriz.
printf("%d\n", x[2]);
El siguiente ejemplo usa la función scanf para leer un valor del teclado en el último elemento de una matriz con diez elementos.
scanf("%d", &x[9]);
Inicializar elementos de matriz
Los arreglos se pueden inicializar como cualquier otra variable por asignación. Como una matriz contiene más de un valor, los valores individuales se colocan entre llaves y se separan con comas. El siguiente ejemplo inicializa una matriz de diez dimensiones con los primeros diez valores de la tabla de tres veces.
int x[10] = {3, 6, 9, 12, 15, 18, 21, 24, 27, 30};
Esto ahorra asignar los valores individualmente como en el siguiente ejemplo.
int x[10];
x[0] = 3;
x[1] = 6;
x[2] = 9;
x[3] = 12;
x[4] = 15;
x[5] = 18;
x[6] = 21;
x[7] = 24;
x[8] = 27;
x[9] = 30;
Bucle a través de una matriz
Como la matriz se indexa secuencialmente, podemos usar el bucle for para mostrar todos los valores de una matriz. El siguiente ejemplo muestra todos los valores de una matriz:
#include <stdio.h>
int main()
{
int x[10];
int counter;
/* Randomise the random number generator */
srand((unsigned)time(NULL));
/* Assign random values to the variable */
for (counter=0; counter<10; counter++)
x[counter] = rand();
/* Display the contents of the array */
for (counter=0; counter<10; counter++)
printf("element %d has the value %d\n", counter, x[counter]);
return 0;
}
aunque la salida imprimirá los diferentes valores cada vez, el resultado se mostrará así:
element 0 has the value 17132
element 1 has the value 24904
element 2 has the value 13466
element 3 has the value 3147
element 4 has the value 22006
element 5 has the value 10397
element 6 has the value 28114
element 7 has the value 19817
element 8 has the value 27430
element 9 has the value 22136
Matrices multidimensionales
Una matriz puede tener más de una dimensión. Permitir que la matriz tenga más de una dimensión proporciona una mayor flexibilidad. Por ejemplo, las hojas de cálculo se basan en una matriz bidimensional; una matriz para las filas y una matriz para las columnas.
El siguiente ejemplo utiliza una matriz bidimensional con dos filas, cada una con cinco columnas:
#include <stdio.h>
int main()
{
/* Declare a 2 x 5 multidimensional array */
int x[2][5] = { {1, 2, 3, 4, 5},
{2, 4, 6, 8, 10} };
int row, column;
/* Display the rows */
for (row=0; row<2; row++)
{
/* Display the columns */
for (column=0; column<5; column++)
printf("%d\t", x[row][column]);
putchar('\n');
}
return 0;
}
La salida de este programa se mostrará de la siguiente manera:
1 2 3 4 5
2 4 6 8 10
Cuerdas
Una cadena es un grupo de caracteres, generalmente letras del alfabeto. Para formatear su pantalla impresa de tal manera que se vea bien, tenga nombres y títulos significativos, y sea estéticamente agradable para usted y las personas que usan el salida de su programa.
De hecho, ya has estado usando cadenas en los ejemplos de los temas anteriores. Pero no es la introducción completa de cadenas. Hay muchos casos posibles en la programación, donde el uso de cadenas formateadas ayuda al programador a evitar demasiadas complicaciones en el programa y, por supuesto, demasiados errores.
Una definición completa de una cadena es una serie de datos de tipo carácter terminados por un carácter nulo ('\0').
Cuando C va a usar una cadena de datos de alguna manera, ya sea para compararla con otra cadena, generarla, copiarla en otra cadena, o lo que sea, las funciones están configuradas para hacer lo que se les pide que hagan. hasta que se detecte un valor nulo.
No hay un tipo de datos básico para una cadena en C En su lugar; las cadenas en C se implementan como una matriz de caracteres. Por ejemplo, para almacenar un nombre, puede declarar una matriz de caracteres lo suficientemente grande como para almacenar el nombre y luego usar las funciones de biblioteca apropiadas para manipular el nombre.
El siguiente ejemplo muestra la cadena en la pantalla, ingresada por el usuario:
#include <stdio.h>
int main()
{
char name[80]; /* Create a character array
called name */
printf("Enter your name: ");
gets(name);
printf("The name you entered was %s\n", name);
return 0;
}
La ejecución del programa será:
Enter your name: Tarun Tyagi
The name you entered was Tarun Tyagi
Algunas funciones de cadena comunes
La biblioteca estándar string.h contiene muchas funciones útiles para manipular cadenas. Algunas de las funciones más útiles se han ejemplificado aquí.
The strlen Function
The strlen function iSe utiliza para determinar la longitud de una cadena. Aprendamos el uso de strlen con un ejemplo:
#include <stdio.h>
#include <string.h>
int main()
{
char name[80];
int length;
printf("Enter your name: ");
gets(name);
length = strlen(name);
printf("Your name has %d characters\n", length);
return 0;
}
Y la ejecución del programa será la siguiente:
Enter your name: Tarun Subhash Tyagi
Your name has 19 characters
Enter your name: Preeti Tarun
Your name has 12 characters
The strcpy Function
The strcpy function se utiliza para copiar una cadena a otra. Aprendamos el uso de esta función con un ejemplo:
#include <stdio.h>
#include <string.h>
int main()
{
char first[80];
char second[80];
printf("Enter first string: ");
gets(first);
printf("Enter second string: ");
gets(second);
printf("first: %s, and second: %s Before strcpy()\n "
, first, second);
strcpy(second, first);
printf("first: %s, and second: %s After strcpy()\n",
first, second);
return 0;
}
y la salida del programa será como:
Enter first string: Tarun
Enter second string: Tyagi
first: Tarun, and second: Tyagi Before strcpy()
first: Tarun, and second: Tarun After strcpy()
The strcmp Function
The strcmp function se utiliza para comparar dos cadenas juntas. El nombre de la variable de una matriz apunta a la dirección base de esa matriz. Por lo tanto, si tratamos de comparar dos cadenas usando lo siguiente, estaríamos comparando dos direcciones, que obviamente nunca serían iguales ya que no es posible almacenar dos valores en la misma ubicación.
if (primero == segundo) /* Nunca se puede comparar cadenas */
El siguiente ejemplo utiliza la función strcmp para comparar dos cadenas:
#include <string.h>
int main()
{
char first[80], second[80];
int t;
for(t=1;t<=2;t++)
{
printf("\nEnter a string: ");
gets(first);
printf("Enter another string: ");
gets(second);
if (strcmp(first, second) == 0)
puts("The two strings are equal");
else
puts("The two strings are not equal");
}
return 0;
}
Y la ejecución del programa será la siguiente:
Enter a string: Tarun
Enter another string: tarun
The two strings are not equal
Enter a string: Tarun
Enter another string: Tarun
The two strings are equal
The strcat Function
The strcat function se utiliza para unir una cadena a otra. ¿Veamos cómo? Con la ayuda del ejemplo:
#include <string.h>
int main()
{
char first[80], second[80];
printf("Enter a string: ");
gets(first);
printf("Enter another string: ");
gets(second);
strcat(first, second);
printf("The two strings joined together: %s\n",
first);
return 0;
}
Y la ejecución del programa será la siguiente:
Enter a string: Data
Enter another string: Recovery
The two strings joined together: DataRecovery
The strtok Function
The strtok function se utiliza para encontrar el siguiente token en una cadena. El token se especifica mediante una lista de posibles delimitadores.
El siguiente ejemplo lee una línea de texto de un archivo y determina una palabra usando los delimitadores, el espacio, el tabulador y la nueva línea. Luego, cada palabra se muestra en una línea separada:
#include <stdio.h>
#include <string.h>
int main()
{
FILE *in;
char line[80];
char *delimiters = " \t\n";
char *token;
if ((in = fopen("C:\\text.txt", "r")) == NULL)
{
puts("Unable to open the input file");
return 0;
}
/* Read each line one at a time */
while(!feof(in))
{
/* Get one line */
fgets(line, 80, in);
if (!feof(in))
{
/* Break the line up into words */
token = strtok(line, delimiters);
while (token != NULL)
{
puts(token);
/* Get the next word */
token = strtok(NULL, delimiters);
}
}
}
fclose(in);
return 0;
}
El programa anterior, en = fopen("C:\\text.txt", "r"), abre un archivo existente C:\\text.txt. Si no existe en la ruta especificada o por alguna razón, el archivo no se pudo abrir, se muestra un mensaje de error en la pantalla.
Considere el siguiente ejemplo, que utiliza algunas de estas funciones:
#include <stdio.h>
#include <string.h>
void main()
{
char line[100], *sub_text;
/* initialize string */
strcpy(line,"hello, I am a string;");
printf("Line: %s\n", line);
/* add to end of string */
strcat(line," what are you?");
printf("Line: %s\n", line);
/* find length of string */
/* strlen brings back */
/* length as type size_t */
printf("Length of line: %d\n", (int)strlen(line));
/* find occurence of substrings */
if ( (sub_text = strchr ( line, 'W' ) )!= NULL )
printf("String starting with \"W\" ->%s\n",
sub_text);
if ( ( sub_text = strchr ( line, 'w' ) )!= NULL )
printf("String starting with \"w\" ->%s\n",
sub_text);
if ( ( sub_text = strchr ( sub_text, 'u' ) )!= NULL )
printf("String starting with \"w\" ->%s\n",
sub_text);
}
La salida del programa se mostrará de la siguiente manera:
Line: hello, I am a string;
Line: hello, I am a string; what are you?
Length of line: 35
String starting with "w" ->what are you?
String starting with "w" ->u?
Funciones
La mejor manera de desarrollar y mantener un programa grande es construirlo a partir de piezas más pequeñas, cada una de las cuales es más fácil de administrar (una técnica a veces denominada Divide y vencerás). Las funciones permiten al programador modularizar el programa.
Las funciones permiten dividir programas complicados en pequeños bloques, cada uno de los cuales es más fácil de escribir, leer y mantener. Ya hemos encontrado la función main y hemos hecho uso de printf de la biblioteca estándar. Por supuesto, podemos crear nuestras propias funciones y archivos de encabezado. Una función tiene el siguiente diseño:
return-type function-name ( argument list if necessary )
{
local-declarations;
statements ;
return return-value;
}
Si se omite el tipo de retorno, el valor predeterminado de C es int. El valor de retorno debe ser del tipo declarado. Todas las variables declaradas dentro de las funciones se denominan variables locales, ya que solo se conocen en la función para la que se han definido.
Algunas funciones tienen una lista de parámetros que proporciona un método de comunicación entre la función y el módulo que llamó a la función. Los parámetros también son variables locales, ya que no están disponibles fuera de la función. Todos los programas cubiertos hasta ahora tienen main, que es una función.
Una función puede simplemente realizar una tarea sin devolver ningún valor, en cuyo caso tiene el siguiente diseño:
void function-name ( argument list if necessary )
{
local-declarations ;
statements;
}
Los argumentos siempre se pasan por valor en las llamadas a funciones de C. Esto significa que se pasan copias locales de los valores de los argumentos a las rutinas. Cualquier cambio realizado en los argumentos internamente en la función se realiza solo en las copias locales de los argumentos.
Para cambiar o definir un argumento en la lista de argumentos, este argumento debe pasarse como una dirección. Utiliza variables regulares si la función no cambia los valores de esos argumentos. DEBE usar punteros si la función cambia los valores de esos argumentos.
Aprendamos con ejemplos:
#include <stdio.h>
void exchange ( int *a, int *b )
{
int temp;
temp = *a;
*a = *b;
*b = temp;
printf(" From function exchange: ");
printf("a = %d, b = %d\n", *a, *b);
}
void main()
{
int a, b;
a = 5;
b = 7;
printf("From main: a = %d, b = %d\n", a, b);
exchange(&a, &b);
printf("Back in main: ");
printf("a = %d, b = %d\n", a, b);
}
Y la salida de este programa se mostrará de la siguiente manera:
From main: a = 5, b = 7
From function exchange: a = 7, b = 5
Back in main: a = 7, b = 5
Veamos otro ejemplo. El siguiente ejemplo utiliza una función llamada cuadrado que escribe el cuadrado de los números entre 1 y 10.
#include <stdio.h>
int square(int x); /* Function prototype */
int main()
{
int counter;
for (counter=1; counter<=10; counter++)
printf("Square of %d is %d\n", counter, square(counter));
return 0;
}
/* Define the function 'square' */
int square(int x)
{
return x * x;
}
La salida de este programa se mostrará de la siguiente manera:
Square of 1 is 1
Square of 2 is 4
Square of 3 is 9
Square of 4 is 16
Square of 5 is 25
Square of 6 is 36
Square of 7 is 49
Square of 8 is 64
Square of 9 is 81
Square of 10 is 100
La función prototipo square declara una función que toma un parámetro entero y devuelve un entero. Cuando el compilador llega a la llamada de función para cuadrar en el programa principal, puede comparar la llamada de función con la definición de la función.
Cuando el programa llega a la línea que llama al cuadrado de función, el programa salta a la función y la ejecuta antes de reanudar su camino a través del programa principal. Los programas que no tienen un tipo de devolución deben declararse usando void. Por lo tanto, los parámetros de la función pueden ser Pasar por valor o Pasar por referencia.
Una función recursiva es una función que se llama a sí misma. Y este proceso se llama recursividad.
Pasar por funciones de valor
Los parámetros de la función cuadrada del ejemplo anterior se pasan por valor. Esto significa que solo se ha pasado una copia de la variable a la función. Cualquier cambio en el valor no se reflejará en la función de llamada.
El siguiente ejemplo usa pass-by-value y cambia el valor del parámetro pasado, lo que no tiene efecto en la función de llamada. La función count_down se ha declarado nula ya que no hay ningún tipo de devolución.
#include <stdio.h>
void count_down(int x);
int main()
{
int counter;
for (counter=1; counter<=10; counter++)
count_down(counter);
return 0;
}
void count_down(int x)
{
int counter;
for (counter = x; counter > 0; counter--)
{
printf("%d ", x);
x--;
}
putchar('\n');
}
La salida del programa se mostrará de la siguiente manera:
1
2 1
3 2 1
4 3 2 1
5 4 3 2 1
6 5 4 3 2 1
7 6 5 4 3 2 1
8 7 6 5 4 3 2 1
9 8 7 6 5 4 3 2 1
10 9 8 7 6 5 4 3 2 1
Veamos otro ejemplo de paso por valor de C para entenderlo mejor. El siguiente ejemplo convierte un número entre 1 y 30 000 escrito por el usuario en palabras.
#include <stdio.h>
void do_units(int num);
void do_tens(int num);
void do_teens(int num);
int main()
{
int num, residue;
do
{
printf("Enter a number between 1 and 30,000: ");
scanf("%d", &num);
} while (num < 1 || num > 30000);
residue = num;
printf("%d in words = ", num);
do_tens(residue/1000);
if (num >= 1000)
printf("thousand ");
residue %= 1000;
do_units(residue/100);
if (residue >= 100)
{
printf("hundred ");
}
if (num > 100 && num%100 > 0)
printf("and ");
residue %=100;
do_tens(residue);
putchar('\n');
return 0;
}
void do_units(int num)
{
switch(num)
{
case 1:
printf("one ");
break;
case 2:
printf("two ");
break;
case 3:
printf("three ");
break;
case 4:
printf("four ");
break;
case 5:
printf("five ");
break;
case 6:
printf("six ");
break;
case 7:
printf("seven ");
break;
case 8:
printf("eight ");
break;
case 9:
printf("nine ");
}
}
void do_tens(int num)
{
switch(num/10)
{
case 1:
do_teens(num);
break;
case 2:
printf("twenty ");
break;
case 3:
printf("thirty ");
break;
case 4:
printf("forty ");
break;
case 5:
printf("fifty ");
break;
case 6:
printf("sixty ");
break;
case 7:
printf("seventy ");
break;
case 8:
printf("eighty ");
break;
case 9:
printf("ninety ");
}
if (num/10 != 1)
do_units(num%10);
}
void do_teens(int num)
{
switch(num)
{
case 10:
printf("ten ");
break;
case 11:
printf("eleven ");
break;
case 12:
printf("twelve ");
break;
case 13:
printf("thirteen ");
break;
case 14:
printf("fourteen ");
break;
case 15:
printf("fifteen ");
break;
case 16:
printf("sixteen ");
break;
case 17:
printf("seventeen ");
break;
case 18:
printf("eighteen ");
break;
case 19:
printf("nineteen ");
}
}
y la salida del programa será la siguiente:
Enter a number between 1 and 30,000: 12345
12345 in words = twelve thousand three hundred and forty five
Llamada por referencia
Para hacer una llamada de función por referencia, en lugar de pasar la variable en sí, pase la dirección de la variable. La dirección de la variable se puede tomar usando el comando & operador. Lo siguiente llama a una función de intercambio que pasa la dirección de las variables en lugar de los valores reales.
swap(&x, &y);
Desreferenciación
El problema que tenemos ahora es que a la función de intercambio se le ha pasado la dirección en lugar de la variable, por lo que necesitamos desreferenciar las variables para mirar los valores reales en lugar de las direcciones de las variables para intercambiar ellos.
La desreferenciación se logra en C mediante el uso de la notación de puntero (*). En términos simples, esto significa colocar un * antes de cada variable antes de usarla para que se refiera al valor de la variable en lugar de a su dirección. El siguiente programa ilustra el paso por referencia para intercambiar dos valores.
#include <stdio.h>
void swap(int *x, int *y);
int main()
{
int x=6, y=10;
printf("Before the function swap, x = %d and y =
%d\n\n", x, y);
swap(&x, &y);
printf("After the function swap, x = %d and y =
%d\n\n", x, y);
return 0;
}
void swap(int *x, int *y)
{
int temp = *x;
*x = *y;
*y = temp;
}
Veamos la salida del programa:
Before the function swap, x = 6 and y = 10
After the function swap, x = 10 and y = 6
Las funciones pueden ser recursivas, es decir, una función puede llamarse a sí misma. Cada llamada a sí misma requiere que el estado actual de la función se coloque en la pila. Es importante recordar este hecho, ya que es fácil crear un desbordamiento de pila, es decir, la pila se ha quedado sin espacio para colocar más datos.
El siguiente ejemplo calcula el Factorial de un número usando recursividad. Un factorial es un número multiplicado por todos los demás enteros debajo de sí mismo, hasta 1. Por ejemplo, el factorial del número 6 es:
Factorial 6 = 6 * 5 * 4 * 3 * 2 * 1
Por lo tanto, el factorial de 6 es 720. En el ejemplo anterior se puede ver que factorial 6 = 6 * factorial 5. De manera similar, factorial 5 = 5 * factorial 4, y así sucesivamente.
La siguiente es la regla general para calcular números factoriales.
factorial(n) = n * factorial(n-1)
La regla anterior termina cuando n = 1, ya que el factorial de 1 es 1. Tratemos de entenderlo mejor con la ayuda de un ejemplo:
#include <stdio.h>
long int factorial(int num);
int main()
{
int num;
long int f;
printf("Enter a number: ");
scanf("%d", &num);
f = factorial(num);
printf("factorial of %d is %ld\n", num, f);
return 0;
}
long int factorial(int num)
{
if (num == 1)
return 1;
else
return num * factorial(num-1);
}
Veamos el resultado de la ejecución de este programa:
Enter a number: 7
factorial of 7 is 5040
Asignación de memoria en C
El compilador de C tiene una biblioteca de asignación de memoria, definida en malloc.h. La memoria se reserva mediante la función malloc y devuelve un puntero a la dirección. Toma un parámetro, el tamaño de la memoria requerida en bytes.
El siguiente ejemplo asigna espacio para la cadena "hola mundo".
ptr = (char *)malloc(strlen("Hello world") + 1);
Se requiere el byte adicional para tener en cuenta el carácter de terminación de cadena, '\0'. El (char *) se denomina conversión y obliga al tipo de retorno a ser char *.
Como los tipos de datos tienen diferentes tamaños y malloc devuelve el espacio en bytes, es una buena práctica por razones de portabilidad usar el operador sizeof al especificar un tamaño para asignar.
El siguiente ejemplo lee una cadena en el búfer de matriz de caracteres y luego asigna la cantidad exacta de memoria requerida y la copia en una variable llamada "ptr".
#include <string.h>
#include <malloc.h>
int main()
{
char *ptr, buffer[80];
printf("Enter a string: ");
gets(buffer);
ptr = (char *)malloc((strlen(buffer) + 1) *
sizeof(char));
strcpy(ptr, buffer);
printf("You entered: %s\n", ptr);
return 0;
}
La salida del programa será la siguiente:
Enter a string: India is the best
You entered: India is the best
Reasignación de memoria
Es posible que muchas veces, mientras está programando, desee reasignar memoria. Esto se hace con la función realloc. La función realloc toma dos parámetros, la dirección base de la memoria que desea cambiar el tamaño y la cantidad de espacio que desea reservar y devuelve un puntero a la dirección base.
Supongamos que hemos reservado espacio para un puntero llamado msg y queremos reasignar espacio a la cantidad de espacio que ya ocupa, más la longitud de otra cadena, entonces podríamos usar lo siguiente.
msg = (char *)realloc(msg, (strlen(msg) + strlen(buffer) + 1)*sizeof(char));
El siguiente programa ilustra el uso de malloc, realloc y free. El usuario ingresa una serie de cadenas que se unen. El programa deja de leer cadenas cuando se ingresa una cadena vacía.
#include <string.h>
#include <malloc.h>
int main()
{
char buffer[80], *msg;
int firstTime=0;
do
{
printf("\nEnter a sentence: ");
gets(buffer);
if (!firstTime)
{
msg = (char *)malloc((strlen(buffer) + 1) *
sizeof(char));
strcpy(msg, buffer);
firstTime = 1;
}
else
{
msg = (char *)realloc(msg, (strlen(msg) +
strlen(buffer) + 1) * sizeof(char));
strcat(msg, buffer);
}
puts(msg);
} while(strcmp(buffer, ""));
free(msg);
return 0;
}
La salida del programa será la siguiente:
Enter a sentence: Once upon a time
Once upon a time
Enter a sentence: there was a king
Once upon a timethere was a king
Enter a sentence: the king was
Once upon a timethere was a kingthe king was
Enter a sentence:
Once upon a timethere was a kingthe king was
Liberando la memoria
Cuando haya terminado con la memoria asignada, nunca debe olvidar liberar la memoria, ya que liberará recursos y mejorará la velocidad. Para liberar la memoria asignada, use la función libre.
free(ptr);
Estructuras
Además de los tipos de datos básicos, C tiene un mecanismo de estructura que le permite agrupar elementos de datos que están relacionados entre sí bajo un nombre común. Esto se conoce comúnmente como un tipo definido por el usuario.
La palabra clave struct inicia la definición de la estructura y una etiqueta le da el nombre único a la estructura. Los tipos de datos y nombres de variables agregados a la estructura son miembros de la estructura. El resultado es una plantilla de estructura que se puede utilizar como especificador de tipo. La siguiente es una estructura con una etiqueta de mes.
struct month
{
char name[10];
char abbrev[4];
int days;
};
Un tipo de estructura generalmente se define cerca del inicio de un archivo usando una declaración typedef. typedef define y nombra un nuevo tipo, permitiendo su uso en todo el programa. typedef generalmente ocurre justo después de las declaraciones #define e #include en un archivo.
La palabra clave typedef se puede usar para definir una palabra para hacer referencia a la estructura en lugar de especificar la palabra clave struct con el nombre de la estructura. Es habitual nombrar el typedef en mayúsculas. Aquí están los ejemplos de definición de estructura.
typedef struct {
char name[64];
char course[128];
int age;
int year;
} student;
Esto define un nuevo tipo de estudiante. Las variables de tipo estudiante se pueden declarar de la siguiente manera.
student st_rec;
Observe cuán similar es esto a declarar un int o float. El nombre de la variable es st_rec, tiene miembros llamados nombre, curso, edad y año. Similar,
typedef struct element
{
char data;
struct element *next;
} STACKELEMENT;
A variable of the user defined type struct element may now be declared as follows.
STACKELEMENT *stack;
Considere la siguiente estructura:
struct student
{
char *name;
int grade;
};
Un apuntador a struct student se puede definir de la siguiente manera.
struct student *hnc;
When accessing a pointer to a structure, the member pointer operator, -> is used instead of the dot operator. To add a grade to a structure,
s.grade = 50;
Puede asignar una calificación a la estructura de la siguiente manera.
s->grade = 50;
Al igual que con los tipos de datos básicos, si desea que los cambios realizados en una función a los parámetros pasados sean persistentes, debe pasar por referencia (pasar la dirección). El mecanismo es exactamente el mismo que los tipos de datos básicos. Pase la dirección y haga referencia a la variable usando la notación de puntero.
Habiendo definido la estructura, puede declarar una instancia de ella y asignar valores a los miembros usando la notación de puntos. El siguiente ejemplo ilustra el uso de la estructura de mes.
#include <stdio.h>
#include <string.h>
struct month
{
char name[10];
char abbreviation[4];
int days;
};
int main()
{
struct month m;
strcpy(m.name, "January");
strcpy(m.abbreviation, "Jan");
m.days = 31;
printf("%s is abbreviated as %s and has %d days\n", m.name, m.abbreviation, m.days);
return 0;
}
La salida del programa será la siguiente:
January is abbreviated as Jan and has 31 days
Todos los compiladores ANSI C le permiten asignar una estructura a otra, realizando una copia por miembro. Si tuviéramos estructuras de mes llamadas m1 y m2, entonces podríamos asignar los valores de m1 a m2 con lo siguiente:
- Estructura con miembros de puntero.
- La estructura se inicializa.
- Pasar una estructura a una función.
- Punteros y Estructuras.
Estructuras con Pointer Members en C
Mantener cadenas en una matriz de tamaño fijo es un uso ineficiente de la memoria. Un enfoque más eficiente sería utilizar punteros. Los punteros se usan en estructuras exactamente de la misma manera que se usan en definiciones de punteros normales. Veamos un ejemplo:
#include <string.h>
#include <malloc.h>
struct month
{
char *name;
char *abbreviation;
int days;
};
int main()
{
struct month m;
m.name = (char *)malloc((strlen("January")+1) *
sizeof(char));
strcpy(m.name, "January");
m.abbreviation = (char *)malloc((strlen("Jan")+1) *
sizeof(char));
strcpy(m.abbreviation, "Jan");
m.days = 31;
printf("%s is abbreviated as %s and has %d days\n",
m.name, m.abbreviation, m.days);
return 0;
}
La salida del programa será la siguiente:
January is abbreviated as Jan and has 31 days
Inicializadores de estructura en C
Para proporcionar un conjunto de valores iniciales para la estructura, se pueden agregar inicializadores a la declaración de declaración. Como los meses comienzan en 1, pero las matrices comienzan en cero en C, en el siguiente ejemplo se ha utilizado un elemento adicional en la posición cero llamado basura.
#include <stdio.h>
#include <string.h>
struct month
{
char *name;
char *abbreviation;
int days;
} month_details[] =
{
"Junk", "Junk", 0,
"January", "Jan", 31,
"February", "Feb", 28,
"March", "Mar", 31,
"April", "Apr", 30,
"May", "May", 31,
"June", "Jun", 30,
"July", "Jul", 31,
"August", "Aug", 31,
"September", "Sep", 30,
"October", "Oct", 31,
"November", "Nov", 30,
"December", "Dec", 31
};
int main()
{
int counter;
for (counter=1; counter<=12; counter++)
printf("%s is abbreviated as %s and has %d days\n",
month_details[counter].name,
month_details[counter].abbreviation,
month_details[counter].days);
return 0;
}
Y la salida se mostrará de la siguiente manera:
January is abbreviated as Jan and has 31 days
February is abbreviated as Feb and has 28 days
March is abbreviated as Mar and has 31 days
April is abbreviated as Apr and has 30 days
May is abbreviated as May and has 31 days
June is abbreviated as Jun and has 30 days
July is abbreviated as Jul and has 31 days
August is abbreviated as Aug and has 31 days
September is abbreviated as Sep and has 30 days
October is abbreviated as Oct and has 31 days
November is abbreviated as Nov and has 30 days
December is abbreviated as Dec and has 31 days
Pasar Estructuras a Funciones en C
Las estructuras se pueden pasar como un parámetro a una función, al igual que cualquiera de los tipos de datos básicos. El siguiente ejemplo usa una estructura llamada fecha que se pasa a una función esAñoBisiesto para determinar si el año es un año bisiesto.
Normalmente, solo pasaría el valor del día, pero se pasa toda la estructura para ilustrar el paso de estructuras a funciones.
#include <stdio.h>
#include <string.h>
struct month
{
char *name;
char *abbreviation;
int days;
} month_details[] =
{
"Junk", "Junk", 0,
"January", "Jan", 31,
"February", "Feb", 28,
"March", "Mar", 31,
"April", "Apr", 30,
"May", "May", 31,
"June", "Jun", 30,
"July", "Jul", 31,
"August", "Aug", 31,
"September", "Sep", 30,
"October", "Oct", 31,
"November", "Nov", 30,
"December", "Dec", 31
};
struct date
{
int day;
int month;
int year;
};
int isLeapYear(struct date d);
int main()
{
struct date d;
printf("Enter the date (eg: 11/11/1980): ");
scanf("%d/%d/%d", &d.day, &d.month, &d.year);
printf("The date %d %s %d is ", d.day,
month_details[d.month].name, d.year);
if (isLeapYear(d) == 0)
printf("not ");
puts("a leap year");
return 0;
}
int isLeapYear(struct date d)
{
if ((d.year % 4 == 0 && d.year % 100 != 0) ||
d.year % 400 == 0)
return 1;
return 0;
}
Y la Ejecución del programa será la siguiente:
Enter the date (eg: 11/11/1980): 9/12/1980
The date 9 December 1980 is a leap year
El siguiente ejemplo asigna dinámicamente una matriz de estructuras para almacenar los nombres y calificaciones de los estudiantes. Luego, las calificaciones se muestran al usuario en orden ascendente.
#include <string.h>
#include <malloc.h>
struct student
{
char *name;
int grade;
};
void swap(struct student *x, struct student *y);
int main()
{
struct student *group;
char buffer[80];
int spurious;
int inner, outer;
int counter, numStudents;
printf("How many students are there in the group: ");
scanf("%d", &numStudents);
group = (struct student *)malloc(numStudents *
sizeof(struct student));
for (counter=0; counter<numStudents; counter++)
{
spurious = getchar();
printf("Enter the name of the student: ");
gets(buffer);
group[counter].name = (char *)malloc((strlen(buffer)+1) * sizeof(char));
strcpy(group[counter].name, buffer);
printf("Enter grade: ");
scanf("%d", &group[counter].grade);
}
for (outer=0; outer<numStudents; outer++)
for (inner=0; inner<outer; inner++)
if (group[outer].grade <
group[inner].grade)
swap(&group[outer], &group[inner]);
puts("The group in ascending order of grades ...");
for (counter=0; counter<numStudents; counter++)
printf("%s achieved Grade %d \n”,
group[counter].name,
group[counter].grade);
return 0;
}
void swap(struct student *x, struct student *y)
{
struct student temp;
temp.name = (char *)malloc((strlen(x->name)+1) *
sizeof(char));
strcpy(temp.name, x->name);
temp.grade = x->grade;
x->grade = y->grade;
x->name = (char *)malloc((strlen(y->name)+1) *
sizeof(char));
strcpy(x->name, y->name);
y->grade = temp.grade;
y->name = (char *)malloc((strlen(temp.name)+1) *
sizeof(char));
strcpy(y->name, temp.name);
}
La ejecución de la salida será la siguiente:
How many students are there in the group: 4
Enter the name of the student: Anuraaj
Enter grade: 7
Enter the name of the student: Honey
Enter grade: 2
Enter the name of the student: Meetushi
Enter grade: 1
Enter the name of the student: Deepti
Enter grade: 4
The group in ascending order of grades ...
Meetushi achieved Grade 1
Honey achieved Grade 2
Deepti achieved Grade 4
Anuraaj achieved Grade 7
Unión
Una unión le permite ver los mismos datos con diferentes tipos, o usar los mismos datos con diferentes nombres. Las uniones son similares a las estructuras. Una unión se declara y se usa de la misma manera que una estructura.
Una unión se diferencia de una estructura en que solo se puede usar uno de sus miembros a la vez. La razón de esto es simple. Todos los miembros de un sindicato ocupan la misma área de memoria. Se colocan uno encima del otro.
Las uniones se definen y declaran de la misma manera que las estructuras. La única diferencia en las declaraciones es que se utiliza la palabra clave union en lugar de struct. Para definir una unión simple de una variable char y una variable entera, escribiría lo siguiente:
union shared {
char c;
int i;
};
Esta unión, compartida, se puede usar para crear instancias de una unión que puede contener un valor de carácter c o un valor entero i. Esta es una condición OR. A diferencia de una estructura que mantendría ambos valores, la unión solo puede contener un valor a la vez.
Una unión se puede inicializar en su declaración. Porque solo se puede usar un miembro a la vez y solo se puede inicializar uno. Para evitar confusiones, solo se puede inicializar el primer miembro de la unión. El siguiente código muestra una instancia de la unión compartida que se declara e inicializa:
unión compartida generic_variable = {`@'};
Observe que la unión de variable_genérica se inicializó de la misma manera que se inicializaría el primer miembro de una estructura.
Los miembros de unión individuales se pueden usar de la misma manera que los miembros de estructura se pueden usar usando el operador miembro (.). Sin embargo, existe una diferencia importante en el acceso a los miembros del sindicato.
Solo se debe acceder a un miembro del sindicato a la vez. Debido a que un sindicato almacena a sus miembros uno encima del otro, es importante acceder solo a un miembro a la vez.
The union Keyword
union tag {
union_member(s);
/* additional statements may go here */
}instance;
La palabra clave union se utiliza para declarar uniones. Una unión es una colección de una o más variables (union_members) que se han agrupado bajo un solo nombre. Además, cada uno de estos miembros del sindicato ocupa la misma área de memoria.
La palabra clave unión identifica el comienzo de una definición de unión. Le sigue una etiqueta que es el nombre que se le da al sindicato. Después de la etiqueta están los miembros de la unión encerrados entre llaves.
También se puede definir una instancia, la declaración real de una unión. Si define la estructura sin la instancia, es solo una plantilla que se puede usar más tarde en un programa para declarar estructuras. El siguiente es el formato de una plantilla:
union tag {
union_member(s);
/* additional statements may go here */
};
Para usar la plantilla, usaría el siguiente formato:
union tag instance;
Para usar este formato, debe haber declarado previamente una unión con la etiqueta dada.
/* Declare a union template called tag */
union tag {
int num;
char alps;
}
/* Use the union template */
union tag mixed_variable;
/* Declare a union and instance together */
union generic_type_tag {
char c;
int i;
float f;
double d;
} generic;
/* Initialize a union. */
union date_tag {
char full_date[9];
struct part_date_tag {
char month[2];
char break_value1;
char day[2];
char break_value2;
char year[2];
} part_date;
}date = {"09/12/80"};
Entendámoslo mejor con la ayuda de ejemplos:
#include <stdio.h>
int main()
{
union
{
int value; /* This is the first part of the union */
struct
{
char first; /* These two values are the second part of it */
char second;
} half;
} number;
long index;
for (index = 12 ; index < 300000L ; index += 35231L)
{
number.value = index;
printf("%8x %6x %6x\n", number.value,
number.half.first,
number.half.second);
}
return 0;
}
Y la salida del programa se mostrará de la siguiente manera:
c c 0
89ab ffab ff89
134a 4a 13
9ce9 ffe9 ff9c
2688 ff88 26
b027 27 ffb0
39c6 ffc6 39
c365 65 ffc3
4d04 4 4d
Un uso práctico de una unión en la recuperación de datos
Ahora veamos un uso práctico de union en la programación de recuperación de datos. Tomemos un pequeño ejemplo. El siguiente programa es el modelo pequeño del programa de escaneo de sectores defectuosos para una unidad de disquete (a: ), sin embargo, no es el modelo completo del software de escaneo de sectores defectuosos.
Examinemos el programa:
#include<dos.h>
#include<conio.h>
int main()
{
int rp, head, track, sector, status;
char *buf;
union REGS in, out;
struct SREGS s;
clrscr();
/* Reset the disk system to initialize to disk */
printf("\n Resetting the disk system....");
for(rp=0;rp<=2;rp++)
{
in.h.ah = 0;
in.h.dl = 0x00;
int86(0x13,&in,&out);
}
printf("\n\n\n Now Testing the Disk for Bad Sectors....");
/* scan for bad sectors */
for(track=0;track<=79;track++)
{
for(head=0;head<=1;head++)
{
for(sector=1;sector<=18;sector++)
{
in.h.ah = 0x04;
in.h.al = 1;
in.h.dl = 0x00;
in.h.ch = track;
in.h.dh = head;
in.h.cl = sector;
in.x.bx = FP_OFF(buf);
s.es = FP_SEG(buf);
int86x(0x13,&in,&out,&s);
if(out.x.cflag)
{
status=out.h.ah;
printf("\n track:%d Head:%d Sector:%d Status ==0x%X",track,head,sector,status);
}
}
}
}
printf("\n\n\nDone");
return 0;
}
Ahora veamos cómo se verá su salida si hay un sector defectuoso en el disquete:
Restableciendo el sistema de disco....
Ahora probando el disco en busca de sectores defectuosos....
track:0 Head:0 Sector:4 Status ==0xA
track:0 Head:0 Sector:5 Status ==0xA
track:1 Head:0 Sector:4 Status ==0xA
track:1 Head:0 Sector:5 Status ==0xA
track:1 Head:0 Sector:6 Status ==0xA
track:1 Head:0 Sector:7 Status ==0xA
track:1 Head:0 Sector:8 Status ==0xA
track:1 Head:0 Sector:11 Status ==0xA
track:1 Head:0 Sector:12 Status ==0xA
track:1 Head:0 Sector:13 Status ==0xA
track:1 Head:0 Sector:14 Status ==0xA
track:1 Head:0 Sector:15 Status ==0xA
track:1 Head:0 Sector:16 Status ==0xA
track:1 Head:0 Sector:17 Status ==0xA
track:1 Head:0 Sector:18 Status ==0xA
track:1 Head:1 Sector:5 Status ==0xA
track:1 Head:1 Sector:6 Status ==0xA
track:1 Head:1 Sector:7 Status ==0xA
track:1 Head:1 Sector:8 Status ==0xA
track:1 Head:1 Sector:9 Status ==0xA
track:1 Head:1 Sector:10 Status ==0xA
track:1 Head:1 Sector:11 Status ==0xA
track:1 Head:1 Sector:12 Status ==0xA
track:1 Head:1 Sector:13 Status ==0xA
track:1 Head:1 Sector:14 Status ==0xA
track:1 Head:1 Sector:15 Status ==0xA
track:1 Head:1 Sector:16 Status ==0xA
track:1 Head:1 Sector:17 Status ==0xA
track:1 Head:1 Sector:18 Status ==0xA
track:2 Head:0 Sector:4 Status ==0xA
track:2 Head:0 Sector:5 Status ==0xA
track:14 Head:0 Sector:6 Status ==0xA
Done
Puede ser un poco difícil entender las funciones e interrupciones utilizadas en este programa para verificar el disco en busca de sectores defectuosos y restablecer el sistema del disco, etc. pero no debe preocuparse, vamos a aprender todas estas cosas en BIOS y programación de interrupciones. secciones más adelante en los próximos capítulos.
Manejo de archivos en C
El acceso a archivos en C se logra asociando una secuencia con un archivo. C se comunica con archivos utilizando un nuevo tipo de datos llamado puntero de archivo. Este tipo se define dentro de stdio.h y se escribe como ARCHIVO *. Un puntero de archivo llamado archivo_salida se declara en una declaración como
FILE *output_file;
Los modos de archivo de la función fopen
Su programa debe abrir un archivo antes de poder acceder a él. Esto se hace usando la función fopen, que devuelve el puntero de archivo requerido. Si el archivo no se puede abrir por algún motivo, se devolverá el valor NULL. Por lo general, usará fopen de la siguiente manera
if ((output_file = fopen("output_file", "w")) == NULL)
fprintf(stderr, "Cannot open %s\n",
"output_file");
fopen takes two arguments, both are strings, the first is the name of the file to be opened, the second is an access character, which is usually one of r, a or w etc. Files may be opened in a number of modes, as shown in the following table.
Modos de archivo |
r |
Abrir un archivo de texto para leer. |
w |
Cree un archivo de texto para escribir. Si el archivo existe, se sobrescribe. |
a |
Abrir un archivo de texto en modo de adición. El texto se agrega al final del archivo. |
rb |
Abrir un archivo binario para leer. |
wb |
Cree un archivo binario para escribir. Si el archivo existe, se sobrescribe. |
ab |
Abrir un archivo binario en modo de adición. Los datos se agregan al final del archivo. |
r+ |
Abrir un archivo de texto para leer y escribir. |
w+ |
Cree un archivo de texto para leer y escribir. Si el archivo existe, se sobrescribe. |
a+ |
Abrir un archivo de texto para leer y escribir al final. |
r+b o rb+ |
Abrir archivo binario para lectura y escritura. |
w+b o wb+ |
Cree un archivo binario para lectura y escritura. Si el archivo existe, se sobrescribe. |
a+b o ab+ |
Abra un archivo de texto para leer y escribir al final. |
Los modos de actualización se utilizan con las funciones fseek, fsetpos y rewind. La función fopen devuelve un puntero de archivo o NULL si se produce un error.
El siguiente ejemplo abre un archivo, tarun.txt en modo de solo lectura. Es una buena práctica de programación probar que el archivo existe.
if ((in = fopen("tarun.txt", "r")) == NULL)
{
puts("Unable to open the file");
return 0;
}
Cerrar archivos
Los archivos se cierran usando la función fclose. La sintaxis es la siguiente:
fclose(in);
Lectura de archivos
La función feof se usa para probar el final del archivo. Las funciones fgetc, fscanf y fgets se utilizan para leer datos del archivo.
El siguiente ejemplo enumera el contenido de un archivo en la pantalla, usando fgetc para leer el archivo un carácter a la vez.
#include <stdio.h>
int main()
{
FILE *in;
int key;
if ((in = fopen("tarun.txt", "r")) == NULL)
{
puts("Unable to open the file");
return 0;
}
while (!feof(in))
{
key = fgetc(in);
/* The last character read is the end of file marker so don't print it */
if (!feof(in))
putchar(key);
}
fclose(in);
return 0;
}
La función fscanf se puede usar para leer diferentes tipos de datos del archivo como en el siguiente ejemplo, siempre que los datos del archivo estén en el formato de la cadena de formato que se usa con fscanf.
fscanf(in, "%d/%d/%d", &day, &month, &year);
La función fgets se usa para leer una cantidad de caracteres de un archivo. stdin es el flujo de archivo de entrada estándar, y la función fgets se puede usar para controlar la entrada.
Escribir en archivos
Los datos se pueden escribir en el archivo usando fputc y fprintf. El siguiente ejemplo usa las funciones fgetc y fputc para hacer una copia de un archivo de texto.
#include <stdio.h>
int main()
{
FILE *in, *out;
int key;
if ((in = fopen("tarun.txt", "r")) == NULL)
{
puts("Unable to open the file");
return 0;
}
out = fopen("copy.txt", "w");
while (!feof(in))
{
key = fgetc(in);
if (!feof(in))
fputc(key, out);
}
fclose(in);
fclose(out);
return 0;
}
La función fprintf se puede utilizar para escribir datos formateados en un archivo.
fprintf(out, "Date: %02d/%02d/%02d\n",
day, month, year);
Argumentos de línea de comando con C
La definición ANSI C para declarar la función main( ) es:
int main() or int main(int argc, char **argv)
La segunda versión permite pasar argumentos desde la línea de comandos. El parámetro argc es un contador de argumentos y contiene el número de parámetros pasados desde la línea de comando. El parámetro argv es el vector de argumento que es una matriz de punteros a cadenas que representan los parámetros reales pasados.
El siguiente ejemplo permite pasar cualquier número de argumentos desde la línea de comando y los imprime. argv[0] es el programa real. El programa debe ejecutarse desde un símbolo del sistema.
#include <stdio.h>
int main(int argc, char **argv)
{
int counter;
puts("The arguments to the program are:");
for (counter=0; counter<argc; counter++)
puts(argv[counter]);
return 0;
}
If the program name was count.c, it could be called as follows from the command line.
count 3
or
count 7
or
count 192 etc.
El siguiente ejemplo usa las rutinas de manejo de archivos para copiar un archivo de texto a un nuevo archivo. Por ejemplo, el argumento de la línea de comando podría llamarse como:
txtcpy one.txt two.txt
#include <stdio.h>
int main(int argc, char **argv)
{
FILE *in, *out;
int key;
if (argc < 3)
{
puts("Usage: txtcpy source destination\n");
puts("The source must be an existing file");
puts("If the destination file exists, it will be
overwritten");
return 0;
}
if ((in = fopen(argv[1], "r")) == NULL)
{
puts("Unable to open the file to be copied");
return 0;
}
if ((out = fopen(argv[2], "w")) == NULL)
{
puts("Unable to open the output file");
return 0;
}
while (!feof(in))
{
key = fgetc(in);
if (!feof(in))
fputc(key, out);
}
fclose(in);
fclose(out);
return 0;
}
Manipuladores bit a bit
A nivel de hardware, los datos se representan como números binarios. La representación binaria del número 59 es 111011. El bit 0 es el bit menos significativo y, en este caso, el bit 5 es el bit más significativo.
Cada conjunto de bits se calcula como 2 a la potencia del conjunto de bits. Los operadores bit a bit le permiten manipular variables enteras a nivel de bit. A continuación se muestra la representación binaria del número 59.
binary representation of the number 59 |
bit 5 4 3 2 1 0 |
2 power n 32 16 8 4 2 1 |
set 1 1 1 0 1 1 |
Con tres bits es posible representar los números del 0 al 7. La siguiente tabla muestra los números del 0 al 7 en su forma binaria.
Binary Digits |
000 |
0 |
001 |
1 |
010 |
2 |
011 |
3 |
100 |
4 |
101 |
5 |
110 |
6 |
111 |
7 |
La siguiente tabla enumera los operadores bit a bit que se pueden usar para manipular números binarios.
Binary Digits |
& |
Bitwise AND |
| |
Bitwise OR |
^ |
Bitwise Exclusive OR |
~ |
Bitwise Complement |
<< |
Bitwise Shift Left |
>> |
Bitwise Shift Right |
Bitwise AND
El AND bit a bit es verdadero solo si ambos bits están establecidos. El siguiente ejemplo muestra el resultado de un AND bit a bit en los números 23 y 12.
10111 (23)
01100 (12) AND
____________________
00100 (result = 4) |
Puede usar un valor de máscara para verificar si se han establecido ciertos bits. Si quisiéramos verificar si los bits 1 y 3 estaban configurados, podríamos enmascarar el número con 10 (el valor de los bits 1 y 3) y probar el resultado contra la máscara.
#include <stdio.h>
int main()
{
int num, mask = 10;
printf("Enter a number: ");
scanf("%d", &num);
if ((num & mask) == mask)
puts("Bits 1 and 3 are set");
else
puts("Bits 1 and 3 are not set");
return 0;
}
O bit a bit
El OR bit a bit es verdadero si cualquiera de los bits está establecido. A continuación se muestra el resultado de un OR bit a bit en los números 23 y 12.
10111 (23)
01100 (12) OR
______________________
11111 (result = 31) |
Puede usar una máscara para asegurarse de que se haya configurado un bit o bits. El siguiente ejemplo asegura que el bit 2 esté establecido.
#include <stdio.h>
int main()
{
int num, mask = 4;
printf("Enter a number: ");
scanf("%d", &num);
num |= mask;
printf("After ensuring bit 2 is set: %d\n", num);
return 0;
}
Exclusivo bit a bit O
El OR exclusivo bit a bit es verdadero si cualquiera de los bits está establecido, pero no ambos. A continuación se muestra el resultado de un OR exclusivo bit a bit en los números 23 y 12.
10111 (23)
01100 (12) Exclusive OR (XOR)
_____________________________
11011 (result = 27) |
El OR exclusivo tiene algunas propiedades interesantes. Si excluye O un número por sí mismo, se establece en cero, ya que los ceros seguirán siendo cero y los unos no se pueden establecer a la vez, por lo que se establecen en cero.
Como resultado de esto, si hace O exclusivo un número con otro número, luego O exclusivo el resultado con el otro número nuevamente, el resultado es el número original. Puede probar esto con los números utilizados en el ejemplo anterior.
23 XOR 12 = 27
27 XOR 12 = 23
27 XOR 23 = 12
Esta función se puede utilizar para el cifrado. El siguiente programa utiliza una clave de cifrado de 23 para ilustrar la propiedad en un número ingresado por el usuario.
#include <stdio.h>
int main()
{
int num, key = 23;
printf("Enter a number: ");
scanf("%d", &num);
num ^= key;
printf("Exclusive OR with %d gives %d\n", key, num);
num ^= key;
printf("Exclusive OR with %d gives %d\n", key, num);
return 0;
}
Complemento bit a bit
El Complemento bit a bit es un operador de complemento que activa o desactiva el bit. Si es 1, se establecerá en 0, si es 0, se establecerá en 1.
#include <stdio.h>
int main()
{
int num = 0xFFFF;
printf("The compliment of %X is %X\n", num, ~num);
return 0;
}
Desplazamiento bit a bit a la izquierda
El operador Desplazamiento a la izquierda bit a bit desplaza el número a la izquierda. Los bits más significativos se pierden a medida que el número se mueve hacia la izquierda y los bits menos significativos vacantes son cero. A continuación se muestra la representación binaria de 43.
0101011 (decimal 43)
Al desplazar los bits hacia la izquierda, perdemos el bit más significativo (en este caso, un cero) y el número se completa con un cero en el bit menos significativo. El siguiente es el número resultante.
1010110 (decimal 86)
Desplazamiento bit a bit a la derecha
El operador Desplazamiento a la derecha bit a bit desplaza el número a la derecha. Se introduce cero en los bits más significativos desocupados y los bits menos significativos desocupados se pierden. A continuación se muestra la representación binaria del número 43.
0101011 (decimal 43)
Al desplazar los bits hacia la derecha, perdemos el bit menos significativo (en este caso, un uno) y el número se completa con un cero en el bit más significativo. El siguiente es el número resultante.
0010101 (decimal 21)
El siguiente programa utiliza Bitwise Shift Right y Bitwise AND para mostrar un número como un número binario de 16 bits. El número se desplaza a la derecha sucesivamente desde 16 hasta cero y se hace AND bit a bit con 1 para ver si el bit está establecido. Un método alternativo sería utilizar máscaras sucesivas con el operador OR bit a bit.
#include <stdio.h>
int main()
{
int counter, num;
printf("Enter a number: ");
scanf("%d", &num);
printf("%d is binary: ", num);
for (counter=15; counter>=0; counter--)
printf("%d", (num >> counter) & 1);
putchar('\n');
return 0;
}
Funciones para Conversiones Binario – Decimal
Las dos funciones dadas a continuación son para la conversión de binario a decimal y de decimal a binario. La función dada a continuación para convertir un número decimal en el número binario correspondiente admite hasta 32 – Número binario de bits. Puede usar este o el programa proporcionado anteriormente para la conversión según sus requisitos.
Función para conversión de decimal a binario:
void Decimal_to_Binary(void)
{
int input =0;
int i;
int count = 0;
int binary [32]; /* 32 Bit, MAXIMUM 32 elements */
printf ("Enter Decimal number to convert into
Binary :");
scanf ("%d", &input);
do
{
i = input%2; /* MOD 2 to get 1 or a 0*/
binary[count] = i; /* Load Elements into the Binary Array */
input = input/2; /* Divide input by 2 to decrement via binary */
count++; /* Count how many elements are needed*/
}while (input > 0);
/* Reverse and output binary digits */
printf ("Binary representation is: ");
do
{
printf ("%d", binary[count - 1]);
count--;
} while (count > 0);
printf ("\n");
}
Función para la conversión de binario a decimal:
La siguiente función es para convertir cualquier número Binario a su correspondiente número Decimal:
void Binary_to_Decimal(void)
{
char binaryhold[512];
char *binary;
int i=0;
int dec = 0;
int z;
printf ("Please enter the Binary Digits.\n");
printf ("Binary digits are either 0 or 1 Only ");
printf ("Binary Entry : ");
binary = gets(binaryhold);
i=strlen(binary);
for (z=0; z<i; ++z)
{
dec=dec*2+(binary[z]=='1'? 1:0); /* if Binary[z] is
equal to 1,
then 1 else 0 */
}
printf ("\n");
printf ("Decimal value of %s is %d",
binary, dec);
printf ("\n");
}
Depuración y prueba
Errores de sintaxis
La sintaxis se refiere a la gramática, la estructura y el orden de los elementos en una declaración. Un error de sintaxis ocurre cuando rompemos las reglas, como olvidar terminar una declaración con un punto y coma. Cuando compila el programa, el compilador generará una lista de los errores de sintaxis que pueda encontrar.
Un buen compilador generará la lista con una descripción del error y puede proporcionar una posible solución. La corrección de los errores puede dar lugar a que se muestren más errores cuando se vuelva a compilar. La razón de esto es que los errores anteriores cambiaron la estructura del programa, lo que significa que se suprimieron más errores durante la compilación original.
Del mismo modo, un solo error puede dar lugar a varios errores. Intente poner un punto y coma al final de la función principal de un programa que compila y se ejecuta correctamente. Cuando lo vuelva a compilar, obtendrá una enorme lista de errores y, sin embargo, es solo un punto y coma fuera de lugar.
Además de los errores de sintaxis, los compiladores también pueden emitir advertencias. Una advertencia no es un error, pero puede causar problemas durante la ejecución de su programa. Por ejemplo, la asignación de un número de punto flotante de precisión doble a un número de punto flotante de precisión simple puede provocar una pérdida de precisión. No es un error de sintaxis, pero podría generar problemas. En este ejemplo en particular, podría mostrar la intención al convertir la variable al tipo de datos apropiado.
Considere el siguiente ejemplo donde x es un número de punto flotante de precisión simple e y es un número de punto flotante de precisión doble. y se convierte explícitamente en un flotante durante la asignación, lo que eliminaría cualquier advertencia del compilador.
x = (float)y;
Errores lógicos
Los errores lógicos ocurren cuando hay un error en la lógica. Por ejemplo, podría probar que un número es menor que 4 y mayor que 8. Eso posiblemente nunca sea cierto, pero si es sintácticamente correcto, el programa se compilará con éxito. Considere el siguiente ejemplo:
if (x < 4 && x > 8)
puts("Will never happen!");
La sintaxis es correcta, por lo que el programa se compilará, pero la instrucción puts nunca se imprimirá ya que el valor de x no podría ser menor que cuatro y mayor que ocho al mismo tiempo.
La mayoría de los errores lógicos se descubren durante la prueba inicial del programa. Cuando no se comporta como esperaba, inspecciona las declaraciones lógicas más de cerca y las corrige. Esto solo es cierto para errores lógicos obvios. Cuanto más grande sea el programa, más caminos habrá a través de él, más difícil será verificar que el programa se comporte como se esperaba.
Prueba
En el proceso de desarrollo de software, se pueden inyectar errores en cualquier etapa durante el desarrollo. Esto se debe a que los métodos de verificación de las primeras fases de desarrollo del software son manuales. Por lo tanto, es probable que el código desarrollado durante la actividad de codificación tenga algunos errores de requisitos y errores de diseño, además de los errores introducidos durante la actividad de codificación. Durante la prueba, el programa que se probará se ejecuta con un conjunto de casos de prueba, y la salida del programa para los casos de prueba se evalúa para determinar si la programación está funcionando como se espera.
Por lo tanto, la prueba es el proceso de analizar un elemento de software para detectar la diferencia entre las condiciones existentes y requeridas (es decir, errores) y para evaluar las características de los elementos de software. Entonces, Probar es el proceso de analizar un programa con la intención de encontrar errores.
Algunos principios de prueba
- Las pruebas no pueden mostrar la ausencia de defectos, solo su presencia.
- Cuanto antes se comete un error, más costoso es.
- Cuanto más tarde se detecte un error, más costoso será.
Ahora analicemos algunas técnicas de prueba:
Pruebas de caja blanca
La prueba de caja blanca es una técnica mediante la cual todas las rutas a través del programa se prueban con todos los valores posibles. Este enfoque requiere cierto conocimiento de cómo debe comportarse el programa. Por ejemplo, si su programa acepta un valor entero entre 1 y 50, una prueba de caja blanca probará el programa con los 50 valores para asegurarse de que sea correcto para cada uno, y luego probará cualquier otro valor posible que un entero pueda tomar y probará que se comportó como se esperaba. Teniendo en cuenta la cantidad de elementos de datos que puede tener un programa típico, las posibles permutaciones hacen que las pruebas de caja blanca sean extremadamente difíciles para programas grandes.
Las pruebas de caja blanca se pueden aplicar a funciones críticas de seguridad de un programa grande, y gran parte del resto se prueba mediante pruebas de caja negra, que se analizan a continuación. Debido a la cantidad de permutaciones, las pruebas de caja blanca generalmente se realizan utilizando un arnés de prueba, donde los rangos de valores se alimentan al programa rápidamente a través de un programa especial, registrando excepciones al comportamiento esperado. Las pruebas de caja blanca a veces se denominan pruebas estructurales, claras o de caja abierta.
Pruebas de caja negra
La prueba de caja negra es similar a la prueba de caja blanca, excepto que en lugar de probar todos los valores posibles, se prueban los valores seleccionados. En este tipo de prueba, el evaluador conoce las entradas y cuáles deberían ser los resultados esperados, pero no necesariamente cómo llegó el programa a ellos. Las pruebas de caja negra a veces se denominan pruebas funcionales.
Los casos de prueba para las pruebas de caja negra normalmente se diseñan tan pronto como se completan las especificaciones del programa. Los casos de prueba se basan en clases de equivalencia.
Clases de equivalencia
Para cada entrada, una clase de equivalencia identifica los estados válidos e inválidos. En general, hay tres escenarios para planificar al definir las clases de equivalencia.
Si la entrada especifica un rango o un valor específico, habrá un estado válido y dos estados no válidos definidos. Por ejemplo, si un número debe estar entre 1 y 20, el estado válido es entre 1 y 20, habrá un estado no válido para menos de 1 y un estado no válido para más de 20.
Si la entrada excluye un rango o un valor específico, se definirán dos estados válidos y un estado no válido. Por ejemplo, si un número no debe estar entre 1 y 20, los estados válidos son menor que uno y mayor que 20, y el estado no válido es entre 1 y 20.
Si la entrada especifica un valor booleano, solo habrá dos estados, uno válido y otro no válido.
Análisis de valor límite
El análisis de valor límite solo considera los valores en el límite de las entradas. Por ejemplo, en el caso de que un número esté entre 1 y 20, los casos de prueba pueden ser 1, 20, 0 y 21. El pensamiento detrás de esto es que si el programa funciona como se espera con estos valores, los otros valores también funcionarán. funciona como se esperaba.
La siguiente tabla ofrece una descripción general de los límites típicos que puede querer identificar.
Rangos de prueba |
Tipo de entrada |
Valores de prueba |
Range |
- x[lower_bound]-1
- x[lower_bound]
- x[upper_bound]
- x[upper_bound]+1
|
Boolean |
|
Elaboración de un plan de prueba
Identifique las clases de equivalencia y, para cada clase, identifique los límites. Habiendo identificado los límites de la clase, escriba una lista de valores válidos e inválidos en el límite y cuál debería ser el comportamiento esperado. Luego, el probador puede ejecutar el programa con los valores límite e indicar qué sucedió cuando se probó el valor límite con el resultado requerido.
El siguiente podría ser un plan de prueba típico que se usa para verificar si se ingresa una edad donde los valores aceptables están en el rango de 10 a 110.
Clase de equivalencia |
Válido |
No válido |
Between 10 and 110 |
> 110 |
|
< 10 |
Habiendo definido nuestra clase de equivalencia, ahora podemos idear un plan de prueba para la edad.
Plan de prueba |
Valor |
Estado |
Resultado esperado |
Resultado real |
10 |
Válido |
Continuar la ejecución para obtener el nombre |
|
110 |
Válido |
Continuar la ejecución para obtener el nombre |
|
9 |
Inválido |
Volver a preguntar la edad |
|
111 |
Inválido |
Volver a preguntar la edad |
|
El "Resultado real" La columna se deja en blanco, ya que se completará durante la prueba. Si el resultado es el esperado, se marcará la columna. De no ser así, se debe ingresar un comentario indicando lo ocurrido.
Página modificada el: 07/01/2022