Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

учебное пособие. Часть1. Информатика

.pdf
Скачиваний:
54
Добавлен:
04.06.2015
Размер:
3 Мб
Скачать
☆

using namespace std; int main ()

{

int i = 1; do

{

printf ( "\n %d", i ); i = i+2;

}

while ( i <= 10 ); return 0;

}

Для переменных, описанных вне функций, т. е. глобальных перемен- ных, по умолчанию назначается внешний класс памяти (extern). Область действия внешней переменной от точки во входном файле, где она была опи- сана, до конца этого файла. Если внутри блока или функции описана автома- тическая переменная, имя которой совпадает с именем глобальной перемен- ной, то внутри блока или функции глобальная переменная маскируется ло- кальной. Это означает, что внутри данного локального блока будет видна именно автоматическая локальная переменная. По умолчанию считается, что все функции внешние.

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

// Пример pr44 #include <iostream> using namespace std; #include "pr59.cpp" int a = 1;

void f ()

{

cout << "В функции f a=" << a<< "\n"; a++;

}

int main ()

{

clrscr ();

cout << "В главной функции до вызова функции f a=" <<a << "\n";

261

f ();

cout << "В главной функции после вызова функции f a=" <<a << "\n";

f1 ();

cout << "В главной функции после вызова функции f1 a=" << a << "\n";

return 0;

}

// Пример pr45 extern int a; void f1()

{

a++;

cout << "В функции f1 a=" << a << "\n";

}

Вприведенном примере память выделяется один раз при описании внешней переменной a в файле pr44.cpp. Результаты работы этой про- граммы следующие:

Вглавной функции до вызова функции f a = 1

Вфункции f a = 1

Вглавной функции после вызова функции f a = 2

Вфункции f1 a = 3

Вглавной функции после вызова функции f1 a = 3

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

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

//Пример pr46 #include <iostream> using namespace std; void f ()

262

{

int a = 1; static int s = 1;

cout << "a=" << a << "\ts=" << s << "\n"; a++;

s++;

}

int main ()

{

int i;

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

{

cout << "Итерация " << i << ":\n"; f();

}

return 0;

}

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

Итерация 1:

a = 1

s = 1

Итерация 2:

s = 2

a = 1

Итерация 3:

s = 3

a = 1

Итерация 4:

s = 4

a = 1

Итерация 5:

s = 5

a = 1

Спецификатор register может использоваться только для автомати- ческих переменных [5]. Такой спецификатор говорит о том, что пользователь желает разместить переменные не в оперативной памяти, а в регистрах ком- пьютера. Так как количество регистров ограничено, то в случае избыточно-

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

Следует заметить, что при отсутствии явной инициализации перемен- ных внешние и статические переменные будут инициализироваться нулем, а

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

263

значения (мусор). Не рекомендуется инициализировать автоматические мас- сивы.

10.2.Расширение области видимости имен

Спомощью операции :: расширяется зона видимости имен. Появляется доступ к глобальным переменным, скрытым локальными переменными. Рас- смотрим фрагмент программы:

int i = 0; int f () { ...

int i = 0;

i++; // локальная ::i++; // глобальная

...

}

В следующем примере автоматическая переменная a в функциях f и main маскируют глобальную переменную a.

//Пример pr47 #include <stdio.h> float a = 1.3;

int f ( int c ) {int a = c+13;

printf ( "В функции f a локальная=%d\n", a ); printf ( "В функции f a внешняя=%f\n", ::a ); return a;

}

void main ()

{

int a = f (13);

printf ( "В функции main a локальная=%d\n", a ); printf ( "В функции main a внешняя=%f\n", ::a );

}

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

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

264

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

//Пример pr48 #include <iostream> using namespace std;

//Функция обмена для переменных типа float void sw ( float *x, float *y )

{

float z; z = *x; *x = *y; *y = z;

}

//Функция обмена для переменных типа int void sw ( int *x, int *y )

{

int z; z = *x;

*x = *y; *y = z;

}

//Функция, вычисляющая z по формуле

float sw ( int a)

{

float z;

z = a*3.14; return z;

}

int main ()

{

float x,y;

int a1 = 9, a2 = 8;

cout << "Введите x y"; cin >> x >> y;

float z;

z = sw (a1);

cout << "z=" << z; sw ( &a1 ,&a2 ); sw ( &x, &y );

265

cout << "\n" << x << "\t" << y << "\n" <<a1<<"\t"<<a2; return 0;

}

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

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

10.4.Шаблоны функций

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

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

Для параметризации используется список параметров шаблона.

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

формальный параметр шаблона обозначается ключевым словом class, за которым следует имя параметра (идентификатор), обозначающего тип [12]. Пример определения шаблона функций, вычисляющих абсолютные значения числовых величин различных типов:

template <class type>

type abs (type x) {return x>0 ? x: -x;}

Шаблон семейства функций состоит из двух частей – заголовка шаблона: template <class type>

и из обыкновенного определения функции, в котором тип возвращае-

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

266

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

template <class T> void swap(T *x, T *y)

{

T z; z=*x; *x=*y; *y=z;

}

Здесь T – параметр шаблона функций. Он используется не только в за- головке для спецификации формальных параметров, но и в теле функции для обозначения типа переменных.

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

long k = 4, d = 8; swap ( &k, &d ); int a = 1, b =2; swap ( &a, &b );

Компилятор в соответствии с вызовом сформирует определение функ- ции. Следующая программа демонстрирует использование шаблонов на при- мере сортировки элементов массива: сначала в одномерном массиве из целых чисел, а затем – из вещественных.

//Пример pr49 #include <iostream> using namespace std;

//Функция сортировки элементов массива методом выбора template <class type>

void sort ( int n, type d[] )

{

int im; type w;

for ( int i = 0; i<n; i++ )

{

im = i;

for ( int j = i+1; j<n; j++ ) im = d[im] < d[j] ? im:j; w = d[im];

d[im] = d[i];

267

d[i] = w;

}

}

int main ()

{

int n = 4;

static int x[] = {10, 20, 30, 14}; sort ( n, x );

cout << "\nМассив Х:"; for ( int I = 0; i<n; i++ ) cout << "\t" << x[i];

static float y[] = {10.3, 20.5, 10.5, 15.2, 1.7, 8.3}; sort ( 6, y );

cout << "\nМассив Y:"; for ( i = 0; i<6; i++ ) cout << "\t" << y[i]; return 0;

}

10.5.Значения фоpмальных паpаметpов по умолчанию

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

[7].Эти значения передаются функции, когда данный параметр не указан при ее вызове.

Предположим, что вы написали функцию drawcircle, которая рисует на экране окружность, и задали ее прототип:

void drawсircle (int x = 100, int y =100, int r=100)

или

void drawсircle ( int = 100, int = 100, int = 100 ).

Тогда вызовы этой функции будут проинтерпретированы в зависимо- сти от передаваемых параметров:

drawcircle(); //Окружностьсцентром(100,100),радиусом100 drawcircle (200);//Окружностьсцентром(200,100),радиусом100 drawcircle(200,300);//Окружностьсцентром(200,300),радиусом100 drawcircle(200,300,200);//Окружностьсцентром(200,300),радиусом200

drawcircle ( , , 50); //Ошибка

268

10.6. Подставляемые функции

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

inline int FCUBE (int x)

{

return x*x*x;

}

……

b = FCUBE (a);

c = FCUBE (a++);

Функции со спецификатором inline не может содержать операторов управления if, for, case, switch, while. Подставляемая функция, функция, вы- зов которой заменяется (необязательно) при компиляции ее телом, то есть последовательностью реализующих ее команд. Тем самым при многократных вызовах подставляемой функции размеры программы увеличиваются, однако исключаются затраты на передачи управления к вызываемой функции и воз- враты из нее. Наиболее эффективно использовать подставляемые функции в тех случаях, когда к функции приходится часто обращаться.

10.8. Рекурсивные функции

Рекурсивным называется объект, частично состоящий или определяе- мый с помощью самого себя [5]. Рекурсия встречается не только в математи- ке, но и в повседневной жизни. Например, рекламная картинка, содержащая свое собственное изображение, или проблема курицы и яйца: курица вылу- пилась из яйца, но яйцо снесла курица, которая, в свою очередь, вылупилась из яйца, и т. д.

Если функция вызывает саму себя, то говорят, что возникает рекурсия. Рекурсию можно рассматривать просто, как еще одну управляющую струк- туру − управление из точки рекурсивного вызова передается на начало функ- ции. В действительности же рекурсия − это мощный инструмент для разра- ботки программ, с помощью которого даже большие объёмы действий могут быть записаны всего несколькими строками программного кода.

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

269

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

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

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

Следующая программа является классическим примером для объясне- ния рекурсии, но при этом не является примером для подражания.

//Пpогpамма, вычисляющая n!, используя рекурсивную функцию

//Пример pr50 #include <cstdio>

using namespace std;

long fac ( int n ) // Описание рекурсивной функции

{

if ( n == 0 )

{ // Тривиальное решение return 1;

}

return n*fac (n-1); //Рекурсивный вызов

}

int main ()

{

int n; long fact;

puts ( "Введите n : " ); scanf ( "%d", &n );

270