Wie kann ich eine std::string
zu einer char*
oder eine const char*
?
Antworten
Zu viele Anzeigen?Wenn Sie nur eine std::string
zu einer Funktion, die const char*
können Sie
std::string str;
const char * c = str.c_str();
Wenn Sie eine beschreibbare Kopie erhalten möchten, wie char *
können Sie das hiermit tun:
std::string str;
char * writable = new char[str.size() + 1];
std::copy(str.begin(), str.end(), writable);
writable[str.size()] = '\0'; // don't forget the terminating 0
// don't forget to free the string after finished using it
delete[] writable;
Editar : Beachten Sie, dass die obige Vorgehensweise nicht ausnahmesicher ist. Wenn etwas zwischen der new
Aufruf und die delete
aufruft, werden Sie Speicherplatz verlieren, da nichts die delete
automatisch für Sie. Es gibt zwei unmittelbare Möglichkeiten, dieses Problem zu lösen.
boost::scoped_array
boost::scoped_array
löscht den Speicher für Sie, wenn Sie den Bereich verlassen:
std::string str;
boost::scoped_array<char> writable(new char[str.size() + 1]);
std::copy(str.begin(), str.end(), writable.get());
writable[str.size()] = '\0'; // don't forget the terminating 0
// get the char* using writable.get()
// memory is automatically freed if the smart pointer goes
// out of scope
std::vector
Dies ist die Standardmethode (erfordert keine externe Bibliothek). Sie verwenden std::vector
, die den Speicher vollständig für Sie verwaltet.
std::string str;
std::vector<char> writable(str.begin(), str.end());
writable.push_back('\0');
// get the char* using &writable[0] or &*writable.begin()
Gesetzt den Fall...
std::string x = "hello";
Abrufen eines `char *` oder `const char*` aus einem `string`
Wie erhält man einen Zeichenzeiger, der gültig ist, während x
bleibt im Geltungsbereich und wird nicht weiter geändert
C++11 vereinfacht die Dinge; die folgenden geben alle Zugriff auf denselben internen String-Puffer:
const char* p_c_str = x.c_str();
const char* p_data = x.data();
char* p_writable_data = x.data(); // for non-const x from C++17
const char* p_x0 = &x[0];
char* p_x0_rw = &x[0]; // compiles iff x is not const...
Alle oben genannten Zeiger enthalten die gleicher Wert - die Adresse des ersten Zeichens im Puffer. Auch eine leere Zeichenkette hat ein "erstes Zeichen im Puffer", da C++11 garantiert, dass nach dem explizit zugewiesenen Zeichenketteninhalt immer ein zusätzliches NUL/0-Terminatorzeichen folgt (z.B. std::string("this\0that", 9)
hat einen Puffer, der "this\0that\0"
).
Wenn einer der oben genannten Hinweise zutrifft:
char c = p[n]; // valid for n <= x.size()
// i.e. you can safely read the NUL at p[x.size()]
Nur für die nicht const
Zeiger p_writable_data
und von &x[0]
:
p_writable_data[n] = c;
p_x0_rw[n] = c; // valid for n <= x.size() - 1
// i.e. don't overwrite the implementation maintained NUL
Das Schreiben eines NUL an einer anderen Stelle der Zeichenkette bewirkt no ändern Sie die string
's size()
; string
dürfen eine beliebige Anzahl von NULs enthalten - sie werden nicht besonders behandelt von std::string
(wie in C++03).
Sur C++03 waren die Dinge wesentlich komplizierter (wesentliche Unterschiede hervorgehoben ):
-
x.data()
- gibt zurück.
const char*
in den internen Puffer des Strings die nach dem Standard nicht mit einem NUL abzuschließen war (d.h. könnte sein['h', 'e', 'l', 'l', 'o']
gefolgt von uninitialisierten Werten oder Garbage-Werten, auf die versehentlich zugegriffen werden kann undefiniertes Verhalten ).x.size()
Zeichen sicher zu lesen sind, d.h.x[0]
überx[x.size() - 1]
- für leere Zeichenketten, sind Sie garantiert einige nicht-NULL-Zeiger, auf die 0 sicher hinzugefügt werden kann (hurra!), aber Sie sollten nicht dereferenzieren, dass Zeiger.
- gibt zurück.
-
&x[0]
- für leere Zeichenfolgen hat dies ein undefiniertes Verhalten (21.3.4)
- z.B. gegeben
f(const char* p, size_t n) { if (n == 0) return; ...whatever... }
Sie dürfen nicht anrufenf(&x[0], x.size());
wennx.empty()
- verwenden Sie einfachf(x.data(), ...)
.
- z.B. gegeben
- andernfalls, je nach
x.data()
sondern:- für Nicht
const
x
ergibt sich ein Nichtconst
char*
Zeiger; Sie können den Inhalt der Zeichenkette überschreiben
- für Nicht
- für leere Zeichenfolgen hat dies ein undefiniertes Verhalten (21.3.4)
-
x.c_str()
- gibt zurück.
const char*
in eine ASCIIZ-Darstellung (NUL-terminiert) des Wertes (d. h. ['h', 'e', 'l', 'l', 'o', ' \0 ']). - obwohl sich nur wenige Implementierungen dafür entschieden haben, wurde der C++03-Standard so formuliert, dass die String-Implementierung die Freiheit hat, eine eindeutiger Puffer mit NUL-Ende spontan aus dem potenziell nicht NUL-terminierten Puffer, der durch
x.data()
y&x[0]
x.size()
+ 1 Zeichen sind sicher zu lesen.- garantiert sicher auch für leere Zeichenketten ([' \0 ']).
- gibt zurück.
Folgen des Zugriffs auf fremde Rechtsverzeichnisse
Unabhängig davon, wie Sie einen Zeiger erhalten, dürfen Sie nicht auf Speicher zugreifen, der weiter vom Zeiger entfernt ist als die Zeichen, die in den obigen Beschreibungen garantiert sind. Versuche, dies zu tun, haben undefiniertes Verhalten Dies birgt die Gefahr, dass die Anwendung abstürzt und selbst bei Lesezugriffen Daten im Müll landen, während bei Schreibzugriffen zusätzlich große Datenmengen, beschädigte Stacks und/oder Sicherheitslücken auftreten können.
Wann werden diese Zeiger ungültig?
Wenn Sie einige string
Mitgliedsfunktion, die die string
oder weitere Kapazitäten reserviert, werden alle Zeigerwerte, die zuvor von einer der oben genannten Methoden zurückgegeben wurden ungültig gemacht . Sie können diese Methoden erneut verwenden, um einen anderen Zeiger zu erhalten. (Die Regeln sind dieselben wie für Iteratoren in string
s).
Siehe auch Wie erhält man einen Zeichenzeiger auch nach x
den Geltungsbereich verlässt oder weiter verändert wird unten....
Also, was ist mejor zu verwenden?
Ab C++11, verwenden Sie .c_str()
für ASCIIZ-Daten, und .data()
für "binäre" Daten (weiter unten erklärt).
In C++03, verwenden Sie .c_str()
wenn nicht sicher ist, dass .data()
angemessen ist, und bevorzugen .data()
über &x[0]
da es für leere Strings sicher ist....
...versuchen Sie, das Programm so weit zu verstehen, dass Sie es benutzen können data()
wenn es angebracht ist, oder Sie werden wahrscheinlich andere Fehler machen...
Das ASCII-NUL ' \0 ' Zeichen garantiert durch .c_str()
wird von vielen Funktionen als Sentinel-Wert verwendet, der das Ende relevanter und zugriffssicherer Daten anzeigt. Dies gilt sowohl für reine C++-Funktionen wie say fstream::fstream(const char* filename, ...)
und shared-with-C-Funktionen wie strchr()
et printf()
.
Angesichts der C++03's .c_str()
Die Garantien für den zurückgegebenen Puffer sind eine Obermenge von .data()
können Sie immer sicher verwenden .c_str()
aber die Leute tun das manchmal nicht, weil:
- mit
.data()
teilt anderen Programmierern, die den Quellcode lesen, mit, dass es sich bei den Daten nicht um ASCIIZ handelt (vielmehr verwenden Sie die Zeichenkette, um einen Datenblock zu speichern (der manchmal nicht einmal wirklich aus Text besteht)), oder dass Sie die Zeichenkette an eine andere Funktion weitergeben, die sie als einen Block "binärer" Daten behandelt. Dies kann eine entscheidende Erkenntnis sein, um sicherzustellen, dass die Codeänderungen anderer Programmierer die Daten weiterhin richtig behandeln. - Nur C++03: Es besteht eine geringe Chance, dass Ihr
string
Die Implementierung wird einige zusätzliche Speicherzuweisungen und/oder Datenkopien vornehmen müssen, um den mit NUL beendeten Puffer vorzubereiten.
Ein weiterer Hinweis: Wenn die Parameter einer Funktion das ( const
) char*
aber bestehen Sie nicht darauf, dass Sie x.size()
die Funktion wahrscheinlich benötigt eine ASCIIZ-Eingabe, also .c_str()
ist eine gute Wahl (die Funktion muss irgendwie wissen, wo der Text endet, wenn es also kein separater Parameter ist, kann es nur eine Konvention wie ein Längen-Präfix oder Sentinel oder eine feste erwartete Länge sein).
Wie erhält man einen Zeichenzeiger auch nach x
den Geltungsbereich verlässt oder weiter verändert wird
Sie müssen kopieren. den Inhalt des string
x
in einen neuen Speicherbereich außerhalb x
. Dieser externe Puffer kann sich an vielen Orten befinden, z. B. an einem anderen string
oder eine Zeichenfeldvariable, kann sie eine andere Lebensdauer haben als x
weil sie sich in einem anderen Bereich befinden (z. B. Namespace, global, statisch, Heap, Shared Memory, Memory-Mapped-Datei).
So kopieren Sie den Text aus std::string x
in ein unabhängiges Zeichenfeld:
// USING ANOTHER STRING - AUTO MEMORY MANAGEMENT, EXCEPTION SAFE
std::string old_x = x;
// - old_x will not be affected by subsequent modifications to x...
// - you can use `&old_x[0]` to get a writable char* to old_x's textual content
// - you can use resize() to reduce/expand the string
// - resizing isn't possible from within a function passed only the char* address
std::string old_x = x.c_str(); // old_x will terminate early if x embeds NUL
// Copies ASCIIZ data but could be less efficient as it needs to scan memory to
// find the NUL terminator indicating string length before allocating that amount
// of memory to copy into, or more efficient if it ends up allocating/copying a
// lot less content.
// Example, x == "ab\0cd" -> old_x == "ab".
// USING A VECTOR OF CHAR - AUTO, EXCEPTION SAFE, HINTS AT BINARY CONTENT, GUARANTEED CONTIGUOUS EVEN IN C++03
std::vector<char> old_x(x.data(), x.data() + x.size()); // without the NUL
std::vector<char> old_x(x.c_str(), x.c_str() + x.size() + 1); // with the NUL
// USING STACK WHERE MAXIMUM SIZE OF x IS KNOWN TO BE COMPILE-TIME CONSTANT "N"
// (a bit dangerous, as "known" things are sometimes wrong and often become wrong)
char y[N + 1];
strcpy(y, x.c_str());
// USING STACK WHERE UNEXPECTEDLY LONG x IS TRUNCATED (e.g. Hello\0->Hel\0)
char y[N + 1];
strncpy(y, x.c_str(), N); // copy at most N, zero-padding if shorter
y[N] = '\0'; // ensure NUL terminated
// USING THE STACK TO HANDLE x OF UNKNOWN (BUT SANE) LENGTH
char* y = alloca(x.size() + 1);
strcpy(y, x.c_str());
// USING THE STACK TO HANDLE x OF UNKNOWN LENGTH (NON-STANDARD GCC EXTENSION)
char y[x.size() + 1];
strcpy(y, x.c_str());
// USING new/delete HEAP MEMORY, MANUAL DEALLOC, NO INHERENT EXCEPTION SAFETY
char* y = new char[x.size() + 1];
strcpy(y, x.c_str());
// or as a one-liner: char* y = strcpy(new char[x.size() + 1], x.c_str());
// use y...
delete[] y; // make sure no break, return, throw or branching bypasses this
// USING new/delete HEAP MEMORY, SMART POINTER DEALLOCATION, EXCEPTION SAFE
// see boost shared_array usage in Johannes Schaub's answer
// USING malloc/free HEAP MEMORY, MANUAL DEALLOC, NO INHERENT EXCEPTION SAFETY
char* y = strdup(x.c_str());
// use y...
free(y);
Weitere Gründe für die Anschaffung eines char*
o const char*
generiert aus einer string
Oben haben Sie also gesehen, wie man eine ( const
) char*
und wie man eine Kopie des Textes unabhängig vom Original erstellt string
aber was können Sie faire mit ihm? Ein paar zufällige Beispiele...
- dem "C"-Code Zugriff auf die C++
string
Text, wie inprintf("x is '%s'", x.c_str());
- kopieren.
x
in einen vom Aufrufer der Funktion angegebenen Puffer (z. B.strncpy(callers_buffer, callers_buffer_size, x.c_str())
), oder flüchtiger Speicher, der für Geräte-E/A verwendet wird (z. B.for (const char* p = x.c_str(); *p; ++p) *p_device = *p;
) - anhängen.
x
in ein Zeichenfeld, das bereits einen ASCIIZ-Text enthält (z. B.strcat(other_buffer, x.c_str())
) - achten Sie darauf, dass der Puffer nicht überläuft (in vielen Situationen müssen Sie diestrncat
) - ein zurückgeben
const char*
ochar*
aus einer Funktion heraus (vielleicht aus historischen Gründen - der Client verwendet Ihre bestehende API - oder aus Gründen der C-Kompatibilität wollen Sie nicht einestd::string
aber Sie möchten Ihrestring
Daten irgendwo für den Anrufer)- darauf achten, dass kein Zeiger zurückgegeben wird, der vom Aufrufer nach einem lokalen Ereignis dereferenziert werden kann
string
die Variable, auf die dieser Zeiger zeigte, den Anwendungsbereich verlassen hat - einige Projekte mit gemeinsam genutzten Objekten, die für verschiedene Länder kompiliert/verlinkt wurden
std::string
Implementierungen (z.B. STLport und Compiler-native) können Daten als ASCIIZ übergeben, um Konflikte zu vermeiden
- darauf achten, dass kein Zeiger zurückgegeben wird, der vom Aufrufer nach einem lokalen Ereignis dereferenziert werden kann
Verwenden Sie die .c_str()
Methode für const char *
.
Sie können verwenden &mystring[0]
um eine char *
Zeiger, aber es gibt ein paar Probleme: Sie erhalten nicht notwendigerweise eine Zeichenkette mit Null-Ende, und Sie können die Größe der Zeichenkette nicht ändern. Sie müssen besonders darauf achten, dass Sie keine Zeichen nach dem Ende der Zeichenkette hinzufügen, sonst kommt es zu einem Pufferüberlauf (und wahrscheinlich zum Absturz).
Bis C++11 gab es keine Garantie, dass alle Zeichen Teil desselben zusammenhängenden Puffers sind, aber in der Praxis sind alle bekannten Implementierungen von std::string
funktionierte ohnehin auf diese Weise; siehe Verweist "&s[0]" auf zusammenhängende Zeichen in einer std::string? .
Beachten Sie, dass viele string
Mitgliedsfunktionen werden den internen Puffer neu zuweisen und alle Zeiger, die Sie möglicherweise gespeichert haben, ungültig machen. Am besten ist es, sie sofort zu verwenden und dann zu verwerfen.
C++17
C++17 (kommender Standard) ändert die Synopse der Vorlage basic_string
Hinzufügen einer nicht-konstanten Überladung von data()
:
charT* data() noexcept;
Rückgabe: Ein Zeiger p, so dass p + i == &operator für jedes i in [0,size()].
CharT const *
de std::basic_string<CharT>
std::string const cstr = { "..." };
char const * p = cstr.data(); // or .c_str()
CharT *
de std::basic_string<CharT>
std::string str = { "..." };
char * p = str.data();
C++11
CharT const *
de std::basic_string<CharT>
std::string str = { "..." };
str.c_str();
CharT *
de std::basic_string<CharT>
Ab C++11 sagt der Standard:
- Die char-ähnlichen Objekte in einer
basic_string
Objekt ist zusammenhängend zu speichern. Das heißt, für jedesbasic_string
Objekts
die Identität&*(s.begin() + n) == &*s.begin() + n
gilt für alle Werte vonn
tal que0 <= n < s.size()
.
const_reference operator[](size_type pos) const;
reference operator[](size_type pos);
Rückgabe:
*(begin() + pos)
sipos < size()
, andernfalls ein Verweis auf ein Objekt des TypsCharT
mit WertCharT()
; der referenzierte Wert darf nicht geändert werden.
const charT* c_str() const noexcept;
const charT* data() const noexcept;
Rückgabe: Ein Zeiger p, der so beschaffen ist, dass
p + i == &operator[](i)
für jedei
en[0,size()]
.
Es gibt mehrere Möglichkeiten, einen nicht-konstanten Zeichenzeiger zu erhalten.
1. Verwenden Sie den zusammenhängenden Speicher von C++11
std::string foo{"text"};
auto p = &*foo.begin();
Pro
- Einfach und kurz
- Schnell (einzige Methode, die keine Kopie erfordert)
Nachteile
- Finale
'\0'
darf nicht verändert werden / muss nicht unbedingt Teil des nicht-konstanten Speichers sein.
2. Verwenden Sie std::vector<CharT>
std::string foo{"text"};
std::vector<char> fcv(foo.data(), foo.data()+foo.size()+1u);
auto p = fcv.data();
Pro
- Einfach
- Automatische Speicherverwaltung
- Dynamisch
Nachteile
- Erfordert eine String-Kopie
3. Verwenden Sie std::array<CharT, N>
si N
ist eine Kompilierzeitkonstante (und klein genug)
std::string foo{"text"};
std::array<char, 5u> fca;
std::copy(foo.data(), foo.data()+foo.size()+1u, fca.begin());
Pro
- Einfach
- Umgang mit Stapelspeicher
Nachteile
- Statisch
- Erfordert eine String-Kopie
4. Rohspeicherzuweisung mit automatischer Speicherlöschung
std::string foo{ "text" };
auto p = std::make_unique<char[]>(foo.size()+1u);
std::copy(foo.data(), foo.data() + foo.size() + 1u, &p[0]);
Pro
- Kleiner Speicherplatzbedarf
- Automatische Löschung
- Einfach
Nachteile
- Erfordert eine String-Kopie
- Statisch (dynamische Verwendung erfordert viel mehr Code)
- Weniger Merkmale als Vektor oder Array
5. Rohspeicherzuweisung mit manueller Handhabung
std::string foo{ "text" };
char * p = nullptr;
try
{
p = new char[foo.size() + 1u];
std::copy(foo.data(), foo.data() + foo.size() + 1u, p);
// handle stuff with p
delete[] p;
}
catch (...)
{
if (p) { delete[] p; }
throw;
}
Pro
- Maximale 'Kontrolle'
Betrug
- Erfordert eine String-Kopie
- Maximale Haftung / Anfälligkeit für Fehler
- Komplexe
Ich arbeite mit einer API mit einer Menge von Funktionen, die eine char*
als Eingabe.
Ich habe eine kleine Klasse erstellt, um diese Art von Problem zu lösen, und ich habe die RAII Idiom.
class DeepString
{
DeepString(const DeepString& other);
DeepString& operator=(const DeepString& other);
char* internal_;
public:
explicit DeepString( const string& toCopy):
internal_(new char[toCopy.size()+1])
{
strcpy(internal_,toCopy.c_str());
}
~DeepString() { delete[] internal_; }
char* str() const { return internal_; }
const char* c_str() const { return internal_; }
};
Und Sie können es als verwenden:
void aFunctionAPI(char* input);
// other stuff
aFunctionAPI("Foo"); //this call is not safe. if the function modified the
//literal string the program will crash
std::string myFoo("Foo");
aFunctionAPI(myFoo.c_str()); //this is not compiling
aFunctionAPI(const_cast<char*>(myFoo.c_str())); //this is not safe std::string
//implement reference counting and
//it may change the value of other
//strings as well.
DeepString myDeepFoo(myFoo);
aFunctionAPI(myFoo.str()); //this is fine
Ich habe die Klasse DeepString
weil es eine tiefe und einzigartige Kopie erstellt (die DeepString
ist nicht kopierbar) einer bestehenden Zeichenkette.
- See previous answers
- Weitere Antworten anzeigen