Главная » Просмотр файлов » лекции (1998) (Буров)

лекции (1998) (Буров) (1161123), страница 14

Файл №1161123 лекции (1998) (Буров) (лекции (1998) (Буров)) 14 страницалекции (1998) (Буров) (1161123) страница 142019-09-19СтудИзба
Просмтор этого файла доступен только зарегистрированным пользователям. Но у нас супер быстрая регистрация: достаточно только электронной почты!

Текст из файла (страница 14)

Stack *pS;

pS = new Stack(20); // Здесь выполняется конструктор

Теперь понятно, почему имя конструктора совпадает с именем объекта, – потому что описание объекта выглядит как вызов конструктора и здесь же он и неявно выполняется. Конструктор можно вызывать и явным образом, как функцию, хотя это редко имеет смысл. Пример явного вызова конструктора:

Stack f() {

return Stack(5);

};

Есть еще одна тонкость. Представим себе некий класс, в котором класс Stack является частью.

сlass X{

Stack S;

};

Пусть объект класса X находится в блоке. При входе в блок инициализируются все конструкторы квазистатических объектов этого блока. Возникает вопрос, в какой момент будет вызван конструктор S? Очевидно, в тот момент, когда выполняется конструктор класса X. Как вызывать этот конструктор? Системная часть любого конструктора, в частности, вызывает конструкторы подобъектов этого класса. В данном случае конструктор класса S будет вызван автоматически (нам не нужно писать инициализацию). Системная часть состоит не только из инициализации подклассов, в случае, когда есть наследование, вызываются конструкторы базовых классов.

Сейчас мы говорили о семантике, которая присуща всем видам конструкторов, не сосредотачиваясь на ее различии для разных типов конструкторов. Чем же отличаются типы конструкторов?

Конструктор умолчания.

Конструктор класса Stack можно легко превратить в конструктор умолчания, задав значение по умолчанию:

struct Stack {

char* body;

int top;

int size;

Stack(int sz=10);

};

Все правила указания параметров в конструкторах и классификации конструкторов в зависимости от вида параметров, относятся и к случаю, когда есть параметр по умолчанию. Т.е. если есть конструктор с одним параметром, и у этого параметра есть значение по умолчанию, то одновременно получаются сразу два конструктора – конструктор по умолчанию и конструктор преобразования. В данном случае, становится понятно, что надо вызывать, когда мы пишем просто new Stack() – вызовется new Stack(10). Аналогично, при описании Stack x вызовется конструктор с параметром 10. Инициализацию параметров по умолчанию следует писать в прототипе функции, а не при описании ее реализации. Правила задания параметров по умолчанию относятся не только к конструкторам, но и к обычным функциям.

Зачем нужны конструкторы по умолчанию? Вернемся к примеру класса стек, который является подклассом класса X. С каким значением будет вызван конструктор S в конструкторе X? Откуда компилятор узнает, с каким параметром инициализировать стек. Тут-то и приходит на помощь конструктор умолчания. Т.е. если компилятор не знает, с каким значением нужно вызывать конструктор S, то он вызывает конструктор по умолчанию.

В некоторых случаях наличие конструктора по умолчанию провоцирует ошибки, например, десяти элементов стека может не хватить. Когда нас заставляют писать какое-то значение, мы, по крайней мере, задумываемся о должном размере стека. Для стека не имеет смысл писать конструктор умолчания, кроме специфических задач. Но как быть, если у подкласса нет конструктора умолчания? Что будет подставлять компилятор в качестве значения параметра? В случае отсутствия конструктора по умолчанию компилятор выдаст ошибку. Очевидно, должен быть способ указания параметра в данном случае.

сlass X{

int i;

float f;

Stack S;

};

Параметр подкласса S указывается в конструкторе класса Х (пусть это будет конструктор умолчания), например при описании тела конструктора, следующим образом: X::X():i(0),f(0.0),S(16){…};. В данном случае мы еще заодно можем инициализировать переменные i и f. Однако будьте осторожны с такой инициализацией, особенно если таким образом нужно проинициализировать несколько конструкторов, потому что априори неизвестно, в каком порядке эти конструкторы вызовет компилятор. Ни в коем случае не пишите конструкторы, которые зависят от глобальных переменных, и в этом смысле, могут зависеть от порядка своего выполнения. Конструктор должен быть вещью в себе, т.е. либо он должен быть конструктором умолчания, либо получать какие-то параметры.

Конструктор копирования.

Конструкторы копирования нужны, прежде всего, в том случае, когда объект создается на основе объекта этого же класса, например, при копировании стека:

Stack S(256);

Stack S1=S;

Это типичный случай вызова конструктора копирования. Аналогично (с точки зрения синтаксиса) можно инициализировать, например, целую переменную int i=j. Однако инициализация стека семантически отличается от инициализации целой переменной. При отсутствии концепции конструктора копирования, семантика структуры данных достаточно не однозначна. Обычное присваивание означает побитовое копирование структуры данных, и два стека будут ссылаться на одно и тоже тело. В этом случае, любая операция Push или Pop нарушает целостность стека. Еще хуже, когда один стек уничтожится, и возникнет один из самых омерзительных случаев – висячая ссылка.

При работе со сложными структурами данных, возникает проблема копирования. Что означает копирование двух объектов сложной структуры? Тут различают два вида копирования: поверхностное (побитовое) и глубокое. Для стека поверхностное копирование не годится, это приводит к труднообнаружимым ошибкам. Для стека требуется глубокое копирование, когда создается отдельный экземпляр тела стека, но содержащий те же значения, что и в исходном стеке. Динамические структуры данных должны копироваться только глубоким видом копирования.

Языки программирования Модула-2 и Оберон никак не решают эту проблему, оставляя ее программисту. Как же дело обстоит в С++. Понятно, что компилятор сам не может решить, к какому виду копирования прибегнуть. С++ следует идеологии языка Си, который понравился программистам за то, что он их нигде не обманывает (компилятор нигде ничего неявно не вставляет). С++ тоже почти никогда не обманывает. Когда программист видит, что структуре данных необходимо глубокое копировании, то он сам обязан написать конструктор копирования. Например, у конструктора копирования стека будет следующее тело:

Stack (Stack &S) {

body = new char[size=S.size];

top = S.top;

memcpy(body, S.body, top*sizeof(char));

};

Конструктор копирования вызывается всегда при такой инициализации: Stack S1=S; (Stack S1(S) – то же самое). Но этот конструктор нужен не только здесь. Вспомним специфику передачи параметров по значению. Пусть есть такая функция:

void f(Stack X) {…};

Когда происходит передача параметров по значению, то в системном стеке заводится место под переменную типа Stack и создается локальный экземпляр передаваемого параметра. Объект любого класса инициализируется только с помощью конструктора, и в данном случае компилятором неявно вызовется конструктор копирования. Т.е. в данном случае, в теле функции как бы (т.е. это эквивалентно) заводится локальная переменная типа Stack, которая инициализируется стеком, переданным по ссылке.

Теперь понятно, что мы никогда не можем написать конструктор копирования вида X(Stack X), потому что в этом случае этот конструктор будет вызывать сам себя бесконечно. Такие объявления запрещены. Именно по этому параметрами конструктора копирования могут быть только ссылки на класс.

Будьте осторожны с передачей параметра по значению. Например, в MFC есть класс CString, работа с которым ведется исключительно в динамической памяти, поэтому при копировании, конкатенации строк никогда не может быть переполнения памяти. Но если вы все время пишите функции вида void S(CString c), то все строки, которые вы передаете, передаются по значению, т.е. всякий раз вызывается конструктор копирования, использующий глубокое копирование с помощью менеджера памяти. Т.е. возникают накладные расходы. Здесь мощность языка С++ может обернуться против программиста. В таких случаях часто имеет смысл передавать параметры по ссылке, чтобы избежать глубокого копирования.

Теперь мы понимаем, что у конструктора копирования, как и у конструктора умолчания, есть своя семантика. В чем еще отличие этих конструкторов? В языке С++ каждый класс имеет хотя бы один конструктор. Откуда они берутся? Мы писали такую структуру:

struct Complex {

double Re, Im;

};

Где здесь конструктор? В случае, если в классе отсутствуют какие-либо конструкторы, то по умолчанию генерируется конструктор умолчания. В данном случае, он ничего не делает, потому что у этого класса нет никаких подобъектов, а также отсутствует наследование (но накладных расходов не будет, потому что компилятор достаточно умен, чтобы это оптимизировать). Конструктор умолчания не везде нужен. Например он не нужен для стека, потому что нам необходимо, чтобы компилятор выдал ошибку, если мы не проинициализируем стек явно. Поэтому, конструктор умолчания генерируется только тогда, когда в классе нет никаких других конструкторов.

