Połącz dwa literały ciągów

121

Czytam Accelerated C ++ autorstwa Koeniga. Pisze, że „nowy pomysł polega na tym, że możemy użyć + do połączenia łańcucha i literału ciągu - lub, w tym przypadku, dwóch łańcuchów (ale nie dwóch literałów łańcuchowych).

W porządku, to chyba ma sens. Teraz przejdźmy do dwóch oddzielnych ćwiczeń, które mają to wyjaśnić.

Czy poniższe definicje są prawidłowe?

const string hello = "Hello";

const string message = hello + ",world" + "!";

Teraz próbowałem wykonać powyższe i zadziałało! Więc byłem szczęśliwy.

Potem spróbowałem wykonać następne ćwiczenie;

const string exclam = "!";

const string message = "Hello" + ",world" + exclam;

To nie zadziałało. Teraz rozumiem, że ma to coś wspólnego z faktem, że nie można łączyć dwóch literałów ciągów, ale nie rozumiem semantycznej różnicy między tym, dlaczego udało mi się uruchomić pierwszy przykład (to nie jest „, world” i „! „dwa literały łańcuchowe? Czy to nie powinno działać?), ale nie drugi.

Arthur Collé
źródło
4
const string message = "Hello" ",world" + exclam(np. pominięcie pierwszego +) powinno działać dobrze.
n0rd
1
@Joe - Dlaczego ktoś miałby pisać, "Hello" + ", world!"kiedy możesz "Hello, world!". Jak zwykle C ++ ma niesamowite i proste obejście zauważonego problemu. :-)
Bo Persson
2
@Bo Jedyne, co przychodzi mi do głowy, to użycie definicji (#define)
Joe Phillips
@Joe Nawet wtedy jest bardziej prawdopodobne, że napiszesz "Hello" ", world!"(bez +). Jest wiele skarg, które można by złożyć na temat C ++, ale nie sądzę, aby rozpatrywanie tutaj było jedną z nich. To dokładnie to samo, co gdybyś pisał 1 / 3 + 1.5i narzekał, ponieważ podział był całkowy. Na dobre lub na złe, tak działa większość języków.
James Kanze
2
@Bo Persson Właściwie ta funkcja "hello" " world" == "hello world"jest przydatna, jeśli musisz napisać długi ciąg i nie chcesz, aby wyszedł z twojego okna lub chcesz znaleźć się w ograniczeniu długości linii. Lub jeśli jeden z ciągów jest zdefiniowany w makrze.
Dimitar Slavchev

Odpowiedzi:

140
const string message = "Hello" + ",world" + exclam;

+Operator lewej do prawej asocjatywnego, więc równoważne nawiasach jest wyrażenie:

const string message = (("Hello" + ",world") + exclam);

Jak widać, dwa literały łańcuchowe "Hello"i ",world"są „dodawane” jako pierwsze, stąd błąd.

Jeden z pierwszych dwóch łączonych ciągów musi być std::stringobiektem:

const string message = string("Hello") + ",world" + exclam;

Alternatywnie możesz wymusić, aby druga +była oceniana jako pierwsza, umieszczając w nawiasach tę część wyrażenia:

const string message = "Hello" + (",world" + exclam);

Ma sens, że pierwszy przykład ( hello + ",world" + "!") działa, ponieważ std::string( hello) jest jednym z argumentów po lewej stronie +. To +jest oceniane, wynik jest std::stringobiektem z połączonym ciągiem, a ten wynik std::stringjest następnie łączony z "!".


Jeśli chodzi o to, dlaczego nie można łączyć dwóch literałów łańcuchowych przy użyciu +, dzieje się tak , ponieważ literał ciągu to po prostu tablica znaków ( const char [N]gdzie gdzie Njest długością łańcucha plus jeden dla terminatora null). Kiedy używasz tablicy w większości kontekstów, jest ona konwertowana na wskaźnik do jej elementu początkowego.

Tak więc, kiedy próbujesz zrobić "Hello" + ",world", to, co naprawdę próbujesz zrobić, to dodać dwa const char*s do siebie, co nie jest możliwe (co oznaczałoby dodanie dwóch wskaźników razem?) I gdyby tak było, nie zrobiłoby tego, co chciałem to zrobić.


Zauważ, że możesz łączyć literały łańcuchowe, umieszczając je obok siebie; na przykład następujące dwa są równoważne:

"Hello" ",world"
"Hello,world"

Jest to przydatne, jeśli masz długi literał ciągu, który chcesz podzielić na wiele wierszy. Muszą to być jednak literały łańcuchowe: to nie zadziała w przypadku const char*wskaźników ani const char[N]tablic.

James McNellis
źródło
3
Również const string message = "Hello" + (",world"+exclam);będzie działać, ponieważ jawne nawiasy (czy to słowo?).
Chinmay Kanchi
1
Może być jeszcze bardziej kompletne, jeśli wskażesz, dlaczego pierwszy przykład działa:const string message = ((hello + ",world") + "!");
Mark Ransom
Dziękuję Ci! Podejrzewałem, że ma to coś wspólnego z łączeniem od lewej do prawej, ale nie byłem pewien, a ta różnica semantyczna nie miała dla mnie większego sensu. Doceniam odpowiedź!
Arthur Collé,
2
Wspomniałbym, że "Hello" ",world"składnia jest przydatna nie tylko do dzielenia na wiele wierszy, ale także wtedy, gdy jeden z literałów ciągu jest makrem (lub nawet obydwoma). Następnie konkatenacja odbywa się w czasie kompilacji.
Melebius
8

Zawsze powinieneś zwracać uwagę na typy .

Chociaż wszystkie wyglądają jak łańcuchy "Hello"i ",world"dosłowne .

W twoim przykładzie exclamjest to std::stringprzedmiot.

C ++ ma przeciążenie operatora, które pobiera std::stringobiekt i dodaje do niego kolejny ciąg. Kiedy łączysz std::stringobiekt z literałem, spowoduje to odpowiednie rzutowanie literału.

Ale jeśli spróbujesz połączyć dwa literały, kompilator nie będzie w stanie znaleźć operatora, który pobiera dwa literały.

Yochai Timmer
źródło
Zobacz std :: operator +, który oferuje przeciążenia do łączenia a std::stringz innym std::string, tablicą znaków lub pojedynczym znakiem.
DavidRR
7

Twój drugi przykład nie działa, ponieważ nie ma operator +dwóch literałów ciągu. Zauważ, że literał ciągu nie jest typu string, ale zamiast tego jest typu const char *. Twój drugi przykład zadziała, jeśli poprawisz go w ten sposób:

const string message = string("Hello") + ",world" + exclam;
Juraj Blaho
źródło
4

Od C ++ 14 możesz używać dwóch prawdziwych literałów łańcuchowych :

const string hello = "Hello"s;

const string message = hello + ",world"s + "!"s;

lub

const string exclam = "!"s;

const string message = "Hello"s + ",world"s + exclam;
Thomas Sablik
źródło
2

W przypadku 1, ze względu na kolejność operacji otrzymujesz:

(witaj + ", świat") + "!" co zamienia się w hello + „!” i na koniec witam

W przypadku 2, jak zauważył James, otrzymasz:

("Hello" + ", world") + exclam, czyli konkatacja 2 literałów łańcuchowych.

Mam nadzieję, że to jasne :)

Stephen
źródło
1

Różnica między łańcuchem znaków (a ściślej mówiąc std::string) a literałem znakowym polega na tym, że dla tego drugiego nie ma +zdefiniowanego operatora. Dlatego drugi przykład zawodzi.

W pierwszym przypadku kompilator może znaleźć odpowiedni operator+z pierwszym argumentem a, stringa drugim literałem znakowym ( const char*), więc użył tego. Wynikiem tej operacji jest znowu a string, więc powtarza tę samą sztuczkę podczas dodawania "!"do niego.

Péter Török
źródło