stringtranslate.com

Sintaxis C

Un fragmento de código C que imprime "¡Hola, mundo!"

La sintaxis del lenguaje de programación C es el conjunto de reglas que rigen la escritura de software en C. Está diseñada para permitir programas que sean extremadamente concisos, tengan una relación estrecha con el código objeto resultante y, sin embargo, proporcionen una abstracción de datos de nivel relativamente alto . C fue el primer lenguaje de alto nivel ampliamente exitoso para el desarrollo de sistemas operativos portátiles .

La sintaxis de C hace uso del principio de munch máximo .

Estructuras de datos

Tipos de datos primitivos

El lenguaje de programación C representa números en tres formas: integral , real y complejo . Esta distinción refleja distinciones similares en la arquitectura del conjunto de instrucciones de la mayoría de las unidades centrales de procesamiento . Los tipos de datos integrales almacenan números en el conjunto de números enteros , mientras que los números reales y complejos representan números (o pares de números) en el conjunto de números reales en forma de punto flotante .

Todos los tipos enteros de C tienen signedy unsignedvariantes. Si signedo unsignedno se especifica explícitamente, en la mayoría de las circunstancias, signedse asume. Sin embargo, por razones históricas, plain chares un tipo distinto de signed chary unsigned char. Puede ser un tipo con signo o sin signo, según el compilador y el conjunto de caracteres (C garantiza que los miembros del conjunto de caracteres básico de C tienen valores positivos). Además, los tipos de campo de bits especificados como plain intpueden tener signo o sin signo, según el compilador.

Tipos enteros

Los tipos enteros de C vienen en diferentes tamaños fijos, capaces de representar varios rangos de números. El tipo charocupa exactamente un byte (la unidad de almacenamiento direccionable más pequeña), que normalmente tiene 8 bits de ancho. (Aunque charpuede representar cualquiera de los caracteres "básicos" de C, puede ser necesario un tipo más amplio para los conjuntos de caracteres internacionales). La mayoría de los tipos enteros tienen variedades con signo y sin signo , designadas por las palabras clave signedy unsigned. Los tipos enteros con signo siempre utilizan la representación de complemento a dos , desde C23 [1] (y en la práctica antes; en versiones anteriores de C23 la representación podría haber sido alternativamente complemento a uno o signo y magnitud , pero en la práctica ese no ha sido el caso durante décadas en el nardware moderno). En muchos casos, hay múltiples formas equivalentes de designar el tipo; por ejemplo, y son sinónimos.signed short intshort

La representación de algunos tipos puede incluir bits de "relleno" no utilizados, que ocupan espacio de almacenamiento pero no se incluyen en el ancho. La siguiente tabla proporciona una lista completa de los tipos de números enteros estándar y sus anchos mínimos permitidos (incluido cualquier bit de signo).

El chartipo es distinto de ambos signed chary unsigned char, pero se garantiza que tiene la misma representación que uno de ellos. Los tipos _Booly long longestán estandarizados desde 1999 y es posible que no sean compatibles con compiladores de C más antiguos. _BoolPor lo general, se accede al tipo a través del typedefnombre booldefinido por el encabezado estándar stdbool.h.

En general, los anchos y el esquema de representación implementados para cualquier plataforma dada se eligen en función de la arquitectura de la máquina, teniendo en cuenta la facilidad de importación del código fuente desarrollado para otras plataformas. El ancho del inttipo varía especialmente entre las implementaciones de C; a menudo corresponde al tamaño de palabra más "natural" para la plataforma específica. El encabezado estándar limits.h define macros para los valores mínimos y máximos representables de los tipos enteros estándar implementados en cualquier plataforma específica.

Además de los tipos de enteros estándar, puede haber otros tipos de enteros "extendido" que se pueden utilizar para typedefs en los encabezados estándar. Para una especificación más precisa del ancho, los programadores pueden y deben utilizar typedefs desde el encabezado estándar stdint.h .

Las constantes enteras se pueden especificar en el código fuente de varias maneras. Los valores numéricos se pueden especificar como decimales (ejemplo: 1022), octales con cero ( 0) como prefijo ( 01776) o hexadecimales con 0x(cero x) como prefijo ( 0x3FE). Un carácter entre comillas simples (ejemplo: 'R'), llamado "constante de carácter", representa el valor de ese carácter en el conjunto de caracteres de ejecución, con tipo int. A excepción de las constantes de caracteres, el tipo de una constante entera se determina por el ancho requerido para representar el valor especificado, pero siempre es al menos tan ancho como int. Esto se puede anular añadiendo un modificador explícito de longitud y/o signo; por ejemplo, 12lutiene tipo unsigned long. No hay constantes enteras negativas, pero a menudo se puede obtener el mismo efecto utilizando un operador de negación unario " -".

Tipo enumerado

El tipo enumerado en C, especificado con la palabraenum clave y a menudo llamado simplemente "enum" (generalmente se pronuncia /ˈiːnʌm/ EE -num o /ˈiːnuːm/ EE - noom ) , es un tipo diseñado para representar valores en una serie de constantes nombradas . Cada una de las constantes enumeradas tiene el tipo .int Cada enumtipo en sí es compatible con un tipochar entero con o sin signo, pero cada implementación define sus propias reglas para elegir un tipo.

Algunos compiladores advierten si a un objeto con un tipo enumerado se le asigna un valor que no es una de sus constantes. Sin embargo, a un objeto de este tipo se le puede asignar cualquier valor dentro del rango de su tipo compatible, y enumlas constantes se pueden utilizar en cualquier lugar donde se espere un entero. Por este motivo, enumlos valores se utilizan a menudo en lugar de directivas del preprocesador #definepara crear constantes con nombre. Estas constantes suelen ser más seguras de utilizar que las macros, ya que residen dentro de un espacio de nombres de identificador específico.

Un tipo enumerado se declara con el enumespecificador y un nombre opcional (o etiqueta ) para la enumeración, seguido de una lista de una o más constantes contenidas entre llaves y separadas por comas, y una lista opcional de nombres de variables. Las referencias posteriores a un tipo enumerado específico utilizan la enumpalabra clave y el nombre de la enumeración. De forma predeterminada, a la primera constante de una enumeración se le asigna el valor cero, y cada valor posterior se incrementa en uno con respecto a la constante anterior. También se pueden asignar valores específicos a las constantes en la declaración, y a cualquier constante posterior sin valores específicos se le asignarán valores incrementados a partir de ese punto. Por ejemplo, considere la siguiente declaración:

enumeración colores { ROJO , VERDE , AZUL = 5 , AMARILLO } color_de_pintura ;          

Esto declara el enum colorstipo; las intconstantes RED(cuyo valor es 0), GREEN(cuyo valor es uno mayor que RED, 1), BLUE(cuyo valor es el valor dado, 5), y YELLOW(cuyo valor es uno mayor que BLUE, 6); y la enum colorsvariable paint_color. Las constantes se pueden usar fuera del contexto de enum(donde se permite cualquier valor entero), y se pueden asignar valores distintos de las constantes a paint_color, o a cualquier otra variable de tipo enum colors.

Tipos de punto flotante

Se utiliza una forma de punto flotante para representar números con un componente fraccionario. Sin embargo, no representan exactamente la mayoría de los números racionales; en cambio, son una aproximación cercana. Hay tres tipos estándar de valores reales, denotados por sus especificadores (y desde C23 tres tipos decimales más): precisión simple ( float), precisión doble ( double) y precisión doble extendida ( long double). Cada uno de estos puede representar valores en una forma diferente, a menudo uno de los formatos de punto flotante IEEE .

Las constantes de punto flotante se pueden escribir en notación decimal , p. ej. 1.23, . La notación científica decimal se puede utilizar añadiendo eo Eseguido de un exponente decimal, también conocido como notación E , p. ej., 1.23e2(que tiene el valor 1,23 × 10 2 = 123,0). Se requiere un punto decimal o un exponente (de lo contrario, el número se analiza como una constante entera). Las constantes de punto flotante hexadecimales siguen reglas similares, excepto que deben ir precedidas de 0xy usar po Ppara especificar un exponente binario, p. ej. 0xAp-2, (que tiene el valor 2,5, ya que A h × 2 −2 = 10 × 2 −2 = 10 ÷ 4). Tanto las constantes de punto flotante decimales como las hexadecimales pueden ir precedidas de fo Fpara indicar una constante de tipo float, de l(letra l) o Lpara indicar el tipo long double, o dejarse sin sufijo para una doubleconstante.

El archivo de encabezado estándar float.hdefine los valores mínimos y máximos de los tipos de punto flotante de la implementación float, doubley long double. También define otros límites que son relevantes para el procesamiento de números de punto flotante.

C23 introduce tres tipos de punto flotante reales decimales adicionales (a diferencia de los binarios): _Decimal32, _Decimal64 y _Decimal128.

NOTA: C no especifica un radio para float , double y long double . Una implementación puede elegir que la representación de float , double y long double sea la misma que la de los tipos decimales flotantes. [2]

A pesar de eso, el radix ha sido históricamente binario (base 2), lo que significa que números como 1/2 o 1/4 son exactos, pero no 1/10, 1/100 o 1/3. Con punto flotante decimal, todos los números son exactos más números como 1/10 y 1/100, pero aún no, por ejemplo, 1/3. Ninguna implementación conocida opta por el radix decimal para los tipos que previamente se sabía que eran binarios. Dado que la mayoría de las computadoras ni siquiera tienen el hardware para los tipos decimales, y las pocas que lo tienen (por ejemplo, los mainframes IBM desde IBM System z10 ), pueden usar los tipos decimales explícitos.


Especificadores de clase de almacenamiento

Cada objeto tiene una clase de almacenamiento. Esta especifica básicamente la duración del almacenamiento, que puede ser estática (predeterminada para global), automática (predeterminada para local) o dinámica (asignada), junto con otras características (vínculo y sugerencia de registro).

1 Asignado y desasignado utilizando las funciones de biblioteca malloc()y free().

Las variables declaradas dentro de un bloque por defecto tienen almacenamiento automático, al igual que las declaradas explícitamente con los especificadores de clase de almacenamiento [nota 2] o . Los especificadores y solo se pueden utilizar dentro de funciones y declaraciones de argumentos de función; como tal, el especificador siempre es redundante. Los objetos declarados fuera de todos los bloques y aquellos declarados explícitamente con el especificador de clase de almacenamiento tienen una duración de almacenamiento estática. Las variables estáticas son inicializadas a cero por defecto por el compilador . autoregisterautoregisterautostatic

Los objetos con almacenamiento automático son locales en el bloque en el que se declararon y se descartan cuando se sale del bloque. Además, registerel compilador puede dar mayor prioridad a los objetos declarados con la clase de almacenamiento para acceder a los registros ; aunque el compilador puede elegir no almacenar ninguno de ellos en un registro. Los objetos con esta clase de almacenamiento no se pueden usar con el &operador unario address-of ( ). Los objetos con almacenamiento estático persisten durante toda la duración del programa. De esta manera, una función puede acceder al mismo objeto en múltiples llamadas. Los objetos con duración de almacenamiento asignada se crean y destruyen explícitamente con malloc, free, y funciones relacionadas.

El externespecificador de clase de almacenamiento indica que el almacenamiento de un objeto se ha definido en otra parte. Cuando se utiliza dentro de un bloque, indica que el almacenamiento se ha definido mediante una declaración fuera de ese bloque. Cuando se utiliza fuera de todos los bloques, indica que el almacenamiento se ha definido fuera de la unidad de compilación. El externespecificador de clase de almacenamiento es redundante cuando se utiliza en una declaración de función. Indica que la función declarada se ha definido fuera de la unidad de compilación.

El especificador de clase de almacenamiento _Thread_local( thread_localen C++ y en C desde C23 y en versiones anteriores de C si <threads.h>se incluye el encabezado), introducido en C11 , se utiliza para declarar una variable local del subproceso. Se puede combinar con statico externpara determinar el enlace.

Tenga en cuenta que los especificadores de almacenamiento se aplican únicamente a funciones y objetos; otras cosas, como las declaraciones de tipo y enumeración, son privadas de la unidad de compilación en la que aparecen. Los tipos, por otro lado, tienen calificadores (consulte a continuación).

Calificadores de tipo

Los tipos se pueden calificar para indicar propiedades especiales de sus datos. El calificador de tipo constindica que un valor no cambia una vez que se ha inicializado. Intentar modificar un constvalor calificado produce un comportamiento indefinido, por lo que algunos compiladores de C los almacenan en rodata o (para sistemas integrados) en la memoria de solo lectura (ROM). El calificador de tipo volatileindica a un compilador optimizador que no puede eliminar lecturas o escrituras aparentemente redundantes, ya que el valor puede cambiar incluso si no fue modificado por ninguna expresión o declaración, o pueden ser necesarias múltiples escrituras, como para la E/S asignada a la memoria .

Tipos incompletos

Un tipo incompleto es un tipo de estructura o unión cuyos miembros aún no se han especificado, un tipo de matriz cuya dimensión aún no se ha especificado o el voidtipo (el voidtipo no se puede completar). No se puede crear una instancia de un tipo de este tipo (no se conoce su tamaño) ni se puede acceder a sus miembros (también se desconocen); sin embargo, se puede utilizar el tipo de puntero derivado (pero no desreferenciarlo).

A menudo se utilizan con punteros, ya sea como declaraciones directas o externas. Por ejemplo, el código podría declarar un tipo incompleto de la siguiente manera:

estructura cosa * pt ;  

Esto declara ptcomo un puntero a struct thing y el tipo incompleto struct thing. Los punteros a datos siempre tienen el mismo ancho de bytes independientemente de a qué apuntan, por lo que esta declaración es válida por sí misma (siempre que ptno se desreferencia). El tipo incompleto se puede completar más adelante en el mismo ámbito volviéndolo a declarar:

struct thing { int num ; }; /* el tipo de estructura thing ahora está completo */     

Los tipos incompletos se utilizan para implementar estructuras recursivas ; el cuerpo de la declaración de tipo puede posponerse para más adelante en la unidad de traducción:

tipo de estructura Bert Bert ; tipo de estructura Wilma Wilma ;      estructura Bert { Wilma * wilma ; };    estructura Wilma { Bert * bert ; };    

Los tipos incompletos también se utilizan para ocultar datos ; el tipo incompleto se define en un archivo de encabezado y el cuerpo solo dentro del archivo fuente relevante.

Punteros

En las declaraciones, el modificador asterisco ( *) especifica un tipo de puntero. Por ejemplo, donde el especificador intharía referencia al tipo entero, el especificador int*hace referencia al tipo "puntero a entero". Los valores de puntero asocian dos piezas de información: una dirección de memoria y un tipo de datos. La siguiente línea de código declara una variable puntero a entero llamada ptr :

int * ptr ; 

Referencias

Cuando se declara un puntero no estático, tiene un valor no especificado asociado. La dirección asociada con dicho puntero debe cambiarse mediante asignación antes de usarlo. En el siguiente ejemplo, ptr se configura de modo que apunte a los datos asociados con la variable a :

entero a = 0 ; entero * ptr = &a a ;      

Para lograr esto, &se utiliza el operador "dirección de" (unario), que genera la ubicación de memoria del objeto de datos que sigue.

Desreferenciación

Se puede acceder a los datos a los que se apunta mediante un valor de puntero. En el siguiente ejemplo, la variable entera b se establece en el valor de la variable entera a , que es 10:

entero a = 10 ; entero * p ; p = &a a ; int b = * p ;       

Para llevar a cabo esta tarea, se utiliza el operador de desreferenciación unario , denotado por un asterisco (*), que devuelve los datos a los que apunta su operando, que debe ser de tipo puntero. Por lo tanto, la expresión * p denota el mismo valor que a . Desreferenciar un puntero nulo es ilegal.

Matrices

Definición de matriz

En C, las matrices se utilizan para representar estructuras de elementos consecutivos del mismo tipo. La definición de una matriz (de tamaño fijo) tiene la siguiente sintaxis:

int matriz [ 100 ]; 

