4 (972472)
Текст из файла
Семинар 4. Дружественные функции.Динамическое распределение памяти.Перегрузкаоператоров.1. Дружественные функцииДружественные функции обладает такими же правами доступа к данным класса, как и егометоды (функции-элементы класса).Пример. Функция сравнения длин двух векторов.const Vector & comp2(const Vector & v1, const Vector & v2){if (v1.x*v1.x + v1.y*v1.y > v2.x*v2.x + v2.y*v2.y ) return v1;else return v2;}void main(){Vector v1(1.,1.);Vector v2(2.,2.);Vector v3 = comp2(v1,v2);v3.show();}Функция comp2 не принадлежит классу (нет модификатора Vector::) и, соответственно,не является методом этого класса. Может ли она, тем не менее, иметь доступ к даннымкласса? – Может, если ее объявить в этом классе дружественной:class Vector{…public:friend const Vector & comp2(const Vector &, const Vector &);…}(Упражнение С4_1)2.
Перегрузка операторовДля сложения двух векторов можно написать метод sum, например:Vector Vector::sum(const Vector v) const {return Vector(x + v.x, y + v.y);}и сложение будет выглядеть так:Vector v3 = v1.sum(v2);Между тем, сложить две целочисленные переменные a и b или переменные любогодругого базового типа можно довольно просто, используя оператор + :c = a + b;Нельзя ли этот оператор приспособить (перегрузить) для сложения новых типов данных,которыми являются классы? – Можно. В Си++ можно перегрузить почти все стандартныеоператоры (кроме ., .*, ::, ?:, sizeof). Для этого нужно написать специальную(операторную) функцию, которая будет вызвана при перегрузке соответствующегооператора.Для примера перегрузим оператор + так, чтобы при его использовании с двумя векторамирезультатом был третий вектор, равный сумме двух исходных.
Операторная функцияимеет вид: operatorоп (список_аргументов). Добавляем в класс прототип этой функции:class Vector{public:Vector operator+(const Vector) const;…}и пишем саму функцию:Vector Vector::operator+(const Vector v) const{return Vector(x + v.x, y + v.y);}Фактически, мы просто заменили имя функции-элемента sum на operator+. По-прежнему,использование модификаторов const гарантирует неизменность исходных векторов.Теперь складывать вектора можно следующим образом:Vector v1(5.,1.);Vector v2(2.,2.);Vector v3 = v1+v2;(*)Примечание.
Порядок следования операндов, в общем случае, важен для операторнойфункции. К первому операнду (v1) операторная функция-элемент имеет неявный доступ,как к объекту, для которого она вызвана, и обращение к данным происходит напрямую.Ко второму операнду (v2) доступ организован явным образом, как к формальномупараметру функции. Это хорошо видно, если выражение (*) записать в эквивалентномфункциональном виде:Vector v3 = v1.operator+(v2);3. Дружественная операторная функцияОператор может быть перегружен не только с помощью операторной функции-элемента,но и с помощью дружественной операторной функции. В последнем случае, перегрузкатого же оператора + для двух векторов может быть выполнена так:Vector operator+(const Vector v1, const Vector v2){return Vector(v1.x + v2.x, v1.y + v2.y);}при одновременном объявлении в классе Vector этой функции дружественной:friend Vector operator+(const Vector, const Vector);Примечание.
Следующие операторы могут быть перегружены только с помощьюфункции-элемента: =, (), [],->.(Упражнения С4_2, С4_3, С4_4)24. Перегрузка оператора вывода в поток <<Для удобства вывода значений координат вектора желательно иметь возможность делатьэто стандартным образом с помощью оператора <<.У оператора вывода в поток << два операнда. Первый (cout) - это объект класса ostream,второй операнд будет объектом класса Vector. Операторная функция может иметь вид:void operator<<(ostream & os, const Vector & v){os << "x = " << v.x << ", y = " << v.y ;}В классе Vector её следует объявить дружественной.
Однако такая перегрузка не позволитиспользовать выражения с цепочкой операторов вывода << вида:cout << v1 << endl;Чтобы реализовать такую возможность нужно, чтобы перегруженный оператор <<возвращал объект класса ostream. Улучшенный вариант операторной функции имеет вид:ostream & operator<<(ostream & os, const Vector & v){os << "x = " << v.x << ", y = " << v.y ;return os;}(Упражнение С4_5)5. Приведение к типу классаБазовые типы могут быть преобразованы друг в друга явным образом, например:int i = 10;double d = (double) i; или double d = double (i);или неявным:double d = i;Аналогичное приведение типа для класса осуществляется путем вызова конструктора содним параметром.
Неявное приведение типа в выраженииVector v = 10;(**)эквивалентно выражению (явное преобразование)Vector v = Vector(10);Примечание. Неявное преобразование (**) можно запретить, если при объявлениипрототипа конструктора с одним параметром указать модификатор explicit.6. Функции преобразованияОбратное преобразование из типа класса осуществляется с помощью специальной формыоператорной функции - функции преобразования:operator имя_типа();Функция преобразования должна быть методом класса без аргументов и не иметь явноговозвращаемого типа, но, тем не менее, возвращать указанный (имя_типа) тип.
Например,функция преобразования в тип double должна иметь следующий прототип:operator double();37. Конструктор копирования и оператор присваиванияVector v1;//вызывается конструктор по умолчанию для создания объекта v1.Vector v2 = v1; //для создания объекта v2 вызывается конструктор копирования.v2 = v1;// оператор присваивания (вызывается операторная функция присваивания).По умолчанию, как конструктор копирования, так и оператор присваивания осуществляютпоэлементное копирование всех элементов данных одного объекта в другой. Это работаетдаже, если элемент данных является массивом, структурой или объектом другого класса.Например, если в класс Vector добавить элемент данных в виде массива:class Vector{private:double x;double y;char c[100];…}то данные объекта v2 из приведенного выше примера будут иметь такие же значения, каку объекта v1, включая одинаковые значения всех элементов массива c[20].Каждый класс снабжается конструктором копирования по умолчанию.
Его имя (как и имялюбого другого конструктора) совпадает с именем класса, при этом он имеет одинпараметр – константную ссылку на объект того же класса. Например, для класса Vectorконструктор копирования имеет следующий прототип:Vector(const Vector &);8. Динамическое распределение памяти в классеПредположим мы хотим добавить в класс Vector элемент данных для хранения именивектора. В принципе, для этого подошло бы поле char c[100]. Однако хранить в каждомновом объекте массив из 100 элементов довольно расточительно, если в большинствеслучаев длина имени гораздо меньше. Возможно, лучшим решением будет динамическоевыделение необходимой памяти при создании объекта.
При этом в классе объявляетсяуказатель:class Vector{private:double x;double y;char *c;…}Непосредственно память удобно выделить объекту с помощью оператора new[] в моментего создания, т.е. при вызове конструктора. Например, класс Vector можно снабдитьконструктором с тремя параметрами, последним из которых будет указатель на строку сименем вектора:Vector::Vector(double xx, double yy, const char *str){x = xx;y = yy;int len = strlen (str)+1;cp = new char[len];strncpy(cp, str, len);}4Для освобождения выделенной оператором new [] объекту дополнительной памятиследует использовать деструктор:Vector::~Vector(){delete [] cp;}Проблемы при таком подходе могут возникнуть с конструктором копирования иоператором присваивания по умолчанию. При их использовании будет скопированозначение указателя cp, но не сама дополнительная память.
Это приведет к тому, что дваобъекта будут иметь два разных (у каждого свой) указателя, которые указывают на одну итуже область памяти. Это является источником потенциальных ошибок. Например, приудалении одного объекта, указатель в другом объекте будет указывать на уженесуществующую область памяти. Чтобы этого избежать, при копировании (иприсваивании) нужно копировать не указатели, а сами данные (эта процедура называется«глубоким копированием»), выделяя новую область памяти для их хранения.Для этого нужно перегрузить конструктор копирования класса:Vector::Vector(const Vector & v2){x = v2.x;y = v2.y;int len = strlen (v2.cp)+1;cp = new char[len];strncpy(cp, v2.cp, len);}В нем обычные элементы данных по-прежнему просто копируются, а для динамическихданных вначале выделяется новое место, куда и происходит затем копирование.По этой же причине следует переопределить и оператор присваивания, которыйдолжен вначале удалить выделенную ранее область памяти, затем выделить новуюобласть необходимого размера и скопировать туда значения динамических элементовданных:Vector::Vector & operator=(const Vector & v){if (this == &v) return *this;delete [] cp;x = v.x;y = v.y;int len = strlen(v.cp)+1;cp = new char[len];strncpy(cp, v.cp, len);return *this;}Условный оператор здесь использован для обработки случая, когда присваиваниепроисходит самому себе: v = v.Такой подход предполагает, что при создании объекта в каждом конструкторе выделяетсядинамическая память.
Характеристики
Тип файла PDF
PDF-формат наиболее широко используется для просмотра любого типа файлов на любом устройстве. В него можно сохранить документ, таблицы, презентацию, текст, чертежи, вычисления, графики и всё остальное, что можно показать на экране любого устройства. Именно его лучше всего использовать для печати.
Например, если Вам нужно распечатать чертёж из автокада, Вы сохраните чертёж на флешку, но будет ли автокад в пункте печати? А если будет, то нужная версия с нужными библиотеками? Именно для этого и нужен формат PDF - в нём точно будет показано верно вне зависимости от того, в какой программе создали PDF-файл и есть ли нужная программа для его просмотра.