Unterschied zwischen String- und Char [] -Typen in C ++


126

Ich kenne ein wenig C und jetzt schaue ich mir C ++ an. Ich bin es gewohnt, Arrays für den Umgang mit C-Strings zu chargen, aber während ich mir C ++ - Code ansehe, sehe ich Beispiele, die sowohl String-Typ- als auch char-Arrays verwenden:

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

und

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

(beide Beispiele von http://www.cplusplus.com )

Ich nehme an, dies ist eine häufig gestellte und beantwortete (offensichtliche?) Frage, aber es wäre schön, wenn mir jemand sagen könnte, was genau der Unterschied zwischen diesen beiden Möglichkeiten für den Umgang mit Zeichenfolgen in C ++ ist (Leistung, API-Integration, wie jede einzelne ist besser, ...).

Danke dir.


Antworten:


187

Ein char-Array ist genau das - ein Array von Zeichen:

  • Wenn es auf dem Stapel zugewiesen ist (wie in Ihrem Beispiel), wird es immer z. 256 Bytes, egal wie lang der darin enthaltene Text ist
  • Wenn Sie auf dem Heap zugewiesen werden (mit malloc () oder new char []), sind Sie dafür verantwortlich, den Speicher anschließend freizugeben, und Sie haben immer den Overhead einer Heap-Zuweisung.
  • Wenn Sie einen Text mit mehr als 256 Zeichen in das Array kopieren, kann dieser abstürzen, hässliche Bestätigungsmeldungen erzeugen oder an einer anderen Stelle in Ihrem Programm unerklärliches (Fehl-) Verhalten verursachen.
  • Um die Länge des Textes zu bestimmen, muss das Array zeichenweise nach einem \ 0-Zeichen durchsucht werden.

Eine Zeichenfolge ist eine Klasse, die ein char-Array enthält, diese jedoch automatisch für Sie verwaltet. Die meisten Zeichenfolgenimplementierungen verfügen über ein integriertes Array mit 16 Zeichen (kurze Zeichenfolgen fragmentieren den Heap nicht) und verwenden den Heap für längere Zeichenfolgen.

Sie können wie folgt auf das char-Array eines Strings zugreifen:

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

C ++ - Zeichenfolgen können eingebettete \ 0-Zeichen enthalten, kennen ihre Länge ohne zu zählen, sind schneller als Heap-zugewiesene Zeichenarrays für kurze Texte und schützen Sie vor Pufferüberläufen. Außerdem sind sie besser lesbar und einfacher zu bedienen.


C ++ - Zeichenfolgen sind jedoch nicht (sehr) für die Verwendung über DLL-Grenzen hinweg geeignet, da dies erfordern würde, dass jeder Benutzer einer solchen DLL-Funktion sicherstellt, dass er genau denselben Compiler und dieselbe C ++ - Laufzeitimplementierung verwendet, damit er nicht riskiert, dass sich seine Zeichenfolgenklasse anders verhält.

Normalerweise gibt eine Zeichenfolgenklasse auch ihren Heapspeicher auf dem aufrufenden Heap frei, sodass sie nur dann wieder Speicher freigeben kann, wenn Sie eine gemeinsam genutzte (.dll oder .so) Version der Laufzeit verwenden.

Kurz gesagt: Verwenden Sie C ++ - Zeichenfolgen in all Ihren internen Funktionen und Methoden. Wenn Sie jemals eine DLL oder SO schreiben, verwenden Sie C-Zeichenfolgen in Ihren öffentlichen (DLL / so exponierten) Funktionen.


4
Darüber hinaus haben Strings eine Reihe von Hilfsfunktionen, die wirklich ordentlich sein können.
Håkon

1
Ich glaube nicht an DLL-Grenzen. Unter ganz besonderen Umständen kann es möglicherweise zu einer Unterbrechung kommen (eine DLL wird statisch mit einer anderen Version der Laufzeit verknüpft als von anderen DLLs verwendet), und in diesen Situationen würden wahrscheinlich zuerst schlimmere Dinge passieren), aber im allgemeinen Fall, in dem jeder die Standardeinstellung verwendet freigegebene Version der Standardlaufzeit (Standardeinstellung) Dies wird nicht passieren.
Martin York

2
Beispiel: Sie verteilen VC2008SP1-kompilierte Binärdateien einer öffentlichen Bibliothek namens libfoo, deren öffentliche API einen std :: string & enthält. Jetzt lädt jemand Ihre libfoo.dll herunter und führt einen Debug-Build durch. Sein std :: string könnte sehr wohl einige zusätzliche Debug-Felder enthalten, wodurch sich der Versatz des Zeigers für dynamische Strings bewegt.
Cygon

2
Beispiel 2: Im Jahr 2010 lädt jemand Ihre libfoo.dll herunter und verwendet sie in seiner von VC2010 erstellten Anwendung. Sein Code lädt MSVCP100.dll und Ihre libfoo.dll lädt immer noch MSVCP90.dll -> Sie erhalten zwei Heaps -> Speicher kann nicht freigegeben werden, Assertionsfehler im Debug-Modus, wenn libfoo die Zeichenfolgenreferenz ändert und eine std :: Zeichenfolge mit einer neuen übergibt Zeiger zurück.
Cygon

1
Ich bleibe nur bei "Kurz gesagt: Verwenden Sie C ++ - Zeichenfolgen in all Ihren internen Funktionen und Methoden." Der Versuch, Ihre Beispiele zu verstehen, hat mein Gehirn zum Platzen gebracht.
Stephen

12

Arkaitz ist richtig, das stringist ein verwalteter Typ. Für Sie bedeutet dies, dass Sie sich niemals Gedanken darüber machen müssen, wie lang die Zeichenfolge ist, und dass Sie sich auch nicht darum kümmern müssen, den Speicher der Zeichenfolge freizugeben oder neu zuzuweisen.

Andererseits hat die char[]Notation im obigen Fall den Zeichenpuffer auf genau 256 Zeichen beschränkt. Wenn Sie versucht haben, mehr als 256 Zeichen in diesen Puffer zu schreiben, überschreiben Sie bestenfalls anderen Speicher, den Ihr Programm "besitzt". Im schlimmsten Fall werden Sie versuchen, Speicher zu überschreiben, den Sie nicht besitzen, und Ihr Betriebssystem wird Ihr Programm sofort beenden.

Endeffekt? Strings sind viel programmiererfreundlicher, Zeichen sind für den Computer viel effizienter.


4
Im schlimmsten Fall überschreiben andere Personen den Speicher und führen schädlichen Code auf Ihrem Computer aus. Siehe auch Pufferüberlauf .
David Johnstone

6

Nun, der Zeichenfolgentyp ist eine vollständig verwaltete Klasse für Zeichenfolgen, während char [] immer noch das ist, was es in C war, einem Byte-Array, das eine Zeichenfolge für Sie darstellt.

In Bezug auf API und Standardbibliothek ist alles in Form von Zeichenfolgen und nicht in Form von char [] implementiert, aber es gibt immer noch viele Funktionen von libc, die char [] empfangen, sodass Sie es möglicherweise für diese verwenden müssen, abgesehen davon, dass ich es tun würde Verwenden Sie immer std :: string.

In Bezug auf die Effizienz ist ein Rohpuffer aus nicht verwaltetem Speicher für viele Dinge natürlich fast immer schneller, aber wenn Sie beispielsweise Zeichenfolgen vergleichen, hat std :: string immer die Größe, um ihn zuerst zu überprüfen, während Sie mit char [] you arbeiten müssen Zeichen für Zeichen vergleichen.


5

Ich persönlich sehe keinen Grund, warum man char * oder char [] verwenden möchte, außer für die Kompatibilität mit altem Code. std :: string ist nicht langsamer als die Verwendung eines c-Strings, außer dass er die Neuzuweisung für Sie übernimmt. Sie können die Größe beim Erstellen festlegen und so eine Neuzuweisung vermeiden, wenn Sie möchten. Der Indexierungsoperator ([]) bietet einen konstanten Zeitzugriff (und ist im wahrsten Sinne des Wortes genau dasselbe wie die Verwendung eines C-String-Indexers). Wenn Sie die at-Methode verwenden, erhalten Sie auch eine überprüfte Sicherheit, die Sie mit C-Strings nur erhalten, wenn Sie sie schreiben. Ihr Compiler optimiert meistens die Verwendung des Indexers im Release-Modus. Es ist leicht, mit C-Strings herumzuspielen; Dinge wie Löschen gegen Löschen [], Ausnahmesicherheit, sogar wie man einen C-String neu zuweist.

Und wenn Sie sich mit fortgeschrittenen Konzepten wie COW-Strings und Nicht-COW für MT usw. befassen müssen, benötigen Sie std :: string.

Wenn Sie sich Sorgen um Kopien machen, solange Sie Referenzen und konstante Referenzen verwenden, wo immer Sie können, haben Sie aufgrund von Kopien keinen Overhead, und es ist dasselbe, wie Sie es mit der C-Zeichenfolge tun würden.


+1 Obwohl Sie Implementierungsprobleme wie DLL-Kompatibilität nicht berücksichtigt haben, haben Sie COW.

Was ist mit ich weiß, dass mein char Array in 12 Bytes? Wenn ich einen String dafür instanziiere, ist er möglicherweise nicht wirklich effizient, oder?
David 天宇 Wong

@ David: Wenn Sie extrem leistungsempfindlichen Code haben, dann ja. Sie können den Aufruf von std :: string ctor zusätzlich zur Initialisierung von std :: string-Mitgliedern als Overhead betrachten. Denken Sie jedoch daran, dass durch vorzeitige Optimierung viele Codebasen unnötig im C-Stil erstellt wurden. Seien Sie also vorsichtig.
Abhay

1

Strings haben Hilfsfunktionen und verwalten Char-Arrays automatisch. Sie können Zeichenfolgen verketten. Für ein char-Array, das Sie in ein neues Array kopieren müssten, können Zeichenfolgen zur Laufzeit ihre Länge ändern. Ein char-Array ist schwieriger zu verwalten als eine Zeichenfolge, und bestimmte Funktionen akzeptieren möglicherweise nur eine Zeichenfolge als Eingabe, sodass Sie das Array in eine Zeichenfolge konvertieren müssen. Es ist besser, Zeichenfolgen zu verwenden. Sie wurden so erstellt, dass Sie keine Arrays verwenden müssen. Wenn Arrays objektiv besser wären, hätten wir keine Strings.


0

Stellen Sie sich (char *) als string.begin () vor. Der wesentliche Unterschied besteht darin, dass (char *) ein Iterator und std :: string ein Container ist. Wenn Sie sich an grundlegende Zeichenfolgen halten, gibt Ihnen ein (char *) an, was std :: string :: iterator tut. Sie können (char *) verwenden, wenn Sie den Vorteil eines Iterators und auch die Kompatibilität mit C wünschen, aber das ist die Ausnahme und nicht die Regel. Achten Sie wie immer auf die Ungültigmachung des Iterators. Wenn Leute sagen (char *) ist nicht sicher, meinen sie das auch. Es ist so sicher wie jeder andere C ++ - Iterator.


0

Einer der Unterschiede ist die Nullterminierung (\ 0).

In C und C ++ nimmt char * oder char [] einen Zeiger auf ein einzelnes Zeichen als Parameter und verfolgt den Speicher, bis ein Speicherwert von 0 erreicht ist (häufig als Nullterminator bezeichnet).

C ++ - Zeichenfolgen können eingebettete \ 0-Zeichen enthalten und deren Länge kennen, ohne zu zählen.

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

Ausgabe:

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


strlen = 11
sizeof = 12
Fee

"Von bestimmten, alle Zeichen werden entfernt" Nein, sie werden nicht "entfernt". Beim Drucken eines Zeichenzeigers wird nur bis zum Nullterminator gedruckt. (da dies die einzige Möglichkeit ist, wie ein char * das Ende kennt) Die String-Klasse kennt die volle Größe selbst und verwendet diese nur. Wenn Sie die Größe Ihres Zeichens * kennen, können Sie alle Zeichen auch selbst drucken / verwenden.
Pfütze
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.