Студопедия
Случайная страница | ТОМ-1 | ТОМ-2 | ТОМ-3
АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатика
ИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханика
ОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторика
СоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансы
ХимияЧерчениеЭкологияЭкономикаЭлектроника

Пример 7.1. Передача в функцию параметров стандартных типов

Читайте также:
  1. I. 1.1. Пример разработки модели задачи технического контроля.
  2. I. Передача параметров запроса методом GET.
  3. II. Учет накладных расходов на примере ТОО «Тепломонолит».
  4. III. Схематическое изображение накопления - второй пример
  5. IP адресация. Правила использования адресов. Маски переменной длины. Пример разбиения на подсети с маской переменной длины.
  6. SWOT- анализ на примере ветеринарной аптечной сети.
  7. Алгоритм установки ОС на примере ОС Debian 6.0.

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

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

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

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

 

double cosh(double x, double eps);

 

Для вычисления суммы ряда понадобят­ся две промежуточные переменные - для хранения очередного члена ряда и его номера. Эти переменные должны быть описаны внутри функции, поскольку вне ее они не нужны.

 

include <stdio.h>

#include <math.h>

double cosh(double x, double eps); //прототип ф-ции

int main()

{

double Xn, Xk, dX, Eps;

printf("Enter Xn, Xk, dX, eps \n");

scanf("%lf%lf%lf%lf.&Xn, &Xk, &dX, &eps);

printf (................................ \n");

printf(| X | Y |\n");

printf(.............................\n");

for (double x = Xn; x <= Xk; x += dX)

printf(“|%9.2]f |%14.6g |\n”\ x. cosh(x. eps));

printf("...............................\n);

return 0;

}

double cosh(double x. double eps)

{

const int Maxlter - 500; /* максимальное количество итераций */

double ch = 1. у = ch; /* первый член ряда и нач. значение суммы */

for (Int n = 0; fabs(ch) > eps; n++)

{

ch *- x * x /((2 * n + 1)*(2 * n + 2)); // член ряда

у += ch; // добавление члена ряда к сумме

if (n > Maxlter)

{

puts(“ Ряд расходится!\n");

return 0;

}

}

return у;

}

 

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

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

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

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

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

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

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

 

Листинг 7.1

 

# include <stdio.h>

# include <math.h>

 

Double cosh(double x, double eps, int &err);

int main()

{

double Xn, Xk, dX, Eps, y;

int err;

 

printf("Enter Xn, Xk, dX, eps \n");

scanf("%lf%lf%lf%lf.&Xn, &Xk, &dX, &eps);

printf (................................ \n");

printf(| X | Y |\n");

printf(.............................\n");

for (double x = Xn; x <= Xk; x += dX)

{

y = cosh(x, eps, err);

if (err) printf(“|%9.2]|Рядрасходится!|\n”,);

printf(“|%9.2]f |%14.6g |\n”\ x, y);

}

printf("...............................\n);

return 0;

}

 

double cosh(double x, double eps, int err)

{

err = 0;

const int Maxlter = 500;

double ch - 1. у - ch;

for (int n - 0: fabs(ch) > eps; n++) (ch *- x * x /((2 * n + l)*(2 * n + 2));

у +- ch;

if (n > Maxlter);

{

err – 1;

return 0;

}

}

 

for(double x=Xn; x<-Xk; x+-dX)

{

у'cosh(x.eps. err);

if (err) prmtf("|X9.21f|Ряд расходится!|\пи.x);

else printf(MH!9.21f |*14.6g |\n\ x. y);

}

printfC................................ \n");

return 0;

 

double cosh(double x. double eps.int &err)

{

err - 0;

const int Maxlter - 500;

double ch = 1. у = ch;

 

for (int n = 0: fabs(ch) > eps; n++)

{

ch *= x * x /((2 * n + l)*(2 * n + 2));

у += ch;

if (n > Maxlter),

{

err – 1;

return 0;

}

}

return y;

}

 

Недостатком этого метода является увеличение количества параметров функции. Знак & перед параметром егг - это признак переда­чи параметра по ссылке. Такой способ позволяет передавать значения из функции в вызывающую программу.

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

 

у = cosh(x + 0.2. eps / 100. err);

 

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

Ссылка, синтаксически являясь синонимом имени некоторого объекта, в то же время содержит его адрес. Поэтому ссылку, в отличие от указателя, не требуется разадресовывать для получения значения объекта. Если мы передаем в функцию ссылку, то есть пишем в списке параметров выражение вида double Seps, а при вы­зове подставляем на его место аргумент, например eps fact, мы тем самым переда­ем в функцию адрес переменной eps fact. Этот адрес обрабатывается так же, как и остальные параметры: в стеке создается его копия. Функция, работая с копией ад­реса, имеет доступ к ячейке памяти, в которой хранится значение переменной eps fact, и тем самым может его изменить.

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

 

// прототип функции;

double cosh(double х, double eps, int * err);

- // вызов функции;

у = cosh(x, eps, &еrr); // & - взятие адреса

// обращение к еrr внутри функции;

*еrr = 0; // * - разадресация

 

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

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

Для нашей программы передача вхлдных данных по константной ссылке выглядит так:

 

// прототип функиии;

double cosh(const double &x. const double &eps. int &err);

- // вызов функции;

 

у = cosh(x, eps, err); /* обращение к x и eps внутри функции не изменяется */

 

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

Еще один способ сообщения об ошибках внутри функции - написать функцию так, чтобы параметр ошибки передавался через возвращаемое значение. Это применяется в основном для функций вывода инфор­мации. Например, функция стандартной библиотеки

 

int fputcdnt ch. FILE *f);

 

записывает символ ch в поток f. При ошибке она возвращает значение EOF, иначе - записанный символ. В этом случае при необходимости передать в точку вызова * какие-либо другие результаты работы функции их передают через список пара­метров.

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

Генерация исключения. Воспользуемся средством C++, называемым значениями параметров по умолчанию. Может оказаться неудоб­ным каждый раз при вызове функции cosh задавать требуемую точность вычисле­ния суммы ряда. Конечно, можно определить точность в виде константы внутри функции, задав максимальное допустимое значение, но иногда это может оказать­ся излишним, поэтому желательно сохранить возможность задания точности че­рез параметры. Для этого либо в определении (если оно находится выше по тексту, чем любой вызов функции), либо в прототипе функции после имени параметра указывается его значение по умолчанию, например:

 

double cosh(double x. double eps - DBL EPSILON);

 

DBL EPSILON - это константа, определенная в файле <float.h>. Ее значение равно минимальному числу, которое, будучи при­бавлено к единице, даст не равный единице результат. Теперь нашу функцию можно вызывать с одним параметром, к примеру:

 

у - cosh(x);

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

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

 

double cosh(const double x.int & err. const double eps = DBL EPSILON);

 

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

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

Пример 7.2. Передача в функцию имени функции

 

Назовем функцию вывода таблицы значений print tabl. Прежде всего надо опре­делить ее интерфейс. Для того чтобы вывести таблицу, нашей функции потребует­ся знать диапазон и шаг изменения значений аргумента, а также какую, собственно, функцию мы собираемся вычислять. В функцию вычисления суммы ряда надо передавать точность, поэтому точность следует включить в список параметров вызывающей ее функции printtabl. Функция print tabl не возвращает никакого значения, то есть перед ее именем надо указать void.

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

 

double (*fun)(double, double);

 

Здесь описывается указатель по имени fun на функцию, получающую два аргу­мента типа doubl e и возвращающую значение того же типа. Часто, если описание типа сложное, с целью улучшения читаемости программы задают для него синоним с помощью ключевого слова typedef:

 

typedef double (*Pfun)(double, double);

 

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

 

void print_tabl(Pfun fun. double Xn, double Xk. double dX. double eps);

 

Запишем теперь текст программы, сведя к минимуму диагностику ошибок (при превышении максимально допустимого количества итераций функция заверша­ется, возвращая 0, а вызывающая программа выводит это значение):

 

Листинг 7.2

 

#include <std1o.h>

#include <math.h>

 

typedef double (*Pfun)(const double, const double);

void print_tabl(Pfun fun, const double Xn, const double Xk, const double eps);

double cosh(const double x, const double eps);

 

int main()

{

double Xn, Xk, dX, eps;

prlntf(Enter Xn, Xk, dX, eps \n");

scanf("%lf%lf%lf%lf.” &Xn, &Xk, &dX, &eps);

print tabl(cosh, Xn, Xk, dX, eps);

return 0;

}

void print_tabl(Pfun fun, const double Xn, const double Xk, const double dX, const double eps)

{

pnintf("..............................\n");

printf("| X | Y |\n");

printfC................................ \n");

for (double x = Xn; x <=Xk; x += dX);

printf("|%9.2lf | %14.6g | \n”\,x,fun(x, eps));

printf("................................\n);

}

 

double cosh(const double x, const double eps)

{

const int Maxlter = 500;

double ch = 1, у = ch;

 

for (int n = 0; fabs(ch) > eps; n++)

{

ch *= x * x /(2 * n + l)/(2 * n * 2);

у += ch;

if (n> Maxlter) return 0;

}

return y;

}

 

Функция print tabl предназначена для вывода таблицы значений любой функ­ции, принимающей два аргумента типа double и возвращающей значение того же типа.

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

Пример 7.3. Передача одномерных массивов в функцию

 

Даны два массива из n целых чисел каждый. Определить, в каком из них больше по­ложительных элементов.

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

 

int n posit(const int *a, const int n);

 

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

 

Листинг 7.3

 

#include <iostream.h>

 

int n posit(const int *a, const int n);

int main()

{

int I, n;

cout «"Введите количество элементов:”:cin» n;

 

int *a = new int[n];

int*b = new int[n];

cout «"Введите элементы первого массива: ";

 

for (i = 0; i< n; i++) cin» a[i];

cout «"Введите элементы второго массива: ";

 

for (i = 0; i< n; i++) cin» b[i];

if (n posit(a, n) > n posit(b, n))cout «" В первом положительных больше” «endl;

else if(n posit(a, n) < n_posit(b, n)) cout «" Во втором положительных больше" «endl;

else cout «" Одинаковое количество" «endl;

 

return 0;

 

int n posit(const int *a, const int n)

{

int count = 0;

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

if (a[i] > 0) count++;

return count;

}

}

 

В этой программе место под массивы выделяется в динамической области памяти, поскольку в задании не указано конкретное количество элементов. Однако функ­цию nposit можно без изменений применять и для «обычных» массивов, потому что для каждого из них имя тоже является указателем на нулевой элемент, только константным. Например, опишем массив из 10 элементов и инициализируем пер­вые шесть из них (оставшимся будут присвоены нулевые значения):

 

int х[10] = {2, 3, -1, -10, 4, -2};

cout «n_posit(x, 10); //будет выведено значение 3

 

Рассмотрим способ анализа результатов работы функции. Функция вызывается в составе выражения в условном операторе. Для пере­бора всех трех вариантов результата приходится вызывать ее для каждого массива дважды, что для больших массивов, конечно, нерационально. Чтобы избежать по­вторного вызова, можно завести две переменные, в которые записываются резуль­таты обработки обоих массивов, а затем использовать эти переменные в условных операторах:

 

int n posit a = n posit(a. n), n posit b = n posit(b, n);

if (n posit a > n posit b) cout «" В первом положительных больше" «endl;

else if (n posit a < n posit b) cout «" Во втором положительных больше" «endl;

else cout «" Одинаковое количество" «endl;

 

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

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

 

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

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

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

 

Листинг 7.4

 

#include <fstream.h>

#include <ctype.h>

 

int num num(const char *str);

 

int main()

{

ifstream fth("test.txt".ios::in|ios::nocreate);

if (!fin)

{

cout «"Нет файла test.txt" «endl;

return 0;

}

 

const int len = 101;

int i = 1;

char str[len];

while (fin.getline(str. len))

{

cout «"В строке “«1 «" содержится " «num num(str) «" чисел " «endl;

i++;

}

return 0;

}

 

int num num(const char *str)

{

int count = 0;

while (*str)

{

if (isdigit(*str) &&! isdigit(*(str + 1))) && *(str + 1)!= '.') count++;

str++;

}

return cout;

}

}

 

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

Пример 7.5. Передача двумерных массивов в функцию

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

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

 

Листинг 7.5

 

#include <fstream.h>

int ser equals(int **a, const int m, const int n);

int main()

{

if stream fin ("matrix.txt", los :: in | ios:: nocreate);

if (!fin)

{

cout «"Нет файла matrix.txt" «endl;

return 0;

}

 

int m, n, 1,j;

fin» m» n;

int **a = new int *[m]; // выделение памяти

 

for (d = 0;i < m; i++)

a[i] = new int [n];

for ( i = 0;i < m; i++) // ввод нассива

for (j = 0; j < n) fin» a[i][j];

int line = ser_equals(a, m, n); // вызовфункции

if (line >= 0)cout «" Самая длинная серия в строке " «line;

else cout «" Серий одинаковых элементов нет ";

return 0;

}

int ser_equals ( int **а, const int m, const int n)

{

int I,j, count, line = -1, maxcount = 0;

for (1 - 0; 1< m; 1++)

{

count = 0;

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

{

if (a[i][j] == a[l][j + 1])

else

{

if (count > maxcount)

{

maxcount = count;

line = i;

}

count = 0;

}

}

if (count > maxcount)

{

maxcount = count;

line = i;

}

}

}

return line;

 

Алгоритм работы функции прост: в каждой строке выполняется сравнение сосед­них элементов (оператор 2). Если они равны, мы находимся внутри серии, при этом увеличиваем ее текущую длину. Она накапливается в переменной count, ко­торая обнуляется перед обработкой каждой строки (оператор 1). Если же элемен­ты не равны, это означает либо окончание серии, либо просто одиночный элемент (оператор 3). В этом случае надо посмотреть, не является ли данная серия самой длинной из рассмотренных и, если да, то запомнить ее длину и номер строки, в ко­торой она встретилась (оператор 4). Для подготовки к анализу следующих серий в этой же строке надо обнулить счетчик count. Аналогичная проверка после цикла просмотра строки (оператор 5) выполняется для серии, которая расположена в конце строки, поскольку в этом случае ветвь else выполняться не будет.

Если в массиве нет ни одной серии одинаковых элементов, функция вернет значе­ние, равное -1.

Пример 7.6. Передача структур в функцию

 

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

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

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

 

Листинг 7.6

 

#include <stdio.h>

#include <string.h>

#include <stdlib.h> //#include <windows.h>

 

const int l name = 30;

char name[l_name + 1];

int birth year;

float pay;

 

Man read data();

int append2binfilet (const Man &man, const char* filename);

int print from bin(const char * filename);

 

int main()

{

bool contin;

char y n[2];

char filename[] = "dbase.bin";

do

{

contin = false;

if (append2binfile(read data(), filename)!= 0)

{

puts(" Ошибка при записи в файл ");

return 0;

}

puts(" Продолжить (у/n)?”);

gets(y i);

if ((y n[0] == 'у')||(у n[0] == 'Y'))contin = true;

}

while (contin); print from bin(fi1ename);

return 0;

}

int append2binfile(const Man &man, const char* filename)

{

FILE *fout;

if ((fout-fopen(filename, "ab"))NULL) return 1;

 

int success = fwrite(&man, sizeof(man), 1, fout), fclose(fout);

if (success == 1) return 0;

else return 2;

}

 

int print from_bin(const char * filename)

{

int num; Man man; FILE *f;

if ((f = fopenCfilename, "rb")) == NULL) return 1;

fseef((f, 0, SEEKEND);

int n record - ftell(f) / sizeof (man);

while (true)

{

puts("Введите номер записи или -1; ");

scanf("&i", &num);

if (num < 0 || num >= n record) break;

fseek(f, num * sizeof(man), SEEK SET);

fread(&man, sizeof(man), 1, f);

// CharToOem(man,name, man,name);

printf("%30s%5i%10,2f\n, man.name, man.birth year. man.pay);

return 0;

}

 

Man read_data()

{

Man man;

char buf[80];

char name[l_name + 1];

puts("Введите фамилию И.О. ");

gets(name);

if (strlen(name) < 1_name)

for (int i = strlen(name); i < l name; i++)

name[l_name] = 0;

// OemToChar(name. name);

strncpy(man.name. name. l_ name + 1);

do

{

puts("Введите год рождения “);

gets(buf)

while ((man.birth_year = atoi(buf)) ==0);

do

{

puts(“Bвeдитe оклад ");

gets(buf);

}

while (!(man.pay =atof(buf)));

return man;

}

}

}

 

В функции ввода read data предусмотрено заполнение пробелами оставшейся час­ти строковой переменной паше, чтобы формат имени был идентичен формату вво­да в текстовом файле.

Следует обратить внимание на то, как в этой функции выполняется проверка правильно­сти ввода числовой информации. Чтение выполняется в буферную строку, кото­рая затем преобразуется с помощью функций atoi () и atof () в числа. Если функ­ции возвращают 0, преобразование выполнить не удалось (например, вместо цифр были введены буквы), и информация запрашивается повторно. Условие повторе­ния циклов 3 и 4 записано в двух разных вариантах, чтобы вы сами могли оценить, какой из них вам более понятен (профессионалы предпочли бы второй, более ла­коничный вариант).

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

Пример 7.7. Рекурсивные функции

 

Написать программу упорядочивания массива методом быстрой сортировки, ис­пользуя рекурсию.

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

Одна из возможных версий программы сортировки приведена ниже.

 

Листинг 7.7

 

include <iostream.h>

void qsort(float* array, intleft, int right);

 

int main()

{

const int n = 10;

float arr[n];

int 1, 1, r;

cout «"введите элементы массива:”;

for (i = 0; i < n; i++) cin» arr[i];

i = 0;r = n - 1; /* левая и правая границы начального фрагмента*/

qsort(arr, 1, r); // 1

for (i = 0; i < n; i++) cout «arr[i] «‘ ‘;

return 0;

}

 

void qsort(float* array, int left, int right)

{

int i = left, j = right;

float middle = array[(left + right) / 2];

float temp;

while (i < j)

{

while (array[i] < middle) i++;

while (middle < array[j]) j--;

if (i <= j)

{

temp = array[i];

array[i] = array[j];

array[j] = temp;

i++;

j--;

}

}

if (left < j) qsort(array, left, j);

if (i < right) qsort(array, I, right);

}

 

Процедура разделения реализована здесь в виде рекурсивно вызываемой функции qsort(), в теле которой есть два обраще­ния к самой себе: в операторе 2 - для сортировки левой половинки текущего фраг­мента, и в операторе 3 - для сортировки его правой половинки.

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

Пример 7.8. Многофайловый проект - форматирование текста

 

Написать программу форматирования текста, читаемого из файла unformt.txt и состоящего из строк ограниченной длины. Слова в строке разделены произвольным количеством пробелов. Программа должна читать входной файл по строкам, фор­матировать каждую строку и выводить результат в выходной файл formatd-.txt. Форматирование заключается в выравнивании границ текста слева и справа пу­тем равномерного распределения пробелов между соседними словами, а также в отступе с левой стороны страницы на margin позиций, то есть результирующий текст должен находиться в позициях margin + 1.. margin + maxljine. Кроме этого, программа должна подсчитать общее количество слов в тексте.

Алгоритм решения задачи:

1. Открыть входной файл.

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

3. Для каждой строки 11 пе выполнить следующие действия:

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

- Вывести каждое слово из строки 1 i пе в выходной файл, вставляя между сло­вами необходимое количество пробелов и одновременно увеличивая счет­чик слов на единицу.

4. После обработки последней строки входного файла вывести на экран значение счетчика слов и закрыть выходной файл.

Разбиение на подзадачи.

В результате детализации описанного алгоритма определяем спецификации нуж­ных нам функций:

 

J void Deflnter (const char* pline, int & base int. int & add int. int & inter)

 

определяет для строки, на которую указывает pline, количество межсловных промежутков inter, требуемую величину основного интервала base int для каж­дого промежутка (количество пробелов) и величину дополнительного интер­вала add int, определяемую как остаток от деления общего количества пробе­лов в строке на количество межсловных промежутков; последняя величина должна быть равномерно распределена путем добавления одного пробела в каж­дый из первых add i nt промежутков;

 

void GetLine (FILE* finp. char* pline)

 

читает очередную строку из входного файла в массив символов с адресом pi ine, ликвидируя при этом пробелы в на­чале строки;

 

void Putlnterval (FILE* fout. const int k)

 

выводит очередной интервал, состо­ящий из к пробелов;

 

int PutWord (FILE*fout,const char*pline.const int startpos)

 

выводит очеред­ное слово в выходной файл, начиная с позиции startpos текущей строки pi ine; возвращает номер позиции в строке pi i ne, следующей за последним передан­ным символом, или 0 - если достигнут конец строки;

 

int SearchNextWord'(const char*pline.const int curpos)

 

возвращает номер пози­ции, с которой начинается следующее слово в строке pi 1 пе, или 0, если достиг­нут конец строки (поиск начинается с позиции curpos).

Разбиение на модули.

Наша программа будет располагаться в двух исходных файлах: task7_7.cpp - с функцией main, edit. cpp - с реализацией перечисленных выше функций, а также заголовочный файл edit.h с интерфейсом этих функций. Ниже приводится содержимое этих файлов.

 

Листинг 7.8

 

///////////////////////////////////////////////////

// Файл Task7_7.cpp

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include "edit.h"

 

// Глобальные переменные

const int maxljine = 63;

const int margin = 5;

 

int main()

{

FILE* finp;

FILE* tout;

char line[maxl_line + 1];

int b i, a i, start, next, inter;

int nword = 0;

printf("Работает программа Task7 7.\n");

if(!(finp = fopen("unformt.txt",”W”)))

{

printf("Файл unformt.txt не найден.\n”);

exit(0);

}

printf("Читается файл unformt.txt \n");

if(!(fout = fopen(“formatd.txt", "w")))

{

printf("Фaйл formatd.txt не создан.\n”);

exit(O);

}

printf("Выполняется запись в файл formatd.txt.\n");

while(GetLine(finp. line))

{

DefInter (line, b i, a_i, inter);

PutInterval(fout, margin);

next = PutWord(fout, line, start, nword);

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

{

start = SearchNextWord(line. next);

Putlnterval(fout.. b i);

if (a j) { a i--; Putlnterval(fout. 1);

}

next = PutWord(fout. line, start, nword);

if (!next) break;

}

fprintf(fout. “\n");

}

printf (“\nКоличество слов - %d\n”, nword);

fclose(fout);

printf("Работа завершена \n");

return 0;

///////////////////////////////////////////////////

// Файл Edit.h

// Прототипы функций

void DefInter(const char* pline, Int& base int, int& add int,

int& inter);

int GetLine(FILE*. char*);

void Putlnterval(FILE*. const int);

int PutWord(FILE*. const char*, const int. int&);

int SearchNextWord(const char*, const int);

 

// Глобальные переменные

extern const int maxl line;

///////////////////////////////////////////////////

// Файл Edit.cpp

#include <stdio.h>

#include <string.h>

#include "edlt.h"

 

int GetLine(FILE* finp. char* pline)

{

int i = 0;

char c;

while ((c = fgetc(finp)) == ' ') i++;

if(c == EOF) return 0;

fseek(finp. -1, SEEK CUR);

fgets(pline. Maxl line -i + 1, finp);

pline[strlen(pline) - 1] = 0;

return 1;

}

int SearchNextWord(const char* pline, const int curpos)

{

int i = curpos;

while(pline[i]!='')

{

if (pline[i] ==;\n”) return 0:

i++;

}

while (pline[i] == ’ ‘&& pline[i + 1] == ' ') i++;

return i + 1;

}

 

void DefInter(const char* pline. int& base int. int& add int. int& inter)

{

int к = 0, end;

end = strlen(pline) - 1;

while ((pline[end] == ' ') || (pline[end] == ’\n') || (pline[end] == '\r')) end--;

inter = 0;

 

for (unsigned int i= 0; i < end; i++)

{

if (pline[i] == ' ')

{

if (pline[i + 1]!= ' ') inter++;

}

}

int blank amount * к + maxl line - end;

if (!k)

{

base int = 0;

add int = 0;

}

else

{

base int = blank amount / inter;

addjnt = blank amount % inter;

}

return;

}

 

int PutWord (FILE* fout, const char* pline, const int startpos, int& n)

{

int i = startpos;

char c;

n++;

while ((c - pline[i++])!=' ‘)

{

fprintf(fout, "%c", c);

if ((c = '\n'> || (c == '\0'))

{

i = 0;

break;

}

}

return i - 1;

void Putlnterval(FILE* fout, const int k)

{

for (int i=0; i<k; i++) fprintf(fout, " );

return;

}

}

///////////////////////////////////////////////////

 

Имена функций мы здесь записали (для разнообразия) в стиле Microsoft, с использованием прописных букв для выделения осмысленных частей имени. Константу maxl 1 пе следует задавать большей, чем максимальная длина строки исходного файла. В качестве самостоятельного упражнения измените про­грамму так, чтобы можно было форматировать текст и в более узкую колонку, чем в исходном файле.

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

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

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

 

void print(char* str. const int i. const int j)

{

cout «str «' I' «oct «setw(4) «i «' |' «setw(4) «j «' |’ «endl;

}

 

void print(float mas[], const int n)

{

cout «"Массив:" «endl;

cout.setf(ios::fixed);

cout.precision(2);

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

{

cout «mas[i] «" ";

if ((i + 1) % 4 == 0) cout «endl;

}

cout «endl;

}

 

void print(Man m)

{

cout.setf(i os::fixed);

cout.precision(2);

cout «setw(40) «m.name «' ‘ «m.birth year <<' ‘<< m.pay «endl;

}

 

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

Во второй функции для вывода вещественных значений по четыре числа на строке задается вид вывода с фиксированной точкой и точностью в два десятичных знака после запятой. Для этого используются методы установки флагов setf, установки точности precision и константа f1xed, определенная в классе 1os. Точность касается только вещественных чисел, ее действие продолжается до следующей установки.

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

 

print("После цикла ", 1, п);

print(a. n);

print(m);

 

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

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

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

Неоднозначность может также возникнуть из-за параметров по умолчанию и ссы­лок. Рассмотрим создание перегруженных функций на примере.


Дата добавления: 2015-10-26; просмотров: 451 | Нарушение авторских прав


Читайте в этой же книге: Операнд_1 ? операнд_2 : операнд_3 2 страница | Операнд_1 ? операнд_2 : операнд_3 3 страница | Операнд_1 ? операнд_2 : операнд_3 4 страница | Операнд_1 ? операнд_2 : операнд_3 5 страница | Операнд_1 ? операнд_2 : операнд_3 6 страница | Операнд_1 ? операнд_2 : операнд_3 7 страница | Операнд_1 ? операнд_2 : операнд_3 8 страница | Пример 6.1. Среднее арифметическое и количество положительных элементов | Пример 6.2. Номер столбца из положительных элементов | Пример 6.4. Сглаживание заданной вещественной матрицы, работа с файлами |
<== предыдущая страница | следующая страница ==>
Пример 6.5. Определение количества отрицательных элементов в тех строках данной целочисленной прямоугольной матрицы, которые содержат хотя бы один нулевой элемент| Пример 7.9. Перегрузка функций

mybiblioteka.su - 2015-2024 год. (0.181 сек.)