Este es un hilo viejo y creo que las otras respuestas son geniales, pero pasan por alto algo, así que aquí están mis (tardíos) 2 centavos.
La complejidad sintética del revestimiento de azúcar oculta
El problema con las cadenas es que son ciudadanos de segunda clase en la mayoría de los idiomas y, de hecho, la mayoría de las veces no son realmente parte de la especificación del lenguaje en sí: son una construcción implementada en la biblioteca con algún recubrimiento de azúcar sintáctico ocasional en la parte superior para que sean menos dolorosos de usar.
La consecuencia directa de esto es que el lenguaje oculta una gran parte de su complejidad fuera de su vista, y usted paga los efectos secundarios furtivos porque se acostumbra a considerarlos como una entidad atómica de bajo nivel, al igual que otros tipos primitivos (como se explica en la respuesta más votada y otros).
Detalles de implementacion
Good Ol 'Array
Uno de los elementos de esta "complejidad" subyacente es que la mayoría de las implementaciones de cadenas recurrirían al uso de una estructura de datos simple con algo de espacio de memoria contiguo para representar la cadena: su buena matriz.
Esto tiene sentido, ya que desea que el acceso a la cadena en su conjunto sea rápido. Pero eso implica costos potencialmente terribles cuando desea manipular esta cadena. Acceder a un elemento en el medio podría ser rápido si sabe qué índice está buscando , pero buscar un elemento basado en una condición no lo es.
Incluso devolver el tamaño de la cadena puede ser costoso, si su idioma no almacena en caché la longitud de la cadena y necesita ejecutarla para contar caracteres.
Por razones similares, agregar elementos a su cadena resultará costoso ya que lo más probable es que necesite reasignar algo de memoria para que esta operación ocurra.
Por lo tanto, diferentes idiomas adoptan diferentes enfoques para estos problemas. Java, por ejemplo, se tomó la libertad de hacer que sus cadenas sean inmutables por algunas razones válidas (longitud de almacenamiento en caché, seguridad de subprocesos) y sus contrapartes mutables (StringBuffer y StringBuilder) optarán por asignar el tamaño utilizando trozos de mayor tamaño para no tener que asignarlos. cada vez, pero más bien esperamos los mejores escenarios. Generalmente funciona bien, pero el inconveniente es que a veces se paga por los impactos en la memoria.
Soporte Unicode
Además, y de nuevo esto se debe al hecho de que la capa de azúcar sintáctica de su idioma lo oculta para que juegue bien, a menudo no lo considera términos de soporte Unicode (especialmente mientras no lo necesite realmente y golpear esa pared). Y algunos lenguajes, siendo progresistas, no implementan cadenas con matrices subyacentes de primitivas char simples de 8 bits. Se hornearon en UTF-8 o UTF-16 o lo que tiene soporte para usted, y la consecuencia es un consumo de memoria tremendamente mayor, que a menudo no es necesario, y un mayor tiempo de procesamiento para asignar memoria, procesar las cadenas, e implementar toda la lógica que va de la mano con la manipulación de puntos de código.
El resultado de todo esto es que cuando haces algo equivalente en pseudocódigo para:
hello = "hello,"
world = " world!"
str = hello + world
Puede que no sea, a pesar de todos los mejores esfuerzos que los desarrolladores de lenguaje pusieron para que se comporten como lo haría excepto, un simple como:
a = 1;
b = 2;
shouldBeThree = a + b
Como seguimiento, es posible que desee leer: