Опубликован: 05.07.2006 | Доступ: свободный | Студентов: 4679 / 885 | Оценка: 4.12 / 3.74 | Длительность: 18:59:00
Лекция 6:

Указатели и массивы

5.6. Указатели - не целые

Вы, возможно, обратили внимание в предыдущих "с"- программах на довольно непринужденное отношение к копированию указателей. В общем это верно, что на большинстве машин указатель можно присвоить целому и передать его обратно, не изменив его; при этом не происходит никакого масштабирования или преобразования и ни один бит не теряется. К сожалению, это ведет к вольному обращению с функциями, возвращающими указатели, которые затем просто передаются другим функциям, - необходимые описания указателей часто опускаются. Рассмотрим, например, функцию strsave(s), которая копирует строку s в некоторое место для хранения, выделяемое посредством обращения к функции alloc, и возвращает указатель на это место. Правильно она должна быть записана так:

char *strsave(s) /* save string s somewhere */
char *s;
{
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, а указатель и целое обычно можно безопасно пересылать туда и обратно. Однако такой стиль программирования в своем существе является рискованным, поскольку зависит от деталей реализации и архитектуры машины и может привести к неправильным результатам на конкретном используемом вами компиляторе. Разумнее всюду использовать полные описания. (Отладочная программа lint предупредит о таких конструкциях, если они по неосторожности все же появятся).

5.7. Многомерные массивы

В языке "C" предусмотрены прямоугольные многомерные массивы, хотя на практике существует тенденция к их значительно более редкому использованию по сравнению с массивами указателей. В этом разделе мы рассмотрим некоторые их свойства.

Рассмотрим задачу преобразования дня месяца в день года и наоборот. Например, 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),
     (1, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
};

day_of_year(year, month, day)      /* set day of year */
int year, month, day;        /* from month & 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(year, yearday, pmonth, pday) /*set month,day */
int year, yearday, *pmonth, *pday; /* from day of year */
{
 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(day_tab) int day_tab[2][13]; 
{ 
      ... 
}

Так как количество строк является несущественным, то описание аргумента в f могло бы быть таким:

int day_tab[][13];

или таким

int (*day_tab)[13];

в котором говорится, что аргумент является указателем массива из 13 целых. Круглые скобки здесь необходимы, потому что квадратные скобки [] имеют более высокий уровень старшинства, чем * ; как мы увидим в следующем разделе, без круглых скобок

int *day_tab[13];

является описанием массива из 13 указателей на целые.