Вопрос 43. Локальные и глобальные величины программы. 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Вопрос 43. Локальные и глобальные величины программы.



Локальные переменные

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

Объявление глобальных переменных

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

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

Правильное задание области действия глобальной переменной

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

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

ополностью определялся прототипом функции.

Немного о функциях.

существует два вида передачи величин в функцию: по значению и по адресу.

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

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

#include <iostream>

using namespace std;

 

void f(int i, int* j, int& k);

 

int main() {

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

cout << "i j k\n";

cout << i << ' ' << j << ' ' << k << '\n';

f(i, &j, k);

cout << i << ' ' << j << ' ' << k;

return 0;

}

 

void f(int i, int* j, int& k) {

i++;

(*j)++;

k++;

}

 

Результат работы программы:

i j k

1 2 3

1 3 4

 

Первый параметр (i) передается по значению. Его изменение в функции не влияет на исходное значение.

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

Третий параметр (k) передается по адресу с помощью ссылки.

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

Если требуется запретить изменение параметра, используется модификатор const:

int f(const char*);

char* t(char* a, const int* b);

 

СОВЕТ

Рекомендуется указывать const перед всеми параметрами, изменение которых в функции не предусмотрено. Это облегчает отладку. Кроме того, на место параметра типа const& может передаваться константа.

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

Передача массивов в качестве параметров

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

#include <iostream>

using namespace std;

 

int sum(const int* mas, const int n);

int const n = 10;

 

int main() {

int marks[n] = {3, 4, 5, 4, 4};

cout << "Сумма элементов массива: " << sum(marks, n);

return 0;

}

 

int sum(const int* mas, const int n) {

/*

варианты:

int sum(int mas[], int n) или

int sum(int mas[n], int n)

(n должна быть константой)

*/

int s = 0;

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

s += mas[i];

return s;

}

 

При передаче многомерных массивов все размерности, если они не известны на этапе компиляции, должны передаваться в качестве параметров.

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

#include <stdio.h>

 

int sum(const int *a, const int nstr, const int nstb);

 

int main() {

int b[2][2] = {{2, 2}, {4, 3}};

printf("b %d\n", sum(&b[0][0], 2, 2)); // имя массива передавать нельзя из-за несоответствия типов

 

int i, j, nstr, nstb, *a;

printf("Введите количество строк и столбцов: \n");

scanf("%d%d", &nstr, &nstb);

 

a = (int*)malloc(nstr * nstb * sizeof(int));

for (i = 0; i < nstr; i++)

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

scanf("%d", &a[i * nstb + j]);

printf("a %d\n", sum(a, nstr, nstb));

 

return 0;

}

 

int sum(const int *a, const int nstr, const int nstb) {

int i, j, s = 0;

for (i = 0; i < nstr; i++)

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

s += a[i * nstb + j];

return s;

}

 

Для того чтобы работать с двумерным массивом естественным образом, можно применить альтернативный способ выделения памяти:

#include <iostream>

using namespace std;

 

int sum(const int **a, const int nstr, const int nstb);

 

int main() {

int nstr, nstb;

cin >> nstr >> nstb;

 

int **a;

a = new int* [nstr];

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

a[i] = new int[nstb];

 

/*... формирование матрицы a */

 

cout << sum(a, nstr, nstb);

return 0;

}

 

int sum(const int **a, const int nstr, const int nstb) {

int i, j, s = 0;

for (i = 0; i < nstr; i++)

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

s += a[i][j];

return s;

}

 

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

Передача имен функций в качестве параметров

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

void f(int a) { /*... */ } // определение функции

void (*pf)(int); // указатель на функцию

 

//...

 

pf = &f; // указателю присваивается адрес функции (можно написать pf = f;)

pf(10); // функция f вызывается через указатель pf (можно написать (*pf)(10);)

 

Для того чтобы сделать программу более читаемой, при описании указателей на функции используют переименование типов (typedef). Можно объявлять массивы указателей на функции (это может быть полезно, например, при реализации меню):

typedef void (*Pf)(int); // описание типа PF как указателя на функцию с одним параметром типа int

PF menu[] = {&new, &open, &save} // описание и инициализация массива указателей

menu[1](10); // вызов функции open

 

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

void fun(PF pf) { // функция fun получает в качестве параметра указатель типа PF

//...

pf(10); // вызов функции, переданной через указатель

//...

}

 

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

Параметры со значениями по умолчанию

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

int f(int a, int b = 0);

void f1(int, int = 100, char* = 0);

void err(int errValue = errno); //errno — глобальная переменная

 

f(100); f(a, 1); // варианты вызова функции f

f1(a); f1(a, 10); f1(a, 10, "Vasia"); // варианты вызова функции f1

// f1(a,,"Vasia") неверно!

 

 

Вопрос 44. Способы обмена информацией между вызывающей и вызываемой функцией.

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

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

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

#include <iostream.h>

int Turn = 5; //объявление глобальной переменной

int main ()

{int Turn = 70; // объявление локальной переменной

cout << Turn <<’\n’; //вывод локального значения

cout <<::Turn <<’\n’; //вывод глобального значения

return 0;

}

В результате в две строки будет выведено два значения: 5 и 70.
Использование возвращаемого значения

Механизм возврата из функции в вызвавшую ее функцию реализуется оператором

return [ выражение ];

Функция может содержать несколько операторов return, это определяется потребностями алгоритма. Однако, после любого из этих операторов прекращается выполнение функции. Если функция описана как void, выражение не указывается и, кроме того, оператор return можно опускать.

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

Примеры:

int sum(int a, int b){ return (a + b);}

int f1(){return 1;} // правильно

void f2(){return 1;} // неправильно, f2 не должна возвращать значение

double f3{return 1;} // правильно, 1 преобразуется к типу double

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

Пример:

int* f(){ int a = 5; return &a;} // нельзя!

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

с = sum(a, b);

Переменной с присваивается значение, вычисленное функцией sum() при фактических параметрах a и b.
Использование параметров функции

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

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

Существует два способа передачи параметров в функцию: по значению и по адресу.

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

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

 

void f(int i, int* j, int& k);

int main(){

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

cout <<"i j k\n";

cout <<i<<’ ‘<<j<<’ ‘<<k<<’\n’;

f(i, &j, k);

cout <<i<<’ ‘<<j<<’ ‘<<k<<’\n’;

}

void f(int i, int* j, int& k){i++; (*j)++; k++;}

Результат работы программы:

i j k

1 2 3

1 3 4

Первый параметр (i) передается по значению. Его изменение в функции не влияет на исходное значение, так как функция его изменить не может. Второй параметр (j) передается по адресу с помощью указателя, при этом для передачи в функцию адреса фактического параметра используется операция взятия адреса, а для получения его значения в функции требуется операция разыменования. Третий параметр (k) передается по адресу с помощью ссылки.

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

Если требуется запретить изменение параметра внутри функции, используется модификатор const:

int f(const char*);

char* t(char* a, const int* b);

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

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

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

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

 

int sum(const int* mas, const int n);

int const n = 10;

int main()

{

int marks[n] = {3, 4, 5, 4, 4};

cout << RUS("Сумма элементов массива: ") <<sum(marks, n);

}

int sum(const int* mas, const int n) // варианты: int sum(int mas[], int n)

// или" int sum(int mas[n]. int n)

// (величина n должна быть константой)

{

int s = 0;

for (int i = 0; i<n; i++) s += mas[i];

return s; }

 

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

int sum(const int *a, const int nstr, const int nstb);

int main()

{

int b[2][2] = {{2, 2},{4, 3}};

printf(RUS("Сумма элементов матрицы b: %d\n"), sum(&b[0][0], 2, 2));

// имя массива передавать в sum нельзя из-за несоответствия типов

//т.к.массив многомерный

int i, j, nstr, nstb;

printf(RUS("Введите количество строк и столбцов:\n"));

scanf("%d%d", &nstr, &nstb);

int*a = (int *)malloc(nstr * nstb * sizeof(int));

printf(RUS("Введите матрицу a\n"));

for (i = 0; i<nstr; i++)

for (j = 0; j<nstb; j++)scanf("%d", &a[i * nstb + j]);

printf RUS("Сумма элементов матрицы a: %d\n"), sum(a, nstr, nstb));

}

int sum(const int *a, const int nstr, const int nstb)

{ int i, j, s = 0;

for (i = 0; i<nstr; i++)

for (j = 0; j<nstb; j++)s += a[i * nstb + j];

return s; }

 

Для того чтобы работать с двумерным массивом естественным образом, можно применить альтернативный способ выделения памяти:

 

int sum(int **a, const int nstr, const int nstb);

int main(){

int nstr, nstb;

cin >> nstr >> nstb;

int i, j;

// Формирование матрицы а:

int **a = new int* [nstr];

for (i = 0; i<nstr; i++) a[i] = new int [nstb];

for (i =0; i<nstr; i++)

for (j = 0; j<nstb; j++)cin >> a[i][j];

cout << sum(a, nstr, nstb);

}

int sum(int **a, const int nstr, const int nstb)

{ int i, j, s = 0;

for (i = 0; i<nstr; i++)

for (j = 0; j<nstb; j++)s += a[i][j];

return s; }

 

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

Передача в функцию имен функций в качестве параметров

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

void f(int a){ /* тело функции */ } // определение функции

void (*pf)(int); // указатель на функцию

pf = &f: // указателю присваивается адрес функции

можно написать

pf = f;

или

void (*pf)(int)=f;

Функция f должна быть до этого определена или объявлена.

Вызов функции через указатель:



Поделиться:


Последнее изменение этой страницы: 2016-08-15; просмотров: 648; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.147.66.178 (0.139 с.)