Динамическое и статистическое связывание в языке C++

Связывание в языке C++. Решение о выборе метода статического или динамического типа. Описание классов и глобальных переменных. Метод для обращения полиморфизма. Разработка библиотеки функций. Разработка приложения на языке С++ с применением Win32 API.

Рубрика Программирование, компьютеры и кибернетика
Вид контрольная работа
Язык русский
Дата добавления 08.05.2012
Размер файла 540,0 K

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

Размещено на http://www.allbest.ru/

Динамическое и статистическое связывание в языке C++

Введение

Системное программирование (или программирование систем) - подраздел программирования, заключающийся в работе над системным программным обеспечением.

Определение «системное» подчеркивает тот факт, что результаты этого вида программирования существенно меняют свойства и возможности вычислительной системы. В то же время бесспорным остаётся тот факт, что в определенной степени этот результат имеет место при применении любых программ, выполняемых в вычислительной системе. Поэтому между программированием «системным» и «несистемным» (прикладным программированием») нет четкой границы.

Вычислительная система имеет иерархическую структуру, которую можно представить в виде набора вложенных слоев, на внешнем из которых находятся прикладные программы, а на самом внутреннем - аппаратура компьютера. Условная степень системности нарастает при программировании, затрагивающем все более внутренние уровни системы.

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

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

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

­ работает максимально эффективно и имеет минимальное запаздывание по времени завершения,

­ имеет маленькую библиотеку времени выполнения (RTL) или не имеет её вообще,

­ позволяет прямое управление (прямой доступ) к памяти и управляющей логике,

­ позволяет делать ассемблерные вставки в код;

­ отладка программы может быть затруднена при невозможности запустить её в отладчике из-за ограничений на ресурсы, поэтому может применяться компьютерное моделирование для решения этой проблемы.

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

1. Связывание в языке C++

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

Связывание методов в этом языке является довольно сложным. Для обычных переменных (не указателей или ссылок) оно осуществляется статически. Но когда объекты обозначаются с помощью указателей или ссылок, используется динамическое связывание. В последнем случае решение о выборе метода статического или динамического типа диктуется тем, описан ли соответствующий метод с помощью ключевого слова virtual. Если он объявлен именно так, то метод поиска сообщения базируется на динамическом классе, если нет на статическом. Даже в тех случаях, когда используется динамическое связывание, законность любого запроса определяется компилятором на основе статического класса получателя.

Рассмотрим, например, следующее описание классов и глобальных переменных: class Mammal

{

public:

void speak()

{

printf («cant speak»);

}

};

class Dog: public Mammal

{

public:

void speak()

{

printf («wouf wouf»);

}

void bark()

{

printf («wouf wouf, as well»);

}

};

Mammal fred;

Dog lassie;

Mammal *fido = new Dog;

Выражение fred.speak() печатает «cant speak», однако вызов fido->speak() также напечатает «cant speak», поскольку соответствующий метод в классе Mammal не объявлен как виртуальный. Выражение fido->bark() не допускается компилятором, даже если динамический тип для fido класс Dog. Тем не менее статический тип переменной всего лишь класс Mammal.

Если мы добавим слово virtual:

class Mammal

{

public:

virtual void speak()

{

printf («cant speak»);

}

};

то получим на выходе для выражения fido->speak() ожидаемый результат.

Относительно недавнее изменение в языке С++ добавление средств для распознавания динамического класса объекта. Они образуют систему RTTI (Run-Time Type Identification идентификация типа во время выполнения).

В системе RTTI каждый класс имеет связанную с ним структуру типа typeinfo, которая кодирует различную информацию о классе. Поле данных name одно из полей данных этой структуры содержит имя класса в виде текстовой строки. Функция typeid может использоваться для анализа информации о типе данных. Следовательно, следующая ниже команда будет печатать строку «Dog» динамический тип данных для fido. В этом примере необходимо разыменовывать переменную-указатель fido, чтобы аргумент был значением, на которое ссылается указатель, а не самим указателем:

cout << «fido is a» << typeid(*fido).name() << endl;

Можно также спросить, используя функцию-член before, соответствует ли одна структура с информацией о типе данных подклассу класса, соотносящегося с другой структурой. Например, следующие два оператора выдают true и false:

if (typeid(*fido).before (typeid(fred)))…

if (typeid(fred).before (typeid(lassie)))…

До появления системы RTTI стандартный программистский трюк состоял в том, чтобы явным образом закодировать в иерархии класса методы быть экземпляром. Например, для проверки значения переменных типа Animal на принадлежность к типу Cat или к типу Dog можно было бы определить следующую систему методов:

class Mammal

{

public:

virtual int isaDog()

{

return=0;

}

virtual int isaCat()

{

return=0;

}

};

