Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Прикладное программирование на CC++ с нуля до мультимедийных и сетевых приложений
.pdf
Перегрузка функций и операторов 81
COMP COMP::operator-(COMP r)
{
COMP tmp;
tmp.a = a-r.a; tmp.b = b-r.b;
return tmp;
}
COMP COMP::operator*(COMP r)
{
COMP tmp;
tmp.a = a*r.a-b*r.b; tmp.b = a*r.b+b*r.a;
return tmp;
}
Конструктор с параметрами выполняет присваивания вещественной и
мнимой частям создаваемого комплексного числа значений, указанных в качестве аргументов конструктора. Конструктор без параметров обнуляет вещественную и мнимую часть создаваемого числа.
Перегрузку рассмотрим на примере операции сложения. Как известно,
при сложении двух комплексных чисел попарно складываются их вещественные и мнимые части. Внутри тела функции-оператора организуется локальная переменная tmp — комплексное число. Следующие два присваивания добавляют к левому операнду — первому слагаемому правый операнд —
второе слагаемое. Результат остается в промежуточной переменной и возвращается инструкцией return. Аналогично перегружается оператор вычитания
и, в соответствии с правилом умножения комплексных чисел, — оператор
произведения.
Теперь нам осталось взглянуть на вызывающую наш пример часть программы, организованную в форме реакции на нажатие кнопки Button1:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
COMP z1(1.1,2.2),z2(-1.0,-2.0),z3,z5;
COMP *z4 = new COMP();
*z4 = z1+z2;
z3 = z1-z2;
z5 = z1*z2;
ShowMessage(FloatToStr(z4->b));
ShowMessage(FloatToStr(z3.b));
ShowMessage(FloatToStr(z5.a));
}
Здесь стандартным образом с использованием конструкторов с параметрами создаются два комплексных числа z1 è z2. С помощью конструкторов
без параметров создаются числа z3 è z5. Комплексное число z4 объявляется
через указатель с использованием оператора new. Далее демонстрируется выполнение сложения, вычитания и умножения с перегруженными на ком
плексные числа операторами. Результаты выводятся в панели ShowMessage.
-

82 Глава 2. Объектно-ориентированное программирование
Многопотоковые приложения
Вычислительные мощности современных компьютеров очень велики, что
позволяет реализовывать на одной машине одновременное выполнение нескольких заданий. Традиционно считается, что это связано с существенным
различием скоростей выполнения операций непосредственно процессором
(логика и арифметика) и операций обмена информацией с относительно медленными внешними устройствами. Кроме того, богатые возможности многозадачности открываются с внедрением в практику многопроцессорных систем. Под термином многозадачности понимается возможность, предоставляемая операционной системой (Windows, Linux и другими) одновременного
выполнения нескольких приложений. Типичным примером, иллюстрирующим многозадачность, является ситуация, когда вы «скачиваете» откуда-то из
Интернета большой файл и одновременно работаете с каким-либо документом, например, в редакторе Word.
Современные системы программирования, включая C++ Builder, предоставляют возможность одновременного выполнения нескольких заданий в одном приложении. Это уже не многозадачность, а многопотоковость, хотя последний термин не слишком удачен, поскольку поток в информационных
системах трактуется очень широко. В этой связи необходимо сделать некоторые разъяснения. В одной программе выполняются параллельно во времени
несколько заданий, которые могут быть как совершенно независимыми, так и
взаимосвязанными. Последнее обеспечено тем, что задания выполняются в
едином для приложения адресном и объектном пространстве. Наряду с термином поток для обозначения параллельно выполняемых заданий в одном
приложении используется термин нить (thread). Распределением вычислительных ресурсов между нитями самостоятельно занимается система, однако
программист имеет возможность регулировать это распределение посредством
установки приоритетов нитей. Отметим также, что пара нить — главная программа также выполняются параллельно.
 Ñ++ Builder нити рекомендуется оформлять в форме объектов специальных классов, наследуемых от предопределенного в системе родительского
