Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь FAQ Написать работу КАТЕГОРИИ: АрхеологияБиология Генетика География Информатика История Логика Маркетинг Математика Менеджмент Механика Педагогика Религия Социология Технологии Физика Философия Финансы Химия Экология ТОП 10 на сайте Приготовление дезинфицирующих растворов различной концентрацииТехника нижней прямой подачи мяча. Франко-прусская война (причины и последствия) Организация работы процедурного кабинета Смысловое и механическое запоминание, их место и роль в усвоении знаний Коммуникативные барьеры и пути их преодоления Обработка изделий медицинского назначения многократного применения Образцы текста публицистического стиля Четыре типа изменения баланса Задачи с ответами для Всероссийской олимпиады по праву Мы поможем в написании ваших работ! ЗНАЕТЕ ЛИ ВЫ?
Влияние общества на человека
Приготовление дезинфицирующих растворов различной концентрации Практические работы по географии для 6 класса Организация работы процедурного кабинета Изменения в неживой природе осенью Уборка процедурного кабинета Сольфеджио. Все правила по сольфеджио Балочные системы. Определение реакций опор и моментов защемления |
Определение и использование классовСодержание книги
Похожие статьи вашей тематики
Поиск на нашем сайте
Сущность объектно-ориентированного подхода. Для работы с десятичными дробями в программировании можем объявить переменную типа float и использовать стандартные операции ввода-вывода, сложения, вычитания и так далее. Если же необходимо для решения задачи воспользоваться простыми дробями, то программисту так же хотелось бы объявить одну переменную, в которой бы хранились и числитель, и знаменатель, использовать стандартные операции ввода-вывода, сложения и так далее, создавать массивы простых дробей. Однако встроенного типа данных для работы с простыми дробями в языке С++ нет. Реализовать желаемое дает возможность другая в отличии от структурного программирования методология – объектно-ориентированное программирование (ООП, Object-Oriented Programming) – совокупность принципов, технологий, а также инструментальных средств для создания программного обеспечения на основе архитектуры взаимодействия объектов. Другими словами разработчик выделяет из предметной области объекты, которые будут использоваться в программе, описывает их (определяет их свойства и методы) в виде классов и далее использует эти классы в программе, создавая и задавая действия над объеками определенных классов. Объект в ООП – некоторая сущность из реального мира в компьютерном пространстве, обладающая определённым состоянием и поведением, имеющая заданные значения свойств (атрибутов) и операций над ними (методов), способная сохранять свое состояние (информацию) и обеспечивающая набор операций (поведение) для проверки и изменения этого состояния. Свойства – это некоторые характеристики объекта, например, длина, ширина. Другими словами, некоторые исходные данные, характеризующие объект. Под методами объекта понимают процедуры и функции, объявление которых включено в определение класса и которые выполняют действия. Объектом в нашем примере выступит «простая дробь». Свойства объекта – числитель и знаменатель. Методы – ввод, вывод, умножение дроби на целое число. В языке С++ в качестве методов обработки данных выступают функции, а для хранения свойств используются переменные. Таким образом, класс в С++ объединяет в себе переменные и функции, обрабатывающие эти переменные. Совокупность методов часто называют интерфейсом объекта. Синтаксис определения классов и создания объектов. Реализация объектов в языке С++ осуществляется через классы. Класс – это структурированный тип данных, включающий в себе в качестве элементов типизированные данные и функции, применяемые по отношению к этим данным. Синтаксис определения класса: class имя { тип1 переменная-свойство1; тип2 переменная-свойство2; public: тип функция-метод1(параметры); тип функция-метод2(параметры); }; Определение класса обязательно заканчивается «;», т.к. это оператор. Если при определении класса свойство помечено ключевым словом conts, то оно становится константным и далее в программе нельзя изменять его значение. Определение функций-членов класса обычно производится отдельно от определения класса. Программный код функций пишется за пределами класса после его определения. При этом нужно указывать, к какому классу принадлежит данная функция. Знак операции принадлежности – «::» или операция глобального разрешения. Формат определения члена-функции: тип имя_класса::имя_функции(параметры){тело функции} При определении класса объекты не создаются, а определяется только их внешний вид. Можно сказать, что объект – это переменная определенного пользователем класса или типа данных. Таким образом, объекты класса определяются как и обычные переменные: имя_класса имя_объекта1, имя_объекта 2; После того как в основной части программы класс поставлен в соответствие определенным переменным, т.е. созданы объекты (или экземпляры) класса, обращение к элементам объектов производится с помощью составного имени через «.» – оператор доступа к члену класса. имя_класса.имя_переменной-объекта; имя_переменной-объекта.имя_функции-метода();//вызов метода Напишем программный код для решения следующей задачи. Пусть требуется ввести простую дробь, умножить ее на заданное число, вывести результат, а также определить дробь является правильной или неправильной. Написание программ в стиле ООП предполагает следующие этапы: 0 (этап проектирования) выявить в предметной области объекты, участвующие в решении задачи и связи между ними; 1 описать каждый объект в виде класса; 2 запрограммировать методы объекта; 3 запрограммировать алгоритм решения основной задачи. Описываем объект дробь в виде класса fraction: class fraction{ int m; // числитель int n; // знаменатель public: void input(); // ввод дроби void output(); // вывод дроби void mult(int a); // умножение дроби на целое число char* isProper(); // проверка, является ли дробь правильной }; Программируем методы класса fraction: void fraction::input(){ // ввод дроби cout<< "Числитель"; cin>>m; cout<< "Знаменатель"; cin>>n; } void fraction::output(){ // вывод дроби cout<<m<<"/"<<n; } void fraction::mult(int a){ // умножение дроби на число m=m*a; } char* fraction:: isProper (){ //проверка, является ли дробь правильной return (m<n)?"правильная":"неправильная"; } Программируем алгоритм решения основной задачи int main(){ fraction A; //дробь int k; // число на которое нужно умножить дробь // ввод дроби cout<<"Введите дробь"; /*Чтобы ввести дробь А, нужно у объекта A вызвать метод input. */ A.input(); //умножение дроби на целое число cout<< "Введите число, на которое нужно умножить дробь"; cin>>k; /*Чтобы умножить дробь А на число k нужно у объекта A вызвать */ A.mult(k);//вызываем метод mult для дроби A // вывод дроби A.output();//вызываем метод output для дроби A /*Чтобы определить, является ли дробь правильной, нужно у объекта А вызвать метод isProper() одновременно с выводом его результата на печать*/ cout<<" Дробь "<<A.isProper()<<endl; } Поскольку метод isProper () возвращает строковое значение, для которого реализованы стандартные средства вывода его на экран, то вызвать этот метод для объекта можно одновременно с операцией вывода. Метод output() ничего не возвращает. Более того внутри себя он содержит операции вывода данных. Поэтому использовать его одновременно с операцией вывода нельзя. То есть запись cout <<"Дробь "<<A.output(); //вызовет ошибку компиляции приведет к ошибке компиляции. В большинстве программ, написанных на С++, выполняемые действие сводятся к вызову функцией main () различных методов определенных в программе объектов. Кроме того функция main () может вызывать другие независимые функции. Таким образом в программе на С++ функция может либо входить в состав класса – тогда она становится методом, либо не входить в состав класса и быть независимой функцией. В С++ внутри класса имя метода не может совпадать с именем свойства. Ключевые слова private, protected, public определяют режим доступа (access mode) к членам класса. Они могут быть использованы в определении класса многократно и в произвольном порядке. Однако хорошим стилем программирования считается использование каждого из этих ключевых слов один раз. Элементы private не доступны за пределами класса. Т.е. они не могут использоваться никакими функциями, не являющимися членами класса. Также private -функции могут вызываться только функциями-членами класса. Переменные и функции, объявленные после ключевого слова public, доступны для всех других функций программы, т. е. остальная часть программы имеет доступ к элементам объекта через public переменные и функции. Такие члены класса предназначены для обеспечения интерфейса объектов класса с программой, в которой они существуют. Как правило, большинство данных объявляются private, а доступ к ним осуществляется через public функции. Режим доступа protected (защищенный) играет существенную роль при использовании механизма наследования классов. Проектируя класс, необходимо тщательно продумать, какие его члены сделать открытыми, а какие – закрытыми. При определении класса с помощью ключевого слова class его члены будут считаться по умолчанию закрытыми (private). В связи с выше сказанным операторы приведут к ошибке компиляции: cin >> A. m >> A. n; // недопустимые операции A. m =1; A. n =2; // недопустимые операции cout << A. m << A. n; // недопустимые операции Как реализовывать данные действия в программах будет рассмотрено в следующем параграфе. В С++ можно создавать и использовать массивы объектов. Принцип объявления и использования их схож с массивами встроенных типов данных. Пример. fraction B[4]; // массив дробей // ввод массива дробей for (int i=0;i<4;i++){ B[i].input(); } // вывод массива дробей for (int i=0;i<4;i++){ B [ i ]. output (); } Доступ к объектам, находящимся в массиве осуществляется через индексные имена, и далее через точку можно обращаться к компонентам объекта. Доступ к объектам можно получить через указатель. В этом случае к элементам объекта обращаются с помощью оператора ->. fraction A; // дробь // объявляем указатель типа fraction и присваиваем ему адрес дроби A fraction* p=&A; p->input();// ввод дроби p -> output (); // вывод дроби UML -диаграмма классов. Для наглядного представления проекта, состоящего из множества классов можно использовать UML-диаграммы. Класс на них представляется в виде прямоугольника, разделенного на три части (рисунок 2.1). В верхней части указывается название, в средней – свойства, в нижней методы. Режим доступа указывается перед названием члена класса с помощью следующих обозначений: - - private + - public # - protected.
Рисунок 4.1 – Структура класса на UML-диаграмме. На рисунке 4.2 представлены пример классов.
Рисунок 4.2 – Примеры классов. Класс student, описывающий объект«студент», имеет два закрытых поля, отражающих его имя (name) и уровень знаний и умений (garde), а также два открытых метода для получения имени (getName ()) и вывода уровня (printGrade ()). Класс circle, описывающий объект«круг», содержит два закрытых свойства радиус (radius) и цвет (color), а также два открытых метода для получения значения радиуса (getRadius ()) и вычисления площади (getArea ()). Класс soccerPlayer, описывающий объект «футболист», содержит своства: имя (name), номер (number), а также положение по осям х (xLocation) и у (yLocation); методы: бежит (run ()), прыгает (jump ()). Класс car, описывающий объект «легковой автомобиль», содержит такие характеристики как номерной знак (plateNumber), скорость (speed), положение по осям х (xLocation) и у (yLocation); методы движится (move ()) и паркуется (park ()). Свойства объектно-ориентированного программирования. В основу объектно-ориентированных языков положены три важнейших принципа: инкапсуляция, наследованием, полиморфизм. Инкапсуляция – механизм объединения данных и методов, позволяющих манипулировать этими данными, и защиты и того и другого от внешнего вмешательства или неправильного использования. Инкапсуляция означает, что в качестве единицы целого рассматривается объединение некоторой группы данных и некоторой группы функций. Наследование – это процесс, посредством которого один объект может наследовать свойства другого объекта и добавлять к ним черты, характерные только для него. Наследование позволяет строить иерархию объектов, переходя от более общего к частному, уточняя и конкретизируя объект. Полиморфизм – это свойство, которое позволяет одно и тоже имя использовать для решения нескольких технически разных задач. Т. е. имя определяет класс (область) действий, которые в зависимости от типа данных могут существенно отличаться. Например, можно определить три типа переменных: целые, комплексные числа и векторы. Для каждого из них можно определить функцию sum(х, у) – сумму двух переменных, а можно сделать так, что для этих трех типов будет определена операция сложения х + у. В зависимости от того, какого типа будут переменные х и у, работать эта функция и операция сложения будут по-разному. Причем полиморфизм поддерживается в С++ и во время компиляции, и во время выполнения программы.
Контрольные вопросы и задания 1. Перечислите, из чего состоит класс в С++? Объясните разницу в терминах «объект» и «класс»? 2. Приведите синтаксис определения класса и описания методов. 3. Как в главной программе объявить объект и вызвать метод? 4. На что влияет режим доступа к элементам объекта? 5. Как представляются классы на UML-диаграммах? Составить UML-диаграмму класса fraction. 6. Как объявить массив объектов и получить доступ к его элементам, а также их свойствам и методам? 7. Как получить доступ к элементам объекта через указатели? 8. Назовите три основных принципа объектно-ориентированного программирования. Поясните на примере, что они означают. 9. Создать метод, позволяющий вывести дробь в форме с выделенной целой частью. Применить его в главной программе к массиву дробей. 10. Создать класс (включая UML-диаграмму), описывающий объект «банковский счет». С использованием данного класса разработать программное обеспечение, позволяющие открыть счет клиенту, выполнять операции полопления и снятия, а также просматривать баланс.
Методы
Перегружаемые функции. Полиморфизм в С++ реализуется через механизм перегрузки. Внутри класса допускается существование нескольких функций с одинаковым именем, но различающимися наборами параметров. Такие функции реализуют разные алгоритмы в зависимости от типлв и количества передаваемых параметров и могут иметь разный тип возвращаемого значения. Например, для класса fraction можно создать метод с одинаковый названием mult (), который будет выполнять умножение, но в зависимости от того на что надо умножать дробь, на целое число или на простую дробь, будут реализованы разные алгоритмы. class fraction {… void mult (int a); // умножение дроби на целое число void mult (fraction obj);// умножение дроби на дробь }; void fraction::mult(int a){ // умножение дроби на число m=m* a; } void fraction::mult(fraction obj){ // умножение дроби на число m = m * obj. m;/* m – свойство объекта 1-го множителя, который вызвал метод mult (); obj. m - свойство объекта 2-го множителя, который пришел параметром в функцию*/ n = n * obj. n; } В данном примере свойства m и n переданного в качестве параметра объекта obj доступны внутри метода, несмотря на то что они являются закрытыми в определении класса. Это происходит потому, что метод может напрямую (без использования операции «.») обращаться по имени к открытым и закрытым членам этого объекта. Кроме того метод имеет непрямой (через операцию «.») доступ к членам других объектов своего класса. int main(){ fraction A, B; A.mult(B);/* будет вызвана реализаия void fraction::mult(fraction obj), так как параметр B типа fraction*/| A.mult(2); /* будет вызвана реализаия void fraction::mult(int a), так как аргумент 2 типа int*/ } Особенностью использования перегруженных функций является то, что они не обязаны возвращать значения одинакового типа по той причине, что компилятор однозначно идентифицирует функцию по ее имени и набору аргументов. Для компилятора функции с одинаковыми именами, но различными типами аргументов – разные функции, поэтому тип возвращаемого значения – прерогатива каждой функции. Встраиваемые функции. Определение функции-члена класса можно включить в определение класса. В этом случае функция становится встраиваемой. То есть она не вызывается, а ее тело встраивается в программу в место вызова, как макроопределение с параметрами в языке С. В зависимости от компилятора возможны ограничения на использование встраиваемых функций: не должна содержать циклов, switch, goto, статических переменных, не должна быть рекурсивной. Константные методы класса идентифицируются ключевым словом const в конце заголовка функции и не могут изменять значения свойств данного объекта. Попытка присвоить новое значение свойству вызовет ошибку компиляции. Пример – добавим в определение класса fraction функцию, которая будет возвращать целую часть дроби. class fraction{… int getWhole()const{// возвращает целую часть дроби /*m = 0; // error: assignment of data-member 'fraction::m' in read-only structure */ return m / n; }; }; Внутри константных методов могут быть вызваны только другие константные методы класса. Методы доступа к закрытым членам класса (getter и setter –методы). Согласно принципам объектно-ориентированного программирования, данные класса должны быть скрыты из вне. Т.е. в программе нельзя получить напрямую доступ к закрытым переменным за пределами класса. В определенном в предыдущем параграфе классе fraction свойства m и n объявлены как private, поэтому нельзя напрямую из главной программы обращаться к ним и изменять значения числителя и знаменателя дроби. cin >> A. m >> A. n; // недопустимые операции A. m =1; A. n =2; // недопустимые операции cout << A. m << A. n; // недопустимые операции Если необходимо получать или устанавливать значения свойств объекта, то в класс необходимо добавить специальные методыс идентификатором public и вызывать их в функции main (), либо других функциях, где это будет необхоимо. Часто методы, предназначенные для присваивания значений называют setter -методами. Принято, что заголовок функции, реализующей такой метод, начинается со слова set и далее следует имя свойства класса, которому данный метод присвоит значение. Данные функции могут обеспечивать проверку данных (например, проверки диапазона), и преобразование исходных данных во внутреннее представление. Для того чтобы получить за пределами класса значение его свойств, создают getter -методы. Данные функции не должны изменять значения переменных-свойств объекта, но они могут их обработать, прежде чем выдать. Такие методы лучше объявлять как константные. Рассмотрим пример, демонстрирующий использование данных методов для класса fraction. class fraction{ int m; // числитель int n; // знаменатель public: void input(); // ввод дроби void output(); // вывод дроби // getter -методы int getM () const { return m;} //получить числитель int getN () const { return n;} //получить знаменатель // setter -методы void setM (int m){ this -> m = m;} // изменение числителя дроби void setN (int);// изменение знаменателя дроби }; void setN (int n){ if (n ==0) {//попытка присвоить знаменателю значение 0 cout <<"Знаменатель не может быть равным 0!"; exit (0);// вызовет завершение программы с ошибкой } this->n=n; } int main(){ fraction A; // дробь A.setM(1); A.setM(2); // присваиваем дроби значение 1/2 A.output(); // выводим дробь cout>> " числитель дроби " >>A.getM(); } В данном примере методы getM (), getN () и setM () определены как встраиваемые функции. В языке С++, как и во многих других языках объектно-ориентированного программирования можно использовать указатель this для того чтобы сослаться на текущий объект внутри класса. Рассмотрим его использование на примере разрешения неоднозначностей между переменными-свойствами объекта и переменными-параметрами функции. В представленном программном коде функции setM () два идентификатора имеют имя т – переменная-свойство и параметр функции. Возникает конфликт имен. Чтобы разрешить конфликт можно использовать имя параметра функции l вместо m. Однако m имеет более приближенное и смысловое значение в данном контексте. Для разрешения конфликта имен можно использовать указатель this для обращения к свойствам объекта. this -> m ссылается на свойство класса, в то время как m – параметр функции. Как альтернативу, чтобы избежать конфликта имен можно использовать префиксы (такие как m_) или суффиксы (такие как _). Пример: void setM (int m _){ m = m _;} В компиляторах C++ имена своих внутренних переменных начинаются с «_», а локальных переменных с «__» (двойного подчеркивания). Следовательно, необходимо избегать имен переменных, начинающихся с «_». В методе setN (int n) реализована проверка корректности ввода данных, то есть знаменателю не может быть равным 0. Можно усовершенствовать функцию ввода зачений свойств дроби: void fraction::input(){ // ввод дроби cout<< "Числитель"; cin>>m; int denominator; cout<< " Знаменатель "; cin>> denominator; setN (denominator); } В этом случае контроль данных будет производиться и при попытке ввода данных. Причем алгоритм проверки реализован один раз в set -методе. Если будет необходимо изменить данный алгоритм, то это достаточно будет сделать автоматически в одном месте и он автоматически применится и при вводе данных. Ввод значения свойства m происходит без использования set -метода. В данном случае это сделано для ускорения работы программы, т.к. какая-либо проверка введенных значений не предполагается, а вызов метода повлечен дополнительные временные затраты. При необходимости программист может создать более удобные методы по присваиванию значений свойств объектам – т.е. присвоить значения сразу всем свойствам, либо части свойств. Например, создадим метод setFraction (), который присвоит значения сразу и числителю и знаменателю дроби. void setFraction(int m, int n){ setM (m); setN (n); } Как видно из примера, поскольку в классе уже реализованы методы по присваиванию значений каждому из свойств, то исходят из концепции повторного использования кода, а также во избежании ошибок и т.д. не нужно заново реализовывать программный код, а всего лишь вызвать уже существующие функции, передав им соответствующие параметры. Константные объекты. Если объект объявлен с ключевым словом const, то далее в программе нельзя изменять значения его свойств, следовательно попытки вызвать неконстантные методы, для данного объекта приведет к ошибкам компиляции. В связи с этим методы, которые не могут изменять значений свойств лучше объявлять как константные. Для дальнейших примеров будем рассматривать объект «куб», который в качестве свойств имеет размер ребра, а также цвет грани. class cube{ float edgeLength=1;//ребро куба const string edgeColor =" red ";// цвет грани – константное свойство public: float getVolume() const {// объем куба return edgeLength * edgeLength * edgeLength; } // присваивание значения ребру куба void setEdgeLength(float edgeLength){ this->edgeLength = edgeLength; } /*// присваивание значения цвету куба void setEdgeColor(float edgeColor){ this -> edgeColor = edgeColor;/* ошибка компиляции – попытка присвоить значение константному свойству*/ }*/ }; int main (){ const cube myUnchangeCube; //объявлен как константный объект cube myChangeCube; cout<<"Объем myUnchangeCube "<< myUnchangeCube.getVolume();//1 cout<<"Объем myChangeCube "<< myChangeCube.getVolume(); //изменение размера ребра куба /* myUnchangeCube. setEdgeLength (2.0);//ошибка компиляции – попытка вызвать неконстантный метод для константного объекта*/ myChangeCube.setEdgeLength(2.0); cout<<"Новый объем myChangeCube "<< myChangeCube.getVolume(); return 0; } Следует обратить внимание, что если метод getVolume () не будет объявлен как константный, то попытка вызвать его для объекта myUnchangeCube (в строке //1) приведет к ошибке компиляции. Конструкторы и деструкторы. Конструктор – это специальный метод, который используется при создании экземпляров класса. Они производит выделения памяти под переменные-свойства, а также инициализирует их значения, дополнительно в нем может выполняться открытие файлов, а также установка параметров экрана.Чтобы создать новый экземпляр класса, необходимо объявить его имя и вызвать конструктор. Конструктор срабатывает автоматически при выполнении операции определения типа переменной. Вызывается функция-конструктор автоматически в тот момент, когда создается объект, т. е. для объекта выделяется место в памяти. Нельзя вызвать функцию-конструктор в явном виде. Создание нового объекта происходит не только при объявлении объектов классов. Новый объект может создаваться, например, при передаче параметров в функцию. Поэтому существует несколько специальных видов конструкторов, которые будут рассмотрены в данном параграфе. Деструктор – это специальный метод, который срабатывает при разрушении объекта, например по завершении работы программы, удалении объекта из памяти с помощью оператора delete или при выходе из функции. Деструктор освобождает память выделенную конструктором, закрывает открытые файлы, восстанавливает значение экрана. Области памяти, занятые данными базовых типов выделяются и освобождаются системой автоматически. Поэтому в примере выше конструкторы и деструкторы не объявлялись. Конструктор и деструктор являются членами класса. Имя конструктора совпадает с именем класса. Имя деструктора начинается с символа ~, за которым следует имя класса. Функция-конструктор как и функция-деструктор не могут иметь тип возвращаемого значения. Конструктор, как и другие функции, может иметь параметры. Класс может иметь несколько конструкторов. Если в классе не объявлен ни один конструктор, компилятор сам создает функцию-конструктор класса. По умолчанию создается конструктор без параметров имеющий режим доступа public. Деструктор не может иметь параметров. В следствии чего для него нельзя создать перегружаемую функцию. Т.е. деструктор в классе может быть только один. Ни конструктор, ни деструктор не вызываются явно. Если в классе не определен деструктор, то компилятор сам генерирует функцию-деструктор, которая просто освобождает память, занятую данными объекта. Таким образом, конструктор отличается от обычных функций следующим: - Имя конструктора совпадает с именем класса. - Конструктор не имеет типа возвращаемого значения (неявно возвращает void). Следовательно, использование оператора return не допускается в конструкторе. - Конструктор может быть вызван только один раз при создании экземпляра класса. Его нельзя вызвать впоследствии в программе. - Конструкторы не наследуются. Добавим конструктор в определенный выше класс cube. class cube{ float edge L ength;// ребро куба string edgeColor;// цвет грани public: cube(float l, string c){ // конструктор edge L ength=l; edgeColor=c; } float getVolume(){ return edge L ength * edge L ength * edge L ength; } }; int main(){ cube A(5," red ");// создали куб с длиной ребра 5 и цветом грани красным cout<<A.getVolume();//выводим объем куба } Конструктор по умолчанию - это конструктор без параметров или имеющий значения по умолчанию для всех параметров. Для класса cube при попытке выполнить с ube В; компилятором будет выдано сообщение об ошибке error: no matching function for call to ' cube:: cube ()'т.е. не определен конструктор по умолчанию. Если в классе не определено ни одного конструктора, то компилятор автоматически создает конструктор по умолчанию без параметров с пустым телом функции. className:: className () { } Для класса cube: cube:: cube (){} Если в классе определен хотя бы один конструктор, то конструктор по умолчанию автоматически не создается. Если для всех созданных конструкторов требуются аргументы, то вызов конструктора по умолчанию без аргументов приводит к ошибке. Это позволяет разработчику классов сделать невозможным создание неинициализированного экземпляра, не предоставляя явный конструктор по умолчанию. Пример конструктора с аргументами по умолчанию cube(float l=1, string c="red"){ edgeLength = l; edgeColor = c; } При наличии такого конструктора в классе cube, после выполнения с ube В; будет создан объект «куб » В с длиной ребра 1 и красным цветом грани. Вместо инициализации private членов класса в теле конструктора, как показано в данном примере, можно использовать альтернативный синтаксис, который имеет название member initialize list (список инициализируемых членов) и следующий синтаксис: имя():имя_перемен1(значение), имя_перемен1(значение){} Пример: cube(float l = 1.0, string c = "red"): edgeLength (l), edgeColor(c) { } Список инициализируемых членов располагается после заголовка конструктора, через «:». Элементы списка отделяются друг от друга «,». Каждый элемент списка записывается в форме: data_member_name(parameter_name). Для базовых типов данных это эквивалентно записи data_member_name = parameter_name. Для объектных типов данных (классов, созданных пользователями) будет вызван конструктор. Тело конструктора, в данном случае пустое, будет выполняться после завершения инициализации параметров. Такой подход рекомендуется использовать для инициализации всех членов класса, так как это часто более эффективно, чем делать присваивание внутри тела конструктора. В C ++ конструктор с одним аргументом может быть использован для неявного преобразования значения в объект. Пример: #include<iostream> using namespace std; class counter { private: int count; public: /* Конструктор с одним аргументом, который принимает значение типа int используется, чтобы неявно конвертировать значение int в объект Counter */ counter(int c = 0): count(c) { } int getCount() const { return count; } void setCount(int c) { count = c; } }; int main () { //Объявляется экземпляр класса и вызывается конструктор по умолчанию counter c1; cout << c1.getCount() << endl; // 0 /*Выполняется неявное преобразование. Вызывается конструктор с одним аргументом Counter (9), чтобы построить временный объект*/ c1 = 9; cout<<c1.getCount() <<endl; // 9 } Такое неявное преобразование может привести к путанице. В C++ существует ключевое слово " explicit ", чтобы отключить неявное преобразование. Тем не менее, все равно можно выполнить явное преобразование, используя оператор cast. Пример: #include <iostream> using namespace std; class counter { private: int count; public: /*В данной реализации конструктора автоматическое преоразование отключено.*/ explicit counter(int c = 0): count(c) { } int getCount() const { return count; } void setCount(int c) { count = c; } }; int main () { //Объявляется экземпляр класса, срабатывает конструктор по умолчанию counter c1; cout << c1.getCount() << endl; // 0 // counter c2 = 9; // error: conversion from 'int' to non-scalar type ' с ounter' requested c1 = (с ounter)9; // Явное преобразование cout << c1.getCount() << endl; // 9 } Конструктор копирования создает новый объект, копируя существующий объект того же типа. Таким образом конструктор копирования принимает аргумент, который является объектом того же класса. Если конструктор копирования не определен, то компилятор создает конструктор по умолчанию, который копирует все элементы данных данного объекта. Для определенного класса cube: int main(){ cube A(5,"red"); // создали куб с длиной ребра 5 и цветом грани красным cout<<A.getVolume();//выводим объем куба cube C (A); создали объект С, который является копией объекта А cout << C. getVolume ();//выводим объем куба } Когда объект передается в функцию по значению, конструктор копирования будет использоваться для создания клон-копии аргумента. Конструктор копии по умолчанию выполняет shadow copy. Он не копирует динамически распределенные элементы данных, созданные с помощью new или new []. Таким образом, если память под переменные-свойства выделялась динамически, то необходимо разрабатывать конструктор копирования, который будет обрабатывать эти данные. Это будет рассмотрено позднее в теме перегразука операции присваивания. Конструктор копирования объявляется следующим образом: className::className(const className & obj) { } Пример для класса cube: cube::cube(const cube & obj) { edgeLength= obj.edgeLength; edgeColor= obj.edgeColor; } Передача в функцию по значению для объекта означает вызов конструктора копирования. Чтобы избежать накладных расходов на создание копии клона, обычно лучше передавать по константной ссылке, что не будет иметь побочного эффекта при изменении вызывающего объекта.Оператор присваивания по умолчанию. Компилятор также предоставляет оператор присваивания по умолчанию (=), который может быть использован для назначения одного объекта другому объекту того же класса через экземплярную копию. Например, cube c6(5.6, "orange"), c7; cout<<"Edge L ength="<<c6.getEdge L ength()<<"Volume="<<c6.getVolume()<< " Color=" << c6.getColor() << endl; // Edge L ength=5.6 Area=98.5206 Color=orange cout << "Edge L ength=" << c7.getEdge L ength () << " Volume=" << c7.getVolume () << " Color=" << c7.getColor() << endl; // Edge L ength =1 Volume =175.616 Color = red (конструктор по умолчанию) c 7 = c 6; // побитовое копирование cout << "Edge L ength=" << c7.getEdge L ength () << " Volume=" << c7.getVolume () << " Color=" << c7.getColor() << endl; // Edge L ength =5.6 Volume =175.616 Color = orange Можно перегрузить оператор присваивания, чтобы переопределить его значение по умолчанию. Конструктор копирования вместо оператора присваивания копии используется в объявлении:: cube c 8 = c 6; // Вызывается конструктор копирования, а не оператор //присваивания копий // Тоже что и cube c 8(c 6) Оператор присваивания копии по умолчанию выполняет теневую копию. Он не копирует динамически распределенные элементы данных, созданные с помощью операторов new или new[]. Оператор присваивания копии имеет следующую сигнатуру: class yClass { private: T1 member1; T2 member2; public: // Оператор присваивания копии по умолчанию, который присваивает объект посредством побитовой копии MyClass & operator=(const MyClass & rhs) { member1 = rhs.member1; member2 = rhs.member2; return *this; } ...... } Оператор присваивания копий отличается от конструктора копирования тем, что он должен освобождать динамически распределенное содержимое объекта и предотвращать самоприсваивание. Оператор присваивания возвращает ссылку на этот объект, чтобы разрешить операцию по цепочке(например, x = y = z). Подводя итог можно сказать. Если в классе не определен конструктор по умолчанию, конструктор копии, оператор присваивания копии и деструктор для типа, то компилятор C ++ создает их автоматически. Эти функции известны как специальные функции-члены; именно благодаря им простые пользовательские типы в С++ действуют подобно структурам в С. Иными словами, их можно создавать, копировать и уничтожать без написания дополнительного кода. В C++11 в язык привносится семантика перемещения: для этого в список специальных функций-членов, которые компилятор может создавать автоматически, добавлены конструктор перемещения, оператор перемещения и присваивания. Это удобно при работе с простыми типами, однако в сложных типах часто определяются собственные специальные функции-члены, которые могут препятствовать автоматическому созданию других специальных функций-членов. Вот как это выглядит на практике. · Если конструк
|
||||
Последнее изменение этой страницы: 2019-05-20; просмотров: 734; Нарушение авторского права страницы; Мы поможем в написании вашей работы! infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.139.69.138 (0.013 с.) |