class Dog: public Mammal

{

public:

virtual int isaDog()

{

return=1;

}

};

class Cat: public Mammal

{

public:

virtual int isaCat()

{

return=1;

}

};

Mammal *fido;

Теперь для определения того, является ли текущим значением переменной fido величина типа Dog, можно использовать команду fido->isaDog(). Если возвращается ненулевое значение, то можно привести тип переменной к нужному типу данных.

Возвращая указатель, а не целое число, мы объединяем проверку на принадлежность к подклассу и приведение типа. Это аналогично другой части системы RTTI, называемой dynamic_cast, которую мы вкратце опишем. Если некая функция в классе Mammal возвращает указатель на Dog, то класс Dog должен быть предварительно описан. Результатом присваивания является либо нулевой указатель, либо правильная ссылка на класс Dog. Итак, проверка результата все еще должна осуществляться, но мы исключаем необходимость приведения типа. Это показано в следующем примере:

class Dog; // предварительное описание

class Cat;

class Mammal

{

public:

virtual Dog* isaDog()

{

return=0;

}

virtual Cat* isaCat()

{

return=0;

}

};

class Dog: public Mammal

{

public:

virtual Dog* isaDog()

{

return this;

}

};

class Cat: public Mammal

{

public:

virtual Cat* isaCat()

{

return this;

}

};

Mammal *fido;

Dog *lassie;

Оператор lassie = fido->isaDog(); теперь выполним всегда. В результате переменная lassie получает ненулевое значение, только если fido имеет динамический класс Dog. Если fido не принадлежит Dog, то переменной lassie будет присвоен нулевой указатель.

lassie = fido->isaDog();

if(lassie)

{

… // fido и в самом деле относится к типу Dog}

else

{… // присваивание не сработало

… // fido не принадлежит к типу Dog};

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

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

 // конвертировать только в том случае, если fido является собакой

lassie = dynamic_cast < Dog* > (fido);

 // затем проверить, выполнено ли приведение

if (lassie)…

В язык C++ были добавлены еще три типа приведения (static_cast, const_cast и reinterpret_cast), но они используются в особых случаях и поэтому здесь не описываются. Программистам рекомендуется применять их как более безопасные средства вместо прежнего механизма приведения типов.

2. Проектная часть

2.1 Разработка библиотеки функций

2.1.1 Постановка задачи

Разработать библиотеку функций, назначение которых описано в таблице 2.1, и использовать эти функции в основной программе.

Таблица 2.1 - Назначение функций

Номер варианта

Подпрограмма №1

Подпрограмма №2

3

Функция, возвращающая значение среднего арифметического элементов целочисленного массива.

Процедура перестановки элементов целочисленного массива в следующем порядке: 1-ый элемент меняется местами с последним, 2-ой - с предпоследним и т.д.

2.1.2 Программный код

program Zad_1; {заголовок программы}

uses {подключение дополнительных модулей}

crt;

type {описание типов}

massiv = array [1..50] of integer; {тип - целочисленный массив с максимальным числом элементов 50}

{функция расчета среднего арифметического элементов массива

Параметры:

mas - массив

n - количество элементов в массиве

Возвращаемое значение:

среднее арифметическое элементов массива, тип - real}

function SredArif (mas:massiv; n:byte): real;

var

i: byte;

sum: integer;

begin

sum:= 0;

for i:=1 to n do

sum:= sum + mas[i];

SredArif:= sum /n;

end;

{процедура перестановки элементов массива местами,

Параметры:

mas - массив

n - количество элементов в массиве

Результат:

выводит на экран элементы матрицы, которые были поменяны местами,

т.е. 1-ый элемент становиться последним, 2-ой предпоследним и т.д.}

procedure Perestanovka (a:massiv; n:byte);

var

i: byte;

tmp: integer;

begin

for i:=1 to (n div 2) do

begin

tmp:= a[i];

a[i]:=a [n-i+1];

a [n-i+1]:= tmp;

end;

for i:=1 to n do

Write (a[i], ' ');

end;

var

mas: massiv;

i, n: byte;

Begin

clrscr;

Write ('Введите количество элементов в массиве:');

ReadLn(n);

for i:=1 to n do

begin

Write ('Введите ', i, '-ый элемент массива:');

ReadLn (mas[i]);

end;

WriteLn ('1. Среднее арифметическое =', sredArif (mas, n):0:2);

ReadLn;

WriteLn ('2. Перестановка элементов массива местами: ');

ReadLn;

End.

2.1.3 Пример работы программы

Рисунок 2.1 - Пример 1

Рисунок 2.2 - Пример 2

2.2 Разработка приложения на языке С++ с применением Win32 API

язык переменная полиморфизм приложение

2.2.1 Постановка задачи

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

2.2.2 Программный код

 //KWnd.h

#include «KWnd.h»

KWnd:KWnd (LPCTSTR windowName, HINSTANCE hInst, int cmdShow,

LRESULT (WINAPI *pWndProc) (HWND, UINT, WPARAM, LPARAM),

LPCTSTR menuName, int x, int y, int width, int height,

UINT classStyle, DWORD windowStyle, HWND hParent)

{

char szClassName[] = «KWndClass»;

wc.cbSize = sizeof(wc);

wc.style = classStyle;

wc.lpfnWndProc = pWndProc;

wc.cbClsExtra = 0;

wc.cbWndExtra = 0;

wc.hInstance = hInst;

wc.hIcon = LoadIcon (NULL, IDI_APPLICATION);

wc.hCursor = LoadCursor (NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);

wc.lpszMenuName = menuName;

wc.lpszClassName = szClassName;

wc.hIconSm = LoadIcon (NULL, IDI_APPLICATION);

 // ? aaeno? e? oai eeann ieia

if (! RegisterClassEx(&wc)) {

char msg[100] = «Cannot register class:»;

strcat (msg, szClassName);

MessageBox (NULL, msg, «Error», MB_OK);

return;

}

 // Nicaaai ieii

hWnd = CreateWindow (szClassName, windowName, windowStyle,

x, y, width, height, hParent, (HMENU) NULL, hInst, NULL);

if (! hWnd) {

char text[100] = «Cannot create window:»;

strcat (text, windowName);

MessageBox (NULL, text, «Error», MB_OK);

return;

}

 // Iieacuaaai ieii

ShowWindow (hWnd, cmdShow);

}

 /////////////////////////////////////////

 //U_T_Diagram.cpp

#define _WIN32_WINNT 0x500

#include <windows.h>

#include <math.h>

#include «KWnd.h»

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);

void DrawDiagram (HWND, HDC);

#define Pi 3.14159265

int WINAPI WinMain (HINSTANCE hlnstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)

{

MSG msg;

KWnd mainWnd («Временная диаграмма напряжения переменного электрического тока», hlnstance, nCmdShow, WndProc);

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return msg.wParam;

}

LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

HDC hDC;

PAINTSTRUCT ps;

switch (uMsg)

{

case WM_PAINT:

hDC = BeginPaint (hWnd, &ps);

DrawDiagram (hWnd, hDC);

EndPaint (hWnd, &ps);

break;

case WM_DESTROY:

PostQuitMessage(0);

break;

default:

return DefWindowProc (hWnd, uMsg, wParam, lParam);

}

return 0;

}

