ВІКІСТОРІНКА
Навигация:
Інформатика
Історія
Автоматизація
Адміністрування
Антропологія
Архітектура
Біологія
Будівництво
Бухгалтерія
Військова наука
Виробництво
Географія
Геологія
Господарство
Демографія
Екологія
Економіка
Електроніка
Енергетика
Журналістика
Кінематографія
Комп'ютеризація
Креслення
Кулінарія
Культура
Культура
Лінгвістика
Література
Лексикологія
Логіка
Маркетинг
Математика
Медицина
Менеджмент
Металургія
Метрологія
Мистецтво
Музика
Наукознавство
Освіта
Охорона Праці
Підприємництво
Педагогіка
Поліграфія
Право
Приладобудування
Програмування
Психологія
Радіозв'язок
Релігія
Риторика
Соціологія
Спорт
Стандартизація
Статистика
Технології
Торгівля
Транспорт
Фізіологія
Фізика
Філософія
Фінанси
Фармакологія


Область видимости и время жизни переменной.

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

 

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

 

Локальная область видимости определяется границами блока, т.е. начинается с открывающей фигурной скобки ({) и оканчивается закрывающей фигурной скобкой (}). Имя, объявленное в локальной области, известно только внутри этой области. Поскольку блоки могут быть вложенными, локальные области также могут быть вложенными. Самой распространенной локальной областью является область, определенная функцией. Локальные переменные создаются при входе в их блок, а разрушаются при выходе из блока. Это означает, что их значения не хранятся между вызовами функций. Чтобы сохранить значения переменных между вызовами, можно использовать модификатор static.

 

В языке С++ и версии С99 локальные переменные можно объявлять практически в любом месте блока, а в версии С89 они должны быть объявлены в начале блока до выполнения каких-либо инструкций "действия".

 

Пример 1

void f(int a)

{

int a;

a = 10;

int b; // ОК для С++ и С99, но не для С89

....

}

 

 

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

 

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

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

В работе с подпрограммами приходится иметь дело с двумя видами параметров: список фактических параметров и список формальных параметров.

 

Список параметров (фактических или формальных — всё равно) записывается в круглых скобках сразу за именем функции. В принципе, список параметров может быть пустым, но скобки за именем функции обязательны!

 

Список фактических параметров — это те реальные данные, с помощью которых можно настроить алгоритм подпрограммы на обработку конкретных данных. Фактические параметры указываются в списке параметров при вызове функции и передаются в эту функцию. В нашем примере при вызове функции fmax() в списке фактических параметров указаны две переменные: a и b. Это реальные объекты, под которые в функции main() выделена память, они могут иметь какие-то значения и т.д.

 

Список формальных параметров — это по сути набор требований, которые предъявляет подпрограмма к передаваемым в неё данным. Список фактических параметров записывается в заголовке функции при её определении в скобках. Для каждого параметра необходимо указать тип и имя. При необходимости задаётся и способ передачи (об этом позже — в следующей теме). Имена формальных параметров локализованы в подпрограмме и дополняют перечень локальных объектов этой подпрограммы. Так, в нашей функции fmax() формальные параметры x и y типа double дополняют список локальных переменных, состоящий из одной переменной max типа double. В итоге функция fmax() имеет три локальных переменных, известных только в этой функции.

 

Между списками фактических и формальных параметров должно выдерживаться полное соответствие по количеству параметров, их типу, порядку следования и способу передачи в функцию. Несоблюдение этих требований в лучшем случае (для программиста) приводит к ошибке на стадии компиляции, в худшем — к «багам» программы, которые будут время от времени проявляться во время выполнения программы. «Выловить» же такого рода ошибки очень непросто. Как правило, они остаются в программе в течение всего периода её использования. Поэтому исключительно важно соблюдать правильность передачи данных в подпрограмму.

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

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

4 float areaRectangle(float, float) //функция, вычисляющая площадь прямоугольника с двумя параметрами a(см) и b(см)

{

return a * b; // умножаем длинны сторон прямоугольника и возвращаем полученное произведение

}

 

 

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

 

Предположим, что наши исходные данные (стороны прямоугольника) заданы в метрах и сантиметрах, например такие: a = 2м 35 см; b = 1м 86 см. В таком случае, удобно было бы использовать функцию с четырьмя параметрами. То есть, каждая длинна сторон прямоугольника передаётся в функцию по двум параметрам: метры и сантиметры.1

4 float areaRectangle(float a_m, float a_sm, float b_m, float b_sm) // функция, вычисляющая площадь прямоугольника с 4-мя параметрами a(м) a(см); b(м) b(cм)

{

return (a_m * 100 + a_sm) * (b_m * 100 + b_sm);

}

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

Теперь, самое главное – у нас есть две функции, с разной сигнатурой, но одинаковыми именами (перегруженные функции). Сигнатура – это комбинация имени функции с её параметрами. Как же вызывать эти функции? А вызов перегруженных функций ничем не отличается от вызова обычных функций, например:

areaRectangle( 32, 43); // будет вызвана функция, вычисляющая площадь прямоугольника с двумя параметрами a(см) и b(см)

areaRectangle( 4, 43, 2, 12); // будет вызвана функция, вычисляющая площадь прямоугольника с 4-мя параметрами a(м) a(см); b(м) b(cм)

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

#include <iostream>

using namespace std;

// прототипы перегруженных функций

float areaRectangle(float a, float b);

float areaRectangle(float a_m, float a_sm, float b_m, float b_sm);

int main()

{

cout << "S1 = " << areaRectangle(32,43) << endl; // вызов перегруженной функции 1

cout << "S2 = " << areaRectangle(4, 43, 2, 12) << endl; // вызов перегруженной функции 2

return 0;

}

// перегруженная функция 1

float areaRectangle(float a, float b) //функция, вычисляющая площадь прямоугольника с двумя параметрами a(см) и b(см)

{

return a * b; // умножаем длинны сторон прямоугольника и возвращаем полученное произведение

}

// перегруженная функция 2

float areaRectangle(float a_m, float a_sm, float b_m, float b_sm) // функция, вычисляющая площадь прямоугольника с 4-мя параметрами a(м) a(см); b(м) b(cм)

{

return (a_m * 100 + a_sm) * (b_m * 100 + b_sm);

}

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

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

 

На самом деле, шаблоны функций -это мощный инструмент в С++, который намного упрощает труд программиста. Например, нам нужно запрограммировать функцию, которая выводила бы на экран элементы массива. Задача не сложная! Но, чтобы написать такую функцию, мы должны знать тип данных массива, который будем выводить на экран. И тут нам говорят — тип данных не один, мы хотим, чтобы функция выводила массивы типа int, double, float и char.

 

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

// перегрузка функции printArray для вывода массива на экран

void printArray(const int * array, int count)

{

for (int ix = 0; ix < count; ix++)

cout << array[ix] << " ";

cout << endl;

}

 

void printArray(const double * array, int count)

{

for (int ix = 0; ix < count; ix++)

cout << array[ix] << " ";

cout << endl;

}

 

void printArray(const float * array, int count)

{

for (int ix = 0; ix < count; ix++)

cout << array[ix] << " ";

cout << endl;

}

 

void printArray(const char * array, int count)

{

for (int ix = 0; ix < count; ix++)

cout << array[ix] << " ";

cout << endl;

}

 

 

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

 

И, если запустить программу с этими функциями, то она будет исправно работать. Компилятор сам будет определять какую функцию использовать при вызове.

 

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

 

Мы создаем один шаблон, в котором описываем все типы данных. Таким образом исходник не будет захламляться никому ненужными строками кода. Ниже рассмотрим пример программы с шаблоном функции. Итак, вспомним условие: «запрограммировать функцию, которая выводила бы на экран элементы массива».

#include <iostream>

#include <cstring>

using namespace std;

// шаблон функции printArray

template <typename T>

void printArray(const T * array, int count)

{

for (int ix = 0; ix < count; ix++)

cout << array[ix] << " ";

cout << endl;

} // конец шаблона функции printArray

int main()

{

// размеры массивов

const int iSize = 10,

dSize = 7,

fSize = 10,

cSize = 5;

// массивы разных типов данных

int iArray[iSize] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

double dArray[dSize] = {1.2345, 2.234, 3.57, 4.67876, 5.346, 6.1545, 7.7682};

float fArray[fSize] = {1.34, 2.37, 3.23, 4.8, 5.879, 6.345, 73.434, 8.82, 9.33, 10.4};

char cArray[cSize] = {"MARS"};

cout << "\t\t Шаблон функции вывода массива на экран\n\n";

// вызов локальной версии функции printArray для типа int через шаблон

cout << "\nМассив типа int:\n"; printArray(iArray, iSize);

// вызов локальной версии функции printArray для типа double через шаблон

cout << "\nМассив типа double:\n"; printArray(dArray, dSize);

// вызов локальной версии функции printArray для типа float через шаблон

cout << "\nМассив типа float:\n"; printArray(fArray, fSize);

// вызов локальной версии функции printArray для типа char через шаблон

cout << "\nМассив типа char:\n";printArray(cArray, cSize);

return 0;

}

Заметьте, код уменьшился в 4 раза, так как в программе объявлен всего один экземпляр функции — шаблон. В main я объявил несколько массивов — четыре, для типов данных: int, double, float, char. После чего, в строках 26, 28, 30, 32, выполняется вызов функции printArray для разных массивов. Обратите внимание, что перед объявлением самой функции, в строке 5, стоит следующая запись template<typenameT>. Как раз эта запись и говорит о том, что функция printArray на самом деле является шаблоном функции, так как в первом параметре printArray стоит тип данных const T*, точно такой же как и в строке 5.

 

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

 

 

или1 template <typename T>

 

 

или1 template <typename T1, typename T2>

 

 

Ключевое слово typename говорит о том, что в шаблоне будет использоваться встроенный тип данных, такой как: int, double, float, char и т. д. А ключевое слово class сообщает компилятору, что в шаблоне функции в качестве параметра будут использоваться пользовательские типы данных, то есть классы.

 

У нас в шаблоне функции использовались встроенные типы данных, поэтому в строке 5 мы написали template<typenameT>. Вместо T можно подставить любое другое имя, какое только придумаете. Давайте подробно рассмотри фрагмент кода из верхней программы, я его вынесу отдельно.1

8 // шаблон функции printArray

template <typename T>

void printArray(const T * array, int count)

{

for (int ix = 0; ix < count; ix++)

cout << array[ix] << " ";

cout << endl;

} // конец шаблона функции printArray

 

 

В строке 2 выполняется определение шаблона с одним параметром — T, причем этот параметр будет иметь один из встроенных типов данных, так как указано ключевое слово typename.

 

Ниже, в строках 3 — 8 объявлена функция, которая соответствует всем критериям объявления обычной функции, есть заголовок, есть тело функции, в заголовке есть имя и параметры функции, все как обычно. Но что эту функции превращает в шаблон функции, так это параметр с типом данных T, это единственная связь с шаблоном, объявленным ранее. Если бы мы написали1

6 void printArray(const int * array, int count)

{

for (int ix = 0; ix < count; ix++)

cout << array[ix] << " ";

cout << endl;

}

 

 

то это была бы простая функция для массива типа int.

 

Так вот, по сути T — это даже не тип данных, это зарезервированное место под любой встроенный тип данных. То есть когда выполняется вызов этой функции, компилятор анализирует параметр шаблонированной функции и создает экземпляр для соответственного типа данных: int, char и так далее.

 

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

Надеюсь основную мысль по шаблонам функций до вас довел. Для закрепления материала, давайте рассмотрим еще один пример программы, с использованием шаблона функции.1

24 #include <iostream>

#include <cstring>

using namespace std;

// шаблон функции для поиска максимального значения в массиве

template <typename T>

T searchMax(const T* array, int size)

{

T max = array[0]; // максимальное значение в массиве

for (int ix = 0; ix < size; ix++)

if (max < array[ix])

max = array[ix];

return max;

}

int main()

{

// тестируем шаблон функции searchMax для массива типа char

char array [] = "aodsiafgerkeio";

int len = strlen(array);

cout << "Максимальный элемент массива типа char: " << searchMax(array, len) << endl;

// тестируем шаблон функции searchMax для массива типа int

int iArray [5] = {3,5,7,2,9};

cout << "Максимальный элемент массива типа int: " << searchMax(iArray, 5) << endl;

return 0;

}

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

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

© 2013 wikipage.com.ua - Дякуємо за посилання на wikipage.com.ua | Контакти