класса Thread со всеми его свойствами и методами. Автоматически создаваемый конструктор нити является методом с одним аргументом Create-
Suspended. Этот аргумент определяет способ запуска нити. Если при создании
объекта-нити конструктор вызывается со значением CreateSuspended = false,
то нить начинает выполняться сразу. Если же аргумент равен true, то нить запускается из вызывающей программы с помощью метода Resume. Исполнение
нити можно приостановить, вызвав метод Suspend, и снова запустить, вызвав
Resume еще раз. Метод Terminate выполняет окончательное завершение (разрушение) нити.
Тело нити-операции, выполняемое в потоке, записывается в методе
Execute. Здесь, собственно, и кодируется алгоритм желаемых действий. При
проектировании метода Execute â C++ Builder следует учесть одно важнейшее
обстоятельство. В этой системе программирования, как мы уже видели, чрез-

Многопотоковые приложения 83
вычайно широко используются визуальные компоненты, размещенные на форме. Форма, как правило, является атрибутом главного модуля программы, в то
время как объект нити создается из другого модуля. При этом и главный модуль и нити могут одновременно оперировать с визуальными компонентами
типа таймеров, полей редактирования и другими. Такая ситуация чревата конфликтами, и они действительно часто возникают, если не принимать специальных мер. Способ разрешения конфликтов заключается в том, что в функции
Execute вызывается единственная функция-член класса нити, называемая
Synchronize. Ее назначением и является отслеживание и урегулирование раз-
личных «пересечений» при параллельном выполнении заданий. Единственным
параметром метода Synchronize является имя самодельной функции-члена (которую рекомендуется делать закрытой), куда переносятся все действия нити.
Теперь, не вдаваясь в детали синтаксиса оформления многопотокового
приложения, сразу перейдем к примеру. Разработаем программу, которая выполняет следующее: в основном модуле на форме каждую секунду рисуется
(перерисовывается) закрашенный эллипс, длины осей которого являются случайными числами в пределах размера картинки, изменяющиеся от перерисовки к перерисовке. Нить обеспечивает ежесекундный вывод в поле редактирования на форме текущего времени.
Создайте новый проект и на его форме разместите таймер (Timer), три
кнопки (Button), поле редактирования (Edit) и графическое поле (Image).
Форма должна выглядеть примерно так, как это показано на рис. 2.7.
Ðèñ. 2.7
Сразу же добавим к проекту новую нить. Для этого из пункта File главного меню Builder'а необходимо открыть подпункт New и далее Other. В открывшемся окне New Items следует выбрать пиктограмму Thread Object. После нажатия кнопки ÎÊ в диалоговом окне указывается имя создаваемого класса
нити. Пусть это будет TIM. После этого имеет смысл сохранить проект с по
-

