Назад | Начало урока | Вперед
Содержание

Глава 2

Что такое программа

В этой главе дается краткий обзор основных этапов создания программ: проектирования, написания, компиляции и отладки. Кроме того, вам придется "проглотить" немного теории, касающейся объектно-ориентированного программирования. Вас также ожидает встреча с настоящей живой работающей .NET-программой.

Введение в програмирование

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

Написание программ — это этап, во время которого вы садитесь и набираете коды своей
программы. Обычно вы набираете инструкции языков программирования высокого уровня.
Например, одной строкой можно сказать компьютеру, что нужно вывести что-то на экран. В
этом и следующих разделах вы познакомитесь с множеством команд языка C++, с помощью
которых можно заставить компьютер делать что-нибудь полезное.

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

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

Процесс поиска и исправления ошибок называется отладкой программы. Для поиска
ошибок обычно используются услуги отладчика. Когда ошибка найдена, вы снова возвращаетесь к этапу редактирования кодов программы для ее исправления.

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

Языки высокого и низкого уровня

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

Код C++:
а = 3*а - Ь*2 + 1;

Эквивалентный ему код ассемблера:

mov eax, DWORD PTR _a$[ebp]
lea eax, DWORD PTR[eax+eax*2]
mov ecx, DWORD PTR _b$[ebp]
1 add ecx, ecx
sub eax, ecx
inc eax
mov DWORD PTR _a$[ebp], eax

В действительности коды ассемблера также относятся к более высокому уровню, чем коды, которые компьютер может воспринимать непосредственно (без компиляциии ). Компьютер может понимать только машинный язык, представляющий любые инструкции и команды в числовом виде.

Вот как выглядит фрагмент кодов машинного языка:

8Ь 45 fc
40 f8 8d
3b 03 2d
40 89 04
4d c9 cl 45 fc

Теперь вы можете сравнить коды языков высокого и низкого уровня и решить, язык какого уровня вы хотели бы использовать для написания программ.

Главная функция программы

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

Инструкции некоторых выражений выполняются только в тех случаях, если справедливо
какое-то условие. Такие выражения называются условными операторами. Так, строка кодов
может содержать, например, следующую информацию для компьютера: "Если пользователь
щелкнет на кнопке Печать, выведи открытый документ на печать ".

Переменные используются для представления данных в программе. Например, если
нужно запомнить имя пользователя, можно создать переменную Имя. Затем в любой момент, когда потребуется имя пользователя, можно просто сослаться на значение переменной Имя. В процессе выполнения программы значения переменных могут изменяться. Например, можно присвоить переменной Имя значение Мартин, а потом другим выражением
присвоить этой же переменной значение Степан. Но само по себе значение переменной
никогда не меняется — в любом случае вы должны написать какое-нибудь выражение, меняющее одно значение на другое.

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

На рис. 2.1 показана небольшая программа, в которой используются функция main, выражения, переменные и комментарии.
Основные компоненты, из которых состоит программа

#using <mscorlib.dll>

int nMyInt; <------- Переменная

int main (void) <-------Начало функции main
{

//Получение значения от пользователя <-----Комментарий
nMylnt = GetNumberf); <-----Выражение
if (nMylnt > 0) <--------Условный оператор
return nMylnt;
return -1;
}

Стандартные подпрограммы

Коды общих для большинства программ алгоритмов хранятся в компоненте среды .NET, называемом CLR (Common Language Runtime). Например, почти все программы отображают какие-то значения на экране. Возможно, это покажется вам странным, но для выполнения такого простого действия компьютеру нужно сделать множество шагов. Вместо того чтобы писать целые наборы кодов для всех этих шагов каждый раз, когда нужно что-то вывести на экран, вы можете просто воспользоваться готовой подпрограммой, созданной для среды .NET.

Когда вы пишете неуправляемые программы (те, которые создаются с использованием старой версии С++), подпрограммы, реализующие типичные алгоритмы, содержатся в так называемых библиотеках. Чем они отличаются от CLR? По сути, ничем. И те и другие являются библиотеками. Разница только в названии. CLR может быть использован при создании программ на языках Visual Basic, C# и Visual С++ (при этом сами библиотеки для разных языков будут несколько отличаться). Хотя CLR, по существу, является тем же набором библиотек, его возможности значительно шире возможностей стандартных библиотек языка C++.

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

Для чего содаются программы

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

В качестве примера рассмотрим вариант создания программы для решения обычной задачи. Предположим, вам нужно вычислить площадь квадрата. (Зачем? Какая разница. Может, у
вас вечеринка и вы хотите чем-то удивить своих друзей.) Еще со школы вы помните, что
площадь квадрата вычисляется как умножение его стороны на саму себя.

В контексте создания программы эта задача может быть разбита на три логические
подзадачи.

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

Например, на языке C++.NET эти коды могут выглядеть так:


// Вычисление площади квадрата при условии, что известна
// Длина его стороны

using namespace System;
//С этой строки начинается выполнение программы

#ifdef _UNICODE
int wmain(void)
#else