void DrawDiagram (HWND hwnd, HDC hdc)

{

RECT rect;

GetClientRect (hwnd, &rect);

const int xVE = rect.right - rect.left;

const int yVE = rect.bottom - rect.top;

const int xWE = xVE;

const int yWE = yVE;

int nPixPerVolt = 1;

int nPixPerMs = 10;

SetMapMode (hdc, MM_ISOTROPIC);

SetWindowExtEx (hdc, xWE, yWE, NULL);

SetViewportExtEx (hdc, xVE, - yVE, NULL);

SetViewportOrgEx (hdc, 10*nPixPerMs, yVE/2, NULL);

const int tMin = 0; // ms

const int tMax = 40; // ms

const int uMin = -400; // V

const int uMax = 400; // V

const int tGridStep = 5;

const int uGridStep = 100;

int x, y;

char* xMark[] = {«0», «5», «10», «15», «20», «25», «30», «35», «40»};

char* yMark[] = {«- 400»,» - 300»,» - 200»,» - 100», «0», «100», «200», «300», «400»};

 // Сетка

HPEN hPenO = CreatePen (PS_SOLID, 1, RGB (0,160, 0));

HPEN hOldPen = (HPEN) SelectObject (hdc, hPenO);

int u = uMin;

int xMin = tMin * nPixPerMs;

int xMax = tMax * nPixPerMs;

for (int i = 0; i < 9; ++i)

{

y = u * nPixPerVolt;

MoveToEx (hdc, xMin, y, NULL);

LineTo (hdc, xMax, y);

TextOut (hdc, xMin-40, y+8, yMark[i], strlen (yMark[i]));

u += uGridStep;

}

int t = tMin;

int yMin = uMin * nPixPerVolt;

int yMax = uMax * nPixPerVolt;

for (i =0; i < 9; ++i) {

x = t * nPixPerMs;

MoveToEx (hdc, x, yMin, NULL);

LineTo (hdc, x, yMax);

TextOut (hdc, x-6, yMin-10, xMark[i], strlen (xMark[i]));

t += tGridStep;

}

 // Ось «х»

HPEN hPenl = CreatePen (PS_SOLID, 3, RGB (0, 0, 0));

SelectObject (hdc, hPenl);

MoveToEx (hdc, 0, 0, NULL);

LineTo (hdc, xMax, 0);

static LOGFONT lf;

lf.lfPitchAndFamily = FIXED_PITCH | FF_MODERN;

lf.lfItalic = TRUE;

lf.lfWeight = FW_BOLD;

lf.lfHeight = 20;

lf.lfCharSet = DEFAULT_CHARSET;

lstrcpy((LPSTR)&lf.lfFaceName, «Arial»);

HFONT hFontO = CreateFontIndirect(&lf);

HFONT hOldFont = (HFONT) SelectObject (hdc, hFontO);

SetTextColor (hdc, RGB (0, 0, 200));

TextOut (hdc, xMax+10, 10, «t (ms)», 6);

 // Ось «у»

MoveToEx (hdc, 0, yMin, NULL);

LineTo (hdc, 0, yMax);

TextOut (hdc, -10, yMax+30, «u (V)», 5);

 // График

HPEN hPen2 = CreatePen (PS_SOLID, 5, RGB (200, 0, 100));

SelectObject (hdc, hPen2);

int tStep = 1;

double radianPerMs = 2 * Pi /20;

const double uAmplit = 311.17; // volt

t = tMin;

MoveToEx (hdc, 0, 0, NULL);

while (t <= tMax) {

u = (int) (uAmplit * sin (t * radianPerMs));

LineTo (hdc, t * nPixPerMs, u * nPixPerVolt);

t += tStep;

}

 // Заголовок

char* title = «Диаграмма напряжения переменного электр. тока»;

lf.lfItalic = FALSE;

lf.lfWeight = FW_BOLD;

lf.lfHeight = 30;

HFONT hFontl = CreateFontIndirect(&lf);

SelectObject (hdc, hFontl);

SetTextColor (hdc, RGB (0, 200, 0));

TextOut (hdc, 0, yMax + 70, title, strlen(title));

SelectObject (hdc, hOldPen);

SelectObject (hdc, hOldFont);

}

 //KWnd.h

