Дано сказати ...
std::string x = "hello";
Отримання `char *` або `const char *` з `string '
Як отримати покажчик символів, який дійсний, поки 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]
- для порожніх рядків ви гарантуєте деякий не-NULL покажчик, до якого 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 ++ - лише функцій, таких як скажімо, 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" коду доступ до
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
)
- повернути функцію
const char*
або char*
з неї (можливо, з історичних причин - клієнт використовує існуючий API - або для сумісності з C ви не хочете повертати std::string
, але хочете скопіювати свої string
дані кудись для абонента)
- будьте обережні, щоб не повернути покажчик, який може бути відкинутий абонентом після локальної
string
змінної, на яку вказаний вказівник залишив область
- деякі проекти зі спільними об’єктами, зібрані / зв'язані для різних
std::string
реалізацій (наприклад, STLport та власник компілятора), можуть передавати дані як ASCIIZ, щоб уникнути конфліктів