Концепция объектно-ориентированного программирования
Стр 1 из 3Следующая ⇒ КОНСПЕКТ ЛЕКЦИЙ По объектно-ориентированному программированию (язык программирования С++) Кафедра Автоматики и Управления Канашев Е.А.
Челябинск Оглавление Оглавление................................................................................................................................................................... 2 Литература.................................................................................................................................................................... 3 1. Концепция объектно-ориентированного программирования.................................... 4 2. Расширение языка C.............................................................................................................................................. 8 3. Абстрактные типы данных.......................................................................................................................... 21 4. Дружественные функции.............................................................................................................................. 39 5. Перегрузка операций......................................................................................................................................... 41 6. Производные классы......................................................................................................................................... 52 7. Параметрический полиморфизм............................................................................................................ 66 8. Библиотека ввода-вывода iostream.h............................................................................................... 76
Литература 1. Бьерн Страуструп. Язык программирование Си++. – М.: Бином, 2005. 2. Айра Пол. Объектно-ориентированное программирование на C++. – СПб.:, М.: Невский диалект – Бином, 1999. 3. Герберт Шилдт. Самоучитель C++: Пер. с англ. – 3-е изд. – СПб.: БХВ-Петербург, 2003. 4. Элиас М., Страуструп Б. Справочное руководство по языку С++ с комментариями. - М.: Мир, 1992. 5. Рассохин Д. От Си к Си++. М.: Эдэль, 1993. 6. От Си к Си++ /Е.И. Козелл, Л.М. Романовская, Т.В. Русс и др. М.: Финансы и статистика, 1993.
Концепция объектно-ориентированного программирования
Понятия объекта, класса объектов. Объектно-ориентированное программирование (ООП) – это новый подход к созданию программ. По мере развития вычислительной техники возникали разные методики программирования. На каждом этапе создавался новый подход, который помогал программистам справляться с растущим усложнением программ. Так язык программирования (даже высокого уровня), легко понимаемый в коротких программах, становился нечитабельным в более длинных. Ситуацию разрешило изобретение в 1960 году языков структурного программирования (к ним относятся Алгол, Паскаль и C). Структурное программирование подразумевает точно обозначенные управляющие структуры, программные блоки, отсутствие (минимальное использование) инструкций GOTO, автономные подпрограммы, в которых поддерживаются локальные переменные и рекурсия. Сутью структурного программирования является возможность разбиения программы на составляющие ее элементы. Хотя структурное программирование в свое время принесло выдающиеся результаты, чтобы написать более сложную программу, отвечающую современным требованиям, необходим новый подход к программированию. Объектно-ориентированное программирование позволяет разложить проблему на составные части, каждая из которых становится самостоятельным объектом. Каждый из объектов содержит свой собственный код и данные, которые относятся к этому объекту. Давайте обратимся к понятию «объект». Технология ООП, прежде всего, накладывает ограничения на способы представления данных в программе. Любая программа отражает в них состояние физических предметов либо абстрактных понятий (назовем их объектами программирования) для работы, с которыми она предназначена. В традиционной технологии варианты представления данных могут быть разными. В худшем случае программист может «равномерно размазать» данные о некотором объекте программирования по всей программе.
Например, вспомните свою первую задачу из курса информатики. Ее условие гласит: «Определить принадлежит ли точка с заданными координатами заштрихованной области», и приводится рисунок. Объектами программирования такой задачи выступают «точка» и «заштрихованная область». Скорее всего, во всех решениях этой задачи как раз и наблюдался принцип «размазанности» данных: координаты точек хранились в независимых переменных X и Y, информация о заштрихованной области формировалась на протяжении всей программы в виде операторов ветвления. В противоположность такому подходу все данные об объекте программирования и его связях с другими объектами можно объединить в одну структурированную переменную. В первом приближении ее можно назвать объектом. Обратимся в данном случае к курсовой работе по тому же предмету информатика. Данные об объекте программирования объединены в большинстве случаев в структуру – структурированную переменную. Кроме того, с объектом связывается набор действий, иначе называемых методами. С точки зрения языка программирования набор действий или методов это функции, получающие в качестве обязательного параметра указатель на объект и выполняющие определенные действия с данными объекта программирования. Технология ООП запрещает работать с объектом иначе, чем через методы, таким образом, внутренняя структура объекта скрыта от внешнего пользователя. Описание множества однотипных объектов называется классом. Объект – это структурированная переменная, содержащая всю информацию о некотором физическом предмете или реализуемом в программе понятии. Класс – это описание множества объектов программирования (объектов) и выполняемых над ними действий. Это определение можно проиллюстрировать средствами классического C: struct myclass /*класс – описание множества объектов*/ { int data1; ... }; void method1(struct myclass *this,...) /*метод*/ {... this->data1... } void method2(struct myclass *this,...) /*метод*/ {... this->data1... } struct myclass obj1, obj2; /*объекты*/ ... method1(&obj1,...); /*для работы с объектами*/ ... method2(&obj2,...); /*применяются методы*/ Основные понятия объектно-ориентированного программирования: инкапсуляция, наследование и полиморфизм. « Эпизодическое» использование технологии ООП заключается в разработке отдельных, не связанных между собой классов и использовании их как необходимых программисту базовых типов данных, отсутствующих в языке. При этом общая структура программы остается традиционной («от функции к функции»).
Однако, строгое следование технологии ООП предполагает, что любая функция в программе представляет собой метод для объекта некоторого класса. Это не означает, что нужно вводить в программу какие попало классы ради того, чтобы написать необходимые для работы функции. Наоборот, класс должен формироваться в программе естественным образом, как только в ней возникает необходимость описания новых объектов программирования. С другой стороны, каждый новый шаг в разработке алгоритма также должен представлять собой разработку нового класса на основе уже существующих. В конце концов, вся программа в таком виде представляет собой объект некоторого класса с единственным методом run (выполнить). Именно этот переход (а не понятия класса и объекта, как таковые) создает психологический барьер перед программистом, осваивающим технологию ООП. Программирование «от класса к классу» включает в себя ряд новых понятий. Прежде всего, это – инкапсуляция данных. Инкапсуляция – это механизм, который объединяет данные и код, манипулирующий с этими данными, а также защищает и то, и другое от внешнего вмешательства или неправильного использования. В ООП код и данные могут быть объединены вместе (в так называемый «черный ящик») при создании объекта. Внутри объекта коды и данные могут быть закрытыми или открытыми. Закрытые коды или данные доступны только для других частей того же самого объекта и, соответственно, недоступны для тех частей программы, которые существуют вне объекта. Открытые коды и данные, напротив, доступны для всех частей программы, в том числе и для других частей того же самого объекта. Для примера, рассмотрим такую структуру хранения информации как стек. Пользователя интересует выполнение операций: push(), top(), empty(), pop(), reset(), full(). Внутренняя реализация (адреса ячеек памяти, значения регистров процессора, имена массивов и т.д.) пользователя не интересует, то есть она должна быть скрыта от него. Пользователю[1] нужны только указанные операции, и он должен получить в свое распоряжение набор методов для их выполнения. Объектом программирования в этом случае является структура хранения информации типа стек. Соответственно, внутренняя организация объекта стек должна быть недоступна (закрыта) для пользователя, то есть все его попытки обратиться непосредственно к элементам, обеспечивающим функционирование структуры хранения данных, должны отвергаться (выдавать сообщение об ошибке). А вызов методов, напротив, должен приводить к выполнению желаемого действия и является общедоступным.
Вторым по значимости понятием является наследование. Новый, или производный класс может быть определен на основе уже имеющегося, или базового класса. При этом новый класс сохраняет все свойства старого: данные объекта базового класса включаются в данные объекта производного, а методы базового класса могут быть вызваны для объекта производного класса, причем они будут выполняться над данными включенного в него объекта базового класса. Иначе говоря, новый класс наследует как данные старого класса, так и методы их обработки. Если объект наследует свои свойства от одного родителя, то говорят об одиночном наследовании. Если же объект наследует данные и методы от нескольких базовых классов, то говорят о множественном наследовании. Простой пример наследования – определение структуры, отдельный член которой является ранее определенной структурой. Рассмотрим еще один пример. Например, базовый класс «животные» может иметь производные классы: «млекопитающие», «рыбы», «птицы» и т.д. Они будут наследовать все характеристики базового класса, но каждый из них может иметь и свои собственные свойства: «рыбы – плавники», «птицы – крылья» и т.д. Третьим по значимости понятием является полиморфизм. Полиморфизм – это свойство, которое позволяет один и тот же идентификатор (одно и то же имя) использовать для решения двух и более схожих, но технически разных задач. Целью полиморфизма, применительно к ООП, является использование одного имени для задания действий, общих для ряда классов объектов. Такой полиморфизм основывается на возможности включения в данные объекта также и информации о методах их обработки (в виде указателей на функции). Принципиально важно, что такой объект становится «самодостаточным». Будучи доступным в некоторой точке программы, даже при отсутствии полной информации о его типе, он всегда может корректно вызвать свойственные ему методы. Таким образом, полиморфная функция – это семейство функций с одним и тем же именем, но выполняющие различные действия в зависимости от условий вызова.
Например, нахождение абсолютной величины в языке С требует трех разных функций: int abs(int); long labs(long); double fabs(double); Эти функции подсчитывают и возвращают абсолютную величину целых, длинных целых и чисел с плавающей точкой соответственно. С точки зрения полиморфизма, каждую из этих функций может быть названа abs(), а тип данных, который используется при вызове функции, определяет, какая конкретная версия функции действительно выполняется.
Расширение языка C Все основные операции, операторы, типы данных языка C присутствуют в С++. Некоторые из них усовершенствованы и добавлены принципиально новые конструкции, которые и позволяют говорить о С++ как о новом языке, а не просто о новой версии языка C. К настоящему времени язык С++ претерпел несколько существенных модернизаций, последняя из которых связана с процедурой стандартизации (1998 г.). Вследствие этого более ранние реализации языка отличаются друг от друга. При этом мы будем рассматривать те возможности языка С++, которые верны для большинства реализаций, и их принято считать общими, полагая что Standard C++ является надмножеством традиционного C++. Название. Название С++ появилось летом 1983 г. Более ранние версии этого языка использовались с 1979 г. под общим названием «C с классами». Название «С++» придумал Риск Маскитти (Rick Mascitti). Это название отражает эволюционный характер изменения языка C. Согласно одной из интерпретаций названия языка «++» – это оператор инкрементации в языке C. Более краткое название «С+» – это синтаксическая ошибка. Однако, в языке C «++» могут применяться как в префиксной, так и в постфиксной формах записи и знатоки семантики C считают, что «С++» хуже чем «++С». Существуют и другие интерпретации названия, тем не менее язык не называется D, потому что он – расширение C. Соответственно, С – подмножество языка С++, и С++ включает в себя возможности языка С. Комментарии. Символы /* открывают комментарий, оканчивающийся символами */. Вся такая последовательность эквивалентна игнорируемому символу (например, пробелу). Это наиболее удобно для написания многострочных комментариев, и является единственным видом комментариев в языке С. Но как только что было сказано, все, что относится к С справедливо и для С++. Кроме того, для написания коротких комментариев, в языке С++ используются символы // int a; /* целая переменная */ float b; // вещественная переменная Переменные. Язык С++ является блочно сконструированным. Переменные, описанные внутри блока, вне блока недоступны. Переменные внешнего блока доступны во внутреннем, если он их не переопределяет. Распределение памяти для переменной происходит при входе в блок, и переменная перестает существовать при выходе из блока. Описание переменных может находиться в любом месте блока. Время жизни переменной –от ее описания до конца блока. Блок – составной оператор ({...}). Объявление переменной в С++ может быть в любом месте блока, в отличии от С, где все объявления находились в начале блока. Рекомендуется помещать объявления как можно ближе к тому месту, где они используются. Константы. Константы в языке С++ аналогичны константам в C. Отличие состоит в том, что для символьного представления константы в C использовалась директива препроцессора #define. В С++ для символьного представления константы рекомендуется использовать объявление переменной с начальным значением и ключевым словом const: const <тип> <имя переменной>=<начальное значение>; Например, const int n=10; Область видимости константы такая же, как у обычной переменной. С помощью ключевого слова const можно объявить указатель на константу const <тип> *<имя переменной>; Например, const int *m; m=&n; m — указатель на константу типа int. Еще одна возможность const состоит в возможности создавать постоянный указатель на величину указанного типа <тип> *const <имя переменной>=<значение>; Например, int i; int *const ptri=&i; Перечислимые константы. С помощью ключевого слова enumможно объявить особый целочисленный тип с набором именованных целых констант, называемых перечислимыми константами: enum <тег> {список_именованных_констант}; Например, enum day {sun,mon,tue,wen,thu,fri,sat}; С помощью такого определения создается целочисленный тип day с названиями 7 дней недели, каждое из которых является целочисленной константой. Перечислимые константы — идентификаторы, которые по умолчанию имеют следующие значения: 0, 1, 2, 3, 4, 5, 6. Первому присваивается значение 0, и каждому последующему — на 1 больше предыдущего. Если не устраивают значения по умолчанию, то перечислимые константы могут быть инициализированы произвольными целыми константами или константными выражениями enum number {a=54;b,c=60,d=c+5}; В этом случае b=55, d=65. При объявлении объектов типа перечислимая константа возможна инициализация объектов: enum flag {false,true}; flag f; enum flag {false,true} f=false; Перечислимая константа может быть объявлена анонимно, т. е. без тега: enum {off,on} signal; ............... signal=on; Ввод-вывод. В С++, как и в С, нет встроенных в язык средств ввода-вывода. В С для этих целей используется стандартная библиотека stdio.h, но она имеет несколько недостатков: § функции ввода-вывода сложны в употреблении, что приводит к частым ошибкам; § затруднителен ввод-вывод абстрактных типов данных (например, структур – struct). В С++ разработана новая библиотека ввода-вывода (iostream.h), написанная на С++, и использующая концепцию объектно-ориентированного программирования. Библиотека iostream.h определяет три стандартных потока:
Для выполнения операций ввода-вывода переопределены две операции поразрядного сдвига:
Ввод значений переменной может быть осуществлен следующим способом: cin >> идентификатор; // (*) По этому выражению из входного потока читается последовательность символов до пробельного символа, затем эта последовательность преобразуется к типу «идентификатора» и получаемое значение помещается в «идентификатор». Вывод информации осуществляется с использованием потока cout: cout << значение; // (**) Здесь «значение» преобразуется в последовательность символов и выводится в выходной поток. Возможно многократное назначение потоков: cin >> ‘переменная1’ >> ‘переменная2’ >>...>> ‘переменная n’; cout << ‘значение1’ << ‘значение2’ <<... << ‘значение n’; При наборе данных на клавиатуре значения для такого оператора должны быть разделены пробельными символами (└┘, \n, \t). Для того чтобы использовать операторы ввода-вывода, необходимо подключить библиотеку iostream.h, в которой определены операции, объекты и перегружены операции сдвига.
endl в данном примере — функция-манипулятор потока, которую можно включать в операции помещения и извлечения потоки (<<, >>). В С++ имеется 13 манипуляторов. Мы пока рассмотрим только 7:
На данный момент рассмотренных возможностей ввода-вывода С++ нам хватит для выполнения большинства заданий. Позднее мы еще вернемся к рассмотрению библиотеки ввода-вывода языка С++. Наряду с возможностями новой библиотеки, в С++ остается возможность использовать все функции ввода-вывода языка С. Использование void. Ключевое слово void было введено в стандарте языка С. На ранних реализациях языка С оно использовалось для указания того, что функция не возвращает значения и не принимает параметров. void proc(void); Позднее введены еще 2 способа использования void. Ключевое слово void может быть использовано в операциях приведения типа для указания компилятору, что значения вычисленного выражения игнорируются. a = (void) func(n); Другой способ — объявление указателя на неопределенный тип void *ptr; Такому указателю может быть присвоен указатель на любой тип, но не наоборот void *ptr; // Указатель на void int i; // Целочисленная переменная float f; // Вещественная переменная int *ptr_i=&i; // Указатель на int float *ptr_f=&f; // Указатель на float ptr=ptr_i; // Допустимо ptr_f=ptr; // Недопустимо Для последней операции необходима операция явного приведения типа: ptr_f=(float*)ptr; Над указателем неопределенного типа нельзя выполнять операцию разыменования без явного приведения типа. Это вызвано тем, что компилятор не знает, как интерпретировать значение, расположенное по указанному адресу. d=*(int*)ptr+c; Ссылки. В С++ введен новый тип данных – ссылка. Ссылка позволяет определять альтернативное имя переменной. Формат объявления ссылки: <тип> &<идентификатор_1> = <идентификатор_2>; Такое объявление фактически назначает переменной с именем Ссылка при объявлении всегда должна быть проинициализирована! int a,b; int &alt=a; // alt — другое имя переменной а (ссылка на a) alt = b; // a=b; alt++; // a++; Если объявлен указатель int *ptr = &a; то истины следующие выражения: *ptr == alt; ptr == &alt; Ссылку можно рассматривать как постоянный указатель, который всегда разыменован, то есть для него не надо выполнять операцию косвенной адресации (*). Ссылка не создает копии объекта, а является лишь другим именем объекта. Возможно инициализировать ссылка на константу: const char &new_line=’\n’; В этом случае компилятор создает некоторую временную переменную temp и ссылку на нее: char temp = ‘\n’; const char &new_line = temp; Основной причиной введения в С++ нового типа данных – ссылки явилась необходимость передачи параметров в функцию через ссылку и получение возвращаемого значения в виде ссылки. Это используется в двух случаях: § для передачи в функцию больших структур, чтобы избежать копирования аргументов в стек; § для передачи функции аргументов, которые должны быть изменены самой функцией. В обоих случаях можно использовать указатели, но это влечет за собой дополнительные накладные расходы: во-первых, в функции для данного параметра надо выполнять операцию разыменования, а во-вторых, при вызове функции надо передавать не саму переменную, а ее адрес. Например, функция, меняющая местами два целых числа, может быть записана следующим образом: void swap (int *a, int *b) { int temp = *a; *a = *b; *b = temp; } Ее вызов int x = 10; int y = 5; swap(&x, &y); Используя ссылки эта функция может быть записана следующим образом: void swap (int &a, int &b) { int temp = a; a = b; b = temp; } Ее вызов int x = 10; int y = 5; swap(x, y); При использовании ссылок в качестве параметров, наряду с указанными преимуществами есть два существенных недостатка. Первый недостаток заключается в том, что фактический аргумент, переданный в функцию по ссылке, может быть изменен функцией без ведома вызывающей программы. Чтобы этого избежать, параметры, которые не должны изменяться, должны определяться с ключевым словом const. При попытке изменить параметр, объявленный как const будет сообщение об ошибке. Второй недостаток передачи параметров в функцию по ссылке проявляется, если при вызове функции происходит несоответствие типов фактических и формальных параметров. В этом случае С++ будет выполнять преобразование типа, но для ссылок преобразование типа выполняется через создание промежуточной переменной void swap (int &, int &); main() { int x=10; unsigned int y; y=5; swap(x, y); } При вызове функции компилятор будет выполнять следующие преобразования: int temp = (int)y; int &t = temp; swap(x, t); В результате функция swap поменяет местами значения переменной x и временной переменной temp. После выхода из функции переменная temp удалится, а переменная y останется неизменной. Кроме того, в С++ функции могут не только принимать ссылку в качестве аргумента, но и возвращать ссылку на переменную. Выражение вызова такой функции может появиться в любой части операции присваивания. При этом необходимо учитывать, что § если возвращаемое значение — указатель, то нельзя оператором return возвращать адрес локальной переменной. § Если возвращаемое значение — ссылка, то нельзя оператором return возвращать локальную переменную. (Так как после выхода из функции переменная не существует, и мы получим повисшую ссылку). Таким образом, использовать ссылки нужно крайне осторожно. Чаще всего потребность в ссылках возникает при перегрузке операций. Функции. Прототипы функций. Определение функции в программе выглядит следующим образом: <заголовок_функции> { <тело функции> } Заголовок функции имеет следующий вид: <тип_функции> <имя_функции> (<спецификация_формальных_параметров>) Если функция не возвращает значения, то ее тип void. Если тип возвращаемого значения не указан, то по умолчанию используется тип int. При обращении к функции, формальные параметры заменяются фактическими, причем соблюдается строгое соответствие параметров по типам. В отличие от своего предшественника – языка C C++ не предусматривает автоматического преобразования в тех случаях, когда фактические параметры не совпадают по типам с соответствующими им формальными параметрами. Говорят, что язык C++ обеспечивает «строгий контроль типов». В связи с этой особенностью языка C++ проверка соответствия типов формальных и фактических параметров выполняется на этапе компиляции. Строгое согласование по типам между формальными и фактическими параметрами требует, чтобы в модуле до первого обращения к функции было помещено либо ее определение, либо ее описание (прототип), содержащее сведения о ее типе, о типе результата (то есть возвращаемого значения) и о типах всех параметров. Именно наличие такого прототипа либо полного определения позволяет компилятору выполнять контроль соответствия типов параметров. Прототип (описание) функции может внешне почти полностью совпадать с заголовком ее определения: <тип_функции> <имя_функции> (<спецификация_формальных_параметров>); Основное различие – точка с запятой в конце описания (прототипа). Второе отличие – необязательность имен формальных параметров в прототипе даже тогда, когда они есть в заголовке определения функции. Функции. Значения формальных параметров по умолчанию. Спецификация формальных параметров – это либо пусто, либо void, либо список спецификаций отдельных параметров, в конце которого может быть поставлено многоточие. Спецификация каждого параметра в определении функции имеет вид: <тип> <имя_параметра> <тип> <имя_параметра> = <значение_по_умолчанию> Как следует из формата, для параметра может быть задано (а может отсутствовать) умалчиваемое значение. Это значение используется в том случае, если при обращении к функции соответствующий параметр опущен. При задании начальных (умалчиваемых) значений должно соблюдаться следующее соглашение. Если параметр имеет значение по умолчанию, то все параметры, специфицированные справа от него, также должны иметь начальные значения. Пусть нужно вычислитьn^k, где k чаще всего равно 2. int pow(int n, int k=2) // по умолчанию k=2 { if(k== 2) return(n*n); else return(pow(n, k-1)*n); } Вызывать эту функции можно двумя способами: t = pow(i+3); q = pow(i+3, 5); Значение по умолчанию может быть задано либо при объявлении функции, либо при определении функции, но только один раз. Функции. Перегрузка функций. Цель перегрузки функций состоит в том, чтобы функция с одним именем по-разному выполнялась и возвращала разные значения при обращении к ней с разными по типам и количеству фактическими параметрами. Например, может потребоваться функция, возвращающая максимальное значение элементов одномерного массива, передаваемого ей в качестве параметра. Массивы, использованные как фактические параметры, могут содержать элементы разных типов, но пользователь функции не должен беспокоиться о типе результата. Функция всегда должна возвращать значение того же типа, что и тип массива – фактического параметра. Для обеспечения перегрузки функций необходимо для каждого имени определить, сколько разных функций связано с ним, т.е. сколько вариантов сигнатур допустимы при обращении к ним. Предположим, что функция выбора максимального значения элемента из массива должна работать для массивов типаint, long, float, double. В этом случае придется написать четыре разных варианта функции с одним и тем же именем. Распознавание перегруженных функций при вызове выполняется по их сигнатурам. Перегруженные функции, поэтому должны иметь одинаковые имена, но спецификации их параметров должны различаться по количеству и (или) по типам, и (или) по расположению. При использовании перегруженных функций нужно с осторожностью задавать начальные значения их параметров. Функции. Встраиваемые функции. В базовом языке C директива препроцессора #define позволяла использовать макроопределения для записи вызова небольших часто используемых конструкций. Некорректная запись макроопределения может приводить к ошибкам, которые очень трудно найти. Макроопределения не позволяют определять локальные переменные и не выполняют проверки и преобразования аргументов. Если вместо макроопределения использовать функцию, то это удлиняет объектный код и увеличивает время выполнения программы. Кроме того, при работе с макроопределениями необходимо тщательно проверять раскрытия макросов: #define SUMMA(a, b) a + b rez = SUMMA(x, y)*10; После работы препроцессора получим: rez = x + y*10; В С++ для определения функции, которая должна встраиваться как макроопределение используется ключевое слово inline. Вызов такой функции приводит к встраиванию кода inline-функции в вызывающую программу. Определение такой функции может выглядеть следующим образом: inline double SUMMA(double a, double b) {return(a + b);} При вызове этой функции rez = SUMMA(x,y)*10; будет получен следующий результат: rez=(x+y)*10. При определении и использовании встраиваемых функций необходимо придерживаться следующих правил: 1. определение и объявление функций должны быть совмещены и располагаться перед первым вызовом встраиваемой функции. 2. Имеет смысл определять inline только очень небольшие функции. 3. Различные компиляторы накладывают ограничения на сложность встраиваемых функций. Компилятор сам решает, может ли функция быть встраиваемой. Если функция не может быть встраиваемой, компилятор рассматривает ее как обычную функцию. Таким образом, использование ключевого слова inline для встраиваемых функций и ключевого слова const для определения констант позволяют практически исключить директиву препроцессора #define из употребления. Динамическое распределение памяти. В С работать с динамической памятью можно при помощи соответствующих функций распределения памяти (calloc, malloc, free), для чего необходимо подключить библиотеку malloc.h. С++ использует новые методы работы с динамической памятью при помощи операторов new и delete: § new — для распределения памяти; § delete — для освобождения памяти. Оператор new используется в следующих формах: new <тип>; // для переменных new <тип>[<размер>]; // для массивов Память может быть распределена для одного объекта или для массива любого типа, в том числе типа, определенного пользователем. Результатом выполнения операции new будет указатель на отведенную память, или нулевой указатель в случае ошибки. int *ptr_i; double *ptr_d; .................. ptr_i=new int; ptr_d=new double[10]; Память, отведенная в результате выполнения new, будет считаться распределенной до тех пор, пока не будет выполнена операция delete. Высвобождение памяти связано с тем, как выделялась память – для одного элемента или для нескольких. В соответствии с этим существует и две формы применения delete delete <указатель>; // для одного элемента delete[] <указатель>; // для массива например, для приведенного выше случая, высвободить память необходимо следующим образом: delete ptr_i; delete []ptr_d; Освобождаться может только память, выделенная оператором new. Рассмотрим использование этих конструкций для динамического распределения массива # include <iostream.h> main() { int size; int *dan; cout << “Ввести размерность массива\n”; cin >> size; dan = new int[size]; for (int i=0, i < size, i++) cout << (dan[i]=i) << endl; delete []dan; } Переменная указатель dan – базовый адрес динамически распределяемого массива, число элементов которого size. Операцией delete освобождается память, распределенная при помощи new. К применению операторов new и delete мы еще вернемся при рассмотрении классов. Абстрактные типы данных Структуры. Мы уже говорили о том, что язык С++ позволяет создавать типы данных, которые ведут себя аналогично базовым типам языка С. Такие типы обычно называют абстрактными типами данных (АТД). Для реализации АТД на С используются структуры. Но использование данных структурного типа значительно ограничено по сравнению с использованием базовых типов данных. Например, структурные данные нельзя использовать как операнды в различных операциях. Для манипуляции с подобными данными надо писать набор функций, выполняющих различные действия, и вместо операций вызывать эти функции. Кроме того, элементы структуры никак не защищены от случайной модификации. То есть любая функция (даже не из набора средств манипуляции структурными данными) может обратиться к элементу структуры. Это противоречит одному из основных принципов объектно-ориентированного программирования — инкапсуляции данных: никакие другие функции, кроме специальных функций манипуляции этим типом данных, не должны иметь доступ к элементам данных. Рассмотрим реализацию понятия даты с использованием struct для того, чтобы определить представление даты date и множества функций для работы с переменными этого типа: struct date { int month, day, year; }; // дата: месяц, день, год void set_date(date*, int, int, int); void next_date(date*); void print_date(date*); //... date today; //... Функции-члены и данные-члены. Никакой явной связи между функциями и типом данных в этом примере нет. Такую связь можно установить, описав функции как члены структуры. Эти функции могут действовать на данные, содержащие в самой структуре. По умолчанию при объявлении структуры ее данные и функции являются общими, то есть у объектов типа структура нет ни инкапсуляции, ни защиты данных: struct date { int month; // месяц int day; // день int year; // год void set(int, int, int); void get(int*, int*, int*); void next(); void print(); }; Функции, описанные таким образом, называются функциями-членами и могут вызываться только для специальной переменной соответствующего типа с использованием стандартного синтаксиса для доступа к данным-членам структуры. В терминологии ООП функция-член это метод. Проиллюстрируем вышесказанное на примере. date today; // сегодня date my_birthday; // мой день рождения void f() { my_birthday.set(30,12,1950); today.set(18,1,1985); my_birthday.print(); today.next(); } Определение функций-членов может осуществляться двумя способами: § описание функции непосредственно при описании структуры; § описание функции вне структуры. Функции-члены, которые определены внутри структуры, являются неявно встроенными (inline). Как правило, только короткие, часто используемые функции-члены, должны определяться внутри структуры. Поскольку разные структуры могут иметь функции члены с одинаковыми именами, при определении функции члена необходимо указывать имя структуры, связывая их с помощью оператора разрешения контекста:: void date::next() { if (++day > 28) { // делает сложную часть работы } } В функции-члене имена членов могут использоваться без явной ссылки на объект. В этом случае имя относится
Воспользуйтесь поиском по сайту: ©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...
|