Дано сказать ...
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
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-концевой буфер на лету , из потенциально ненулевого терминированного буфера, "выставленного"
x.data()
и &x[0]
x.size()
+ 1 символ безопасен для чтения.
- гарантировано безопасно даже для пустых строк (['\ 0']).
Последствия доступа к внешним правовым индексам
Какой бы способ вы ни указали, вы не должны обращаться к памяти дальше от указателя, чем символы, гарантированно присутствующие в описаниях выше. Попытки сделать это имеют неопределенное поведение , с очень реальной вероятностью сбоев приложений и результатов мусора даже для операций чтения, а также дополнительных оптовых данных, повреждения стека и / или уязвимостей безопасности для записей.
Когда эти указатели становятся недействительными?
Если вы вызываете какую-то string
функцию-член, которая изменяет string
или резервирует дополнительную емкость, любые значения указателя, возвращенные заранее любым из вышеперечисленных методов, аннулируются . Вы можете использовать эти методы снова, чтобы получить другой указатель. (Правила те же, что и для итераторов в string
с).
См. Также Как сделать указатель на символ действительным даже после того, как x
покидает область видимости или изменяется далее ниже ....
Итак, какой лучше использовать?
Начиная с C ++ 11, используйте .c_str()
для данных ASCIIZ и .data()
для «двоичных» данных (поясняется ниже).
В C ++ 03 используйте .c_str()
, если не уверены, что .data()
является адекватным, и предпочитайте .data()
над &x[0]
, поскольку это безопасно для пустых строк ....
... попытайтесь понять программу настолько, чтобы при необходимости использовать data()
, иначе вы, вероятно, допустите другие ошибки ...
Символ ASCII NUL '\ 0', гарантируемый .c_str()
, используется многими функциями в качестве значения часового, обозначающего конец соответствующих и безопасных для доступа данных. Это относится как к функциям только на C ++, например, скажем, fstream::fstream(const char* filename, ...)
, так и к функциям с общим доступом, таким как 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" доступ к тексту C ++
string
, как в 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
)
- возвращает
const char*
или char*
из функции (возможно, по историческим причинам - клиент использует ваш существующий API) или для совместимости с C вы не хотите возвращать std::string
, но хотите скопировать string
данные где-то для звонящего)
- будьте осторожны, чтобы не вернуть указатель, который может быть разыменован вызывающей стороной после локальной переменной
string
, на которую указывает указатель, оставившей область действия
- некоторые проекты с общими объектами, скомпилированными / связанными для различных реализаций
std::string
(например, STLport и compiler-native), могут передавать данные как ASCIIZ, чтобы избежать конфликтов