Разница между типами string и char [] в C ++

126

Я немного знаю C, а теперь смотрю на C ++. Я привык к массивам символов для работы со строками 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, способ каждого из них лучше, ...).

Спасибо.

ramosg
источник
Это может помочь: C ++ char * vs std :: string
Wael Dalloul

Ответы:

187

Массив char - это просто массив символов:

  • Если он выделен в стеке (как в вашем примере), он всегда будет занимать, например. 256 байт независимо от длины содержащегося в нем текста
  • Если выделено в куче (с помощью malloc () или new char []), вы несете ответственность за последующее освобождение памяти, и у вас всегда будут накладные расходы на выделение кучи.
  • Если вы скопируете текст, состоящий из более чем 256 символов, в массив, он может дать сбой, создать уродливые сообщения с утверждениями или вызвать необъяснимое (неправильное) поведение где-то еще в вашей программе.
  • Чтобы определить длину текста, необходимо просканировать массив посимвольно на наличие символа \ 0.

Строка - это класс, который содержит массив символов, но автоматически управляет им за вас. Большинство реализаций строк имеют встроенный массив из 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-visible) функциях.

Cygon
источник
4
Кроме того, у строк есть множество вспомогательных функций, которые могут быть действительно полезными.
Håkon
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-строки, за исключением того, что он будет обрабатывать перераспределение за вас. Вы можете установить его размер при его создании и, таким образом, избежать перераспределения, если хотите. Его оператор индексации ([]) обеспечивает постоянный доступ по времени (и во всех смыслах этого слова то же самое, что и использование индексатора c-строк). Использование метода at также дает вам проверенные границы безопасности, чего вы не получите с c-строками, если вы его не напишете. Ваш компилятор чаще всего оптимизирует использование индексатора в режиме выпуска. С до-струнами легко возиться; такие вещи, как delete vs delete [], безопасность исключений, даже как перераспределить c-строку.

И когда вам нужно иметь дело с продвинутыми концепциями, такими как наличие строк COW и не-COW для MT и т. Д., Вам понадобится std :: string.

Если вы беспокоитесь о копиях, пока вы используете ссылки и ссылки на константы везде, где можете, у вас не будет никаких накладных расходов из-за копий, и это то же самое, что и с c-строкой.

Абхай
источник
+1 Хотя вы не учли такие вопросы реализации, как совместимость с DLL, но у вас COW.
как насчет того, что я знаю, что мой массив символов в 12 байтов? Если я создаю для этого экземпляр строки, это может быть не очень эффективно, верно?
Дэвид 天宇 Вонг
@David: Если у вас очень чувствительный к производительности код, тогда да. Вы можете рассматривать вызов std :: string ctor как накладные расходы в дополнение к инициализации членов 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 * знает конец), строковый класс сам знает полный размер, поэтому он просто использует его. если вы знаете размер вашего char *, вы также можете распечатать / использовать все символы.
Puddle