строки в C ++ - PullRequest
       56

строки в C ++

8 голосов
/ 10 февраля 2009

У меня есть следующие вопросы относительно строк в C ++

1 >> какой вариант лучше (учитывая производительность) и почему?

1

string a;
a = "hello!";

OR

2

string *a;
a = new string("hello!");
...
delete(a);

2 >>

string a;
a = "less"; 
a = "moreeeeeee"; 

как именно управление памятью обрабатывается в c ++, когда большая строка копируется в меньшую строку? Строки c ++ изменчивы?

Ответы [ 9 ]

14 голосов
/ 10 февраля 2009

Почти никогда не нужно и не желательно говорить

string * s = new string("hello");

В конце концов, вы бы (почти) никогда не сказали:

int * i = new int(42);

Вместо этого вы должны сказать

string s( "hello" );

или

string s = "hello";

И да, строки C ++ являются изменяемыми.

8 голосов
/ 10 февраля 2009

Все, что делает наивный компилятор, будет следующим. Конечно, до тех пор, пока это не изменит поведение программы, компилятор может выполнить любую оптимизацию.

string a;
a = "hello!";

Сначала вы инициализируете, чтобы содержать пустую строку. (установите длину в 0 и одну или две другие операции). Затем вы назначаете новое значение, перезаписывая значение длины, которое уже было установлено. Может также потребоваться выполнить проверку, чтобы увидеть, насколько велик текущий буфер, и нужно ли выделять больше памяти.

string *a;
a = new string("hello!");
...
delete(a);

Для вызова new требуется, чтобы ОС и распределитель памяти находили свободный кусок памяти. Это медленно. Затем вы немедленно инициализируете его, поэтому вы ничего не назначаете дважды и не требуете изменения размера буфера, как в первой версии. Затем происходит что-то плохое, и вы забываете вызвать delete, и у вас возникает утечка памяти, в дополнение к к строке, которую выделять очень медленно. Так что это плохо.

string a;
a = "less"; 
a = "moreeeeeee";

Как и в первом случае, вы сначала инициализируете a, чтобы содержать пустую строку. Затем вы назначаете новую строку, а затем другую. Каждый из этих может потребовать вызова new, чтобы выделить больше памяти. Каждая строка также требует длины и, возможно, других внутренних переменных, которые должны быть назначены.

Обычно вы бы выделяли его так:

string a = "hello";

Одна строка, выполнить инициализацию один раз, а не сначала инициализацию по умолчанию, а затем присвоить нужное значение.

Это также сводит к минимуму ошибки, потому что у вас нет бессмысленной пустой строки в вашей программе. Если строка существует, она содержит требуемое значение.

Об управлении памятью, Google RAII. Короче говоря, строка вызывает new / delete для внутреннего изменения размера буфера. Это означает, что никогда не нужно выделять строку с новым. Строковый объект имеет фиксированный размер и предназначен для размещения в стеке, так что деструктор автоматически вызывается , когда выходит из области видимости. Затем деструктор гарантирует освобождение любой выделенной памяти. Таким образом, вам не нужно использовать новый / удалить в своем коде пользователя, что означает, что вы не потеряете память.

4 голосов
/ 10 февраля 2009

Есть ли конкретная причина, по которой вы постоянно используете назначение вместо инициализации? То есть почему ты не пишешь

string a = "Hello";

и др.? Это позволяет избежать конструкции по умолчанию и имеет больше смысла семантически. Создание указателя на строку только для ее размещения в куче никогда не имеет смысла, т. Е. Ваш случай 2 не имеет смысла и несколько менее эффективен.

Что касается вашего последнего вопроса, да, строки в C ++ являются изменяемыми, если не объявлено const.

2 голосов
/ 10 февраля 2009
string a;
a = "hello!";

2 операции: вызывает конструктор по умолчанию std: string (), а затем вызывает оператор :: =

string *a; a = new string("hello!"); ... delete(a);

только одна операция: вызывает конструктор std: string (const char *), но вы не должны забывать освободить указатель.

Как насчет строка а ("привет");

0 голосов
/ 11 февраля 2009

