Сделай Сам Свою Работу на 5

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





 

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

<тип функции> < имя функции> (<спецификация явных параметров>,…); .

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

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



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



#include <iostream.h>

void main()

{

long summa (int,...); //Прототип функции

cout<<"\n summa(2,4,6)="<<summa (2,4,6);

cout<<"\n summa(6,1,2,3,4,5,6)="

<<summa (6,1,2,3,4,5,6);

}

long summa (int k,...) //Передаем количество параметров

{

int *pk=&k;

long sm=0;

for (;k;k--)

sm+=*(++pk);

return sm;

}

Перепишем программу, используя предопределенное значение (пусть это будет 0).

#include <iostream.h>

long summa (int k,...)

{

int *pk=&k;

long sm=0;

for (;*pk;)

sm+=*(pk++);

return sm;

}

void main() //Прототип функции sum отсутствует

{

cout<<"\n summa(4,6,0)="<<summa (4,6,0);

cout<<"\n summa(1,2,3,4,5,6,0)="

<<summa (1,2,3,4,5,6,0);

cout<<"\n summa(1,2,0,4,5,6,0)="

<<summa (1,2,0,4,5,6,0);

}

В последнем случае на экран будет выведено число 3, так как суммируются только числа 1 и 2.

 

Рекурсивные функции

 

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

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



Пример: составим функцию вычисления факториала числа.

long factorial (int k)

{

if (k<0) return 0;

if (k==0) return 1;

return k*factorial(k-1);

}

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

 

Подставляемые (инлайн-) функции

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

inline float module(float x = 0, float у = 0)

{ return sqrt(x * x + у * у); }

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

Перечислим причины, по которым функция со спецификаторомinline будет трактоваться как обычная не подставляемая:

· встраиваемая функция велика;

· встраиваемая функция рекурсивна;

· обращение к встраиваемой функции в программе размещено до ее определения;

· встраиваемая функция используется в выражении более одного раза;

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

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

 

Функции и массивы

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

При передаче массивов через механизм параметров возникает задача определения в теле функции количества элементов массива, использованного в качестве фактического параметра. При работе со строками, то есть с массивами типа char[], последний элемент каждого из которых имеет значение '\0', анализируется каждый элемент, пока не встретится символ '\0', и это считается концом строки-массива.

Пример 1: проиллюстрируем передачу строк, описанных по-разному, в функцию.

#include <iostream.h>

int len(char e[]) //Функция вычисления

{ //длины строки

int m=0;

while (e[m++]);

return m-1;

}

 

void main()

{

char *E=”Пример строки”;

char F[ ]=”Еще пример строки”;

char *t;

t=new char[80];

t=”Последний пример строки”;

cout<< “\nДлина строки \” ”<<E<<”\” равна “<<len(E);

cout<< “\nДлина строки \” ”<<F<<”\” равна “<<len(F);

cout<< “\nДлина строки \” ”<<F<<”\” равна “<<len(t);

delete t;

}

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

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

 

Пример 2: составить массив, каждый элемент которого равен максимальному из соответствующих значений двух других массивов.

#include <iostream.h>

void max_vect(int n, int *x, int *y, int *z)

{

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

z[i]=x[i]>y[i]?x[i]:y[i];

}

 

void main()

{

int a[]={1,2,3,4,5,6,7};

int b[]={7,6,5,4,3,2,1};

int c[7];

max_vect(7,a,b,c);

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

cout<<”\t”<<c[i];

}

Заголовок функции max_vect можно записать по-другому, учитывая тот факт, что имя массива – это указатель на начальный элемент массива: void max_vect(int n, int x[], int y[], int z[]).

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

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

int *max_vect(int n, int *x, int *y)

{

int *z = new int[n]; //Выделение памяти для

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

for (int i=0;i<n;i++) //Расчет элементов массива

z[i]=x[i]>y[i]?x[i]:y[i];

return z;

}

 

void main()

{

int a[]={1,2,3,4,5,6,7};

int b[]={7,6,5,4,3,2,1};

int kc=sizeof(a)/sizeof(a[0]); //Количество элементов

//массива

int *c; //Указатель на результирующий массив

c=max_vect(kc,a,b); //Создание массива

for (int i=0;i<kc;i++) //Вывод результата

cout<<"\t"<<c[i];

delete []c; //Возврат памяти в “кучу”

}

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

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

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

Пример 4: составить программу транспонирования квадратной матрицы.

#include <iostream.h>

#include <stdlib.h>

void main()

{

void print (int,int*); //Прототип функции печати матрицы

void trans (int,int*);//Прототип функции транспонирования

int n;

cout<<"\nЗадайте размерность таблицы: ";

cin>>n;

int *a= new int[n*n]; //Резервируем место под элементы таблицы

cout<<"\nДана таблица: \n";

randomize(); //Используем датчик

for (int i=0;i<n;i++) //псевдослучайных чисел

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

*(a+i*n +j)=10+random(50);

print (n,a); //Печать исходной таблицы

trans(n,a); //Транспонирование матрицы

cout<<"\nТаблица -результат: \n";

print (n,a) ; //Печать результирующей таблицы

delete a; //Возвращение памяти в “кучу”

}

 

void print (int x, int *b) //Функция печати таблицы

{

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

{

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

cout<<"\t"<<*(b+x*i+j);

cout<<"\n";

}

}

 

void trans (int x, int *b) //Функция транспонирования матрицы

{

int c;

for (int i=0;i<x-1;i++)

for (int j=i+1;j<x;j++)

{

c= *(b+i*x+j);

*(b+i*x+j)=*(b+j*x+i);

*(b+j*x+i) = c;

}

}

Многомерный массив с переменными размерами, сформирован­ный в функции, непосредственно невозможно вернуть в вызывающую программу как результат выполнения функции. Однако возвра­щаемым функцией значением может быть указатель на одномерный массив указателей на одномерные массивы с элементами известной размерности и заданного типа. В следующей программе функция single_matr() возвращает именно такой указатель, так как имеет тип int **. В тексте функции формируется набор одномерных массивов с элементами типаint и создается массив указателей на эти одно­мерные массивы. Количество одномерных массивов и их длины определяются значением параметра функции, описанного как int n. Совокупность создаваемых динамических массивов пред­ставляет квадратную матрицу порядка n. Диагональным элементам матрицы присваиваются единичные значения, остальным - нулевые, то есть создается единичная матрица. Локализован­ный в функции single_matr() указатель int** p "настраивается" на создаваемый динамический массив указателей и используется в операторе возврата из функции как возвращаемое значение. В основной программе вводится с клавиатуры желаемое значение порядка матри­цы (int n), а после ее формирования печатается результат.

#include <iostream.h>

#include <process.h> //Для exit()

int **single_matr(int n)

{

//Вспомогательный указатель на матрицу

int **p;

//Массив указателей на строки – одномерные массивы

p= new int *[n];

if (p==NULL)

{

cout<<"Не создан динамический массив!";

exit(1);

}

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

{ //Формирование строки элементов типа int

p[i]=new int [n];

if (p[i]==NULL)

{

cout<<" Не создан динамический массив!";

exit(1);

}

//Заполнение текущей строки

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

if (j!=i) p[i][j]=0;

else p[i][j]=1;

}

return p;

}

 

void main()

{

int n;

cout<<"\nЗадайте порядок матрицы: ";

cin>>n;

int **matr; //Указатель для формируемой матрицы

matr = single_matr(n);

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

{

cout<<"\nстрока "<<(i+1)<<": ";

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

cout<<"\t"<<matr[i][j];

}

for (i=0;i<n;i++) //Очистка памяти

delete matr[i];

delete [] matr;

}

 

 

5. Работа с файлами в Си++

5.1 Функции высокоуровневого ввода-вывода

 

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

<указатель на файл> = fopen (<имя файла>,<режим доступа>); .

В этой функции узазатель на файл должен быть описан так: FILE *fp; где тип FILE фактически является типом int (см. файл заголовков stdio.h). Возможные режимы доступа перечислены в таблице:

Таблица 8. Режимы доступа к файлам для функции fopen()

Строка режима Описание
“r” Открывает файл только для чтения. Модификация файла не разрешена.
“w” Создает новый файл только для записи. Перезаписывает любой существующий файл с тем же именем. Чтение информации из файла не разрешено.
“a” Открывает файл в режиме “только для записи” с добавлением новой информации в конец файла. Если файл не существует, он создается, а любой существующий файл с таким же именем перезаписывается. Чтение информации из файла не разрешено.
“r+” Открывает существующий файл для чтения и записи.
“w+” Создает новый файл для чтения и записи. Перезаписывает любой существующий файл с тем же именем.

 

Продолжение таблицы 8.

“a+” Открывает файл в режиме чтения и записи для добавления новой информации в конец файла. Если файл не существует, он создается, и любой существующий файл с тем же именем перезаписыватся.

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

Функция fclose (<указатель на файл>); "разрывает" связь между указателем на файл и физическим именем, установленную функцией fopen(), и освобождает указатель для другого файла.

Для чтения информации из уже открытого файла или записи в него существует несколько возможностей. Рассмотрим пока самые простые - функции getc() и putc(). Функция getc (<указатель на файл>); возвращает очередной символ из файла с заданным указателем. Если уже достигнут конец файла, то функция возвращает значение EOF. Функция putc (<переменная>, <указатель на файл>); помещает значение переменной в файл с заданным указателем.

С началом работы любой программы автоматически открываются три файла:

· стандартный вход,

· стандартный выход и

· стандартный выход для ошибок.

Соответствующие ссылки на файлы называются stdin, stdout и stderr. По умолчанию все они связаны с терминалом, однако stdin и stdout можно связать с файлами и межпрограммными каналами. Ссылки на stdin, stdout и stderr имеются в файле стандартных заголовков stdio.h.

Пример 1. Имитация команды type операционной системы MS DOS.

#include<iostream.h>

#include<stdio.h>

void main ()

{

FILE *fp; /* Указатель на файл */

char ch;

/* --------------------------------- */

if ((fp = fopen ("f1.cpp","r")) != NULL)

{ /* Открываем файл "f1.cpp" для чтения, одновременно */

/* проверяя, существует ли он. Указатель fp ссыла- */

/* ется на файл "f1.c" */

while ((ch = getc (fp)) != EOF)

/* Получаем символ из файла и проверяем, не */

/* является ли этот символ концом файла */

putc (ch,stdout); /* Выводим этот символ */

fclose (fp); /* Закрываем файл */

}

else cout<<"Файл \"f1.cpp\" отсутствует.\n";

}

 

Пример 2. Имитация команды copy операционной системы MS DOS.

#include <stdio.h>

#include <iostream.h>

void main ()

{

FILE *in,*out;

char ch;

/* --------------------------------- */

if ( (in=fopen ("f1.cpp","r")) != NULL )

{ out = fopen ("primer.cpp","w");

while ((ch=getc(in)) != EOF)

putc (ch,out);

putc (EOF,out); /*Записываем признак конца файла */

fclose (in);

fclose (out);

}

else cout<<"Исходный файл не найден!\n";

}

 

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

· int feof (<указатель на файл>);- проверка достижения конца файла. Возвращает истину (ненулевое значение), если указатель находится в конце файла, и ложь – в противном случае.

· int fflush (<указатель на файл>); -запись на диск содержимого буфера для заданного файла.

· char *fgets(<указатель на буфер>,<максимальное число читаемых символов>,<указатель на файл>); - последовательное чтение символов из файла до начала новой строки, или заданное количество символов, уменьшенное на 1. Прочитанные символы помещаются в буфер, начиная с ячейки, адресуемой первым параметром.

· int fprintf (<указатель на файл>,<формат>,<аргументы>); -записывает данные в файл по заданному формату. Возвращает количество записанных байтов. Если обнаружены ошибки, то возвращает EOF.

· int fputs(<строка>,<указатель на файл>); -запись заданной строки в файл.

· int fread (<указатель на буфер>,<размер в байтах одного элемента>,<количество читаемых элементов>,<указатель на файл >); -чтение данных из файла, начиная с текущего положения указателя. Возвращает число прочитанных элементов. Число прочитанных байтов равно результату функции, умноженному на число байтов в элементе. В случае ошибки возвращается нуль.

· int fscanf (<указатель на файл>,<формат>,<список адресных аргументов>);- чтение данных из файла, преобразование их в соответствующий формат и размещение в ячейки, заданные адресными аргументами.

· int fseek (<указатель на файл>,<количество байтов>,<начальная позиция отсчета>); -перемещение указателя файла на заданное количество байтов. В случае успеха возвращается нуль, в случае ошибки - ненулевое значение. Третий параметр может принимать следующие значения: SEEK_SET - отсчет идет от начала файла, SEEK_END - отсчет идет от конца файла, SEEK_CUR - отсчет идет от начала текущей позиции.

· void rewind (<указатель на файл>); -установка указателя на начало файла.

 

 

 








Не нашли, что искали? Воспользуйтесь поиском по сайту:



©2015 - 2024 stydopedia.ru Все материалы защищены законодательством РФ.