Строки С++: [] vs. *

Думал, какая разница между объявлением переменной с [] или *? Как я его вижу:

char *str = new char[100];
char str2[] = "Hi world!";

.. должно быть главным отличием, хотя Im unsure, если вы можете сделать что-то вроде

char *str = "Hi all";

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

В любом случае, что действительно меня беспокоит, я знаю разницу между:

void upperCaseString(char *_str) {};
void upperCaseString(char _str[]) {};

Итак, было бы очень признательно, если бы кто-нибудь мог сказать мне разницу? У меня есть догадка, что оба могут быть скомпилированы одинаково, за исключением некоторых особых случаев?

Ти

Ответ 1

Посмотрим на него (для следующего, обратите внимание, что char const и const char совпадают в С++):

Строковые литералы и char *

"hello" - массив из 6 константных символов: char const[6]. Как и каждый массив, он может неявно преобразовывать указатель на свой первый элемент: char const * s = "hello"; Для совместимости с C-кодом С++ допускает другое преобразование, которое иначе было бы плохо сформировано: char * s = "hello"; он удаляет const!. Это исключение, чтобы этот код C-ish был скомпилирован, но он устарел, чтобы сделать char * ссылкой на строковый литерал. Итак, что у нас есть для char * s = "foo";?

"foo"array-to-pointerchar const*qualification-conversionchar *. Строковый литерал доступен только для чтения и не будет выделен в стеке. Вы можете свободно указывать на них указатель и возвращать его из функции без сбоев:).

Инициализация массива с использованием строкового литерала

Теперь, что такое char s[] = "hello";? Это совсем другое. Это создаст массив символов и заполнит его строкой "hello". Литерал не указывается. Вместо этого он копируется в массив символов. И массив создается в стеке. Вы не можете корректно вернуть указатель на него из функции.

Типы параметров массива.

Как вы можете заставить вашу функцию принимать массив в качестве параметра? Вы просто объявляете свой параметр как массив:

void accept_array(char foo[]); 

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

void accept_array(char * foo);

Экскурсия: многомерные массивы

Подставлять char любым типом, включая сами массивы:

void accept_array(char foo[][10]);

принимает двумерный массив, последний размер которого имеет размер 10. Первый элемент многомерного массива является его первым подматрицей следующего измерения! Теперь изменим его. Он снова будет указателем на его первый элемент. Таким образом, на самом деле он примет указатель на массив из 10 символов: (удалите [] в голове, а затем просто создайте указатель на тип, который вы видите в голове):

void accept_array(char (*foo)[10]);

Поскольку массивы неявно преобразуются в указатель на их первый элемент, вы можете просто передать в него двумерный массив (последний размер которого равен 10), и он будет работать. Действительно, это случай для любого n-мерного массива, включая специальный случай n = 1;

Заключение

void upperCaseString(char *_str) {}; 

и

void upperCaseString(char _str[]) {};

совпадают, так как первый - это просто указатель на char. Но обратите внимание, если вы хотите передать строковый литерал к этому (скажем, он не меняет свой аргумент), тогда вы должны изменить параметр на char const* _str, чтобы не делать устаревшие вещи.

Ответ 2

Три разных объявления позволяют указателю указывать на разные сегменты памяти:

char* str = new char[100];

позволяет str указывать на кучу.

char str2[] = "Hi world!";

помещает строку в стек.

char* str3 = "Hi world!";

указывает на сегмент данных.

Два объявления

void upperCaseString(char *_str) {};
void upperCaseString(char _str[]) {};

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

Ответ 3

Хорошо, я оставил два отрицательных комментария. Это не очень полезно; Я удалил их.

  • Следующий код инициализирует указатель char, указывая на начало динамически распределенной части памяти (в куче.)

char *str = new char[100];

Этот блок можно освободить, используя delete [].

  • Следующий код создает массив char в стеке, инициализированный значением, заданным строковым литералом.

char [] str2 = "Hi world!";

Этот массив может быть изменен без проблем, что приятно. Так


str2[0] = 'N';
cout << str2;

должен печатать Ni world! на стандартный вывод, заставляя некоторых рыцарей чувствовать себя очень неудобно.

  • Следующий код создает указатель char в стеке, указывая на строковый литерал... Указатель может быть переназначен без проблем, но заостренный блок не может быть изменен (это undefined, например, segfaults под Linux.)

char *str = "Hi all";
str[0] = 'N'; // ERROR!
  • Следующие два объявления

void upperCaseString(char *_str) {};
void upperCaseString(char [] _str) {};

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

Однако все это задает вопрос: почему вы используете char * для выражения строк в С++?

Ответ 4

Как дополнение к уже полученным ответам, вы должны прочитать часто задаваемые вопросы по C в отношении массивов и указателей. Да, это часто задаваемые вопросы по C, а не С++ FAQ, но есть небольшое существенное различие между двумя языками в этой области.

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

Ответ 5

Также обратите внимание на http://c-faq.com/aryptr/aryptr2.html C-FAQ может показаться интересным для чтения.

Ответ 6

Первая опция динамически выделяет 100 байт.

Второй параметр статически выделяет 10 байт (9 для символа string + nul).

Ваш третий пример не должен работать - вы пытаетесь статически заполнить динамический элемент.

Что касается вопроса upperCaseString(), как только C-строка будет выделена и определена, вы можете выполнить итерацию через нее либо путем индексирования массива, либо с помощью указателя указателя, потому что массив - это просто удобный способ обернуть арифметику указателя в С.


(Что простой ответ - я ожидаю, что у кого-то будет авторитетный, сложный ответ из спецификации:))