Указатели символов и функции
Строчная константа, как, например, "Я - строка" является массивом символов. Компилятор завершает внутреннее представление такого массива символом \0, так что программы могут находить его конец. Таким образом, длина массива в памяти оказывается на единицу больше числа символов между двойными кавычками. По-видимому чаще всего строчные константы появляются в качестве аргументов функций, как, например, в: printf ("Здравствуй, Мир !\n");
когда символьная строка, подобная этой, появляется в программе, то доступ к ней осуществляется с помощью указателя символов; функция printf фактически получает указатель символьного массива. Конечно, символьные массивы не обязаны быть только аргументами функций. Если описать message как: char *message;
то в результате оператора: message = "Now is the time";
переменная message станет указателем на фактический массив символов. Это не копирование строки; здесь участвуют только указатели. В языке «C» не предусмотрены какие-либо операции для обработки всей строки символов как целого.
Мы проиллюстрируем другие аспекты указателей и массивов, разбирая две полезные функции из стандартной библиотеки ввода-вывода, которая будет рассмотрена в главе 9. Первая функция – это strcpy(s,t), которая копирует строку t в строку s. Аргументы написаны именно в этом порядке по аналогии с операцией присваивания, когда для того, чтобы присвоить t к s, обычно пишут s = t . Пример 6-6. Сначала приведем версию с массивами: void strcpy(char s[],char t[])// Скоприровать t в s { int i; i = 0; while ((s[i] = t[i]) != '\0') i++; }
Пример 6-7. Для сопоставления ниже даются 3 варианта strcpy с указателями: void strcpy(char *s, char *t) // Вариант 1 { while ((*s = *t) != '\0') { s++; t++; } }
Так как аргументы передаются по значению, функция strcpy может использовать s и t так, как она пожелает. Здесь они с удобством полагаются указателями, которые передвигаются вдоль массивов, по одному символу за шаг, пока не будет скопирован в s завершающий в t символ \0. Пример 6-8. На практике функция strcpy была бы записана не так, как мы показали выше. Вот вторая возможность: strcpy(char *s, char *t) // Вариант 2 { while ((*s++ = *t++) != '\0') ; }
Здесь увеличение s и t внесено в проверочную часть. Значением *t++ является символ, на который указывал t до увеличения; постфиксная операция ++ не изменяет t, пока этот символ не будет извлечен. Точно так же этот символ помещается в старую позицию s, до того как s будет увеличено. Конечный результат заключается в том, что все символы, включая завершающий \0, копируются из t в s. Пример 6-9. И как последнее сокращение мы опять отметим, что сравнение с \0 является излишним, так что функцию можно записать в виде: strcpy(char *s, char *t) // Вариант 3 { while (*s++ = *t++) ; }
хотя с первого взгляда эта запись может показаться загадочной, она дает значительное удобство. Этой идиомой следует овладеть уже хотя бы потому, что вы с ней будете часто встречаться в «C»-программах. Вторая функция – это strcmp(s,t), которая сравнивает символы строк s и t, возвращая отрицательное, нулевое или положительное значение в соответствии с тем, меньше, равно или больше лексикографически s, чем t. Пример 6-10. Возвращаемое значение получается в результате вычитания символов из первой позиции, в которой s и t не совпадают. // Получить return < 0, если s<t, // return = 0, если s == t, // return > 0, если s > t strcmp(char s[], char t[]){ int i; i = 0; while (s[i] == t[i]) if (s[i++] == '\0') return(0); return(s[i]-t[i]); } Пример 6-11. А вот версия strcmp с указателями:
// Получить return < 0, если s<t, // return = 0, если s == t, // return > 0, если s > t strcmp(char *s, char *t) { for ( ; *s == *t; s++, t++) if (*s == '\0') return(0); return(*s-*t); }
Так как ++ и -- могут быть как постфиксными, так и префиксными операциями, то встречаются и другие комбинации * и ++ и --, хотя менее часто. Например *++p увеличивает p до извлечения символа, на который указывает p, а *--p сначала уменьшает p. Упражнение 6-2. Напишите вариант с указателями функции strcat из главы 3: strcat(s,t) копирует строку t в конец s. Упражнение 6-3. Напишите макрос для strcpy. Упражнение 6-4. Перепишите подходящие программы из предыдущих глав и упражнений, используя указатели вместо индексации массивов. Хорошие возможности для этого предоставляют функции getline (главы 2 и 6), atoi, itoa и их варианты (главы 3, 4 и 5), reverse (глава 4), index и getop (глава 5).
Указатели – не целые
Вы, возможно, обратили внимание в предыдущих «С»-программах на довольно непринужденное отношение к копированию указателей. В общем это верно, что на большинстве машин указатель можно присвоить целому и передать его обратно, не изменив его; при этом не происходит никакого масштабирования или преобразования и ни один бит не теряется. К сожалению, это ведет к вольному обращению с функциями, возвращающими указатели, которые затем просто передаются другим функциям, – необходимые описания указателей часто опускаются. Пример 6-12. Рассмотрим функцию strsave(s), которая копирует строку s в некоторое место для хранения, выделяемое посредством обращения к функции alloc, и возвращает указатель на это место. Правильно она должна быть записана так:
char *strsave(char *s) /* save string s somewhere */ { char *p, *alloc(); if ((p = alloc(strlen(s)+1)) != null) strcpy(p, s); return(p); }
На практике существует сильное стремление опускать описания:
*strsave(s) /* save string s somewhere */ { char *p; if ((p = alloc(strlen(s)+1)) != null) strcpy(p, s); return(p); }
Эта программа будет правильно работать на многих машинах, потому что по умолчанию функции и аргументы имеют тип int, а указатель и целое обычно можно безопасно пересылать туда и обратно. Однако такой стиль программирования в своем существе является рискованным, поскольку зависит от деталей реализации и архитектуры машины и может привести к неправильным результатам на конкретном используемом вами компиляторе. Разумнее всюду использовать полные описания. Среда программирования Visual C ++ или отладочная программа lint предупредят о таких конструкциях, если они по неосторожности все же появятся.
Многомерные массивы
В языке «C» предусмотрены прямоугольные многомерные массивы, хотя на практике существует тенденция к их значительно более редкому использованию по сравнению с массивами указателей. В этом разделе мы рассмотрим некоторые их свойства. Пример 6-13. Рассмотрим задачу преобразования дня месяца в день года и наоборот. Например, 1-ое марта является 60-м днем невисокосного года и 61-м днем високосного года. Давайте введем две функции для выполнения этих преобразований: day_of_year преобразует месяц и день в день года, а month_day преобразует день года в месяц и день. Так как эта последняя функция возвращает два значения, то аргументы месяца и дня должны быть указателями:
month_day(1977, 60, &m, &d)
Полагает m равным 3 и d равным 1 (1-ое марта). Обе эти функции нуждаются в одной и той же информационной таблице, указывающей число дней в каждом месяце. Так как число дней в месяце в високосном и в невисокосном году отличается, то проще представить их в виде двух строк двумерного массива, чем пытаться прослеживать во время вычислений, что именно происходит в феврале. Вот этот массив и выполняющие эти преобразования функции:
static int day_tab[2][13] = { (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31), (0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) };
// Определить день года по месяцу и дню day_of_year(int year, int month, int day) { int i, leap; leap= year%4 == 0 && year%100 != 0 || year%400 == 0; for (i = 1; i < month; i++) day += day_tab[leap][i]; return(day); }
// Опредлить месяц и день по дню года month_day(int year, int yearday, int *pmonth, int *pday) { leap= year%4 == 0 && year%100 != 0 || year%400 == 0; for (i = 1; yearday > day_tab[leap][i]; i++) yearday -= day_tab[leap][i]; *pmonth = i; *pday = yearday; }
Массив day_tab должен быть внешним как для day_of_year, так и для month_day, поскольку он используется обеими этими функциями. Массив day_tab является первым двумерным массивом, с которым мы имеем дело. По определению в «C» двумерный массив по существу является одномерным массивом, каждый элемент которого является массивом. Поэтому индексы записываются следующим образом: day_tab[i][j] Нельзя писать так: day_tab[i,j] – как в большинстве языков. В остальном с двумерными массивами можно, в основном, обращаться таким же образом, как в других языках. Элементы хранятся по строкам, т.е. при обращении к элементам в порядке их размещения в памяти быстрее всего изменяется самый правый индекс.
Массив инициализируется с помощью списка начальных значений, заключенных в фигурные скобки; каждая строка двумерного массива инициализируется соответствующим подсписком. Мы поместили в начало массива day_tab столбец из нулей для того, чтобы номера месяцев изменялись естественным образом от 1 до 12, а не от 0 до 11. Так как за экономию памяти у нас пока не награждают, такой способ проще, чем подгонка индексов. Если двумерный массив передается функции, то описание соответствующего аргумента функции должно содержать количество столбцов; количество строк несущественно, поскольку, как и прежде, фактически передается указатель. В нашем конкретном случае это указатель объектов, являющихся массивами из 13 чисел типа int. Таким образом, если бы требовалось передать массив day_tab функции f, то описание в f имело бы вид: f(int day_tab[2][13]) { ... } Так как количество строк является несущественным, то описание аргумента в f могло бы быть и таким: int day_tab[][13]; или таким int (*day_tab)[13];
в которм говорится, что аргумент является указателем массива из 13 целых. Круглые скобки здесь необходимы, потому что квадратные скобки [ ] имеют более высокий уровень старшинства, чем *; как мы увидим в следующем разделе, без круглых скобок int *day_tab[13]; является описанием массива из 13 указателей на целые.
Популярное: Модели организации как закрытой, открытой, частично открытой системы: Закрытая система имеет жесткие фиксированные границы, ее действия относительно независимы... Почему стероиды повышают давление?: Основных причин три... Генезис конфликтологии как науки в древней Греции: Для уяснения предыстории конфликтологии существенное значение имеет обращение к античной... ©2015-2024 megaobuchalka.ru Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. (251)
|
Почему 1285321 студент выбрали МегаОбучалку... Система поиска информации Мобильная версия сайта Удобная навигация Нет шокирующей рекламы |