Ты же с Явы, да? В C ++ объекты обрабатываются так же (в большинстве случаев), как и базовые типы значений. Объекты могут жить в стеке или в статическом хранилище и передаваться по значению. Когда вы объявляете строку в функции, она выделяет в стеке столько байтов, сколько занимает строковый объект. Сам строковый объект использует динамическую память для хранения реальных символов, но это прозрачно для вас. Еще одна вещь, которую нужно помнить, это то, что когда функция завершается и объявленная вами строка больше не находится в области видимости, вся используемая память освобождается. Нет необходимости в сборке мусора (RAII - ваш лучший друг).

В вашем примере:

string a;
a = "less"; 
a = "moreeeeeee";

Это помещает блок памяти в стек и дает ему имя a, затем вызывается конструктор и инициализируется a пустой строкой. Компилятор хранит байты для "less" и "moreeeeeee" в (я думаю) разделе .rdata вашего exe. Строка a будет иметь несколько полей, таких как поле длины и символ * (я значительно упрощаю). Когда вы назначаете "less" для a, вызывается метод operator = (). Он динамически распределяет память для хранения входного значения, а затем копирует его. Когда вы позже назначаете "moreeeeeee" для a, снова вызывается метод operator = (), и он перераспределяет достаточно памяти для хранения нового значения при необходимости, затем копирует его во внутренний буфер.

Когда выходит строка из области a, вызывается деструктор строки и освобождается память, которая была динамически выделена для хранения фактических символов. Затем указатель стека уменьшается, и память, в которой хранится a, больше не находится в стеке.

0 голосов
/ 11 февраля 2009

Скорее всего

   string a("hello!");

быстрее, чем все остальное.

0 голосов
/ 10 февраля 2009

Создание строки непосредственно в куче обычно не очень хорошая идея, просто как создание базовых типов. Это не стоит того, поскольку объект может легко оставаться в стеке, и в нем есть все конструкторы копирования и оператор присваивания, необходимые для эффективного копирования.

У самого std: string есть буфер в куче, который может совместно использоваться несколькими строками в зависимости от реализации.

Для примера, с помощью Microsoft STL вы можете сделать это:

string a = "Hello!";
string b = a;

И обе строки будут использовать один и тот же буфер, пока вы его не измените:

a = "Something else!";

Вот почему было очень плохо хранить c_str () для последующего использования; Функция c_str () действует только до следующего вызова этого строкового объекта.

Это привело к очень неприятным ошибкам параллелизма, которые требовали выключения этой функции совместного использования с определением, если вы использовали их в многопоточном приложении

0 голосов
/ 10 февраля 2009

Если вы посмотрите на документы для класса строки STL (я считаю, что документы SGI соответствуют спецификации), многие из методов гарантируют сложность списка. Я полагаю, что многие из гарантий сложности намеренно оставлены расплывчатыми, чтобы разрешить разные реализации. Я думаю, что некоторые реализации на самом деле используют подход «копировать при изменении», так что назначение одной строки другой - это операция с постоянным временем, но вы можете столкнуться с непредвиденными затратами, когда попытаетесь изменить один из этих экземпляров. Не уверен, правда ли это в современном STL.

Вам также следует проверить функцию capacity(), которая сообщит вам строку максимальной длины, которую вы можете поместить в данный экземпляр строки, прежде чем он будет вынужден перераспределить память. Вы также можете использовать reserve(), чтобы вызвать перераспределение на определенную сумму, если вы знаете, что будете хранить большую строку в переменной в более позднее время.

Как уже говорили другие, что касается ваших примеров, вы должны действительно отдавать предпочтение инициализации по сравнению с другими подходами, чтобы избежать создания временных объектов.

0 голосов
/ 10 февраля 2009

В случае 1.1 ваша строка members (которая включает в себя указатель на данные) хранится в stack, и память, занятая экземпляром класса, освобождается, когда a выходит из области действия.

В случае 1.2 память для членов также динамически выделяется из кучи.

Когда вы присваиваете строковую константу char*, память, в которой будут храниться данные, будет realloc 'подгоняться под новые данные.

Вы можете увидеть, сколько памяти выделено, набрав string::capacity().

Когда вы вызываете string a("hello"), память выделяется в конструкторе.

И конструктор, и оператор присваивания вызывают одни и те же методы внутри выделенной памяти и копируют туда новые данные.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...