Gömülü boş değer içeren bir std :: dizge nasıl oluşturulur?


89

Aşağıdaki gibi bir satır içeren bir std :: string oluşturmak istersem:

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

Elde edilen dizede üç karakter olmasını istediğimde (a, null, b), yalnızca bir tane alırım. Doğru sözdizimi nedir?


4
Bu konuda dikkatli olmalısın. 'B'yi herhangi bir sayısal karakterle değiştirirseniz, sessizce yanlış dizeyi oluşturursunuz. Bakınız: stackoverflow.com/questions/10220401/…
David Stone

Yanıtlar:


129

C ++ 14'ten beri

gerçek anlamda yaratabildik 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'ten önce

Sorun, girdinin bir C-dizesi olduğunu varsayan std::stringyapıcıdır const char*. C-dizeleri \0sonlandırılır ve dolayısıyla \0karaktere ulaştığında ayrıştırma durur .

Bunu telafi etmek için, dizeyi bir char dizisinden (bir C-String değil) oluşturan yapıcıyı kullanmanız gerekir. Bu, iki parametre alır - diziye bir işaretçi ve bir uzunluk:

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.

Not: C ++ std::stringedilir DEĞİL \0 (diğer mesajlar önerildiği gibi) nihayetti. Ancak, yöntemle bir C-String içeren bir dahili tampona bir işaretçi ayıklayabilirsiniz c_str().

Ayrıca Doug T'nin a vector<char>.

Ayrıca bir C ++ 14 çözümü için RiaD'ye bakın.


8
güncelleme: c ++ itibariyle 11 dizeleri boş olarak sonlandırılmıştır. Bununla birlikte Loki'nin yazısı geçerliliğini koruyor.
matthewaveryusa

14
@mna: Depolama açısından boş sonlandırılmışlardır, ancak terimin olağan anlamı olan anlamlı boş sonlandırmayla (yani dizgi uzunluğunu tanımlayan anlambilimle) boş sonlandırılmış olmaları anlamında değildir .
Orbit'te Hafiflik Yarışları

İyi açıklanmış. Teşekkür ederim.
Joma

22

Eğer bir c-stili dizgeyle (karakter dizisi) yaptığınız gibi manipülasyon yapıyorsanız, kullanmayı düşünün

std::vector<char>

Bir c-dizgesine davrandığınız gibi onu bir dizi gibi ele almak için daha fazla özgürlüğünüz var. Bir dizeye kopyalamak için copy () kullanabilirsiniz:

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

ve c-dizelerini kullanabileceğiniz birçok yerde kullanabilirsiniz.

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

Doğal olarak, yine de, c-dizgileriyle aynı sorunlardan muzdaripsiniz. Boş uçbiriminizi unutabilir veya ayrılan alanın ötesine yazabilirsiniz.


Baytları dizeye kodlamaya çalışıyorsanız (grpc bayt dizge olarak saklanır) yanıtta belirtildiği gibi vektör yöntemini kullanın; olağan yol değil (aşağıya bakın) ki bu tüm dizeyi 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;
OLUŞTURMAYACAKTIR

13

Neden böyle bir şey yapmak istediğiniz hakkında hiçbir fikrim yok, ama şunu deneyin:

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

1
Bunu yapmakla ilgili endişeleriniz neler? Hiç "a \ 0b" saklama ihtiyacını mı sorguluyorsunuz? veya böyle bir depolama için std :: string kullanımını sorguluyor musunuz? İkincisi ise, alternatif olarak ne önerirsiniz?
Anthony Kramp

3
@Constantin, ikili verileri bir dizge olarak depoluyorsanız, yanlış bir şey yapıyorsunuz demektir. Bunun için icat edildi vector<unsigned char>ya unsigned char *da icat edildi.
Mahmoud Al-Qudsi

2
İplerin güvenliği hakkında daha fazla şey öğrenmeye çalışırken bununla karşılaştım. Kodumu, bir dosyadan / ağdan metinsel veri olmasını beklediği şeyi okurken boş bir karakter okurken bile çalıştığından emin olmak için test etmek istedim. std::stringVerilerin düz metin olarak değerlendirilmesi gerektiğini belirtmek için kullanıyorum , ancak bazı hash işlemleri yapıyorum ve her şeyin hala boş karakterlerle çalıştığından emin olmak istiyorum. Bu, gömülü bir boş karaktere sahip bir dizgenin geçerli bir kullanımı gibi görünüyor.
David Stone

3
@DuckMaestro Hayır, bu doğru değil. \0UTF-8 dizesindeki bir bayt yalnızca NUL olabilir. Çok baytlı kodlanmış bir karakter asla - \0ya da bu konuda başka bir ASCII karakteri içermez .
John Kugelman

1
Test durumunda bir algoritma oluşturmaya çalışırken bununla karşılaştım. Yani geçerli nedenler var; az da olsa.
namezero

12

Kullanıcı tanımlı değişmez değerler C ++ 'ya hangi yeni yetenekler ekler? zarif bir cevap sunar: Tanımla

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

o zaman dizenizi şu şekilde oluşturabilirsiniz:

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

hatta öyle:

auto my_string = "a\0b"_s;

"Eski tarz" bir yol var:

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

o zaman tanımlayabilirsin

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

8

Aşağıdakiler işe yarayacak ...

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

Köşeli parantez içinde parantez kullanmanız gerekir.
jk.

5

Bu konuda dikkatli olmalısın. 'B'yi herhangi bir sayısal karakterle değiştirirseniz, çoğu yöntemi kullanarak sessizce yanlış dizeyi oluşturursunuz. Bakınız: C ++ dize değişmezleri için kurallar kaçış karakteri .

Örneğin, bu masum görünümlü parçacığı bir programın ortasına düşürdüm

// 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";

İşte bu programın bana çıkardığı şey:

Entering loop.
Entering loop.

vector::_M_emplace_ba
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ

Bu, iki kez ilk yazdırma ifademdi, birkaç basılmayan karakter, ardından bir satırsonu, ardından dahili bellekte üzerine yazdığım bir şey geldi (ve sonra üzerine yazıldığını göstererek yazdırdım). Hepsinden kötüsü, bunu kapsamlı ve ayrıntılı gcc uyarılarıyla derlemek bile bana bir şeyin yanlış olduğuna dair hiçbir gösterge vermedi ve programı valgrind aracılığıyla çalıştırmak, herhangi bir uygunsuz bellek erişim modelinden şikayet etmedi. Başka bir deyişle, modern araçlar tarafından tamamen tespit edilemez.

Aynı sorunu çok daha basit olanla da alabilirsiniz std::string("0", 100);, ancak yukarıdaki örnek biraz daha karmaşıktır ve bu nedenle neyin yanlış olduğunu görmek daha zordur.

Neyse ki, C ++ 11 bize başlatıcı listesi sözdizimini kullanarak soruna iyi bir çözüm sunar. Bu sizi karakter sayısını (yukarıda gösterdiğim gibi yanlış yapabilirsiniz) belirtme zorunluluğundan kurtarır ve kaçan sayıları birleştirmekten kaçınır. std::string str({'a', '\0', 'b'})dizi charve boyut alan sürümlerin aksine tüm dizgi içeriği için güvenlidir .


2
Bu gönderi için hazırlığımın bir parçası olarak, bunu biraz daha güvenli hale getirmek için bir uyarı eklemeleri umuduyla gcc'ye
David Stone

4

C ++ 14'te artık değişmez değerleri kullanabilirsiniz

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

1
ve 2. satır alternatif olarak, daha güzel bir şekilde imho olarak yazılabilirauto s{"a\0b"s};
altçizgi_d

Güzel cevap Teşekkürler.
Joma

1

Bu soru sadece eğitim amaçlı değilse std :: vector <char> kullanmak daha iyidir.


1

anonimin cevabı mükemmel, ancak C ++ 98'de de makro olmayan bir çözüm var:

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

Bu işlevle RawString(/* literal */)aynı dizeyi şu şekilde üretir 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;

Ek olarak, makro ile ilgili bir sorun var: ifade aslında std::stringyazıldığı gibi değil ve bu nedenle örneğin basit atama-başlatma için kullanılamaz:

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

... bu nedenle kullanılması tercih edilebilir:

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

Açıkçası, projenizde yalnızca birini veya diğerini kullanmalı ve uygun olduğunu düşündüğünüz şekilde adlandırmalısınız.


-5

Bu sorunun uzun zamandır sorulduğunu biliyorum. Ancak benzer bir sorunu olan herkes aşağıdaki kodla ilgilenebilir.

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

Bu yanıt Microsoft platformlarına çok özeldir ve orijinal soruyu (std :: string hakkında sorulan) ele almamaktadır.
Haziran Rodos

-8

Std :: strings'in neredeyse tüm gerçeklenimleri boş sonlandırılmıştır, bu yüzden muhtemelen bunu yapmamalısınız. Otomatik boş sonlandırıcı (a, null, b, null) nedeniyle "a \ 0b" nin aslında dört karakter uzunluğunda olduğuna dikkat edin. Bunu gerçekten yapmak ve std :: string'in sözleşmesini bozmak istiyorsanız, şunları yapabilirsiniz:

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

ama yaparsan, bütün arkadaşların sana gülecek, gerçek mutluluğu asla bulamayacaksın.


1
std :: string'in NULL ile sonlandırılması gerekli DEĞİLDİR.
Martin York

2
Zorunlu değildir, ancak neredeyse tüm uygulamalarda, muhtemelen c_str () erişimcisinin size boş sonlandırılmış eşdeğerini sağlaması gerektiğinden dolayıdır.
Jurney

2
Verimliliği için bir boş karakter olabilir veri tampon arkasında tutulmalıdır. Ancak bir dizge üzerindeki işlemlerin hiçbiri (yani yöntemler) bu bilgiyi kullanmaz veya NULL karakter içeren bir dizeden etkilenmez. NULL karakter, diğer herhangi bir karakterle aynı şekilde işlenecektir.
Martin York

String'in std olması çok komik olmasının nedeni budur :: - davranışı HERHANGİ bir platformda tanımlanmaz.

Keşke user595447 hala burada olsaydı, onlara Dünya'da ne hakkında konuştuklarını düşündüklerini sorabilseydim.
alt çizgi_d
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.