@Anvario0

Чем обусловлены различия в работе со строками и другими массивами?

Учу C++ и на данном моменте на теме "Строки". Мне объяснили, что по своей сути строка такой же массив как все прочие, но я заметил некоторые особенности, которые не могу объяснить для себя.
Например, мы можем инициализировать массив так:
char str[] = "Hello";
Или, что показалось мне странным, вот так:
const char* str = "Hello";
Но при этом вот такая запись неприемлима:
const int* mas = { 4, 5, 7, 9 };
Да и почему во втором способе инициализации строки мы присваиваем указателю массив? Указателю ведь нужно присваивать адрес?
Так же меня смутила запись массива строк:
const char* strArr[] = { "Hello", "world", "and" };

Это ведь по сути объявление и инициализация двумерного массива, верно? Но при работе с другими массивами такое недопустимо, почему?
Может, это просто такие особенности и их стоит просто принять?
И ещё хотелось бы понять, почему каждый такой указатель является константой?
  • Вопрос задан
  • 224 просмотра
Пригласить эксперта
Ответы на вопрос 5
Adamos
@Adamos
Это наследство С.
Используйте STL - и const std::vector< int > mas = { 4, 5, 7, 9 };
скомпилируется без проблем.
Вообще, больше используйте STL и меньше - массивы и строки в С-стиле. Во всяком случае, пока не освоитесь в языке настолько, чтобы лезть глубже.
Например, вопрос о константности указателя говорит о непонимании того, что на самом деле происходит в памяти.
Ответ написан
mayton2019
@mayton2019
Bigdata Engineer
И ещё хотелось бы понять, почему каждый такой указатель является константой?


Это - тяжелое наследие старых систем. В данном примере ты разбираешся с ASCIIZ-строками. С массивами символов которые завершаются нулем. Такая организация позволяет хранить строки компактно. Маркер const защищает строку от случайного изменения. И правильно делает. От этого больше проблем. Тот формат и тот способ хранения строки вообще не предполагает активных изменений. Особенно с размером. Максимум что можно делать - только читать.

С точки зрения современного бизнес-программирования ASCIIZ строки устарели. Но поскольку этот формат является единственным транспортом который понимает ядро ОС - то его часто можно встретить в исходниках.

Для С++ более удобной строкой является std::string. Его использовать приятнее. И с размерами там веселее.
В легаси исходниках можно встретить причудливые CString (MFC), TString (Borland). Это все разные реализации строк.

Для Qt есть тоже свой тип строк.
Ответ написан
Комментировать
Rsa97
@Rsa97
Для правильного вопроса надо знать половину ответа
Всё достаточно просто.
char str[] = "Hello"; - здесь создаётся массив и инициализируется символами из строки.
const char *str = "Hello"; - здесь создаётся скалярная переменная-указатель и инициализируется указателем на строку в памяти.
const int *mas = { 4, 5, 7, 9 }; - здесь вы пытаетесь создать переменную-указатель, а инициализировать её как массив, что недопустимо.
const char *strArr[] = { "Hello", "world", "and" };
- здесь создаётся массив указателей и инициализируется массивом указателей на строки. Правильно читается как (*strArr)[], а не *(strArr[])
Ответ написан
Комментировать
@res2001
Developer, ex-admin
void func()
{
char str1[] = "Hello";
const char* str2 = "Hello";
int* mas1 = { 4, 5, 7, 9 };  // compile error
int mas2[] = { 4, 5, 7, 9 };
char* str3 = {'s', 't', 'r', '3', '\0'};  // compile error
char str4[] = {'s', 't', 'r', '4', '\0'};
...
}

Объявление str1 и str2 это не совсем одно и то же.
str1 выделяет на стеке массив charов подходящего размера и копирует в этот массив строку "Hello". Строка "Hello" до копирования будет хранится в сегменте памяти для констант (rodata), который сформируется ОС автоматически при загрузку исполняемого файла в память. Таким образом строка str1 лежит полностью на стеке, ее можно изменять при желании, но увеличивать размер нельзя. str1 может быть как константной строкой, так и не константной.
str2 - на стеке выделяется память под указатель, указателю присваивается адрес строки "Hello", которая все так же лежит в сегменте rodata. Поэтому const тут необходим, т.к. оригинальная строка, на которую ссылается str2 не изменяемая. Если убрать const у str2, то, например, gcc выдаст предупреждение по умолчанию, но код скомпилирует. Если дальше попытаться изменить str2 (например str2[0] = 'h';), то компилятор на присвоение уже ничего не скажет, т.к. str2 уже не константная строка, но при выполнении получишь segfault, т.к. пытаешься изменить read-only память.
В случае инициализации mas1, выражение в фигурных скобках - это список инициализации. На основании списка инициализации компилятор генерирует код, который при выполнении проинициализирует массив mas1, т.е. он не генерирует массив, он генерирует код, что-то типа такого mas1[0] = 4; mas[1] = 5; ... при этом компилятор подразумевает, что память под mas1 уже должна быть выделена подходящего объема. Но в примере mas1 - это не массив - это указатель, который никуда не ссылается. Инициализировать указатель таким образом не возможно. Нужно делать так, как показано для mas2.
Точно так же списком инициализации можно проинициализировать и массив для строки (str3) и получить ту же ошибку, что и для mas1.
Из примеров можно сделать вывод, что для строк в языке существует специальный синтаксис, который больше нигде не применим - пример str2. Это так и есть.

Сырые строки (Си строки, ASCIIZ строки, строки оканчивающиеся нулем) ни разу не устаревшие. Объект string, конечно удобный и прочее, но он медленный из-за того, что хранит свою строку в динамической памяти, и при изменении строки перевыделяет память по мере надобности и копирует строку в новую память. Операции выделения/удаления динамической памяти очень дорогие. Чаще всего string - вполне адекватный выбор, но там где нужна быстра работа со строками лучше поискать другие варианты, например сырые строки.
В С++17 появился std::string_view - это обертка над константной сырой строкой, делает код немного более понятным, но не производит никаких манипуляций с динамической памятью - ему на вход надо дать сырую строку. Он ее и использует для своих операций. Из-за того, что string_view не изменяет свою строку и не манипулирует с памятью - он работает быстрее, но у него ограниченное количество операций - отсутствуют операции модифицирующие строку.
Ответ написан
wataru
@wataru Куратор тега C++
Разработчик на С++, экс-олимпиадник.
Различия обусловнены тем, что строки - особый вид массивов. Весьма частый и использующийся повсеместно. Плюс, в отличии от обычных массивов, в строках есть нулевой завершающий байт. Поэтому некоторые вещи стандартом Си были добавлены в язык отдельно для строк. Как, например, инициализацию const char* строковым литералом, или инициализацию массива с рассчетом его размера (учитывая нулевой символ в конце). Оно же потом перекочевало в Си++.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы