Глава 7. Функции 

Краеугольным камнем, лежащим в основе языков С и C++, являются функции. В данной главе рассматриваются основные концепции создания и использования функций. Вы познакомитесь с понятием прототипа функции, которое было введено в стандарте ANSIС. На многочисленных примерах будет показано, как работать с аргументами различных типов и как создавать функции, возвращающие значения различных типов. Вы также узнаете, как с помощью стандартных параметров argc и argv получать аргументы командной строки в функции main(). Кроме того, будет рассказано об уникальных возможностях функций в языке. C++.

Основную часть программного кода в C/C++ составляют функции. Они позволяют разбивать программу на отдельные блоки или модули. Таким образом, модульное программирование обязано своим появлением именно функциям. Под модульным программированием подразумевается создание программ из отдельных, самостоятельно отлаживаемых частей, совокупность которых образует единое приложение. Например, один модуль может выполнять функцию ввода данных, другой — реализовывать вывод данных на печать, а третий — отвечать за сохранение данных на диске. По сути, программирование на языках С и C++ как раз и заключается в написании функций. Любая программа на этих языках содержит, по крайней мере, одну функцию — main(). От того, насколько успешно будут проработаны функции, зависит эффективность и надежность работы программы.

В данной главе рассматривается много примеров программ, которые помогут вам лучше уяснить принципы создания функций. Многие из этих программ, помимо пользовательских, используют также встроенные библиотечные функции C/C++, которые значительно расширяют возможности программирования на этих языках.

Прототипы функций

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

Синтаксис объявления функции

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

В этой книге мы придерживаемся нового стиля, синтаксис которого таков:

тип_результата имя_функции(тип_аргумента необязательное_имя_аргумента[, ...]);

Функция может возвращать значения типа void, int,float и т.д. Имя функции может быть произвольным, но желательно, чтобы оно указывало на ее назначение. Если для выполнения функции нужно предоставить ей некоторую информацию в виде аргумента, то в круглых скобках должен быть указан тип аргумента и, при необходимости, его имя. Тип аргумента может быть любым. Если аргументов несколько, их описания (тип плюс имя) разделяются запятыми. Не будет ошибкой указание в прототипе только типа аргумента, без имени. Такая форма записи прототипов применяется в библиотечных функциях.

Описание функции представляет собой часть программного кода, которая, как правило, следует за телом функции main(). Синтаксис описания следующий:

тип_результата имя_функции(тип_аргумента имя_аргумента[,   ...])

{

тело функции )

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

/*
*  prototyp.c
*   Эта программа на языке С содержит описание функции.
*  Функция находит сумму двух целочисленных аргументов и возвращает
*  результат в виде целого числа.
*/

#include <stdio.h>

int iadder(int ix, int iy) ; /* прототип функции */

int main () {

int ia = 23;

int ib = 13;

int ic;

ic = iadder(ia, ib) ;

printf("Сумма равна %d\n",ic) ;

return(0);
}

int ladder(int ix, int iy)  /* описание функции */

{
int iz;

iz = ix + iy;

return(iz);    /* возвращаемое значение */
}

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

Описание функции iadder() в программе на языке C++ будет выглядеть идентично:

//
//      
prototyp.cpp
//       Эта программа на языке C++ содержит описание функции.
//       Функция находит  сумму двух целочисленных аргументов и возвращает
//       результат в виде целого числа.
//

#include <iostream.h>

int ladder(int ix, int iy); // прототипфункции

int main() {

int ia = 23;

int ib = 13;

int ic;

ic = iadder(ia,ib) ;

cout<< "Сумма равна " << ic<< "\n";

return (0); }

int ladder(int ix, int iy)  // описаниефункцииint iz; iz = ix + iy;

return(iz);       // возвращаемое значение

  

Способы передачи аргументов

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

В случае передачи переменной-аргумента по ссылке функция получает адрес аргумента, а не его значение. Создаваемая при этом локальная переменная является указателем. Такой подход, кроме всего прочего, позволяет немного сэкономить память. И, естественно, во время выполнения функции значение переменной-аргу­мента может измениться. Особенность этого метода состоит в том, что функция может возвращать сразу несколько значений тому процессу, из которого она была вызвана: как через аргументы-ссылки, так и непосредственно через инструкцию return.

В следующем примере рассмотренная выше функция ladder() принимает адреса аргументов, передаваемых по ссылкам. Такой способ может быть реализован как в С, так и в C++.

/*
*       ref.c
*  Эта программа на языке С демонстрирует использование указателей
*  в качестве аргументов функции.
*/

#include <stdio.h>
int ladder(int *pix, int *piy);
int main () 
int ia = 23;

int ib = 13;

int ic;

ic = iadder(&ia,Sib) ;

printf("Сумма равна %d\n", ic);

return(0); }

int ladder (int*pix, int *piy)

  

int iz;

iz = *pix + *piy; return(iz); }

В языке C++ при передаче аргументов можно вместо указателей использовать непосредственно ссылки. Это гораздо удобнее и упрощает текст программы, так как ссылки тоже указывают на аргумент, но не требуют использования оператора раскрытия указателя. Вот пример той же программы на языке C++:

//    
//       ref.cpp
//  Эта программа на языке C++ демонстрирует использование ссылок
//  в качестве аргументов функции.
//

#include <iostream.h>

int iadder (int Srix, int Sriy) ;

int main ()

{ 
int ia = 23;

int ib = 13;

int ic;

ic = iadder (ia,ib) ;

cout<< "Сумма равна " << ic<< "\n";

return (0);

)

int ladder(int Srix, int sriy) I

int iz;

iz = rix + riy; return(iz); }

Обратите внимание на отсутствие операторов взятия адреса при вызове функции и операторов раскрытия указателей в теле функции. В качестве аргументов функции используются ссылки rixи riy.

В C++ не допускается использование ссылок на ссылки, ссылок на битовые поля, массивов ссылок и указателей на ссылки.

Правила видимости переменных

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

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

Одно и то же имя переменной может быть сначала описано на глобальном, а затем на локальном уровне. В таком случае локальная переменная "закроет" собой глобальную переменную в теле функции. Для подобных ситуаций в языке C++ существует оператор расширения области видимости (::). Будучи примененным к переменной в теле функции, он позволяет сослаться на глобальную переменную с указанным именем, даже если в самой функции объявлена одноименная локальная переменная. Синтаксис оператора таков:

::переменная

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

Рекурсия

Рекурсия возникает в том случае, когда функция вызывает саму себя. Рекурсивные алгоритмы можно использовать для элегантного решения некоторых задач, из которых одна из наиболее распространенных — нахождение факториала числа. Факториалом называется произведение всех целых чисел от единицы до заданного. Например:

8!=8*7*6*5*4*3*2*1=40320

Обратите внимание на используемый тип данных — double, который позволяет работать с числами порядка 1Е+308. Особенностью факториала является то, что даже для сравнительно небольшого числа результат может быть огромным. Например, факториал числа 15 равен 1307674368000.

/*
*       factor.с
*  Эта программа на языке С демонстрирует применение
*   рекурсии при вычислении факториала.
*/


#include <stdio.h> 
double dfactorial(int inumber);

int main ()

{

int Inumber =15;
double dresult; 

dresult = dfactorial(inumber);

printf ("Факториал%d равен%15.0f\n"; inumber, dresult) ;

return(0);

}

double dfactorial(int inumber)  

{
if (inumber <= 1)  

return(1.0);  
else

return(inumber * dfactorial(inumber-1));

}

Аргументы функций

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

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

Формальные и фактические аргументы

Любая функция содержит список формальных аргументов, который может быть и пустым, если функция не принимает аргументов. Когда в программе осуществляется вызов функции, ей передается список значений аргументов, который называется списком фактических аргументов. В соответствии со стандартом ANSI С типы идентификаторов в обоих списках должны совпадать. Но в действительности совпадение может быть нестрогим, и над фактическими аргументами могут выполняться неявные операции преобразования типов данных.

Рассмотрим следующий фрагмент программы на языке С:

printf("Пример шестнадцатеричного %х и восьмеричного %о значения", ians);

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

int имя_функции(int it,float fu = 4.2,int iv = 10)

Если в вызове функции не будут указаны аргументы fu и/или iv, то им по умолчанию будут присвоены значения 4,2 и 10 соответственно. Стандарт языка C++ требует, чтобы аргументы, для которых заданы значения по умолчанию, находились в конце списка формальных аргументов. В нашем примере допустимы следующие варианты вызова функции:

имя_ функции(10)

имя_ функции(10,15.2)

имя_функции(10,15.2,8)

Если аргумент fu не будет задан, установка аргумента iv также станет невозможной.

Аргументы типа void

В соответствии со стандартом ANSI С ключевое слово void применяется для явного указания на отсутствие аргументов функции. В C++ указывать слово void не обязательно, хотя данное соглашение довольно широко используется. В следующем примере создается функция voutput( ) , которая не получает аргументов и не возвращает никаких значений. Это, пожалуй, один из простейших видов функций.

/*
*   fvoid.с
*   Эта программа на языке С содержит пример функции, не принимающей
*  никаких аргументов .
*/

#include <stdio.h>

#include <math.h>

void voutput (void) ;

int raain() {

printf("Этaпрограмма вычисляет квадратный корень числа. \n\n");

voutput () ;

return (0);

}

void voutput(void) {

int it = 12345;

double du;

du = sqrt(it);

printf("Квадратный корень числа %dравен %f \n", it, du); } 

В функции voutput() вызывается стандартная библиотечная функция sqrt(), объявленная в файле МАТН.Н. Данная функция возвращает квадратный корень своего аргумента в виде значения типа double.

Аргументы типа char

