Радиоконструктор

Язык C, Часть 26

Массивы переменной длины

Как уже упоминалось, в С89 размер массива должен быть объявлен с помощью константных выражений. Поэтому компилятор С89 устанавливает фиксированный размер массива, не изменяющийся в процессе выполнения программы. Однако это не относится к С99, в котором определено новое мощное средство: массивы переменной длины. Стандарт С99 позволяет в объявлении размера массива использовать любые выражения, в том числе такие, значение которых становится известным только во время выполнения. Объявленный таким образом массив называется массивом переменной длины. Однако переменную длину могут иметь только локальные массивы (т.е. видимые в блоке или в прототипе). Приведем пример массива переменной длины:
void f(int dim)
{
   char str[dim]; /* символьный массив переменной длины */

   /* ... */
}
 
 
Здесь размер массива str определяется значением переменной dim, которая передается в функцию f() как параметр. Таким образом, при каждом вызове f() создается массив str разной длины.
Массивы переменной длины добавлены в С99 главным образом для поддержки численных методов обработки данных. В программировании это средство распространено достаточно широко. Однако следует помнить, что стандарт С89 (и некоторые компиляторы C++) не поддерживает массивы переменной длины. Более подробно этот вопрос рассматривается в части II

Приемы использования массивов и строк на примере игры в крестики-нолики

Представленный длинный пример иллюстрирует большое количество приемов использования строк. Рассматривается простая программа игры в крестики-нолики. Двухмерный массив используется в качестве матрицы, изображающей игральную доску.
Компьютер играет в очень простую игру. Когда наступает очередь хода компьютера, функция get_computer_move() просматривает матрицу в поиске незанятых ячеек. Если функция находит незанятую ячейку, она помещает туда символ O. Если незанятой ячейки нет, то функция выводит сообщение об окончании игры и прекращает работу программы. Функция get_player_move() спрашивает играющего, где он хочет поместить символ X. Верхний левый угол имеет координаты (1, 1), а нижний правый — (3, 3).
Массив matrix, содержащий матрицу игры, инициализирован символами пробела. Каждый ход, сделанный игроком или компьютером, заменяет символ пробела символом X или O. Это позволяет легко отобразить матрицу на экране.
После каждого хода вызывается функция check(), которая возвращает пробел, если победителя еще нет, или X, если победил игрок, или O, когда победил компьютер. Эта функция просматривает строки, столбцы и диагонали в поиске трех одинаковых символов (X или O) подряд.
Функция disp_matrix() отображает на экране текущее состояние игры. Обратите внимание на то, как существенно упрощает эту функцию инициализация матрицы пробелами.
Функции получают доступ к массиву matrix различными способами. Их стоит внимательно изучить для лучшего понимания приемов работы с массивами.
/* Простая игра в крестики-нолики. */
#include <stdio.h>
#include <stdlib.h>

char matrix[3][3];  /* матрица игры */

char check(void);
void init_matrix(void);
void get_player_move(void);
void get_computer_move(void);
void disp_matrix(void);

int main(void)
{
  char done;

  printf("Это игра в крестики-нолики.\n");
  printf("Вы будете играть против компьютера.\n");

  done =  ' ';
  init_matrix();

  do {
    disp_matrix();
    get_player_move();
    done = check(); /* проверка, есть ли победитель */
    if(done!= ' ') break; /* есть победитель */
    get_computer_move();
    done = check(); /* проверка, есть ли победитель */
  } while(done== ' ');

  if(done=='X') printf("Вы победили!\n");
  else printf("Победил компьютер!!!!\n");
  disp_matrix(); /* показ финальной позиции */

  return 0;
}

/* Инициализация матрицы игры. */
void init_matrix(void)
{
  int i, j;

  for(i=0; i<3; i++)
    for(j=0; j<3; j++) matrix[i][j] =  ' ';
}

/* Ход игрока. */
void get_player_move(void)
{
  int x, y;

  printf("Введите координаты X,Y Вашего хода: ");
  scanf("%d%*c%d", &x, &y);

  x--; y--;

  if(matrix[x][y]!= ' '){
    printf("Неверный ход, попытайтесь еще.\n");
    get_player_move();
  }
  else matrix[x][y] = 'X';
}

/* Ход компьютера. */
void get_computer_move(void)
{
  int i, j;
  for(i=0; i<3; i++){
    for(j=0; j<3; j++)
      if(matrix[i][j]==' ') break;
    if(matrix[i][j]==' ') break;
  /* Второй break нужен для выхода из цикла по i */
  }

  if(i*j==9)  {
    printf("Конец игры\n");
    exit(0);
  }
  else
    matrix[i][j] = 'O';
}

/* Вывод матрицы на экран. */
void disp_matrix(void)
{
  int t;

  for(t=0; t<3; t++) {
    printf(" %c | %c | %c ",matrix[t][0],
            matrix[t][1], matrix [t][2]);
    if(t!=2) printf("\n---|---|---\n");
  }
  printf("\n");
}

/* Определение победителя. */
char check(void)
{
  int i;

  for(i=0; i<3; i++)  /* проверка строк */
    if(matrix[i][0]==matrix[i][1] &&
       matrix[i][0]==matrix[i][2]) return matrix[i][0];

  for(i=0; i<3; i++)  /* проверка столбцов */
    if(matrix[0][i]==matrix[1][i] &&
       matrix[0][i]==matrix[2][i]) return matrix[0][i];

  /* проверка диагоналей */
  if(matrix[0][0]==matrix[1][1] &&
     matrix[1][1]==matrix[2][2])
       return matrix[0][0];

  if(matrix[0][2]==matrix[1][1] &&
     matrix[1][1]==matrix[2][0])
       return matrix[0][2];

  return ' ';
}
Пояснение к программе. В функции get_player_move() с помощью библиотечной функции scanf() считываются с клавиатуры два целых числа x и y. Функция scanf() при считывании чисел предполагает, что во входном потоке они разделены пробелами (или пробельными символами), другие разделительные символы не допускаются. Однако многие пользователи привыкли к тому, что числа можно разделять, например, запятыми. (Собственно говоря, именно так и предлагается в подсказке, выдаваемой программой.) В приведенном примере символ, следующий непосредственно после первого числа, просто игнорируется, именно для этого в функции scanf() используется спецификатор формата %*c. Звездочка означает, что символ считывается из потока, но в память не записывается.


Комментарии