2010-03-24 9 views
11

Fui picado recientemente por un error sutil.¿Por qué permitir la concatenación de cadenas literales?

char ** int2str = { 
    "zero", // 0 
    "one", // 1 
    "two" // 2 
    "three",// 3 
    nullptr }; 

assert(int2str[1] == std::string("one")); // passes 
assert(int2str[2] == std::string("two")); // fails 

Si tiene poderes divinos de revisión de código se dará cuenta de que se olvidó de la , después "two".

Después del esfuerzo considerable para encontrar ese error, tengo que preguntar ¿por qué alguien querría este comportamiento?

Puedo ver cómo esto podría ser útil para macro magia, pero entonces ¿por qué es esto una "característica" en un lenguaje moderno como Python?

¿Alguna vez ha usado la concatenación literal de cadenas en el código de producción?

+3

Tuve un error como este pero tenía números en filas diferentes y la fila después de la coma faltada tenía un signo negativo, así que no obtuve un error de compilación. –

+0

Creo que C++ 0x 'literales definidos por el usuario: http://public.research.att.com/~bs/C++0xFAQ.html#UD-literals –

+0

Con el mismo espíritu, ¿qué pasa si se olvida de la '_s' después de los literales de cadena? – visitor

Respuesta

4

Veo varias C++ y C respuestas, pero ninguno de los realmente a qué o realmente lo que era la razón de esta característica? En C++ esto es característica viene de C99 y podemos encontrar la razón de esta característica, vaya a Rationale for International Standard—Programming Languages—C sección 6.4.5literales de cadena que dice (énfasis del):

Una cadena puede ser continúa en varias líneas mediante el uso de la continuación de línea barra invertida-línea nueva, pero esto requiere que la continuación de la cadena comience en la primera posición de la siguiente línea. Para permitir un diseño más flexible, y para resolver algunos problemas de preproceso (ver §6.10.3), el Comité C89 introdujo la concatenación literal de cadenas. Dos literales de cadena en una fila se pegan juntos, sin ningún carácter nulo en el medio, para hacer una cadena combinada literal. Esta adición al lenguaje C permite a un programador extender un literal de cadena más allá del final de una línea física sin tener que usar el mecanismo de barra invertida-nueva línea y destruyendo así el esquema de indentación del programa. No se introdujo un operador explícito de concatenación porque la concatenación es una construcción léxica en lugar de una operación en tiempo de ejecución.

Python que parece tener la misma razón, esto reduce la necesidad de continuar fea \ literales de cadena larga. Que se trata en la sección 2.4.2 String literal concatenation del The Python Language Reference.

+0

Esta parece ser la razón real _No se introdujo un operador explícito de concatenación porque la concatenación es una construcción léxica en lugar de una operación en tiempo de ejecución_. Todos los demás simplemente hicieron lo mismo. –

+0

Esto es extremadamente útil si alguna vez necesita representar grandes cantidades de texto en C literales. Por ejemplo, un mensaje extenso de "uso" para programas de CLI o si tiene la mala suerte de escribir programas de CGI en C. –

0

Ciertamente tengo tanto en C como en C++. A simple vista, no veo mucha relación entre su utilidad y cuán "moderno" es el lenguaje.

+0

No me refiero a que los idiomas más antiguos no deberían usarse de forma moderna. Me refería a cuando la especificación de idioma fue escrita originalmente. C# definitivamente ha aprendido de Java y ha evitado sus verrugas. Lo mismo es cierto para Java/C# aprendizaje de C++. –

1

Para que pueda dividir literales largos de cadena entre líneas.

Y sí, lo he visto en el código de producción.

5

Los casos en que esto puede ser útil:

  • cadenas electrógeno que incluye componentes definidos por el preprocesador (esto es tal vez el más grande caso de uso en C, y es que veo muy, muy frecuentemente).
  • constantes de cadena Dividir en múltiples líneas

Para proporcionar un ejemplo más concreto para la antigua:

// in version.h 
#define MYPROG_NAME "FOO" 
#define MYPROG_VERSION "0.1.2" 

// in main.c 
puts("Welcome to " MYPROG_NAME " version " MYPROG_VERSION "."); 
17

Es una gran característica que le permite combinar las cadenas del preprocesador con sus cuerdas.

// Here we define the correct printf modifier for time_t 
#ifdef TIME_T_LONG 
    #define TIME_T_MOD "l" 
#elif defined(TIME_T_LONG_LONG) 
    #define TIME_T_MOD "ll" 
#else 
    #define TIME_T_MOD "" 
#endif 

// And he we merge the modifier into the rest of our format string 
printf("time is %" TIME_T_MOD "u\n", time(0)); 
+0

+1, esa es la mejor razón técnica. El sistema también define varios de esos tipos de cosas; mi respuesta tiene un ejemplo. –

+0

La mejor razón técnica, si ignora el hecho de que realmente no debería usar el preprocesador para hacer este tipo de cosas en primer lugar ... –

+1

@STingRaySC, ¿qué pasa con 'PRIx32' o' PRIuLEAST32' y sus amigos? http://www.opengroup.org/onlinepubs/9699919799/basedefs/inttypes.h.html –

22

Claro, es la manera fácil de hacer que el código se ven bien:

char *someGlobalString = "very long " 
         "so broken " 
         "onto multiple " 
         "lines"; 

La mejor razón, sin embargo, es para los formatos de printf extraños, como el tipo forzando:

uint64_t num = 5; 
printf("Here is a number: %"PRIX64", what do you think of that?", num); 

Hay muchos definidos, y pueden ser útiles si tiene requisitos de tamaño de letra. Compruébelos todos at this link. Unos pocos ejemplos:

PRIo8 PRIoLEAST16 PRIoFAST32 PRIoMAX PRIoPTR 
+0

@Carl - debe modificar su ejemplo de printf para tener un argumento 'uint64_t'. –

+0

¡Gracias por eso! Fijo. –

2

no estoy seguro acerca de otros lenguajes de programación, pero por ejemplo C# no le permiten hacer esto (y creo que esto es una buena cosa). Por lo que yo puedo decir, la mayor parte de los ejemplos que muestran por qué esto es útil en C++ todavía funcionaría si se podía utilizar algún operador especial para la concatenación de cadenas:

string someGlobalString = "very long " + 
          "so broken " + 
          "onto multiple " + 
          "lines"; 

esto puede no ser tan cómodo, pero es ciertamente más seguro. En su ejemplo motivador, el código no sería válido a menos que haya agregado , para separar elementos o + para concatenar cadenas ...

+0

Eso no sería válido. Al menos una de esas cadenas debería tener un molde para std :: string antes de que compile. Además, la pregunta está etiquetada con C. –

+0

@BillyONeal: la pregunta está etiquetada con Python/C++ y pregunta por qué "los lenguajes modernos como Python" lo permiten, así que pensé en publicar un contraejemplo. Y quería mostrar que no necesita la característica (en general) para soportar cosas como saltos de línea y expansión de macros. –

+0

Hmm ... ¿qué estaba fumando? +1 –

3

Desde el pitón referencia análisis léxico, sección 2.4.2:

Esta característica se puede utilizar para reducir el número de barras invertidas necesarios, para dividir cadenas largas convenientemente a través de largas líneas, o incluso a añadir comentarios a partes de cuerdas

http://docs.python.org/reference/lexical_analysis.html

+0

¿No haría una cadena '' 'lo mismo? –

+0

@Caspin: una cadena sin formato (r '' o triple citada) incluirá todos los caracteres de nueva línea y espacio en blanco. Los literales de cadena separados solo se concatenarán. – JimB

0

Mientras peo Por haber sacado las palabras de mi boca sobre los usos prácticos de la función, hasta ahora nadie ha intentado defender la elección de la sintaxis.

Por lo que sé, el error tipográfico que se puede deslizar como resultado probablemente se haya pasado por alto. Después de todo, parece robustez frente a errores tipográficos no estaba en la parte delantera de la mente de Dennis, como se muestra además por:

if (a = b); 
{ 
    printf("%d", a); 
} 

Por otra parte, está la posible opinión de que no valía la pena utilizar hasta un símbolo adicional para la concatenación de literales de cadena - después de todo, no hay mucho más que se pueda hacer con dos de ellos, y tener un símbolo allí podría crear la tentación de tratar de usarlo para la concatenación de cadenas en tiempo de ejecución, que está por encima del nivel de las características incorporadas de C .

Algunos lenguajes modernos de alto nivel basados ​​en sintaxis C han descartado esta notación presumiblemente porque es propensa a errores tipográficos. Pero estos lenguajes tienen un operador para la concatenación de cadenas, como + (JS, C#), . (Perl, PHP), ~ (D, aunque esto también ha mantenido la sintaxis de yuxtaposición de C) y plegado constante (en idiomas compilados, de todos modos) significa que no hay una sobrecarga de rendimiento en el tiempo de ejecución.

1

Por razón de ser, la expansión y la simplificación de la respuesta de Shafik Yaghmour: concatenación de cadenas literal se originó en C (de ahí heredado por C++), al igual que el término, por dos razones (referencias son de Rationale for the ANSI C Programming Language):

  • Para el formateo: para permitir que los literales de cadena larga abarquen varias líneas con la sangría adecuada, en contraste con la continuación de línea, que destruye el esquema de sangría (3.1.4 String literals); y
  • Para macro magia: para permitir la construcción de literales de cadena por macros (a través de stringizing) (3.8.3.2 The # operator).

Se incluye en los lenguajes modernos Python y D porque lo copiaron de C, aunque en ambos se ha propuesto como desaprobación, ya que es propenso a errores (como se señala) e innecesario (ya uno solo puede tener un operador de concatenación y constant folding para la evaluación en tiempo de compilación; no puede hacer esto en C porque las cadenas son punteros, por lo que no puede agregarlos).

No es fácil de eliminar porque rompe la compatibilidad, y debe tener cuidado con la precedencia (la concatenación implícita ocurre durante el léxico, antes de los operadores, pero reemplazarla por un operador significa que debe tener cuidado con la precedencia), por lo tanto por qué todavía está presente.

Sí, está en el código de producción utilizado. Google Python Style Guide: Line length especifica:

Cuando una cadena literal no cabe en una sola línea, utilizan paréntesis para la línea implícita unión.

x = ('This will build a very long long ' 
    'long long long long long long string') 

Ver “String literal concatenation” en la Wikipedia para más detalles y referencias.

+0

Es un gran punto que el plegado constante elimina todas las ventajas. Esto incluso podría funcionar en C/C++ si está limitado solo a literales de cadena. –

+0

Gracias! Es cierto que los literales de cadena podrían tener una envoltura especial, aunque eso también sería un truco, y confuso a su manera: ¿por qué '" foo "+" bar "' funciona pero 'cadena s =" barra "; "foo" + s' no? Creo que el razonamiento es que los literales de las cuerdas son decididamente del tipo 'char []' (C)/'const char []' (C++). Sin embargo, * does * funciona en C++ 14 con el nuevo literal estándar de cadena (con sufijo 's'):' "foo" s + "bar" s' es legítimo y está sujeto a plegado. –

Cuestiones relacionadas