Функции с переменным количеством параметров
В языке Си++ допустимы функции, у которых количество параметров и их типы при компиляции определения функции не определены. Эти значения становятся известными только в момент вызова функции, когда явно задан список фактических параметров. При определении и описании таких функций, имеющих списки параметров неопределенной длины, спецификация формальных параметров заканчивается многоточием. Формат прототипа функции с переменным списком параметров:
<тип функции> < имя функции> (<спецификация явных параметров>,…); .
Здесь <спецификация явных параметров> - список спецификаций отдельных параметров, количество и типы которых фиксированы и известны в момент компиляции. Эти параметры называются обязательными. После списка явных (обязательных) параметров ставится необязательная запятая, а затем многоточие, извещающее компилятор, что дальнейший контроль соответствия количества и типов параметров при обработке вызова функции проводить не нужно.
Каждая функция с переменным списком параметров должна иметь механизм определения их количества и типов. Существует два подхода к решению этой задачи. Первый подход предполагает добавление в конец списка реально 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 Все материалы защищены законодательством РФ.
|