Почему указателю на массив char нужен strcpy для назначения символов его массиву, а назначение двойных кавычек не будет работать?
Первый пример не работает, когда вы пытаетесь удалить указатель. Программа зависает, когда я добавляю нулевой терминатор или без него, я получаю:
Debug Assertion Failed Expression: _BLOCK_TYPE_IS_VALID(pHead->nBlockUse)
из Visual Studio 2008
//Won't work when deleting pointer:
char *at = new char [3];
at = "tw"; // <-- not sure what going on here that strcpy does differently
at[2] = '\0'; // <-- causes program to hang
delete at;
//Works fine when deleting pointer:
char *at = new char [3];
strcpy(at,"t");
at[1] = 'w';
at[2] = '\0';
delete at;
Итак, что происходит, когда я использую двойные кавычки вместо strcpy? Оба из них отлично справятся с строкой, а отладчик не покажет ничего другого.
Ответы
Ответ 1
Потому что char*
не является строкой. Это просто указатель на какой-то символ, с соглашением о том, что может быть больше персонажей, и что после последнего есть '\0'
.
Строковый литерал в C (и, следовательно, на С++), такой как "abc"
, представляет собой всего лишь массив символов, при этом компилятор молча добавляет '\0'
. Когда вы назначаете массив указателю, массив молча преобразует указатель в первый элемент. В результате
at = "tw";
означает, что указателю at
присваивается адрес первого символа в строковом литерале "tw"
. Таким образом, он потеряет свою старую ценность. Поскольку это был адрес динамически выделенного массива символов, вы пропускаете этот массив.
Когда вы позже назначаете символ в массиве at
теперь указывает на, вы назначаете новое значение некоторому символу в строковом литерале. То, что вызывает поведение undefined, и программа, зависающая или сбой немедленно, вероятно, лучшая, что может случиться с вами, когда вы это сделаете. (На многих платформах, которые вы пишете в память только для чтения).
Позже вы пройдете at
до delete[]
(и not delete
, так как вы вызвали new[]
, а не new
). При этом вы передаете ему адрес строкового литерала вместо выделенного массива символов. Это, конечно, испортит менеджер кучи. (Библиотека времени выполнения VC ловит это в режиме отладки.)
std::strcpy
, с другой стороны, копирует строковый символ по символу из одного массива в другой массив. Никакие указатели не будут изменены, копируются только фрагменты памяти. Указатель на целевой массив по-прежнему указывает на целевой массив впоследствии, только данные в этом массиве изменились.
Позвольте мне добавить это: Как новичок в С++, вы должны использовать std::string
, а не строки C. Это делает всю грязную работу для вас и имеет разумную семантику.
Ответ 2
Когда вы делаете
char *at = ...;
at = "hello";
В основном вы переписываете значение указателя (т.е. адрес выделенной вам памяти new[]
) с адресом статической константной строки. Это означает, что когда вы позже удалите эту память, вы передаете delete
указатель, который ранее не возвращался new
.
Это плохо, что нужно делать.
В C и С++ назначения указателям обычно ничего не делают для указателя на память, они меняют сам указатель. Это может сбивать с толку, если вы привыкли к языку, на котором строки являются более "гражданами первого класса".
Кроме того, вы должны использовать delete[]
, если вы использовали new[]
.
Ответ 3
Есть три вещи для понимания:
1) char *at;
- это просто указательная переменная.
Переменная указателя просто означает, что она содержит адрес памяти.
2) new char[3]
возвращает начальный адрес памяти, выделенной в куче.
3) "hello"
возвращает адрес строкового литерала.
char *at = new char [3];
//at now contains the address of the memory allocated on the heap
at = "hello";
//at now contains the address of the static string.
// (and by the way you just created a 3 byte memory leak)
delete[] at;
//WOOPS!!!! you can't do that because you aren't deleting
// the original 3 chars anymore which were allocated on the heap!
//Since at contains the string literal memory address you're
// trying to delete the string literal.
Заметка об изменении памяти только для чтения:
Также вы никогда не должны изменять строковый литерал. То есть это никогда не должно быть сделано:
char *at = "hello";
at[2] = '\0';
Память для строковых литералов должна быть только для чтения, и если вы ее измените, результаты будут undefined на языке С++.
Поскольку вы используете С++:
Так как вы используете С++, используйте вместо этого тип std::string
.
#include <string>
using namespace std;
int main(int argc, char **argv)
{
string s = "hello";
s += " world!";
//s now contains "hello world!"
s = "goodbye!";
//Everything is still valid, and s contains "goodbye!"
//No need to cleanup s.
return 0;
}
Ответ 4
Не забудьте использовать
delete []
всякий раз, когда вы выделяете что-то с помощью [].
Ответ 5
Указатель содержит адрес. Оператор = для указателя изменяет адрес.
at = "tw";
Делает точку в массиве "tw" (массив, созданный компилятором для хранения символов tw), он больше не указывает на массив, который вы создали с новым. созданный в файле.
at[2] = '\0';
Добавляет NULL в конец массива complier.
Ответ 6
В первом примере вы перебираете значение на, во втором вы меняете значение того, что находится в точках. Присвоение строки char * в двойную кавычку присваивает ее статическому указателю const.
В частности, в первом примере теперь указывает другое место в памяти.
Ответ 7
В первом примере вы выделяете некоторую память и указываете на нее с переменной "at". Когда вы делаете
at = "tw"
вы эффективно перенаправляете char * в строку с постоянным символом. Это приведет к утечке памяти. Когда вы продолжаете удалять "at", вы пытаетесь удалить стек стека.
strcpy проходит через каждый символ и копирует их значения в новую память, которую вы выделяете. Это также называется глубокой копией.
Ответ 8
В первом примере вы обнаружили утечку памяти.
Ваша переменная at
- это указатель на адрес памяти, а не сама строка. Когда вы назначаете адрес "tw"
указателю, вы потеряли исходный адрес, который вы получили с помощью new
. at
теперь указывает на адрес, который вы не выделили с помощью new
, поэтому вы не можете delete
его.
Если вы думаете о указателях как целые числа, это, вероятно, будет иметь больше смысла. Я назначил произвольные числа в качестве адресов для обсуждения.
char *at = new char[3]; // 0x1000
at = "tw"; // 0x2000
at[2] = '\0'; // set char at 0x2002 to 0
delete at; // delete 0x2000 (whoops, didn't allocate that!)
Ответ 9
Вы допускаете две вещи: указание указателя на что-то другое (это то, что присваивание) и копирование некоторых данных в место, указанное указателем.
at = "tw";
этот код делает at
указывать на литерал "tw", созданный где-то в постоянной памяти. Попытка написать ему поведение undefined.
char *at = new char [3];
strcpy(at,"t");
этот код выделяет память для трех символов и делает at
указывать на эту часть памяти (строка 1), а затем копирует некоторые данные в память, на которые указывает at
.
И помните, что память, выделенная с помощью new[]
, должна быть освобождена с помощью delete[]
, а не delete
Я советую вам узнать больше о указателях. Это обсуждение охватывает это.