В следующем примере в функции main() считывается символ, введенный с клавиатуры, и передается в функцию voutput(), которая выводит его на экран. Считывание символа осуществляется функцией _getch(). В языке С есть ряд похожих на нее библиотечных функций: getc(), getchar () и _getche (). Данные функции можно использовать и в языке C++, хотя всех их инкапсулирует в себе объект cin, управляющий вводом данных. Функция _getch() запрашивает символ, поступающий со стандартного устройства ввода (как правило, это клавиатура), и записывает его в переменную типа char без эха на экране.

/*
*       fchar.с
*  Эта программа на языке С считывает символ, введенный с клавиатуры,
*  и передает его в функцию voutput(),осуществляющую вывод нескольких
*  копий символа на экран.
*/

#include <stdio.h>
#include <conio.h>

void voutput(char c) ;

int main() {

char cyourchar;

printf("Введите символ: ");

cyourchar = _getch();

voutput(cyourchar);   

return (0); }

void voutput(char c) <

int   j ;

for (j   =  0;   j   <   16;   j++)

printf("\nБыл введен символ %с ", с); )

Спецификатор %с в функции printf() указывает, что выводится единственный символ.

Аргументы типа int

В следующем примере введенная пользователем длина ребра куба передается в функцию vside(), которая на основании полученного значения вычисляет площадь грани куба, его объем и площадь поверхности.

/*
*       fint.c
*  Эта программа на языке С предназначена для вычисления площади грани
* и поверхности куба, а также его объема. Функция vside() принимает
*  целочисленное значение, содержащее длину ребра куба.
*/

#include <stdio.h>

void, vsidefint is);

int main()

int iyourlength;

printf ("Введите длину ребра куба: ") ;
scanf("%d",&iyourlength) ;
vside (iyourlength) ;

return (0);

}

void vside(int is) {

int iarea, ivolume, isarea;

iarea = is * is; ivolume = is * is * is; isarea = 6 * iarea;

printf("\nДлина ребра куба: %d\n", is);
printf("Площадь грани куба: %d\n", iarea);
printf("Объем куба: %d\n", ivolume);
printf("Площадь поверхности куба: %d\n", isarea); }

Аргументы типа float

В следующем примере в функцию vhypotenuse() передаются два аргумента типа float, определяющие длину катетов прямоугольного треугольника. В функции вычисляется длина гипотенузы vhypotenuse().

/*
*       ffloat.с
*  Эта программа на языке С вычисляет длину гипотенузы
*  прямоугольного треугольника.
*/

#include <stdio.h>

#include <math.h>

void vhypotenuse(float ft,float fu);

int main()

{
float fxlen, fylen;

printf("Введите длину первого катета: ");

scanf("%f",&fxlen);

printf("Введите длину второго катета: ");

scanf("%f",&fylen);

vhypotenuse(fxlen,fylen);

return (0);

}

void vhypotenuse(float ft,float fu)
double dresult;

dresult = hypot((double) ft,(double) fu) ;
printf("\nДлина гипотенузы равна%g \n", dresult); }

Функция hypot (), возвращающая длину гипотенузы, объявлена в файле МАТН.Н и принимает аргументы типа double, поэтому параметры fx и fу функции vhypotenuse() должны быть приведены к этому типу.

Аргументы типа double

Следующая программа считывает два числа типа double и возводит первое в степень второго.

/*
*       fdouble.с
*   Эта программа на языке С возводит число в указанную степень.
*/

#include <stdio.h>

#include <math.h> 

void vpower(double dt, double du);

int main () {

double dtnum, dunum;

printf("Введите основание степени: ");

scanf("%lf",&dtnum);

printf("\nВведите показатель степени: ");

scanf("%lf",&dunum);

vpower(dtnum, dunum);

return(0) ;

}

void vpower(double dt, double du)

double danswer;

danswer = pow(dt, du) ;

printf("\n%fв степени%f равно%f\n",dt, du, danswer);}

Массивы в качестве аргументов

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

/*
*       fpointer.с
*   Эта программа на языке С демонстрирует, как передать в функцию массив
*  данных. Функция получает указатель на первый элемент массива.
*/

#include <stdio.h>

void voutput(int *pinums);

int main ()

{
int iyourarray[7] = {2,7, 15,32,45,3, 1} ;

printf("Передаем массив данных ,в функцию.\n");

voutput(iyourarray);

return(0); }

void voutput (int*pinurns) , {

int t ;

for(t= 0; t < 7; t++)

printf("Массив данных: %d \n",pinums[t]); }

printf("Введите основание степени: ");

scanf("%lf",&dtnum);

printf("\nВведите показатель степени: ");

scanf("%lf",&dunum);

vpower(dtnum, dunum);

return(0);

}

void vpower(double dt, double du) {

double danswer;

danswer = pow(dt,du) ;

printf("\n%fв степени%f равно%f\n",dt,du, danswer), }

По сути, функция voutput () получаетимямассива iyourarray [ ]. Но данное имя одновременно является указателем на первый элемент массива. Это справедливо для любых массивов.

В объявлении функции можно также указать, что ее аргументом является массив неопределенного размера. В приводимом ниже примере показано, как реализовать это в языке C++. (Аналогичный подход допустим и в языке С.)

