Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


3.2.3f  Constantes de cadena

§1 Sinopsis

Las constantes de cadena, también llamadas cadenas literales o alfanuméricas ("String literals"), son utilizadas para albergar secuencias de caracteres alfanuméricos, y forman una categoría especial dentro de las constantes, aunque también pueden ser consideradas un caso especial de matrices de caracteres ( 4.3.4). Se representan en el fuente como una secuencia de caracteres entre comillas dobles ("):

"Esto es una cadena literal!"

L"Esto es una cadena de caracteres anchos"


Vistas con cierta perspectiva, las cadenas literales aparecen como un tipo algo extraño, que en cierta forma desentona del resto de entidades del universo C++ y que no deberían tener cabida en él. En realidad hay algo de esto; representan una herencia de su antecesor C, en el que se utilizan estas "extrañas" construcciones para almacenar texto alfanumérico en el que su punto final se identifica mediante el carácter nulo [2].

El texto de una cadena literal puede contener cualquier carácter del juego de caracteres imprimibles ASCII ( 2.2.1a). Para representar los caracteres no imprimibles se utilizan las denominadas secuencias de escape ; un truco que consiste en sustituir cada carácter no imprimible por una secuencia de dos o tres caracteres. Naturalmente, una cadena literal no debe contener un carácter nulo en su interior (que como hemos indicado señala el final de la cadena); en caso contrario, al aplicarse las funciones de Librería Estándar clásicas (heredadas de C) el resultado es impredecible.


§1.1 El punto importante a entender aquí podría sintetizarse en que, desde la óptica C++, las cadenas literales:

  • Representan valores (datos) constantes.

  • Para el compilador son en realidad matrices de caracteres constantes, aunque les permite una sintaxis algo especial en atención a que el antiguo código C sea compatible con los compiladores C++ .

  • Si la cadena no está precedida por la letra L, es una matriz de caracteres tipo const char ( 3.2.3), también denominada cadena estrecha u ordinaria ("Narrow string literal").

  • Si la cadena está precedida por la letra L, los miembros de la matriz son caracteres anchos ( 2.2.1a1) del tipo const w_char, y se denomina cadena ancha ("Wide string literal").

  • Estos objetos tienen almacenamiento estático en el sentido indicado en 2.2.6; es decir, que el compilador conoce los valores en tiempo de compilación, y que probablemente sean guardados en el segmento ( 1.3.2) [4].


§1.2
Generalmente aparecen a la derecha en expresiones de asignación o como parámetros de funciones; pueden aparecer incluso como valores devueltos por funciones. Ejemplos:

char* ptr = "Hola mundo";

printf("%s\n", "Soy una cadena literal");

cout << "acabo de llegar" << endl;

return "Se termina la función";

§2 Secuencias de escape

Dentro de las comillas se pueden representar caracteres especiales (no imprimibles) mediante secuencias de escape ( 3.2.3e).  Por ejemplo, el código:

"\t\t\"Nombre\"\\\tDirección\n\n"

Se representa como:

      "Nombre"\   Dirección

"Nombre" es precedido por dos tabulaciones; Dirección esta precedido por una tabulación. La línea va seguida de dos nueva línea (NL). La secuencia \" proporciona las comillas interiores. Si se compila con la opción -A para compatibilidad ANSI, la secuencia de escape "\\", es traducida por el compilador a "\".


§3 Como hemos señalado, en C++ las cadenas alfanuméricas son técnicamente matrices de caracteres constantes; se almacenan internamente como secuencias de caracteres más un carácter final nulo  '\0', lo que significa que el almacenamiento usado es igual a la longitud visible de la cadena mas uno.  Según esto, la cadena nula "" es almacenada como un solo carácter '\0', y no hay más límite que la memoria disponible para la longitud posible de una cadena. Por ejemplo, la cadena "Hola\n" se guarda internamente como:

 H 

 o 

 l 

 a 

 \n 

 \0 


Nota histórica: El método de identificar el final de cadena mediante un carácter nulo es ineficiente, y aparte de que no permite utilizar cadenas que contengan un carácter nulo en su interior (por ejemplo ficheros binarios), su manejo conlleva problemas de rendimiento [3].

Aparentemente, la razón por la que C y C++ utilizan este método se debe a que C se desarrolló sobre máquinas Unix ( 1), y a su vez este sistema operativo fue desarrollado inicialmente sobre una máquina DEC ("Digital Equipment Corporation") PDP-7, que tenían un tipo de dato denominado ASICZ (ASCII con un "Zero" al final) que era directamente reconocible por su ensamblador. 

§4 Peculiaridades

En el preámbulo señalamos que una cadena literal es "un tipo de matriz de caracteres constantes". Se trata pues de verdaderas matrices, aunque la coletilla "de caracteres constantes" es importante. Significa esto que no se trata de ningún nuevo tipo de dato, solo un tipo particular de matrices. En consecuencia, una cadena como "Hola" es del tipo const char [5] (matriz de caracteres constantes de cinco elementos). Su única singularidad es que el compilador C++ les permite ciertas formas particulares de definición y declaración que les confiere cierta personalidad. Por ejemplo, el hecho de inicializarlas directamente con expresiones como las señaladas §1.2 ;  o que añada automáticamente el carácter nulo de terminación a los caracteres explícitamente indicados por el programador.

Respecto a esto último, solo tiene justificación histórica, porque el carácter nulo se utilizaba en las antiguas funciones de librería para indicar el final de la cadena. Por esta razón, aunque es posible incluir caracteres nulos dentro de las cadenas literales. Por ejemplo: "Hola\0 mundo", no está garantizado que funcione en todos los casos (será malinterpretado por las funciones printf, strcpy y strlen de la Librería Estándar).

Ejemplo:

char* ptr = "Hola\0 mundo";

printf("%s\n", ptr);

Salida:

Hola


§5  Una cadena nula (vacía) se representa "" o "\0"; tiene un solo carácter; el carácter nulo (ver a continuación). Observe que desde el punto de vista del Rvalue, la constante de cadena "A" es A\0, mientras que la constante carácter ( 3.2.3d) 'A' es A.

Se considera que la longitud de un NTBS es el número de caracteres que preceden al de terminación, de forma que la cadena nula tiene longitud 0 (aunque en realidad contiene un carácter). Sin embargo, el valor de la cadena incluye el carácter de final.

  Conviene no confundir una constante de cadena (matriz) de un solo carácter con un carácter char, constante ( 3.2.3d) o variable ( 2.2.1a). La cadena de un solo carácter es necesariamente la cadena nula, su único elemento es el de fin de cadena. Ejemplo:

char   x1= 'a' ;     // L.1: variable x1 tipo char

                     // valor == ASCII a == 97 decimal

const char x2= 'a';  // L.2: constante x2 tipo const char

char* x3 = "a" ;     // L.3: variable x3 tipo puntero-a-char

                     // señala a cadena de dos caracteres, 97 y 0 decimal

const char* x4 = "a"; // L.4: variable x4 tipo puntero a constante carácter

char x5[1]= 'a' ;    // L.5: variable x5 tipo matriz de un carácter

                     // valor x[0]== a == 97 decimal


Los objetos representados por los Rvalues ( 2.1.5) de las expresiones anteriores son de distinto tipo y se almacenan con tamaños distintos. La primera, segunda y quinta son constantes carácter (const char); la tercera y cuarta son cadenas (matrices) de dos caracteres.

En lo que respecta a los cinco objetos definidos, x1 es una variable char; x2 es una constante char; x3 es un puntero a cadena de caracteres; x4 es un puntero cadena de caracteres constantes, y x5 es una matriz de caracteres de un elemento.

Es también muy importante señalar que la asignación:

str = "AEIOU";

solo es posible si str se ha declarado previamente como puntero a carácter, es decir:

char* str;               // §g

Aunque se puede declarar y definir en la misma sentencia (preferible):

char* str = "AEIOU";     // §h


El lector observador advertirá en la expresión anterior una evidente inconsistencia en la gramática del C++. En efecto, como se ha señalado, las cadenas literales son de "caracteres constantes". En consecuencia, no asignables a punteros tales como los definidos en §g o §h ; técnicamente "punteros a carácter". Teóricamente solo hubiese sido aceptable la asignación a puntero-a-carácter-constante, tal como:

const char* str = "AEIOU";      // §i

ya que un puntero-a-constante-tipoX no es intercambiable por un puntero-a-tipoX ( 4.2.1a). La razón de esta inconsistencia en la definición de las cadenas literales, hay que buscarla en otra de las desafortunadas herencias del C clásico, y en la necesidad de mantener compatibilidad con millones de líneas de código existente. En C y en las primitivas versiones de C++, las cadenas literales eran consideradas como de tipo char* (puntero a carácter).

Aunque la mayoría de compiladores C++ permiten este tipo de sentencias por razones de tipo histórico, parece que tal permisividad tiende a ser "deprecated" (a extinguir), de forma que las últimas revisiones de algunos compiladores pueden lanzar una advertencia o error en tales circunstancias.  La forma  canónica de definir este tipo de cadenas sería:

const char str[] = "AEIOU"; 


La expresión §h define una matriz de seis caracteres constantes (almacenado en algún sitio), que no tiene identificador simple (nombre). También define un puntero, str al primer elemento de la matriz (más formalmente: es un puntero a carácter).  Por tanto, a falta de un nombre, la matriz tiene que ser accedida a través del puntero (que tendrá que ser tratado como tal -puntero-). Sin embargo, las expresiones:

char arr[] = "AEIOU";              // §j

char arr[6] = "AEIOU";

char arr[6] = {'A','E','I','O','U','\0'};

char arr[6] = {'A','E','I','O','U',''};

char arr[6] = {'A','E','I','O','U',0};

definen arr como matrices de caracteres de 6 elementos (las 5 expresiones son equivalentes); en estos casos cada matriz puede ser accedida por su nombre (que tienen que ser tratado como tal -identificador-) y no son de contenido constante.

Todo esto tiene varias implicaciones: En el primer caso (§h ) , str es un puntero; un objeto que puede ser usado con el álgebra de punteros. Por ejemplo, es válida la expresión: str++ que equivale a: str = str+1;.  En los otros cinco casos (§j ),  arr es un nemónico que representa una matriz, y la expresión arr++, que equivale a: arr = arr+1; es ilegal. El operando arr+1 es tratado como (&arr[0])+1, lo que es correcto (a un puntero se le puede sumar un entero). La asignación no sería correcta porque intentaría asignar el puntero, &arr[1], a la matriz arr.

No olvidar que en el primer caso *str no significa la matriz "AEIOU", solo el primer elemento 'A', por lo que no tiene sentido intentar la asignación *str = "aeiou";, ni siquiera *str = "a";; solo es posible *str = 'a'; [1].  Recuerde que C++ no tiene operadores para tratar las matrices como una unidad, es decir, para hacer una asignación del tipo:  x = "aeiou".  Si tiene en cambio poderosas funciones en su Librería Estándar para hacer todo tipo de manipulaciones con cadenas alfanuméricas.

Es también importante distinguir otra diferencia entre las expresiones:

char a1[6] = "AEIOU\0", *p1 = &a1[0];

char* a2 = "AEIOU";

Ambas producen matrices de caracteres absolutamente idénticos en contenido y tamaño, pero a1 es una matriz, por lo que sus elementos podrían ser alterados usando el identificador. Por ejemplo: es válido a1[1]= 'e', o su equivalente: *(p1+1)='e';. En cambio, un intento análogo sobre el segundo obliga a usar necesariamente la versión con puntero: *(a2+1)='e'.  Además de que por las razones ya expuestas [2] el resultado no está garantizado. Si se quiere que los caracteres de una cadena puedan ser modificados lo mejor es incluirlos en una matriz como en a1.

Es posible todavía la asignación:

a2 = "aeiou";

En este caso, el compilador almacena en algún sitio la cadena "aeiou\0" y asigna a a2 la dirección del primer elemento. A partir de ahora es accesible mediante a2, pero la primitiva cadena "AEIOU", que sigue existiendo en su sitio, se ha perdido irremisiblemente, no es accesible de ningún modo, aunque sigue malgastando espacio de memoria.

También es posible declarar cadenas de caracteres anchos con el prefijo L como en el ejemplo:

wchar_t* wptr = L"aeiou";

en este caso wptr señala una cadena de caracteres anchos ( 2.2.1a1); la cadena es del tipo const wchar_t.

Para saber la longitud de una constante literal, es necesario que el programa repase la cadena hasta encontrar el carácter de fin de cadena, lo que se consigue con la función de librería strlen (incluida en la cabecera estándar <string.h>), que proporciona la longitud sin contar el carácter final.

§6 Concatenación de cadenas

Las cadenas literales adyacentes separadas solo por un especio son automáticamente concatenadas durante la fase de preprocesado (1.4.1) de la compilación. Por ejemplo: "Hola" " mundo", es equivalente a "Hola mundo". También puede usarse la barra invertida ( \ ) como símbolo de continuación para extender una cadena literal más allá del límite de una línea:

puts("En realidad, esto es \

una cadena de una linea");
char *p = "Esto es en otra cadena, "

          "también de una sola línea.";


La concatenación suprime el carácter nulo de final en las cadenas intermedias y mantiene el de la última. Esta operación no altera el significado de los caracteres que intervienen en las cadenas concatenadas. Por ejemplo, la concatenación

"\xA" "B"

Produce la cadena

"\xAB"

que tiene tres caracteres: el hexadecimal '\xA';  el carácter 'B', y el caracter final nulo '\0'. En vez del carácter hexadecimal '\xAB'.

La concatenación de cadenas anchas y estrechas tiene resultados impredecibles.

  Inicio.


[1] Respecto de esta "posibilidad", evidentemente resulta cuando menos aventurada. Se trata de la asignación a una "constante", cosa en principio prohibida. La "Biblia" de C++ nos dice al respecto que el resultado es "indefinido", lo que significa que depende del compilador y sin garantía de que el código resulte portable. Por ejemplo, C++ Builder sí permite este tipo de asignaciones.

[2] Este tipo de cadenas de caracteres reciben distintos nombres. Se conocen com ASCII-Z en atención a que el carácter nulo de terminación es el ASCII cero ("Zero").  También como NTBSs ("Null Terminated Byte String"). Las cadenas NTMBS ("Null Terminated MultiByte String") son las constituidas por caracteres de anchura variable; dentro de estas últimas se encuadran las de caracteres anchos.

[3] Puede encontrarse un extenso y divertido artículo (en español) sobre las consecuencias de esta deplorable elección en "De vuelta a las bases" de Joel Spolsky   http://spanish.joelonsoftware.com/Articles/BacktoBasics.html.

[4]  Tenga en cuenta que no deben hacerse demasiadas suposiciones sobre la existencia de un almacenamiento específico para este tipo de constantes da cadena, porque en el caso de que en una aplicación existan distintas de idéntico valor, algunos compiladores permiten crear una única copia (de solo-lectura) de tales constantes en la imagen del programa en disco (fichero) y en la imagen en memoria durante la ejecución; optimización que tiene por objeto reducir el tamaño de los ejecutables y que recibe el nombre de "string pooling" (Por ejemplo, opciones /GF y /GF- en el compilador MS Visual C++).