84 Глава 2. Объектно-ориентированное программирование
мощью комбинации клавиш Ctrl+Shift+S (сохранить все). Подтвердите предлагаемые по умолчанию имена сохранения. Теперь в указанном вами месте
сохранения вы можете увидеть, что в проекте имеются два заголовочных файла Unit1.h è Unit2.h и два модуля реализации Unit1.cpp è Unit2.cpp.
Заголовок Unit1 оставляем без изменения, а в Unit2 добавляем одну
строку в раздел private. Теперь этот заголовок должен выглядеть следующим
образом:
#ifndef Unit2H
#define Unit2H
//—————————————————————————————————————#include <Classes.hpp>
//—————————————————————————————————————class TIM : public TThread
{
private:
void __fastcall WORK();
protected:
void __fastcall Execute();
public:
__fastcall TIM(bool CreateSuspended);
};
//—————————————————————————————————————#endif
В раздел закрытых членов мы внесли объявление функции WORK. Ýòî —
та самая функция, которая будет являться аргументом метода Synchronize.
Теперь перейдем в модуль реализации Unit2.cpp, представленный ниже:
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#include "Unit2.h"
#pragma package(smart_init)
extern BOOL KEY,TTT;
__fastcall TIM::TIM(bool CreateSuspended)
: TThread(CreateSuspended)
{
}
//—————————————————————————————————————void __fastcall TIM::Execute()
{
Synchronize(WORK);
}
//—————————————————————————————————————void __fastcall TIM::WORK()
{
while(KEY)
{
if(TTT)
{
TTT = false;

Многопотоковые приложения 85
Form1->Edit1->Text = Time();
}
Application->ProcessMessages();
}
}
Мы видим, что метод Execute вызывает функцию Synchronize, и больше
ничего в нем не делается. В функции WORK организован цикл, который повторяется до тех пор, пока логическая переменная KEY не примет значение
false. Сразу укажем, что присвоение этой переменной значения false будет
производиться из главного модуля при нажатии кнопки STOP. Внутри цикла
проверяется, является ли значение булевой переменной ÒÒÒ истиной. Если
это так, то TTT сразу принимает противоположное значение (скоро поймем
зачем), а в поле редактирования формы с помощью функции Time выводится
текущее время. В главном модуле сделаем так, чтобы ÒÒÒ получало значение
истины при срабатывании таймера раз в секунду. Это даст сигнал к выводу
времени, а ÒÒÒ станет ложью до следующего срабатывания.
Последним выражением в цикле является вызов системной функции
ProcessMessages. Эта функция принудительно извлекает из очереди сообщения, которыми обмениваются между собой операционная система и процессы, и выполняет действия, соответствующие этим сообщениям. Безусловно,
согласимся с тем, что сказанное воспринимается достаточно туманным. Столь
же туманно и разъяснение в справке по этой функции. Реально дело обстоит
следующим образом. По каким-то причинам при выполнении циклов далеко
не все внешние по отношению к циклу события немедленно анализируются.
Так, в нашем случае, нажатие кнопки STOP в отсутствии функции
ProcessMessages игнорируется, и только благодаря ей удается «вовремя» определить, что переменная KEY изменила значение. Ситуация достаточно распространенная, в силу чего сделаем следующую рекомендацию. Если обнаруживается неправильная работа цикла, который должен реагировать на внешние события, попробуйте включить в цикл указанную функцию.
Теперь рассмотрим главный модуль Unit1.cpp:
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#include "Unit2.h"
//—————————————————————————————————————#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
TIM *Tim = new TIM(true);
BOOL KEY = true,TTT = false;
//—————————————————————————————————————__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//—————————————————————————————————————-

86 Глава 2. Объектно-ориентированное программирование
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Timer1->Enabled=true;
}
//—————————————————————————————————————-
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
Image1->Canvas->Brush->Color = clWhite;
Image1->Canvas->FillRect(Rect(0,0, Image1->Width,Image1->Height));
Image1->Canvas->Brush->Color = clRed;
Image1->Canvas->Ellipse(0,0, random(100),random(100));
TTT = true;
}
//—————————————————————————————————————-
void __fastcall TForm1::Button2Click(TObject *Sender)
{
Tim->Resume();
}
//—————————————————————————————————————void __fastcall TForm1::Button3Click(TObject *Sender)
{
KEY = false;
Close();
}
Прежде всего, обратим внимание на то, что к этому модулю необходимо
подключить не только заголовок Unit1.h, что делается автоматически, но и
Unit2.h, о чем необходимо позаботиться программисту. Это необходимо, поскольку класс и его методы описаны во внешних файлах. Кстати, и обратное
тоже необходимо — к модулю Unit2.cpp нужно подключить Unit1.h. В качестве
глобального сформирован объект класса TIM с именем Tim. Как можно видеть, здесь мы вызвали конструктор с помощью оператора new. Аргументом
конструктора является значение true, следовательно, нить сразу не запускается, а ждет вызова метода Resume. Также глобально описаны булевы переменные KEY è ÒÒÒ, назначение которых нам уже понятно.
Далее модуль содержит обработчики событий. Нажатие кнопки Button1
(START) переключает свойство таймера Enabled â true, тем самым запуская
таймер. Обработчик срабатывания таймера выполняет следующую последовательность действий:
1. Цвет кисти для рисования на канве задается белым.
2. Прямоугольник, совпадающий по размерам со всей графической обла-
стью, закрашивается белым цветом, то есть стирается все, что там было.
3. Цвет кисти задается красным.
4. Красным цветом (со сплошной заливкой) рисуется эллипс со случай-
ными размерами осей.
5. Переменная ÒÒÒ устанавливается в true, тем самым разрешая выпол
нить действия в цикле в нити.
-

