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

Задумывались, в чем разница между объявлением переменной с помощью [] или *? На мой взгляд:

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

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

char *str = "Hi all";

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

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

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

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

Ty

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
18
0
4 113
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

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

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

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

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


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

Третий пример работает, он будет указывать на постоянную память, содержащую строку. Однако если вы попытаетесь его изменить, у вас возникнут проблемы.

Graeme Perrow 21.11.2008 16:48

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

warren 21.11.2008 17:44

# 3 предупреждает в GCC с помощью -Wwrite-strings, но это не входит ни в -Wall, ни в -Wextra. literal -> non-const char *, я думаю, устарел, поэтому предупреждение является разумным.

Steve Jessop 21.11.2008 21:16

У меня довольно высокий уровень ошибок / предупреждений, так что это может быть поведение, которое, как я вижу, не по умолчанию :)

warren 22.11.2008 13:28

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

char* str = new char[100];

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

char str2[] = "Hi world!";

кладет строку в стек.

char* str3 = "Hi world!";

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

Две декларации

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

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

Ответ принят как подходящий

Давайте посмотрим на это (обратите внимание, что char const и const char одинаковы в C++):

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

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

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

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

Итак, что такое 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, чтобы не делать устаревшие вещи.

Знаете, я никогда не тратил время на то, чтобы понять сложность объявления массива [] и указателя. Я всегда просто использовал массивы или указатели с явной длиной. Спасибо!

Nick Bedford 02.10.2009 02:35

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

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

Это не правда. Символы с начальным подчеркиванием, за которым следует заглавная буква, зарезервированы для реализации. Подчеркивание с последующим нижним регистром - это нормально, по крайней мере, в C++. Я не проверял стандарт C, но он резервирует всевозможные вещи, включая имена функций, начинающиеся с s или w IIRC.

Steve Jessop 21.11.2008 21:09

Хорошо, чтобы прояснить это правило, похоже, что ведущие подчеркивания, за которыми следует заглавная буква, всегда зарезервированы, а ведущие подчеркивания, за которыми следует строчная буква, зарезервированы только в std :: и в глобальном пространстве имен, поэтому ведущее подчеркивание в член на самом деле в порядке.

Tyler McHenry 22.11.2008 05:24

"зарезервировано только в std :: and ::". Я не совсем понимаю это, поскольку вы все равно не можете добавлять символы в std :: (то есть все пространство имен зарезервировано для реализации).

Steve Jessop 22.11.2008 18:33

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

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

char *str = new char[100];

Этот блок можно освободить с помощью delete [].

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

char [] str2 = "Hi world!";

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


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

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

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

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

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

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

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

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

Другие вопросы по теме