В. Ю. Наумов Введение в информатику
Скачать 2.05 Mb.
|
Функция, возвращающая void – подпрограмма языка C++, предназначенная для формирования нескольких значений и/или выполнения некоторых действий, не связанных напрямую с изменением значений параметров. Описывается она точно также, как и предыдущий вид функций, но в качестве типа возвращаемого значения используется void и в ее определении отсутствует оператор return. X[i][j] > 0 int summa(int x[10][15], int n, int m) Выход i=0; i S=0 j=0; j Рис. 6.4. Функция суммы положительных элементов 181 ПРИМЕР procedure Vvod(var X:T2mx; Var N,M:byte; Name:char); или так: void Vvod (int x[10][15], int &n, int &m) { … } void SqrtMass(int x[10][15], int n, int m) { … } Вызов такой функции – это отдельный оператор, в котором указывается <Имя_функции>(<список фактических параметров>); Опишем полностью функции, описанные выше. Функция ввода двумерного массива (рис. 6.5, а): void Vvod (int x[10][15], int &n, int &m) { cout<<”\nInput count of rows”; cin< Ввод x[i][j] void SqrtMass(int x[10][15], int n, int m) Выход i=0; i 2 а) б) Рис. 6.5. Ввод двумерного массива (а), возведение его элементов в квадрат (б) 182 cin< } } Возведение в квадрат элементов двумерного массива будет таким (рис. 6.5, б): void SqrtMass(int x[10][15], int n, int m) { for (int i=0; i 6.3. Локальные и глобальные идентификаторы Ранее отмечалось, что главная программа и входящие в ее состав функции имеют свои разделы описаний, а потому объявленные в этих разделах идентификаторы (константы, переменные) обладают разными свойствами. Идентификаторы, описанные в функции, являются локальными для нее, т. е. работа с ними возможна только внутри этой функции и внутри вложенных в нее блоков. Имена, описанные в модулях более высокого уровня, являются глобальными для всех своих подчиненных. Эти имена могут быть использованы в любом модуле стоящем ниже на иерархической лестнице, а также в исполнительной части самого модуля. Если объявление глобальных переменных 20 происходит в основной программе, то во время ее работы значения глобальных переменных 20 Здесь под переменной подразумеваются данные, которые могут храниться за любым идентификатором (константой, типом и т.д.), дело в том, что именно переменные являются основным объектом работы большинства алгоритмов. 183 записываются в область памяти, называемую сегментом данных (статический сегмент) и доступны постоянно на протяжении всей работы программы. Локальные данные записываются в иную специальную область памяти, называемую стеком и доступны только во время работы функции, в которой они описаны; по завершении работы функции эти данные стираются. Основные правила работы с глобальными и локальными переменными: – локальные переменные доступны внутри блока, в котором они описаны, и во вложенных в него блоках; – имя, описанное в локальном блоке, «закрывает» совпадающее с ним имя из блока более высокого уровня. То есть если при обработке функции возникла коллизия имен (имена глобальной и локальной переменных совпадают), то обрабатываться будет локальная переменная, до тех пор, пока работа с функцией не закончится. Однако, как говорилось ранее, все глобальные переменные доступны в подпрограмме. Если возникает потребность в обращении к переменной при коллизии имен, то следует полностью указывать ее имя вместе с названием модуля. Делается это так: вначале указывается название модуля (модуль основной программы – это, собственно, название программы, указанное после слова program), а далее через двойное двоеточие имя переменной (или иной идентификатор), к которому нужно обратиться. Для примера, изображенного на рис. 6.6, полные обращения к переменным такие: 184 G::X, G::Y, G::A::A1::XA1, G::C::YC и т. д. На рис. 6.6 изображена иерархическая структура некоторой условной программы. Эта программа имеет основной глобальный модуль обозначенный как G. В G объявлены переменные X и Y, которые являются глобальными по отношению ко всем подчиненным модулям. Область видимости этих переменных абсолютная, т.е. в данной программе они доступны из любой точки. А переменные XA и YA из блока A будут видны помимо собственно блока A еще и в блоках A1 и A2. А, например, переменные XB22 и YB22 нигде, кроме B22, не будут видны (они для B22 являются локальными идентификаторами). Блок А1 Блок А2 Блок А Блок B1 Блок B Блок С Блок B21 Блок B2 Блок B22 Глобальный блок G ( переменные X, Y) ( переменные XA, YA) ( переменные XA1, YA1) ( переменные XA2, YA2) ( переменные XB, YB) ( переменные XB1, YB1) ( переменные XC, YC) ( переменные XB2, YB2) ( переменные XB21, YB21) ( переменные XB22, YB22) Рис. 6.6. Пример иерархии функций и переменных 185 Упрощенно можно определить локальные идентификаторы функций как те, которые описываются в теле функции и в скобках с параметрами. Глобальными можно назвать все те идентификаторы, которые используются в основной программе, в том числе и в скобках при вызове функций. 6.4. Параметры функций Все то, что записывается в скобках сразу после названия функции, называется ее параметрами. Параметры, по сути, являются тем интерфейсом, с помощью которого данная функция «общается с внешним миром». При обмене данными между программой и функциями используется механизм передачи входных и выходных параметров. Входные параметры – это исходные для функции данные, а выходные – результат ее работы. Для того чтобы функция могла быть использована многократно для разных наборов входных и выходных параметров, используют наборы формальных и фактических параметров. Формальные параметры – это локальные переменные, необходимые для описания алгоритма функции, они описываются в ее заголовке и используются в ее определении. Выше говорилось, что формальные параметры – все то, что указывается в скобках справа от названия функции при ее описании. Потому описание формальных параметров происходит только один раз. Вообще говоря, список формальных параметров является необязательной частью заголовка, его наличие зависит от способа обмена информацией функции с вызывающим блоком, т. е., например, возможен такой вариант заголовка: void fun(); 186 В этом случае функция вызывается просто по имени (обязательно указывать круглые скобки): fun(); Однако использование функций без параметров, как правило, предполагает либо независимость исходных данных от данных глобального модуля, либо использование глобальных переменных (или иных идентификаторов). Если есть возможность, то лучше не пользоваться глобальными переменными, поскольку это позволяет повысить автономность функции и надежность ее алгоритма. Фактические параметры – это набор данных, в обработке которых и заключается предназначение алгоритма. В момент вызова формальные параметры связываются с фактическими во всей функции. Другими словами, фактические параметры – все то, что указывается в скобках справа от названия функции при ее вызове. Фактические параметры у функции могут меняться при каждом вызове, а формальные нет. Имена формальных и фактических параметров могут совпадать, это не отразится на выполнении программы, но может привести к проблемам при понимании алгоритма работы, поэтому рекомендуется использовать для формальных и фактических переменных разные имена. Следует отметить, что поскольку параметры представляют собой интерфейс связи между главным модулем и функциями, то параметры заявленные (формальные) должны соответствовать параметрам фактическим. Критериев такого соответствия принято выделять всего четыре: – по количеству, т. е. количество заявленных и реально используемых переменных должно совпадать; – по типу, т. е. тип заявленных и реально используемых переменных должен совпадать; 187 – по порядку следования, т. е. переменные в описании функции и при ее вызове должны быть перечислены в одинаковом порядке; – по способу передачи, т. е. статус параметров в главной программе должен быть совместимым с заявленным статусом параметров функции. На способах передачи параметров стоит остановиться подробнее. В зависимости от того, является передаваемый параметр входным или выходным, различают и способ его передачи. Для языка С++ условно можно выделить три способа передачи: – по значению; – по ссылке с правом изменения; – по ссылке без права изменения. Основным моментом, важным для понимания, является усвоение принципов, лежащих в основе передачи по значению или по ссылке. Условно разницу этих двух видов передач можно изобразить так, как показано на рис. 6.7. По значению передаются параметры-значения, являющиеся простыми входными данными, т. е. константами, именами переменных и простыми выражениями. При этом значение передаваемого фактического параметра копируется в память, отводимую под функцию (стек), и работа с ним осуществляется как с локальной переменной, т. е. его можно изменять, но результат изменений в вызывающий блок передан не будет, а будет удален при завершении работы функции. В качестве начального значения формальный параметр получает текущее значение соответствующего фактического параметра. Таким образом, параметры-значения можно использовать в тех случаях, когда, например, результат работы функции выводится непосредственно на экран и его не надо передавать вызывающему блоку. 188 Можно сказать, что при передаче по значению в локальном блоке организуется копия переданной переменной. Таким образом, мы имеем сразу две переменные, одна из них – глобальная, а вторая – локальная. Это может привести к тому, что при достаточно большом размере этих переменных возникнет дефицит памяти. Кроме того, при завершении функции локальная копия параметра уничтожается, вместе со всеми остальными переменными функции. Это может являться причиной ситуации, когда функции не содержит синтаксических ошибок и производит модификацию параметра, переданного по значению; при ее завершении вычисленное значение теряется, принимая значение бывшее при входе в нее. Подобного рода ошибки не всегда удается четко отследить, а потому рекомендуется как можно реже прибегать к передаче параметров по значению. Если нужно запретить подпрограмме модификацию передаваемого параметра, то стоит воспользоваться передачей по ссылке без права изменения. При описании параметров, передаваемых по значению в языке С++, перед их именами в скобках никаких префиксов не ставится. Вот примеры передачи всех параметров по значению: Блок А Глобальный блок G X Y XA YA Блок А Глобальный блок G X Y XA YA а) б) Рис. 6.7. Передача параметров: по значению (а), по ссылке (б) 189 void Summa (int x[10][15], int y, int z) {…} или так: void Vivod (int x[10][15], int n, int m, char name) {…} При передаче по ссылке передается ссылка на адрес памяти в сегменте данных, т. е. на область памяти, в которой хранится фактический параметр. По ссылке можно передавать параметры с правом или без права модификации. В зависимости от разрешения на модификацию, различают параметры-константы и параметры-переменные. Параметры константы – параметры, переданные по ссылке без права их изменения. Параметры-константы используются, когда передаются входные данные, являющиеся сложными структурированными переменными (например, массивы). При таком способе передачи изменение формального параметра запрещено; если переданный параметр будет изменяться, компилятор выдаст ошибку. Для использования этого способа передачи в списке формальных параметров перед параметром-константой ставится префикс const. Вот примеры объявления параметров-констант: void Vivod(cons tint x[10][10], const int n) {…} Параметры-переменные – параметры, переданные по ссылке с правом их изменения. Параметры-переменные используются для передачи выходных значений функций. При изменении параметров-переменных изменяется соответствующий фактический параметр; таким образом, изменения сохранятся и после завершения работы функции. Для использования этого способа передачи в списке формальных параметров перед параметром-переменной ставится префикс &. Не стоит передавать по ссылке с правом изменения параметры, о которых точно известно, что в данной функции они не меняются. Соблюдение этого правила поможет предотвратить возможные ошибки. Примеры заголовка функции с параметрами-переменными: 190 void Vvod (int x[10][15], int&n, int &m) {…} Так как имя массива является указателем на область памяти, в которой храянтся его элементы, то массивы в функции передаются всегда по ссылке. Этим обусловлено отсутствие знака & перед именами массивов в предыдущих примерах. Строки при передаче в функции могут передаваться как одномерные массивы типа char или как указатели типа char*. В отличие от обычных массивов в функции не указывается длина строки, т. к. в конце строки есть признак конца строки /0. Как говорилось ранее, при передаче необходимо соблюдение соответствия между формальными и фактическими параметрами по способу передачи. Чтобы понять, что это такое, есть смысл рассмотреть пример, в котором данное соответствие не выполняется: формальный набор: void pr1(int &a, int &b) {…} фактический набор: pr1(10, S); Очевидно несоответствие по способу передачи, поскольку для переменной A заявлена передача по ссылке с правом изменения. Для A при вызове функции в стеке не будет резервироваться место и потому возникает неопределенность с местом хранения этой переменной, ведь при вызове функции вместо переменной указана константа 10. Еще одно скрытое несоответствие может быть, если, например, указанный фактический параметр S будет являться не переменной, а, скажем, именованной константой, тогда при попытке изменения его при фактическом вызове программы опять возникнет проблема доступа к S. В функции сказано, что на месте S находится переменная (int &b), а в основной программе – константа. Подобные несоответствия выявляются еще на этапе компиляции. В случае если перед заявленным параметром 191 указать префикс const (параметр-константа), то С++ при несоответствии не выдаст ошибку, а поместит значение в стек и продолжит работу. В определении функции может содержаться начальное (умалчиваемое) значение параметра. Это значение используется, если при вызове функции соответствующий параметр опущен. Все параметры, описанные справа от такого параметра также должны быть умалчиваемыми. ПРИМЕР void print(char *name=”Номер дома: ”,int value=1) { cout<<”\n”< Вызовы: 1. print(); // Вывод: Номер дома: 1 2. print(“Номер квартиры”,15); // Вывод: Номер квартиры: 15 3. print(,15); // ошибка //т. к. параметры можно пускать только с конца Поэтому функцию лучше переписать так: void print(int value=1, char*name=”Номер дома: ”) { cout<<”\n”< Вызовы: 1. print(); //Вывод: Номер дома: 1 2. print(15); //Вывод: Номер дома: 15 3. print(6, “Размерность пространства”); //Вывод: Размерность пространства: 6 192 В С++ реализован механизм перегрузки функций. Цель перегрузки состоит в том, чтобы функция с одним именем по-разному выполнялась и возвращала разные значения при обращении к ней с различными типами и различным числом фактических параметров. Для обеспечения перегрузки необходимо для каждой перегруженной функции определить возвращаемые значения и передаваемые параметры так, чтобы каждая перегруженная функция отличалась от другой функции с тем же именем. Компилятор определяет какую функцию выбрать по типу фактических параметров. ПРИМЕР void fun (int a, int b, int &c) { c=a+b; } int fun(int a, int b) { return a*b; } Правила описания перегруженных функций: 1) Перегруженные функции должны находиться в одной области видимости. 2) Перегруженные функции могут иметь параметры по умолчанию, при этом значения одного и того же параметра в разных функциях должны совпадать. В разных вариантах перегруженных функций может быть разное количество умалчиваемых параметров. 3) Функции не могут быть перегружены, если описание их параметров отличается только модификатором const или наличием ссылки. Например, функции int&f1(int&,const int&){. . . } и int f1(int,int){. . . } – не являются перегруженными, т. к. компилятор не сможет узнать какая из функций вызывается: нет синтаксических отличий между вызовом функции, которая передает параметр по значению и функции, которая передает параметр по ссылке. 193 ПРИМЕР Ввести пять квадратных матриц Na Na A , Nb Nb B , Nc Nc C , Nd Nd D , и Ne Ne E Для этого можно использовать функцию ввода матрицы с набором формальных параметров, состоящего из матрицы X и размерности матрицы Y, и переменной символьного типа, используемой для передачи имени матрицы в функцию. При этом функция ввода будет описана один раз, а вызываться пять раз (рис. 6.8). #include { Функция формирования матрицы void Vvod(int x[10][10], int &n, char name) Выход i=0; i Начало Конец Vvod(A,Na, ’A’) Vvod(B,Nb, ’B’) Vvod(C,Nc, ’C’) Vvod(D,Nd, ’D’) Vvod(E,Ne, ’E’) Ввод n Ввод x[i][j] A,Na, ’A’,B,Nb,’B’,C,Nc,’C’, D,Nd, ’D’,E,Ne,’E’ – фактические параметры A,Na,B,Nb,C,Nc,D,Nd,E,Ne, – глобальные переменные X, N, Mname – формальные параметры X, N, Mname, i, j – локальные переменные Рис. 6.8. Ввод пяти разных матриц при помощи одной функции 194 cout<<”\nInput count of rows and columns”; cin< } } int main() { int a[10][10], b[10][10], c[10][10], d[10][10], e[10][10]; int na, nb, nc, nd, ne; vvod(a,na,’A’); //вызов процедуры Vvod vvod(b,nb,’B’); //с фактическими параметрами vvod(c,nc,’C’); //A,Na,’A’ , B,Nb,’B’ и т.д. vvod(d,nd,’D’); //A,B,C,D,E и Na,Nb,Nc,Nd,Ne - это vvod(e,ne,’E’); //глобальные переменные } |