Стивен Прата - Язык программирования C. Лекции и упражнения (6-е изд.) 2015
- Название:Язык программирования C. Лекции и упражнения (6-е изд.) 2015
- Автор:
- Жанр:
- Издательство:Вильямс
- Год:0101
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Стивен Прата - Язык программирования C. Лекции и упражнения (6-е изд.) 2015 краткое содержание
Язык программирования C. Лекции и упражнения (6-е изд.) 2015 - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
В дальнейшем компилятор будет распознавать имя массива arl как синоним адреса первого элемента массива, &arl [0]. Один важный аспект состоит здесь в том, что в форме массива arl является адресной константой. Значение arl изменять нельзя, т.к. это означало бы изменение места (адреса), где хранится массив. Для идентификации следующего элемента в массиве можно задействовать операции наподобие arl + 1, но выражение ++arl не разрешено. Операция инкремента может применяться только к именам переменных (или, в общем смысле, к модифицируемым 1-значениям), но не к константам.
Форма указателя (*ptl) также приводит к тому, что в статической памяти под строку резервируются 26 элементов. Вдобавок, как только программа начнет выполня ться, она выделяет в памяти место под переменную типа указателя ptl и сохраняет в пей адрес строки. Первоначально эта переменная указывает на первый символ строки, но
424 Глава 11 ее значение можно изменять. Следовательно, можно использовать операцию инкремента. Например, ++ptl будет указывать на второй символ ('т').
Строковые литералы считаются данными const. Поскольку указатель ptl ссылается на такие данные, он должен быть объявлен, как указывающий на данные const. Это вовсе не означает, что нельзя изменять значение ptl (т.е. место, на которое он указывает), просто ptl не допускается применять для изменения самих данных. С другой стороны, при копировании строкового литерала в массив данные можно свободно изменять, если только сам массив не был объявлен как const.
Короче говоря, инициализация массива приводит к копированию строки из статической памяти в массив, тогда как инициализация указателя просто копирует адрес строки. Эти утверждения демонстрируются в листинге 11.3.
Листинг 11.3. Программа addresses. с
Вот вывод, полученный в одной из систем:
адрес "Я особенный.": 0xlOOOOOfOc
адрес ar: 0x7fff5fbff8c7 адрес pt: 0xlOOOOOeeO адрес MSG: 0xlOOOOOeeO
адрес "Я особенный.": 0xlOOOOOfOc
О чем это свидетельствует? Во-первых, pt и MSG — это один и тот же адрес, но ar, как и было сказано, является другим адресом. Во-вторых, хотя литерал "Я особенный. " встречается в операторах printf() дважды, компилятор использует одну область памяти, но с адресом, отличающимся от адреса MSG. Компилятору предоставляется свобода выбора сохранять литерал, который применяется более одного раза, в одном или нескольких местах. Другой компилятор мог бы представить все три экземпляра "Я особенный." в одной области памяти. В-третьих, часть памяти, использованная для статических данных, отличается от применяемой для динамической памяти, которая выделена под ar. Различны не только значения; этот конкретный компилятор использует даже разное количество битов для представления двух видов памяти.
Важны ли различия между представлениями строк в виде массива и указателя? Часто нет, однако это зависит от того, что именно вы пытаетесь сделать. Продолжим рассмотрение этих вопросов.
Символьные строки и строковые функции 425
Различия между массивами и указателями
Давайте исследуем отличия между инициализацией символьного массива, предназначенного для хранения строки, и инициализацией указателя, который указывает на эту строку. (Под “указанием на строку” подразумевается указание на первый символ строки.) Например, взгляните на следующие два объявления:
char heart[] = "Я люблю Тилли!"; const char *head = "Я люблю Милли!";
Главное отличие между ними заключается в том, что имя массива heart является константой, а указатель head — переменной. Во что это выливается на практике? Прежде всего, в обоих случаях можно применять форму записи с массивом:
for (i = 0; i < 7; i++) putchar(heart[i]); putchar('\n'); for (i = 0; i < 7; i++) putchar(head[i])); putchar('\n');
Получается следующий вывод:
Я люблю Я люблю
Также в обоих случаях можно использовать добавление значения к указателю:
for (i = 0; i < 7; i++)
putchar(*(heart + i) ); putchar('\n'); for (i = 0; i < 7; i++) putchar(*(head + i) ); putchar('\n');
И снова получается тот же самый вывод:
Я люблю Я люблю
Однако операция инкремента может применяться только в версии с указателем:
while (* (head) != '\0') /* остановиться в конце строки */
putchar(*(head++)); /* вывести символ, переместить указатель */
Этот код дает следующий вывод:
Я люблю Милли!
Предположим, вы хотите, чтобы head и heart совпадали. Тогда можно записать так:
head = heart; /* head теперь указывает на массив heart */
В результате head будет указывать на первый элемент массива heart.
Однако следующий оператор не допускается:
heart = head; /* недопустимая конструкция */
Ситуация аналогична случаю с операторами х = 3; иЗ = х;.В левой части оператора присваивания должна быть переменная, или в более общем смысле /-значение, такое как *p_int. Кстати, оператор head = heart; не приводит к затиранию строки "Я люблю Милли ! "; она всего лишь меняет адрес, хранящийся в head. Однако если адрес строки "Я люблю Милли ! " не будет сохранен где-то в другом месте, вы не сможете
426 глава 11 получить доступ к этой строке после того, как head станет указывать на другую ячейку памяти.
Существует способ изменить сообщение heart — для этого нужно обращаться к отдельным элементам массива:
heart[8]= 'М'; или
*(heart + 8) = 'М';
Элементы массива являются переменными (если только массив не объявлен как const), но имя массива — это не переменная.
Давайте возвратимся к инициализации указателя, в которой модификатор const не используется:
char * word = "дело";
Можно ли применить указатель для изменения этой строки?
word[2] = [n]; // допустимо??
Ваш компилятор может разрешить подобное, но согласно текущему стандарту С, поведение в этом случае не определено. Такой оператор может, например, привести к ошибке доступа в память. Причина связана с тем, что, как упоминалось ранее, компилятор может выбрать вариант представления всех идентичных строковых литералов в виде единственной копии в памяти. Например, все приведенные ниже операторы могут ссылаться на единственную ячейку памяти, в которой хранится строка "Клинтон":
char * p1 = "Клинтон";
р1[0] = 'Ф'; // все ли правильно?
printf("Клинтон");
printf(": берегитесь %sueB!\n", "Клингон");
Это значит, что компилятор может заменить каждый экземпляр строкового литерала "Клингон" одним и тем же адресом. Если компилятор использует представление в виде единственной копии и разрешит произвести замену p1[0] на ' Ф', то это затронет все случаи использования данной строки, поэтому операторы, выводящие строковый литерал "Клингон", в действительности отобразят строку "Флингон":
Клинтон: берегитесь Флингонцев!
На самом деле в прошлом некоторые компиляторы вели себя таким путающим образом, в то время как другие генерировали программы, которые в подобных случаях завершались аварийно. Поэтому рекомендуется при инициализации указателя строковым литералом применять модификатор const:
Читать дальшеИнтервал:
Закладка: