
La sintaxis de C es la forma que debe tener un texto para ser considerado código del lenguaje de programación C. Las reglas de sintaxis del lenguaje están diseñadas para permitir un código conciso, con una estrecha relación con el código objeto resultante, y que a la vez proporcione 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 . [ 1 ] La sintaxis de C utiliza el principio de máxima concisión . Como lenguaje de formato libre , el código C puede formatearse de diferentes maneras sin afectar su naturaleza sintáctica. La sintaxis de C influyó en la sintaxis de lenguajes posteriores, como C++ , Java y C# .
Estructura de alto nivel
El código C se compone de directivas de preprocesador , tipos de lenguaje , variables y funciones , organizados en uno o más archivos fuente. La compilación del código generalmente implica el preprocesamiento y posterior compilación de cada archivo fuente en un archivo objeto . Luego, los archivos objeto se enlazan para crear una imagen ejecutable .
Las variables y funciones pueden declararse por separado de su definición. Una declaración identifica el nombre de un elemento definido por el usuario y proporciona información, parcial o totalmente, sobre cómo se puede utilizar dicho elemento en tiempo de ejecución. Una definición es una descripción completa de un elemento que incluye la declaración, así como información adicional que lo completa. Por ejemplo, la declaración de una función indica su nombre y, opcionalmente, el tipo y el número de argumentos que acepta. La definición de una función incluye la misma información (la información sobre los argumentos es obligatoria), además del código que implementa la lógica de la función.
Punto de entrada

