Переименование типов typedef
Язык Си позволяет дать новое название уже существующим типам данных. Для этого используется ключевое словоtypedefи при этом новый тип не создается.
typedef < имя ранее определенного типа >< имя нового типа1>
[,<имя нового типа2>…];
Новое имя становится синонимом имен ранее определенным.
Например:
typedef float real; Теперь вместе fboat можно использовать real.
typedef char symbol;
Часто используется для переопределения структур.
Typedef struct st
{
char name [30];
char group [4];
int god;
} STUDENT;
Теперь для определения переменной можно записать st AN;
или STUDENT AN;
Область действия зависит от расположения оператора typedef. Если определение находится внутри функции, то область действия локальна и ограничена этой функцией. Если вне, то – глобальна.
С typedef может быть объявлен любой тип, включая указатели, функции и массивы, структуры и объединения.
Пример1.
typedef char arr [40]; // FIO- массив символов
arr FIO, *adres; // adres- указатель на массив символов
Это эквивалентно char FIO[40], *adres;
Пример2.
typedef int* Pi; //объявлен новый тип Pi-указатель на целое.
typedef void (*pfn) ( ); //объявлен новый тип pfn-указатель на функцию, не-
возвращающую значения, с любым списком типов аргументов.
typedef void (*pfnI) (int); // объявление типа pfnI-указатель на функцию с
одним аргументом типа int, невозвращающую значения
typedef void (*pptn[10]) (); //объявление типа pptn- массив из 10 указателей на
функцию, не возвращающую значения, с любым списком аргументов.
Объявление typedef применяется
1) для создания удобных распознаваемых имен часто встречающихся;
2) для вложенных типов;
а также, чтобы сделать программы переносимыми для различных целых типов.
Файлы
Прежде чем читать информацию из файла или записывать в него, нужно его открыть. В библиотеке <stdio.h>для этого имеется специальная функция
FILE *fopen(char *fname, char *mode);
где *fname– имя файла, *mode– режим (табл.3).Функция возвращает указа-тель (ссылку) на файл, который должен быть предварительно описан.
Например, FILE *fu; *fu–указатель на файловый тип
Пример. Объявим указатели на переменные файлового типа
FILE *uin, *uout; // (указатели на переменные файлового типа)
uin = fopen(“name1”, “r”); //открыть файл “name1” для чтения и далее
идентифицировать как uin
uout = fopen(”name2”, “w”); //открывается для записи и связывается с
идентификатором uout.
Если производиться открытие несуществующего файла, то он создается.
Для открытия файла с именем test рекомендуется метод, который позволяет определить ошибку при открытии файла.
# include <stdio.h>// работа с файлами и константа NULL (FILEOPEN.C)
# include <stdlib.h>//для ф-ии exit()
Void main()
{
FILE *fp;
if ((fp=fopen("test","w"))==NULL)
{
puts("Не могу открыть файл\n"); // печать строки
exit(1);
}
puts("Файл открыт\n");
}
Таблица 3
Таблица режимов для открываемого файла
Режим | Действие |
“r” | Открыть для чтения |
“w” | Создать для записи |
“a” | Открыть для добавленияв существующий файл |
“rb” | Открыть двоичный файл для чтения |
“wb” | Создать двоичный файл для записи |
“ab” | Открыть двоичный файл для добавления |
“r+” | Открыть для чтения и записи |
“w+” | Создатьдля чтения и записи |
“a+” | Открыть для добавления или создать для чтения и записи |
“r+b” | Открыть двоичный файл для чтения и записи |
“w+b” | Создать двоичный файл для чтения и записи |
“a+b” | Открыть двоичный файл для добавления или создать для чтения и записи |
“rt” | Открыть текстовыйфайл для чтения |
“wt” | Создать текстовый файл для записи |
“at” | Открыть текстовый файл для добавления |
“r+t” | Открыть открыть текстовый файл для чтения и записи |
“w+t” | Создать текстовый файл для чтения и записи |
“a+t” | Открыть текстовый файл для добавления или создать для чтения и записи |
В библиотеке <stdio.h> определены также следующие функции работы с файлами ( fp- указатель на файл, возвращаемый функцией):
fopen( ) - открытие файла;
int fclose(FILE *fp) - закрытиефайла. Возвращает нуль, если операция выполнена успешно и иное значение в противном случае. Функция является рекомендуемой, поскольку файлы при нормальном завершении закрываются автоматически;
int puts(int ch, FILE *fp) -записать символа типа intв поток. Возвращается записанный символ, если операция была успешной. Если произошла ошибка возвращается EOF;
int gets(FILE *fp) - прочесть символ типа intиз потока. Возвращает EOF, если достигнут конец файла, или произошла ошибка при чтении файла;
int feof(FILE *fp)- возвращает значение «истинно», если достигнут конец файла и нуль в противном случае;
Пример. while (!feof(fp)) {ch=getc(fp);}
Выполняется ввод символа, пока не достигнут конец файла;
int ferror(FILE *fp) - возвращает значение нуль, если обнаружена ошибка. Рекомендуется для обнаружения ошибок чтения и записи после каждой операции с файлами;
int fprintf(FILE *fp, const, char *string, …)- форматированная запись в файл. Содержит указатель на файл и управляющую строку со списком;
int fscanf(ILE *fp, const,char *string) –форматированное чтение из файла;
unsign fread(void *buf, int bytes, int c, FILE *fp) - читает блок данных из потока (буферный обмен); buf - указатель на область памяти, откуда проис-ходит обмен информации. с- количество единиц записи длиной bytes, которая будит считываться.
unsign fwrite(void *buf, int bytes, int c, FILE *fp) - пишется блок данных в поток;
int remove(char *filename)- уничтожается файл, возвращается нуль при успешной операции;
unsign rewind( FILE *fp)- устанавливается указатель на начало файла;
int fseck( FILE *fp, long numbyte, int orig) - установить указатель позиции файла в заданное место, numbyte - количество байт от точки отсчета (0,1,2), orig – макрос 0 - начало, 1- текущая позоция, 2 - конец;
void abort() - <stdlib.h> - немедленное прекращение программы без закрытия файлов и без освобождения буферов.
2.10 Операторы динамического распределения памяти в С++
В С++ введены 2 «интеллектуальных» оператора new и delete, освобождающие про-граммиста от необходимости явно использовать библиотечные функции malloс, calloс и free.
Оператор new выделяет блок памяти, необходимый для размещения переменной или массива (необходимо указывать тип и, в случае массива, размерность), и при этом можно присваивать вновь созданной переменной начальное значение.
New type_name [(инициатор)]; илиNew (type_name [(инициатор)]);
Возвращает указатель на объект а= new int[n] для неизвестного числа элементов. Оператор delete освобождает ранее выделенную память. Размер занятого блока для правильной работы delete, записывается в его начало и обычно занимает дополнительно 4 байта.
Примечание: Следует помнить, что реальный размер занятого блока не произволен, а кратен определенному числу байт (в С++3.0-16), поэтому, с точки зрения расхода памяти невыгодно резервировать много блоков под небольшие объекты.
В случае успешного выполнение new возвращает адрес начала занятого блока памяти, увеличенный на количество байт, занимаемых информацией о размере блока (т.е. возвращает адрес созданной переменной или адрес нулевого элемента созданного массива). Когда new не может выделить требуемую память, он возвращает значение (void *), в этом случае рекомендуется предусмотреть в программе реакцию. Например:
# include <stdio.h> //
# include <iostream.h> // для в\в в С++
intmain()
{
int *u_i;
double *u_d;
char *string;
int str_len=80;
u_i=new int; //Зарезервировать место под переменную типа int и
//присвоить u_i ее адрес (Значение *u_i не определено)
u_d=new double(3.1415);// -"- и *u_d инициализируется знач. 3.1415
string=new char[str_len];
if (!(u_i && u_d && string))
{
cout<<"Не хватает памяти для всех динамически"
"размещаемых переменных!";
return 1;
}
string[0]='Y';
string[1]='e';
string[2]='s';
string[3]='!'; string[4]='\0';
cout<<"\n u_i="<<u_i<<" случайное значение *u_i="<<*u_i ;
delete u_i; //Освободить блок памяти, на кот указывает u_i
cout<<"\n u_d="<<u_d<<" *u_d="<< *u_d ;
delete u_d; //Освободить блок памяти, на кот указывает u_D
printf("\n string=%p ", string);
cout<<" string contents="<<string<<"\n";
delete string; // Можно и delete [str_len] string;
return 0; // но выдается предупр: размер массива игнорируется
}
Если указатель, на который действует оператор delete, не содержит адреса блока, зарегистрированного ранее оператором new, или же не равен NULL, то последствия будут непредсказуемыми.
Помимо проверки возвращаемого значения, в С++ для обработки ситуации нехватки памяти, программист может определить специальную функцию обработки, которая будет вызываться при неудачном выполнение оператора new и «пытаться» изыскать необходимую память, либо выдать сообщение и завершить выполнение программы библиотечной функцией exit или abort.
Директивы препроцессора
Условная компиляция
Можно избирательно компилировать части файла в зависимости от значения некоторого константного выражения или идентификатора.
Для этого служат директивы #if, #elif, #else, #endif.
Синтаксис условной конструкции:
#if <выражение1>
последовательность операторов //компилировать, если <выражение1> истинно.
#elif <выражение2>
операторы//компилировать, если <выражение1> ложно, а <выражение2> истинно
#elif <выражение3>
последовательность операторов // компилировать, если <выражения1 и 2>
ложны, а <выражение3> истинно
#else
операторы // компилировать, если все выражения ложны.
#endif // конец для условной компиляции if.
Правило выполнения условных директив.
1. Для каждого #if должна быть соотвествующая #endif.
2. Директивы #elif и #else являются опциональными (исключают друг друга).
3. Число директив #elif между #if и #endif не ограничено. Директива #else должна быть одна и находиться перед #endif.
4. Аналогично оператору if ...else компилируется та секция, которая соответс-твует первому истинному выражению.
5. Если ни одно из выражений не истинно, то компилируется секция, следую-щая за частью #else.
6. Значение выражения должно быть целой константой; в выражении нельзя использовать операцию sizeof и преобразование типов.
Оператор defined или знак операции препроцессора
Он может применяться в директивах #if и #elif и позволяет проверить, был ли определен идентификатор или макрос.
void ShowMessage(char *msg)
{
# if defined(DOS_target)
puts(msg);
# else MessageBox(NULL,msg,''MSG'',MB_OK);
# endif
}
Если макрос DOS_target определен, то функция ShowMessage использует для вывода сообщения функцию puts, в противном случае компилируется функция MessageBox для специального вывода.
Можно применять логическую операцию ! для проверки того, что иденти-фикатор не определен. Функцию void ShowMessage(DOS_target) можно перепи-сать таким образом
{ # if !defined(DOS_target)
MessageBox(NULL,msg,''MSG'',MB_OK);
# else
puts(msg);
# endif
}
Директивы #ifdef и #ifndef
Эти директивы эквивалентны соответственно #if defined и #if !defined и позволяют проверить, определен идентификатор в данный момент или нет. Однако применение оператора defined является более предпочтительным, т.к. он позволяет проверить сразу несколько макросов в сложных логических выражениях.
# ifdef DOS_target можно упростить до # if defined (DOS_target)
# ifndef NDEBUG &&!defined(NDEBUG)
puts(msg); puts(msg);
# endif # endif
# endif
Данные директивы требуют наличия соответствующей директивы #endif, между ними может (но не обязательно) размещаться директива #else или #elif.
Директива #error
Позволяет выдавать во время компиляции сообщение об ошибке.
Ее структура: #error <сообщение об ошибке>
Сообщение может включать в себя идентификаторы макросов, которые будут расширены препроцессором. Она применяется, обычно, когда не был определен необходимый идентификатор.
Директива #line
Позволяет изменить внутренний счетчик строк компилятора и имеет
Структуру: #line номер строки["имя файла"]
Номер строки должен быть целой константой. В строке может присутство-вать опциональное имя файла. Эта директива изменяет предопределенный макрос _ _LINE_ _. Если присутствует имя файла, то меняется макрос
_ _FILE_ _. Директива используется, чтобы в процессе трансляции заменить наименование текущего файла исходного текста программы и (или) номер строки этого текста.
Предопределенные макросы
Компилятор автоматически определяет некоторые макросы ANSI.(ANSI – официальное название стандарта языка Си, препроцессора и библиотеки поддержки выполнения программы, принятое Американским Национальным институтом Стандартов)
__DATE__ - строка, представляющая дату, когда данный файл обрабатывался препроцессором (форма даты mm dd yyyy).
__FILE__- строка, представляющая имя текущего файла в двойных кавычках.
__LINE__- целое, представляющее текущий номер строки.
__STDC__- значение является константой, равной 1, если установлено со-ответствие со стандартом ANSI, в противном случае макрос не определен.
__TIME__- строка, представляющая время в форме hh:mm:ss, когда данный файл обрабатывался препроцессором.
Директива #pragma
Позволяет управлять специфическими возможностями компилятора.
Ее синтаксис: #pragma <директива>
Если реализация системы программирования, обнаружив pragma, ее не узнает, то система ее игнорирует. (Стандартных прагм не существует.)
Директивы #pragma, поддерживаемые компилятором C++:
аrgsusd - подавляет предупреждающее сообщение о том, что параметр xхх не ис- пользован для функции, следующей за директивой;
exit - позволяет указать функцию, которая должна быть вызвана перед завер-шением программы;
еxtref - заставляет компилятор включить ссылку на неиспользованную внеш-нюю переменную или функцию;
hdrfile специфицирует имя заранее откомпилированного файла-заголовка;
hdrignore - т.к. макросы и типы, определяемые в заголовочном файле, могут изменяться, когда определяется другой макрос, компилятор не использует информацию из перекомпилированного заголовка, если встречает директиву условной компиляции. Данная директива указывает, что заголовок должен использоваться, если встречается в директиве условной компиляции;
hdrstop - предписывает компилятору не включать дальнейшую информацию, перекомпилированную в заголовочный файл;
inline - указывает, что компиляция текущего модуля должна производиться через ассемблер;
intrinsic - эта директива может быть использована, чтобы разрешить или запре-тить генерацию inline-кода для встроенной функции; (встроенная функция - это библиотечная процедура, для которой компилятор генерирует inline-код вместо вызова библиотеки);
obsolete - приводит к тому, что компилятор генерирует сообщение-предупреж-дение о том, что имя файла является устаревшей функцией. Ее можно исполь-зовать, чтобы сообщить другим программистам, что улучен код и предусмотрена для данной задачи новая функция;
option – позволяет включить в код опции командной строки компилятора;
startup – является дополнительной к #pragma exit, позволяет указать функцию, которая должна выполняться до функции main;
warn – позволяет выборочно разрешать или подавлять предупреждающее сообще-ние. Если предупреждению предшествует знак «+», то выдача сообщения разре- шается, если знак «-» , то запрещается.
Некоторые компиляторы, в частности ANSI, узнают директиву pragma, которая указывает на то, как плотно упакованы смежные члены в структуру, например: #pragma pack (n) , где n может быть 1,2 или 4, указывая, что имеет место выравнивание на границу байта, слова или двойного слова.
В языке С/С++ имеются также директивы подключения библиотек # include и макроподстановок #define [7, 8].