Znam trochę C, a teraz przyjrzę się C ++. Jestem przyzwyczajony do znakowania tablic do obsługi ciągów znaków C, ale kiedy patrzę na kod C ++, widzę przykłady używające zarówno typu ciągów, jak i tablic znaków:
#include <iostream>
#include <string>
using namespace std;
int main () {
string mystr;
cout << "What's your name? ";
getline (cin, mystr);
cout << "Hello " << mystr << ".\n";
cout << "What is your favorite team? ";
getline (cin, mystr);
cout << "I like " << mystr << " too!\n";
return 0;
}
i
#include <iostream>
using namespace std;
int main () {
char name[256], title[256];
cout << "Enter your name: ";
cin.getline (name,256);
cout << "Enter your favourite movie: ";
cin.getline (title,256);
cout << name << "'s favourite movie is " << title;
return 0;
}
(oba przykłady z http://www.cplusplus.com )
Przypuszczam, że jest to często zadawane i (oczywiste?) Pytanie, na które udzielono odpowiedzi, ale byłoby miło, gdyby ktoś mógł mi powiedzieć, jaka jest dokładnie różnica między tymi dwoma sposobami radzenia sobie z ciągami znaków w C ++ (wydajność, integracja z API, sposób, w jaki każdy z nich jest lepszy, ...).
Dziękuję Ci.
Odpowiedzi:
Tablica znaków to po prostu tablica znaków:
Łańcuch to klasa, która zawiera tablicę znaków, ale automatycznie zarządza nią za Ciebie. Większość implementacji ciągów ma wbudowaną tablicę 16 znaków (więc krótkie łańcuchy nie fragmentują sterty) i używają sterty dla dłuższych ciągów.
Możesz uzyskać dostęp do tablicy znaków ciągu w następujący sposób:
Ciągi C ++ mogą zawierać osadzone znaki \ 0, znają ich długość bez liczenia, są szybsze niż tablice znaków przydzielone na sterty dla krótkich tekstów i chronią przed przepełnieniem bufora. Ponadto są bardziej czytelne i łatwiejsze w użyciu.
Jednak ciągi C ++ nie są (bardzo) odpowiednie do użycia poza granicami DLL, ponieważ wymagałoby to od każdego użytkownika takiej funkcji DLL upewnienia się, że używa dokładnie tego samego kompilatora i implementacji środowiska wykonawczego C ++, aby nie ryzykować, że jego klasa łańcuchów zachowa się inaczej.
Zwykle klasa łańcuchowa zwalnia również swoją pamięć sterty na stercie wywołującym, więc będzie mogła ponownie zwolnić pamięć tylko wtedy, gdy używasz udostępnionej (.dll lub .so) wersji środowiska wykonawczego.
W skrócie: używaj ciągów C ++ we wszystkich wewnętrznych funkcjach i metodach. Jeśli kiedykolwiek napiszesz plik .dll lub .so, użyj ciągów C w funkcjach publicznych (dll / tak eksponowanych).
źródło
Arkaitz ma rację, że
string
jest to typ zarządzany. Oznacza to dla ciebie , że nigdy nie musisz martwić się o długość łańcucha, ani nie musisz martwić się o zwolnienie lub ponowne przydzielenie pamięci ciągu.Z drugiej strony
char[]
notacja w powyższym przypadku ograniczyła bufor znaków do dokładnie 256 znaków. Jeśli próbowałeś zapisać więcej niż 256 znaków w tym buforze, w najlepszym przypadku nadpiszesz inną pamięć, którą "posiada" twój program. W najgorszym przypadku spróbujesz nadpisać pamięć, której nie posiadasz, a system operacyjny zabije Twój program na miejscu.Konkluzja? Łańcuchy są dużo bardziej przyjazne programistom, a znaki char [] są dużo bardziej wydajne dla komputera.
źródło
Cóż, typ string jest w pełni zarządzaną klasą dla ciągów znaków, podczas gdy char [] jest nadal tym, czym był w C, tablicą bajtów reprezentującą ciąg znaków dla ciebie.
Jeśli chodzi o API i bibliotekę standardową, wszystko jest zaimplementowane w postaci łańcuchów znaków, a nie znaków [], ale wciąż jest wiele funkcji z biblioteki libc, które otrzymują znak [], więc może być konieczne użycie go do tych celów, poza tym chciałbym zawsze używaj std :: string.
Jeśli chodzi o wydajność, oczywiście surowy bufor niezarządzanej pamięci prawie zawsze będzie szybszy dla wielu rzeczy, ale weź pod uwagę na przykład porównywanie ciągów znaków, std :: string ma zawsze rozmiar do sprawdzenia go jako pierwszy, podczas gdy z char [] ty trzeba porównywać znak po znaku.
źródło
Osobiście nie widzę żadnego powodu, dla którego ktoś chciałby używać char * lub char [] poza kompatybilnością ze starym kodem. std :: string nie jest wolniejsze niż użycie c-string, z wyjątkiem tego, że zajmie się ponownym przydzieleniem za Ciebie. Możesz ustawić jego rozmiar podczas tworzenia, a tym samym uniknąć ponownego przydzielania, jeśli chcesz. Jego operator indeksujący ([]) zapewnia stały dostęp do czasu (i jest w każdym znaczeniu tego słowa dokładnie tym samym, co użycie indeksatora łańcuchów c). Użycie metody at daje również sprawdzone granice bezpieczeństwa, coś, czego nie dostajesz z c-stringami, chyba że to napiszesz. Twój kompilator najczęściej optymalizuje użycie indeksatora w trybie wydania. Łatwo jest bawić się sznurkami c; rzeczy takie jak delete vs delete [], bezpieczeństwo wyjątków, a nawet jak ponownie przydzielić łańcuch c.
A kiedy masz do czynienia z zaawansowanymi koncepcjami, takimi jak posiadanie łańcuchów COW i nie-COW dla MT itp., Będziesz potrzebować std :: string.
Jeśli martwisz się o kopie, tak długo, jak używasz referencji i stałych referencji, gdziekolwiek możesz, nie będziesz mieć żadnych narzutów z powodu kopii i jest to to samo, co robisz z c-stringiem.
źródło
Ciągi znaków mają funkcje pomocnicze i automatycznie zarządzają tablicami znaków. Możesz łączyć łańcuchy, w przypadku tablicy znaków musisz skopiować ją do nowej tablicy, łańcuchy mogą zmieniać swoją długość w czasie wykonywania. Tablica znaków jest trudniejsza w zarządzaniu niż ciągiem, a niektóre funkcje mogą akceptować tylko ciąg jako dane wejściowe, co wymaga konwersji tablicy na ciąg. Lepiej jest użyć ciągów, zostały one stworzone, abyś nie musiał używać tablic. Gdyby tablice były obiektywnie lepsze, nie mielibyśmy łańcuchów.
źródło
Pomyśl o (char *) jako string.begin (). Zasadnicza różnica polega na tym, że (char *) jest iteratorem, a std :: string jest kontenerem. Jeśli trzymasz się podstawowych łańcuchów, a (char *) da ci to, co robi std :: string :: iterator. Możesz użyć (char *), jeśli chcesz skorzystać z iteratora, a także zgodności z C, ale to wyjątek, a nie reguła. Jak zawsze uważaj na unieważnienie iteratora. Kiedy ludzie mówią (char *) nie jest bezpieczny, to właśnie mają na myśli. Jest tak samo bezpieczny, jak każdy inny iterator C ++.
źródło
Jedną z różnic jest zakończenie zerowe (\ 0).
W C i C ++, char * lub char [] przyjmą wskaźnik do pojedynczego znaku jako parametr i będą śledzić w pamięci aż do osiągnięcia wartości pamięci 0 (często nazywanej terminatorem zerowym).
Ciągi C ++ mogą zawierać osadzone znaki \ 0, znają ich długość bez liczenia.
Wynik:
źródło