Różnica między typami string i char [] w C++

Znam trochę C i teraz patrzę na C++. Jestem przyzwyczajony do znakowania tablic do radzenia sobie z ciągami C, ale gdy patrzę na kod C++, widzę przykłady używające zarówno tablic typu string, jak i tablic znakowych:

#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 szeroko zadawane pytania i odpowiedzi (oczywiste? pytanie, ale byłoby miło, gdyby ktoś mi powiedział, jaka jest dokładnie różnica między tymi dwoma sposobami radzenia sobie z ciągi w C++ (wydajność, integracja API, sposób, w jaki każdy z nich jest lepszy, ...).

Dziękuję.
Author: ramosg, 2009-08-17

7 answers

Tablica znaków jest po prostu tablicą znaków:

  • jeśli zostanie przydzielony na stos (jak w twoim przykładzie), zawsze będzie zajmował np. 256 bajtów bez względu na długość tekstu
  • Jeśli przydzielono na stercie (używając malloc() lub new char []), jesteś odpowiedzialny za zwolnienie pamięci i zawsze będziesz miał narzut alokacji sterty.
  • jeśli skopiujesz do tablicy tekst zawierający więcej niż 256 znaków, może to spowodować awarię i powstanie brzydkiego twierdzenia wiadomości lub spowodować niewytłumaczalne (mis-)zachowanie w innym miejscu w programie.
  • Aby określić długość tekstu, tablica musi być skanowana, znak po znaku, dla znaku \0.

String jest klasą, która zawiera tablicę znaków, ale automatycznie zarządza nią za Ciebie. Większość implementacji łańcuchów ma wbudowaną tablicę 16 znaków (więc krótkie łańcuchy nie fragmentują sterty) i używa sterty dla dłuższych łańcuchów.

Możesz uzyskać dostęp do tablicy znaków łańcucha jak to:

std::string myString = "Hello World";
const char *myStringChars = myString.c_str();

Ciągi C++ mogą zawierać osadzone znaki \0, znać ich długość bez zliczania, są szybsze niż tablice znaków przydzielanych stercie dla krótkich tekstów i chronią przed przekroczeniem bufora. Dodatkowo są bardziej czytelne i łatwiejsze w użyciu.

-

Jednak łańcuchy 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 C++, ryzykuje, że jego Klasa smyczków zachowuje się inaczej.

Normalnie, Klasa string również zwolni swoją pamięć sterty na stercie wywołującej, więc będzie mogła ponownie zwolnić pamięć tylko wtedy, gdy używasz współdzielonego (.dll lub. so) wersja runtime.

W skrócie: używaj ciągów C++ we wszystkich swoich wewnętrznych funkcjach i metodach. Jeśli kiedykolwiek napiszesz .dll lub. so, używaj ciągów C w publicznych (dll/so-exposed) funkcjach.

 151
Author: Cygon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-17 11:06:05

Arkaitz ma rację, że string jest typem zarządzanym. Oznacza to dla Ciebie, że nigdy nie musisz martwić się o długość sznurka, ani nie musisz martwić się o uwolnienie lub ponowne przydzielenie pamięci Sznurka.

Z drugiej strony, notacja char[] w powyższym przypadku ograniczyła bufor znaków do dokładnie 256 znaków. Jeśli spróbujesz zapisać więcej niż 256 znaków w tym buforze, w najlepszym wypadku nadpiszesz inną pamięć, którą twój program "posiada". W najgorszym przypadku spróbujesz nadpisać pamięć, której nie posiadasz, a Twój system operacyjny zabije Twój program na miejscu.

Podsumowując? Ciągi są o wiele bardziej przyjazne dla programistów, char [] s są o wiele bardziej wydajne dla komputera.

 7
Author: Mark Rushakoff,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-17 11:00:27

Cóż, string type jest całkowicie zarządzaną klasą dla łańcuchów znaków, podczas gdy char [] jest nadal tym, co było 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 libc, które otrzymują znak [], więc może być konieczne użycie go do tych celów, poza tym zawsze używałbym std:: string.

Pod względem wydajności oczywiście bufor surowy pamięć niezarządzana prawie zawsze będzie szybsza dla wielu rzeczy, ale weź pod uwagę porównywanie łańcuchów, na przykład, std::string ma zawsze rozmiar, aby sprawdzić to najpierw, podczas gdy z char[] musisz porównać znak po znaku.

 6
Author: Arkaitz Jimenez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-17 10:54:27

