Différence entre les types string et char [] en C ++

126

Je connais un peu C et maintenant je regarde C ++. Je suis habitué aux tableaux de caractères pour traiter les chaînes C, mais pendant que je regarde le code C ++, je vois qu'il y a des exemples utilisant à la fois le type de chaîne et les tableaux de caractères:

#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;
}

et

#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;
}

(deux exemples de http://www.cplusplus.com )

Je suppose que c'est une question largement posée et répondue (évidente?), Mais ce serait bien si quelqu'un pouvait me dire quelle est exactement la différence entre ces deux façons de traiter les chaînes en C ++ (performances, intégration d'API, la façon dont chacune est meilleur, ...).

Je vous remercie.

ramosg
la source
Cela peut aider: C ++ char * vs std :: string
Wael Dalloul

Réponses:

187

Un tableau de caractères est juste cela - un tableau de caractères:

  • S'il est alloué sur la pile (comme dans votre exemple), il occupera toujours par exemple. 256 octets quelle que soit la longueur du texte qu'il contient
  • S'il est alloué sur le tas (en utilisant malloc () ou new char []), vous êtes responsable de la libération de la mémoire par la suite et vous aurez toujours la surcharge d'une allocation de tas.
  • Si vous copiez un texte de plus de 256 caractères dans le tableau, il peut planter, produire des messages d'assertion laids ou provoquer un comportement inexplicable (erroné) ailleurs dans votre programme.
  • Pour déterminer la longueur du texte, le tableau doit être scanné, caractère par caractère, pour un caractère \ 0.

Une chaîne est une classe qui contient un tableau de caractères, mais qui le gère automatiquement pour vous. La plupart des implémentations de chaînes ont un tableau intégré de 16 caractères (les chaînes courtes ne fragmentent donc pas le tas) et utilisent le tas pour les chaînes plus longues.

Vous pouvez accéder au tableau de caractères d'une chaîne comme ceci:

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

Les chaînes C ++ peuvent contenir des caractères \ 0 incorporés, connaître leur longueur sans compter, sont plus rapides que les tableaux de caractères alloués par tas pour les textes courts et vous protègent des dépassements de tampon. De plus, ils sont plus lisibles et plus faciles à utiliser.


Cependant, les chaînes C ++ ne conviennent pas (très) à une utilisation à travers les limites de la DLL, car cela exigerait que tout utilisateur d'une telle fonction DLL s'assure qu'il utilise exactement le même compilateur et la même implémentation d'exécution C ++, de peur qu'il ne risque que sa classe de chaînes se comporte différemment.

Normalement, une classe de chaîne libère également sa mémoire de tas sur le tas appelant, donc elle ne pourra à nouveau libérer de la mémoire que si vous utilisez une version partagée (.dll ou .so) du runtime.

En bref: utilisez des chaînes C ++ dans toutes vos fonctions et méthodes internes. Si jamais vous écrivez un .dll ou .so, utilisez des chaînes C dans vos fonctions publiques (dll / ainsi exposées).

Cygone
la source
4
De plus, les chaînes ont un tas de fonctions d'assistance qui peuvent être vraiment intéressantes.
Håkon
1
Je ne crois pas le peu sur les limites DLL. Dans des circonstances très spéciales, il pourrait potentiellement casser ((une DLL est reliée statiquement à une version différente du runtime que celle utilisée par d'autres DLL) et des choses pires se produiraient probablement en premier dans ces situations) mais dans le cas général où tout le monde utilise la valeur par défaut version partagée du runtime standard (par défaut) cela ne se produira pas.
Martin York
2
Exemple: vous distribuez les binaires compilés par VC2008SP1 d'une bibliothèque publique appelée libfoo, qui a un std :: string & dans son API publique. Maintenant, quelqu'un télécharge votre libfoo.dll et effectue une compilation de débogage. Son std :: string pourrait très bien contenir des champs de débogage supplémentaires, provoquant le déplacement du décalage du pointeur pour les chaînes dynamiques.
Cygon
2
Exemple 2: en 2010, quelqu'un télécharge votre libfoo.dll et l'utilise dans son application VC2010. Son code charge MSVCP100.dll et votre libfoo.dll charge toujours MSVCP90.dll -> vous obtenez deux tas -> la mémoire ne peut pas être libérée, des erreurs d'assertion en mode débogage si libfoo modifie la référence de chaîne et remet une chaîne std :: avec un nouveau pointeur en arrière.
Cygon
1
Je vais simplement m'en tenir à "En bref: utilisez des chaînes C ++ dans toutes vos fonctions et méthodes internes." Essayer de comprendre vos exemples maid my brain pop.
Stephen
12

Arkaitz a raison de dire qu'il strings'agit d'un type géré. Cela signifie pour vous que vous n'avez jamais à vous soucier de la longueur de la chaîne, ni à vous soucier de la libération ou de la réallocation de la mémoire de la chaîne.

D'autre part, la char[]notation dans le cas ci-dessus a limité le tampon de caractères à exactement 256 caractères. Si vous essayez d'écrire plus de 256 caractères dans ce tampon, au mieux vous écraserez une autre mémoire que votre programme "possède". Au pire, vous essaierez d'écraser la mémoire que vous ne possédez pas, et votre système d'exploitation tuera votre programme sur place.

En bout de ligne? Les chaînes sont beaucoup plus conviviales pour les programmeurs, les char [] sont beaucoup plus efficaces pour l'ordinateur.

Mark Rushakoff
la source
4
Au pire, d'autres personnes écraseront la mémoire et exécuteront un code malveillant sur votre ordinateur. Voir aussi débordement de tampon .
David Johnstone
6

Eh bien, le type de chaîne est une classe entièrement gérée pour les chaînes de caractères, tandis que char [] est toujours ce qu'il était en C, un tableau d'octets représentant une chaîne de caractères pour vous.

En termes d'API et de bibliothèque standard, tout est implémenté en termes de chaînes et non de char [], mais il y a encore beaucoup de fonctions de la libc qui reçoivent char [] donc vous devrez peut-être l'utiliser pour celles-ci, à part cela, je voudrais utilisez toujours std :: string.

En termes d'efficacité bien sûr, un tampon brut de mémoire non gérée sera presque toujours plus rapide pour beaucoup de choses, mais prenez en compte la comparaison des chaînes par exemple, std :: string a toujours la taille pour le vérifier en premier, tandis qu'avec char [] vous besoin de comparer caractère par caractère.

Arkaitz Jimenez
la source
5

Personnellement, je ne vois aucune raison pour laquelle on aimerait utiliser char * ou char [] sauf pour la compatibilité avec l'ancien code. std :: string n'est pas plus lent que d'utiliser une chaîne en C, sauf qu'il gérera la réallocation pour vous. Vous pouvez définir sa taille lors de sa création, et ainsi éviter la réallocation si vous le souhaitez. Son opérateur d'indexation ([]) fournit un accès en temps constant (et dans tous les sens du mot, c'est exactement la même chose que l'utilisation d'un indexeur de chaînes en C). L'utilisation de la méthode at vous donne également la sécurité des limites vérifiées, ce que vous n'obtenez pas avec les chaînes C, à moins que vous ne l'écriviez. Votre compilateur optimisera le plus souvent l'utilisation de l'indexeur en mode version. Il est facile de jouer avec les chaînes C; des choses telles que delete vs delete [], la sécurité des exceptions, même comment réallouer une chaîne de caractères.

Et lorsque vous devez gérer des concepts avancés comme avoir des chaînes COW, et non-COW pour MT, etc., vous aurez besoin de std :: string.

Si vous vous inquiétez des copies, tant que vous utilisez des références et des références const partout où vous le pouvez, vous n'aurez pas de surcharge due aux copies, et c'est la même chose que vous feriez avec la chaîne c.

Abhay
la source
+1 Bien que vous n'ayez pas pris en compte les problèmes de mise en œuvre tels que la compatibilité des DLL, vous avez obtenu COW.
que dire de je sais que mon tableau de caractères en 12 octets? Si j'instancie une chaîne pour cela, ce n'est peut-être pas vraiment efficace, non?
David 天宇 Wong
@David: Si vous avez un code extrêmement sensible aux performances, alors oui. Vous pouvez considérer l'appel de std :: string ctor comme une surcharge en plus de l'initialisation des membres std :: string. Mais rappelez-vous que l'optimisation prématurée a fait de nombreuses bases de code inutilement style C, alors soyez prudent.
Abhay
1

Les chaînes ont des fonctions d'assistance et gèrent automatiquement les tableaux de caractères. Vous pouvez concaténer des chaînes, pour un tableau de caractères, vous devez le copier dans un nouveau tableau, les chaînes peuvent changer leur longueur au moment de l'exécution. Un tableau de caractères est plus difficile à gérer qu'une chaîne et certaines fonctions peuvent n'accepter qu'une chaîne en entrée, ce qui vous oblige à convertir le tableau en chaîne. Il est préférable d'utiliser des chaînes, elles ont été conçues pour que vous n'ayez pas à utiliser de tableaux. Si les tableaux étaient objectivement meilleurs, nous n'aurions pas de chaînes.


la source
0

Pensez à (char *) comme string.begin (). La différence essentielle est que (char *) est un itérateur et std :: string est un conteneur. Si vous vous en tenez aux chaînes de base, un (char *) vous donnera ce que fait std :: string :: iterator. Vous pouvez utiliser (char *) lorsque vous souhaitez bénéficier d'un itérateur et également de la compatibilité avec C, mais c'est l'exception et non la règle. Comme toujours, faites attention à l'invalidation de l'itérateur. Quand les gens disent que (char *) n'est pas sûr, c'est ce qu'ils veulent dire. Il est aussi sûr que tout autre itérateur C ++.

Samuel Danielson
la source
0

L'une des différences est la terminaison Null (\ 0).

En C et C ++, char * ou char [] prendra un pointeur vers un seul caractère comme paramètre et suivra le long de la mémoire jusqu'à ce qu'une valeur de mémoire 0 soit atteinte (souvent appelée le terminateur nul).

Les chaînes C ++ peuvent contenir des caractères \ 0 incorporés, connaître leur longueur sans compter.

#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;
}

Production:

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


strlen = 11
sizeof = 12
Fee
Eswaran Pandi
la source
"de spécifique, tous les caractères sont supprimés" non, ils ne sont pas "supprimés", l'impression d'un pointeur char n'imprime que jusqu'au terminateur nul. (puisque c'est la seule façon pour un char * de connaître la fin), la classe de chaîne connaît la taille complète elle-même donc elle l'utilise simplement. si vous connaissez la taille de votre caractère *, vous pouvez également imprimer / utiliser tous les caractères vous-même.
Puddle