С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 55
Текст из файла (страница 55)
Ключевое слово class означает, чтоидентификатор Type служит именем параметра, при конкретизации шаблона функции331С++ для начинающих332putValues() он заменяется на реальный тип – 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<int> вместо встроенного типа массива.Контейнерный тип является классом и обеспечивает значительно большуюфункциональность, чем встроенные массивы. Так, vector<int> “знает” собственныйразмер. В предыдущем подразделе мы видели, что размер параметра-массива неизвестенфункции и для его передачи приходится задавать дополнительный параметр.Использование vector<int> позволяет обойти это ограничение. Например, можноизменить определение нашей putValues() на такое:С++ для начинающих#include <iostream>#include <vector>const lineLength =12; // количество элементов в строкеvoid putValues( vector<int> 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";}void putValues( vector<int> );int main() {int i, j[ 2 ];// присвоить i и j некоторые значенияvector<int> vec1(1); // создадим вектор из 1 элементаvecl[0] = i;putValues( vecl );vector<int> vec2;// создадим пустой вектор// добавим элементы к vec2for ( int ix = 0;ix < sizeof( j ) / sizeof( j[0] );++ix )// vec2[ix] == j [ix]vec2.push_back( j[ix] );putValues( vec2 );return 0;Функция main(), вызывающая нашу новую функцию putValues(), выглядит так:}Заметим, что параметр putValues()передается по значению.
В подобных случаяхконтейнер со всеми своими элементами всегда копируется в стек вызванной функции.Поскольку операция копирования весьма неэффективна, такие параметры лучшеобъявлять как ссылки.Как бы вы изменили объявление putValues()?Вспомним, что если функция не модифицирует значение своего параметра, топредпочтительнее, чтобы он был ссылкой на константный тип:void putValues( const vector<int> & ) { ...333С++ для начинающих3347.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, '#');Фактические аргументы сопоставляются с формальными параметрами позиционно (впорядке следования), и значения по умолчанию могут использоваться только дляподстановки вместо отсутствующихпоследних аргументов. В нашем примере// эквивалентно screenInit('?',80,' ')cursor = screenInit('?');// ошибка, неэквивалентно screenInit(24,80,'?')невозможно задать значение для background, не задавая его для height и width.cursor = screenInit( , ,'?');При разработке функции с параметрами по умолчанию придется позаботиться об ихрасположении.
Те, для которых значения по умолчанию вряд ли будут употребляться,необходимо поместить в начало списка. Функция screenInit() предполагает(возможно, основываясь на опыте применения), что параметр height будет востребованпользователем наиболее часто.С++ для начинающих335Значения по умолчанию могут задаваться для всех параметров или только для некоторых.При этом параметры без таких значений должны идти раньше тех, для которых они// ошибка: width должна иметь значение по умолчанию,// если такое значение имеет heightchar *screenlnit( int height = 24, int width,указаны.char background = ' ' );Значение по умолчанию может указываться только один раз в файле.
Следующая запись// tf.hint ff( int = 0 );// ft.С#include "ff.h"ошибочна:int ff( int i = 0) { ... } // ошибкаПо соглашению значение задается в объявлении функции, которое размещается вобщедоступном заголовочном файле (описывающем интерфейс), а не в ее определении.Если же указать его в определении, это значение будет доступно только для вызововфункции внутри исходного файла, содержащего это определение.Можно объявить функцию повторно и таким образом задать дополнительные параметрыпо умолчанию.
Это удобно при настройке универсальной функции для конкретногоприложения. Скажем, в системной библиотеке UNIX есть функция chmod(), изменяющаярежим доступа к файлу. Ее объявление содержится в системном заголовочном файле<cstdlib>:int chmod( char *filePath, int protMode );protMode представляет собой режим доступа, а filePath – имя и каталог файла. Если внекотором приложении файл только читается, можно переобъявить функцию chmod(),задав для соответствующего параметра значение по умолчанию, чтобы не указывать его#include <cstdlib>при каждом вызове: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 расположен правеедругих, не имеющих значения по умолчанию.
Поэтому требование присваивать такие#include "ff.h"int ff( int a, int b = 0, int с ); // правильнозначения справа налево не нарушается. Теперь мы можем переобъявить ff() еще раз: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( ... );336С++ для начинающихПервый формат предоставляет объявления для части параметров.
В этом случае проверкатипов для объявленных параметров производится, а для оставшихся фактическихаргументов – нет. Запятая после объявления известных параметров необязательна.Примером вынужденного использования многоточия служит функция 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Какие из следующих объявлений содержат ошибки? Объясните.337С++ для начинающих338(a) void print( int arr[][], int size );(b) int ff( int a, int b = 0, int с = 0 );(d)char*screenInit( int height = 24, int width,char background );(c) void operate( int *matrix[] );(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.6void 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];Вызовы этих функций содержат ошибки.