Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Теория ответы.doc
Скачиваний:
39
Добавлен:
24.12.2018
Размер:
1.61 Mб
Скачать

18.Графика. Возможности стандартной библиотеки graphics.H. Алгоритмы построения графических изображений.

Программирование в графическом режиме

В данном разделе приводится краткое изложение функций, используемых в графическом режиме.

Borland C++ имеет отдельную библиотеку с более чем 70 графическими функциями, начиная от функций высокого уровня (таких как setviewport, bar3d и drawpoly) и кончая бит-ориентированными функциями (типа getimage и putimage). Графическая библиотека поддерживает многочисленные типы линий и заполнителей, а также предоставляют вам различные текстовые шрифты, которые вы можете изменять по размерам, способу выравнивания, а также ориентировать их либо по горизонтали, либо по вертикали.

Эти функции находятся в библиотечном файле GRAPHICS.LIB, а их прототипы - в файле заголовка graphics.h. Кроме этих двух файлов, в состав графического пакета входят драйверы графических устройств (файлы *.BGI) и символьные шрифты (файлы *.CHR). Эти дополнительные файлы рассматриваются в следующих разделах.

Если вы используете компилятор BCC.EXE, нужно в командной строке указать библиотеку GRAPHICS.LIB. Например, если ваша программа, MYPROG.C, использует графику, то командная строка компилятора BCC должна иметь вид:

BCC MYPROG GRAPHICS.LIB

При построении программы компоновщик автоматически компонует графическую библиотеку С++.

Поскольку графические функции используют указатели far, графика в случае модели памяти tiny не поддерживается.

Графическая библиотека только одна и не имеет версий по моделям памяти (по сравнению со стандартными библиотеками CS.LIB, CC.LIB, CM.LIB и т.д., которые зависят от используемой модели памяти). Каждая функция в файле GRAPHICS.LIB является far (дальней) функцией, а графические функции, использующие указатели работают с дальними указателями. Для правильной работы графических функций в каждом использующем графические функции модуле требуется директива #include graphics.h.

Функции библиотеки graphics

Графические функции Borland C++ делятся на несколько категорий:

функции управления графической системой;

функции черчения и заполнения;

функции манипулирования экранами и графическими окнами;

функции вывода текстов;

функции управления цветами;

функции обработки ошибок;

функции запроса состояния.

19.Понятие и назначение файлов. Открытие файла, чтение и запись информации в файл, закрытие файла, проверка достижения конца файла.

Файловый ввод-вывод

В С++-системе ввода-вывода также предусмотрены средства для выполнения соответствующих операций с использованием файлов. Файловые операции ввода-выволя можно реализовать после включения в программу заголовка <fstream>, в котором определены все необходимые для этого классы и значения.

Как открыть и закрыть файл

В C++ файл открывается путем связывания его с потоком. Как вы знаете, существуют потоки трех типов: ввода, вывода и ввода-вывода. Чтобы открыть входной поток, необходимо объявить потоковый объект типа ifstream. Для открытия выходного потока нужно объявить поток класса ofstream. Поток, который предполагается использовать для операций как ввода, так и вывода, должен быть объявлен как объект класса fstream. Например, при выполнении следующего фрагмента кода будет создан входной поток, выходной и поток, позволяющий выполнение операций в обоих направлениях.

ifstream in; // входной поток

ofstream out; // выходной поток

fstream both; // поток ввода-вывода

Чтобы открыть файл, используйте функцию open ().

Создав поток, его нужно связать с файлом. Это можно сделать с помощью функции open (), причем в каждом из трех потоковых классов есть своя функция-член open (). Представим их прототипы.

void ifstream::open(const char * filename,

ios::openmode mode = ios::in);

void ofstream::open(const char * filename,

ios::openmode mode = ios::out I ios::trunc);

void fstream::open(const char * filename,

ios::openmode mode = ios::in | ios::out);

