объявление членов (иногда говорят полей) структуры, например,




Int numberDay(char ch)

{

Тело функции

return (//число или выражение необязательно в скобках);

}

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

Вероятно читателю не очень ясны обобщенные рассуждения. Поэтому перейдем к примеру.

//Прототип которая выполняет сложение двух целых чисел

int summa(int x, int y);

//описание функции которая выполняет сложение двух целых чисел

int summa(int x, int y) /*объявляется тип возвращаемого значения,

* имя функции тип и имена аргументов */

{

z=x+y; //код программы

return z; //возвращаемая величина – z должна иметь объявленный тип

}

Если теперь в программе встретится оператор cout<<summa(1,2); то еа экране появится число 3.

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

//Программа калькулятор

#include <iostream.h>

//сначала описываются прототипы функций

//вот они

double summa(double arg1, double arg2);

double subtraction(double arg1, double arg2);

double multiply(double arg1, double);

double division(double, double);

/* В данном случае используются 4 функции.

Каждая выполняет одну арифметическую операцию.

Описание используемых в программе функций вполне здравая операция подобная присоединению библиотек */

//=======================================================

main(){

double x,y,result;

char symbol;

while(true){ //бесконечный цикл для работы калькулятора

cout <<"\n";

cin >>x>>symbol>>y;

switch (symbol){

case '+': result= summa(x,y); break;

case '-': result= subtruction(x,y); break;

case '*': result= multiply(x,y); break;

case '/': result= division(x,y); break;

default: cout << "\nThe program is over";

}

cout<<"\n"<< result;

}

}

//==========================================================

/*После окончания главной части программы дадим описание самих функций подобно тому как происходит описание переменных*/

//+++++++++++++++++++++++++++++++++++++++++++++++++++++

double summa (double argument1, double argument2)

{ //начало тела функции

double consequence; // consequence значит результат

consequence=argument1+argument2;

return consequence;

// конец тела функции

}

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++

// описание следующей функции

//-------------------------------------------------------

double subtraction (double arg2, double arg1)

{

return arg2-arg1; /* из первого аргумента вычитается второй */

}

//-----------------------------------------------------

//*****************************************************

double multiply (double a, double b)

{

return (a*b); /* обратите внимание, что можно

возвращать не число, а результат */

}

//*****************************************************

//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

double division (double x,double y)

{

if (y!=0)return x/y;

else return 1.0e300; /* оператор return может встречаться

в функции несколько раз*/

}

//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

 

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

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

void temperature(int t){

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

return; // этот оператор можно не записывать

}

 

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

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

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

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

{//тело функции

return 0;

}

 

Вы видите, что в данном случае функция main() имеет два аргумента, один это целое типа int – число аргументов, другой символьный char, даже не просто символьный а целый массив с некоторой звездочкой. Возврат функции main() значения 0 свидетельствует об успешном завершении программы. В наших примерах это никак не используется, хотя в серьезных проектах это свойство функции применяется довольно часто. Может возникнуть вопрос о том кому функция main() возвращает значение? Ответ простой – операционной системе. Заметим, что в наших задачах операционная система никак не реагирует на полученное сообщение.

По поводу аргументов функции main() мы подробно поговорим когда познакомися с другим типом данных, который называются указателями. Сейчас только заметим, что, на самом деле, функция main(int argc, char* argv[])) может иметь сколько угодно аргументов, или не иметь ни одного. Хотя в скобкак вроде бы стоит всего два аргумента. Действительное количество аргументов задается следующим образом. Первый аргумент, стоящий в скобках функции main() и обозначенный как argc, говорит о количестве этих аргументов, его значение не может быть меньше 1, а второй, argv[], об их адресах, которые записываются в специальную таблицу, которая называется массивом. Впрочем, о массивах мы тоже будем говорить позднее.

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

 

//рекурсивная функция факториал

#include <iostream.h>

int factorial(int n);

main(){

int n,result;

while(true){

cout <<"\n" <<"n=";

cin>> n;

cout<<factorial(n);

}

}

int factorial(int n){

if(n==1)return 1;

else return (n*factorial(n-1));

}

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

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

Аргумент или аргументы по умолчанию задаются присваиванием его значения при объявлении. Например:

