Функция - это поименованная часть программы, которая может вызываться
из других частей программы столько раз, сколько необходимо. Приведем
программу, выдающую степени числа два:
extern float pow ( float, int );
// pow () определена в другом месте
int main ()
{
for ( int i=0; i<10; i++ ) cout << pow ( 2, i ) << '\n';
}
Первая строка является описанием функции. Она задает pow как функцию с
параметрами типа float и int, возвращающую значение типа float. Описание
функции необходимо для ее вызова, ее определение находится в другом месте.
При вызове функции тип каждого фактического параметра сверяется с
типом, указанным в описании функции, точно так же, как если бы
инициализировалась переменная описанного типа. Это гарантирует надлежащую
проверку и преобразования типов. Например, вызов функции pow(12.3,"abcd")
транслятор сочтет ошибочным, поскольку "abcd" является строкой, а не
параметром типа int. В вызове pow(2,i) транслятор преобразует целую
константу (целое 2) в число с плавающей точкой (float), как того требует
функция. Функция pow может быть определена следующим образом:
float pow ( float x, int n )
{
if ( n < 0 )
error ( "ошибка: для pow () задан отрицательный показатель");
switch ( n )
{
case 0: return 1;
case 1: return x;
default: return x * pow ( x, n-1 );
}
}
Первая часть определения функции задает ее имя, тип возвращаемого
значения (если оно есть), а также типы и имена формальных параметров (если
они существуют). Значение возвращается из функции с помощью оператора
return.
Разные функции обычно имеют разные имена, но функциям, выполняющим
сходные операции над объектами разных типов, лучше дать одно имя. Если
типы параметров таких функций различны, то транслятор всегда может
разобраться, какую функцию нужно вызывать. Например, можно иметь две
функции возведения в степень: одну - для целых чисел, а другую - для чисел
с плавающей точкой:
int pow ( int, int );
double pow ( double, double );
//...
x = pow ( 2,10 ); // вызов pow ( int, int )
y = pow ( 2.0, 10.0 );// вызов pow ( double, double )
Такое многократное использование имени называется перегрузкой имени
функции или просто перегрузкой; перегрузка рассматривается особо в главе
7.
Параметры функции могут передаваться либо "по значению", либо "по
ссылке". Рассмотрим определение функции, которая осуществляет взаимообмен
значений двух целых переменных. Если используется стандартный способ
передачи параметров по значению, то придется передавать указатели:
void swap ( int * p, int * q )
{
int t = * p;
* p = * q;
* q = t;
}
Унарная операция * называется косвенностью (или операцией
разыменования), она выбирает значение объекта, на который настроен
указатель. Функцию можно вызывать следующим образом:
void f ( int i, int j )
{
swap ( & i, & j );
}
Если использовать передачу параметра по ссылке, можно обойтись без
явных операций с указателем:
void swap (int & r1, int & r2 )
{
int t = r1;
r1 = r2;
r2 = t;
}
void g ( int i, int j )
{
swap ( i, j );
}
Для любого типа T запись T& означает "ссылка на T". Ссылка служит
синонимом той переменной, которой она инициализировалась. Отметим, что
перегрузка допускает сосуществование двух функций swap в одной программе.
1.3.6 Модули
Программа С++ почти всегда состоит из нескольких раздельно
транслируемых "модулей". Каждый "модуль" обычно называется исходным
файлом, но иногда - единицей трансляции. Он состоит из последовательности
описаний типов, функций, переменных и констант. Описание extern позволяет
из одного исходного файла ссылаться на функцию или объект, определенные в
другом исходном файле. Например:
extern "C" double sqrt ( double );
extern ostream cout;
Самый распространенный способ обеспечить согласованность описаний
внешних во всех исходных файлах - поместить такие описания в специальные
файлы, называемые заголовочными. Заголовочные файлы можно включать во все
исходные файлы, в которых требуются описания внешних. Например, описание
функции sqrt хранится в заголовочном файле стандартных математических
функций с именем math.h, поэтому, если нужно извлечь квадратный корень из
4, можно написать:
#include
//...
x = sqrt ( 4 );
Поскольку стандартные заголовочные файлы могут включаться во многие
исходные файлы, в них нет описаний, дублирование которых могло бы вызвать
ошибки. Так, тело функции присутствует в таких файлах, если только это
функция-подстановка, а инициализаторы указаны только для констант ($$4.3).
Не считая таких случаев, заголовочный файл обычно служит хранилищем для
типов, он предоставляет интерфейс между раздельно транслируемыми частями
программы.
В команде включения заключенное в угловые скобки имя файла (в нашем
примере - ) ссылается на файл, находящийся в стандартном каталоге
включаемых файлов. Часто это - каталог /usr/include/CC. Файлы, находящиеся
в других каталогах, обозначаются своими путевыми именами, взятыми в
кавычки. Поэтому в следующих командах:
#include "math1.h"
#include "/usr/bs/math2.h"
включаются файл math1.h из текущего каталога пользователя и файл
math2.h из каталога /usr/bs.
Приведем небольшой законченный пример, в котором строка определяется в
одном файле, а печатается в другом. В файле header.h определяются нужные
типы:
// header.h
extern char * prog_name;
extern void f ();
Файл main.c является основной программой:
// main.c
#include "header.h"
char * prog_name = "примитивный, но законченный пример";
int main ()
{
f ();
}
а строка печатается функцией из файла f.c:
// f.c
#include
#include "header.h"
void f ()
{
cout << prog_name << '\n';
}
При запуске транслятора С++ и передаче ему необходимых
файлов-параметров в различных реализациях могут использоваться разные
расширения имен для программ на С++. На машине автора трансляция и запуск
программы выглядит так:
$ CC main.c f.c -o silly
$ silly
примитивный, но законченный пример
$
Кроме раздельной трансляции концепцию модульности в С++ поддерживают
классы ($$5.4).
Достарыңызбен бөлісу: |