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



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

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

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

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

Лекция 19

Многофайловые программы

 

Цели:

ü освоить методику разработки многофайловых программ; методику написания алгоритмов с использованием функций, перевода таких алгоритмов на язык программирования С++ и разработки соответствующего проекта в среде Visual C++ 6.0.

Создание многофайловой программы.

Создание и добавление головного файла в проект

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

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

Для реализации многофайловой программы в программный проект добавляют головные файлы, в которых описываются функции, затем при помощи директивы препроцессора #include эти файлы подключаются в основной программе, где описывается главная функция main().

Реализация многофайловой программы:

1. Создать проект программы.

2. Выбрать пункт меню Project à Add to Project à New (для создания нового головного файла) и Project à Add to Project à Files… (для подключения уже созданного головного файла). Среда Visual C++ 6.0 будет выглядеть следующим образом (рис.10).

3. Появится диалоговое окно, в котором будет предложено выбрать тип подключаемого файла (C / C ++ Header File) и, если это нужно, набрать имя создаваемого головного файла в поле File name. После этого нужно нажать на кнопку (рис.11).

4. Созданный головной файл будет находиться в той же папке, где и располагаются файлы проекта. В среде программирования файл можно найти на вкладке FileView, раскрыв папку Header Files (рис.12):

5. Открыв созданный головной файл, в окне редактора можно набрать текст функции. Для возможности использования созданной функции в своей программе необходимо в файле главной функции <имя_проекта>.cрр (на вкладке FileView окна рабочего пространства открыть папку Source Files) при помощи директивы #includе подключить созданный головной файл (рис.13).

 

Пример 1. Дан одномерный массив целых чисел. Вычислить сумму максимального и первого элементов массива.

 

Ход выполнения работы

1. Основной алгоритм решения задачи (декомпозиция) выглядит следующим образом:

1) ввод элементов массива;

2) нахождение максимального элемента;

3) вычисление суммы;

4) вывод элементов массива на печать.

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

ввод  элементов массива;

нахождение максимального элемента;

вывод элементов массива на печать.

Определим параметры и результаты работы каждой функции.

Ø Результатом работы первой функции должен быть сформированный массив. Этого можно достичь двумя способами: тип результата работы функции можно сделать указателем на тип элементов массива (1 вариант) или сделать формальным параметром указатель на тип элементов массива (2 вариант). Если в первом случае в теле функции будет необходимо использовать оператор return и будет возвращаться указатель на начало массива в ОП, то во втором случае этого не надо будет делать, поскольку в качестве фактического параметра будет передаваться адрес массива в ОП. При передаче указателя – фактического параметра в функцию произойдет заполнение ячеек ОП, которые будут отведены под массив. При этом их адреса не будут изменены. Еще одним параметром функции должна быть переменная, значение которой определяет количество вводимых переменных.

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

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

2. Создать проект (консольное приложение) с именем massive. В проекте создать и подключить два головных файла с именами vvod.h и comp.h. В первом файле описать функцию ввода и функцию вывода элементов одномерного массива, во втором – функцию, которая будет искать максимальный элемент.

3. Записать тексты функций в головные файлы. Для этого открыть в окне рабочего пространства вкладку FileView, затем открыть папку Header Files. В этой папке открыть файл vvod.h и записать в него тексты первых двух функций. В той же папке открыть файл comp.h и записать в него текст функции. Тексты программ, записанных в каждом файле:

файл vvod.h:

//функция ввода элементов массива: 1-ый вариант

int *vv(int a[], int n) //в качестве аргументов выступают одномерный

                             //массив и количество его элементов

{

int i;//локальная переменная – номер элемента массива. Область

     //видимости переменной – тело функции

for(i=0;i<=n-1;i++)

{

    cout<<"a["<<i<<"]=";

    cin>>a[i];

}

return a; //возвращаем адрес на начало массива в ОП

}

 

//функция ввода элементов массива: 2-ой вариант

void vv(int *a, int n)

{

int i;//локальная переменная – номер элемента массива.

     //Область видимости – тело функции

for(i=0;i<=n-1;i++)

{

    cout<<"a"<<i<<"=";

    cin>>*(a+i);

}

}

 

//функция вывода элементов массива

void viv(int *a, int n)

{

int i;//локальная переменная – номер элемента массива.

     //Область видимости – тело функции

for(i=0;i<=n-1;i++)

{

    cout<<"a"<<i<<"="<<*(a+i)<< "    ";

}

cout<<endl;

}

 

файл comp.h:

//функция поиска максимального элемента массива

int max(int *a, int n)

{

int i;//локальная переменная – номер элемента массива.

     //Область видимости – тело функции

int max_a;//локальная переменная – максимальный элемент массива.

            //Область видимости – тело функции

max_a=*a;

for(i=1;i<=n-1;i++)

{

    if(max_a<*(a+i))           

max_a=*(a+i);

}

return max_a;

}

4. Записать текст главной функции. Для этого открыть в окне рабочего пространства вкладку FileView и папку Source Files. В этой папке открыть файл massive.cpp и записать в него текст главной функции:

#include "stdio.h"

#include "stdlib.h"

#include "iostream.h"

#include "iomanip.h"

#include "vvod.h"

#include "comp.h"

int main()

{

int n, *p, s;

cout<<"Введите количество элементов массива n=";

cin>>n;

p=(int*)malloc(n*sizeof(int));

//введем элементы массива при помощи нашей функции 2-ой

//вариант

vv(p, n);

// при втором варианте описания этой функции был бы вызов:

// p=vv(p, n);

//вычислим сумму

s=*p+max(p, n);

//выведем значение суммы на экран

cout<<"summa="<<s<<endl;

//выведем элементы массива

viv(p, n);

free(p);

return 1;

}

 

Примечания:  

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

ü Как видно из примера имена фактических и формальных параметров не совпадают, что не влияет на правильность работы программы.

5. Откомпилировать файл massive.cpp. Запустить программу на выполнение.

 

Пример 2. Каждого студента можно описать при помощи характеристик: ФИО, курс, специальность, предмет1, предмет2, предмет3. Написать программу (с использованием файлов и функций), определяющую количество студентов:

1) сдавших сессию на «отлично»;

2) не сдавших хотя бы 1 экзамен.

 

Ход выполнения работы

1. Опишем основной алгоритм решения задачи (декомпозиция). Переменная var обозначает вариант работы:

var=-1

пока var!=0

ввод var

выбор var

случай 1: // создание и запись в файл

вызов функции создания и записи в файл

выход

случай 2: //открытие файла для добавления

вызов функции добавления данных в файл

выход

случай 3: // открытие файл в режиме чтения

//определение характеристик

вызов функции вычисления искомых величин

печать характеристик

выход

все_выбор

все_цикл

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

a). Функция создания и записи в файл. Функционально результат ее работы будет зависеть от имени файла, указателя на поток (файл) и режима открытия файла. Следовательно, эти величины и будут формальными параметрами этой функции. Данная функция будет возвращать результат – целое число 0 в случае неудачной попытки открытия файла и число 1, если функция отработала нормально.

b). Функция добавления данных в файл. Функционально результат ее работы будет зависеть от имени файла, указателя на поток (файл) и режима открытия файла. Следовательно, эти величины и будут формальными параметрами этой функции. Данная функция будет возвращать результат – целое число 0 в случае неудачной попытки открытия файла и число 1, если функция отработала нормально. Из п.п.a), b) следует, что можно написать одну функцию для реализации действий, описанных в этих двух пунктах.

c). Функция вычисления искомых величин. Функционально результат ее работы будет зависеть от имени файла, указателя на поток (файл) и режима открытия файла. Следовательно, эти величины и будут формальными параметрами этой функции. Данная функция будет возвращать результат – целое число 0 в случае неудачной попытки открытия файла и число 1, если функция отработала нормально. В список формальных параметров этой функции добавим еще два параметра: указатели на искомые величины (будем называть их характеристиками) – значения, расположенные по переданным адресам, будут изменены в процессе выполнения функции.

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

2. Создать проект (консольное приложение) с именем student. В проекте создать и подключить один головной файл с именем my_function.h. Открыть этот файл в окне рабочего пространства и описать в нем все функции. Тексты функций:

файл my_function.h:

//описание структурного типа

typedef struct

{

char fio[30];

int kurs;

char spec[30];

int hist;

int math;

int phis;

} stud;

//функция записи или добавления данных в файл

int write(char *name, FILE *pf, char *rezh)

{

stud *st;

int i;

long int n;

cout<<"n=";

cin>>n;

st=(stud*)malloc(n*sizeof(stud));

//заполнение массива структур

for(i=0;i<=n-1;i++)

{

cout<<"fio=";

cin>>((st+i)–>fio);

cout<<"kurs=";

cin>>((st+i)–>kurs);

cout<<"spec=";

cin>>((st+i)–>spec);

cout<<"history=";

cin>>((st+i)–>hist);

cout<<"math=";

cin>>((st+i)–>math);

cout<<"phis=";

cin>>((st+i)–>phis);

}

//файл pf с именем name открывается в режиме rezh

if((pf=fopen(name,rezh))==NULL)

{

printf("файл не открыт\n");

return 0;

}

//запись в файл

fwrite(st, sizeof(stud), n, pf);

fclose(pf);

free(st);

return 1;

}

//функция вычисления искомых величин

int read(char *name, FILE *pf, char *rezh, int *c_5, int *c_2)

{

stud *st;

int i;

long int n;

//локальные переменные cnt5 и cnt2 нужны для вычисления

//характеристик

int cnt5,cnt2;

// файл pf с именем name открывается в режиме rezh

if((pf=fopen(name,rezh))==NULL)

{

printf("файл не открыт\n"); return 0;

}

//определение длины файла в байтах

fseek(pf, 0, SEEK_END);

n=ftell(pf);

//определение количества записей в файле

n=n/sizeof(stud);

st=(stud*)malloc(n*sizeof(stud));

//возвращаем указатель в начало файла

rewind(pf);

//заполнение массива структур

fread(st, sizeof(stud), n, pf);

cnt5=cnt2=0;

for (i=0;i<=n-1;i++)

{ //определяем количество отличников

if((st+i)–>hist==5&&(st+i)–>math==5&&(st+i)–>phis==5)

cnt5++;

//определяем количество студентов, не сдавших хотя бы один

//экзамен

if((st+i)–>hist==2||(st+i)–>math==2||(st+i)–>phis==2)

cnt2++;

}

fclose(pf);

free(st);

//возвращение найденных характеристик в главную программу

//через указатели

*c_5=cnt5;

*c_2=cnt2;

return 1;

}

3. Записать текст главной функции. Открыть в окне рабочего пространства вкладку FileView и папку Source Files. В этой папке открыть файл student.cpp и записать в него текст главной функции:

#include "stdio.h"

#include "stdlib.h"

#include "iostream.h"

#include "iomanip.h"

#include "my_function.h"

int main()

{

FILE *pf;

int var, count_5, count_2, i;

char name_file[25];

//вводится имя файла, с которым предстоит работать

cout<<"name_file=";

cin>>name_file;

var=-1;

while(var!=0)

{

cout<<"0–выход; 1–запись; 2–добавление; 3–характеристики: "

<<"var=";

cin>>var;

switch(var)

{

case 1:

//записываем данные в файл

i=write(name_file, pf, "w");

break;

case 2:

cout<<"name_file=";

cin>>name_file;

//дозаписываем данные в файл

i=write(name_file, pf, "a");

break;

case 3:

cout<<"name_file=";

cin>>name_file;

//определяем характеристики

i=read(name_file, pf, "r", &count_5, &count_2);

if(i!=0)

{

cout<<"count_5="<<count_5<<endl;

cout<<"count_2="<<count_2<<endl;

}

break;

}

}

return 1;

}

 

Примечания:

1. В списке формальных параметров функции read() присутствуют два параметра-указателя. Это сделано для того, чтобы можно было при помощи функции возвращать не одну, а две величины (характеристики).  Таким образом, если в результате работы функции должно быть получено более одной результирующей величины, то одну из этих величин можно возвращать при помощи оператора return, а другие добавлять в список формальных параметров как указатели на искомые переменные. В этом случае напрямую использовать эти параметры в операторах тела функции (например, в выражениях) не рекомендуется. Целесообразнее использовать локальные переменные для вычисления характеристик (в нашем примере это cnt2 и cnt5). Затем, полученные значения этих переменных записать по адресам ОП, переданным в функцию через указатели – фактические параметры.

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

4. Откомпилировать файл massive.cpp. Запустить программу на выполнение.

Рекурсия

 

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

 

Пример. Вычислить сумму факториалов целых чисел, принадлежащих отрезку [m;n].

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

 

Ход выполнения работы

1. В файле f.h опишем рекурсивную функцию, вычисляющую факториал числа n:

int fact(int n)

{

if(n= =0 || n= =1) return 1;

else

    return n*fact(n-1);

}

Как видно, среди операторов тела функции происходит ее вызов, т.е. функция является рекурсивной.

В файле главной функции запишем основной алгоритм решения задачи на языке С++:

#include "stdio.h"

#include "stdlib.h"

#include "iostream.h"

#include "iomanip.h"

#include "f.h"

int main()

{

int i, n, m, s;

cout<<"Введите границы отрезка m=";

cin>>m;

cout<<"n=";

cin>>n;

if(m<n)

{

//вычислим сумму

s=0;

for(i=m; i<=n; i++)

//вызываем функцию fact(); ее аргумент изменяется в цикле

s=s+fact(i);

//выведем значение суммы на экран

cout<<"summa="<<s<<endl;

}

else

cout<<"Границы отрезка введены неверно"<<endl;

return 1;

}

 

Примечание. При вычислении 4! функция  fact() будет вызывать сама себя три раза следующим образом:

4*fact(3)

3*fact(2)

2*fact(1)

Как только значение параметра этой функции достигнет 1, результат ее работы будет вычисляться в обратном порядке:

2*1=2

3*2=6

4*6=24

Л екция 20


Дата добавления: 2019-11-25; просмотров: 190; Мы поможем в написании вашей работы!

Поделиться с друзьями:






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