La sintaxis del lenguaje de programación C es el conjunto de normas que regulan la escritura de software en el lenguaje C . Está diseñado para permitir programas que son extremadamente concisos, tienen una relación cercana con el código de objeto resultante y, sin embargo, proporcionan una abstracción de datos de nivel relativamente alto . C fue el primer lenguaje de alto nivel de gran éxito 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 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 de enteros de C tienen variantes signed
y unsigned
. Si signed
o unsigned
no se especifica explícitamente, en la mayoría de las circunstancias signed
se asume. Sin embargo, por razones históricas, plain char
es un tipo distinto de ambos signed char
y unsigned char
. Puede ser un tipo con signo o sin signo, según el compilador y el juego de caracteres (C garantiza que los miembros del juego de caracteres básico de C tienen valores positivos). Además, los tipos de campo de bits especificados como simples int
pueden estar firmados o sin firmar, según el compilador.
Tipos de enteros
Los tipos de enteros de C vienen en diferentes tamaños fijos, capaces de representar varios rangos de números. El tipo char
ocupa exactamente un byte (la unidad de almacenamiento direccionable más pequeña), que normalmente tiene un ancho de 8 bits. (Aunque char
puede representar cualquiera de los caracteres "básicos" de C, es posible que se requiera un tipo más amplio para los conjuntos de caracteres internacionales). La mayoría de los tipos enteros tienen variedades con y sin signo , designadas por las palabras clave signed
y unsigned
. Tipos de enteros con signo pueden utilizar un complemento de dos , complemento a uno , o de firma y magnitud representación . En muchos casos, existen múltiples formas equivalentes de designar el tipo; por ejemplo, signed short int
y short
son sinónimos.
La representación de algunos tipos puede incluir bits de "relleno" no utilizados, que ocupan almacenamiento pero no se incluyen en el ancho. La siguiente tabla proporciona una lista completa de los tipos de enteros estándar y sus anchos mínimos permitidos (incluido cualquier bit de signo).
Forma más corta de especificador | Ancho mínimo (bits) |
---|---|
_Bool | 1 |
char | 8 |
signed char | 8 |
unsigned char | 8 |
short | dieciséis |
unsigned short | dieciséis |
int | dieciséis |
unsigned int | dieciséis |
long | 32 |
unsigned long | 32 |
long long [1] | 64 |
unsigned long long [1] | 64 |
El char
tipo es distinto de ambos signed char
y unsigned char
, pero se garantiza que tendrá la misma representación que uno de ellos. Los tipos _Bool
y long long
están estandarizados desde 1999 y es posible que los compiladores de C anteriores no los admitan. Tipo _Bool
normalmente se accede a través del typedef
nombre bool
definido 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, prestando cierta atención a la facilidad de importar el código fuente desarrollado para otras plataformas. El ancho del int
tipo 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 de enteros estándar implementados en cualquier plataforma específica.
Además de los tipos de enteros estándar, puede haber otros tipos de enteros "extendidos", que pueden usarse para typedef
s en encabezados estándar. Para una especificación más precisa del ancho, los programadores pueden y deben usar typedef
s del encabezado estándar stdint.h .
Las constantes enteras se pueden especificar en el código fuente de varias formas. Los valores numéricos se pueden especificar como decimal (ejemplo :)1022
, octal con cero (0) como prefijo ( 01776
) o hexadecimal 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 juego de caracteres de ejecución, con tipo int
. A excepción de las constantes de caracteres, el tipo de una constante entera está determinado por el ancho requerido para representar el valor especificado, pero siempre es al menos tan ancho como int
. Esto se puede anular agregando un modificador explícito de longitud y / o firma; por ejemplo, 12lu
tiene 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 enum
palabra clave, y a menudo llamado simplemente "enum" (generalmente se pronuncia ee'-num /ˌi.nʌm/ o ee'-noom /ˌi.nuːm/), es un tipo diseñado para representar valores a través de una serie de constantes nombradas. Cada una de las constantes enumeradas tiene tipo int
. Cada enum
tipo en sí mismo es compatible con char
un tipo 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 tipo enumerado se le asigna un valor que no es una de sus constantes. Sin embargo, a dicho objeto se le puede asignar cualquier valor en el rango de su tipo compatible, y las enum
constantes se pueden usar en cualquier lugar donde se espere un número entero. Por esta razón, los enum
valores se utilizan a menudo en lugar de las #define
directivas del preprocesador para crear constantes con nombre. Estas constantes son generalmente más seguras de usar que las macros, ya que residen dentro de un espacio de nombres de identificador específico.
Un tipo enumerado se declara con el enum
especificador y un nombre (o etiqueta ) opcional 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 usan la enum
palabra 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 sobre la constante anterior. También se pueden asignar valores específicos a las constantes en la declaración, y cualquier constante subsiguiente sin valores específicos recibirá valores incrementados desde ese punto en adelante. Por ejemplo, considere la siguiente declaración:
enum colors { ROJO , VERDE , AZUL = 5 , AMARILLO } paint_color ;
Esto declara el enum colors
tipo; las int
constantes 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 colors
variable paint_color
. Las constantes se pueden usar fuera del contexto de la enumeración (donde se permite cualquier valor entero), y se pueden asignar valores distintos a las constantes paint_color
o cualquier otra variable de tipo enum colors
.
Tipos de coma flotante
La forma de punto flotante se utiliza 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 de valores reales, indicados por sus especificadores: precisión simple ( float
), precisión doble ( double
) y precisión extendida doble ( long double
). Cada uno de estos puede representar valores en una forma diferente, a menudo uno de los formatos de punto flotante IEEE .
Especificadores de tipo | Precisión (dígitos decimales) | Rango de exponente | ||
---|---|---|---|---|
Mínimo | IEEE 754 | Mínimo | IEEE 754 | |
float | 6 | 7,2 (24 bits) | ± 37 | ± 38 (8 bits) |
double | 10 | 15,9 (53 bits) | ± 37 | ± 307 (11 bits) |
long double | 10 | 34,0 (113 bits) | ± 37 | ± 4931 (15 bits) |
Las constantes de coma flotante se pueden escribir en notación decimal , por ejemplo 1.23
. La notación científica decimal se puede usar agregando e
o E
seguida de un exponente decimal, también conocida como notación E , por ejemplo 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 hexadecimales de coma flotante siguen reglas similares, excepto que deben tener el prefijo 0x
y usar p
o P
para especificar un exponente binario, por ejemplo 0xAp-2
(que tiene el valor 2.5, ya que A h × 2 −2 = 10 × 2 −2 = 10 ÷ 4 ). Tanto las constantes de coma flotante decimal como hexadecimal pueden tener el sufijo f
o F
para indicar una constante de tipo float
, por l
(letra l
) o L
para indicar el tipo long double
, o dejarse sin sufijo para una double
constante.
El archivo de cabecera estándar float.h
define los valores mínimos y máximos de los tipos de punto flotante de la implementación float
, double
y long double
. También define otros límites que son relevantes para el procesamiento de números de punto flotante.
Especificadores de clase de almacenamiento
Cada objeto tiene una clase de almacenamiento. Esto especifica básicamente la duración del almacenamiento , que puede ser estático (predeterminado para global), automático (predeterminado para local) o dinámico (asignado), junto con otras características (enlace y sugerencia de registro).
Especificadores | Toda la vida | Alcance | Inicializador predeterminado |
---|---|---|---|
auto | Bloque (pila) | Cuadra | Sin inicializar |
register | Bloque (pila o registro de CPU) | Cuadra | Sin inicializar |
static | Programa | Unidad de bloque o compilación | Cero |
extern | Programa | Global (programa completo) | Cero |
(ninguno) 1 | Dinámico (montón) | Sin inicializar (inicializado en 0 si se usa calloc() ) |
- 1 Asignado y desasignado mediante las funciones de la biblioteca
malloc()
yfree()
.
Las variables declaradas dentro de un bloque por defecto tienen almacenamiento automático, al igual que las declaradas explícitamente con [2] o especificadores de clase de almacenamiento. Los especificadores y solo se pueden usar 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 los declarados explícitamente con el especificador de clase de almacenamiento tienen una duración de almacenamiento estática. Las variables estáticas se inicializan a cero de forma predeterminada por el compilador .auto
register
auto
register
auto
static
Los objetos con almacenamiento automático son locales al bloque en el que fueron declarados y se descartan cuando se sale del bloque. Además, register
el compilador puede dar mayor prioridad a los objetos declarados con la clase de almacenamiento para acceder a los registros ; aunque el compilador puede optar por 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 a través de múltiples llamadas. Los objetos con una duración de almacenamiento asignado se crean y se destruyen de forma explícita con malloc
, free
y funciones relacionadas.
El extern
especificador de clase de almacenamiento indica que el almacenamiento de un objeto se ha definido en otro lugar. Cuando se usa dentro de un bloque, indica que el almacenamiento ha sido definido por una declaración fuera de ese bloque. Cuando se usa fuera de todos los bloques, indica que el almacenamiento se ha definido fuera de la unidad de compilación. El extern
especificador de clase de almacenamiento es redundante cuando se usa en una declaración de función. Indica que la función declarada se ha definido fuera de la unidad de compilación.
Tenga en cuenta que los especificadores de almacenamiento se aplican solo a funciones y objetos; otras cosas, como las declaraciones de tipo y enumeración, son privadas para la unidad de compilación en la que aparecen. Los tipos, por otro lado, tienen calificadores (ver más abajo).
Calificadores de tipo
Los tipos se pueden calificar para indicar propiedades especiales de sus datos. El calificador de tipo const
indica que un valor no cambia una vez que se ha inicializado. Intentar modificar un const
valor calificado produce un comportamiento indefinido, por lo que algunos compiladores de C los almacenan en rodata o (para sistemas integrados) en memoria de solo lectura (ROM). El calificador de tipo volatile
indica a un compilador de optimización 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 escrituras múltiples, como para I mapeado en memoria / O .
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 void
tipo (el void
tipo no se puede completar). No se puede crear una instancia de este tipo (se desconoce su tamaño), ni se puede acceder a sus miembros (también se desconocen); sin embargo, el tipo de puntero derivado puede usarse (pero no desreferenciarse).
A menudo se utilizan con punteros, ya sea como declaraciones directas o externas. Por ejemplo, el código podría declarar un tipo incompleto como este:
estructura cosa * pt ;
Esto declara pt
como un puntero struct thing
y el tipo incompleto struct thing
. Los punteros a los datos siempre tienen el mismo ancho de bytes independientemente de lo que apunten, por lo que esta declaración es válida por sí misma (siempre que pt
no esté desreferenciada). El tipo incompleto se puede completar más tarde en el mismo alcance volviéndolo a declarar:
estructura cosa { int num ; }; / * el tipo de estructura de la cosa ahora está completo * /
Los tipos incompletos se utilizan para implementar estructuras recursivas ; el cuerpo de la declaración de tipo puede diferirse más adelante en la unidad de traducción:
typedef struct Bert Bert ; typedef struct Wilma Wilma ;struct Bert { Wilma * wilma ; };struct 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 de asterisco ( *
) especifica un tipo de puntero. Por ejemplo, donde el especificador int
se referiría al tipo de entero, el especificador se int*
refiere 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 de puntero a entero llamada ptr :
int * ptr ;
Referenciar
Cuando se declara un puntero no estático, tiene un valor no especificado asociado. La dirección asociada con dicho puntero debe cambiarse por asignación antes de usarlo. En el siguiente ejemplo, ptr se establece para que apunte a los datos asociados con la variable a :
int a = 0 ; int * ptr = & a ;
Para lograr esto, &
se utiliza el operador "dirección de" (unario ). Produce la ubicación de memoria del objeto de datos que sigue.
Desreferenciar
Se puede acceder a los datos apuntados a través de 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:
int a = 10 ; int * p ; p = & a ; int b = * p ;
Para realizar esa tarea , se utiliza el operador unario de desreferencia , denotado por un asterisco (*). Devuelve los datos a los que apunta su operando, que debe ser de tipo puntero. Por tanto, la expresión * p denota el mismo valor que a . Desreferenciar un puntero nulo es ilegal.
Matrices
Definición de matriz
Las matrices se utilizan en C 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 llamada matriz para contener 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 el número especificado de elementos. En la mayoría de los contextos de uso posterior, una mención de la matriz de variables se convierte en un puntero al primer elemento de la matriz. El sizeofoperador es una excepción: sizeof array
produce el tamaño de toda la matriz (es decir, 100 veces el tamaño de an int
, y sizeof(array) / sizeof(int)
devolverá 100). Otra excepción es el operador & (dirección de), que produce un puntero a toda la matriz, por ejemplo
int ( * ptr_to_array ) [ 100 ] = & matriz ;
Accediendo a 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 i- indexado de la matriz , la sintaxis sería array[i]
, que se refiere al valor almacenado en ese elemento de la matriz.
La numeración de subíndices de matriz comienza en 0 (consulte Indexación basada en cero ). Por lo tanto, el subíndice de matriz permitido más grande es igual al número de elementos de la matriz menos 1. Para ilustrar esto, considere que una matriz a declarada tiene 10 elementos; el primer elemento sería a[0]
y el último elemento sería a[9]
.
C no proporciona ninguna función para la comprobación automática de límites para el uso de matrices. Aunque lógicamente el último subíndice en 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:
Elemento | Primero | Segundo | Tercero | n th |
---|---|---|---|---|
Subíndice de matriz | array[0] | array[1] | array[2] | array[n - 1] |
Puntero desreferenciado | *array | *(array + 1) | *(array + 2) | *(array + n - 1) |
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 usa.
Matrices de longitud variable
C99 matrices de longitud variable (VLA) estandarizadas dentro del alcance del bloque. Dichas 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 cambiar de tamaño dinámicamente pueden ser producidas con la ayuda de la biblioteca estándar C . La malloc
función proporciona un método simple para asignar memoria. Se necesita un parámetro: la cantidad de memoria para asignar en bytes. Tras una asignación exitosa, malloc
devuelve un void
valor de puntero genérico ( ), que apunta al comienzo del espacio asignado. El valor de puntero devuelto se convierte a un tipo apropiado implícitamente por asignación. Si no se pudo completar la asignación, malloc
devuelve un puntero nulo . Por lo tanto, el siguiente segmento es similar en función a la declaración deseada anterior:
#include / * declara malloc * /... int * a = malloc ( n * tamaño de * a ); a [ 3 ] = 10 ;
El resultado es una int
variable "puntero a " ( a ) que apunta al primero de nint
objetos contiguos ; debido a la equivalencia matriz-puntero, esto se puede usar en lugar de un nombre de matriz real, como se muestra en la última línea. La ventaja de usar 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 (usando la función de biblioteca estándar realloc).
Cuando la memoria asignada dinámicamente ya no se necesita, debe liberarse de nuevo al sistema de tiempo de ejecución. Esto se hace con una llamada a la free
función. Toma un solo parámetro: un puntero a la memoria previamente asignada. Este es el valor devuelto por una llamada anterior a malloc
.
Como medida de seguridad, algunos programadores [ ¿quién? ] luego establezca la variable de puntero en NULL
:
libre ( a ); a = NULO ;
Esto asegura que los intentos posteriores de eliminar la referencia del puntero, en la mayoría de los sistemas, bloqueen el programa. Si no se hace esto, la variable se convierte en un puntero colgante que puede provocar un error de uso después de la liberación. Sin embargo, si el puntero es una variable local, establecerlo en NULL
no evita que el programa utilice otras copias del puntero. Los errores de uso local después de la eliminación suelen ser fáciles de reconocer para los analizadores estáticos . Por lo tanto, este enfoque es menos útil para punteros locales y se usa más a menudo con punteros almacenados en estructuras de larga duración. Sin embargo, en general, establecer punteros NULL
es una buena práctica [¿ según quién? ], ya que permite que un programador NULL
-compruebe los punteros antes de eliminar las referencias, ayudando así a evitar 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 matriz.
El acceso al puntero a la matriz se puede hacer de dos maneras:
( * a ) [ índice ];índice [ * a ];
La iteración también se puede hacer de dos formas:
para ( int i = 0 ; i < 100 ; i ++ ) ( * a ) [ i ];para ( int * i = a [ 0 ]; i < a [ 1 ]; i ++ ) * i ;
El beneficio de usar 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 se puede ejecutar sin modificaciones.
Matrices multidimensionales
Además, C admite matrices de varias 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 FILAS y COLUMNAS son constantes. Esto define una matriz bidimensional. Al leer los subíndices de izquierda a derecha, array2d es un arreglo de FILAS de longitud , cada elemento del cual es un arreglo de COLUMNAS enteros.
Para acceder a un elemento entero en esta matriz multidimensional, se usaría
array2d [ 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, que luego estamos subíndice con [3] para acceder al cuarto entero.
Elemento | Primero | Segunda fila, segunda columna | i- ésima fila, j- ésima columna |
---|---|---|---|
Subíndice de matriz | array[0][0] | array[1][1] | array[i - 1][j - 1] |
Puntero desreferenciado | *(*(array + 0) + 0) | *(*(array + 1) + 1) | *(*(array + i - 1) + j - 1) |
Las matrices de mayor dimensión se pueden declarar de manera similar.
Una matriz multidimensional no debe confundirse con una matriz de referencias a matrices (también conocida como vectores Iliffe o, a veces, una matriz de matrices ). El primero es siempre rectangular (todos los subarreglos deben tener el mismo tamaño) y ocupa una región contigua de memoria. Este último es un arreglo unidimensional de punteros, cada uno de los cuales puede apuntar al primer elemento de un subarreglo en un lugar diferente de la memoria, y los subarreglos no tienen que ser del mismo tamaño. Este último puede crearse 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 los char
valores especificados con un código adicional de carácter de terminación nulo (valor 0) para marcar el final de la cadena.
Los literales de cadena no pueden contener nuevas líneas incrustadas; esta proscripción simplifica un poco el análisis sintáctico del idioma. Para incluir una nueva línea en una cadena, se puede usar el escape de barra invertida \n
, como se muestra a continuación.
Hay varias funciones de biblioteca estándar para operar con datos de cadena (no necesariamente constantes) organizados como una matriz de char
usar este formato terminado en nulo; ver más abajo .
La sintaxis literal de cadena de C ha sido muy influyente y se ha abierto camino en muchos otros lenguajes, como C ++, Objective-C, Perl, Python, PHP, Java, Javascript, C #, Ruby. Hoy en día, casi todos los lenguajes nuevos adoptan o se basan en la sintaxis de cadenas de estilo C. Los idiomas que carecen de esta sintaxis tienden a preceder a C.
Escapes de barra invertida
Si desea incluir una comilla doble dentro de la cadena, puede hacerlo escapándola con una barra invertida ( \
), por ejemplo "This string contains \"double quotes\"."
,. Para insertar una barra invertida literal, se debe duplicar, por ejemplo "A backslash looks like this: \\"
.
Se pueden usar barras invertidas para ingresar caracteres de control, etc., en una cadena:
Escapar | Significado |
---|---|
\\ | Barra invertida literal |
\" | Cotización doble |
\' | Una frase |
\n | Newline (salto de línea) |
\r | Retorno de carro |
\b | Retroceso |
\t | Pestaña horizontal |
\f | Alimentación de formulario |
\a | Alerta (campana) |
\v | Pestaña vertical |
\? | Signo de interrogación (usado para escapar de los trígrafos ) |
%% | Marca de porcentaje, solo cadenas de formato printf (Nota \% no es estándar y no siempre se reconoce) |
\OOO | Carácter con valor octal OOO (donde OOO es 1-3 dígitos octales, '0' - '7') |
\xHH | Carácter con valor hexadecimal HH (donde HH es 1 o más dígitos hexadecimales, '0' - '9', 'A' - 'F', 'a' - 'f') |
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 de lenguaje. Uno de ellos es la secuencia de escape \e
para 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 literal de cadena
C tiene concatenación literal de cadena , lo que significa que las cadenas literales 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 las cadenas literales resultantes de las definiciones del preprocesador de C y las macros se agreguen a las cadenas en tiempo de compilación:
printf ( __FILE__ ":% d: Hola" "mundo \ n " , __LINE__ );
se expandirá a
printf ( "helloworld.c" ":% d: Hola" "mundo \ n " , 10 );
que es sintácticamente equivalente a
printf ( "hola mundo.c:% d: Hola mundo \ n " , 10 );
Constantes de caracteres
Las constantes de caracteres individuales están entrecomilladas, por ejemplo '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 'A'
que representa directamente el valor del carácter (65 si se usa ASCII). Se admiten los mismos escapes de barra invertida que para las cadenas, excepto que (por supuesto) "
se pueden usar válidamente como un carácter sin escaparse, mientras '
que ahora se debe escapar.
Una constante de carácter no puede estar vacía ( ''
es decir, es una sintaxis inválida), aunque una cadena puede estarlo (todavía tiene el carácter de terminación nulo). Las constantes de varios caracteres (p 'xy'
. Ej. ) Son válidas, aunque raras veces útiles; permiten almacenar varios caracteres en un entero (p. Ej., 4 caracteres ASCII pueden caber en un entero de 32 bits y 8 en uno de 64 bits). Dado que el orden en el que se empaquetan los caracteres en un int
no está especificado (se deja a la implementación para definir), el uso portátil de constantes de múltiples caracteres es difícil.
Sin embargo, en situaciones limitadas a una plataforma específica y la implementación del compilador, las constantes de múltiples caracteres encuentran su uso para especificar firmas. Un caso de uso común es el OSType , donde la combinación de compiladores Classic Mac OS 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 "implementaciones" populares es de hecho coherente: en GCC, Clang y Visual C ++ , se '1234'
obtiene en ASCII. [5] [6]0x31323334
Cadenas de caracteres anchas
Dado que el tipo char
tiene 1 byte de ancho, un char
valor único normalmente puede representar como máximo 255 códigos de caracteres distintos, lo que no es suficiente para todos los caracteres en uso en todo el mundo. Para proporcionar un mejor soporte para caracteres internacionales, el primer estándar C (C89) introdujo caracteres anchos (codificados en tipo wchar_t
) y cadenas de caracteres anchas, 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 (generalmente UTF-32 ), pero el estándar C no especifica el ancho wchar_t
, dejando la elección al implementador. Microsoft Windows generalmente usa UTF-16, por lo que la cadena anterior tendría 26 bytes de longitud para un compilador de Microsoft; el mundo Unix prefiere UTF-32, por lo que compiladores como GCC generarían una cadena de 52 bytes. Un ancho de 2 bytes wchar_t
sufre la misma limitación que char
, en el sentido de que ciertos caracteres (los que están fuera del BMP ) no se pueden representar en un solo wchar_t
; pero debe representarse mediante pares sustitutos .
El estándar C original especificaba solo funciones mínimas para operar con cadenas de caracteres anchas; en 1995, el estándar se modificó para incluir un soporte mucho más amplio, comparable al de las char
cuerdas. La mayoría de las funciones relevantes reciben el nombre de sus char
equivalentes, con la adición de una "w" o el reemplazo de "str" por "wcs"; se especifican en
, con
funciones de clasificación y mapeo de caracteres anchos que contienen.
El método ahora generalmente recomendado [7] para admitir caracteres internacionales es a través de UTF-8 , que se almacena en char
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 ASCII directa .
Cadenas de ancho variable
Una alternativa común wchar_t
es usar una codificación de ancho variable , por lo que un carácter lógico puede extenderse sobre múltiples posiciones de la cadena. Las cadenas de ancho variable pueden codificarse literalmente en literales, con el riesgo de confundir al compilador o utilizando escapes de barra invertida numérica (por ejemplo, "\xc3\xa9"
para "é" en UTF-8). La codificación UTF-8 se diseñó específicamente (según el Plan 9 ) para ser compatible con las funciones de cadena de la biblioteca estándar; Las características de apoyo de la codificación incluyen la falta de nulos incrustados, la falta de interpretaciones válidas para las subsecuencias y la resincronización trivial. Es probable que las codificaciones que carecen de estas características resulten incompatibles con las funciones de biblioteca estándar; Las funciones de cadena con reconocimiento de codificación se utilizan a menudo en tales casos.
Funciones de 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
Las estructuras y uniones en C se definen como contenedores de datos que constan de una secuencia de miembros con nombre de varios tipos. Son similares a los registros de 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 o después de los miembros (pero no antes del primer miembro) por eficiencia o como relleno requerido para la 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 estructuras y se definen como objetos que pueden contener (en diferentes momentos) objetos de diferentes tipos y tamaños. Son análogos a los registros variantes en otros lenguajes de programación. A diferencia de las estructuras, todos los componentes de una unión se refieren a la misma ubicación en la memoria. De esta manera, una unión se puede usar 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 struct
palabra clave y las uniones se declaran con la union
palabra 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 de la estructura o el cuerpo de la 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 nombrada s
que contiene tres miembros; también declarará una instancia de la estructura conocida como tee
:
estructura s { int x ; flotar y ; char * z ; } tee ;
Y la siguiente declaración declarará una unión similar nombrada u
y una instancia de ella nombrada n
:
unión u { int x ; flotar y ; char * z ; } n ;
Los miembros de estructuras y sindicatos no pueden tener un tipo de función o incompleta. 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 indicadores del tipo que se declara.
Una vez que se ha declarado una estructura o cuerpo de unión y se le ha dado un nombre, se puede considerar un nuevo tipo de datos utilizando el especificador struct
o 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 s
denominada r
:
struct s r ;
También es común usar el typedef
especificador para eliminar la necesidad de la palabra clave struct
o union
en referencias posteriores a la estructura. El primer identificador después del cuerpo de la estructura se toma como el nuevo nombre para el tipo de estructura (las instancias de estructura no pueden declararse en este contexto). Por ejemplo, la siguiente declaración declarará un nuevo tipo conocido como s_type que contendrá alguna estructura:
typedef struct {...} s_type ;
Las declaraciones futuras pueden usar el especificador s_type (en lugar del struct
especificador expandido ...) para hacer referencia a la estructura.
Acceso a miembros
Se accede a los miembros utilizando 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
) usando la siguiente sintaxis:
tee . y
Se suele acceder a las estructuras a través de punteros. Considere el siguiente ejemplo que define un puntero a tee , conocido como ptr_to_tee :
struct s * ptr_to_tee = & tee ;
Luego se puede acceder al miembro y de tee eliminando la referencia ptr_to_tee y usando el resultado como el operando izquierdo:
( * ptr_to_tee ). y
Lo cual es idéntico al más simple tee.y
anterior siempre que ptr_to_tee apunte a tee . Debido a la precedencia del operador ("." Es mayor que "*"), el más corto *ptr_to_tee.y
es 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
Se accede a los miembros de los sindicatos de la misma forma.
Esto se puede encadenar; por ejemplo, en una lista vinculada, se puede hacer referencia al n->next->next
segundo nodo siguiente (asumiendo que n->next
no es nulo).
Asignación
Asignar valores a miembros individuales de estructuras y uniones es sintácticamente idéntico a asignar valores a cualquier otro objeto. La única diferencia es que el lvalue 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 una unidad a otra estructura del mismo tipo. Las estructuras (y 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 del código ASCII para la letra 't') al miembro llamado x en la estructura tee , desde arriba:
tee . x = 74 ;
Y la misma asignación, usando ptr_to_tee en lugar de tee , se vería así:
ptr_to_tee -> x = 74 ;
La asignación con miembros de sindicatos es idéntica.
Otras operaciones
Según el estándar C, las únicas operaciones legales que se pueden realizar en una estructura son copiarla, asignarla como una unidad (o inicializarla), tomar su dirección con el &
operador unario address-of ( ) y acceder a sus miembros. . Los sindicatos tienen las mismas restricciones. Una de las operaciones implícitamente prohibido es la comparación: estructuras y uniones no se pueden comparar utilizando las instalaciones estándar de comparación de C ( ==
, >
, <
, etc.).
Campos de bits
C también proporciona un tipo especial de miembro de estructura conocido como campo de bits , que es un número entero con un número de bits especificado explícitamente. Un campo de bits se declara como un miembro de estructura de tipo int
, signed int
, unsigned int
, o _Bool
, tras el nombre del miembro de dos puntos ( :
) y el número de bits que debe ocupar. El número total de bits en un campo de un solo bit no debe exceder el número total de bits en su tipo declarado.
Como una excepción especial a las reglas de sintaxis habituales de C, se define por implementación si un campo de bits declarado como tipo int
, sin especificar signed
o unsigned
, está firmado o sin firmar. Por lo tanto, se recomienda especificar explícitamente signed
o unsigned
en todos los miembros de la estructura para la portabilidad.
También se permiten campos sin nombre que constan de dos puntos seguidos de varios bits; estos indican relleno . La especificación de un ancho de cero para un campo sin nombre se usa para forzar la alineación con una nueva palabra. [8]
Los miembros de los campos de bits no tienen direcciones y, como tales, no se pueden utilizar con el &
operador unario address-of ( ). El sizeof
operador no puede aplicarse a campos de bits.
La siguiente declaración declara un nuevo tipo de estructura conocido como f
y una instancia del mismo conocido como g
. Los comentarios proporcionan una descripción de cada uno de los miembros:
struct f { bandera int sin firmar : 1 ; / * Un indicador de bits: puede ser o bien en (1) o fuera (0) * / firmado int num : 4 ; / * un campo firmado de 4 bits; rango -7 ... 7 o -8 ... 7 * / firmado 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 idioma, un inicializador escalar puede incluirse en cualquier número de pares de llaves. Sin embargo, la mayoría de los compiladores emiten una advertencia si hay más de uno de esos pares.
int x = 12 ; int y = { 23 }; // Legal, sin advertencia int z = { { 34 } }; // Legal, espere una advertencia
Las estructuras, uniones y matrices se pueden inicializar en sus declaraciones utilizando 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 precedentes 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 produce un error.
La siguiente declaración le inicializar una nueva instancia de la estructura s conocido como PI :
estructura s { int x ; flotar y ; char * z ; };struct s pi = { 3 , 3.1415 , "Pi" };
Inicializadores designados
Los inicializadores designados permiten que los miembros se inicialicen por nombre, en cualquier orden y sin proporcionar explícitamente los valores anteriores. La siguiente inicialización es equivalente a la anterior:
struct 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 siguiente ejemplo, si MAX
es mayor que 10, habrá algunos elementos de valor cero en el medio de a
; si es menor que 10, algunos de los valores proporcionados por los primeros cinco inicializadores serán anulados por los segundos cinco (si MAX
es menor que 5, habrá un error de compilación):
int a [ MAX ] = { 1 , 3 , 5 , 7 , 9 , [ MAX -5 ] = 8 , 6 , 4 , 2 , 0 };
En C89 , una unión se inicializó con un único valor aplicado a su primer miembro. Es decir, la unión u definida anteriormente solo podría tener su miembro int x inicializado:
unión u valor = { 3 };
Con un inicializador designado, el miembro que se 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 ), el número de inicializadores determina el tamaño de la matriz y su tipo se completa:
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 malinterpretarse. En el siguiente ejemplo, w
se 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 w
en 2 y establece los valores del primer elemento de cada uno a
:
struct { int a [ 3 ], b ; } w [] = { [ 0 ]. a = { 1 }, [ 1 ]. a [ 0 ] = 2 };
Esto es equivalente a:
struct { 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 el estándar C.
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 una matriz literal. int * ptr = ( int []) { 10 , 20 , 30 , 40 };// puntero a la matriz. float ( * foo ) [ 3 ] = & ( float []) { 0.5f , 1.f , -0.5f };struct s pi = ( struct 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 = ( struct s ) { . z = "Pi" , . x = 3 , . y = 3,1415 };
Operadores
Estructuras de Control
C es un lenguaje de forma libre .
El estilo de los refuerzos varía de un programador a otro y puede ser objeto de debate. Consulte Estilo de sangría para obtener más detalles.
Declaraciones compuestas
En los elementos de esta sección, cualquier
{ < Opcional - Declaración - lista > < opcional - Declaración - lista > }
y se utilizan como el cuerpo de una función o en cualquier lugar donde se espere 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 a realizar. Los corchetes definen su propio alcance, y las variables definidas dentro de esos corchetes se desasignarán automáticamente en el corchete de cierre. Las declaraciones y los enunciados se pueden mezclar libremente dentro de un enunciado compuesto (como en C ++ ).
Declaraciones de selección
C tiene dos tipos de enunciados de selección : el ifenunciado y el switchenunciado .
La if
declaración tiene la forma:
if ( < expresión > ) < instrucción1 > else < instrucción2 >
En la if
declaración, si el
entre paréntesis es distinto de cero (verdadero), el control pasa a
. Si la else
cláusula está presente y
es cero (falso), el control pasará a
. La else
parte es opcional y, si está ausente, un falso
simplemente resultará en omitir el
. An else
siempre coincide con el anterior no emparejado más cercano if
; Se pueden usar llaves para anular esto cuando sea necesario, o para mayor claridad.
La switch
declaración hace que el control se transfiera a una de varias declaraciones dependiendo del valor de una expresión , que debe tener un tipo integral . La subdeclaración controlada por un interruptor suele ser compuesta. Cualquier declaración dentro de la subdeclaración puede etiquetarse con una o más case
etiquetas, que consisten en la palabra clave case
seguida 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 es posible que dos de las constantes de casos asociadas con el mismo conmutador tengan el mismo valor. Puede haber como máximo una default
etiqueta asociada con un interruptor. Si ninguna de las etiquetas de caso es igual a la expresión entre paréntesis switch
, el control pasa a la default
etiqueta o, si no hay default
etiqueta, la ejecución se reanuda más allá de la construcción completa.
Los interruptores pueden estar anidados; una etiqueta case
o default
está asociada con la más interna switch
que la contiene. Las sentencias de cambio pueden "fallar", es decir, cuando una sección del caso ha completado su ejecución, las sentencias continuarán ejecutándose hacia abajo hasta break;
que se encuentre una sentencia. La caída es útil en algunas circunstancias, pero generalmente no se desea. En el ejemplo anterior, si
se alcanza, las sentencias
se ejecutan y nada más dentro de las llaves. Sin embargo, si
se alcanza, ambos
y
se ejecutan ya que no hay break
que separar las dos declaraciones de caso.
Es posible, aunque inusual, insertar las switch
etiquetas en los sub-bloques de otras estructuras de control. Ejemplos de esto incluyen el dispositivo de Duff y la implementación de corrutinas en Putty por Simon Tatham . [9]
Declaraciones de iteración
C tiene tres formas de declaración de iteración :
hacer < declaración > while ( < expresión > ) ;while ( < expresión > ) < declaración >para ( < expresión > ; < expresión > ; < expresión > ) < declaración >
En las declaraciones whiley do
, la subsección se ejecuta repetidamente siempre que el valor de expression
permanezca distinto de cero (equivalente a verdadero). Con while
, la prueba, incluidos todos los efectos secundarios de
, se produce antes de cada iteración (ejecución de
); con do
, la prueba ocurre después de cada iteración. Por tanto, una do
sentencia siempre ejecuta su sub sentencia al menos una vez, mientras que while
puede que no ejecute la sub sentencia en absoluto.
La declaración:
para ( e1 ; e2 ; e3 ) s ;
es equivalente a:
e1 ; while ( e2 ) { s ; cont : e3 ; }
excepto por el comportamiento de una continue;
declaración (que en el for
bucle salta en e3
lugar de e2
). Si e2
está en blanco, debería reemplazarse con un 1
.
Se for
puede omitir cualquiera de las tres expresiones del ciclo. Una segunda expresión faltante hace que la while
prueba siempre sea distinta de cero, creando un bucle potencialmente infinito.
Desde C99 , la primera expresión puede tomar la forma de una declaración, que generalmente incluye un inicializador, como:
para ( int i = 0 ; i < límite ; ++ i ) { // ... }
El alcance de la declaración se limita a la extensión del for
bucle.
Saltar declaraciones
Las declaraciones de salto transfieren el control incondicionalmente. Hay cuatro tipos de sentencias de salto en C: goto, continue
, break
, y return.
La goto
declaración se ve así:
ir a < identificador > ;
El identificador debe ser una etiqueta (seguida de dos puntos) ubicada en la función actual. El control se transfiere a la declaración etiquetada.
Una continue
instrucción puede aparecer solo dentro de una instrucción de iteración y hace que el control pase a la parte de continuación de bucle de la instrucción de iteración que la encierra más internamente. Es decir, dentro de cada una de las declaraciones
mientras que ( expresión ) { / * ... * / cont : ; }hacer { / * ... * / cont : ; } while ( expresión );para ( expr1 ; expr2 ; expr3 ) { / * ... * / cont : ; }
un continue
no contenido dentro de una instrucción de iteración anidada es lo mismo que goto cont
.
La break
declaración se utiliza para finalizar un for
bucle, while
bucle, do
bucle o switch
declaración. El control pasa al enunciado que sigue al enunciado terminado.
Una función vuelve a su llamador mediante la return
declaración. Cuando return
va seguido de una expresión, el valor se devuelve al llamador como el valor de la función. Encontrar el final de la función es equivalente a un return
sin expresión. En ese caso, si se declara que la función devuelve un valor y la persona que llama intenta utilizar el valor devuelto, el resultado no está definido.
Almacenamiento de la dirección de una etiqueta
GCC amplía 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 de variable y se puede utilizar más adelante en una goto
instrucció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 función de CA consta de un tipo de retorno ( void
si no se devuelve ningún valor), un nombre único, una lista de parámetros entre paréntesis y varias declaraciones:
< Retorno - Tipo > functionName ( < parámetro - lista > ) { < declaraciones > retorno < expresión del tipo de retorno - Tipo > ; }
Una función con void
tipo de no retorno debe incluir al menos una return
declaración. Los parámetros son dados por el
, una lista separada por comas de declaraciones de parámetros, cada elemento de la lista de ser un tipo de datos seguido de un identificador:
.
Si no hay parámetros,
se puede dejar vacío u opcionalmente especificarse con una sola palabra void
.
Es posible definir una función tomando un número 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 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 .
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:
#include int ( * operación ) ( int x , int y );int add ( int x , int y ) { return x + y ; }int restar ( int x , int y ) { return x - y ; }int main ( int argc , char * args []) { int foo = 1 , bar = 1 ; operación = agregar ; printf ( "% d +% d =% d \ n " , foo , bar , operación ( foo , bar )); operación = restar ; printf ( "% d -% d =% d \ n " , foo , bar , operación ( foo , bar )); return 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 del archivo. Estos pueden dividirse en varios archivos fuente independientes, que pueden compilarse por separado; los módulos de objeto resultantes se vinculan luego junto con los módulos de soporte en tiempo de ejecución proporcionados 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 deja de lado el almacenamiento y posiblemente lo inicializa, una definición de función proporciona su cuerpo.
Una implementación de C que proporciona todas las funciones de biblioteca estándar se denomina implementación alojada . Los programas escritos para implementaciones alojadas deben definir una función especial llamada main, que es la primera función llamada cuando un programa comienza a ejecutarse.
Las implementaciones alojadas inician la ejecución del programa invocando la main
función, que debe definirse siguiendo uno de estos prototipos:
int main () {...} int main ( void ) {...} int main ( int argc , char * argv []) {...} int main ( int argc , char ** argv ) {.. .}
Las dos primeras definiciones son equivalentes (y ambas son compatibles con C ++). Probablemente dependa de la preferencia individual cuál se use (el estándar C actual contiene dos ejemplos de main()
y dos de main(void)
, pero el borrador del estándar C ++ usa main()
). El valor de retorno de main
(que debería ser int
) sirve como estado de terminación devuelto al entorno de host.
El estándar C define los valores de retorno 0
y EXIT_SUCCESS
como indicativos de éxito y EXIT_FAILURE
como indicativos de fracaso. ( EXIT_SUCCESS
y EXIT_FAILURE
se definen en ). Otros valores devueltos tienen significados definidos por la implementación; por ejemplo, bajo Linux, un programa muerto 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 main
rutina vacía , sin argumentos y sin hacer nada:
int main ( void ) {}
Debido a que no hay una return
declaración presente, main
devuelve 0 al salir. [3] (Esta es una característica de caso especial introducida en C99 que se aplica solo a main
).
La main
función normalmente llamará a otras funciones para ayudarla a realizar su trabajo.
Algunas implementaciones no están alojadas, generalmente porque no están diseñadas para usarse con un sistema operativo . Estas 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 un programa para definir una main
función.
Las funciones pueden ser escritas por el programador o proporcionadas por bibliotecas existentes. Las interfaces para este último generalmente se declaran 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 la biblioteca, como printf, están definidas por el estándar C; estas se conocen como funciones de biblioteca estándar .
Una función puede devolver un valor a la persona que llama (normalmente otra función de C o el entorno de alojamiento de la función main
). La printf
función mencionada anteriormente devuelve cuántos caracteres se imprimieron, pero este valor a menudo se ignora.
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 de otra función, la persona que llama debe pasar su dirección (un puntero a ella), que luego se puede desreferenciar en la función de recepción. Consulte Punteros para obtener más información.
nulo incInte ( int * y ) { ( * y ) ++ ; // Incrementa el valor de 'x', en 'main' a continuación, en uno }int main ( vacío ) { int x = 0 ; incInt ( & x ); // pasar una referencia a la var '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 #include void allocate_array ( int ** const a_p , const int A ) { / * allocate array of A ints asignando a * a_p altera la 'a' en main () * / * a_p = malloc ( sizeof ( int ) * A ); }int main ( vacío ) { int * a ; / * crea un puntero a una o más entradas, esta será la matriz * / / * pasar la dirección de 'a' * / allocate_array ( & a , 42 ); / * 'a' es ahora una matriz de longitud 42 y se puede manipular y liberar aquí * / libre ( a ); return 0 ; }
El parámetro int **a_p
es un puntero a un puntero a una int
, que es la dirección del puntero p
definido en la función principal en este caso.
Parámetros de matriz
Los parámetros de función del tipo de matriz pueden parecer a primera vista una excepción a la regla de paso por valor de C. El siguiente programa imprimirá 2, no 1:
#include void setArray ( int matriz [], int índice , int valor ) { matriz [ índice ] = valor ; }int main ( vacío ) { int a [ 1 ] = { 1 }; setArray ( a , 0 , 2 ); printf ( "a [0] =% d \ n " , a [ 0 ]); return 0 ; }
Sin embargo, hay 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 setArray
equivale a lo siguiente:
void setArray ( int * matriz , int índice , valor int )
Al mismo tiempo, las reglas de C para el uso de matrices en expresiones hacen que el valor de a
en la llamada setArray
se 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 que se pasa por valor, no el contenido de la matriz.
Diverso
Palabras clave reservadas
Las siguientes palabras están reservadas y no se pueden utilizar como identificadores:
|
|
|
|
Las implementaciones pueden reservar otras palabras clave, como asm
, aunque las implementaciones suelen proporcionar palabras clave no estándar que comienzan con uno o dos guiones bajos.
Sensibilidad a mayúsculas y minúsculas
Identificadores de C son sensibles (por ejemplo, foo
, FOO
, y Foo
son los nombres de diferentes objetos). Algunos enlazadores pueden asignar identificadores externos a un solo caso, 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 ocurrir dentro de expresiones y puede abarcar varias líneas. La omisión accidental del terminador de comentarios es problemática porque el terminador de comentarios correctamente construido del siguiente comentario se utilizará para terminar el comentario inicial, y todo el código entre los comentarios se considerará como 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í se puede producir una advertencia del compilador. Estas líneas también se ignorarán.El token de apertura de comentarios anterior no inició un nuevo comentario,y el token de cierre de comentarios a continuación cerrará el comentario que comenzó en la línea 1.* /Esta línea y la línea de abajo se van a no ser ignorados . Es probable que ambos produzcan errores de compilación . * /
Los comentarios de línea de 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 C válida en C99 ; no está disponible en el K&R C original ni en ANSI C :
// 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 en C con dos variables predefinidas: el recuento de los argumentos de la línea de comandos argc
y los argumentos individuales como cadenas de caracteres en la matriz de punteros argv
. Entonces el comando:
myFilt p1 p2 p3
resulta en algo como:
metro | y | F | I | l | t | \ 0 | pag | 1 | \ 0 | pag | 2 | \ 0 | pag | 3 | \ 0 |
argv [0] | argv [1] | argv [2] | argv [3] |
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 al imprimir mensajes de diagnóstico o para hacer que un binario sirva para múltiples propósitos. Los valores individuales de los parámetros pueden ser accedidos con argv[1]
, argv[2]
y argv[3]
, como se muestra en el siguiente programa:
#include int main ( int argc , char * argv []) { printf ( "argc \ t =% d \ n " , argc ); for ( int i = 0 ; i < argc ; i ++ ) printf ( "argv [% i] \ t =% s \ n " , i , argv [ i ]); }
Orden de evaluación
En cualquier expresión razonablemente complejo, surge la posibilidad de elegir el orden en que para evaluar las partes de la expresión: se puede evaluar 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 que el compilador realice 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)
- La declaración termina en punto y coma.
- El operador de secuenciación : una coma. Sin embargo, las comas que delimitan los argumentos de la función no son puntos de secuencia.
- Los operadores de cortocircuito : lógico y (
&&
que se puede leer y luego ) y lógico o (||
que se puede leer o si no ). - El operador ternario (
?:
): este operador evalúa su primera subexpresión primero, y luego su segunda o tercera (nunca ambas) basándose en el valor de la primera. - Entrada y salida de una llamada de función (pero no entre evaluaciones de los argumentos).
Las expresiones antes de un punto de secuencia siempre se evalúan antes que las que están después de un punto de secuencia. En el caso de la evaluación de cortocircuito, es posible que la segunda expresión no se evalúe 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 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 más que falso, por lo que no se evalúa.(a() || b())
b()
(a() && b())
b()
Los argumentos de la llamada a una función pueden evaluarse en cualquier orden, siempre que todos sean evaluados en el momento en que se ingresa 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 (no 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 este código puede hacer cualquier cosa, desde funcionar como pretendía el programador, hasta fallar 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 un punto de secuencia intermedio:
#include int main ( vacío ) { int b = 1 ; int a = b ++ + b ++ ; printf ( "% d \ n " , a ); }
Debido a que no existe 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 resulta en una declaración ambigua. Esto se puede solucionar reescribiendo el código para insertar un punto de secuencia con el fin de imponer un comportamiento inequívoco, por ejemplo:
a = b ++ ; a + = b ++ ;
Ver también
- Bloques (extensión de lenguaje C)
- Lenguaje de programación C
- Declaraciones y tipos de variables C
- Operadores en C y C ++
- Biblioteca estándar de C
- Lista de lenguajes de programación de la familia C (lenguajes influenciados por C)
Referencias
- ^ a b El
long long
modificador se introdujo en el estándar C99 . - ^ El significado de auto es un especificador de tipo en lugar de un especificador de clase de almacenamiento en C ++ 0x
- ↑ a b c Klemens, Ben (2012). 21st Century C . O'Reilly Media . ISBN 978-1449327149.
- ^ Balagurusamy, E. Programación en C ANSI . Tata McGraw Hill. pag. 366.
- ^ "El preprocesador C: comportamiento definido por la implementación" . gcc.gnu.org .
- ^ "Literales de cadena y carácter (C ++)" . Documentación de Visual C ++ 19 . Consultado el 20 de noviembre de 2019 .
- ^ consulte laprimera sección de UTF-8 para obtener referencias
- ^ Kernighan y Richie
- ^ Tatham, Simon (2000). "Corutinas en C" . Consultado el 30 de abril de 2017 .
- General
- Kernighan, Brian W .; Ritchie, Dennis M. (1988). El lenguaje de programación C (2ª ed.). Upper Saddle River, Nueva Jersey: Prentice Hall PTR. ISBN 0-13-110370-9.
- Estándar nacional estadounidense para sistemas de información - Lenguaje de programación - C - ANSI X3.159-1989
enlaces externos
- La sintaxis de C en forma Backus-Naur
- Programación en C
- La página de preguntas frecuentes de comp.lang.c