int main (void)
#endif
{

String *pszSize;
int nSize;
int nArea;

//Запрос от пользователя значения длины стороны квадрата
Console::WriteLine(L"Чему равна длина стороны квадрата?"]

//Получение ответа
DszSiire = Console :: ReadLine();

//Преобразование полученного значения в число
nSize = nSize.Parse(pszSize);

//Вычисление площади квадрата
nArea = nSize*nSize;

//Отображение результата
//Обратите внимание, что для этого вначале нужно
//преобразовать полученное значение в строку

Consolе::WriteLine(L"Площадь квадрата составляет {} единиц.", nArea.ToString());

//Ожидание, пока пользователь не остановит
//выполнение программы

Console: :WriteLine (L"Нажмите Enter, чтобы: завершить
выполнение программы");

Console::ReadLine();
return 0;

}

Код программы в файле SquareArea.

Итак, вы только что узнали, как выглядят коды программы. Но как разобраться, для чего
нужна каждая отдельная строка? Для этого сначала познакомьтесь с общими принципами
чтения кодов программы (если вы их еще не знаете).

  1. Начинайте читать с самого начала.
  2. Читайте по одной строке.
  3. Старайтесь понять, для чего нужна каждая строка.
  4. Если вы чего-то не понимаете, переходите к следующей строке.
Эти же принципы используются при работе компилятора, за исключением разве что четвертого пункта.

Далее описывается, как вы могли бы интерпретировать коды приведенной выше про-
граммы. Итак, обратимся к первым двум строкам:

//SquareArea
//Вычисление площади квадрата при условии, что известна
//длина его стороны

Увидев их, вы можете подумать: "Ну, в этих строках нет ничего сложного. В них говорит-
ся, что эта программа вычисляет площадь квадрата исходя из длины его стороны". (Две косые черты в начале строк указывают, что эти строки являются комментариями.)

#include "stdafx.h"
#using <mscorlib.dll>
using namespace System;

"А это уже что-то совсем непонятное. Наверное, о том, для чего все
это нужно, я узнаю в следующих глаэах". (Совершенно правильный ход
мыслей.)

#ifdef _UNICODE
int wmain(void)
#else

int main (void)
#endif

"Опять ЧТО-ТО непонятное. Пожалуй, сейчас это можно пропустить".

String *pszSize;
i n t nSize;
int nArea;

"Зачем нужны зти строки я точно не знаю, однако слова size (длина)
и area (площадь) уже как-то созвучны с решаемой задачей".

//Запрос от пользователя значения длины стороны квадрата
Console::WriteLine(L"Чеыу равна длина стороны квадрата?"!;

//Получение ответа
pszSize = Console::ReadLine();

//Преобразование полученного значения в число
nSize = nSize.Parse(pszSize);

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

// Вычисление площади квадрата
nArea = nSize*nSize;

"Да, да! Это я понимаю! В этой строке длина стороны умножается на саму себя, и мы на-
ходим площадь квадрата"'.

Ну и так далее.

Закончив читать эту книгу, вы будете знать, для чего нужна каждая из встретившихся вам
здесь команд. Более того, вы будете знать не только как читать коды программ, но и как их
создавать. Другими словами, вы научитесь программировать.

А теперь немного теории
Вы только что узнали, как выглядят коды настоящей программы на С++. Однако в этой
программе не были использованы возможности объектно-ориентированного программирования. Язык C++ настолько популярен во многом благодаря именно этим возможностям. Почему? На это есть ряд причин.

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

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

Уже готовые объекты можно объединять между собой для создания новых объектов. Это
называется композицией. Например, можно создать объект Караоке, объединив объект
ДИСКОБОЛ С объектом Микрофон. (Можно создать объект Сутки, объединив объект День с
объектом Ночь.)

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

Наследование— одно из наиболее важных свойств объектно-ориентированного программирования. Создавая новые объекты путем наследования кодов уже существующих работающих объектов, вы получаете ряд преимуществ.

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

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

На рис. 2.2 показано семь классов. Класс Звук является самым простым. Он содержит
название воспроизводимого звука и его продолжительность. Класс Трек основан на классе
Звук, а потому также содержит информацию о названии и продолжительности. Кроме того, он содержит данные об исполнителе и о дате записи. Класс Rock является производным от класса Трек. Он содержит все элементы класса трек (как и все элементы класса
Звук) плюс еще некоторые. Таким образом, на основе любых классов можно создавать
более сложные классы, содержащие в себе больше разных данных и функций для их обработки. При этом каждый производный класс содержат в себе все данные и функции, которые есть в базовом классе.

Звук
Название
Продолжительность
7 \
ЗвукиЖивотных Трек
Животное Исполнитель
ДатаЗаписи
Rock
t
Классика Pop
Группа
Альбом
1
Дирижер
Оркестр
ПерваяСкрипка
Альбом
Рейтинг
Alternative Rock
Направление

Рис. 2.2. Новые классы можно создавать путем наследования свойств и методов уже созданных классов

Полиморфизм

При создании новых классов можно использовать не только возможность наследования, но также полиморфизм для определения способа поведения новых классов. Если вы еще не запомнили все греческие слова, употребляемые в этой главе, напомним, что под полиморфизмом подразумевается возможность одних и тех же функций по-разному обрабатывать данные, принадлежащие разным объектам. Например, класс Звук может иметь функцию Получить. Если запустить ее для объекта ЗвукиЖивотных, она может отобразить на экране сообщение, советующее взять с собой диктофон и сходить в ближайший зоопарк. Если запустить эту же функцию для объекта Rock, может быть предпринята попытка открыть соответствующую Web-страницу с возможностью загрузки нужного файла.

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

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


Назад | Начало урока | Вверх | Вперед
Содержание