<< Пред.           стр. 33 (из 121)           След. >>

Список литературы по разделу

 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
 
 const lineLength =12; // количество элементов в строке
 void putValues( int *ia, int sz )
 {
  cout << "( " << sz << " )< ";
  for (int i=0;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
 
 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
 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.
 7.3.4. Абстрактные контейнерные типы в качестве параметров
 Абстрактные контейнерные типы, представленные в главе 6, также используются для объявления параметров функции. Например, можно определить putValues() как имеющую параметр типа vector вместо встроенного типа массива.
 Контейнерный тип является классом и обеспечивает значительно большую функциональность, чем встроенные массивы. Так, vector “знает” собственный размер. В предыдущем подразделе мы видели, что размер параметра-массива неизвестен функции и для его передачи приходится задавать дополнительный параметр. Использование vector позволяет обойти это ограничение. Например, можно изменить определение нашей putValues() на такое:
 #include
 #include
 
 const lineLength =12; // количество элементов в строке
 void putValues( vector vec )
 {
  cout << "( " << vec.size() << " )< ";
  for ( int i = 0; i < vec.size(); ++1 ) {
  if ( i % lineLength == 0 && i )
  cout << "\n\t"; // строка заполнена
 
  cout << vec[ i ];
 
  // разделитель, печатаемый после каждого элемента,
  // кроме последнего
  if ( 1 % lineLength != lineLength-1 &&
  i != vec.size()-1 )
  cout << ", ";
  }
  cout << " >\n";
 }
 Функция main(), вызывающая нашу новую функцию putValues(), выглядит так:
 void putValues( vector );
 int main() {
  int i, j[ 2 ];
  // присвоить i и j некоторые значения
  vector vec1(1); // создадим вектор из 1 элемента
  vecl[0] = i;
  putValues( vecl );
 
  vector vec2; // создадим пустой вектор
  // добавим элементы к vec2
  for ( int ix = 0;
  ix < sizeof( j ) / sizeof( j[0] );
  ++ix )
  // vec2[ix] == j [ix]
  vec2.push_back( j[ix] );
  putValues( vec2 );
 
  return 0;
 }
 Заметим, что параметр putValues()передается по значению. В подобных случаях контейнер со всеми своими элементами всегда копируется в стек вызванной функции. Поскольку операция копирования весьма неэффективна, такие параметры лучше объявлять как ссылки.
 Как бы вы изменили объявление putValues()?
 Вспомним, что если функция не модифицирует значение своего параметра, то предпочтительнее, чтобы он был ссылкой на константный тип:
 void putValues( const vector & ) { ...
 7.3.5. Значения параметров по умолчанию
 Значение параметра по умолчанию – это значение, которое разработчик считает подходящим в большинстве случаев употребления функции, хотя и не во всех. Оно освобождает программиста от необходимости уделять внимание каждой детали интерфейса функции.
 Значения по умолчанию для одного или нескольких параметров функции задаются с помощью того же синтаксиса, который употребляется при инициализации переменных. Например, функция для создания и инициализации двумерного массива, моделирующего экран терминала, может использовать такие значения для высоты, ширины и символа фона экрана:
 char *screenInit( int height = 24, int width = 80,
  char background = ' ' );
 Функция, для которой задано значение параметра по умолчанию, может вызываться по-разному. Если аргумент опущен, используется значение по умолчанию, в противном случае – значение переданного аргумента. Все следующие вызовы screenInit() корректны:
 char *cursor;
 
 // эквивалентно screenInit(24,80,' ')
 cursor = screenInit();
 
 // эквивалентно screenInit(66,80,' ')
 cursor = screenlnit(66);
 
 // эквивалентно screenInit(66,256,' ')
 cursor = screenlnit(66, 256);
 cursor = screenlnit(66, 256, '#');
 Фактические аргументы сопоставляются с формальными параметрами позиционно (в порядке следования), и значения по умолчанию могут использоваться только для подстановки вместо отсутствующих последних аргументов. В нашем примере невозможно задать значение для background, не задавая его для height и width.
 // эквивалентно screenInit('?',80,' ')
 cursor = screenInit('?');
 
 // ошибка, неэквивалентно screenInit(24,80,'?')
 cursor = screenInit( , ,'?');
 При разработке функции с параметрами по умолчанию придется позаботиться об их расположении. Те, для которых значения по умолчанию вряд ли будут употребляться, необходимо поместить в начало списка. Функция screenInit() предполагает (возможно, основываясь на опыте применения), что параметр height будет востребован пользователем наиболее часто.
 Значения по умолчанию могут задаваться для всех параметров или только для некоторых. При этом параметры без таких значений должны идти раньше тех, для которых они указаны.
 // ошибка: width должна иметь значение по умолчанию,
 // если такое значение имеет height
 char *screenlnit( int height = 24, int width,
  char background = ' ' );
 Значение по умолчанию может указываться только один раз в файле. Следующая запись ошибочна:
 // tf.h
 int ff( int = 0 );
 
 // ft.С
 #include "ff.h"
 int ff( int i = 0) { ... } // ошибка
 По соглашению значение задается в объявлении функции, которое размещается в общедоступном заголовочном файле (описывающем интерфейс), а не в ее определении. Если же указать его в определении, это значение будет доступно только для вызовов функции внутри исходного файла, содержащего это определение.
 Можно объявить функцию повторно и таким образом задать дополнительные параметры по умолчанию. Это удобно при настройке универсальной функции для конкретного приложения. Скажем, в системной библиотеке UNIX есть функция chmod(), изменяющая режим доступа к файлу. Ее объявление содержится в системном заголовочном файле :
 int chmod( char *filePath, int protMode );
 protMode представляет собой режим доступа, а filePath – имя и каталог файла. Если в некотором приложении файл только читается, можно переобъявить функцию chmod(), задав для соответствующего параметра значение по умолчанию, чтобы не указывать его при каждом вызове:
 #include
 int chmod( char *filePath, int protMode=0444 );
 Если функция объявлена в заголовочном файле так:
 file int ff( int a, int b, int с = 0 ); // ff.h
 то как переобъявить ее, чтобы присвоить значение по умолчанию для параметра b? Следующая строка ошибочна, поскольку она повторно задает значение для с:
 #include "ff.h"
 int ff( int a, int b = 0, int с = 0 ); // ошибка
 Так выглядит правильное объявление:
 #include "ff.h"
 int ff( int a, int b = 0, int с ); // правильно
 В том месте, где мы переобъявляем функцию ff(), параметр b расположен правее других, не имеющих значения по умолчанию. Поэтому требование присваивать такие значения справа налево не нарушается. Теперь мы можем переобъявить ff() еще раз:
 #include "ff.h"
 int ff( int a, int b = 0, int с ); // правильно
 int ff( int a = 0, int b, int с ); // правильно
 Значение по умолчанию не обязано быть константным выражением, можно использовать любое:
 int aDefault();
 int bDefault( int );
 int cDefault( double = 7.8 );
 
 int glob;
 
 int ff( int a = aDefault() ,
  int b = bDefau1t( glob ) ,
  int с = cDefault() );
 Если такое значение является выражением, то оно вычисляется во время вызова функции. В примере выше cDefault() работает каждый раз, когда происходит вызов функции ff() без указания третьего аргумента.
 7.3.6. Многоточие
 Иногда нельзя перечислить типы и количество всех возможных аргументов функции. В этих случаях список параметров представляется многоточием (...), которое отключает механизм проверки типов. Наличие многоточия говорит компилятору, что у функции может быть произвольное количество аргументов неизвестных заранее типов. Многоточие употребляется в двух форматах:
 void foo( parm_list, ... );
 void foo( ... );
 Первый формат предоставляет объявления для части параметров. В этом случае проверка типов для объявленных параметров производится, а для оставшихся фактических аргументов – нет. Запятая после объявления известных параметров необязательна.
 Примером вынужденного использования многоточия служит функция printf() стандартной библиотеки С. Ее первый параметр является C-строкой:
 int printf( const char* ... );
 Это гарантирует, что при любом вызове printf() ей будет передан первый аргумент типа const char*. Содержание такой строки, называемой форматной, определяет, необходимы ли дополнительные аргументы при вызове. При наличии в строке формата метасимволов, начинающихся с символа %, функция ждет присутствия этих аргументов. Например, вызов
 printf( "hello, world\n" );
 имеет один строковый аргумент. Но
 printf( "hello, %s\n", userName );
 имеет два аргумента. Символ % говорит о наличии второго аргумента, а буква s, следующая за ним, определяет его тип – в данном случае символьную строку.
 Большинство функций с многоточием в объявлении получают информацию о типах и количестве фактических параметров по значению явно объявленного параметра. Следовательно, первый формат многоточия употребляется чаще.
 Отметим, что следующие объявления неэквивалентны:
 void f();
 void f( ... );
 В первом случае f() объявлена как функция без параметров, во втором – как имеющая ноль или более параметров. Вызовы
 f( someValue );
 f( cnt, a, b, с );
 корректны только для второго объявления. Вызов
 f();
 применим к любой из двух функций.
 Упражнение 7.4
 Какие из следующих объявлений содержат ошибки? Объясните.
 (a) void print( int arr[][], int size );
 (b) int ff( int a, int b = 0, int с = 0 );
 (c) void operate( int *matrix[] );
 (d) char *screenInit( int height = 24, int width,
  char background );
 (e) void putValues( int (&ia)[] );
 Упражнение 7.5
 Повторные объявления всех приведенных ниже функций содержат ошибки. Найдите их.
 (a) char *screenInit( int height, int width,
  char background = ' ' );
  char *screenInit( int height = 24, int width,
  char background );
 
 (b) void print( int (*arr)[6], int size );
  void print( int (*arr)[5], int size );
 
 (c) void manip( int *pi, int first, int end = 0 );
  void manip( int *pi, int first = 0, int end = 0 );
 Упражнение 7.6
 Даны объявления функций.
 void print( int arr[][5], int size );
 void operate(int *matrix[7]);
 char *screenInit( int height = 24, int width = 80,
  char background = ' ' );
 Вызовы этих функций содержат ошибки. Найдите их и объясните.
 (a) screenInit();
 
 (b) int *matrix[5];
  operate( matrix );
 
 (c) int arr[5][5];
  print( arr, 5 );
 Упражнение 7.7
 Перепишите функцию putValues( vector ), приведенную в подразделе 7.3.4, так, чтобы она работала с контейнером list. Печатайте по одному значению на строке. Вот пример вывода для списка из двух строк:
 
 ( 2 )
 <
 "first string"
 "second string"
 >
 
 Напишите функцию main(), вызывающую новый вариант putValues() со следующим списком строк:
 "put function declarations in header files"
 "use abstract container types instead of built-in arrays"
 "declare class parameters as references"
 "use reference to const types for invariant parameters"
 "use less than eight parameters"
 Упражнение 7.8
 В каком случае вы применили бы параметр-указатель? А в каком – параметр-ссылку? Опишите достоинства и недостатки каждого способа.
 7.4. Возврат значения
 В теле функции может встретиться инструкция return. Она завершает выполнение функции. После этого управление возвращается той функции, из которой была вызвана данная. Инструкция return может употребляться в двух формах:
 return;
 return expression;
 Первая форма используется в функциях, для которых типом возвращаемого значения является void. Использовать return в таких случаях обязательно, если нужно принудительно завершить работу. (Такое применение return напоминает инструкцию break, представленную в разделе 5.8.) После конечной инструкции функции подразумевается наличие return. Например:
 void d_copy( double "src, double *dst, int sz )
 {
  /* копируем массив "src" в "dst"
  * для простоты предполагаем, что они одного размера
  */
 
  // завершение, если хотя бы один из указателей равен 0
  if ( !src || !dst )
  return;
 
  // завершение,
  // если указатели адресуют один и тот же массив
  if ( src == dst )
  return;
 
  // копировать нечего
  if ( sz == 0 )
  return;
 
  // все еще не закончили?
  // тогда самое время что-то сделать
  for ( int ix = 0; ix < sz; ++ix )
  dst[ix] = src[ix];
 
  // явного завершения не требуется
 }
 Во второй форме инструкции return указывается то значение, которое функция должна вернуть. Это значение может быть сколь угодно сложным выражением, даже содержать вызов функции. В реализации функции factorial(), которую мы рассмотрим в следующем разделе, используется return следующего вида:
 return val * factorial(val-1);
 В функции, не объявленная с void в качестве типа возвращаемого значения, обязательно использовать вторую форму return, иначе произойдет ошибка компиляции. Хотя компилятор не отвечает за правильность результата, он сможет гарантировать его наличие. Следующая программа не компилируется из-за двух мест, где программа завершается без возврата значения:
 // определение интерфейса класса Matrix
 #include "Matrix.h"
 
 bool is_equa1( const Matrix &ml, const Matrix &m2 )
 {

<< Пред.           стр. 33 (из 121)           След. >>

Список литературы по разделу