void function(int n=5, int m=10);

Теперь вызывая функцию function можно передать ей целочисленные параметры function(5,10), а можно не передавать function(). В этом случае n будет равно 5, а m равно 10.

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

void function1(int n, int m=10);

void function2(int n, int m=10, double p=3.1415926);

но если изменить порядок аргументов, например, так:

void function3(double p=3.1415926, int n, int m=10,);

то это вызовет ошибку. Это вполне естественно, т.к. к функции function2 можно обратиться указав значение только первого аргумента function2(5). К функции function3 следовало бы обращаться иначе function2(,5,). Запятые без аргументов являются дополнительным источником ошибок и, поэтому, в С++ такой порядок следования аргументов запрещен.

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

тип имя_функции (список_аргументов, …);

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

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

int func(int x, int y, …);

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

Для работы с неопределенным списком в заголовочгом файле stdarg.h определен вспомогательный тип va_list и три макроса va_start, va_arg и va_end.

Макрос va_start имеет синтаксис:

void va_start(va_list ap, lastfix)

Этот макрос начинает работу со списком, записывая в ap адрес первого передаваемого в функцию аргумента из списка с неопределенным числом аргументов. Параметр lastfix это имя последнего из обязательных аргументов функции.

Макрос va_arg имеет синтаксис:

type va_arg(va_list ap, type)

Макрос возвращает значение очередного аргумента из списка. Параметр type указывает тип аргумента. Перед вызовом va_arg значение ap должно быть установленовызовом va_start или va_arg. Каждый вызов va_arg переводит указатель ap на следующий аргумент.

Макрос va_end имеет синтаксис

va_end(va_list up)

Макрос завершает работу со списком, освобождая память. Он должен вызываться после того как прочтется весь список аргументов.

Рассмотрим пример в котором вызывается функция которая рассчитывает сумму m первых натуральных чисел меньших 6.

#include <iostream>

#include <stdarg.h> // подключние файла

sum(int n,...); // объявление прототипа функции sum

using namespace std;

//---------------------------------------------------

main(){

int m;

cin>>m;

cout<< sum(m,1,2,3,4,5); // вызов функции суммирования m чисел

}

//---------------------описание функции

sum(int m,...){

double s=0;

va_list ap; // ap – переменная для записи адреса

va_start(ap, m); //Определяется адрес первого аргумента списка

for(int i=1;i<=m;i++)

s+=va_arg(ap,int); //добавляется очередной аргумент из списка

va_end(ap); // освобождается память

return s;

}

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

 

2.1. Передача параметров

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

Чтобы компилятор мог отличить способ передачи параметра, необходимо как-то, особым образом, выделить способ передачи по ссылке. Для этого используется знак амперсанда "&", который располагается после имени формального параметра передаваемого по ссылке, как в прототипе функции, так и в заголовке ее определения. Например,

void summa (int& x, int& y);

В качестве более серьезного примера рассмотрим программу пересчета длины заданной в дюймах в метрическую систему, исходя из равенства 1дюйм=0,0254 м.

//Программа, демонстрирующая передачу по ссылке

#include <iostream>

using namespace std;

/*далее идут прототипы трех функций

void getLength(double&); //ввод длины в дюймах

void translate(double&); //перевод в метры

void print(double&); // вывод на экран

 

void main()

{

double x=0; /*определяем переменную к адресу которой

будем обращаться в дальнейшем,

присваиваем ей начальное значение */

while (x>=0) //организуем бесконечный цикл

{

getLength(x); /* вводим значение в ячейку с адресом

по которому хранится переменная x */

translate(x); /* пересчитываем величину длины из

дюймов в метры */

print(x); /*вывод на экран переменной расположенной

по адресу */

}

}

/*далее идут определения функций

void getLength(double& y)

{

cout<<"Length inch=";

cin>>y;

}

void translate(double& z)

{

z=0.0254*z;

}

void print(double& u)

{

cout<<"Length meter="<<u<<endl;

}

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

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

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

 

#include<iostream.h>

#include<iomanip.h>

#include<windows.h>

 

//-------------------------

void modify_array(int [], int);

void modify_element(int);

//--------------------------

void main(){

SetConsoleOutputCP(1251);

const int size=5;

int a[size]={0,1,2,3,4};

cout<<"Результаты передачи всего массива по ссылке"<<endl<<endl

<<"значения исходного массива"<<endl;

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

cout<<setw(3)<<a[i]<<endl;

modify_array(a,size);

cout<<"Значения модифицированного массива:"<<endl;

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

cout<<setw(3)<<a[i];

cout<<endl<<endl<<endl;

cout<<"Результаты передачи элемента массива по значению: "

<<endl<<endl<<"a[3]="<<a[3]<<endl;

modify_element(a[3]);

cout<<"a[3]="<<a[3]<<endl;

char z;

cin>>z;

}

 

void modify_array(int b[], int d){

for(int j=0; j<d; j++)

b[j]*=2;

}

void modify_element(int e){

cout<<"Значение в modify_element равно: "<<(e*=2)<<endl;

}

 

2.2. Библиотечные функции

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

Арифметические функции.

Прототип Описание Заголовочный файл
int abs(int); Абсолютное значение cstdlib
long labs(long); Абсолютное значение cstdlib
double fabs(double); Абсолютное значение cmath
double sqrt(double); Квадратный корень cmath
double pow(double,double); Первый аргумент возводится в степень равную второму аргументу cmath
double exp(double); e возводится в степень аргумента cmath
double log(double); Натуральные логарифм ln cmath
double log 10(double); Десятичный логарифм lg cmath
double ceil(double); Округление аргумента в меньшую сторону cmath
double floor(double); Округление аргумента в большую сторону cmath

 

Тригонометрические функции

 

Прототип Орисание Заголовочный файл
double cos(double); Косинус cmath
double cosh(double); Косинус гиперболический cmath
double sin(double); Синус cmath
double sinh(double); Синус гиперболический cmath
double tan(double); Тангенс cmath
double tanh(double); Тангенс гиперболический cmath
double acos(double); Арккосинус cmath
double asin(double); Арксинус cmath
double atan(double); Арктангенс cmath

 

Генератор случайных чисел

 

Прототип Орисание Заголовочный файл
int random(int n); random(n) возвращает псевдослучайное целое число большее или равное 0 и меньшее или равное n cstdlib
int rand(); Возвращает псевдослучайное целое число от 0 до константы определенной в конкретной реализации компилятора, но не меньшей 32767 cstdlib

 

Функции работы с символами

Прототип Описание Заголовочный файл
bool isalnum(char) Возвращает true если аргумент буква или цифра, в противном случае false. cctype
bool isalpha(char) Возвращает true если аргумент буква, в противном случае false. cctype
bool isdigit(char) Возвращает true если аргумент цифра, в противном случае false. cctype
bool ispunct(char) Возвращает true если аргумент не буква, не цифра и не пробел, в противном случае false. cctype
bool isspace(char) Возвращает true если аргумент прбел, табуляция или возврат каретки, в противном случае false. cctype
bool isстек(char) Возвращает true если управляющий символ, в противном случае false. cctype
bool islower(char) Возвращает true если аргумент строчная буква, в противном случае false. cctype
bool isupper(char) Возвращает true если аргумент прописная буква, в противном случае false. cctype
int tolower(char) Возвращает код строчной версии символа-аргумента. При ее отсутствии возвращает значение аргумента. cctype
int toupper(char) Возвращает код прописной версии символа-аргумента. При ее отсутствии возвращает значение аргумента. cctype

 

Конечно, здесь приведена лишь малая часть библиотечных функций. Для того, чтобы воспользоваться этими функциями нужно создать соответствующую директиву препроцессору в которой указать название библиотекибиблиотеки, в которой находится вызываемая программой функция. Впрочем, мы уже знакомы с библиотечными функциями cin и cout, которые предназначены для обеспечения ввода и вывода. Напомним, что для включения их в программу нужно дать директиву препроцессору в виде #include <iostream>, или #include <iostream.h>, >. которая Каждая директива должна начинаться с новой строки. В этих директивах в угловых скобках указывается название библиотеки функции, которой потребуются в программе.

Как и всегда рассмотрим пример программы. В данном случае это будет, котпрограмма для перевода вводимых строчных символов в заглавные.

 

//Преобразование строчных символов в заглавные

#include <iostream.h> //подключение библиотек

#include <cctype>

void main(){

char ch;

while(true){

cin >> ch;

ch= toupper(ch); /*определение кода и преобразование кода

в символ с помощью оператора присваивания*/

cout << ch;

}

}

 

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

Среди большого числа библиотек с которыми работает С++ есть библиотека функций предназначенных для работы с операционной системой Windows. В эту библиотеку входит функция, которая позволяет сменить кодировку консоли. Дело в том, что консоль использует, так называемую кодировку “OEM”. В то время как windows работает в кодировке " Windows 3.1 Cyrillic ", или иначе 1251. Коды символов национальных алфавитов в этих кодировках различные. Поэтому на экране вместо русских букв появляются всякие крючки.

Для вывода кириллицы, нужно выполнить два условия:

- сменить кодировку консоли;

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

В приведенной ниже программе используются функции GetConsoleOutputCP()и SetConsoleOutputCP(номер_кодировки). Первая функция позволяет определить тип применяемой кодировки, в данном случае это будет 866 (т.е. DOS 866) это и есть “OEM”. Вторая назначить кодировку, в данном случае это будет " Windows 3.1 Cyrillic ". Аргументом функции в этом случае будет идентификатор соответствующий данному названию – 1251 (см. приложение).

//Пример программы с печатью русским шрифтом Windows

#include <iostream>

#include <windows>

using namespace std;

int main()

{

cout << GetConsoleOutputCP()<<endl;

SetConsoleOutputCP(1251);

cout << GetConsoleOutputCP()<<endl;

cout<<"Привет!"<<endl<<endl;

return 0;

}

Если вы запустите выполнение программы, то вновь не увидите желаемых русских букв. Чтобы их получить необходимоужно выполнить второе условие – применить нужный специальный шрифт. Выбор шрифтов у консоли невелик. Нам подойдет "Lucida Console". Для того чтобы его установить щелкните по левому верхнему углу окна консоли. В появившемся меню выберите пункт "Свойства". На вкладке "Шрифт" выберете "Lucida Console". Если компилятор говорит о том, что не может найти файл windows попробуйте присоединить windows.h.

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

 

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

 

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

#include<iostream>

#include<windows.h>

using namespace std;

main(){

for(int i=0;i<20;i++){

SetCursorPos(rand()%640,rand()%480); /*установка курсора в точку

со случайными координатами */

Sleep(100); //пауза 100 миллисекунд } }

}

}

 

Заканчивая разговор о функциях, сделаем еще одно замечание.

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

//Программа для нахождения наибольшего из двух целых меньших 100

#include <iostream>

#include <windows>

using namespace std;

int max(int x, int y){return x>y?x:y;} /*Определяет максимальное

из двух целых x и y*/

void main(){

SetConsoleOutputCP(1251);

int i,j;

do {

cout<<"i=";

cin>>i;

cout<<"j=";

cin>>j;

if(max(i,j)<0) //

cout<<"введите числа еще раз\n";

else cout<<"max="<<max(i,j)<<endl;

}

while(max(i,j)<100);

}

 

2.3. Локальные и глобальные переменные

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

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

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

//Нахождение максимального числа

#include <iostream.h>

int max(int n, int m); // прототип функции

 

void main(){

int n=-2147483648,m,k;

while(true){

cout <<endl<<"m=";

cin >>m; // ввод числа подлежащего сравнению

n=max(m,n); //максимальное значение присваивается n

cout <<"\nmax="<<n;

}

}

 

int max(int n, int m){

if (n>m) m=n; /* сравнение введенного числа с наибольшим из введенных ранее. Максимальное значение присваивается m */

return m;

}

 

Программа вводит число m и обращается к функции max(), которая сравнивает его с наибольшим числом n введенным ранее. Но в функции этим числа даны противоположные имена, т.е. вместо n используется имя m и наоборот. Тем не менее, программа работает нормально.

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

То же самое можно сказать о константах, которые разделяются на локальные и глобальные. Единственное отличие от переменных состоит в том, что они должны быть помечены зарезервированным словом const.

Вообще говоря каждая функция локальна в том блоке где она объявлена.

#include <iostream.h>

int n=0; //Объявлена глобальная переменная

void main(){

int n=1; cout<<endl<<"n="<<n; //Главный блок

{int n=2; cout<<endl<<"n="<<n; /*Вложенный блок, n новая

переменная*/

{int n=3; cout<<endl<<"n="<<n; /*Второй вложенный блок и вновь

новая переменная n*/

}

}

}

 

Итак, если в блоке видна только локальная переменная, то сразу возникает вопрос: как обратиться к глобальной переменной? Это просто сделать, используяприменив новый оператор, который называется оператором расширения области видимости. Обозначается этот оператор двойным двоеточием ::.

Рассмотрим предыдущий пример, но в каждом блоке применим оператор расширения области видимости.

#include <iostream.h>

int n=0;

void main(){

int n=1; cout<<endl<<"n="<<n;

cout<<endl<<"n="<<::n;

{int n=2; cout<<endl<<"n="<<n;

cout<<endl<<"n="<<::n;

{int n=3; cout<<endl<<"n="<<n;

cout<<endl<<"n="<<::n<<endl;

}

}

}

Действие этого оператора распространяется только на одну переменную стоящую после оператора видимости.

Есть еще один интересный вопрос. Он касается времени жизни переменной. Представьте себе, что иемеется несколько вложенных блоков, например,

int i=1, j=2, k=3;

{int i=10,j=20,k=30; // видны переменные только этого блока

........

{int i=100, j=200, k=300; //видны переменные только этого блока

........

}

}

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

{static int i=100, j=200, k=300;

}

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

И еще одно замечание. Выделение нового вида статических переменных требует как то называть старые переменные. Они называются автоматическими. Для того чтобы подчеркнуть их отличие от статических в программах при их объявлении можно записывать ключевое слово auto, например,

{ auto int i=100, j=200, k=300;}

хотя никто так не поступает.

 

 

Объявления функций

Функции всегда определяются глобально. Они могут быть объявлены с классом памяти static или extern. Объявления функций на локальном и глобальном уровнях имеют одинаковый смысл.

Правила определения области видимости для функций отличаются от правил видимости для переменных и состоят в следующем.

1. Функция, объявленная как static, видима в пределах того файла, в котором она определена. Каждая функция может вызвать другую функцию с классом памяти static из своего исходного файла, но не может вызвать функцию определенную с классом static в другом исходном файле. Разные функции с классом памяти static имеющие одинаковые имена могут быть определены в разных исходных файлах, и это не ведет к конфликту.

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

3. Если в объявлении функции отсутствует спецификатор класса памяти, то по умолчанию принимается класс extern.

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

 

Время жизни и область видимости программных объектов

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

1. Переменная, объявленная глобально (т.е. вне всех блоков), существует на протяжении всего времени выполнения программы.

2. Локальные переменные (т.е. объявленные внутри блока) с классом памяти register или auto, имеют время жизни только на период выполнения того блока, в котором они объявлены. Если локальная переменная объявлена с классом памяти static или extern, то она имеет время жизни на период выполнения всей программы.

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

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

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

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

4. Функции с классом памяти static видимы только в исходном файле, в котором они определены. Всякие другие функции видимы во всей программе.

Метки в функциях видимы на протяжении всей функции.

Имена формальных параметров, объявленные в списке параметров прототипа функции, видимы только от точки объявления параметра до конца объявления функции

 

При инициализации необходимо придерживаться следующих правил:

1. Объявления содержащие спецификатор класса памяти extern не могут содержать инициаторов.

2. Глобальные переменные всегда инициализируются, и если это не сделано явно, то они инициализируются нулевым значением.

3. Переменная с классом памяти static может быть инициализирована константным выражением. Инициализация для них выполняется один раз перед началом программы. Если явная инициализация отсутствует, то переменная инициализируется нулевым значением.

4. Инициализация переменных с классом памяти auto или register выполняется всякий раз при входе в блок, в котором они объявлены. Если инициализация переменных в объявлении отсутствует, то их начальное значение не определено.

5. Начальными значениями для глобальных переменных и для переменных с классом памяти static должны быть ко



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-02-16 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: