Нижегородский государственный университет им. Н.И.Лобачевского
Опубликован: 25.11.2008 | Доступ: свободный | Студентов: 9592 / 1296 | Оценка: 4.06 / 3.66 | Длительность: 21:16:00
Лекция 8:

Функции и их аргументы

< Лекция 7 || Лекция 8: 123 || Лекция 9 >

7.4. Параметры-константы

В заголовках некоторых функций можно встретить объявление формального параметра с добавлением служебного слова const. Это означает, что в теле программы значение формального параметра меняться не должно. Но ведь тот программист, который пишет тело функции, вряд ли захочет по собственной воле изменять значение параметра, зная, что компилятор предупредит такую ошибку. В чем же смысл объявления некоторых параметров константными?

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

7.5. Параметры по умолчанию

В языке C++ допускается определение функций, у которых в заголовке указаны значения некоторых параметров:

double mid1(double x=0.5, double y=0.5)
  { return (x+y)/2.; }

К такой функции можно обратиться с одним (первым) аргументом или вообще без аргументов:

z=mid1(0.75);	//результат равен 0.625=(0.75+0.5)/2.
  p=mid1();	//результат равен 0.5=(0.5+0.5)/2.

Значения параметров по умолчанию могут быть заданы не выборочно, а только для параметров, идущих подряд и расположенных в конце списка. Это означает, что компилятор расценивает как ошибку следующий заголовок функции:

double mid2(double x=0.5,double y)

В отличие от этого следующий заголовок считается правильным:

double mid3(double x,double y=0.5)

Обратите внимание на одну практическую деталь. Функция с параметрами по умолчанию работает правильно в двух случаях. Во-первых, если ее описание находится выше вызывающей функции и в заголовке функции содержится информация о параметрах по умолчанию. Во-вторых, если ее описание находится ниже и в заголовке функции отсутствуют сведения о параметрах по умолчанию, но они содержатся в прототипе. Одновременное упоминание значений по умолчанию и в заголовке функции, и в прототипе приводит к сообщению об ошибке как в системе BC 3.1, так и в ВСВ. Ниже приводится один из вариантов правильного оформления такой программы:

#include <iostream.h>
#include <conio.h>

double mid(double x=1.,double y=1.);

void main()
{ //double mid(double x,double y);
  double x=0.4,y=0.2,z;
  z=mid(x,y); cout<<"z="<<z<<endl;
  z=mid(x);   cout<<"z="<<z<<endl;
  z=mid();    cout<<"z="<<z<<endl;
  getch();
}

double mid(double x,double y)
{ return (x+y)/2.; }

7.6. Функции с переменным количеством аргументов

Постоянная работа с функциями типа printf или scanf вызывает у программистов зависть – это же функции с переменным количеством аргументов. А как написать свою функцию, обрабатывающую столько параметров, сколько будет задано в обращении, и, естественно, допускающую задание разного количества аргументов?

Рассмотрим в качестве примера функцию, вычисляющую среднее арифметическое нескольких своих аргументов, имеющих тип double. Вызванная функция может догадаться о количестве переданных ей параметров только в том случае, если ей сообщают (например, значением первого аргумента) это число n, либо список параметров завершается обусловленным признаком – например, нулевым значением последнего параметра.

Если мы собираемся сообщать функции количество передаваемых ей аргументов, то заголовок функции можно оформить следующим образом:

double mid_var(int n,...)

Три точки в конце списка предупреждают компилятор о том, что он не должен контролировать количество и типы следующих аргументов. Все заботы о доступе к списку параметров переменной длины берет на себя вызываемая функция. Предположим, далее, что все аргументы передаются в функцию mid_var как значения, т.е. к моменту передачи управления функции они находятся в стеке. Добраться до них можно следующим образом. Заведем указатель типа int и занесем в него адрес формального параметра n (система знает, где находится стек, и адрес n ей доступен):

int *ip=&n;

Продвинем указатель ip на 1, т.е. переместимся на адрес начала следующего параметра, и занесем его в новый указатель dp уже типа double:

ip++;	//переход на адрес первого слагаемого
  double *dp=(double *)ip; 	//преобразование типа указателя

Теперь адрес начала списка слагаемых у нас есть, количество слагаемых мы тоже знаем, поэтому все остальное – дело техники. Окончательный вид функции таков:

double mid_var(int n,...)
{ int *ip=&n+1;
  double *dp=(double *)ip; 
  double s=0.;
  for(int j=0; j<n; j++)
    s += dp[j];	//или s += *(dp+j); или s += *(dp++);
  return s/n;
}

Теперь попытаемся построить аналогичную функцию, которая суммирует свои аргументы до тех пор, пока не встречает нулевое слагаемое. Она устроена еще проще:

double mid_var(double a1,...)
{ double *dp=&a1;
  double s=0;
  int c=0;
  while(*dp != 0)
    { s += *(dp++); c++; }
  return s/c;
}

Аналогичные функции можно построить, когда список передаваемых параметров состоит из переменного количества однотипных указателей. Только здесь придется использовать не просто указатели типа *dp, а "двойные" указатели типа **dp. И доставать значения нужных данных придется также через двойные указатели s += (**dp);

В файле stdarg.h находится несколько функций (точнее, макроопределений) которые обеспечивают перемещение по списку параметров, завершающемуся нулем:

va_list p;		//объявление указателя на список параметров
va_start(p,p1);	//установка указателя списка на последний явный
			//параметр
va_arg(p,тип);	//перемещение указателя на очередной неявный параметр
va_end(p);		//уничтожение указателя на список параметров

Продемонстрируем использование этих средств на примере той же самой функции mid_var:

double mid_var(int n,...)	//функции передают количество параметров
{ va_list p;
  double s=0,c=0;
  va_start(p,n);
  while(n--)	//до тех пор, пока n != 0
  { s += va_arg(p,double); c++; }
  va_end(p);
  return s/c;
}

Если список параметров начинается с первого слагаемого a1, то программа меняется очень незначительно:

double mid_var(double a1,...)
{ va_list p;
  double s=0,c=0,u=a1;
  va_start(p,a1);
  do {s += u; c++; }
  while(u=va_arg(p,double));	//до тех пор, пока u != 0
  va_end(p);
  return s/c;
}

На наш взгляд, применение обычных указателей выглядит несколько проще, чем использование стандартных средств.

7.7. Локальные, глобальные и статические переменные

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

К числу локальных переменных следует отнести и формальные параметры функции. Всем им оперативная память выделяется в момент вызова функции и возвращается системе после завершения работы функции. Если объявление локальной переменной совмещается с ее инициализацией, то инициализация происходит при каждом вызове функции. Этим языки C, C++ отличаются от языка Паскаль, где инициализация происходит только один раз в момент загрузки программы в память.

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

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

  • глобальная переменная объявлена без спецификатора static ;
  • в другом программном файле или в теле "чужой" функции эта переменная упомянута со спецификатором extern.

Глобальная переменная, объявленная со спецификатором static, доступна только для функций данного программного файла.

7.8. Возврат значения функции

Значение, которое функция возвращает вызывающей программе, указывается в операторе return. В теле функции, возвращающей значение, может находиться несколько операторов return.

< Лекция 7 || Лекция 8: 123 || Лекция 9 >
Alexey Ku
Alexey Ku

Попробуйте часть кода до слова main заменить на 

#include "stdafx.h" //1

#include <iostream> //2
#include <conio.h>

using namespace std; //3

Александр Талеев
Александр Талеев

#include <iostream.h>
#include <conio.h>
int main(void)
{
int a,b,max;
cout << "a=5";
cin >> a;
cout <<"b=3";
cin >> b;
if(a>b) max=a;
else max=b;
cout <<" max="<<max;
getch();
return 0;
}

при запуске в visual express выдает ошибки 

Ошибка    1    error C1083: Не удается открыть файл включение: iostream.h: No such file or directory    c:\users\саня\documents\visual studio 2012\projects\проект3\проект3\исходный код.cpp    1    1    Проект3

    2    IntelliSense: не удается открыть источник файл "iostream.h"    c:\Users\Саня\Documents\Visual Studio 2012\Projects\Проект3\Проект3\Исходный код.cpp    1    1    Проект3

    3    IntelliSense: идентификатор "cout" не определен    c:\Users\Саня\Documents\Visual Studio 2012\Projects\Проект3\Проект3\Исходный код.cpp    6    1    Проект3

    4    IntelliSense: идентификатор "cin" не определен    c:\Users\Саня\Documents\Visual Studio 2012\Projects\Проект3\Проект3\Исходный код.cpp    7    1    Проект3

при создании файла я выбрал пустой проект. Может нужно было выбрать консольное приложение?