В том случае, если конструктор копирования не описан явно, то он генерируется неявно. Этот конструктор выполняет побитовое копирование. Если бы при описании стека сразу задавалась бы его длина (char body[50]), то мы могли бы обойтись только конструктором копирования, сгенерированным компилятором.

Конструктор преобразования.

Почему этот тип конструкторов выделен в отдельный класс, а не отнесен к прочим конструкторам? У конструктора преобразования есть особая семантика, которая тоже иногда настигает программиста и бьет по голове. Дело в том, что в языке С++ есть неявные преобразования, которые были оставлены для совместимости с языком Си. Неявные преобразования, это когда компилятор, вместо присваивания x=y (T1 x; T2 y;) вставляет код x=T1(y) (в Си преобразование типа выглядит иначе: x=(T1)y). В языке Си всегда были следующие неявные преобразования:

char => short => int => float => double

T* => void*

Т.к. более сложных типов в Си не было, то все было нормально. Одной из идей введения классов, было то, чтоб можно было расширять возможности языка без явных добавлений новых возможностей в базис. Например, программу на языке Fortran трудно переписать на язык Си, потому что в Си нет типа Complex. Для работы с комплексными числами, приходится писать специальные функции, и в результате выражения сильно усложняются:

A = B*C + D*(0,1); // Выражение на языке Fortran

A = Plus( Mult(B,C) , Mult(D,Im1) ); // То же самое выражение на языке Си

Разумеется, любой физик откажется писать такие выражения на Си, когда есть более удобный язык Fortran. В С++ концепция класса позволяет смоделировать комплексный тип (и не только комплексный), причем на С++ можно написать выражение, почти эквивалентное соответствующему выражению в языке Fortran. Для этого используется понятие класса, понятие функций-членов, и понятие перекрытия операций. Функции члены можно тоже перекрывать, и мы уже с этим сталкивались – это наличие нескольких конструкторов с одним и тем же именем, но с разным набором параметров. Страуструп разрешил перекрытие любых операций, за исключением трех: "?:" (условная операция), "." (операция точка), ".*" (операция взятия указателя элемента структуры). Остальные знаки операций перекрывать можно (в т.ч. операции умножения, сложения, вычитания, и даже скобки). Для структуры Complex перекрытие оператора сложения можно сделать следующим образом:

Complex operator + (Complex C1&, Complex C2&) { return Complex(C1.Re+C2.Re, C1.Im+C2.Im); };

Такие же перекрытия надо написать для других операций. Возникает проблема: если переменная С в выражении будет вещественной, то как поступить в этом случае? Можно написать, конечно, конструктор преобразования Complex(double,double), но тогда его придется вписывать явно в выражение. Можно для каждого из базисных типов данных написать свой оператор "+" и все прочие операторы, но это приводит к значительному "раздуванию" библиотек.

Лекция 12

Обсудим подробнее конструкторы преобразования. Почему они появились? Изначальной идеей Страуструпа было создать классы так, чтобы с их помощью можно было определять любые типы с произвольной семантикой. При этом новые типы ничем не отличались от базисных с точки зрения эксплуатации. Хорошим примером в данном случае будет тип комплексных чисел

A=B*C+D*(0,1)

Это выражение на языке Fortran, в нем подразумевается, что все переменные имеют тип комплексного числа. Написать подобное выражение на языке, не обладающем гибкостью C++, вообще говоря, сложно. С другой стороны можно добавлять типы данных в базис и интегрировать их с языком, но это не самое лучшее решение.

С этой точки зрения Страуструп обеспечил следующие средства развития. Во-первых, сам механизм классов, во-вторых, перекрытие операций. То есть можно перекрывать произвольные функции и знаки операций, причем, функции операции могут быть, как функциями членами, так и глобальными.

Мы писали уже перекрытие оператора «+» для комплексных чисел, он у нас выглядел, как функция-член. Это не совсем хорошо, но об этом мы еще поговорим.

С точки зрения языка Fortran, такое решение выглядит далеко не самым лучшим, так как в этом случае, если A,B,C,D - комплексные

Complex A,B,C,D;

(для определения константы следует воспользоваться конструктором)

Выражение на C++ примет вид:

A=B*C+D*Complex(0,1);

Характеристики

Тип файла
Документ
Размер
1,12 Mb
Тип материала
Высшее учебное заведение

Список файлов лекций

Свежие статьи
Популярно сейчас
Зачем заказывать выполнение своего задания, если оно уже было выполнено много много раз? Его можно просто купить или даже скачать бесплатно на СтудИзбе. Найдите нужный учебный материал у нас!
Ответы на популярные вопросы
Да! Наши авторы собирают и выкладывают те работы, которые сдаются в Вашем учебном заведении ежегодно и уже проверены преподавателями.
Да! У нас любой человек может выложить любую учебную работу и зарабатывать на её продажах! Но каждый учебный материал публикуется только после тщательной проверки администрацией.
Вернём деньги! А если быть более точными, то автору даётся немного времени на исправление, а если не исправит или выйдет время, то вернём деньги в полном объёме!
Да! На равне с готовыми студенческими работами у нас продаются услуги. Цены на услуги видны сразу, то есть Вам нужно только указать параметры и сразу можно оплачивать.
Отзывы студентов
Ставлю 10/10
Все нравится, очень удобный сайт, помогает в учебе. Кроме этого, можно заработать самому, выставляя готовые учебные материалы на продажу здесь. Рейтинги и отзывы на преподавателей очень помогают сориентироваться в начале нового семестра. Спасибо за такую функцию. Ставлю максимальную оценку.
Лучшая платформа для успешной сдачи сессии
Познакомился со СтудИзбой благодаря своему другу, очень нравится интерфейс, количество доступных файлов, цена, в общем, все прекрасно. Даже сам продаю какие-то свои работы.
Студизба ван лав ❤
Очень офигенный сайт для студентов. Много полезных учебных материалов. Пользуюсь студизбой с октября 2021 года. Серьёзных нареканий нет. Хотелось бы, что бы ввели подписочную модель и сделали материалы дешевле 300 рублей в рамках подписки бесплатными.
Отличный сайт
Лично меня всё устраивает - и покупка, и продажа; и цены, и возможность предпросмотра куска файла, и обилие бесплатных файлов (в подборках по авторам, читай, ВУЗам и факультетам). Есть определённые баги, но всё решаемо, да и администраторы реагируют в течение суток.
Маленький отзыв о большом помощнике!
Студизба спасает в те моменты, когда сроки горят, а работ накопилось достаточно. Довольно удобный сайт с простой навигацией и огромным количеством материалов.
Студ. Изба как крупнейший сборник работ для студентов
Тут дофига бывает всего полезного. Печально, что бывают предметы по которым даже одного бесплатного решения нет, но это скорее вопрос к студентам. В остальном всё здорово.
Спасательный островок
Если уже не успеваешь разобраться или застрял на каком-то задание поможет тебе быстро и недорого решить твою проблему.
Всё и так отлично
Всё очень удобно. Особенно круто, что есть система бонусов и можно выводить остатки денег. Очень много качественных бесплатных файлов.
Отзыв о системе "Студизба"
Отличная платформа для распространения работ, востребованных студентами. Хорошо налаженная и качественная работа сайта, огромная база заданий и аудитория.
Отличный помощник
Отличный сайт с кучей полезных файлов, позволяющий найти много методичек / учебников / отзывов о вузах и преподователях.
Отлично помогает студентам в любой момент для решения трудных и незамедлительных задач
Хотелось бы больше конкретной информации о преподавателях. А так в принципе хороший сайт, всегда им пользуюсь и ни разу не было желания прекратить. Хороший сайт для помощи студентам, удобный и приятный интерфейс. Из недостатков можно выделить только отсутствия небольшого количества файлов.
Спасибо за шикарный сайт
Великолепный сайт на котором студент за не большие деньги может найти помощь с дз, проектами курсовыми, лабораторными, а также узнать отзывы на преподавателей и бесплатно скачать пособия.
Популярные преподаватели
Добавляйте материалы
и зарабатывайте!
Продажи идут автоматически
6353
Авторов
на СтудИзбе
311
Средний доход
с одного платного файла
Обучение Подробнее