#include <windows.h>

class KWnd {

public:

KWnd (LPCTSTR windowName, HINSTANCE hInst, int cmdShow,

LRESULT (WINAPI *pWndProc) (HWND, UINT, WPARAM, LPARAM),

LPCTSTR menuName = NULL,

int x = CW_USEDEFAULT, int y = 0,

int width = CW_USEDEFAULT, int height = 0,

UINT classStyle = CS_HREDRAW | CS_VREDRAW,

DWORD windowStyle = WS_OVERLAPPEDWINDOW,

HWND hParent = NULL);

HWND GetHWnd() {return hWnd;}

protected:

HWND hWnd;

WNDCLASSEX wc;

};

2.2.3 Пример работы программы

Рисунок 2.3 - Диаграмма напряжения переменного электрического тока

Заключение

Си++ как преемник языка Си широко используется в системном программировании. На нем можно писать высокоэффективные программы, в том числе операционные системы, драйверы и т.п. Язык Си++ - один из основных языков разработки трансляторов.

Поскольку системное программное обеспечение часто бывает написано на языке Си или Си++, то и программные интерфейсы к подсистемам ОС тоже часто пишут на Си++.

API - это аббревиатура названия Application Programming Interface (интерфейс прикладного программирования). API представляет собой совокупность функций и инструментов, позволяющих программисту создавать приложения (программы), работающие в некоторой среде.

Win32 API - это набор функций для создания программ, работающих под управлением Microsoft Windows 98, Windows NT или Windows 2000. Все функции этого набора являются 32-битными, что отражено в названии интерфейса.

Windows API был изначально спроектирован для использования в программах, написанных на языке C (или C++). Работа через Windows API - это наиболее близкий к системе способ взаимодействия с ней из прикладных программ. Более низкий уровень доступа, необходимый только для драйверов устройств, в текущих версиях Windows предоставляется через Windows Driver Model.

В процессе выполнения курсовой работы были приобретены навыки работы с языком C++. В аналитической части был разобран материал по динамическому и статическому связыванию методов.

В практической части изучен принцип работы с массивами и создания библиотек, а также навыкам создания приложения на языке С++ с использование Win32 API.

Список литературы

1 Е.А. Зуев. Программирование на языке Turbo Pascal 6.0, 7.0, М.: Веста, Радио и связь, 1993.

2 Моргун Александр Николаевич. Справочник по Turbo Pascal для студентов. - М.: Диалектика, 2006.

3 Щупак Ю.А. Win32 API. Эффективная разработка приложений. - СПб.: Питер, 2007.

Размещено на Allbest.ru


Подобные документы

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.