Jeśli chcę skonstruować std :: string z linią taką jak:
std::string my_string("a\0b");
Tam, gdzie chcę mieć trzy znaki w wynikowym ciągu (a, null, b), otrzymuję tylko jeden. Jaka jest prawidłowa składnia?
Jeśli chcę skonstruować std :: string z linią taką jak:
std::string my_string("a\0b");
Tam, gdzie chcę mieć trzy znaki w wynikowym ciągu (a, null, b), otrzymuję tylko jeden. Jaka jest prawidłowa składnia?
Odpowiedzi:
udało nam się stworzyć dosłowne 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";
}
Problem polega na tym, std::string
że konstruktor, który przyjmuje a const char*
zakłada, że dane wejściowe są łańcuchem C. C-stringi są \0
przerywane, a zatem analizowanie zatrzymuje się, gdy osiągnie \0
znak.
Aby to zrekompensować, musisz użyć konstruktora, który buduje ciąg z tablicy znaków (nie C-String). To wymaga dwóch parametrów - wskaźnika do tablicy i długości:
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.
Uwaga: C ++ NIEstd::string
jest zakończony (jak sugerowano w innych postach). Można jednak wyodrębnić wskaźnik do wewnętrznego bufora, który zawiera C-String za pomocą metody . \0
c_str()
Sprawdź również odpowiedź Douga T. na temat używania pliku vector<char>
.
Sprawdź również RiaD, aby uzyskać rozwiązanie C ++ 14.
Jeśli robisz manipulację tak, jak w przypadku łańcucha znaków w stylu c (tablicy znaków), rozważ użycie
std::vector<char>
Masz większą swobodę w traktowaniu go jak tablicy w taki sam sposób, w jaki traktowałbyś łańcuch c. Możesz użyć funkcji copy (), aby skopiować do ciągu:
std::vector<char> vec(100)
strncpy(&vec[0], "blah blah blah", 100);
std::string vecAsStr( vec.begin(), vec.end());
i możesz go używać w wielu tych samych miejscach, w których możesz używać c-stringów
printf("%s" &vec[0])
vec[10] = '\0';
vec[11] = 'b';
Naturalnie jednak cierpisz na te same problemy, co struny c. Możesz zapomnieć o swoim pustym terminalu lub pisać poza przydzielonym miejscem.
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;
Nie mam pojęcia, dlaczego chciałbyś to zrobić, ale spróbuj tego:
std::string my_string("a\0b", 3);
vector<unsigned char>
lub unsigned char *
zostały wymyślone.
std::string
do wskazania, że dane powinny być traktowane jako zwykły tekst, ale wykonuję trochę pracy haszującej i chcę się upewnić, że wszystko nadal działa z zaangażowanymi znakami zerowymi. Wydaje się, że jest to prawidłowe użycie literału ciągu z osadzonym znakiem null.
\0
Bajt w ciąg znaków UTF-8 może być tylko NUL. Znak zakodowany wielobajtowo nigdy nie będzie zawierał - \0
ani żadnego innego znaku ASCII.
Jakie nowe możliwości dodają literały zdefiniowane przez użytkownika do C ++? przedstawia elegancką odpowiedź: Zdefiniuj
std::string operator "" _s(const char* str, size_t n)
{
return std::string(str, n);
}
wtedy możesz stworzyć swój ciąg w ten sposób:
std::string my_string("a\0b"_s);
a nawet tak:
auto my_string = "a\0b"_s;
Jest sposób na „stary styl”:
#define S(s) s, sizeof s - 1 // trailing NUL does not belong to the string
wtedy możesz zdefiniować
std::string my_string(S("a\0b"));
Poniższe będą działać ...
std::string s;
s.push_back('a');
s.push_back('\0');
s.push_back('b');
Musisz z tym uważać. Jeśli zamienisz „b” na dowolny znak numeryczny, po cichu utworzysz niewłaściwy ciąg przy użyciu większości metod. Zobacz: Reguły dotyczące znaku zmiany znaczenia literałów ciągów C ++ .
Na przykład umieściłem ten niewinnie wyglądający fragment w środku programu
// 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";
Oto, co ten program wyświetla dla mnie:
Entering loop.
Entering loop.
vector::_M_emplace_ba
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ
To była moja pierwsza instrukcja drukowania dwukrotnie, kilka niedrukowalnych znaków, po których następował znak nowej linii, a następnie coś w pamięci wewnętrznej, które właśnie nadpisałem (a następnie wydrukowałem, pokazując, że zostało nadpisane). Co najgorsze, nawet skompilowanie tego z dokładnymi i szczegółowymi ostrzeżeniami gcc nie dało mi żadnej wskazówki, że coś jest nie tak, a uruchomienie programu przez valgrind nie narzekało na żadne niewłaściwe wzorce dostępu do pamięci. Innymi słowy, jest to całkowicie niewykrywalne przez nowoczesne narzędzia.
Możesz uzyskać ten sam problem w znacznie prostszym std::string("0", 100);
, ale powyższy przykład jest trochę trudniejszy, a zatem trudniej jest zobaczyć, co jest nie tak.
Na szczęście C ++ 11 daje nam dobre rozwiązanie problemu przy użyciu składni listy inicjalizacyjnej. Pozwala to uniknąć konieczności określania liczby znaków (co, jak pokazałem powyżej, można zrobić niepoprawnie) i pozwala uniknąć łączenia liczb uciekających. std::string str({'a', '\0', 'b'})
jest bezpieczny dla dowolnej zawartości ciągu, w przeciwieństwie do wersji, które przyjmują tablicę char
i rozmiar.
W C ++ 14 możesz teraz używać literałów
using namespace std::literals::string_literals;
std::string s = "a\0b"s;
std::cout << s.size(); // 3
auto s{"a\0b"s};
Lepiej użyć std :: vector <char>, jeśli to pytanie nie jest tylko dla celów edukacyjnych.
odpowiedź anonym jest doskonała, ale w C ++ 98 jest też rozwiązanie inne niż makro:
template <size_t N>
std::string RawString(const char (&ch)[N])
{
return std::string(ch, N-1); // Again, exclude trailing `null`
}
Dzięki tej funkcji RawString(/* literal */)
wygeneruje taki sam ciąg jak 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;
Dodatkowo występuje problem z makrem: wyrażenie nie jest w rzeczywistości takie, std::string
jak napisano, i dlatego nie może być używane np. Do prostej inicjalizacji przypisania:
std::string s = S("a\0b"); // ERROR!
... więc lepiej byłoby użyć:
#define std::string(s, sizeof s - 1)
Oczywiście w swoim projekcie powinieneś używać tylko jednego lub drugiego rozwiązania i nazywać je jak uważasz za stosowne.
Wiem, że zadawano to pytanie od dawna. Ale dla każdego, kto ma podobny problem, może być zainteresowany poniższym kodem.
CComBSTR(20,"mystring1\0mystring2\0")
Prawie wszystkie implementacje std :: strings są zakończone znakiem null, więc prawdopodobnie nie powinieneś tego robić. Zauważ, że „a \ 0b” ma w rzeczywistości cztery znaki ze względu na automatyczny terminator wartości null (a, null, b, null). Jeśli naprawdę chcesz to zrobić i złamać kontrakt std :: string, możesz zrobić:
std::string s("aab");
s.at(1) = '\0';
ale jeśli to zrobisz, wszyscy twoi przyjaciele będą się z ciebie śmiać, nigdy nie znajdziesz prawdziwego szczęścia.