Многопотоковые приложения 87
Нажатие кнопки Button2 (RESUME) запускает выполнение нити. Наконец, кнопка Button3 (STOP) задает переменной KEY значение false, в результате чего происходит выход из цикла в нити. Сразу после этого срабатывает
функция Close, закрывающая приложение.
Теперь, если все сделано правильно, после запуска программы, нажатия
кнопки START и запуска нити на экране можно видеть картинку, представленную на рис. 2.8.
Ðèñ. 2.8
Если интервал срабатывания таймера установлен в 1000 (миллисекунд), то
каждую секунду изображение меняется — отображается новое текущее время
и новый эллипс.

Глава 3
ПЕРЕДАЧА ИНФОРМАЦИИ В СЕТЯХ
Введение в компьютерные коммуникации
Понятно, что тема передачи данных в цифровых сетях совершенно необъятна. Соответствующим частным и общим вопросам посвящено громадное
количество литературы. Более того, отрасль сетевой транспортировки информации стремительно развивается, и при всем желании уследить за всеми последними достижениями здесь просто невозможно. По этой причине представляемый здесь материал совершенно не претендует на хоть малую степень
полноты изложения, а всего лишь может рассматриваться как стартовая площадка для тех, кто намерен заниматься этой серьезной темой. Тем не менее,
мы представим минимум сведений, которые позволят нам разрабатывать
«действующие макеты» приложений, связанных с сетевыми технологиями.
Основой основ сетевых коммуникаций является понятие коммуникационных протоколов. В самом простейшем варианте под термином сетевой протокол следует понимать набор правил, по которым формируются пакеты
(порции) данных для их дальнейшей передачи по компьютерным сетям. Протоколов создано великое множество, и, что самое поразительное, практически
все это множество находит свое применение для решения тех или иных задач.
Для того чтобы хоть как-то упорядочить все разработанные протоколы, принята специальная классификация, в основе которой лежат так называемые
уровни. Заметим здесь, что таких уровней вычленили семь, и появился целый
раздел информатики, называемый «семиуровневая иерархия сетевых протоколов». Для нас наибольший интерес будет представлять та часть иерархической
структуры протоколов, корнем которой будет группа протоколов сетевого
уровня. Сетевые протоколы ориентированы на конкретные виды сетей. Из
всего этого семейства нам здесь понадобится наиболее популярный протокол,
называемый просто IP — Internet Protocol. IP-протокол предназначен для работы в глобальных и локальных вычислительных сетях, в которых все узлы
снабжены так называемыми IP-адресами. Назначение протокола сетевого
уровня заключается только в обеспечении возможности передачи пакетов, то
есть обеспечении маршрутизации данных. Сам по себе сетевой протокол не
гарантирует доставку пакетов. Никакие функции контроля правильности пе
редачи данных или исправления ошибок непосредственно на сетевой прото
-
-