//
//   farray.cpp
//  Эта программа на языке C++ вычисляет среднее арифметическое ряда чисел.
//

#include <iostream.h>
void avg (float fnums [ ]) ;

int main () {

float iyourarray[8] = (12.3,25.7,82.1,6.0,7.01,0.25,4.2,6.28);

cout<< "Передаем массив в функцию для вычисления среднего значения. \n"; avg (iyourarray) ;

return (0); }

void avg (float f nums [ ] ) {

int iv;

float fsum = 0.0;

float faverage;

for(iv= 0; iv < 8; iv++) {

fsum +=  fnums[iv];

cout<< lv+1 << "-и элемент равен " << fnums[iv] << "\n"; }

faverage  =  fsum/iv; cout  <<   "\nСреднее  равно   "   <<   faverage  <<   "\n";

}

Типы значений, возвращаемых функциями

В данном параграфе вы найдете примеры функций, возвращающих значения всех стандартных типов. В предыдущих параграфах мы рассматривали функции, которые не возвращали никаких данных. В таких случаях говорят, что функция возвращает значение типа void. По сути, функция voutput() получает имя массива iyourarray[ ]. Но данное имя одновременно является указателем на первый элемент массива. Это справедливо для любых массивов.

В объявлении функции можно также указать, что ее аргументом является массив неопределенного размера. В приводимом ниже примере показано, как реализовать это в языке C++. (Аналогичный подход допустим и в языке С.)

//
//       farray.cpp
//   Эта программа на языке C++ вычисляет среднее арифметическое ряда чисел.
//

#include <iostream.h>
void avg (float f nums [] ) ;

int main() {

float iyourarray[8] = (12.3,25.7,82.1,6.0,7.01,0.25,4.2,6.28);

cout<< "Передаем массив в функцию для вычисления среднего значения. \n"; avg (iyourarray) ;

return (0); }

void avg (float fnums[]) {

int iv;

float fsum = 0.0;

float f average;

for(iv= 0; iv < 8; iv++) {

fsum  +=  fnums[iv];

cout<< iv+1 << "-и элемент равен " << fnumsfiv]  << "\n"; }

faverage  =  fsum/iv; cout  <<   "\nСреднее равно  "  <<  faverage  <<  "\n";

}

Тип результата: void

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

/*
*       voidf.c
*   Эта программа на языке С находит двоичный эквивалент заданного
*  десятичного числа.
*/

#include <stdio.h>

void vbinary(int idata) ;

int main ()

int ivalue;

printf ("Введите десятичное число: "); scanf("%d",Sivalue) ; vbinary (ivalue);

return (0); }

void vbinary (int idata)

int t = 0;

int iyourarray [50];

while (idata != 0) {
iyourarray [t]= (idata % 2);
idata /= 2;

t++; )

printf("\n");

for( ; t >= 0; t--)

printf("%d",iyourarray[t]); }

Преобразование числа в систему счисления более низкого порядка реализуется довольно простым математическим алгоритмом. Например, чтобы перевести десятичное число в двоичную систему, нужно разделить его на основание новой системы — 2 — максимально возможное количество раз. На каждом шаге результатом деления будет дробное число, состоящее из целой части и остатка. Целая часть используется для следующего деления на 2, а остаток определяет цифру двоичного числа в позиции, соответствующей номеру шага. В двоичной системе используются цифры 0 и 1.

В функции vbinary() этот алгоритм реализуется в цикле while. В результате операции деления по модулю (%) остаток от деления (0 или 1) заносится в массив iyourarray[]. Целая часть, полученная при делении, присваивается переменной idata. Этот процесс продолжается до тех пор, пока целая часть результата деления (переменная idata) не станет равной нулю.

Тип результата: char

В следующей программе на языке С функция clowercase() принимает аргумент типа charи возвращает значение такого же типа. Предполагается, что вводится буква в верхнем регистре. В функции clowercase() вызывается библиотечная функция tolower(), прототип которой находится в файле CTYPE.H, она переводит символ в нижний регистр.

/*
*       charf.c
*   Эта программа на языке С преобразует символ из верхнего регистра' в нижний.
*/

#include <stdio.h>
#include <ctype.h>

char clowercase(char c) ;

