Este é um tópico antigo e acho que as outras respostas são ótimas, mas ignoram alguma coisa, então aqui estão meus 2 centavos (atrasados).
Revestimento sintático de açúcar esconde complexidade
O problema com as strings é que eles são cidadãos de segunda classe na maioria dos idiomas e, na maioria das vezes, na verdade não fazem parte da própria especificação de idioma: eles são uma construção implementada em uma biblioteca com um revestimento sintático ocasional de açúcar no topo para torná-los menos dolorosos de usar.
A conseqüência direta disso é que a linguagem esconde grande parte de sua complexidade da sua vista e você paga pelos efeitos colaterais sorrateiros, porque adquire o hábito de considerá-los como uma entidade atômica de baixo nível, assim como outros tipos primitivos (como explicado pela resposta mais votada e outras).
Detalhes da implementação
Good Ol 'Array
Um dos elementos dessa "complexidade" subjacente é que a maioria das implementações de cadeias recorreria ao uso de uma estrutura de dados simples com algum espaço de memória contíguo para representar a cadeia: sua boa e velha matriz.
Isso faz sentido, lembre-se, pois você deseja que o acesso à cadeia como um todo seja rápido. Mas isso implica custos potencialmente terríveis quando você deseja manipular essa sequência. O acesso a um elemento no meio pode ser rápido se você souber qual índice procura , mas a procura de um elemento com base em uma condição não é.
Até o retorno do tamanho da string pode ser caro, se o seu idioma não armazenar em cache o comprimento da string e precisar percorrê-la para contar caracteres.
Por razões semelhantes, a adição de elementos à sua cadeia de caracteres será dispendiosa, pois você provavelmente precisará realocar um pouco de memória para que esta operação ocorra.
Portanto, idiomas diferentes adotam abordagens diferentes para esses problemas. O Java, por exemplo, teve a liberdade de tornar suas seqüências imutáveis por alguns motivos válidos (tamanho do cache, segurança de threads) e, por suas contrapartes mutáveis (StringBuffer e StringBuilder), optar por alocar tamanho usando blocos de tamanho maior para não precisar alocar sempre, mas sim espere os melhores cenários. Geralmente funciona bem, mas o lado ruim é pagar às vezes por impactos na memória.
Suporte Unicode
Além disso, e novamente, isso se deve ao fato de o revestimento sintático de açúcar do seu idioma esconder isso de você para ser agradável, geralmente você não considera termos de suporte unicode (especialmente enquanto você realmente não precisar dele) e bateu nessa parede). E algumas linguagens, com visão de futuro, não implementam seqüências de caracteres com matrizes subjacentes de primitivas char simples de 8 bits. Eles são compatíveis com UTF-8 ou UTF-16 ou o que você tem para você, e a conseqüência é um consumo de memória tremendamente maior, que muitas vezes não é necessário, e um tempo de processamento maior para alocar memória, processar as seqüências de caracteres, e implemente toda a lógica que anda de mãos dadas com a manipulação de pontos de código.
Os resultados de tudo isso é que, quando você faz algo equivalente no pseudo-código a:
hello = "hello,"
world = " world!"
str = hello + world
Pode não ser - apesar de todos os melhores esforços que os desenvolvedores de linguagem enviam para que eles se comportem como você exceto - - simples como:
a = 1;
b = 2;
shouldBeThree = a + b
Como acompanhamento, você pode querer ler: