Попробуйте часть кода до слова main заменить на #include "stdafx.h" //1 #include <iostream> //2 using namespace std; //3 |
Функции и их аргументы
Функции являются основными программными единицами в языках C, C++. Из них как из кирпичиков складывается программа. В отличие от алголоподобных языков в программах на C, C++ не допускается вложенность функций. Любая программа на C должна содержать главную функцию с именем main, с которой начинается выполнение программы. Вызов всех остальных функций прямо или косвенно инициируется главной функцией.
Аргументы функций – один из основных способов обмена информацией между частями программы. Конечно, для этих целей можно использовать и другие средства – глобальные переменные, внешнюю память (файлы на дисках). Но обилие глобальных переменных заставляет очень тщательно согласовывать имена общих переменных, усиливает зависимость программных единиц друг от друга, налагает ограничения на выбор имен локальных переменных. Кроме того, общедоступность глобальных переменных может привести к несогласованному их изменению разными функциями. А использование внешней памяти в ряде случаев приводит к резкому замедлению работы программы.
В системах программирования на IBM-совместимых компьютерах используются два основных механизма передачи параметров – через стек и через машинные регистры. Первый способ наиболее распространен, т.к. он не ограничивает объем передаваемой информации. В качестве стека используется определенный участок оперативной памяти с фиксированным, но управляемым диапазоном адресов (размер стека можно регулировать при настройке компилятора). Специальный регистр "следит" за очередным доступным участком стека. По адресу, хранящемуся в этом регистре, можно положить нужную порцию данных в стек и одновременно продвинуть содержимое регистра стека. Для этой цели система машинных команд предусматривает специальную операцию PUSH (от англ. – протолкнуть). Вторая машинная операция POP (от англ. pop up – выскочить наверх) позволяет извлечь из стека очередную порцию данных с одновременной коррекцией регистра стека. Системная программа, обслуживающая стек, следит за тем, чтобы стек не переполнился при записи и не оказался пустым при извлечении данных. Иногда механизм работы стека сравнивают с магазином огнестрельного оружия – в нем, пуля, попавшая последней в рожок автомата, стреляет первой. Этим же объясняется технология обслуживания стека LIFO (Last In – First Out, т.е. последним вошел – первым вышел). Зарядка магазина имитирует запись в стек, а процедура стрельбы напоминает извлечение данных из стека.
Второй механизм позволяет ускорить процедуру передачи параметров, помещая их в специально выделенные регистры процессора. Однако таких регистров мало, поэтому этот механизм применяют в тех случаях, когда количество передаваемых параметров не превышает трех. В системе программирования BCB для регистровой передачи параметров существует специальная конструкция быстрого вызова ( fastcall ).
Несмотря на то, что главную функцию никто не вызывает, у нее тоже могут быть параметры, передаваемые ей операционной системой при запуске из командной строки. В большинстве случаев таких параметров два:
int main(int argc, char* argv[])
или
int main(int argc, char **argv)
Предположим, что наша программа с именем nameprog.exe была запущена из командной строки со следующими аргументами:
>nameprog par1 par2 par3
Тогда первый аргумент главной функции argc будет равен 4 (имя программы входит в список параметров командной строки). Второй аргумент функции main представляет собой строковый массив, элементами которого являются отдельные компоненты командной строки:
Параметры командной строки позволяют упростить запуск программы, работа которой зависит от информации, набираемой вручную с клавиатуры. Вспомните довольно старую, но надежную программу архивации данных:
>pkzip –a –r arch.zip qq1.doc qq2.doc
Функция может возвращать значение, – результат своей работы, или выполнять некоторое другое действие, не связанное с возвратом результата. Например, функция clrscr() осуществляет очистку экрана, но не возвращает никакого значения. Если функция возвращает значение, то в ее заголовке перед именем функции должен быть указан тип возвращаемого значения:
double mid(double x,double y)
В теле функции, возвращающей значение, обязан присутствовать оператор return (от англ. – возврат), содержащий результат работы функции – ее значение:
double mid(double x, double y) { return (x+y)/2.; }
Если функция не возвращает значение, то в ее заголовке перед именем функции должен быть указан тип void. В этом случае в теле функции может встретиться оператор return без параметра. Но оператор return может и отсутствовать – выход из функции произойдет при достижении последней фигурной скобки:
void print_v(int *a,int n) { int j; printf("\n"); for(j=0; j<n; j++) printf("%8d",a[j]); printf("\n"); }
Если перед именем функции не указан ни один из стандартных типов и отсутствует спецификатор void, то считается, что функция возвращает значение типа int.
7.1. Параметры-значения
Формальный параметр в заголовке функции называют параметром-значением, если перед его именем указан только тип. Например, функция mid, вычисляющая среднее арифметическое двух величин, получает в качестве фактических аргументов два числовых значения определенного типа:
double mid(double x, double y) { return (x+y)/2.; }
В качестве фактических аргументов, соответствующих параметрам- значениям, могут быть заданы любые числовые выражения (формулы):
w1 = mid(x*cos(fi)+y*sin(fi), x*sin(fi)-y*cos(fi));
Значения этих выражений вычисляются и записываются в стек, откуда их извлекает функция mid и помещает переданные значения в свои локальные переменные x и y (формальные параметры-значения можно рассматривать как локальные переменные функции). При необходимости, значение вычисленного выражения автоматически приводится к типу формального параметра. После работы функции возвращаемый результат возвращается в специально выделенном регистре.
Почти все математические функции раздела math.h используют передачу аргумента по значению.
7.2. Параметры-указатели
Формальный параметр в заголовке функции называют явным параметром- указателем, если перед его именем находится символ *. Например, функция swap1, осуществляющая перестановку местами значений двух переменных, должна получить в качестве параметров адреса этих переменных, т.е. указатели на них:
void swap1(int *x,int *y) //явные параметры-указатели { int tmp=*x; *x=*y; *y=tmp; }
Кроме явных параметров-указателей в объявлении функции можно использовать и косвенные параметры-указатели, описанные с помощью механизма подстановок:
#define pint int* ............... void swap1(pint x,pint y) //косвенные параметры указатели { int tmp=*x; *x=*y; *y=tmp; }
Косвенное объявление указателей может использовать и другую синтаксическую конструкцию:
typedef int* pint;
Представим себе, что функция swap1 была бы оформлена с параметрами-значениями:
void swap1(int x,int y) { int tmp=x; x=y; y=tmp; }
Тогда переставляемые значения поступили бы через стек и попали бы в локальные переменные (формальные параметры) x и y. Перестановка значений в локальных переменных была бы произведена, но вызывающая программа об этом ничего бы не узнала.
Таким образом, если мы хотим, чтобы результат работы вызываемой функции нашел отражение в передаваемых параметрах, мы должны сообщать не значения параметров, а их адреса. Зная адрес, вызванная функция сама может извлечь нужное значение и, при необходимости, отправить по этому адресу полученный результат.
Для вызова функции, параметрами которой являются указатели, в качестве фактических аргументов надо задавать адреса переменных, т.е. их имена с предшествующим символом &:
int x=2,y=3; swap1(&x,&y);
Рассмотрим еще один пример передачи указателя по указателю:
void swap3(int **v1, int **v2) { int *tmp=*v1; *v1=*v2; *v2=tmp; } К этой функции можно обратиться следующим образом: int i=10, j=20; int *pi=&i, *pj=&j; swap(&pi,&pj);
после такого обращения указатели pi и pj "смотрят" на новые значения, т.к. они поменялись адресами ( *pi=20, *pj=10 ), но сами переменные i и j свои значения не поменяли.
Одним из наиболее распространенных способов использования указателей является передача в качестве адреса имени массива. Например, для суммирования компонент вектора можно воспользоваться следующей функцией:
int sum_v(int *a,int n) { int j,s=0; for(j=0; j<n; j++) s += a[j]; return s; }
Обращение к такой функции может выглядеть следующим образом:
int q[20]; .......... k1=sum_v(q,20); //суммирование всех компонент вектора k2=sum_v(q,10); //суммирование первых 10 компонент вектора k3=sum_v(&q[5],3); //суммирование q[5]+q[6]+q[7] k4=sum_v(q+5,3); //суммирование q[5]+q[6]+q[7]
Не забывайте, что имя массива одновременно является и указателем на его первый элемент (т.е. q и &q[0] – это одно и то же).
7.3. Параметры-ссылки
Формальный параметр в заголовке функции называют явным параметром-ссылкой, если перед его именем находится символ &. Например, функция swap2, осуществляющая перестановку местами значений двух переменных, должна получить в качестве параметров ссылки на эти переменные, т.е. их адреса:
void swap2(int &x,int &y) //явные параметры-ссылки { int tmp=x; x=y; y=tmp; }
Параметры-ссылки могут быть объявлены в заголовках функций и с помощью косвенных типов, предварительно описанных в конструкциях #define или typedef:
#define rint int& //или typedef int& rint; ................... void swap2(rint x,rint y)
Точно так же, как и параметр-указатель, параметр-ссылка является адресом. Поэтому вызванная функция по ссылке может извлечь и, при необходимости, изменить нужное значение. В отличие от указателя доступ по ссылке не требует добавления к имени переменной какого-либо символа. Во-первых, упрощается организация тела функции – в нем можно использовать просто имена параметров-указателей. Во-вторых, упрощается вызов такой функции – на месте фактических аргументов тоже можно писать просто имена объектов (т.к. имена объектов и ссылки на них являются эквивалентами):
int x=2,y=3; swap2(x,y);
По ссылке можно передавать не только имена переменных, но и имена указателей:
void swap3(int *&v1,int *&v2) { int *t=v2; v2=v1; v1=t; }
К этой функции можно обратиться следующим образом:
int i=10, j=20; int *pi=&i, *pj=&j; swap3(pi,pj);
После этого обращения указатели pi и pj "смотрят" на новые значения ( *pi=20, *pj=10 ), но сами переменные i и j сохранили свои прежние значения.