Дано сказать ...
std::string x = "hello";
Получение `char *` или `const char *` из `строки`
Как получить указатель на символ, который действителен, пока x
остается в области видимости и больше не изменяется
C ++ 11 упрощает вещи; следующие все дают доступ к одному и тому же внутреннему строковому буферу:
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...
Все вышеперечисленные указатели будут содержать одно и то же значение - адрес первого символа в буфере. Даже пустая строка имеет «первый символ в буфере», потому что C ++ 11 гарантирует всегда сохранять дополнительный символ-терминатор NUL / 0 после явно назначенного содержимого строки (например, std::string("this\0that", 9)
будет содержать буфер "this\0that\0"
).
Учитывая любой из вышеперечисленных указателей:
char c = p[n]; // valid for n <= x.size()
// i.e. you can safely read the NUL at p[x.size()]
Только для не const
указателя p_writable_data
и от &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
Дать NUL в другом месте в строке ничего не изменить string
«s size()
; string
Разрешается содержать любое количество NUL - им не предоставляется особая обработка std::string
(то же самое в C ++ 03).
В C ++ 03 все было значительно сложнее ( выделены ключевые различия ):
x.data()
- возвращает
const char*
во внутренний буфер строки, который не требовался Стандартом для завершения с NUL (т. е. может ['h', 'e', 'l', 'l', 'o']
сопровождаться неинициализированными или мусорными значениями со случайным доступом к нему с неопределенным поведением ).
x.size()
символы безопасны для чтения, то есть x[0]
черезx[x.size() - 1]
- для пустых строк вам гарантирован некоторый ненулевой указатель, к которому можно безопасно добавить 0 (ура!), но вы не должны разыменовывать этот указатель.
&x[0]
- для пустых строк это имеет неопределенное поведение (21.3.4)
- например, учитывая, что
f(const char* p, size_t n) { if (n == 0) return; ...whatever... }
вы не должны звонить, f(&x[0], x.size());
когда x.empty()
- просто используйте f(x.data(), ...)
.
- в противном случае,
x.data()
но согласно :
- для не-
const
x
это дает не const
char*
указатель; Вы можете перезаписать содержимое строки
x.c_str()
- возвращает
const char*
ASCIIZ (NUL-оканчивающееся) представление значения (то есть ['h', 'e', 'l', 'l', 'o', '\ 0']).
- хотя немногие, если какие-либо реализации решили сделать это, стандарт C ++ 03 был сформулирован так, чтобы позволить строковой реализации свободно создавать отдельный NUL-концевой буфер на лету из потенциально не-NUL-концевого буфера, «выставляемого»
x.data()
и&x[0]
x.size()
+1 символы безопасны для чтения.
- гарантировано безопасно даже для пустых строк (['\ 0']).
Последствия доступа к внешним правовым индексам
Какой бы способ вы ни указали, вы не должны получать доступ к памяти дальше от указателя, чем символы, гарантированно присутствующие в описаниях выше. Попытки сделать это имеют неопределенное поведение , с очень реальной вероятностью сбоев приложений и результатов мусора даже для операций чтения, а также оптовых данных, повреждения стека и / или уязвимостей безопасности для записей.
Когда эти указатели становятся недействительными?
Если вы вызываете какую-либо string
функцию-член, которая изменяет string
или резервирует дополнительную емкость, любые значения указателя, предварительно возвращенные любым из вышеуказанных методов, становятся недействительными . Вы можете использовать эти методы снова, чтобы получить другой указатель. (Правила те же, что и для итераторов в string
s).
См. Также Как сделать указатель символа действительным даже после того, как x
покидает область видимости или изменяется далее ниже ....
Итак, что лучше использовать?
Начиная с C ++ 11, используйте .c_str()
для данных ASCIIZ и .data()
для «двоичных» данных (объяснено ниже).
В C ++ 03, использование , .c_str()
если уверены , что .data()
достаточно, и предпочитают .data()
более , &x[0]
как это безопасно для пустых строк ....
... попытайтесь понять программу достаточно, чтобы использовать ее, data()
когда это уместно, иначе вы, вероятно, допустите другие ошибки ...
Символ ASCII NUL '\ 0', гарантируемый символом, .c_str()
используется многими функциями в качестве контрольного значения, обозначающего конец соответствующих и безопасных для доступа данных. Это относится как к C ++ - только функциям, таким как say, так fstream::fstream(const char* filename, ...)
и к функциям shared-with-C, таким как strchr()
, и printf()
.
Учитывая, что .c_str()
гарантии возвращаемого буфера в C ++ 03 являются супер-набором .data()
, вы всегда можете безопасно его использовать .c_str()
, но люди иногда этого не делают, потому что:
- Использование
.data()
сообщает другим программистам, читающим исходный код, что данные не являются ASCIIZ (скорее, вы используете строку для хранения блока данных (который иногда даже не является действительно текстовым)), или что вы передаете его другая функция, которая обрабатывает его как блок «двоичных» данных. Это может быть критически важным для обеспечения того, чтобы изменения кода других программистов продолжали правильно обрабатывать данные.
- Только C ++ 03: есть небольшая вероятность того, что вашей
string
реализации потребуется дополнительное выделение памяти и / или копирование данных для подготовки буфера с NUL-ограничением
В качестве еще одной подсказки, если для параметров функции требуется ( const
), char*
но они не настаивают на получении x.size()
, функция, вероятно, нуждается в вводе ASCIIZ, так .c_str()
что это хороший выбор (функция должна знать, где текст заканчивается каким-то образом, так что если это не так В качестве отдельного параметра это может быть только соглашение, такое как префикс длины или часовой или некоторая фиксированная ожидаемая длина).
Как сделать указатель на символ действительным даже после того, как x
покидает область видимости или изменяется дальше
Вам нужно будет скопировать содержимое в string
x
новую область памяти снаружи x
. Этот внешний буфер может находиться во многих местах, например в другой string
переменной или переменной массива символов, он может иметь или не иметь другое время жизни, отличное от того, x
что находится в другой области видимости (например, пространство имен, глобальное, статическое, куча, общая память, файл отображения памяти) ,
Чтобы скопировать текст из std::string x
в отдельный массив символов:
// 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);
Другие причины, чтобы хотеть char*
или const char*
порожденный изstring
Итак, выше вы видели, как получить ( const
) char*
и как сделать копию текста независимой от оригинала string
, но что вы можете с этим сделать ? Случайная выборка примеров ...
- дать коду "C" доступ к
string
тексту C ++ , как вprintf("x is '%s'", x.c_str());
- скопируйте
x
текст в буфер, указанный вызывающей стороной вашей функции (например strncpy(callers_buffer, callers_buffer_size, x.c_str())
), или в энергозависимую память, используемую для ввода-вывода устройства (например for (const char* p = x.c_str(); *p; ++p) *p_device = *p;
)
- добавить
x
текст в массив символов, уже содержащий некоторый текст ASCIIZ (например strcat(other_buffer, x.c_str())
) - будьте осторожны, чтобы не переполнить буфер (во многих ситуациях вам может понадобиться использовать strncat
)
- вернуть a
const char*
или char*
функцию (возможно, по историческим причинам - клиент использует ваш существующий API - или для совместимости с C вы не хотите возвращать a std::string
, но действительно хотите скопировать свои string
данные куда-нибудь для вызывающей стороны)
- будьте осторожны, чтобы не вернуть указатель, который может быть разыменован вызывающей стороной после того, как локальная
string
переменная, на которую указывает этот указатель, вышла из области видимости
- некоторые проекты с общими объектами, скомпилированными / связанными для разных
std::string
реализаций (например, STLport и compiler-native), могут передавать данные как ASCIIZ, чтобы избежать конфликтов