Ja osobiście nie widzę powodu, dla którego ktoś chciałby używać char * lub char [] poza zgodnością ze starym kodem. std:: string nie jest wolniejszy niż użycie C-string, poza tym, że będzie obsługiwał re-alokację dla Ciebie. Możesz ustawić jego rozmiar podczas tworzenia, a tym samym uniknąć ponownej alokacji, jeśli chcesz. Operator indeksowania ([]) zapewnia stały dostęp w czasie (i jest w każdym tego słowa znaczeniu dokładnie tym samym, co używanie indeksatora c-string). Zastosowanie metody at daje sprawdzone granice bezpieczeństwo też, coś czego nie dostajesz z c-stringami, chyba że to napiszesz. Twój kompilator najczęściej optymalizuje użycie indexera w trybie release. Łatwo jest zadzierać z ciągami c; rzeczy takie jak delete vs delete [], bezpieczeństwo WYJĄTKÓW, nawet jak ponownie przydzielić ciąg C.

A kiedy masz do czynienia z zaawansowanymi pojęciami, takimi jak posiadanie ciągów krowich i nie-krowich dla MT itp., Będziesz potrzebował std:: string.

Jeśli martwisz się o kopie, o ile używasz referencji i const reference gdziekolwiek możesz, nie będziesz miał żadnych kosztów z powodu kopii, i to jest to samo, co robisz z ciągiem C.

 5
Author: Abhay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-17 11:04:04

Łańcuchy mają funkcje pomocnicze i automatycznie zarządzają tablicami znaków. Można łączyć łańcuchy znaków, dla tablicy znaków należy skopiować ją do nowej tablicy, łańcuchy mogą zmieniać swoją długość w czasie wykonywania. Tablica znaków jest trudniejsza do zarządzania niż łańcuch znaków, a niektóre funkcje mogą akceptować tylko łańcuch znaków jako dane wejściowe, co wymaga przekonwertowania tablicy na łańcuch znaków. Lepiej jest użyć ciągów, zostały one wykonane tak, że nie trzeba używać tablic. Gdyby tablice były obiektywnie lepsze nie mielibyśmy struny.

 1
Author: theo2003,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-09 13:46:35

Think of (char *) as string.begin (). Zasadnicza różnica polega na tym, że (char *) jest iteratorem, a std:: string jest kontenerem. Jeśli trzymasz się podstawowych ciągów a (char *) da ci to, co robi std::string::iterator. Możesz użyć (char *), gdy chcesz korzystać z iteratora i kompatybilności z C, ale to jest wyjątek, a nie reguła. Jak zawsze, uważaj na unieważnienie iteratora. Kiedy ludzie mówią, że (char *) nie jest bezpieczne, To właśnie mają na myśli. Jest tak samo bezpieczny jak każdy inny C++ iterator.

 0
Author: Samuel Danielson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-09 19:28:49

Jedną z różnic jest zakończenie zerowe (\0).

W C i C++, char * lub char[] pobierze wskaźnik do pojedynczego znaku jako parametru i będzie śledzić wzdłuż pamięci aż do osiągnięcia wartości 0 pamięci (często nazywanej terminatorem null).

Ciągi C++ mogą zawierać osadzone znaki \0, znać ich długość bez zliczania.

#include<stdio.h>
#include<string.h>
#include<iostream>

using namespace std;

void NullTerminatedString(string str){
   int NUll_term = 3;
   str[NUll_term] = '\0';       // specific character is kept as NULL in string
   cout << str << endl <<endl <<endl;
}

void NullTerminatedChar(char *str){
   int NUll_term = 3;
   str[NUll_term] = 0;     // from specific, all the character are removed 
   cout << str << endl;
}

int main(){
  string str = "Feels Happy";
  printf("string = %s\n", str.c_str());
  printf("strlen = %d\n", strlen(str.c_str()));  
  printf("size = %d\n", str.size());  
  printf("sizeof = %d\n", sizeof(str)); // sizeof std::string class  and compiler dependent
  NullTerminatedString(str);


  char str1[12] = "Feels Happy";
  printf("char[] = %s\n", str1);
  printf("strlen = %d\n", strlen(str1));
  printf("sizeof = %d\n", sizeof(str1));    // sizeof char array
  NullTerminatedChar(str1);
  return 0;
}

Wyjście:

strlen = 11
size = 11
sizeof = 32  
Fee s Happy


strlen = 11
sizeof = 12
Fee
 0
Author: Eswaran Pandi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-10 07:05:06