que define una matriz denominada matriz para almacenar 100 valores del tipo primitivo int. Si se declara dentro de una función, la dimensión de la matriz también puede ser una expresión no constante, en cuyo caso se asignará memoria para la cantidad especificada de elementos. En la mayoría de los contextos, en el uso posterior, una mención de la variable matriz se convierte en un puntero al primer elemento de la matriz. El sizeofoperador es una excepción: sizeof arrayproduce el tamaño de la matriz completa (es decir, 100 veces el tamaño de un int, y sizeof(array) / sizeof(int)devolverá 100). Otra excepción es el operador & (dirección de), que produce un puntero a la matriz completa, por ejemplo

int ( * ptr_to_array )[ 100 ] = & matriz ;   

Acceder a los elementos

La función principal para acceder a los valores de los elementos de una matriz es el operador de subíndice de matriz. Para acceder al elemento indexado i de la matriz , la sintaxis sería array[i], que hace referencia al valor almacenado en ese elemento de la matriz.

La numeración de subíndices de matrices comienza en 0 (consulte Indexación basada en cero ). Por lo tanto, el subíndice de matriz más grande permitido es igual al número de elementos de la matriz menos 1. Para ilustrar esto, considere una matriz a declarada con 10 elementos; el primer elemento sería a[0]y el último elemento sería a[9].

C no ofrece ninguna función para la comprobación automática de límites en el uso de matrices. Aunque, lógicamente, el último subíndice de una matriz de 10 elementos sería 9, los subíndices 10, 11, etc. podrían especificarse accidentalmente, con resultados indefinidos.

Debido a que las matrices y los punteros son intercambiables, las direcciones de cada uno de los elementos de la matriz se pueden expresar en aritmética de punteros equivalente . La siguiente tabla ilustra ambos métodos para la matriz existente:

Dado que la expresión a[i]es semánticamente equivalente a *(a+i), que a su vez es equivalente a *(i+a), la expresión también se puede escribir como i[a], aunque esta forma rara vez se utiliza.

Matrices de longitud variable

C99 estandarizó las matrices de longitud variable (VLA) dentro del ámbito de bloque. Estas variables de matriz se asignan en función del valor de un valor entero en tiempo de ejecución al ingresar a un bloque y se desasignan al final del bloque. [3] A partir de C11, ya no es necesario que el compilador implemente esta característica.

int n = ...; int a [ n ]; a [ 3 ] = 10 ;      

Esta sintaxis produce una matriz cuyo tamaño es fijo hasta el final del bloque.

Matrices dinámicas

Las matrices que se pueden redimensionar dinámicamente se pueden producir con la ayuda de la biblioteca estándar de C. La mallocfunción proporciona un método simple para asignar memoria. Toma un parámetro: la cantidad de memoria a asignar en bytes. Tras una asignación exitosa, mallocdevuelve un valor de puntero genérico ( void), que apunta al comienzo del espacio asignado. El valor de puntero devuelto se convierte a un tipo apropiado de forma implícita mediante la asignación. Si no se pudo completar la asignación, mallocdevuelve un puntero nulo . Por lo tanto, el siguiente segmento es similar en función a la declaración deseada anterior:

#include <stdlib.h> /* declara malloc */ ... int * a = malloc ( n * sizeof * a ); a [ 3 ] = 10 ;         

El resultado es un "puntero a int" variable ( a ) que apunta al primero de n objetos contiguos int; debido a la equivalencia entre punteros y matrices, esto se puede utilizar en lugar de un nombre de matriz real, como se muestra en la última línea. La ventaja de utilizar esta asignación dinámica es que la cantidad de memoria que se le asigna se puede limitar a lo que realmente se necesita en tiempo de ejecución, y esto se puede cambiar según sea necesario (utilizando la función de biblioteca estándar realloc).

Cuando la memoria asignada dinámicamente ya no es necesaria, se debe liberar nuevamente al sistema en tiempo de ejecución. Esto se hace con una llamada a la freefunción. Toma un solo parámetro: un puntero a la memoria asignada previamente. Este es el valor que fue devuelto por una llamada anterior a malloc.

Como medida de seguridad, algunos programadores [¿ quiénes? ] establecen la variable puntero en NULL:

libre ( a ); a = NULL ;  

Esto garantiza que los intentos posteriores de desreferenciar el puntero, en la mayoría de los sistemas, harán que el programa se bloquee. Si no se hace esto, la variable se convierte en un puntero colgante que puede provocar un error de uso después de liberación. Sin embargo, si el puntero es una variable local, configurarlo en NULLno impide que el programa use otras copias del puntero. Los errores de uso después de liberación locales suelen ser fáciles de reconocer para los analizadores estáticos . Por lo tanto, este enfoque es menos útil para punteros locales y se utiliza con más frecuencia con punteros almacenados en estructuras de larga duración. Sin embargo, en general, configurar punteros en NULLes una buena práctica [ ¿según quién? ] ya que permite a un programador NULLverificar los punteros antes de desreferenciarlos, lo que ayuda a prevenir bloqueos.

Recordando el ejemplo de la matriz, también se podría crear una matriz de tamaño fijo a través de la asignación dinámica:

int ( * a )[ 100 ] = malloc ( tamaño de * a );    

...Lo que produce un puntero a una matriz.

El acceso al puntero a la matriz se puede realizar de dos maneras:

( * a )[ índice ];índice [ * a ];

La iteración también se puede realizar de dos maneras:

para ( int i = 0 ; i < 100 ; i ++ ) ( * a )[ i ];         para ( int * i = a [ 0 ]; i < a [ 1 ]; i ++ ) * i ;         

La ventaja de utilizar el segundo ejemplo es que no se requiere el límite numérico del primer ejemplo, lo que significa que el puntero a la matriz podría ser de cualquier tamaño y el segundo ejemplo puede ejecutarse sin ninguna modificación.

Matrices multidimensionales

Además, C admite matrices de múltiples dimensiones, que se almacenan en orden de fila principal . Técnicamente, las matrices multidimensionales de C son simplemente matrices unidimensionales cuyos elementos son matrices. La sintaxis para declarar matrices multidimensionales es la siguiente:

int array2d [ FILAS ][ COLUMNAS ]; 

donde ROWS y COLUMNS son constantes. Esto define una matriz bidimensional. Al leer los subíndices de izquierda a derecha, array2d es una matriz de longitud ROWS , cada elemento de la cual es una matriz de números enteros COLUMNS .

Para acceder a un elemento entero en esta matriz multidimensional, se utilizaría

matriz2d [ 4 ][ 3 ]

Nuevamente, leyendo de izquierda a derecha, se accede a la quinta fila y al cuarto elemento de esa fila. La expresión array2d[4]es una matriz, a la que luego le agregamos el subíndice [3] para acceder al cuarto entero.

Las matrices de dimensiones superiores se pueden declarar de manera similar.

Una matriz multidimensional no debe confundirse con una matriz de punteros a matrices (también conocida como vector de Iliffe o, a veces, matriz de matrices ). La primera siempre es rectangular (todas las submatrices deben tener el mismo tamaño) y ocupa una región contigua de la memoria. La segunda es una matriz unidimensional de punteros, cada uno de los cuales puede apuntar al primer elemento de una submatriz en un lugar diferente de la memoria, y las submatrices no tienen que tener el mismo tamaño. La segunda se puede crear mediante múltiples usos de malloc.

Instrumentos de cuerda

En C, los literales de cadena están rodeados por comillas dobles ( ") (por ejemplo, "Hello world!") y se compilan en una matriz de charvalores especificados con un código de carácter de terminación nulo adicional (valor 0) para marcar el final de la cadena.

Los literales de cadena no pueden contener saltos de línea incrustados; esta prohibición simplifica un poco el análisis del lenguaje. Para incluir un salto de línea en una cadena, \nse puede utilizar la barra invertida como escape, como se muestra a continuación.

Hay varias funciones de biblioteca estándar para operar con datos de cadena (no necesariamente constantes) organizados como matrices charutilizando este formato terminado en nulo; consulte a continuación.

La sintaxis de cadenas literales de C ha sido muy influyente y se ha incorporado a muchos otros lenguajes, como C++, Objective-C, Perl, Python, PHP, Java, JavaScript, C# y Ruby. Hoy en día, casi todos los lenguajes nuevos adoptan o se basan en la sintaxis de cadenas de estilo C. Los lenguajes que carecen de esta sintaxis tienden a preceder a C.

La barra invertida se escapa

Debido a que ciertos caracteres no pueden formar parte directamente de una expresión de cadena literal, se identifican mediante una secuencia de escape que comienza con una barra invertida ( \). Por ejemplo, las barras invertidas en "This string contains \"double quotes\"."indican (al compilador) que el par de comillas internas están pensadas como una parte real de la cadena, en lugar de la lectura predeterminada como un delimitador (punto final) de la cadena en sí.

Se pueden utilizar barras invertidas para ingresar varios caracteres de control, etc., en una cadena:

El uso de otros escapes de barra invertida no está definido por el estándar C, aunque los proveedores de compiladores a menudo proporcionan códigos de escape adicionales como extensiones del lenguaje. Uno de ellos es la secuencia de escape \epara el carácter de escape con valor hexadecimal ASCII 1B que no se agregó al estándar C debido a la falta de representación en otros conjuntos de caracteres (como EBCDIC ). Está disponible en GCC , clang y tcc .

Concatenación de literales de cadena

C tiene concatenación de literales de cadena , lo que significa que los literales de cadena adyacentes se concatenan en tiempo de compilación; esto permite que las cadenas largas se dividan en varias líneas y también permite que los literales de cadena resultantes de las definiciones y macros del preprocesador C se agreguen a las cadenas en tiempo de compilación:

 printf ( __FILE__ ": %d: Hola " "mundo \n " , __LINE__ );   

se expandirá a

 printf ( "holamundo.c" ": %d: Hola " "mundo \n " , 10 );   

que es sintácticamente equivalente a

 printf ( "holamundo.c: %d: Hola mundo \n " , 10 ); 

Constantes de caracteres

Las constantes de caracteres individuales se escriben entre comillas simples, p. ej 'A'., y tienen tipo int(en C++, char). La diferencia es que "A"representa una matriz terminada en nulo de dos caracteres, 'A' y '\0', mientras que 'A'representa directamente el valor del carácter (65 si se utiliza ASCII). Se admiten los mismos escapes de barra invertida que para las cadenas, excepto que (por supuesto) "se pueden usar de forma válida como un carácter sin que se escape, mientras que 'ahora se deben escapar.

Una constante de carácter no puede estar vacía (es decir, no ''tiene sintaxis válida), aunque una cadena sí puede estarlo (aún tiene el carácter de terminación nulo). Las constantes de múltiples caracteres (por ejemplo, 'xy') son válidas, aunque rara vez son útiles: permiten almacenar varios caracteres en un entero (por ejemplo, 4 caracteres ASCII pueden caber en un entero de 32 bits, 8 en uno de 64 bits). Dado que intno se especifica el orden en el que se empaquetan los caracteres en un entero (se deja a la implementación definirlo), el uso portátil de constantes de múltiples caracteres es difícil.

Sin embargo, en situaciones limitadas a una plataforma específica y a la implementación del compilador, las constantes multicarácter sí encuentran su uso para especificar firmas. Un caso de uso común es el OSType , donde la combinación de compiladores Mac OS clásicos y su inherente big-endianness significa que los bytes en el entero aparecen en el orden exacto de los caracteres definidos en el literal. La definición de las "implementaciones" populares es, de hecho, consistente: en GCC, Clang y Visual C++ , '1234'se obtiene bajo ASCII. [5] [6]0x31323334

Al igual que los literales de cadena, las constantes de caracteres también se pueden modificar mediante prefijos, por ejemplo, L'A'tiene tipo wchar_ty representa el valor del carácter "A" en la codificación de caracteres amplia.

Cadenas de caracteres anchos

Dado que el tipo chartiene un ancho de 1 byte, un único charvalor normalmente puede representar como máximo 255 códigos de caracteres distintos, lo que no es suficiente para todos los caracteres que se utilizan en todo el mundo. Para proporcionar un mejor soporte para caracteres internacionales, el primer estándar C (C89) introdujo caracteres anchos (codificados en type wchar_t) y cadenas de caracteres anchos, que se escriben comoL"Hello world!"

Los caracteres anchos son más comúnmente de 2 bytes (usando una codificación de 2 bytes como UTF-16 ) o 4 bytes (usualmente UTF-32 ), pero el Estándar C no especifica el ancho para wchar_t, dejando la elección al implementador. Microsoft Windows generalmente usa UTF-16, por lo tanto la cadena anterior tendría 26 bytes de longitud para un compilador de Microsoft; el mundo Unix prefiere UTF-32, por lo tanto los compiladores como GCC generarían una cadena de 52 bytes. Un ancho de 2 bytes wchar_tsufre la misma limitación que char, en el sentido de que ciertos caracteres (aquellos fuera del BMP ) no pueden representarse en un solo wchar_t; sino que deben representarse usando pares sustitutos .

El estándar C original especificaba únicamente funciones mínimas para operar con cadenas de caracteres anchos; en 1995, el estándar se modificó para incluir un soporte mucho más amplio, comparable al de las charcadenas. Las funciones relevantes se nombran en su mayoría según sus charequivalentes, con la adición de una "w" o el reemplazo de "str" ​​por "wcs"; se especifican en <wchar.h>, con <wctype.h>funciones de mapeo y clasificación de caracteres anchos.

El método ahora generalmente recomendado [nota 3] para admitir caracteres internacionales es a través de UTF-8 , que se almacena en matrices y se puede escribir directamente en el código fuente si se usa un editor UTF-8, porque UTF-8 es una extensión ASCIIchar directa .

Cadenas de ancho variable

Una alternativa común a la codificación de cadenas wchar_tes utilizar una codificación de ancho variable , por la cual un carácter lógico puede extenderse sobre múltiples posiciones de la cadena. Las cadenas de ancho variable pueden codificarse en literales textualmente, con el riesgo de confundir al compilador, o utilizando escapes numéricos de barra invertida (por ejemplo, "\xc3\xa9"para "é" en UTF-8). La codificación UTF-8 fue diseñada específicamente (según el Plan 9 ) para la compatibilidad con las funciones de cadena de la biblioteca estándar; las características de soporte de la codificación incluyen la falta de nulos incrustados, ninguna interpretación válida para subsecuencias y resincronización trivial. Las codificaciones que carecen de estas características probablemente resulten incompatibles con las funciones de la biblioteca estándar; las funciones de cadena que reconocen la codificación se utilizan a menudo en tales casos.

Funciones de la biblioteca

Las cadenas , tanto constantes como variables, se pueden manipular sin utilizar la biblioteca estándar . Sin embargo, la biblioteca contiene muchas funciones útiles para trabajar con cadenas terminadas en nulo.

Estructuras y uniones

Estructuras

En C, las estructuras y uniones se definen como contenedores de datos que consisten en una secuencia de miembros nombrados de varios tipos. Son similares a los registros en otros lenguajes de programación. Los miembros de una estructura se almacenan en ubicaciones consecutivas en la memoria, aunque el compilador puede insertar relleno entre los miembros o después de ellos (pero no antes del primer miembro) para lograr eficiencia o como relleno requerido para una alineación adecuada por la arquitectura de destino. El tamaño de una estructura es igual a la suma de los tamaños de sus miembros, más el tamaño del relleno.

Sindicatos

Las uniones en C están relacionadas con las estructuras y se definen como objetos que pueden contener (en diferentes momentos) objetos de diferentes tipos y tamaños. Son análogas a los registros de variantes en otros lenguajes de programación. A diferencia de las estructuras, todos los componentes de una unión hacen referencia a la misma ubicación en la memoria. De esta manera, una unión se puede utilizar en varios momentos para contener diferentes tipos de objetos, sin la necesidad de crear un objeto separado para cada nuevo tipo. El tamaño de una unión es igual al tamaño de su tipo de componente más grande.

Declaración

Las estructuras se declaran con la structpalabra clave y las uniones se declaran con la unionpalabra clave . La palabra clave del especificador va seguida de un nombre de identificador opcional, que se utiliza para identificar la forma de la estructura o unión. El identificador va seguido de la declaración del cuerpo de la estructura o unión: una lista de declaraciones de miembros, contenidas entre llaves, con cada declaración terminada con un punto y coma. Finalmente, la declaración concluye con una lista opcional de nombres de identificadores, que se declaran como instancias de la estructura o unión.

Por ejemplo, la siguiente declaración declara una estructura denominada sque contiene tres miembros; también declarará una instancia de la estructura conocida como tee:

estructura s { int x ; float y ; char * z ; } tee ;         

Y la siguiente declaración declarará una unión similar llamada uy una instancia de ella llamada n:

unión u { int x ; float y ; char * z ; } n ;         

Los miembros de estructuras y uniones no pueden tener un tipo incompleto o de función. Por lo tanto, los miembros no pueden ser una instancia de la estructura o unión que se declara (porque está incompleta en ese punto), pero pueden ser punteros al tipo que se declara.

Una vez que se ha declarado una estructura o un cuerpo de unión y se le ha asignado un nombre, se puede considerar un nuevo tipo de datos utilizando el especificador structo union, según corresponda, y el nombre. Por ejemplo, la siguiente declaración, dada la declaración de estructura anterior, declara una nueva instancia de la estructura sdenominada r:

estructura s r ;  

También es habitual utilizar el typedefespecificador para eliminar la necesidad de la palabra clave structor unionen referencias posteriores a la estructura. El primer identificador después del cuerpo de la estructura se toma como el nuevo nombre del tipo de estructura (las instancias de estructura no se pueden declarar en este contexto). Por ejemplo, la siguiente declaración declarará un nuevo tipo conocido como s_type que contendrá alguna estructura:

tipodef estructura {...} s_type ;   

Las declaraciones futuras pueden entonces utilizar el especificador s_type (en lugar del structespecificador expandido ...) para hacer referencia a la estructura.

Acceder a miembros

Se accede a los miembros mediante el nombre de la instancia de una estructura o unión, un punto ( .) y el nombre del miembro. Por ejemplo, dada la declaración de tee de arriba, se puede acceder al miembro conocido como y (de tipo float) mediante la siguiente sintaxis:

camiseta . y

Se accede a las estructuras normalmente a través de punteros. Considere el siguiente ejemplo que define un puntero a tee , conocido como ptr_to_tee :

estructura s * ptr_to_tee = & tee ;    

Luego se puede acceder al miembro y de tee desreferenciando ptr_to_tee y usando el resultado como operando izquierdo:

( * ptr_to_tee ). y

Lo cual es idéntico al más simple tee.yanterior siempre que ptr_to_tee apunte a tee . Debido a que la precedencia del operador («." es mayor que "*"), el más corto *ptr_to_tee.yes incorrecto para este propósito, en su lugar se analiza como *(ptr_to_tee.y)y, por lo tanto, los paréntesis son necesarios. Debido a que esta operación es común, C proporciona una sintaxis abreviada para acceder a un miembro directamente desde un puntero. Con esta sintaxis, el nombre de la instancia se reemplaza con el nombre del puntero y el punto se reemplaza con la secuencia de caracteres ->. Por lo tanto, el siguiente método para acceder a y es idéntico a los dos anteriores:

ptr_to_tee -> y

A los miembros de los sindicatos se accede de la misma manera.

Esto se puede encadenar; por ejemplo, en una lista enlazada, se puede hacer referencia n->next->nextal segundo nodo siguiente (asumiendo que n->nextno es nulo).

Asignación

La asignación de valores a miembros individuales de estructuras y uniones es sintácticamente idéntica a la asignación de valores a cualquier otro objeto. La única diferencia es que el valor l de la asignación es el nombre del miembro, al que se accede mediante la sintaxis mencionada anteriormente.

Una estructura también se puede asignar como unidad a otra estructura del mismo tipo. Las estructuras (y los punteros a estructuras) también se pueden utilizar como parámetros de función y tipos de retorno.

Por ejemplo, la siguiente declaración asigna el valor de 74 (el punto de código ASCII para la letra 't') al miembro llamado x en la estructura tee , de arriba:

tee . x = 74 ;  

Y la misma asignación, utilizando ptr_to_tee en lugar de tee , se vería así:

ptr_to_tee -> x = 74 ;  

El trabajo con los miembros de los sindicatos es idéntico.

Otras operaciones

Según el estándar C, las únicas operaciones legales que se pueden realizar en una estructura son copiarla, asignarla como unidad (o inicializarla), tomar su dirección con el &operador unario address-of ( ) y acceder a sus miembros. Las uniones tienen las mismas restricciones. Una de las operaciones implícitamente prohibidas es la comparación: las estructuras y las uniones no se pueden comparar utilizando las facilidades de comparación estándar de C ( ==, >, <, etc.).

Campos de bits

C también proporciona un tipo especial de miembro conocido como campo de bits , que es un entero con un número de bits especificado explícitamente. Un campo de bits se declara como un miembro de estructura (o unión) de tipo int, signed int, unsigned int, o _Bool, [nota 4] seguido del nombre del miembro por dos puntos ( :) y el número de bits que debe ocupar. El número total de bits en un solo campo de bits no debe exceder el número total de bits en su tipo declarado (sin embargo, esto está permitido en C++, donde los bits adicionales se utilizan para relleno).

Como excepción especial a las reglas de sintaxis habituales de C, la implementación define si un campo de bits declarado como tipo int, sin especificar signedo unsigned, tiene signo o no. Por lo tanto, se recomienda especificar explícitamente signedo unsigneden todos los miembros de la estructura para facilitar la portabilidad.

También se permiten los campos sin nombre que constan de dos puntos seguidos de una cantidad de bits; estos indican relleno . La especificación de un ancho de cero para un campo sin nombre se utiliza para forzar la alineación a una nueva palabra. [7] Dado que todos los miembros de una unión ocupan la misma memoria, los campos de bits sin nombre de ancho cero no hacen nada en las uniones; sin embargo, los campos de bits sin nombre de ancho distinto de cero pueden cambiar el tamaño de la unión, ya que tienen que caber en ella.

Los miembros de los campos de bits no tienen direcciones y, por lo tanto, no se pueden utilizar con el &operador unario address-of ( ). El sizeofoperador no se puede aplicar a los campos de bits.

La siguiente declaración declara un nuevo tipo de estructura conocido como fy una instancia de este conocido como g. Los comentarios proporcionan una descripción de cada uno de los miembros:

struct f { unsigned int flag : 1 ; /* un bit flag: puede estar activado (1) o desactivado (0) */ signed int num : 4 ; /* un campo de 4 bits con signo; rango -7...7 o -8...7 */ signed int : 3 ; /* 3 bits de relleno para redondear a 8 bits */ } g ;                    

Inicialización

La inicialización predeterminada depende del especificador de clase de almacenamiento, descrito anteriormente.

Debido a la gramática del lenguaje, un inicializador escalar puede estar encerrado entre cualquier número de pares de llaves. Sin embargo, la mayoría de los compiladores emiten una advertencia si hay más de un par de llaves.

int x = 12 ; int y = { 23 }; //Legal, sin advertencia int z = { { 34 } }; //Legal, se espera una advertencia                 

Las estructuras, uniones y matrices se pueden inicializar en sus declaraciones mediante una lista de inicializadores. A menos que se utilicen designadores, los componentes de un inicializador se corresponden con los elementos en el orden en que se definen y almacenan, por lo que todos los valores anteriores deben proporcionarse antes del valor de cualquier elemento en particular. Todos los elementos no especificados se establecen en cero (excepto las uniones). Mencionar demasiados valores de inicialización genera un error.

La siguiente declaración inicializará una nueva instancia de la estructura s conocida como pi :

estructura s { int x ; float y ; char * z ; };        estructura s pi = { 3 , 3.1415 , "Pi" };        

Inicializadores designados

Los inicializadores designados permiten inicializar los miembros por nombre, en cualquier orden y sin proporcionar explícitamente los valores anteriores. La siguiente inicialización es equivalente a la anterior:

estructura s pi = { . z = "Pi" , . x = 3 , . y = 3.1415 };              

El uso de un designador en un inicializador mueve el "cursor" de inicialización. En el ejemplo siguiente, si MAXes mayor que 10, habrá algunos elementos con valor cero en el medio de a; si es menor que 10, algunos de los valores proporcionados por los primeros cinco inicializadores serán reemplazados por los segundos cinco (si MAXes menor que 5, habrá un error de compilación):

int a [ MÁX ] = { 1 , 3 , 5 , 7 , 9 , [ MÁX -5 ] = 8 , 6 , 4 , 2 , 0 };                

En C89 , una unión se inicializaba con un único valor aplicado a su primer miembro. Es decir, la unión u definida anteriormente solo podía tener su miembro int x inicializado:

unión u valor = { 3 };      

Al utilizar un inicializador designado, el miembro que se va a inicializar no tiene que ser el primer miembro:

unión u valor = { . y = 3.1415 };        

Si una matriz tiene un tamaño desconocido (es decir, la matriz era de un tipo incompleto), la cantidad de inicializadores determina el tamaño de la matriz y su tipo se vuelve completo:

int x [] = { 0 , 1 , 2 } ;        

Los designadores compuestos se pueden utilizar para proporcionar una inicialización explícita cuando las listas de inicializadores sin adornos pueden ser malinterpretadas. En el ejemplo siguiente, wse declara como una matriz de estructuras, cada estructura consta de un miembro a(una matriz de 3 int) y un miembro b(an int). El inicializador establece el tamaño de wen 2 y establece los valores del primer elemento de cada a:

estructura { int a [ 3 ], b ; } w [] = { [ 0 ]. a = { 1 }, [ 1 ]. a [ 0 ] = 2 };               

Esto es equivalente a:

estructura { int a [ 3 ], b ; } w [] = { { { 1 , 0 , 0 }, 0 }, { { 2 , 0 , 0 }, 0 } };                        

No hay forma de especificar la repetición de un inicializador en C estándar.

Literales compuestos

Es posible tomar prestada la metodología de inicialización para generar estructuras compuestas y literales de matriz:

// puntero creado a partir de un literal de matriz. int * ptr = ( int []){ 10 , 20 , 30 , 40 };        // puntero a la matriz. float ( * foo )[ 3 ] = & ( float []){ 0.5f , 1.f , -0.5f };       estructura s pi = ( estructura s ){ 3 , 3.1415 , "Pi" };         

Los literales compuestos a menudo se combinan con inicializadores designados para hacer que la declaración sea más legible: [3]

pi = ( estructura s ){ .z = " Pi " , .x = 3 , .y = 3.1415 } ;             

Operadores

Estructuras de control

C es un lenguaje de forma libre .

El estilo de uso de las llaves varía de un programador a otro y puede ser motivo de debate. Consulte Estilo de sangría para obtener más detalles.

Declaraciones compuestas

En los elementos de esta sección, cualquier <statement> se puede reemplazar por una declaración compuesta . Las declaraciones compuestas tienen la forma:

{ < opcional - declaración - lista > < opcional - declaración - lista > }  

y se utilizan como el cuerpo de una función o en cualquier lugar donde se espera una sola declaración. La lista de declaraciones declara las variables que se utilizarán en ese ámbito , y la lista de declaraciones son las acciones que se realizarán. Los corchetes definen su propio ámbito, y las variables definidas dentro de esos corchetes se desasignarán automáticamente en el corchete de cierre. Las declaraciones y las declaraciones se pueden entremezclar libremente dentro de una declaración compuesta (como en C++ ).

Declaraciones de selección

C tiene dos tipos de declaraciones de selección : la ifdeclaración y la switchdeclaración .

La ifdeclaración tiene el formato:

if ( < expresión > ) < declaración1 > else < declaración2 >   

En la ifdeclaración, si el <expression>entre paréntesis es distinto de cero (verdadero), el control pasa a <statement1>. Si la elsecláusula está presente y el <expression>es cero (falso), el control pasará a <statement2>. La else <statement2>parte es opcional y, si está ausente, un falso <expression>simplemente hará que se omita el <statement1>. Un elsesiempre coincide con el anterior no coincidente más cercano if; se pueden usar llaves para anular esto cuando sea necesario o para mayor claridad.

La switchsentencia hace que el control se transfiera a una de varias sentencias según el valor de una expresión , que debe tener un tipo entero . La subsentencia controlada por un switch es normalmente compuesta. Cualquier sentencia dentro de la subsentencia puede etiquetarse con una o más caseetiquetas, que consisten en la palabra clave caseseguida de una expresión constante y luego dos puntos (:). La sintaxis es la siguiente:

switch ( < expresión > ) { caso < etiqueta1 > : < declaraciones 1 > caso < etiqueta2 > : < declaraciones 2 > break ; predeterminado : < declaraciones 3 > }                

No puede haber dos constantes de caso asociadas con el mismo conmutador que tengan el mismo valor. Puede haber como máximo una defaultetiqueta asociada con un conmutador. Si ninguna de las etiquetas de caso es igual a la expresión entre paréntesis que sigue a switch, el control pasa a la defaultetiqueta o, si no hay defaultetiqueta, la ejecución se reanuda justo más allá de toda la construcción.

Los conmutadores pueden estar anidados; una etiqueta caseo defaultse asocia con la más interna switchque la contiene. Las sentencias de conmutación pueden "fallar", es decir, cuando una sección de caso ha completado su ejecución, las sentencias continuarán ejecutándose hacia abajo hasta que break;se encuentre una sentencia. El fall-through es útil en algunas circunstancias, pero por lo general no es deseable. En el ejemplo anterior, si <label2>se alcanza if, <statements 2>se ejecutan las sentencias y nada más dentro de las llaves. Sin embargo, si se <label1>alcanza if, se ejecutan tanto como ya que no hay que separar las dos sentencias de caso.<statements 1><statements 2>break

Es posible, aunque poco habitual, insertar las switchetiquetas en los subbloques de otras estructuras de control. Algunos ejemplos de ello son el dispositivo de Duff y la implementación de corrutinas en Putty de Simon Tatham . [8]

Declaraciones de iteración

C tiene tres formas de declaración de iteración :

hacer < declaración > mientras ( < expresión > ) ;     mientras ( < expresión > ) < declaración >    para ( < expresión > ; < expresión > ; < expresión > ) < declaración >        

En las instrucciones whiley do, la subdeclaración se ejecuta repetidamente siempre que el valor de expressionpermanezca distinto de cero (equivalente a verdadero). Con while, la prueba, incluidos todos los efectos secundarios de <expression>, se produce antes de cada iteración (ejecución de <statement>); con do, la prueba se produce después de cada iteración. Por lo tanto, una doinstrucción siempre ejecuta su subdeclaración al menos una vez, mientras que whilepuede no ejecutar la subdeclaración en absoluto.

La declaración:

para ( e1 ; e2 ; e3 ) s ;    

es equivalente a:

e1 ; mientras ( e2 ) { s ; cont : e3 ; }   

excepto por el comportamiento de una continue;declaración (que en el forbucle salta a e3en lugar de e2). Si e2está en blanco, debería reemplazarse con un 1.

forSe puede omitir cualquiera de las tres expresiones del bucle. Si falta una segunda expresión, la whileprueba siempre será distinta de cero, lo que crea un bucle potencialmente infinito.

Desde C99 , la primera expresión puede tomar la forma de una declaración, que normalmente incluye un inicializador, como:

para ( int i = 0 ; i < límite ; ++ i ) { // ... }          

El alcance de la declaración está limitado a la extensión del forbucle.

Sentencias de salto

Las sentencias de salto transfieren el control incondicionalmente. Hay cuatro tipos de sentencias de salto en C: goto, continue, break, y return.

La gotodeclaración se lee así:

goto < identificador > ;  

El identificador debe ser una etiqueta (seguida de dos puntos) ubicada en la función actual. El control se transfiere a la instrucción etiquetada.

Una continuedeclaración puede aparecer solo dentro de una declaración de iteración y hace que el control pase a la parte de continuación de bucle de la declaración de iteración que la encierra más internamente. Es decir, dentro de cada una de las declaraciones

mientras ( expresión ) { /* ... */ cont : ; }    hacer { /* ... */ cont : ; } while ( expresión );     para ( expr1 ; expr2 ; expr3 ) { /* ... */ cont : ; }       

un continueno contenido dentro de una declaración de iteración anidada es lo mismo que goto cont.

La breakinstrucción se utiliza para finalizar un forbucle, whileciclo, dobucle o switchinstrucción. El control pasa a la instrucción que sigue a la instrucción finalizada.

Una función retorna a quien la llama mediante la returndeclaración. Cuando returnva seguida de una expresión, el valor se retorna a quien la llama como el valor de la función. Encontrar el final de la función es equivalente a una returnsin expresión. En ese caso, si la función se declara como que retorna un valor y quien la llama intenta usar el valor retornado, el resultado es indefinido.

Almacenamiento de la dirección de una etiqueta

GCC extiende el lenguaje C con un &&operador unario que devuelve la dirección de una etiqueta. Esta dirección se puede almacenar en un void*tipo variable y se puede utilizar más adelante en una gotoinstrucción. Por ejemplo, lo siguiente se imprime "hi "en un bucle infinito:

 vacío * ptr = && J1 ;   J1 : printf ( "hola " ); goto * ptr ;   

Esta función se puede utilizar para implementar una tabla de salto .

Funciones

Sintaxis

La definición de la función AC consta de un tipo de retorno ( voidsi no se devuelve ningún valor), un nombre único, una lista de parámetros entre paréntesis y varias declaraciones:

< return - type > functionName ( < lista - parámetros > ) { < declaraciones > return < expresión de tipo return - type > ; }         

Una función con voidun tipo de retorno distinto de debe incluir al menos una returndeclaración. Los parámetros se proporcionan mediante <parameter-list>, una lista separada por comas de declaraciones de parámetros, en la que cada elemento de la lista es un tipo de datos seguido de un identificador: <data-type> <variable-identifier>, <data-type> <variable-identifier>, ....

El tipo de retorno no puede ser un tipo de matriz o un tipo de función.

int f ()[ 3 ]; // Error: función que devuelve una matriz int ( * g ())[ 3 ]; // OK: función que devuelve un puntero a una matriz.    void h ())(); // Error: función que devuelve una función void ( * k ())(); // OK: función que devuelve un puntero a función    

Si no hay parámetros, se <parameter-list>puede dejar vacío o, opcionalmente, especificarse con una sola palabra void.

Es posible definir una función que tome una cantidad variable de parámetros proporcionando la ...palabra clave como último parámetro en lugar de un tipo de datos y un identificador de variable. Una función de uso común que hace esto es la función de la biblioteca estándar printf, que tiene la declaración:

int printf ( const char * , ...);    

La manipulación de estos parámetros se puede realizar utilizando las rutinas en el encabezado de la biblioteca estándar <stdarg.h>.

Punteros de función

Un puntero a una función se puede declarar de la siguiente manera:

< retorno - tipo > ( *< función - nombre > )( < parámetro - lista > ); 

El siguiente programa muestra el uso de un puntero de función para seleccionar entre suma y resta:

#incluir <stdio.h> int ( * operación )( int x , int y );    int add ( int x , int y ) { devolver x + y ; }        int restar ( int x , int y ) { devolver x - y ; }        int principal ( int argc , char * args []) { int foo = 1 , bar = 1 ;            operación = sumar ; printf ( "%d + %d = %d \n " , foo , barra , operación ( foo , barra )); operación = restar ; printf ( "%d - %d = %d \n " , foo , barra , operación ( foo , barra )); devolver 0 ; }                 

Estructura global

Después del preprocesamiento, en el nivel más alto, un programa en C consta de una secuencia de declaraciones en el ámbito de archivo. Estas pueden dividirse en varios archivos fuente separados, que pueden compilarse por separado; los módulos de objeto resultantes se vinculan luego con módulos de soporte de tiempo de ejecución provistos por la implementación para producir una imagen ejecutable.

Las declaraciones introducen funciones , variables y tipos . Las funciones de C son similares a las subrutinas de Fortran o los procedimientos de Pascal .

Una definición es un tipo especial de declaración. Una definición de variable reserva el almacenamiento y posiblemente lo inicializa, mientras que una definición de función proporciona su cuerpo.

Una implementación de C que proporciona todas las funciones de la biblioteca estándar se denomina implementación hospedada . Los programas escritos para implementaciones hospedadas deben definir una función especial denominada main, que es la primera función a la que se llama cuando un programa comienza a ejecutarse.

Las implementaciones alojadas inician la ejecución del programa invocando la mainfunción, que debe definirse siguiendo uno de estos prototipos (se permite usar diferentes nombres de parámetros o escribir los tipos de manera diferente):

int main () {...} int main ( void ) {...} int main ( int argc , char * argv []) {...} int main ( int argc , char ** argv ) {...} // char *argv[] y char **argv tienen el mismo tipo que los parámetros de la función               

Las dos primeras definiciones son equivalentes (y ambas son compatibles con C++). Probablemente, depende de las preferencias individuales cuál se utiliza (el estándar actual de C contiene dos ejemplos de main()y dos de main(void), pero el borrador del estándar de C++ utiliza main()). El valor de retorno de main(que debería ser int) sirve como estado de terminación que se devuelve al entorno del host.

El estándar C define los valores de retorno 0y EXIT_SUCCESScomo indicadores de éxito y EXIT_FAILUREcomo indicadores de fracaso. ( EXIT_SUCCESSy EXIT_FAILUREse definen en <stdlib.h>). Otros valores de retorno tienen significados definidos por la implementación; por ejemplo, en Linux, un programa finalizado por una señal produce un código de retorno del valor numérico de la señal más 128.

Un programa C mínimo correcto consiste en una mainrutina vacía, que no toma argumentos y no hace nada:

int principal ( vacío ){} 

Como no hay ninguna returndeclaración presente, maindevuelve 0 al salir. [3] (Esta es una característica de caso especial introducida en C99 que se aplica solo a main).

La mainfunción normalmente llamará a otras funciones para ayudarla a realizar su trabajo.

Algunas implementaciones no están alojadas, generalmente porque no están pensadas para usarse con un sistema operativo . Dichas implementaciones se denominan independientes en el estándar C. Una implementación independiente es libre de especificar cómo maneja el inicio del programa; en particular, no necesita que un programa defina una mainfunción.

Las funciones pueden ser escritas por el programador o proporcionadas por bibliotecas existentes. Las interfaces para estas últimas se declaran generalmente incluyendo archivos de encabezado (con la #include directiva de preprocesamiento ) y los objetos de la biblioteca se vinculan a la imagen ejecutable final. Ciertas funciones de biblioteca, como printf, están definidas por el estándar C; se las conoce como funciones de biblioteca estándar .

Una función puede devolver un valor al llamador (normalmente otra función C o el entorno de alojamiento de la función main). La printffunción mencionada anteriormente devuelve la cantidad de caracteres que se imprimieron, pero este valor suele ignorarse.

Paso de argumentos

En C, los argumentos se pasan a las funciones por valor , mientras que otros lenguajes pueden pasar variables por referencia . Esto significa que la función receptora obtiene copias de los valores y no tiene una forma directa de alterar las variables originales. Para que una función altere una variable pasada desde otra función, el invocador debe pasar su dirección (un puntero a ella), que luego puede desreferenciarse en la función receptora. Consulte Punteros para obtener más información.

void incInt ( int * y ) { ( * y ) ++ ; // Incrementa el valor de 'x', en 'main' a continuación, en uno }    int main ( void ) { int x = 0 ; incInt ( & x ); // pasa una referencia a la variable 'x' return 0 ; }         

La función scanf funciona de la misma manera:

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

Para pasar un puntero editable a una función (por ejemplo, con el propósito de devolver una matriz asignada al código de llamada), debe pasar un puntero a ese puntero: su dirección.

#include <stdio.h> #include <stdlib.h>  void allocate_array ( int ** const a_p , const int A ) { /* asignar una matriz de enteros A al asignar a *a_p altera 'a' en main() */ * a_p = malloc ( sizeof ( int ) * A ); }              int main ( void ) { int * a ; /* crea un puntero a uno o más ints, este será el array */       /* pasa la dirección de 'a' */ allocate_array ( &a a , 42 );  /* 'a' ahora es una matriz de longitud 42 y se puede manipular y liberar aquí */ libre ( a ); devuelve 0 ; }  

El parámetro int **a_pes un puntero a un puntero a un int, que es la dirección del puntero pdefinido en la función principal en este caso.

Parámetros de matriz

A primera vista, los parámetros de función de tipo matriz pueden parecer una excepción a la regla de paso por valor de C. El siguiente programa imprimirá 2, no 1:

#incluir <stdio.h> void setArray ( int matriz [], int índice , int valor ) { matriz [ índice ] = valor ; }         int main ( void ) { int a [ 1 ] = { 1 }; setArray ( a , 0 , 2 ); printf ( "a[0]=%d \n " , a [ 0 ]); devolver 0 ; }             

Sin embargo, existe una razón diferente para este comportamiento. De hecho, un parámetro de función declarado con un tipo de matriz se trata como uno declarado como puntero. Es decir, la declaración anterior de setArrayes equivalente a lo siguiente:

void setArray ( int * matriz , int índice , int valor )      

Al mismo tiempo, las reglas de C para el uso de matrices en expresiones hacen que el valor de aen la llamada a setArrayse convierta en un puntero al primer elemento de la matriz a. Por lo tanto, de hecho, este sigue siendo un ejemplo de paso por valor, con la salvedad de que es la dirección del primer elemento de la matriz la que se pasa por valor, no el contenido de la matriz.

Desde C99, el programador puede especificar que una función tome una matriz de un tamaño determinado mediante el uso de la palabra clave static. En void setArray(int array[static 4], int index, int value)el primer parámetro debe haber un puntero al primer elemento de una matriz de longitud al menos 4. También es posible agregar calificadores ( const, volatiley restrict) al tipo de puntero al que se convierte la matriz colocándolos entre corchetes.

Funciones anónimas

Sección 'C (extensión no estándar)' no encontrada

Misceláneas

Palabras clave reservadas

Las siguientes palabras están reservadas y no pueden utilizarse como identificadores:

Las implementaciones pueden reservar otras palabras clave, como asm, aunque las implementaciones generalmente proporcionan palabras clave no estándar que comienzan con uno o dos guiones bajos.

Sensibilidad a mayúsculas y minúsculas

Los identificadores de C distinguen entre mayúsculas y minúsculas (por ejemplo, foo, FOOy Fooson los nombres de objetos diferentes). Algunos enlazadores pueden asignar identificadores externos a una sola mayúscula y minúscula, aunque esto es poco común en la mayoría de los enlazadores modernos.

Comentarios

El texto que comienza con el token /* se trata como un comentario y se ignora. El comentario termina en el siguiente */; puede aparecer dentro de expresiones y puede abarcar varias líneas. La omisión accidental del terminador de comentario es problemática, ya que el terminador de comentario construido correctamente del siguiente comentario se utilizará para terminar el comentario inicial, y todo el código entre los comentarios se considerará un comentario. Los comentarios de estilo C no se anidan; es decir, colocar accidentalmente un comentario dentro de un comentario tiene resultados no deseados:

/*Esta línea será ignorada./*Aquí puede aparecer una advertencia del compilador. Estas líneas también se ignorarán.El token de apertura de comentario anterior no inició un nuevo comentario.y el token de cierre de comentario a continuación cerrará el comentario iniciado en la línea 1.*/Esta línea y la línea que se encuentra debajo no se ignorarán . Ambas probablemente producirán errores de compilación .                */

Los comentarios de línea al estilo C++ comienzan con //y se extienden hasta el final de la línea. Este estilo de comentario se originó en BCPL y se convirtió en sintaxis de C válida en C99 ; no está disponible en el C K&R original ni en el C ANSI :

// esta línea será ignorada por el compilador/* estas líneas  serán ignoradas  por el compilador */x = * p /*q; /* este comentario comienza después de la 'p' */  

Argumentos de la línea de comandos

Los parámetros dados en una línea de comandos se pasan a un programa C con dos variables predefinidas: el recuento de los argumentos de la línea de comandos en argcy los argumentos individuales como cadenas de caracteres en la matriz de punteros argv. Por lo tanto, el comando:

mi filtro p1 p2 p3

El resultado es algo como:

Si bien las cadenas individuales son matrices de caracteres contiguos, no hay garantía de que las cadenas se almacenen como un grupo contiguo.

El nombre del programa, argv[0], puede ser útil para imprimir mensajes de diagnóstico o para que un binario sirva para múltiples propósitos. Se puede acceder a los valores individuales de los parámetros con argv[1], argv[2], y argv[3], como se muestra en el siguiente programa:

#incluir <stdio.h> int principal ( int argc , char * argv []) { printf ( "argc \t = %d \n " , argc ); para ( int i = 0 ; i < argc ; i ++ ) printf ( "argv[%i] \t = %s \n " , i , argv [ i ]); }                  

Orden de evaluación

En cualquier expresión razonablemente compleja, surge una elección en cuanto al orden en el que se evalúan las partes de la expresión: pueden evaluarse en el orden , , , , o en el orden , , , . Formalmente, un compilador de C conforme puede evaluar expresiones en cualquier orden entre puntos de secuencia (esto permite al compilador realizar alguna optimización). Los puntos de secuencia se definen por:(1+1)+(3+3)(1+1)+(3+3)(2)+(3+3)(2)+(6)(8)(1+1)+(3+3)(1+1)+(6)(2)+(6)(8)

Las expresiones anteriores a un punto de secuencia siempre se evalúan antes que las posteriores a un punto de secuencia. En el caso de la evaluación por cortocircuito, la segunda expresión puede no evaluarse dependiendo del resultado de la primera expresión. Por ejemplo, en la expresión , si el primer argumento se evalúa como distinto de cero (verdadero), el resultado de toda la expresión no puede ser nada más que verdadero, por lo que no se evalúa. De manera similar, en la expresión , si el primer argumento se evalúa como cero (falso), el resultado de toda la expresión no puede ser nada más que falso, por lo que no se evalúa.(a() || b())b()(a() && b())b()

Los argumentos de una llamada a una función se pueden evaluar en cualquier orden, siempre que todos se hayan evaluado antes de que se introduzca la función. La siguiente expresión, por ejemplo, tiene un comportamiento indefinido:

 printf ( "%s %s \n " , argv [ i = 0 ], argv [ ++ i ]);    

Comportamiento indefinido

Un aspecto del estándar C (que no es exclusivo de C) es que se dice que el comportamiento de cierto código es "indefinido". En la práctica, esto significa que el programa producido a partir de ese código puede hacer cualquier cosa, desde funcionar como lo pretendía el programador hasta bloquearse cada vez que se ejecuta.

Por ejemplo, el siguiente código produce un comportamiento indefinido, porque la variable b se modifica más de una vez sin ningún punto de secuencia intermedio:

#incluir <stdio.h> int principal ( void ) { int b = 1 ; int a = b ++ + b ++ ; printf ( "%d \n " , a ); }             

Como no hay un punto de secuencia entre las modificaciones de b en " b +++ b ++", es posible realizar los pasos de evaluación en más de un orden, lo que genera una declaración ambigua. Esto se puede solucionar reescribiendo el código para insertar un punto de secuencia a fin de imponer un comportamiento inequívoco, por ejemplo:

a = b ++ ; a += b ++ ;    

Véase también

Notas

  1. ^ ab El long longmodificador fue introducido en el estándar C99 .
  2. ^ El significado de auto es un especificador de tipo en lugar de un especificador de clase de almacenamiento en C++0x
  3. ^ Ver la primera sección de UTF-8 para referencias
  4. ^ También se permiten otros tipos definidos por la implementación. C++ permite utilizar todos los tipos integrales y enumerados y muchos compiladores de C hacen lo mismo.

Referencias

  1. ^ "WG14-N2412: Representación del signo de complemento a dos" (PDF) . open-std.org . 11 de agosto de 2019. Archivado (PDF) del original el 27 de diciembre de 2022.
  2. ^ "WG14-N2341: ISO/IEC TS 18661-2 - Extensiones de punto flotante para C - Parte 2: Aritmética de punto flotante decimal" (PDF) . open-std.org . 26 de febrero de 2019. Archivado (PDF) del original el 21 de noviembre de 2022.
  3. ^ abc Klemens, Ben (2012). Siglo XXI C. O'Reilly Media . ISBN 978-1449327149.
  4. ^ Balagurusamy, E. Programación en ANSI C. Tata McGraw Hill. pag. 366.
  5. ^ "El preprocesador C: comportamiento definido por la implementación". gcc.gnu.org .
  6. ^ "Literales de cadenas y caracteres (C++)". Documentación de Visual C++ 19 . Consultado el 20 de noviembre de 2019 .
  7. ^ Kernighan y Richie
  8. ^ Tatham, Simon (2000). "Corrutinas en C" . Consultado el 30 de abril de 2017 .
General

Enlaces externos