En un entorno alojado , un programa comienza en una función de punto de entradamain llamada . A esta función se le pasan dos argumentos, aunque una implementación de la función puede ignorarlos. La función debe declararse según uno de los siguientes prototipos (los nombres de los parámetros que se muestran son típicos, pero pueden ser cualquiera):
int main (); int main ( void ) ; int main ( int argc , char * argv []); int main ( int argc , char ** argv );Las dos primeras definiciones son equivalentes, lo que significa que la función no utiliza los dos argumentos. Las dos siguientes también son equivalentes, lo que permite que la función acceda a los dos argumentos.
El valor de retorno, tipado como int, sirve como indicador de estado para el entorno del host. Definido en <stdlib.h>, la biblioteca estándar proporciona macros para valores de estado estándar: EXIT_SUCCESSy EXIT_FAILURE. En cualquier caso, un programa puede indicar el estado utilizando cualquier valor. Por ejemplo, el killcomando devuelve el valor numérico de la señal más 128.
Un programa mínimo consta de una mainfunción vacía, como por ejemplo:
int main () {}A diferencia de otras funciones, el lenguaje [ a ] requiere que un programa actúe como si devolviera 0 incluso si no termina con una returninstrucción. [ 2 ]
En un entorno independiente (no alojado), como un sistema sin sistema operativo , el estándar permite un manejo de inicio diferente. No es necesario que requiera una mainfunción.
Argumentos de la línea de comandos
Los argumentos incluidos en la línea de comandos para iniciar un programa se pasan a un programa como dos valores : el número de argumentos (denominado habitualmente argc) y una matriz de cadenas terminadas en nulo (denominada habitualmente argv) con el nombre del programa como primer elemento.
El siguiente código imprime el valor de los parámetros de la línea de comandos.
#include <stdio.h>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 ]); } }$ ./a.out abc def argc = 3 argv[0] = ./a.out argv[1] = abc argv[2] = defPalabras clave reservadas
Las siguientes palabras están reservadas ; no se permiten como identificadores, de las cuales hay 43.
alignasalignofautoboolbreakcasecharconstconstexprcontinuedefaultdodoubleelseenumexternfloatforgotoifinlineintlongregisterrestrictreturnshortsignedsizeofstaticstatic_assertstructswitchthread_localtypedeftypeoftypeof_unqualunionunsignedvoidvolatilewhile
Las siguientes palabras clave suelen sustituir a una macro o a una palabra clave apropiada de la lista anterior, que consta de 14. Algunas de las siguientes palabras clave están obsoletas desde la versión C23.
_Alignas(obsoleto)_Alignof(obsoleto)_Atomic_BitInt_Bool(obsoleto)_Complex_Countof_Decimal32_Decimal64_Decimal128_Generic_Noreturn(obsoleto)_Static_assert(obsoleto)_Thread_local(obsoleto)
La palabra clave _Imaginaryfue eliminada en C2Y .
Las siguientes palabras se refieren a valores literales utilizados por el idioma, de los cuales hay 3.
nullptrtruefalse
Las implementaciones pueden reservar otras palabras clave, aunque normalmente proporcionan palabras clave no estándar que comienzan con uno o dos guiones bajos. Las siguientes palabras clave se clasifican como extensiones y son compatibles condicionalmente, de las cuales hay 2.
asm(indica ensamblador en línea )fortran(indica enlace Fortran externo )
Directivas del preprocesador
Las siguientes son directivas para el preprocesador , de las cuales hay 19.
#if#elif#else#endif#ifdef#ifndef#elifdef#elifndef#define#undef#include#embed#line#error#warning#pragma#__has_include#__has_embed#__has_c_attribute
El _Pragmaoperador proporciona una sintaxis alternativa para la funcionalidad proporcionada por #pragma.
Comentarios
Un comentario —texto informativo para un programador que un traductor de lenguaje ignora— puede incluirse en el código como comentario de línea o de bloque. Un comentario de línea comienza con //y termina al final de la misma línea. Un comentario de bloque comienza con /*y termina con */— abarcando cualquier número de líneas (o solo una).
En algunos casos, los marcadores de comentarios se ignoran. El texto de una cadena literal no se considera inicio de comentario. Además, los comentarios no se pueden anidar. Por ejemplo, un comentario dentro de una línea no se trata como inicio de un comentario de bloque /*, ni viceversa .//
La sintaxis de comentarios de línea, a veces llamada estilo C++ , se originó en BCPL y se convirtió en sintaxis válida en C99 . No está disponible en la versión original de K&R ni en ANSI C.
El siguiente código muestra cómo funcionan los comentarios. La línea 1 contiene un comentario de línea y las líneas 3 y 4 contienen un comentario de bloque. La línea 4 demuestra que un comentario de bloque puede insertarse en una línea con código tanto antes como después del mismo.
int i ; // comentario de línea /* comentario de bloque */ int ii = /* siempre cero */ 0 ;A continuación se muestra un posible problema con la sintaxis de los comentarios. Lo que debería ser el operador de división /y luego el operador de desreferenciación *, se evalúa como el inicio de un comentario de bloque.
x = * p /*q;El siguiente texto no es sintaxis C válida, ya que los comentarios no se anidan. Parece que las líneas 3 a 5 son un comentario anidado dentro del bloque de comentarios que abarca las líneas 1 a 7. Sin embargo, en realidad la línea 5 finaliza el comentario que comenzó en la línea 1. Esto hace que la línea 6 se interprete como código, lo cual claramente no es sintaxis C válida.
/* Primera línea del bloque de comentarios /* Primera línea de lo que se supone que es un bloque interno */ El compilador trata esta línea como código , ¡ pero no es válida ! */Identificadores
La sintaxis admite identificadores definidos por el usuario. Un identificador debe comenzar con una letra (AZ, az) o un guion bajo (_), los caracteres subsiguientes pueden ser letras, números (0-9) o guiones bajos, y no debe ser una palabra reservada. Los identificadores distinguen entre mayúsculas y minúsculas, lo que hace que foo, FOO, y Foosean distintos.
Orden de evaluación
Puede haber varias formas de evaluar una expresión consistente con la notación matemática . Por ejemplo, puede evaluarse en el orden , , , , o en el orden , , , .(1+1)+(3+3)(1+1)+(3+3)(2)+(3+3)(2)+(6)(8)(1+1)+(3+3)(1+1)+(6)(2)+(6)(8)
Para reducir los problemas de tiempo de ejecución relacionados con el orden de evaluación y, al mismo tiempo, permitir algunas optimizaciones, el estándar establece que las expresiones pueden evaluarse en cualquier orden entre los puntos de secuencia que se definen como cualquiera de los siguientes:
- Fin de la declaración
- Un operador de secuenciación : una coma; las comas que delimitan los argumentos de una función no son puntos de secuencia.
- Operadores de cortocircuito : AND lógico ( ,
&&que se puede leer como y luego ) y OR lógico (||, que se puede leer como o de lo contrario ) - Un operador ternario (
?:): Este operador evalúa primero su primera subexpresión y luego su segunda o tercera (nunca ambas) en función del valor de la primera. - Entrada y salida de una llamada a función (pero no entre evaluaciones de los argumentos).
Las expresiones anteriores a un punto de secuencia siempre se evalúan antes que las posteriores. En el caso de la evaluación en cortocircuito, la segunda expresión puede no evaluarse dependiendo del resultado de la primera. Por ejemplo, en la expresión (a() || b()), si el primer argumento se evalúa como distinto de cero (verdadero), el resultado de la expresión completa no puede ser otro que verdadero, por lo que b()no se evalúa. De manera similar, en la expresión , si el primer argumento se evalúa como cero (falso), el resultado de la expresión completa no puede ser otro que falso, por lo que no se evalúa.(a()&&b())b()
Los argumentos de una llamada a función pueden evaluarse en cualquier orden, siempre y cuando se evalúen todos antes de que se ejecute la función. La siguiente expresión, por ejemplo, tiene un comportamiento indefinido :
printf ( "%s %s \n " , argv [ i = 0 ], argv [ ++ i ]);Inclusión de código
Encabezados
El código se incluye desde otros archivos mediante la #includedirectiva, que copia textualmente el contenido del archivo en su lugar. Tradicionalmente, el código C se divide entre un archivo de cabecera (con extensión .h ) y un archivo fuente (con extensión .c ). La cabecera contiene las declaraciones de los símbolos, mientras que el archivo fuente contiene la implementación completa. Esta separación se impone para evitar que el compilador vuelva a compilar el mismo código fuente repetidamente cada vez que se incluye una biblioteca, lo que provoca un aumento excesivo en los tiempos de compilación. Las cabeceras y las unidades de traducción separadas no tienen sentido de espacios de nombres , lo que significa que todos los nombres de los símbolos son globales y entrarán en conflicto si existe el mismo nombre para varios objetos en la misma unidad de traducción.
Para evitar que un encabezado se incluya en un archivo más de una vez, se pueden usar las directivas #include o #pragma once .
Los archivos de cabecera de C se pueden compilar en archivos de cabecera precompilados , cuyo procesamiento por parte del compilador es más rápido. Por lo general, las bibliotecas que cambian con poca frecuencia (como los archivos de cabecera de la biblioteca estándar de C) se pueden precompilar para una compilación más rápida en un proyecto.
Para distinguir entre la búsqueda en un directorio incluido y la búsqueda en una ruta relativa, utilice corchetes angulares para los directorios incluidos y comillas para las rutas relativas.
#pragma una vez// Inclusiones de la biblioteca estándar #include <stdio.h> #include <stdlib.h>// local #include "Personalities.h" #include "Math/SpecialFunctions.h"// bibliotecas externas #include <sqlite3.h>Módulos C de Clang
Clang ofrece una característica no estándar llamada módulos , que son similares a los módulos de C++ pero semánticamente diferentes. Ambos se utilizan para aumentar el tiempo de compilación al compilar una unidad de traducción una sola vez.
Empotrar
Esta #embeddirectiva se puede utilizar para incrustar contenido binario en un archivo, incluso si no se trata de código C válido.
constexpr char ICON_DISPLAY_DATA [] = { #embed "art.png" };// especifique cualquier tipo que pueda inicializarse a partir de expresiones constantes enteras lo hará constexpr char RESET_BLOB [] = { #embed "data.bin" };// los atributos funcionan igual de bien alignas ( 8 ) constexpr char ALIGNED_DATA_STRING [] = { #embed "attributes.xml" };int main () { return #embed </dev/urandom> limit(1) ; }Sistema de tipos
Tipos primitivos
El lenguaje admite tipos numéricos primitivos para valores enteros y reales que normalmente se corresponden directamente con la arquitectura del conjunto de instrucciones de una unidad central de procesamiento (CPU). Los tipos de datos enteros almacenan valores en un subconjunto de enteros , y los tipos de datos reales almacenan valores en un subconjunto de números reales de coma flotante . Un tipo de dato complejo almacena dos valores reales.
Los tipos enteros tienen variantes signedy unsigned. Si no se especifica ninguna, signedse asume en la mayoría de los casos. Sin embargo, por razones históricas, chares un tipo distinto de signed chary unsigned char. Puede ser con signo o sin signo, según el compilador y el conjunto de caracteres (el estándar exige que los miembros del conjunto básico de caracteres tengan valores positivos). Asimismo, los tipos de campo de bits especificados como intpueden ser con signo o sin signo, según el compilador.
Tipos enteros
Los tipos enteros 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 suele tener 8 bits de ancho. (Aunque charpuede representar cualquier carácter "básico", puede ser necesario un tipo más amplio para conjuntos de caracteres internacionales). La mayoría de los tipos enteros tienen variedades con y sin signo , designadas por las palabras clave signedy unsigned. Los tipos enteros con signo siempre usan la representación de complemento a dos , desde C23 [ 3 ] (y en la práctica antes; en versiones anteriores a C23 la representación podría haber sido alternativamente el complemento a uno , o signo y magnitud , pero en la práctica ese no ha sido el caso durante décadas en el hardware 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 enumera los tipos enteros utilizando el nombre más corto posible e indicando el ancho mínimo en bits.
El chartipo es distinto de y signed char, unsigned charpero se garantiza que tendrá la misma representación que uno de ellos. Los tipos _Booly están estandarizados desde 1999 y es posible que no sean compatibles con compiladores más antiguos. El tipo se suele acceder mediante el nombre definido por el encabezado estándar <stdbool.h> , sin embargo, desde C23 el tipo se ha renombrado y se ha descontinuado.long long_Booltypedefbool_Boolbool<stdbool.h>
En general, el ancho y el esquema de representación implementados para cada plataforma 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 traductores; a menudo corresponde al tamaño de palabra más "natural" para una plataforma. El archivo de cabecera estándar <limits.h> define macros para los valores mínimos y máximos representables de los tipos enteros estándar implementados en cada plataforma específica.
Además de los tipos enteros estándar, puede haber otros tipos enteros "extendidos", que se pueden usar para typedefs en los encabezados estándar. Para una especificación más precisa del ancho, los programadores pueden y deben usar typedefs del encabezado estándar <stdint.h> .
Las constantes enteras pueden especificarse en el código fuente de varias maneras. Los valores numéricos pueden especificarse 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 conjunto de caracteres de ejecución, con tipo int. Excepto para las constantes de carácter, 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 puede ser anulado agregando un modificador explícito de longitud y/o signo; por ejemplo, 12lutiene tipo unsigned long. No hay constantes enteras negativas, pero el mismo efecto a menudo puede obtenerse usando un operador de negación unario " -".
Tipo enumerado
El tipo enumerado , especificado con la enumpalabra clave y a menudo llamado simplemente "enum" (generalmente pronunciado / ˈ iː n ʌ m / EE -num o / ˈ iː n uː m / EE -noom ), es un tipo diseñado para representar valores a través de una serie de constantes con nombre. Cada una de las constantes enumeradas tiene el tipo int. Cada enumtipo en sí es compatible con charo 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 de tipo enumerado se le asigna un valor que no es una de sus constantes. Sin embargo, a dicho objeto se le pueden asignar valores dentro del rango de su tipo compatible, y enumlas constantes se pueden usar en cualquier lugar donde se espere un entero. Por esta razón, enuma menudo se utilizan valores en lugar de #definedirectivas de preprocesador para crear constantes con nombre. Estas constantes son generalmente más seguras que las macros, ya que residen dentro de un espacio de nombres de identificadores específico.
Un tipo enumerado se declara con el enumespecificador y un nombre (o etiqueta ) opcional para la enumeración, seguido de una lista de una o más constantes 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. Por defecto, a la primera constante de una enumeración se le asigna el valor cero, y cada valor subsiguiente 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 partir de ese momento, a cualquier constante subsiguiente sin valores específicos se le asignarán valores incrementados. Por ejemplo, considere la siguiente declaración:
enum Color { ROJO , VERDE , AZUL = 5 , AMARILLO } color_pintura ;Esto declara el enum Colortipo; 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 Colorvariable 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 Color.
A diferencia de C++, las enumeraciones en C no tienen ámbito, ya que C no tiene el concepto de espacios de nombres. En C, enumlas enumeraciones se pueden convertir implícitamente a tipos numéricos, lo cual no es seguro en cuanto a tipos.
typedef enum Color { ROJO , NARANJA , AMARILLO , VERDE , AZUL , ÍNDIGO , VIOLETA } Color ;Color c = ROJO ; // en C Color d = Color :: ROJO ; // en C++, pero no en CDesde C23, es posible especificar manualmente el tipo subyacente para el enum, como en C++.
enum CardSuit : char { CORAZONES , TRÉBOLES , PICAS , DIAMANTES };Tipos de punto flotante
La notación de punto flotante se utiliza para representar números con una parte fraccionaria. Sin embargo, no representa la mayoría de los números racionales con exactitud; en cambio, es una aproximación cercana. Existen tres tipos estándar de valores reales, indicados por sus especificadores (y desde C23, tres tipos decimales adicionales): precisión simple ( float), precisión doble ( double) y precisión doble extendida ( long double). Cada uno de estos puede representar valores en un formato diferente, a menudo uno de los formatos de punto flotante IEEE .
Las constantes de punto flotante pueden escribirse en notación decimal , p. ej 1.23. . La notación científica decimal puede usarse agregando 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 hexadecimales pueden ir sufijadas por fo Fpara indicar una constante de tipo float, por l(letra l) o Lpara indicar el tipo long double, o dejarse sin sufijo para una doubleconstante.
El archivo de cabecera estándar <float.h> define los valores mínimo y máximo de los tipos de punto flotante de la implementación , , y . También define otros límites que son relevantes para el procesamiento de números de punto flotante.floatdoublelong double
C23 introduce tres tipos de punto flotante reales decimales_Decimal32 (a diferencia de los binarios) adicionales: , _Decimal64, y _Decimal128.
- NOTA: C no especifica una base para
float,double, ylong double. Una implementación puede elegir que la representación defloat,double, ylong doublesea la misma que la de los tipos de punto flotante decimal. [ 4 ]
A pesar de ello, la base históricamente ha sido binaria (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 el punto flotante decimal, todos los mismos números son exactos, además de números como 1/10 y 1/100, pero aún no, por ejemplo, 1/3. Ninguna implementación conocida opta por la base decimal para los tipos que antes 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 sí lo tienen (por ejemplo, las computadoras centrales de IBM desde el IBM System z10 ), pueden usar los tipos explícitamente decimales.
Clase de almacenamiento
La siguiente tabla describe los especificadores que definen varios atributos de almacenamiento, incluida la duración : estática (predeterminada para global), automática (predeterminada para local) o dinámica (asignada).
- 1 Asignado y desasignado utilizando las funciones
malloc()de lafree()biblioteca.
Las variables declaradas dentro de un bloque tienen almacenamiento automático por defecto, al igual que las declaradas explícitamente con los especificadores de clase de almacenamiento [nota 2]. Los especificadores y solo pueden usarse dentro de funciones y declaraciones de argumentos de función; por lo tanto, 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 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 al bloque en el que se declararon y se descartan cuando se sale del bloque. Además, los objetos declarados con la registerclase de almacenamiento pueden tener mayor prioridad por parte del compilador para el acceso a los registros , aunque el compilador puede optar por no almacenar realmente ninguno de ellos en un registro. Los objetos con esta clase de almacenamiento no se pueden usar con el &operador unario de dirección de ( ). Los objetos con almacenamiento estático persisten durante toda la duración del programa. De esta manera, se puede acceder al mismo objeto mediante una función 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 otro lugar. Cuando se usa dentro de un bloque, indica que el almacenamiento se ha definido mediante 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 externespecificador 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.
El especificador de clase de almacenamiento thread_local( _Thread_localantes de C23 , y en versiones anteriores de C si se incluye el encabezado), introducido en C11 , se utiliza para declarar una variable local de hilo. Se puede combinar con o para determinar el enlace.<threads.h>staticextern
Cabe destacar que los especificadores de almacenamiento se aplican únicamente a funciones y objetos; otros elementos, como las declaraciones de tipo y enumeración, son exclusivos de la unidad de compilación en la que aparecen. Los tipos, por otro lado, tienen calificadores (véase más abajo).
Desde C23, C puede usar autopara declarar una variable de tipo inferido .
Calificadores de tipo
Los tipos pueden calificarse 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 los almacenan en datos de solo lectura o (para sistemas embebidos) en memoria de solo lectura (ROM). De manera similar, constexprpuede pensarse como una forma "más fuerte" de const, donde el valor debe conocerse en tiempo de compilación (lo que lo convierte en un reemplazo seguro de tipos para las constantes macro). Una constexprfunción, de manera similar, también debe poder evaluarse en tiempo de compilación. El calificador de tipo volatileindica a un compilador optimizador que puede no eliminar lecturas o escrituras aparentemente redundantes, ya que el valor puede cambiar incluso si no fue modificado por ninguna expresión o instrucción, o pueden ser necesarias múltiples escrituras, como para E/S mapeada en memoria .
Tipos incompletos
Un tipo incompleto es una estructura o unión cuyos miembros aún no se han especificado, un array cuya dimensión aún no se ha especificado o un voidtipo voidque no se puede completar. Dicho tipo no se puede instanciar (se desconoce su tamaño) ni se puede acceder a sus miembros (también se desconocen); sin embargo, se puede usar el puntero derivado (pero no desreferenciarlo).
Se suelen usar con punteros, ya sea como declaraciones anticipadas o externas. Por ejemplo, el código podría declarar un tipo incompleto de esta manera:
struct Integer * pt ;Esto declara ptcomo un puntero a struct Integer(así como el tipo de estructura incompleta). Como todos los punteros tienen el mismo tamaño (independientemente de a qué apunten), el código puede usarlo ptcomo un puntero aunque no puede acceder a los campos de struct Integer.
Un tipo incompleto puede completarse posteriormente en el mismo ámbito volviéndolo a declarar. Por ejemplo:
struct Entero { int num ; };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:
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 la definición completa se oculta en un único archivo de cuerpo.
Consejos
En una declaración de variable, el asterisco ( * ) puede considerarse como "puntero a". Por ejemplo, declara una variable de tipo int y declara una variable que es un puntero a un entero. Algunos sostienen que, según la definición del lenguaje, está más relacionado con la variable que con el tipo y, por lo tanto, formatean el código como o incluso . Esto se debe a que el asterisco ( * ) es parte del declarador [ 5 ] y no del especificador de tipo; otra consecuencia de esto es que declarará dos variables tales que es un puntero a un entero y es un entero en sí mismo.int xint* pxpx*int *pxint * pxint* a,b;ab
Un valor de puntero asocia dos datos: una dirección de memoria y un tipo de dato.
Referencias
Cuando se declara un puntero no estático, este tiene un valor no especificado. Si se intenta desreferenciarlo sin asignarle previamente un valor, el comportamiento será indefinido.
El &operador especifica la dirección del objeto de datos a continuación. En el siguiente ejemplo, ptrse le asigna la dirección de a:
int a = 0 ; int * ptr = &a a ;Desreferenciación
Un asterisco delante del nombre de una variable (cuando no está en una declaración o expresión matemática) desreferencia un puntero para permitir el acceso al valor al que apunta. En el siguiente ejemplo, la variable entera bse establece al valor de la variable entera a, que es 10:
int a = 10 ; int * p ; p = &a a ; int b = * p ;Matrices
Definición de matriz
Los arreglos almacenan elementos consecutivos del mismo tipo. El siguiente código declara un arreglo de 100 elementos, llamado a, de tipo int.
int a [ 100 ];Si se declara fuera de una función (de forma global), el tamaño debe ser un valor constante. Si se declara dentro de una función, el tamaño del array puede ser una expresión no constante.
El número de elementos está disponible como , pero si el valor se pasa a otra función, el número de elementos no está disponible a través de la variable de parámetro formal.sizeof(a)/sizeof(int)
Acceso a los elementos
La principal herramienta para acceder a los elementos de un array es el operador de subíndice. Por ejemplo, a[i]accede al elemento en el índice idel array a. La indexación del array comienza en 0 , por lo que el último índice es igual al número de elementos menos 1. Dado que el estándar no contempla la comprobación de límites de indexación , especificar un índice fuera de rango produce un comportamiento indefinido.
Debido a que los arreglos y los punteros son intercambiables, la dirección de cada elemento se puede expresar mediante aritmética de punteros . La siguiente tabla ilustra ambos métodos para el mismo arreglo:
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 se usa raramente.
Matrices de longitud variable
C99 estandarizó el arreglo de longitud variable (VLA) en el ámbito de bloque, que producía un arreglo cuyo tamaño dependía de la información de tiempo de ejecución (no de un valor constante), pero con un tamaño fijo hasta el final del bloque. [ 2 ] A partir de C11 , esta característica ya no requiere ser implementada por el compilador.
int n = 20 ; // n puede ser cualquier valor arbitrario int a [ n ]; a [ 3 ] = 10 ;matrices multidimensionales
El lenguaje admite matrices multidimensionales , almacenadas en orden de filas, que es esencialmente una matriz unidimensional cuyos elementos son matrices. Dado que ROWS y COLUMNS son constantes, la siguiente declaración crea una matriz bidimensional de longitud ROWS , donde cada elemento es una matriz de enteros COLUMNS .
int array2d [ FILAS ][ COLUMNAS ];El siguiente es un ejemplo de cómo acceder a un elemento entero:
array2d [ 4 ][ 3 ]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 se indexa con [3] para acceder al cuarto entero.
Las matrices de dimensiones superiores se pueden declarar de manera similar.
No se debe confundir una matriz multidimensional con una matriz de punteros a matrices (también conocida como vector de Iliffe o, a veces, matriz de matrices ). La primera es siempre rectangular (todas las submatrices deben tener el mismo tamaño) y ocupa una región contigua de memoria. La segunda es una matriz unidimensional de punteros, cada uno de los cuales puede apuntar al primer elemento de una submatriz ubicada en una posición diferente de la memoria, y las submatrices no tienen por qué tener el mismo tamaño.
Texto
Aunque el lenguaje proporciona tipos para datos de caracteres textuales, ni el lenguaje ni la biblioteca estándar definen un tipo de cadena, pero la cadena terminada en nulo es de uso común. Un valor de cadena es una serie contigua de caracteres cuyo final está marcado por un cero. La biblioteca estándar contiene muchas funciones para el manejo de cadenas terminadas en nulo, pero la manipulación de cadenas puede realizarse, y de hecho se realiza con frecuencia, mediante código personalizado.
Literal de cadena
Una cadena literal es un texto de código rodeado de comillas dobles, como por ejemplo "Hello world!". Una cadena literal se compila en una matriz de los charvalores especificados con un carácter nulo de terminación para marcar el final de la cadena.
El lenguaje admite la concatenación de literales de cadena : los literales de cadena adyacentes se tratan como unidos en tiempo de compilación. Esto permite dividir cadenas largas en varias líneas y también permite agregar literales de cadena de macros de preprocesador a cadenas en tiempo de compilación. Por ejemplo, el código fuente:
printf ( __FILE__ ": %d: Hola " "mundo \n " );se convierte en lo siguiente después de que el preprocesador se expande __FILE__:
printf ( "helloworld.c" ": %d: Hola " "mundo \n " );lo cual es equivalente a:
printf ( "helloworld.c: %d: Hola mundo \n " );constantes de caracteres
El literal de carácter, llamado constante de carácter, está entre comillas simples, por ejemplo 'A', , y tiene el tipo int. Para ilustrar la diferencia entre un literal de cadena y una constante de carácter, considere que "A"son dos caracteres, 'A'y '\0', mientras que 'A'representa un solo carácter (65 en ASCII).
Una constante de caracteres no puede estar vacía (es decir, ''la sintaxis es inválida). Las constantes de varios caracteres (por ejemplo, 'xy') son válidas, aunque rara vez útiles: permiten almacenar varios caracteres en un entero (por ejemplo, 4 caracteres ASCII caben en un entero de 32 bits, 8 en uno de 64 bits). Dado que el orden en que se empaquetan los caracteres en una constante intno está especificado (queda a criterio de la implementación), el uso portable de constantes de varios caracteres es difícil.
Sin embargo, en situaciones limitadas a una plataforma específica y a la implementación del compilador, las constantes multicaracteres sí se utilizan para especificar firmas. Un caso de uso común es OSType , donde la combinación de los compiladores de Mac OS clásico y su inherente big-endianidad implica que los bytes del entero aparecen en el orden exacto de los caracteres definidos en el literal. La definición de las implementaciones más populares es, de hecho, consistente: en GCC, Clang y Visual C++ , '1234'produce bajo ASCII. [ 7 ] [ 8 ]0x31323334
Al igual que los literales de cadena, las constantes de caracteres también pueden modificarse mediante prefijos; por ejemplo, L'A'tiene el tipo wchar_ty representa el valor del carácter "A" en la codificación de caracteres anchos.
Escapes de barra invertida
Los caracteres de control no pueden incluirse directamente en una cadena o literal de carácter. En su lugar, pueden codificarse mediante una secuencia de escape que comienza con una barra invertida ( \). Por ejemplo, las barras invertidas en "This string contains \"double quotes\"."indican que el par de comillas internas se consideran parte de la cadena, en lugar de interpretarse como un delimitador (punto final) de la misma.
Las secuencias de escape incluyen:
El uso de otras secuencias de escape con barra invertida no está definido por el estándar, aunque los compiladores suelen proporcionar códigos de escape adicionales como extensiones del lenguaje. Por ejemplo, la secuencia de escape \epara el carácter de escape con valor hexadecimal ASCII 1B, que no se añadió al estándar por carecer de representación en otros conjuntos de caracteres (como EBCDIC ), está disponible en GCC , clang y tcc .
Tenga en cuenta que la función de la biblioteca estándar printf()se utiliza %%para representar el %carácter literal.
cadenas de caracteres anchas
Dado que el tipo chartiene un ancho de 1 byte, un solo charvalor normalmente puede representar como máximo 255 códigos de caracteres distintos, lo cual no es suficiente para todos los caracteres diferentes que se utilizan en todo el mundo. Para brindar un mejor soporte para los caracteres internacionales, el primer estándar (C89) introdujo caracteres anchos (codificados en el tipo wchar_t) y cadenas de caracteres anchos, que se escriben comoL"Hello world!"
Los caracteres anchos suelen ser de 2 bytes (usando una codificación de 2 bytes como UTF-16 ) o de 4 bytes (normalmente 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 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_tsufre 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, y deben representarse usando pares sustitutos .
El estándar original especificaba solo funciones mínimas para operar con cadenas de caracteres anchos; en 1995, el estándar se modificó para incluir un soporte mucho más extenso, 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 la sustitución de "str" por "wcs"; se especifican en <wchar.h>, que <wctype.h>contiene funciones de clasificación y mapeo de caracteres anchos.
El método generalmente recomendado ahora [ 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 utiliza un editor UTF-8, porque UTF-8 es una extensiónchar directa de ASCII .
cadenas de ancho variable
Una alternativa común es wchar_tusar una codificación de ancho variable , donde un carácter lógico puede extenderse a lo largo de varias posiciones de la cadena. Las cadenas de ancho variable se pueden codificar en literales tal cual, con el riesgo de confundir al compilador, o usar secuencias de escape numéricas con barra invertida (por ejemplo, "\xc3\xa9"para " é " en UTF-8). La codificación UTF-8 se diseñó específicamente (en el Plan 9 ) para ser compatible con las funciones de cadena de la biblioteca estándar; entre las características que la respaldan se incluyen la ausencia de caracteres nulos incrustados, la ausencia de interpretaciones válidas para subsecuencias y una resincronización trivial. Es probable que las codificaciones que carecen de estas características resulten incompatibles con las funciones de la biblioteca estándar; en estos casos, se suelen usar funciones de cadena que tienen en cuenta la codificación.
Estructura
Una estructura es un contenedor que consta de una secuencia de miembros con nombre de tipos heterogéneos, similar a un registro en otros lenguajes. El primer campo comienza en la dirección de la estructura y los miembros se almacenan en ubicaciones consecutivas de la memoria, pero el compilador puede insertar relleno entre o después de los miembros para mejorar la eficiencia o como relleno necesario para una correcta alineación con la arquitectura de destino. El tamaño de una estructura incluye el relleno.
Una estructura se declara con la structpalabra clave seguida de un nombre de identificador opcional, que se utiliza para identificar la forma de la estructura. A continuación, el cuerpo contiene las declaraciones de campo, cada una compuesta por un nombre de tipo, un nombre de campo y finalizada con un punto y coma.
A continuación se declara una estructura llamada MyStructque contiene tres miembros. También se declara una instancia llamada tee:
struct MyStruct { int x ; float y ; char * z ; } tee ;Los miembros de una estructura no pueden tener un tipo incompleto o de función. Por lo tanto, los miembros no pueden ser una instancia de la estructura que se está declarando (porque está incompleta en ese momento), pero un campo puede ser un puntero al tipo que se está declarando.
Una vez declarada, se puede declarar una variable del tipo de estructura. Lo siguiente declara una nueva instancia de la estructura MyStructdenominada r:
struct MyStruct r ;Aunque algunos prefieren declarar una variable de estructura usando la structpalabra clave, otros usan typedefpara crear un alias del tipo de estructura en el espacio de nombres del tipo principal. Lo siguiente declara un tipo como Integerque luego se puede usar como Integer n.
typedef struct { int i ; } Entero ;Acceso a los miembros
Se accede a un miembro utilizando la notación de punto. Por ejemplo, dada la declaración teeanterior, yse puede acceder al miembro como tee.y.
Se suele acceder a una estructura mediante un puntero. Consideremos que define un puntero a , llamado . Se puede acceder a un miembro de desreferenciando y usando el resultado como operando izquierdo como . [ b ] Debido a que esta operación es común, el lenguaje proporciona una sintaxis abreviada para acceder a un miembro directamente desde un puntero, (por ejemplo, ).structMyStruct*ptee=&teeteepteeyteeptee(*ptee).y->ptee->y
Asignación
Asignar un valor a un miembro es similar a asignar un valor a una variable. La única diferencia radica en que el lvalue (valor del lado izquierdo) de la asignación es el nombre del miembro, según la sintaxis anterior.
Una estructura también puede asignarse como un todo a otra estructura del mismo tipo, pasándose por copia como argumento de función o valor de retorno. Por ejemplo, tee.x = 74asigna el valor 74 al miembro llamado x en la estructura tee , y ptee->x = 74hace lo mismo para ptee .
Otras operaciones
Las operaciones admitidas para una estructura son: inicializar, copiar, obtener la dirección y acceder a un campo. Cabe destacar que el lenguaje no admite la comparación del valor de dos estructuras, salvo mediante código personalizado para comparar cada campo.
Campos de bits
El lenguaje proporciona un tipo especial de miembro conocido como campo de bits , que es un entero con un tamaño especificado en bits. Un campo de bits se declara como un miembro de tipo ( signed/ unsigned) int, bool, o _BitInt(N) . [ nota 4 ] más un sufijo después del nombre del miembro que consiste en dos puntos y un número de bits. El número total de bits en un solo campo de bits no debe exceder el número total de bits de su tipo base. Contrariamente a las reglas de sintaxis habituales de C, depende de la implementación si un campo de bits es con signo o sin signo si no se especifica explícitamente. Por lo tanto, la mejor práctica es especificar o .signedunsigned
Los campos sin nombre indican relleno y consisten simplemente en dos puntos seguidos de una cantidad de bits. Especificar un ancho de cero para un campo sin nombre se utiliza para forzar la alineación a una nueva palabra. [ 9 ] 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 deben caber en ella.
Los campos de bits están limitados en comparación con los campos normales, ya que no se admiten los operadores de dirección ( &) ni .sizeof
A continuación se declara un tipo de estructura llamado FlagStatusy una instancia de la misma llamada g. El primer campo, flag, es un indicador de un solo bit, que solo puede ser 1 o 0. El segundo campo, num, es un campo con signo de 4 bits; rango -7...7 o -8...7. El último campo agrega 3 bits de relleno para completar la estructura a 8 bits.
struct FlagStatus { unsigned int flag : 1 ; signed int num : 4 ; signed int : 3 ; } g ;Espacios de nombres
A diferencia de C++ y Java, C no ofrece soporte nativo para espacios de nombres . Esto provoca conflictos entre los nombres de los símbolos en C. Sin embargo, es posible utilizar estructuras anónimas para emular espacios de nombres.
Matemáticas.h :
#pragma una vezconst struct { double PI ; double ( * sin )( double ); } Math ;Matemáticas.c :
#include <math.h>static double _sin ( double arg ) { return sin ( arg ); }const struct { double PI ; double ( * sin )( double ); } Math = { M_PI , _sin };Main.c :
#include <stdio.h> #include "Math.h"int main () { printf ( "sin(0) = %d \n " , Math . sin ( 0 )); printf ( "pi es %f \n " , Math . PI ); }Unión
En general, una unión es similar a una estructura, con la diferencia de que los campos se superponen en la memoria para permitir el almacenamiento de valores de distintos tipos, aunque no simultáneamente. La unión es similar al registro variante de otros lenguajes. Cada campo hace referencia a la misma ubicación en la memoria. El tamaño de una unión es igual al tamaño de su componente de mayor tamaño más cualquier relleno.
Una unión se declara con la unionpalabra clave. Lo siguiente declara una unión llamada MyUniony una instancia de la misma llamada n:
unión MyUnion { int x ; float y ; char * z ; } n ;Inicialización
Escalar
Inicializar una variable, además de declararla, implica agregar un signo de igual y luego una construcción compatible con el tipo de dato. El siguiente ejemplo inicializa un entero:
entero x = 12 ;Debido a la gramática del lenguaje, un inicializador escalar puede estar encerrado entre cualquier número de pares de llaves. La mayoría de los compiladores emiten una advertencia si hay más de un par de llaves. Los siguientes ejemplos son válidos, aunque posiblemente inusuales:
int y = { 23 }; int z = { { 34 } };Lista de inicializadores
Las estructuras, las uniones y los arreglos se pueden inicializar después de una declaración mediante una lista de inicialización.
Dado que los elementos no coincidentes se establecen en 0, una lista vacía establece todos los elementos en 0. Por ejemplo, lo siguiente establece todos los elementos de la matriz a y todos los campos de s en 0:
int a [ 10 ] = {}; struct MyStruct s = {};Si se declara un array sin un tamaño explícito, el array es un tipo incompleto . El número de inicializadores determina el tamaño del array y completa el tipo. Por ejemplo:
int x [] = { 0 , 1 , 2 };Por defecto, los elementos de una lista de inicialización se corresponden con los elementos en el orden en que se definen. Incluir demasiados valores produce un error. La siguiente instrucción inicializa una instancia de la estructura MyStruct llamada pi :
struct MyStruct { int x ; float y ; char * z ; };struct MyStruct pi = { 3 , 3.1415 , "Pi" };Inicializadores designados
Los inicializadores designados permiten inicializar los miembros por nombre, en cualquier orden y sin proporcionar explícitamente valores previos. La siguiente inicialización es funcionalmente equivalente a la anterior:
struct MyStruct 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 medio de a; si es menor que 10, algunos de los valores proporcionados por los primeros cinco inicializadores serán sobrescritos por los siguientes cinco. Si MAXes 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 inicializaba con un único valor aplicado a su primer miembro. Es decir, la unión MyUnion definida anteriormente solo podía tener su miembro x inicializado:
unión MyUnion valor = { 3 };Utilizando un inicializador designado, el miembro que se va a inicializar no tiene por qué ser el primer miembro:
unión MyUnion valor = { . y = 3.1415 };Los designadores compuestos se pueden usar para proporcionar una inicialización explícita cuando las listas de inicialización simples podrían malinterpretarse. 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(un int). El inicializador establece el tamaño de wen 2 y establece los valores del primer elemento de cada 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 } };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 };struct MyStruct pi = ( struct MyStruct ){ 3 , 3.1415 , "Pi" };Los literales compuestos a menudo se combinan con inicializadores designados para hacer que la declaración sea más legible: [ 2 ]
pi = ( struct MyStruct ){ . z = "Pi" , . x = 3 , . y = 3.1415 };Punteros a funciones
Un puntero a una función se puede declarar de la siguiente manera:
tipo-nombre (* nombre-función )( lista-parámetros );
El siguiente código de programa demuestra el uso de un puntero a función para seleccionar entre suma y resta. La línea 12 define una variable de puntero a función llamada operationque admite la misma interfaz que las funciones addy subtract. Según la condición (argc), operationse asigna a la dirección de addo subtract. En la línea 14operation , se llama a la función que apunta a .
[[ no descartar ]]constexpr int add ( int x , int y ) {devolver x + y ;}[[ no descartar ]]constexpr int subtract ( int x , int y ) {devolver x - y ;}int main ( int argc , char * argv []) {int ( * operación )( int x , int y );operación = argc ? sumar : restar ;int resultado = operación ( 1 , 1 );devolver resultado ;}Operadores
Esta es una lista de operadores en los lenguajes de programación C y C++ .
Todos los operadores listados están en C++ y, a menos que se indique lo contrario, también en C. Algunas tablas incluyen una columna "En C" que indica si un operador también está en C. Tenga en cuenta que C no admite la sobrecarga de operadores .
Cuando no están sobrecargados, para los operadores &&, ||, y ,(el operador coma ), hay un punto de secuencia después de la evaluación del primer operando.
La mayoría de los operadores disponibles en C y C++ también están disponibles en otros lenguajes de la familia C, como C# , D , Java , Perl y PHP , con la misma precedencia, asociatividad y semántica.
Muchos operadores, especificados mediante una secuencia de símbolos, suelen denominarse con un nombre que consiste en el nombre de cada símbolo. Por ejemplo, +=y -=se suelen llamar "suma igual" y "resta igual", en lugar de los términos más extensos "asignación por suma" y "asignación por resta".
Flujo de control
Declaración compuesta
Una instrucción compuesta , también conocida como bloque de instrucciones, es un par de llaves emparejadas con cualquier número de instrucciones entre ellas, como por ejemplo:
{ { declaración } }Dado que una instrucción compuesta es un tipo de instrucción, cuando se utiliza , puede ser una instrucción simple (sin llaves) o una instrucción compuesta (con llaves). Se requiere una instrucción compuesta para el cuerpo de una función y para una rama de una estructura de control que no sea una sola instrucción.
Una variable declarada en un bloque puede ser referenciada por el código de ese bloque (y de los bloques internos) que se encuentre debajo de la declaración. El acceso a la memoria utilizada para una variable declarada en un bloque después de que este se cierre (es decir, mediante un puntero) produce un comportamiento indefinido.
Si la declaración
La instrucción condicional es como:if
si ( expresión ) { instrucción } de lo contrario { instrucción }Si la expresión no es cero, el control pasa a la primera instrucción. De lo contrario, el control pasa a la segunda instrucción. Si la elseparte está ausente, cuando la expresión se evalúa como cero, la primera instrucción simplemente se omite. elseSiempre coincide con el no coincidente anterior más cercano if. Se pueden usar llaves para anular esto cuando sea necesario o para mayor claridad.
Cabe destacar que la segunda afirmación puede ser otra ifafirmación. Por ejemplo:
if ( i == 1 ) { printf ( "es 1" ); } else if ( i == 2 ) { printf ( "es 2" ); } else { printf ( "es otra cosa" ); }Sentencia switch
El switchcontrol se transfiere a la etiqueta del caso que tenga un valor que coincida con la expresión de tipo enterodefault o, de lo contrario, a la etiqueta (si la hay). El control continúa en las instrucciones que siguen a la caseetiqueta hasta una breakinstrucción o hasta el final de la switchinstrucción. La sintaxis es la siguiente:
interruptor ( expresión ) { caso nombre-de-etiqueta : declaración nombre de la etiqueta del caso : declaración . . . por defecto: declaración }Cada casevalor debe ser único dentro de la declaración. Puede haber como máximo una defaultetiqueta.
La ejecución continúa de una caseetiqueta a la siguiente si no breakse encuentra ninguna instrucción. Esto se conoce como ejecución secuencial , lo cual resulta útil en algunas circunstancias, pero a menudo no es lo deseado.
Es posible, aunque poco común, ubicar caseetiquetas en los subbloques de las estructuras de control internas. Algunos ejemplos son el dispositivo de Duff y la implementación de corrutinas de Simon Tatham en Putty . [ 10 ]
El siguiente es un ejemplo de un switchsobre un int:
#include <stdio.h>// ... int num = 2 ;switch ( num ) { case 1 : printf ( "El número es 1 \n " ); break case 2 : printf ( "El número es 2 \n " ); break ; case 3 : printf ( "El número es 3 \n " ); break ; default : printf ( "El número no es 1, 2 ni 3 \n " ); }A partir de C2Y , es posible usar un " caserango" entre dos constantes enteras, usando puntos ...suspensivos (...). [ 11 ] Debe haber un espacio entre el valor y los puntos suspensivos. Este rango es inclusivo. Por ejemplo:
#include <stdio.h>// ... int num = 2 ;// nuevo estilo switch ( num ) { case 1 ... 3 : printf ( "El número es 1, 2 o 3 \n " ); break default : printf ( "El número no es 1, 2 o 3 \n " ); }// estilo antiguo, usando un bucle de casos ( num ) { case 1 : case 2 : case 3 : printf ( "El número es 1, 2 o 3 \n " ); break default : printf ( "El número no es 1, 2 o 3 \n " ); }Declaración de iteración
Existen tres formas de enunciado de iteración :
mientras ( expresión ) { instrucción }hacer { instrucción } mientras ( expresión )para ( inicializar ; probar ; siguiente ) { instrucción }Para las sentencias while`and` do, la subsentencia se ejecuta repetidamente mientras el valor de la expresión sea distinto de cero. Para ` while, la prueba, incluyendo cualquier efecto secundario, se realiza antes de cada iteración. Para ` do, la prueba se realiza después de cada iteración. Por lo tanto, una dosentencia `and` siempre ejecuta su subsentencia al menos una vez, mientras que while`and` podría no ejecutarla en absoluto.
La lógica de forpuede describirse en términos de whileque esto:
para ( e1 ; e2 ; e3 ) { s ; }es equivalente a:
e1 ; mientras ( e2 ) { s ; cont : e3 ; }excepto por el comportamiento de una continue;instrucción (que en el forbucle salta a e3en lugar de e2). Si e2está en blanco, tendría que ser reemplazado por un 1.
Cualquiera de las tres expresiones del forbucle puede omitirse. Si falta la segunda expresión, la whileprueba siempre dará como resultado un valor distinto de cero, lo que describe un bucle infinito.
Desde C99, la primera expresión puede adoptar la forma de una declaración con un alcance limitado a la subinstrucción. Por ejemplo:
para ( int i = 0 ; i < límite ; ++ i ) { // ... }El bucle foreach no existe en C, como sí existe en Java y C++. Sin embargo, se puede emular mediante macros.
Sentencia de salto
Hay cuatro instrucciones de salto (transfieren el control incondicionalmente): goto, continue, break, y return.
La gotoinstrucción transfiere el control del programa a una instrucción etiquetada. Tiene la siguiente sintaxis:
ir a nombre de etiqueta
Una continueinstrucción que es simplemente la palabra continue, transfiere el control al punto de continuación del bucle de la instrucción de iteración más interna que la contiene. Debe estar encerrada dentro de una instrucción de iteración. Por ejemplo:
mientras ( verdadero ) { // ... continuar ; }hacer { // ... continuar ; } mientras ( verdadero );para (;;) { // ... continuar ; }La breakinstrucción que es simplemente la palabra breaktermina una instrucción for, while, do, o switch. El control pasa a la instrucción que sigue a la instrucción de control que la contiene.
La returninstrucción transfiere el control a quien llama a la función. Cuando returnva seguida de una expresión, el valor se devuelve a quien llama. Encontrar el final de la función es equivalente a una returninstrucción sin expresión. En ese caso, si la función está declarada para devolver un valor y quien llama intenta usar dicho valor, el comportamiento es indefinido.
Etiquetas
Una etiqueta marca un punto en el código al que se puede transferir el control. Una etiqueta es un identificador seguido de dos puntos. Por ejemplo:
if ( i == 1 ) { goto END ; } // otro código END :El estándar no define un método para recuperar la dirección de una etiqueta, pero GCC extiende el lenguaje con un &&operador unario que devuelve la dirección de una etiqueta. La dirección se puede almacenar en una void*variable y se puede usar posteriormente con una función goto. Esta característica se puede usar para implementar una tabla de saltos .
Por ejemplo, lo siguiente se imprime hirepetidamente:
void * ptr = && J1 ; J1 : printf ( "hola" ); goto * ptr ;Desde C2Y , C tiene bucles etiquetados, de forma similar a Java. Esto permite adjuntar etiquetas a fory transferir el control a través de breaky continuecon etiquetas ( saltos multinivel ). [ 12 ]
// usando break: outer : for ( int i = 0 ; i < n ; ++ i ) { switch ( i ) { case 1 : break ; // salta a 1 case 2 : break outer ; // salta a 2 default : continue ; } // 1 } // 2// usando continue: outer : for ( int i = 0 ; i < m ; ++ i ) { for ( int j = 0 ; j < n ; ++ j ) { continue ; // salta a 1 continue outer ; // salta a 2 // 1 } // 2 }Funciones
Definición
Para una función que devuelve un valor, una definición consta de un nombre de tipo de retorno , un nombre de función único en el código fuente, una lista de parámetros entre paréntesis y un bloque de instrucciones que finaliza con una returninstrucción. El bloque puede contener una returninstrucción para salir de la función antes de su finalización. La sintaxis es la siguiente:
nombre-tipo nombre-función ( lista-parámetros ) { lista-de-instrucciones valor- de-retorno ; }Una función que no devuelve ningún valor se declara con voiden lugar de un nombre de tipo, como por ejemplo:
void nombre-de-función ( lista-de-parámetros ) { lista-de-instrucciones }El estándar no incluye funciones lambda , pero algunos traductores sí.
Parámetros
Una lista de parámetros es una lista separada por comas de declaraciones de parámetros formales; cada elemento es un nombre de tipo seguido de un nombre de variable:
tipo-nombre variable-nombre { , tipo-nombre variable-nombre }El tipo de retorno no puede ser una matriz ni una función. Por ejemplo:
int f ()[ 3 ]; // Error: la función devuelve un array int ( * g ())[ 3 ]; // OK: la función devuelve un puntero a un arrayvoid h ()(); // Error: la función devuelve una función void ( * k ())(); // OK: la función devuelve un puntero a funciónSi la función no acepta parámetros, la lista de parámetros puede ser la palabra clave `new` voido estar vacía, pero esto tiene implicaciones diferentes. Llamar a una función con argumentos cuando se declara con `new` voidpara la lista de parámetros es una sintaxis inválida. Llamar a una función con argumentos cuando se declara con una lista de parámetros vacía no es una sintaxis inválida, pero puede resultar en un comportamiento indefinido. voidPor lo tanto, usar `new` es la mejor práctica.
Una función puede aceptar un número variable de argumentos incluyendo ...al final de la lista de argumentos. Una función comúnmente utilizada con esta declaración es la función de la biblioteca estándar printfque tiene el siguiente prototipo:
int printf ( const char * , ...);El consumo de argumentos de longitud variable se puede lograr mediante funciones de la biblioteca estándar declaradas en <stdarg.h>.
Vocación
El código puede acceder a una función de una biblioteca si esta está declarada y definida. Generalmente, la declaración de una función de biblioteca se proporciona mediante un archivo de cabecera que el código que la utiliza emplea a través de la #includedirectiva. Alternativamente, el código que utiliza la función puede declararla en su propio archivo. La definición de la función se asocia al código que la utiliza en tiempo de enlace. La biblioteca estándar se enlaza por defecto, mientras que otras bibliotecas requieren configuración en tiempo de enlace.
Acceder a una función definida por el usuario en un archivo diferente es similar a usar una función de biblioteca. El código que utiliza la función la declara incluyendo un archivo de cabecera o directamente en su propio archivo. La vinculación con la definición en el otro archivo se realiza al enlazar los archivos objeto.
Llamar a una función definida en el mismo archivo es relativamente sencillo. Su definición o declaración debe estar antes de la llamada.
Pase de argumento
Un argumento se pasa a una función por valor, lo que significa que la función llamada recibe una copia del argumento y no puede modificar la variable que contiene. Para que una función modifique el valor de una variable, quien la llama le pasa la dirección de la variable (un puntero), lo que simula el paso por referencia que ofrecen otros lenguajes . La función llamada puede modificar la variable desreferenciando la dirección pasada.
En el siguiente código, la dirección xse pasa especificando &xen la llamada. La función llamada recibe la dirección como yy accede xcomo *y.
vacío incInt ( int * y ) { ( * y ) ++ ; }int main ( void ) { int x = 7 ; incInt ( & x ); return 0 ; }El siguiente código demuestra un uso más avanzado de punteros :a pasar un puntero a otro puntero. En la línea 9 se define un puntero de tipo int llamado y su dirección se pasa a la función en la línea 10. La función recibe un puntero a un puntero de tipo int llamado a_p. Le asigna a(como *a_p). Después de la llamada, en la línea 11, se libera la memoria asignada a la dirección a.
#include <stdio.h> #include <stdlib.h>void allocate_array ( int ** const p , const int count ) { * p = malloc ( sizeof ( int ) * count ); }int main ( void ) { int * a ; allocate_array ( &a a , 42 ); free ( a ); return 0 ; }Paso de array
Los parámetros de función de tipo matriz pueden parecer a primera vista una excepción a la regla de paso por valor, como lo demuestra el siguiente programa que imprime 123, no 1:
#include <stdio.h>void setArray ( int a [], int index ) { array [ index ] = 123 ; }int main ( void ) { int a [ 1 ] = {1} ; setArray ( a , 0 ) ; printf ( "a[0]=%d \n " , a [ 0 ]); return 0 ; }Sin embargo, existe otra razón para este comportamiento. Un parámetro de matriz se trata como un puntero. El siguiente prototipo es equivalente al prototipo de función anterior:
void setArray ( int * a , int index );Al mismo tiempo, las reglas para el uso de arreglos en expresiones hacen que el valor ase trate como un puntero al primer elemento. Por lo tanto, sigue siendo paso por valor, con la salvedad de que se pasa por valor la dirección del primer elemento del arreglo, no su contenido.
Desde C99, el programador puede especificar que una función acepte un array de un tamaño determinado mediante la palabra clave ` static.`. El primer parámetro debe ser un puntero al primer elemento de un array de longitud mínima 4. También es posible usar calificadores ( , y ) para especificar el tipo de puntero al que se convierte el array.voidsetArray(intarray[static4],intindex)constvolatilerestrict
Atributos
Agregado en C23 y originado de C++11, C admite secuencias de especificadores de atributos. [ 13 ] Los atributos se pueden aplicar a cualquier símbolo que los admita, incluidas funciones y variables, y cualquier símbolo marcado con un atributo será tratado específicamente por el compilador como necesario. Estos pueden pensarse como similares a las anotaciones de Java para proporcionar información adicional al compilador, sin embargo, difieren en que los atributos en C no están destinados a ser accedidos mediante reflexión. Además, no se pueden crear atributos personalizados en C, a diferencia de Java, donde se pueden definir anotaciones personalizadas además de las estándar. Sin embargo, C tiene atributos específicos de implementación/proveedor que no son estándar. Estos generalmente tienen un espacio de nombres asociado. Por ejemplo, GCC y Clang tienen atributos bajo el gnu::espacio de nombres, y todos esos atributos tienen la forma , aunque C no tiene soporte para espacios de nombres en el lenguaje.[[gnu::*]]
La sintaxis para usar un atributo en una función es la siguiente:
[[ nodiscard ]] bool satisfiesProperty ( const struct MyStruct * s );La norma define los siguientes atributos:
Memoria dinámica
La asignación dinámica de memoria en C se refiere a la realización de una gestión manual de la memoria para la asignación dinámica de memoria en el lenguaje de programación C a través de un grupo de funciones en la biblioteca estándar de C , principalmente malloc, realloc, calloc, aligned_allocy free. [ 14 ] [ 15 ] [ 16 ]
El lenguaje de programación C++ incluye estas funciones; sin embargo, los operadores new y delete proporcionan una funcionalidad similar y son recomendados por los autores de ese lenguaje. [ 17 ] Aun así, existen varias situaciones en las que el uso de / no es aplicable, como el código de recolección de basura o el código crítico para el rendimiento, y puede ser necesaria una combinación de y en lugar del operador de nivel superior.newdeletemallocnewnew
Existen diversas implementaciones del mecanismo de asignación de memoria utilizado por malloc . Su rendimiento varía tanto en tiempo de ejecución como en la memoria requerida.
Véase también
Notas
- 1 2 El
long longmodificador se introdujo en el estándar C99 . - ↑ En C++0x, el significado de auto es un especificador de tipo en lugar de un especificador de clase de almacenamiento.
- ↑ Consulte la primera sección sobre UTF-8 para obtener referencias.
- ↑ C++ permite usar todos los tipos enteros y enumerados, y muchos compiladores de C hacen lo mismo.
- ↑ desde C99
- ↑ Debido a la precedencia de operadores ("." es mayor que "*"),
*ptee.yno es correcto; se analiza como*(ptee.y)y por lo tanto los paréntesis son necesarios.
Referencias
- ↑ "C | Definición, historia, aplicaciones y datos | Britannica" . Enciclopedia Británica . Archivado del original el 11 de septiembre de 2025. Consultado el 3 de febrero de 2026 .
- ^ Klemens , Ben (2012) . Siglo XXI C. Medios O'Reilly . ISBN 978-1449327149.
- ↑ "WG14-N2412: Representación de signos en complemento a dos" (PDF) . open-std.org . 11 de agosto de 2019. Archivado (PDF) del original el 27 de diciembre de 2022.
- ↑ "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.
- ↑ https://ptgmedia.pearsoncmg.com/images/0131774298/samplechapter/0131774298.pdf
- ↑ Balagurusamy, E. Programación en ANSI C. Tata McGraw Hill. pag. 366.
- ↑ "El preprocesador de C: comportamiento definido por la implementación" . gcc.gnu.org .
- ↑ "Literales de cadena y de caracteres (C++)" . Documentación de Visual C++ 19. Consultado el 20 de noviembre de 2019 .
- ↑ Kernighan y Richie
- ↑ Tatham, Simon (2000). "Coroutines in C" . Recuperado el 30 de abril de 2017 .
- ↑ "WG14-N3370: Expresiones de rango de casos, v3.1" . open-std.org . 1 de octubre de 2024. Archivado del original el 4 de agosto de 2025.
- ↑ Alex Celeste (18 de septiembre de 2024). "Bucles con nombre, v3" . open-std.org . WG 14.
- ↑ "Secuencia de especificador de atributo (desde C23)" . cppreference.com . Consultado el 6 de junio de 2025 .
- ↑ 7.20.3 Funciones de gestión de memoria (PDF) . Especificación ISO/IEC 9899:1999 (Informe técnico). pág. 313.
- ↑ Summit, Steve. "Capítulo 11: Asignación de memoria" . Notas de programación en C. Consultado el 11 de julio de 2020 .
- ↑ "aligned_alloc(3) - Página man de Linux" .
- ↑ Stroustrup, Bjarne (2008). Programación: Principios y práctica con C++ . Addison Wesley. pág. 1009. ISBN 978-0-321-54372-1.
- 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.
- Norma Nacional Estadounidense para Sistemas de Información - Lenguaje de Programación - C - ANSI X3.159-1989
- "ISO/IEC 9899:2018 - Tecnología de la información - Lenguajes de programación - C" . Organización Internacional de Normalización .
- "ISO/IEC 9899:1999 - Lenguajes de programación - C" . Iso.org. 8 de diciembre de 2011. Consultado el 8 de abril de 2014 .
Enlaces externos
- La sintaxis de C en forma Backus-Naur
- Programación en C
- Página de preguntas frecuentes de comp.lang.c
- Referencia C
- C (lenguaje de programación)
- Código fuente
- sintaxis del lenguaje de programación