- •Списки §1. Общие сведения о списках
- •§2. Создание списка
- •§3. Просмотр и анализ списка
- •3.1. Просмотр и анализ списка целых чисел.
- •3.2. Просмотр и анализ списка одномерных массивов.
- •§6. Сравнительный анализ списков.
- •§1. Порядок работы с файлом
- •1.1. Потоки и файлы
- •1.2. Объявление файла
- •1.3. Открытие файла.
- •1.4. Закрытие файла.
- •§2. Работа с текстовым файлом
- •2.1. Посимвольная работа с текстовым файлом
- •Int fputc(int ch, file *stream)
- •2.2. Построчная работа с текстовым файлом
- •§3. Функции блокового ввода/вывода
- •3.1. Экономические задачи с использованием файлов
- •3.2. Математические задачи с использованием файлов
- •§4. Прямой (произвольный) доступ к файлу
- •4.1. Функция fseek()
- •4.2. Замена записи. Функции ftell, fgetpos, fsetpos, rewind.
- •Пример. В файл записать координаты точек плоскости. Найти две (любые) точки с наибольшим расстоянием между ними. Массив для хранения координат всех точек не использовать.
- •Упражнения, тесты.
- •Функции (дополнительные возможности)
- •§1. Функции с переменным количеством параметров.
- •§2. Указатели на функции.
- •§3. Массив указателей на функции.
- •§4. Введение в рекурсивные функции.
- •Упражнения, тесты.
- •Void Fun1 (float); void Fun2(float); void Fun3(float);
- •Лабораторная работа № 12.
- •Команды препроцессора (директивы компиляции)
- •§1. Директива define (замены в тексте)
- •Простое макроопределение (макрос)
- •Макрос с аргументами.
- •Директива #undef.
- •§2. Директива #include (включение файлов).
- •§3. Директивы условной компиляции.
- •Директива #if.
- •Директивы #ifdef и #ifndef.
- •Упражнения, тесты
- •История развития технологий программирования
- •§1. Программирование в машинных кодах и на языках символического кодирования
- •§2. Языки высокого уровня. Структурное и модульное программирование
- •§3. Интегрированные системы программирования.
- •§4. История и идеи объектно-ориентированного программирования.
- •§5. Программирование для Windows. Визуальное программирование.
- •Литература
- •Оглавление Предисловие………………………………………………………….…………………3
- •Г л а в а 4. Структуры и другие типы, определяемые пользователем.84
- •Г л а в а 6. Файлы ………………………………………………………..154
- •Г л а в а 7. Функции (дополнительные возможности) ………………190
- •Г л а в а 9. История развития технологий программирования ……220
Функции (дополнительные возможности)
§1. Функции с переменным количеством параметров.
В языке С++ допустимы функции, количество параметров у которых в заголовке функции точно не определено. Кроме этого, могут быть неизвестными и типы параметров. Количество и типы параметров становятся известными только в момент вызова функции, когда явно задан список фактических параметров.
Формат прототипа такой функции следующий:
тип имя_функции (список_явных_параметров, …);
Здесь тип — тип возвращаемого функцией значения, список_явных_параметров— список спецификаций отдельных параметров, количество и типы которых фиксированы и известны в момент компиляции. Их можно назвать обязательными. Многоточие извещает компилятор, что дальнейший контроль соответствия количества и типов параметров при обработке вызова функции проводить не нужно.
Один из двух механизмов определения количества и типов параметров предусматривает передачу в функцию значения реального количества фактических параметров. Это выполняется с помощью одного из явно задаваемых (обязательных) параметров. Рассмотрим это на следующем примере(++):
void fun(int,...);
void main()
{ int a1=10, a2=-20, a3=33, a4=-44;
fun(1,a1);
fun(2,a1,a2);
fun(4,a1,a2,a3,a4);
getch();
}
void fun(int k,...)
{ int *p=&k; cout<<"\n There are "<<k<<" arguments\n";
for (int i=1; i<=k; i++)
// или for (; k; k--)
cout<<*(++p)<<" ";
}
В функции fun первый и единственный обязательный параметр k определяет количество действительно используемых при вызове необязательных фактических параметров. Для доступа к списку параметров используется указатель p типа int *. Вначале ему присваивается адрес явно заданного параметра k, то есть он устанавливается на начало списка параметров в памяти. Затем в цикле указатель p перемещается по адресам следующих фактических параметров, соответствующих неявным формальным параметрам. С помощью операции разыменования *p выполняется выборка их значений, которые в данном примере используются для вывода.
Цикл для вывода нескольких значений можно организовать двумя способами. Первый, тривиальный, использует счётчик i. Во втором, более оригинальном способе, параметром цикла является аргумент k, значение которого уменьшается на единицу после каждой итерации. Когда его значение станет нулевым, осуществляется выход из цикла.
Особенность и недостаток такой функции —возможность работы только с фактическими параметрами одного типа (в примере целочисленными), так как указатель p объявляется с одним конкретным типом (у нас int *).
§2. Указатели на функции.
Чтобы понять, как работают указатели на функции, надо иметь в виду следующее. По мере компиляции функций исходный код, записанный по правилам языка С++, преобразуется в объектный и устанавливается точка входа, к которой происходит обращение при вызове функции.
Указатель на функцию в программе определяется следующим образом:
тип_функции (*имя_ указателя) (список_параметров);
Например,
float (*PFun) (int, int);
определяет указатель PFun на функцию с двумя целочисленными параметрами, возвращающую одно вещественное значение. Если приведённое выше объявление записать без первых круглых скобок, то есть в виде
float *PFun (int, int);,
то компилятор воспримет его как прототип функции с именем PFun с двумя целочисленными параметрами, возвращающей значение указателя типа float *. Другими словами, это не указатель на функцию.
Второй пример. Объявление char *(*PFun2 (char *, float); определяет указатель Pfun2 на функцию с параметрами типа указатель на char и типа float, возвращающую значение типа указатель на char.
При использовании указателя на функцию тип возвращаемого значения, типы, количество и последовательность параметров должны совпадать с соответствующими типами, количеством и последовательностью параметров тех функций, адреса которых (их имена) предполагается присваивать вводимому указателю при инициализации или с помощью оператора присваивания.
Определим обычным образом, как делали в первом семестре, конкретную функцию от двух целочисленных параметров, возвращающую вещественное значение. Например, в качестве такой функции может быть функция, возвращающая среднее значение двух целых чисел:
float Aver (int x, int y)
{ return (x+y)/2.;
}
Тогда объявленный выше указатель на функцию PFun можем определить с помощью следующего присваивания:
PFun=Aver;
При таком присваивании необходимо соблюдать соответствие типов возвращаемых значений функций (в примере float), количеством (2), порядком следования и типами (int x, int y) параметров для указателей правой и левой частей оператора присваивания. Почему такое присваивание будет корректным? PFun мы объявили как указатель на функцию. По аналогии с массивом имя функции (Aver) без последующих скобок и параметров без дополнительного объявления выступает в качестве указателя на эту функцию, и его значением служит адрес размещения функции в памяти. Хотя функция — это не переменная, она всё равно имеет физическое местоположение в памяти, которое может быть присвоено другому указателю.
Адрес функции является входной точкой функции. Поэтому указатель на функцию может использоваться для её вызова следующим, например, образом:
float Res; Res= (*PFun) (5, 2);
С помощью операции разыменования * обеспечивается обращение по адресу к этой функции. Заметим. что будет ошибкой записать вызов функции без первых скобок в виде:
Res= *PFun (5, 2);
Дело в том, что круглые скобки имеют более высокий приоритет, чем операция обращения по адресу *. Поэтому сначала будет сделана попытка обратиться к функции PFun. А операция разименования будет отнесена к результату этой функции. Но у нас нет функции PFun! Это указатель на функцию, что не одно и то же.
При объявлении указатель на функцию может быть проинициализирован. Требования к функции те же, что и при присваивании. Например, пусть определена та же функция Aver. Тогда объявить указатель на функцию можем так:
float *PFun (int, int)=Aver;
В качестве конкретного практического использования указателя на функцию рассмотрим следующий пример.
Пример 1. (+) Составить функцию, которая по формуле Симпсона вычисляет значение определённого интеграла от произвольной функции одной переменной. Функцию проверить для вычисления двух интегралов:
Формула
Симпсона:
n
– чётное.
Функция Integral для вычисления определённого интеграла от произвольной функции по формуле Симпсона имеет следующие параметры: пределы интегрирования a и b; количество точек, на которые разбивается отрезок [a, b], которое объявлено с типом float, чтобы исключить преобразования типов при реализации алгоритма; указатель на функцию, для которой вычисляется определённый интеграл.
float Integral (float, float, float, float (*Fun)(float));
// Две тестовые функции, для которых вычисляется интеграл.
float Function1(float x){ return (sqrt(1-x*x));
};
float Function2(float x){ return (exp(-x*x));
};
void main()
{ float a,b,n,I;
// Вычисление первого интеграла.
a=-1; b=1; n=100; I=Integral(a,b,n,Function1); cout<<I;
// Вычисление второго интеграла.
a=0; b=1; n=100; I=Integral(a,b,n,Function2); cout<<endl<<I;
getch();
}
// Функция для вычисления интеграла по формуле Симпсона.
float Integral(float a,float b,float n,float (*Fun)(float))
{ float h,s1=0,s2=0;
h=(b-a)/n;
for(int i=2;i<=(n-2);i+=2)
s1+=(*Fun)(a+i*h);
for(int i=1;i<=(n-1);i+=2)
s2+=(*Fun)(a+i*h);
return ((b-a)/(3*n)*((*Fun)(a)+2*s1+4*s2+(*Fun)(b)));
}