int main () {

char clowchar, chichar;

printf("Введите букву в верхнем регистре. \n");

chichar = getchar();

clowchar = clowercase(chichar);

printf("%c\n",clowchar);

return(0);

char clowercase(char c) {

return(tolower(c));

}

Тип результата: bool

Ниже дан пример использования двух функций, is_upper() и is_lower(),которые возвращают значения типа bool, проверяя, представлен ли переданный им символ в верхнем или нижнем регистре соответственно. Этот тип данных специфичен для языка C++.

//
//   boolf.cpp
//   Эта программа на языке C++ демонстрирует возможность
//   возвращения функциями значений типа bool.
//

#include <iostream.h>

bool is_upper(void) ; bool is_lower(void) ;

int main () {

char cTestChar = 'T';

bool bIsUppercase, bIsLowercase;

bIsUppercase = is__upper (cTestChar) ; bIsLowercase = is_lower(cTestChar);

cout <<       "Буква " << (blsUppercase ? "является " : "не является ")

<<   "прописной.\n";

cout<<       "Буква " << {bIsLowercase? "является " : "не является ")

<<   "строчной.\n";

return(0); }

bool is_upper(int ch) {

return(ch>= 'A'&& ch <= 'Z'); }

bool is_lower(int ch) {

return(ch >= 'a'&& ch <= 'z'); }

В этой программе для выбора выводимого сообщения применяется оператор ?:, что позволяет каждую операцию вывода записать в одну строку и не использовать многострочную инструкцию if /else.

Тип результата: int

В следующем примере функция icube() принимает целое число, возводит его в куб и возвращает результат тоже в виде целого числа.

/*
*        intf.c
*   Эта программа на языке С возводит целое число в куб.
*/

#include <stdio.h>

int icube(int ivalue); int n(ain()

int k, inumbercube;

for(k=0;k < 20;k += 2) {

inumbercube = icube(k);

printf("Ky6 числа%d равен%d\n",k, inumbercube) }

return(0);

int  icube(int  ivalue)

return(ivalue  *  ivalue  *   ivalue);

}

Тип результата: long

В следующей программе, написанной на языке C++, функция Ipower() получает целочисленный аргумент, возводит число 2 в степень, заданную аргументом, и возвращает значение типа long.

//
//   longf .cpp
//  Эта программа на языке C++ демонстрирует возможность возвращения
//  функциями значений типа long. Функция Ipower() получает целое число.и
//  возводит 2 в степень, определяемую этим числом.
//

#include <iostream.h> long Ipower(int ivalue); int main()

int k;

long lanswer;

for(k =0;k < 20; k++) {

lanswer = Ipower(k);

cout<< "2 в степени " << k<< " равно " << ianswer<< "\n"

return(0); }

long Ipower(int ivalue) .

int t;

long Iseed = 1;

for(t =0;t < ivalue; t++) Iseed *= 2;

return(Iseed);

} 

В функции Ipower() используется цикл, повторяющийся заданное число раз, в котором число 2 последовательно умножается само на себя. На практике для этого лучше применять стандартную функцию pow(), объявленную в файле МАТН.Н.

Тип результата: float

В следующем примере в функцию fproduct () передается массив чисел типа float и возвращается значение того же типа. Данная программа, написанная на языке C++, вычисляет произведение всех элементов массива.

//
//   floatf.cpp
//   Эта программа на языке C++ демонстрирует возможность возвращения
//   функциями значений типа float. Функция fproductО получает массив
//   данных типа floatи возвращает произведение элементов массива.
//

#include <iostream.h>

float fproduct(float farray[]);

int main()

float fmyarray[7] = (4.3,1.8,6.12,3.19,0.01234,0.1,9876.2}, float fmultiplied;

fmultiplied = fproduct(fmyarray) ;

cout<< "Произведение всех элементов массива равно: " << fmultiplied << "\n";

return(0); }

float fproduct(float farray[])

int i;

float fpartial;

fpartial = farray[0]; for (1 = 1; i < 7; i++)

fpartial *= farray[i];

return (fpartial); }

Значение первого элемента массива присваивается переменной fpartial еще до начала цикла for. Вот почему цикл начинается с индекса 1, а не 0.

Тип результата: double

В следующем примере функция dtrigcosine() находит значение косинуса угла, заданного в градусах.

/*
*       doublef.c
*  Эта программа на языке С последовательно находит косинусы углов
*  от 0 до 90 градусов с интервалом в пять градусов.
*/

#include <stdio.h>

#include <math.h>

const double dPi = 3.14159265359; double dtrigcosine(double dangle);

int main() {

int j;

double dcosine;

for(j= 0; j < 91;j+=5) {>
dcosine = dtrigcosine((double) j);
printf("Косинус угла%d градусов равен%.3f\n",j, dcosine); }

return(0);}

double dtrigcosine(double dangle) {

double dpartial;

dpartial = cos((dPi/180.0) * dangle);
return(dpartial);}

Для вычисления косинуса в функции dtrigcosine() вызывается стандартная функция cos(),объявленная в файле МАТН.Н. Но предварительно необходимо преобразовать величину угла из градусов в радианы. Для этого параметр dangleделится на 180 градусов и умножается на число пи, заданное в программе как константа dpi.

Аргументы командной строки

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

имя_программы аргумент1, аргумент2 ...   

Аргументы командной строки обрабатываются в функции main(),котoрая принимает два параметра: argc и argv[ ]. Целочисленный параметр а где содержит число аргументов командной строки плюс 1 — с учетом имени программы, которое, в принципе, тоже является аргументом. Вторым параметром является указатель на массив строковых указателей. Все аргументы представляют собой строки, поэтому массив argv[ ] имеет тип char*. Имена параметров — argc и argv— не являются стандартными элементами языка, но называть их именно так — общепринятое соглашение, широко применяемое большинством программистов на C/C++.

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

Текстовые аргументы

Аргументы, задаваемые в командной строке, всегда передаются в программу в виде наборов символов, что облегчает работу с ними. В следующей программе на языке С от пользователя ожидается ввод в командной строке ряда аргументов. При запуске программы проверяется значение параметра argc. Если оно меньше двух, то пользователю будет предложено перезапустить программу.

/*
*       sargv.c
*   Эта программа на языке С демонстрирует процесс
*   считывания строковых аргументов командной строки.
*/

#include <stdio.h>

#include <process.h>

int main(int argc, char *argv[]) {

int t;

if(argc < 2) {

printf("Необходимо ввести несколько аргументов командной строки.\n");

printf("Повторите запуск программы.\n");

exit (1);

forft =1;t < argc; t++)

.   printf("Аргумент№%d —  %s\n",   t,   argv[t] );

  exit(0);

}  

Все аргументы, введенные в командной строке, выводятся на экран в той же последовательности. Если были введены числовые значения, они все равно будут рассматриваться как ASCII-символы.

Целочисленные аргументы

Часто в командной строке нужно указывать числовые значения. В таком случае необходимо выполнить преобразование строк в числа. Следующая программа на языке C++ читает из командной строки символьный аргумент и преобразовывает его в целое число с помощью стандартной функции atoi(). Полученное число сохраняется в переменной ivalueи передается в функцию vbinary(), которая выводит его двоичный эквивалент. В функции main() отображаются также восьмеричный и шестнадцатеричный эквиваленты числа.

//
//        iargv.cpp
//       Эта программа на языке C++ преобразует аргумент
//       командной строки в целое число.
//

#include <iostream.h>
#include <stdlib.h>

void vbinary (int idigits);

int main (int argc, char *argv[]) {

int ivalue;

if (argc != 2) {

cout<< "Введите в командной строке целое число. \n";
cout<< "Это число будет преобразовано в двоичный, \n"
cout<< "восьмеричный и шестнадцатеричный форматы . \n";
return (0);

}

ivalue = atoi(argv[1]);

vbinary(ivalue) ;

cout <<  "В восьмеричном формате:   " << oct << ivalue << "\n";

cout <<  "В шестнадцатеричном формате:   "  << hex<<  ivalue <<   "\n"

return(0) ; }

void vbinary (int  idigits)

int t = 0;

int iyourarray[50];

while(idigits != 0) {

iyourarray[t]= (idigits % 2);

idigits /= 2;

t++; }

t-- ;

cout<< "В двоичном формате: ";

for(; t >= 0; t--)

cout << dec << iyourarray[t] ; cout << "\n"; }

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

Для вывода числа в восьмеричном формате используется следующая запись:

cout<< "В восьмеричном формате: " << oct << ivalue << "\n";

Чтобы вывести то же число в шестнадцатеричном формате, достаточно просто поменять флаг oкна флаг hex:

cout<< "В шестнадцатеричном формате: " << hex << ivalue << "\n";

При отсутствии дополнительного форматирования шестнадцатеричные цифры а, b, с, d, e и f будут отображаться в нижнем регистре. Более подробно о средствах форматирования, используемых в языке C++, рассказано в главе "Основы ввода-вывода в языке C++". Среди прочего в ней объясняется, как управлять выводом шестнадцатиричных цифр в верхнем и нижнем регистре.

Аргументы с плавающей запятой

Следующая программа на языке С принимает в командной строке несколько
значений углов в градусах. Полученные данные используются для вычисления
косинусов.    

/*
*       fargv.c
*       Эта программа на языке С демонстрирует процесс считывания аргументов
*       командной строки с плавающей запятой.
*/

#include <stdio.h>
#include <math.h>
#include <process.h>

const double dPi = 3.14159265359;

int main(int argc, char *argv[]) {

int t;

double ddegree;

if(argc< 2) {

printf("Введите в командной строке значения углов в градусах.\n") ,

printf("Программа вычислит косинусы углов.\n"),•

exit(l);

}

for(t=  1;   t  <  argc;   t++)    {

ddegree = atof<argv[t]);

printf("Косинус угла  %f равен  %.3f\n",   ddegree,   cos((dPi/180.0)   *  ddegree)); )

exit(0);

}

Функция atof() преобразовывает строковые значения в значения типа double. Для вычисления косинуса используется библиотечная функция cos().

Дополнительные особенности функций

Макроподстановка функций

В C++ при использовании функций доступны некоторые дополнительные воз­можности. Например, можно встраивать весь код функции непосредственно по месту ее вызова. Такие функции, называемые inline-функциями, встраиваются в програм­му автоматически в процессе компиляции, а в результате может значительно сокра­щаться время выполнения программы, особенно если в ней часто вызываются короткие функции.

Ключевое слово inlineявляется особым видом спецификатора. Его можно пред­ставить как рекомендацию компилятору C++ подставить в программный код тело функции по месту ее вызова. Компилятор может проигнорировать эту рекомендацию, если, например, функция слишком велика. Макроподстановка, как правило, применя­ется, когда какая-нибудь небольшая функция вызывается в программе много раз.

//
//   inline. срр
//  Эта программа на языке C++ содержит пример макроподстановки функции.
//

#include <iostream.h>

inline long squareit(int iValue) (return iValue * iValue;}

int main ()

{
int iValue;

for(iValue          = 1; ivalue <= 10; iValue++)

cout<<       "Квадрат числа " << iValue<< " равен "

<<  squareit(iValue) << "\n";.

return(0); }

Функция squareit(),возвращающая квадрат целочисленного аргумента ivalue, описана со спецификатором inline. Когда в программе будет обнаружен вызов данной функции, компилятор подставит в этом месте строку ivalue*ivalue. Другими словами, компилятор заменяет вызов функции ее телом, присваивая при этом аргументам функции их фактические значения.

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

Перегрузка функций

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

В следующей программе создаются два прототипа функции с одним именем и общей областью видимости, но аргументами разных типов. При вызове функции adder() будут обрабатываться данные либо типа int, либо типа float.

//
//  overload.cpp
//  Эта программа на языке C++ содержит пример перегрузки функции.
//

#include <iostream.h>

int adder (int iarray[]);

float adder (float f array []); 

int main() {

int iarray[7]  = {5,1, 6, 20,15,0, 12);
float farray[7] = {3.3,5.2,0.05,1.49,3.12345,31.0,2.007};

int isum;

float fsum;

isura     = adder (iarray) ;

fsum= adder (f array) ;

cout<< "Сумма массива целых чисел равна " << isura << "\n";

cout<< "Сумма массива дробных чисел равна " << fsum<< "\n";

return ( 0 ).;

}

int adder(int iarrayt]) {

int i;

int ipartial;

ipartial = iarray[0]; for(i= 1; i < 7; i++) ipartial += iarray[i];

return(ipartial);

}

float adder (float farrayf]) {

int i;

float f partial,

fpartial = f array [0];

for(i= 1; i < 7; i++) 

fpartial += farray[i]; 

return (fpartial) ; }

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

int имя_функции(int имя_аргумента) ;

int имя_функции(int имя_аргумента) ; // недопустимая перегрузка имени

Это неправильно, поскольку аргументы имеют одинаковый тип.

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

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

void имя_функции(int  first,   float  second,    . . . ) ; 

Данный прототип говорит компилятору о том, что за аргументами firstи secondмогут следовать и другие аргументы, если возникнет такая необходимость. При этом тип последних не контролируется компилятором.

В следующей программе, написанной на языке С, создается функция vsmalltest( ) с переменным числом аргументов. Текст этой программы может вам показаться трудным для понимания, поскольку мы еще не рассматривали подробно работу с указателями. В таком случае рекомендуем вернуться к этой программе после прочтения главы "Указатели".

/*
*       ellipsis. с
*  Эта программа на языке С содержит пример функции с переменным числом
*   аргументов и демонстрирует использование макросов va_arg, va_startи va_end.
*/

#include <stdio.h>
#include <stdarg.h>
#include <string.h>

void vsmallest (char *szmessage, ...);

intmain() {

vsmallest("Выводим %dцелых чисел, %d %d %d",10,4, 1);

return (0); }

void vsmallest (char *szmessage, ...)

{

int inumber_of_percent_ds = 0;
va_listtype_for_ellipsis;

int ipercent_d_format = 'd';

char *pchar;

pchar = strchr (szmessage, ipercent_d_format) ;

while (*++pchar != '\0'){ pchar++;

pchar = strchr (pchar, ipercent_d_format) ; inumber_of_percent_ds++;

}

printf{"Выводим%d целых чисел,",inumber_pf percent_ds) ;

va_start(type_for_ellipsis, szmessage); 

while(inumber_of_percent_ds--)

printf(" %d", va_arg(type_for_ellipsis, int));

va_end(type_for_ellipsis); }

Функция vsmallest() ожидает двух формальных аргументов: указателя на строку и списка неопределенной длины. Естественно, функция должна иметь возможность каким-то образом определить, сколько же аргументов она получила на самом деле. В данной программе эта информация передается в строковом аргументе.

Созданная нами функция vsmallest() частично имитирует работу стандартной функции printf (). Аргумент szmessage рассматривается как строка форматирования, в которой подсчитывается число спецификаций %d. Полученная информация позволяет вычислить количество дополнительных аргументов.

Функция strchr() возвращает адрес позиции спецификатора d в строке форматирования. Первый элемент %d игнорируется, поскольку на его месте будет выведено общее число аргументов. В первом цикле while определяется количество спецификаторов d в строке szmessage, и полученное значение заносится в переменную inumber_of_percent_ds. По завершении цикла на экран выводится первая часть сообщения.

Макрос va_start() устанавливает указатель type_for_ellipsis(обратите внимание на его тип — va_list) в начало списка аргументов функции. Второй параметр макроса является именем обязательного аргумента анализируемой функции, который стоит непосредственно перед многоточием. Макрос va_arg() возвращает очередной аргумент из списка. Второй параметр макроса указывает на тип возвращаемого аргумента (в нашей программе это тип int). Макрос va_end() очищает указатель type_for_ellipsis, делая его равным нулю.

