prog_lk12_new.pptx
- Количество слайдов: 87
Пример Программа реализует следующий алгоритм: • создание объекта c выделенной памятью для массива слов ; • ввод массива слов в объект; • вывод данных объекта; • выполнение функции вычисления слова с max длиной; • распечатка в основной программе найденного слова и его длины с использованием возвращенных параметров - слова и его длины.
Задана структура класса Q: • члены данные: • массив слов (максимальный размер массива 100); • максимальная длина слова len=11; • n - количество элементов массива (текущий размер); Члены функции (методы): • функция ввода данных объекта; • функция вывода данных объекта; • функция определения слова с максимальной длиной (передача этого слова в вызывающую функцию и его порядкового номера).
#include <iostream. h> #include <string. h> #include <conio. h> class Q //Объявление класcа Q { int kol; //текущий размер массива слов char mas[100][11]; //массив слов public: //Объявление член-функций (методов) void Enter(); //Объявление член-функции ввода данных void Output(); //Объявление член-функции вывода данных int Funk(char *); /*объявление член-функции нахождения слова максимальной длины и длины этого слова*/ }; //конец объявления класса Q
int main() { setlocale(LC_ALL, "rus"); int max=NULL; // результат- максимальная длина и слово максимальной длины char str[255]={NULL}; Q obj; //Объявление объекта obj. Enter(); //Ввод данных obj. Output(); //Вывод данных max=obj. Funk(str); /*Вычисление слова с максимальной длиной и запись слова в строку str и длины в переменную max */ cout <<"n Результат: "n. Самое длинное слово: "<<str<<"nего длина: "<<max; return 0; }
void Q: : Enter() /* отложенное определение член-функции ввода данных */ { cout <<"n. Введите размер массива kol="; cin >>kol; cout <<"n Ввод массива слов: ”; cin. get(); for(int i=0; i<kol; i++) //цикл ввода массива слов { cout <<"n. Введите mas["<<(i+1)<<"] : ”; cin. getline(mas[i], 11); if(!cin) cin. clear(); } }
void Q: : Output() /* отложенное определение член-функция вывода данных */ /* отложенное определение член-функция определения слова с max длиной */ int Q: : Funk(char *max_word) // возвращает слово и длину { cout <<"nn Вывод массива слов: " <<"n------------"; for(int i=0; i<kol; i++) cout <<"n["<<(i+1)<<"]. ->"<<mas[i]; { int max_len=strlen(mas[0]); // вычисление длины первого слова int temp_len=NULL; // рабочая переменная для текущей длины strcpy(max_word, mas[0]); /* фиксирование первого слова в результате */ } for(int i=1; i<kol; i++) { temp_len=strlen(mas[i]); if(temp_len>max_len) { длины max_len=temp_len; // фиксирование максимальной strcpy(max_word, mas[i]); /* фиксирование слова с макси- мальной длиной */ } } return(max_len); /*Возврат длины самого длинного слова в вызвавшую функцию */ }
Переопределение операций
Переопределение операций • В языке С++ допустимо, что класс будет практически неотличим от предопределенных встроенных типов при использовании в выражениях. • Для класса можно определить операции сложения, умножения и т. д. пользуясь стандартной записью таких операций, т. е. x + y. • В языке С++ считается, что подобная запись – это также вызов метода с именем operator+ того класса, к которому принадлежит переменная x.
Переопределение операций // определение класса комплексных чисел class Complex { public: int real; // вещественная часть int imaginary; // мнимая часть // прибавить комплексное число Complex operator+ (const Complex x) const; };
Complex operator+ (const Complex x) const; • Вместо метода Add появился метод operator+. • Метод возвращает значение типа Complex (операция сложения в результате дает новое значение того же типа, что и типы операндов). • Перед аргументом метода появилось ключевое слово const, означающее, что при выполнении данного метода аргумент изменяться не будет. • Второе ключевое слово const означает, что объект, выполняющий метод, не будет изменен. • При выполнении операции сложения x + y над двумя величинами x и y сами эти величины не изменяются.
Определение операции сложения Complex: : operator+ (const Complex x) const { Complex result; result. real = real + x. real; result. imaginary = imaginary + x. imaginary; return result; }
Переопределение операций • В языке С++ допускается определение в одном классе нескольких методов с одним и тем же именем, но разными типами и количеством аргументов. • Определение методов или атрибутов с одинаковыми именами в разных классах не вызывает проблем, поскольку пространства имен разных классов не пересекаются.
Переопределение операций // определение класса комплексных чисел class Complex { public: int real; // вещественная часть int imaginary; // мнимая часть // прибавить комплексное число Сomplex operator+ (const Complex x) const; // прибавить целое число Complex operator+ (long x) const; };
Пример Complex c 1; Complex c 2; long x; c 1 + c 2; c 2 + x;
Полный пример #include <iostream> Complex: : operator+(const Complex x) const #include <conio. h> { using namespace std; Complex result; class Complex { result. real = real + x. real; public: result. imaginary = imaginary + x. imaginary; int real; // вещественная часть int imaginary; // мнимая часть return result; } // прибавить комплексное число Complex operator+(const Complex x)const; // прибавить целое число Complex: : operator+ (long x) const { Complex operator+(long x)const; Complex result; result. real = real + x; result. imaginary = imaginary + x; }; return result; }
setlocale(LC_ALL, "rus"); Complex c 1; c 1. real=1; c 1. imaginary=2; cout<<"Объект с1, вещественная часть: "<<c 1. real<<", мнимая часть: "<<c 1. imaginary; Complex c 2; c 2. real=2; c 2. imaginary=3; cout<<"n. Объект num 2, вещественная часть: "<<c 2. real<<", мнимая часть: "<<c 2. imaginary; long x=10; Complex c 3; c 3=c 1+c 2; cout<<"n. Объект c 3 после выполнения метода c 1+c 2, вещественная часть: "<<c 3. real<<", мнимая часть: "<<c 3. imaginary; c 2=c 2+x; cout<<"n. Объект c 2 после выполнения метода c 2+x, вещественная часть: "<<c 2. real<<", мнимая часть: "<<c 2. imaginary; return 0; }
Контроль доступа к объекту
Контроль доступа к объекту • Основной характеристикой класса с точки зрения его использования является интерфейс - перечень методов, с помощью которых можно обратиться к объекту данного класса. • Кроме интерфейса, объект обладает текущим значением или состоянием, которое он хранит в атрибутах класса. • Определение класса можно разделить на три части - внешнюю, внутреннюю и защищенную.
Внешняя часть • Внешняя часть начинается с ключевого слова public, затем следует двоеточие. • Внешняя часть - это определение интерфейса. • Методы и атрибуты, определенные во внешней части класса, доступны как объектам данного класса, так и любым функциям и объектам других классов.
Внешняя часть class String { public: void Concat (const String& x); // добавить строку в конец текущей строки void To. Lower (void); // заменить заглавные буквы на строчные int Get. Length (void) const; // сообщить длину строки ……. . };
Внутренняя и защищенная части класса • Внутренняя и защищенная части класса доступны только при реализации методов этого класса. • Внутренняя часть начинается с ключевого слова private, защищенная – ключевым словом protected.
Внутренняя и защищенная части класса class String { public: void Concat (const String& x); /* добавить строку в конец текущей строки */ void To. Lower (void); /* заменить заглавные буквы на строчные */ int Get. Length (void) const; /* сообщить длину строки */ private: char *str; int length; };
Внутренняя и защищенная части класса Объявляя атрибуты str и length как private, сообщается, что непосредственно к ним можно обращаться только при реализации методов класса, как бы изнутри класса. Int String: : Get. Length (void) const { return length; }
Внутренняя и защищенная части класса Внутри определения методов класса можно обращаться не только к внутренним атрибутам текущего объекта, но и к внутренним атрибутам любых других известных данному методу объектов того же класса. Реализация методов Concat будет выглядеть следующим образом: void String: : Concat (const String& x) { length +=x. length; char* tmp = new char[length + 1]; strcpy(tmp, str); strcat(tmp, x. str); delete [] str; str = tmp; }
Внутренняя и защищенная части класса Если в программе будет предпринята попытка обратиться к внутреннему атрибуту или методу класса вне определения метода, компилятор выдаст ошибку, например: main() { String s; if (s. length > 0) // ошибка }
Внутренняя и защищенная части класса Если в программе будет предпринята попытка обратиться к внутреннему атрибуту или методу класса вне определения метода, компилятор выдаст ошибку, например: main() { String s; if (s. length > 0) // ошибка }
Дружественные функции
Дружественные функции При необходимости, допустимо создание в дополнение к интерфейсу класса String иных функций, например, функция, которая формирует новую строку, являющуюся результатом слияния двух строк, но не изменяет сами аргументы. Для того чтобы эта функция работала быстро, желательно, чтобы она имела доступ к внутренним атрибутам класса String. Доступ можно разрешить, объявив функцию “другом” класса String cпомощью ключевого слова friend.
Дружественные функции class String { ……. friend String concat (const String& s 1, const String& s 2); }
Дружественные функции String concat (const String& s 1, const String& s 2) { String result; result. length = s 1. length + s 2. length; result. str = new char [result. length + 1]; if (result. str == 0) // обработка ошибки strcpy(result. str, s 1. str); strcat(result. str, s 2. str); return result; }
Дружественные функции С помощью механизма friend можно разрешить обращение к внутренним элементам класса как отдельной функции, отдельному методу другого класса или всем методам другого класса. class String { friend class String. Parser; /* все методы класса String Parser обладают правом доступа */ // ко всем атрибутам класса String friend int Lexer: : Char. Counter (const String& s, char c); /* из класса Lexer только метод Char. Counter может обращаться к внутренним атрибутам String */ };
Использование описателя const
Использование описателя const Если в начале описания переменной стоит описатель const, то описываемый объект во время выполнения программы не изменяется. const double pi=3. 1415; const Complex one(1, 1);
Использование описателя const Если const стоит перед определением указателя и ссылки, то это означает, что не изменяется объект, на который данный указатель или ссылка указывает: const char* ptr = &string; /* указатель на неизменяемую строку */ char x = *ptr; // обращение по указателю - допустимо ptr++; // изменение указателя - допустимо ptr = ‘ 0’; /* попытка изменения объекта, на который указатель указывает – ошибка */
Использование описателя const Объявление указателя, значение которого не изменяется: char* const ptr = &string; // неизменяемый указатель char x = *ptr; // обращение по указателю - допустимо ptr++; // изменение указателя - ошибка ptr = ‘ 0’; /* изменение объекта, на который указатель указывает – допустимо */
Доступ к объекту по чтению и записи
Доступ к объекту по чтению и записи Кроме контроля доступа к атрибутам класса с помощью разделения класса на внутреннюю, защищенную и внешнюю части, нужно следить за тем, с помощью каких методов можно изменять текущее значение объекта, а с помощью каких – нельзя. При описании метода класса как const выполнение метода не может изменять значение объекта, который этот метод выполняет.
Доступ к объекту по чтению и записи class A { public: int Get. Value (void) const; int Add. Value (int x) const; int A: : Get. Value (void) const { return value; // объект не изменяется } private: int value; } int A: : Add. Value (int x) const { value +=x; /* попытка изменить атрибут объекта приводит к ошибке компиляции */ return value; }
Доступ к объекту по чтению и записи Использование описателя const позволяет программисту контролировать возможность изменения информации в программе, тем самым предупреждая ошибки. В описании класса String один из методов - Get. Length - представлен как неизменяемый (в конце описания метода стоит слово const). В этом случае вызов данного метода не изменяет текущее значение объекта. Остальные методы изменяют его значение. Контроль использования тех или иных методов ведется на стадии компиляции.
Доступ к объекту по чтению и записи Если аргументом какой-либо функции объявлена ссылка на неизменяемый объект, то, соответственно, эта функция может вызвать только методы, объявленные как const: int Lexer: : Char. Counter(const String& s, char c) { int n = s. Get. Length(); // допустимо s. Concat(“ab”); // ошибка – Concat изменяет значение s }
Доступ к объекту по чтению и записи Общим правилом является объявление всех методов как неизменяемых, за исключением тех, которые действительно изменяют значение объекта. Также, это соответствует правилу объявления аргументов как const. Объявление константных аргументов запрещает изменение объектов во время выполнения функции и тем самым предотвращает случайные ошибки.
Производные классы, наследование
Производные классы, наследование Важнейшим свойством ООП является наследование. Для того, чтобы показать, что класс B наследует классу A (класс B выведен из класса A), в определении класса B после имени класса ставится двоеточие и затем перечисляются классы, из которых B наследует.
Производные классы, наследование class A { public: A(); ~A(); Method. A(); }; class B : public A { public: B(); …. };
Производные классы, наследование Термин “наследование” означает, что класс B обладает всеми свойствами класса A, он их унаследовал. У объекта производного класса есть все атрибуты и методы базового класса. Новый класс может добавлять собственные атрибуты и методы. B b; b. Method. A(); // вызов метода базового класса
Производные классы, наследование Часто выведенный класс называют подклассом, а базовый класс – суперклассом. Из одного базового класса можно вывести сколько угодно подклассов. В свою очередь, производный класс может служить базовым для других классов. Отношения наследования можно изобразить в виде иерархии или дерева.
Производные классы, наследование
Пример Для библиотечной системы необходимо создать классы, описывающие различные книги, журналы и т. п. , которые хранятся в библиотеке. Книга, журнал, газета обладают как общими, так и различными свойствами: • • у книги имеется автор, название и год издания. у журнала есть название, номер и содержание – список статей. В то же время книги, журналы и т. д. имеют общие свойства: единицы хранения в библиотеке (т. е. инвентарный номер, они могут быть у читателей, в фонде хранения или в читальном зале. Их можно выдать или сдать в библиотеку). Эти общие свойства удобно объединить в одном базовом классе Item, который описывает единицу хранения в библиотеке
Пример class Item { public: Item(); ~Item(); bool Is. Taken() const; /* истина, если единица хранения на руках */ bool Is. Available() const; /* истина, если этот предмет имеется в библиотеке */ long Getinv. Number() const; // инвентарный номер void Take(); // операция “взять” void Return(); // операция “вернуть” private: long inv. Number; // инвентарный номер – целое число bool taken; // хранит состояние объекта – взят на руки }
Пример Для реализации процесса выдачи и возврата книг, достаточно того интерфейса, который представляет базовый класс. Например: // выдать на руки void Take. An. Item(Item& i) { …… if (i. is. Available()) i. Take(); }
Пример Свойства книги представлены классом Book class Book : public Item { public: String Author(void) const; // автор String Title(void) const; // название String Publisher(void) const; // издательство long Year. Of. Publishing(void) const; // год выпуска String Reference(void) const; // полная ссылка на книгу private: String author; String title; String publisher; short year; };
Пример Класс Magazin представляет сведения о журнале: class Magazin : public Item { public: String Volume(void) const; // том short Number(void) const; // номер String Title(void) const; // название Date. Oflssue() const; // дата выпуска private: String volume; short number; String title; Date date; };
Пример Ключевое слово public перед именем базового класса определяет, что внешний интерфейс базового класса становится внешним интерфейсом порожденного класса. Такой вариант наследования является наиболее употребляемым типом наследования.
Пример У объекта класса Book имеются методы, определенные в классе Item. Book b; long in = b. Getinv. Number(); String t = b. Reference(); Производный класс имеет доступ к методам и атрибутам базового класса, объявленным во внешней и защищенной части базового класса, однако доступ к внутренней части базового класса не разрешен.
Конструкторы
Конструкторы При определении класса имеется возможность задать для объекта начальное значение. Специальный метод класса, называемый конструктором, выполняется каждый раз, когда создается новый объект этого класса. Конструктор – это метод, имя которого совпадает с именем класса. Конструктор не возвращает никакого значения.
Конструкторы Для класса String имеет смысл в качестве начального значения использовать пустую строку: class String { public: String(); // объявление конструктора private: char *str; int length; }; // определение конструктора String: : String() { str=0; length=0; }
Конструкторы Конструктор без аргументов называется стандартным конструктором или конструктором по умолчанию. Можно определить несколько конструкторов с различными наборами аргументов. Возможности инициализации объектов в таком случае расширяются.
Конструкторы Для класса String логично инициализировать переменную с помощью указателя на строку. class String { … public: String(); //стандартный конструктор String(const char* p); //дополнительный конструктор }; // определение второго конструктора String: : String(const char* p) { length = strlen(p); str=new char[length + 1]; if (str == 0) { // обработка ошибок } strcpy(str, p); // копирование строки }
Конструкторы Теперь, создавая переменные типа String, можно инициализировать их тем или иным образом: char* p; // выполняется стандартный конструктор String s 1; // выполняется второй конструктор String s 2(“Начальное значение”); // выполняется стандартный конструктор String* sptr = new String; // выполняется второй конструктор String* ssptr = new String(cp);
Пример
Пример Задана структура класса Q - члены данные; -массив целых чисел; - n - переменная, определяющая текущий размер массива; - члены-функции (методы): -конструктор с формальными параметрами; -количество элементов передаваемого в объект массива и указатель массива; - функция вывода; -функция вычисления максимального элемента массива объекта; Программа реализует следующий алгоритм: - ввод массива целых чисел; - создание объекта заданного класса и инициализация с использованием конструктора с параметрами: - адрес передаваемого в конструктор массива целых чисел и количество элементов передаваемого массива; - вывод данных объекта; - вычисление максимальной величины массива объекта - печать результата.
#include <iostream. h> #include <conio. h> //Объявление класа Q class Q { //Объявление член-данных int mas[100]; //массив целых чисел int kol; //текущий размер массива //Объявление член-функций (методов) public: Q(int, int *); /*объявление конструктора с параметрами */ void output(); //объявление член-функции вывода int funk(); /*объявление член-функции нахождения максимального элемента массива */ }; //конец объявления класс Q
int main() { int m, n; // n- текущий размер массива mas m- макс. член массива int mas[100]; // массив целых чисел /* ввод массива целых чисел для передачи через конструктор в объект */ cout <<"n Введите размер массива n="; cin >>n; cout <<"n Ввод массива чисел : n"; for(int i=0; i<n; i++) { cout <<"n. Введите mas["<<(i+1)<<"]="; cin >>mas[i]; cout <<endl; } Q obj(n, mas); obj. output(); //вывод данных m=obj. funk(); //нахождение max элемента массива cout <<"nn Результат: " <<"n Max элемент равен" <<m; return 0; }
Q: : Q(int size, int *tmp_mas)/*конструктор с параметрами: размер массива и указатель массива */ { kol=size; // передача формального размера массива в размер массива объекта for(int i=0; i<kol; i++) mas[i]=tmp_mas[i]; }
Копирующий конструктор
Копирующий конструктор Существует конструктор с аргументом, в котором в качестве аргумента выступает объект того же самого класса. Такой конструктор называют копирующим, т. к. при его выполнении создается объект – копия другого объекта.
Копирующий конструктор class String { … public: String(const String& s); } String: : String(const String& s) { length = s. length; str=new char[length + 1]; strcpy(str, s. str); }
Копирующий конструктор Новый объект – копия своего аргумента. При этом новый объект независим от первоначального, в том смысле, что при изменении значения одного не изменяет значение другого. // первый объект с начальным значением “Astring” String a(“Astring”); // новый объект – копия первого, т. е. со значением “Astring” String b(a); // изменение значения b на “Astring”, значение объекта a не изменяется b. Concat(a);
Копирующий конструктор Подобное поведение объектов класса String обусловлено наличием копирующего конструктора. Если бы его не было, компилятор создал бы его по умолчанию, и такой конструктор просто копировал бы все атрибуты класса, т. е. был бы эквивалентен: String: : String(const String& s) { length = s. length; str = s. str; }
Копирующий конструктор При вызове метода Concat для объекта b произошло бы следующее: объект b перераспределил бы память под строку str, выделив новый участок памяти и удалив предыдущий. Однако указатель str объекта a по-прежнему указывает на первоначальный участок памяти, только что освобожденный объектом b. Соответственно, значение объекта a испорчено.
Копирующий конструктор Конструкторы, особенно копирующие, довольно часто выполняются неявно. Предположим, что метод Concat описан иначе: Concat(String s); //вместо Concat(const String& s); т. е. использовали бы передачу аргумента по значению вместо передачи по ссылке. Конечный результат не изменился бы, однако при вызове метода b. Concat(a) компилятор создал бы временную переменную типа String – копию объекта a, и передал бы ее в качестве аргумента. При выходе из метода String эта переменная была бы уничтожена.
Копирующий конструктор Для класса Complex, рассмотренного ранее, кроме стандартного конструктора можно задать конструктор, строящий комплексное число из целых чисел: class Complex { public: Complex(); Complex(int rl, int im = 0); Complex(const Complex& c); // прибавить комплексное число Complex operator+(const Complex x) const; private: int real; // вещественная часть int imaginary; // мнимая часть };
Копирующий конструктор // Стандартный конструктор создает число (0, 0) Complex: : Complex() : real(0), imaginary(0) {} /* Создать комплексное число из действительной и мнимой частей. */ /* У второго аргумента есть значение по умолчанию – мнимая часть равна нулю */ Complex: : Complex(int rl, int im) : real(rl), imaginary(0) {} // Скопировать значение комплексного числа Complex: : Complex(const Complex& c) : real(c. real), imaginary(c. imaginary) {}
Копирующий конструктор Теперь при создании комплексных чисел происходит их инициализация: Complex x 1; // начальное значение - ноль Complex x 2(3); // мнимая часть по умолчанию равна 0 // создается действительное число 3 Complex x 3(0, 1); // мнимая единица Complex y(x 3); // мнимая единица
Копирующий конструктор Теперь при создании комплексных чисел происходит их инициализация: Complex x 1; // начальное значение - ноль Complex x 2(3); // мнимая часть по умолчанию равна 0 // создается действительное число 3 Complex x 3(0, 1); // мнимая единица Complex y(x 3); // мнимая единица
Деструкторы
Деструкторы Аналогично тому, что при создании объекта выполняется конструктор, при уничтожении объекта выполняется специальный метод класса, называемый деструктором. Обычно деструктор освобождает ресурсы, использованные данным объектом. У класса может быть только один деструктор. Имя деструктора – это имя класса, перед которым добавлен знак “тильда” - ~.
Деструкторы Для объектов класса String деструктор должен освободить память, используемую для хранения строки: class String { ~ String(); }; String: : ~String() { if(str) delete str; }
Деструкторы Если деструктор в определении класса не объявлен, то при уничтожении объекта никаких действий не производится. Деструктор всегда вызывается перед тем, как освобождается память, выделенная при объект. Если объект типа String был создан с помощью операции new, то при вызове delete str; выполняется деструктор ~String(), а затем освобождается память, занимаемая этим объектом.
Деструкторы Если деструктор в определении класса не объявлен, то при уничтожении объекта никаких действий не производится. Деструктор всегда вызывается перед тем, как освобождается память, выделенная при объект. Если объект типа String был создан с помощью операции new, то при вызове delete str; выполняется деструктор ~String(), а затем освобождается память, занимаемая этим объектом.
Деструкторы Предположим, в некоторой функции объявлена автоматическая переменная типа String: int funct(void) { String str; ………. . return 0; } При выходе из функции funct по оператору return переменная str будет уничтожена: выполнится деструктор и затем освободится память, занимаемая этой переменной.
Деструкторы Предположим, в некоторой функции объявлена автоматическая переменная типа String: int funct(void) { String str; ………. . return 0; } При выходе из функции funct по оператору return переменная str будет уничтожена: выполнится деструктор и затем освободится память, занимаемая этой переменной.
Операции new и delete
Операции new и delete Выделение памяти под объекты некоторого класса производится либо при создании переменных типа этого класса, либо с помощью операции new. В стандартной библиотеке языка С++ определена операция new. Эта операция не выделяет память, а лишь создает объект на заранее выделенном участке памяти. Формат операции: new (адрес) имя_класса (аргументы_конструктора)
Операции new и delete Перед именем класса в круглых скобках указывается адрес, по которому должен располагаться создаваемый объект. Фактически такая операция new не выделяет память, а лишь создает объект по указанному адресу. Соответственно, можно не выполнять операцию delete для этого объекта, а лишь вызвать его деструктор перед тем, как поместить новый объект на то же место памяти.
Операции new и delete Любой класс может использовать два вида операций new и delete – глобальную и определенную для класса. Если класс и ни один из его базовых классов, как прямых, так и косвенных, не определяет операцию new, то используется глобальная операция new. Глобальная операция new всегда используется для выделения памяти под встроенные типы и под массивы. Если класс определит операцию new, то для всех экземпляров этого класса и любых классов, производных от него, глобальная операция будет переопределена, и будет использоваться new данного класса.
prog_lk12_new.pptx