Реферат: Объектно-Ориентированное программирование
Название: Объектно-Ориентированное программирование Раздел: Рефераты по информатике, программированию Тип: реферат |
I. Развитие языков программирования Определения: ANSI – American National Standards Institute - Национальный Институт Стандартизации США ISO - International Organization for Standardization - Международная организация по стандартизации Цель развития языков программирования - более рациональная разработка ПП. Схема развития:
Коды
процессора
а
assembler а
языки
высокого уровня
(ЯВУ) При разработке процессоров(П)/микропроцессоров – для каждого П разрабатывается набор команд, полный набор насчитывает ~150 команд: арифметика, логика, работа с памятью, ввод и вывод. Команда для процессора – это цифровой код команды и операнд (операнды): ячейки памяти, регистры, порты ввода/вывода...
Коды
процессора
–
набор в цифровом
коде команд
процессора
и их параметров,
например, команды:
занесение
значения на
регистр, вывод
с регистра по
адресу памяти,
сложение, чтение
байта из порта
ввода, запись
байта в порт
вывода … Разработка программ в кодах была характерна для самых первых ВМ – это очень неудобно для человека-программиста. Assembler – низкоуровневый язык программирования, разработанный для конкретного процессора.
Assembler
использует
мнемоническое
обозначение
кодов команд
процессора
и переменных
памяти, что
облегчает
процесс программирования
по сравнению
с кодированием:
Недостатки – высокая трудоемкость разработки, привязка программы к конкретному типу процессора. Языки высокого уровня – FORTRAN, ALGOL, COBOL, PL/I, ADA, Prolog, PASCAL, C, C++, Perl, JavaScript, ASP, PHP, Java, С#, SQL…
ЯВУ
не зависят от
архитектуры
компьютера,
ориентированы
на эффективную
разработку
ПП, обеспечивают
быструю разработку
и надежность
ПО.
Среди
ЯВУ есть специализация:
научные расчеты
(FORTRAN),
для обучения
(ранний Basic,
Pascal),
экономические
рачеты (COBOL),
работа с БД
(dBase, FoxPRO,
SQL)
,
II.
Развитие технологий
разработки
программ
Низкоуровневое
программирование
(коды,
Assembler’s) а аООП (C++,Object PASCAL, Java, C#…) а а…(что дальше?)
Вспоминаем,
как обстояло
дело с разработкой
программ в 60-е
– 70-е годы, Процедурное программирование Основная идея – выделение части кода в отдельную процедуру (подпрограмму, функцию) (SUBROUTINE в FORTRAN, PROCEDURE и FUNCTION в PASCAL).
Будем
считать для
нашего курса
понятия процедура,
подпрограмма
и функция
синонимами
– так в Си есть
только функция.
Пример: // здесь реализация } В процедурных языках поддерживается вызов процедур/подпрограмм/функций, который обычно включает такие механизмы:
Каждая из базовых конструкций имеет один вход и один выход.
“Правильная”
структура
программы
(блока программы,
подпрограммы),
имеет один вход
и один выход.
Причем,
любую программу
можно и нужно
писать без
использования
оператора GoTo,
который очень
запутывает
структуру
программы
(аналог – команда
перехода JMP
в assembler). Одновременно с определением набора базовых конструкций была осознана необходимость предварительного анализа данных и конструирования структур данных на начальной стадии разработки программы. Следование этим правилам при разработке программ и означало применение структурного подхода к программированию. Эти правила ограничивали “свободу” программистов (по сравнению с assembler и Fortran), но позволяли писать более понятные, простые, надежные программы. Улучшился контроль над кодом, стала возможна реализация более крупных проектов.
На
базе принципов
структурного
программирования
(СП) был создан
новый, элегантный
язык PASCAL
(примерно
1968 г, Никлаус
Вирт) !Важно: Структура программы на Pascal состоит из разделов:
PASCAL
жестко навязывал
программистам
использование
стиля СП – это
был переход
на новый технологический
уровень в разработке
программ
общего назначения
(обработка
данных, научные
расчеты) –
это был технологический
прорыв. Cтруктурное программирование на С/С++ На
прошлом занятии
говорили о СП,
акцентировали
основные
положения: Сложнее всего следовать стилю СП было на assembler’s. Язык С(Си) создан в конце 60-х - начале 70-х для разработки системного ПО в рамках проекта ОС Unix (Деннис Ричи – один из авторов). С включает конструкции СП, в нем реализован механизм построения сложных структур данных. Но этот язык не навязывает жестко дисциплины СП, не имеет такой строгой структуры программы и строгого контроля типа данных, как PASCAL, позволяет напрямую работать с адресами через указатели и ссылки, он ближе к аппаратуре, позволяет делать вставки на ассемблере для работы с регистрами процессора. С
дает большую
свободу действий,
но требует от
программиста
большей самодисциплины.
В настоящее время С – универсальный язык программирования для разработки системного ПО (Unix и Windows), графических интерфейсов, сложного прикладного ПО (например, СУБД) - задач, где необходима эффективность выполнения программ. С – распространен, компиляторы реализованы в большинстве ОС. В
каждой реализации
UNIX есть
компилятор
С/С++ как важная
часть ОС. Первая широко известная реализация принципов ООП – это описание языка С++, начало 80-х, автором является Бьярне Страуструп. Кроме
компиляторов
в каждой Unix-подобной
системе распространены
компиляторы
В 1997 г был принят международный стандарт ANSI C/C++ - итог 20-тилетнего развития Существующий стандарт ANSI C++ - это классическое описание ООП. Сейчас язык С++ является языком публикаций по вопросам ООП. Практикум
на С/С++: Мы находимся сейчас на технологической ступени структурного программирования, поэтому начинаем с Си: Знакомство с С, некоторые конструкции СП: 0.Программа выводит на экран строку "Hello? World". #include
void main(){ // в программе всегда д.б. функция main, с нее начинается выполнение printf("Hello, World!"); getc(); } 1.// Комментарий до конца строки /* Комментарий много- строчный. В С/С++ отличаются прописные и строчные буквы. */ 2.Операторные скобки {} задают программный блок. int
i=5; // выделение
памяти и присваивание
значения int i=7; .. if(i<7){...}; // не выполнится } if(i<7){...} // выполнится // i внутри блока и i вне блока – это разные переменные 3.
Цикл ... if(...) break; // прервать цикл } if(i<99){...} //error, i не определено 4. Условный оператор bool fOk=true; // true/false int a, b=7; ... fOk=(a==b); // вычисляется логическое значение a==b и присваивается ... if(fOk){ //... выполняется, когда логическое выражение true } else{ //... выполняется, когда логическое выражение false } 5. Цикл с постусловием и выбор. char c; // 1 byte bool fOk; do{ // Начало цикла с постусловием fOk=true; printf(”Вы любите программировать?\n”); printf(”Ответ Д/Н/?:”); c=getc(); switch(c){ // case ‘Д’: printf(“Yes, sir”); break; case ‘Н’: printf(“No”); break; case ‘?’: printf(“Не знаю”); break; default: printf(“*Ошибка, повторите ввод”); fOk=false; } } while(!fOk); // Конец цикла с постусловием 6.
Структуры.
char Name[51]; int Age; char Course; char
Group[4]; // ПЭ2х float S; // Стипендия }; //! Важно, в конце описания структуры ставится точка-с-запятой; Student S1, S2; // Объявление двух переменных (выделение памяти) // Занесение значений в элементы структуры: S1.Age=21; S1.Kurs='2'; S1.S=550.5; strcpy(S1.Name,"Мистер Х."); // Копирование строки в строку структуры Student PE21[50]; // Массив структур strcpy(PE21[0].Name,"Lady Y"); PE21[0].Age=17; ... for(int i=0; i<50; i++){ // Начисление стипендии if(PE21[i].Ball>=4.0) PE21[i].S=400.0; else PE21[i].S=0.0; } Недостатки массивов – жесткие рамки Усложнения: расчет суммы стипендии по группе C/C++ Функция. Модуль. Проект. Заголовочный файл. В Pascal
есть понятия
процедура и
функция. Процедура – выполняет часть кода, не обязательно возвращает результат. В С/С++ нет процедур – только функции. Функции не могут быть вложенными. Функция вС/С++ - это самостоятельный фрагмент кода, имеющий свое имя, список параметров и возвращаемое значение. [<тип возвр.значения>] <имя функции>([список параметров]){ // тело функции (body) return [<возврашаемое значение>]; } Для
каждого параметра
в списке указывается
тип. Функция, которая не возвращает значения, должна быть описана с типом void. // Пример функции вычисления среднего: float Average(float a, float b){ return (a+b)/2; } void main(){ //… float A=10.5, B=11.7, C=0.0; C=Average(A, B); // Вызов функции //… } Любая функция (кроме main) должна быть описана в тексте программы до момента ее вызова. В
качестве описания
можно применить
объявление
прототипа
(прототип/заголовок/интерфейс
функции - синонимы),
т.е. указать
имя функции,
список передаваемых
параметров
и тип возвращаемого
значения, например:
Прототип функции обычно размещают перед функцией main, или в заголовочном файле, а реализация функции (ее код, тело) может находиться в другом месте, например: в конце текущего модуля, или в другом модуле, или в библиотеке функций. В С++ допустимо иметь несколько функций с одинаковым именем и различающимися списками параметров (это проявление полиморфизма), например: int Summ(int A, int B); // вычисление суммы для int float Summ(float A, float B); // вычисление суммы для float В программе С/С++ должна быть функция main (главная) – именно она вызывается при запуске программы на выполнение: int main(int argc, char* argv[]){ // ДЗ: разобраться с параметрами main //
int
argc
– число параметров
в командной
строке if(fOk) return 0; //возвращение кода завершения else return -1; //возвращение кода завершения } Параметры функций. Параметр, или аргумент – это значение, передаваемое в функцию. При разработке функций используются имена параметров, которые являются формальными (условными). В момент вызова функции ей передаются фактические (реальные) значения параметров, которые подставляются на место формальных. Параметры в функцию могут передаваться по значению и по ссылке. При передаче параметра по значению создается его копия в локальной переменной и далее функция работает с этой копией. Это неудобно в следующих случаях:
При передаче параметра по ссылке в функцию передается только адрес исходной переменной. Изменение значения параметра приводит к изменению исходной переменной. Передача параметра по ссылке подобна передаче в качестве параметра указателя на переменную. Примеры: // 1.Функция сохраняет значения структуры в файле bool SaveToFile(TStudent S){ // Это передача параметра по значению, //... // создается локальная копия параметра FILE *outS; outS=fopen(PathOutFile, "at"); //... fprintf(outS, "%s %s \n",S.Name, strAge ); //... fclose(outS); return true; } // 2.Функция читает значения из файла в структуру и возвращает ввод bool InputFromFile(TStudent &S){ // Это передача параметра по ссылке, //... // иначе ввод будет потерян FILE* inS; inS=fopen(PathInFile,"rt"); scanf(inS,"%s", &S.Name); //... return true; } ДЗ
Самостоятельно:
функции, параметры,
возвращаемые
значения; функция
main; Модуль.
Оптимальный
размер модуля,
удобный для
разработки
и отладки –
200-400 строк текста. Обычно
в модуле располагается
одна или несколько
тематически
однородных
функции:
Проект C/C++ - это средство для объединения модулей в единую программу или библиотеку. Целью разработки проекта может быть не только программа, но и библиотека функций, библиотека классов. В проекте исполняемой программы есть головной модуль, который содержит функцию main. В проект могут подключаться модули в виде исходного текста С++ (файлы *.cpp), в виде объектного модуля (файлы *.obj), в виде библиотек функций (файлы *.lib), и др... В IDE Borland C++ есть средства ведения проекта: окно проекта, пункт меню Project. Можно
создать новый
модуль, либо
добавить существующий
модуль в проект. На практической работе предполагается создание рабочего проекта, состоящего из нескольких модулей. Заголовочные
файлы. Препроцессор
С/С++. Имя заголовочного файла может совпадать или не совпадать с именем модуля. Пример заголовочного файла (Student.h) //----------------------------------------------------------------- #define MIN_AGE 12 // Константы, часто используются такие описания #define MAX_AGE 99 struct TStudent { //... Описание нового типа данных TStudent }; bool InputDataStudent(TStudent* S); // Прототипы функций void OutputDataStudent(TStudent* S); int AverageAge(); //----------------------------------------------------------------- Для доступа к этим константам, описаниям типов данных и прототипов функций необходимо включить заголовочный файл в наш модуль с помощью директивы (оператора) препроцессора:
Препроцессор был разработан для языка C. Он выполняется до начала компиляции и производит обработку директив в тексте модуля. Например, на место #include <файл.h> подставляется содержимое этого файла, по тексту программы производится замена макросов из #define MIN_AGE на 12, и т. д. С
системой
программирования
C/C++
поставляется
множество
библиотек,
содержащих
предопределенные
константы,
макросы, функции
и классы. Приложение: // Пример заголовочного файла (Student.h) #ifndef STUDENT_H #define STUDENT_H #define MIN_AGE 12 // Константы, часто используются такие описания #define MAX_AGE 99 typedef struct s{ // Описание новых типов данных char Name[20]; int Age; //... } TStudent; bool InputDataStudent(TStudent* S); // Прототипы функций void OutputDataStudent(TStudent* S); int AverageAge(); #endif //----------------------------------------------------------- #include
#define EOF (-1) /* End of file indicator */ #define MAX(a,b) ((a>b)? a : b) // Макрос Макросы ненадежны, их контроль компилятором ограничен.
В С++ применение
макросов не
рекомендовано,
в этом нет
необходимости,
т.к. в базовых
конструкциях
языка появились
константы,
шаблоны и inline
функции. Ввод/вывод C/С++. Ввод/вывод (Input/Output, I/O) – это операции обмена данными между программой и внешними устройствами. Операции ввода/вывода инициирует программа, поэтому:
Операции Assembler: IN: взять байт из порта и поместить в ОП OUT: взять байт из ОП и поместить в порт Потоки и файлы Файл
(file) – это
способ хранения
информации
на физическом
устройстве: Ввод/вывод
в программе
не должен зависеть
от устройства, Для
отделения
операций ввода/вывода
в программе
от физического
устройства
хранения файлов
в системах
программирования
реализуется
схема вв./выв.
с промежуточным
звеном: function I/O в программе а а stream (поток) а а file (файл на устройстве) Поток
(stream) – это
абстрактный
уровень взаимодействия
между программой
и физическим
устройством.
В Си (не в С++) для этого служит управляющая структура потока FILE. (FILE,
например, включает
буфер для
промежуточного
вв/выв, указатель
на текущую
позицию в буфере)
(FILE описана
с Потоки бывают буферизованные (блокированные) и небуферизованные (НЕблокированные). Буферизованные потоки вывода накапливают информацию в буфере перед выводом на устройство. Буферизованные потоки ввода производят опережающее чтение в буфер потока. Буферизация служит для оптимизации вв/вывода (размер сектора на диске 512 байт, кластер. Для вв/вывода даже 1 байта необходимо записать/перезаписать сектор/кластер). Небуферизованные потоки выводят информацию немедленно – например, срочные сообщения необходимо выводить в небуферизованный поток (стандартный поток stderr). Для принудительного вывода при работе с буферизованными потоками есть специальная операция: fflush(...) – выравнивание, сброс на диск Ввод/вывод в С В описании языка Си нет операций ввода/вывода, они реализованы через функции. Ключевые слова в названиях функций: read/write – неформатированный вв/вывод, сплошной массив байт get/put, scan/print, open/close например: printf, fprintf, sprintf - ключевые слова используются с различными префиксами – вывод в стандартный поток, вывод в файл, вывод в область памяти. Функций избыточно много, например, реализован дублирующий набор функций для совместимости с UNIX, есть функции для работы с консолью в DOS. Охватить все в рамках занятий нереально, поэтому, ограничимся общими рекомендациями: Что-то все-равно нужно знать аДокументация (общее представление) а Help а Заголовочный файл Пример Си, вывод данных структуры в текстовый файл: struct Student{ char* Name; int Age; }; Student AS[43]; //... FILE *outS; // Объявление потока outS=fopen("D:\\Data\\Person.dat",
"wt"); // Открытие
потока,
связывание
с
файлом for(int i=0; i<43; i++){ fprintf(outs,"%s, - ему %s лет\n", AS[i].Name, itoa(AS[i].Age) ); } fclose (outS); //-------------------------------- //-------------------------------- Student P; FILE*InP=fopen("Person.dat",
"rt"); // Открытие
потока,
связывание
его
с
конкретным char* cAge[3]; //Временная переменая для ввода символьного числа, соответств. Age fscanf(InP,"%s %s", &P.Name, cAge); // Ввод из файла через поток //или fgets(InP,P.Name); fgetc(InP,cAge); fclose(InP); // закрытие потока и файла. Ввод/вывод в С++(начальные сведения) Функции Си можно использовать в С++. В С++ ввод/вывод определен через базовый набор классов, поставляемых с компилятором. Следующие
классы реализуют
потоки, они
объявлены в
заголовочном
файле istream – поток для ввода iostream - поток для ввода/вывода В С++ есть возможность, и есть необходимость, разрабатывать ввод/вывод для вновь конструируемых классов. Обычно это называется переопределением операций ввода/вывода. Подробнее о переопределении операций вв/выв поговорим позже, когда будем конструировать классы.А пока познакомимся со стандартными средствами: Cтандартные потоки С++: cin – стандартный поток ввода (консоль) cout – стандартный поток вывода (консоль) cerr – стандартный поток вывода ошибок небуферизированный clog – стандартный поток вывода ошибок буферизированный Стандартные потоки объявлять не надо. Операция
ввода из потока
>> , операция
вывода в поток
<< Пример: cout<<"Введите имя:"; // Вывод строки на консоль (в поток cout) cin>>P.Name;
// Ввод значения
с клавиатуры
через поток
cin
cout<<"\n"<<"Введено имя: "<<P.Name<<"\n"; Для освоения темы ввода/вывода в С++ необходимо разобраться с переопределением операций ввода/вывода для классов (см. "Практическую работу №2") Потоковые классы, стандартные потоки, файловые потоки С++: Павловская, стр 265. Схемы иерархии модулей в типичном проекте программы обработки данных. Проект-проектирование. Структура проекта – состав модулей и их взаимодействие. В
процессе
проектирования
происходит
разбиение всей
программы на
функциональные
модули и составление
схемы иерархии.
Здесь
типичная схема
программы
обработки
данных: Головной модуль осуществляет общее управление и последовательно вызывает и контролирует выполнение подчиненных функциональных модулей, например, в такой последовательности: Ввод данныха Обработка а Диалог с оператором а Сохранение данных Например, (см. схему) есть три формата исходных данных в файлах, которые необходимо обработать и сохранить результат. Модуль ввода – двухуровневый - может содержать три функции разбора формата, и одну головную функцию ввода. Далее вызывается модуль обработки, ему передаются данные ввода, после обработки вызывается модуль сохранения данных. Диалог с оператором ведется по мере необходимости. Модуль на этапе проектирования - это функционально обособленый фрагмент проекта, например: модуль ввода, модуль обработки, головной модуль программы. Разбиение проекта на функциональные модули и составление схемы иерархии модулей происходит на этапе проектирования. Разбиение программы на модули позволяет справиться со сложностью больших проектов. Разработка больших модулей ведется как разработка отдельных проектов. C/C++. Преобразование проекта из исходного текста в исполняемый модуль Рассмотрим процесс разработки программы. В процессе разработки исходный текст проходит несколько этапов обработки, прежде чем получится готовая к исполнению программа TextС/С++-> PreProc-> Compile-> ObjectModule(*.obj)-> Link-> EXEModule Edit Library(*.lib)-> (*.c,*.cpp,*.h) ЯDebugЯ (см. также Павловская, стр 17) TextEditor – создание текста исходных модулей проекта на языке программирования – "кодирование"( "кодировщики" - жаргон) Кодированию предшествует проектирование.. Compile
(compilation, компилятор,
составитель)
– производит
контроль текста
программы,
выдает сообщения
об ошибках
времени компиляции
– это начальный
период отладки. Object
module(obj)
– операторы
исходного
текста программы,
преобразованные
во внутреннее
машинное
представление
– "код", инструкции
процессора.
Obj
содержит
откомпилированные
операторы языка
+ вызовы функций. например, это вызовы библиотечных функций printf, scanf, …, или вызовы разработанных ранее функций из другого модуля проекта. Link – редактор связей, сборщик программы - производит сборку исполняемого модуля - execute module, exe - из одного или нескольких объектных модулей и библиотек. При
сборке в ехе
-модуль из библиотек
и других obj-модулей
проекта заносится
код функций,
вызовы которых
указаны в исходном
тексте программы
Далее *.ехе запускается на выполнение, тестируется, отлаживается. Замечания по стилю разработки и оформлению текстов программ Комментирование Первой строкй любого модуля д.б. комментарий с указанием // Автор, ПЭ21, ДВГТУ, ООП, dd.mm.yy // Проект, назначение модуля Каждую функцию также следует предворять комментарием, если ее код не тривиален. Тест для комментария – понятность для не посвященного в проблему специалиста. Комментирование
программ –
профессиональный
навык. Фрагмент текста программы (строку), который уже не используется, а удалять еще жалко – можно закомментровать. Правила именования переменных Имя
может состоять
из нескольких
слов: каждое
слово пишется
с большой буквы,
слова пишутся
слитно; TCar, TAutobus InputFromFileToPerson
Разумное
комментирование
и выбор имен
придает текстам
программы
свойство
самодокументированности.
Текст
программы легче
анализировать,
когда он структурирован,
разбит на блоки
Например:
описание структуры,
функция, тело
цикла и др.
Это позволяет
визуально,
графически
контролировать
структуру
программы. Пропуск закрывающей скобки иногда приводит к непонятным сообщениям компилятора. Комбинация клавиш < Ctrl+ {> и < Ctrl }> – позволяет контролировать блоки.
!Все без
исключения
практические
работы необходимо
оформлять по
этим правилам C/C++.
Интегрированная
среда разработки
фирмы Borland
IDE - Integrated Development Environment IDE
для разных
платформ
разработки
ПО: MS
Visual Studio Linux (?) (Emacs) Основные компоненты IDE:
А также:
Цикл
разработки
ПО с точки зрения
программиста: ->Подготовка текста программы в редакторе -> Запуск компилятора ->Возврат:Анализ и исправление ошибок ->Получение загрузочного модуля -> Выполнение загрузочного модуля -> Возврат:Анализ и исправление ошибок ->… и так далее Интегрированная среда разработки ПО объединяет все этапы в единую технологическую цепочку, помогает программисту на каждом этапе цикла разработки ПО. Не путать интегрированную среду и компилятор языка программирования! Компилятор
– отдельная
программа для
преобразования
исходного
текста в исполняемый
код программы.
Современные IDE предлагают также визуальное конструирование для оконного интерфейса и создания скелетного кода программы. Пример: обработка события нажатия кнопки. Многие современные среды разработки имеют сходные функции, освоив одну - не сложно переключиться на другую. Освоение современной среды программирования сравнимо по сложности с освоением нового языка программирования. Borland IDE – это современная профессиональная среда разработки, весьма сложная в освоении. Начало работы в IDE: В
процессе разработки
исходный текст
программы
проходит несколько
этапов обработки,
прежде чем
получится
готовая к исполнению
программа.
Это важный класс задач, многие полезные программы не требуют графического интерфейса. Создание программ с графическим интерфейсом Windows – в перспективе, на следующей дсциплине. Последовательность
действий в IDE
по созданию
приложеня
Console
application:
->Запуск
на компиляцию
– выявление
ошибок – сначала
потребует
сохранить
файлы –> выполнение ЗМ с возможностью отладки. Типы файлов (расширения), создаваемые системой IDE в проекте: *.cpp, *.h - исходные тексты программы, исходные модули; этим файлам необходимо обеспечить максимальную защиту. *.bpr, *.bpg, *.bpf – файлы проекта и группы проектов; (желательно сохранять, но проект легко можно собрать и заново из исходных модулей) *.~cpp, *.~h *.~bpr - backup files (со знаком "тильда") – содержат копию исходного текста/проекта, предшествующую последней корректировке. *.obj – объектные модули, промежуточный формат, создаются каждый раз при компиляции исходных модулей (опция настройки Intermediate output) *.tds – временный файл отладчика, обычно это самый большой файл в проекте. *.exe – загрузочный модуль, исполняемый модуль (опция настройки Final output) Рекомендации по управлению файлами проекта:
Отладка в интегрированной среде С++ Builder. Цель отладки – локализация и устранение ошибок, приводящих к неверному функционированию программ. Т. е. программа уже откомпилирована, запускается, но работает неверно, либо выдает системное сообщение и завершается с ошибкой. Примеры ошибок:
Что делать? Ошибки в программе при разработке – это вполне нормально, и отладка - естественный этап разработки. В основе отладки лежит возможность просмотра значения переменных в процессе выполнения программы. Сначала необходимо выдвинуть предположение, связанное с местом возникновения ошибки и возможной причиной. Затем установить контрольную точку (break point) до этого места. Запустить программу, она начнет выполняться и остановиться на строке break point. В этот момент можно просмотреть значения переменных, критичных для выполнения последующих строк. Далее возможно пошаговое выполнение программы с наблюдением значений переменных до возникновения ошибки. Основные приемы отладки:
Современные системы разработки имеют хорошие средства отладки. В IDE C++Builer есть хороший встроенный отладчик. Некоторые ключевые слова и клавиши управления Inspect – Alt+F5 – наблюдение за объектом (подробно) Watсh - – наблюдение за несколькими объектами Trace into – F7 – пошаговое выполнение с переходом внутрь функций Step ower - F8 – пошаговое выполнение, включая вызов функций …Развитие С а С++ – переводит разработку ПП на современную технологию ООП. С++.
Основы Объектно-ориентированного
программирования
(ООП). C++ является объектно-ориентированным расширением языка C. Первая широко известная реализация принципов ООП – это описание языка С++, Бьярне Страуструп, начало 80-х. В
каждой реализации
UNIX
есть компилятор
С/С++ как составная
часть ОС. В 1997 г был принят международный стандарт ANSI C/C++ - итог 20-тилетнего развития Существующий стандарт ANSI C++ - это классическое описание ООП. Сейчас язык С++ является языком публикаций по вопросам ООП. _______ Итак, С++, ООП: Мы уже хорошо знаем работу со структурами данных в Си, это наша отправная точка.. А сейчас познакомимся с важными понятиями ООП в языке С++: объект и класс. ! Основная идея ООП – размещение внутри одного объекта структуры данных и функций обработки этих данных: Объект = структура данных + функций обработки этих данных; Объект
– определение
Ивари Якобсона:
В С++: Новое понятие class – служит для определения новых типов объектов. (подобно структуре struct TStudent, которая служит для определения нового сложного типа данных. Объектом в С++ называют конкретный экземпляр реализации класса. (Как переменная типа TStruct является экземпляром структуры, который наполняется реальными данными) class включает поля и функции / свойства и методы / атрибуты и операции – эти пары терминов, по терминологии процедурного программирования, относятся к данным и функциям обработки этих данных. class имеет разделы: private и protected – защищенные, недоступные снаружи, и public – доступный, реализующий интерфейс класса, т.е. способы работы с ним. Принципы, составляющие суть ООП. Рассмотрим три:
Д.З. Павловская. Часть II. ООП (стр. 173). Основные идеи, принципы ООП. Глава 4. Классы (179). Глава 5. Наследование (201). Березин, стр.166. Описание класса и объявление объекта в языке С++: Пример конструирования класса, подобного структуре TStudent class TStudent{ // Описание класса private: char Name[20]; //... описание внутренних, недоступных снаружи, int Age; // методов и свойств public: //... описание интерфейса, т.е. доступных методов и свойств класса TStudent(...){...}; // Конструктор, об этом позднее void SetAge(int a){Age=a;}; void SetName(char* n){strcpy(Name,n);}; char* GetName(){ return &Name[0];); int GetAge(){ return Age;); void InputFromConsole(); void OutToConsole(); bool SaveToFile(char* PathFile); // только прототипы методов, bool GetFromFile(FILE* inStud); // реализация где-то позднее }; TStudent S1; // Объявление объекта (конкретного экземпляра класса) // Работа с объектом через вызов методов S1.SetName("Илья Муромец"); S1.SetAge(33); S1.SaveToFile("C:\\Temp\\Student.dat"); TStudent AS[43]; // Объявление массива объектов AS[0].InputFromConsole(); AS[0].SaveToFile(); Классы позволяют осуществлять строгий контроль доступа к объекту. Можно работать c объектом только через функции и переменные из раздела public, т.е. через интерфейс. S1.Age=18;
// это попытка
прямого обращения
к полю из private
раздела, Такой контроль обеспечивает первый принцип ООП – инкапсуляцию – сокрытие и защиту механизма реализации класса. ------------------- Далее важные вопросы ООП: классы и функции-члены класса, конструкторы и деструкторы. Классы и функции - члены класса. Класс – это определяемый разработчиком новый тип. В С++ есть средства для определения класса, создания объектов этого класса, работы с этими объектами и средства уничтожения этих объектов. Функции, описанные внутри разделов класса (как полные функции, так и прототипы), называются members functions – функции-члены класса/методы класса. Они могут вызываться только через обращение к объекту этого класса (аналогия с элементами структуры): SaveToFile();// Неверно, не указан объект, // для которого вызывается метод S1.SaveToFile(); // Верно Если в описании класса указан только прототип функции, а реализация находится за пределами класса (описание класса в заголовочном файле, а реализация функций-членов класса в файле *.cpp), то необходимо указывать имя класса и знак "::" : void OutToConsole(); // так был описан прототип в классе TStudent void TStudent::OutToConsole(){ // А это реализация в функции printf("Name=%s\n", Name); printf("Age=%d\n",Age); } ------------------------------------------------- Конструкторы и деструктор Конструкторы Есть необходимость инициализировать объекты класса в момент создания (т.е. задавать начальное состояние). Для этого служат специальные функции-члены класса, которые называют конструкторами. Имя
функции-конструктора
совпадает с
именем класса. Конструкторов в классе может быть несколько, они отличаются списками параметров. Возможность создать несколько одноименных функций класса (методов) - это проявление полиморфизма ООП Примеры конструкторов: class TDate{ // Класс для работы с датой ... public: // Конструкторы, только прототипы, реализации в другом месте TDate(int dd, int mm, int yy); // TDate(int); // порядковый номер дня от Р.Х. TDate(char*); // строка "dd.mm.yy" TDate(); // текущая дата по компьютерным часам ... }; ... // Создание и инициализация объектов через вызов конструкторов TDate today(22, 11, 2002); TDate April1("01.04.2001"); TDate now(); В конструкторах, как и в обычных функциях, возможно использование значений по умолчанию: class TDate{ ... public: TDate(int d=0,int m=0,int y=0); // по умолчанию все значения 0 ... } ... TDate::TDate(int d,int m,int y){ ... day=d?d:today.day; // если d==0, то d=today.day }; //?TDate d=today; // возможна инициализация объекта посредством присваивания Деструктор Имя
функции-деструктора
совпадает с
именем класса
с добавлением
знака ~ "тильда". Деструктор вызывается автоматически при выходе объекта из зоны видимости. Возможен явный вызов деструктора для динамически создаваемых объектов через операцию delete. Пример деструктора: class X{ char* S; public: X(int); ~X(); }; X::X(int N){ // Конструктор S=new char[N]; // Динамическое выделение памяти } X::~X(){ // Деструктор delete
S;
// Освобождение
динамически
выделенной
памяти X Obj1(256); X* pObj2=new X(125);// динамическое выделение памяти под объект типа X delete pObj2; // явный вызов деструктора Итоговые замечания по конструкторам и деструкторам:
Прежде, чем идти дальше, необходимо поговорить про механизм распределения памяти в программах на С/С++. С/С++: Виды объектов в памяти и время их жизни :
Под объектом здесь понимается переменная, массив, структура, объект класса. Примеры: // Статическая переменная: static char* sPathData="C:\\Temp\\Test.dat"; // Автоматическая переменная, "живет" только внутри блока: { char aS[80]; //... } // Динамически создаваемая переменная, явное выделение памяти // и явное уничтожение: char* pStr=new char[80]; //... delete
pStr; С++. Основы ООП. Наследование классов. Важнейшим свойством ООП является наследование. Например, есть класс с определенными свойствами. В целом он нас устраивает, но необходимо добавить некоторую функциональность. аМожно создать новый класс на основе существующего через наследование. Исходный класс называют предок/родитель/базовый класс/parent, а новый класс - наследник/потомок/производный класс/derived/child. Производный
класс обладает
всеми свойствами
и методами
предка (он их
унаследовал),
плюс добавляются
новые. Так строится иерархия классов – важное понятие ООП. При необходимости в производном классе можно перекрыть (переопределить) свойства и методы базового класса. Примером многоуровневой иерархии классов является VCL от фирмы Borland. Форма записи заголовка производного класса: class <наследник> : <режим доступа> <базовый класс> {<…>} class B : public A // класс B наследует классу A, класс B выведен из A { // реализация B, расширяющая возможности А } class ColorPoint : public Coord{ //... } Режимы доступа: внешнее, защищенное и внутреннее наследование: Вспомним описание разделов класса сlass имеет разделы с различным режимом доступа: public – доступный, открытый раздел, реализующий интерфейс класса, т.е. способы работы с ним. private – внутренний, закрытый раздел класса, недоступный снаружи (недоступен и в порожденном классе тоже). protected
– защищеный
раздел класса,
недоступный
снаружи, доступен
только в порожденном
классе при
наследованиии.
Важно: protected используется при проектировании базовых классов в иерархии. Т.е. классы разрабатываются в предположении возможного наследования. Эти же режимы доступа - public, private и protected - используются при описании заголовка наследуемого класса перед именем базового класса: class <наследник>: {public | private | protected} <предок>{ // задается один из режимов }; Режим доступа public – внешнее наследование - интерфейс базового класса (раздел public) становится внешним интерфейсом производного класса (применяется чаще всего). Режим доступа protected – защищенное наследование - внешний и защищенный разделы базового класса становится защищеными разделами производного класса, т.е. доступны только при следующем наследовании. Режим доступа private – внутреннее наследование - внешний и защищенный разделы базового класса становится внутренними разделами производного класса, недоступны снаружи. Пример: class Coord{ // базовый класс, описание двумерных координат protected: int x,y; public: Coord(); SetCoord(int x,int y); void Draw(); } //
Через наследование
создадим класс
для описания
трехмерных
координат protected: int z; // Добавим новое свойство – еще одну координату public: Coord3D(); SetCoord(int x,int y, int z); // перекрытие метода, полиморфизм void Draw(); } void Coord3D::SetCoord(int X, int Y, int Z){ // реализация метода z=Z; // Задать координаты x и y можно через метод базового класса: Coord::SetCoord(X,Y); // уточнение имени // ... или прямым присваиванием: x=X; y=Y; }
class ColorPoint3D : protected Coord3D{ // защищенное наследование private: int Color; int Radius; public: Draw(); }; Множественное наследование В С++ при наследовании есть возможность задать несколько классов в качестве базовых: class Brush{ // кисть для рисования, "пятно" protected: int Color; int Radius; … }; // Новый класс является наследником сразу двух базовых классов: class ColorPoint : protected Coord, private Brush{ // public: Draw();// метод Draw использует свойства и методы двух классов }; class ColorPoint наследует двум классам: Coord и Brush – это и есть множественное наследование . Множественное
наследование
– мощное средство
языка С++, но
порождает ряд
проблем. --------------------------------------------------- Наследование классов (продолжение) Помним,
что наследование
применяется
для построения
иерархии
классов. При необходимости в производном классе можно перекрыть (переопределить) свойства и методы базового класса. Виртуальные методы (виртуальные функции) Виртуальные методы– это методы, прототипы которых объявлены в базовом классе с использованием ключевого слова virtual. Виртуальные
методы позволяют
уже в базовом
классе задать
действия, общие
для всех производных
классов, т.е.
базовый класс
задает основной
интерфейс,
который будут
иметь производные
классы. Реализации виртуальных методов в базовом классе может быть переопределена в производных классах. Каждый
производный
класс может
задать свою
реализацию
виртуальных
методов, а если
не задает, то
используется
метод базового
класса. Пример: Иерархия классов для графического редактора: Базовый класс Shape (форма, шаблон), порожденные Circle, Ellipse, Square, Triangle У всех порожденных классов будет метод Draw. Класс Shape объявляет метод Draw как virtual. class Shape : public Coord{ // форма, шаблон, фигура public: // виртуальный метод, реализация фиктивна virtual void Draw(){cout<<"This is Shape";}; // или virtual void Draw()=0; // чистый виртуальный метод (см. ниже), // реализация отсутствует, требуется // переопределение в производном классе. } // Через наследование создаем новый класс: class Circle : public Shape{ // окружность, через наследование protected: int Radius; public: ... void Draw(); // переопределение виртуального метода для Circle } … Circle C1; // Объект типа Circle C1.Draw(); // Вызов метода Draw для объекта Circle C1.Shape::Draw(); // Вызов метода Draw для объекта Shape Shape* pSh; // Указатель на базовый класс pSh=&C1; // Указатель на базовый класс используется для // объекта порожденного класса pSh->Draw(); // Вызов метода Draw для объекта Shape ((Circle*)pSh)->Draw(); // Преобразование указателя на базовый класс // в указатель на производный и вызов // метода Draw для объекта Circle // подробнее см. в литературе Чистые виртуальные методы и абстрактные классы Чистые
виртуальные
методы объявляются
в базовом классе
как virtual,
но не имеют
реализации
в базовом классе.
virtual void Draw()=0; // чистый виртуальный метод (pure virtual), // реализация отсутствует, требуется // переопределение в производном классе. Если класс имеет чисто виртуальные методы, то такой класс называется абстрактным. Не может быть объекта такого класса, т.к. не определена реализация чистой виртуальной функции. Абстрактные классы используются как базовые при создании иерархии классов. Виртуальные методы - это одно из проявлений полиморфизма ООП. С++. Основы ООП. Спецификатор inline class date{ int month, day, year; public: // inline members function void date(int d,int m,int y){ day=d; month=m; year=y } void set_date(char*); } ... // inline members function inline void date::set_date(char* StrDate){ // здесь реализация } При разработке классов используется большое число маленьких функций. Это может понизить эффективность работы программы за счет накладных расходов на вызов функций. Inline-механизм делает подстановку вместо вызова функций, что увеличивает объем кода программы, но повышает скорость выполнения. Inline считаются все функции, определенные внутри описания класса. Функции можно описать как inline и вне описания класса. Статические члены класса Класс - это тип, по которому строятся объекты. Каждый объект имеет свое содержание, свою копию данных. Иногда есть необходимость в единых данных для всех объектов одного класса. Такие члены класса называют статическими, они создаются с помощью описателя static. Статический элемент класса - только один для всех объектов этого класса. class AirMove{ static float g=9.8; // статическая переменная, ... // единая для всех объектов типа AirMove } Указатель this
class TStudent{ char* Name; int Age; public: SetData(char* Name, int Age); //... }; void TStudent::SetData(char* Name, int Age){ strcpy(this->Name,Name); this->Age=Age; } Спецификатор доступа friend – используется для объявления функций, дружественных классу. Дружественные
функции не
являются членами
класса, но имеют
доступ в закрытый
и защищенный
разделы класса
(private и protected). Почему бы не использовать наследование? Главная цель создания "друзей" – это перегрузка бинарных операторов (=, +,-…) и операций ввода << и вывода >> - об этом позже. Дружественными могут быть не только функции, но и классы. (см. также Павловская, стр. 187) -------------------------------------------- С++. Основы ООП. Переопределение операторов (перегрузка операций) В С++ понятие оператора (операции) трактуется очень широко: операторы
арифметические,
логические,
ввода/вывода,
присваивания,
индексирования
[], Операции над объектами нового класса желательно записывать в привычной форме, с использованием знаков операций. Например, присваивание или сравнение для объекта типа TStudent хотелось бы записать так: TStudent P1, P2; // Объявление двух переменных P2=P1; // Присваивание //... if(P2==P1){ // Сравнение // здесь действие ...; } В С++ есть механизм переопределения операций. Перегрузка операций является, фактически, одним из видов перегрузки функций. Для перегрузки операции задается функция операции: <возвращ.знач>
<имя класса>::operator#(<список
аргументов>) // здесь реализация операции } Приведем пример переопределения операций = и + для класс Сoord Class Coord{ // Координаты на плоскости private: int x,y; public: //... Coord operator= (Coord ob2); // Переопредение operator=, прототип Coord operator+ (Coord ob2); // Переопредение operator+, прототип } Переопределим operator= для Coord как член класса, вот реализация: Coord Coord::operator=(Coord ob2){ x=ob2.x; y=ob2.y; return this; // возвращение объекта, которому присвоено значение } Coord P1, P2, P3; //
Следующие
записи
эквивалентны: P1=P2; // Это вызов operator=() через знак переопределенной операции Переопределим operator+ для Coord как член класса, вот реализация: Coord Coord:: operator+ (Coord ob2){ Coord temp; temp.x=x+ob2.x; temp.y=y+ob2.y; return temp; // } //
Следующие
записи эквивалентны: P3= P1.operator+(P2); // Это вызов operator+() как метод класса Переопределим operator+ для Coord как дружественную функцию класса Class Coord{ // Координаты на плоскости // Объявление прототипа дружественной функции в описании класса: friend Coord operator+ (Coord ob1, Coord ob2); private: //... } // Реализация дружественной функции Coord operator+ (Coord ob1, Coord ob2){ Coord temp; temp.x=ob1.x+ob2.x; temp.y=ob1.y+ob2.y; return temp; // } Переопределение операторов ввода/вывода
Ввод/извлечение из потока/extracting Следующие
классы реализуют
потоки, объявлены
в заголовочном
файле istream – поток для ввода iostream - поток для ввода/вывода Вспоминаем: cin, cout – имена стандартных потоков int i=10; cout<<"i="<<i<<";"; // несколько операторов вывода в одной конструкции cout<<"Input int:"; cin>>i; Перегрузка операторов ввода << и вывода >> для создаваемого класса может заменить методы ввода/вывода и упростить использование класса. Форма записи: ostream &operator<<(ostream &stream, имя_класса ob){ // реализация вывода; } istream &operator>>(istream &stream, имя_класса ob){ // реализация ввода; } Оператор
ввода/вывода
должен возвращать
ссылку на поток
для корректной
работы такой
конструкции: выполняется
вывод ob1-
возврашается
ссылка на поток,
поэтому, перегрузка операторов вв/выв. для класса реализуется как дружественные функции класса. Пример: class Coord{ friend istream &operator>>(istream &In, Coord &Ob2); friend ostream& operator<<(ostream &Out, Coord Ob2); private: public: // Constuctors: Coord(){ x=0; y=0;}; //... }; //------------------------------------------------------------- // Переопределение оператора вывода, дружественная функция, реализация: ostream &operator<<(ostream &Out, Coord Ob2){
Out<<"x="<
Out<<"y="< return Out; } cout< Итог: Перегрузка операций – это проявление полиморфизма ООП. Перегружаемая операция всегда связана с классом: является членом класса или дружественна классу. Замечание: Невозможно изменить приоритет выполнения операций. Проработать эту тему подробно по литературе (например, Березин. С и С++. стр 249) Пример перегрузки операций сложения, присваивания, ввода/вывода (файл Coord.cpp) // A. Zhuravlev. // OOP. Themes: OOP->Inheritance, OOP->Polymorphism #include
// Base class Coord class Coord{ // Redefine input/output //friend istream &operator>>(istream &In, Coord &Ob2); friend ostream& operator<<(ostream &Out, Coord Ob2); privateЖ: int x,y; public: // Constuctors: Coord(){ x=0; y=0;} Coord(int X, int Y){ x=X; y=Y;} // Implementation: void Set_Coord(int X, int Y){ x=X; y=Y;} int GetX(){return x;} int GetY(){return y;} // Redefine operators: Coord operator+(Coord Ob2); Coord operator=(Coord Ob2); }; //------------------------------------------------------------- // Redefine operations: Coord Coord::operator+(Coord Ob2){ Coord temp; temp.x=x+Ob2.x; temp.y=y+Ob2.y; return temp; } Coord Coord::operator=(Coord Ob2){ x=Ob2.x; y=Ob2.y; return *this; } //------------------------------------------------------------- // Redefine input/output ostream &operator<<(ostream &Out, Coord Ob2){
Out<<"x="<
Out<<"y="< return Out; } /* istream &operator>>(istream &In, Coord Ob2){ //... } */ //------------------------------------------------------------ // Testing: void main(){ Coord C1(10,20); Coord C2; //Coord* C3=new Coord(-1,-1); C2=C1; C1.Set_Coord(9999,7777); //C2=C1+C3; //??? cout<<"Coord
C1:"< cout<<"Coord
C2:\n"< //cout<<"(C1+C3)="<<(C1+ *C3);
//cout< return; } ДВГТУ, ООП, Май 2004
Вопросы
на экзамен по
учебной дисциплине I. Общие вопросы программирования
II. Основы объектно-ориентированного программирования
III. Практика программирования.
IV. Ввод/вывод в С/С++.
|