Як побудувати std :: string із вбудованою null?


88

Якщо я хочу побудувати std :: рядок з рядком, як:

std::string my_string("a\0b");

Там, де я хочу мати три символи в отриманому рядку (a, null, b), я отримую лише один. Який правильний синтаксис?


4
З цим потрібно бути обережним. Якщо ви заміните "b" будь-яким числовим символом, ви мовчки створите неправильний рядок. Див .: stackoverflow.com/questions/10220401/…
Девід Стоун

Відповіді:


128

З C ++ 14

ми змогли створити буквальний std::string

#include <iostream>
#include <string>

int main()
{
    using namespace std::string_literals;

    std::string s = "pl-\0-op"s;    // <- Notice the "s" at the end
                                    // This is a std::string literal not
                                    // a C-String literal.
    std::cout << s << "\n";
}

До С ++ 14

Проблема полягає в тому, що std::stringконструктор приймає, const char*що вхідним сигналом є C-рядок. С-рядки \0припиняються, і таким чином розбір зупиняється, коли він досягає \0символу.

Щоб компенсувати це, вам потрібно використовувати конструктор, який будує рядок із масиву char (а не C-String). Для цього потрібно два параметри - вказівник на масив і довжина:

std::string   x("pq\0rs");   // Two characters because input assumed to be C-String
std::string   x("pq\0rs",5); // 5 Characters as the input is now a char array with 5 characters.

Примітка: C ++ std::stringце НЕ \0 -завершённий (як це було запропоновано в інших постах). Однак ви можете витягнути покажчик на внутрішній буфер, який містить C-String разом із методом c_str().

Також ознайомтеся з відповіддю Дага Т про використання avector<char> .

Також перевірте RiaD для рішення C ++ 14.


6
оновлення: станом на c ++ 11 рядки закінчуються нулем. З огляду на це, пост Локі залишається дійсним.
matthewaveryusa

14
@mna: Вони закінчуються нулем з точки зору зберігання, але не в тому сенсі, що вони закінчуються нулем із значущим закінченням нуля (тобто із семантикою, що визначає довжину рядка), що є звичайним значенням цього терміна.
Гонки легкості на орбіті

Добре пояснили. Дякую.
Джома

22

Якщо ви робите маніпуляції, як це було б з рядком у стилі c (масив символів), розгляньте можливість використання

std::vector<char>

Ви маєте більше свободи обробляти це як масив так само, як і c-рядок. Ви можете скопіювати copy () для копіювання в рядок:

std::vector<char> vec(100)
strncpy(&vec[0], "blah blah blah", 100);
std::string vecAsStr( vec.begin(), vec.end());

і ви можете використовувати його в багатьох тих самих місцях, де ви можете використовувати c-рядки

printf("%s" &vec[0])
vec[10] = '\0';
vec[11] = 'b';

Однак, природно, ви страждаєте від тих самих проблем, що і c-струни. Ви можете забути свій нульовий термінал або написати минуле виділений простір.


Якщо ви кажете, що намагаєтесь кодувати байти в рядок (байти grpc зберігаються як рядок), використовуйте векторний метод, як зазначено у відповіді; не звичайний спосіб (див. нижче), який НЕ буде будувати цілий рядок byte *bytes = new byte[dataSize]; std::memcpy(bytes, image.data, dataSize * sizeof(byte)); std::string test(reinterpret_cast<char *>(bytes)); std::cout << "Encoded String length " << test.length() << std::endl;
Алекс Пуннен

13

Я не уявляю, чому ви хочете зробити таке, але спробуйте наступне:

std::string my_string("a\0b", 3);

1
Що вас турбує для цього? Ви ставите під сумнів необхідність зберігати "a \ 0b" ніколи? або ставить під сумнів використання std :: string для такого зберігання? Якщо останнє, що ви пропонуєте як альтернативу?
Ентоні Крамп

3
@Constantin, тоді ви робите щось не так, якщо зберігаєте двійкові дані як рядок. Ось для чого vector<unsigned char>або unsigned char *були винайдені.
Махмуд Аль-Кудсі

2
Я зіткнувся з цим, намагаючись дізнатись більше про безпеку рядків. Я хотів протестувати свій код, щоб переконатися, що він все ще працює, навіть якщо він читає нульовий символ під час читання з файлу / мережі того, що, як він очікує, буде текстовими даними. Я вживаю, std::stringщоб вказати, що дані слід розглядати як текстовий текст, але я роблю деяку роботу з хешування, і я хочу переконатись, що все ще працює із задіяними нульовими символами. Це здається допустимим використанням рядкового літералу із вбудованим нульовим символом.
Девід Стоун,

3
@DuckMaestro Ні, це неправда. \0Байт в рядку UTF-8 може бути тільки NUL. Багатобайтовий закодований символ ніколи не міститиме - \0ані будь-якого іншого символу ASCII.
Джон Кугельман,

1
Я зіткнувся з цим, намагаючись спровокувати алгоритм у тестовому випадку. Отже, є вагомі причини; хоч і мало.
namezero

12

Які нові можливості додають визначені користувачем літерали до C ++? представляє елегантну відповідь: Визначте

std::string operator "" _s(const char* str, size_t n) 
{ 
    return std::string(str, n); 
}

тоді ви можете створити свій рядок таким чином:

std::string my_string("a\0b"_s);

або навіть так:

auto my_string = "a\0b"_s;

Існує "старий стиль":

#define S(s) s, sizeof s - 1 // trailing NUL does not belong to the string

тоді ви можете визначити

std::string my_string(S("a\0b"));

8

Наступне буде працювати ...

std::string s;
s.push_back('a');
s.push_back('\0');
s.push_back('b');

Ви повинні використовувати дужки, введені в квадратні дужки.
jk.

5

З цим потрібно бути обережним. Якщо ви заміните "b" будь-яким числовим символом, ви безшумно створите неправильний рядок, використовуючи більшість методів. Див.: Правила для рядових літералів C ++ символу екранування .

Наприклад, я скинув цей невинно виглядаючий фрагмент посеред програми

// Create '\0' followed by '0' 40 times ;)
std::string str("\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00", 80);
std::cerr << "Entering loop.\n";
for (char & c : str) {
    std::cerr << c;
    // 'Q' is way cooler than '\0' or '0'
    c = 'Q';
}
std::cerr << "\n";
for (char & c : str) {
    std::cerr << c;
}
std::cerr << "\n";

Ось що виводить ця програма для мене:

Entering loop.
Entering loop.

vector::_M_emplace_ba
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ

Це було моє перше друковане твердження двічі, кілька недрукованих символів, після чого новий рядок, а потім щось у внутрішній пам’яті, яку я щойно переписав (а потім надрукував, показуючи, що вона була перезаписана). Гірше за все, навіть складання цього з ретельними та детальними попередженнями gcc не дало мені жодних ознак того, що щось не так, і запуск програми через valgrind не скаржився на неправильні шаблони доступу до пам'яті. Іншими словами, це абсолютно неможливо виявити сучасними інструментами.

Ви можете отримати цю саму проблему набагато простіше std::string("0", 100); , але наведений вище приклад трохи складніший і, отже, важче зрозуміти, що не так.

На щастя, C ++ 11 дає нам хороше рішення проблеми за допомогою синтаксису списку ініціалізаторів. Це позбавляє вас від необхідності вказувати кількість символів (що, як я показав вище, ви можете зробити неправильно), і уникає поєднання зниклих чисел. std::string str({'a', '\0', 'b'})є безпечним для будь-якого рядкового вмісту, на відміну від версій, які приймають масив charта розмір.


2
Як частина моєї підготовки до цієї публікації, я подав звіт про помилку до gcc в надії, що вони додадуть попередження, щоб зробити це трохи безпечнішим: gcc.gnu.org/bugzilla/show_bug.cgi?id=54924
Девід Стоун

4

У C ++ 14 тепер ви можете використовувати літерали

using namespace std::literals::string_literals;
std::string s = "a\0b"s;
std::cout << s.size(); // 3

1
а 2-й рядок можна записати, більш приємно imho, asauto s{"a\0b"s};
underscore_d

Приємна відповідь Дякую.
Джома


1

Відповідь аноніму відмінна, але в C ++ 98 також є немакрокомандне рішення:

template <size_t N>
std::string RawString(const char (&ch)[N])
{
  return std::string(ch, N-1);  // Again, exclude trailing `null`
}

За допомогою цієї функції RawString(/* literal */)буде видавати той самий рядок, що і S(/* literal */):

std::string my_string_t(RawString("a\0b"));
std::string my_string_m(S("a\0b"));
std::cout << "Using template: " << my_string_t << std::endl;
std::cout << "Using macro: " << my_string_m << std::endl;

Крім того, є проблема з макросом: вираз насправді не є таким, std::stringяк написано, і тому не може використовуватися, наприклад, для простого призначення-ініціалізації:

std::string s = S("a\0b"); // ERROR!

... тому може бути кращим використовувати:

#define std::string(s, sizeof s - 1)

Очевидно, що ви повинні використовувати лише одне чи інше рішення у своєму проекті і називати його так, як вважаєте за потрібне.


-5

Я знаю, що це питання вже давно не задається. Але для кожного, хто має подібну проблему, може бути цікавий наступний код.

CComBSTR(20,"mystring1\0mystring2\0")

Ця відповідь занадто специфічна для платформ Microsoft і не стосується вихідного питання (яке задавало питання std :: string).
Червень Родос

-8

Майже всі реалізації std :: strings закінчуються нулем, тому ви, мабуть, не повинні цього робити. Зверніть увагу, що "a \ 0b" насправді має чотири символи через автоматичний нульовий термінатор (a, null, b, null). Якщо ви дійсно хочете зробити це і порушити договір std :: string, ви можете зробити:

std::string s("aab");
s.at(1) = '\0';

але якщо ви це зробите, всі ваші друзі будуть сміятися з вас, ви ніколи не знайдете справжнього щастя.


1
std :: рядок НЕ вимагає припинення NULL.
Мартін Йорк,

2
Це не потрібно, але майже у всіх реалізаціях це, можливо, через необхідність доступу c_str () для надання вам еквівалента, що закінчується нулем.
Jurney

2
Для ефективності на звороті буфера даних може зберігатися нульовий символ . Але жодна з операцій (тобто методів) над рядком не використовує цих знань або на них не впливає рядок, що містить символ NULL. Символом NULL буде маніпулювати точно так само, як і будь-яким іншим символом.
Мартін Йорк,

Ось чому це так смішно, що рядок є std :: - його поведінка не визначена на БУДЬ-ЯКІЙ платформі.

Я хотів би, щоб користувач595447 все ще був тут, щоб я міг запитати їх, про що на Землі вони думають, про що вони говорять.
underscore_d
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.