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 не изменяет свою строку и не манипулирует с памятью - он работает быстрее, но у него ограниченное количество операций - отсутствуют операции модифицирующие строку.