Главная
Регистрация
Вход
Суббота
23.11.2024
05:28
Приветствую Вас, Гость | RSS

Меню сайта

Друзья сайта

Категории раздела
1. Основы работы в среде C++ Builder.Основы языка Си++ [9]
2. Операторы ветвления и операторы передачи управления [4]
3. Операторы цикла и операторы передачи управления [5]
4. Одномерные массивы [3]
5. Многомерные массивы [3]
6. Указатели.Динамическое распределение памяти [6]
7. Отладка программы [3]
8. Функции [8]
9. Строки [5]
10. Функция Main [2]
11. Типы данных,определяемые пользователем [5]
12. Потоки ввода/вывода [7]
13. Основы объектно-ориентированного программирования [5]
Приложения [11]

Форма входа

Наш опрос
Оцените мой сайт
Всего ответов: 446

Пользователи

Онлайн всего: 9
Гостей: 9
Пользователей: 0

 Каталог статей 
Главная » Статьи » Лекции по C++ » 8. Функции

PDA-версия страницы

3. Обмен информацией между функциями

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

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

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

#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 должна быть до этого определена или объявлена.

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

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

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

(*pf)(10)

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

// Описание типа PF как указателя

// на функцию с одним параметром типа int:

typedef void (*PF)(int):

// Описание и инициализация массива указателей:

PF menu[] = {&new, &open, &save};

menu[l](10); // Вызов функции open через указатель


Здесь new, open и save – имена функций, которые должны быть объявлены ранее.

Через указатель на функцию одна функция может вызвать другую.

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

#include <iostream.h>

typedef void (*PF)(int);

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

{

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

}

void f(int i ){cout << i ;}

int main()

{

fl(f)

return 0;

}

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

Прототип функции можно использовать в качестве параметра :

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

 

#pragma hdrstop

#include <iostream.h>

#include <conio.h>

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

 

#pragma argsused

 

void fl(void p( int ))// функция fl получает в качестве формального параметра прототип

{

p(5);  //вызов функции

}

void f(int i ){cout << i ;}

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

{

fl(f);    // вызов функции с фактическим параметром

getch();

return 0;

}


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


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

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

void fl(int, int = 100, char*  =0);  /* обратите внимание на пробел между * и

= (без него получилась бы операция сложного присваивания *=) */

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


Варианты вызова функций:

f(100);  // по умолчанию b=0

f(a, 1): // b явно присвоено значение 1

fl(a); // первому формальному параметру присвоено значение переменной a,

         // остальным по умолчанию

fl(a, 10); // первому формальному параметру присвоено значение переменной a,

          //второму 10, третьему по умолчанию

fl(a, 10, "Vasia"):  // всем формальным параметрам явно присвоены значения

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


 
Функции с переменным числом параметров

 

Если список формальных параметров функции заканчивается многоточием, это означает, что при ее вызове на этом месте можно указать еще несколько параметров. Проверка соответствия типов для этих параметров не выполняется, char и short передаются как int, a float – как double. В качестве примера можно привести функцию printf, прототип которой имеет вид:

int printf (const char*, ...):

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

printf ("Введите исходные данные"): // один параметр

printf("Cyммa: %5.2f рублей", sum): // два параметра

printf("%d %d %d %d" , a, b, c, d); // пять параметров


Для доступа к необязательным параметрам внутри функции используются макросы библиотеки va_start, va_arg и va_end, находящиеся в заголовочном файле <stdarg.h>.

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

Если необходимо вывести только один результат целесообразно воспользоваться оператором return.

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

#pragma hdrstop

#include <iostream.h>

#include <conio.h>

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

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

#pragma argsused

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

{

int i = 1, j = 2, k = 3,p=0,prt=0;

cout <<"i  j  k\n";

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

f(i, &j, k, p, prt);

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

cout <<"\ni+j+k="<<p;

cout <<"\ni*k="<<prt;

cout <<"\nPress and key...";

getch();

return 0;

}

void f(int i, int* j, int& k,int& s,int& pr)

{i++; (*j)++; k++;s=i+(*j)+k,pr=i*k;}


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

i  j  k

1 2 3

1 3 4

i+j+k=9

i*k=8

Категория: 8. Функции | Добавил: Admin (01.09.2011)
Просмотров: 14471 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]

Помочь сайту!
рублей WMR
WebMoney на кошелек R407507742179

Партнёры сайта

Закладки

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

Copyright H-L-L.RU © 2024
Сделать бесплатный сайт с uCoz