С++ для начинающих


Параметры-массивы


Массив в С++ никогда не передается по значению, а только как указатель на его первый, точнее нулевой, элемент. Например, объявление

void putValues( int[ 10 ] );

рассматривается компилятором так, как будто оно имеет вид

void putValues( int* );

Размер массива неважен при объявлении параметра. Все три приведенные записи эквивалентны:

// три эквивалентных объявления putValues()

void putValues( int* );

void putValues( int[] );

void putValues( int[ 10 ] );

Передача массивов как указателей имеет следующие особенности:

  • изменение значения аргумента внутри функции затрагивает сам переданный объект, а не его локальную копию. Если такое поведение нежелательно, программист должен позаботиться о сохранении исходного значения. Можно также при объявлении функции указать, что она не должна изменять значение параметра, объявив этот параметр константой:
  • void putValues( const int[ 10 ] );

    • размер массива не является частью типа параметра. Поэтому функция не знает реального размера передаваемого массива. Компилятор тоже не может это проверить. Рассмотрим пример:
    • void putValues( int[ 10 ] ); // рассматривается как int*



      int main() {

          int i, j [ 2 ];

          putValues( &i ); // правильно: &i is int*;

                           // однако при выполнении возможна ошибка

          putValues( j ); // правильно: j - адрес 0-го элемента - int*;

                          // однако при выполнении возможна ошибка

      При проверке типов параметров компилятор способен распознать, что в обоих случаях тип аргумента int* соответствует объявлению функции. Однако контроль за тем, не является ли аргумент массивом, не производится.

      По принятому соглашению C-строка является массивом символов, последний элемент которого равен нулю. Во всех остальных случаях при передаче массива в качестве параметра необходимо указывать его размер. Это относится и к массивам символов, внутри которых встречается 0. Обычно для такого указания используют дополнительный параметр функции. Например:

      void putValues( int[], int size );


      int main() {

          int i, j[ 2 ];

          putValues( &i, 1 );

          putValues( j, 2 );

          return 0;

      }

      putValues() печатает элементы массива в следующем формате:

       ( 10 )< 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 >

      где 10 – это размер массива. Вот как выглядит реализация putValues(), в которой используется дополнительный параметр:

      #include <iostream>

      const lineLength =12; // количество элементов в строке

      void putValues( int *ia, int sz )

      {

          cout << "( " << sz << " )< ";

          for (int i=0;i<sz; ++i )

          {

              if ( i % lineLength == 0 && i )

                  cout << "\n\t"; // строка заполнена

              cout << ia[ i ];

              // разделитель, печатаемый после каждого элемента,

              // кроме последнего

              if ( i % lineLength != lineLength-1 &&

                       i != sz-1 )

                  cout << ", ";

          }

          cout << " >\n";

      }

      Другой способ сообщить функции размер массива-параметра – объявить параметр как ссылку. В этом случае размер становится частью типа, и компилятор может проверить аргумент в полной мере.

      // параметр - ссылка на массив из 10 целых

      void putValues( int (&arr)[10] );

      int main() {

          int i, j [ 2 ];

          putValues(i); // ошибка:

                        // аргумент не является массивом из 10 целых

          putValues(j); // ошибка:

                        // аргумент не является массивом из 10 целых

          return 0;

      }

      Поскольку размер массива теперь является частью типа параметра, новая версия putValues() способна работать только с массивами из 10 элементов. Конечно, это ограничивает ее область применения, зато реализация значительно проще:

      #include <iostream>

      void putValues( int (&ia)[10] )

      {

          cout << "( 10 )< ";

          for ( int 1 =0; i < 10; ++i ) { cout << ia[ i ];

          // разделитель, печатаемый после каждого элемента,

          // кроме последнего



          if ( i != 9 )

              cout << ", ";

          }

          cout << " >\n";

      }

      Еще один способ получить размер переданного массива в функции – использовать абстрактный контейнерный тип. (Такие типы были представлены в главе 6. В следующем подразделе мы поговорим об этом подробнее.)

      Хотя две предыдущих реализации putValues() правильны, они обладают серьезными недостатками. Так, первый вариант работает только с массивами типа int. Для типа double* нужно писать другую функцию, для long* – еще одну и т.д. Второй вариант производит операции только над массивом из 10 элементов типа int. Для обработки массивов разного размера нужны дополнительные функции. Лучшим решением было бы использовать шаблон – функцию, или, скорее, обобщенную реализацию кода целого семейства функций, которые отличаются только типами обрабатываемых данных. Вот как можно сделать из первого варианта putValues() шаблон, способный работать с массивами разных типов и размеров:

      template <class Type>

      void putValues( Type *ia, int sz )

      {

          // так же, как и раньше

      }

      Параметры шаблона заключаются в угловые скобки. Ключевое слово class означает, что идентификатор Type служит именем параметра, при конкретизации шаблона функции putValues() он заменяется на реальный тип – int, double, string и т.д. (В главе 10 мы продолжим разговор о шаблонах функций.)

      Параметр может быть многомерным массивом. Для такого параметра должны быть заданы правые границы всех измерений, кроме первого. Например:

      putValues( int matrix[][10], int rowSize );

      Здесь matrix объявляется как двумерный массив, который содержит десять столбцов и неизвестное число строк. Эквивалентным объявлением для matrix будет:

      int (*matrix)[10]

      Многомерный массив передается как указатель на его нулевой элемент. В нашем случае тип matrix – указатель на массив из десяти элементов типа int. Как и для одномерного массива, граница первого измерения не учитывается при проверке типов. Если параметры являются многомерными массивами, то контролируются все измерения, кроме первого.

      Заметим, что скобки вокруг *matrix необходимы из-за более высокого приоритета операции взятия индекса. Инструкция

      int *matrix[10];

      объявляет matrix как массив из десяти указателей на int.


      Содержание раздела