Різниця між типами string і char [] в C ++


126

Я знаю трохи C, і зараз я дивлюсь на C ++. Я звик до масивів char для роботи з рядками C, але переглядаючи код C ++, я бачу, що є приклади, що використовують як тип рядка, так і масиви char:

#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;
}

і

#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;
}

(обидва приклади з http://www.cplusplus.com )

Я припускаю, що це запитання з широкими відповідями (очевидно?), Але було б непогано, якби хтось міг сказати мені, в чому полягає різниця між цими двома способами для обробки рядків у C ++ (продуктивність, інтеграція API, спосіб кожного з них краще, ...).

Дякую.


Це може допомогти: C ++ char * vs std :: string
Wael Dalloul

Відповіді:


187

Масив char є саме цим - масивом символів:

  • Якщо виділено на стеку (як у вашому прикладі), він завжди займатиме, наприклад. 256 байт незалежно від того, який довгий текст він містить
  • Якщо виділено на купі (використовуючи malloc () або новий char []), ви несете відповідальність за звільнення пам'яті після цього, і ви завжди матимете накладні витрати розподілу купи.
  • Якщо ви скопіюєте в масив текст, що має більше 256 символів, він може вийти з ладу, створити некрасиві повідомлення про твердження або спричинити незрозумілу (неправильну) поведінку десь у вашій програмі.
  • Щоб визначити довжину тексту, масив повинен бути сканований, символом за символом, для символу \ 0.

Рядок - це клас, який містить масив char, але автоматично керує ним. Більшість рядкових реалізацій мають вбудований масив із 16 символів (тому короткі рядки не фрагментують купу) та використовують купу для довших рядків.

Ви можете отримати доступ до масиву символів рядка таким чином:

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

Рядки C ++ можуть містити вбудовані символи \ 0, знати їх довжину без підрахунку, швидші, ніж виділені купу масивів для коротких текстів і захищають вас від перевищення буфера. Плюс вони більш зручні для читання та простішого використання.


Однак рядки C ++ не дуже (дуже) підходять для використання через межі DLL, оскільки для цього потрібен будь-який користувач такої функції DLL, щоб переконатися, що він використовує той самий компілятор та реалізацію C ++ під час виконання, щоб не ризикувати, що його рядковий клас поводиться інакше.

Зазвичай, рядок рядків також звільнятиме свою пам’ять накопичувача на виклику, тому він знову зможе звільнити пам'ять лише за умови використання спільної (.dll або .so) версії виконання.

Якщо коротко: використовуйте рядки C ++ у всіх своїх внутрішніх функціях та методах. Якщо ви коли-небудь пишете .dll або .so, використовуйте рядки C у своїх загальнодоступних (dll / so-експонованих) функціях.


4
Крім того, струни мають купу допоміжних функцій, які можуть бути справді акуратними.
Оконь

1
Я не вірю в те, що стосується меж DLL. За дуже особливих обставин він може потенційно зламатися ((одна DLL статично пов'язується з іншою версією виконання, ніж використовується іншими DLL), і гірші речі, мабуть, трапляться спочатку в цих ситуаціях), але в загальному випадку, коли всі використовують за замовчуванням Спільна версія стандартного режиму виконання (за замовчуванням) цього не відбудеться.
Мартін Йорк

2
Приклад: Ви поширюєте компільовані VC2008SP1 бінарні файли публічної бібліотеки під назвою libfoo, яка має std :: string & у своєму публічному API. Тепер хтось завантажує ваш libfoo.dll і робить налагодження. Його std :: string цілком може мати в ньому деякі додаткові поля налагодження, що призводить до зміщення покажчика для переміщення динамічних рядків.
Cygon

2
Приклад 2: У 2010 році хтось завантажує ваш libfoo.dll і використовує його у своїй вбудованій програмі VC2010. Його код завантажує MSVCP100.dll, а ваш libfoo.dll все ще завантажує MSVCP90.dll -> ви отримуєте дві купи -> пам'ять не може бути звільнена, помилки твердження в режимі налагодження, якщо libfoo змінює посилання на рядок і передає std :: string з новим вказівник назад.
Cygon

1
Я просто дотримуюся "Коротше кажучи: використовуйте рядки C ++ у всіх своїх внутрішніх функціях та методах". Намагаючись зрозуміти ваші приклади, покоївка мого мозку.
Стівен

12

Аркаіц правильно, що stringце керований тип. Це означає для вас те, що вам ніколи не потрібно турбуватися про те, як триває струна, і вам не потрібно турбуватися про звільнення або перерозподіл пам'яті струни.

З іншого боку, char[]позначення у верхньому випадку обмежили буфер символів рівним рівнем 256 символів. Якщо ви спробували записати більше 256 символів у цей буфер, у кращому випадку ви перезаписаєте іншу пам'ять, якою володіє ваша програма. У гіршому випадку ви спробуєте перезаписати пам'ять, якою ви не володієте, і ваша ОС вбить вашу програму на місці.

Нижня лінія? Струни набагато більш зручні для програмістів, char [] - набагато ефективніші для комп'ютера.


4
У гіршому випадку, інші люди замінять пам’ять і запустять шкідливий код на вашому комп’ютері. Дивіться також переповнення буфера .
Девід Джонстоун

6

Ну, тип рядка - це повністю керований клас для символьних рядків, тоді як char [] - це все-таки те, що було в C, байтовий масив, що представляє для вас символьну рядок.

Що стосується API та стандартної бібліотеки, все реалізовано у вигляді рядків, а не char [], але є ще багато функцій від libc, які отримують char [], тож вам може знадобитися використовувати їх для тих, окрім того, що я б завжди використовувати std :: string.

З точки зору ефективності, звичайний буфер некерованої пам'яті майже завжди буде швидшим для багатьох речей, але врахуйте, порівнюючи рядки, наприклад, std :: string завжди має розмір, щоб перевірити його спочатку, тоді як з char [] ви потрібно порівнювати персонаж за характером.


5

Я особисто не бачу жодної причини, чому б хотілося використовувати char * або char [], крім сумісності зі старим кодом. std :: string не повільніше, ніж використання c-string, за винятком того, що він буде обробляти перерозподіл для вас. Ви можете встановити його розмір під час його створення, і таким чином уникнути повторного розподілу, якщо хочете. Це оператор індексації ([]) забезпечує постійний доступ у часі (і в будь-якому сенсі цього слова є точно тим же, що і за допомогою індексатора c-string). Використання методу at також дає змогу перевірити безпеку меж, чого ви не отримаєте із c-рядками, якщо ви не пишете це. Ваш компілятор найчастіше оптимізує використання індексатора в режимі випуску. Легко возитися з c-струнами; такі речі, як delete vs delete [], виняток безпеки, навіть як перерозподілити c-рядок.

І коли вам доведеться мати справу з передовими концепціями, такими як наявність рядків COW та non-COW для MT тощо, вам знадобиться std :: string.

Якщо ви переживаєте за копії, якщо ви використовуєте посилання та посилання const, де тільки зможете, у вас не буде накладних витрат через копії, і це те саме, що ви робили з c-рядком.


+1 Хоча ви не розглядали проблеми впровадження, такі як сумісність з DLL, ви отримали COW.

а що я знаю, що мій масив char у 12 байтах? Якщо я інстантую рядок для цього, це може бути не дуже ефективно?
Девід 天宇 Вонг

@David: Якщо у вас дуже чутливий код до перф, тоді так. Ви можете розглянути std :: string ctor call як накладні витрати на додаток до ініціалізації елементів std :: string. Але пам’ятайте, що передчасна оптимізація зробила безліч баз коду без необхідності в стилі C, тому будьте обережні.
Абхай

1

Струни мають допоміжні функції та керують масивами символів автоматично. Ви можете об'єднати рядки, для масиву char вам потрібно буде скопіювати його в новий масив, рядки можуть змінити їх довжину під час виконання. Масивом char важче керувати, ніж рядок, і деякі функції можуть приймати лише рядок як вхідний, вимагаючи від вас перетворити масив у рядок. Краще використовувати рядки, вони були зроблені так, що вам не доведеться використовувати масиви. Якби масиви були об'єктивно кращими, у нас не було б рядків.


0

Подумайте про (char *) як string.begin (). Істотна відмінність полягає в тому, що (char *) є ітератором, а std :: string - контейнером. Якщо ви будете дотримуватися основних рядків a (char *), ви отримаєте те, що робить std :: string :: iterator. Ви можете використовувати (char *), коли хочете отримати перевагу ітератора, а також сумісність із C, але це виняток, а не правило. Як завжди, будьте обережні щодо недійсності ітератора. Коли люди кажуть (char *) не безпечно, це те, що вони означають. Це так само безпечно, як і будь-який інший ітератор C ++.


0

Одна з різниць - Нульове припинення (\ 0).

У C і C ++ char * або char [] буде приймати покажчик на один char як параметр і буде відслідковувати вздовж пам'яті, поки не буде досягнуто значення пам'яті 0 (часто називається нульовим термінатором).

Рядки C ++ можуть містити вбудовані \ 0 символів, знати їх довжину, не рахуючи.

#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;
}

Вихід:

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


strlen = 11
sizeof = 12
Fee

"з конкретних, всі символи видаляються" ні, вони не "видаляються", друкуючи покажчик символів, друкується лише до нульового термінатора. (оскільки це єдиний спосіб, коли char * знає кінець) клас рядка знає сам повнорозмірний розмір, тому він просто використовує це. якщо ви знаєте розмір вашої картки *, ви можете також друкувати / використовувати всі символи.
Калюжа
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.