Область видимости переменных

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

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

В следующем примере в функции main() объявляются четыре локальные пере­менные. В программе как будто бы нет никаких ошибок. Тем не менее, когда функция iproduct() попытается обратиться к переменной in, она не сможет ее обнаружить. Почему? Потому что область видимости этой переменной ограничена только функцией main().

/*
*       scope.с
*  Эта программа на языке С иллюстрирует проблему неправильного
*  определения области видимости переменной. Во время компиляции
*  программы появится сообщение об ошибке.
*/

#include <stdio.h>    
int iproduct(int iw, int ix);

int main ()  {

int il = 3;

int im = 7;

int in = 10;

int io;

io = iproductfil, im) ;

printf("Произведение чисел равно %d\n",io) ;

return(0); }

int iproduct (int iw, int ix)
{

int iy;

iy = iw * ix * in;    

return (iy); }

Компилятор выдаст сообщение об ошибке, в котором говорится о том, что в функции iproduct( ) обнаружен нераспознанный идентификатор in. Чтобы решить эту проблему, нужно сделать переменную in глобальной.

В следующем примере переменная inописана как глобальная. В результате обе функции, как main(),так и iproduct(),могут использовать ее. Обратите также внимание на то, что и та, и другая функция может изменить значение переменной in.

/*
*   fscope.с
*   Это исправленная версия предыдущей программы. Проблема решена путем
*   объявления переменной in как глобальной.
*/

#include <stdio.h>

int iproduct (int iw, int ix) ; int in = 10;

int main() (

int il = 3; 

int im = 7;

int io;

io = iproduct (il,im) ;

printf("Произведение чисел равно %d\n",io) ;

return (0);

}

int iproduct(int iw, int ix)

int iy;

iy = iw * ix * in;

return(iy);

Эта программа будет корректно скомпилирована, и на экране появится результат - 210.

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

/*
*       Isсоре.с
*  Эта программа на языке С иллюстрирует взаимоотношение
*  между одноименными локальной и глобальной переменными.
*   Функция iproduct() находит произведение трех переменных,
*  из которых две передаются как аргументы функции, а еще
*   одна, in,объявлена и как глобальная, и как1 локальная.
*/

#include <stdio.h>

int iproduct(int iw, int ix);

int in = 10;

int main()

int il = 3; int im = 7;

int io;

io = iproduct(il,im) ;

printf("Произведение чисел равно %d\n",io) ;

return (0);

int iproduct(int iw, int ix) {

int iy;

int in = 2;

iy = iw * ix * in;

return(iy); }

В этом примере переменная inописывается дважды: на уровне файла и на уровне функции. В функции iproduct(), где эта переменная объявлена как локальная, будет использовано "локальное" значение. Поэтому результатом умножения будет 3*7*2 = 42.

Оператор расширения области видимости

В следующей программе, написанной на языке C++, все работает нормально до момента вывода информации на экран. Объект cout правильно отобразит значения переменных 11 и im, но для переменной inбудет выбрано "глобальное" значение. Вследствие этого пользователю будет представлен неправильный результат: "3*7*10= 42". Как вы уже поняли, ошибка возникает из-за того, что в функции iproduct() используется локальная переменная in.

//
//   scopel.срр
//   Эта программа на языке C++ содержит логическую ошибку.
//  Функция iproduct() находит произведение трех переменных,
//  используя при этом значение локальной переменной in.
//  В то же время в выводимых данных программа сообщает
//  о том, что значение переменной inравно 10.
//

#include <iostream.h>

int iproduct (int iw, int ix); 
int in = 10;

int main()

{
int 11 = 3;
int im = 7 ;
int io;

io = iproduct (il,im) ;

cout << il << " * " << im << " * " << in << " = " << io << "\n";

return (0);

}

int iproduct(int iw, int ix) {

int iy; int in = 2;

iy = iw * ix * in;

return (iy);}

Что же нужно сделать, чтобы получить правильный результат? Для этого достаточно воспользоваться оператором расширения области видимости (::), о котором мы уже упоминали:

iy = iw * ix * ::in;

Следующая программа иллюстрирует сказанное.

//
//   scope2.cpp
//  Это исправленная версия предыдущего примера. Проблема решена путем
//  использования оператора расширения области видимости (::).
//

#include <iostream.h>

int iproduct(intiw, int ix) ; int in = 10;

int main ()

int il = 3; int im = 7; int io;

io = iproduct(il,im) ;

cout << il << " * " << im << " * " << in << " = " << io << "\n";

return (0);

}

int iproduct(intiw, int ix)

int iy;
int in = 2;

iy = iw * ix * (::in); return(iy);}

Переменная in вместе с оператором :: для наглядности взяты в скобки, хотя в этом нет необходимости, так как данный оператор имеет самый высокий приоритет среди остальных. Таким образом, на экран будет выведен правильный результат: "3*7*10 = 210".