технология программирования
.pdfС перечислениями можно работать как с целыми типами. Перечисления неявно могут преобразовываться в обычные целочисленные типы, но не наоборот.
#include <stdio.h> //Пример использования перечислений.
void main() |
|
|
{ |
|
|
enum test {t1,t2,t3,t4,t5} t; |
|
|
printf("t1=%d t4=%d\n", t1,t4); |
// |
0 3 |
t=t1; printf("t=t1 = %d\n ", t); |
// |
0 |
t++; printf("t++ = %d\n ", t); |
// |
1 |
t=t4; t-=2; |
|
|
printf("t4=%d t-=2 = %d\n ", t4, t); |
// |
3 1 |
t--; printf("t-- = %d\n ", t); |
// |
0 |
t=t4; t=t-t2; printf("t= = %d\n ", t); |
// |
2 |
} |
|
|
Нельзя использовать другие арифметические операции. Перечислимые константы могут быть объявлены анонимно (без имени).
Пример.enum{ false, true} boolean; объявляет переменную boolean с допустимыми значениями false, true.
Основное назначение перечислений улучшить читаемость программы.
2.8.5 Переименование типов 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 может быть объявлен любой тип, включая указатели, функции и массивы, структуры и объединения.
21
Пример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)для вложенных типов;
атакже, чтобы сделать программы переносимыми для различных целых типов.
2.9 Файлы
Прежде чем читать информацию из файла или записывать в него, нужно его открыть. В библиотеке <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);
}
22
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);}
Выполняется ввод символа, пока не достигнут конец файла;
23
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 *), в этом случае рекомендуется предусмотреть в программе реакцию. Например:
24
#include <stdio.h> //
#include <iostream.h> // для в\в в С++ int main()
{
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.
2.11 Директивы препроцессора
Условная компиляция
Можно избирательно компилировать части файла в зависимости от значения некоторого константного выражения или идентификатора.
25
Для этого служат директивы #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
26
}
Директивы #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, когда данный файл обрабатывался препроцессором.
27
Директива #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].
28
3 ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ
3.1 Объект
Наиболее важное понятие ООП – это объект. Объект - это логическая единица, которая содержит данные и правила (методы) обработки этих данных.
В С++ в качестве правил выступают функции, обрабатывающие эти данные. Внутри объекта данные и функции могут быть частными или приватными
(private), защищенным (protected) и общими (public) [4-10]. Объектно-
ориентированные языки характеризуютя абстракцией типов и обладают следующими важнейшими характеристиками:
1)инкапсуляцией,
2)наследованием,
3)полиморфизмом.
Инкапсуляция - это слияние данных и функций, работающих с этими данными, порождающее абстрактные типы данных, определяемые пользователем.
В терминологии С++ эти абстрактные типы данных называются классами. Концепция классов отражает принцип пакетирования: данные и функции, которые манипулируют этими данными, объединяются в единый пакет; устанавливаются специальные правила доступа к элементам пакета (функциям и данным). Максимально изолируются друг от друга интерфейс и реализация, отражающая всю совокупность действий, необходимых для поддержки интерфейса.
Наследование позволяет одним объектам приобретать свойства и атрибуты других объектов, строить иерархию объектов, переходя от более общего к частному, уточняя и конкретизируя объект. Наследование обеспечивает общность интерфейса между базовыми и производными классами. Базовый класс является родителем, а производный - ребенком. Родитель передает все лучшее, что он имеет,
аребенок наследует это лучшее и дополняет его чем-то своим, индивидуальным. Производный класс является более мощным, чем его базовый класс. Использование данного принципа позволяет развивать и совершенствовать программные продукты, используя в них идеи предшествующих программ (например, развитие библиотек классов) без изменения их исходного кода и без повторной компиляции.
Полиморфизм - это метод использования одного и того же имени для выполнения различных действий. Например, + - это сложение для базовых типов языка,
адля строк – это переопределение операций.
Другое применение полиморфизма связано с применением так называемых виртуальных функций. При этом использовуется одно и то же имя для выражения разных действий в базовом и производных классах. Полиморфизм в С++ поддерживается как во время компиляции, так и во время выполнения программы.
Одним из главных понятий языка является понятие класса. Класс - это расширенное понятие структуры в языке С. Он позволяет создавать типы и определять функции, которые задают поведение типа. Каждый представитель класса называется объектом.
Определение класса напоминает определение структуры за исключением
29
следующего:
– класс обычно содержит одну или несколько спецификаций доступа: private, protected, public;
–вместо ключевого слова struct может применяться ключевое слово class или union;
–класс обычно включает в себя функции (функции - элементы или методы) наряду с элементами-данными.
–обычно в классе имеются некоторые специальные функции, такие, как конструктор и деструктор. Конструктор имеет то же имя, что и класс. Деструктор имеет то же имя, но ему предшествует префикс-тильда ~.
Примеры объявления классов.
1) Определение класса Rect совпадает с определением структуры
struct Rect |
|
//прямоугольник |
{ |
|
|
int x1; |
// |
Элементы-данные Rect |
int y1; |
|
|
int x2; |
|
|
int y2; |
|
|
} |
|
|
2)Класс Point содержит как элементы-данные, так и элементы-функции,
атакже использует спецификаторы доступа.
struct Point |
|
// точка |
{ |
|
|
private: |
// Спецификатор частного доступа |
|
int x; |
// Элементы данных класса типа «Point» |
|
int y; |
// Члены-данные |
|
public: |
// Спецификатор общего доступа |
|
int GetX(); |
// Элементы-функции класса |
|
int GetY(); |
|
|
void SetX(int); |
|
|
void SetY(int); |
|
|
} |
|
|
3) Класс Line |
|
|
class Line |
|
|
{ |
|
|
Point pt1; |
// |
точка 1 // Элементы-данные |
Point pt2; |
// |
точка 2 |
int width; |
// толщина |
public: |
// Спецификатор общего доступа |
Line(int _x, int _y); |
// Функция-элемент: конструктор! |
~Line(); |
// Функция-элемент: деструктор! |
} |
|
30