Как вы строите std :: string со встроенным нулем?

StackOverflow https://stackoverflow.com/questions/164168

  •  03-07-2019
  •  | 
  •  

Вопрос

Если я хочу создать std :: string с такой строкой, как:

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

Если я хочу, чтобы в результирующей строке было три символа (a, null, b), я получаю только один. Какой правильный синтаксис?

Это было полезно?

Решение

Начиная с 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";
}

До C ++ 14

Проблема в том, что const char* конструктор принимает \0, предполагая, что входные данные являются C-строкой. C-строки завершаются c_str() и, таким образом, синтаксический анализ останавливается, когда он достигает символа vector<char>.

Чтобы компенсировать это, вам нужно использовать конструктор, который строит строку из массива 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 ++ <=> НЕ <=> - прекращено (как предлагается в других публикациях). Однако вы можете извлечь указатель на внутренний буфер, содержащий строку C, с помощью метода <=>.

Также ознакомьтесь с ответом Дуга Т ниже об использовании <=>.

Также ознакомьтесь с RiaD для решения C ++ 14.

Другие советы

Если вы делаете манипуляции, как если бы вы использовали строку в стиле 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-strings. Вы можете забыть свой нулевой терминал или написать после выделенного пространства.

Я понятия не имею, почему вы хотели бы сделать это, но попробуйте следующее:

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

Какие новые возможности пользовательские литералы добавляют в 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"));

Следующее будет работать ...

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

Вы должны быть осторожны с этим. Если вы замените '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 и размер.

В C ++ 14 теперь вы можете использовать литералы

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

Лучше использовать std :: vector < char > если этот вопрос не только для образовательных целей.

Ответ анонима превосходен, но в 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)

Очевидно, что вы должны использовать только одно или другое решение в своем проекте и называть его так, как считаете нужным.

Я знаю, что этот вопрос давно задавался. Но для тех, кто сталкивается с подобной проблемой, может быть интересен следующий код.

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

Почти все реализации std :: strings заканчиваются нулем, поэтому вам, вероятно, не следует этого делать. Обратите внимание, что & Quot; a \ 0b & Quot; на самом деле длиной четыре символа из-за автоматического нулевого терминатора (a, null, b, null). Если вы действительно хотите сделать это и разорвать контракт std :: string, вы можете сделать следующее:

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

но если вы это сделаете, все ваши друзья будут смеяться над вами, вы никогда не найдете истинного счастья.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top