Здесь элемент filename означает имя файла, которое может включать спецификатор пути. Элемент mode определяет способ открытия файла. Он должен принимать одно или несколько значений перечисления openmode, которое определено в классе ios.

ios

app

ios

ate

ios

binary

ios

in

ios

out

ios

trunc

Несколько значений перечисления openmode можно объединять посредством логического сложения (ИЛИ).

На заметку. Параметр mode для функции fstream:: open () может не устанавливаться по умолчанию равным значению in | out (это зависит от используемого компилятора). Поэтому при необходимости этот параметр вам придется задавать в явном виде.

Включение значения ios :: app в параметр mode обеспечит присоединение к концу файла всех выводимых данных. Это значение можно применять только к файлам, открытым для вывода данных. При открытии файла с использованием значения ios::ate поиск будет начинаться с конца файла. Несмотря на это, операции ввода-вывода могут по-прежнему выполняться по всему файлу.

Значение ios :: in говорит о том, что данный файл открывается для ввода данных, а значение ios :: out обеспечивает открытие файла для вывода данных.

Значение ios :: binary позволяет открыть файл в двоичном режиме. По умолчанию все файлы открываются в текстовом режиме. Как упоминалось выше, в текстовом режиме могут происходить некоторые преобразования символов (например, последовательность, состоящая из символов возврата каретки и перехода на новую строку, может быть преобразована в символ новой строки). При открытии файла в двоичном режиме никакого преобразования символов не выполняется. Следует иметь в виду, любой файл, содержащий форматированный текст или еще необработанные данные, можно открыть как в двоичном, так и в текстовом режиме. Единственное различие между этими режимами состоит в преобразовании (или нет) символов.

Использование значения ios::trunc приводит к разрушению содержимого файла, имя которого совпадает с параметром filename, а сам этот файл усекается до нулевой длины. При создании выходного потока типа ofstream любой существующий файл с именем filename автоматически усекается до нулевой длины.

При выполнении следующего фрагмента кода открывается обычный выходной файл.

ofstream out; out.open("тест");

Поскольку параметр mode функции open () по умолчанию устанавливается равным значению, соответствующему типу открываемого потока, в предыдущем примере вообще нет необходимости задавать его значение.

Не открытый в результате неудачного выполнения функции open () поток при использовании в булевом выражении устанавливается равным значению ЛОЖЬ. Этот факт может служить для подтверждения успешного открытия файла, например, с помощью такой if-инструкции.

if(!mystream) {

cout << "He удается открыть файл.\n"; // обработка ошибки

Прежде чем делать попытку получения доступа к файлу, следует всегда проверять результат вызова функции open ().

Можно также проверить факт успешного открытия файла с помощью функции is_open (), которая является членом классов fstream, if stream и o stream. Вот ее прототип.

bool is_open();

Эта функция возвращает значение ИСТИНА, если поток связан с открытым файлом, и ЛОЖЬ — в противном случае. Например, используя следующий код, можно узнать, открыт ли в данный момент потоковый объект mystream.

if(!mystream.is_open()) {

cout << "Файл не открыт.\n";

Хотя вполне корректно использовать функцию open () для открытия файла, в большинстве случаев это делается по-другому, поскольку классы ifstream, ofstream и fstream включают конструкторы, которые автоматически открывают заданный файл. Параметры у этих конструкторов и их значения (действующие по умолчанию) совпадают с параметрами и соответствующими значениями функции open (). Поэтому чаще всего файл открывается так, как показано в следующем примере.

ifstream mystream("myfile"); // файл открывается для ввода

Если по какой-то причине файл открыть невозможно, потоковая переменная, связываемая с этим файлом, устанавливается равной значению ЛОЖЬ.

Чтобы закрыть файл, вызовите функцию close ().

Чтобы закрыть файл, используйте функцию-член close (). Например, чтобы закрыл файл, связанный с потоковым объектом mystream, используйте такую инструкцию.

mystream.close() ;

Функция close () не имеет параметров и не возвращает никакого значения.

Чтение и запись текстовых файлов

Проще всего считывать данные из текстового файла или записывать их в него с помощью операторов "<<" и ">>". Например, в следующей программе выполняется запись в файл test целого числа, значения с плавающей точкой и строки.

// Запись данных в файл,

#include <iostream>

#include <fstream>

using namespace std;

int main(){

ofstream out("test");

if(!out) {

cout << "He удается открыть файл.\n"; return 1; }

out << 10 << " " << 123.23 << "\n";

out << "Это короткий текстовый файл.";

out.close (); return 0;}

Следующая программа считывает целое число, float-значение, символ и строку из файла, созданного при выполнении предыдущей программой.

// Считывание данных из файла,

#include <iostream>

#include <fstream>

using namespace std;

int main() {

char ch;

int i;

float f;

char str[80] ;

ifstream in ("test"); if(!in) {

cout << "He удается открыть файл.\n";

return 1; }

in >> i;

in >> f;

in >> ch;

in >> str;

cout << i << " " << f << " " << ch << "\n"; cout << str;

in.close() ;

return 0; }

Следует иметь в виду, что при использовании оператора ">>" для считывания данных из текстовых файлов происходит преобразование некоторых символов. Например, "пробельные" символы опускаются. Если необходимо предотвратить какие бы то ни было преобразования символов, откройте файл в двоичном режиме доступа. Кроме того, помните, что при использовании оператора ">>" для считывания строки ввод прекращается при обнаружении первого "пробельного" символа.

Неформатированный ввод-вывод данных в двоичном режиме

Форматированные текстовые файлы (подобные тем, которые использовались в предыдущих примерах) полезны во многих ситуациях, но они не обладают гибкостью неформатированных двоичных файлов. Поэтому C++ поддерживает ряд функций файлового ввода-вывода в двоичном режиме, которые могут выполнять операции без форматирования данных.

Для выполнения двоичных операций файлового ввода-вывода необходимо открыть файл с использованием спецификатора режима ios::binary. Необходимо отметить, что функции обработки неформатированных файлов могут работать с файлами, открытыми в текстовом режиме доступа, но при этом могут иметь место преобразования символов, которые сводят на нет основную цель выполнения двоичных файловых операций.

Функция get () считывает символ из файла, а функция put () записывает символ в файл.

В общем случае существует два способа записи неформатированных двоичных данных в файл и считывания их из файла. Первый состоит в использовании функции-члена put () (для записи байта в файл) и функции-члена get () (для считывания байта из файла). Второй способ предполагает применение "блочных" С++-функций ввода-вывода read () и write (). Рассмотрим каждый способ в отдельности.

Использование функций get () и put ()

Функции get () и put () имеют множество форматов, но чаще всего используются следующие их версии:

istream &get (char &ch);

ostream &put(char ch);

Функция get() считывает один символ из соответствующего потока и помещает его значение в переменную ch. Она возвращает ссылку на поток, связанный с предварительно открытым файлом. При достижении конца этого файла значение ссылки станет равным нулю. Функция put() записывает символ ch в поток и возвращает ссылку на этот поток.

При выполнении следующей программы на экран будет выведено содержимое любого заданного файла. Здесь используется функция get().

// Отображение содержимого файла с помощью функции get().

#include <iostream>

#include <fstream>

using namespace std;

int main(int argc, char *argv[]) {

char ch;

if(argc!=2) {

cout << "Применение: имя_программы <имя_файла>\n";

return 1;}

ifstream in(argv[l], ios::in | ios::binary); if(!in) {

cout << "He удается открыть файл.\n;

return 1;}

while(in) { // При достижении конца файла потоковый

// объект in примет значение false,

in.get(ch);

if (in) cout << ch;

in.close () ;

return 0; }

При достижении конца файла потоковый объект in примет значение ЛОЖЬ, которое остановит выполнение цикла while.

Существует более короткий вариант цикла, предназначенного для считывания и отображения содержимого файла.

while(in.get(ch)) cout << ch;

Этот вариант также имеет право на существование, поскольку функция get() возвращает потоковый объект in, который при достижении конца файла примет значение false.

В следующей программе для записи строки в файл используется функция put().

// Использование функции put() для записи строки в файл.

#include <iostream>

#include <fstream>

using namespace std;

int main() {

char *p = "Всем привет!";

ofstream out(test", ios::out | ios::binary);

if (!out) {cout << "He удается открыть файл\n";return 1; }

while(*p) out.put(*p++);

out.close () ; return 0;}

Считывание и запись в файл блоков данных

Чтобы считывать и записывать в файл блоки двоичных данных, используйте функции-члены read() и write (). Их прототипы имеют следующий вид.

istream &read(char *buf, streamsize пит);

ostream &write(const char *buf, int streamsize пит);

Функция read () считывает пит байт данных из связанного с файлом потока и помещает их в буфер, адресуемый параметром buf. Функция write () записывает num байт данных в связанный с файлом поток из буфера, адресуемого параметром buf. Как упоминалось выше, тип streamsize определен как некоторая разновидность целочисленного типа. Он позволяет хранить самое большое количество байтов, которое может быть передано в процессе любой операции ввода-вывода.

Функция read () вводит блок данных, а функция write () выводит его.

При выполнении следующей программы сначала в файл записывается массив целых чисел, а затем он же считывается из файла.

// Использование функций read() и write().

#include <iostream>

#include <fstream>

using namespace std;

int main() {

int n[5] = {1, 2, 3, 4, 5};

register int i;

ofstream out("test", ios::out | ios::binary);

if(!out) {cout << "He удается открыть файл.\n";return 1;}

out.write ( (char *) &n, sizeof n); out.close();

for(i=0; i<5; i++) // очищаем массив

n[i] = 0;

ifstream in ("test", ios : :in | ios::binary);

if(!in) {cout << "He удается открыть файл.\n";return 1; }

in.read((char *) &n, sizeof n);

for(i=0; i<5; i++) // Отображаем значения, считанные из файла,

cout << n[i] << " ";

in.close (); return 0; }

Обратите внимание на то, что в инструкциях обращения к функциям read () и write () выполняются операции приведения типа, которые обязательны при использовании буфера, определенного не в виде символьного массива.

Функция gcount () возвращает количество символов, считанных при выполнении последней операции ввода данных.

Если конец файла будет достигнут до того, как будет считано пит символов, функция read () просто прекратит выполнение, а буфер будет содержать столько символов, сколько удалось считать до этого момента. Точное количество считанных символов можно узнать с помощью еще одной функции-члена gcount (), которая имеет такой прототип.

streamsize gcount();

Функция gcount () возвращает количество символов, считанных в процессе выполнения последней операции ввода данных.

Обнаружение конца файла

Обнаружить конец файла можно с помощью функции-члена eof (), которая имеет такой прототип.

bool eof ();

Эта функция возвращает значение true при достижении конца файла; в противном случае она возвращает значение false.

Функция eof () позволяет обнаружить конец файла.

В следующей программе для вывода на экран содержимого файла используется функция eof().

//Обнаружение конца файла с помощью функции eof() .

#include <iostream>

#include <fstream>

using namespace std;

int main(int argc, char *argv[]) {

char ch;

if(argc!=2) {cout << "Применение: имя_программы <имя_файла>\n";return 1; }

ifstream in(argv[l], ios::in | ios::binary);

if(!in) {cout « "He удается открыть файл.\n";return 1; }

while(!in.eof()) { // использование функции eof()

in.get(ch);

if(!in.eof()) cout << ch; }

in.close ();

return 0; }

20.Реализация модульности программ в С++: директивы препроцессора, классы памяти. Создание собственной библиотеки программиста.

Модульное программирование

Модульность — фундаментальный аспект всех успешно работающих крупных систем.

Б. Страуструп

С увеличением объема программы становится невозможным удерживать в памяти все детали. Естественным способом борьбы со сложностью любой задачи является ее разбиение на части. В C++ задача может быть разделена на более простые и обозримые с помощью функций, после чего программу можно рассматривать в более укрупненном виде — на уровне взаимодействия функций. Это важно, поскольку человек способен помнить ограниченное количество фактов. Использование функций является первым шагом к повышению степени абстракции программы и ведет к упрощению ее структуры.

Разделение программы на функции позволяет также избежать избыточности кода, поскольку функцию записывают один раз, а вызывать ее на выполнение мож­но многократно из разных точек программы. Процесс отладки программы, содержащей функции, можно лучше структурировать. Часто используемые функции можно помещать в библиотеки. Таким образом создаются более простые в отладке и сопровождении программы.

Следующим шагом в повышении уровня абстракции программы является группировка функций и связанных с ними данных в отдельные файлы (модули), компилируемые раздельно. Получившиеся в результате компиляции объектные модули объединяются в исполняемую программу с помощью компоновщика. Разбиение на модули уменьшает время перекомпиляции и облегчает процесс отладки, скрывая несущественные детали за интерфейсом модуля и позволяя отлаживать программу по частям (или разными программистами).

Модуль содержит данные и функции их обработки. Другим модулям нежелательно иметь собственные средства обработки этих данных, они должны пользоваться для этого функциями первого модуля. Для того чтобы использовать модуль, нужно знать только его интерфейс, а не все детали его реализации. Чем более независимы модули, тем легче отлаживать программу. Это уменьшает общий объем информации, которую необходимо одновременно помнить при отладке. Разделение программы на максимально обособленные части является сложной задачей, которая должна решаться на этапе проектирования программы.

Скрытие деталей реализации называется инкапсуляцией. Инкапсуляция является ключевой идеей как структурного, так и объектно-ориентированного программирования. Пример инкапсуляции — помещение фрагмента кода в функцию и передача всех необходимых ей данных в качестве параметров. Чтобы использовать такую функцию, требуется знать только ее интерфейс, определяемый заголовком (имя, тип возвращаемого значения и типы параметров). Интерфейсом модуля являются заголовки всех функций и описания доступных извне типов, переменных и констант. Описания глобальных программных объектов во всех модулях программы должны быть согласованы.

Модульность в языке C++ поддерживается с помощью директив препроцессора, пространств имен, классов памяти, исключений и раздельной компиляции (строго говоря, раздельная компиляция не является элементом языка, а относится к его реализации).

Директивы препроцессора

Препроцессором называется первая фаза компилятора. Инструкции препроцессора называются директивами. Они должны начинаться с символа #, перед которым в строке могут находиться только пробельные символы (с. 18).

Директива #include

Директива #include <имя_файла> вставляет содержимое указанного файла в ту точку исходного файла, где она записана. Включаемый файл также может содержать директивы #inc1ude. Поиск файла, если не указан полный путь, ведется в стандартных каталогах включаемых файлов. Вместо угловых скобок могут использоваться кавычки (" ") — в этом случае поиск файла ведется в каталоге, содержащем исходный файл, а затем уже в стандартных каталогах.

Директива #include является простейшим средством обеспечения согласованности объявлений в различных файлах, она включает в них информацию об интерфейсе из заголовочных файлов.

Заголовочные файлы обычно имеют расширение . h и могут содержать:

  • определения типов, констант, встроенных функций, шаблонов, перечислений;

  • объявления функций, данных, имен, шаблонов;

  • пространства имен;

  • директивы препроцессора;

  • комментарии.

В заголовочном файле не должно быть определений функций и данных. Эти правила не являются требованием языка, а отражают разумный способ использования директивы.

При указании заголовочных файлов стандартной библиотеки расширение .h можно опускать1. Это сделано для того, чтобы не ограничивать способы их хранения. Для каждого файла библиотеки С с именем <name.h> имеется соответствующий файл библиотеки C++ <cname>, в котором те же средства описываются в пространстве имен std. Например, директива #include <cstdio> обеспечивает те же возможности, что и #include <stdio.h>, но при обращении к стандартным функциям требуется указывать имя пространства имен std (см. раздел «Поименованные области», с. 99).

Директива #define

Директива #define определяет подстановку в тексте программы. Она используется для определения:

  • символических констант: #define имя текст_подстановки

  • (все вхождения имени заменяются на текст подстановки);

  • макросов, которые выглядят как функции, но реализуются подстановкой их текста в текст программы: #define имя( параметры ) текст_подстановки

  • символовt управляющих условной компиляцией. Они используются вместе с директивами #i fdef и #i fndef.

Формат: #define имя

Примеры:

#define VERSION 1

#define VASIA "Василий Иванович"

#define MAX(x.y) ((x)>(y)?(x):(y))

#define MUX

Имена рекомендуется записывать прописными буквами, чтобы зрительно отличать их от имен переменных и функций. Параметры макроса используются при макроподстановке, например, если в тексте программы используется вызов макроса

у = MAX(suml, sum2);, он будет заменен на

у = ((suml)>(sum2)?(suml):(sum2));

Отсутствие круглых скобок может привести к неправильному порядку вычисления, поскольку препроцессор не оценивает вставляемый текст с точки зрения синтаксиса. Например, если к макросу #define sqr(x) (x*x) обратиться как sqr(y+l), в результате подстановки получится выражение (у+1*у+1).

Макросы и символические константы унаследованы из языка С, при написании программ на C++ их следует избегать. Вместо символических констант предпочтительнее использовать const или enum, а вместо макросов — встроенные функции или шаблоны.

1 Старые версии компиляторов могут не поддерживать это свежее требование стандарта.

Директивы условной компиляции

Директивы условной компиляции #if, #ifdef и #ifndef применяются для того, чтобы исключить компиляцию отдельных частей программы. Это бывает полезно при отладке или, например, при поддержке нескольких версий программы для различных платформ.

Формат директивы

#if: #if константное_выражение

[ #elif константное_выражение

]

[#elif константное_выражение

]

[#else

]

#endif

Количество директив #elif — произвольное. Исключаемые блоки кода могут содержать как описания, так и исполняемые операторы. Пример условного включения различных версий заголовочного файла:

#if VERSION == 1

#define INCFILE "versl.h"

#elif VERSION == 2

#define INCFILE "vers2.h" /* и так далее */

#else

#define INCFILE "versN.h"

#endif

#include INCFILE

В константных выражениях может использоваться проверка, определена ли константа, с помощью defined(HMfl_KOHCTaHTbi), например:

#if defined(--BORLANDC__) && __BORLANDC__ == 0x530 // BC5.3:

typedef istream_iterator<int. char, char_traits<char>, ptrdiff_t>

istream_iter;

#elif defined(__BORLAND_) // BC5.2:

typedef i stream_iterator<int. ptrdiff_t> i streamjter;

#else // VC5.0:

typedef istream_iterator<int> istreamjiter;

#endif

Другое назначение директивы — временно закомментировать фрагменты кода, например:

#if 0

int i. j;

double x. у;

#endif

Поскольку допускается вложенность директив, такой способ весьма удобен.

Наиболее часто в программах используются директивы #ifdef и #ifndef, позволяющие управлять компиляцией в зависимости от того, определен ли с помощью директивы #define указанный в них символ (хотя бы как пустая строка, например, #define 32_BIT_SUPP0RT):

#ifdef символ

// Расположенный ниже код компилируется, если символ определен

#i fndef символ

// Расположенный ниже код компилируется, если символ не определен

Действие этих директив распространяется до первого #elif, #else или #endif.

Директива #ifndef часто применяется для того, чтобы обеспечить включение заголовочного файла только один раз:

#ifndef HEADERJNCLUDED

#include "myheader.h"

#define HEADERJNCLUDED

#endif

Директива #undef

Директива #undef имя удаляет определение символа. Используется редко, например, для отключения какой-либо опции компилятора.