Введение в компьютерные коммуникации 89
кол не возлагаются. Этим занимаются так называемые протоколы транспортного уровня.
Передаваемые по сети данные можно разделить на два типа: командные
данные и информационные данные. К командной информации относятся
сигналы вызова, разъединения, а также другие служебные сообщения. Информационные данные могут представлять собой текст, цифровой звук, цифровое изображение. Основное требование к передаче командной информации — отсутствие ошибок передачи. В результате необходимо использовать
достоверный (надежный) протокол доставки сообщений. Обычно в качестве
такого протокола используется протокол транспортного уровня TCP
(Transport Control Protocol), обеспечивающий гарантированную доставку сообщений. Необходимо отчетливо представлять себе, что пакеты данных, путешествующие по сети, могут искажаться в результате сбоев в работе аппаратного обеспечения узлов или вообще теряться, например, из-за того, что время
существования пакета может быть ограниченным. В этой связи во многих ситуациях (хотя и не всегда) контроль за правильностью прохождения пакетов
оказывается совершенно необходимым. Именно этот контроль и выполняет
протокол TCP. Не вдаваясь в детали, укажем только, что протокол имеет возможность получать из пункта назначения пакета информацию, подтверждающую правильность передачи данных. Если по истечении заданного времени
соответствующее подтверждение не получено, производится повторная пересылка пакета. Так может продолжаться, в принципе, очень продолжительное
время. Теперь, наверное, понятно, что при работе в Интернете, а иногда и в
загруженной локальной сети, мы сталкиваемся с большими задержками при
транспортировке данных. При передаче командной информации с этим приходится мириться. В то же время может возникать ситуация, когда гарантированная доставка информации не только не обязательна, но и нежелательна.
Ситуация, о которой мы говорим, имеет место, например, при передаче
звука в реальном времени. Действительно, живой звук — человеческая речь,
музыка, это объект, который нельзя «заморозить» во времени, по крайней
мере, на сколько-нибудь продолжительное время. Оборванная на середине
слова фраза в разговоре совершенно недопустима. (Считается, что максимально возможные задержки в речи не должны превышать величину
200—300 миллисекунд.) Запись цифрового звука осуществляется порциями.
Совершенно очевидно, что и передача звуковой (и любой другой) информации производится порциями. В этой связи понятно, что если ошибки передачи пакетов не слишком частые, то лучше пожертвовать отдельным фрагментом речи длительностью, например, в десятую долю секунды, чем ждать, возможно, очень долго, пока этот фрагмент все же будет правильно передан.
С передачей изображения ситуация менее жесткая. Изображение как раз и
можно «заморозить» на экране. Хотя и здесь в некоторых случаях имеет
смысл пропустить один или несколько кадров.
Теперь понятно, что для передачи мультимедиа реального времени протокол TCP не очень подходит. Необходимы инструменты негарантированной передачи информации, и такие инструменты имеются. В наших разработках мы
будем применять протокол транспортного уровня UDP (User Datagram

90 Глава 3. Передача информации в сетях
Protocol — протокол передачи пользовательских дейтаграмм). Задачей этого
инструмента является только отправка на заданный адрес порции данных любого типа — пользовательских дейтограмм. При этом факт неправильной пересылки информации фиксируется, но никакие действия при этом не предпринимаются. Очевидно, что для передачи служебных данных UDP не применим,
но для передачи звука с возможными потерями фрагментов речи или музыки
протокол оказывается очень полезным. При использовании UDP, помимо потерь отдельных блоков данных, может происходить нарушение очередности
следования пакетов. Поскольку блоки информации могут следовать по различным маршрутам и на пути следования испытывать различные задержки, существует вероятность того, что пакет, отправленный позже предыдущего, достигнет адресата раньше. Для передачи звука такая ситуация крайне нежелательна.
Хотя практика показала, что подобные нарушения очередности возникают
достаточно редко, меры к их устранению все же принимаются. Одним из вариантов борьбы с этим явлением является использование джиттер-буфера временного хранения группы пришедших пакетов, где они упорядочиваются по
времени отправки. Неизбежным при этом будет задержка, обусловленная накоплением пакетов, но это все же лучше, чем обмен местами, например, начала и конца слова или фразы. Другим способом устранения этой досадной помехи в современных профессиональных системах IP-телефонии является использование протокола RTP (Real Time Protocol) и его сателлита — протокола
RTCP (Real Time Control Protocol). Протокол реального времени также является протоколом негарантированной доставки пакетов и также работает на основе сетевого IP. Говоря точнее, в отличие от UDP этот протокол выполняет гарантированную доставку только в течение заданного интервала времени. Если
в этом интервале передать блок не удалось, то на этом обработка текущей порции данных заканчивается и начинает передаваться следующая. Таким образом, хотя и не обеспечивается доставка каждого из блоков, нарушений очередности не возникает и передача и прием пакетов строго распределены по равным временным интервалам.
Итак, мы имеем в своем распоряжении несколько наборов правил формирования
пакетов данных, которые можем применять для тех или иных задач транспортировки информации. Работая с сетевым протоколом IP, мы
имеем возможность адресовать передачу с помощью IP-адресов. В дальнейшем узел, которому предназначена транспортировка данных, будем называть
удаленным хостом (remote host), а узел, являющийся источником информации, — локальным хостом (local host). Обычно пара из передающего и принимающего узлов называется парой КЛИЕНТ — СЕРВЕР, хотя такая классификация узлов довольно условна. Адреса узлов могут быть представлены четырехбайтовым числом, в котором десятичные представления байтов отделены
друг от друга символом точка — обычное цифровое представление IP-адреса.
Кроме того, хост может быть идентифицирован и символьным именем типа
mail.ru. Символьное имя однозначно связано с цифровым адресом и назначается специальной службой сети. При программировании наших задач мы будем использовать исключительно цифровые IP-адреса. Здесь важно напомнить, что если компьютер не подключен к сети, но в операционную систему
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
