Int mul(double x,double y)
{if (x>y)return 1; else return 2;}
При использовании перегруженных функций желательно не пользоваться умалчиваемыми параметрами. Если определить несколько перегруженных функций с умалчиваемыми параметрами, то может выдаваться сообщение о неоднозначности:
Пример.
#include <stdio.h>
int sum(int x) {return 2*x;}
int sum(int x, int y=5) {return x+y;}
Void main()
{int S1=sum(10); // 20 или 15
… }
1.11 Шаблоны функций
Для автоматизации создания функций различного вида (в т.ч. и перегруженных) в С++ введены шаблоны функций. Определяется шаблон семейства функций следующим образом
template <class type1,class type2, class type3 …>
где type1, type2, type3. – формальные параметры шаблона, которыми можно обозначать тип возвращаемого значения, типы формальных и локальных параметров внутри функции.
Пример.
template <class Tip>
Tip max(Tip x, Tip y )
{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
1.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
2 Работа с файлами
Функции разделяют на: потоковыеи префиксные. Главное их отличие в том, что потоковые функции выполняют дополнительную буферизацию информации.
Мы будем рассматривать потоковые функции работы с файлами. Для пользователей поток– это последовательность байтов, которые передаются в файл или из файла, либо на физическое устройство или с физического устройства (например, монитор, клавиатура).
Ввод-вывод потока позволяет выполнять следующие действия: 1) открывать и закрывать потоки; 2) создавать и удалять временные потоки; 3) считывать и записывать символы, строки, форматированные и неформатированные данные; 4) анализировать ошибки ввода-вывода потока и условия конца потока (конца файла) и т.д.
2.1 Потоковый ввод-вывод
С началом выполнения программы автоматически открываются стандартные потоки: ввод (с клавиатуры), вывод (на экран монитора), вывод сообщений об ошибках (на экран монитора). Для функций библиотеки <stdio.h>эти потоки stdin, stdout, stderr,которые работают с консолью (клавиатурой и монитором).
Все другие потоки для обмена данными между файлами нужно открыть явно. Причем, эти потоки могут открываться в одном направлении (только для чтения или записи) и в двух направлениях (для чтения и записи). Когда поток открывается, он связывается со структурой типаFILE, содержащей всю информацию для работы с потоком. Тип этой структуры имеет следующий вид:
Struct FILE
{int level; //пуст или полон буфер
unsigned flags; //флаги состояния файла
char fd; //логический номер файла, дескриптор файла
unsigned char hold; //непереданный символ
int bsize; //размер буфера
unsigned char _FAR *buffer;//указатель на буфер потока
unsigned char _FAR *curp; //указатель текущей позиции
unsigned istemp; //флаг временного файла
short token; //маркер действительности файла
};
Флаги состояния задают битовую позицию в члене структуры flags.
Определена специальная константа, обозначающая конец файла:
#define EOF (-1) //индикатор конца файла
Функции обмена с файлами могут работать в двух режимах: текстовом и двоичном. По умолчанию режим устанавливается текстовый. В текстовом режиме при записи в файл символ '\n' (перевод на начало новой строки) преобразуется в два символа '\n' и '\0', а при чтении из файла такие символы уже воспринимаются как два символа '\n' и '\0'. ТакженажатиеклавишCtrl-Z (конец ввода строки) воспринимается как конец строки '\0', который не записывается в файл.
При обмене с файлом в двоичном режиме никаких преобразований символов не производится.
В файлах <fcntl.h> и <stdlib.h> определена специальная переменная _fmode, которая может принимать значения O_BINARY (бинарный режим) или O_TEXT (текстовый режим). Например:
_fmode=O_BINARY;
2.2 Открытие и закрытие потока
Для начала обмена данными между программой и файлом необходимо открыть поток.
Функция открытия потока имеет прототип:
FILE* fopen(const char* filename, const char* mode);
Эта функция открывает поток, который связывает программу с заданным файлом. В случае успешного открытия возвращается указатель на структуру типа FILE (FILE*), описывающую открытый поток. Если файл не удалось открыть, функция возвращает значение NULL-указатель. Возвращаемое значение указателя присваивается ранее определенной переменной типа FILE*, которую в дальнейшем можно использовать для обмена данными с файлами.
Переменнаяchar* filename – указатель на строку, задающую спецификацию открытого файла, т.е. путь и имя файла. Если путь не задан, то по умолчанию выбирается текущие диск и директорий.
Переменнаяchar* mode ‑ указатель на строку, задающую режим открытия потока и состоящую из определенных символов:
r– поток открывается только для чтения из существующего файла, r+– поток открывается для чтения из существующего и записи в существующий файл;
w– поток открывается только для записи в файл, w+– поток открывается для записи в файл и для чтения из файла. Если файла нет, он создается, если файл существует, его старое содержимое пропадает;
a– поток открывается для добавления данных в конец файла, a+– поток открывается для добавления данных в конец файла и для чтения из файла. Если файл не существует, он создается;
t– поток открывается в текстовом режиме, задается после r, w, a;
b– поток открывается в двоичном режиме, задается после r, w, a.
Пример. Фрагмент программы, в которой открывается поток fp для записи данных в файл myfile.txt ипоток finдля записи данных в файл abc.txt на диске D в директории USER:
FILE *fp, *fin;
fp=fopen("D:\\User\\myfile.txt","w");
char *fname=("D:\\User\\abc.txt");
fin=fopen(fname,"r");
Если открытие потока оказалось неудачным, то функция возвращает NULL-указатель. При открытии потока рекомендуется выполнять проверку возвращаемого значения функции открытия файла:
if(fp==NULL){puts(“ошибка открытия файла”); return -1;}
if(fin==NULL){puts("ошибка открытия файла");return -1;}
После работы с потоком его закрывают.
Функция закрытия потока имеет прототип:
int fclose(FILE *имя);
Эта функция закрывает открытый поток. В функцию передается указатель на структуру типа FILE(имя открытого потока), и при успешном закрытии потока функция возвращает 0, иначе – EOF(–1). Эта функция удаляет структуру типа FILE, выгружает содержимое буфера на диск, если файл был открыт для записи, и вызывает функцию закрытия файла, которая обновляет содержимое директория (обновляет информацию о размере, дате и времени). Например: fclose(fp);
В программе может быть открыто несколько файлов. В конце работы с ними их необходимо все закрыть. Есть функция закрытия всех открытых файлов с прототипом: int fcloseall(void);
Эта функция возвращает число закрытых файлов, или EOF, если хотя бы один файл не удалось закрыть.
int n=fcloseall(void);
printf("n=%d",n); //n=2,если закрылись два открытых потока)
//или n=-1,если какой-либо поток из двух открытых не закрывается
Для переоткрытия файла с другим режимом доступа существует функция со следующим прототипом:
FILE *freopen(const char *filename,const char*mode,FILE *fp);
Эта функция закрывает поток fpс прежним именем файла и открывает снова этот же поток с именем файла, на который указывает строка filename в режиме, указанном в строке mode. В случае успеха функция возвращает указатель на описание открытого потока, которое будет совпадать с fp, иначе NULL. Если имена файлов разные, происходит переадресация файлового ввода-вывода.
fp=freopen("D:\\User\\myfile.txt","a",fp);
FILE *fp1=freopen("D:\\User\\myfile1.txt","ab",fp);
freopen("D:\\User\\abc.txt","r",fin);
Пример.Файл сначала открывается для чтения, затем снова открывается для дополнения.
#include<stdio.h>
Int main()
{FILE *fp;
char buff[100], *fname ="myfile.txt";
fp=fopen(fname,"r"); //открытие потока для чтения из файла
if(fp!=NULL)fgets(buff,50,fp);//чтение из файла строки
else {puts("ERROR_1"); return -1;}
puts(buff); //вывод на экран считанной из файла строки
fp=freopen(fname,"a",fp); //переоткрытие потока
if(fp!=NULL) fputs("abcdef",fp);//добавление строки
else {puts("ERROR_2"); return -1;}
fclose(fp); //закрытие потока
return 0;}
При работе с файлами большая роль отводится обработке ошибок. Для анализа ошибок можно использовать функцию c прототипом:
void perror(const char *str);
Эта функция выводит в поток (на экран) строку strвместе с системной ошибкой. Список системных ошибок определен в файле <stdlib.h>.
FILE *ptr=fopen("abc.doc","r"); //открытие файла для чтения
if(fp==NULL){perror("Не могу открыть файл: "); return -1;}
char c=fgetc(ptr); //чтение одного символа
fclose(ptr); //закрытие потока
Если файл в текущем директории отсутствует, то на экран выведется:
Не могу открыть файл: No such file or directory
После успешного открытия файла программа может осуществлять файловый ввод-вывод.
2.3 Посимвольный ввод-вывод
Функция посимвольной записи (вывода) в файл имеет прототип:
int fputc(int ch, FILE *fp);
Эта функция помещает символ int ch после преобразования в тип char в открытый поток fp, связанный с файлом. В случае успеха возвращает код записываемого в файл символа, а в случае ошибки или достижения конца файла – EOF.
Пример.Запись символов в открытый для записи файл через поток fp
FILE *fp=fopen("abc.txt","w"); //открытие потока для записи
if(fp==NULL){puts("ошибка открытия файла"); return -1;}
char s1='Z', str1[]="Запорожье",s2;
fputc(s1,fp); //запись символа в файл через поток fp
for(int i=0; i<9; i++)
fputc(str1[i],fp); //запись 9 символов в файл
fputc('\n',fp); //запись символа \nв файл
printf("Введите символ или Ctrl-Z");
while((s2=getchar())!=EOF)
fputc(s2,fp); //запись символов в файл
fclose(fp); //закрытие потока
Функция посимвольного чтения (ввода) из файла имеет прототип:
int fgetc(FILE *fp);
Эта функция возвращает символ из открытого потока fp, связанного с файлом, или EOF (–1) в случае ошибки или достижения конца файла.
Пример.Чтениесимволов из открытого для чтения файла через поток fp
FILE *fp=fopen("abc.txt","r"); //открытие потока для чтения
if(fp==NULL){puts("ошибка открытия файла"); return -1;}
char z1, str2[9],z2;
z1=fgetc(fp); //чтение символа из файла через потокfp
printf("%c\n",z1);//вывод на экран прочитанного символа
for(int j=0;j<9;j++)
{str2[j]=fgetc(fp); //чтение 9-и символов из файла
printf("%c",str2[j]);} //вывод на экран cчитанных символов
while((z2=fgetc(fp))!=EOF) //чтение символов из файла
putchar(z2);//вывод на экран прочитанных символа
fclose(fp); //закрытие потока
2.4 Построчный ввод-вывод
Функция построчной записи(вывода) в файл имеет прототип:
int fputs(char*string, FILE *fp);
Эта функция переносит байты из строки string в открытый файл через поток fp. Перенос завершается при достижении символа '\0', который в файл не заносится. В случае успеха функция возвращает код последнего записанного в файл символа, а в случае ошибки – EOF. Необходимо следить, чтобы записываемая в файл последовательность символов имела '\0', иначе файл может оказаться большим.
Пример.Запись строк в открытый для записи файл через поток fp
FILE *fp=fopen("abc.txt","w"); //открытие потока для записи
if(fp==NULL){puts("ошибка открытия файла"); return -1;}
char *s1=”Физика”,*ms1[3]={"ТЕК","КЕ","ПТЦА"};
fputs(s1,fp); //з апись строки в файл через потокfp
fputs("\n",fp); //з апись строки с\n в файл через потокfp
for (int i=0;i<3;i++)
{fputs(ms1[i],fp); //запись3-х строк в файл через потокfp
fputs("\n",fp);}
fclose(fp);//закрытие потока
Функция построчного чтения(ввода) из файла имеет прототип:
char*fgets(char *string,int N,FILE *fp);
Эта функция принимает байты из открытого файла и размещает их по адресу, заданному указателем string. Прием завершается после приема N байт или при получении символа '\n', который тоже передается в строку string. Последним символом является '\0'. В случае успеха возвращает указатель на строку string, а в случае ошибки или достижения конца файла – NULL–указатель. Для успешного чтения строки из файла надо зарезервировать достаточное место в памяти.
Пример.Чтение строк из открытого для чтения файла через поток fp
FILE *fp=fopen("abc.txt","r"); //открытие потока для чтения
if(fp==NULL){puts("ошибка открытия файла"); return -1;}
char s2[20], ms2[3][20]; //зрезервируем память
fgets(s2,20,fp);//чтение из файла строки,< 20символов
puts(st2); //вывод на экран прочитанной строки
for(int j=0;j<3;j++)
{fgets(ms2[j],20,fp); //чтение из файла3-хстрок,<20сим-в
puts(ms2[j]);} //вывод на экран считанных строк
fclose(fp); //закрытие потока
Функция fgets(),получив из потока (файла) символ '\n', записывает его в строку и еще дописывает символ '\0'. Функция fputs(), встретив символ '\0' завершает запись в файл.
Таким образом, если записывать в файл строки без символа '\n' вконце строки функциейfputs(),то в файле будет только одна строка. При чтении функцией fgets() строки из файла функцияputs() выведет на экран одну суммарную строку. Если записывать в файл строки с символом '\n' в конце, используя функциюfputs(),в файле будет несколько строк с символом '\n'.При чтении строк из файла функцией fgets() символ '\n'преобразуется в символы'\n', '\0', а функцияputs() выведет на экран строки с символом '\n'в конце каждой строки, т.е. между выводимыми строками появятся пустые строки.
Пример. Запись и чтение строк без символа '\n'и с символом '\n'.
FILE* pf=fopen("b.txt","w"); //открытие потока
if(pf==NULL){puts("ошибка открытия файла"); return -1;}
char st1[]="Anna",st2[]="Маria",st3[20]; int n=0;
fputs(st1,pf); //запись в файл4символов
// fputs("\n",fp); //запись \n в файл через поток fp (2символа)
fputs(st2,pf); //запись в файл5символов
//fputs("\n",fp); //запись \n в файл через поток fp (2символа)
fclose(pf); //размер файла 9 байт (или 13 байт)
pf=fopen("b.txt","r");
if(pf==NULL){puts("ошибка открытия файла"); return -1;}
while(fgets(st3,20,pf)!=NULL)
{puts(st3); n++;}
printf("Число строк=%d",n);
Fclose(pf);
На экран выведется
в первом случае: | во втором случае |
AnnaМаria Число строк=1 | Anna Маria Число строк=2 |
2.5 Форматированный ввод-вывод
Форматированный ввод-вывод данных является частным случаем строкового ввода-вывода. Особенность этого ввода-вывода то, что при переносе данных между файлом через открытый поток и программой происходит преобразование этих данных в соответствии с заданными спецификаторами формата. Спецификаторы формата такие же, как при консольном вводе-выводе.
Функция форматированной записи(вывода) в файл имеет прототип:
int fprintf(FILE *fp, char *format [,аргумент]…);
Эта функция выводит данные с преобразованием по формату в открытый поток fp, связанный с файлом. В случае успеха возвращает число выведенных байтов (символов), а в случае ошибки – EOF.
Пример.Запись чисел в открытый для записи файл через поток fp
FILE *fp=fopen("abc.txt","w");//открытие потока для записи
if(fp==NULL){puts("ошибка открытия файла"); return -1;}
int a=5; float b=3.56; long c=50000;
fprintf(fp,"%d %f %ld\n",a,b,c);
int arr[5]={1,2,3,4,5};
float mas[5]={1.1,2.2,3.3,4.4,5.5};
//запись массивов5-и чисел типаint,float в файл
for(int i=0;i<5;i++)
fprintf(fp,"%d %f\n",arr[i],mas[i]);
Функция форматированного чтения(ввода) из файла имеет прототип:
int fscanf(FILE *fp, char *format [,указатель]…);
Эта функция выполняет ввод из открытого файла данных с преобразованием по формату и с занесением в ячейки памяти, определяемые указателями. В случае успеха возвращает число обработанных при вводе полей (спецификаторов) или EOF (–1) в случае ошибки или достижения конца файла.
Пример.Чтениечисел из открытого для чтения файла через поток fp
freopen("data.txt","r",fp); //переоткрытие потока
if(fp==NULL){perror("Входной файл не открыт "); return 1;}
Int n; float f; long l;
fscanf(fp,"%d %f %ld\n",&n,&f,&l);//чтение чисел из файла
printf("%d %f %ld\n",n,f,l); //вывод чисел на экран
int a[5]; float m[5];
//чтение из файла и вывод на экран массивов из 5-и чисел типа int, float
for(int j=0;j<5;j++)
{fscanf(fp,"%d %f\n",&a[j],&m[j]);
printf("%d %f\n",a[j],m[j]); }
Fclose(fp);
2.6 Форматированные вывод в строку и ввод из строки
Не редко возникает необходимость форматированного вывода данных в строку и ввода данных из строки .
Функция форматированного записи(вывода) в строку имеет прототип:
int sprintf(char *buf, char *format [,аргумент]…);
Эта функция выводит данные с преобразованием по формату в строку buf. Последний символ устанавливается '\0'. В случае успеха возвращает число выведенных байтов (символов), а в случае ошибки – EOF.
Функция форматированного чтения(ввода) из строки имеет прототип:
int sscanf(char *buf, char *format [,указатель]…);
Эта функция вводит из строки данные с преобразованием по формату и помещает их в ячейки памяти, определяемые указателями. В случае успеха возвращает число обработанных при вводе полей или EOF(–1) в случае ошибки.
Пример.Записьчисел в строку и чтение чисел из строки
int a1=10; long b1=50000; char buf1[20];
sprintf(buf1,"a1=%d, b1=%ld",a,b);
puts(buf1);// a1=10, b1=50000
float e, pi; char buf2[25]="2.718281, 3.141592";
sscanf(buf2,"%f, %f",&e,&pi);
printf("e=%f pi=%f\n",e,pi); );//e=2.718281 pi=3.141592
Int a2; long b2;
sscanf(buf1,"a1=%d, b1=%ld",&a2,&b2);
printf("a2=%d b2=%ld\n",a2,b2); //a2=10 b2=50000
2.7 Блоковый ввод-вывод
Эти функции предназначены для ввода-вывода блоков байт. Блок – это группа рядом расположенных байт, между которыми нет никаких специальных разделителей. Для того чтобы не иметь проблем с символами '\n', '\0', блоковый ввод-вывод выполняют в бинарном режиме. За одно обращение можно переносить несколько блоков.
Функция блокового записи (вывода) в файл имеет прототип:
int fwrite(void *ptr, int size, int N, FILE *fp);
Эта функция записывает через поток fpв открытый файл N блоков, размером size байт каждый, из области памяти, определяемой указателем ptr. В случае успеха возвращает число блоков, а в случае ошибки – EOF.
Пример.Запись блоков данных в открытый для записи в двоичном режиме файл через поток fp
FILE *fp=fopen(“abc.txt”,"wb");
if(fp==NULL){puts("ошибка открытия файла"); return -1;}
struct REC{int a[2]; float b;}; //определение структ.типа
REC str1={1,2, 3.3}; //определение и инициализация структуры
fwrite(&str1,sizeof(REC),1,fp); //запись структуры в файл
REC str2[3]={{3,4, 5.5},{6,7, 8.8},{9,10, 1.1}};
fwrite(str2,sizeof(REC),3,fp);//запись массива структур в файл
Fclose(fp);
Функция блокового чтения (ввода) из файла имеет прототип:
int fread(void *ptr,int size,int N,FILE *fp);
Эта функция считывает через поток fpиз открытого файла N блоков, размером size байт каждый, в область памяти, определяемой указателем ptr. В случае успеха возвращает число блоков, а в случае ошибки или достижения конца файла – EOF.
Пример.Чтение блоков данных из открытого для чтения в двоичном режиме файла через поток fp
FILE *fp=fopen(“abc.txt”,"rb");
if(fp==NULL){puts("ошибка открытия файла"); return -1;}
REC str3, str4[3]; //определение структуры и массива структур
fread(&str3,sizeof(REC),1,fp); //чтение структуры из файла
printf("%d %d %f\n",str3.a[0],str3.a[1],str3.b);
fread(str4,sizeof(REC),3,fp);//чтение массива структур из файла
for(int i=0;i<3;i++) //вывод массива структур на экран
printf("%d %d %f\n",str4[i].a[0],str4[i].a[1],str4[i].b);
Fclose(fp);
2.8 Произвольный доступ к файлу
При обмене данными с файлом (записи и чтении) – место, куда записывается или считывается информация, определяется указателем записи-чтения. Ранее рассматривались функции обмена с файлом, которые осуществляли последовательный доступ к файлу. В библиотеке языка Си имеются функции, позволяющие выполнить произвольный доступ к файлу, т.е. доступ в любую точку файла.
Функция установки указателя записи-чтения на начало файла имеет прототип void rewind(FILE *fp); Например: rewind(fp);
Функция установки указателя записи-чтения в произвольную точку файла имеет прототип:
int fseek(FILE *fp, long offset, int from_where);
Эта функция устанавливает указатель записи-чтения в потоке fp на offsetбайт вправо или влево. Если offset>0, то указатель сдвигается в сторону конца файла, если offset<0, то указатель сдвигается в сторону начала файла. Параметр from_where задает точку отсчета для сдвига: SEEK_SET – сдвиг от начала файла, SEEK_CUR – сдвиг от текущей позиции указателя, SEEK_END – сдвиг от конца файла. В случае успеха функция возвращает 0, иначе – EOF.
Установка указателя записи-чтения левее начала файла считается ошибкой, установка указателя правее конца файла делает значение указателя записи-чтения неопределенным. Например:
//установка указателя записи-чтения через 10байт от начала файлаfseek(fp,10L,SEEK_SET);
Функция определения текущего положения указателя записи-чтенияимеет прототип: long ftell(FILE *fp);
Эта функция возвращает значение в байтах (тип long) от начала файла до текущего положения указателя записи-чтения файла, открытого через поток fp. В случае ошибки функция возвращает –1L. Например:
long cur=ftell(fp);
Пример.Запись и добавление в файл блоков данных (структур) и чтение одной заданной структуры записанных ранее в файл структур.
#include<stdio.h>
struct REC{unsigned id;char name[20];unsigned mark;};
Int main()
{FILE *out,*in; REC rec;
int flag, sz=sizeof(REC); long len,kol,n;
if((out=fopen("mark.txt","wb"))==NULL)
{perror("Выходной файл не открыт"); return 1;}
do {puts("Введите номер записи, фамилию, оценку");
scanf("%d",&rec.id); gets(rec.name);
scanf("%d",&rec.mark);
fwrite(&rec,sz,1,out);//запись в файл блока данных
puts("Продолжить ввод? Да-1, Нет-0");
scanf("%d",&flag);
}while(flag);
fclose(out);//закрытие файла
if((in=fopen("mark.txt","rb"))==NULL)
{perror("Входной файл не открыт"); return 2;}
//сдвиг указателя чтения-записи на конец файла
Fseek(in,0,SEEK_END);
len=ftell(in); //определение текущего зн-ия указателя чтения-записи
kol=len/sz; //количество блоков в файле
printf("В файле %d записей\n", kol);
puts("Введите номер записи для чтения:");
scanf("%ld",&n);
if(n>0&&n<=kol)
{fseek(in,(n-1)*sz,SEEK_SET); //установка указателя
//чтения-записи на заданный блок от начала файла
fread(&rec,sz,1,in); //чтение из файла блока данных
printf("%d %s – %d\n",rec.id_rec,rec.name,rec.mark);
}
else puts("Вне файла!");
fclose(in); //закрытие файла
return 0;}
3 Многофайловая компиляция
Программы – это обычно совокупность функций. Функции, как правило, размещают в разных файлах, группируя их по каким-либо признакам. В результате для создания конечной программы необходимо их объединить в единое целое.
Напомним, что из всей совокупности функций в программе может быть только одна главная функция main(), которая является точкой входа в программу.
Для объединения функций в одну программу существует несколько механизмов.
Самый простой способ: создать программу из нескольких файлов, используя препроссесорную директиву #include.
Пример.
//файлfile1.cpp
extern int b; //описание внешней переменнойb
int func2(int,int); //прототип функцииfunc2
int a=5; //определение внешней переменнойа
int func1(int c) //определение функцииfunc1
{int f=func2(c,a); // f=30
return f*b; } // 30*10=300
//файлfile2.cpp
extern int k; //описание внешней переменнойk
int func2(int x, int y) //определение функцииfunc2
{return x*y+k;} // 2*5+20=30
//файл file3.cpp
#include<stdio.h> //подключение библиотечных функций
#include "file1.cpp" //подключение файлаfile1.cpp
#include "file2.cpp" //подключение файлаfile2.cpp
int b=10; //определение внешней переменнойb
int k=20; //определение внешней переменнойk
Void main()
{int d=2,res;
res=func1(d);//вызов фукцииfunc1 res=300
printf(“k=%d res=%d”,k,res);//вывод результатов на экран
}
В данном примере препроцессор подставил в файл filе3.cpp тексты файлов filе1.cpp и file2.cpp. Переменная a (определена как внешняя в файле filе1), функции func1(), func2()видны и в файле filе3, благодаря работе препроцессора. В файле filе1 для доступа к функции func2()(определена в файле filе2) прописан прототип этой функции. В файлах filе1, filе2для доступа к переменным b, k (определены как внешние в файле filе3) эти переменные описаны со словом extern.
Поведем некоторые итоги.
1) Функции по определению являются глобальными, и им по умолчанию присваивается класс памяти extern,поэтому функции доступны во всех файлах. Но перед первым использованием функций необходимо записать прототипы, которые необходимы для компилятора.
2) Другие объекты (переменные, массивы, структуры и т.д.) также могут использоваться в файлах, где они не определены. Но они должны быть глобальными, т.е. определены вне функций, и им необходимо присвоить класс памяти extern, если они используются выше своего определения при данном методе создания многофайловой программы.
Каждый объект может быть определен только один раз, но может быть неоднократно описан.
Если использовать в разных файлах одни и те же функции, переменные, то в начале всех файлов необходимо поместить прототипы нужных функций и объявление переменных. Можно поместить прототипы функций, объявления переменных в отдельный файл с расширением .h, и включать этот файл в начало других файлов с помощью оператора #include.
//файл common.h
Exrtern int a;
Exrtern int b;
Exrtern int k;
int func1(int);//прототипфункцииfunc1
int func2(int, int);//прототип функцииfunc2
//файл filе1.cpp
#include "common.h"
int a=5; //определение внешней переменнойа
int func1(int c){int f=func2(c,a);return f*b;}
//файл file2.cpp
#include "common.h"
int func2(int x, int y) {return x*y+k;}
// файл file3.cpp
#include<stdio.h> //подключение библиотечных функций
#include "common.h" //подключение заголовочного файла
#include "file1.cpp" //подключение файлаfile1.cpp
#include "file2.cpp" //подключение файлаfile2.cpp
int b=10; //определение внешней переменнойb
Void main()
{int d=2,res; res=func1(d+k);
printf(“k=%d res=%d ”,k,res);}
int k=20; //определение внешней переменнойk
Фактически оператор подставляет содержимое файла *.h в текущий файл перед тем, как начать его компиляцию. Файл с расширением .h называется файлом заголовков.
Для создания многофайловых программ существует удобный механизм многофайловой компиляции и линковки.
При разработке некоторой группы функций (для работы с матрицами, с комплексными числами и т.д.) бывает удобно объединить все эти функции в библиотеку функций. Библиотеки также создаются с помощью многофайловой компиляции и имеют расширение .lib.
В библиотеку может также включаться и файл с главной функцией main(), но тогда в такую программу будет затруднительно вносить изменения. Поэтому обычно функция main()не входит в библиотеки. Пример.
//файл arith.h
Float sum(float, float);
Float sub(float, float);
Float mul(float, float);
//файл sum.cpp
float sum(float a, float b){return a+b;}
//файл sub.cpp
float sub(float a, float b){return a-b;}
//файл mul.cpp
float mul(float a, float b){return a*b;}
4 Объекты и их атрибуты
Объект в языке С++ – это некоторая поименованная область памяти. Переменная – это частный случай такой поименованной области памяти.
Каждый объект (переменная, массив, указатель, структура, объединение, функция, класс, файл) имеет некоторые атрибуты.
Для объекта задается тип, который: 1) определяет требуемое для объекта количество памяти при ее начальном определении; 2) задает совокупность операций, допустимых для данного объекта; 3) интерпретирует двоичные коды значений при последующих обращениях к объекту; 4) используется для контроля типов для обнаружения случаев недопустимого присваивания.
Кроме типов, для объекта явно или по умолчанию определяются:
§ класс памяти (задает размещение объекта);
§ продолжительность существования объектов и их имен
§ область действия связанного с объектом идентификатора (имени);
§ область видимости объекта;
§ тип компоновки.
Класс памяти определяет размещение объекта в памяти и продолжительность его существования. Класс памяти в общем случае зависит от места расположения объекта в программе. Для явного задания класса памяти при определении (описании) объекта используются или подразумеваются по умолчанию следующие идентификаторы:auto, register, static, extern.
auto –автоматически выделяемая, локальная память. Этот класс памяти всегда присваивается по умолчанию всем объектам, определенным в блоке или в функции. Память под такие объекты запрашивается при каждом входе в блок или функцию, где они определены, и освобождается при выходе. При определении автоматических объектов они никак не инициализируются, т.е. их значение сразу не определено.
register –автоматически выделяемая, по возможности регистровая память.Спецификатор register аналогичен auto, но для размещения значений объектов используются по возможности регистры, а не участки основной памяти. В случае отсутствия регистровой памяти (регистры заняты другими данными) объекты класса registerобрабатываются как объекты класса auto.
static –статически выделяемая память. Этот класс памяти присваивается всем объектам, которые определены со спецификатором static.Память для таких объектов выделяется в начале выполнения программы и сохраняется до конца ее выполнения. Такие объекты по умолчанию инициализируются нулями. При явной инициализации значение заносится только первый раз.
extern –глобальная (внешняя) память.Этот класс памяти всегда присваивается по умолчанию всем объектам, определенным вне функций. Память для таких объектов выделяется в начале выполнения программы и сохраняется до конца ее выполнения. Такие объекты по умолчанию инициализируются нулями. По умолчания класс памяти extern имеют функции и все файлы.
Продолжительность существования объектов и их имен определяет период, в течение которого именам в программе соответствуют конкретные объекты в памяти. Три вида продолжительности: локальная (автоматическая), статистическая и динамическая.
Переменные с локальной (автоматической)продолжительностью существования – это объекты с автоматической и регистровой памятью. Они создаются при каждом входе в блок или функцию, где определены, и уничтожаются при выходе.
Объектам со статической продолжительностью существования память выделяется в начале выполнения программы и сохраняется до конца ее выполнения. Это объекты с классом памяти static и extern.По умолчанию статическую продолжительность существования имеют функции и все файлы.
Объекты с динамической продолжительностью существования создаются (получают память) и уничтожаются с помощью явных операций или функций в процессе выполнения программы. Созданный с помощью операции new и функции malloc()объект будет существовать до тех пор, пока память не будет явно освобождена с помощью операции delete или функции free().
Область действия объекта (имени) – это часть программы, в которой имя объекта может быть использовано для доступа к объекту. Бывает локальная и глобальная область действия. Зависит от того, где и как определены и описаны объекты: в блоке, в функции, в прототипе функции, в файле (модуль), в классе.
Файл является областью действия для всех глобальных объектов, т.е. для объектов, определенных вне любых функций и классов, от точки определения до конца программы. Для доступа к глобальным объектам выше определения их необходимо описать (для функции записать прототип). Если объект определен в блоке или функции, то область его действия – от точки определения до конца блока или функции. Область действия формальных параметров в определении функции – тело функции.
Объекты с локальной областью действия могут иметь статическую продолжительность существования. Например, если в функции описать переменную static int K,то область действия – локальная (тело функции), а продолжительность существования – статическая.
Область видимости объекта – это часть программы, в которой имя объекта может быть использовано для просмотра значений объекта. Это понятие появилось с возможностью повторных определений идентификатора внутри вложенных блоков (или функций). Чаще всего область действия имени и видимость связанного с ним объекта совпадают. Область видимости может превышать область действия, но наоборот невозможно. Например:
Void main()
{char oper; ---ù
int x=2,y=4,z; |
. . . |
{int k=5; –––ù |
int x=3; | |
x+=3 .. | |
} –––û |
…} –––û
В первом примере область видимости первого х весь фрагмент программы, второго хтолько блок. Область действия первого х весь фрагмент программы, за исключением блока, где определен второй х, в этом блоке область действиявторого х.
Тип компоновки, или тип связывания, определяет соответствие идентификатора конкретному объекту или функции в программе, исходный текст которой размещен в нескольких файлах (модулях). Все глобальные объекты имеют внешний вид компоновки. Файлы могут транслироваться отдельно. Такие файлы нуждаются во внешней компоновке. Для остальных объектов, которые находятся внутри файлов, используется внутренняя компоновка.
5 Ссылки
Ссылка – это еще одно имя существующего объекта.
Синтаксис определения ссылки следующий:
тип & имя_ссылки=инициализирующее_выражение;
гдеинициализирующее_выражение – это имя объекта, который уже имеет место в памяти, а тип– это тип этого объекта.
Например:
int x=20; //определена и проинициализирована переменнаях
int &pх=х; // определена ссылка, значением которой является
//адрес переменной х
float g=5.5;//определена и проинициализирована переменнаяg.
float &pg=g; //определена ссылка, значением которой является
//адрес переменной g
В определении ссылки символ & не является частью типа, т.е. xилиpх имеют типint. Имя_ссылки определяет место расположения инициализирующего объекта.
Ссылка – это адрес объекта, поэтому сходна с указателем. Но у ссылки имеются существенные особенности.
1) Обращение к ссылке аналогично обращению к переменной, для доступа к содержимому ссылки не нужно выполнять операцию разыменования. Например, допустимо:
double f=5.5;//определена и проинициализирована переменная f.
double &pf=f; //определена ссылка pf c адресом f
double *fp1=&f; //определен указатель fp1 c адресом f.
f=2.5; //переменной fприсвоено значение2.5
pf=1.5; //переменной fприсвоено значение1.5
*fp1=10.5; //переменной fприсвоено значение10.5
2) Размер ссылки – это размер переменной, связанной со ссылкой при инициализации, а размер указателя, в общем случае 4 байта.
int n1=sizeof(pf); // n1=8(типdouble)
int n2=sizeof(fp1);// n2=4(2 байта-сегмент, байта-смещение)
3) При определении ссылки ее необходимо инициализировать.
int x=100;
int &хref; // ошибка!!
int &хref=х;// правильно!!
4) Нельзя переопределить ссылку, т.е. изменить адрес переменной, на которую она ссылается.
int x1=50;
хref=х1; //переменной х присвоится значение х1.
// ссылкахref будет по-прежнему ссылаться х .
Cсылки – это не настоящие объекты, поэтому существуют ограничения при определении и использовании ссылок:
1) Ссылка не может иметь тип void.
2) Ссылку нельзя создать с помощью операции new, т.е. для ссылки нельзя выделить новый участок памяти.
3) Нельзя определить ссылку на другую ссылку и указатель на ссылку.
4) Нельзя создать массив ссылок.
В определении ссылки можно использовать модификатор const,т.е. допустимо определить ссылку на константу. Например:
double d=2.728282; //определили переменную
const double &ref_d=d; //определили ссылку на константу
d=0.1;//допустимо
ref_f=0.2; // ошибка!!
При определении ссылок обязательна инициализация, но при описании ссылки инициализация необязательна в таких случаях:
1) при описании внешних ссылок.
float & ref1; // ошибка!!нет инициализации ссылки
extern float &ref2; //допустимо!Инициализируется в другом блоке
2) в спецификации формальных параметров и при описании типа функции.
3) при описании ссылок на компоненты класса.
При использовании ссылки в качестве формального параметра обеспечивается доступ из тела функции к соответствующему фактическому параметру, т.е. к участку памяти, выделенному для фактического параметра. Ссылка обеспечивает те же возможности, что и параметр-указатель, но при этом не нужно применять операцию разыменования. В этом случае фактический параметр является обычной переменной, а не адресом, как для параметра-указателя.
Пример.
#include<stdio.h>
int f1_ref(int &a, int &b) {return a+b;}
int f2_ptr(int *a, int *b) {return *a-*b;}
int f3_ref(int &a, int &b) {a=100;return a+b;}
int f4_ref(const int&a, int &b)
{//a=100;//недопустимо
b=10; ;//допустимо
return a+b;}
Void main()
{int x=20,y=5,res1,res2,res3,res4;
res1=f1_ref(x,y);//х=20 y=5, обращение через ссылку
res2=f2_ptr(&x,&y); //х=20 y=5,обращение через указатель,
res3=f3_ref(x,y); //х=20 y=5, обращение через ссылку,
//затем х=100
res4=f4_ref(x,y); //х=100 y=5,обращение через ссылку,
//x нельзя изменить, y=10, res4=110
printf(“res1=%d res2=%d ”, res1,res2);
printf(“res3=%d res4=%d\n”, res3,res4);
}
В результате работы программы на экран выведется:
res1=25 res2=15 res3=105 res4=110
Литература
1. Подбельский В.В. Язык Си++: Учеб. пособие. – 5-е изд. – М.: Финансы и статистика, 2000. – 560 с.
2. Подбельский В.В. Практикум по программированию на языке Си: Учебн. пособие. – М.: Финансы и статистика, 2004. – 576 с.
3. Подбельский В.В., Фомин С.С. Программирование на языке Си: Учебн. пособие. – 2-е изд.,– М.: Финансы и статистика, 2002. – 600с.
4. Павловская Т.А. С/С++. Программирование на языке высокого уровня. СПб: Питер, 2003. – 461 с.
5. Касаткин А.И., Вальвачев А.Н. Профессиональное программирование на языке Си: От Turbo C Borland C++. – Мн.: Выш. Шк., 1992. – 240 с.
6. Березин Б.И., Березин С.Б. Начальный курс С и С++. – М.: ДИАЛОГ-МИФИ, 2002. – 288 с.
7. Фридман А.П. Основы объектно-ориентированного программирования на языке Си++. – М: Горячая линия. – Телеком. Радио и связь, 1999. – 208 с.
8. Уильям Топп, Уильям Форд. Структуры данных в С++. / Пер. с англ. – М.: ЗАО “Издательство БИНОМ”, 1999. – 816с.: ил.
9. Крячков А.В., Сухинина И.В., Томшин В.К. Программирование на С и С++. Практикум: Учебное пособие для вузов / А.В. Крячков, И.В. Сухинина, В.К. Томшин. – 2-е изд., исправ. – М.: Гарячая линия.-Телеком, 2000. – 344с.
10. Крупник А. Изучаем Си. – СПб.: Питер, – 2001. – 256с.
11. С/С++. Структурное программирование. Практикум / Т.А. Павловская, Ю.А. Щупак. – СПб.: Питер, 2002. – 240с.
12. Культин Н.Б. С /С++ в задачах и примерах.- СПб.: БХВ: Петербург, 2001. – 288с.
13. Х.М. Дейтел, П.Дж. Дейтел. Как программировать на С++. – 5-е изд. / Пер. с англ. – М.: ООО "Бином-Пресс", 2008.– 1456 с.
14. Шилдт, Герберт. Полный справочник по С++. – 4-е изд. / Пер. с англ. – М.: Вильямс, 2008. – 800 с.