Указатели и строки С++
Я учу себя С++, и я немного запутался в указателях (в частности, в следующем исходном коде). Но сначала я продолжаю показывать вам то, что знаю (а затем противопоставляя код этому, потому что чувствую, что происходят какие-то противоречия).
Что я знаю:
int Age = 30;
int* pointer = &Age;
cout << "The location of variable Age is: " << pointer << endl;
cout << "The value stored in this location is: " << *pointer << endl;
Указатели содержат адреса памяти. Используя оператор косвенности (разыменования) (*), вы можете получить доступ к тому, что хранится в ячейке памяти указателя. На код в этой книге у меня возникли проблемы с пониманием...
cout << "Enter your name: ";
string name;
getline(cin, name); //gets full line up to NULL terminating character
int CharsToAllocate = name.length() + 1; //calculates length of string input
//adds one onto it to adjust for NULL character
char* CopyOfName = new char[CharsToAllocate];
// pointer to char called CopyOfName, is given the memory address of the
//beginning of a block
//of memory enough to fit CharsToAllocate. Why we added 1? Because char need a
//NULL terminating character (\0)
strcpy(CopyOfName, name.c_str()); //copies the string name, into a pointer?
cout << "Dynamically allocated buffer contains: " << CopyOfName << endl;
delete[] CopyOfName; //always delete a pointer assigned by new to prevent memory leaks
Вывод:
Enter your name: Adam
Dynamically allocated buffer contains: Adam
Комментарии в приведенном выше коде являются моими комментариями. Моя проблема начинается с strcpy
. Почему name.c_str()
скопирован в указатель CopyOfName
? Означает ли это, что все строки являются важными указателями? Так что string testing = "Hello world";
На самом деле это указатель, указывающий на место памяти, где хранится "H"?
Затем, почему это делается в инструкции print, используя CopyOfName
, а не *CopyOfName
? Указатели содержат адреса памяти? Использование *CopyOfName
распечатает содержимое ячейки памяти. Я пробовал это в Code:: Blocks, и если входной текст был "Hello World". Использование *CopyOfName
в заявлении распечатки просто даст "H". Это имеет смысл, поскольку, когда я объявил, что мне нужен блок памяти с "новой" вещью, это фактически возвращает указатель на первую часть динамически выделенного блока памяти.
Единственный способ, которым я могу смириться, это то, что строка на самом деле является указателем.
string testing = "Confused";
cout << testing << endl;
выведет слово "Confused"
Однако, если я попытаюсь скомпилировать
string testing = "Confused";
cout << *testing;
Появляется сообщение об ошибке.
В принципе, чтобы обобщить мой вопрос, я пытаюсь понять код с помощью strcpy
и оператора cout
.
Ответы
Ответ 1
Похоже, вы понимаете, что такое строки C-стиля, но суммировать, это всего лишь массивы символов в памяти, по соглашению, заканчивающимся символом nul \0
. Обычно они ссылаются через char*
, указывающие на первую букву в строке. Когда они печатаются, как правило, символы строки печатаются начиная с первого, и печать (или копирование и т.д.) Останавливается, когда достигается терминатор \0
.
An std::string
- это класс, который (обычно) обертывает строку стиля C. Это означает, что объект std::string
(обычно) имеет частную строку стиля C, которая используется для реализации ее функциональности. Функция std::string::c_str()
возвращает указатель на эту базовую строку C-стиля.
Предположим, что char *str;
указывает на строку C-стиля. Если вы попытаетесь запустить cout << *str << endl;
, вы заметили, что печатается только первый символ. Это из-за перегрузки функций С++. Тип данных *str
равен char
, поэтому вызывается char
версия cout
и верно печатает единственный символ *str
. Для совместимости со строками типа C версия cout
, которая принимает char*
в качестве аргумента, рассматривает указатель как строку стиля C для целей печати. Если вы cout
a int*
, например, базовый int
не будет напечатан.
Изменить: Еще один комментарий:
Причина, по которой ваша попытка разыменовать объект std::string
не удалась, заключается в том, что, действительно, это не указатель. Вы можете разыменовать возвращаемое значение std::string::c_str()
, и вы вернете первый char
строки.
Связано: Как реализовано std::string?.
Ответ 2
В C строки - это просто массивы символов. И массивы распадаются на указатели при использовании в качестве аргумента функции.
В С++ std::string
- это класс. Он включает в себя массив символов C-стиля внутри, и это то, что возвращает c_str()
. Но сама строка не указатель, поэтому вы не можете ее разыгрывать; вы должны использовать метод c_str()
, чтобы получить указатель на содержимое строки.
Ответ 3
Так как string testing = "Hello world";
На самом деле это указатель, указывающий на ячейку памяти, где хранится "H"?
Нет, над вами есть объект с именем string
. Это верно для char* testing = "Hello World"
. Как вы можете видеть, он даже объявлен как указатель и указывает на первый символ в строке - H.
Далее, почему в инструкции print out CopyOfName
нет *CopyOfName
? Указатели содержат адреса памяти? Использование *CopyOfName
распечатает содержимое ячейки памяти. Я пробовал это в блоках кода, и если входной текст был "Hello World". Использование *CopyOfName
в инструкции распечатки просто даст "H"
cout принимает указатель на первый символ строки, поэтому CopyOfName
прав. В этом случае он будет печатать каждый символ, начиная с H, пока не найдет \0 (нулевой символ). Строки типа "hello" имеют на самом деле 6 символов - "h" 'e' 'l' 'l' 'o' '\ 0'
Когда вы пишете *CopyOfName
, вы разыгрываете этот указатель, а *CopyOfName
на самом деле является только одним символом
Ответ 4
Отвечая на ваши вопросы в порядке:
"Почему имя .c_str() скопировано в указатель CopyOfName? Означает ли это что все строки являются важными указателями? Так, например, тестирование строк =" Привет мир "; На самом деле это указатель, указывающий на расположение памяти где хранится" Н"?
Как указал Юй Хао в своем комментарии, важно понять разницу между строками стиля C++ и строками типа С. В первом случае вы имеете дело с "непрозрачным" объектом, тогда как в последнем случае вы в основном имеете дело с "массивом" символов.
С строковыми объектами С++ вы можете использовать метод c_str()
, чтобы получить (указатель на) массив символов C-стиля. В C массив представлен с использованием указателя на начало массива, а затем ссылки достигаются путем подачи смещения (индекса в массив) от этого начального адреса. Таким образом, ответ на последний вопрос в этом пакете "да", указатель на строку C-стиля является указателем на первый символ "H".
"Далее, почему в заявлении для печати указано, что CopyOfName не является * CopyOfName? Указатели имеют адреса памяти?"
Поскольку оператор <<
перегружен для обработки C-строк. Реализация этого метода "знает, что делать с" указателем.
Ответ 5
Указатели не совпадают с массивами. Строковые литералы неизменяемы, и когда у вас есть указатель на строковый литерал, вы можете проверить его содержимое, но их изменение - это поведение undefined. При использовании этого синтаксиса:
char arr[] = "hi there";
Строковый литерал копируется в массив. Поскольку вы не указываете размер, компилятор автоматически выводит его. Терминатор NUL
также автоматически добавляется. Если вы укажете размер, вы должны убедиться, что буфер может содержать терминатор NUL
. Поэтому:
char arr[5] = "hello";
- ошибка. Если вы используете синтаксис инициализатора привязки:
char arr[5] = { "h", "e", "l", "l", "o" };
Это ошибка, потому что нет терминатора NUL
. Если вы используете strcpy
, для вас будет добавлен терминатор NUL
.
std::string
предоставляет два метода возврата указателя на его содержимое: data
и c_str
. Pre-С++ 11, единственное отличие - data
не включает терминатор NUL
. В С++ 11 теперь это делается, поэтому их поведение идентично. Поскольку указатель может быть легко признан недействительным, небезопасно манипулировать этими указателями. Также небезопасно делать char * ptr = str.c_str();
, потому что время жизни массива, возвращаемого c_str
, умирает в точке с запятой. Вам нужно скопировать его в буфер.
Ответ 6
Вы задаете правильные вопросы как ученик.
Ответы:
- В С++,
string
- это объект, c_str()
по существу возвращает указатель на первый
символ строки (стиль C)
- Вы правы в строках в C, переменная на самом деле указывает на первый символ
строка
- С++ делает много вещей, основанных на типе переменной. Когда вы передаете объект
string
cout
печатает строку. Кроме того, С++ достаточно умен, чтобы определить, что *testing
является незаконным
Ответ 7
Почему имя .c_str() скопировано в указатель CopyOfName?
"name" - это строка STL. Это объект, который отличается от c-строки. С-строка представляет собой набор памяти, который содержит символы и имеет нулевое завершение. Итак, если вы используете STL-строки и хотите превратить их в c-строки, вы используете .c_str() для получения c-строки.
CopyOfName содержит достаточное количество памяти для хранения имени, поскольку оно было выделено для его хранения.
cout имеет TON разных вещей, которые вы можете использовать с < <. Похоже, что он может принимать char * (которые являются c-строками) или строками STL. Не похоже, что он может принимать указатели на строки STL.
Я немного смутился, когда вы представили "тестирование", но я думаю, что вы путаетесь между c-строками (которые являются char *) и строками STL, которые являются объектами. Не чувствуй себя плохо или не сдавайся. Этот материал сложный и занимает некоторое время, чтобы добраться.
Я бы рекомендовал попробовать и понять разные термины "c-string", "char *", "stl string" и, возможно, "указатель на stl string".
Ответ 8
В C, где стандартные строки С++ не существовали, char * была так называемой "строкой". Как вы отметили, это массив символов, заканчивающийся символом NULL. Почти любая стандартная библиотечная функция, которая принимает строку C-стиля, примет указатель на указанную строку по двум причинам:
- Легче думать о строке C-Style в целом, а не о наборе символов, в отличие от других массивов, поэтому с помощью указателя сохраняется эта идея
- Это самый простой способ взять массив как параметр функции, чтобы просто получить указатель на первый элемент, особенно в случае C-строк, где их можно просто прочитать до символа NULL.
Ответ 9
Я думаю, что вы делаете, а другие, пожалуйста, поправьте меня, если я ошибаюсь, это то, что вы копируете свою строку в динамический массив char. Так что вы не копируете его в указатель. Причина, по которой используется указатель, заключается в том, что динамические массивы требуют указателей, чтобы правильно распределять их память, если я прав.