Diferença entre os tipos string e char [] em C ++

126

Eu sei um pouco de C e agora estou dando uma olhada no C ++. Estou acostumado a matrizes de caracteres para lidar com seqüências de caracteres C, mas enquanto eu observo o código C ++, vejo que existem exemplos usando o tipo de sequência de caracteres e as matrizes de caracteres:

#include <iostream>
#include <string>
using namespace std;

int main () {
  string mystr;
  cout << "What's your name? ";
  getline (cin, mystr);
  cout << "Hello " << mystr << ".\n";
  cout << "What is your favorite team? ";
  getline (cin, mystr);
  cout << "I like " << mystr << " too!\n";
  return 0;
}

e

#include <iostream>
using namespace std;

int main () {
  char name[256], title[256];

  cout << "Enter your name: ";
  cin.getline (name,256);

  cout << "Enter your favourite movie: ";
  cin.getline (title,256);

  cout << name << "'s favourite movie is " << title;

  return 0;
}

(os dois exemplos de http://www.cplusplus.com )

Suponho que essa seja uma pergunta amplamente respondida (óbvia?), Mas seria bom se alguém pudesse me dizer qual é exatamente a diferença entre essas duas maneiras de lidar com seqüências de caracteres em C ++ (desempenho, integração de API, a maneira como cada uma delas é Melhor, ...).

Obrigado.

ramosg
fonte
Isso pode ajudar: C ++ char * vs std :: string #
Wael Dalloul 17/08/09

Respostas:

187

Uma matriz de caracteres é exatamente isso - uma matriz de caracteres:

  • Se alocado na pilha (como no seu exemplo), ela sempre ocupará, por exemplo. 256 bytes, não importa quanto tempo o texto contenha
  • Se alocado no heap (usando malloc () ou novo char []), você é responsável por liberar a memória posteriormente e sempre terá a sobrecarga de uma alocação de heap.
  • Se você copiar um texto com mais de 256 caracteres na matriz, ele poderá falhar, produzir mensagens feias de afirmação ou causar um comportamento inexplicável (incorreto) em outro lugar do seu programa.
  • Para determinar o comprimento do texto, a matriz deve ser digitalizada, caractere por caractere, para um caractere \ 0.

Uma string é uma classe que contém uma matriz de caracteres, mas a gerencia automaticamente para você. A maioria das implementações de cadeia de caracteres possui uma matriz interna de 16 caracteres (portanto, cadeias curtas não fragmentam a pilha) e usam a pilha para cadeias mais longas.

Você pode acessar o array de caracteres de uma string assim:

std::string myString = "Hello World";
const char *myStringChars = myString.c_str();

As seqüências de caracteres C ++ podem conter caracteres \ 0 incorporados, saber seu tamanho sem contar, são mais rápidas que matrizes de caracteres alocadas em heap para textos curtos e protegem você de excedentes de buffer. Além disso, são mais legíveis e fáceis de usar.


No entanto, as seqüências de caracteres C ++ não são (muito) adequadas para uso através dos limites da DLL, porque isso exigiria que qualquer usuário dessa função DLL certificasse-se de que ele está usando exatamente o mesmo compilador e implementação de tempo de execução do C ++, para que não arrisque que sua classe de string se comporte de maneira diferente.

Normalmente, uma classe de string também liberaria sua memória heap no heap de chamada, portanto, somente poderá liberar memória novamente se você estiver usando uma versão compartilhada (.dll ou .so) do tempo de execução.

Em resumo: use seqüências de caracteres C ++ em todas as suas funções e métodos internos. Se você escrever uma .dll ou .so, use seqüências de caracteres C em suas funções públicas (dll / tão expostas).

Cygon
fonte
4
Além disso, as strings têm várias funções auxiliares que podem ser realmente legais.
21179 Håkon
1
Não acredito um pouco sobre os limites da DLL. Sob circunstâncias muito especiais, ele pode potencialmente quebrar ((uma DLL está estaticamente vinculada a uma versão diferente do tempo de execução que a usada por outras DLL) e coisas piores provavelmente aconteceriam primeiro nessas situações), mas no caso geral em que todos estão usando o padrão versão compartilhada do tempo de execução padrão (o padrão) isso não acontecerá.
Martin York
2
Exemplo: Você distribui binários compilados pelo VC2008SP1 de uma biblioteca pública chamada libfoo, que possui um std :: string e em sua API pública. Agora, alguém baixa o seu libfoo.dll e faz uma compilação de depuração. Seu std :: string pode muito bem ter alguns campos de depuração adicionais, fazendo com que o deslocamento do ponteiro para as seqüências dinâmicas se movam.
Cygon 21/08/09
2
Exemplo 2: em 2010, alguém baixa o libfoo.dll e o usa em seu aplicativo criado pelo VC2010. O código dele carrega o MSVCP100.dll e o libfoo.dll ainda carrega o MSVCP90.dll -> você recebe dois heaps -> a memória não pode ser liberada, erros de asserção no modo de depuração se o libfoo modifica a referência da string e entrega um std :: string com um novo ponteiro de volta.
Cygon 21/08/09
1
Vou apenas continuar com "Em suma: use seqüências de caracteres C ++ em todas as suas funções e métodos internos". Tentando entender seus exemplos, meu cérebro estalou.
Stephen
12

Arkaitz está correto, que stringé um tipo gerenciado. O que isso significa para você é que você nunca precisa se preocupar com a duração da string, nem precisa liberar ou realocar a memória da string.

Por outro lado, a char[]notação no caso acima restringiu o buffer de caracteres a exatamente 256 caracteres. Se você tentou gravar mais de 256 caracteres nesse buffer, na melhor das hipóteses, substituirá a outra memória que o seu programa "possui". Na pior das hipóteses, você tentará sobrescrever a memória que você não possui, e seu sistema operacional matará seu programa no local.

Bottom line? Strings são muito mais amigáveis ​​para programadores, char [] s são muito mais eficientes para o computador.

Mark Rushakoff
fonte
4
Na pior das hipóteses, outras pessoas substituirão a memória e executarão códigos maliciosos no seu computador. Veja também buffer overflow .
David Johnstone
6

Bem, o tipo de seqüência de caracteres é uma classe completamente gerenciada para seqüências de caracteres, enquanto char [] ainda é o que era em C, uma matriz de bytes que representa uma sequência de caracteres para você.

Em termos de API e biblioteca padrão, tudo é implementado em termos de strings e não char [], mas ainda existem muitas funções da libc que recebem char [], então você pode precisar usá-lo para elas, além do que eu faria sempre use std :: string.

Em termos de eficiência, é claro, um buffer bruto de memória não gerenciada quase sempre será mais rápido para muitas coisas, mas leve em consideração a comparação de strings, por exemplo, std :: string sempre tem o tamanho para checá-lo primeiro, enquanto char [] você precisa comparar caractere por caractere.

Arkaitz Jimenez
fonte
5

Pessoalmente, não vejo nenhuma razão pela qual alguém queira usar char * ou char [], exceto pela compatibilidade com o código antigo. std :: string não é mais lento do que usar uma c-string, exceto que ele manipulará a realocação para você. Você pode definir seu tamanho ao criá-lo e, assim, evitar a realocação, se desejar. O operador de indexação ([]) fornece acesso em tempo constante (e em todos os sentidos da palavra é exatamente a mesma coisa que usar um indexador de c-string). Usar o método at também oferece segurança verificada nos limites, algo que você não obtém com c-strings, a menos que você o escreva. Seu compilador geralmente otimiza o uso do indexador no modo de liberação. É fácil mexer com c-strings; coisas como excluir x excluir [], segurança de exceção e até mesmo como realocar uma string c.

E quando você tiver que lidar com conceitos avançados, como ter strings COW e não COW para MT, etc, precisará de std :: string.

Se você está preocupado com cópias, contanto que você use referências e referências constantes sempre que puder, você não terá nenhuma sobrecarga devido a cópias, e é a mesma coisa que você faria com o c-string.

Abhay
fonte
+1 Embora você não tenha considerado problemas de implementação como compatibilidade com DLL, você tem COW.
que tal eu sei que minha matriz de caracteres em 12 bytes? Se eu instanciar uma string para isso, pode não ser realmente eficiente, certo?
precisa saber é o seguinte
@ David: Se você tiver um código extremamente sensível, então sim. Você pode considerar a chamada std :: string ctor como uma sobrecarga, além da inicialização dos membros std :: string. Mas lembre-se de que a otimização prematura criou muitas bases de código desnecessariamente no estilo C, portanto, tenha cuidado.
Abhay
1

As strings têm funções auxiliares e gerenciam matrizes de char automaticamente. Você pode concatenar seqüências de caracteres; para uma matriz de caracteres, você precisará copiá-la para uma nova matriz; as seqüências de caracteres podem alterar seu comprimento no tempo de execução. Um array de caracteres é mais difícil de gerenciar do que uma string e certas funções podem aceitar apenas uma string como entrada, exigindo a conversão da matriz em uma string. É melhor usar seqüências de caracteres, elas foram feitas para que você não precise usar matrizes. Se as matrizes fossem objetivamente melhores, não teríamos seqüências de caracteres.


fonte
0

Pense em (char *) como string.begin (). A diferença essencial é que (char *) é um iterador e std :: string é um contêiner. Se você seguir as strings básicas, um (char *) fornecerá o que std :: string :: iterator faz. Você pode usar (char *) quando quiser o benefício de um iterador e também a compatibilidade com C, mas essa é a exceção e não a regra. Como sempre, tenha cuidado com a invalidação do iterador. Quando as pessoas dizem (char *) não é seguro, é isso que elas querem dizer. É tão seguro quanto qualquer outro iterador C ++.

Samuel Danielson
fonte
0

Uma das diferenças é a terminação nula (\ 0).

Em C e C ++, char * ou char [] levará um ponteiro para um único char como parâmetro e acompanhará a memória até que um valor de memória 0 seja atingido (geralmente chamado de terminador nulo).

As strings C ++ podem conter caracteres \ 0 incorporados, saber seu tamanho sem contar.

#include<stdio.h>
#include<string.h>
#include<iostream>

using namespace std;

void NullTerminatedString(string str){
   int NUll_term = 3;
   str[NUll_term] = '\0';       // specific character is kept as NULL in string
   cout << str << endl <<endl <<endl;
}

void NullTerminatedChar(char *str){
   int NUll_term = 3;
   str[NUll_term] = 0;     // from specific, all the character are removed 
   cout << str << endl;
}

int main(){
  string str = "Feels Happy";
  printf("string = %s\n", str.c_str());
  printf("strlen = %d\n", strlen(str.c_str()));  
  printf("size = %d\n", str.size());  
  printf("sizeof = %d\n", sizeof(str)); // sizeof std::string class  and compiler dependent
  NullTerminatedString(str);


  char str1[12] = "Feels Happy";
  printf("char[] = %s\n", str1);
  printf("strlen = %d\n", strlen(str1));
  printf("sizeof = %d\n", sizeof(str1));    // sizeof char array
  NullTerminatedChar(str1);
  return 0;
}

Resultado:

strlen = 11
size = 11
sizeof = 32  
Fee s Happy


strlen = 11
sizeof = 12
Fee
Eswaran Pandi
fonte
"de específico, todos os caracteres são removidos" não, eles não são "removidos", a impressão de um ponteiro de caracteres imprime apenas até o terminador nulo. (já que essa é a única maneira que um caractere * conhece o fim), a classe string conhece o tamanho completo, por isso apenas usa isso. se você souber o tamanho do seu caractere *, também poderá imprimir / usar todos os caracteres.
Puddle