{if(x>y)return x;
return y;}
int main()
{int a1=-7,a2=40,res1; float f1=-3.6,f2=-10.5,res2;
res1=max(a1,a2);
res2=max(f1,f2);
printf("res1=%d res2=%.1f\n",res1,res2);
//res1=40 res2=-3.6
return 0;}
При компиляции программы с шаблоном функции, компилятор при вызове функции доопределяет ее. В данном примере при вызове res1=max(a1,a2); сформируется определение int max(int x,int y) и вызовется именно эта функция. При вызове функции res2=max(f1,f2); сформируется определение float max(float x,float y) и теперь вызываться будет данная функция.
Свойства параметров шаблонов:
1. Имена параметров шаблона должны быть уникальными во всем определении шаблона;
2. Список параметров шаблона не может быть пустым;
3. Объявление каждого параметра должно начинаться со слова class;
4. Имя параметра шаблона имеет свойства и права типа, т.е. его именем можно называть типы формальных параметров, тип функции, типы локальных переменные функции. Область видимости параметра шаблона во всем определении функции;
5. Все параметры шаблона, используемыев определении функции, должны быть обязательно использованы в сигнатуре функции. Т.е. недопустимо, например, определение
|
|
template <class A,class B>
B func(A x){…} // Ошибка, параметр В не использован
// в спецификации формальных параметров
Шаблон функции может быть использован и при описании прототипа функции.
template <class A>
void func(A);
Пример. Функция меняет местами значение объектов
#include <stdio.h>
template <class A>
void swp(A *x,A *y) // указатель типа А
{A z=*x; // Шаблон использован в теле функции
*x=*y; *y=z;}
int main()
{double d1=3.3, d2=2.2; swp(&d1,&d2);
printf("d1_n=%. 2lf d2_n=%. 2lf\n",d1,d2);
//d1_n=2.20 d1_n=3.30
char c1='M', c2='Z'; swp(&c1,&c2);
printf("c1_n=%c c2_n=%c\n",c1,c2);//c1_n=Z c2_n=M
char *st1="Москва", *st2="Киев"; swp(&st1,&st2);
printf("st1_n=%s st2_n=%s\n",st1,st2);
//st1_n=Киев st1_n=Москва
swp(st1,st2);
printf("st1_nn=%s st2_nn=%s\n",st1,st2);
// st1_nn=Миев st1_nn=Косква
return 0;}
В этом примере компилятор сформирует следующие определения к функциям:
void swp(double *x,double*y), void swp(char*x,char*y),
void swp(char**x,char**y), void swp(char*x,char*y).
Функция, содержащая параметры, определяемые в шаблоне, может содержать любое количество не параметризованных формальных параметров и возвращать не параметризованное значение.
Пример. Функция определяет индекс максимального элемента
template <class T>
int func_max(T x[], int n)
{T m=x[0]; int k=0;
for(int i=0; i<n; i++)
if(x[i]>max){max=x[i]; k=i;}
return k;}
void main()
{int arr[5]={5,12,40,23,4}, in1, in2;
in1=func_max(arr,5); //in1=2
printf("max1=arr[%d]=%d\n",in1,arr[in1]);
float mas[4]={5.3,1.2,4.0,20.3};
in2=func_max(mas,4); //in2=3
printf("max2=mas[%d]=%.2f\n",in2,arr[in2]);}
В результате работы программы на экран выведется:
max1=arr[2]=40 max2=mas[3]=20.30
2.12 Указатели на функции
Имя функции, по определению, есть указатель, значением которого является адрес размещения функции в памяти. Если значение этого адреса присвоить другому указателю, то данный указатель можно использовать для вызова функции.
|
|
Указатель на функцию определяется специальным образом:
тип_функции (*имя_указателя)(спецификация_параметров);
где тип_функции – тип возвращаемого функцией значения;
спецификация_параметров – типыформальных параметров;
*имя_указателя – имя указателя на функцию в круглых скобках.
Примеры определения указателей на функцию:
int (*ptrfunc)(int); // указатель на функцию
char (*pf1)(char*);
char* (*pf3)(char*);
Если этот пример записать без скобок, то он будет воспринят компилятором как прототип функции, возвращающей указатель.
int *ptrfunc(int); // прототип
В определении указателя на функцию тип возвращаемого значения и сигнатура функции должны совпадать с соответствующими типами и сигнатурами тех функций, адреса которых предполагается присвоить вводимому указателю.
Пример.
float mul(float x,float y) {return x*y;}
float sum(float x,float y) {return x+y;}
void main()
{float (*ptrmul)(float,float); // указатель на функцию
float (*ptrsum}(float,float); // указатель на функцию
ptrmul=mul; // указателю присвоили имя функции mul
ptrsum=sum; // указателю присвоили имя функции sum
float f1=3.0,f2=9.0,f3;
f3=(*ptrmul)(f1,f2)+(*ptrsum)(f1,f2);
printf("f3=%.1f", f3); // f3=39.0
float(*ptr)(float,float); // указатель на функцию
ptr=mul; // указателю присвоили имя функции mul
printf("mul=%.1f", (*ptr)(f1,f2)); // mul=27.0
ptr=sum; // указателю присвоили имя функции sum
printf("sum=%.1f",(*ptr)(f1,f2)); }// sum=12.0
Указатели на функцию могут образовывать массив, при этом тип и сигнатура во всех функциях должны быть одинаковые. Синтаксис: тип_функции(*имя_указателя[n])(спецификация_параметров);
где n – размер массива указателей на функции (константное выражение).
Пример. Определен массив из трех указателей на функции, имеющие тип int с параметром типа int
#include <stdio.h>
int f1(int x){return x+x;};
int f2(int x){return x*x;};
int f3(int x){return ~x;}; // побитовое отрицание
void main()
{int(*p[3])(int);
p[0]=f1; p[1]=f2; p[2]=f3;
int a=(*p[0])(5);
int b=(*p[1])(a);
int c=(*p[2])(a);
printf("a=%d b=%d c=%u\n",a,b,c);
}
В результате работы программы на экран выведется:
a=10 b=100 c=65525
Для задания типа указателя на функцию можно применять спецификатор typedef. Синтаксис такой:
// Определение тип указателя на функцию
typedef тип _ функции(*имя _ типа_указателя)(спец_ия _ парам);
// Определение указателя на функцию, массива указателей на функцию
имя_типа_указателя имя_указателя_ф-ции; имя_типа_указателя имя_указателя_ф-ции[n];
Например:
typedef int (*ABC)(int); // тип указателя на функцию ABC
АВС р1; // указатель на функцию р1 типа ABC
АВС р2[4];// массивиз 4 указателей на функцию р2 типа ABC
Пример. Определили массив указателей на функции
#include <stdio.h>
void typ0(){printf("Привет,");}
void typ1(){printf(" дорогой");}
void typ2(){printf(" друг!");}
typedef void (*TYPE)();
TYPE mpf[]={typ0,typ1,typ2};
//void (*mpf[])()={typ0,typ1,typ2};
void main()
{for(int i=0;i<3;i++) mpf[i]();}
Указатели на функции могут использоваться в качестве параметра функции. В этом случае в функцию можно передать указатели на различные функции.
Пример. Функция суммирует сначала квадраты элементов массива, а затем ‑ их кубы.
#include <stdio.h>
float sqr(float e){return e*e;} // Определение функции
float cube(float y){return y*y*y;} // Определение функции
typedef float(*PF)(float);
float summa(PF f,float a[],int N)// Определение функции
{float sum=0.0;
for(int i=0;i<N;i++) sum+=(*f)(a[i]);
return sum;}
void main()
{float res1, res2, A[3]={1.5,2.0,3.0};
res1=summa(sqr,A,3); //2.25+4+9=15.25
res2=summa(cube,A,3); //3.375+8+27=15.25
printf("res1=%.3f res2=%.3f\n",res1,res2);}
На экран выведется: res1=15.250 res2=38.375
3 Работа с файлами
Функции для работы с файлами в BC31 построены на функциях MS-DOS. Их разделяют на: потоковые и префиксные. Главное их отличие в том, что потоковые функции выполняют дополнительную буферизацию информации. Это приводит к двойной буферизации информации: на уровне MS-DOS и на уровне библиотечной функции (создается регулируемый буфер). Префиксные функции (функции нижнего уровня) не создают дополнительный буфер, они более эффективны при переносе блоков данных по 512 байт.
|
|
Мы будем рассматривать потоковые функции работы с файлами. Для пользователей поток – это последовательность байтов, которые передаются в файл или из файла, либо на физическое устройство или с физического устройства (например, монитор, клавиатура). Поток позволяет обрабатывать данные различных размеров и форматов.
Ввод-вывод потока позволяет выполнять следующие действия: 1) открывать и закрывать потоки; 2) создавать и удалять временные потоки; 3) считывать и записывать символы, строки, форматированные и неформатированные данные; 4) анализировать ошибки ввода-вывода потока и условия конца потока (конца файла) и т.д.
3.1 Потоковый ввод-вывод
С началом выполнения программы автоматически открываются стандартные потоки: стандартный ввод (с клавиатуры), стандартный вывод (на экран монитора), стандартный вывод сообщений об ошибках (на экран монитора). Для функций библиотеки <stdio.h> эти потоки stdin, stdout, stderr, которые работают с консолью (клавиатурой и монитором).
Все другие потоки для обмена данными между файлами нужно открыть явно. Причем, эти потоки могут открываться в одном направлении (только для чтения или записи) и в двух направлениях (для чтения и записи). Когда поток открывается, он связывается со структурой определенного типа, содержащей всю информацию для работы с потоком. Тип этой структуры с именем FILE определен в файле <stdio.h> и имеет следующий вид: